@xiaoso/dsh-tool-plus 0.1.16 → 0.1.17-beta.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/lib/{abort-DZ54CaKU.mjs → abort-BTblp0Rt.mjs} +2 -2
- package/lib/{alibaba-coding-plan-DODpkcqi.mjs → alibaba-coding-plan-t3R4YKCB.mjs} +3 -3
- package/lib/{alibaba-token-plan-C6lpKCda.mjs → alibaba-token-plan-CO4djG1y.mjs} +4 -4
- package/lib/{alibaba-token-plan-CHGyWOfX.mjs → alibaba-token-plan-CUjYJX0-.mjs} +1 -1
- package/lib/{anthropic-Dv_GGX6g.mjs → anthropic-nf7w6so1.mjs} +48 -21
- package/lib/{api-key-validation-MnMf_ihX.mjs → api-key-validation-C_tZ7Y9a.mjs} +2 -2
- package/lib/behavior-BwvTUtag.mjs +111 -0
- package/lib/{browser-render-BZXt548F.mjs → browser-render-BQaEVK_n.mjs} +4 -4
- package/lib/{reference-CtvM4zLU.mjs → bundled-references-CrQ4dQcT.mjs} +57 -4
- package/lib/cascade-BWjRTD1q.mjs +30265 -0
- package/lib/{cline-pass-BT9CBEOj.mjs → cline-pass-Bjm-O8GG.mjs} +2 -2
- package/lib/cloudflare-CKXRJ0l9.mjs +37 -0
- package/lib/{cloudflare-ai-gateway-DReN6ohJ.mjs → cloudflare-ai-gateway-BpnBSjwV.mjs} +8 -4
- package/lib/cloudflare-workers-ai-odj9XswL.mjs +32 -0
- package/lib/collapse-_nkb2dn7.mjs +1945 -0
- package/lib/{coreweave-C82HrLpb.mjs → coreweave-CGDZOD8-.mjs} +1 -1
- package/lib/{coreweave-C68UgRQO.mjs → coreweave-DwffcgW5.mjs} +4 -4
- package/lib/cursor-BgOuV7Qf.mjs +794 -0
- package/lib/{cursor-_gHHq-Dq.mjs → cursor-CF1iHHbv.mjs} +68 -19
- package/lib/{cursor-proto-Cr_WEMuv.mjs → cursor-proto-BuNTjX3L.mjs} +823 -23
- package/lib/{devin-QpfTSl9H.mjs → devin-7OEeEb80.mjs} +112 -31
- package/lib/{devin-proto-DYhp4Ty6.mjs → devin-proto-C-QreeAv.mjs} +71 -5
- package/lib/{dom-DG3sPESc.mjs → dom-BmbZv4v9.mjs} +598 -182
- package/lib/{error-O63pC8zg.mjs → error-8LVo7tug.mjs} +725 -104
- package/lib/factory-droid-ChhmsGWb.mjs +71 -0
- package/lib/{fs-promises-shim-B2YFWkcd.mjs → fs-promises-shim-CANAxFWh.mjs} +10 -1
- package/lib/{github-copilot-BMEcw8N1.mjs → github-copilot-QwevdT30.mjs} +4 -4
- package/lib/{github-copilot-headers-DnjGEbko.mjs → github-copilot-headers-CAUZ0rUD.mjs} +138 -26
- package/lib/{gitlab-duo-DnHqJjFY.mjs → gitlab-duo-DigV7k_o.mjs} +2 -2
- package/lib/{google-antigravity-B1mSckrK.mjs → google-antigravity-D-oe0r8A.mjs} +6 -7
- package/lib/{google-gemini-cli-DJwThCCn.mjs → google-gemini-cli-CQBHgGJT.mjs} +5 -6
- package/lib/{google-oauth-shared-BctLpVUN.mjs → google-oauth-shared-CvgVIUwJ.mjs} +2 -2
- package/lib/google-validation-CdvRj4ec.mjs +126 -0
- package/lib/id-EJwVLDHE.mjs +2548 -0
- package/lib/index.mjs +248 -258
- package/lib/{kilo-BEgEwMJQ.mjs → kilo-5r6OffCa.mjs} +2 -2
- package/lib/{kimi-T9NldEIJ.mjs → kimi-DOOtbXmm.mjs} +9 -10
- package/lib/{launch-BIWNyrBB.mjs → launch-DkAjib7h.mjs} +1 -1
- package/lib/{markit-Dvp2JPdT.mjs → markit-CCcyeAds.mjs} +359 -140
- package/lib/{models-DnXMYGcJ.mjs → models-DCpDT_im.mjs} +199203 -112275
- package/lib/{muse-code-CNvqqAp6.mjs → muse-code-CYf8zj_B.mjs} +5 -5
- package/lib/{openai-codex-D5-CyTxa.mjs → openai-codex-Cj-TvSFh.mjs} +13 -10
- package/lib/{perplexity-A2PEoGaC.mjs → perplexity-Dh_Qgsmx.mjs} +68 -25
- package/lib/{pkce-BYjmugmw.mjs → pkce-DO-jBJLV.mjs} +1 -1
- package/lib/pricing-DusBEhwm.mjs +593 -0
- package/lib/{readability-uGhqb4GC.mjs → readability-XHK0o3bv.mjs} +287 -27
- package/lib/{scrapers-C1ujjV8k.mjs → scrapers-B6PADOVm.mjs} +13 -13
- package/lib/slugify-ynYWBIA5.mjs +11 -0
- package/lib/snowflake-B_jap6lz.mjs +666 -0
- package/lib/snowflake-Cu1ZyOp6.mjs +151 -0
- package/lib/{src-AELUUb5U.mjs → src-BhinCSpi.mjs} +1172 -458
- package/lib/{stream-AVOoe5Hj.mjs → stream-D2ivg3GH.mjs} +3740 -5311
- package/lib/{turndown-D6fof1OX.mjs → turndown-Vya8SavL.mjs} +82 -30
- package/lib/types/tools/hashline/native/index.d.ts +2 -2
- package/lib/types/tools/hashline/native/policy.d.ts +19 -21
- package/lib/types/tools/omp/session/agent-storage.d.ts +3 -3
- package/lib/types/tools/shared/bun-named-shim.d.ts +5 -0
- package/lib/{types-BLrLohwK.mjs → types-tH1v9uRm.mjs} +2 -2
- package/lib/{utils-cZX3f_Ls.mjs → utils-C_Ncya1r.mjs} +1737 -509
- package/lib/utils-D2tK38xq.mjs +34006 -0
- package/lib/{utils-I4I8azHN.mjs → utils-DrDwmNtu.mjs} +8 -3
- package/lib/{xai-oauth-CbeldUSV.mjs → xai-oauth-DLLzX6my.mjs} +4 -4
- package/lib/{xiaomi-Dl3VSs04.mjs → xiaomi-CqK-KivK.mjs} +2 -2
- package/lib/{zai-FyjGSypr.mjs → zai-DoJ2xPIH.mjs} +2 -2
- package/lib/{zip-DRrycScC.mjs → zip-DG1mUV6X.mjs} +1 -1
- package/package.json +8 -8
- package/lib/amazon-bedrock-Dx0rr9dY.mjs +0 -996
- package/lib/anthropic-ClCw0PV_.mjs +0 -3624
- package/lib/anthropic-wire-lbGAFzYb.mjs +0 -927
- package/lib/azure-openai-responses-WE_bgFWp.mjs +0 -285
- package/lib/bundled-references-DY3EJxZV.mjs +0 -56
- package/lib/cloudflare-ai-gateway-CrMa97Ap.mjs +0 -44
- package/lib/collapse-CkMNRNr7.mjs +0 -2304
- package/lib/cursor-C6jaIJDg.mjs +0 -283
- package/lib/cursor-D-XZYDf_.mjs +0 -4544
- package/lib/deterministic-id-CUEIMSqR.mjs +0 -17
- package/lib/devin-ExGIghmx.mjs +0 -69
- package/lib/devin-u0ljw5P6.mjs +0 -628
- package/lib/effort-BdtQMN-_.mjs +0 -23
- package/lib/event-stream-BKEtb45Y.mjs +0 -167
- package/lib/gemini-headers-Co9cVvex.mjs +0 -54
- package/lib/google-auth-qAN-6DMW.mjs +0 -232
- package/lib/google-gemini-cli-B2Aw1S5w.mjs +0 -844
- package/lib/google-lTxuq28n.mjs +0 -38
- package/lib/google-shared-Y01j9xOr.mjs +0 -856
- package/lib/google-validation-BEkNs-Bx.mjs +0 -18
- package/lib/google-vertex-DcM-FRC5.mjs +0 -99
- package/lib/hosts-00rjRzUD.mjs +0 -203
- package/lib/id-AL5obQzR.mjs +0 -71
- package/lib/idle-iterator-D0ZXEcUq.mjs +0 -366
- package/lib/ollama-6R0oXZks.mjs +0 -574
- package/lib/openai-codex-responses-C8bnBWOe.mjs +0 -3232
- package/lib/openai-completions-DNr3mUsn.mjs +0 -1430
- package/lib/openai-configuration-update-xMYUjaK_.mjs +0 -150
- package/lib/openai-reasoning-fallback-Ju-5pSR8.mjs +0 -380
- package/lib/openai-responses-CjpafAEr.mjs +0 -861
- package/lib/protobuf-BMgDYu_U.mjs +0 -717
- package/lib/provider-response-SqKSkkUc.mjs +0 -22
- package/lib/proxy-DxvCQOCA.mjs +0 -230
- package/lib/request-debug-BwPOqN_j.mjs +0 -183
- package/lib/sanitize-text-BCf5KmCf.mjs +0 -33
- package/lib/sse-debug-B-tkLCPW.mjs +0 -11
- package/lib/stream-markup-healing-CN3RsqG9.mjs +0 -267
- package/lib/strict-tool-validation-CBTz1dAA.mjs +0 -105
- package/lib/taxonomy-DSIM99rS.mjs +0 -15392
- package/lib/thinking-ngMwf_1Y.mjs +0 -336
- package/lib/type-guards-CttAANEx.mjs +0 -11
- package/lib/utils-CE3DSebh.mjs +0 -11763
- package/lib/vision-guard-BCpXWUj3.mjs +0 -77
|
@@ -1,856 +0,0 @@
|
|
|
1
|
-
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
|
-
__installBunShim();
|
|
3
|
-
import { o as readSseJson } from "./src-AELUUb5U.mjs";
|
|
4
|
-
import { R as GoogleApiError, _ as AbortError, c as finalize, r as ValidationError, s as ProviderResponseError, t as ConfigurationError } from "./error-O63pC8zg.mjs";
|
|
5
|
-
import { A as toolWireSchema, N as normalizeSchemaForCCA, P as normalizeSchemaForGoogle, T as renderDemotedThinking, h as transformMessages, o as normalizeSystemPrompts } from "./utils-cZX3f_Ls.mjs";
|
|
6
|
-
import { t as calculateCost } from "./models-DnXMYGcJ.mjs";
|
|
7
|
-
import { o as shouldSendServiceTier, t as NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard-BCpXWUj3.mjs";
|
|
8
|
-
import { t as AssistantMessageEventStream } from "./event-stream-BKEtb45Y.mjs";
|
|
9
|
-
import { scheduler } from "node:timers/promises";
|
|
10
|
-
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/dialect/thinking-fence-strip.ts
|
|
11
|
-
/**
|
|
12
|
-
* Strip self-referential reasoning-fence delimiters that a provider leaks
|
|
13
|
-
* *inside* a structured thinking part.
|
|
14
|
-
*
|
|
15
|
-
* The visible-channel healers ({@link ../utils/leaked-thinking-stream},
|
|
16
|
-
* {@link ./fenced-thinking}) split leaked ` ```thinking ` fences out of the
|
|
17
|
-
* *text* stream. They never run over parts a provider already flags as
|
|
18
|
-
* thinking, so when a model (observed on Gemini thought summaries — issue
|
|
19
|
-
* #8719) emits a bare ` ```thinking ` / ` ``````thinking ` opener line between
|
|
20
|
-
* summaries, that delimiter reaches display and persistence verbatim as fence
|
|
21
|
-
* spam inside the reasoning block.
|
|
22
|
-
*
|
|
23
|
-
* This stripper removes only a *standalone* reasoning-fence opener line — a line
|
|
24
|
-
* that is nothing but a run of ≥3 backticks immediately followed by the info
|
|
25
|
-
* string `thinking` or `reasoning`. Such a line is always redundant inside an
|
|
26
|
-
* already-structured thinking block and never carries content. Inline mentions
|
|
27
|
-
* (prose on the same line), language-tagged code fences (` ```rs `), and bare
|
|
28
|
-
* closers (` ``` `) are left untouched so legitimate fenced code inside the
|
|
29
|
-
* reasoning survives.
|
|
30
|
-
*
|
|
31
|
-
* Streaming-safe: deltas may split a line anywhere. A trailing partial line is
|
|
32
|
-
* held only while it remains a viable opener prefix; the moment it cannot be an
|
|
33
|
-
* opener it is flushed and the rest of the line passes through character-level.
|
|
34
|
-
* Correctness never depends on the prefix heuristic — every held line is
|
|
35
|
-
* classified strictly on its newline (or on {@link ThinkingFenceStripper.flush})
|
|
36
|
-
* before it is dropped.
|
|
37
|
-
*/
|
|
38
|
-
/**
|
|
39
|
-
* A complete standalone reasoning-fence opener: ≤3 lead spaces, ≥3 backticks,
|
|
40
|
-
* `thinking`/`reasoning`, optional trailing spaces, tolerating a trailing CR
|
|
41
|
-
* from a CRLF newline.
|
|
42
|
-
*/
|
|
43
|
-
const OPENER_LINE = /^ {0,3}`{3,}(?:thinking|reasoning)[ \t]*\r?$/i;
|
|
44
|
-
/** Could `line` (a partial, newline-not-yet-seen) still grow into {@link OPENER_LINE}? */
|
|
45
|
-
function couldBeOpenerPrefix(line) {
|
|
46
|
-
const s = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
47
|
-
const m = /^ {0,3}(`*)([\s\S]*)$/.exec(s);
|
|
48
|
-
if (!m) return false;
|
|
49
|
-
const ticks = m[1].length;
|
|
50
|
-
const rest = m[2];
|
|
51
|
-
if (rest === "") return true;
|
|
52
|
-
if (ticks < 3) return false;
|
|
53
|
-
const word = rest.replace(/[ \t]+$/, "").toLowerCase();
|
|
54
|
-
return "thinking".startsWith(word) || "reasoning".startsWith(word);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Stateful, line-oriented stripper for leaked reasoning-fence openers in one
|
|
58
|
-
* structured thinking block. One instance per thinking block; feed every
|
|
59
|
-
* thinking delta through {@link push} and drain the tail with {@link flush}.
|
|
60
|
-
*/
|
|
61
|
-
var ThinkingFenceStripper = class {
|
|
62
|
-
/** Buffered content of the current line still being classified. */
|
|
63
|
-
#carry = "";
|
|
64
|
-
/** True once the current line is known not to be an opener; passes through until newline. */
|
|
65
|
-
#passthrough = false;
|
|
66
|
-
/** Consume one thinking delta; returns the sanitized text to emit (may be empty). */
|
|
67
|
-
push(chunk) {
|
|
68
|
-
let out = "";
|
|
69
|
-
for (const ch of chunk) {
|
|
70
|
-
if (this.#passthrough) {
|
|
71
|
-
out += ch;
|
|
72
|
-
if (ch === "\n") this.#passthrough = false;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (ch === "\n") {
|
|
76
|
-
if (!OPENER_LINE.test(this.#carry)) out += `${this.#carry}\n`;
|
|
77
|
-
this.#carry = "";
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
this.#carry += ch;
|
|
81
|
-
if (!couldBeOpenerPrefix(this.#carry)) {
|
|
82
|
-
out += this.#carry;
|
|
83
|
-
this.#carry = "";
|
|
84
|
-
this.#passthrough = true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return out;
|
|
88
|
-
}
|
|
89
|
-
/** Drain any held partial line at block end; returns text to emit (may be empty). */
|
|
90
|
-
flush() {
|
|
91
|
-
const carry = this.#carry;
|
|
92
|
-
this.#carry = "";
|
|
93
|
-
this.#passthrough = false;
|
|
94
|
-
return OPENER_LINE.test(carry) ? "" : carry;
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
//#endregion
|
|
98
|
-
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/providers/google-shared.ts
|
|
99
|
-
/**
|
|
100
|
-
* Shared utilities for Google Generative AI and Google Cloud Code Assist providers.
|
|
101
|
-
*/
|
|
102
|
-
function convertGoogleImagePart(image) {
|
|
103
|
-
if (image.providerFile?.provider === "google" && image.providerFile.uri) return { fileData: {
|
|
104
|
-
fileUri: image.providerFile.uri,
|
|
105
|
-
mimeType: image.mimeType
|
|
106
|
-
} };
|
|
107
|
-
return image.url ? { fileData: {
|
|
108
|
-
fileUri: image.url,
|
|
109
|
-
mimeType: image.mimeType
|
|
110
|
-
} } : { inlineData: {
|
|
111
|
-
mimeType: image.mimeType,
|
|
112
|
-
data: image.data
|
|
113
|
-
} };
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Determines whether a streamed Gemini `Part` should be treated as "thinking".
|
|
117
|
-
*
|
|
118
|
-
* Protocol note (Gemini / Vertex AI thought signatures):
|
|
119
|
-
* - `thought: true` is the definitive marker for thinking content (thought summaries).
|
|
120
|
-
* - `thoughtSignature` is an encrypted representation of the model's internal thought process
|
|
121
|
-
* used to preserve reasoning context across multi-turn interactions.
|
|
122
|
-
* - `thoughtSignature` can appear on ANY part type (text, functionCall, etc.) - it does NOT
|
|
123
|
-
* indicate the part itself is thinking content.
|
|
124
|
-
* - For non-functionCall responses, the signature appears on the last part for context replay.
|
|
125
|
-
* - When persisting/replaying model outputs, signature-bearing parts must be preserved as-is;
|
|
126
|
-
* do not merge/move signatures across parts.
|
|
127
|
-
*
|
|
128
|
-
* See: https://ai.google.dev/gemini-api/docs/thought-signatures
|
|
129
|
-
*/
|
|
130
|
-
function isThinkingPart(part) {
|
|
131
|
-
return part.thought === true;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Retain thought signatures during streaming.
|
|
135
|
-
*
|
|
136
|
-
* Some backends only send `thoughtSignature` on the first delta for a given part/block; later deltas may omit it.
|
|
137
|
-
* This helper preserves the last non-empty signature for the current block.
|
|
138
|
-
*
|
|
139
|
-
* Note: this does NOT merge or move signatures across distinct response parts. It only prevents
|
|
140
|
-
* a signature from being overwritten with `undefined` within the same streamed block.
|
|
141
|
-
*/
|
|
142
|
-
function retainThoughtSignature(existing, incoming) {
|
|
143
|
-
if (typeof incoming === "string" && incoming.length > 0) return incoming;
|
|
144
|
-
return existing;
|
|
145
|
-
}
|
|
146
|
-
const base64SignaturePattern = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
147
|
-
const SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
|
|
148
|
-
function isValidThoughtSignature(signature) {
|
|
149
|
-
if (!signature) return false;
|
|
150
|
-
if (signature.length % 4 !== 0) return false;
|
|
151
|
-
return base64SignaturePattern.test(signature);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Only keep signatures from the same provider/model and with valid base64.
|
|
155
|
-
*/
|
|
156
|
-
function resolveThoughtSignature(isSameProviderAndModel, signature) {
|
|
157
|
-
return isSameProviderAndModel && isValidThoughtSignature(signature) ? signature : void 0;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Convert internal messages to Gemini Content[] format.
|
|
161
|
-
*/
|
|
162
|
-
function convertMessages(model, context) {
|
|
163
|
-
const contents = [];
|
|
164
|
-
const emittedToolCallNames = /* @__PURE__ */ new Map();
|
|
165
|
-
const normalizeToolCallId = (id) => {
|
|
166
|
-
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
167
|
-
};
|
|
168
|
-
const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
|
|
169
|
-
let pendingToolImageParts = [];
|
|
170
|
-
const flushPendingToolImages = () => {
|
|
171
|
-
if (pendingToolImageParts.length === 0) return;
|
|
172
|
-
contents.push({
|
|
173
|
-
role: "user",
|
|
174
|
-
parts: pendingToolImageParts
|
|
175
|
-
});
|
|
176
|
-
pendingToolImageParts = [];
|
|
177
|
-
};
|
|
178
|
-
for (const msg of transformedMessages) {
|
|
179
|
-
if (msg.role !== "toolResult") flushPendingToolImages();
|
|
180
|
-
if (msg.role === "user" || msg.role === "developer") {
|
|
181
|
-
if (typeof msg.content === "string") {
|
|
182
|
-
if (!msg.content || msg.content.trim() === "") continue;
|
|
183
|
-
contents.push({
|
|
184
|
-
role: "user",
|
|
185
|
-
parts: [{ text: msg.content.toWellFormed() }]
|
|
186
|
-
});
|
|
187
|
-
} else {
|
|
188
|
-
const supportsImages = model.input.includes("image");
|
|
189
|
-
const parts = [];
|
|
190
|
-
let omittedImages = false;
|
|
191
|
-
for (const item of msg.content) if (item.type === "text") {
|
|
192
|
-
const text = item.text.toWellFormed();
|
|
193
|
-
if (text.trim().length === 0) continue;
|
|
194
|
-
parts.push({ text });
|
|
195
|
-
} else if (supportsImages) parts.push(convertGoogleImagePart(item));
|
|
196
|
-
else omittedImages = true;
|
|
197
|
-
if (omittedImages) parts.push({ text: NON_VISION_IMAGE_PLACEHOLDER });
|
|
198
|
-
if (parts.length === 0) continue;
|
|
199
|
-
contents.push({
|
|
200
|
-
role: "user",
|
|
201
|
-
parts
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
} else if (msg.role === "assistant") {
|
|
205
|
-
const parts = [];
|
|
206
|
-
const isSameProviderAndModel = msg.provider === model.provider && msg.model === model.id;
|
|
207
|
-
const dropsUnsignedThinking = model.compat.dropUnsignedThinking;
|
|
208
|
-
let isFirstToolCall = true;
|
|
209
|
-
for (const block of msg.content) if (block.type === "text") {
|
|
210
|
-
if (!block.text || block.text.trim() === "") continue;
|
|
211
|
-
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.textSignature);
|
|
212
|
-
parts.push({
|
|
213
|
-
text: block.text.toWellFormed(),
|
|
214
|
-
...thoughtSignature && { thoughtSignature }
|
|
215
|
-
});
|
|
216
|
-
} else if (block.type === "thinking") {
|
|
217
|
-
if (!block.thinking || block.thinking.trim() === "") continue;
|
|
218
|
-
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thinkingSignature);
|
|
219
|
-
if (dropsUnsignedThinking && !thoughtSignature) continue;
|
|
220
|
-
if (thoughtSignature) parts.push({
|
|
221
|
-
thought: true,
|
|
222
|
-
text: block.thinking.toWellFormed(),
|
|
223
|
-
thoughtSignature
|
|
224
|
-
});
|
|
225
|
-
else parts.push({ text: renderDemotedThinking(model.id, block.thinking) });
|
|
226
|
-
} else if (block.type === "toolCall") {
|
|
227
|
-
emittedToolCallNames.set(block.id, block.name);
|
|
228
|
-
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
|
|
229
|
-
const requiresFallback = model.compat.requiresSkipThoughtSignature || isFirstToolCall && model.compat.requiresSkipThoughtSignatureOnFirstFunctionCall;
|
|
230
|
-
const effectiveSignature = thoughtSignature || (requiresFallback ? SKIP_THOUGHT_SIGNATURE : void 0);
|
|
231
|
-
isFirstToolCall = false;
|
|
232
|
-
const part = { functionCall: {
|
|
233
|
-
name: block.name,
|
|
234
|
-
args: block.arguments ?? {},
|
|
235
|
-
...model.compat.supportsFunctionPartId ? { id: block.id } : {}
|
|
236
|
-
} };
|
|
237
|
-
if (effectiveSignature) part.thoughtSignature = effectiveSignature;
|
|
238
|
-
parts.push(part);
|
|
239
|
-
}
|
|
240
|
-
if (parts.length === 0) continue;
|
|
241
|
-
contents.push({
|
|
242
|
-
role: "model",
|
|
243
|
-
parts
|
|
244
|
-
});
|
|
245
|
-
} else if (msg.role === "toolResult") {
|
|
246
|
-
const supportsImages = model.input.includes("image");
|
|
247
|
-
const textResult = msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
248
|
-
const imageContent = supportsImages ? msg.content.filter((c) => c.type === "image") : [];
|
|
249
|
-
const omittedImages = !supportsImages && msg.content.some((c) => c.type === "image");
|
|
250
|
-
const hasText = textResult.length > 0;
|
|
251
|
-
const hasImages = imageContent.length > 0;
|
|
252
|
-
const modelSupportsMultimodalFunctionResponse = model.compat.multimodalFunctionResponse;
|
|
253
|
-
const responseValue = omittedImages ? [hasText ? textResult.toWellFormed() : "", NON_VISION_IMAGE_PLACEHOLDER].filter(Boolean).join("\n") : hasText ? textResult.toWellFormed() : hasImages ? "(see attached image)" : "";
|
|
254
|
-
const imageParts = imageContent.map(convertGoogleImagePart);
|
|
255
|
-
const includeId = model.compat.supportsFunctionPartId;
|
|
256
|
-
const functionResponsePart = { functionResponse: {
|
|
257
|
-
name: emittedToolCallNames.get(msg.toolCallId) ?? msg.toolName,
|
|
258
|
-
response: msg.isError ? { error: responseValue } : { output: responseValue },
|
|
259
|
-
...hasImages && modelSupportsMultimodalFunctionResponse && { parts: imageParts },
|
|
260
|
-
...includeId ? { id: msg.toolCallId } : {}
|
|
261
|
-
} };
|
|
262
|
-
const lastContent = contents[contents.length - 1];
|
|
263
|
-
if (lastContent?.role === "user" && lastContent.parts?.some((p) => p.functionResponse)) lastContent.parts.push(functionResponsePart);
|
|
264
|
-
else contents.push({
|
|
265
|
-
role: "user",
|
|
266
|
-
parts: [functionResponsePart]
|
|
267
|
-
});
|
|
268
|
-
if (hasImages && !modelSupportsMultimodalFunctionResponse) pendingToolImageParts.push({ text: "Tool result image:" }, ...imageParts);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
flushPendingToolImages();
|
|
272
|
-
return contents;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Convert tools to Gemini function declarations format.
|
|
276
|
-
*
|
|
277
|
-
* We prefer `parametersJsonSchema` (full JSON Schema: anyOf/oneOf/const/etc.).
|
|
278
|
-
*
|
|
279
|
-
* Claude models via Cloud Code Assist require the legacy `parameters` field; the API
|
|
280
|
-
* translates it into Anthropic's `input_schema`. When using that path, we sanitize the
|
|
281
|
-
* schema to remove Google-unsupported JSON Schema keywords.
|
|
282
|
-
*/
|
|
283
|
-
function convertTools(tools, model) {
|
|
284
|
-
if (tools.length === 0) return void 0;
|
|
285
|
-
/**
|
|
286
|
-
* Claude models on Cloud Code Assist need the legacy `parameters` field;
|
|
287
|
-
* the API translates it into Anthropic's `input_schema`.
|
|
288
|
-
*/
|
|
289
|
-
const useParameters = model.compat.ccaLegacyParametersSchema;
|
|
290
|
-
return [{ functionDeclarations: tools.map((tool) => ({
|
|
291
|
-
name: tool.name,
|
|
292
|
-
description: tool.description || "",
|
|
293
|
-
...useParameters ? { parameters: normalizeSchemaForCCA(toolWireSchema(tool)) } : { parametersJsonSchema: normalizeSchemaForGoogle(toolWireSchema(tool)) }
|
|
294
|
-
})) }];
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Map tool choice string to Gemini FunctionCallingConfigMode.
|
|
298
|
-
*/
|
|
299
|
-
function mapToolChoice(choice) {
|
|
300
|
-
switch (choice) {
|
|
301
|
-
case "auto": return "AUTO";
|
|
302
|
-
case "none": return "NONE";
|
|
303
|
-
case "any": return "ANY";
|
|
304
|
-
default: return "AUTO";
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Map Gemini FinishReason to our StopReason.
|
|
309
|
-
*/
|
|
310
|
-
function mapStopReason(reason) {
|
|
311
|
-
switch (reason) {
|
|
312
|
-
case "STOP": return "stop";
|
|
313
|
-
case "MAX_TOKENS": return "length";
|
|
314
|
-
case "BLOCKLIST":
|
|
315
|
-
case "PROHIBITED_CONTENT":
|
|
316
|
-
case "SPII":
|
|
317
|
-
case "SAFETY":
|
|
318
|
-
case "IMAGE_SAFETY":
|
|
319
|
-
case "IMAGE_PROHIBITED_CONTENT":
|
|
320
|
-
case "IMAGE_RECITATION":
|
|
321
|
-
case "IMAGE_OTHER":
|
|
322
|
-
case "RECITATION":
|
|
323
|
-
case "FINISH_REASON_UNSPECIFIED":
|
|
324
|
-
case "OTHER":
|
|
325
|
-
case "LANGUAGE":
|
|
326
|
-
case "MALFORMED_FUNCTION_CALL":
|
|
327
|
-
case "UNEXPECTED_TOOL_CALL":
|
|
328
|
-
case "NO_IMAGE": return "error";
|
|
329
|
-
default: throw new ConfigurationError(`Unhandled stop reason: ${reason}`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Map string finish reason to our StopReason (for raw API responses).
|
|
334
|
-
*/
|
|
335
|
-
function mapStopReasonString(reason) {
|
|
336
|
-
switch (reason) {
|
|
337
|
-
case "STOP": return "stop";
|
|
338
|
-
case "MAX_TOKENS": return "length";
|
|
339
|
-
default: return "error";
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Whether a completed Google assistant message carries content worth delivering.
|
|
344
|
-
*
|
|
345
|
-
* A tool call or any non-whitespace text counts as meaningful. An empty/whitespace-only
|
|
346
|
-
* text part — or thinking that never produced an answer — is the "empty response" failure:
|
|
347
|
-
* delivered as-is the agent loop has nothing to act on and silently halts, so the request
|
|
348
|
-
* must be retried instead of surfaced.
|
|
349
|
-
*/
|
|
350
|
-
function hasMeaningfulGoogleContent(output) {
|
|
351
|
-
for (const block of output.content) {
|
|
352
|
-
if (block.type === "toolCall") return true;
|
|
353
|
-
if (block.type === "text" && block.text.trim().length > 0) return true;
|
|
354
|
-
}
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
/** Wipe a streamed message between empty-response retries so the next attempt starts clean. */
|
|
358
|
-
function resetGoogleStreamOutputForRetry(output) {
|
|
359
|
-
output.content = [];
|
|
360
|
-
output.usage = {
|
|
361
|
-
input: 0,
|
|
362
|
-
output: 0,
|
|
363
|
-
cacheRead: 0,
|
|
364
|
-
cacheWrite: 0,
|
|
365
|
-
totalTokens: 0,
|
|
366
|
-
cost: {
|
|
367
|
-
input: 0,
|
|
368
|
-
output: 0,
|
|
369
|
-
cacheRead: 0,
|
|
370
|
-
cacheWrite: 0,
|
|
371
|
-
total: 0
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
output.stopReason = "stop";
|
|
375
|
-
output.errorMessage = void 0;
|
|
376
|
-
output.timestamp = Date.now();
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Module-local counter for generating unique tool call IDs across Google providers.
|
|
380
|
-
* Shared so that a single monotonically-increasing sequence is used regardless of which
|
|
381
|
-
* Google API surface produced the stream — purely for uniqueness, not ordering semantics.
|
|
382
|
-
*/
|
|
383
|
-
let toolCallCounter = 0;
|
|
384
|
-
function nextToolCallId(name) {
|
|
385
|
-
return `${name}_${Date.now()}_${++toolCallCounter}`;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Push the appropriate `text_end` / `thinking_end` event for the given block.
|
|
389
|
-
* Shared between the SDK-backed stream consumer and the gemini-cli SSE consumer so
|
|
390
|
-
* the end-of-block event shape stays in lockstep.
|
|
391
|
-
*/
|
|
392
|
-
function pushBlockEndEvent(block, contentIndex, output, stream) {
|
|
393
|
-
if (block.type === "text") stream.push({
|
|
394
|
-
type: "text_end",
|
|
395
|
-
contentIndex,
|
|
396
|
-
content: block.text,
|
|
397
|
-
partial: output
|
|
398
|
-
});
|
|
399
|
-
else stream.push({
|
|
400
|
-
type: "thinking_end",
|
|
401
|
-
contentIndex,
|
|
402
|
-
content: block.thinking,
|
|
403
|
-
partial: output
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Push the three lifecycle events (`toolcall_start` / `toolcall_delta` / `toolcall_end`) for a
|
|
408
|
-
* fully-assembled `ToolCall`. Caller is responsible for appending the toolCall to `output.content`
|
|
409
|
-
* before invoking — this helper does not mutate `output.content`.
|
|
410
|
-
*/
|
|
411
|
-
function pushToolCallEvents(toolCall, contentIndex, output, stream) {
|
|
412
|
-
stream.push({
|
|
413
|
-
type: "toolcall_start",
|
|
414
|
-
contentIndex,
|
|
415
|
-
partial: output
|
|
416
|
-
});
|
|
417
|
-
stream.push({
|
|
418
|
-
type: "toolcall_delta",
|
|
419
|
-
contentIndex,
|
|
420
|
-
delta: JSON.stringify(toolCall.arguments),
|
|
421
|
-
partial: output
|
|
422
|
-
});
|
|
423
|
-
stream.push({
|
|
424
|
-
type: "toolcall_end",
|
|
425
|
-
contentIndex,
|
|
426
|
-
toolCall,
|
|
427
|
-
partial: output
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
function startTextOrThinkingBlock(isThinking, output, stream, onBeforeStartEvent) {
|
|
431
|
-
const block = isThinking ? {
|
|
432
|
-
type: "thinking",
|
|
433
|
-
thinking: "",
|
|
434
|
-
thinkingSignature: void 0
|
|
435
|
-
} : {
|
|
436
|
-
type: "text",
|
|
437
|
-
text: ""
|
|
438
|
-
};
|
|
439
|
-
output.content.push(block);
|
|
440
|
-
onBeforeStartEvent?.();
|
|
441
|
-
const contentIndex = output.content.length - 1;
|
|
442
|
-
if (isThinking) stream.push({
|
|
443
|
-
type: "thinking_start",
|
|
444
|
-
contentIndex,
|
|
445
|
-
partial: output
|
|
446
|
-
});
|
|
447
|
-
else stream.push({
|
|
448
|
-
type: "text_start",
|
|
449
|
-
contentIndex,
|
|
450
|
-
partial: output
|
|
451
|
-
});
|
|
452
|
-
return block;
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Drives the chunked `generateContentStream` iterator into an `AssistantMessage` and
|
|
456
|
-
* the corresponding `AssistantMessageEventStream`. Shared between `streamGoogle` and
|
|
457
|
-
* `streamGoogleVertex` — every observable event order and stop-reason rule is preserved.
|
|
458
|
-
*
|
|
459
|
-
* The caller still owns: `output` construction, timing fields (`duration`/`ttft`),
|
|
460
|
-
* `rawRequestDump`, the `client.models.generateContentStream(params)` call itself,
|
|
461
|
-
* pushing `start`/`done`/`error` events, and the surrounding try/catch that translates
|
|
462
|
-
* thrown errors into `output.stopReason`/`errorMessage`.
|
|
463
|
-
*
|
|
464
|
-
* This helper handles: the chunk loop, currentBlock flush transitions, usage metadata
|
|
465
|
-
* decoding (`calculateCost` included), tool-call id collision avoidance, finish-reason
|
|
466
|
-
* mapping, and the abort/stop-reason post-checks that re-throw to bubble into the
|
|
467
|
-
* caller's catch.
|
|
468
|
-
*/
|
|
469
|
-
async function consumeGoogleStream(args) {
|
|
470
|
-
const { googleStream, output, stream, model, options, retainTextSignature, onFirstToken } = args;
|
|
471
|
-
const blocks = output.content;
|
|
472
|
-
const blockIndex = () => blocks.length - 1;
|
|
473
|
-
let currentBlock = null;
|
|
474
|
-
let thinkingStripper = null;
|
|
475
|
-
let firstTokenSeen = false;
|
|
476
|
-
let sawFinishReason = false;
|
|
477
|
-
const flushCurrent = () => {
|
|
478
|
-
if (!currentBlock) return;
|
|
479
|
-
if (currentBlock.type === "thinking" && thinkingStripper) {
|
|
480
|
-
const tail = thinkingStripper.flush();
|
|
481
|
-
if (tail) {
|
|
482
|
-
currentBlock.thinking += tail;
|
|
483
|
-
stream.push({
|
|
484
|
-
type: "thinking_delta",
|
|
485
|
-
contentIndex: blockIndex(),
|
|
486
|
-
delta: tail,
|
|
487
|
-
partial: output
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
thinkingStripper = null;
|
|
492
|
-
pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
|
|
493
|
-
};
|
|
494
|
-
for await (const chunk of googleStream) {
|
|
495
|
-
if (chunk.error) {
|
|
496
|
-
const message = `Google API stream error: ${chunk.error.message || chunk.error.status || "unknown error"}`;
|
|
497
|
-
throw typeof chunk.error.code === "number" && chunk.error.code >= 400 ? new GoogleApiError(message, chunk.error.code) : new ProviderResponseError(message, {
|
|
498
|
-
provider: model.provider,
|
|
499
|
-
kind: "output"
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
if (!chunk.candidates?.length && chunk.promptFeedback?.blockReason) {
|
|
503
|
-
const detail = chunk.promptFeedback.blockReasonMessage;
|
|
504
|
-
throw new ProviderResponseError(`Request blocked by Google (${chunk.promptFeedback.blockReason})${detail ? `: ${detail}` : ""}`, {
|
|
505
|
-
provider: model.provider,
|
|
506
|
-
kind: "content-blocked"
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
const candidate = chunk.candidates?.[0];
|
|
510
|
-
if (candidate?.content?.parts) for (const part of candidate.content.parts) {
|
|
511
|
-
if (part.text !== void 0 && part.text !== "") {
|
|
512
|
-
if (!firstTokenSeen) {
|
|
513
|
-
firstTokenSeen = true;
|
|
514
|
-
onFirstToken?.();
|
|
515
|
-
}
|
|
516
|
-
const isThinking = isThinkingPart(part);
|
|
517
|
-
if (!currentBlock || isThinking && currentBlock.type !== "thinking" || !isThinking && currentBlock.type !== "text") {
|
|
518
|
-
flushCurrent();
|
|
519
|
-
currentBlock = startTextOrThinkingBlock(isThinking, output, stream);
|
|
520
|
-
}
|
|
521
|
-
if (currentBlock.type === "thinking") {
|
|
522
|
-
thinkingStripper ??= new ThinkingFenceStripper();
|
|
523
|
-
const cleaned = thinkingStripper.push(part.text);
|
|
524
|
-
currentBlock.thinking += cleaned;
|
|
525
|
-
currentBlock.thinkingSignature = retainThoughtSignature(currentBlock.thinkingSignature, part.thoughtSignature);
|
|
526
|
-
if (cleaned) stream.push({
|
|
527
|
-
type: "thinking_delta",
|
|
528
|
-
contentIndex: blockIndex(),
|
|
529
|
-
delta: cleaned,
|
|
530
|
-
partial: output
|
|
531
|
-
});
|
|
532
|
-
} else {
|
|
533
|
-
currentBlock.text += part.text;
|
|
534
|
-
if (retainTextSignature) currentBlock.textSignature = retainThoughtSignature(currentBlock.textSignature, part.thoughtSignature);
|
|
535
|
-
stream.push({
|
|
536
|
-
type: "text_delta",
|
|
537
|
-
contentIndex: blockIndex(),
|
|
538
|
-
delta: part.text,
|
|
539
|
-
partial: output
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
} else if (part.text === "" && part.thoughtSignature && currentBlock && !part.functionCall) {
|
|
543
|
-
if (currentBlock.type === "thinking") currentBlock.thinkingSignature = retainThoughtSignature(currentBlock.thinkingSignature, part.thoughtSignature);
|
|
544
|
-
else if (retainTextSignature) currentBlock.textSignature = retainThoughtSignature(currentBlock.textSignature, part.thoughtSignature);
|
|
545
|
-
}
|
|
546
|
-
if (part.functionCall) {
|
|
547
|
-
if (currentBlock) {
|
|
548
|
-
flushCurrent();
|
|
549
|
-
currentBlock = null;
|
|
550
|
-
}
|
|
551
|
-
const providedId = part.functionCall.id;
|
|
552
|
-
const toolCall = {
|
|
553
|
-
type: "toolCall",
|
|
554
|
-
id: !providedId || output.content.some((b) => b.type === "toolCall" && b.id === providedId) ? nextToolCallId(part.functionCall.name || "tool") : providedId,
|
|
555
|
-
name: part.functionCall.name || "",
|
|
556
|
-
arguments: part.functionCall.args ?? {},
|
|
557
|
-
...part.thoughtSignature && { thoughtSignature: part.thoughtSignature }
|
|
558
|
-
};
|
|
559
|
-
output.content.push(toolCall);
|
|
560
|
-
pushToolCallEvents(toolCall, blockIndex(), output, stream);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (candidate?.finishReason) {
|
|
564
|
-
sawFinishReason = true;
|
|
565
|
-
const mapped = mapStopReason(candidate.finishReason);
|
|
566
|
-
if ((mapped === "stop" || mapped === "length") && output.content.some((b) => b.type === "toolCall")) output.stopReason = "toolUse";
|
|
567
|
-
else {
|
|
568
|
-
output.stopReason = mapped;
|
|
569
|
-
if (mapped === "error") output.errorMessage = `Generation failed with finish reason: ${candidate.finishReason}`;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
if (chunk.usageMetadata) {
|
|
573
|
-
const cachedTokens = chunk.usageMetadata.cachedContentTokenCount || 0;
|
|
574
|
-
const thinkingTokens = chunk.usageMetadata.thoughtsTokenCount || 0;
|
|
575
|
-
output.usage = {
|
|
576
|
-
input: (chunk.usageMetadata.promptTokenCount || 0) - cachedTokens,
|
|
577
|
-
output: (chunk.usageMetadata.candidatesTokenCount || 0) + thinkingTokens,
|
|
578
|
-
cacheRead: cachedTokens,
|
|
579
|
-
cacheWrite: 0,
|
|
580
|
-
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
|
581
|
-
...thinkingTokens > 0 ? { reasoningTokens: thinkingTokens } : {},
|
|
582
|
-
cost: {
|
|
583
|
-
input: 0,
|
|
584
|
-
output: 0,
|
|
585
|
-
cacheRead: 0,
|
|
586
|
-
cacheWrite: 0,
|
|
587
|
-
total: 0
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
calculateCost(model, output.usage, output.timestamp);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
flushCurrent();
|
|
594
|
-
if (options?.signal?.aborted) throw new AbortError();
|
|
595
|
-
if (!sawFinishReason) throw new ProviderResponseError("Google API stream ended without a finish reason (connection dropped or response truncated)", {
|
|
596
|
-
provider: model.provider,
|
|
597
|
-
kind: "incomplete-stream"
|
|
598
|
-
});
|
|
599
|
-
if (output.stopReason === "aborted" || output.stopReason === "error") throw new ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
|
|
600
|
-
provider: model.provider,
|
|
601
|
-
kind: "output"
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Build the `GenerateContentParameters` payload for the public Gemini API and Vertex AI.
|
|
606
|
-
* Both surfaces accept the same `GenerateContentConfig` shape — every numeric/string knob,
|
|
607
|
-
* tool-config, thinking-config, and system-instruction conversion is identical.
|
|
608
|
-
*
|
|
609
|
-
* `google-gemini-cli` is NOT routed through here: its `CloudCodeAssistRequest` body has a
|
|
610
|
-
* distinct top-level shape (project/request/requestType) and a different thinking-config
|
|
611
|
-
* placement on `generationConfig`.
|
|
612
|
-
*/
|
|
613
|
-
function buildGoogleGenerateContentParams(model, context, options) {
|
|
614
|
-
const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
|
615
|
-
const contents = convertMessages(model, context);
|
|
616
|
-
const generationConfig = {};
|
|
617
|
-
if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
|
|
618
|
-
if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
|
|
619
|
-
if (options.topP !== void 0) generationConfig.topP = options.topP;
|
|
620
|
-
if (options.topK !== void 0) generationConfig.topK = options.topK;
|
|
621
|
-
if (options.minP !== void 0) generationConfig.minP = options.minP;
|
|
622
|
-
if (options.presencePenalty !== void 0) generationConfig.presencePenalty = options.presencePenalty;
|
|
623
|
-
if (options.repetitionPenalty !== void 0) generationConfig.repetitionPenalty = options.repetitionPenalty;
|
|
624
|
-
const config = {
|
|
625
|
-
...Object.keys(generationConfig).length > 0 && generationConfig,
|
|
626
|
-
...systemPrompts.length > 0 && { systemInstruction: { parts: systemPrompts.map((text) => ({ text })) } },
|
|
627
|
-
...context.tools && context.tools.length > 0 && { tools: convertTools(context.tools, model) }
|
|
628
|
-
};
|
|
629
|
-
if (model.provider === "google" && shouldSendServiceTier(options.serviceTier, model.provider)) config.serviceTier = options.serviceTier;
|
|
630
|
-
if (context.tools && context.tools.length > 0 && options.toolChoice) {
|
|
631
|
-
const choice = options.toolChoice;
|
|
632
|
-
if (typeof choice === "string") {
|
|
633
|
-
const mode = mapToolChoice(choice);
|
|
634
|
-
if (mode !== "AUTO") config.toolConfig = { functionCallingConfig: { mode } };
|
|
635
|
-
} else config.toolConfig = { functionCallingConfig: {
|
|
636
|
-
mode: "ANY",
|
|
637
|
-
allowedFunctionNames: [...choice.allowedFunctionNames]
|
|
638
|
-
} };
|
|
639
|
-
} else config.toolConfig = void 0;
|
|
640
|
-
const thinking = options.thinking;
|
|
641
|
-
if (thinking && model.reasoning && (thinking.enabled || thinking.level !== void 0 || thinking.budgetTokens !== void 0)) {
|
|
642
|
-
const cfg = { includeThoughts: thinking.enabled && !options.hideThinkingSummary };
|
|
643
|
-
if (thinking.level !== void 0) cfg.thinkingLevel = thinking.level;
|
|
644
|
-
else if (thinking.budgetTokens !== void 0) cfg.thinkingBudget = thinking.budgetTokens;
|
|
645
|
-
config.thinkingConfig = cfg;
|
|
646
|
-
}
|
|
647
|
-
if (options.signal) {
|
|
648
|
-
if (options.signal.aborted) throw new AbortError("Request aborted");
|
|
649
|
-
config.abortSignal = options.signal;
|
|
650
|
-
}
|
|
651
|
-
if (options.cachedContent !== void 0) {
|
|
652
|
-
if (options.cachedContent.trim().length === 0) throw new ValidationError("cachedContent must not be blank");
|
|
653
|
-
const incompatibleFields = [
|
|
654
|
-
config.systemInstruction !== void 0 && "systemInstruction",
|
|
655
|
-
config.tools !== void 0 && "tools",
|
|
656
|
-
config.toolConfig !== void 0 && "toolConfig"
|
|
657
|
-
].filter((field) => Boolean(field));
|
|
658
|
-
if (incompatibleFields.length > 0) throw new ValidationError(`cachedContent cannot be combined with request-level ${incompatibleFields.join(", ")}`);
|
|
659
|
-
config.cachedContent = options.cachedContent;
|
|
660
|
-
}
|
|
661
|
-
return {
|
|
662
|
-
model: model.id,
|
|
663
|
-
contents,
|
|
664
|
-
config
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
function streamGoogleGenAI(args) {
|
|
668
|
-
const { model, options, api, retainTextSignature, prepare } = args;
|
|
669
|
-
const stream = new AssistantMessageEventStream();
|
|
670
|
-
(async () => {
|
|
671
|
-
const startTime = performance.now();
|
|
672
|
-
let firstTokenTime;
|
|
673
|
-
const output = {
|
|
674
|
-
role: "assistant",
|
|
675
|
-
content: [],
|
|
676
|
-
api,
|
|
677
|
-
provider: model.provider,
|
|
678
|
-
model: model.id,
|
|
679
|
-
usage: {
|
|
680
|
-
input: 0,
|
|
681
|
-
output: 0,
|
|
682
|
-
cacheRead: 0,
|
|
683
|
-
cacheWrite: 0,
|
|
684
|
-
totalTokens: 0,
|
|
685
|
-
cost: {
|
|
686
|
-
input: 0,
|
|
687
|
-
output: 0,
|
|
688
|
-
cacheRead: 0,
|
|
689
|
-
cacheWrite: 0,
|
|
690
|
-
total: 0
|
|
691
|
-
}
|
|
692
|
-
},
|
|
693
|
-
stopReason: "stop",
|
|
694
|
-
timestamp: Date.now()
|
|
695
|
-
};
|
|
696
|
-
let rawRequestDump;
|
|
697
|
-
try {
|
|
698
|
-
const plan = await prepare();
|
|
699
|
-
let params = plan.params;
|
|
700
|
-
const replacement = await options?.onPayload?.(params, model);
|
|
701
|
-
if (replacement !== void 0) params = replacement;
|
|
702
|
-
rawRequestDump = {
|
|
703
|
-
provider: model.provider,
|
|
704
|
-
api: output.api,
|
|
705
|
-
model: model.id,
|
|
706
|
-
method: "POST",
|
|
707
|
-
url: plan.url,
|
|
708
|
-
body: params,
|
|
709
|
-
headers: plan.headers
|
|
710
|
-
};
|
|
711
|
-
const bodyJson = JSON.stringify(paramsToWireBody(params));
|
|
712
|
-
const fetchImpl = plan.fetch ?? options?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
713
|
-
const openStreamAt = async (requestUrl) => {
|
|
714
|
-
const response = await fetchImpl(requestUrl, {
|
|
715
|
-
method: "POST",
|
|
716
|
-
headers: {
|
|
717
|
-
...plan.headers,
|
|
718
|
-
"Content-Type": "application/json",
|
|
719
|
-
Accept: "text/event-stream"
|
|
720
|
-
},
|
|
721
|
-
body: bodyJson,
|
|
722
|
-
signal: options?.signal
|
|
723
|
-
});
|
|
724
|
-
if (!response.ok) {
|
|
725
|
-
const errorText = await response.text().catch(() => "");
|
|
726
|
-
throw new GoogleApiError(`Google API error (${response.status}): ${extractGoogleErrorMessage(errorText)}`, response.status, { headers: response.headers });
|
|
727
|
-
}
|
|
728
|
-
if (!response.body) throw new ProviderResponseError("Google API returned an empty response body", {
|
|
729
|
-
provider: model.provider,
|
|
730
|
-
kind: "empty-body"
|
|
731
|
-
});
|
|
732
|
-
return response.body;
|
|
733
|
-
};
|
|
734
|
-
const openStream = async () => {
|
|
735
|
-
if (!plan.fallbackUrl) return openStreamAt(plan.url);
|
|
736
|
-
try {
|
|
737
|
-
return await openStreamAt(plan.url);
|
|
738
|
-
} catch (error) {
|
|
739
|
-
if (error instanceof GoogleApiError && error.status === 404) return openStreamAt(plan.fallbackUrl);
|
|
740
|
-
throw error;
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
let body = await openStream();
|
|
744
|
-
stream.push({
|
|
745
|
-
type: "start",
|
|
746
|
-
partial: output
|
|
747
|
-
});
|
|
748
|
-
for (let emptyAttempt = 0;; emptyAttempt++) {
|
|
749
|
-
await consumeGoogleStream({
|
|
750
|
-
googleStream: readSseJson(body, options?.signal, (event) => options?.onSseEvent?.({
|
|
751
|
-
event: event.event,
|
|
752
|
-
data: event.data,
|
|
753
|
-
raw: [...event.raw]
|
|
754
|
-
}, model)),
|
|
755
|
-
output,
|
|
756
|
-
stream,
|
|
757
|
-
model,
|
|
758
|
-
options,
|
|
759
|
-
retainTextSignature,
|
|
760
|
-
onFirstToken: () => {
|
|
761
|
-
firstTokenTime = performance.now();
|
|
762
|
-
}
|
|
763
|
-
});
|
|
764
|
-
if (output.stopReason !== "stop" || hasMeaningfulGoogleContent(output) || options?.acceptEmptyResponse === true) break;
|
|
765
|
-
if (emptyAttempt >= 2) throw new ProviderResponseError(`Google API returned an empty response (finishReason STOP with no content) after 3 attempts`, {
|
|
766
|
-
provider: model.provider,
|
|
767
|
-
kind: "empty-body"
|
|
768
|
-
});
|
|
769
|
-
try {
|
|
770
|
-
await scheduler.wait(500 * 2 ** emptyAttempt, { signal: options?.signal });
|
|
771
|
-
} catch {
|
|
772
|
-
throw new AbortError();
|
|
773
|
-
}
|
|
774
|
-
resetGoogleStreamOutputForRetry(output);
|
|
775
|
-
body = await openStream();
|
|
776
|
-
}
|
|
777
|
-
output.duration = performance.now() - startTime;
|
|
778
|
-
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
779
|
-
stream.push({
|
|
780
|
-
type: "done",
|
|
781
|
-
reason: output.stopReason,
|
|
782
|
-
message: output
|
|
783
|
-
});
|
|
784
|
-
stream.end();
|
|
785
|
-
} catch (error) {
|
|
786
|
-
const result = await finalize(error, {
|
|
787
|
-
api: model.api,
|
|
788
|
-
signal: options?.signal,
|
|
789
|
-
rawRequestDump
|
|
790
|
-
});
|
|
791
|
-
output.stopReason = result.stopReason;
|
|
792
|
-
output.errorStatus = result.status;
|
|
793
|
-
output.errorId = result.id;
|
|
794
|
-
output.errorMessage = result.message;
|
|
795
|
-
output.duration = performance.now() - startTime;
|
|
796
|
-
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
797
|
-
stream.push({
|
|
798
|
-
type: "error",
|
|
799
|
-
reason: output.stopReason,
|
|
800
|
-
error: output
|
|
801
|
-
});
|
|
802
|
-
stream.end();
|
|
803
|
-
}
|
|
804
|
-
})();
|
|
805
|
-
return stream;
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* Lift the SDK's `params.config` fields out of `config` and place them where the
|
|
809
|
-
* Gemini / Vertex AI REST API expects them on the request body. Mirrors the
|
|
810
|
-
* generateContentParametersTo{Mldev,Vertex} transformation in @google/genai
|
|
811
|
-
* for the subset of fields this codebase actually sets.
|
|
812
|
-
*
|
|
813
|
-
* `abortSignal` is intentionally dropped — the SDK propagates it via `fetch.signal`,
|
|
814
|
-
* which our caller already wires up through `options.signal`.
|
|
815
|
-
*/
|
|
816
|
-
function paramsToWireBody(params) {
|
|
817
|
-
const body = { contents: params.contents };
|
|
818
|
-
const config = params.config;
|
|
819
|
-
if (!config) return body;
|
|
820
|
-
if (config.systemInstruction !== void 0) body.systemInstruction = config.systemInstruction;
|
|
821
|
-
if (config.tools !== void 0) body.tools = config.tools;
|
|
822
|
-
if (config.toolConfig !== void 0) body.toolConfig = config.toolConfig;
|
|
823
|
-
if (config.safetySettings !== void 0) body.safetySettings = config.safetySettings;
|
|
824
|
-
if (config.cachedContent !== void 0) body.cachedContent = config.cachedContent;
|
|
825
|
-
if (config.serviceTier !== void 0) body.serviceTier = config.serviceTier;
|
|
826
|
-
const gen = {};
|
|
827
|
-
if (config.temperature !== void 0) gen.temperature = config.temperature;
|
|
828
|
-
if (config.maxOutputTokens !== void 0) gen.maxOutputTokens = config.maxOutputTokens;
|
|
829
|
-
if (config.topP !== void 0) gen.topP = config.topP;
|
|
830
|
-
if (config.topK !== void 0) gen.topK = config.topK;
|
|
831
|
-
if (config.candidateCount !== void 0) gen.candidateCount = config.candidateCount;
|
|
832
|
-
if (config.stopSequences !== void 0) gen.stopSequences = config.stopSequences;
|
|
833
|
-
if (config.presencePenalty !== void 0) gen.presencePenalty = config.presencePenalty;
|
|
834
|
-
if (config.frequencyPenalty !== void 0) gen.frequencyPenalty = config.frequencyPenalty;
|
|
835
|
-
if (config.seed !== void 0) gen.seed = config.seed;
|
|
836
|
-
if (config.responseMimeType !== void 0) gen.responseMimeType = config.responseMimeType;
|
|
837
|
-
if (config.responseSchema !== void 0) gen.responseSchema = config.responseSchema;
|
|
838
|
-
if (config.responseJsonSchema !== void 0) gen.responseJsonSchema = config.responseJsonSchema;
|
|
839
|
-
if (config.responseModalities !== void 0) gen.responseModalities = config.responseModalities;
|
|
840
|
-
if (config.thinkingConfig !== void 0) gen.thinkingConfig = config.thinkingConfig;
|
|
841
|
-
const generationConfig = config;
|
|
842
|
-
if (generationConfig.minP !== void 0) gen.minP = generationConfig.minP;
|
|
843
|
-
if (generationConfig.repetitionPenalty !== void 0) gen.repetitionPenalty = generationConfig.repetitionPenalty;
|
|
844
|
-
if (Object.keys(gen).length > 0) body.generationConfig = gen;
|
|
845
|
-
return body;
|
|
846
|
-
}
|
|
847
|
-
function extractGoogleErrorMessage(errorText) {
|
|
848
|
-
if (!errorText) return "Unknown error";
|
|
849
|
-
try {
|
|
850
|
-
const parsed = JSON.parse(errorText);
|
|
851
|
-
if (parsed.error?.message) return parsed.error.message;
|
|
852
|
-
} catch {}
|
|
853
|
-
return errorText;
|
|
854
|
-
}
|
|
855
|
-
//#endregion
|
|
856
|
-
export { isThinkingPart as a, nextToolCallId as c, retainThoughtSignature as d, startTextOrThinkingBlock as f, hasMeaningfulGoogleContent as i, pushBlockEndEvent as l, convertMessages as n, mapStopReasonString as o, streamGoogleGenAI as p, convertTools as r, mapToolChoice as s, buildGoogleGenerateContentParams as t, pushToolCallEvents as u };
|