@juspay/neurolink 9.54.1 → 9.54.3

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +288 -288
  3. package/dist/cli/factories/commandFactory.js +43 -4
  4. package/dist/cli/utils/abortHandler.d.ts +22 -0
  5. package/dist/cli/utils/abortHandler.js +53 -0
  6. package/dist/core/baseProvider.d.ts +7 -1
  7. package/dist/core/baseProvider.js +19 -0
  8. package/dist/lib/core/baseProvider.d.ts +7 -1
  9. package/dist/lib/core/baseProvider.js +19 -0
  10. package/dist/lib/neurolink.js +17 -1
  11. package/dist/lib/providers/anthropic.js +1 -0
  12. package/dist/lib/providers/anthropicBaseProvider.js +1 -0
  13. package/dist/lib/providers/azureOpenai.js +1 -0
  14. package/dist/lib/providers/googleAiStudio.js +1 -0
  15. package/dist/lib/providers/googleVertex.d.ts +14 -0
  16. package/dist/lib/providers/googleVertex.js +51 -12
  17. package/dist/lib/providers/huggingFace.js +1 -0
  18. package/dist/lib/providers/litellm.js +1 -0
  19. package/dist/lib/providers/mistral.js +1 -0
  20. package/dist/lib/providers/openAI.js +1 -0
  21. package/dist/lib/providers/openRouter.js +1 -0
  22. package/dist/lib/providers/openaiCompatible.js +1 -0
  23. package/dist/lib/proxy/routingPolicy.d.ts +27 -17
  24. package/dist/lib/proxy/routingPolicy.js +53 -209
  25. package/dist/lib/server/routes/claudeProxyRoutes.js +35 -73
  26. package/dist/lib/types/proxyTypes.d.ts +9 -50
  27. package/dist/lib/types/streamTypes.d.ts +6 -0
  28. package/dist/lib/utils/messageBuilder.js +39 -6
  29. package/dist/lib/utils/toolCallRepair.d.ts +21 -0
  30. package/dist/lib/utils/toolCallRepair.js +298 -0
  31. package/dist/neurolink.js +17 -1
  32. package/dist/providers/anthropic.js +1 -0
  33. package/dist/providers/anthropicBaseProvider.js +1 -0
  34. package/dist/providers/azureOpenai.js +1 -0
  35. package/dist/providers/googleAiStudio.js +1 -0
  36. package/dist/providers/googleVertex.d.ts +14 -0
  37. package/dist/providers/googleVertex.js +51 -12
  38. package/dist/providers/huggingFace.js +1 -0
  39. package/dist/providers/litellm.js +1 -0
  40. package/dist/providers/mistral.js +1 -0
  41. package/dist/providers/openAI.js +1 -0
  42. package/dist/providers/openRouter.js +1 -0
  43. package/dist/providers/openaiCompatible.js +1 -0
  44. package/dist/proxy/routingPolicy.d.ts +27 -17
  45. package/dist/proxy/routingPolicy.js +53 -209
  46. package/dist/server/routes/claudeProxyRoutes.js +35 -73
  47. package/dist/types/proxyTypes.d.ts +9 -50
  48. package/dist/types/streamTypes.d.ts +6 -0
  49. package/dist/utils/messageBuilder.js +39 -6
  50. package/dist/utils/toolCallRepair.d.ts +21 -0
  51. package/dist/utils/toolCallRepair.js +297 -0
  52. package/package.json +1 -1
@@ -20,6 +20,7 @@ import { initializeCliParser } from "../parser.js";
20
20
  import { formatFileSize, saveAudioToFile } from "../utils/audioFileUtils.js";
21
21
  import { resolveFilePaths } from "../utils/pathResolver.js";
22
22
  import { animatedWrite } from "../utils/typewriter.js";
23
+ import { createStreamAbortHandler } from "../utils/abortHandler.js";
23
24
  import { formatVideoFileSize, getVideoMetadataSummary, saveVideoToFile, } from "../utils/videoFileUtils.js";
24
25
  import { OllamaCommandFactory } from "./ollamaCommandFactory.js";
25
26
  import { SageMakerCommandFactory } from "./sagemakerCommandFactory.js";
@@ -2176,6 +2177,11 @@ export class CLICommandFactory {
2176
2177
  let lastImageBase64;
2177
2178
  let contentReceived = false;
2178
2179
  const abortController = new AbortController();
2180
+ // BZ-667: Wire SIGINT to abort stream gracefully
2181
+ const abortHandler = createStreamAbortHandler();
2182
+ abortHandler.signal.addEventListener("abort", () => {
2183
+ abortController.abort();
2184
+ }, { once: true });
2179
2185
  // Create timeout promise for stream consumption (default: 30 seconds, respects user-provided timeout)
2180
2186
  const streamTimeout = options.timeout && typeof options.timeout === "number"
2181
2187
  ? options.timeout * 1000
@@ -2197,22 +2203,37 @@ export class CLICommandFactory {
2197
2203
  clearTimeout(timeoutId);
2198
2204
  });
2199
2205
  });
2206
+ const streamIterator = stream.stream[Symbol.asyncIterator]();
2200
2207
  try {
2201
2208
  // Process the stream with timeout handling
2202
- const streamIterator = stream.stream[Symbol.asyncIterator]();
2203
2209
  let timeoutActive = true;
2210
+ // BZ-667: Create an abort promise that rejects when the user presses Ctrl+C,
2211
+ // so we can race it against streamIterator.next() and unblock pending reads.
2212
+ const abortPromise = new Promise((_, reject) => {
2213
+ if (abortHandler.signal.aborted) {
2214
+ reject(new DOMException("Stream aborted", "AbortError"));
2215
+ return;
2216
+ }
2217
+ abortHandler.signal.addEventListener("abort", () => {
2218
+ reject(new DOMException("Stream aborted", "AbortError"));
2219
+ }, { once: true });
2220
+ });
2204
2221
  while (true) {
2205
2222
  let nextResult;
2206
2223
  if (timeoutActive && !contentReceived) {
2207
- // Race between next chunk and timeout for first chunk only
2224
+ // Race between next chunk, timeout, and abort signal
2208
2225
  nextResult = await Promise.race([
2209
2226
  streamIterator.next(),
2210
2227
  timeoutPromise,
2228
+ abortPromise,
2211
2229
  ]);
2212
2230
  }
2213
2231
  else {
2214
- // No timeout for subsequent chunks
2215
- nextResult = await streamIterator.next();
2232
+ // Race between next chunk and abort signal
2233
+ nextResult = await Promise.race([
2234
+ streamIterator.next(),
2235
+ abortPromise,
2236
+ ]);
2216
2237
  }
2217
2238
  if (nextResult.done) {
2218
2239
  break;
@@ -2266,8 +2287,26 @@ export class CLICommandFactory {
2266
2287
  }
2267
2288
  catch (error) {
2268
2289
  abortController.abort(); // Clean up timeout
2290
+ // BZ-667: Close the stream iterator so the provider connection is released.
2291
+ // Wrap in try/catch to prevent cleanup failures from masking the original error.
2292
+ try {
2293
+ await streamIterator.return?.();
2294
+ }
2295
+ catch {
2296
+ // Iterator cleanup failed — swallow so the original error propagates
2297
+ }
2298
+ abortHandler.cleanup();
2299
+ // BZ-667: Handle graceful abort — return partial content instead of throwing
2300
+ if (abortHandler.signal.aborted ||
2301
+ (error instanceof Error && error.name === "AbortError")) {
2302
+ if (!options.quiet) {
2303
+ process.stdout.write("\n");
2304
+ }
2305
+ return { content: fullContent, imageBase64: lastImageBase64 };
2306
+ }
2269
2307
  throw error;
2270
2308
  }
2309
+ abortHandler.cleanup();
2271
2310
  if (!contentReceived) {
2272
2311
  throw new Error("\n❌ No content received from stream\n" +
2273
2312
  "Check your credentials and provider configuration");
@@ -0,0 +1,22 @@
1
+ /**
2
+ * CLI Abort Handler (BZ-667)
3
+ *
4
+ * Bridges SIGINT (Ctrl+C) to an AbortController for graceful stream cancellation.
5
+ * First Ctrl+C aborts the stream and shows "Stream cancelled."
6
+ * Second Ctrl+C within 1 second force-exits the process.
7
+ *
8
+ * Uses `prependListener` so the stream handler fires BEFORE the top-level
9
+ * SIGINT handler in cli/index.ts (which calls process.exit). The listener
10
+ * remains registered until `cleanup()` removes it. On the first Ctrl+C the
11
+ * stream is cancelled gracefully; only a rapid second press exits.
12
+ *
13
+ * @module cli/utils/abortHandler
14
+ */
15
+ /**
16
+ * Create an abort handler that wires SIGINT to an AbortController.
17
+ * Call cleanup() when the stream finishes (success or error) to remove listeners.
18
+ */
19
+ export declare function createStreamAbortHandler(): {
20
+ signal: AbortSignal;
21
+ cleanup: () => void;
22
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * CLI Abort Handler (BZ-667)
3
+ *
4
+ * Bridges SIGINT (Ctrl+C) to an AbortController for graceful stream cancellation.
5
+ * First Ctrl+C aborts the stream and shows "Stream cancelled."
6
+ * Second Ctrl+C within 1 second force-exits the process.
7
+ *
8
+ * Uses `prependListener` so the stream handler fires BEFORE the top-level
9
+ * SIGINT handler in cli/index.ts (which calls process.exit). The listener
10
+ * remains registered until `cleanup()` removes it. On the first Ctrl+C the
11
+ * stream is cancelled gracefully; only a rapid second press exits.
12
+ *
13
+ * @module cli/utils/abortHandler
14
+ */
15
+ import chalk from "chalk";
16
+ /**
17
+ * Create an abort handler that wires SIGINT to an AbortController.
18
+ * Call cleanup() when the stream finishes (success or error) to remove listeners.
19
+ */
20
+ export function createStreamAbortHandler() {
21
+ const controller = new AbortController();
22
+ let aborted = false;
23
+ let forceExitTimer = null;
24
+ const sigintHandler = () => {
25
+ if (aborted) {
26
+ // Second Ctrl+C — force exit
27
+ if (forceExitTimer) {
28
+ clearTimeout(forceExitTimer);
29
+ }
30
+ // Let the top-level SIGINT handler in cli/index.ts handle the exit
31
+ return;
32
+ }
33
+ aborted = true;
34
+ controller.abort();
35
+ process.stderr.write(chalk.yellow("\nStream cancelled.\n"));
36
+ // Allow force exit on second Ctrl+C within 1 second
37
+ forceExitTimer = setTimeout(() => {
38
+ forceExitTimer = null;
39
+ }, 1000);
40
+ };
41
+ // Use prependListener so our handler fires before the top-level
42
+ // SIGINT handler in cli/index.ts. cleanup() removes it after the stream ends.
43
+ process.prependListener("SIGINT", sigintHandler);
44
+ const cleanup = () => {
45
+ process.removeListener("SIGINT", sigintHandler);
46
+ if (forceExitTimer) {
47
+ clearTimeout(forceExitTimer);
48
+ forceExitTimer = null;
49
+ }
50
+ };
51
+ return { signal: controller.signal, cleanup };
52
+ }
53
+ //# sourceMappingURL=abortHandler.js.map
@@ -1,4 +1,4 @@
1
- import type { LanguageModel, ModelMessage, Tool } from "ai";
1
+ import type { LanguageModel, ModelMessage, Tool, ToolCallRepairFunction, ToolSet } from "ai";
2
2
  import type { AIProviderName } from "../constants/enums.js";
3
3
  import type { EvaluationData } from "../index.js";
4
4
  import type { NeuroLink } from "../neurolink.js";
@@ -189,6 +189,12 @@ export declare abstract class BaseProvider implements AIProvider {
189
189
  * @returns The default embedding model name, or undefined if not supported
190
190
  */
191
191
  protected getDefaultEmbeddingModel(): string | undefined;
192
+ /**
193
+ * Create an `experimental_repairToolCall` handler for streamText/generateText.
194
+ * Dynamically reads the tool's JSON schema to repair wrong names and params.
195
+ * Returns undefined when repair is disabled via options.
196
+ */
197
+ protected getToolCallRepairFn(options?: StreamOptions | TextGenerationOptions): ToolCallRepairFunction<ToolSet> | undefined;
192
198
  /**
193
199
  * Provider-specific streaming implementation (only used when tools are disabled)
194
200
  */
@@ -861,6 +861,25 @@ export class BaseProvider {
861
861
  // Default implementation returns undefined - providers override this
862
862
  return undefined;
863
863
  }
864
+ // ===================
865
+ // ===================
866
+ // BZ-665: Schema-driven tool call repair
867
+ // ===================
868
+ /**
869
+ * Create an `experimental_repairToolCall` handler for streamText/generateText.
870
+ * Dynamically reads the tool's JSON schema to repair wrong names and params.
871
+ * Returns undefined when repair is disabled via options.
872
+ */
873
+ getToolCallRepairFn(options) {
874
+ if (options?.disableToolCallRepair) {
875
+ return undefined;
876
+ }
877
+ // Lazy import to avoid circular dependency at module load time
878
+ return (async (...args) => {
879
+ const { createToolCallRepair } = await import("../utils/toolCallRepair.js");
880
+ return createToolCallRepair()(...args);
881
+ });
882
+ }
864
883
  /**
865
884
  * Get AI SDK model with middleware applied
866
885
  * This method wraps the base model with any configured middleware
@@ -1,4 +1,4 @@
1
- import type { LanguageModel, ModelMessage, Tool } from "ai";
1
+ import type { LanguageModel, ModelMessage, Tool, ToolCallRepairFunction, ToolSet } from "ai";
2
2
  import type { AIProviderName } from "../constants/enums.js";
3
3
  import type { EvaluationData } from "../index.js";
4
4
  import type { NeuroLink } from "../neurolink.js";
@@ -189,6 +189,12 @@ export declare abstract class BaseProvider implements AIProvider {
189
189
  * @returns The default embedding model name, or undefined if not supported
190
190
  */
191
191
  protected getDefaultEmbeddingModel(): string | undefined;
192
+ /**
193
+ * Create an `experimental_repairToolCall` handler for streamText/generateText.
194
+ * Dynamically reads the tool's JSON schema to repair wrong names and params.
195
+ * Returns undefined when repair is disabled via options.
196
+ */
197
+ protected getToolCallRepairFn(options?: StreamOptions | TextGenerationOptions): ToolCallRepairFunction<ToolSet> | undefined;
192
198
  /**
193
199
  * Provider-specific streaming implementation (only used when tools are disabled)
194
200
  */
@@ -861,6 +861,25 @@ export class BaseProvider {
861
861
  // Default implementation returns undefined - providers override this
862
862
  return undefined;
863
863
  }
864
+ // ===================
865
+ // ===================
866
+ // BZ-665: Schema-driven tool call repair
867
+ // ===================
868
+ /**
869
+ * Create an `experimental_repairToolCall` handler for streamText/generateText.
870
+ * Dynamically reads the tool's JSON schema to repair wrong names and params.
871
+ * Returns undefined when repair is disabled via options.
872
+ */
873
+ getToolCallRepairFn(options) {
874
+ if (options?.disableToolCallRepair) {
875
+ return undefined;
876
+ }
877
+ // Lazy import to avoid circular dependency at module load time
878
+ return (async (...args) => {
879
+ const { createToolCallRepair } = await import("../utils/toolCallRepair.js");
880
+ return createToolCallRepair()(...args);
881
+ });
882
+ }
864
883
  /**
865
884
  * Get AI SDK model with middleware applied
866
885
  * This method wraps the base model with any configured middleware
@@ -4831,15 +4831,31 @@ Current user's request: ${currentInput}`;
4831
4831
  catch {
4832
4832
  /* non-blocking */
4833
4833
  }
4834
- const fallbackRoute = ModelRouter.getFallbackRoute(originalPrompt || enhancedOptions.input.text || "", {
4834
+ // BZ-1341: Support fallback provider override via options or env vars
4835
+ const optFallbackProvider = enhancedOptions.fallbackProvider?.trim() || undefined;
4836
+ const optFallbackModel = enhancedOptions.fallbackModel?.trim() || undefined;
4837
+ const envFallbackProvider = process.env.FALLBACK_PROVIDER?.trim() || undefined;
4838
+ const envFallbackModel = process.env.FALLBACK_MODEL?.trim() || undefined;
4839
+ const modelConfigRoute = ModelRouter.getFallbackRoute(originalPrompt || enhancedOptions.input.text || "", {
4835
4840
  provider: providerName,
4836
4841
  model: enhancedOptions.model || "gpt-4o",
4837
4842
  reasoning: "primary failed",
4838
4843
  confidence: 0.5,
4839
4844
  }, { fallbackStrategy: "auto" });
4845
+ const fallbackRoute = {
4846
+ ...modelConfigRoute,
4847
+ provider: optFallbackProvider ?? envFallbackProvider ?? modelConfigRoute.provider,
4848
+ model: optFallbackModel ?? envFallbackModel ?? modelConfigRoute.model,
4849
+ };
4840
4850
  logger.warn("Retrying with fallback provider", {
4841
4851
  originalProvider: providerName,
4842
4852
  fallbackProvider: fallbackRoute.provider,
4853
+ fallbackModel: fallbackRoute.model,
4854
+ fallbackSource: optFallbackProvider || optFallbackModel
4855
+ ? "options"
4856
+ : envFallbackProvider || envFallbackModel
4857
+ ? "env"
4858
+ : "model_config",
4843
4859
  reason: errorMsg,
4844
4860
  });
4845
4861
  try {
@@ -799,6 +799,7 @@ export class AnthropicProvider extends BaseProvider {
799
799
  stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
800
800
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
801
801
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
802
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
802
803
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
803
804
  onStepFinish: ({ toolCalls, toolResults }) => {
804
805
  this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
@@ -94,6 +94,7 @@ export class AnthropicProviderV2 extends BaseProvider {
94
94
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
95
95
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
96
96
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
97
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
97
98
  onStepFinish: ({ toolCalls, toolResults }) => {
98
99
  this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
99
100
  logger.warn("[AnthropicBaseProvider] Failed to store tool executions", {
@@ -124,6 +124,7 @@ export class AzureOpenAIProvider extends BaseProvider {
124
124
  stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
125
125
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
126
126
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
127
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
127
128
  onStepFinish: (event) => {
128
129
  this.handleToolExecutionStorage([...event.toolCalls], [...event.toolResults], options, new Date()).catch((error) => {
129
130
  logger.warn("[AzureOpenaiProvider] Failed to store tool executions", {
@@ -478,6 +478,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
478
478
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
479
479
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
480
480
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
481
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
481
482
  // Gemini 3: use thinkingLevel via providerOptions
482
483
  // Gemini 2.5: use thinkingBudget via providerOptions
483
484
  ...(options.thinkingConfig?.enabled && {
@@ -5,6 +5,20 @@ import { BaseProvider } from "../core/baseProvider.js";
5
5
  import type { EnhancedGenerateResult, TextGenerationOptions } from "../types/generateTypes.js";
6
6
  import type { NeurolinkCredentials } from "../types/providers.js";
7
7
  import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
8
+ /**
9
+ * Resolve the correct Vertex AI location for a given model.
10
+ *
11
+ * Google-published models (gemini-*) require the global endpoint
12
+ * (`aiplatform.googleapis.com`), not regional endpoints like
13
+ * `us-east5-aiplatform.googleapis.com`. Regional endpoints return
14
+ * "model not found" for these models.
15
+ *
16
+ * Anthropic-on-Vertex models (claude-*) require regional endpoints
17
+ * and are handled separately by `createVertexAnthropicSettings`.
18
+ *
19
+ * Embedding models and custom models use the configured location as-is.
20
+ */
21
+ export declare const resolveVertexLocation: (modelName: string | undefined, configuredLocation: string) => string;
8
22
  /**
9
23
  * Vertex Model Aliases
10
24
  *
@@ -79,7 +79,36 @@ const getVertexLocation = () => {
79
79
  return (process.env.GOOGLE_CLOUD_LOCATION ||
80
80
  process.env.VERTEX_LOCATION ||
81
81
  process.env.GOOGLE_VERTEX_LOCATION ||
82
- "us-central1");
82
+ "global");
83
+ };
84
+ /**
85
+ * Resolve the correct Vertex AI location for a given model.
86
+ *
87
+ * Google-published models (gemini-*) require the global endpoint
88
+ * (`aiplatform.googleapis.com`), not regional endpoints like
89
+ * `us-east5-aiplatform.googleapis.com`. Regional endpoints return
90
+ * "model not found" for these models.
91
+ *
92
+ * Anthropic-on-Vertex models (claude-*) require regional endpoints
93
+ * and are handled separately by `createVertexAnthropicSettings`.
94
+ *
95
+ * Embedding models and custom models use the configured location as-is.
96
+ */
97
+ export const resolveVertexLocation = (modelName, configuredLocation) => {
98
+ if (!modelName) {
99
+ return configuredLocation;
100
+ }
101
+ const normalized = modelName.toLowerCase();
102
+ // Google-published models always use the global endpoint.
103
+ // Hardcoded because Google's Vertex AI serves Gemini models exclusively
104
+ // from the global endpoint — regional endpoints like us-east5 return
105
+ // "Publisher Model was not found" errors. The env var GOOGLE_VERTEX_LOCATION
106
+ // is typically set for Anthropic-on-Vertex (which needs regional), so we
107
+ // cannot rely on it for Gemini routing.
108
+ if (normalized.startsWith("gemini-")) {
109
+ return "global";
110
+ }
111
+ return configuredLocation;
83
112
  };
84
113
  const getDefaultVertexModel = () => {
85
114
  // Use gemini-2.5-flash as default - latest and best price-performance model
@@ -96,8 +125,9 @@ const hasGoogleCredentials = () => {
96
125
  // Module-level cache for runtime-created credentials file to avoid per-request writes
97
126
  let cachedCredentialsPath = null;
98
127
  // Enhanced Vertex settings creation with authentication fallback and proxy support
99
- const createVertexSettings = async (region, credentials) => {
100
- const location = credentials?.location || region || getVertexLocation();
128
+ const createVertexSettings = async (region, credentials, modelName) => {
129
+ const configuredLocation = credentials?.location || region || getVertexLocation();
130
+ const location = resolveVertexLocation(modelName, configuredLocation);
101
131
  const project = credentials?.projectId || getVertexProjectId();
102
132
  const baseSettings = {
103
133
  project,
@@ -326,7 +356,12 @@ const createVertexAnthropicSettings = async (region, credentials) => {
326
356
  // which is invalid. The correct global endpoint omits the region prefix entirely.
327
357
  // Since the SDK doesn't handle this, redirect "global" to "us-east5" for Anthropic.
328
358
  const anthropicRegion = !region || region === "global" ? "us-east5" : region;
329
- const baseVertexSettings = await createVertexSettings(anthropicRegion, credentials);
359
+ // Override credentials.location so it cannot conflict with the redirected
360
+ // region — createVertexSettings checks credentials.location first.
361
+ const anthropicCredentials = credentials?.location
362
+ ? { ...credentials, location: anthropicRegion }
363
+ : credentials;
364
+ const baseVertexSettings = await createVertexSettings(anthropicRegion, anthropicCredentials);
330
365
  // GoogleVertexAnthropicProviderSettings extends GoogleVertexProviderSettings
331
366
  // so we can use the same settings with proper typing
332
367
  return {
@@ -570,7 +605,9 @@ export class GoogleVertexProvider extends BaseProvider {
570
605
  networkConfig: {
571
606
  projectId: this.projectId,
572
607
  location: this.location,
573
- expectedEndpoint: `https://${this.location}-aiplatform.googleapis.com`,
608
+ expectedEndpoint: this.location === "global"
609
+ ? "https://aiplatform.googleapis.com"
610
+ : `https://${this.location}-aiplatform.googleapis.com`,
574
611
  httpProxy: process.env.HTTP_PROXY || process.env.http_proxy,
575
612
  httpsProxy: process.env.HTTPS_PROXY || process.env.https_proxy,
576
613
  noProxy: process.env.NO_PROXY || process.env.no_proxy,
@@ -582,7 +619,7 @@ export class GoogleVertexProvider extends BaseProvider {
582
619
  message: "Starting Vertex settings creation with network configuration analysis",
583
620
  });
584
621
  try {
585
- const vertexSettings = await createVertexSettings(this.location, this.credentials);
622
+ const vertexSettings = await createVertexSettings(this.location, this.credentials, modelName);
586
623
  const vertexSettingsEndTime = process.hrtime.bigint();
587
624
  const vertexSettingsDurationNs = vertexSettingsEndTime - vertexSettingsStartTime;
588
625
  logger.debug(`[GoogleVertexProvider] ✅ LOG_POINT_V009_VERTEX_SETTINGS_SUCCESS`, {
@@ -957,6 +994,7 @@ export class GoogleVertexProvider extends BaseProvider {
957
994
  }),
958
995
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
959
996
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
997
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
960
998
  ...(options.thinkingConfig?.enabled && {
961
999
  providerOptions: {
962
1000
  vertex: {
@@ -1116,12 +1154,13 @@ export class GoogleVertexProvider extends BaseProvider {
1116
1154
  /**
1117
1155
  * Create @google/genai client configured for Vertex AI
1118
1156
  */
1119
- async createVertexGenAIClient(regionOverride) {
1157
+ async createVertexGenAIClient(regionOverride, modelName) {
1120
1158
  const project = this.credentials?.projectId || getVertexProjectId();
1121
- const location = this.credentials?.location ||
1159
+ const configuredLocation = this.credentials?.location ||
1122
1160
  regionOverride ||
1123
1161
  this.location ||
1124
1162
  getVertexLocation();
1163
+ const location = resolveVertexLocation(modelName, configuredLocation);
1125
1164
  const mod = await import("@google/genai");
1126
1165
  const ctor = mod.GoogleGenAI;
1127
1166
  if (!ctor) {
@@ -1308,8 +1347,8 @@ export class GoogleVertexProvider extends BaseProvider {
1308
1347
  }, (span) => this.executeNativeGemini3StreamWithSpan(options, modelName, span));
1309
1348
  }
1310
1349
  async executeNativeGemini3StreamWithSpan(options, modelName, span) {
1311
- const client = await this.createVertexGenAIClient(options.region);
1312
- const effectiveLocation = options.region || this.location || getVertexLocation();
1350
+ const client = await this.createVertexGenAIClient(options.region, modelName);
1351
+ const effectiveLocation = resolveVertexLocation(modelName, options.region || this.location || getVertexLocation());
1313
1352
  logger.debug("[GoogleVertex] Using native @google/genai for Gemini 3", {
1314
1353
  model: modelName,
1315
1354
  hasTools: !!options.tools && Object.keys(options.tools).length > 0,
@@ -1503,8 +1542,8 @@ export class GoogleVertexProvider extends BaseProvider {
1503
1542
  [ATTR.NL_PROVIDER]: this.providerName,
1504
1543
  },
1505
1544
  }, async (span) => {
1506
- const client = await this.createVertexGenAIClient(options.region);
1507
- const effectiveLocation = options.region || this.location || getVertexLocation();
1545
+ const client = await this.createVertexGenAIClient(options.region, modelName);
1546
+ const effectiveLocation = resolveVertexLocation(modelName, options.region || this.location || getVertexLocation());
1508
1547
  logger.debug("[GoogleVertex] Using native @google/genai for Gemini 3 generate", {
1509
1548
  model: modelName,
1510
1549
  project: this.projectId,
@@ -139,6 +139,7 @@ export class HuggingFaceProvider extends BaseProvider {
139
139
  toolChoice: resolveToolChoice(options, (shouldUseTools ? streamOptions.tools || allTools : {}), shouldUseTools),
140
140
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
141
141
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
142
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
142
143
  onStepFinish: ({ toolCalls, toolResults }) => {
143
144
  this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
144
145
  logger.warn("[HuggingFaceProvider] Failed to store tool executions", {
@@ -169,6 +169,7 @@ export class LiteLLMProvider extends BaseProvider {
169
169
  }),
170
170
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
171
171
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
172
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
172
173
  onError: (event) => {
173
174
  const error = event.error;
174
175
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -67,6 +67,7 @@ export class MistralProvider extends BaseProvider {
67
67
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
68
68
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
69
69
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
70
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
70
71
  onStepFinish: ({ toolCalls, toolResults }) => {
71
72
  this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
72
73
  logger.warn("[MistralProvider] Failed to store tool executions", {
@@ -330,6 +330,7 @@ export class OpenAIProvider extends BaseProvider {
330
330
  stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
331
331
  toolChoice: resolvedToolChoice,
332
332
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
333
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
333
334
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
334
335
  onStepFinish: ({ toolCalls, toolResults }) => {
335
336
  logger.info("Tool execution completed", {
@@ -252,6 +252,7 @@ export class OpenRouterProvider extends BaseProvider {
252
252
  }),
253
253
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
254
254
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
255
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
255
256
  onError: (event) => {
256
257
  const error = event.error;
257
258
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -195,6 +195,7 @@ export class OpenAICompatibleProvider extends BaseProvider {
195
195
  stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
196
196
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
197
197
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
198
+ experimental_repairToolCall: this.getToolCallRepairFn(options),
198
199
  onStepFinish: (event) => {
199
200
  this.handleToolExecutionStorage([...event.toolCalls], [...event.toolResults], options, new Date()).catch((error) => {
200
201
  logger.warn("[OpenAiCompatibleProvider] Failed to store tool executions", {
@@ -1,33 +1,43 @@
1
- import type { ClaudeProxyModelTier, ClaudeProxyRequestClass, ClaudeProxyRequestProfile, CooldownScope, CooldownSkippedAccount, FallbackEligibilityDecision, FallbackEntry, ParsedClaudeRequest, ProxyTranslationAttempt, ProxyTranslationPlan, RuntimeAccountState } from "../types/index.js";
2
- export type { ClaudeProxyModelTier, ClaudeProxyRequestClass, ClaudeProxyRequestProfile, CooldownScope, CooldownSkippedAccount, FallbackEligibilityDecision, ProxyTranslationAttempt, ProxyTranslationPlan, };
1
+ import type { ClaudeProxyModelTier, CooldownSkippedAccount, FallbackEntry, ParsedClaudeRequest, ProxyTranslationAttempt, ProxyTranslationPlan, RuntimeAccountState } from "../types/index.js";
2
+ export type { ClaudeProxyModelTier, ProxyTranslationAttempt, ProxyTranslationPlan, };
3
3
  export declare function inferClaudeProxyModelTier(modelName: string): ClaudeProxyModelTier;
4
- export declare function classifyClaudeProxyRequest(requestedModel: string, parsed: ParsedClaudeRequest): ClaudeProxyRequestProfile;
5
- export declare function getRequestClassCooldownKey(profile: ClaudeProxyRequestProfile): string;
6
- export declare function getModelTierCooldownKey(profile: ClaudeProxyRequestProfile): string;
7
- export declare function evaluateFallbackEligibility(profile: ClaudeProxyRequestProfile, candidate: {
8
- provider?: string;
9
- model?: string;
10
- }): FallbackEligibilityDecision;
4
+ /**
5
+ * Build a translation plan for a Claude-compatible proxy request.
6
+ * The plan lists the primary provider followed by eligible fallback targets.
7
+ * All configured fallback entries are always eligible — no contract-based gating.
8
+ * When no fallback chain is configured, an "auto-provider" entry is appended.
9
+ */
11
10
  export declare function buildProxyTranslationPlan(primary: {
12
11
  provider: string;
13
12
  model?: string;
14
- }, fallbackChain: FallbackEntry[], requestedModel: string, parsed: ParsedClaudeRequest): ProxyTranslationPlan;
15
- export declare function summarizeSkippedFallbacks(plan: Pick<ProxyTranslationPlan, "profile" | "skipped">): string | null;
16
- export declare function getActiveCooldownScope(state: RuntimeAccountState, profile: ClaudeProxyRequestProfile, now?: number): CooldownScope | null;
13
+ }, fallbackChain: FallbackEntry[], requestedModel: string, _parsed: ParsedClaudeRequest): ProxyTranslationPlan;
14
+ /**
15
+ * Check whether an account is currently cooling down.
16
+ * Returns the cooldown timestamp if active, null otherwise.
17
+ */
18
+ export declare function getAccountCooldownUntil(state: RuntimeAccountState, now?: number): number | null;
19
+ /**
20
+ * Partition accounts into eligible (no cooldown) and skipped (cooling down).
21
+ */
17
22
  export declare function partitionAccountsByCooldown<T extends {
18
23
  key: string;
19
- }>(accounts: T[], getState: (account: T) => RuntimeAccountState, profile: ClaudeProxyRequestProfile, now?: number): {
24
+ }>(accounts: T[], getState: (account: T) => RuntimeAccountState, now?: number): {
20
25
  eligible: T[];
21
26
  skipped: CooldownSkippedAccount<T>[];
22
27
  };
23
- export declare function applyRateLimitCooldownScope(args: {
28
+ /**
29
+ * Apply a rate-limit cooldown to an account.
30
+ * Uses simple exponential backoff with a floor and cap.
31
+ */
32
+ export declare function applyRateLimitCooldown(args: {
24
33
  state: RuntimeAccountState;
25
- profile: ClaudeProxyRequestProfile;
26
34
  retryAfterMs?: number;
27
35
  now?: number;
28
36
  capMs: number;
29
37
  }): {
30
38
  backoffMs: number;
31
- requestClassKey: string;
32
- modelTierKey: string;
33
39
  };
40
+ /**
41
+ * Clear cooldown state for an account after a successful request.
42
+ */
43
+ export declare function clearAccountCooldown(state: RuntimeAccountState): void;