@fleetagent/pi-ai 0.1.8 → 0.2.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -24
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -1
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.generated.d.ts +225 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +227 -2
- package/dist/image-models.generated.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +37 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +6120 -2593
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +5347 -2110
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +259 -9
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +6 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +4 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +5 -4
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +303 -330
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -4
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +492 -499
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +4 -2
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +52 -35
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/faux.d.ts +19 -22
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +106 -70
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +7 -4
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +24 -5
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +139 -140
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +3 -7
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +216 -219
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +4 -7
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +214 -218
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts.map +1 -1
- package/dist/providers/images/openrouter.js +37 -27
- package/dist/providers/images/openrouter.js.map +1 -1
- package/dist/providers/mistral.d.ts +7 -6
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +287 -265
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +8 -5
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +493 -391
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +7 -10
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +670 -664
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts +5 -4
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +390 -360
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +5 -5
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +134 -109
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/simple-options.d.ts +4 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +112 -137
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/stream.d.ts +0 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +0 -1
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +91 -63
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/error-body.d.ts.map +1 -1
- package/dist/utils/error-body.js +15 -4
- package/dist/utils/error-body.js.map +1 -1
- package/dist/utils/estimate.d.ts +4 -0
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +3 -1
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/headers.d.ts +3 -0
- package/dist/utils/headers.d.ts.map +1 -1
- package/dist/utils/headers.js +21 -0
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +17 -19
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/oauth/anthropic.d.ts +4 -11
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +100 -117
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts.map +1 -1
- package/dist/utils/oauth/device-code.js +51 -38
- package/dist/utils/oauth/device-code.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts +6 -9
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +47 -42
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -17
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +27 -23
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -1
- package/dist/utils/oauth/oauth-page.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +7 -12
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +72 -89
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/pkce.d.ts +5 -4
- package/dist/utils/oauth/pkce.d.ts.map +1 -1
- package/dist/utils/oauth/pkce.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +7 -2
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/provider-retry.d.ts +16 -0
- package/dist/utils/provider-retry.d.ts.map +1 -0
- package/dist/utils/provider-retry.js +94 -0
- package/dist/utils/provider-retry.js.map +1 -0
- package/dist/utils/retry.d.ts +14 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +84 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +5 -4
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +102 -99
- package/dist/utils/validation.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAKX,mBAAmB,EACnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAW9D,MAAM,WAAW,aAAc,SAAQ,aAAa;IACnD,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,EAAE;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,mBAAmB,CAAC;KAC5B,CAAC;CACF;AAKD,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,sBAAsB,EAAE,aAAa,CAoO9E,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,sBAAsB,EAAE,mBAAmB,CAoC1F,CAAC","sourcesContent":["import {\n\ttype GenerateContentConfig,\n\ttype GenerateContentParameters,\n\tGoogleGenAI,\n\ttype ThinkingConfig,\n} from \"@google/genai\";\nimport { getEnvApiKey } from \"../env-api-keys.ts\";\nimport { calculateCost, clampThinkingLevel } from \"../models.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n\tTextContent,\n\tThinkingBudgets,\n\tThinkingContent,\n\tThinkingLevel,\n\tToolCall,\n} from \"../types.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.ts\";\nimport type { GoogleThinkingLevel } from \"./google-shared.ts\";\nimport {\n\tconvertMessages,\n\tconvertTools,\n\tisThinkingPart,\n\tmapStopReason,\n\tmapToolChoice,\n\tretainThoughtSignature,\n} from \"./google-shared.ts\";\nimport { buildBaseOptions } from \"./simple-options.ts\";\n\nexport interface GoogleOptions extends StreamOptions {\n\ttoolChoice?: \"auto\" | \"none\" | \"any\";\n\tthinking?: {\n\t\tenabled: boolean;\n\t\tbudgetTokens?: number; // -1 for dynamic, 0 to disable\n\t\tlevel?: GoogleThinkingLevel;\n\t};\n}\n\n// Counter for generating unique tool call IDs\nlet toolCallCounter = 0;\n\nexport const streamGoogle: StreamFunction<\"google-generative-ai\", GoogleOptions> = (\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions?: GoogleOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"google-generative-ai\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider) || \"\";\n\t\t\tconst client = createClient(model, apiKey, options?.headers);\n\t\t\tlet params = buildParams(model, context, options);\n\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\tif (nextParams !== undefined) {\n\t\t\t\tparams = nextParams as GenerateContentParameters;\n\t\t\t}\n\t\t\tconst googleStream = await client.models.generateContentStream(params);\n\n\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\tlet currentBlock: TextContent | ThinkingContent | null = null;\n\t\t\tconst blocks = output.content;\n\t\t\tconst blockIndex = () => blocks.length - 1;\n\t\t\tfor await (const chunk of googleStream) {\n\t\t\t\t// @google/genai documents GenerateContentResponse.responseId as an output-only field\n\t\t\t\t// used to identify each response. Keep the first non-empty one from the stream.\n\t\t\t\toutput.responseId ||= chunk.responseId;\n\t\t\t\tconst candidate = chunk.candidates?.[0];\n\t\t\t\tif (candidate?.content?.parts) {\n\t\t\t\t\tfor (const part of candidate.content.parts) {\n\t\t\t\t\t\tif (part.text !== undefined) {\n\t\t\t\t\t\t\tconst isThinking = isThinkingPart(part);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!currentBlock ||\n\t\t\t\t\t\t\t\t(isThinking && currentBlock.type !== \"thinking\") ||\n\t\t\t\t\t\t\t\t(!isThinking && currentBlock.type !== \"text\")\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tif (currentBlock) {\n\t\t\t\t\t\t\t\t\tif (currentBlock.type === \"text\") {\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: blocks.length - 1,\n\t\t\t\t\t\t\t\t\t\t\tcontent: currentBlock.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\t\t\t\t\tcontent: currentBlock.thinking,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (isThinking) {\n\t\t\t\t\t\t\t\t\tcurrentBlock = { type: \"thinking\", thinking: \"\", thinkingSignature: undefined };\n\t\t\t\t\t\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tcurrentBlock = { type: \"text\", text: \"\" };\n\t\t\t\t\t\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (currentBlock.type === \"thinking\") {\n\t\t\t\t\t\t\t\tcurrentBlock.thinking += part.text;\n\t\t\t\t\t\t\t\tcurrentBlock.thinkingSignature = retainThoughtSignature(\n\t\t\t\t\t\t\t\t\tcurrentBlock.thinkingSignature,\n\t\t\t\t\t\t\t\t\tpart.thoughtSignature,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\t\t\tdelta: part.text,\n\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcurrentBlock.text += part.text;\n\t\t\t\t\t\t\t\tcurrentBlock.textSignature = retainThoughtSignature(\n\t\t\t\t\t\t\t\t\tcurrentBlock.textSignature,\n\t\t\t\t\t\t\t\t\tpart.thoughtSignature,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\t\t\tdelta: part.text,\n\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (part.functionCall) {\n\t\t\t\t\t\t\tif (currentBlock) {\n\t\t\t\t\t\t\t\tif (currentBlock.type === \"text\") {\n\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\t\t\t\tcontent: currentBlock.text,\n\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\t\t\t\tcontent: currentBlock.thinking,\n\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcurrentBlock = null;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Generate unique ID if not provided or if it's a duplicate\n\t\t\t\t\t\t\tconst providedId = part.functionCall.id;\n\t\t\t\t\t\t\tconst needsNewId =\n\t\t\t\t\t\t\t\t!providedId || output.content.some((b) => b.type === \"toolCall\" && b.id === providedId);\n\t\t\t\t\t\t\tconst toolCallId = needsNewId\n\t\t\t\t\t\t\t\t? `${part.functionCall.name}_${Date.now()}_${++toolCallCounter}`\n\t\t\t\t\t\t\t\t: providedId;\n\n\t\t\t\t\t\t\tconst toolCall: ToolCall = {\n\t\t\t\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\t\t\t\tid: toolCallId,\n\t\t\t\t\t\t\t\tname: part.functionCall.name || \"\",\n\t\t\t\t\t\t\t\targuments: (part.functionCall.args as Record<string, any>) ?? {},\n\t\t\t\t\t\t\t\t...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\toutput.content.push(toolCall);\n\t\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\t\tdelta: JSON.stringify(toolCall.arguments),\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tstream.push({ type: \"toolcall_end\", contentIndex: blockIndex(), toolCall, partial: output });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (candidate?.finishReason) {\n\t\t\t\t\toutput.stopReason = mapStopReason(candidate.finishReason);\n\t\t\t\t\tif (output.content.some((b) => b.type === \"toolCall\")) {\n\t\t\t\t\t\toutput.stopReason = \"toolUse\";\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (chunk.usageMetadata) {\n\t\t\t\t\toutput.usage = {\n\t\t\t\t\t\tinput:\n\t\t\t\t\t\t\t(chunk.usageMetadata.promptTokenCount || 0) - (chunk.usageMetadata.cachedContentTokenCount || 0),\n\t\t\t\t\t\toutput:\n\t\t\t\t\t\t\t(chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),\n\t\t\t\t\t\tcacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,\n\t\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\t\ttotalTokens: chunk.usageMetadata.totalTokenCount || 0,\n\t\t\t\t\t\tcost: {\n\t\t\t\t\t\t\tinput: 0,\n\t\t\t\t\t\t\toutput: 0,\n\t\t\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentBlock) {\n\t\t\t\tif (currentBlock.type === \"text\") {\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tcontent: currentBlock.text,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tcontent: currentBlock.thinking,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(\"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\t// Remove internal index property used during streaming\n\t\t\tfor (const block of output.content) {\n\t\t\t\tif (\"index\" in block) {\n\t\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t}\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimpleGoogle: StreamFunction<\"google-generative-ai\", SimpleStreamOptions> = (\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, context, options, apiKey);\n\tif (!options?.reasoning) {\n\t\treturn streamGoogle(model, context, { ...base, thinking: { enabled: false } } satisfies GoogleOptions);\n\t}\n\n\tconst clampedReasoning = clampThinkingLevel(model, options.reasoning);\n\tconst effort = (clampedReasoning === \"off\" ? \"high\" : clampedReasoning) as ClampedThinkingLevel;\n\tconst googleModel = model as Model<\"google-generative-ai\">;\n\n\tif (isGemini3ProModel(googleModel) || isGemini3FlashModel(googleModel) || isGemma4Model(googleModel)) {\n\t\treturn streamGoogle(model, context, {\n\t\t\t...base,\n\t\t\tthinking: {\n\t\t\t\tenabled: true,\n\t\t\t\tlevel: getThinkingLevel(effort, googleModel),\n\t\t\t},\n\t\t} satisfies GoogleOptions);\n\t}\n\n\treturn streamGoogle(model, context, {\n\t\t...base,\n\t\tthinking: {\n\t\t\tenabled: true,\n\t\t\tbudgetTokens: getGoogleBudget(googleModel, effort, options.thinkingBudgets),\n\t\t},\n\t} satisfies GoogleOptions);\n};\n\nfunction createClient(\n\tmodel: Model<\"google-generative-ai\">,\n\tapiKey?: string,\n\toptionsHeaders?: Record<string, string>,\n): GoogleGenAI {\n\tconst httpOptions: { baseUrl?: string; apiVersion?: string; headers?: Record<string, string> } = {};\n\tif (model.baseUrl) {\n\t\thttpOptions.baseUrl = model.baseUrl;\n\t\thttpOptions.apiVersion = \"\"; // baseUrl already includes version path, don't append\n\t}\n\tif (model.headers || optionsHeaders) {\n\t\thttpOptions.headers = { ...model.headers, ...optionsHeaders };\n\t}\n\n\treturn new GoogleGenAI({\n\t\tapiKey,\n\t\thttpOptions: Object.keys(httpOptions).length > 0 ? httpOptions : undefined,\n\t});\n}\n\nfunction buildParams(\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions: GoogleOptions = {},\n): GenerateContentParameters {\n\tconst contents = convertMessages(model, context);\n\n\tconst generationConfig: GenerateContentConfig = {};\n\tif (options.temperature !== undefined) {\n\t\tgenerationConfig.temperature = options.temperature;\n\t}\n\tif (options.maxTokens !== undefined) {\n\t\tgenerationConfig.maxOutputTokens = options.maxTokens;\n\t}\n\n\tconst config: GenerateContentConfig = {\n\t\t...(Object.keys(generationConfig).length > 0 && generationConfig),\n\t\t...(context.systemPrompt && { systemInstruction: sanitizeSurrogates(context.systemPrompt) }),\n\t\t...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools) }),\n\t};\n\n\tif (context.tools && context.tools.length > 0 && options.toolChoice) {\n\t\tconfig.toolConfig = {\n\t\t\tfunctionCallingConfig: {\n\t\t\t\tmode: mapToolChoice(options.toolChoice),\n\t\t\t},\n\t\t};\n\t} else {\n\t\tconfig.toolConfig = undefined;\n\t}\n\n\tif (options.thinking?.enabled && model.reasoning) {\n\t\tconst thinkingConfig: ThinkingConfig = { includeThoughts: true };\n\t\tif (options.thinking.level !== undefined) {\n\t\t\t// Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values\n\t\t\tthinkingConfig.thinkingLevel = options.thinking.level as any;\n\t\t} else if (options.thinking.budgetTokens !== undefined) {\n\t\t\tthinkingConfig.thinkingBudget = options.thinking.budgetTokens;\n\t\t}\n\t\tconfig.thinkingConfig = thinkingConfig;\n\t} else if (model.reasoning && options.thinking && !options.thinking.enabled) {\n\t\tconfig.thinkingConfig = getDisabledThinkingConfig(model);\n\t}\n\n\tif (options.signal) {\n\t\tif (options.signal.aborted) {\n\t\t\tthrow new Error(\"Request aborted\");\n\t\t}\n\t\tconfig.abortSignal = options.signal;\n\t}\n\n\tconst params: GenerateContentParameters = {\n\t\tmodel: model.id,\n\t\tcontents,\n\t\tconfig,\n\t};\n\n\treturn params;\n}\n\ntype ClampedThinkingLevel = Exclude<ThinkingLevel, \"xhigh\">;\n\nfunction isGemma4Model(model: Model<\"google-generative-ai\">): boolean {\n\treturn /gemma-?4/.test(model.id.toLowerCase());\n}\n\nfunction isGemini3ProModel(model: Model<\"google-generative-ai\">): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-pro/.test(model.id.toLowerCase());\n}\n\nfunction isGemini3FlashModel(model: Model<\"google-generative-ai\">): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-flash/.test(model.id.toLowerCase());\n}\n\nfunction getDisabledThinkingConfig(model: Model<\"google-generative-ai\">): ThinkingConfig {\n\t// Google docs: Gemini 3.1 Pro cannot disable thinking, and Gemini 3 Flash / Flash-Lite\n\t// do not support full thinking-off either. For Gemini 3 models, use the lowest supported\n\t// thinkingLevel without includeThoughts so hidden thinking remains invisible to pi.\n\tif (isGemini3ProModel(model)) {\n\t\treturn { thinkingLevel: \"LOW\" as any };\n\t}\n\tif (isGemini3FlashModel(model)) {\n\t\treturn { thinkingLevel: \"MINIMAL\" as any };\n\t}\n\tif (isGemma4Model(model)) {\n\t\treturn { thinkingLevel: \"MINIMAL\" as any };\n\t}\n\n\t// Gemini 2.x supports disabling via thinkingBudget = 0.\n\treturn { thinkingBudget: 0 };\n}\n\nfunction getThinkingLevel(effort: ClampedThinkingLevel, model: Model<\"google-generative-ai\">): GoogleThinkingLevel {\n\tif (isGemini3ProModel(model)) {\n\t\tswitch (effort) {\n\t\t\tcase \"minimal\":\n\t\t\tcase \"low\":\n\t\t\t\treturn \"LOW\";\n\t\t\tcase \"medium\":\n\t\t\tcase \"high\":\n\t\t\t\treturn \"HIGH\";\n\t\t}\n\t}\n\tif (isGemma4Model(model)) {\n\t\tswitch (effort) {\n\t\t\tcase \"minimal\":\n\t\t\tcase \"low\":\n\t\t\t\treturn \"MINIMAL\";\n\t\t\tcase \"medium\":\n\t\t\tcase \"high\":\n\t\t\t\treturn \"HIGH\";\n\t\t}\n\t}\n\tswitch (effort) {\n\t\tcase \"minimal\":\n\t\t\treturn \"MINIMAL\";\n\t\tcase \"low\":\n\t\t\treturn \"LOW\";\n\t\tcase \"medium\":\n\t\t\treturn \"MEDIUM\";\n\t\tcase \"high\":\n\t\t\treturn \"HIGH\";\n\t}\n}\n\nfunction getGoogleBudget(\n\tmodel: Model<\"google-generative-ai\">,\n\teffort: ClampedThinkingLevel,\n\tcustomBudgets?: ThinkingBudgets,\n): number {\n\tif (customBudgets?.[effort] !== undefined) {\n\t\treturn customBudgets[effort]!;\n\t}\n\n\tif (model.id.includes(\"2.5-pro\")) {\n\t\tconst budgets: Record<ClampedThinkingLevel, number> = {\n\t\t\tminimal: 128,\n\t\t\tlow: 2048,\n\t\t\tmedium: 8192,\n\t\t\thigh: 32768,\n\t\t};\n\t\treturn budgets[effort];\n\t}\n\n\tif (model.id.includes(\"2.5-flash-lite\")) {\n\t\tconst budgets: Record<ClampedThinkingLevel, number> = {\n\t\t\tminimal: 512,\n\t\t\tlow: 2048,\n\t\t\tmedium: 8192,\n\t\t\thigh: 24576,\n\t\t};\n\t\treturn budgets[effort];\n\t}\n\n\tif (model.id.includes(\"2.5-flash\")) {\n\t\tconst budgets: Record<ClampedThinkingLevel, number> = {\n\t\t\tminimal: 128,\n\t\t\tlow: 2048,\n\t\t\tmedium: 8192,\n\t\t\thigh: 24576,\n\t\t};\n\t\treturn budgets[effort];\n\t}\n\n\treturn -1;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAMX,mBAAmB,EACnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAGX,qBAAqB,EACrB,gBAAgB,EAChB,MAAM,oBAAoB,CAAC;AAY5B,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,MAAM,WAAW,aAAc,SAAQ,aAAa;IACnD,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CACjC;AA6MD,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,sBAAsB,EAAE,aAAa,CAa9E,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,sBAAsB,EAAE,mBAAmB,CAoC1F,CAAC","sourcesContent":["import {\n\ttype Candidate,\n\ttype GenerateContentConfig,\n\ttype GenerateContentParameters,\n\ttype GenerateContentResponse,\n\tGoogleGenAI,\n\ttype Part,\n\ttype ThinkingConfig,\n} from \"@google/genai\";\nimport { getEnvApiKey } from \"../env-api-keys.ts\";\nimport { calculateCost, clampThinkingLevel } from \"../models.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n\tTextContent,\n\tThinkingBudgets,\n\tThinkingContent,\n\tThinkingLevel,\n\tToolCall,\n} from \"../types.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { providerHeadersToRecord } from \"../utils/headers.ts\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.ts\";\nimport type {\n\tGoogleStreamState,\n\tGoogleThinkingLevel,\n\tGoogleThinkingOptions,\n\tGoogleToolChoice,\n} from \"./google-shared.ts\";\nimport {\n\tconvertMessages,\n\tconvertTools,\n\tisThinkingPart,\n\tmapStopReason,\n\tmapToolChoice,\n\tretainThoughtSignature,\n\tretryGoogleRequest,\n} from \"./google-shared.ts\";\nimport { buildBaseOptions } from \"./simple-options.ts\";\n\nexport type { GoogleThinkingOptions } from \"./google-shared.ts\";\n\nexport interface GoogleOptions extends StreamOptions {\n\ttoolChoice?: GoogleToolChoice;\n\tthinking?: GoogleThinkingOptions;\n}\n\n// Counter for generating unique tool call IDs\nlet toolCallCounter = 0;\n\ntype GoogleGenerativeStreamState = GoogleStreamState;\n\nfunction createGoogleOutput(model: Model<\"google-generative-ai\">): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [],\n\t\tapi: \"google-generative-ai\" as Api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n}\n\nfunction googleBlockIndex(state: GoogleGenerativeStreamState): number {\n\treturn state.output.content.length - 1;\n}\n\nfunction endGoogleBlock(state: GoogleGenerativeStreamState): void {\n\tconst block = state.currentBlock;\n\tif (!block) return;\n\tif (block.type === \"text\") {\n\t\tstate.stream.push({\n\t\t\ttype: \"text_end\",\n\t\t\tcontentIndex: googleBlockIndex(state),\n\t\t\tcontent: block.text,\n\t\t\tpartial: state.output,\n\t\t});\n\t} else {\n\t\tstate.stream.push({\n\t\t\ttype: \"thinking_end\",\n\t\t\tcontentIndex: googleBlockIndex(state),\n\t\t\tcontent: block.thinking,\n\t\t\tpartial: state.output,\n\t\t});\n\t}\n\tstate.currentBlock = null;\n}\n\nfunction ensureGoogleTextBlock(state: GoogleGenerativeStreamState, isThinking: boolean): TextContent | ThinkingContent {\n\tconst current = state.currentBlock;\n\tif (current && ((isThinking && current.type === \"thinking\") || (!isThinking && current.type === \"text\"))) {\n\t\treturn current;\n\t}\n\tendGoogleBlock(state);\n\tif (isThinking) {\n\t\tconst block: ThinkingContent = { type: \"thinking\", thinking: \"\", thinkingSignature: undefined };\n\t\tstate.currentBlock = block;\n\t\tstate.output.content.push(block);\n\t\tstate.stream.push({ type: \"thinking_start\", contentIndex: googleBlockIndex(state), partial: state.output });\n\t\treturn block;\n\t}\n\tconst block: TextContent = { type: \"text\", text: \"\" };\n\tstate.currentBlock = block;\n\tstate.output.content.push(block);\n\tstate.stream.push({ type: \"text_start\", contentIndex: googleBlockIndex(state), partial: state.output });\n\treturn block;\n}\n\nfunction appendGoogleTextPart(state: GoogleGenerativeStreamState, part: Part): void {\n\tif (part.text === undefined) return;\n\tconst block = ensureGoogleTextBlock(state, isThinkingPart(part));\n\tif (block.type === \"thinking\") {\n\t\tblock.thinking += part.text;\n\t\tblock.thinkingSignature = retainThoughtSignature(block.thinkingSignature, part.thoughtSignature);\n\t\tstate.stream.push({\n\t\t\ttype: \"thinking_delta\",\n\t\t\tcontentIndex: googleBlockIndex(state),\n\t\t\tdelta: part.text,\n\t\t\tpartial: state.output,\n\t\t});\n\t\treturn;\n\t}\n\tblock.text += part.text;\n\tblock.textSignature = retainThoughtSignature(block.textSignature, part.thoughtSignature);\n\tstate.stream.push({\n\t\ttype: \"text_delta\",\n\t\tcontentIndex: googleBlockIndex(state),\n\t\tdelta: part.text,\n\t\tpartial: state.output,\n\t});\n}\n\nfunction appendGoogleToolCall(state: GoogleGenerativeStreamState, part: Part): void {\n\tif (!part.functionCall) return;\n\tendGoogleBlock(state);\n\tconst providedId = part.functionCall.id;\n\tconst needsNewId =\n\t\t!providedId || state.output.content.some((block) => block.type === \"toolCall\" && block.id === providedId);\n\tconst toolCall: ToolCall = {\n\t\ttype: \"toolCall\",\n\t\tid: needsNewId ? `${part.functionCall.name}_${Date.now()}_${++toolCallCounter}` : providedId,\n\t\tname: part.functionCall.name || \"\",\n\t\targuments: (part.functionCall.args as Record<string, any>) ?? {},\n\t\t...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),\n\t};\n\tstate.output.content.push(toolCall);\n\tstate.stream.push({ type: \"toolcall_start\", contentIndex: googleBlockIndex(state), partial: state.output });\n\tstate.stream.push({\n\t\ttype: \"toolcall_delta\",\n\t\tcontentIndex: googleBlockIndex(state),\n\t\tdelta: JSON.stringify(toolCall.arguments),\n\t\tpartial: state.output,\n\t});\n\tstate.stream.push({\n\t\ttype: \"toolcall_end\",\n\t\tcontentIndex: googleBlockIndex(state),\n\t\ttoolCall,\n\t\tpartial: state.output,\n\t});\n}\n\nfunction applyGoogleFinishReason(output: AssistantMessage, candidate: Candidate | undefined): void {\n\tif (!candidate?.finishReason) return;\n\toutput.stopReason = mapStopReason(candidate.finishReason);\n\tif (output.content.some((block) => block.type === \"toolCall\")) output.stopReason = \"toolUse\";\n}\n\nfunction applyGoogleUsage(\n\toutput: AssistantMessage,\n\tchunk: GenerateContentResponse,\n\tmodel: Model<\"google-generative-ai\">,\n): void {\n\tif (!chunk.usageMetadata) return;\n\toutput.usage = {\n\t\tinput: (chunk.usageMetadata.promptTokenCount || 0) - (chunk.usageMetadata.cachedContentTokenCount || 0),\n\t\toutput: (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),\n\t\tcacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,\n\t\tcacheWrite: 0,\n\t\ttotalTokens: chunk.usageMetadata.totalTokenCount || 0,\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n\tcalculateCost(model, output.usage);\n}\n\nfunction processGoogleChunk(\n\tstate: GoogleGenerativeStreamState,\n\tchunk: GenerateContentResponse,\n\tmodel: Model<\"google-generative-ai\">,\n): void {\n\tstate.output.responseId ||= chunk.responseId;\n\tconst candidate = chunk.candidates?.[0];\n\tfor (const part of candidate?.content?.parts ?? []) {\n\t\tappendGoogleTextPart(state, part);\n\t\tappendGoogleToolCall(state, part);\n\t}\n\tapplyGoogleFinishReason(state.output, candidate);\n\tapplyGoogleUsage(state.output, chunk, model);\n}\n\nfunction completeGoogleStream(state: GoogleGenerativeStreamState, signal: AbortSignal | undefined): void {\n\tendGoogleBlock(state);\n\tif (signal?.aborted) throw new Error(\"Request was aborted\");\n\tif (state.output.stopReason === \"pending\") throw new Error(\"Google stream ended without a finish reason\");\n\tif (state.output.stopReason === \"aborted\" || state.output.stopReason === \"error\") {\n\t\tthrow new Error(\"An unknown error occurred\");\n\t}\n\tstate.stream.push({ type: \"done\", reason: state.output.stopReason, message: state.output });\n\tstate.stream.end();\n}\n\nfunction failGoogleStream(state: GoogleGenerativeStreamState, error: unknown, signal: AbortSignal | undefined): void {\n\tfor (const block of state.output.content) {\n\t\tif (\"index\" in block) delete (block as { index?: number }).index;\n\t}\n\tstate.output.stopReason = signal?.aborted ? \"aborted\" : \"error\";\n\tstate.output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\tstate.stream.push({ type: \"error\", reason: state.output.stopReason, error: state.output });\n\tstate.stream.end();\n}\n\nasync function runGoogleStream(\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions: GoogleOptions | undefined,\n\tstate: GoogleGenerativeStreamState,\n): Promise<void> {\n\ttry {\n\t\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider) || \"\";\n\t\tconst client = createClient(model, apiKey, options?.headers);\n\t\tlet params = buildParams(model, context, options);\n\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\tif (nextParams !== undefined) params = nextParams as GenerateContentParameters;\n\t\tconst googleStream = await retryGoogleRequest(() => client.models.generateContentStream(params), options);\n\t\tstate.stream.push({ type: \"start\", partial: state.output });\n\t\tfor await (const chunk of googleStream) processGoogleChunk(state, chunk, model);\n\t\tcompleteGoogleStream(state, options?.signal);\n\t} catch (error) {\n\t\tfailGoogleStream(state, error, options?.signal);\n\t}\n}\n\nexport const streamGoogle: StreamFunction<\"google-generative-ai\", GoogleOptions> = (\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions?: GoogleOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\tconst state: GoogleGenerativeStreamState = {\n\t\toutput: createGoogleOutput(model),\n\t\tstream,\n\t\tcurrentBlock: null,\n\t};\n\tvoid runGoogleStream(model, context, options, state);\n\treturn stream;\n};\n\nexport const streamSimpleGoogle: StreamFunction<\"google-generative-ai\", SimpleStreamOptions> = (\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, context, options, apiKey);\n\tif (!options?.reasoning) {\n\t\treturn streamGoogle(model, context, { ...base, thinking: { enabled: false } } satisfies GoogleOptions);\n\t}\n\n\tconst clampedReasoning = clampThinkingLevel(model, options.reasoning);\n\tconst effort = (clampedReasoning === \"off\" ? \"high\" : clampedReasoning) as ClampedThinkingLevel;\n\tconst googleModel = model as Model<\"google-generative-ai\">;\n\n\tif (isGemini3ProModel(googleModel) || isGemini3FlashModel(googleModel) || isGemma4Model(googleModel)) {\n\t\treturn streamGoogle(model, context, {\n\t\t\t...base,\n\t\t\tthinking: {\n\t\t\t\tenabled: true,\n\t\t\t\tlevel: getThinkingLevel(effort, googleModel),\n\t\t\t},\n\t\t} satisfies GoogleOptions);\n\t}\n\n\treturn streamGoogle(model, context, {\n\t\t...base,\n\t\tthinking: {\n\t\t\tenabled: true,\n\t\t\tbudgetTokens: getGoogleBudget(googleModel, effort, options.thinkingBudgets),\n\t\t},\n\t} satisfies GoogleOptions);\n};\n\nfunction createClient(\n\tmodel: Model<\"google-generative-ai\">,\n\tapiKey?: string,\n\toptionsHeaders?: ProviderHeaders,\n): GoogleGenAI {\n\tconst httpOptions: { baseUrl?: string; apiVersion?: string; headers?: Record<string, string> } = {};\n\tif (model.baseUrl) {\n\t\thttpOptions.baseUrl = model.baseUrl;\n\t\thttpOptions.apiVersion = \"\"; // baseUrl already includes version path, don't append\n\t}\n\tconst headers = providerHeadersToRecord({ ...model.headers, ...optionsHeaders });\n\tif (headers) {\n\t\thttpOptions.headers = headers;\n\t}\n\n\treturn new GoogleGenAI({\n\t\tapiKey,\n\t\thttpOptions: Object.keys(httpOptions).length > 0 ? httpOptions : undefined,\n\t});\n}\n\nfunction resolveGoogleThinkingConfig(\n\tmodel: Model<\"google-generative-ai\">,\n\tthinking: GoogleThinkingOptions | undefined,\n): ThinkingConfig | undefined {\n\tif (thinking?.enabled && model.reasoning) {\n\t\tconst config: ThinkingConfig = { includeThoughts: true };\n\t\tif (thinking.level !== undefined) {\n\t\t\t// Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values\n\t\t\tconfig.thinkingLevel = thinking.level as any;\n\t\t} else if (thinking.budgetTokens !== undefined) {\n\t\t\tconfig.thinkingBudget = thinking.budgetTokens;\n\t\t}\n\t\treturn config;\n\t}\n\tif (model.reasoning && thinking && !thinking.enabled) return getDisabledThinkingConfig(model);\n\treturn undefined;\n}\n\nfunction buildParams(\n\tmodel: Model<\"google-generative-ai\">,\n\tcontext: Context,\n\toptions: GoogleOptions = {},\n): GenerateContentParameters {\n\tconst contents = convertMessages(model, context);\n\n\tconst generationConfig: GenerateContentConfig = {};\n\tif (options.temperature !== undefined) {\n\t\tgenerationConfig.temperature = options.temperature;\n\t}\n\tif (options.maxTokens !== undefined) {\n\t\tgenerationConfig.maxOutputTokens = options.maxTokens;\n\t}\n\n\tconst config: GenerateContentConfig = {\n\t\t...(Object.keys(generationConfig).length > 0 && generationConfig),\n\t\t...(context.systemPrompt && { systemInstruction: sanitizeSurrogates(context.systemPrompt) }),\n\t\t...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools) }),\n\t};\n\n\tif (context.tools && context.tools.length > 0 && options.toolChoice) {\n\t\tconfig.toolConfig = {\n\t\t\tfunctionCallingConfig: {\n\t\t\t\tmode: mapToolChoice(options.toolChoice),\n\t\t\t},\n\t\t};\n\t} else {\n\t\tconfig.toolConfig = undefined;\n\t}\n\n\tconst thinkingConfig = resolveGoogleThinkingConfig(model, options.thinking);\n\tif (thinkingConfig) config.thinkingConfig = thinkingConfig;\n\n\tif (options.signal) {\n\t\tif (options.signal.aborted) {\n\t\t\tthrow new Error(\"Request aborted\");\n\t\t}\n\t\tconfig.abortSignal = options.signal;\n\t}\n\n\tconst params: GenerateContentParameters = {\n\t\tmodel: model.id,\n\t\tcontents,\n\t\tconfig,\n\t};\n\n\treturn params;\n}\n\ntype ClampedThinkingLevel = Exclude<ThinkingLevel, \"xhigh\">;\n\nfunction isGemma4Model(model: Model<\"google-generative-ai\">): boolean {\n\treturn /gemma-?4/.test(model.id.toLowerCase());\n}\n\nfunction isGemini3ProModel(model: Model<\"google-generative-ai\">): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-pro/.test(model.id.toLowerCase());\n}\n\nfunction isGemini3FlashModel(model: Model<\"google-generative-ai\">): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-flash/.test(model.id.toLowerCase());\n}\n\nfunction getDisabledThinkingConfig(model: Model<\"google-generative-ai\">): ThinkingConfig {\n\t// Google docs: Gemini 3.1 Pro cannot disable thinking, and Gemini 3 Flash / Flash-Lite\n\t// do not support full thinking-off either. For Gemini 3 models, use the lowest supported\n\t// thinkingLevel without includeThoughts so hidden thinking remains invisible to pi.\n\tif (isGemini3ProModel(model)) {\n\t\treturn { thinkingLevel: \"LOW\" as any };\n\t}\n\tif (isGemini3FlashModel(model)) {\n\t\treturn { thinkingLevel: \"MINIMAL\" as any };\n\t}\n\tif (isGemma4Model(model)) {\n\t\treturn { thinkingLevel: \"MINIMAL\" as any };\n\t}\n\n\t// Gemini 2.x supports disabling via thinkingBudget = 0.\n\treturn { thinkingBudget: 0 };\n}\n\nfunction getThinkingLevel(effort: ClampedThinkingLevel, model: Model<\"google-generative-ai\">): GoogleThinkingLevel {\n\tif (isGemini3ProModel(model)) {\n\t\tswitch (effort) {\n\t\t\tcase \"minimal\":\n\t\t\tcase \"low\":\n\t\t\t\treturn \"LOW\";\n\t\t\tcase \"medium\":\n\t\t\tcase \"high\":\n\t\t\t\treturn \"HIGH\";\n\t\t}\n\t}\n\tif (isGemma4Model(model)) {\n\t\tswitch (effort) {\n\t\t\tcase \"minimal\":\n\t\t\tcase \"low\":\n\t\t\t\treturn \"MINIMAL\";\n\t\t\tcase \"medium\":\n\t\t\tcase \"high\":\n\t\t\t\treturn \"HIGH\";\n\t\t}\n\t}\n\tswitch (effort) {\n\t\tcase \"minimal\":\n\t\t\treturn \"MINIMAL\";\n\t\tcase \"low\":\n\t\t\treturn \"LOW\";\n\t\tcase \"medium\":\n\t\t\treturn \"MEDIUM\";\n\t\tcase \"high\":\n\t\t\treturn \"HIGH\";\n\t}\n}\n\nfunction getGoogleBudget(\n\tmodel: Model<\"google-generative-ai\">,\n\teffort: ClampedThinkingLevel,\n\tcustomBudgets?: ThinkingBudgets,\n): number {\n\tif (customBudgets?.[effort] !== undefined) {\n\t\treturn customBudgets[effort]!;\n\t}\n\n\tif (model.id.includes(\"2.5-pro\")) {\n\t\tconst budgets: Record<ClampedThinkingLevel, number> = {\n\t\t\tminimal: 128,\n\t\t\tlow: 2048,\n\t\t\tmedium: 8192,\n\t\t\thigh: 32768,\n\t\t};\n\t\treturn budgets[effort];\n\t}\n\n\tif (model.id.includes(\"2.5-flash-lite\")) {\n\t\tconst budgets: Record<ClampedThinkingLevel, number> = {\n\t\t\tminimal: 512,\n\t\t\tlow: 2048,\n\t\t\tmedium: 8192,\n\t\t\thigh: 24576,\n\t\t};\n\t\treturn budgets[effort];\n\t}\n\n\tif (model.id.includes(\"2.5-flash\")) {\n\t\tconst budgets: Record<ClampedThinkingLevel, number> = {\n\t\t\tminimal: 128,\n\t\t\tlow: 2048,\n\t\t\tmedium: 8192,\n\t\t\thigh: 24576,\n\t\t};\n\t\treturn budgets[effort];\n\t}\n\n\treturn -1;\n}\n"]}
|
package/dist/providers/google.js
CHANGED
|
@@ -2,215 +2,205 @@ import { GoogleGenAI, } from "@google/genai";
|
|
|
2
2
|
import { getEnvApiKey } from "../env-api-keys.js";
|
|
3
3
|
import { calculateCost, clampThinkingLevel } from "../models.js";
|
|
4
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
|
+
import { providerHeadersToRecord } from "../utils/headers.js";
|
|
5
6
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
6
|
-
import { convertMessages, convertTools, isThinkingPart, mapStopReason, mapToolChoice, retainThoughtSignature, } from "./google-shared.js";
|
|
7
|
+
import { convertMessages, convertTools, isThinkingPart, mapStopReason, mapToolChoice, retainThoughtSignature, retryGoogleRequest, } from "./google-shared.js";
|
|
7
8
|
import { buildBaseOptions } from "./simple-options.js";
|
|
8
9
|
// Counter for generating unique tool call IDs
|
|
9
10
|
let toolCallCounter = 0;
|
|
11
|
+
function createGoogleOutput(model) {
|
|
12
|
+
return {
|
|
13
|
+
role: "assistant",
|
|
14
|
+
content: [],
|
|
15
|
+
api: "google-generative-ai",
|
|
16
|
+
provider: model.provider,
|
|
17
|
+
model: model.id,
|
|
18
|
+
usage: {
|
|
19
|
+
input: 0,
|
|
20
|
+
output: 0,
|
|
21
|
+
cacheRead: 0,
|
|
22
|
+
cacheWrite: 0,
|
|
23
|
+
totalTokens: 0,
|
|
24
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
25
|
+
},
|
|
26
|
+
stopReason: "stop",
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function googleBlockIndex(state) {
|
|
31
|
+
return state.output.content.length - 1;
|
|
32
|
+
}
|
|
33
|
+
function endGoogleBlock(state) {
|
|
34
|
+
const block = state.currentBlock;
|
|
35
|
+
if (!block)
|
|
36
|
+
return;
|
|
37
|
+
if (block.type === "text") {
|
|
38
|
+
state.stream.push({
|
|
39
|
+
type: "text_end",
|
|
40
|
+
contentIndex: googleBlockIndex(state),
|
|
41
|
+
content: block.text,
|
|
42
|
+
partial: state.output,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
state.stream.push({
|
|
47
|
+
type: "thinking_end",
|
|
48
|
+
contentIndex: googleBlockIndex(state),
|
|
49
|
+
content: block.thinking,
|
|
50
|
+
partial: state.output,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
state.currentBlock = null;
|
|
54
|
+
}
|
|
55
|
+
function ensureGoogleTextBlock(state, isThinking) {
|
|
56
|
+
const current = state.currentBlock;
|
|
57
|
+
if (current && ((isThinking && current.type === "thinking") || (!isThinking && current.type === "text"))) {
|
|
58
|
+
return current;
|
|
59
|
+
}
|
|
60
|
+
endGoogleBlock(state);
|
|
61
|
+
if (isThinking) {
|
|
62
|
+
const block = { type: "thinking", thinking: "", thinkingSignature: undefined };
|
|
63
|
+
state.currentBlock = block;
|
|
64
|
+
state.output.content.push(block);
|
|
65
|
+
state.stream.push({ type: "thinking_start", contentIndex: googleBlockIndex(state), partial: state.output });
|
|
66
|
+
return block;
|
|
67
|
+
}
|
|
68
|
+
const block = { type: "text", text: "" };
|
|
69
|
+
state.currentBlock = block;
|
|
70
|
+
state.output.content.push(block);
|
|
71
|
+
state.stream.push({ type: "text_start", contentIndex: googleBlockIndex(state), partial: state.output });
|
|
72
|
+
return block;
|
|
73
|
+
}
|
|
74
|
+
function appendGoogleTextPart(state, part) {
|
|
75
|
+
if (part.text === undefined)
|
|
76
|
+
return;
|
|
77
|
+
const block = ensureGoogleTextBlock(state, isThinkingPart(part));
|
|
78
|
+
if (block.type === "thinking") {
|
|
79
|
+
block.thinking += part.text;
|
|
80
|
+
block.thinkingSignature = retainThoughtSignature(block.thinkingSignature, part.thoughtSignature);
|
|
81
|
+
state.stream.push({
|
|
82
|
+
type: "thinking_delta",
|
|
83
|
+
contentIndex: googleBlockIndex(state),
|
|
84
|
+
delta: part.text,
|
|
85
|
+
partial: state.output,
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
block.text += part.text;
|
|
90
|
+
block.textSignature = retainThoughtSignature(block.textSignature, part.thoughtSignature);
|
|
91
|
+
state.stream.push({
|
|
92
|
+
type: "text_delta",
|
|
93
|
+
contentIndex: googleBlockIndex(state),
|
|
94
|
+
delta: part.text,
|
|
95
|
+
partial: state.output,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function appendGoogleToolCall(state, part) {
|
|
99
|
+
if (!part.functionCall)
|
|
100
|
+
return;
|
|
101
|
+
endGoogleBlock(state);
|
|
102
|
+
const providedId = part.functionCall.id;
|
|
103
|
+
const needsNewId = !providedId || state.output.content.some((block) => block.type === "toolCall" && block.id === providedId);
|
|
104
|
+
const toolCall = {
|
|
105
|
+
type: "toolCall",
|
|
106
|
+
id: needsNewId ? `${part.functionCall.name}_${Date.now()}_${++toolCallCounter}` : providedId,
|
|
107
|
+
name: part.functionCall.name || "",
|
|
108
|
+
arguments: part.functionCall.args ?? {},
|
|
109
|
+
...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),
|
|
110
|
+
};
|
|
111
|
+
state.output.content.push(toolCall);
|
|
112
|
+
state.stream.push({ type: "toolcall_start", contentIndex: googleBlockIndex(state), partial: state.output });
|
|
113
|
+
state.stream.push({
|
|
114
|
+
type: "toolcall_delta",
|
|
115
|
+
contentIndex: googleBlockIndex(state),
|
|
116
|
+
delta: JSON.stringify(toolCall.arguments),
|
|
117
|
+
partial: state.output,
|
|
118
|
+
});
|
|
119
|
+
state.stream.push({
|
|
120
|
+
type: "toolcall_end",
|
|
121
|
+
contentIndex: googleBlockIndex(state),
|
|
122
|
+
toolCall,
|
|
123
|
+
partial: state.output,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function applyGoogleFinishReason(output, candidate) {
|
|
127
|
+
if (!candidate?.finishReason)
|
|
128
|
+
return;
|
|
129
|
+
output.stopReason = mapStopReason(candidate.finishReason);
|
|
130
|
+
if (output.content.some((block) => block.type === "toolCall"))
|
|
131
|
+
output.stopReason = "toolUse";
|
|
132
|
+
}
|
|
133
|
+
function applyGoogleUsage(output, chunk, model) {
|
|
134
|
+
if (!chunk.usageMetadata)
|
|
135
|
+
return;
|
|
136
|
+
output.usage = {
|
|
137
|
+
input: (chunk.usageMetadata.promptTokenCount || 0) - (chunk.usageMetadata.cachedContentTokenCount || 0),
|
|
138
|
+
output: (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
|
139
|
+
cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
|
|
140
|
+
cacheWrite: 0,
|
|
141
|
+
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
|
142
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
143
|
+
};
|
|
144
|
+
calculateCost(model, output.usage);
|
|
145
|
+
}
|
|
146
|
+
function processGoogleChunk(state, chunk, model) {
|
|
147
|
+
state.output.responseId ||= chunk.responseId;
|
|
148
|
+
const candidate = chunk.candidates?.[0];
|
|
149
|
+
for (const part of candidate?.content?.parts ?? []) {
|
|
150
|
+
appendGoogleTextPart(state, part);
|
|
151
|
+
appendGoogleToolCall(state, part);
|
|
152
|
+
}
|
|
153
|
+
applyGoogleFinishReason(state.output, candidate);
|
|
154
|
+
applyGoogleUsage(state.output, chunk, model);
|
|
155
|
+
}
|
|
156
|
+
function completeGoogleStream(state, signal) {
|
|
157
|
+
endGoogleBlock(state);
|
|
158
|
+
if (signal?.aborted)
|
|
159
|
+
throw new Error("Request was aborted");
|
|
160
|
+
if (state.output.stopReason === "pending")
|
|
161
|
+
throw new Error("Google stream ended without a finish reason");
|
|
162
|
+
if (state.output.stopReason === "aborted" || state.output.stopReason === "error") {
|
|
163
|
+
throw new Error("An unknown error occurred");
|
|
164
|
+
}
|
|
165
|
+
state.stream.push({ type: "done", reason: state.output.stopReason, message: state.output });
|
|
166
|
+
state.stream.end();
|
|
167
|
+
}
|
|
168
|
+
function failGoogleStream(state, error, signal) {
|
|
169
|
+
for (const block of state.output.content) {
|
|
170
|
+
if ("index" in block)
|
|
171
|
+
delete block.index;
|
|
172
|
+
}
|
|
173
|
+
state.output.stopReason = signal?.aborted ? "aborted" : "error";
|
|
174
|
+
state.output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
175
|
+
state.stream.push({ type: "error", reason: state.output.stopReason, error: state.output });
|
|
176
|
+
state.stream.end();
|
|
177
|
+
}
|
|
178
|
+
async function runGoogleStream(model, context, options, state) {
|
|
179
|
+
try {
|
|
180
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
181
|
+
const client = createClient(model, apiKey, options?.headers);
|
|
182
|
+
let params = buildParams(model, context, options);
|
|
183
|
+
const nextParams = await options?.onPayload?.(params, model);
|
|
184
|
+
if (nextParams !== undefined)
|
|
185
|
+
params = nextParams;
|
|
186
|
+
const googleStream = await retryGoogleRequest(() => client.models.generateContentStream(params), options);
|
|
187
|
+
state.stream.push({ type: "start", partial: state.output });
|
|
188
|
+
for await (const chunk of googleStream)
|
|
189
|
+
processGoogleChunk(state, chunk, model);
|
|
190
|
+
completeGoogleStream(state, options?.signal);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
failGoogleStream(state, error, options?.signal);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
10
196
|
export const streamGoogle = (model, context, options) => {
|
|
11
197
|
const stream = new AssistantMessageEventStream();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
model: model.id,
|
|
19
|
-
usage: {
|
|
20
|
-
input: 0,
|
|
21
|
-
output: 0,
|
|
22
|
-
cacheRead: 0,
|
|
23
|
-
cacheWrite: 0,
|
|
24
|
-
totalTokens: 0,
|
|
25
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
26
|
-
},
|
|
27
|
-
stopReason: "stop",
|
|
28
|
-
timestamp: Date.now(),
|
|
29
|
-
};
|
|
30
|
-
try {
|
|
31
|
-
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
32
|
-
const client = createClient(model, apiKey, options?.headers);
|
|
33
|
-
let params = buildParams(model, context, options);
|
|
34
|
-
const nextParams = await options?.onPayload?.(params, model);
|
|
35
|
-
if (nextParams !== undefined) {
|
|
36
|
-
params = nextParams;
|
|
37
|
-
}
|
|
38
|
-
const googleStream = await client.models.generateContentStream(params);
|
|
39
|
-
stream.push({ type: "start", partial: output });
|
|
40
|
-
let currentBlock = null;
|
|
41
|
-
const blocks = output.content;
|
|
42
|
-
const blockIndex = () => blocks.length - 1;
|
|
43
|
-
for await (const chunk of googleStream) {
|
|
44
|
-
// @google/genai documents GenerateContentResponse.responseId as an output-only field
|
|
45
|
-
// used to identify each response. Keep the first non-empty one from the stream.
|
|
46
|
-
output.responseId ||= chunk.responseId;
|
|
47
|
-
const candidate = chunk.candidates?.[0];
|
|
48
|
-
if (candidate?.content?.parts) {
|
|
49
|
-
for (const part of candidate.content.parts) {
|
|
50
|
-
if (part.text !== undefined) {
|
|
51
|
-
const isThinking = isThinkingPart(part);
|
|
52
|
-
if (!currentBlock ||
|
|
53
|
-
(isThinking && currentBlock.type !== "thinking") ||
|
|
54
|
-
(!isThinking && currentBlock.type !== "text")) {
|
|
55
|
-
if (currentBlock) {
|
|
56
|
-
if (currentBlock.type === "text") {
|
|
57
|
-
stream.push({
|
|
58
|
-
type: "text_end",
|
|
59
|
-
contentIndex: blocks.length - 1,
|
|
60
|
-
content: currentBlock.text,
|
|
61
|
-
partial: output,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
stream.push({
|
|
66
|
-
type: "thinking_end",
|
|
67
|
-
contentIndex: blockIndex(),
|
|
68
|
-
content: currentBlock.thinking,
|
|
69
|
-
partial: output,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (isThinking) {
|
|
74
|
-
currentBlock = { type: "thinking", thinking: "", thinkingSignature: undefined };
|
|
75
|
-
output.content.push(currentBlock);
|
|
76
|
-
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
currentBlock = { type: "text", text: "" };
|
|
80
|
-
output.content.push(currentBlock);
|
|
81
|
-
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (currentBlock.type === "thinking") {
|
|
85
|
-
currentBlock.thinking += part.text;
|
|
86
|
-
currentBlock.thinkingSignature = retainThoughtSignature(currentBlock.thinkingSignature, part.thoughtSignature);
|
|
87
|
-
stream.push({
|
|
88
|
-
type: "thinking_delta",
|
|
89
|
-
contentIndex: blockIndex(),
|
|
90
|
-
delta: part.text,
|
|
91
|
-
partial: output,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
currentBlock.text += part.text;
|
|
96
|
-
currentBlock.textSignature = retainThoughtSignature(currentBlock.textSignature, part.thoughtSignature);
|
|
97
|
-
stream.push({
|
|
98
|
-
type: "text_delta",
|
|
99
|
-
contentIndex: blockIndex(),
|
|
100
|
-
delta: part.text,
|
|
101
|
-
partial: output,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (part.functionCall) {
|
|
106
|
-
if (currentBlock) {
|
|
107
|
-
if (currentBlock.type === "text") {
|
|
108
|
-
stream.push({
|
|
109
|
-
type: "text_end",
|
|
110
|
-
contentIndex: blockIndex(),
|
|
111
|
-
content: currentBlock.text,
|
|
112
|
-
partial: output,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
stream.push({
|
|
117
|
-
type: "thinking_end",
|
|
118
|
-
contentIndex: blockIndex(),
|
|
119
|
-
content: currentBlock.thinking,
|
|
120
|
-
partial: output,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
currentBlock = null;
|
|
124
|
-
}
|
|
125
|
-
// Generate unique ID if not provided or if it's a duplicate
|
|
126
|
-
const providedId = part.functionCall.id;
|
|
127
|
-
const needsNewId = !providedId || output.content.some((b) => b.type === "toolCall" && b.id === providedId);
|
|
128
|
-
const toolCallId = needsNewId
|
|
129
|
-
? `${part.functionCall.name}_${Date.now()}_${++toolCallCounter}`
|
|
130
|
-
: providedId;
|
|
131
|
-
const toolCall = {
|
|
132
|
-
type: "toolCall",
|
|
133
|
-
id: toolCallId,
|
|
134
|
-
name: part.functionCall.name || "",
|
|
135
|
-
arguments: part.functionCall.args ?? {},
|
|
136
|
-
...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),
|
|
137
|
-
};
|
|
138
|
-
output.content.push(toolCall);
|
|
139
|
-
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
|
|
140
|
-
stream.push({
|
|
141
|
-
type: "toolcall_delta",
|
|
142
|
-
contentIndex: blockIndex(),
|
|
143
|
-
delta: JSON.stringify(toolCall.arguments),
|
|
144
|
-
partial: output,
|
|
145
|
-
});
|
|
146
|
-
stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (candidate?.finishReason) {
|
|
151
|
-
output.stopReason = mapStopReason(candidate.finishReason);
|
|
152
|
-
if (output.content.some((b) => b.type === "toolCall")) {
|
|
153
|
-
output.stopReason = "toolUse";
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (chunk.usageMetadata) {
|
|
157
|
-
output.usage = {
|
|
158
|
-
input: (chunk.usageMetadata.promptTokenCount || 0) - (chunk.usageMetadata.cachedContentTokenCount || 0),
|
|
159
|
-
output: (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
|
160
|
-
cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
|
|
161
|
-
cacheWrite: 0,
|
|
162
|
-
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
|
163
|
-
cost: {
|
|
164
|
-
input: 0,
|
|
165
|
-
output: 0,
|
|
166
|
-
cacheRead: 0,
|
|
167
|
-
cacheWrite: 0,
|
|
168
|
-
total: 0,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
calculateCost(model, output.usage);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (currentBlock) {
|
|
175
|
-
if (currentBlock.type === "text") {
|
|
176
|
-
stream.push({
|
|
177
|
-
type: "text_end",
|
|
178
|
-
contentIndex: blockIndex(),
|
|
179
|
-
content: currentBlock.text,
|
|
180
|
-
partial: output,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
stream.push({
|
|
185
|
-
type: "thinking_end",
|
|
186
|
-
contentIndex: blockIndex(),
|
|
187
|
-
content: currentBlock.thinking,
|
|
188
|
-
partial: output,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (options?.signal?.aborted) {
|
|
193
|
-
throw new Error("Request was aborted");
|
|
194
|
-
}
|
|
195
|
-
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
196
|
-
throw new Error("An unknown error occurred");
|
|
197
|
-
}
|
|
198
|
-
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
199
|
-
stream.end();
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
// Remove internal index property used during streaming
|
|
203
|
-
for (const block of output.content) {
|
|
204
|
-
if ("index" in block) {
|
|
205
|
-
delete block.index;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
209
|
-
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
210
|
-
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
211
|
-
stream.end();
|
|
212
|
-
}
|
|
213
|
-
})();
|
|
198
|
+
const state = {
|
|
199
|
+
output: createGoogleOutput(model),
|
|
200
|
+
stream,
|
|
201
|
+
currentBlock: null,
|
|
202
|
+
};
|
|
203
|
+
void runGoogleStream(model, context, options, state);
|
|
214
204
|
return stream;
|
|
215
205
|
};
|
|
216
206
|
export const streamSimpleGoogle = (model, context, options) => {
|
|
@@ -248,14 +238,31 @@ function createClient(model, apiKey, optionsHeaders) {
|
|
|
248
238
|
httpOptions.baseUrl = model.baseUrl;
|
|
249
239
|
httpOptions.apiVersion = ""; // baseUrl already includes version path, don't append
|
|
250
240
|
}
|
|
251
|
-
|
|
252
|
-
|
|
241
|
+
const headers = providerHeadersToRecord({ ...model.headers, ...optionsHeaders });
|
|
242
|
+
if (headers) {
|
|
243
|
+
httpOptions.headers = headers;
|
|
253
244
|
}
|
|
254
245
|
return new GoogleGenAI({
|
|
255
246
|
apiKey,
|
|
256
247
|
httpOptions: Object.keys(httpOptions).length > 0 ? httpOptions : undefined,
|
|
257
248
|
});
|
|
258
249
|
}
|
|
250
|
+
function resolveGoogleThinkingConfig(model, thinking) {
|
|
251
|
+
if (thinking?.enabled && model.reasoning) {
|
|
252
|
+
const config = { includeThoughts: true };
|
|
253
|
+
if (thinking.level !== undefined) {
|
|
254
|
+
// Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values
|
|
255
|
+
config.thinkingLevel = thinking.level;
|
|
256
|
+
}
|
|
257
|
+
else if (thinking.budgetTokens !== undefined) {
|
|
258
|
+
config.thinkingBudget = thinking.budgetTokens;
|
|
259
|
+
}
|
|
260
|
+
return config;
|
|
261
|
+
}
|
|
262
|
+
if (model.reasoning && thinking && !thinking.enabled)
|
|
263
|
+
return getDisabledThinkingConfig(model);
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
259
266
|
function buildParams(model, context, options = {}) {
|
|
260
267
|
const contents = convertMessages(model, context);
|
|
261
268
|
const generationConfig = {};
|
|
@@ -280,20 +287,9 @@ function buildParams(model, context, options = {}) {
|
|
|
280
287
|
else {
|
|
281
288
|
config.toolConfig = undefined;
|
|
282
289
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (options.thinking.level !== undefined) {
|
|
286
|
-
// Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values
|
|
287
|
-
thinkingConfig.thinkingLevel = options.thinking.level;
|
|
288
|
-
}
|
|
289
|
-
else if (options.thinking.budgetTokens !== undefined) {
|
|
290
|
-
thinkingConfig.thinkingBudget = options.thinking.budgetTokens;
|
|
291
|
-
}
|
|
290
|
+
const thinkingConfig = resolveGoogleThinkingConfig(model, options.thinking);
|
|
291
|
+
if (thinkingConfig)
|
|
292
292
|
config.thinkingConfig = thinkingConfig;
|
|
293
|
-
}
|
|
294
|
-
else if (model.reasoning && options.thinking && !options.thinking.enabled) {
|
|
295
|
-
config.thinkingConfig = getDisabledThinkingConfig(model);
|
|
296
|
-
}
|
|
297
293
|
if (options.signal) {
|
|
298
294
|
if (options.signal.aborted) {
|
|
299
295
|
throw new Error("Request aborted");
|