@juspay/neurolink 9.40.0 → 9.42.0
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 +12 -0
- package/README.md +7 -1
- package/dist/auth/anthropicOAuth.d.ts +18 -3
- package/dist/auth/anthropicOAuth.js +137 -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 +471 -445
- package/dist/cli/commands/mcp.js +3 -0
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +279 -16
- package/dist/cli/commands/task.d.ts +56 -0
- package/dist/cli/commands/task.js +838 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +38 -0
- package/dist/cli/parser.js +8 -4
- package/dist/client/aiSdkAdapter.js +3 -0
- package/dist/client/streamingClient.js +30 -10
- 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 +20 -8
- package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
- package/dist/lib/auth/anthropicOAuth.js +137 -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/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 +20 -8
- package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/neurolink.d.ts +18 -1
- package/dist/lib/neurolink.js +367 -484
- 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 -1
- package/dist/lib/providers/googleVertex.js +28 -6
- package/dist/lib/providers/huggingFace.d.ts +3 -3
- package/dist/lib/providers/huggingFace.js +6 -8
- package/dist/lib/providers/litellm.js +41 -29
- package/dist/lib/providers/mistral.js +2 -1
- package/dist/lib/providers/ollama.js +80 -23
- package/dist/lib/providers/openAI.js +3 -2
- 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 +25 -20
- 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 +65 -72
- package/dist/lib/proxy/proxyConfig.js +44 -24
- package/dist/lib/proxy/proxyEnv.d.ts +19 -0
- package/dist/lib/proxy/proxyEnv.js +73 -0
- package/dist/lib/proxy/proxyFetch.js +50 -4
- 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 +406 -37
- package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
- package/dist/lib/proxy/sseInterceptor.js +402 -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 +7 -2
- package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
- package/dist/lib/tasks/backends/bullmqBackend.d.ts +33 -0
- package/dist/lib/tasks/backends/bullmqBackend.js +196 -0
- package/dist/lib/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
- package/dist/lib/tasks/backends/nodeTimeoutBackend.js +141 -0
- package/dist/lib/tasks/backends/taskBackendRegistry.d.ts +31 -0
- package/dist/lib/tasks/backends/taskBackendRegistry.js +66 -0
- package/dist/lib/tasks/errors.d.ts +31 -0
- package/dist/lib/tasks/errors.js +18 -0
- package/dist/lib/tasks/store/fileTaskStore.d.ts +43 -0
- package/dist/lib/tasks/store/fileTaskStore.js +179 -0
- package/dist/lib/tasks/store/redisTaskStore.d.ts +43 -0
- package/dist/lib/tasks/store/redisTaskStore.js +197 -0
- package/dist/lib/tasks/taskExecutor.d.ts +21 -0
- package/dist/lib/tasks/taskExecutor.js +166 -0
- package/dist/lib/tasks/taskManager.d.ts +63 -0
- package/dist/lib/tasks/taskManager.js +426 -0
- package/dist/lib/tasks/tools/taskTools.d.ts +135 -0
- package/dist/lib/tasks/tools/taskTools.js +274 -0
- 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 +65 -5
- package/dist/lib/types/cli.d.ts +10 -0
- package/dist/lib/types/configTypes.d.ts +3 -0
- package/dist/lib/types/generateTypes.d.ts +13 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/proxyTypes.d.ts +37 -5
- package/dist/lib/types/streamTypes.d.ts +25 -3
- package/dist/lib/types/taskTypes.d.ts +275 -0
- package/dist/lib/types/taskTypes.js +37 -0
- package/dist/lib/utils/messageBuilder.js +3 -2
- package/dist/lib/utils/providerHealth.d.ts +18 -0
- package/dist/lib/utils/providerHealth.js +240 -9
- package/dist/lib/utils/providerUtils.js +14 -8
- package/dist/lib/utils/toolChoice.d.ts +4 -0
- package/dist/lib/utils/toolChoice.js +7 -0
- package/dist/neurolink.d.ts +18 -1
- package/dist/neurolink.js +367 -484
- 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 -1
- package/dist/providers/googleVertex.js +28 -6
- package/dist/providers/huggingFace.d.ts +3 -3
- package/dist/providers/huggingFace.js +6 -7
- package/dist/providers/litellm.js +41 -29
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/ollama.js +80 -23
- package/dist/providers/openAI.js +3 -2
- 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 +25 -20
- 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 +65 -72
- package/dist/proxy/proxyConfig.js +44 -24
- package/dist/proxy/proxyEnv.d.ts +19 -0
- package/dist/proxy/proxyEnv.js +72 -0
- package/dist/proxy/proxyFetch.js +50 -4
- 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 +406 -37
- package/dist/proxy/sseInterceptor.d.ts +97 -0
- package/dist/proxy/sseInterceptor.js +401 -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 +7 -2
- package/dist/server/routes/claudeProxyRoutes.js +1737 -508
- package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/services/server/ai/observability/instrumentation.js +240 -40
- package/dist/tasks/backends/bullmqBackend.d.ts +33 -0
- package/dist/tasks/backends/bullmqBackend.js +195 -0
- package/dist/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
- package/dist/tasks/backends/nodeTimeoutBackend.js +140 -0
- package/dist/tasks/backends/taskBackendRegistry.d.ts +31 -0
- package/dist/tasks/backends/taskBackendRegistry.js +65 -0
- package/dist/tasks/errors.d.ts +31 -0
- package/dist/tasks/errors.js +17 -0
- package/dist/tasks/store/fileTaskStore.d.ts +43 -0
- package/dist/tasks/store/fileTaskStore.js +178 -0
- package/dist/tasks/store/redisTaskStore.d.ts +43 -0
- package/dist/tasks/store/redisTaskStore.js +196 -0
- package/dist/tasks/taskExecutor.d.ts +21 -0
- package/dist/tasks/taskExecutor.js +165 -0
- package/dist/tasks/taskManager.d.ts +63 -0
- package/dist/tasks/taskManager.js +425 -0
- package/dist/tasks/tools/taskTools.d.ts +135 -0
- package/dist/tasks/tools/taskTools.js +273 -0
- 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 +65 -5
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/configTypes.d.ts +3 -0
- package/dist/types/generateTypes.d.ts +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/proxyTypes.d.ts +37 -5
- package/dist/types/streamTypes.d.ts +25 -3
- package/dist/types/taskTypes.d.ts +275 -0
- package/dist/types/taskTypes.js +36 -0
- package/dist/utils/messageBuilder.js +3 -2
- package/dist/utils/providerHealth.d.ts +18 -0
- package/dist/utils/providerHealth.js +240 -9
- package/dist/utils/providerUtils.js +14 -8
- 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 -1
- 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 +184 -0
- package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
- package/scripts/observability/proxy-observability.env.example +23 -0
package/dist/proxy/oauthFetch.js
CHANGED
|
@@ -10,63 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module proxy/oauthFetch
|
|
12
12
|
*/
|
|
13
|
-
import { CLAUDE_CLI_USER_AGENT, MCP_TOOL_PREFIX, } from "../auth/anthropicOAuth.js";
|
|
13
|
+
import { CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, MCP_TOOL_PREFIX, buildStableClaudeCodeBillingHeader, getOrCreateClaudeCodeIdentity, } from "../auth/anthropicOAuth.js";
|
|
14
14
|
import { logger } from "../utils/logger.js";
|
|
15
|
-
import { randomBytes, randomUUID } from "crypto";
|
|
16
15
|
// Re-export constants for consumers that previously imported them alongside
|
|
17
16
|
// the function from `providers/anthropic.ts`.
|
|
18
17
|
export { CLAUDE_CLI_USER_AGENT, MCP_TOOL_PREFIX };
|
|
19
18
|
// ---------------------------------------------------------------------------
|
|
20
|
-
// Helpers
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
/** Cache fake user IDs per token prefix (1-hour TTL, max 1000 entries). */
|
|
23
|
-
const userIdCache = new Map();
|
|
24
|
-
const USER_ID_CACHE_TTL_MS = 3_600_000; // 1 hour
|
|
25
|
-
const USER_ID_CACHE_MAX_SIZE = 1_000;
|
|
26
|
-
/** Evict expired entries and enforce max size (LRU-approximation via insertion order). */
|
|
27
|
-
function evictUserIdCache() {
|
|
28
|
-
const now = Date.now();
|
|
29
|
-
for (const [key, entry] of userIdCache) {
|
|
30
|
-
if (entry.expires <= now) {
|
|
31
|
-
userIdCache.delete(key);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// If still over capacity, remove oldest entries (Map preserves insertion order)
|
|
35
|
-
if (userIdCache.size > USER_ID_CACHE_MAX_SIZE) {
|
|
36
|
-
const excess = userIdCache.size - USER_ID_CACHE_MAX_SIZE;
|
|
37
|
-
let removed = 0;
|
|
38
|
-
for (const key of userIdCache.keys()) {
|
|
39
|
-
if (removed >= excess) {
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
userIdCache.delete(key);
|
|
43
|
-
removed++;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function getOrCreateUserId(tokenPrefix) {
|
|
48
|
-
evictUserIdCache();
|
|
49
|
-
const cached = userIdCache.get(tokenPrefix);
|
|
50
|
-
if (cached && cached.expires > Date.now()) {
|
|
51
|
-
return cached.id;
|
|
52
|
-
}
|
|
53
|
-
const hex = randomBytes(32).toString("hex");
|
|
54
|
-
const id = `user_${hex}_account_${randomUUID()}_session_${randomUUID()}`;
|
|
55
|
-
userIdCache.set(tokenPrefix, {
|
|
56
|
-
id,
|
|
57
|
-
expires: Date.now() + USER_ID_CACHE_TTL_MS,
|
|
58
|
-
});
|
|
59
|
-
return id;
|
|
60
|
-
}
|
|
61
|
-
/** Compute a short hex hash of a string using Web Crypto (SHA-256). */
|
|
62
|
-
async function shortSha256(data) {
|
|
63
|
-
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data));
|
|
64
|
-
return Array.from(new Uint8Array(buf))
|
|
65
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
66
|
-
.join("")
|
|
67
|
-
.substring(0, 5);
|
|
68
|
-
}
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
19
|
// Main factory
|
|
71
20
|
// ---------------------------------------------------------------------------
|
|
72
21
|
/**
|
|
@@ -77,7 +26,7 @@ async function shortSha256(data) {
|
|
|
77
26
|
* - Sets User-Agent to Claude CLI
|
|
78
27
|
* - Adds ?beta=true query parameter to /v1/messages
|
|
79
28
|
* - Injects billing header & agent block into system prompt
|
|
80
|
-
* - Injects
|
|
29
|
+
* - Injects Claude-Code-shaped user ID into metadata
|
|
81
30
|
* - Adds Stainless SDK headers for fingerprint matching
|
|
82
31
|
* - Disables thinking when tool_choice is forced
|
|
83
32
|
*
|
|
@@ -154,7 +103,9 @@ export function createOAuthFetch(getToken, includeOptionalBetas = true, enableMc
|
|
|
154
103
|
// Only add our betas if not already present in client's headers
|
|
155
104
|
if (!skipBodyTransform) {
|
|
156
105
|
// Direct NeuroLink usage — set full beta list
|
|
157
|
-
requiredBetas.push(
|
|
106
|
+
requiredBetas.push(...(includeOptionalBetas
|
|
107
|
+
? CLAUDE_CODE_OAUTH_BETAS.filter((beta) => beta !== "oauth-2025-04-20")
|
|
108
|
+
: []));
|
|
158
109
|
}
|
|
159
110
|
const allBetas = [...new Set([...existingBetas, ...requiredBetas])];
|
|
160
111
|
const mergedBetas = allBetas.join(",");
|
|
@@ -165,6 +116,8 @@ export function createOAuthFetch(getToken, includeOptionalBetas = true, enableMc
|
|
|
165
116
|
if (!skipBodyTransform) {
|
|
166
117
|
// Only override user-agent for direct NeuroLink usage
|
|
167
118
|
requestHeaders.set("user-agent", CLAUDE_CLI_USER_AGENT);
|
|
119
|
+
requestHeaders.set("anthropic-version", "2023-06-01");
|
|
120
|
+
requestHeaders.set("accept", "application/json");
|
|
168
121
|
}
|
|
169
122
|
requestHeaders.delete("x-api-key");
|
|
170
123
|
// Identity / fingerprint headers (skip in passthrough — client sends its own)
|
|
@@ -248,41 +201,67 @@ export function createOAuthFetch(getToken, includeOptionalBetas = true, enableMc
|
|
|
248
201
|
parsed.tool_choice?.type === "tool") {
|
|
249
202
|
delete parsed.thinking;
|
|
250
203
|
}
|
|
251
|
-
// --- Inject billing header + agent block into system prompt ----
|
|
252
|
-
const version = "2.1.63";
|
|
253
|
-
const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(2)))
|
|
254
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
255
|
-
.join("")
|
|
256
|
-
.substring(0, 3);
|
|
257
|
-
const bodyString = JSON.stringify(parsed);
|
|
258
|
-
const cch = await shortSha256(bodyString);
|
|
259
|
-
const billingBlock = {
|
|
260
|
-
type: "text",
|
|
261
|
-
text: `x-anthropic-billing-header: cc_version=${version}.${randomHex}; cc_entrypoint=cli; cch=${cch};`,
|
|
262
|
-
};
|
|
263
204
|
const agentBlock = {
|
|
264
205
|
type: "text",
|
|
265
206
|
text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.",
|
|
266
207
|
};
|
|
267
|
-
// Normalise `system` to an array and
|
|
208
|
+
// Normalise `system` to an array and APPEND billing + agent blocks.
|
|
209
|
+
// IMPORTANT: We append (not prepend) to preserve the client's cache
|
|
210
|
+
// prefix chain. Anthropic's prompt caching uses prefix matching — if
|
|
211
|
+
// we insert anything before the client's system blocks, we invalidate
|
|
212
|
+
// all cached content (tools, system prompt, message history).
|
|
213
|
+
//
|
|
214
|
+
// Claude Code sends a billing block with a `cch=<hash>` value that
|
|
215
|
+
// changes on every request. We remove any existing billing/agent
|
|
216
|
+
// blocks from their positions and always append our stable
|
|
217
|
+
// Claude-Code-shaped versions at the end.
|
|
268
218
|
if (parsed.system) {
|
|
269
219
|
if (typeof parsed.system === "string") {
|
|
270
220
|
parsed.system = [{ type: "text", text: parsed.system }];
|
|
271
221
|
}
|
|
272
222
|
if (Array.isArray(parsed.system)) {
|
|
273
|
-
|
|
223
|
+
// Find and remove existing billing/agent blocks from wherever
|
|
224
|
+
// the client placed them (typically at system[0])
|
|
225
|
+
const billingIdx = parsed.system.findIndex((b) => typeof b.text === "string" &&
|
|
226
|
+
b.text.includes("x-anthropic-billing-header"));
|
|
227
|
+
const agentIdx = parsed.system.findIndex((b) => typeof b.text === "string" &&
|
|
228
|
+
b.text.includes("Claude Agent SDK"));
|
|
229
|
+
const billingBlock = {
|
|
230
|
+
type: "text",
|
|
231
|
+
text: buildStableClaudeCodeBillingHeader(parsed.system[billingIdx]?.text),
|
|
232
|
+
};
|
|
233
|
+
// Remove in reverse index order so indices stay valid
|
|
234
|
+
const indicesToRemove = [billingIdx, agentIdx]
|
|
235
|
+
.filter((i) => i >= 0)
|
|
236
|
+
.sort((a, b) => b - a);
|
|
237
|
+
for (const idx of indicesToRemove) {
|
|
238
|
+
parsed.system.splice(idx, 1);
|
|
239
|
+
}
|
|
240
|
+
// Always append deterministic billing + agent blocks at the end
|
|
241
|
+
parsed.system = [...parsed.system, billingBlock, agentBlock];
|
|
274
242
|
}
|
|
275
243
|
}
|
|
276
244
|
else {
|
|
245
|
+
const billingBlock = {
|
|
246
|
+
type: "text",
|
|
247
|
+
text: buildStableClaudeCodeBillingHeader(),
|
|
248
|
+
};
|
|
277
249
|
parsed.system = [billingBlock, agentBlock];
|
|
278
250
|
}
|
|
279
|
-
// --- Inject
|
|
280
|
-
|
|
281
|
-
|
|
251
|
+
// --- Inject Claude-Code-shaped identity into metadata ----------
|
|
252
|
+
// Prefer existing metadata.user_id (refresh-stable) over the access
|
|
253
|
+
// token prefix, which changes on every token rotation.
|
|
254
|
+
const stableId = parsed.metadata?.user_id ??
|
|
255
|
+
getToken().substring(0, Math.min(20, getToken().length));
|
|
256
|
+
const identity = getOrCreateClaudeCodeIdentity(stableId, {
|
|
257
|
+
existingUserId: parsed.metadata?.user_id,
|
|
258
|
+
preferredSessionId: requestHeaders.get("x-claude-code-session-id") ?? undefined,
|
|
259
|
+
});
|
|
282
260
|
parsed.metadata = {
|
|
283
261
|
...parsed.metadata,
|
|
284
|
-
user_id:
|
|
262
|
+
user_id: identity.metadataUserId,
|
|
285
263
|
};
|
|
264
|
+
requestHeaders.set("x-claude-code-session-id", identity.sessionId);
|
|
286
265
|
body = JSON.stringify(parsed);
|
|
287
266
|
}
|
|
288
267
|
catch {
|
|
@@ -292,6 +271,20 @@ export function createOAuthFetch(getToken, includeOptionalBetas = true, enableMc
|
|
|
292
271
|
// Remove any inherited content-length — the body may have been transformed
|
|
293
272
|
// above, so the original length is stale. Let fetch/undici recalculate it.
|
|
294
273
|
requestHeaders.delete("content-length");
|
|
274
|
+
// Inject OTel traceparent so the proxy can link to this trace
|
|
275
|
+
try {
|
|
276
|
+
const { propagation: otelPropagation, context: otelContext } = await import("@opentelemetry/api");
|
|
277
|
+
const carrier = {};
|
|
278
|
+
otelPropagation.inject(otelContext.active(), carrier);
|
|
279
|
+
for (const [key, value] of Object.entries(carrier)) {
|
|
280
|
+
if (!requestHeaders.has(key)) {
|
|
281
|
+
requestHeaders.set(key, value);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// OTel not available — skip silently
|
|
287
|
+
}
|
|
295
288
|
// Make the request
|
|
296
289
|
const response = await fetch(requestUrl?.toString() ||
|
|
297
290
|
(input instanceof Request ? input.url : input.toString()), {
|
|
@@ -198,33 +198,47 @@ export function validateProxyConfig(config) {
|
|
|
198
198
|
if (cfg.version !== undefined && typeof cfg.version !== "number") {
|
|
199
199
|
errors.push(`"version" must be a number, got ${typeof cfg.version}`);
|
|
200
200
|
}
|
|
201
|
-
|
|
202
|
-
typeof cfg.accounts
|
|
203
|
-
Array.isArray(cfg.accounts)
|
|
201
|
+
const hasAccounts = !!cfg.accounts &&
|
|
202
|
+
typeof cfg.accounts === "object" &&
|
|
203
|
+
!Array.isArray(cfg.accounts);
|
|
204
|
+
const hasRouting = !!cfg.routing &&
|
|
205
|
+
typeof cfg.routing === "object" &&
|
|
206
|
+
!Array.isArray(cfg.routing);
|
|
207
|
+
if (cfg.routing !== undefined && !hasRouting) {
|
|
208
|
+
errors.push('"routing" must be an object');
|
|
209
|
+
return errors;
|
|
210
|
+
}
|
|
211
|
+
if (!hasAccounts && !hasRouting) {
|
|
212
|
+
errors.push('Config must contain at least one of "accounts" or "routing"');
|
|
213
|
+
return errors;
|
|
214
|
+
}
|
|
215
|
+
if (cfg.accounts !== undefined && !hasAccounts) {
|
|
204
216
|
errors.push('"accounts" must be an object mapping provider names to account arrays');
|
|
205
217
|
return errors;
|
|
206
218
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
totalAccounts += list.length;
|
|
215
|
-
for (let i = 0; i < list.length; i++) {
|
|
216
|
-
const acct = list[i];
|
|
217
|
-
if (!acct || typeof acct !== "object") {
|
|
218
|
-
errors.push(`accounts.${provider}[${i}] must be an object`);
|
|
219
|
+
if (hasAccounts) {
|
|
220
|
+
const accounts = cfg.accounts;
|
|
221
|
+
let totalAccounts = 0;
|
|
222
|
+
for (const [provider, list] of Object.entries(accounts)) {
|
|
223
|
+
if (!Array.isArray(list)) {
|
|
224
|
+
errors.push(`accounts.${provider} must be an array, got ${typeof list}`);
|
|
219
225
|
continue;
|
|
220
226
|
}
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
totalAccounts += list.length;
|
|
228
|
+
for (let i = 0; i < list.length; i++) {
|
|
229
|
+
const acct = list[i];
|
|
230
|
+
if (!acct || typeof acct !== "object") {
|
|
231
|
+
errors.push(`accounts.${provider}[${i}] must be an object`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (typeof acct.apiKey !== "string" || acct.apiKey.length === 0) {
|
|
235
|
+
errors.push(`accounts.${provider}[${i}].apiKey is required and must be a non-empty string`);
|
|
236
|
+
}
|
|
223
237
|
}
|
|
224
238
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
239
|
+
if (totalAccounts === 0 && !hasRouting) {
|
|
240
|
+
errors.push('"accounts" must contain at least one account');
|
|
241
|
+
}
|
|
228
242
|
}
|
|
229
243
|
return errors;
|
|
230
244
|
}
|
|
@@ -422,8 +436,10 @@ export async function loadProxyConfig(filePath, options = {}) {
|
|
|
422
436
|
const raw = parsed;
|
|
423
437
|
const accounts = {};
|
|
424
438
|
const rawAccounts = raw.accounts;
|
|
425
|
-
|
|
426
|
-
|
|
439
|
+
if (rawAccounts && typeof rawAccounts === "object") {
|
|
440
|
+
for (const [provider, list] of Object.entries(rawAccounts)) {
|
|
441
|
+
accounts[provider] = list.map((item) => applyAccountDefaults(item));
|
|
442
|
+
}
|
|
427
443
|
}
|
|
428
444
|
// 7. Extract routing config
|
|
429
445
|
const routing = parseRoutingConfig(raw.routing);
|
|
@@ -483,8 +499,12 @@ export async function parseProxyConfigString(content, options = {}) {
|
|
|
483
499
|
const raw = parsed;
|
|
484
500
|
const accounts = {};
|
|
485
501
|
const rawAccounts = raw.accounts;
|
|
486
|
-
|
|
487
|
-
|
|
502
|
+
if (rawAccounts &&
|
|
503
|
+
typeof rawAccounts === "object" &&
|
|
504
|
+
!Array.isArray(rawAccounts)) {
|
|
505
|
+
for (const [provider, list] of Object.entries(rawAccounts)) {
|
|
506
|
+
accounts[provider] = list.map((item) => applyAccountDefaults(item));
|
|
507
|
+
}
|
|
488
508
|
}
|
|
489
509
|
const routing = parseRoutingConfig(raw.routing);
|
|
490
510
|
const cloaking = validateCloakingConfig(raw.cloaking);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ProxyEnvSource = "cli" | "environment" | "default" | "none";
|
|
2
|
+
export type ProxyEnvResolution = {
|
|
3
|
+
path?: string;
|
|
4
|
+
source: ProxyEnvSource;
|
|
5
|
+
required: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type ProxyEnvLoadResult = {
|
|
8
|
+
loaded: boolean;
|
|
9
|
+
path?: string;
|
|
10
|
+
source: ProxyEnvSource;
|
|
11
|
+
};
|
|
12
|
+
type ProxyEnvOptions = {
|
|
13
|
+
explicitEnvFile?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
homeDir?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function resolveProxyEnvFile(options?: ProxyEnvOptions): ProxyEnvResolution;
|
|
18
|
+
export declare function loadProxyEnvFile(options?: ProxyEnvOptions): Promise<ProxyEnvLoadResult>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
export function resolveProxyEnvFile(options = {}) {
|
|
5
|
+
const env = options.env ?? process.env;
|
|
6
|
+
const homeDir = options.homeDir ?? homedir();
|
|
7
|
+
if (options.explicitEnvFile?.trim()) {
|
|
8
|
+
return {
|
|
9
|
+
path: resolve(options.explicitEnvFile.trim()),
|
|
10
|
+
source: "cli",
|
|
11
|
+
required: true,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (env.NEUROLINK_ENV_FILE?.trim()) {
|
|
15
|
+
return {
|
|
16
|
+
path: resolve(env.NEUROLINK_ENV_FILE.trim()),
|
|
17
|
+
source: "environment",
|
|
18
|
+
required: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const defaultPath = resolve(homeDir, ".neurolink", ".env");
|
|
22
|
+
if (existsSync(defaultPath)) {
|
|
23
|
+
return {
|
|
24
|
+
path: defaultPath,
|
|
25
|
+
source: "default",
|
|
26
|
+
required: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
source: "none",
|
|
31
|
+
required: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function loadProxyEnvFile(options = {}) {
|
|
35
|
+
const resolution = resolveProxyEnvFile(options);
|
|
36
|
+
const env = options.env ?? process.env;
|
|
37
|
+
if (!resolution.path) {
|
|
38
|
+
return {
|
|
39
|
+
loaded: false,
|
|
40
|
+
source: "none",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (!existsSync(resolution.path)) {
|
|
44
|
+
if (resolution.required) {
|
|
45
|
+
throw new Error(`Proxy env file not found: ${resolution.path}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
loaded: false,
|
|
49
|
+
source: resolution.source,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const { config } = await import("dotenv");
|
|
54
|
+
const result = config({
|
|
55
|
+
path: resolution.path,
|
|
56
|
+
override: true,
|
|
57
|
+
quiet: true,
|
|
58
|
+
});
|
|
59
|
+
if (result.error) {
|
|
60
|
+
throw result.error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new Error(`Failed to load proxy env file ${resolution.path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
65
|
+
}
|
|
66
|
+
env.NEUROLINK_ENV_FILE = resolution.path;
|
|
67
|
+
return {
|
|
68
|
+
loaded: true,
|
|
69
|
+
path: resolution.path,
|
|
70
|
+
source: resolution.source,
|
|
71
|
+
};
|
|
72
|
+
}
|
package/dist/proxy/proxyFetch.js
CHANGED
|
@@ -4,9 +4,51 @@
|
|
|
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
|
+
async function getLangfuseContext() {
|
|
11
|
+
try {
|
|
12
|
+
// Dynamic import to avoid hard dependency — getLangfuseContext is only
|
|
13
|
+
// available when the observability module is loaded.
|
|
14
|
+
const mod = await import("../services/server/ai/observability/instrumentation.js");
|
|
15
|
+
return mod.getLangfuseContext?.();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Inject OTel trace context (traceparent/tracestate) and NeuroLink session context
|
|
23
|
+
* into outgoing request headers. This enables:
|
|
24
|
+
* - The NeuroLink proxy to link proxy spans as children of the calling SDK's trace
|
|
25
|
+
* - Conversation-level session/user attribution on proxy spans
|
|
26
|
+
*/
|
|
27
|
+
async function injectTraceContext(init) {
|
|
28
|
+
const carrier = {};
|
|
29
|
+
propagation.inject(context.active(), carrier);
|
|
30
|
+
// Also inject NeuroLink session context from Langfuse AsyncLocalStorage
|
|
31
|
+
const langfuseContext = await getLangfuseContext();
|
|
32
|
+
if (langfuseContext?.sessionId) {
|
|
33
|
+
carrier["x-neurolink-session-id"] = langfuseContext.sessionId;
|
|
34
|
+
}
|
|
35
|
+
if (langfuseContext?.userId) {
|
|
36
|
+
carrier["x-neurolink-user-id"] = langfuseContext.userId;
|
|
37
|
+
}
|
|
38
|
+
if (langfuseContext?.conversationId) {
|
|
39
|
+
carrier["x-neurolink-conversation-id"] = langfuseContext.conversationId;
|
|
40
|
+
}
|
|
41
|
+
if (Object.keys(carrier).length === 0) {
|
|
42
|
+
return init ?? {};
|
|
43
|
+
}
|
|
44
|
+
const existingHeaders = new Headers(init?.headers);
|
|
45
|
+
for (const [key, value] of Object.entries(carrier)) {
|
|
46
|
+
if (!existingHeaders.has(key)) {
|
|
47
|
+
existingHeaders.set(key, value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { ...init, headers: existingHeaders };
|
|
51
|
+
}
|
|
10
52
|
const fetchTracer = tracers.http;
|
|
11
53
|
/**
|
|
12
54
|
* Extract hostname from a URL string for safe logging (no auth tokens or paths).
|
|
@@ -317,6 +359,8 @@ export function createProxyFetch() {
|
|
|
317
359
|
if (!httpsProxy && !httpProxy && !allProxy && !socksProxy) {
|
|
318
360
|
logger.debug("[Proxy Fetch] No proxy environment variables found - using standard fetch");
|
|
319
361
|
return async (input, init) => {
|
|
362
|
+
// Inject OTel traceparent so the proxy can link to this trace
|
|
363
|
+
const enrichedInit = await injectTraceContext(init);
|
|
320
364
|
const reqId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
321
365
|
const startTs = Date.now();
|
|
322
366
|
const url = typeof input === "string"
|
|
@@ -325,17 +369,17 @@ export function createProxyFetch() {
|
|
|
325
369
|
? input.href
|
|
326
370
|
: input.url;
|
|
327
371
|
if (logger.shouldLog("debug")) {
|
|
328
|
-
const { size: bodySize, type: bodyType } = parseBody(
|
|
372
|
+
const { size: bodySize, type: bodyType } = parseBody(enrichedInit?.body);
|
|
329
373
|
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
330
374
|
requestId: reqId,
|
|
331
375
|
url,
|
|
332
|
-
method:
|
|
376
|
+
method: enrichedInit?.method || "POST",
|
|
333
377
|
bodySize,
|
|
334
378
|
bodyType,
|
|
335
379
|
});
|
|
336
380
|
}
|
|
337
381
|
try {
|
|
338
|
-
const response = await fetchWithRetry(input,
|
|
382
|
+
const response = await fetchWithRetry(input, enrichedInit);
|
|
339
383
|
if (logger.shouldLog("debug")) {
|
|
340
384
|
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
341
385
|
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
@@ -371,6 +415,8 @@ export function createProxyFetch() {
|
|
|
371
415
|
logger.debug(`[Proxy Fetch] NO_PROXY: ${noProxy || "not set"}`);
|
|
372
416
|
// Return enhanced proxy-aware fetch function
|
|
373
417
|
return async (input, init) => {
|
|
418
|
+
// Inject OTel traceparent so the proxy can link to this trace
|
|
419
|
+
init = await injectTraceContext(init);
|
|
374
420
|
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
375
421
|
const requestStartTime = Date.now();
|
|
376
422
|
// Determine target URL
|
|
@@ -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, };
|