@juspay/neurolink 12.14.15 → 12.14.16
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 +5 -2
- package/dist/browser/neurolink.min.js +382 -382
- package/dist/core/baseProvider.js +34 -6
- package/dist/models/manifests/vertex.js +6 -2
- package/dist/neurolink.js +39 -9
- package/dist/providers/anthropic/loopAdapter.js +13 -0
- package/dist/providers/googleNativeGemini3/utils.d.ts +5 -1
- package/dist/providers/googleNativeGemini3/utils.js +9 -2
- package/dist/providers/googleVertex/client.d.ts +48 -0
- package/dist/providers/googleVertex/client.js +221 -17
- package/dist/types/config.d.ts +12 -2
- package/dist/types/generate.d.ts +18 -9
- package/dist/utils/modelDetection.d.ts +10 -0
- package/dist/utils/modelDetection.js +38 -0
- package/dist/utils/streamResultAccessors.d.ts +38 -0
- package/dist/utils/streamResultAccessors.js +55 -0
- package/dist/utils/thinkingConfig.d.ts +21 -2
- package/dist/utils/thinkingConfig.js +62 -3
- package/package.json +5 -1
|
@@ -1406,8 +1406,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1406
1406
|
if (effectiveSystemPrompt) {
|
|
1407
1407
|
config.systemInstruction = effectiveSystemPrompt;
|
|
1408
1408
|
}
|
|
1409
|
-
// Add thinking config
|
|
1410
|
-
|
|
1409
|
+
// Add thinking config. Gemini 3 takes `thinkingLevel` directly; Gemini
|
|
1410
|
+
// 2.5 rejects that field and needs a translated `thinkingBudget` instead
|
|
1411
|
+
// — createNativeThinkingConfig picks the wire shape from modelName.
|
|
1412
|
+
const nativeThinkingConfig = createNativeThinkingConfig(options.thinkingConfig, modelName);
|
|
1411
1413
|
if (nativeThinkingConfig) {
|
|
1412
1414
|
config.thinkingConfig = nativeThinkingConfig;
|
|
1413
1415
|
}
|
|
@@ -2216,8 +2218,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2216
2218
|
if (effectiveSystemPrompt) {
|
|
2217
2219
|
config.systemInstruction = effectiveSystemPrompt;
|
|
2218
2220
|
}
|
|
2219
|
-
// Add thinking config
|
|
2220
|
-
|
|
2221
|
+
// Add thinking config. Gemini 3 takes `thinkingLevel` directly; Gemini
|
|
2222
|
+
// 2.5 rejects that field and needs a translated `thinkingBudget` instead
|
|
2223
|
+
// — createNativeThinkingConfig picks the wire shape from modelName.
|
|
2224
|
+
const nativeThinkingConfig2 = createNativeThinkingConfig(options.thinkingConfig, modelName);
|
|
2221
2225
|
if (nativeThinkingConfig2) {
|
|
2222
2226
|
config.thinkingConfig = nativeThinkingConfig2;
|
|
2223
2227
|
}
|
|
@@ -2883,6 +2887,72 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2883
2887
|
* Execute stream using native @anthropic-ai/vertex-sdk for Claude models on Vertex AI
|
|
2884
2888
|
* This bypasses @ai-sdk/google-vertex completely and uses Anthropic's native SDK
|
|
2885
2889
|
*/
|
|
2890
|
+
/**
|
|
2891
|
+
* Extended thinking for Claude-on-Vertex.
|
|
2892
|
+
*
|
|
2893
|
+
* The direct Anthropic provider has always translated `thinkingConfig` into
|
|
2894
|
+
* the Messages API's `thinking` field (anthropic/client.ts). This path never
|
|
2895
|
+
* did — it built the request without one — so `thinkingConfig` was accepted
|
|
2896
|
+
* and silently ignored here: no error, `usage.reasoning` 0, no reasoning
|
|
2897
|
+
* content. Measured against the raw @anthropic-ai/vertex-sdk with the same
|
|
2898
|
+
* project, region, credentials and model, `thinking` is supported and
|
|
2899
|
+
* returns a real thinking block, so the capability was ours to send.
|
|
2900
|
+
*
|
|
2901
|
+
* `thinkingLevel` is deliberately not mapped to a budget: the option's
|
|
2902
|
+
* contract reserves `budgetTokens` for Anthropic models and `thinkingLevel`
|
|
2903
|
+
* for Gemini 3, and inventing a conversion here would be guessing at the
|
|
2904
|
+
* caller's intent.
|
|
2905
|
+
*
|
|
2906
|
+
* The budget must leave room inside `max_tokens` for an answer, so it is
|
|
2907
|
+
* clamped below the ceiling rather than passed through — an over-large
|
|
2908
|
+
* budget is a 400 from the API.
|
|
2909
|
+
*
|
|
2910
|
+
* `useFinalResultTool` (schema / structured-output mode) always pairs with
|
|
2911
|
+
* a forced `tool_choice:{type:"any"}` in the request built right after this
|
|
2912
|
+
* call — Anthropic hard-rejects `thinking` combined with a `tool_choice`
|
|
2913
|
+
* that forces tool use: a live 400, "Thinking may not be enabled when
|
|
2914
|
+
* tool_choice forces tool use." So `thinking` is omitted entirely in that
|
|
2915
|
+
* mode, preserving the prior (pre-thinking-fix) behaviour of silently
|
|
2916
|
+
* ignoring `thinkingConfig` for schema calls instead of hard-failing them —
|
|
2917
|
+
* mirrors the same guard already applied to the OAuth proxy path in
|
|
2918
|
+
* `src/lib/proxy/oauthFetch.ts` (`delete parsed.thinking` when
|
|
2919
|
+
* `tool_choice.type` is `"any"` or `"tool"`). The step/context-cap
|
|
2920
|
+
* finalization backstop reuses this same omission for free since it spreads
|
|
2921
|
+
* `...requestParams`. `tool_choice:{type:"none"}` (the separate
|
|
2922
|
+
* tools-disabled backstop, used only outside schema mode) does not force
|
|
2923
|
+
* tool use — it forces the opposite — so it is not gated here, consistent
|
|
2924
|
+
* with oauthFetch.ts leaving `"none"` alone too.
|
|
2925
|
+
*/
|
|
2926
|
+
buildClaudeThinkingParam(
|
|
2927
|
+
// Structural, not TextGenerationOptions: the streaming caller passes
|
|
2928
|
+
// StreamOptions, and only these two fields are read.
|
|
2929
|
+
options, maxTokens, useFinalResultTool) {
|
|
2930
|
+
if (useFinalResultTool) {
|
|
2931
|
+
if (options.thinkingConfig?.enabled) {
|
|
2932
|
+
logger.debug("[GoogleVertex] Omitting thinking: schema mode forces tool_choice:any, which Anthropic rejects alongside thinking");
|
|
2933
|
+
}
|
|
2934
|
+
return undefined;
|
|
2935
|
+
}
|
|
2936
|
+
const budget = options.thinkingConfig?.budgetTokens;
|
|
2937
|
+
if (!options.thinkingConfig?.enabled || !budget) {
|
|
2938
|
+
return undefined;
|
|
2939
|
+
}
|
|
2940
|
+
// Two independent floors, checked explicitly rather than folded into one
|
|
2941
|
+
// Math.max/min expression: Anthropic's own 1024 minimum on budget_tokens,
|
|
2942
|
+
// and this function's 1024-token reservation for the answer that follows
|
|
2943
|
+
// thinking. When maxTokens leaves no room for both — 1025..2047 is the
|
|
2944
|
+
// narrow band where that happens — there is no valid budget, so thinking
|
|
2945
|
+
// is omitted rather than silently coerced to exactly 1024 regardless of
|
|
2946
|
+
// what the caller asked for.
|
|
2947
|
+
if (maxTokens <= 1024) {
|
|
2948
|
+
return undefined;
|
|
2949
|
+
}
|
|
2950
|
+
const clamped = Math.min(budget, maxTokens - 1024);
|
|
2951
|
+
if (clamped < 1024) {
|
|
2952
|
+
return undefined;
|
|
2953
|
+
}
|
|
2954
|
+
return { type: "enabled", budget_tokens: clamped };
|
|
2955
|
+
}
|
|
2886
2956
|
async executeNativeAnthropicStream(options) {
|
|
2887
2957
|
const modelName = toVertexAnthropicModelId(options.model || this.modelName || "claude-sonnet-4-5@20250929");
|
|
2888
2958
|
const startTime = Date.now();
|
|
@@ -3102,13 +3172,16 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3102
3172
|
}),
|
|
3103
3173
|
...(options.topP !== undefined && { topP: options.topP }),
|
|
3104
3174
|
}, "vertex.anthropic.stream");
|
|
3175
|
+
const streamMaxTokens = resolveClaudeMaxTokens(modelName, options.maxTokens);
|
|
3176
|
+
const streamThinking = this.buildClaudeThinkingParam(options, streamMaxTokens, useFinalResultTool);
|
|
3105
3177
|
const requestParams = {
|
|
3106
3178
|
model: modelName,
|
|
3107
3179
|
// Default to the model's real output ceiling (e.g. 64K for Sonnet 4.x)
|
|
3108
3180
|
// instead of the legacy 4096, which silently truncated large structured
|
|
3109
3181
|
// responses mid-JSON. resolveClaudeMaxTokens also clamps over-large
|
|
3110
3182
|
// caller values so the native Vertex path never 400s.
|
|
3111
|
-
max_tokens:
|
|
3183
|
+
max_tokens: streamMaxTokens,
|
|
3184
|
+
...(streamThinking && { thinking: streamThinking }),
|
|
3112
3185
|
messages: messages,
|
|
3113
3186
|
...(tools && tools.length > 0 && { tools }),
|
|
3114
3187
|
...(useFinalResultTool && { tool_choice: { type: "any" } }),
|
|
@@ -3606,6 +3679,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3606
3679
|
});
|
|
3607
3680
|
const pump = (async () => {
|
|
3608
3681
|
for await (const chunk of engineStream) {
|
|
3682
|
+
if (chunk.reasoning) {
|
|
3683
|
+
channel.push({ content: "", reasoning: chunk.reasoning });
|
|
3684
|
+
}
|
|
3609
3685
|
if (chunk.content) {
|
|
3610
3686
|
channel.push({ content: chunk.content });
|
|
3611
3687
|
liveTextPushedLength += chunk.content.length;
|
|
@@ -3636,6 +3712,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3636
3712
|
if (engineResult) {
|
|
3637
3713
|
usage.input += engineResult.usage.inputTokens;
|
|
3638
3714
|
usage.output += engineResult.usage.outputTokens;
|
|
3715
|
+
// A SUBSET already included in outputTokens above — added here
|
|
3716
|
+
// only for observability, never folded into input/output/total.
|
|
3717
|
+
if (engineResult.usage.reasoningTokens) {
|
|
3718
|
+
usage.reasoning =
|
|
3719
|
+
(usage.reasoning ?? 0) + engineResult.usage.reasoningTokens;
|
|
3720
|
+
}
|
|
3639
3721
|
finishReasonRef.value =
|
|
3640
3722
|
engineResult.rawStopReason ?? finishReasonRef.value;
|
|
3641
3723
|
// NOT `toolCalls.length === 0`: that array accumulates across the
|
|
@@ -3841,9 +3923,22 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3841
3923
|
tools,
|
|
3842
3924
|
messages: currentMessages,
|
|
3843
3925
|
});
|
|
3926
|
+
// budget_tokens must stay below max_tokens or Anthropic 400s
|
|
3927
|
+
// the call; terminalMaxTokens can land far below the ceiling
|
|
3928
|
+
// streamThinking was sized against (the context guard can
|
|
3929
|
+
// shrink it well under the original request), so the budget
|
|
3930
|
+
// is rebuilt for the shrunk limit rather than inherited
|
|
3931
|
+
// through the spread below.
|
|
3932
|
+
const backstopThinking = this.buildClaudeThinkingParam(options, terminalMaxTokens, false);
|
|
3844
3933
|
const backstopStream = await client.messages.stream({
|
|
3845
3934
|
...requestParams,
|
|
3846
3935
|
max_tokens: terminalMaxTokens,
|
|
3936
|
+
// Assigned outright, never a conditional spread: spreading
|
|
3937
|
+
// requestParams above may already carry a `thinking` sized
|
|
3938
|
+
// for the original max_tokens, and only adding a key when
|
|
3939
|
+
// backstopThinking is truthy would leave that stale value
|
|
3940
|
+
// in place instead of clearing it.
|
|
3941
|
+
thinking: backstopThinking,
|
|
3847
3942
|
tool_choice: { type: "none" },
|
|
3848
3943
|
system: cachedBackstop.system,
|
|
3849
3944
|
...(cachedBackstop.tools &&
|
|
@@ -3874,7 +3969,28 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3874
3969
|
usage.output +
|
|
3875
3970
|
turnCacheUsage.read +
|
|
3876
3971
|
turnCacheUsage.creation;
|
|
3972
|
+
// A SUBSET already included in usage.output above (Anthropic
|
|
3973
|
+
// bills thinking as part of output_tokens) — additive only,
|
|
3974
|
+
// matching the main loop's own usage.reasoning accumulation.
|
|
3975
|
+
if (response.usage?.output_tokens_details?.thinking_tokens) {
|
|
3976
|
+
usage.reasoning =
|
|
3977
|
+
(usage.reasoning ?? 0) +
|
|
3978
|
+
response.usage.output_tokens_details.thinking_tokens;
|
|
3979
|
+
}
|
|
3877
3980
|
lastStopReason = response.stop_reason;
|
|
3981
|
+
// Extract<> rather than a hand-written object shape: the
|
|
3982
|
+
// real SDK's ThinkingBlock also requires `signature`, which
|
|
3983
|
+
// a literal `{ type: "thinking"; thinking: string }` predicate
|
|
3984
|
+
// omits — TS then rejects the predicate as not assignable to
|
|
3985
|
+
// ContentBlock. Extracting the union member sidesteps that
|
|
3986
|
+
// without hard-coding its full shape here.
|
|
3987
|
+
const backstopReasoning = response.content
|
|
3988
|
+
.filter((block) => block.type === "thinking")
|
|
3989
|
+
.map((block) => block.thinking)
|
|
3990
|
+
.join("");
|
|
3991
|
+
if (backstopReasoning) {
|
|
3992
|
+
channel.push({ content: "", reasoning: backstopReasoning });
|
|
3993
|
+
}
|
|
3878
3994
|
const backstopText = response.content
|
|
3879
3995
|
.filter((block) => block.type === "text")
|
|
3880
3996
|
.map((b) => b.text)
|
|
@@ -4325,10 +4441,13 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4325
4441
|
}),
|
|
4326
4442
|
...(options.topP !== undefined && { topP: options.topP }),
|
|
4327
4443
|
}, "vertex.anthropic.generate");
|
|
4444
|
+
const generateMaxTokens = resolveClaudeMaxTokens(modelName, options.maxTokens);
|
|
4445
|
+
const generateThinking = this.buildClaudeThinkingParam(options, generateMaxTokens, useFinalResultTool);
|
|
4328
4446
|
const requestParams = {
|
|
4329
4447
|
model: modelName,
|
|
4330
4448
|
// Default to the model's real output ceiling (see stream path note).
|
|
4331
|
-
max_tokens:
|
|
4449
|
+
max_tokens: generateMaxTokens,
|
|
4450
|
+
...(generateThinking && { thinking: generateThinking }),
|
|
4332
4451
|
messages,
|
|
4333
4452
|
...(tools && tools.length > 0 && { tools }),
|
|
4334
4453
|
...(useFinalResultTool && { tool_choice: { type: "any" } }),
|
|
@@ -4371,6 +4490,16 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4371
4490
|
// ~1.25x cache-write tiers instead of billing everything at full input rate.
|
|
4372
4491
|
let totalCacheReadTokens = 0;
|
|
4373
4492
|
let totalCacheCreationTokens = 0;
|
|
4493
|
+
// A SUBSET already included in totalOutputTokens above (Anthropic bills
|
|
4494
|
+
// thinking tokens as part of output_tokens) — reported separately for
|
|
4495
|
+
// observability, never folded into the input/output/total rollup.
|
|
4496
|
+
let totalReasoningTokens = 0;
|
|
4497
|
+
// Reasoning text has no tool-triggered accumulation path the way
|
|
4498
|
+
// `accumulatedStepText` does (buildToolResultMessages only fires for
|
|
4499
|
+
// steps with tool calls), so it is captured directly from the engine's
|
|
4500
|
+
// per-chunk stream below — otherwise a plain one-shot thinking answer
|
|
4501
|
+
// never surfaces its reasoning text at all.
|
|
4502
|
+
let accumulatedReasoningText = "";
|
|
4374
4503
|
// Track the final Anthropic stop_reason so we can surface finishReason
|
|
4375
4504
|
// (notably "length" on token truncation) — the legacy native path always
|
|
4376
4505
|
// reported "stop", hiding truncation from callers.
|
|
@@ -4601,15 +4730,30 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4601
4730
|
// engine's own per-tool bound cannot undercut it.
|
|
4602
4731
|
toolTimeoutMs: toolExecTimeoutMs,
|
|
4603
4732
|
});
|
|
4604
|
-
//
|
|
4605
|
-
// streaming, and the per-step text is accumulated in
|
|
4733
|
+
// Text chunks are discarded here: generate() returns one result rather
|
|
4734
|
+
// than streaming, and the per-step text is accumulated in
|
|
4606
4735
|
// buildToolResultMessages instead — appendStepText joins steps with a
|
|
4607
4736
|
// NEWLINE, which a chunk-by-chunk `+=` here would silently drop, running
|
|
4608
|
-
// consecutive steps' text together.
|
|
4609
|
-
//
|
|
4737
|
+
// consecutive steps' text together. Reasoning has no such step-boundary
|
|
4738
|
+
// hook (buildToolResultMessages only runs for steps WITH tool calls, so
|
|
4739
|
+
// a plain one-shot thinking answer never reaches it), so it is captured
|
|
4740
|
+
// directly off the chunk stream instead. The drain still has to happen
|
|
4741
|
+
// regardless: leaving the channel unread stalls the engine once its
|
|
4742
|
+
// buffer fills.
|
|
4743
|
+
//
|
|
4744
|
+
// The plain `+=` below is deliberate, not the same gap as the text path:
|
|
4745
|
+
// each `chunk.reasoning` here is one `thinking_delta` from inside a
|
|
4746
|
+
// single step (loopAdapter pushes one per delta, not one per step), so
|
|
4747
|
+
// joining on a NEWLINE the way appendStepText does would insert a break
|
|
4748
|
+
// before nearly every delta and fragment one coherent thinking block
|
|
4749
|
+
// into one line per token. Concatenation across steps is a known,
|
|
4750
|
+
// accepted blur — there is no per-step boundary on this channel to join
|
|
4751
|
+
// on — while concatenation within a step is exactly what the API sent.
|
|
4610
4752
|
const pump = (async () => {
|
|
4611
4753
|
for await (const chunk of engineStream) {
|
|
4612
|
-
|
|
4754
|
+
if (chunk.reasoning) {
|
|
4755
|
+
accumulatedReasoningText += chunk.reasoning;
|
|
4756
|
+
}
|
|
4613
4757
|
}
|
|
4614
4758
|
})();
|
|
4615
4759
|
let engineResult;
|
|
@@ -4638,6 +4782,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4638
4782
|
totalOutputTokens += engineResult.usage.outputTokens;
|
|
4639
4783
|
totalCacheReadTokens += engineResult.usage.cacheReadTokens ?? 0;
|
|
4640
4784
|
totalCacheCreationTokens += engineResult.usage.cacheWriteTokens ?? 0;
|
|
4785
|
+
totalReasoningTokens += engineResult.usage.reasoningTokens ?? 0;
|
|
4641
4786
|
lastStopReason = engineResult.rawStopReason ?? lastStopReason;
|
|
4642
4787
|
finalText = engineResult.text || finalText;
|
|
4643
4788
|
// Replace in place: the terminal block and the finalization call both
|
|
@@ -4796,9 +4941,22 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4796
4941
|
tools,
|
|
4797
4942
|
messages: currentMessages,
|
|
4798
4943
|
});
|
|
4944
|
+
// budget_tokens must stay below max_tokens or Anthropic 400s the
|
|
4945
|
+
// call; terminalMaxTokens can land far below the ceiling
|
|
4946
|
+
// generateThinking was sized against (the context guard can
|
|
4947
|
+
// shrink it well under the original request), so the budget is
|
|
4948
|
+
// rebuilt for the shrunk limit rather than inherited through the
|
|
4949
|
+
// spread below.
|
|
4950
|
+
const backstopThinking = this.buildClaudeThinkingParam(options, terminalMaxTokens, false);
|
|
4799
4951
|
const response = await withTimeout(client.messages.create({
|
|
4800
4952
|
...requestParams,
|
|
4801
4953
|
max_tokens: terminalMaxTokens,
|
|
4954
|
+
// Assigned outright, never a conditional spread: spreading
|
|
4955
|
+
// requestParams above may already carry a `thinking` sized
|
|
4956
|
+
// for the original max_tokens, and only adding a key when
|
|
4957
|
+
// backstopThinking is truthy would leave that stale value
|
|
4958
|
+
// in place instead of clearing it.
|
|
4959
|
+
thinking: backstopThinking,
|
|
4802
4960
|
tool_choice: { type: "none" },
|
|
4803
4961
|
system: cachedBackstop.system,
|
|
4804
4962
|
...(cachedBackstop.tools &&
|
|
@@ -4813,7 +4971,25 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4813
4971
|
response.usage?.cache_read_input_tokens || 0;
|
|
4814
4972
|
totalCacheCreationTokens +=
|
|
4815
4973
|
response.usage?.cache_creation_input_tokens || 0;
|
|
4974
|
+
// A SUBSET already included in totalOutputTokens above
|
|
4975
|
+
// (Anthropic bills thinking as part of output_tokens) —
|
|
4976
|
+
// additive only, matching the main loop's own accumulation.
|
|
4977
|
+
totalReasoningTokens +=
|
|
4978
|
+
response.usage?.output_tokens_details?.thinking_tokens ?? 0;
|
|
4816
4979
|
lastStopReason = response.stop_reason;
|
|
4980
|
+
// Extract<> rather than a hand-written object shape: the real
|
|
4981
|
+
// SDK's ThinkingBlock also requires `signature`, which a literal
|
|
4982
|
+
// `{ type: "thinking"; thinking: string }` predicate omits — TS
|
|
4983
|
+
// then rejects the predicate as not assignable to ContentBlock.
|
|
4984
|
+
// Extracting the union member sidesteps that without
|
|
4985
|
+
// hard-coding its full shape here.
|
|
4986
|
+
const backstopReasoning = response.content
|
|
4987
|
+
.filter((block) => block.type === "thinking")
|
|
4988
|
+
.map((block) => block.thinking)
|
|
4989
|
+
.join("");
|
|
4990
|
+
if (backstopReasoning) {
|
|
4991
|
+
accumulatedReasoningText += backstopReasoning;
|
|
4992
|
+
}
|
|
4817
4993
|
const backstopText = response.content
|
|
4818
4994
|
.filter((block) => block.type === "text")
|
|
4819
4995
|
.map((b) => b.text)
|
|
@@ -4927,11 +5103,19 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4927
5103
|
...(totalCacheCreationTokens > 0 && {
|
|
4928
5104
|
cacheCreationTokens: totalCacheCreationTokens,
|
|
4929
5105
|
}),
|
|
5106
|
+
// A SUBSET already included in `output` above (Anthropic bills
|
|
5107
|
+
// thinking tokens as part of output_tokens) — reported additively,
|
|
5108
|
+
// never folded into output/total.
|
|
5109
|
+
...(totalReasoningTokens > 0 && { reasoning: totalReasoningTokens }),
|
|
4930
5110
|
},
|
|
4931
5111
|
responseTime,
|
|
4932
5112
|
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
4933
5113
|
toolExecutions: resolveToolExecutionRecords(options, externalToolExecutions),
|
|
4934
5114
|
enhancedWithTools: externalToolCalls.length > 0,
|
|
5115
|
+
...(accumulatedReasoningText && { reasoning: accumulatedReasoningText }),
|
|
5116
|
+
...(totalReasoningTokens > 0 && {
|
|
5117
|
+
reasoningTokens: totalReasoningTokens,
|
|
5118
|
+
}),
|
|
4935
5119
|
};
|
|
4936
5120
|
// Route through enhanceResult so analytics/evaluation/tracing are picked
|
|
4937
5121
|
// up the same way the BaseProvider.generate() path picks them up. The
|
|
@@ -6245,6 +6429,17 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
6245
6429
|
/**
|
|
6246
6430
|
* Get model suggestions when a model is not found
|
|
6247
6431
|
*/
|
|
6432
|
+
/**
|
|
6433
|
+
* Candidate model ids for a "model not available" error.
|
|
6434
|
+
*
|
|
6435
|
+
* This list is static. It cannot know what the caller's project and region
|
|
6436
|
+
* actually serve, so it must not claim to: it previously announced
|
|
6437
|
+
* "always available" and included the requested model itself, which
|
|
6438
|
+
* produced errors that recommended the very id that had just failed —
|
|
6439
|
+
* `gemini-3-pro-preview-11-2025` suggesting `gemini-3-pro-preview-11-2025`.
|
|
6440
|
+
* The requested model is now filtered out, and the wording says these are
|
|
6441
|
+
* ids to try rather than ids known to work.
|
|
6442
|
+
*/
|
|
6248
6443
|
getModelSuggestions(requestedModel) {
|
|
6249
6444
|
const availableModels = {
|
|
6250
6445
|
google: [
|
|
@@ -6270,20 +6465,29 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
6270
6465
|
"claude-3-opus-20240229",
|
|
6271
6466
|
],
|
|
6272
6467
|
};
|
|
6273
|
-
|
|
6274
|
-
|
|
6468
|
+
// Never offer the id that just failed back to the caller. Compared by
|
|
6469
|
+
// base id (before "@") so a versioned request (e.g. "gemini-2.5-pro@002")
|
|
6470
|
+
// still excludes the unversioned list entry it failed against.
|
|
6471
|
+
const requestedBase = (requestedModel ?? "").split("@")[0].toLowerCase();
|
|
6472
|
+
const notRequested = (model) => model.split("@")[0].toLowerCase() !== requestedBase;
|
|
6473
|
+
const google = availableModels.google.filter(notRequested);
|
|
6474
|
+
const claude = availableModels.claude.filter(notRequested);
|
|
6475
|
+
let suggestions = "\n🤖 Google Models (availability depends on your project and region):\n";
|
|
6476
|
+
google.forEach((model) => {
|
|
6275
6477
|
suggestions += ` • ${model}\n`;
|
|
6276
6478
|
});
|
|
6277
6479
|
suggestions += "\n🧠 Claude Models (requires Anthropic integration):\n";
|
|
6278
|
-
|
|
6480
|
+
claude.forEach((model) => {
|
|
6279
6481
|
suggestions += ` • ${model}\n`;
|
|
6280
6482
|
});
|
|
6281
|
-
// If the requested model looks like a Claude model, provide specific guidance
|
|
6483
|
+
// If the requested model looks like a Claude model, provide specific guidance.
|
|
6484
|
+
// Worded without requestedModel so the just-failed id isn't echoed back here too.
|
|
6282
6485
|
if (requestedModel && requestedModel.toLowerCase().includes("claude")) {
|
|
6283
|
-
suggestions +=
|
|
6486
|
+
suggestions +=
|
|
6487
|
+
"\n💡 Tip: this model id appears to be a Claude model, which just failed.\n";
|
|
6284
6488
|
suggestions +=
|
|
6285
6489
|
"Ensure Anthropic integration is enabled in your Google Cloud project.\n";
|
|
6286
|
-
suggestions += "Try
|
|
6490
|
+
suggestions += "Try another Claude model from the list above.";
|
|
6287
6491
|
}
|
|
6288
6492
|
return suggestions;
|
|
6289
6493
|
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -506,8 +506,18 @@ export type CreateThinkingConfigOptions = {
|
|
|
506
506
|
/** Thinking level for Gemini 3 models */
|
|
507
507
|
thinkingLevel?: ThinkingLevel;
|
|
508
508
|
};
|
|
509
|
-
/**
|
|
509
|
+
/**
|
|
510
|
+
* Native SDK thinkingConfig structure for Gemini native SDK.
|
|
511
|
+
*
|
|
512
|
+
* `thinkingLevel` and `thinkingBudget` are mutually exclusive on the wire:
|
|
513
|
+
* Gemini 3 models accept `thinkingLevel`; Gemini 2.5 (and earlier) models
|
|
514
|
+
* reject it outright with INVALID_ARGUMENT "thinking_level not supported by
|
|
515
|
+
* this model" and require the numeric `thinkingBudget` instead. Both fields
|
|
516
|
+
* are optional here so a single type can represent either shape — callers
|
|
517
|
+
* building the request pick whichever field the target model accepts.
|
|
518
|
+
*/
|
|
510
519
|
export type NativeThinkingConfig = {
|
|
511
520
|
includeThoughts: boolean;
|
|
512
|
-
thinkingLevel
|
|
521
|
+
thinkingLevel?: ThinkingLevel;
|
|
522
|
+
thinkingBudget?: number;
|
|
513
523
|
};
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -1394,19 +1394,28 @@ export type TextGenerationOptions = {
|
|
|
1394
1394
|
* - Range: 5000-100000 tokens
|
|
1395
1395
|
* - `thinkingLevel` is ignored for Anthropic
|
|
1396
1396
|
*
|
|
1397
|
-
* **Google Gemini 3 (gemini-
|
|
1398
|
-
* -
|
|
1397
|
+
* **Google Gemini 2.5 and 3 (gemini-2.5-pro/flash/flash-lite,
|
|
1398
|
+
* gemini-3.1-pro-preview, gemini-3-flash-preview), via `thinkingConfig`:**
|
|
1399
|
+
* - Use `thinkingConfig.thinkingLevel` (the public contract is the same
|
|
1400
|
+
* `thinkingLevel` lever for the whole Gemini family)
|
|
1399
1401
|
* - Levels: minimal, low, medium, high
|
|
1400
|
-
* - `budgetTokens` is
|
|
1402
|
+
* - `thinkingConfig.budgetTokens` is not read for Gemini — the wire-level
|
|
1403
|
+
* Vertex/native-SDK request always carries a level-derived value.
|
|
1404
|
+
* Internally, Gemini 3 sends the vendor's `thinkingLevel` field as-is;
|
|
1405
|
+
* Gemini 2.5 has no such field (Vertex rejects it with INVALID_ARGUMENT
|
|
1406
|
+
* "thinking_level not supported by this model") and the requested level
|
|
1407
|
+
* is translated to the vendor's numeric `thinkingBudget` within that
|
|
1408
|
+
* model's verified range instead. That translation is an implementation
|
|
1409
|
+
* detail of the provider — callers keep using `thinkingLevel` either way.
|
|
1401
1410
|
*
|
|
1402
1411
|
* ### Option Compatibility Matrix
|
|
1403
1412
|
*
|
|
1404
|
-
* | Option | Anthropic | Gemini 3 | Other Providers |
|
|
1405
|
-
*
|
|
1406
|
-
* | thinking | Yes | Yes
|
|
1407
|
-
* | thinkingBudget | Yes | Ignored
|
|
1408
|
-
* | thinkingLevel | Ignored | Yes
|
|
1409
|
-
* | thinkingConfig | Yes | Yes
|
|
1413
|
+
* | Option | Anthropic | Gemini 2.5 / 3 | Other Providers |
|
|
1414
|
+
* |----------------|-----------|----------------|------------------|
|
|
1415
|
+
* | thinking | Yes | Yes | Ignored |
|
|
1416
|
+
* | thinkingBudget | Yes | Ignored | Ignored |
|
|
1417
|
+
* | thinkingLevel | Ignored | Yes | Ignored |
|
|
1418
|
+
* | thinkingConfig | Yes | Yes | Ignored |
|
|
1410
1419
|
*
|
|
1411
1420
|
* ### Examples
|
|
1412
1421
|
*
|
|
@@ -6,6 +6,16 @@ export declare function isGemini25Model(modelName: string): boolean;
|
|
|
6
6
|
export declare function supportsThinkingConfig(modelName: string): boolean;
|
|
7
7
|
export declare function supportsPromptCaching(modelName: string): boolean;
|
|
8
8
|
export declare function getMaxThinkingBudgetTokens(modelName: string): number;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the Vertex-accepted `thinkingBudget` bounds for a Gemini 2.5
|
|
11
|
+
* model. Returns undefined for anything that isn't a Gemini 2.5 model —
|
|
12
|
+
* Gemini 3 takes `thinkingLevel` directly, and no thinking-budget mapping
|
|
13
|
+
* has been verified for earlier Gemini generations.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getGemini25ThinkingBudgetRange(modelName: string): {
|
|
16
|
+
min: number;
|
|
17
|
+
max: number;
|
|
18
|
+
} | undefined;
|
|
9
19
|
export declare function getModelFamily(modelName: string): string;
|
|
10
20
|
/**
|
|
11
21
|
* Check if a model has restricted output token limit (32768 max)
|
|
@@ -58,6 +58,44 @@ export function getMaxThinkingBudgetTokens(modelName) {
|
|
|
58
58
|
}
|
|
59
59
|
return 10000;
|
|
60
60
|
}
|
|
61
|
+
// Per-model `thinkingBudget` bounds for the Gemini 2.5 family, confirmed
|
|
62
|
+
// against live Vertex generateContent responses (not just vendor docs, which
|
|
63
|
+
// have been observed to drift from runtime behavior — see the "known gotcha"
|
|
64
|
+
// on Gemini 2.5 Pro's range in public bug reports). Vertex rejects a budget
|
|
65
|
+
// outside these bounds with INVALID_ARGUMENT "thinking_budget is out of
|
|
66
|
+
// range; supported values are integers from <min> to <max>".
|
|
67
|
+
//
|
|
68
|
+
// Flash's true floor is 1, but budget:1 measured 0 thoughtsTokenCount — i.e.
|
|
69
|
+
// technically accepted but indistinguishable from thinking being off. The
|
|
70
|
+
// floor is bumped to 128 (Pro's real floor) here so a "minimal" mapping
|
|
71
|
+
// still produces observable thinking. Must precede the plain "flash" pattern
|
|
72
|
+
// below — "gemini-2.5-flash-lite" also matches a bare `/^gemini-2\.5-flash/`.
|
|
73
|
+
const GEMINI_25_THINKING_BUDGET_RANGES = [
|
|
74
|
+
{ pattern: /^gemini-2\.5-flash-lite/i, min: 512, max: 24576 },
|
|
75
|
+
{ pattern: /^gemini-2\.5-flash/i, min: 128, max: 24576 },
|
|
76
|
+
{ pattern: /^gemini-2\.5-pro/i, min: 128, max: 32768 },
|
|
77
|
+
];
|
|
78
|
+
// Bounds safe for any Gemini 2.5 variant not explicitly listed above — the
|
|
79
|
+
// intersection of all three known models' verified ranges, so a model we
|
|
80
|
+
// haven't named still gets a budget every family member accepts.
|
|
81
|
+
const GEMINI_25_FALLBACK_BUDGET_RANGE = { min: 512, max: 24576 };
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the Vertex-accepted `thinkingBudget` bounds for a Gemini 2.5
|
|
84
|
+
* model. Returns undefined for anything that isn't a Gemini 2.5 model —
|
|
85
|
+
* Gemini 3 takes `thinkingLevel` directly, and no thinking-budget mapping
|
|
86
|
+
* has been verified for earlier Gemini generations.
|
|
87
|
+
*/
|
|
88
|
+
export function getGemini25ThinkingBudgetRange(modelName) {
|
|
89
|
+
if (!isValidModelName(modelName) || !isGemini25Model(modelName)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
for (const { pattern, min, max } of GEMINI_25_THINKING_BUDGET_RANGES) {
|
|
93
|
+
if (pattern.test(modelName)) {
|
|
94
|
+
return { min, max };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return GEMINI_25_FALLBACK_BUDGET_RANGE;
|
|
98
|
+
}
|
|
61
99
|
export function getModelFamily(modelName) {
|
|
62
100
|
if (!isValidModelName(modelName)) {
|
|
63
101
|
return "unknown";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers that keep lazily-populated StreamResult fields (`toolsUsed`,
|
|
3
|
+
* `toolExecutions`, and any other field a provider defines via
|
|
4
|
+
* `Object.defineProperty` with only a `get`) alive as the result object is
|
|
5
|
+
* re-wrapped or narrowed while it travels from a provider's `stream()`
|
|
6
|
+
* implementation up through BaseProvider and NeuroLink to the caller.
|
|
7
|
+
*
|
|
8
|
+
* A plain object spread (`{ ...result }`) or an object literal that reads
|
|
9
|
+
* `result.someField` invokes any getter on `result` immediately and stores
|
|
10
|
+
* the RETURNED VALUE as a static data property on the copy. For a
|
|
11
|
+
* background-loop stream (native Vertex/Anthropic paths, and any
|
|
12
|
+
* OpenAI-compatible provider that appends to `toolExecutionSummaries` while
|
|
13
|
+
* the consumer drains the stream), that snapshot happens before a single
|
|
14
|
+
* chunk has been pulled — so the copy is permanently frozen at empty/undefined
|
|
15
|
+
* even though the source object goes on to reflect real values as the
|
|
16
|
+
* background loop runs. Re-applying the ORIGINAL property descriptor (not
|
|
17
|
+
* the value it currently returns) keeps the copy lazy: every later read on
|
|
18
|
+
* the copy still calls the same getter, closing over the same live backing
|
|
19
|
+
* state.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Re-apply every getter-defined accessor from `source` onto `target`,
|
|
23
|
+
* in place. Plain data properties (arrays copied by reference, primitives)
|
|
24
|
+
* are left untouched — an object spread already preserves those correctly,
|
|
25
|
+
* since copying an array reference keeps observing later `.push()`es.
|
|
26
|
+
*/
|
|
27
|
+
export declare function preserveLiveStreamAccessors<T extends object>(source: T, target: T): T;
|
|
28
|
+
/**
|
|
29
|
+
* Build a `PropertyDescriptorMap` carrying the named fields' descriptors
|
|
30
|
+
* (data or accessor) from `source`, for use with `Object.defineProperties`
|
|
31
|
+
* when constructing a NEW, narrower object at a boundary that lists an
|
|
32
|
+
* explicit field set. Reading `source[key]` directly at such a boundary
|
|
33
|
+
* (`target.toolsUsed = source.toolsUsed`) would invoke a getter immediately;
|
|
34
|
+
* copying the descriptor instead defers that read to whoever accesses the
|
|
35
|
+
* field on `target` later. Keys absent from `source` are omitted, matching
|
|
36
|
+
* how an optional field would be omitted from a manual copy.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pickLiveStreamDescriptors(source: object, keys: readonly string[]): PropertyDescriptorMap;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers that keep lazily-populated StreamResult fields (`toolsUsed`,
|
|
3
|
+
* `toolExecutions`, and any other field a provider defines via
|
|
4
|
+
* `Object.defineProperty` with only a `get`) alive as the result object is
|
|
5
|
+
* re-wrapped or narrowed while it travels from a provider's `stream()`
|
|
6
|
+
* implementation up through BaseProvider and NeuroLink to the caller.
|
|
7
|
+
*
|
|
8
|
+
* A plain object spread (`{ ...result }`) or an object literal that reads
|
|
9
|
+
* `result.someField` invokes any getter on `result` immediately and stores
|
|
10
|
+
* the RETURNED VALUE as a static data property on the copy. For a
|
|
11
|
+
* background-loop stream (native Vertex/Anthropic paths, and any
|
|
12
|
+
* OpenAI-compatible provider that appends to `toolExecutionSummaries` while
|
|
13
|
+
* the consumer drains the stream), that snapshot happens before a single
|
|
14
|
+
* chunk has been pulled — so the copy is permanently frozen at empty/undefined
|
|
15
|
+
* even though the source object goes on to reflect real values as the
|
|
16
|
+
* background loop runs. Re-applying the ORIGINAL property descriptor (not
|
|
17
|
+
* the value it currently returns) keeps the copy lazy: every later read on
|
|
18
|
+
* the copy still calls the same getter, closing over the same live backing
|
|
19
|
+
* state.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Re-apply every getter-defined accessor from `source` onto `target`,
|
|
23
|
+
* in place. Plain data properties (arrays copied by reference, primitives)
|
|
24
|
+
* are left untouched — an object spread already preserves those correctly,
|
|
25
|
+
* since copying an array reference keeps observing later `.push()`es.
|
|
26
|
+
*/
|
|
27
|
+
export function preserveLiveStreamAccessors(source, target) {
|
|
28
|
+
for (const key of Object.getOwnPropertyNames(source)) {
|
|
29
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
30
|
+
if (descriptor && typeof descriptor.get === "function") {
|
|
31
|
+
Object.defineProperty(target, key, descriptor);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return target;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a `PropertyDescriptorMap` carrying the named fields' descriptors
|
|
38
|
+
* (data or accessor) from `source`, for use with `Object.defineProperties`
|
|
39
|
+
* when constructing a NEW, narrower object at a boundary that lists an
|
|
40
|
+
* explicit field set. Reading `source[key]` directly at such a boundary
|
|
41
|
+
* (`target.toolsUsed = source.toolsUsed`) would invoke a getter immediately;
|
|
42
|
+
* copying the descriptor instead defers that read to whoever accesses the
|
|
43
|
+
* field on `target` later. Keys absent from `source` are omitted, matching
|
|
44
|
+
* how an optional field would be omitted from a manual copy.
|
|
45
|
+
*/
|
|
46
|
+
export function pickLiveStreamDescriptors(source, keys) {
|
|
47
|
+
const descriptors = {};
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
50
|
+
if (descriptor) {
|
|
51
|
+
descriptors[key] = descriptor;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return descriptors;
|
|
55
|
+
}
|