@xiaoso/dsh-tool-plus 0.1.0-beta.2 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.en.md +122 -0
  2. package/README.md +3 -2
  3. package/THIRD_PARTY_NOTICES.md +4 -35
  4. package/lib/abort-B7izMMna.mjs +51 -0
  5. package/lib/amazon-bedrock-D-shAr1X.mjs +917 -0
  6. package/lib/anthropic-BWsZFzU8.mjs +262 -0
  7. package/lib/anthropic-CMT1Qj1t.mjs +3115 -0
  8. package/lib/api-key-login-CpzSezsi.mjs +157 -0
  9. package/lib/aws-credentials-8Mgn5MAy.mjs +867 -0
  10. package/lib/azure-openai-responses-CNkHiLUh.mjs +274 -0
  11. package/lib/bun-shim.mjs +172 -7
  12. package/lib/bundled-references-CSuBco5p.mjs +55 -0
  13. package/lib/callback-server-BEFNf-jf.mjs +398 -0
  14. package/lib/client.js +110 -40
  15. package/lib/cursor-BIQfBC6V.mjs +135 -0
  16. package/lib/cursor-DNk6eXDd.mjs +5470 -0
  17. package/lib/cursor-MjSm74IJ.mjs +273 -0
  18. package/lib/device-code-OI_tQcmc.mjs +51 -0
  19. package/lib/devin-CldyPD8K.mjs +95 -0
  20. package/lib/devin-CzRpTUOA.mjs +114 -0
  21. package/lib/devin-DXOeNyU7.mjs +542 -0
  22. package/lib/error-B3bevhrs.mjs +1449 -0
  23. package/lib/event-stream-BDPi64mK.mjs +151 -0
  24. package/lib/github-copilot-4GaMtQmg.mjs +208 -0
  25. package/lib/github-copilot-B2HGSIJK.mjs +104 -0
  26. package/lib/gitlab-duo-akICyfeD.mjs +155 -0
  27. package/lib/gitlab-duo-workflow-CSEgYk3N.mjs +91 -0
  28. package/lib/google-antigravity-Dlkdhahq.mjs +198 -0
  29. package/lib/google-auth-DhqpOYrX.mjs +232 -0
  30. package/lib/google-gemini-cli-BG_LRdIV.mjs +850 -0
  31. package/lib/google-gemini-cli-zJhiOcND.mjs +211 -0
  32. package/lib/google-oauth-shared-CwNWYdmR.mjs +147 -0
  33. package/lib/google-shared-DzhCb9b0.mjs +760 -0
  34. package/lib/google-vertex-kscbkyRb.mjs +99 -0
  35. package/lib/google-zyDOG9x7.mjs +30 -0
  36. package/lib/identity-DnB63pS7.mjs +219 -0
  37. package/lib/idle-iterator-C6XbWcuB.mjs +366 -0
  38. package/lib/index.mjs +2865 -2745
  39. package/lib/kimi-DMVI-zrZ.mjs +236 -0
  40. package/lib/markit-M0jhziqU.mjs +1417 -0
  41. package/lib/minimax-code-BK-3eBbz.mjs +53 -0
  42. package/lib/models-CfT4coYT.mjs +104660 -0
  43. package/lib/ollama-BQMGPxtV.mjs +589 -0
  44. package/lib/openai-codex-CQL_kvYL.mjs +255 -0
  45. package/lib/openai-codex-responses-Cz-2qpC7.mjs +3144 -0
  46. package/lib/openai-completions-i8doczAk.mjs +1360 -0
  47. package/lib/openai-reasoning-fallback-DGVVLgd1.mjs +286 -0
  48. package/lib/openai-responses-DTkBiiy-.mjs +867 -0
  49. package/lib/opencode-DaJjcUbf.mjs +38 -0
  50. package/lib/perplexity-9b-RQ0mV.mjs +194 -0
  51. package/lib/proxy-ZtUcl6r5.mjs +190 -0
  52. package/lib/scrapers-CL-WgdU1.mjs +9198 -0
  53. package/lib/scrapers-CipyKyCH.mjs +9198 -0
  54. package/lib/scrapers-DGfrYhtw.mjs +9198 -0
  55. package/lib/scrapers-DSUPYoUF.mjs +9198 -0
  56. package/lib/scrapers-DZ2GXmp7.mjs +9198 -0
  57. package/lib/src-DTid0g4K.mjs +5001 -0
  58. package/lib/stream-C_RAoxJC.mjs +10236 -0
  59. package/lib/stream-markup-healing-2toHXWXM.mjs +491 -0
  60. package/lib/types/client/locales.d.ts +1 -1
  61. package/lib/types/config/fields.d.ts +1 -1
  62. package/lib/types/config/settings.d.ts +16 -2
  63. package/lib/types/tools/omp/sdk.d.ts +6 -0
  64. package/lib/types/tools/omp/tools/fetch.d.ts +2 -0
  65. package/lib/types/tools/omp/tools/read.d.ts +2 -8
  66. package/lib/types/tools/omp/utils/image-loading.d.ts +10 -36
  67. package/lib/types/tools/read/adapter/index.d.ts +4 -1
  68. package/lib/types/tools/read-image/index.d.ts +4 -0
  69. package/lib/types/tools/shared/bun-shim.d.ts +35 -3
  70. package/lib/types/tools/shared/image-bridge.d.ts +98 -0
  71. package/lib/types/tools/shared/omp-prompt.d.ts +4 -2
  72. package/lib/utils-2Wq2eATG.mjs +8119 -0
  73. package/lib/utils-BnX8O3vi.mjs +12303 -0
  74. package/lib/utils-CE8N8P4Q.mjs +12315 -0
  75. package/lib/utils-C_MrQ_0z.mjs +12301 -0
  76. package/lib/utils-D0Z1fyoO.mjs +12299 -0
  77. package/lib/utils-DppGHXQB.mjs +51 -0
  78. package/lib/utils-W8WdSBpX.mjs +12315 -0
  79. package/lib/vision-guard-BDD4OrKo.mjs +92 -0
  80. package/lib/wafer-DRU0EjZX.mjs +17 -0
  81. package/lib/xai-oauth-DPHDXKD7.mjs +446 -0
  82. package/lib/xiaomi-Dd87KSg5.mjs +157 -0
  83. package/lib/zai-CFeOt-53.mjs +203 -0
  84. package/lib/zip-D6QXpvaJ.mjs +1316 -0
  85. package/package.json +14 -10
  86. package/presets/package.json +1 -1
  87. package/presets/tool-plus-ptc/preset.yml +1 -1
  88. package/presets/tool-plus-standard/preset.yml +1 -1
@@ -0,0 +1,760 @@
1
+ import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
+ __installBunShim();
3
+ import { o as readSseJson } from "./src-DTid0g4K.mjs";
4
+ import { R as GoogleApiError, l as finalize, r as ValidationError, s as ProviderResponseError, t as ConfigurationError, v as AbortError } from "./error-B3bevhrs.mjs";
5
+ import { M as normalizeSchemaForCCA, N as normalizeSchemaForGoogle, a as normalizeSystemPrompts, k as toolWireSchema, m as transformMessages, w as renderDemotedThinking } from "./utils-2Wq2eATG.mjs";
6
+ import { t as calculateCost } from "./models-CfT4coYT.mjs";
7
+ import { o as shouldSendServiceTier, t as NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard-BDD4OrKo.mjs";
8
+ import { t as AssistantMessageEventStream } from "./event-stream-BDPi64mK.mjs";
9
+ import { scheduler } from "node:timers/promises";
10
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/google-shared.ts
11
+ /**
12
+ * Shared utilities for Google Generative AI and Google Cloud Code Assist providers.
13
+ */
14
+ /**
15
+ * Determines whether a streamed Gemini `Part` should be treated as "thinking".
16
+ *
17
+ * Protocol note (Gemini / Vertex AI thought signatures):
18
+ * - `thought: true` is the definitive marker for thinking content (thought summaries).
19
+ * - `thoughtSignature` is an encrypted representation of the model's internal thought process
20
+ * used to preserve reasoning context across multi-turn interactions.
21
+ * - `thoughtSignature` can appear on ANY part type (text, functionCall, etc.) - it does NOT
22
+ * indicate the part itself is thinking content.
23
+ * - For non-functionCall responses, the signature appears on the last part for context replay.
24
+ * - When persisting/replaying model outputs, signature-bearing parts must be preserved as-is;
25
+ * do not merge/move signatures across parts.
26
+ *
27
+ * See: https://ai.google.dev/gemini-api/docs/thought-signatures
28
+ */
29
+ function isThinkingPart(part) {
30
+ return part.thought === true;
31
+ }
32
+ /**
33
+ * Retain thought signatures during streaming.
34
+ *
35
+ * Some backends only send `thoughtSignature` on the first delta for a given part/block; later deltas may omit it.
36
+ * This helper preserves the last non-empty signature for the current block.
37
+ *
38
+ * Note: this does NOT merge or move signatures across distinct response parts. It only prevents
39
+ * a signature from being overwritten with `undefined` within the same streamed block.
40
+ */
41
+ function retainThoughtSignature(existing, incoming) {
42
+ if (typeof incoming === "string" && incoming.length > 0) return incoming;
43
+ return existing;
44
+ }
45
+ const base64SignaturePattern = /^[A-Za-z0-9+/]+={0,2}$/;
46
+ const SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
47
+ function isValidThoughtSignature(signature) {
48
+ if (!signature) return false;
49
+ if (signature.length % 4 !== 0) return false;
50
+ return base64SignaturePattern.test(signature);
51
+ }
52
+ /**
53
+ * Only keep signatures from the same provider/model and with valid base64.
54
+ */
55
+ function resolveThoughtSignature(isSameProviderAndModel, signature) {
56
+ return isSameProviderAndModel && isValidThoughtSignature(signature) ? signature : void 0;
57
+ }
58
+ function supportsFunctionPartId(model) {
59
+ if (model.api === "google-vertex") return false;
60
+ return model.id.startsWith("claude-") || model.api === "google-generative-ai" && isGemini3Model(model.id);
61
+ }
62
+ function getGeminiMajorVersion(modelId) {
63
+ const match = modelId.toLowerCase().match(/^gemini(?:-live)?-(\d+)/);
64
+ if (!match) return void 0;
65
+ return Number.parseInt(match[1], 10);
66
+ }
67
+ function supportsMultimodalFunctionResponse(modelId) {
68
+ const geminiMajorVersion = getGeminiMajorVersion(modelId);
69
+ if (geminiMajorVersion !== void 0) return geminiMajorVersion >= 3;
70
+ return true;
71
+ }
72
+ function isGemini3Model(modelId) {
73
+ return modelId.includes("gemini-3");
74
+ }
75
+ /**
76
+ * Convert internal messages to Gemini Content[] format.
77
+ */
78
+ function convertMessages(model, context) {
79
+ const contents = [];
80
+ const emittedToolCallNames = /* @__PURE__ */ new Map();
81
+ const normalizeToolCallId = (id) => {
82
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
83
+ };
84
+ const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
85
+ let pendingToolImageParts = [];
86
+ const flushPendingToolImages = () => {
87
+ if (pendingToolImageParts.length === 0) return;
88
+ contents.push({
89
+ role: "user",
90
+ parts: pendingToolImageParts
91
+ });
92
+ pendingToolImageParts = [];
93
+ };
94
+ for (const msg of transformedMessages) {
95
+ if (msg.role !== "toolResult") flushPendingToolImages();
96
+ if (msg.role === "user" || msg.role === "developer") {
97
+ if (typeof msg.content === "string") {
98
+ if (!msg.content || msg.content.trim() === "") continue;
99
+ contents.push({
100
+ role: "user",
101
+ parts: [{ text: msg.content.toWellFormed() }]
102
+ });
103
+ } else {
104
+ const supportsImages = model.input.includes("image");
105
+ const parts = [];
106
+ let omittedImages = false;
107
+ for (const item of msg.content) if (item.type === "text") {
108
+ const text = item.text.toWellFormed();
109
+ if (text.trim().length === 0) continue;
110
+ parts.push({ text });
111
+ } else if (supportsImages) parts.push({ inlineData: {
112
+ mimeType: item.mimeType,
113
+ data: item.data
114
+ } });
115
+ else omittedImages = true;
116
+ if (omittedImages) parts.push({ text: NON_VISION_IMAGE_PLACEHOLDER });
117
+ if (parts.length === 0) continue;
118
+ contents.push({
119
+ role: "user",
120
+ parts
121
+ });
122
+ }
123
+ } else if (msg.role === "assistant") {
124
+ const parts = [];
125
+ const isSameProviderAndModel = msg.provider === model.provider && msg.model === model.id;
126
+ const dropsUnsignedThinking = model.provider === "google-antigravity" && model.id.toLowerCase().includes("claude");
127
+ for (const block of msg.content) if (block.type === "text") {
128
+ if (!block.text || block.text.trim() === "") continue;
129
+ const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.textSignature);
130
+ parts.push({
131
+ text: block.text.toWellFormed(),
132
+ ...thoughtSignature && { thoughtSignature }
133
+ });
134
+ } else if (block.type === "thinking") {
135
+ if (!block.thinking || block.thinking.trim() === "") continue;
136
+ const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thinkingSignature);
137
+ if (dropsUnsignedThinking && !thoughtSignature) continue;
138
+ if (thoughtSignature) parts.push({
139
+ thought: true,
140
+ text: block.thinking.toWellFormed(),
141
+ thoughtSignature
142
+ });
143
+ else parts.push({ text: renderDemotedThinking(model.id, block.thinking) });
144
+ } else if (block.type === "toolCall") {
145
+ emittedToolCallNames.set(block.id, block.name);
146
+ const effectiveSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature) || (isGemini3Model(model.id) ? SKIP_THOUGHT_SIGNATURE : void 0);
147
+ const part = { functionCall: {
148
+ name: block.name,
149
+ args: block.arguments ?? {},
150
+ ...supportsFunctionPartId(model) ? { id: block.id } : {}
151
+ } };
152
+ if (model.provider === "google-vertex" && part?.functionCall?.id) delete part.functionCall.id;
153
+ if (effectiveSignature) part.thoughtSignature = effectiveSignature;
154
+ parts.push(part);
155
+ }
156
+ if (parts.length === 0) continue;
157
+ contents.push({
158
+ role: "model",
159
+ parts
160
+ });
161
+ } else if (msg.role === "toolResult") {
162
+ const supportsImages = model.input.includes("image");
163
+ const textResult = msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
164
+ const imageContent = supportsImages ? msg.content.filter((c) => c.type === "image") : [];
165
+ const omittedImages = !supportsImages && msg.content.some((c) => c.type === "image");
166
+ const hasText = textResult.length > 0;
167
+ const hasImages = imageContent.length > 0;
168
+ const modelSupportsMultimodalFunctionResponse = supportsMultimodalFunctionResponse(model.id);
169
+ const responseValue = omittedImages ? [hasText ? textResult.toWellFormed() : "", NON_VISION_IMAGE_PLACEHOLDER].filter(Boolean).join("\n") : hasText ? textResult.toWellFormed() : hasImages ? "(see attached image)" : "";
170
+ const imageParts = imageContent.map((imageBlock) => ({ inlineData: {
171
+ mimeType: imageBlock.mimeType,
172
+ data: imageBlock.data
173
+ } }));
174
+ const includeId = supportsFunctionPartId(model);
175
+ const functionResponsePart = { functionResponse: {
176
+ name: emittedToolCallNames.get(msg.toolCallId) ?? msg.toolName,
177
+ response: msg.isError ? { error: responseValue } : { output: responseValue },
178
+ ...hasImages && modelSupportsMultimodalFunctionResponse && { parts: imageParts },
179
+ ...includeId ? { id: msg.toolCallId } : {}
180
+ } };
181
+ if (model.provider === "google-vertex" && functionResponsePart.functionResponse?.id) delete functionResponsePart.functionResponse.id;
182
+ const lastContent = contents[contents.length - 1];
183
+ if (lastContent?.role === "user" && lastContent.parts?.some((p) => p.functionResponse)) lastContent.parts.push(functionResponsePart);
184
+ else contents.push({
185
+ role: "user",
186
+ parts: [functionResponsePart]
187
+ });
188
+ if (hasImages && !modelSupportsMultimodalFunctionResponse) pendingToolImageParts.push({ text: "Tool result image:" }, ...imageParts);
189
+ }
190
+ }
191
+ flushPendingToolImages();
192
+ return contents;
193
+ }
194
+ /**
195
+ * Convert tools to Gemini function declarations format.
196
+ *
197
+ * We prefer `parametersJsonSchema` (full JSON Schema: anyOf/oneOf/const/etc.).
198
+ *
199
+ * Claude models via Cloud Code Assist require the legacy `parameters` field; the API
200
+ * translates it into Anthropic's `input_schema`. When using that path, we sanitize the
201
+ * schema to remove Google-unsupported JSON Schema keywords.
202
+ */
203
+ function convertTools(tools, model) {
204
+ if (tools.length === 0) return void 0;
205
+ /**
206
+ * Claude models on Cloud Code Assist need the legacy `parameters` field;
207
+ * the API translates it into Anthropic's `input_schema`.
208
+ */
209
+ const useParameters = model.id.startsWith("claude-");
210
+ return [{ functionDeclarations: tools.map((tool) => ({
211
+ name: tool.name,
212
+ description: tool.description || "",
213
+ ...useParameters ? { parameters: normalizeSchemaForCCA(toolWireSchema(tool)) } : { parametersJsonSchema: normalizeSchemaForGoogle(toolWireSchema(tool)) }
214
+ })) }];
215
+ }
216
+ /**
217
+ * Map tool choice string to Gemini FunctionCallingConfigMode.
218
+ */
219
+ function mapToolChoice(choice) {
220
+ switch (choice) {
221
+ case "auto": return "AUTO";
222
+ case "none": return "NONE";
223
+ case "any": return "ANY";
224
+ default: return "AUTO";
225
+ }
226
+ }
227
+ /**
228
+ * Map Gemini FinishReason to our StopReason.
229
+ */
230
+ function mapStopReason(reason) {
231
+ switch (reason) {
232
+ case "STOP": return "stop";
233
+ case "MAX_TOKENS": return "length";
234
+ case "BLOCKLIST":
235
+ case "PROHIBITED_CONTENT":
236
+ case "SPII":
237
+ case "SAFETY":
238
+ case "IMAGE_SAFETY":
239
+ case "IMAGE_PROHIBITED_CONTENT":
240
+ case "IMAGE_RECITATION":
241
+ case "IMAGE_OTHER":
242
+ case "RECITATION":
243
+ case "FINISH_REASON_UNSPECIFIED":
244
+ case "OTHER":
245
+ case "LANGUAGE":
246
+ case "MALFORMED_FUNCTION_CALL":
247
+ case "UNEXPECTED_TOOL_CALL":
248
+ case "NO_IMAGE": return "error";
249
+ default: throw new ConfigurationError(`Unhandled stop reason: ${reason}`);
250
+ }
251
+ }
252
+ /**
253
+ * Map string finish reason to our StopReason (for raw API responses).
254
+ */
255
+ function mapStopReasonString(reason) {
256
+ switch (reason) {
257
+ case "STOP": return "stop";
258
+ case "MAX_TOKENS": return "length";
259
+ default: return "error";
260
+ }
261
+ }
262
+ /**
263
+ * Whether a completed Google assistant message carries content worth delivering.
264
+ *
265
+ * A tool call or any non-whitespace text counts as meaningful. An empty/whitespace-only
266
+ * text part — or thinking that never produced an answer — is the "empty response" failure:
267
+ * delivered as-is the agent loop has nothing to act on and silently halts, so the request
268
+ * must be retried instead of surfaced.
269
+ */
270
+ function hasMeaningfulGoogleContent(output) {
271
+ for (const block of output.content) {
272
+ if (block.type === "toolCall") return true;
273
+ if (block.type === "text" && block.text.trim().length > 0) return true;
274
+ }
275
+ return false;
276
+ }
277
+ /** Wipe a streamed message between empty-response retries so the next attempt starts clean. */
278
+ function resetGoogleStreamOutputForRetry(output) {
279
+ output.content = [];
280
+ output.usage = {
281
+ input: 0,
282
+ output: 0,
283
+ cacheRead: 0,
284
+ cacheWrite: 0,
285
+ totalTokens: 0,
286
+ cost: {
287
+ input: 0,
288
+ output: 0,
289
+ cacheRead: 0,
290
+ cacheWrite: 0,
291
+ total: 0
292
+ }
293
+ };
294
+ output.stopReason = "stop";
295
+ output.errorMessage = void 0;
296
+ output.timestamp = Date.now();
297
+ }
298
+ /**
299
+ * Module-local counter for generating unique tool call IDs across Google providers.
300
+ * Shared so that a single monotonically-increasing sequence is used regardless of which
301
+ * Google API surface produced the stream — purely for uniqueness, not ordering semantics.
302
+ */
303
+ let toolCallCounter = 0;
304
+ function nextToolCallId(name) {
305
+ return `${name}_${Date.now()}_${++toolCallCounter}`;
306
+ }
307
+ /**
308
+ * Push the appropriate `text_end` / `thinking_end` event for the given block.
309
+ * Shared between the SDK-backed stream consumer and the gemini-cli SSE consumer so
310
+ * the end-of-block event shape stays in lockstep.
311
+ */
312
+ function pushBlockEndEvent(block, contentIndex, output, stream) {
313
+ if (block.type === "text") stream.push({
314
+ type: "text_end",
315
+ contentIndex,
316
+ content: block.text,
317
+ partial: output
318
+ });
319
+ else stream.push({
320
+ type: "thinking_end",
321
+ contentIndex,
322
+ content: block.thinking,
323
+ partial: output
324
+ });
325
+ }
326
+ /**
327
+ * Push the three lifecycle events (`toolcall_start` / `toolcall_delta` / `toolcall_end`) for a
328
+ * fully-assembled `ToolCall`. Caller is responsible for appending the toolCall to `output.content`
329
+ * before invoking — this helper does not mutate `output.content`.
330
+ */
331
+ function pushToolCallEvents(toolCall, contentIndex, output, stream) {
332
+ stream.push({
333
+ type: "toolcall_start",
334
+ contentIndex,
335
+ partial: output
336
+ });
337
+ stream.push({
338
+ type: "toolcall_delta",
339
+ contentIndex,
340
+ delta: JSON.stringify(toolCall.arguments),
341
+ partial: output
342
+ });
343
+ stream.push({
344
+ type: "toolcall_end",
345
+ contentIndex,
346
+ toolCall,
347
+ partial: output
348
+ });
349
+ }
350
+ function startTextOrThinkingBlock(isThinking, output, stream, onBeforeStartEvent) {
351
+ const block = isThinking ? {
352
+ type: "thinking",
353
+ thinking: "",
354
+ thinkingSignature: void 0
355
+ } : {
356
+ type: "text",
357
+ text: ""
358
+ };
359
+ output.content.push(block);
360
+ onBeforeStartEvent?.();
361
+ const contentIndex = output.content.length - 1;
362
+ if (isThinking) stream.push({
363
+ type: "thinking_start",
364
+ contentIndex,
365
+ partial: output
366
+ });
367
+ else stream.push({
368
+ type: "text_start",
369
+ contentIndex,
370
+ partial: output
371
+ });
372
+ return block;
373
+ }
374
+ /**
375
+ * Drives the chunked `generateContentStream` iterator into an `AssistantMessage` and
376
+ * the corresponding `AssistantMessageEventStream`. Shared between `streamGoogle` and
377
+ * `streamGoogleVertex` — every observable event order and stop-reason rule is preserved.
378
+ *
379
+ * The caller still owns: `output` construction, timing fields (`duration`/`ttft`),
380
+ * `rawRequestDump`, the `client.models.generateContentStream(params)` call itself,
381
+ * pushing `start`/`done`/`error` events, and the surrounding try/catch that translates
382
+ * thrown errors into `output.stopReason`/`errorMessage`.
383
+ *
384
+ * This helper handles: the chunk loop, currentBlock flush transitions, usage metadata
385
+ * decoding (`calculateCost` included), tool-call id collision avoidance, finish-reason
386
+ * mapping, and the abort/stop-reason post-checks that re-throw to bubble into the
387
+ * caller's catch.
388
+ */
389
+ async function consumeGoogleStream(args) {
390
+ const { googleStream, output, stream, model, options, retainTextSignature, onFirstToken } = args;
391
+ const blocks = output.content;
392
+ const blockIndex = () => blocks.length - 1;
393
+ let currentBlock = null;
394
+ let firstTokenSeen = false;
395
+ let sawFinishReason = false;
396
+ const flushCurrent = () => {
397
+ if (!currentBlock) return;
398
+ pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
399
+ };
400
+ for await (const chunk of googleStream) {
401
+ if (chunk.error) {
402
+ const message = `Google API stream error: ${chunk.error.message || chunk.error.status || "unknown error"}`;
403
+ throw typeof chunk.error.code === "number" && chunk.error.code >= 400 ? new GoogleApiError(message, chunk.error.code) : new ProviderResponseError(message, {
404
+ provider: model.provider,
405
+ kind: "output"
406
+ });
407
+ }
408
+ if (!chunk.candidates?.length && chunk.promptFeedback?.blockReason) {
409
+ const detail = chunk.promptFeedback.blockReasonMessage;
410
+ throw new ProviderResponseError(`Request blocked by Google (${chunk.promptFeedback.blockReason})${detail ? `: ${detail}` : ""}`, {
411
+ provider: model.provider,
412
+ kind: "content-blocked"
413
+ });
414
+ }
415
+ const candidate = chunk.candidates?.[0];
416
+ if (candidate?.content?.parts) for (const part of candidate.content.parts) {
417
+ if (part.text !== void 0 && part.text !== "") {
418
+ if (!firstTokenSeen) {
419
+ firstTokenSeen = true;
420
+ onFirstToken?.();
421
+ }
422
+ const isThinking = isThinkingPart(part);
423
+ if (!currentBlock || isThinking && currentBlock.type !== "thinking" || !isThinking && currentBlock.type !== "text") {
424
+ flushCurrent();
425
+ currentBlock = startTextOrThinkingBlock(isThinking, output, stream);
426
+ }
427
+ if (currentBlock.type === "thinking") {
428
+ currentBlock.thinking += part.text;
429
+ currentBlock.thinkingSignature = retainThoughtSignature(currentBlock.thinkingSignature, part.thoughtSignature);
430
+ stream.push({
431
+ type: "thinking_delta",
432
+ contentIndex: blockIndex(),
433
+ delta: part.text,
434
+ partial: output
435
+ });
436
+ } else {
437
+ currentBlock.text += part.text;
438
+ if (retainTextSignature) currentBlock.textSignature = retainThoughtSignature(currentBlock.textSignature, part.thoughtSignature);
439
+ stream.push({
440
+ type: "text_delta",
441
+ contentIndex: blockIndex(),
442
+ delta: part.text,
443
+ partial: output
444
+ });
445
+ }
446
+ } else if (part.text === "" && part.thoughtSignature && currentBlock && !part.functionCall) {
447
+ if (currentBlock.type === "thinking") currentBlock.thinkingSignature = retainThoughtSignature(currentBlock.thinkingSignature, part.thoughtSignature);
448
+ else if (retainTextSignature) currentBlock.textSignature = retainThoughtSignature(currentBlock.textSignature, part.thoughtSignature);
449
+ }
450
+ if (part.functionCall) {
451
+ if (currentBlock) {
452
+ flushCurrent();
453
+ currentBlock = null;
454
+ }
455
+ const providedId = part.functionCall.id;
456
+ const toolCall = {
457
+ type: "toolCall",
458
+ id: !providedId || output.content.some((b) => b.type === "toolCall" && b.id === providedId) ? nextToolCallId(part.functionCall.name || "tool") : providedId,
459
+ name: part.functionCall.name || "",
460
+ arguments: part.functionCall.args ?? {},
461
+ ...part.thoughtSignature && { thoughtSignature: part.thoughtSignature }
462
+ };
463
+ output.content.push(toolCall);
464
+ pushToolCallEvents(toolCall, blockIndex(), output, stream);
465
+ }
466
+ }
467
+ if (candidate?.finishReason) {
468
+ sawFinishReason = true;
469
+ const mapped = mapStopReason(candidate.finishReason);
470
+ if ((mapped === "stop" || mapped === "length") && output.content.some((b) => b.type === "toolCall")) output.stopReason = "toolUse";
471
+ else {
472
+ output.stopReason = mapped;
473
+ if (mapped === "error") output.errorMessage = `Generation failed with finish reason: ${candidate.finishReason}`;
474
+ }
475
+ }
476
+ if (chunk.usageMetadata) {
477
+ const cachedTokens = chunk.usageMetadata.cachedContentTokenCount || 0;
478
+ const thinkingTokens = chunk.usageMetadata.thoughtsTokenCount || 0;
479
+ output.usage = {
480
+ input: (chunk.usageMetadata.promptTokenCount || 0) - cachedTokens,
481
+ output: (chunk.usageMetadata.candidatesTokenCount || 0) + thinkingTokens,
482
+ cacheRead: cachedTokens,
483
+ cacheWrite: 0,
484
+ totalTokens: chunk.usageMetadata.totalTokenCount || 0,
485
+ ...thinkingTokens > 0 ? { reasoningTokens: thinkingTokens } : {},
486
+ cost: {
487
+ input: 0,
488
+ output: 0,
489
+ cacheRead: 0,
490
+ cacheWrite: 0,
491
+ total: 0
492
+ }
493
+ };
494
+ calculateCost(model, output.usage);
495
+ }
496
+ }
497
+ flushCurrent();
498
+ if (options?.signal?.aborted) throw new AbortError();
499
+ if (!sawFinishReason) throw new ProviderResponseError("Google API stream ended without a finish reason (connection dropped or response truncated)", {
500
+ provider: model.provider,
501
+ kind: "incomplete-stream"
502
+ });
503
+ if (output.stopReason === "aborted" || output.stopReason === "error") throw new ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
504
+ provider: model.provider,
505
+ kind: "output"
506
+ });
507
+ }
508
+ /**
509
+ * Build the `GenerateContentParameters` payload for the public Gemini API and Vertex AI.
510
+ * Both surfaces accept the same `GenerateContentConfig` shape — every numeric/string knob,
511
+ * tool-config, thinking-config, and system-instruction conversion is identical.
512
+ *
513
+ * `google-gemini-cli` is NOT routed through here: its `CloudCodeAssistRequest` body has a
514
+ * distinct top-level shape (project/request/requestType) and a different thinking-config
515
+ * placement on `generationConfig`.
516
+ */
517
+ function buildGoogleGenerateContentParams(model, context, options) {
518
+ const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
519
+ const contents = convertMessages(model, context);
520
+ const generationConfig = {};
521
+ if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
522
+ if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
523
+ if (options.topP !== void 0) generationConfig.topP = options.topP;
524
+ if (options.topK !== void 0) generationConfig.topK = options.topK;
525
+ if (options.minP !== void 0) generationConfig.minP = options.minP;
526
+ if (options.presencePenalty !== void 0) generationConfig.presencePenalty = options.presencePenalty;
527
+ if (options.repetitionPenalty !== void 0) generationConfig.repetitionPenalty = options.repetitionPenalty;
528
+ const config = {
529
+ ...Object.keys(generationConfig).length > 0 && generationConfig,
530
+ ...systemPrompts.length > 0 && { systemInstruction: { parts: systemPrompts.map((text) => ({ text })) } },
531
+ ...context.tools && context.tools.length > 0 && { tools: convertTools(context.tools, model) }
532
+ };
533
+ if (model.provider === "google" && shouldSendServiceTier(options.serviceTier, model.provider)) config.serviceTier = options.serviceTier;
534
+ if (context.tools && context.tools.length > 0 && options.toolChoice) {
535
+ const choice = options.toolChoice;
536
+ if (typeof choice === "string") {
537
+ const mode = mapToolChoice(choice);
538
+ if (mode !== "AUTO") config.toolConfig = { functionCallingConfig: { mode } };
539
+ } else config.toolConfig = { functionCallingConfig: {
540
+ mode: "ANY",
541
+ allowedFunctionNames: [...choice.allowedFunctionNames]
542
+ } };
543
+ } else config.toolConfig = void 0;
544
+ const thinking = options.thinking;
545
+ if (thinking && model.reasoning && (thinking.enabled || thinking.level !== void 0 || thinking.budgetTokens !== void 0)) {
546
+ const cfg = { includeThoughts: thinking.enabled && !options.hideThinkingSummary };
547
+ if (thinking.level !== void 0) cfg.thinkingLevel = thinking.level;
548
+ else if (thinking.budgetTokens !== void 0) cfg.thinkingBudget = thinking.budgetTokens;
549
+ config.thinkingConfig = cfg;
550
+ }
551
+ if (options.signal) {
552
+ if (options.signal.aborted) throw new AbortError("Request aborted");
553
+ config.abortSignal = options.signal;
554
+ }
555
+ if (options.cachedContent !== void 0) {
556
+ if (options.cachedContent.trim().length === 0) throw new ValidationError("cachedContent must not be blank");
557
+ const incompatibleFields = [
558
+ config.systemInstruction !== void 0 && "systemInstruction",
559
+ config.tools !== void 0 && "tools",
560
+ config.toolConfig !== void 0 && "toolConfig"
561
+ ].filter((field) => Boolean(field));
562
+ if (incompatibleFields.length > 0) throw new ValidationError(`cachedContent cannot be combined with request-level ${incompatibleFields.join(", ")}`);
563
+ config.cachedContent = options.cachedContent;
564
+ }
565
+ return {
566
+ model: model.id,
567
+ contents,
568
+ config
569
+ };
570
+ }
571
+ function streamGoogleGenAI(args) {
572
+ const { model, options, api, retainTextSignature, prepare } = args;
573
+ const stream = new AssistantMessageEventStream();
574
+ (async () => {
575
+ const startTime = performance.now();
576
+ let firstTokenTime;
577
+ const output = {
578
+ role: "assistant",
579
+ content: [],
580
+ api,
581
+ provider: model.provider,
582
+ model: model.id,
583
+ usage: {
584
+ input: 0,
585
+ output: 0,
586
+ cacheRead: 0,
587
+ cacheWrite: 0,
588
+ totalTokens: 0,
589
+ cost: {
590
+ input: 0,
591
+ output: 0,
592
+ cacheRead: 0,
593
+ cacheWrite: 0,
594
+ total: 0
595
+ }
596
+ },
597
+ stopReason: "stop",
598
+ timestamp: Date.now()
599
+ };
600
+ let rawRequestDump;
601
+ try {
602
+ const plan = await prepare();
603
+ let params = plan.params;
604
+ const replacement = await options?.onPayload?.(params, model);
605
+ if (replacement !== void 0) params = replacement;
606
+ rawRequestDump = {
607
+ provider: model.provider,
608
+ api: output.api,
609
+ model: model.id,
610
+ method: "POST",
611
+ url: plan.url,
612
+ body: params,
613
+ headers: plan.headers
614
+ };
615
+ const bodyJson = JSON.stringify(paramsToWireBody(params));
616
+ const fetchImpl = plan.fetch ?? options?.fetch ?? globalThis.fetch.bind(globalThis);
617
+ const openStreamAt = async (requestUrl) => {
618
+ const response = await fetchImpl(requestUrl, {
619
+ method: "POST",
620
+ headers: {
621
+ ...plan.headers,
622
+ "Content-Type": "application/json",
623
+ Accept: "text/event-stream"
624
+ },
625
+ body: bodyJson,
626
+ signal: options?.signal
627
+ });
628
+ if (!response.ok) {
629
+ const errorText = await response.text().catch(() => "");
630
+ throw new GoogleApiError(`Google API error (${response.status}): ${extractGoogleErrorMessage(errorText)}`, response.status, { headers: response.headers });
631
+ }
632
+ if (!response.body) throw new ProviderResponseError("Google API returned an empty response body", {
633
+ provider: model.provider,
634
+ kind: "empty-body"
635
+ });
636
+ return response.body;
637
+ };
638
+ const openStream = async () => {
639
+ if (!plan.fallbackUrl) return openStreamAt(plan.url);
640
+ try {
641
+ return await openStreamAt(plan.url);
642
+ } catch (error) {
643
+ if (error instanceof GoogleApiError && error.status === 404) return openStreamAt(plan.fallbackUrl);
644
+ throw error;
645
+ }
646
+ };
647
+ let body = await openStream();
648
+ stream.push({
649
+ type: "start",
650
+ partial: output
651
+ });
652
+ for (let emptyAttempt = 0;; emptyAttempt++) {
653
+ await consumeGoogleStream({
654
+ googleStream: readSseJson(body, options?.signal, (event) => options?.onSseEvent?.({
655
+ event: event.event,
656
+ data: event.data,
657
+ raw: [...event.raw]
658
+ }, model)),
659
+ output,
660
+ stream,
661
+ model,
662
+ options,
663
+ retainTextSignature,
664
+ onFirstToken: () => {
665
+ firstTokenTime = performance.now();
666
+ }
667
+ });
668
+ if (output.stopReason !== "stop" || hasMeaningfulGoogleContent(output) || options?.acceptEmptyResponse === true) break;
669
+ if (emptyAttempt >= 2) throw new ProviderResponseError(`Google API returned an empty response (finishReason STOP with no content) after 3 attempts`, {
670
+ provider: model.provider,
671
+ kind: "empty-body"
672
+ });
673
+ try {
674
+ await scheduler.wait(500 * 2 ** emptyAttempt, { signal: options?.signal });
675
+ } catch {
676
+ throw new AbortError();
677
+ }
678
+ resetGoogleStreamOutputForRetry(output);
679
+ body = await openStream();
680
+ }
681
+ output.duration = performance.now() - startTime;
682
+ if (firstTokenTime) output.ttft = firstTokenTime - startTime;
683
+ stream.push({
684
+ type: "done",
685
+ reason: output.stopReason,
686
+ message: output
687
+ });
688
+ stream.end();
689
+ } catch (error) {
690
+ const result = await finalize(error, {
691
+ api: model.api,
692
+ signal: options?.signal,
693
+ rawRequestDump
694
+ });
695
+ output.stopReason = result.stopReason;
696
+ output.errorStatus = result.status;
697
+ output.errorId = result.id;
698
+ output.errorMessage = result.message;
699
+ output.duration = performance.now() - startTime;
700
+ if (firstTokenTime) output.ttft = firstTokenTime - startTime;
701
+ stream.push({
702
+ type: "error",
703
+ reason: output.stopReason,
704
+ error: output
705
+ });
706
+ stream.end();
707
+ }
708
+ })();
709
+ return stream;
710
+ }
711
+ /**
712
+ * Lift the SDK's `params.config` fields out of `config` and place them where the
713
+ * Gemini / Vertex AI REST API expects them on the request body. Mirrors the
714
+ * generateContentParametersTo{Mldev,Vertex} transformation in @google/genai
715
+ * for the subset of fields this codebase actually sets.
716
+ *
717
+ * `abortSignal` is intentionally dropped — the SDK propagates it via `fetch.signal`,
718
+ * which our caller already wires up through `options.signal`.
719
+ */
720
+ function paramsToWireBody(params) {
721
+ const body = { contents: params.contents };
722
+ const config = params.config;
723
+ if (!config) return body;
724
+ if (config.systemInstruction !== void 0) body.systemInstruction = config.systemInstruction;
725
+ if (config.tools !== void 0) body.tools = config.tools;
726
+ if (config.toolConfig !== void 0) body.toolConfig = config.toolConfig;
727
+ if (config.safetySettings !== void 0) body.safetySettings = config.safetySettings;
728
+ if (config.cachedContent !== void 0) body.cachedContent = config.cachedContent;
729
+ if (config.serviceTier !== void 0) body.serviceTier = config.serviceTier;
730
+ const gen = {};
731
+ if (config.temperature !== void 0) gen.temperature = config.temperature;
732
+ if (config.maxOutputTokens !== void 0) gen.maxOutputTokens = config.maxOutputTokens;
733
+ if (config.topP !== void 0) gen.topP = config.topP;
734
+ if (config.topK !== void 0) gen.topK = config.topK;
735
+ if (config.candidateCount !== void 0) gen.candidateCount = config.candidateCount;
736
+ if (config.stopSequences !== void 0) gen.stopSequences = config.stopSequences;
737
+ if (config.presencePenalty !== void 0) gen.presencePenalty = config.presencePenalty;
738
+ if (config.frequencyPenalty !== void 0) gen.frequencyPenalty = config.frequencyPenalty;
739
+ if (config.seed !== void 0) gen.seed = config.seed;
740
+ if (config.responseMimeType !== void 0) gen.responseMimeType = config.responseMimeType;
741
+ if (config.responseSchema !== void 0) gen.responseSchema = config.responseSchema;
742
+ if (config.responseJsonSchema !== void 0) gen.responseJsonSchema = config.responseJsonSchema;
743
+ if (config.responseModalities !== void 0) gen.responseModalities = config.responseModalities;
744
+ if (config.thinkingConfig !== void 0) gen.thinkingConfig = config.thinkingConfig;
745
+ const generationConfig = config;
746
+ if (generationConfig.minP !== void 0) gen.minP = generationConfig.minP;
747
+ if (generationConfig.repetitionPenalty !== void 0) gen.repetitionPenalty = generationConfig.repetitionPenalty;
748
+ if (Object.keys(gen).length > 0) body.generationConfig = gen;
749
+ return body;
750
+ }
751
+ function extractGoogleErrorMessage(errorText) {
752
+ if (!errorText) return "Unknown error";
753
+ try {
754
+ const parsed = JSON.parse(errorText);
755
+ if (parsed.error?.message) return parsed.error.message;
756
+ } catch {}
757
+ return errorText;
758
+ }
759
+ //#endregion
760
+ 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 };