melchizedek-agents 0.18.0 → 0.20.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.
Files changed (293) hide show
  1. package/.env.example +12 -0
  2. package/CHANGELOG.md +1001 -0
  3. package/DOCUMENTATION.md +252 -65
  4. package/QUICKSTART.md +9 -6
  5. package/README.md +42 -4
  6. package/config/agents/examples/harness.yaml +3 -1
  7. package/config/agents/examples/model_zoo.yaml +10 -9
  8. package/config/agents/syndicateSchema.yaml +53 -16
  9. package/config/agents/templates/account_memory.yaml +2 -6
  10. package/config/agents/templates/case_desk.yaml +3 -9
  11. package/config/agents/templates/draft_review.yaml +3 -9
  12. package/config/agents/templates/intake_extractor.yaml +1 -3
  13. package/config/agents/templates/research_brief.yaml +3 -9
  14. package/config/agents/templates/research_desk.yaml +3 -9
  15. package/config/agents/templates/review_panel.yaml +4 -12
  16. package/config/agents/templates/support_triage.yaml +4 -12
  17. package/config/agents/templates/systems_operator.yaml +2 -6
  18. package/db/hardening.sql +7 -1
  19. package/db/migrations/0013_tool_credentials.sql +260 -0
  20. package/dist/lib/a2a/app.d.ts +70 -6
  21. package/dist/lib/a2a/app.js +107 -2
  22. package/dist/lib/a2a/executor.d.ts +20 -3
  23. package/dist/lib/a2a/executor.js +66 -7
  24. package/dist/lib/a2a/remoteAgent.d.ts +24 -9
  25. package/dist/lib/a2a/remoteAgent.js +31 -14
  26. package/dist/lib/adkPeer.d.ts +63 -0
  27. package/dist/lib/adkPeer.js +240 -0
  28. package/dist/lib/compile.d.ts +159 -30
  29. package/dist/lib/compile.js +215 -134
  30. package/dist/lib/compileAdk.d.ts +32 -0
  31. package/dist/lib/compileAdk.js +142 -0
  32. package/dist/lib/compileNative.d.ts +111 -0
  33. package/dist/lib/compileNative.js +278 -0
  34. package/dist/lib/config.d.ts +6 -5
  35. package/dist/lib/config.js +6 -5
  36. package/dist/lib/doctor.d.ts +28 -1
  37. package/dist/lib/doctor.js +64 -2
  38. package/dist/lib/index.d.ts +6 -1
  39. package/dist/lib/index.js +7 -1
  40. package/dist/lib/loadSyndicate.d.ts +18 -0
  41. package/dist/lib/memory/erase.d.ts +5 -3
  42. package/dist/lib/memory/erase.js +5 -2
  43. package/dist/lib/memory/namespace.d.ts +8 -1
  44. package/dist/lib/memory/namespace.js +12 -6
  45. package/dist/lib/memory/providers.js +2 -1
  46. package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
  47. package/dist/lib/memory/supabaseMemoryService.js +42 -2
  48. package/dist/lib/model.d.ts +47 -0
  49. package/dist/lib/model.js +37 -0
  50. package/dist/lib/models/adapterResolver.d.ts +115 -0
  51. package/dist/lib/models/adapterResolver.js +137 -0
  52. package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
  53. package/dist/lib/models/adkGeminiAdapter.js +369 -0
  54. package/dist/lib/models/adkShim.d.ts +111 -0
  55. package/dist/lib/models/adkShim.js +136 -0
  56. package/dist/lib/models/capabilities.d.ts +3 -0
  57. package/dist/lib/models/capabilities.js +33 -23
  58. package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
  59. package/dist/lib/models/chatCompletionsAdapter.js +854 -0
  60. package/dist/lib/models/circuitBreaker.d.ts +42 -0
  61. package/dist/lib/models/circuitBreaker.js +70 -0
  62. package/dist/lib/models/claudeAdapter.d.ts +103 -0
  63. package/dist/lib/models/claudeAdapter.js +669 -0
  64. package/dist/lib/models/claudeLlm.d.ts +39 -62
  65. package/dist/lib/models/claudeLlm.js +45 -350
  66. package/dist/lib/models/claudeModels.d.ts +127 -0
  67. package/dist/lib/models/claudeModels.js +237 -0
  68. package/dist/lib/models/contract.d.ts +395 -0
  69. package/dist/lib/models/contract.js +39 -0
  70. package/dist/lib/models/endpoints.js +2 -1
  71. package/dist/lib/models/errorResponse.d.ts +50 -0
  72. package/dist/lib/models/errorResponse.js +48 -0
  73. package/dist/lib/models/fallback.d.ts +18 -11
  74. package/dist/lib/models/fallback.js +54 -43
  75. package/dist/lib/models/fallbackAdapter.d.ts +55 -0
  76. package/dist/lib/models/fallbackAdapter.js +62 -0
  77. package/dist/lib/models/gateway.js +2 -1
  78. package/dist/lib/models/gatewayAdapter.d.ts +51 -0
  79. package/dist/lib/models/gatewayAdapter.js +81 -0
  80. package/dist/lib/models/gatewayLlm.d.ts +12 -33
  81. package/dist/lib/models/gatewayLlm.js +16 -77
  82. package/dist/lib/models/geminiAdapter.d.ts +125 -0
  83. package/dist/lib/models/geminiAdapter.js +854 -0
  84. package/dist/lib/models/geminiState.d.ts +46 -0
  85. package/dist/lib/models/geminiState.js +51 -0
  86. package/dist/lib/models/genaiMapping.d.ts +283 -0
  87. package/dist/lib/models/genaiMapping.js +965 -0
  88. package/dist/lib/models/gptAdapter.d.ts +240 -0
  89. package/dist/lib/models/gptAdapter.js +804 -0
  90. package/dist/lib/models/gptLlm.d.ts +63 -123
  91. package/dist/lib/models/gptLlm.js +89 -603
  92. package/dist/lib/models/grokAdapter.d.ts +86 -0
  93. package/dist/lib/models/grokAdapter.js +142 -0
  94. package/dist/lib/models/grokLlm.d.ts +22 -45
  95. package/dist/lib/models/grokLlm.js +24 -69
  96. package/dist/lib/models/kimiAdapter.d.ts +157 -0
  97. package/dist/lib/models/kimiAdapter.js +203 -0
  98. package/dist/lib/models/kimiLlm.d.ts +15 -107
  99. package/dist/lib/models/kimiLlm.js +19 -155
  100. package/dist/lib/models/ollamaAdapter.d.ts +125 -0
  101. package/dist/lib/models/ollamaAdapter.js +155 -0
  102. package/dist/lib/models/ollamaLlm.d.ts +11 -107
  103. package/dist/lib/models/ollamaLlm.js +15 -153
  104. package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
  105. package/dist/lib/models/openAiCompatibleLlm.js +109 -671
  106. package/dist/lib/models/providerState.d.ts +63 -0
  107. package/dist/lib/models/providerState.js +61 -0
  108. package/dist/lib/models/reasoning.d.ts +26 -0
  109. package/dist/lib/models/reasoning.js +59 -0
  110. package/dist/lib/models/registry.d.ts +62 -35
  111. package/dist/lib/models/registry.js +100 -124
  112. package/dist/lib/models/retry.d.ts +2 -1
  113. package/dist/lib/models/retry.js +3 -2
  114. package/dist/lib/models/schemaNormalize.d.ts +98 -0
  115. package/dist/lib/models/schemaNormalize.js +321 -1
  116. package/dist/lib/models/tracedGemini.d.ts +41 -0
  117. package/dist/lib/models/tracedGemini.js +98 -0
  118. package/dist/lib/models/urls.d.ts +11 -0
  119. package/dist/lib/models/urls.js +16 -0
  120. package/dist/lib/net/redirects.d.ts +21 -2
  121. package/dist/lib/net/redirects.js +11 -34
  122. package/dist/lib/observability/audit.d.ts +5 -1
  123. package/dist/lib/observability/lineage.d.ts +14 -0
  124. package/dist/lib/observability/lineage.js +33 -0
  125. package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
  126. package/dist/lib/observability/supabaseSpanExporter.js +17 -9
  127. package/dist/lib/observability/tracer.d.ts +26 -8
  128. package/dist/lib/observability/tracer.js +43 -18
  129. package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
  130. package/dist/lib/runtime/adkMemoryBridge.js +68 -0
  131. package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
  132. package/dist/lib/runtime/adkSessionBridge.js +176 -0
  133. package/dist/lib/runtime/approvals.d.ts +5 -0
  134. package/dist/lib/runtime/approvals.js +39 -8
  135. package/dist/lib/runtime/credentials.d.ts +61 -0
  136. package/dist/lib/runtime/credentials.js +67 -0
  137. package/dist/lib/runtime/events.d.ts +229 -0
  138. package/dist/lib/runtime/events.js +274 -0
  139. package/dist/lib/runtime/logging.d.ts +32 -0
  140. package/dist/lib/runtime/logging.js +55 -0
  141. package/dist/lib/runtime/memoryService.d.ts +72 -0
  142. package/dist/lib/runtime/memoryService.js +25 -0
  143. package/dist/lib/runtime/native/agentLoop.d.ts +209 -0
  144. package/dist/lib/runtime/native/agentLoop.js +705 -0
  145. package/dist/lib/runtime/native/compaction.d.ts +103 -0
  146. package/dist/lib/runtime/native/compaction.js +191 -0
  147. package/dist/lib/runtime/native/delegate.d.ts +151 -0
  148. package/dist/lib/runtime/native/delegate.js +256 -0
  149. package/dist/lib/runtime/native/history.d.ts +83 -0
  150. package/dist/lib/runtime/native/history.js +312 -0
  151. package/dist/lib/runtime/native/interrupts.d.ts +129 -0
  152. package/dist/lib/runtime/native/interrupts.js +310 -0
  153. package/dist/lib/runtime/native/request.d.ts +215 -0
  154. package/dist/lib/runtime/native/request.js +585 -0
  155. package/dist/lib/runtime/native/selfCorrection.d.ts +164 -0
  156. package/dist/lib/runtime/native/selfCorrection.js +327 -0
  157. package/dist/lib/runtime/native/step.d.ts +156 -0
  158. package/dist/lib/runtime/native/step.js +263 -0
  159. package/dist/lib/runtime/native/taskMode.d.ts +64 -0
  160. package/dist/lib/runtime/native/taskMode.js +126 -0
  161. package/dist/lib/runtime/native/telemetry.d.ts +75 -0
  162. package/dist/lib/runtime/native/telemetry.js +299 -0
  163. package/dist/lib/runtime/native/tempState.d.ts +31 -0
  164. package/dist/lib/runtime/native/tempState.js +56 -0
  165. package/dist/lib/runtime/nativeTurn.d.ts +86 -0
  166. package/dist/lib/runtime/nativeTurn.js +107 -0
  167. package/dist/lib/runtime/questions.d.ts +29 -7
  168. package/dist/lib/runtime/questions.js +47 -32
  169. package/dist/lib/runtime/runtimeFlag.d.ts +38 -0
  170. package/dist/lib/runtime/runtimeFlag.js +58 -0
  171. package/dist/lib/runtime/sessions.d.ts +166 -0
  172. package/dist/lib/runtime/sessions.js +201 -0
  173. package/dist/lib/runtime/syndicateTurn.d.ts +46 -5
  174. package/dist/lib/runtime/syndicateTurn.js +233 -36
  175. package/dist/lib/runtime/valueDepth.d.ts +23 -0
  176. package/dist/lib/runtime/valueDepth.js +41 -0
  177. package/dist/lib/session/supabaseSessionService.d.ts +65 -18
  178. package/dist/lib/session/supabaseSessionService.js +148 -105
  179. package/dist/lib/session/transcript.d.ts +69 -23
  180. package/dist/lib/session/transcript.js +105 -29
  181. package/dist/lib/skills.js +33 -7
  182. package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
  183. package/dist/lib/storage/postgres/credentialStore.js +82 -0
  184. package/dist/lib/storage/postgres/index.d.ts +12 -0
  185. package/dist/lib/storage/postgres/index.js +6 -1
  186. package/dist/lib/storage/postgres/sessionService.d.ts +44 -16
  187. package/dist/lib/storage/postgres/sessionService.js +128 -66
  188. package/dist/lib/syndicateSchema.d.ts +49 -0
  189. package/dist/lib/syndicateSchema.js +99 -9
  190. package/dist/lib/toolRegistry.d.ts +26 -2
  191. package/dist/lib/toolRegistry.js +73 -17
  192. package/dist/lib/tools/adkTool.d.ts +81 -0
  193. package/dist/lib/tools/adkTool.js +268 -0
  194. package/dist/lib/tools/auth.d.ts +118 -0
  195. package/dist/lib/tools/auth.js +89 -0
  196. package/dist/lib/tools/collectionsSearchTool.d.ts +8 -7
  197. package/dist/lib/tools/collectionsSearchTool.js +12 -27
  198. package/dist/lib/tools/credentialCipher.d.ts +58 -0
  199. package/dist/lib/tools/credentialCipher.js +116 -0
  200. package/dist/lib/tools/credentialStore.d.ts +77 -0
  201. package/dist/lib/tools/credentialStore.js +246 -0
  202. package/dist/lib/tools/examples.d.ts +43 -0
  203. package/dist/lib/tools/examples.js +62 -0
  204. package/dist/lib/tools/generateImageTool.d.ts +15 -6
  205. package/dist/lib/tools/generateImageTool.js +19 -29
  206. package/dist/lib/tools/inspectImageTool.d.ts +9 -6
  207. package/dist/lib/tools/inspectImageTool.js +13 -17
  208. package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
  209. package/dist/lib/tools/mcpToolFactory.js +46 -47
  210. package/dist/lib/tools/memoryTools.d.ts +61 -0
  211. package/dist/lib/tools/memoryTools.js +119 -0
  212. package/dist/lib/tools/nativeTools.d.ts +38 -0
  213. package/dist/lib/tools/nativeTools.js +43 -0
  214. package/dist/lib/tools/oauthConsent.d.ts +181 -0
  215. package/dist/lib/tools/oauthConsent.js +330 -0
  216. package/dist/lib/tools/openapi/call.d.ts +108 -0
  217. package/dist/lib/tools/openapi/call.js +295 -0
  218. package/dist/lib/tools/openapi/parse.d.ts +147 -0
  219. package/dist/lib/tools/openapi/parse.js +570 -0
  220. package/dist/lib/tools/openapiTools.d.ts +25 -17
  221. package/dist/lib/tools/openapiTools.js +124 -121
  222. package/dist/lib/tools/scienceTools.d.ts +14 -14
  223. package/dist/lib/tools/skillToolset.d.ts +58 -87
  224. package/dist/lib/tools/skillToolset.js +61 -142
  225. package/dist/lib/tools/skills/env.d.ts +58 -0
  226. package/dist/lib/tools/skills/env.js +165 -0
  227. package/dist/lib/tools/skills/executor.d.ts +114 -0
  228. package/dist/lib/tools/skills/executor.js +331 -0
  229. package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
  230. package/dist/lib/tools/skills/frontmatter.js +215 -0
  231. package/dist/lib/tools/skills/loader.d.ts +91 -0
  232. package/dist/lib/tools/skills/loader.js +257 -0
  233. package/dist/lib/tools/skills/tools.d.ts +187 -0
  234. package/dist/lib/tools/skills/tools.js +386 -0
  235. package/dist/lib/tools/taskTools.d.ts +15 -15
  236. package/dist/lib/tools/tool.d.ts +354 -0
  237. package/dist/lib/tools/tool.js +304 -0
  238. package/dist/lib/tools/toolContract.d.ts +89 -33
  239. package/dist/lib/tools/toolContract.js +104 -82
  240. package/dist/lib/tools/urlContextTool.d.ts +4 -1
  241. package/dist/lib/tools/urlContextTool.js +6 -2
  242. package/dist/lib/tools/webExtractTool.d.ts +3 -3
  243. package/dist/lib/tools/webExtractTool.js +2 -1
  244. package/dist/lib/tools/webSearchTool.d.ts +7 -10
  245. package/dist/lib/tools/webSearchTool.js +15 -60
  246. package/dist/lib/tools/wikiTools.d.ts +19 -19
  247. package/dist/lib/tools/wikiTools.js +13 -9
  248. package/dist/lib/tools/xApiSearchTool.d.ts +3 -3
  249. package/dist/lib/tools/xApiSearchTool.js +2 -1
  250. package/dist/lib/tools/xSearchTool.d.ts +8 -10
  251. package/dist/lib/tools/xSearchTool.js +12 -63
  252. package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
  253. package/dist/lib/tools/xaiSearchParams.js +126 -0
  254. package/dist/lib/wiki/agentRun.d.ts +12 -0
  255. package/dist/lib/wiki/agentRun.js +74 -35
  256. package/dist/lib/wiki/entities.d.ts +12 -0
  257. package/dist/lib/wiki/entities.js +45 -3
  258. package/dist/lib/workflow/agentNode.d.ts +153 -0
  259. package/dist/lib/workflow/agentNode.js +365 -0
  260. package/dist/lib/workflow/graph.d.ts +178 -0
  261. package/dist/lib/workflow/graph.js +468 -0
  262. package/dist/lib/workflow/nodeEvents.d.ts +50 -0
  263. package/dist/lib/workflow/nodeEvents.js +101 -0
  264. package/dist/lib/workflow/pause.d.ts +122 -0
  265. package/dist/lib/workflow/pause.js +180 -0
  266. package/dist/lib/workflow/resume.d.ts +166 -0
  267. package/dist/lib/workflow/resume.js +441 -0
  268. package/dist/lib/workflow/route.d.ts +58 -0
  269. package/dist/lib/workflow/route.js +69 -0
  270. package/dist/lib/workflow/scheduler.d.ts +418 -0
  271. package/dist/lib/workflow/scheduler.js +756 -0
  272. package/dist/lib/workflow/toolNode.d.ts +127 -0
  273. package/dist/lib/workflow/toolNode.js +236 -0
  274. package/dist/lib/workflow/turn.d.ts +118 -0
  275. package/dist/lib/workflow/turn.js +216 -0
  276. package/dist/lib/workflow.d.ts +33 -11
  277. package/dist/lib/workflow.js +49 -27
  278. package/dist/lib/workflowConfig.d.ts +7 -2
  279. package/dist/lib/workflowConfig.js +7 -12
  280. package/dist/scripts/a2a_server.js +4 -3
  281. package/dist/scripts/assistant_worker.d.ts +2 -1
  282. package/dist/scripts/assistant_worker.js +9 -4
  283. package/dist/scripts/doctor.d.ts +3 -1
  284. package/dist/scripts/doctor.js +4 -2
  285. package/dist/scripts/syndicate_chat.js +19 -10
  286. package/package.json +9 -3
  287. package/skills/briefs/melchizedek-author.md +3 -2
  288. package/skills/briefs/melchizedek-models.md +13 -9
  289. package/skills/briefs/melchizedek.md +3 -3
  290. package/skills/melchizedek/SKILL.md +2 -1
  291. package/skills/melchizedek-author/SKILL.md +2 -1
  292. package/skills/melchizedek-author/assets/minimal.yaml +2 -0
  293. package/skills/melchizedek-models/SKILL.md +23 -6
@@ -0,0 +1,854 @@
1
+ /**
2
+ * lib/models/geminiAdapter.ts — Gemini behind the engine's own model
3
+ * contract (lib/models/contract.ts, ADR 0048), on @google/genai directly.
4
+ *
5
+ * WHY this file exists:
6
+ * Gemini runs today through ADK's own Gemini class, wrapped as
7
+ * TracedGemini (lib/models/tracedGemini.ts). The native runtime (ADR 0045)
8
+ * needs Gemini as a contract ModelAdapter that shapes its own requests,
9
+ * aggregates its own stream and reports its own failures, with no ADK in
10
+ * the path. This is that adapter. resolveAdapter (lib/models/registry.ts)
11
+ * returns it only when asked (GEMINI_ADAPTER=engine) until gate G3.
12
+ *
13
+ * THE MAPPING is the Gemini table of wiki/models/model-contract.md;
14
+ * wiki/models/gemini-adapter.md records the choices made inside it:
15
+ * - Schemas go as written, lowercase JSON Schema, in `parametersJsonSchema`
16
+ * and `responseJsonSchema`. Nothing converts them to Gemini's uppercase
17
+ * Schema dialect.
18
+ * - `outputFormat: 'json'` without a schema is `responseMimeType:
19
+ * 'application/json'` alone, Gemini's JSON mode (ADR 0061).
20
+ * - `reasoning` maps through lib/compile.ts's reasoningConfig (ADR 0047),
21
+ * for this adapter's model, with `includeThoughts` unless it is `none`.
22
+ * - A thought signature (ADR 0046) is written as providerState on the
23
+ * output part it arrived on. One on a thought or an empty text part
24
+ * moves to the next part. Signatures are replayed on the same part,
25
+ * within the current turn, for this model only.
26
+ * - Parts the contract has no type for (code execution's `executableCode`
27
+ * and `codeExecutionResult`, server-side `toolCall` and `toolResponse`)
28
+ * are carried whole as providerState of kind `carried_parts` on the next
29
+ * output part, and replayed before it within the current turn (ADR 0065).
30
+ * - A function call Gemini returns without an id gets
31
+ * `adk-<conversation length>-<call index>-<name>`. Ids that start with
32
+ * `adk-` (the engine's) or `genai-noid-` (the genai mapping's) never go
33
+ * on the wire.
34
+ * - `includeServerSideToolInvocations` goes with native tools beside
35
+ * function declarations, on the Gemini API only: the SDK refuses it for
36
+ * Vertex AI.
37
+ * - Grounding is the search queries, and the cited pages with the answer
38
+ * span each supports; urlContext's retrieved pages are cited too.
39
+ *
40
+ * THE ADAPTER RULES (contract.ts header) as this adapter keeps them:
41
+ * - Errors are finals, never throws: a thrown call is GEMINI_ERROR with its
42
+ * status and lib/models/retry.ts's `retryable`; a blocked prompt or an
43
+ * empty or withheld candidate carries Gemini's own reason as the code.
44
+ * - The abort signal is the request's alone (ADR 0053: the caller passes
45
+ * the turn's). It goes on the request config (the SDK aborts its fetch)
46
+ * and also ends the iteration at once, whatever the transport does.
47
+ * - Transient failures are retried here, only before the first chunk.
48
+ */
49
+ import { FunctionCallingConfigMode, GoogleGenAI } from '@google/genai';
50
+ import { endpointFromEnv, endpointProblems, platformModel } from './endpoints.js';
51
+ import { CARRIED_PARTS_KIND, GEMINI_PROVIDER, GENAI_PART_KIND, MINTED_CALL_ID_PREFIX, PLACEHOLDER_THOUGHT_SIGNATURE, THOUGHT_SIGNATURE_KIND, isCarriedWirePart, } from './geminiState.js';
52
+ import { currentTurnStart, providerStateOf } from './providerState.js';
53
+ import { classifyError, errorStatus, retryUntilFirstYield } from './retry.js';
54
+ import { reasoningConfig } from './reasoning.js';
55
+ import { setLlmSpanAttribute } from '../observability/tracer.js';
56
+ /**
57
+ * The provider id this adapter reports and writes its state under, and the
58
+ * providerState kind for a Gemini thought signature (ADR 0046). Defined once
59
+ * in lib/models/geminiState.ts, so the genai mapping spells them the same.
60
+ */
61
+ export { GEMINI_PROVIDER, THOUGHT_SIGNATURE_KIND };
62
+ /**
63
+ * The prefix of a call id the engine made (this adapter, or ADK before it).
64
+ * Such an id is the engine's own and is left off the wire.
65
+ */
66
+ export const ENGINE_CALL_ID_PREFIX = 'adk-';
67
+ /**
68
+ * The providerState kind for Gemini parts the contract has no type for
69
+ * (code execution and server-side tool invocations), carried whole on the
70
+ * next output part (ADR 0065). The payload is a `CarriedParts`. Defined in
71
+ * lib/models/geminiState.ts, so the genai mapping writes them back out as
72
+ * the parts they were when an event is stored (ADR 0100).
73
+ */
74
+ export { CARRIED_PARTS_KIND };
75
+ /**
76
+ * The thought signature Gemini documents for a function call that has no
77
+ * real one: a call another provider or model made earlier in the current
78
+ * turn (a mid-turn fallback). Gemini 3 rejects such a call without a
79
+ * signature. Sent only when the adapter is built with `placeholderSignatures`
80
+ * (default PLACEHOLDER_SIGNATURES_BY_DEFAULT, off) until the G3 live run
81
+ * confirms it (ADR 0065).
82
+ */
83
+ export { PLACEHOLDER_THOUGHT_SIGNATURE };
84
+ /** Off: no placeholder signature is sent unless the adapter is built to send it. */
85
+ export const PLACEHOLDER_SIGNATURES_BY_DEFAULT = false;
86
+ const defaultClientFactory = (options) => new GoogleGenAI(options);
87
+ // ── The adapter ──────────────────────────────────────────────────────────────
88
+ export class GeminiAdapter {
89
+ provider = GEMINI_PROVIDER;
90
+ model;
91
+ #apiKey;
92
+ #endpoint;
93
+ #clientFactory;
94
+ #connection;
95
+ #warned = new Set();
96
+ #placeholderSignatures;
97
+ constructor(options) {
98
+ this.model = options.model;
99
+ this.#apiKey = nonEmpty(options.apiKey);
100
+ this.#endpoint = options.endpoint;
101
+ this.#clientFactory = options.clientFactory ?? defaultClientFactory;
102
+ this.#placeholderSignatures = options.placeholderSignatures ?? PLACEHOLDER_SIGNATURES_BY_DEFAULT;
103
+ }
104
+ async *generate(request) {
105
+ // The request's signal alone: the caller (the ADK shim, the native loop)
106
+ // passes the turn's (ADR 0053).
107
+ const signal = request.signal;
108
+ const model = request.model || this.model;
109
+ const reply = new ReplyBuilder(model, request.messages.length);
110
+ if (signal?.aborted) {
111
+ yield reply.aborted();
112
+ return;
113
+ }
114
+ const connection = this.#connect();
115
+ if ('code' in connection) {
116
+ yield { partial: false, parts: [], finishReason: 'error', error: connection };
117
+ return;
118
+ }
119
+ const stream = request.stream === true;
120
+ try {
121
+ const built = buildRequest(request, {
122
+ model,
123
+ wireModel: platformModel(connection.endpoint, model),
124
+ signal,
125
+ geminiApi: connection.endpoint.platform === 'direct',
126
+ placeholderSignatures: this.#placeholderSignatures,
127
+ });
128
+ this.#report(built);
129
+ const { client } = connection;
130
+ const start = () => (stream ? streamOf(client, built.params) : responseOf(client, built.params));
131
+ const chunks = retryUntilFirstYield(start, {
132
+ signal,
133
+ onRetry: ({ retries }) => setLlmSpanAttribute('llm.retries', retries),
134
+ });
135
+ for await (const chunk of untilAborted(chunks, signal)) {
136
+ const deltas = reply.add(chunk);
137
+ if (stream && deltas.length)
138
+ yield { partial: true, parts: deltas };
139
+ }
140
+ if (!stream && reply.thinking.length)
141
+ yield { partial: true, parts: reply.thinking };
142
+ const final = reply.final(built.searchTool);
143
+ if (reply.finishReason)
144
+ setLlmSpanAttribute('llm.finish_reason', reply.finishReason);
145
+ yield final;
146
+ }
147
+ catch (err) {
148
+ if (signal?.aborted) {
149
+ yield reply.aborted();
150
+ return;
151
+ }
152
+ const status = errorStatus(err);
153
+ if (status !== undefined)
154
+ setLlmSpanAttribute('llm.http_status', status);
155
+ yield reply.failed(err, connection.secret);
156
+ }
157
+ }
158
+ /** The client for this adapter's endpoint, built once; or why it cannot be built. */
159
+ #connect() {
160
+ if (this.#connection)
161
+ return this.#connection;
162
+ let endpoint;
163
+ try {
164
+ endpoint = this.#endpoint ?? endpointFromEnv('gemini');
165
+ }
166
+ catch (err) {
167
+ return setupError('ENDPOINT_MISCONFIGURED', messageOf(err));
168
+ }
169
+ let options;
170
+ let secret;
171
+ if (endpoint.platform === 'vertex') {
172
+ const problems = endpointProblems('gemini', endpoint);
173
+ if (problems.length)
174
+ return setupError('ENDPOINT_MISCONFIGURED', `Gemini on Vertex AI: ${problems.join('; ')}.`);
175
+ // Google Application Default Credentials for the project and location.
176
+ // No AI Studio key goes to Vertex AI, the caller's included.
177
+ options = { vertexai: true, project: endpoint.project, location: endpoint.location };
178
+ }
179
+ else if (endpoint.platform === 'direct') {
180
+ secret = this.#apiKey ?? nonEmpty(endpoint.apiKey) ?? envApiKey();
181
+ if (!secret) {
182
+ return setupError('MISSING_API_KEY', 'No Gemini API key: set GOOGLE_GENAI_API_KEY (or GEMINI_API_KEY), or GEMINI_PLATFORM=vertex for Vertex AI.');
183
+ }
184
+ // vertexai: false keeps the SDK from switching to Vertex AI on its own
185
+ // environment variables once the endpoint has chosen the Gemini API.
186
+ options = { vertexai: false, apiKey: secret };
187
+ }
188
+ else {
189
+ return setupError('ENDPOINT_MISCONFIGURED', `Gemini has no ${endpoint.platform} platform; use direct or vertex.`);
190
+ }
191
+ try {
192
+ this.#connection = { client: this.#clientFactory(options), endpoint, secret };
193
+ }
194
+ catch (err) {
195
+ return setupError('ENDPOINT_MISCONFIGURED', `The Gemini client failed to build: ${scrub(messageOf(err), secret)}`);
196
+ }
197
+ return this.#connection;
198
+ }
199
+ /** Marks the span with what the request carries and drops, and warns once per dropped tool. */
200
+ #report(built) {
201
+ if (built.grounded)
202
+ setLlmSpanAttribute('llm.web_search.native', true);
203
+ if (!built.dropped.length)
204
+ return;
205
+ setLlmSpanAttribute('llm.capability.dropped', built.dropped.join(','));
206
+ for (const tool of built.dropped) {
207
+ if (this.#warned.has(tool))
208
+ continue;
209
+ this.#warned.add(tool);
210
+ console.warn(`⚠ ${tool} is not a Gemini tool; ${this.model} runs without it.`);
211
+ }
212
+ }
213
+ }
214
+ /** The native tools this adapter sends, and the Gemini tool each becomes. */
215
+ const NATIVE_TOOLS = {
216
+ web_search: { googleSearch: {} },
217
+ google_search: { googleSearch: {} },
218
+ url_context: { urlContext: {} },
219
+ code_execution: { codeExecution: {} },
220
+ };
221
+ function buildRequest(request, options) {
222
+ const { model, wireModel, signal } = options;
223
+ const system = request.system ? [request.system] : [];
224
+ const contents = [];
225
+ const turnStart = currentTurnStart(request.messages);
226
+ request.messages.forEach((message, index) => {
227
+ let parts;
228
+ switch (message.role) {
229
+ case 'system':
230
+ for (const part of message.parts)
231
+ if (part.text)
232
+ system.push(part.text);
233
+ return;
234
+ case 'user':
235
+ parts = message.parts.map((part) => (part.type === 'text' ? { text: part.text } : blobToWire(part)));
236
+ break;
237
+ case 'assistant': {
238
+ const replay = index > turnStart;
239
+ parts = assistantToWire(message, replay, model);
240
+ if (replay && options.placeholderSignatures)
241
+ withPlaceholderSignature(parts);
242
+ break;
243
+ }
244
+ case 'tool':
245
+ parts = message.parts.map(resultToWire);
246
+ break;
247
+ }
248
+ // A content with no parts fails the whole request on Vertex AI.
249
+ if (parts.length)
250
+ contents.push({ role: message.role === 'assistant' ? 'model' : 'user', parts });
251
+ });
252
+ const config = {};
253
+ if (system.length)
254
+ config.systemInstruction = { parts: system.map((text) => ({ text })) };
255
+ const tools = [];
256
+ const declared = !!request.tools?.length;
257
+ if (request.tools?.length) {
258
+ tools.push({
259
+ functionDeclarations: request.tools.map((t) => ({
260
+ name: t.name,
261
+ description: t.description,
262
+ parametersJsonSchema: t.parameters,
263
+ })),
264
+ });
265
+ }
266
+ const named = [...new Set(request.nativeTools ?? [])];
267
+ const dropped = named.filter((t) => !NATIVE_TOOLS[t]);
268
+ const grounded = named.includes('web_search') || named.includes('google_search');
269
+ if (grounded)
270
+ tools.push({ googleSearch: {} });
271
+ if (named.includes('url_context'))
272
+ tools.push({ urlContext: {} });
273
+ if (named.includes('code_execution'))
274
+ tools.push({ codeExecution: {} });
275
+ if (tools.length)
276
+ config.tools = tools;
277
+ const native = tools.length > (declared ? 1 : 0);
278
+ let toolConfig = toolConfigFor(request);
279
+ // Native tools beside function declarations: the response carries the
280
+ // server-side invocations, as the ADK path asks on every Gemini agent
281
+ // (lib/compile.ts). The SDK refuses the flag for Vertex AI.
282
+ if (declared && native && options.geminiApi)
283
+ toolConfig = { ...toolConfig, includeServerSideToolInvocations: true };
284
+ if (toolConfig)
285
+ config.toolConfig = toolConfig;
286
+ if (request.outputSchema) {
287
+ config.responseMimeType = 'application/json';
288
+ config.responseJsonSchema = request.outputSchema;
289
+ }
290
+ else if (request.outputFormat === 'json') {
291
+ // JSON mode: the MIME type alone (ADR 0061).
292
+ config.responseMimeType = 'application/json';
293
+ }
294
+ if (request.reasoning !== undefined) {
295
+ const thinking = thinkingConfigFor(model, request.reasoning);
296
+ if (thinking)
297
+ config.thinkingConfig = thinking;
298
+ }
299
+ const sampling = request.sampling;
300
+ if (sampling?.temperature !== undefined)
301
+ config.temperature = sampling.temperature;
302
+ if (sampling?.topP !== undefined)
303
+ config.topP = sampling.topP;
304
+ if (sampling?.maxOutputTokens !== undefined)
305
+ config.maxOutputTokens = sampling.maxOutputTokens;
306
+ if (sampling?.stop?.length)
307
+ config.stopSequences = [...sampling.stop];
308
+ if (signal)
309
+ config.abortSignal = signal;
310
+ return {
311
+ params: { model: wireModel, contents, config },
312
+ dropped,
313
+ grounded,
314
+ searchTool: named.includes('web_search') || !named.includes('google_search') ? 'web_search' : 'google_search',
315
+ };
316
+ }
317
+ /**
318
+ * An assistant message's parts on the wire. Thinking is never sent. With
319
+ * `replay` (the message belongs to the current turn), this model's thought
320
+ * signatures go back on the part they were written on; a signature stored on
321
+ * a thinking part goes on the next part sent, as it did when it was received.
322
+ * Carried parts (code execution, server-side invocations) go back before the
323
+ * part that carries them, as they arrived; another Gemini model's go back
324
+ * without their signatures. Outside the current turn neither is sent.
325
+ */
326
+ function assistantToWire(message, replay, model) {
327
+ const out = [];
328
+ let carried;
329
+ for (const part of message.parts) {
330
+ const signature = replay ? signatureOf(part, model) : undefined;
331
+ if (part.type === 'thinking') {
332
+ carried = signature ?? carried;
333
+ continue;
334
+ }
335
+ // A carried part read back from a stored event (the mapping's genai_part):
336
+ // itself, verbatim, within its turn; outside it, nothing (ADR 0065, ADR 0100).
337
+ const stored = storedCarriedPart(part);
338
+ if (stored) {
339
+ if (!replay)
340
+ continue;
341
+ if (carried && !stored.thoughtSignature)
342
+ stored.thoughtSignature = carried;
343
+ carried = undefined;
344
+ out.push(stored);
345
+ continue;
346
+ }
347
+ const before = replay ? carriedPartsOf(part, model) : [];
348
+ if (before.length && carried) {
349
+ if (!before[0].thoughtSignature)
350
+ before[0].thoughtSignature = carried;
351
+ carried = undefined;
352
+ }
353
+ out.push(...before);
354
+ const sent = signature ?? carried;
355
+ carried = undefined;
356
+ let wire;
357
+ if (part.type === 'text') {
358
+ // An empty text part is sent only to carry a signature.
359
+ if (!part.text && !sent)
360
+ continue;
361
+ wire = { text: part.text };
362
+ }
363
+ else if (part.type === 'toolCall') {
364
+ wire = { functionCall: { ...wireId(part.id), name: part.name, args: part.args } };
365
+ }
366
+ else {
367
+ wire = blobToWire(part);
368
+ }
369
+ if (sent)
370
+ wire.thoughtSignature = sent;
371
+ out.push(wire);
372
+ }
373
+ return out;
374
+ }
375
+ /** This model's signature on a part: a thought signature, or the one a carried_parts state holds. */
376
+ function signatureOf(part, model) {
377
+ const payload = providerStateOf(part, GEMINI_PROVIDER, THOUGHT_SIGNATURE_KIND, model)?.payload;
378
+ if (typeof payload === 'string' && payload)
379
+ return payload;
380
+ const carried = providerStateOf(part, GEMINI_PROVIDER, CARRIED_PARTS_KIND, model)?.payload;
381
+ return typeof carried?.signature === 'string' && carried.signature ? carried.signature : undefined;
382
+ }
383
+ /**
384
+ * The genai parts a part carries, as copies. Another Gemini model's go
385
+ * without their signatures, which bind to the model that wrote them.
386
+ */
387
+ function carriedPartsOf(part, model) {
388
+ const state = providerStateOf(part, GEMINI_PROVIDER, CARRIED_PARTS_KIND);
389
+ const before = state?.payload?.before;
390
+ if (!Array.isArray(before))
391
+ return [];
392
+ const own = state?.model === undefined || state.model === model;
393
+ return before.filter(isPlainObject).map((raw) => {
394
+ const { thoughtSignature, ...rest } = raw;
395
+ return own && typeof thoughtSignature === 'string' && thoughtSignature ? { ...rest, thoughtSignature } : { ...rest };
396
+ });
397
+ }
398
+ /**
399
+ * A copy of the code execution or server-side invocation part a stored
400
+ * event held, as the genai mapping reads it back (`genai_part` state on its
401
+ * text projection), or undefined for any other part.
402
+ */
403
+ function storedCarriedPart(part) {
404
+ const payload = providerStateOf(part, GEMINI_PROVIDER, GENAI_PART_KIND)?.payload;
405
+ return isPlainObject(payload) && isCarriedWirePart(payload) ? { ...payload } : undefined;
406
+ }
407
+ /**
408
+ * Gemini 3 rejects a current-turn step whose first function call has no
409
+ * signature (a call another provider or model made). With placeholder
410
+ * signatures on, that call gets PLACEHOLDER_THOUGHT_SIGNATURE.
411
+ */
412
+ function withPlaceholderSignature(parts) {
413
+ const first = parts.find((p) => p.functionCall);
414
+ if (first && !first.thoughtSignature)
415
+ first.thoughtSignature = PLACEHOLDER_THOUGHT_SIGNATURE;
416
+ }
417
+ function resultToWire(part) {
418
+ const { result } = part;
419
+ const response = part.isError ? { error: result } : isPlainObject(result) ? result : { result };
420
+ return { functionResponse: { ...wireId(part.id), name: part.name, response } };
421
+ }
422
+ function blobToWire(part) {
423
+ return part.url !== undefined
424
+ ? { fileData: { mimeType: part.mimeType, fileUri: part.url } }
425
+ : { inlineData: { mimeType: part.mimeType, data: part.data } };
426
+ }
427
+ /** The id for the wire: Gemini's own ids go back; ids the engine or the genai mapping made stay home. */
428
+ function wireId(id) {
429
+ return id && !id.startsWith(ENGINE_CALL_ID_PREFIX) && !id.startsWith(MINTED_CALL_ID_PREFIX) ? { id } : {};
430
+ }
431
+ function toolConfigFor(request) {
432
+ if (!request.tools?.length)
433
+ return undefined;
434
+ const choice = request.toolChoice;
435
+ if (typeof choice === 'object') {
436
+ return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY, allowedFunctionNames: [choice.name] } };
437
+ }
438
+ const mode = choice === 'none'
439
+ ? FunctionCallingConfigMode.NONE
440
+ : choice === 'required'
441
+ ? FunctionCallingConfigMode.ANY
442
+ : request.tools.some((t) => t.strict)
443
+ ? FunctionCallingConfigMode.VALIDATED
444
+ : choice === 'auto'
445
+ ? FunctionCallingConfigMode.AUTO
446
+ : undefined;
447
+ return mode ? { functionCallingConfig: { mode } } : undefined;
448
+ }
449
+ /** ADR 0047's mapping for this model, with the thoughts asked for unless the setting is `none`. */
450
+ function thinkingConfigFor(model, setting) {
451
+ const mapped = reasoningConfig(model, setting).thinkingConfig;
452
+ if (!mapped)
453
+ return undefined;
454
+ const none = setting === 'none' || (typeof setting === 'object' && setting.budget_tokens <= 0);
455
+ return none ? { ...mapped } : { ...mapped, includeThoughts: true };
456
+ }
457
+ /** Finish reasons Gemini gives for withholding an answer on policy grounds. */
458
+ const CONTENT_FILTER = new Set(['SAFETY', 'RECITATION', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII']);
459
+ /** The finish reasons kept verbatim as an error code (contract.ts, GeminiErrorCode). */
460
+ const FINISH_CODES = new Set([
461
+ 'MAX_TOKENS', 'SAFETY', 'RECITATION', 'LANGUAGE', 'OTHER', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII',
462
+ 'MALFORMED_FUNCTION_CALL', 'IMAGE_SAFETY', 'UNEXPECTED_TOOL_CALL', 'TOO_MANY_TOOL_CALLS',
463
+ 'IMAGE_PROHIBITED_CONTENT', 'NO_IMAGE', 'IMAGE_RECITATION', 'IMAGE_OTHER',
464
+ ]);
465
+ /** The prompt block reasons kept verbatim as an error code. */
466
+ const BLOCK_CODES = new Set(['SAFETY', 'OTHER', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'IMAGE_SAFETY', 'MODEL_ARMOR', 'JAILBREAK']);
467
+ function isContentFilter(reason) {
468
+ return CONTENT_FILTER.has(reason) || reason.startsWith('IMAGE_');
469
+ }
470
+ function finishReasonOf(reason) {
471
+ if (!reason || reason === 'STOP')
472
+ return 'stop';
473
+ if (reason === 'MAX_TOKENS')
474
+ return 'max_tokens';
475
+ return isContentFilter(reason) ? 'content_filter' : 'other';
476
+ }
477
+ /**
478
+ * A part the contract has no type for, which Gemini wants back within the
479
+ * turn: code execution, and a server-side tool invocation.
480
+ */
481
+ function isCarried(part) {
482
+ return isCarriedWirePart(part);
483
+ }
484
+ /**
485
+ * Folds Gemini's responses (one, or a stream's chunks) into the contract's
486
+ * deltas and its one final. The first candidate is the answer.
487
+ */
488
+ class ReplyBuilder {
489
+ /** The thought parts seen, for the one thinking partial of a non-streamed call. */
490
+ thinking = [];
491
+ /** Gemini's last finish reason, as it sent it. */
492
+ finishReason;
493
+ #model;
494
+ #position;
495
+ #parts = [];
496
+ /** A signature whose own part the final does not carry, waiting for the next part. */
497
+ #carried;
498
+ /** Carried genai parts waiting for the next output part. */
499
+ #wire = [];
500
+ #calls = 0;
501
+ #usage;
502
+ #finishMessage;
503
+ #grounding;
504
+ #urlContext;
505
+ #blockReason;
506
+ #blockMessage;
507
+ #sawCandidate = false;
508
+ /** `position` is the conversation's length: the index the answer will take in it. */
509
+ constructor(model, position) {
510
+ this.#model = model;
511
+ this.#position = position;
512
+ }
513
+ /** Takes one response or chunk; returns its text and thinking deltas, in order. */
514
+ add(response) {
515
+ if (response.usageMetadata)
516
+ this.#usage = response.usageMetadata;
517
+ if (response.promptFeedback?.blockReason) {
518
+ this.#blockReason = response.promptFeedback.blockReason;
519
+ this.#blockMessage = response.promptFeedback.blockReasonMessage;
520
+ }
521
+ const candidate = response.candidates?.[0];
522
+ if (!candidate)
523
+ return [];
524
+ this.#sawCandidate = true;
525
+ if (candidate.finishReason)
526
+ this.finishReason = candidate.finishReason;
527
+ if (candidate.finishMessage)
528
+ this.#finishMessage = candidate.finishMessage;
529
+ if (candidate.groundingMetadata)
530
+ this.#grounding = candidate.groundingMetadata;
531
+ if (candidate.urlContextMetadata)
532
+ this.#urlContext = candidate.urlContextMetadata;
533
+ const deltas = [];
534
+ for (const part of candidate.content?.parts ?? []) {
535
+ const signature = typeof part.thoughtSignature === 'string' && part.thoughtSignature ? part.thoughtSignature : undefined;
536
+ if (part.thought) {
537
+ if (part.text) {
538
+ const delta = { type: 'thinking', text: part.text };
539
+ deltas.push(delta);
540
+ this.thinking.push(delta);
541
+ }
542
+ if (signature)
543
+ this.#carried = signature;
544
+ }
545
+ else if (part.functionCall?.name) {
546
+ const { id, name, args } = part.functionCall;
547
+ const index = this.#calls++;
548
+ this.#push({
549
+ type: 'toolCall',
550
+ id: id || `${ENGINE_CALL_ID_PREFIX}${this.#position}-${index}-${name}`,
551
+ name,
552
+ args: isPlainObject(args) ? args : {},
553
+ }, signature);
554
+ }
555
+ else if (part.inlineData?.data !== undefined && part.inlineData.mimeType) {
556
+ this.#push({ type: 'blob', mimeType: part.inlineData.mimeType, data: part.inlineData.data }, signature);
557
+ }
558
+ else if (part.fileData?.fileUri && part.fileData.mimeType) {
559
+ this.#push({ type: 'blob', mimeType: part.fileData.mimeType, url: part.fileData.fileUri }, signature);
560
+ }
561
+ else if (part.text) {
562
+ deltas.push({ type: 'text', text: part.text });
563
+ this.#addText(part.text, signature);
564
+ }
565
+ else if (isCarried(part)) {
566
+ // Kept whole, its own signature with it; a thought's signature
567
+ // waiting for a part goes on this one, the next part Gemini sent.
568
+ const wire = { ...part };
569
+ if (!signature && this.#carried)
570
+ wire.thoughtSignature = this.#carried;
571
+ this.#carried = undefined;
572
+ this.#wire.push(wire);
573
+ }
574
+ else if (signature) {
575
+ // An empty text part carrying a signature closes the text before it.
576
+ const last = this.#parts.at(-1);
577
+ if (typeof part.text === 'string' && last?.part.type === 'text' && !last.signature && !this.#carried && !this.#wire.length) {
578
+ last.signature = signature;
579
+ }
580
+ else {
581
+ this.#carried = signature;
582
+ }
583
+ }
584
+ }
585
+ return deltas;
586
+ }
587
+ /** The one final for a call that ran to its end. */
588
+ final(searchTool) {
589
+ const base = this.#base();
590
+ const answer = base.parts.map((p) => (p.type === 'text' ? p.text : '')).join('');
591
+ const grounding = groundingOf(this.#grounding, this.#urlContext, searchTool, answer);
592
+ const response = { ...base, ...(grounding ? { grounding } : {}) };
593
+ if (!this.#sawCandidate) {
594
+ if (this.#blockReason) {
595
+ const code = BLOCK_CODES.has(this.#blockReason) ? this.#blockReason : 'OTHER';
596
+ const why = code === this.#blockReason ? '' : `, reported as ${this.#blockReason}`;
597
+ return {
598
+ ...response,
599
+ finishReason: 'content_filter',
600
+ error: geminiError(code, `Gemini blocked the prompt (${code}${why})${detail(this.#blockMessage)}.`),
601
+ };
602
+ }
603
+ return { ...response, finishReason: 'error', error: geminiError('UNKNOWN_ERROR', 'Gemini returned neither a candidate nor prompt feedback.') };
604
+ }
605
+ const reason = this.finishReason;
606
+ if (reason && isContentFilter(reason)) {
607
+ // Withheld on policy grounds: an error even when some text came first.
608
+ return { ...response, error: geminiError(codeOf(reason), `Gemini withheld the answer (${reason})${detail(this.#finishMessage)}.`) };
609
+ }
610
+ const answered = base.parts.some((p) => p.type !== 'text' || p.text);
611
+ if (!answered && reason && reason !== 'STOP') {
612
+ const message = reason === 'MAX_TOKENS'
613
+ ? 'Gemini ran out of output tokens before it answered (MAX_TOKENS). Raise sampling.maxOutputTokens or lower reasoning.'
614
+ : `Gemini stopped without an answer (${reason})${detail(this.#finishMessage)}.`;
615
+ return { ...response, error: geminiError(codeOf(reason), message) };
616
+ }
617
+ return response;
618
+ }
619
+ /** The final for a call that threw: GEMINI_ERROR, with what arrived before it. */
620
+ failed(err, secret) {
621
+ const status = errorStatus(err);
622
+ return {
623
+ ...this.#base(),
624
+ finishReason: 'error',
625
+ error: {
626
+ code: 'GEMINI_ERROR',
627
+ message: scrub(messageOf(err), secret),
628
+ retryable: classifyError(err).retryable,
629
+ ...(status !== undefined ? { status } : {}),
630
+ },
631
+ };
632
+ }
633
+ /** The final for a canceled call: never retryable, so no fallback answers it. */
634
+ aborted() {
635
+ return { ...this.#base(), finishReason: 'error', error: geminiError('GEMINI_ERROR', 'The Gemini request was aborted.') };
636
+ }
637
+ #base() {
638
+ const parts = this.#outputParts();
639
+ const usage = usageOf(this.#usage);
640
+ const finishReason = parts.some((p) => p.type === 'toolCall') ? 'tool_call' : finishReasonOf(this.finishReason);
641
+ return { partial: false, parts, finishReason, ...(usage ? { usage } : {}) };
642
+ }
643
+ #push(part, signature) {
644
+ this.#parts.push({ part, signature: signature ?? this.#carried, ...(this.#wire.length ? { before: this.#wire } : {}) });
645
+ this.#carried = undefined;
646
+ this.#wire = [];
647
+ }
648
+ /** Streamed text joins the text part before it until a signature or a carried part closes that part. */
649
+ #addText(text, signature) {
650
+ const last = this.#parts.at(-1);
651
+ if (last?.part.type === 'text' && !last.signature && !this.#carried && !this.#wire.length) {
652
+ last.part = { type: 'text', text: last.part.text + text };
653
+ if (signature)
654
+ last.signature = signature;
655
+ return;
656
+ }
657
+ this.#push({ type: 'text', text }, signature);
658
+ }
659
+ #outputParts() {
660
+ let pending = this.#parts;
661
+ let trailing;
662
+ if (this.#wire.length) {
663
+ // Carried parts with no output part after them ride on an empty text
664
+ // part of their own, with any signature that came after them.
665
+ pending = [...pending, { part: { type: 'text', text: '' }, before: this.#wire, ...(this.#carried ? { signature: this.#carried } : {}) }];
666
+ }
667
+ else {
668
+ // A signature with no output part after it stays with the last part
669
+ // that has none of its own.
670
+ const last = pending.at(-1);
671
+ trailing = this.#carried && last && !last.signature ? this.#carried : undefined;
672
+ }
673
+ return pending.map(({ part, signature, before }, i) => {
674
+ const s = signature ?? (i === pending.length - 1 ? trailing : undefined);
675
+ const state = this.#stateOf(s, before);
676
+ return state ? { ...part, providerState: state } : part;
677
+ });
678
+ }
679
+ #stateOf(signature, before) {
680
+ if (before?.length) {
681
+ const payload = { before, ...(signature ? { signature } : {}) };
682
+ return { provider: GEMINI_PROVIDER, kind: CARRIED_PARTS_KIND, model: this.#model, payload };
683
+ }
684
+ return signature ? { provider: GEMINI_PROVIDER, kind: THOUGHT_SIGNATURE_KIND, model: this.#model, payload: signature } : undefined;
685
+ }
686
+ }
687
+ /** Gemini's usage under the contract's meanings: cached tokens inside the input, thinking inside the output. */
688
+ function usageOf(meta) {
689
+ if (!meta)
690
+ return undefined;
691
+ const usage = {
692
+ inputTokens: (meta.promptTokenCount ?? 0) + (meta.toolUsePromptTokenCount ?? 0),
693
+ outputTokens: (meta.candidatesTokenCount ?? 0) + (meta.thoughtsTokenCount ?? 0),
694
+ };
695
+ if (meta.thoughtsTokenCount !== undefined)
696
+ usage.thinkingTokens = meta.thoughtsTokenCount;
697
+ if (meta.cachedContentTokenCount !== undefined)
698
+ usage.cacheReadTokens = meta.cachedContentTokenCount;
699
+ return usage;
700
+ }
701
+ /**
702
+ * The grounding: the queries run, each page a grounding support cites with
703
+ * the span of the answer it supports, each other searched page once without
704
+ * a span, and each page urlContext retrieved without a span. `answer` is the
705
+ * final's text parts joined, which the spans index.
706
+ */
707
+ function groundingOf(meta, urls, tool, answer) {
708
+ const citations = [];
709
+ const chunks = meta?.groundingChunks ?? [];
710
+ const spanned = new Set();
711
+ for (const support of meta?.groundingSupports ?? []) {
712
+ const span = spanOf(support.segment, answer);
713
+ if (!span)
714
+ continue;
715
+ for (const index of new Set(support.groundingChunkIndices ?? [])) {
716
+ const web = chunks[index]?.web;
717
+ if (!web?.uri)
718
+ continue;
719
+ spanned.add(web.uri);
720
+ citations.push({ url: web.uri, ...(web.title ? { title: web.title } : {}), ...span });
721
+ }
722
+ }
723
+ const seen = new Set(spanned);
724
+ for (const chunk of chunks) {
725
+ const url = chunk.web?.uri;
726
+ if (!url || seen.has(url))
727
+ continue;
728
+ seen.add(url);
729
+ citations.push(chunk.web?.title ? { url, title: chunk.web.title } : { url });
730
+ }
731
+ for (const entry of urls?.urlMetadata ?? []) {
732
+ const url = entry.retrievedUrl;
733
+ // A page urlContext could not read (an error, a paywall, an unsafe page) supports nothing.
734
+ if (!url || seen.has(url) || entry.urlRetrievalStatus !== 'URL_RETRIEVAL_STATUS_SUCCESS')
735
+ continue;
736
+ seen.add(url);
737
+ citations.push({ url });
738
+ }
739
+ const searchQueries = (meta?.webSearchQueries ?? []).filter((q) => typeof q === 'string' && q).map((query) => ({ tool, query }));
740
+ if (!citations.length && !searchQueries.length)
741
+ return undefined;
742
+ return { ...(citations.length ? { citations } : {}), ...(searchQueries.length ? { searchQueries } : {}) };
743
+ }
744
+ /**
745
+ * A segment's span in `answer` as UTF-16 offsets, end exclusive. Gemini
746
+ * gives UTF-8 byte offsets (a start of 0 is left out of the JSON) and the
747
+ * segment's text. When the offsets do not land on that text (they are
748
+ * relative to one part, or the stream was joined differently), the text's
749
+ * first place in the answer is the span; when it is not in the answer,
750
+ * there is none.
751
+ */
752
+ function spanOf(segment, answer) {
753
+ if (!segment)
754
+ return undefined;
755
+ const text = typeof segment.text === 'string' && segment.text ? segment.text : undefined;
756
+ if (typeof segment.endIndex === 'number') {
757
+ const start = utf16Offset(answer, segment.startIndex ?? 0);
758
+ const end = utf16Offset(answer, segment.endIndex);
759
+ if (start !== undefined && end !== undefined && start < end && (text === undefined || answer.slice(start, end) === text))
760
+ return { start, end };
761
+ }
762
+ if (text === undefined)
763
+ return undefined;
764
+ const at = answer.indexOf(text);
765
+ return at < 0 ? undefined : { start: at, end: at + text.length };
766
+ }
767
+ /** The UTF-16 offset in `text` of a UTF-8 byte offset; undefined when it falls inside a character or past the end. */
768
+ function utf16Offset(text, bytes) {
769
+ if (!Number.isInteger(bytes) || bytes < 0)
770
+ return undefined;
771
+ let seen = 0;
772
+ let i = 0;
773
+ while (seen < bytes && i < text.length) {
774
+ const code = text.codePointAt(i) ?? 0;
775
+ seen += code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
776
+ i += code >= 0x10000 ? 2 : 1;
777
+ }
778
+ return seen === bytes ? i : undefined;
779
+ }
780
+ function codeOf(reason) {
781
+ return FINISH_CODES.has(reason) ? reason : 'OTHER';
782
+ }
783
+ function geminiError(code, message) {
784
+ return { code, message, retryable: false };
785
+ }
786
+ function setupError(code, message) {
787
+ return { code, message, retryable: false };
788
+ }
789
+ function detail(message) {
790
+ return message ? `: ${message}` : '';
791
+ }
792
+ // ── Transport ────────────────────────────────────────────────────────────────
793
+ async function* streamOf(client, params) {
794
+ yield* await client.models.generateContentStream(params);
795
+ }
796
+ async function* responseOf(client, params) {
797
+ yield await client.models.generateContent(params);
798
+ }
799
+ /**
800
+ * Iterates `source` until `signal` aborts, and then stops at once: the SDK
801
+ * aborts its fetch on the same signal, but a stalled stream or a backoff
802
+ * sleep must not hold the caller.
803
+ */
804
+ async function* untilAborted(source, signal) {
805
+ if (!signal) {
806
+ yield* source;
807
+ return;
808
+ }
809
+ let onAbort = () => { };
810
+ const aborted = new Promise((_, reject) => {
811
+ onAbort = () => reject(new Error('aborted'));
812
+ });
813
+ aborted.catch(() => { });
814
+ if (signal.aborted)
815
+ onAbort();
816
+ else
817
+ signal.addEventListener('abort', onAbort, { once: true });
818
+ let done = false;
819
+ try {
820
+ for (;;) {
821
+ const next = await Promise.race([source.next(), aborted]);
822
+ if (next.done) {
823
+ done = true;
824
+ return;
825
+ }
826
+ yield next.value;
827
+ }
828
+ }
829
+ finally {
830
+ signal.removeEventListener('abort', onAbort);
831
+ if (!done)
832
+ source.return(undefined).catch(() => { });
833
+ }
834
+ }
835
+ // ── Helpers ──────────────────────────────────────────────────────────────────
836
+ function isPlainObject(value) {
837
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
838
+ }
839
+ function nonEmpty(value) {
840
+ return value?.trim() || undefined;
841
+ }
842
+ /** The Gemini API key from the environment, in ADK's order. */
843
+ function envApiKey() {
844
+ return nonEmpty(process.env.GOOGLE_GENAI_API_KEY) ?? nonEmpty(process.env.GOOGLE_API_KEY) ?? nonEmpty(process.env.GEMINI_API_KEY);
845
+ }
846
+ function messageOf(err) {
847
+ return err instanceof Error ? err.message : String(err);
848
+ }
849
+ /** A Google API key's shape, so a key in a provider message never reaches a person or a model. */
850
+ const GOOGLE_API_KEY_SHAPE = /AIza[0-9A-Za-z_-]{35}/g;
851
+ function scrub(message, secret) {
852
+ const out = secret ? message.split(secret).join('[redacted]') : message;
853
+ return out.replace(GOOGLE_API_KEY_SHAPE, '[redacted]');
854
+ }