@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
|
+
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
3
|
import { embed, embedMany, NoOutputGeneratedError, stepCountIs, streamText, } from "ai";
|
|
3
|
-
import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
4
4
|
import { AIProviderName } from "../constants/enums.js";
|
|
5
5
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
6
6
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
@@ -12,6 +12,7 @@ import { calculateCost } from "../utils/pricing.js";
|
|
|
12
12
|
import { createOpenAIConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
13
13
|
import { isZodSchema } from "../utils/schemaConversion.js";
|
|
14
14
|
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
15
|
+
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
15
16
|
import { getModelId } from "./providerTypeUtils.js";
|
|
16
17
|
/**
|
|
17
18
|
* Retrieve a tool's schema, handling both AI SDK v6 (`inputSchema`) and
|
|
@@ -278,6 +279,16 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
278
279
|
// Build message array from options with multimodal support
|
|
279
280
|
// Using protected helper from BaseProvider to eliminate code duplication
|
|
280
281
|
const messages = await this.buildMessagesForStream(options);
|
|
282
|
+
let resolvedToolChoice = resolveToolChoice(options, tools, shouldUseTools);
|
|
283
|
+
// Guard: if toolChoice names a specific tool that was filtered out, fall back to "auto"
|
|
284
|
+
if (resolvedToolChoice !== null &&
|
|
285
|
+
typeof resolvedToolChoice === "object" &&
|
|
286
|
+
"toolName" in resolvedToolChoice &&
|
|
287
|
+
typeof resolvedToolChoice.toolName === "string" &&
|
|
288
|
+
!tools[resolvedToolChoice.toolName]) {
|
|
289
|
+
logger.warn(`OpenAI: toolChoice references tool "${resolvedToolChoice.toolName}" which was removed during filtering; falling back to "auto"`);
|
|
290
|
+
resolvedToolChoice = "auto";
|
|
291
|
+
}
|
|
281
292
|
// Debug the actual request being sent to OpenAI
|
|
282
293
|
logger.debug(`OpenAI: streamText request parameters:`, {
|
|
283
294
|
modelName: this.modelName,
|
|
@@ -285,7 +296,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
285
296
|
temperature: options.temperature,
|
|
286
297
|
maxTokens: options.maxTokens,
|
|
287
298
|
toolsCount: Object.keys(tools).length,
|
|
288
|
-
toolChoice:
|
|
299
|
+
toolChoice: resolvedToolChoice,
|
|
289
300
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
290
301
|
firstToolExample: Object.keys(tools).length > 0
|
|
291
302
|
? {
|
|
@@ -314,7 +325,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
314
325
|
maxRetries: 0, // NL11: Disable AI SDK's invisible internal retries; we handle retries with OTel instrumentation
|
|
315
326
|
tools,
|
|
316
327
|
stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
|
|
317
|
-
toolChoice:
|
|
328
|
+
toolChoice: resolvedToolChoice,
|
|
318
329
|
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
319
330
|
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
320
331
|
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
@@ -381,150 +392,14 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
381
392
|
hasToolResults: !!result.toolResults,
|
|
382
393
|
resultType: typeof result,
|
|
383
394
|
});
|
|
384
|
-
|
|
385
|
-
const transformedStream = async function* () {
|
|
386
|
-
try {
|
|
387
|
-
logger.debug(`OpenAI: Starting stream transformation`, {
|
|
388
|
-
hasTextStream: !!result.textStream,
|
|
389
|
-
hasFullStream: !!result.fullStream,
|
|
390
|
-
resultKeys: Object.keys(result),
|
|
391
|
-
toolsEnabled: shouldUseTools,
|
|
392
|
-
toolsCount: Object.keys(tools).length,
|
|
393
|
-
});
|
|
394
|
-
let chunkCount = 0;
|
|
395
|
-
let contentYielded = 0;
|
|
396
|
-
// Try fullStream first (handles both text and tool calls), fallback to textStream
|
|
397
|
-
const streamToUse = result.fullStream || result.textStream;
|
|
398
|
-
if (!streamToUse) {
|
|
399
|
-
logger.error("OpenAI: No stream available in result", {
|
|
400
|
-
resultKeys: Object.keys(result),
|
|
401
|
-
});
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
logger.debug(`OpenAI: Stream source selected:`, {
|
|
405
|
-
usingFullStream: !!result.fullStream,
|
|
406
|
-
usingTextStream: !!result.textStream && !result.fullStream,
|
|
407
|
-
streamSourceType: result.fullStream ? "fullStream" : "textStream",
|
|
408
|
-
});
|
|
409
|
-
for await (const chunk of streamToUse) {
|
|
410
|
-
chunkCount++;
|
|
411
|
-
logger.debug(`OpenAI: Processing chunk ${chunkCount}:`, {
|
|
412
|
-
chunkType: typeof chunk,
|
|
413
|
-
chunkValue: typeof chunk === "string"
|
|
414
|
-
? chunk.substring(0, 50)
|
|
415
|
-
: "not-string",
|
|
416
|
-
chunkKeys: chunk && typeof chunk === "object"
|
|
417
|
-
? Object.keys(chunk)
|
|
418
|
-
: "not-object",
|
|
419
|
-
hasText: chunk && typeof chunk === "object" && "text" in chunk,
|
|
420
|
-
hasTextDelta: chunk && typeof chunk === "object" && "textDelta" in chunk,
|
|
421
|
-
hasType: chunk && typeof chunk === "object" && "type" in chunk,
|
|
422
|
-
chunkTypeValue: chunk && typeof chunk === "object" && "type" in chunk
|
|
423
|
-
? chunk.type
|
|
424
|
-
: "no-type",
|
|
425
|
-
});
|
|
426
|
-
let contentToYield = null;
|
|
427
|
-
// Handle different chunk types from fullStream
|
|
428
|
-
if (chunk && typeof chunk === "object") {
|
|
429
|
-
// Log the full chunk structure for debugging (debug mode only)
|
|
430
|
-
if (process.env.NEUROLINK_DEBUG === "true") {
|
|
431
|
-
logger.debug(`OpenAI: Full chunk structure:`, {
|
|
432
|
-
chunkKeys: Object.keys(chunk),
|
|
433
|
-
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
if ("type" in chunk && chunk.type === "error") {
|
|
437
|
-
// Handle error chunks when tools are enabled
|
|
438
|
-
const errorChunk = chunk;
|
|
439
|
-
logger.error(`OpenAI: Error chunk received:`, {
|
|
440
|
-
errorType: errorChunk.type,
|
|
441
|
-
errorDetails: errorChunk.error,
|
|
442
|
-
fullChunk: JSON.stringify(chunk),
|
|
443
|
-
});
|
|
444
|
-
// Throw a more descriptive error for tool-related issues
|
|
445
|
-
const errorMessage = errorChunk.error &&
|
|
446
|
-
typeof errorChunk.error === "object" &&
|
|
447
|
-
"message" in errorChunk.error
|
|
448
|
-
? String(errorChunk.error.message)
|
|
449
|
-
: "OpenAI API error when tools are enabled";
|
|
450
|
-
throw new Error(`OpenAI streaming error with tools: ${errorMessage}. Try disabling tools with --disableTools`);
|
|
451
|
-
}
|
|
452
|
-
else if ("type" in chunk &&
|
|
453
|
-
chunk.type === "text-delta" &&
|
|
454
|
-
"textDelta" in chunk) {
|
|
455
|
-
// Text delta from fullStream
|
|
456
|
-
contentToYield = chunk.textDelta;
|
|
457
|
-
logger.debug(`OpenAI: Found text-delta:`, {
|
|
458
|
-
textDelta: contentToYield,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
else if ("text" in chunk) {
|
|
462
|
-
// Direct text chunk
|
|
463
|
-
contentToYield = chunk.text;
|
|
464
|
-
logger.debug(`OpenAI: Found direct text:`, {
|
|
465
|
-
text: contentToYield,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
// Log unhandled chunks in debug mode only
|
|
470
|
-
if (process.env.NEUROLINK_DEBUG === "true") {
|
|
471
|
-
logger.debug(`OpenAI: Unhandled object chunk:`, {
|
|
472
|
-
chunkKeys: Object.keys(chunk),
|
|
473
|
-
chunkType: chunk.type || "no-type",
|
|
474
|
-
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
else if (typeof chunk === "string") {
|
|
480
|
-
// Direct string chunk from textStream
|
|
481
|
-
contentToYield = chunk;
|
|
482
|
-
logger.debug(`OpenAI: Found string chunk:`, {
|
|
483
|
-
content: contentToYield,
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
logger.warn(`OpenAI: Unhandled chunk type:`, {
|
|
488
|
-
type: typeof chunk,
|
|
489
|
-
value: String(chunk).substring(0, 100),
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
if (contentToYield) {
|
|
493
|
-
contentYielded++;
|
|
494
|
-
logger.debug(`OpenAI: Yielding content ${contentYielded}:`, {
|
|
495
|
-
content: contentToYield.substring(0, 50),
|
|
496
|
-
length: contentToYield.length,
|
|
497
|
-
});
|
|
498
|
-
yield { content: contentToYield };
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
logger.debug(`OpenAI: Stream transformation completed`, {
|
|
502
|
-
totalChunks: chunkCount,
|
|
503
|
-
contentYielded,
|
|
504
|
-
success: contentYielded > 0,
|
|
505
|
-
});
|
|
506
|
-
if (contentYielded === 0) {
|
|
507
|
-
logger.warn(`OpenAI: No content was yielded from stream despite processing ${chunkCount} chunks`);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
catch (streamError) {
|
|
511
|
-
// AI SDK v6 throws NoOutputGeneratedError when the stream produced no output.
|
|
512
|
-
// Treat as an empty stream rather than crashing with an unhandled rejection.
|
|
513
|
-
if (NoOutputGeneratedError.isInstance(streamError)) {
|
|
514
|
-
logger.warn("OpenAI: Stream produced no output (NoOutputGeneratedError)");
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
logger.error(`OpenAI: Stream transformation error:`, streamError);
|
|
518
|
-
throw streamError;
|
|
519
|
-
}
|
|
520
|
-
};
|
|
395
|
+
const transformedStream = this.createOpenAITransformedStream(result, shouldUseTools, tools);
|
|
521
396
|
// Create analytics promise that resolves after stream completion
|
|
522
397
|
const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, result, Date.now() - startTime, {
|
|
523
398
|
requestId: `openai-stream-${Date.now()}`,
|
|
524
399
|
streamingMode: true,
|
|
525
400
|
});
|
|
526
401
|
return {
|
|
527
|
-
stream: transformedStream
|
|
402
|
+
stream: transformedStream,
|
|
528
403
|
provider: this.providerName,
|
|
529
404
|
model: this.modelName,
|
|
530
405
|
analytics: analyticsPromise,
|
|
@@ -539,6 +414,131 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
539
414
|
throw this.handleProviderError(error);
|
|
540
415
|
}
|
|
541
416
|
}
|
|
417
|
+
async *createOpenAITransformedStream(result, shouldUseTools, tools) {
|
|
418
|
+
try {
|
|
419
|
+
logger.debug(`OpenAI: Starting stream transformation`, {
|
|
420
|
+
hasTextStream: !!result.textStream,
|
|
421
|
+
hasFullStream: !!result.fullStream,
|
|
422
|
+
resultKeys: Object.keys(result),
|
|
423
|
+
toolsEnabled: shouldUseTools,
|
|
424
|
+
toolsCount: Object.keys(tools).length,
|
|
425
|
+
});
|
|
426
|
+
let chunkCount = 0;
|
|
427
|
+
let contentYielded = 0;
|
|
428
|
+
const streamToUse = result.fullStream || result.textStream;
|
|
429
|
+
if (!streamToUse) {
|
|
430
|
+
logger.error("OpenAI: No stream available in result", {
|
|
431
|
+
resultKeys: Object.keys(result),
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
logger.debug(`OpenAI: Stream source selected:`, {
|
|
436
|
+
usingFullStream: !!result.fullStream,
|
|
437
|
+
usingTextStream: !!result.textStream && !result.fullStream,
|
|
438
|
+
streamSourceType: result.fullStream ? "fullStream" : "textStream",
|
|
439
|
+
});
|
|
440
|
+
for await (const chunk of streamToUse) {
|
|
441
|
+
chunkCount++;
|
|
442
|
+
logger.debug(`OpenAI: Processing chunk ${chunkCount}:`, {
|
|
443
|
+
chunkType: typeof chunk,
|
|
444
|
+
chunkValue: typeof chunk === "string"
|
|
445
|
+
? chunk.substring(0, 50)
|
|
446
|
+
: "not-string",
|
|
447
|
+
chunkKeys: chunk && typeof chunk === "object"
|
|
448
|
+
? Object.keys(chunk)
|
|
449
|
+
: "not-object",
|
|
450
|
+
hasText: chunk && typeof chunk === "object" && "text" in chunk,
|
|
451
|
+
hasTextDelta: chunk && typeof chunk === "object" && "textDelta" in chunk,
|
|
452
|
+
hasType: chunk && typeof chunk === "object" && "type" in chunk,
|
|
453
|
+
chunkTypeValue: chunk && typeof chunk === "object" && "type" in chunk
|
|
454
|
+
? chunk.type
|
|
455
|
+
: "no-type",
|
|
456
|
+
});
|
|
457
|
+
const contentToYield = this.extractOpenAIChunkContent(chunk);
|
|
458
|
+
if (contentToYield) {
|
|
459
|
+
contentYielded++;
|
|
460
|
+
logger.debug(`OpenAI: Yielding content ${contentYielded}:`, {
|
|
461
|
+
content: contentToYield.substring(0, 50),
|
|
462
|
+
length: contentToYield.length,
|
|
463
|
+
});
|
|
464
|
+
yield { content: contentToYield };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
logger.debug(`OpenAI: Stream transformation completed`, {
|
|
468
|
+
totalChunks: chunkCount,
|
|
469
|
+
contentYielded,
|
|
470
|
+
success: contentYielded > 0,
|
|
471
|
+
});
|
|
472
|
+
if (contentYielded === 0) {
|
|
473
|
+
logger.warn(`OpenAI: No content was yielded from stream despite processing ${chunkCount} chunks`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (streamError) {
|
|
477
|
+
if (NoOutputGeneratedError.isInstance(streamError)) {
|
|
478
|
+
logger.warn("OpenAI: Stream produced no output (NoOutputGeneratedError)");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
logger.error(`OpenAI: Stream transformation error:`, streamError);
|
|
482
|
+
throw streamError;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
extractOpenAIChunkContent(chunk) {
|
|
486
|
+
if (chunk && typeof chunk === "object") {
|
|
487
|
+
if (process.env.NEUROLINK_DEBUG === "true") {
|
|
488
|
+
logger.debug(`OpenAI: Full chunk structure:`, {
|
|
489
|
+
chunkKeys: Object.keys(chunk),
|
|
490
|
+
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if ("type" in chunk && chunk.type === "error") {
|
|
494
|
+
const errorChunk = chunk;
|
|
495
|
+
logger.error(`OpenAI: Error chunk received:`, {
|
|
496
|
+
errorType: errorChunk.type,
|
|
497
|
+
errorDetails: errorChunk.error,
|
|
498
|
+
fullChunk: JSON.stringify(chunk),
|
|
499
|
+
});
|
|
500
|
+
const errorMessage = errorChunk.error &&
|
|
501
|
+
typeof errorChunk.error === "object" &&
|
|
502
|
+
"message" in errorChunk.error
|
|
503
|
+
? String(errorChunk.error.message)
|
|
504
|
+
: "OpenAI API error when tools are enabled";
|
|
505
|
+
throw new Error(`OpenAI streaming error with tools: ${errorMessage}. Try disabling tools with --disableTools`);
|
|
506
|
+
}
|
|
507
|
+
if ("type" in chunk &&
|
|
508
|
+
chunk.type === "text-delta" &&
|
|
509
|
+
"textDelta" in chunk) {
|
|
510
|
+
const textDelta = chunk.textDelta;
|
|
511
|
+
logger.debug(`OpenAI: Found text-delta:`, { textDelta });
|
|
512
|
+
return textDelta;
|
|
513
|
+
}
|
|
514
|
+
if ("text" in chunk) {
|
|
515
|
+
const text = chunk.text;
|
|
516
|
+
logger.debug(`OpenAI: Found direct text:`, { text });
|
|
517
|
+
return text;
|
|
518
|
+
}
|
|
519
|
+
if (process.env.NEUROLINK_DEBUG === "true") {
|
|
520
|
+
logger.debug(`OpenAI: Unhandled object chunk:`, {
|
|
521
|
+
chunkKeys: Object.keys(chunk),
|
|
522
|
+
chunkType: "type" in chunk
|
|
523
|
+
? String(chunk.type)
|
|
524
|
+
: "no-type",
|
|
525
|
+
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
if (typeof chunk === "string") {
|
|
531
|
+
logger.debug(`OpenAI: Found string chunk:`, {
|
|
532
|
+
content: chunk,
|
|
533
|
+
});
|
|
534
|
+
return chunk;
|
|
535
|
+
}
|
|
536
|
+
logger.warn(`OpenAI: Unhandled chunk type:`, {
|
|
537
|
+
type: typeof chunk,
|
|
538
|
+
value: String(chunk).substring(0, 100),
|
|
539
|
+
});
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
542
|
/**
|
|
543
543
|
* Generate embeddings for text using OpenAI text-embedding models
|
|
544
544
|
* @param text - The text to embed
|
|
@@ -9,6 +9,7 @@ import { isAbortError } from "../utils/errorHandling.js";
|
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
10
|
import { getProviderModel } from "../utils/providerConfig.js";
|
|
11
11
|
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
12
|
+
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
12
13
|
// Constants
|
|
13
14
|
const MODELS_DISCOVERY_TIMEOUT_MS = 5000; // 5 seconds for model discovery
|
|
14
15
|
// Configuration helpers
|
|
@@ -234,7 +235,7 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
234
235
|
...(shouldUseTools &&
|
|
235
236
|
Object.keys(tools).length > 0 && {
|
|
236
237
|
tools,
|
|
237
|
-
toolChoice:
|
|
238
|
+
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
|
|
238
239
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
239
240
|
}),
|
|
240
241
|
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type {
|
|
3
|
-
import { AIProviderName } from "../constants/enums.js";
|
|
4
|
-
import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
|
|
1
|
+
import { type LanguageModel, type Schema } from "ai";
|
|
2
|
+
import type { AIProviderName } from "../constants/enums.js";
|
|
5
3
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
4
|
+
import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
|
|
5
|
+
import type { ZodUnknownSchema } from "../types/typeAliases.js";
|
|
6
6
|
/**
|
|
7
7
|
* OpenAI Compatible Provider - BaseProvider Implementation
|
|
8
8
|
* Provides access to one of the OpenAI-compatible endpoint (OpenRouter, vLLM, LiteLLM, etc.)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
2
|
import { NoOutputGeneratedError, streamText, } from "ai";
|
|
3
|
-
import { AIProviderName } from "../constants/enums.js";
|
|
4
3
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
|
-
import { logger } from "../utils/logger.js";
|
|
6
|
-
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
7
4
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
8
5
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
7
|
+
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
8
|
+
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
9
9
|
import { toAnalyticsStreamResult } from "./providerTypeUtils.js";
|
|
10
10
|
// Constants
|
|
11
11
|
const FALLBACK_OPENAI_COMPATIBLE_MODEL = "gpt-3.5-turbo";
|
|
@@ -178,7 +178,7 @@ export class OpenAICompatibleProvider extends BaseProvider {
|
|
|
178
178
|
? { temperature: options.temperature }
|
|
179
179
|
: {}),
|
|
180
180
|
tools,
|
|
181
|
-
toolChoice:
|
|
181
|
+
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
|
|
182
182
|
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
183
183
|
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
184
184
|
onStepFinish: (event) => {
|
|
@@ -74,6 +74,7 @@ export declare function formatSSE(eventType: string, data: unknown): string;
|
|
|
74
74
|
export declare class ClaudeStreamSerializer {
|
|
75
75
|
private state;
|
|
76
76
|
private currentBlockType;
|
|
77
|
+
private sawToolUseBlock;
|
|
77
78
|
private blockIndex;
|
|
78
79
|
private hasOpenedBlock;
|
|
79
80
|
private outputTokens;
|
|
@@ -107,8 +108,8 @@ export declare class ClaudeStreamSerializer {
|
|
|
107
108
|
*/
|
|
108
109
|
private openBlock;
|
|
109
110
|
/**
|
|
110
|
-
* Emit the opening frames: message_start
|
|
111
|
-
*
|
|
111
|
+
* Emit the opening frames: message_start and ping.
|
|
112
|
+
* The first actual content decides which content block opens next.
|
|
112
113
|
*/
|
|
113
114
|
start(): Generator<string>;
|
|
114
115
|
/**
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Reference: https://docs.anthropic.com/en/api/messages
|
|
10
10
|
*/
|
|
11
|
+
import { jsonSchema, tool } from "ai";
|
|
11
12
|
import { randomBytes } from "crypto";
|
|
12
|
-
import { jsonSchema } from "ai";
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Helpers
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -148,13 +148,13 @@ export function parseClaudeRequest(body) {
|
|
|
148
148
|
const tools = {};
|
|
149
149
|
if (body.tools) {
|
|
150
150
|
for (const t of body.tools) {
|
|
151
|
-
tools[t.name] = {
|
|
151
|
+
tools[t.name] = tool({
|
|
152
152
|
description: t.description ?? "",
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
};
|
|
153
|
+
// Fallback providers consume AI SDK-style tools, not Claude wire-format
|
|
154
|
+
// tool descriptors. Wrap the raw JSON schema once here so every
|
|
155
|
+
// downstream provider sees a canonical `inputSchema` shape.
|
|
156
|
+
inputSchema: jsonSchema(t.input_schema ?? { type: "object" }),
|
|
157
|
+
});
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
// --- tool_choice ---
|
|
@@ -239,6 +239,11 @@ function mapStopReason(finishReason) {
|
|
|
239
239
|
*/
|
|
240
240
|
export function serializeClaudeResponse(result, requestModel) {
|
|
241
241
|
const content = [];
|
|
242
|
+
const inferredFinishReason = result.toolCalls &&
|
|
243
|
+
result.toolCalls.length > 0 &&
|
|
244
|
+
(!result.finishReason || result.finishReason === "stop")
|
|
245
|
+
? "tool_use"
|
|
246
|
+
: result.finishReason;
|
|
242
247
|
// Thinking/reasoning content block (if present)
|
|
243
248
|
if (result.reasoning) {
|
|
244
249
|
content.push({ type: "thinking", thinking: result.reasoning });
|
|
@@ -250,11 +255,15 @@ export function serializeClaudeResponse(result, requestModel) {
|
|
|
250
255
|
// Tool use blocks — normalize IDs to Claude `toolu_` format
|
|
251
256
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
252
257
|
for (const tc of result.toolCalls) {
|
|
258
|
+
const toolInput = tc.args ??
|
|
259
|
+
tc.parameters ??
|
|
260
|
+
tc.input ??
|
|
261
|
+
{};
|
|
253
262
|
content.push({
|
|
254
263
|
type: "tool_use",
|
|
255
264
|
id: generateToolUseId(),
|
|
256
265
|
name: tc.toolName,
|
|
257
|
-
input:
|
|
266
|
+
input: toolInput,
|
|
258
267
|
});
|
|
259
268
|
}
|
|
260
269
|
}
|
|
@@ -268,7 +277,7 @@ export function serializeClaudeResponse(result, requestModel) {
|
|
|
268
277
|
role: "assistant",
|
|
269
278
|
content,
|
|
270
279
|
model: result.model ?? requestModel,
|
|
271
|
-
stop_reason: mapStopReason(
|
|
280
|
+
stop_reason: mapStopReason(inferredFinishReason),
|
|
272
281
|
stop_sequence: null,
|
|
273
282
|
usage: {
|
|
274
283
|
input_tokens: result.usage?.input ?? 0,
|
|
@@ -363,6 +372,7 @@ export function formatSSE(eventType, data) {
|
|
|
363
372
|
export class ClaudeStreamSerializer {
|
|
364
373
|
state = "idle";
|
|
365
374
|
currentBlockType = null;
|
|
375
|
+
sawToolUseBlock = false;
|
|
366
376
|
blockIndex = 0;
|
|
367
377
|
hasOpenedBlock = false;
|
|
368
378
|
outputTokens = 0;
|
|
@@ -465,15 +475,14 @@ export class ClaudeStreamSerializer {
|
|
|
465
475
|
// Public API
|
|
466
476
|
// -----------------------------------------------------------------------
|
|
467
477
|
/**
|
|
468
|
-
* Emit the opening frames: message_start
|
|
469
|
-
*
|
|
478
|
+
* Emit the opening frames: message_start and ping.
|
|
479
|
+
* The first actual content decides which content block opens next.
|
|
470
480
|
*/
|
|
471
481
|
*start() {
|
|
472
482
|
if (this.state !== "idle") {
|
|
473
483
|
return;
|
|
474
484
|
}
|
|
475
485
|
yield* this.ensureMessageStarted();
|
|
476
|
-
yield* this.openBlock({ type: "text", text: "" });
|
|
477
486
|
}
|
|
478
487
|
/**
|
|
479
488
|
* Push a text delta. Returns zero or more SSE frames.
|
|
@@ -529,6 +538,7 @@ export class ClaudeStreamSerializer {
|
|
|
529
538
|
if (this.state === "done" || this.state === "error") {
|
|
530
539
|
return;
|
|
531
540
|
}
|
|
541
|
+
this.sawToolUseBlock = true;
|
|
532
542
|
yield* this.ensureMessageStarted();
|
|
533
543
|
// Open a tool_use block (closes any current block)
|
|
534
544
|
yield* this.openBlock({ type: "tool_use", id, name, input: "" });
|
|
@@ -562,19 +572,22 @@ export class ClaudeStreamSerializer {
|
|
|
562
572
|
*finish(outputTokens, finishReason) {
|
|
563
573
|
// If we never started (empty response), start first
|
|
564
574
|
if (this.state === "idle") {
|
|
565
|
-
yield* this.
|
|
575
|
+
yield* this.ensureMessageStarted();
|
|
566
576
|
}
|
|
567
577
|
if (this.state === "done" || this.state === "error") {
|
|
568
578
|
return;
|
|
569
579
|
}
|
|
570
580
|
this.outputTokens = outputTokens ?? this.outputTokens;
|
|
581
|
+
const resolvedFinishReason = this.sawToolUseBlock && (!finishReason || finishReason === "stop")
|
|
582
|
+
? "tool_use"
|
|
583
|
+
: finishReason;
|
|
571
584
|
// Close any open content block
|
|
572
585
|
yield* this.closeCurrentBlock();
|
|
573
586
|
// message_delta
|
|
574
587
|
const messageDelta = {
|
|
575
588
|
type: "message_delta",
|
|
576
589
|
delta: {
|
|
577
|
-
stop_reason: mapStopReason(
|
|
590
|
+
stop_reason: mapStopReason(resolvedFinishReason),
|
|
578
591
|
stop_sequence: null,
|
|
579
592
|
},
|
|
580
593
|
usage: { output_tokens: this.outputTokens },
|
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
* so that Anthropic sees consistent "user" fingerprints even when requests are
|
|
4
4
|
* spread across multiple accounts.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* IDs are cached with a 1-hour TTL and reused for subsequent requests from
|
|
10
|
-
* the same account within that window.
|
|
6
|
+
* The generated metadata matches Claude Code's shape:
|
|
7
|
+
* {"device_id":"<64 hex>","account_uuid":"<uuid>","session_id":"<uuid>"}
|
|
11
8
|
*/
|
|
12
9
|
import type { CloakingPlugin } from "../../../types/index.js";
|
|
13
|
-
/** Purge all expired sessions from the cache. Exported for external timer use. */
|
|
14
10
|
export declare function purgeExpiredSessions(): void;
|
|
15
11
|
export declare function createSessionIdentity(): CloakingPlugin;
|
|
@@ -3,29 +3,12 @@
|
|
|
3
3
|
* so that Anthropic sees consistent "user" fingerprints even when requests are
|
|
4
4
|
* spread across multiple accounts.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* IDs are cached with a 1-hour TTL and reused for subsequent requests from
|
|
10
|
-
* the same account within that window.
|
|
6
|
+
* The generated metadata matches Claude Code's shape:
|
|
7
|
+
* {"device_id":"<64 hex>","account_uuid":"<uuid>","session_id":"<uuid>"}
|
|
11
8
|
*/
|
|
12
|
-
import {
|
|
13
|
-
// ── Session cache with TTL ───────────────────────────────────────────────────
|
|
14
|
-
const TTL_MS = 3_600_000; // 1 hour
|
|
15
|
-
const sessionCache = new Map();
|
|
16
|
-
/** Generate a new session user ID in the required format. */
|
|
17
|
-
function generateUserId() {
|
|
18
|
-
const hex = randomBytes(32).toString("hex"); // 64 hex chars, take first 32
|
|
19
|
-
return `user_${hex.slice(0, 32)}_account_${randomUUID()}_session_${randomUUID()}`;
|
|
20
|
-
}
|
|
21
|
-
/** Purge all expired sessions from the cache. Exported for external timer use. */
|
|
9
|
+
import { getOrCreateClaudeCodeIdentity, purgeExpiredClaudeCodeIdentities, } from "../../../auth/anthropicOAuth.js";
|
|
22
10
|
export function purgeExpiredSessions() {
|
|
23
|
-
|
|
24
|
-
for (const [key, entry] of sessionCache) {
|
|
25
|
-
if (entry.expiresAt <= now) {
|
|
26
|
-
sessionCache.delete(key);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
11
|
+
purgeExpiredClaudeCodeIdentities();
|
|
29
12
|
}
|
|
30
13
|
export function createSessionIdentity() {
|
|
31
14
|
return {
|
|
@@ -34,23 +17,16 @@ export function createSessionIdentity() {
|
|
|
34
17
|
enabled: true,
|
|
35
18
|
async transformRequest(ctx) {
|
|
36
19
|
const accountId = ctx.account.id;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!cached || cached.expiresAt <= now) {
|
|
41
|
-
cached = {
|
|
42
|
-
userId: generateUserId(),
|
|
43
|
-
expiresAt: now + TTL_MS,
|
|
44
|
-
};
|
|
45
|
-
sessionCache.set(accountId, cached);
|
|
46
|
-
}
|
|
20
|
+
const identity = getOrCreateClaudeCodeIdentity(accountId, {
|
|
21
|
+
existingUserId: ctx.request.body.metadata?.user_id,
|
|
22
|
+
});
|
|
47
23
|
const body = { ...ctx.request.body };
|
|
48
24
|
// Only set user_id if not already present — in passthrough mode,
|
|
49
|
-
// oauthFetch.ts owns this field and sets it from
|
|
25
|
+
// oauthFetch.ts owns this field and sets it from the shared helper.
|
|
50
26
|
if (!body.metadata?.user_id) {
|
|
51
27
|
body.metadata = {
|
|
52
28
|
...body.metadata,
|
|
53
|
-
user_id:
|
|
29
|
+
user_id: identity.metadataUserId,
|
|
54
30
|
};
|
|
55
31
|
}
|
|
56
32
|
return {
|
|
@@ -15,6 +15,9 @@ export class ModelRouter {
|
|
|
15
15
|
if (this.passthrough.has(requestedModel)) {
|
|
16
16
|
return { provider: "anthropic", model: requestedModel };
|
|
17
17
|
}
|
|
18
|
+
if (requestedModel.startsWith("gemini-")) {
|
|
19
|
+
return { provider: "vertex", model: requestedModel };
|
|
20
|
+
}
|
|
18
21
|
if (requestedModel.startsWith("claude-")) {
|
|
19
22
|
return { provider: "anthropic", model: requestedModel };
|
|
20
23
|
}
|
|
@@ -20,7 +20,7 @@ export { CLAUDE_CLI_USER_AGENT, MCP_TOOL_PREFIX };
|
|
|
20
20
|
* - Sets User-Agent to Claude CLI
|
|
21
21
|
* - Adds ?beta=true query parameter to /v1/messages
|
|
22
22
|
* - Injects billing header & agent block into system prompt
|
|
23
|
-
* - Injects
|
|
23
|
+
* - Injects Claude-Code-shaped user ID into metadata
|
|
24
24
|
* - Adds Stainless SDK headers for fingerprint matching
|
|
25
25
|
* - Disables thinking when tool_choice is forced
|
|
26
26
|
*
|