@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.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +288 -288
- package/dist/cli/factories/commandFactory.js +43 -4
- package/dist/cli/utils/abortHandler.d.ts +22 -0
- package/dist/cli/utils/abortHandler.js +53 -0
- package/dist/core/baseProvider.d.ts +7 -1
- package/dist/core/baseProvider.js +19 -0
- package/dist/lib/core/baseProvider.d.ts +7 -1
- package/dist/lib/core/baseProvider.js +19 -0
- package/dist/lib/neurolink.js +17 -1
- package/dist/lib/providers/anthropic.js +1 -0
- package/dist/lib/providers/anthropicBaseProvider.js +1 -0
- package/dist/lib/providers/azureOpenai.js +1 -0
- package/dist/lib/providers/googleAiStudio.js +1 -0
- package/dist/lib/providers/googleVertex.d.ts +14 -0
- package/dist/lib/providers/googleVertex.js +51 -12
- package/dist/lib/providers/huggingFace.js +1 -0
- package/dist/lib/providers/litellm.js +1 -0
- package/dist/lib/providers/mistral.js +1 -0
- package/dist/lib/providers/openAI.js +1 -0
- package/dist/lib/providers/openRouter.js +1 -0
- package/dist/lib/providers/openaiCompatible.js +1 -0
- package/dist/lib/proxy/routingPolicy.d.ts +27 -17
- package/dist/lib/proxy/routingPolicy.js +53 -209
- package/dist/lib/server/routes/claudeProxyRoutes.js +35 -73
- package/dist/lib/types/proxyTypes.d.ts +9 -50
- package/dist/lib/types/streamTypes.d.ts +6 -0
- package/dist/lib/utils/messageBuilder.js +39 -6
- package/dist/lib/utils/toolCallRepair.d.ts +21 -0
- package/dist/lib/utils/toolCallRepair.js +298 -0
- package/dist/neurolink.js +17 -1
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/anthropicBaseProvider.js +1 -0
- package/dist/providers/azureOpenai.js +1 -0
- package/dist/providers/googleAiStudio.js +1 -0
- package/dist/providers/googleVertex.d.ts +14 -0
- package/dist/providers/googleVertex.js +51 -12
- package/dist/providers/huggingFace.js +1 -0
- package/dist/providers/litellm.js +1 -0
- package/dist/providers/mistral.js +1 -0
- package/dist/providers/openAI.js +1 -0
- package/dist/providers/openRouter.js +1 -0
- package/dist/providers/openaiCompatible.js +1 -0
- package/dist/proxy/routingPolicy.d.ts +27 -17
- package/dist/proxy/routingPolicy.js +53 -209
- package/dist/server/routes/claudeProxyRoutes.js +35 -73
- package/dist/types/proxyTypes.d.ts +9 -50
- package/dist/types/streamTypes.d.ts +6 -0
- package/dist/utils/messageBuilder.js +39 -6
- package/dist/utils/toolCallRepair.d.ts +21 -0
- package/dist/utils/toolCallRepair.js +297 -0
- 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
|
|
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
|
-
//
|
|
2215
|
-
nextResult = await
|
|
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
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -4831,15 +4831,31 @@ Current user's request: ${currentInput}`;
|
|
|
4831
4831
|
catch {
|
|
4832
4832
|
/* non-blocking */
|
|
4833
4833
|
}
|
|
4834
|
-
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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,
|
|
2
|
-
export type { ClaudeProxyModelTier,
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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,
|
|
15
|
-
|
|
16
|
-
|
|
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,
|
|
24
|
+
}>(accounts: T[], getState: (account: T) => RuntimeAccountState, now?: number): {
|
|
20
25
|
eligible: T[];
|
|
21
26
|
skipped: CooldownSkippedAccount<T>[];
|
|
22
27
|
};
|
|
23
|
-
|
|
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;
|