@juspay/neurolink 9.41.0 → 9.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +7 -1
- package/dist/auth/anthropicOAuth.d.ts +18 -3
- package/dist/auth/anthropicOAuth.js +149 -4
- package/dist/auth/providers/firebase.js +5 -1
- package/dist/auth/providers/jwt.js +5 -1
- package/dist/auth/providers/workos.js +5 -1
- package/dist/auth/sessionManager.d.ts +1 -1
- package/dist/auth/sessionManager.js +58 -27
- package/dist/browser/neurolink.min.js +354 -334
- package/dist/cli/commands/mcp.d.ts +6 -0
- package/dist/cli/commands/mcp.js +188 -181
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +713 -431
- package/dist/cli/commands/task.js +3 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +38 -0
- package/dist/cli/parser.js +4 -3
- package/dist/client/aiSdkAdapter.js +3 -0
- package/dist/client/streamingClient.js +30 -10
- package/dist/core/baseProvider.d.ts +6 -1
- package/dist/core/baseProvider.js +208 -230
- package/dist/core/factory.d.ts +3 -0
- package/dist/core/factory.js +138 -188
- package/dist/core/modules/GenerationHandler.js +3 -2
- package/dist/core/redisConversationMemoryManager.js +7 -3
- package/dist/evaluation/BatchEvaluator.js +4 -1
- package/dist/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/evaluation/pipeline/evaluationPipeline.js +24 -9
- package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
- package/dist/evaluation/scorers/scorerRegistry.js +353 -282
- package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
- package/dist/lib/auth/anthropicOAuth.js +149 -4
- package/dist/lib/auth/providers/firebase.js +5 -1
- package/dist/lib/auth/providers/jwt.js +5 -1
- package/dist/lib/auth/providers/workos.js +5 -1
- package/dist/lib/auth/sessionManager.d.ts +1 -1
- package/dist/lib/auth/sessionManager.js +58 -27
- package/dist/lib/client/aiSdkAdapter.js +3 -0
- package/dist/lib/client/streamingClient.js +30 -10
- package/dist/lib/core/baseProvider.d.ts +6 -1
- package/dist/lib/core/baseProvider.js +208 -230
- package/dist/lib/core/factory.d.ts +3 -0
- package/dist/lib/core/factory.js +138 -188
- package/dist/lib/core/modules/GenerationHandler.js +3 -2
- package/dist/lib/core/redisConversationMemoryManager.js +7 -3
- package/dist/lib/evaluation/BatchEvaluator.js +4 -1
- package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/lib/evaluation/pipeline/evaluationPipeline.js +24 -9
- package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
- package/dist/lib/evaluation/scorers/scorerRegistry.js +353 -282
- package/dist/lib/mcp/toolRegistry.d.ts +2 -0
- package/dist/lib/mcp/toolRegistry.js +32 -31
- package/dist/lib/neurolink.d.ts +41 -2
- package/dist/lib/neurolink.js +1616 -1681
- package/dist/lib/observability/otelBridge.d.ts +2 -2
- package/dist/lib/observability/otelBridge.js +12 -3
- package/dist/lib/providers/amazonBedrock.js +2 -4
- package/dist/lib/providers/anthropic.d.ts +9 -5
- package/dist/lib/providers/anthropic.js +19 -14
- package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/lib/providers/anthropicBaseProvider.js +5 -4
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +5 -4
- package/dist/lib/providers/googleAiStudio.js +30 -6
- package/dist/lib/providers/googleVertex.d.ts +10 -0
- package/dist/lib/providers/googleVertex.js +437 -423
- package/dist/lib/providers/huggingFace.d.ts +3 -3
- package/dist/lib/providers/huggingFace.js +6 -8
- package/dist/lib/providers/litellm.d.ts +1 -0
- package/dist/lib/providers/litellm.js +76 -55
- package/dist/lib/providers/mistral.js +2 -1
- package/dist/lib/providers/ollama.js +93 -23
- package/dist/lib/providers/openAI.d.ts +2 -0
- package/dist/lib/providers/openAI.js +141 -141
- package/dist/lib/providers/openRouter.js +2 -1
- package/dist/lib/providers/openaiCompatible.d.ts +4 -4
- package/dist/lib/providers/openaiCompatible.js +4 -4
- package/dist/lib/proxy/claudeFormat.d.ts +3 -2
- package/dist/lib/proxy/claudeFormat.js +27 -14
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/lib/proxy/modelRouter.js +3 -0
- package/dist/lib/proxy/oauthFetch.d.ts +1 -1
- package/dist/lib/proxy/oauthFetch.js +289 -316
- package/dist/lib/proxy/proxyConfig.js +46 -24
- package/dist/lib/proxy/proxyEnv.d.ts +19 -0
- package/dist/lib/proxy/proxyEnv.js +73 -0
- package/dist/lib/proxy/proxyFetch.js +291 -217
- package/dist/lib/proxy/proxyTracer.d.ts +133 -0
- package/dist/lib/proxy/proxyTracer.js +645 -0
- package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/lib/proxy/rawStreamCapture.js +83 -0
- package/dist/lib/proxy/requestLogger.d.ts +32 -5
- package/dist/lib/proxy/requestLogger.js +503 -47
- package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
- package/dist/lib/proxy/sseInterceptor.js +427 -0
- package/dist/lib/proxy/usageStats.d.ts +4 -3
- package/dist/lib/proxy/usageStats.js +25 -12
- package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/lib/rag/chunking/markdownChunker.js +15 -6
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +17 -3
- package/dist/lib/server/routes/claudeProxyRoutes.js +3032 -1349
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/lib/services/server/ai/observability/instrumentation.js +337 -161
- package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
- package/dist/lib/tasks/backends/bullmqBackend.js +35 -22
- package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
- package/dist/lib/tasks/store/redisTaskStore.js +54 -39
- package/dist/lib/tasks/taskManager.d.ts +5 -0
- package/dist/lib/tasks/taskManager.js +158 -30
- package/dist/lib/telemetry/index.d.ts +2 -1
- package/dist/lib/telemetry/index.js +2 -1
- package/dist/lib/telemetry/telemetryService.d.ts +3 -0
- package/dist/lib/telemetry/telemetryService.js +69 -5
- package/dist/lib/types/cli.d.ts +10 -0
- package/dist/lib/types/proxyTypes.d.ts +160 -5
- package/dist/lib/types/streamTypes.d.ts +25 -3
- package/dist/lib/utils/messageBuilder.js +3 -2
- package/dist/lib/utils/providerHealth.d.ts +19 -0
- package/dist/lib/utils/providerHealth.js +279 -33
- package/dist/lib/utils/providerUtils.js +17 -22
- package/dist/lib/utils/toolChoice.d.ts +4 -0
- package/dist/lib/utils/toolChoice.js +7 -0
- package/dist/mcp/toolRegistry.d.ts +2 -0
- package/dist/mcp/toolRegistry.js +32 -31
- package/dist/neurolink.d.ts +41 -2
- package/dist/neurolink.js +1616 -1681
- package/dist/observability/otelBridge.d.ts +2 -2
- package/dist/observability/otelBridge.js +12 -3
- package/dist/providers/amazonBedrock.js +2 -4
- package/dist/providers/anthropic.d.ts +9 -5
- package/dist/providers/anthropic.js +19 -14
- package/dist/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/providers/anthropicBaseProvider.js +5 -4
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +5 -4
- package/dist/providers/googleAiStudio.js +30 -6
- package/dist/providers/googleVertex.d.ts +10 -0
- package/dist/providers/googleVertex.js +437 -423
- package/dist/providers/huggingFace.d.ts +3 -3
- package/dist/providers/huggingFace.js +6 -7
- package/dist/providers/litellm.d.ts +1 -0
- package/dist/providers/litellm.js +76 -55
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/ollama.js +93 -23
- package/dist/providers/openAI.d.ts +2 -0
- package/dist/providers/openAI.js +141 -141
- package/dist/providers/openRouter.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +4 -4
- package/dist/providers/openaiCompatible.js +4 -3
- package/dist/proxy/claudeFormat.d.ts +3 -2
- package/dist/proxy/claudeFormat.js +27 -14
- package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/proxy/modelRouter.js +3 -0
- package/dist/proxy/oauthFetch.d.ts +1 -1
- package/dist/proxy/oauthFetch.js +289 -316
- package/dist/proxy/proxyConfig.js +46 -24
- package/dist/proxy/proxyEnv.d.ts +19 -0
- package/dist/proxy/proxyEnv.js +72 -0
- package/dist/proxy/proxyFetch.js +291 -217
- package/dist/proxy/proxyTracer.d.ts +133 -0
- package/dist/proxy/proxyTracer.js +644 -0
- package/dist/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/proxy/rawStreamCapture.js +82 -0
- package/dist/proxy/requestLogger.d.ts +32 -5
- package/dist/proxy/requestLogger.js +503 -47
- package/dist/proxy/sseInterceptor.d.ts +97 -0
- package/dist/proxy/sseInterceptor.js +426 -0
- package/dist/proxy/usageStats.d.ts +4 -3
- package/dist/proxy/usageStats.js +25 -12
- package/dist/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/rag/chunking/markdownChunker.js +15 -6
- package/dist/server/routes/claudeProxyRoutes.d.ts +17 -3
- package/dist/server/routes/claudeProxyRoutes.js +3032 -1349
- package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/services/server/ai/observability/instrumentation.js +337 -161
- package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
- package/dist/tasks/backends/bullmqBackend.js +35 -22
- package/dist/tasks/store/redisTaskStore.d.ts +1 -0
- package/dist/tasks/store/redisTaskStore.js +54 -39
- package/dist/tasks/taskManager.d.ts +5 -0
- package/dist/tasks/taskManager.js +158 -30
- package/dist/telemetry/index.d.ts +2 -1
- package/dist/telemetry/index.js +2 -1
- package/dist/telemetry/telemetryService.d.ts +3 -0
- package/dist/telemetry/telemetryService.js +69 -5
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/proxyTypes.d.ts +160 -5
- package/dist/types/streamTypes.d.ts +25 -3
- package/dist/utils/messageBuilder.js +3 -2
- package/dist/utils/providerHealth.d.ts +19 -0
- package/dist/utils/providerHealth.js +279 -33
- package/dist/utils/providerUtils.js +18 -22
- package/dist/utils/toolChoice.d.ts +4 -0
- package/dist/utils/toolChoice.js +6 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
- package/docs/changelog.md +252 -0
- package/package.json +19 -2
- package/scripts/observability/check-proxy-telemetry.mjs +235 -0
- package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
- package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
- package/scripts/observability/manage-local-openobserve.sh +215 -0
- package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
- package/scripts/observability/proxy-observability.env.example +23 -0
package/dist/proxy/proxyFetch.js
CHANGED
|
@@ -4,9 +4,62 @@
|
|
|
4
4
|
* Lightweight implementation extracted from research of major proxy packages
|
|
5
5
|
*/
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
|
-
import { SpanStatusCode } from "@opentelemetry/api";
|
|
7
|
+
import { SpanStatusCode, propagation, context } from "@opentelemetry/api";
|
|
8
8
|
import { tracers } from "../telemetry/tracers.js";
|
|
9
9
|
import { shouldBypassProxy } from "./utils/noProxyUtils.js";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
async function getLangfuseContext() {
|
|
12
|
+
try {
|
|
13
|
+
// Dynamic import to avoid hard dependency — getLangfuseContext is only
|
|
14
|
+
// available when the observability module is loaded.
|
|
15
|
+
const mod = await import("../services/server/ai/observability/instrumentation.js");
|
|
16
|
+
return mod.getLangfuseContext?.();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Inject OTel trace context (traceparent/tracestate) and NeuroLink session context
|
|
24
|
+
* into outgoing request headers. This enables:
|
|
25
|
+
* - The NeuroLink proxy to link proxy spans as children of the calling SDK's trace
|
|
26
|
+
* - Conversation-level session/user attribution on proxy spans
|
|
27
|
+
*/
|
|
28
|
+
function mergeTraceHeaders(input, init) {
|
|
29
|
+
const existingHeaders = new Headers(input instanceof Request ? input.headers : undefined);
|
|
30
|
+
if (init?.headers) {
|
|
31
|
+
const initHeaders = new Headers(init.headers);
|
|
32
|
+
for (const [key, value] of initHeaders.entries()) {
|
|
33
|
+
existingHeaders.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return existingHeaders;
|
|
37
|
+
}
|
|
38
|
+
async function injectTraceContext(input, init) {
|
|
39
|
+
const carrier = {};
|
|
40
|
+
propagation.inject(context.active(), carrier);
|
|
41
|
+
// Also inject NeuroLink session context from Langfuse AsyncLocalStorage
|
|
42
|
+
const langfuseContext = await getLangfuseContext();
|
|
43
|
+
if (langfuseContext?.sessionId) {
|
|
44
|
+
carrier["x-neurolink-session-id"] = langfuseContext.sessionId;
|
|
45
|
+
}
|
|
46
|
+
if (langfuseContext?.userId) {
|
|
47
|
+
carrier["x-neurolink-user-id"] = langfuseContext.userId;
|
|
48
|
+
}
|
|
49
|
+
if (langfuseContext?.conversationId) {
|
|
50
|
+
carrier["x-neurolink-conversation-id"] = langfuseContext.conversationId;
|
|
51
|
+
}
|
|
52
|
+
if (Object.keys(carrier).length === 0) {
|
|
53
|
+
return init ?? {};
|
|
54
|
+
}
|
|
55
|
+
const existingHeaders = mergeTraceHeaders(input, init);
|
|
56
|
+
for (const [key, value] of Object.entries(carrier)) {
|
|
57
|
+
if (!existingHeaders.has(key)) {
|
|
58
|
+
existingHeaders.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { ...init, headers: existingHeaders };
|
|
62
|
+
}
|
|
10
63
|
const fetchTracer = tracers.http;
|
|
11
64
|
/**
|
|
12
65
|
* Extract hostname from a URL string for safe logging (no auth tokens or paths).
|
|
@@ -279,6 +332,234 @@ async function createProxyAgent(proxyUrl) {
|
|
|
279
332
|
throw new Error(`Unsupported proxy protocol: ${parsed.protocol}`);
|
|
280
333
|
}
|
|
281
334
|
}
|
|
335
|
+
function sanitizeProxyUrl(url) {
|
|
336
|
+
return maskProxyUrl(url) ?? "NOT_SET";
|
|
337
|
+
}
|
|
338
|
+
function getTargetUrl(input) {
|
|
339
|
+
return typeof input === "string"
|
|
340
|
+
? input
|
|
341
|
+
: input instanceof URL
|
|
342
|
+
? input.href
|
|
343
|
+
: input.url;
|
|
344
|
+
}
|
|
345
|
+
function createDirectFetchHandler() {
|
|
346
|
+
return async (input, init) => {
|
|
347
|
+
const enrichedInit = await injectTraceContext(input, init);
|
|
348
|
+
const reqId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
349
|
+
const startTs = Date.now();
|
|
350
|
+
const url = getTargetUrl(input);
|
|
351
|
+
if (logger.shouldLog("debug")) {
|
|
352
|
+
const { size: bodySize, type: bodyType } = parseBody(enrichedInit?.body);
|
|
353
|
+
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
354
|
+
requestId: reqId,
|
|
355
|
+
url,
|
|
356
|
+
method: enrichedInit?.method || "POST",
|
|
357
|
+
bodySize,
|
|
358
|
+
bodyType,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const response = await fetchWithRetry(input, enrichedInit);
|
|
363
|
+
if (logger.shouldLog("debug")) {
|
|
364
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
365
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
366
|
+
requestId: reqId,
|
|
367
|
+
url,
|
|
368
|
+
status: response.status,
|
|
369
|
+
statusText: response.statusText,
|
|
370
|
+
durationMs: Date.now() - startTs,
|
|
371
|
+
contentLength: responseSize,
|
|
372
|
+
hasContent: !!responseBody,
|
|
373
|
+
bodyType: responseType,
|
|
374
|
+
responseHeaders,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return response;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
381
|
+
requestId: reqId,
|
|
382
|
+
url,
|
|
383
|
+
error: error instanceof Error ? error.message : String(error),
|
|
384
|
+
durationMs: Date.now() - startTs,
|
|
385
|
+
});
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async function executeProxiedFetch(input, init, proxyEnv) {
|
|
391
|
+
const { httpsProxy, httpProxy, allProxy, socksProxy, noProxy } = proxyEnv;
|
|
392
|
+
init = await injectTraceContext(input, init);
|
|
393
|
+
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
394
|
+
const requestStartTime = Date.now();
|
|
395
|
+
const targetUrl = getTargetUrl(input);
|
|
396
|
+
if (logger.shouldLog("debug")) {
|
|
397
|
+
const { size: bodySize, type: bodyType } = parseBody(init?.body);
|
|
398
|
+
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
399
|
+
requestId,
|
|
400
|
+
url: targetUrl,
|
|
401
|
+
method: init?.method || "POST",
|
|
402
|
+
bodySize,
|
|
403
|
+
bodyType,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
logger.debug(`[Proxy Fetch] ENHANCED REQUEST START`, {
|
|
407
|
+
requestId,
|
|
408
|
+
targetUrl,
|
|
409
|
+
timestamp: new Date().toISOString(),
|
|
410
|
+
httpProxy: sanitizeProxyUrl(httpProxy),
|
|
411
|
+
httpsProxy: sanitizeProxyUrl(httpsProxy),
|
|
412
|
+
allProxy: sanitizeProxyUrl(allProxy),
|
|
413
|
+
socksProxy: sanitizeProxyUrl(socksProxy),
|
|
414
|
+
noProxy: noProxy || "NOT_SET",
|
|
415
|
+
initMethod: init?.method || "GET",
|
|
416
|
+
});
|
|
417
|
+
// Clone the request before any proxy attempt so that if the proxy path
|
|
418
|
+
// consumes the body stream and then fails, the fallback still has an intact
|
|
419
|
+
// body to send.
|
|
420
|
+
const requestClone = input instanceof Request ? input.clone() : null;
|
|
421
|
+
try {
|
|
422
|
+
const proxyUrl = selectProxyUrl(targetUrl);
|
|
423
|
+
if (proxyUrl) {
|
|
424
|
+
const url = new URL(targetUrl);
|
|
425
|
+
logger.debug(`[Proxy Fetch] 🔗 ENHANCED URL ANALYSIS`, {
|
|
426
|
+
requestId,
|
|
427
|
+
targetUrl,
|
|
428
|
+
urlHostname: url.hostname,
|
|
429
|
+
urlProtocol: url.protocol,
|
|
430
|
+
urlPort: url.port,
|
|
431
|
+
selectedProxyUrl: sanitizeProxyUrl(proxyUrl),
|
|
432
|
+
timestamp: new Date().toISOString(),
|
|
433
|
+
});
|
|
434
|
+
logger.debug(`[Proxy Fetch] 🎯 ENHANCED PROXY AGENT CREATION`, {
|
|
435
|
+
requestId,
|
|
436
|
+
proxyUrl: sanitizeProxyUrl(proxyUrl),
|
|
437
|
+
targetHostname: url.hostname,
|
|
438
|
+
targetProtocol: url.protocol,
|
|
439
|
+
aboutToCreateProxyAgent: true,
|
|
440
|
+
timestamp: new Date().toISOString(),
|
|
441
|
+
});
|
|
442
|
+
const globalWithCache = globalThis;
|
|
443
|
+
if (!globalWithCache.__NL_PROXY_AGENT_CACHE__) {
|
|
444
|
+
globalWithCache.__NL_PROXY_AGENT_CACHE__ = new Map();
|
|
445
|
+
}
|
|
446
|
+
const agentCache = globalWithCache.__NL_PROXY_AGENT_CACHE__;
|
|
447
|
+
const cacheKey = createHash("sha256")
|
|
448
|
+
.update(maskProxyUrl(proxyUrl) ?? proxyUrl)
|
|
449
|
+
.digest("hex");
|
|
450
|
+
const dispatcher = agentCache.get(cacheKey) || (await createProxyAgent(proxyUrl));
|
|
451
|
+
agentCache.set(cacheKey, dispatcher);
|
|
452
|
+
logger.debug(`[Proxy Fetch] ✅ ENHANCED PROXY AGENT CREATED`, {
|
|
453
|
+
requestId,
|
|
454
|
+
hasDispatcher: !!dispatcher,
|
|
455
|
+
dispatcherType: typeof dispatcher,
|
|
456
|
+
dispatcherConstructor: dispatcher?.constructor?.name || "unknown",
|
|
457
|
+
timestamp: new Date().toISOString(),
|
|
458
|
+
});
|
|
459
|
+
let fetchInput;
|
|
460
|
+
let fetchInit = { ...init };
|
|
461
|
+
if (input instanceof Request) {
|
|
462
|
+
fetchInput = input.url;
|
|
463
|
+
fetchInit = {
|
|
464
|
+
method: input.method,
|
|
465
|
+
headers: input.headers,
|
|
466
|
+
body: input.body,
|
|
467
|
+
...init,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
fetchInput = input;
|
|
472
|
+
}
|
|
473
|
+
const undici = await import("undici");
|
|
474
|
+
const response = await undici.fetch(fetchInput, {
|
|
475
|
+
...fetchInit,
|
|
476
|
+
dispatcher,
|
|
477
|
+
});
|
|
478
|
+
if (logger.shouldLog("debug")) {
|
|
479
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
480
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
481
|
+
requestId,
|
|
482
|
+
url: targetUrl,
|
|
483
|
+
status: response?.status,
|
|
484
|
+
statusText: response?.statusText,
|
|
485
|
+
durationMs: Date.now() - requestStartTime,
|
|
486
|
+
contentLength: responseSize,
|
|
487
|
+
hasContent: !!responseBody,
|
|
488
|
+
bodyType: responseType,
|
|
489
|
+
proxied: true,
|
|
490
|
+
responseHeaders,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
logger.debug(`[Proxy Fetch] ENHANCED PROXY SUCCESS`, {
|
|
494
|
+
requestId,
|
|
495
|
+
responseStatus: response?.status,
|
|
496
|
+
responseOk: response?.ok,
|
|
497
|
+
proxyUsed: true,
|
|
498
|
+
timestamp: new Date().toISOString(),
|
|
499
|
+
});
|
|
500
|
+
return response;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
505
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
506
|
+
requestId,
|
|
507
|
+
url: targetUrl,
|
|
508
|
+
error: errorMessage,
|
|
509
|
+
durationMs: Date.now() - requestStartTime,
|
|
510
|
+
});
|
|
511
|
+
logger.debug(`[Proxy Fetch] ENHANCED ERROR ANALYSIS`, {
|
|
512
|
+
requestId,
|
|
513
|
+
error: errorMessage,
|
|
514
|
+
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
515
|
+
willFallback: true,
|
|
516
|
+
timestamp: new Date().toISOString(),
|
|
517
|
+
});
|
|
518
|
+
logger.warn(`[Proxy Fetch] Enhanced proxy failed (${errorMessage}), falling back to direct connection`);
|
|
519
|
+
}
|
|
520
|
+
logger.debug(`[Proxy Fetch] ENHANCED FALLBACK TO STANDARD FETCH`, {
|
|
521
|
+
requestId,
|
|
522
|
+
fallbackReason: "No proxy configured or proxy failed",
|
|
523
|
+
timestamp: new Date().toISOString(),
|
|
524
|
+
});
|
|
525
|
+
// Use the cloned request for the fallback so that the body stream is not
|
|
526
|
+
// already consumed from the proxy attempt above.
|
|
527
|
+
const fallbackInput = (input instanceof Request ? (requestClone ?? input) : input);
|
|
528
|
+
try {
|
|
529
|
+
const response = await fetchWithRetry(fallbackInput, init);
|
|
530
|
+
if (logger.shouldLog("debug")) {
|
|
531
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
532
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
533
|
+
requestId,
|
|
534
|
+
url: targetUrl,
|
|
535
|
+
status: response.status,
|
|
536
|
+
statusText: response.statusText,
|
|
537
|
+
durationMs: Date.now() - requestStartTime,
|
|
538
|
+
contentLength: responseSize,
|
|
539
|
+
hasContent: !!responseBody,
|
|
540
|
+
bodyType: responseType,
|
|
541
|
+
proxied: false,
|
|
542
|
+
responseHeaders,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return response;
|
|
546
|
+
}
|
|
547
|
+
catch (fallbackError) {
|
|
548
|
+
const fallbackMessage = fallbackError instanceof Error
|
|
549
|
+
? fallbackError.message
|
|
550
|
+
: String(fallbackError);
|
|
551
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
552
|
+
requestId,
|
|
553
|
+
url: targetUrl,
|
|
554
|
+
error: fallbackMessage,
|
|
555
|
+
durationMs: Date.now() - requestStartTime,
|
|
556
|
+
});
|
|
557
|
+
throw fallbackError;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function createProxiedFetchHandler(proxyEnv) {
|
|
561
|
+
return async (input, init) => executeProxiedFetch(input, init, proxyEnv);
|
|
562
|
+
}
|
|
282
563
|
// ==================== ENHANCED PROXY FETCH FUNCTION ====================
|
|
283
564
|
/**
|
|
284
565
|
* Create a proxy-aware fetch function with enhanced capabilities
|
|
@@ -291,9 +572,14 @@ export function createProxyFetch() {
|
|
|
291
572
|
const allProxy = process.env.ALL_PROXY || process.env.all_proxy;
|
|
292
573
|
const socksProxy = process.env.SOCKS_PROXY || process.env.socks_proxy;
|
|
293
574
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
575
|
+
const proxyEnv = {
|
|
576
|
+
httpsProxy,
|
|
577
|
+
httpProxy,
|
|
578
|
+
allProxy,
|
|
579
|
+
socksProxy,
|
|
580
|
+
noProxy,
|
|
581
|
+
};
|
|
294
582
|
// ENHANCED LOGGING: Capture ALL proxy-related environment variables — credentials redacted
|
|
295
|
-
// Reuse module-level maskProxyUrl, defaulting to "NOT_SET" for undefined values
|
|
296
|
-
const sanitizeProxyUrl = (url) => maskProxyUrl(url) ?? "NOT_SET";
|
|
297
583
|
if (logger.shouldLog("debug")) {
|
|
298
584
|
const allProxyRelatedEnvVars = Object.keys(process.env)
|
|
299
585
|
.filter((key) => key.toLowerCase().includes("proxy"))
|
|
@@ -316,52 +602,7 @@ export function createProxyFetch() {
|
|
|
316
602
|
// If no proxy configured, return instrumented standard fetch
|
|
317
603
|
if (!httpsProxy && !httpProxy && !allProxy && !socksProxy) {
|
|
318
604
|
logger.debug("[Proxy Fetch] No proxy environment variables found - using standard fetch");
|
|
319
|
-
return
|
|
320
|
-
const reqId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
321
|
-
const startTs = Date.now();
|
|
322
|
-
const url = typeof input === "string"
|
|
323
|
-
? input
|
|
324
|
-
: input instanceof URL
|
|
325
|
-
? input.href
|
|
326
|
-
: input.url;
|
|
327
|
-
if (logger.shouldLog("debug")) {
|
|
328
|
-
const { size: bodySize, type: bodyType } = parseBody(init?.body);
|
|
329
|
-
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
330
|
-
requestId: reqId,
|
|
331
|
-
url,
|
|
332
|
-
method: init?.method || "POST",
|
|
333
|
-
bodySize,
|
|
334
|
-
bodyType,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
const response = await fetchWithRetry(input, init);
|
|
339
|
-
if (logger.shouldLog("debug")) {
|
|
340
|
-
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
341
|
-
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
342
|
-
requestId: reqId,
|
|
343
|
-
url,
|
|
344
|
-
status: response.status,
|
|
345
|
-
statusText: response.statusText,
|
|
346
|
-
durationMs: Date.now() - startTs,
|
|
347
|
-
contentLength: responseSize,
|
|
348
|
-
hasContent: !!responseBody,
|
|
349
|
-
bodyType: responseType,
|
|
350
|
-
responseHeaders,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
return response;
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
logger.debug("[Observability] HTTP request failed", {
|
|
357
|
-
requestId: reqId,
|
|
358
|
-
url,
|
|
359
|
-
error: error instanceof Error ? error.message : String(error),
|
|
360
|
-
durationMs: Date.now() - startTs,
|
|
361
|
-
});
|
|
362
|
-
throw error;
|
|
363
|
-
}
|
|
364
|
-
};
|
|
605
|
+
return createDirectFetchHandler();
|
|
365
606
|
}
|
|
366
607
|
logger.debug(`[Proxy Fetch] Configuring enhanced proxy with multiple protocol support`);
|
|
367
608
|
logger.debug(`[Proxy Fetch] HTTP_PROXY: ${sanitizeProxyUrl(httpProxy)}`);
|
|
@@ -369,174 +610,7 @@ export function createProxyFetch() {
|
|
|
369
610
|
logger.debug(`[Proxy Fetch] ALL_PROXY: ${sanitizeProxyUrl(allProxy)}`);
|
|
370
611
|
logger.debug(`[Proxy Fetch] SOCKS_PROXY: ${sanitizeProxyUrl(socksProxy)}`);
|
|
371
612
|
logger.debug(`[Proxy Fetch] NO_PROXY: ${noProxy || "not set"}`);
|
|
372
|
-
|
|
373
|
-
return async (input, init) => {
|
|
374
|
-
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
375
|
-
const requestStartTime = Date.now();
|
|
376
|
-
// Determine target URL
|
|
377
|
-
const targetUrl = typeof input === "string"
|
|
378
|
-
? input
|
|
379
|
-
: input instanceof URL
|
|
380
|
-
? input.href
|
|
381
|
-
: input.url;
|
|
382
|
-
// Request logging with sensitive header redaction — gated behind debug check
|
|
383
|
-
if (logger.shouldLog("debug")) {
|
|
384
|
-
const { size: bodySize, type: bodyType } = parseBody(init?.body);
|
|
385
|
-
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
386
|
-
requestId,
|
|
387
|
-
url: targetUrl,
|
|
388
|
-
method: init?.method || "POST",
|
|
389
|
-
bodySize,
|
|
390
|
-
bodyType,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
logger.debug(`[Proxy Fetch] ENHANCED REQUEST START`, {
|
|
394
|
-
requestId,
|
|
395
|
-
targetUrl,
|
|
396
|
-
timestamp: new Date().toISOString(),
|
|
397
|
-
httpProxy: sanitizeProxyUrl(httpProxy),
|
|
398
|
-
httpsProxy: sanitizeProxyUrl(httpsProxy),
|
|
399
|
-
allProxy: sanitizeProxyUrl(allProxy),
|
|
400
|
-
socksProxy: sanitizeProxyUrl(socksProxy),
|
|
401
|
-
initMethod: init?.method || "GET",
|
|
402
|
-
});
|
|
403
|
-
try {
|
|
404
|
-
// Enhanced proxy selection with NO_PROXY bypass and multiple protocols
|
|
405
|
-
const proxyUrl = selectProxyUrl(targetUrl);
|
|
406
|
-
if (proxyUrl) {
|
|
407
|
-
const url = new URL(targetUrl);
|
|
408
|
-
const sanitizedProxy = sanitizeProxyUrl(proxyUrl);
|
|
409
|
-
logger.debug(`[Proxy Fetch] 🔗 ENHANCED URL ANALYSIS`, {
|
|
410
|
-
requestId,
|
|
411
|
-
targetUrl,
|
|
412
|
-
urlHostname: url.hostname,
|
|
413
|
-
urlProtocol: url.protocol,
|
|
414
|
-
urlPort: url.port,
|
|
415
|
-
selectedProxyUrl: sanitizedProxy,
|
|
416
|
-
timestamp: new Date().toISOString(),
|
|
417
|
-
});
|
|
418
|
-
logger.debug(`[Proxy Fetch] 🎯 ENHANCED PROXY AGENT CREATION`, {
|
|
419
|
-
requestId,
|
|
420
|
-
proxyUrl: sanitizedProxy,
|
|
421
|
-
targetHostname: url.hostname,
|
|
422
|
-
targetProtocol: url.protocol,
|
|
423
|
-
aboutToCreateProxyAgent: true,
|
|
424
|
-
timestamp: new Date().toISOString(),
|
|
425
|
-
});
|
|
426
|
-
// Create/reuse proxy agent (HTTP/HTTPS/SOCKS)
|
|
427
|
-
const agentCache = globalThis.__NL_PROXY_AGENT_CACHE__ ??
|
|
428
|
-
(globalThis.__NL_PROXY_AGENT_CACHE__ = new Map());
|
|
429
|
-
const cacheKey = maskProxyUrl(proxyUrl) ?? proxyUrl; // mask credentials in cache key
|
|
430
|
-
const dispatcher = agentCache.get(cacheKey) || (await createProxyAgent(proxyUrl));
|
|
431
|
-
agentCache.set(cacheKey, dispatcher);
|
|
432
|
-
logger.debug(`[Proxy Fetch] ✅ ENHANCED PROXY AGENT CREATED`, {
|
|
433
|
-
requestId,
|
|
434
|
-
hasDispatcher: !!dispatcher,
|
|
435
|
-
dispatcherType: typeof dispatcher,
|
|
436
|
-
dispatcherConstructor: dispatcher?.constructor?.name || "unknown",
|
|
437
|
-
timestamp: new Date().toISOString(),
|
|
438
|
-
});
|
|
439
|
-
// Handle Request objects by extracting URL and merging properties
|
|
440
|
-
let fetchInput;
|
|
441
|
-
let fetchInit = { ...init };
|
|
442
|
-
if (input instanceof Request) {
|
|
443
|
-
fetchInput = input.url;
|
|
444
|
-
fetchInit = {
|
|
445
|
-
method: input.method,
|
|
446
|
-
headers: input.headers,
|
|
447
|
-
body: input.body,
|
|
448
|
-
...init, // Allow init to override Request properties
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
fetchInput = input;
|
|
453
|
-
}
|
|
454
|
-
// Use undici fetch with enhanced dispatcher (supports HTTP/HTTPS/SOCKS)
|
|
455
|
-
const undici = await import("undici");
|
|
456
|
-
const response = await undici.fetch(fetchInput, {
|
|
457
|
-
...fetchInit,
|
|
458
|
-
dispatcher: dispatcher,
|
|
459
|
-
});
|
|
460
|
-
if (logger.shouldLog("debug")) {
|
|
461
|
-
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
462
|
-
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
463
|
-
requestId,
|
|
464
|
-
url: targetUrl,
|
|
465
|
-
status: response?.status,
|
|
466
|
-
statusText: response?.statusText,
|
|
467
|
-
durationMs: Date.now() - requestStartTime,
|
|
468
|
-
contentLength: responseSize,
|
|
469
|
-
hasContent: !!responseBody,
|
|
470
|
-
bodyType: responseType,
|
|
471
|
-
proxied: true,
|
|
472
|
-
responseHeaders,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
logger.debug(`[Proxy Fetch] ENHANCED PROXY SUCCESS`, {
|
|
476
|
-
requestId,
|
|
477
|
-
responseStatus: response?.status,
|
|
478
|
-
responseOk: response?.ok,
|
|
479
|
-
proxyUsed: true,
|
|
480
|
-
timestamp: new Date().toISOString(),
|
|
481
|
-
});
|
|
482
|
-
return response;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
catch (error) {
|
|
486
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
487
|
-
logger.debug("[Observability] HTTP request failed", {
|
|
488
|
-
requestId,
|
|
489
|
-
url: targetUrl,
|
|
490
|
-
error: errorMessage,
|
|
491
|
-
durationMs: Date.now() - requestStartTime,
|
|
492
|
-
});
|
|
493
|
-
logger.debug(`[Proxy Fetch] ENHANCED ERROR ANALYSIS`, {
|
|
494
|
-
requestId,
|
|
495
|
-
error: errorMessage,
|
|
496
|
-
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
497
|
-
willFallback: true,
|
|
498
|
-
timestamp: new Date().toISOString(),
|
|
499
|
-
});
|
|
500
|
-
logger.warn(`[Proxy Fetch] Enhanced proxy failed (${errorMessage}), falling back to direct connection`);
|
|
501
|
-
}
|
|
502
|
-
// Fallback to standard fetch
|
|
503
|
-
logger.debug(`[Proxy Fetch] ENHANCED FALLBACK TO STANDARD FETCH`, {
|
|
504
|
-
requestId,
|
|
505
|
-
fallbackReason: "No proxy configured or proxy failed",
|
|
506
|
-
timestamp: new Date().toISOString(),
|
|
507
|
-
});
|
|
508
|
-
try {
|
|
509
|
-
const response = await fetchWithRetry(input, init);
|
|
510
|
-
if (logger.shouldLog("debug")) {
|
|
511
|
-
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
512
|
-
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
513
|
-
requestId,
|
|
514
|
-
url: targetUrl,
|
|
515
|
-
status: response.status,
|
|
516
|
-
statusText: response.statusText,
|
|
517
|
-
durationMs: Date.now() - requestStartTime,
|
|
518
|
-
contentLength: responseSize,
|
|
519
|
-
hasContent: !!responseBody,
|
|
520
|
-
bodyType: responseType,
|
|
521
|
-
proxied: false,
|
|
522
|
-
responseHeaders,
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
return response;
|
|
526
|
-
}
|
|
527
|
-
catch (fallbackError) {
|
|
528
|
-
const fallbackMessage = fallbackError instanceof Error
|
|
529
|
-
? fallbackError.message
|
|
530
|
-
: String(fallbackError);
|
|
531
|
-
logger.debug("[Observability] HTTP request failed", {
|
|
532
|
-
requestId,
|
|
533
|
-
url: targetUrl,
|
|
534
|
-
error: fallbackMessage,
|
|
535
|
-
durationMs: Date.now() - requestStartTime,
|
|
536
|
-
});
|
|
537
|
-
throw fallbackError;
|
|
538
|
-
}
|
|
539
|
-
};
|
|
613
|
+
return createProxiedFetchHandler(proxyEnv);
|
|
540
614
|
}
|
|
541
615
|
/**
|
|
542
616
|
* Mask credentials in a proxy URL for safe logging/reporting.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy Request Tracer
|
|
3
|
+
*
|
|
4
|
+
* Creates and manages OTel spans for the proxy request lifecycle.
|
|
5
|
+
* Provides a clean API for claudeProxyRoutes to trace each phase:
|
|
6
|
+
* receive -> account_selection -> upstream (per retry) -> stream -> end
|
|
7
|
+
*
|
|
8
|
+
* Uses the existing instrumentation infrastructure:
|
|
9
|
+
* - getTracer() from instrumentation.ts for span creation
|
|
10
|
+
* - setLangfuseContext() for Langfuse enrichment
|
|
11
|
+
* - OtelBridge for context propagation to/from upstream
|
|
12
|
+
* - SpanAttributes from spanTypes.ts for attribute naming
|
|
13
|
+
* - calculateCost() from pricing.ts for cost tracking
|
|
14
|
+
* - TelemetryService for metrics recording
|
|
15
|
+
*/
|
|
16
|
+
import { type Span } from "@opentelemetry/api";
|
|
17
|
+
type ProxyRequestContext = {
|
|
18
|
+
requestId: string;
|
|
19
|
+
method: string;
|
|
20
|
+
path: string;
|
|
21
|
+
model: string;
|
|
22
|
+
stream: boolean;
|
|
23
|
+
toolCount: number;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
userAgent?: string;
|
|
26
|
+
clientApp?: string;
|
|
27
|
+
};
|
|
28
|
+
type AccountSelectionContext = {
|
|
29
|
+
strategy: string;
|
|
30
|
+
accountsTotal: number;
|
|
31
|
+
accountsHealthy: number;
|
|
32
|
+
selectedAccount: string;
|
|
33
|
+
accountType: string;
|
|
34
|
+
rateLimitBefore5h?: number;
|
|
35
|
+
rateLimitBefore7d?: number;
|
|
36
|
+
};
|
|
37
|
+
type UpstreamAttemptContext = {
|
|
38
|
+
attempt: number;
|
|
39
|
+
account: string;
|
|
40
|
+
polyfillHeaders: boolean;
|
|
41
|
+
polyfillBody: boolean;
|
|
42
|
+
upstreamUrl: string;
|
|
43
|
+
};
|
|
44
|
+
type UsageContext = {
|
|
45
|
+
inputTokens: number;
|
|
46
|
+
outputTokens: number;
|
|
47
|
+
cacheCreationTokens: number;
|
|
48
|
+
cacheReadTokens: number;
|
|
49
|
+
reasoningTokens?: number;
|
|
50
|
+
rateLimitAfter5h?: number;
|
|
51
|
+
rateLimitAfter7d?: number;
|
|
52
|
+
};
|
|
53
|
+
declare class ProxyTracer {
|
|
54
|
+
private readonly rootSpan;
|
|
55
|
+
private readonly proxyTracer;
|
|
56
|
+
private readonly bridge;
|
|
57
|
+
private readonly requestId;
|
|
58
|
+
private readonly model;
|
|
59
|
+
private readonly startTime;
|
|
60
|
+
private readonly isStream;
|
|
61
|
+
private accountEmail?;
|
|
62
|
+
private usage?;
|
|
63
|
+
private mode;
|
|
64
|
+
private constructor();
|
|
65
|
+
/**
|
|
66
|
+
* Create a root span for a proxy request and set Langfuse context.
|
|
67
|
+
*
|
|
68
|
+
* If the incoming request carries a `traceparent` header, the root span
|
|
69
|
+
* will be linked to the caller's trace via OtelBridge.extractContext().
|
|
70
|
+
*/
|
|
71
|
+
static startRequest(ctx: ProxyRequestContext, incomingHeaders?: Record<string, string>): ProxyTracer;
|
|
72
|
+
/** Span covering the initial request receive and parse phase. */
|
|
73
|
+
startReceive(): Span;
|
|
74
|
+
/** Span covering account selection logic (fill-first / round-robin). */
|
|
75
|
+
startAccountSelection(): Span;
|
|
76
|
+
/** Span covering a single upstream attempt. One per retry. */
|
|
77
|
+
startUpstreamAttempt(ctx: UpstreamAttemptContext): Span;
|
|
78
|
+
/** Span covering the SSE stream relay phase. */
|
|
79
|
+
startStream(): Span;
|
|
80
|
+
/** Record account selection outcome on the root span. */
|
|
81
|
+
setAccountSelection(ctx: AccountSelectionContext): void;
|
|
82
|
+
/** Record token usage and cost on the root span. */
|
|
83
|
+
setUsage(ctx: UsageContext): void;
|
|
84
|
+
/** Record an error on the root span. */
|
|
85
|
+
setError(errorType: string, errorMessage: string): void;
|
|
86
|
+
/** Record whether the request was handled in full or passthrough mode. */
|
|
87
|
+
setMode(mode: "full" | "passthrough" | "passthrough-cli"): void;
|
|
88
|
+
/**
|
|
89
|
+
* Record that the proxy substituted a different model than was requested.
|
|
90
|
+
* Sets span attributes and increments the substitution metric counter.
|
|
91
|
+
*/
|
|
92
|
+
setModelSubstitution(requestedModel: string, actualModel: string): void;
|
|
93
|
+
/** Log the incoming client request body (redacted). */
|
|
94
|
+
logRequestBody(body: string): void;
|
|
95
|
+
/** Log the incoming client request headers (redacted). */
|
|
96
|
+
logRequestHeaders(headers: Record<string, string>): void;
|
|
97
|
+
/** Log the upstream request body (redacted, as sent to Anthropic). */
|
|
98
|
+
logUpstreamRequestBody(body: string): void;
|
|
99
|
+
/** Log the upstream request headers (redacted). */
|
|
100
|
+
logUpstreamRequestHeaders(headers: Record<string, string>): void;
|
|
101
|
+
/** Log the upstream response headers (redacted). */
|
|
102
|
+
logUpstreamResponseHeaders(headers: Record<string, string>): void;
|
|
103
|
+
/** Log the upstream response body (redacted). */
|
|
104
|
+
logUpstreamResponseBody(body: string): void;
|
|
105
|
+
/** Log SSE stream events (each event has type, timestamp, data). */
|
|
106
|
+
logStreamEvents(events: Array<{
|
|
107
|
+
type: string;
|
|
108
|
+
timestamp: number;
|
|
109
|
+
data: string;
|
|
110
|
+
}>): void;
|
|
111
|
+
/** Record an upstream retry attempt. */
|
|
112
|
+
recordRetry(account: string, reason: string): void;
|
|
113
|
+
/** Record request and/or response body sizes for bandwidth tracking. */
|
|
114
|
+
recordBodySizes(requestBytes?: number, responseBytes?: number): void;
|
|
115
|
+
/** Return the OTel trace/span IDs for this request (for log correlation). */
|
|
116
|
+
getTraceContext(): {
|
|
117
|
+
traceId: string;
|
|
118
|
+
spanId: string;
|
|
119
|
+
};
|
|
120
|
+
/** Return the captured usage (set by setUsage). */
|
|
121
|
+
getUsage(): UsageContext | undefined;
|
|
122
|
+
/** End the root span with final HTTP status and duration, and emit OTEL metrics. */
|
|
123
|
+
end(responseStatus: number, durationMs: number): void;
|
|
124
|
+
/** Record metrics via TelemetryService (call after setUsage). */
|
|
125
|
+
recordMetrics(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Get trace context headers for propagation to the upstream Anthropic request.
|
|
128
|
+
* Injects the current trace's `traceparent` / `tracestate` into a new header map.
|
|
129
|
+
*/
|
|
130
|
+
getTraceHeaders(): Record<string, string>;
|
|
131
|
+
}
|
|
132
|
+
export { ProxyTracer };
|
|
133
|
+
export type { ProxyRequestContext, AccountSelectionContext, UpstreamAttemptContext, UsageContext, };
|