Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions containers/api-proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ COPY server.js logging.js metrics.js rate-limiter.js \
ai-credits-pricing.js models-dev-catalog.js models.dev.catalog.json \
oidc-refresh-utils.js body-transform.js body-utils.js rate-limit.js websocket-proxy.js \
deprecated-header-tracker.js billing-headers.js upstream-response.js \
upstream-log.js upstream-retry.js upstream-token.js \
anthropic-cache.js otel.js otel-exporters.js otel-serialization.js \
token-budget-log.js blocked-request-diagnostics.js \
provider-env-constants.js provider-names.js ./
Expand Down
89 changes: 89 additions & 0 deletions containers/api-proxy/upstream-log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';

const { COPILOT_PLACEHOLDER_TOKEN } = require('./providers/copilot-byok');
const { stripBearerPrefix } = require('./providers/copilot-auth');

function buildCopilotAuthErrorMessage(statusCode, env = process.env) {
const baseMessage = `Upstream returned ${statusCode}`;
const byokBaseUrl = (env.COPILOT_PROVIDER_BASE_URL || '').trim();
const byokKey = stripBearerPrefix(env.COPILOT_PROVIDER_API_KEY);
const hasByokBaseUrl = Boolean(byokBaseUrl);

if (hasByokBaseUrl && byokKey === COPILOT_PLACEHOLDER_TOKEN) {
return `${baseMessage} — COPILOT_PROVIDER_API_KEY is the AWF placeholder sentinel. ` +
'This indicates an internal credential-isolation misconfiguration (real BYOK key not forwarded to api-proxy).';
}

if (hasByokBaseUrl && !byokKey) {
return `${baseMessage} — BYOK provider request to COPILOT_PROVIDER_BASE_URL failed because COPILOT_PROVIDER_API_KEY is not set.`;
}

if (hasByokBaseUrl) {
return `${baseMessage} — BYOK provider request to COPILOT_PROVIDER_BASE_URL failed. ` +
'Verify COPILOT_PROVIDER_BASE_URL and COPILOT_PROVIDER_API_KEY.';
}

return `${baseMessage} — check that the API key is valid and correctly formatted`;
}

function createLogRequestCompletion({ metrics, logRequest, sanitizeForLog, applyMaxRunsInvocation }) {
return function logRequestCompletion(statusCode, responseBytes, initiatorSent, billingInfo, {
startTime, provider, req, requestBytes, targetHost, requestId,
}) {
const duration = Date.now() - startTime;
const sc = metrics.statusClass(statusCode);
metrics.gaugeDec('active_requests', { provider });
metrics.increment('requests_total', { provider, method: req.method, status_class: sc });
metrics.increment('response_bytes_total', { provider }, responseBytes);
metrics.observe('request_duration_ms', duration, { provider });
if (statusCode >= 200 && statusCode < 300) {
applyMaxRunsInvocation();
}
const logFields = {
request_id: requestId, provider, method: req.method,
path: sanitizeForLog(req.url), status: statusCode,
duration_ms: duration, request_bytes: requestBytes,
response_bytes: responseBytes, upstream_host: targetHost,
};
if (initiatorSent) logFields.x_initiator = initiatorSent;
if (billingInfo) logFields.billing = billingInfo;
logRequest('info', 'request_complete', logFields);
};
}

function createLogUpstreamAuthError({
logRequest,
sanitizeForLog,
applyPermissionDenied,
parseModelNotSupportedFromBody,
}) {
return function logUpstreamAuthError(statusCode, { requestId, provider, targetHost, req, responseBody }) {
const authErrorMessage = provider === 'copilot'
? buildCopilotAuthErrorMessage(statusCode)
: `Upstream returned ${statusCode} — check that the API key is valid and correctly formatted`;

if (statusCode === 401 || statusCode === 403) {
applyPermissionDenied();
logRequest('warn', 'upstream_auth_error', {
request_id: requestId, provider, status: statusCode,
upstream_host: targetHost, path: sanitizeForLog(req.url),
message: authErrorMessage,
});
} else if (statusCode === 400) {
// Suppress generic auth-error message when the 400 is a model-not-supported
// error — that case is handled by the model_unavailable diagnostic.
if (responseBody && parseModelNotSupportedFromBody(responseBody)) return;
logRequest('warn', 'upstream_auth_error', {
request_id: requestId, provider, status: statusCode,
upstream_host: targetHost, path: sanitizeForLog(req.url),
message: authErrorMessage,
});
}
};
}

module.exports = {
createLogRequestCompletion,
createLogUpstreamAuthError,
buildCopilotAuthErrorMessage,
};
62 changes: 62 additions & 0 deletions containers/api-proxy/upstream-log.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const {
createLogRequestCompletion,
createLogUpstreamAuthError,
} = require('./upstream-log');

describe('upstream-log', () => {
test('logRequestCompletion records metrics and invokes max-runs on success', () => {
const metrics = {
statusClass: jest.fn(() => '2xx'),
gaugeDec: jest.fn(),
increment: jest.fn(),
observe: jest.fn(),
};
const logRequest = jest.fn();
const applyMaxRunsInvocation = jest.fn();
const logRequestCompletion = createLogRequestCompletion({
metrics,
logRequest,
sanitizeForLog: (value) => value,
applyMaxRunsInvocation,
});

logRequestCompletion(200, 42, 'agent', { prompt_tokens: 10 }, {
startTime: Date.now() - 5,
provider: 'copilot',
req: { method: 'POST', url: '/v1/chat/completions' },
requestBytes: 12,
targetHost: 'api.githubcopilot.com',
requestId: 'req-1',
});

expect(metrics.gaugeDec).toHaveBeenCalledWith('active_requests', { provider: 'copilot' });
expect(applyMaxRunsInvocation).toHaveBeenCalledTimes(1);
expect(logRequest).toHaveBeenCalledWith('info', 'request_complete', expect.objectContaining({
request_id: 'req-1',
status: 200,
x_initiator: 'agent',
}));
});

test('logUpstreamAuthError suppresses 400 model-not-supported auth log noise', () => {
const logRequest = jest.fn();
const applyPermissionDenied = jest.fn();
const logUpstreamAuthError = createLogUpstreamAuthError({
logRequest,
sanitizeForLog: (value) => value,
applyPermissionDenied,
parseModelNotSupportedFromBody: () => true,
});

logUpstreamAuthError(400, {
requestId: 'req-1',
provider: 'copilot',
targetHost: 'api.githubcopilot.com',
req: { url: '/v1/chat/completions' },
responseBody: Buffer.from('The requested model is not supported'),
});

expect(logRequest).not.toHaveBeenCalled();
expect(applyPermissionDenied).not.toHaveBeenCalled();
});
});
Loading
Loading