|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +const { COPILOT_PLACEHOLDER_TOKEN } = require('./providers/copilot-byok'); |
| 4 | +const { stripBearerPrefix } = require('./providers/copilot-auth'); |
| 5 | + |
| 6 | +function buildCopilotAuthErrorMessage(statusCode, env = process.env) { |
| 7 | + const baseMessage = `Upstream returned ${statusCode}`; |
| 8 | + const byokBaseUrl = (env.COPILOT_PROVIDER_BASE_URL || '').trim(); |
| 9 | + const byokKey = stripBearerPrefix(env.COPILOT_PROVIDER_API_KEY); |
| 10 | + const hasByokBaseUrl = Boolean(byokBaseUrl); |
| 11 | + |
| 12 | + if (hasByokBaseUrl && byokKey === COPILOT_PLACEHOLDER_TOKEN) { |
| 13 | + return `${baseMessage} — COPILOT_PROVIDER_API_KEY is the AWF placeholder sentinel. ` + |
| 14 | + 'This indicates an internal credential-isolation misconfiguration (real BYOK key not forwarded to api-proxy).'; |
| 15 | + } |
| 16 | + |
| 17 | + if (hasByokBaseUrl && !byokKey) { |
| 18 | + return `${baseMessage} — BYOK provider request to COPILOT_PROVIDER_BASE_URL failed because COPILOT_PROVIDER_API_KEY is not set.`; |
| 19 | + } |
| 20 | + |
| 21 | + if (hasByokBaseUrl) { |
| 22 | + return `${baseMessage} — BYOK provider request to COPILOT_PROVIDER_BASE_URL failed. ` + |
| 23 | + 'Verify COPILOT_PROVIDER_BASE_URL and COPILOT_PROVIDER_API_KEY.'; |
| 24 | + } |
| 25 | + |
| 26 | + return `${baseMessage} — check that the API key is valid and correctly formatted`; |
| 27 | +} |
| 28 | + |
| 29 | +function createLogRequestCompletion({ metrics, logRequest, sanitizeForLog, applyMaxRunsInvocation }) { |
| 30 | + return function logRequestCompletion(statusCode, responseBytes, initiatorSent, billingInfo, { |
| 31 | + startTime, provider, req, requestBytes, targetHost, requestId, |
| 32 | + }) { |
| 33 | + const duration = Date.now() - startTime; |
| 34 | + const sc = metrics.statusClass(statusCode); |
| 35 | + metrics.gaugeDec('active_requests', { provider }); |
| 36 | + metrics.increment('requests_total', { provider, method: req.method, status_class: sc }); |
| 37 | + metrics.increment('response_bytes_total', { provider }, responseBytes); |
| 38 | + metrics.observe('request_duration_ms', duration, { provider }); |
| 39 | + if (statusCode >= 200 && statusCode < 300) { |
| 40 | + applyMaxRunsInvocation(); |
| 41 | + } |
| 42 | + const logFields = { |
| 43 | + request_id: requestId, provider, method: req.method, |
| 44 | + path: sanitizeForLog(req.url), status: statusCode, |
| 45 | + duration_ms: duration, request_bytes: requestBytes, |
| 46 | + response_bytes: responseBytes, upstream_host: targetHost, |
| 47 | + }; |
| 48 | + if (initiatorSent) logFields.x_initiator = initiatorSent; |
| 49 | + if (billingInfo) logFields.billing = billingInfo; |
| 50 | + logRequest('info', 'request_complete', logFields); |
| 51 | + }; |
| 52 | +} |
| 53 | + |
| 54 | +function createLogUpstreamAuthError({ |
| 55 | + logRequest, |
| 56 | + sanitizeForLog, |
| 57 | + applyPermissionDenied, |
| 58 | + parseModelNotSupportedFromBody, |
| 59 | +}) { |
| 60 | + return function logUpstreamAuthError(statusCode, { requestId, provider, targetHost, req, responseBody }) { |
| 61 | + const authErrorMessage = provider === 'copilot' |
| 62 | + ? buildCopilotAuthErrorMessage(statusCode) |
| 63 | + : `Upstream returned ${statusCode} — check that the API key is valid and correctly formatted`; |
| 64 | + |
| 65 | + if (statusCode === 401 || statusCode === 403) { |
| 66 | + applyPermissionDenied(); |
| 67 | + logRequest('warn', 'upstream_auth_error', { |
| 68 | + request_id: requestId, provider, status: statusCode, |
| 69 | + upstream_host: targetHost, path: sanitizeForLog(req.url), |
| 70 | + message: authErrorMessage, |
| 71 | + }); |
| 72 | + } else if (statusCode === 400) { |
| 73 | + // Suppress generic auth-error message when the 400 is a model-not-supported |
| 74 | + // error — that case is handled by the model_unavailable diagnostic. |
| 75 | + if (responseBody && parseModelNotSupportedFromBody(responseBody)) return; |
| 76 | + logRequest('warn', 'upstream_auth_error', { |
| 77 | + request_id: requestId, provider, status: statusCode, |
| 78 | + upstream_host: targetHost, path: sanitizeForLog(req.url), |
| 79 | + message: authErrorMessage, |
| 80 | + }); |
| 81 | + } |
| 82 | + }; |
| 83 | +} |
| 84 | + |
| 85 | +module.exports = { |
| 86 | + createLogRequestCompletion, |
| 87 | + createLogUpstreamAuthError, |
| 88 | + buildCopilotAuthErrorMessage, |
| 89 | +}; |
0 commit comments