@juspay/neurolink 12.37.4 → 12.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/README.md +60 -50
- package/dist/browser/neurolink.min.js +423 -423
- package/dist/cli/commands/decide.d.ts +2 -2
- package/dist/cli/commands/decide.js +22 -3
- package/dist/cli/commands/setup.js +2 -1
- package/dist/cli/factories/commandFactory.js +1 -1
- package/dist/constants/enums.d.ts +9 -0
- package/dist/constants/enums.js +10 -0
- package/dist/core/baseProvider.d.ts +10 -1
- package/dist/core/baseProvider.js +35 -7
- package/dist/factories/providerDescriptors.d.ts +2 -0
- package/dist/factories/providerDescriptors.js +52 -6
- package/dist/factories/providerRegistry.js +9 -1
- package/dist/models/manifestRegistry.js +2 -0
- package/dist/models/manifests/anthropic.js +2 -2
- package/dist/models/manifests/xor.d.ts +12 -0
- package/dist/models/manifests/xor.js +30 -0
- package/dist/models/modelRegistry.d.ts +12 -1
- package/dist/models/modelRegistry.js +57 -5
- package/dist/neurolink.js +6 -0
- package/dist/providers/amazonSagemaker.js +1 -1
- package/dist/providers/anthropic/client.js +29 -5
- package/dist/providers/anthropic/structuredOutput.d.ts +4 -1
- package/dist/providers/anthropic/structuredOutput.js +13 -5
- package/dist/providers/catalog/cerebras.json +7 -8
- package/dist/providers/catalog/fireworks.json +10 -12
- package/dist/providers/googleVertex/client.d.ts +3 -3
- package/dist/providers/googleVertex/client.js +79 -26
- package/dist/providers/laya.js +1 -19
- package/dist/providers/openaiChatCompletionsBase.js +1 -1
- package/dist/providers/systemOneDecision.d.ts +21 -2
- package/dist/providers/systemOneDecision.js +64 -4
- package/dist/providers/xor.d.ts +48 -0
- package/dist/providers/xor.js +192 -0
- package/dist/proxy/vertexAnthropicFallback.d.ts +1 -12
- package/dist/proxy/vertexAnthropicFallback.js +43 -2
- package/dist/types/classifierRouter.d.ts +5 -4
- package/dist/types/cli.d.ts +6 -2
- package/dist/types/decision.d.ts +60 -1
- package/dist/types/model.d.ts +6 -0
- package/dist/types/providers.d.ts +10 -0
- package/dist/utils/decisionMedia.d.ts +9 -0
- package/dist/utils/decisionMedia.js +160 -0
- package/dist/utils/modelChoices.js +9 -1
- package/dist/utils/providerConfig.d.ts +5 -0
- package/dist/utils/providerConfig.js +17 -0
- package/docs-site/static/search-index.json +63 -46
- package/package.json +2 -1
|
@@ -41,14 +41,14 @@ import { buildNoOutputSentinel, stampNoOutputSpan, } from "../../utils/noOutputS
|
|
|
41
41
|
import { convertZodToJsonSchema } from "../../utils/schemaConversion.js";
|
|
42
42
|
import { resolveClaudeMaxTokens } from "../../utils/tokenLimits.js";
|
|
43
43
|
import { toAnthropicImageBlock, fileToAnthropicBlock, } from "../anthropicImageBlocks.js";
|
|
44
|
-
import { resolveSamplingParams } from "../../models/modelRegistry.js";
|
|
44
|
+
import { modelSupportsForcedToolChoice, resolveSamplingParams, } from "../../models/modelRegistry.js";
|
|
45
45
|
import { createDeferredAnalytics, stringifyToolInput, } from "../openaiChatCompletionsClient.js";
|
|
46
46
|
import { createStreamChannel } from "../../core/streamChannel.js";
|
|
47
47
|
import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
|
|
48
48
|
import { ANTHROPIC_BETA_HEADERS } from "./constants.js";
|
|
49
49
|
import { cacheControlOf, withLastToolCacheBreakpoint } from "./cacheControl.js";
|
|
50
50
|
import { createNativeGenerateGuard } from "../../context/nativeGenerateGuard.js";
|
|
51
|
-
import { appendFinalResultInstruction, appendFinalResultTool, FINAL_RESULT_TOOL_NAME, } from "./structuredOutput.js";
|
|
51
|
+
import { appendFinalResultInstruction, appendFinalResultTool, appendToolAnswerInstruction, FINAL_RESULT_TOOL_NAME, } from "./structuredOutput.js";
|
|
52
52
|
// AnthropicProviderConfig is imported from types/providers.ts
|
|
53
53
|
// Re-export for backward compatibility
|
|
54
54
|
// Configuration helpers - now using consolidated utility
|
|
@@ -424,6 +424,23 @@ const toolChoiceToAnthropic = (choice) => {
|
|
|
424
424
|
}
|
|
425
425
|
return undefined;
|
|
426
426
|
};
|
|
427
|
+
/**
|
|
428
|
+
* Claude 5.5 / 5.1 models answer a forced choice with a 400, so for them it
|
|
429
|
+
* becomes `auto`: the tool stays declared and the model still picks it.
|
|
430
|
+
*/
|
|
431
|
+
const relaxForcedToolChoice = (modelId, choice) => {
|
|
432
|
+
if ((choice?.type !== "any" && choice?.type !== "tool") ||
|
|
433
|
+
modelSupportsForcedToolChoice(modelId)) {
|
|
434
|
+
return choice;
|
|
435
|
+
}
|
|
436
|
+
logger.warn(`[anthropic] ${modelId} rejects tool_choice "${choice.type}"; sending "auto" instead`);
|
|
437
|
+
return {
|
|
438
|
+
type: "auto",
|
|
439
|
+
...(choice.disable_parallel_tool_use !== undefined
|
|
440
|
+
? { disable_parallel_tool_use: choice.disable_parallel_tool_use }
|
|
441
|
+
: {}),
|
|
442
|
+
};
|
|
443
|
+
};
|
|
427
444
|
/** Map Anthropic stop_reason onto the V3 unified finish reason. */
|
|
428
445
|
const mapAnthropicStopReason = (raw) => {
|
|
429
446
|
switch (raw) {
|
|
@@ -1107,8 +1124,15 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1107
1124
|
input_schema: schema,
|
|
1108
1125
|
},
|
|
1109
1126
|
];
|
|
1110
|
-
|
|
1127
|
+
if (modelSupportsForcedToolChoice(modelId)) {
|
|
1128
|
+
toolChoice = { type: "tool", name: jsonTool };
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
toolChoice = undefined;
|
|
1132
|
+
system = appendToolAnswerInstruction(system, jsonTool);
|
|
1133
|
+
}
|
|
1111
1134
|
}
|
|
1135
|
+
toolChoice = relaxForcedToolChoice(modelId, toolChoice);
|
|
1112
1136
|
// Additive structured output: when the caller wants a schema AND real
|
|
1113
1137
|
// tools, the forced-json path above cannot be used (it replaces the
|
|
1114
1138
|
// tools array), and structured output is excluded for this surface
|
|
@@ -1458,7 +1482,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1458
1482
|
options.disableInternalFallback === true;
|
|
1459
1483
|
// The native loop bypasses BaseProvider.executeGeneration, so the turn
|
|
1460
1484
|
// budget has to be composed here or it stops existing for this provider.
|
|
1461
|
-
return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback);
|
|
1485
|
+
return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback, options);
|
|
1462
1486
|
}
|
|
1463
1487
|
async executeNativeGenerate(options) {
|
|
1464
1488
|
const startTime = Date.now();
|
|
@@ -1717,7 +1741,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1717
1741
|
}
|
|
1718
1742
|
const modelId = this.modelName || getDefaultAnthropicModel();
|
|
1719
1743
|
const anthropicToolChoice = shouldUseTools && anthropicTools && anthropicTools.length > 0
|
|
1720
|
-
? toolChoiceToAnthropic(resolveToolChoice(options, toolsRecord, shouldUseTools))
|
|
1744
|
+
? relaxForcedToolChoice(modelId, toolChoiceToAnthropic(resolveToolChoice(options, toolsRecord, shouldUseTools)))
|
|
1721
1745
|
: undefined;
|
|
1722
1746
|
// Extended thinking: enabled when the caller supplies an explicit token
|
|
1723
1747
|
// budget (mirrors the previous experimental_thinking gating). Thinking
|
|
@@ -17,8 +17,10 @@ import type Anthropic from "@anthropic-ai/sdk";
|
|
|
17
17
|
*/
|
|
18
18
|
/** Internal tool name — filtered out of every returned tool call / execution. */
|
|
19
19
|
export declare const FINAL_RESULT_TOOL_NAME = "final_result";
|
|
20
|
+
/** The system-prompt instruction to answer through the named tool. */
|
|
21
|
+
export declare function toolAnswerInstruction(toolName: string): string;
|
|
20
22
|
/** Appended to the system prompt whenever the final_result tool is in play. */
|
|
21
|
-
export declare const FINAL_RESULT_INSTRUCTION
|
|
23
|
+
export declare const FINAL_RESULT_INSTRUCTION: string;
|
|
22
24
|
/**
|
|
23
25
|
* Build the `final_result` tool definition from a JSON Schema.
|
|
24
26
|
*
|
|
@@ -47,6 +49,7 @@ export declare function appendFinalResultTool(tools: Anthropic.Messages.Tool[] |
|
|
|
47
49
|
* existing one: rewriting a block that carries a `cache_control` marker would
|
|
48
50
|
* change the cached prefix and invalidate the prompt cache on every turn.
|
|
49
51
|
*/
|
|
52
|
+
export declare function appendToolAnswerInstruction(system: string | Anthropic.Messages.TextBlockParam[] | undefined, toolName: string): string | Anthropic.Messages.TextBlockParam[];
|
|
50
53
|
export declare function appendFinalResultInstruction(system: string | Anthropic.Messages.TextBlockParam[] | undefined): string | Anthropic.Messages.TextBlockParam[];
|
|
51
54
|
/**
|
|
52
55
|
* Canonical JSON text for a `final_result` payload.
|
|
@@ -19,8 +19,12 @@ import { inlineJsonSchema } from "../../utils/schemaConversion.js";
|
|
|
19
19
|
/** Internal tool name — filtered out of every returned tool call / execution. */
|
|
20
20
|
export const FINAL_RESULT_TOOL_NAME = "final_result";
|
|
21
21
|
const FINAL_RESULT_TOOL_DESCRIPTION = "Return the final structured result. You MUST call this tool when you have gathered all information and are ready to provide the final answer. The arguments should contain the structured data matching the expected schema.";
|
|
22
|
+
/** The system-prompt instruction to answer through the named tool. */
|
|
23
|
+
export function toolAnswerInstruction(toolName) {
|
|
24
|
+
return `\n\nIMPORTANT: You MUST call the '${toolName}' tool to return your response in the required structured format. Do not respond with plain text - always use the ${toolName} tool.`;
|
|
25
|
+
}
|
|
22
26
|
/** Appended to the system prompt whenever the final_result tool is in play. */
|
|
23
|
-
export const FINAL_RESULT_INSTRUCTION =
|
|
27
|
+
export const FINAL_RESULT_INSTRUCTION = toolAnswerInstruction(FINAL_RESULT_TOOL_NAME);
|
|
24
28
|
/**
|
|
25
29
|
* Build the `final_result` tool definition from a JSON Schema.
|
|
26
30
|
*
|
|
@@ -71,14 +75,18 @@ export function appendFinalResultTool(tools, jsonSchema) {
|
|
|
71
75
|
* existing one: rewriting a block that carries a `cache_control` marker would
|
|
72
76
|
* change the cached prefix and invalidate the prompt cache on every turn.
|
|
73
77
|
*/
|
|
74
|
-
export function
|
|
78
|
+
export function appendToolAnswerInstruction(system, toolName) {
|
|
79
|
+
const instruction = toolAnswerInstruction(toolName);
|
|
75
80
|
if (system === undefined) {
|
|
76
|
-
return
|
|
81
|
+
return instruction.trim();
|
|
77
82
|
}
|
|
78
83
|
if (typeof system === "string") {
|
|
79
|
-
return system +
|
|
84
|
+
return system + instruction;
|
|
80
85
|
}
|
|
81
|
-
return [...system, { type: "text", text:
|
|
86
|
+
return [...system, { type: "text", text: instruction.trim() }];
|
|
87
|
+
}
|
|
88
|
+
export function appendFinalResultInstruction(system) {
|
|
89
|
+
return appendToolAnswerInstruction(system, FINAL_RESULT_TOOL_NAME);
|
|
82
90
|
}
|
|
83
91
|
/**
|
|
84
92
|
* Canonical JSON text for a `final_result` payload.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"models": {
|
|
11
11
|
"default": "gpt-oss-120b",
|
|
12
|
-
"fallbacks": ["gpt-oss-120b"
|
|
12
|
+
"fallbacks": ["gpt-oss-120b"],
|
|
13
13
|
"defaultContextWindow": 65536,
|
|
14
14
|
"defaultMaxOutputTokens": 8192,
|
|
15
15
|
"catalog": {
|
|
@@ -29,13 +29,12 @@
|
|
|
29
29
|
"input": 0.99,
|
|
30
30
|
"output": 1.49
|
|
31
31
|
},
|
|
32
|
-
"vision":
|
|
33
|
-
"status": "
|
|
34
|
-
"description": "
|
|
32
|
+
"vision": false,
|
|
33
|
+
"status": "retired",
|
|
34
|
+
"description": "Retired 2026-09 — gone from the authenticated /v1/models roster; the nightly vision test has failed on it since 2026-09-10"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
-
"
|
|
38
|
-
"topModels": ["gpt-oss-120b", "gemma-4-31b"]
|
|
37
|
+
"topModels": ["gpt-oss-120b"]
|
|
39
38
|
},
|
|
40
39
|
"capabilities": {
|
|
41
40
|
"text": true,
|
|
@@ -68,8 +67,8 @@
|
|
|
68
67
|
},
|
|
69
68
|
"evidence": {
|
|
70
69
|
"rosterVerified": {
|
|
71
|
-
"date": "2026-
|
|
72
|
-
"method": "authenticated GET /v1/models"
|
|
70
|
+
"date": "2026-09-28",
|
|
71
|
+
"method": "authenticated GET /v1/models — roster is now gpt-oss-120b and qwen-3.8-27b; gemma-4-31b is gone, so it is retired and Cerebras has no verified vision model. qwen-3.8-27b is not added: the account answers 402 payment_required, so no chat call could verify it"
|
|
73
72
|
},
|
|
74
73
|
"authProbe": {
|
|
75
74
|
"date": "2026-08-26",
|
|
@@ -8,12 +8,11 @@
|
|
|
8
8
|
"baseURL": "https://api.fireworks.ai/inference/v1"
|
|
9
9
|
},
|
|
10
10
|
"models": {
|
|
11
|
-
"default": "accounts/fireworks/models/kimi-
|
|
11
|
+
"default": "accounts/fireworks/models/kimi-k3",
|
|
12
12
|
"fallbackModelName": "accounts/fireworks/models/gpt-oss-120b",
|
|
13
13
|
"fallbacks": [
|
|
14
|
-
"accounts/fireworks/models/kimi-k2p6",
|
|
15
|
-
"accounts/fireworks/models/gpt-oss-120b",
|
|
16
14
|
"accounts/fireworks/models/kimi-k3",
|
|
15
|
+
"accounts/fireworks/models/gpt-oss-120b",
|
|
17
16
|
"accounts/fireworks/models/qwen3p8-max",
|
|
18
17
|
"accounts/fireworks/models/glm-5p3"
|
|
19
18
|
],
|
|
@@ -22,9 +21,9 @@
|
|
|
22
21
|
"catalog": {
|
|
23
22
|
"accounts/fireworks/models/kimi-k2p6": {
|
|
24
23
|
"enumMember": "KIMI_K2P6",
|
|
25
|
-
"vision":
|
|
26
|
-
"status": "
|
|
27
|
-
"description": "
|
|
24
|
+
"vision": false,
|
|
25
|
+
"status": "retired",
|
|
26
|
+
"description": "Retired 2026-09 — still on the Fireworks roster, but a real chat call returns 404 'not deployed' (since the 2026-09-26 nightly)"
|
|
28
27
|
},
|
|
29
28
|
"accounts/fireworks/models/gpt-oss-120b": {
|
|
30
29
|
"enumMember": "GPT_OSS_120B",
|
|
@@ -36,7 +35,7 @@
|
|
|
36
35
|
"enumMember": "KIMI_K3",
|
|
37
36
|
"vision": true,
|
|
38
37
|
"status": "production",
|
|
39
|
-
"description": "Kimi K3
|
|
38
|
+
"description": "Recommended - Kimi K3, newest Moonshot generation; vision-capable"
|
|
40
39
|
},
|
|
41
40
|
"accounts/fireworks/models/qwen3p8-max": {
|
|
42
41
|
"enumMember": "QWEN3P8_MAX",
|
|
@@ -106,9 +105,8 @@
|
|
|
106
105
|
}
|
|
107
106
|
},
|
|
108
107
|
"topModels": [
|
|
109
|
-
"accounts/fireworks/models/kimi-k2p6",
|
|
110
|
-
"accounts/fireworks/models/gpt-oss-120b",
|
|
111
108
|
"accounts/fireworks/models/kimi-k3",
|
|
109
|
+
"accounts/fireworks/models/gpt-oss-120b",
|
|
112
110
|
"accounts/fireworks/models/qwen3p8-max"
|
|
113
111
|
]
|
|
114
112
|
},
|
|
@@ -143,12 +141,12 @@
|
|
|
143
141
|
},
|
|
144
142
|
"evidence": {
|
|
145
143
|
"rosterVerified": {
|
|
146
|
-
"date": "2026-
|
|
144
|
+
"date": "2026-09-28",
|
|
147
145
|
"method": "GET /models against the live account, cross-checked with a real chat call per id"
|
|
148
146
|
},
|
|
149
147
|
"liveMatrix": {
|
|
150
|
-
"date": "2026-
|
|
151
|
-
"result": "
|
|
148
|
+
"date": "2026-09-28",
|
|
149
|
+
"result": "kimi-k2p6 is still listed on /models but a real chat call now returns 404 'not deployed', which failed the nightly model-liveness gate from 2026-09-26 and sent the matrix vision test down the text-only fallback. kimi-k3 answers a text call and a call carrying a PNG with 200, so it becomes the default; gpt-oss-120b, qwen3p8-max and glm-5p3 also answer text calls. Earlier (2026-08-30): deepseek-v4-pro was listed but hung past 120s, six ids returned 404 'not deployed', and glm-5p3 answers 'does not support image inputs'."
|
|
152
150
|
},
|
|
153
151
|
"addedInPR": "https://github.com/juspay/neurolink/pull/1587"
|
|
154
152
|
}
|
|
@@ -222,9 +222,9 @@ export declare class GoogleVertexProvider extends BaseProvider {
|
|
|
222
222
|
* clamped below the ceiling rather than passed through — an over-large
|
|
223
223
|
* budget is a 400 from the API.
|
|
224
224
|
*
|
|
225
|
-
* `
|
|
226
|
-
* a forced `tool_choice:{type:"any"}` in the request built right
|
|
227
|
-
* call — Anthropic hard-rejects `thinking` combined with a `tool_choice`
|
|
225
|
+
* `forcesToolChoice` (schema mode on a model that accepts a forced choice)
|
|
226
|
+
* pairs with a forced `tool_choice:{type:"any"}` in the request built right
|
|
227
|
+
* after this call — Anthropic hard-rejects `thinking` combined with a `tool_choice`
|
|
228
228
|
* that forces tool use: a live 400, "Thinking may not be enabled when
|
|
229
229
|
* tool_choice forces tool use." So `thinking` is omitted entirely in that
|
|
230
230
|
* mode, preserving the prior (pre-thinking-fix) behaviour of silently
|
|
@@ -46,7 +46,7 @@ import { SpanKind, SpanStatusCode, context as otelContext, trace as otelTrace, }
|
|
|
46
46
|
import { calculateCost } from "../../utils/pricing.js";
|
|
47
47
|
import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
48
48
|
import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
|
|
49
|
-
import { resolveSamplingParams } from "../../models/modelRegistry.js";
|
|
49
|
+
import { modelSupportsForcedToolChoice, resolveSamplingParams, } from "../../models/modelRegistry.js";
|
|
50
50
|
import { sanitizeAnthropicMessagesForTrace } from "../../utils/anthropicTraceSanitizer.js";
|
|
51
51
|
import { extractToolFailureText } from "../../utils/mcpErrorText.js";
|
|
52
52
|
// Import proper types for multimodal message handling
|
|
@@ -707,6 +707,20 @@ async function collectVertexStreamChunks(stream, channel, hooks = {}) {
|
|
|
707
707
|
...(stepReasoningTokens ? { reasoningTokens: stepReasoningTokens } : {}),
|
|
708
708
|
};
|
|
709
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* The answer text of an unforced finalization reply. Text that leads into a
|
|
712
|
+
* further tool call is preamble, not an answer, so a reply still asking for a
|
|
713
|
+
* tool yields none.
|
|
714
|
+
*/
|
|
715
|
+
function finalizationAnswerText(content) {
|
|
716
|
+
if (content.some((block) => block.type === "tool_use")) {
|
|
717
|
+
return "";
|
|
718
|
+
}
|
|
719
|
+
return content
|
|
720
|
+
.filter((block) => block.type === "text")
|
|
721
|
+
.map((b) => b.text)
|
|
722
|
+
.join("");
|
|
723
|
+
}
|
|
710
724
|
export class GoogleVertexProvider extends BaseProvider {
|
|
711
725
|
projectId;
|
|
712
726
|
location;
|
|
@@ -2949,9 +2963,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2949
2963
|
* clamped below the ceiling rather than passed through — an over-large
|
|
2950
2964
|
* budget is a 400 from the API.
|
|
2951
2965
|
*
|
|
2952
|
-
* `
|
|
2953
|
-
* a forced `tool_choice:{type:"any"}` in the request built right
|
|
2954
|
-
* call — Anthropic hard-rejects `thinking` combined with a `tool_choice`
|
|
2966
|
+
* `forcesToolChoice` (schema mode on a model that accepts a forced choice)
|
|
2967
|
+
* pairs with a forced `tool_choice:{type:"any"}` in the request built right
|
|
2968
|
+
* after this call — Anthropic hard-rejects `thinking` combined with a `tool_choice`
|
|
2955
2969
|
* that forces tool use: a live 400, "Thinking may not be enabled when
|
|
2956
2970
|
* tool_choice forces tool use." So `thinking` is omitted entirely in that
|
|
2957
2971
|
* mode, preserving the prior (pre-thinking-fix) behaviour of silently
|
|
@@ -2968,8 +2982,8 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2968
2982
|
buildClaudeThinkingParam(
|
|
2969
2983
|
// Structural, not TextGenerationOptions: the streaming caller passes
|
|
2970
2984
|
// StreamOptions, and only these two fields are read.
|
|
2971
|
-
options, maxTokens,
|
|
2972
|
-
if (
|
|
2985
|
+
options, maxTokens, forcesToolChoice) {
|
|
2986
|
+
if (forcesToolChoice) {
|
|
2973
2987
|
if (options.thinkingConfig?.enabled) {
|
|
2974
2988
|
logger.debug("[GoogleVertex] Omitting thinking: schema mode forces tool_choice:any, which Anthropic rejects alongside thinking");
|
|
2975
2989
|
}
|
|
@@ -3215,7 +3229,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3215
3229
|
...(options.topP !== undefined && { topP: options.topP }),
|
|
3216
3230
|
}, "vertex.anthropic.stream");
|
|
3217
3231
|
const streamMaxTokens = resolveClaudeMaxTokens(modelName, options.maxTokens);
|
|
3218
|
-
|
|
3232
|
+
// Claude 5.5 / 5.1 400 on a forced tool_choice; schema mode leaves them on
|
|
3233
|
+
// auto, steered by the final_result instruction already in the prompt.
|
|
3234
|
+
const forceFinalResult = useFinalResultTool && modelSupportsForcedToolChoice(modelName);
|
|
3235
|
+
const streamThinking = this.buildClaudeThinkingParam(options, streamMaxTokens, forceFinalResult);
|
|
3219
3236
|
const requestParams = {
|
|
3220
3237
|
model: modelName,
|
|
3221
3238
|
// Default to the model's real output ceiling (e.g. 64K for Sonnet 4.x)
|
|
@@ -3226,7 +3243,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3226
3243
|
...(streamThinking && { thinking: streamThinking }),
|
|
3227
3244
|
messages: messages,
|
|
3228
3245
|
...(tools && tools.length > 0 && { tools }),
|
|
3229
|
-
...(
|
|
3246
|
+
...(forceFinalResult && { tool_choice: { type: "any" } }),
|
|
3230
3247
|
...(systemPromptWithSchema && { system: systemPromptWithSchema }),
|
|
3231
3248
|
...(streamSampling.temperature !== undefined && {
|
|
3232
3249
|
temperature: streamSampling.temperature,
|
|
@@ -3858,10 +3875,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3858
3875
|
const finalizationStream = await client.messages.stream({
|
|
3859
3876
|
...requestParams,
|
|
3860
3877
|
max_tokens: terminalMaxTokens,
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3878
|
+
// Resized, not inherited: terminalMaxTokens can sit below the
|
|
3879
|
+
// budget requestParams' thinking was sized for.
|
|
3880
|
+
thinking: this.buildClaudeThinkingParam(options, terminalMaxTokens, forceFinalResult),
|
|
3881
|
+
...(forceFinalResult && {
|
|
3882
|
+
tool_choice: {
|
|
3883
|
+
type: "tool",
|
|
3884
|
+
name: "final_result",
|
|
3885
|
+
},
|
|
3886
|
+
}),
|
|
3865
3887
|
...(cachedFinal.system !== undefined && {
|
|
3866
3888
|
system: cachedFinal.system,
|
|
3867
3889
|
}),
|
|
@@ -3905,11 +3927,23 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3905
3927
|
logger.debug("[GoogleVertex] Forced finalization returned structured output (stream)", { keys: Object.keys(forcedFinalResult.input) });
|
|
3906
3928
|
}
|
|
3907
3929
|
else {
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3930
|
+
// Unforced (the model refuses a forced choice), the call may
|
|
3931
|
+
// answer in text instead; keep that answer over a cap notice.
|
|
3932
|
+
const finalizationText = forceFinalResult
|
|
3933
|
+
? ""
|
|
3934
|
+
: finalizationAnswerText(response.content);
|
|
3935
|
+
if (finalizationText) {
|
|
3936
|
+
synthesizedFinalAnswer = true;
|
|
3937
|
+
channel.push({ content: finalizationText });
|
|
3938
|
+
aggregatedTurnText += finalizationText;
|
|
3939
|
+
}
|
|
3940
|
+
else {
|
|
3941
|
+
const capMessage = hitContextLimit
|
|
3942
|
+
? buildContextCapMessage(externalToolCallCount)
|
|
3943
|
+
: buildToolLoopCapMessage(maxSteps, externalToolCallCount);
|
|
3944
|
+
channel.push({ content: capMessage });
|
|
3945
|
+
aggregatedTurnText += capMessage;
|
|
3946
|
+
}
|
|
3913
3947
|
}
|
|
3914
3948
|
}
|
|
3915
3949
|
catch (error) {
|
|
@@ -4484,7 +4518,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4484
4518
|
...(options.topP !== undefined && { topP: options.topP }),
|
|
4485
4519
|
}, "vertex.anthropic.generate");
|
|
4486
4520
|
const generateMaxTokens = resolveClaudeMaxTokens(modelName, options.maxTokens);
|
|
4487
|
-
|
|
4521
|
+
// Claude 5.5 / 5.1 400 on a forced tool_choice; schema mode leaves them on
|
|
4522
|
+
// auto, steered by the final_result instruction already in the prompt.
|
|
4523
|
+
const forceFinalResult = useFinalResultTool && modelSupportsForcedToolChoice(modelName);
|
|
4524
|
+
const generateThinking = this.buildClaudeThinkingParam(options, generateMaxTokens, forceFinalResult);
|
|
4488
4525
|
const requestParams = {
|
|
4489
4526
|
model: modelName,
|
|
4490
4527
|
// Default to the model's real output ceiling (see stream path note).
|
|
@@ -4492,7 +4529,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4492
4529
|
...(generateThinking && { thinking: generateThinking }),
|
|
4493
4530
|
messages,
|
|
4494
4531
|
...(tools && tools.length > 0 && { tools }),
|
|
4495
|
-
...(
|
|
4532
|
+
...(forceFinalResult && { tool_choice: { type: "any" } }),
|
|
4496
4533
|
...(systemPromptWithSchema && { system: systemPromptWithSchema }),
|
|
4497
4534
|
...(generateSampling.temperature !== undefined && {
|
|
4498
4535
|
temperature: generateSampling.temperature,
|
|
@@ -4901,10 +4938,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4901
4938
|
const response = await withTimeout(client.messages.create({
|
|
4902
4939
|
...requestParams,
|
|
4903
4940
|
max_tokens: terminalMaxTokens,
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4941
|
+
// Resized, not inherited: terminalMaxTokens can sit below the
|
|
4942
|
+
// budget requestParams' thinking was sized for.
|
|
4943
|
+
thinking: this.buildClaudeThinkingParam(options, terminalMaxTokens, forceFinalResult),
|
|
4944
|
+
...(forceFinalResult && {
|
|
4945
|
+
tool_choice: {
|
|
4946
|
+
type: "tool",
|
|
4947
|
+
name: "final_result",
|
|
4948
|
+
},
|
|
4949
|
+
}),
|
|
4908
4950
|
...(cachedFinal.system !== undefined && {
|
|
4909
4951
|
system: cachedFinal.system,
|
|
4910
4952
|
}),
|
|
@@ -4928,9 +4970,20 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4928
4970
|
logger.debug("[GoogleVertex] Forced finalization returned structured output (generate)", { keys: Object.keys(structuredOutput) });
|
|
4929
4971
|
}
|
|
4930
4972
|
else {
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4973
|
+
// Unforced (the model refuses a forced choice), the call may
|
|
4974
|
+
// answer in text instead; keep that answer over a cap notice.
|
|
4975
|
+
const finalizationText = forceFinalResult
|
|
4976
|
+
? ""
|
|
4977
|
+
: finalizationAnswerText(response.content);
|
|
4978
|
+
if (finalizationText) {
|
|
4979
|
+
finalText = finalizationText;
|
|
4980
|
+
synthesizedFinalAnswer = true;
|
|
4981
|
+
}
|
|
4982
|
+
else {
|
|
4983
|
+
finalText = hitContextLimit
|
|
4984
|
+
? buildContextCapMessage(externalToolCallCount)
|
|
4985
|
+
: buildToolLoopCapMessage(maxSteps, externalToolCallCount);
|
|
4986
|
+
}
|
|
4934
4987
|
}
|
|
4935
4988
|
}
|
|
4936
4989
|
catch (error) {
|
package/dist/providers/laya.js
CHANGED
|
@@ -2,25 +2,7 @@ import { LayaModels } from "../constants/enums.js";
|
|
|
2
2
|
import { logger } from "../utils/logger.js";
|
|
3
3
|
import { redactUrlForError } from "../utils/logSanitize.js";
|
|
4
4
|
import { getProviderModel } from "../utils/providerConfig.js";
|
|
5
|
-
import { describeValidationErrors, isRecord, SystemOneDecisionProvider, } from "./systemOneDecision.js";
|
|
6
|
-
/**
|
|
7
|
-
* LiteLLM echoes credentials back in its error texts, in more than one
|
|
8
|
-
* wording: a masked key plus its hash on a rejected key, the whole key when it
|
|
9
|
-
* does not look like a LiteLLM key, a key hash on a rate limit. None of it
|
|
10
|
-
* belongs in an error message or a log, whatever the wording, so the
|
|
11
|
-
* configured key, anything shaped like a LiteLLM key and any long hex run are
|
|
12
|
-
* all removed.
|
|
13
|
-
*/
|
|
14
|
-
function redactCredentials(message, apiKey) {
|
|
15
|
-
const withoutKey = apiKey
|
|
16
|
-
? message.split(apiKey).join("[redacted]")
|
|
17
|
-
: message;
|
|
18
|
-
return withoutKey
|
|
19
|
-
.replace(/\.?\s*Received API Key\s*=[\s\S]*$/, "")
|
|
20
|
-
.replace(/\bsk-[A-Za-z0-9._-]+/g, "[redacted]")
|
|
21
|
-
.replace(/\b[0-9a-f]{32,}\b/gi, "[redacted]")
|
|
22
|
-
.trim();
|
|
23
|
-
}
|
|
5
|
+
import { describeValidationErrors, isRecord, redactCredentials, SystemOneDecisionProvider, } from "./systemOneDecision.js";
|
|
24
6
|
/**
|
|
25
7
|
* Normalise the two envelopes a Laya failure can arrive in.
|
|
26
8
|
*
|
|
@@ -873,7 +873,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
873
873
|
options.disableInternalFallback === true;
|
|
874
874
|
// The native loop bypasses BaseProvider.executeGeneration, so the turn
|
|
875
875
|
// budget has to be composed here or it stops existing for this provider.
|
|
876
|
-
return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback);
|
|
876
|
+
return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback, options);
|
|
877
877
|
}
|
|
878
878
|
async executeNativeGenerate(options) {
|
|
879
879
|
const startTime = Date.now();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AIProviderName } from "../constants/enums.js";
|
|
2
2
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
3
3
|
import { ProviderError } from "../types/index.js";
|
|
4
|
-
import type { DecisionError, DecisionQuestion, DecisionRequest, DecisionResult, DecisionState, LanguageModel, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
|
|
4
|
+
import type { DecisionError, DecisionQuestion, DecisionRequest, DecisionResult, DecisionPreparedMedia, DecisionState, LanguageModel, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* Generous enough for a cold start (measured at 2.0–2.7s after idle on Jev)
|
|
7
7
|
* while still bounded. Every internal caller is fail-open, so this only bites
|
|
@@ -16,6 +16,15 @@ export declare const asNumber: (v: unknown) => number | undefined;
|
|
|
16
16
|
* request back — possibly the whole state — so it is never surfaced.
|
|
17
17
|
*/
|
|
18
18
|
export declare function describeValidationErrors(detail: readonly unknown[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* A LiteLLM proxy echoes credentials back in its error texts, in more than one
|
|
21
|
+
* wording: a masked key plus its hash on a rejected key, the whole key when it
|
|
22
|
+
* does not look like a LiteLLM key, a key hash on a rate limit. None of it
|
|
23
|
+
* belongs in an error message or a log, whatever the wording, so the
|
|
24
|
+
* configured key, anything shaped like a LiteLLM key, any long hex run and any
|
|
25
|
+
* embedded `data:` URL are all removed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function redactCredentials(message: string, apiKey: string): string;
|
|
19
28
|
/**
|
|
20
29
|
* The shared half of every "System One" decision provider — a model that takes
|
|
21
30
|
* one `state` plus typed questions and returns one typed answer each, with no
|
|
@@ -52,7 +61,7 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
52
61
|
protected missingConfigMessage(): string | undefined;
|
|
53
62
|
protected abstract decisionEndpoint(): string;
|
|
54
63
|
protected abstract decisionHeaders(): Record<string, string>;
|
|
55
|
-
protected abstract buildDecisionBody(state: DecisionState, questions: Record<string, Record<string, unknown>>, model: string): Record<string, unknown>;
|
|
64
|
+
protected abstract buildDecisionBody(state: DecisionState, questions: Record<string, Record<string, unknown>>, model: string, media?: DecisionPreparedMedia): Record<string, unknown>;
|
|
56
65
|
protected abstract parseDecisionError(status: number, payload: unknown, requestId: string | undefined): DecisionError;
|
|
57
66
|
protected abstract readRequestId(headers: Headers): string | undefined;
|
|
58
67
|
/**
|
|
@@ -87,6 +96,16 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
87
96
|
* which returns null instead.
|
|
88
97
|
*/
|
|
89
98
|
decide(request: DecisionRequest): Promise<DecisionResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Turn the request's images and video into data URLs, or refuse it. Refused
|
|
101
|
+
* as a non-retryable `invalid_request`: resending the same media cannot help.
|
|
102
|
+
*/
|
|
103
|
+
private prepareMedia;
|
|
104
|
+
/**
|
|
105
|
+
* The encoded body, not just the media, is what the server's size limit
|
|
106
|
+
* applies to.
|
|
107
|
+
*/
|
|
108
|
+
private assertWithinRequestBytes;
|
|
90
109
|
/**
|
|
91
110
|
* Refuse a request the model cannot read in full. An encoder cuts the state
|
|
92
111
|
* off past its window without saying so, so without this a decision would
|