melchizedek-agents 0.18.0 → 0.19.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 (281) hide show
  1. package/.env.example +12 -0
  2. package/CHANGELOG.md +825 -0
  3. package/DOCUMENTATION.md +217 -59
  4. package/README.md +42 -1
  5. package/config/agents/examples/harness.yaml +3 -1
  6. package/config/agents/examples/model_zoo.yaml +10 -9
  7. package/config/agents/syndicateSchema.yaml +50 -16
  8. package/config/agents/templates/account_memory.yaml +2 -6
  9. package/config/agents/templates/case_desk.yaml +3 -9
  10. package/config/agents/templates/draft_review.yaml +3 -9
  11. package/config/agents/templates/intake_extractor.yaml +1 -3
  12. package/config/agents/templates/research_brief.yaml +3 -9
  13. package/config/agents/templates/research_desk.yaml +3 -9
  14. package/config/agents/templates/review_panel.yaml +4 -12
  15. package/config/agents/templates/support_triage.yaml +4 -12
  16. package/config/agents/templates/systems_operator.yaml +2 -6
  17. package/db/hardening.sql +7 -1
  18. package/db/migrations/0013_tool_credentials.sql +260 -0
  19. package/dist/lib/a2a/app.d.ts +70 -6
  20. package/dist/lib/a2a/app.js +107 -2
  21. package/dist/lib/a2a/executor.d.ts +20 -3
  22. package/dist/lib/a2a/executor.js +66 -7
  23. package/dist/lib/a2a/remoteAgent.d.ts +24 -9
  24. package/dist/lib/a2a/remoteAgent.js +31 -14
  25. package/dist/lib/compile.d.ts +121 -30
  26. package/dist/lib/compile.js +137 -124
  27. package/dist/lib/compileAdk.d.ts +29 -0
  28. package/dist/lib/compileAdk.js +132 -0
  29. package/dist/lib/compileNative.d.ts +85 -0
  30. package/dist/lib/compileNative.js +222 -0
  31. package/dist/lib/config.d.ts +6 -5
  32. package/dist/lib/config.js +6 -5
  33. package/dist/lib/doctor.js +1 -0
  34. package/dist/lib/index.d.ts +2 -1
  35. package/dist/lib/index.js +2 -1
  36. package/dist/lib/loadSyndicate.d.ts +18 -0
  37. package/dist/lib/memory/erase.d.ts +5 -3
  38. package/dist/lib/memory/erase.js +5 -2
  39. package/dist/lib/memory/namespace.d.ts +8 -1
  40. package/dist/lib/memory/namespace.js +12 -6
  41. package/dist/lib/memory/providers.js +2 -1
  42. package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
  43. package/dist/lib/memory/supabaseMemoryService.js +42 -2
  44. package/dist/lib/model.d.ts +47 -0
  45. package/dist/lib/model.js +37 -0
  46. package/dist/lib/models/adapterResolver.d.ts +113 -0
  47. package/dist/lib/models/adapterResolver.js +136 -0
  48. package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
  49. package/dist/lib/models/adkGeminiAdapter.js +367 -0
  50. package/dist/lib/models/adkShim.d.ts +111 -0
  51. package/dist/lib/models/adkShim.js +136 -0
  52. package/dist/lib/models/capabilities.d.ts +3 -0
  53. package/dist/lib/models/capabilities.js +20 -13
  54. package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
  55. package/dist/lib/models/chatCompletionsAdapter.js +854 -0
  56. package/dist/lib/models/circuitBreaker.d.ts +42 -0
  57. package/dist/lib/models/circuitBreaker.js +70 -0
  58. package/dist/lib/models/claudeAdapter.d.ts +103 -0
  59. package/dist/lib/models/claudeAdapter.js +669 -0
  60. package/dist/lib/models/claudeLlm.d.ts +39 -62
  61. package/dist/lib/models/claudeLlm.js +44 -349
  62. package/dist/lib/models/claudeModels.d.ts +127 -0
  63. package/dist/lib/models/claudeModels.js +237 -0
  64. package/dist/lib/models/contract.d.ts +395 -0
  65. package/dist/lib/models/contract.js +39 -0
  66. package/dist/lib/models/endpoints.js +2 -1
  67. package/dist/lib/models/errorResponse.d.ts +50 -0
  68. package/dist/lib/models/errorResponse.js +48 -0
  69. package/dist/lib/models/fallback.d.ts +17 -10
  70. package/dist/lib/models/fallback.js +53 -42
  71. package/dist/lib/models/fallbackAdapter.d.ts +55 -0
  72. package/dist/lib/models/fallbackAdapter.js +62 -0
  73. package/dist/lib/models/gateway.js +2 -1
  74. package/dist/lib/models/gatewayAdapter.d.ts +51 -0
  75. package/dist/lib/models/gatewayAdapter.js +81 -0
  76. package/dist/lib/models/gatewayLlm.d.ts +12 -33
  77. package/dist/lib/models/gatewayLlm.js +14 -75
  78. package/dist/lib/models/geminiAdapter.d.ts +123 -0
  79. package/dist/lib/models/geminiAdapter.js +831 -0
  80. package/dist/lib/models/geminiState.d.ts +25 -0
  81. package/dist/lib/models/geminiState.js +25 -0
  82. package/dist/lib/models/genaiMapping.d.ts +263 -0
  83. package/dist/lib/models/genaiMapping.js +862 -0
  84. package/dist/lib/models/gptAdapter.d.ts +240 -0
  85. package/dist/lib/models/gptAdapter.js +804 -0
  86. package/dist/lib/models/gptLlm.d.ts +63 -123
  87. package/dist/lib/models/gptLlm.js +88 -602
  88. package/dist/lib/models/grokAdapter.d.ts +86 -0
  89. package/dist/lib/models/grokAdapter.js +142 -0
  90. package/dist/lib/models/grokLlm.d.ts +22 -45
  91. package/dist/lib/models/grokLlm.js +22 -67
  92. package/dist/lib/models/kimiAdapter.d.ts +157 -0
  93. package/dist/lib/models/kimiAdapter.js +203 -0
  94. package/dist/lib/models/kimiLlm.d.ts +15 -107
  95. package/dist/lib/models/kimiLlm.js +17 -153
  96. package/dist/lib/models/ollamaAdapter.d.ts +125 -0
  97. package/dist/lib/models/ollamaAdapter.js +155 -0
  98. package/dist/lib/models/ollamaLlm.d.ts +11 -107
  99. package/dist/lib/models/ollamaLlm.js +13 -151
  100. package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
  101. package/dist/lib/models/openAiCompatibleLlm.js +109 -671
  102. package/dist/lib/models/providerState.d.ts +63 -0
  103. package/dist/lib/models/providerState.js +61 -0
  104. package/dist/lib/models/reasoning.d.ts +26 -0
  105. package/dist/lib/models/reasoning.js +59 -0
  106. package/dist/lib/models/registry.d.ts +58 -35
  107. package/dist/lib/models/registry.js +89 -118
  108. package/dist/lib/models/retry.d.ts +2 -1
  109. package/dist/lib/models/retry.js +3 -2
  110. package/dist/lib/models/schemaNormalize.d.ts +98 -0
  111. package/dist/lib/models/schemaNormalize.js +321 -1
  112. package/dist/lib/models/tracedGemini.d.ts +41 -0
  113. package/dist/lib/models/tracedGemini.js +98 -0
  114. package/dist/lib/models/urls.d.ts +11 -0
  115. package/dist/lib/models/urls.js +16 -0
  116. package/dist/lib/net/redirects.d.ts +21 -2
  117. package/dist/lib/net/redirects.js +11 -34
  118. package/dist/lib/observability/audit.d.ts +5 -1
  119. package/dist/lib/observability/lineage.d.ts +14 -0
  120. package/dist/lib/observability/lineage.js +33 -0
  121. package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
  122. package/dist/lib/observability/supabaseSpanExporter.js +17 -9
  123. package/dist/lib/observability/tracer.d.ts +26 -8
  124. package/dist/lib/observability/tracer.js +43 -18
  125. package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
  126. package/dist/lib/runtime/adkMemoryBridge.js +68 -0
  127. package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
  128. package/dist/lib/runtime/adkSessionBridge.js +176 -0
  129. package/dist/lib/runtime/credentials.d.ts +59 -0
  130. package/dist/lib/runtime/credentials.js +62 -0
  131. package/dist/lib/runtime/events.d.ts +229 -0
  132. package/dist/lib/runtime/events.js +274 -0
  133. package/dist/lib/runtime/logging.d.ts +32 -0
  134. package/dist/lib/runtime/logging.js +55 -0
  135. package/dist/lib/runtime/memoryService.d.ts +72 -0
  136. package/dist/lib/runtime/memoryService.js +25 -0
  137. package/dist/lib/runtime/native/agentLoop.d.ts +203 -0
  138. package/dist/lib/runtime/native/agentLoop.js +687 -0
  139. package/dist/lib/runtime/native/compaction.d.ts +103 -0
  140. package/dist/lib/runtime/native/compaction.js +191 -0
  141. package/dist/lib/runtime/native/delegate.d.ts +98 -0
  142. package/dist/lib/runtime/native/delegate.js +173 -0
  143. package/dist/lib/runtime/native/history.d.ts +83 -0
  144. package/dist/lib/runtime/native/history.js +312 -0
  145. package/dist/lib/runtime/native/interrupts.d.ts +126 -0
  146. package/dist/lib/runtime/native/interrupts.js +291 -0
  147. package/dist/lib/runtime/native/request.d.ts +215 -0
  148. package/dist/lib/runtime/native/request.js +585 -0
  149. package/dist/lib/runtime/native/selfCorrection.d.ts +106 -0
  150. package/dist/lib/runtime/native/selfCorrection.js +253 -0
  151. package/dist/lib/runtime/native/step.d.ts +148 -0
  152. package/dist/lib/runtime/native/step.js +239 -0
  153. package/dist/lib/runtime/native/taskMode.d.ts +64 -0
  154. package/dist/lib/runtime/native/taskMode.js +126 -0
  155. package/dist/lib/runtime/native/telemetry.d.ts +75 -0
  156. package/dist/lib/runtime/native/telemetry.js +299 -0
  157. package/dist/lib/runtime/native/tempState.d.ts +31 -0
  158. package/dist/lib/runtime/native/tempState.js +56 -0
  159. package/dist/lib/runtime/nativeTurn.d.ts +86 -0
  160. package/dist/lib/runtime/nativeTurn.js +107 -0
  161. package/dist/lib/runtime/questions.d.ts +29 -7
  162. package/dist/lib/runtime/questions.js +47 -32
  163. package/dist/lib/runtime/runtimeFlag.d.ts +30 -0
  164. package/dist/lib/runtime/runtimeFlag.js +50 -0
  165. package/dist/lib/runtime/sessions.d.ts +166 -0
  166. package/dist/lib/runtime/sessions.js +201 -0
  167. package/dist/lib/runtime/syndicateTurn.d.ts +41 -5
  168. package/dist/lib/runtime/syndicateTurn.js +202 -32
  169. package/dist/lib/session/supabaseSessionService.d.ts +64 -17
  170. package/dist/lib/session/supabaseSessionService.js +148 -105
  171. package/dist/lib/session/transcript.d.ts +68 -22
  172. package/dist/lib/session/transcript.js +102 -28
  173. package/dist/lib/skills.js +33 -7
  174. package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
  175. package/dist/lib/storage/postgres/credentialStore.js +82 -0
  176. package/dist/lib/storage/postgres/index.d.ts +12 -0
  177. package/dist/lib/storage/postgres/index.js +6 -1
  178. package/dist/lib/storage/postgres/sessionService.d.ts +43 -15
  179. package/dist/lib/storage/postgres/sessionService.js +128 -66
  180. package/dist/lib/syndicateSchema.d.ts +47 -0
  181. package/dist/lib/syndicateSchema.js +72 -3
  182. package/dist/lib/toolRegistry.d.ts +15 -2
  183. package/dist/lib/toolRegistry.js +33 -17
  184. package/dist/lib/tools/adkTool.d.ts +81 -0
  185. package/dist/lib/tools/adkTool.js +268 -0
  186. package/dist/lib/tools/auth.d.ts +118 -0
  187. package/dist/lib/tools/auth.js +89 -0
  188. package/dist/lib/tools/collectionsSearchTool.d.ts +7 -6
  189. package/dist/lib/tools/collectionsSearchTool.js +11 -26
  190. package/dist/lib/tools/credentialCipher.d.ts +58 -0
  191. package/dist/lib/tools/credentialCipher.js +116 -0
  192. package/dist/lib/tools/credentialStore.d.ts +77 -0
  193. package/dist/lib/tools/credentialStore.js +246 -0
  194. package/dist/lib/tools/examples.d.ts +43 -0
  195. package/dist/lib/tools/examples.js +62 -0
  196. package/dist/lib/tools/generateImageTool.d.ts +15 -6
  197. package/dist/lib/tools/generateImageTool.js +19 -29
  198. package/dist/lib/tools/inspectImageTool.d.ts +9 -6
  199. package/dist/lib/tools/inspectImageTool.js +13 -17
  200. package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
  201. package/dist/lib/tools/mcpToolFactory.js +46 -47
  202. package/dist/lib/tools/memoryTools.d.ts +61 -0
  203. package/dist/lib/tools/memoryTools.js +119 -0
  204. package/dist/lib/tools/nativeTools.d.ts +38 -0
  205. package/dist/lib/tools/nativeTools.js +43 -0
  206. package/dist/lib/tools/oauthConsent.d.ts +181 -0
  207. package/dist/lib/tools/oauthConsent.js +330 -0
  208. package/dist/lib/tools/openapi/call.d.ts +108 -0
  209. package/dist/lib/tools/openapi/call.js +295 -0
  210. package/dist/lib/tools/openapi/parse.d.ts +147 -0
  211. package/dist/lib/tools/openapi/parse.js +570 -0
  212. package/dist/lib/tools/openapiTools.d.ts +25 -17
  213. package/dist/lib/tools/openapiTools.js +124 -121
  214. package/dist/lib/tools/scienceTools.d.ts +14 -14
  215. package/dist/lib/tools/skillToolset.d.ts +58 -87
  216. package/dist/lib/tools/skillToolset.js +61 -142
  217. package/dist/lib/tools/skills/env.d.ts +58 -0
  218. package/dist/lib/tools/skills/env.js +165 -0
  219. package/dist/lib/tools/skills/executor.d.ts +114 -0
  220. package/dist/lib/tools/skills/executor.js +331 -0
  221. package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
  222. package/dist/lib/tools/skills/frontmatter.js +215 -0
  223. package/dist/lib/tools/skills/loader.d.ts +91 -0
  224. package/dist/lib/tools/skills/loader.js +257 -0
  225. package/dist/lib/tools/skills/tools.d.ts +187 -0
  226. package/dist/lib/tools/skills/tools.js +386 -0
  227. package/dist/lib/tools/taskTools.d.ts +15 -15
  228. package/dist/lib/tools/tool.d.ts +354 -0
  229. package/dist/lib/tools/tool.js +304 -0
  230. package/dist/lib/tools/toolContract.d.ts +89 -33
  231. package/dist/lib/tools/toolContract.js +104 -82
  232. package/dist/lib/tools/urlContextTool.d.ts +3 -0
  233. package/dist/lib/tools/urlContextTool.js +5 -1
  234. package/dist/lib/tools/webExtractTool.d.ts +2 -2
  235. package/dist/lib/tools/webExtractTool.js +2 -1
  236. package/dist/lib/tools/webSearchTool.d.ts +6 -9
  237. package/dist/lib/tools/webSearchTool.js +14 -59
  238. package/dist/lib/tools/wikiTools.d.ts +19 -19
  239. package/dist/lib/tools/wikiTools.js +13 -9
  240. package/dist/lib/tools/xApiSearchTool.d.ts +2 -2
  241. package/dist/lib/tools/xApiSearchTool.js +2 -1
  242. package/dist/lib/tools/xSearchTool.d.ts +7 -9
  243. package/dist/lib/tools/xSearchTool.js +11 -62
  244. package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
  245. package/dist/lib/tools/xaiSearchParams.js +126 -0
  246. package/dist/lib/wiki/agentRun.d.ts +12 -0
  247. package/dist/lib/wiki/agentRun.js +72 -34
  248. package/dist/lib/wiki/entities.d.ts +12 -0
  249. package/dist/lib/wiki/entities.js +45 -3
  250. package/dist/lib/workflow/agentNode.d.ts +136 -0
  251. package/dist/lib/workflow/agentNode.js +274 -0
  252. package/dist/lib/workflow/graph.d.ts +177 -0
  253. package/dist/lib/workflow/graph.js +458 -0
  254. package/dist/lib/workflow/nodeEvents.d.ts +50 -0
  255. package/dist/lib/workflow/nodeEvents.js +101 -0
  256. package/dist/lib/workflow/pause.d.ts +122 -0
  257. package/dist/lib/workflow/pause.js +180 -0
  258. package/dist/lib/workflow/resume.d.ts +165 -0
  259. package/dist/lib/workflow/resume.js +440 -0
  260. package/dist/lib/workflow/route.d.ts +58 -0
  261. package/dist/lib/workflow/route.js +69 -0
  262. package/dist/lib/workflow/scheduler.d.ts +415 -0
  263. package/dist/lib/workflow/scheduler.js +743 -0
  264. package/dist/lib/workflow/toolNode.d.ts +127 -0
  265. package/dist/lib/workflow/toolNode.js +236 -0
  266. package/dist/lib/workflow/turn.d.ts +118 -0
  267. package/dist/lib/workflow/turn.js +216 -0
  268. package/dist/lib/workflowConfig.d.ts +3 -2
  269. package/dist/lib/workflowConfig.js +3 -12
  270. package/dist/scripts/a2a_server.js +4 -3
  271. package/dist/scripts/assistant_worker.d.ts +2 -1
  272. package/dist/scripts/assistant_worker.js +9 -4
  273. package/dist/scripts/syndicate_chat.js +19 -10
  274. package/package.json +4 -1
  275. package/skills/briefs/melchizedek-author.md +3 -2
  276. package/skills/briefs/melchizedek-models.md +13 -9
  277. package/skills/briefs/melchizedek.md +3 -3
  278. package/skills/melchizedek/SKILL.md +2 -1
  279. package/skills/melchizedek-author/SKILL.md +2 -1
  280. package/skills/melchizedek-author/assets/minimal.yaml +2 -0
  281. package/skills/melchizedek-models/SKILL.md +23 -6
@@ -0,0 +1,862 @@
1
+ /**
2
+ * lib/models/genaiMapping.ts — @google/genai `Content`, and ADK's LlmRequest
3
+ * and LlmResponse, to and from the engine's model contract
4
+ * (lib/models/contract.ts, ADR 0048).
5
+ *
6
+ * WHY this file exists:
7
+ * The ADK runtime holds and stores @google/genai `Content`: the history of
8
+ * an LlmRequest, and the `content` of every stored event. The native
9
+ * runtime and every adapter on the contract speak `Message` and `Part`.
10
+ * This module converts between the two, so the native runtime can read a
11
+ * session ADK stored, and an adapter can move onto the contract while ADK
12
+ * still runs: its ADK class maps the LlmRequest in and each ModelResponse
13
+ * out. It is pure (no I/O, clock or randomness) and never mutates its
14
+ * input; leaf values (args, results, payloads) are shared, not copied.
15
+ * The contract stays a leaf: only this module names @google/*.
16
+ * wiki/models/model-contract.md ("From genai Content") is the spec.
17
+ *
18
+ * LOSSLESS FOR STORED CONTENT. Content → contract → Content gives back the
19
+ * same JSON, keys aside (both event tables are jsonb, which keeps no key
20
+ * order), for every part kind. tests/genaiMapping.test.ts runs every stored
21
+ * session fixture through it.
22
+ *
23
+ * Roles. `model` → assistant, `system` → system, a `user` content made
24
+ * only of functionResponse parts → tool, any other `user` content → user.
25
+ * Back: assistant → `model`, system → `system`, user and tool → `user`.
26
+ * A content with no role reads as user and comes back as `user`. A user
27
+ * content that mixes results with other parts is a user message, its
28
+ * results described as text (below), because a ToolMessage holds only
29
+ * results; the calls they answered are then left unanswered there. The
30
+ * engine's surfaces send an approval or an answer as a part of its own.
31
+ *
32
+ * Parts. Each genai part becomes one contract part:
33
+ * { text } ↔ TextPart
34
+ * { text, thought: true } ↔ ThinkingPart
35
+ * { functionCall: {name,args,id} } ↔ ToolCallPart
36
+ * { functionResponse: {id,name,response} } ↔ ToolResultPart
37
+ * { inlineData: {mimeType,data} } ↔ BlobPart with data
38
+ * { fileData: {mimeType,fileUri} } ↔ BlobPart with url
39
+ * A part's `thoughtSignature` ↔ providerState { provider: 'gemini', kind:
40
+ * 'thought_signature', payload: <signature> } on the same part, and any
41
+ * providerState another adapter wrote passes through unchanged.
42
+ *
43
+ * Everything else is carried whole. A part the contract cannot hold
44
+ * exactly (Gemini code execution, a field such as `videoMetadata` or
45
+ * `displayName`, a call with no `args`, a signature beside another
46
+ * provider's state) or cannot place in its message (a functionCall in a
47
+ * `user` content, as ADK writes `adk_request_confirmation`) keeps the
48
+ * original genai part as providerState { provider: 'gemini', kind:
49
+ * 'genai_part', payload: <the part> }, on the nearest part the message
50
+ * allows: a functionCall or functionResponse as itself where it may stand,
51
+ * else as text describing it; `executableCode` as its code in a fenced
52
+ * block; `codeExecutionResult` as its output; anything unknown as empty
53
+ * text. Back, that state is the genai part, verbatim. So only the Gemini
54
+ * adapter (provider `gemini`) replays it, and every other adapter sees
55
+ * the projection.
56
+ *
57
+ * Tool results. `response` is the result when it is an object; a response
58
+ * of exactly `{ result: <not an object> }` is that value; exactly
59
+ * `{ error: <not null or false> }` is that value with `isError: true` (a
60
+ * tool ADK caught throwing). Back: `{ error: result }` for an error, the
61
+ * result when it is an object, else `{ result }`. A tool's successful
62
+ * object result shaped `{ error }` therefore reads back as an error: the
63
+ * genai bytes cannot tell the two apart.
64
+ *
65
+ * Ids. A ToolCallPart always has an id. A genai call without one (Gemini
66
+ * returns none, and ADK strips its own `adk-` ids from every request) gets
67
+ * `genai-noid-<content>-<part>`, from its position, and a result without
68
+ * one takes the id of the latest open minted call of the same name, else
69
+ * one of its own (it then answers no call in the history). Back, a minted
70
+ * id is left off again, so the round trip restores the absence. Stored
71
+ * events keep the ids ADK assigned, which pass through as they are.
72
+ *
73
+ * THE REQUEST (llmRequestToModelRequest). The system instruction's text;
74
+ * the contents as above; client tools from `toolsDict` through
75
+ * contractToolDeclaration (lib/models/schemaNormalize.ts), which converts
76
+ * Gemini's dialect once; native tools from the toolsDict entries
77
+ * nativeToolOf names (the web_search, x_search and collections_search
78
+ * sentinels) and from Gemini's `config.tools` entries (`googleSearch` and
79
+ * `googleSearchRetrieval` → web_search, `urlContext`, `codeExecution`);
80
+ * `responseJsonSchema` or `responseSchema` as a lowercase outputSchema, and
81
+ * `responseMimeType: 'application/json'` with neither as `outputFormat:
82
+ * 'json'` (JSON mode, ADR 0061); the function-calling mode as toolChoice
83
+ * (`VALIDATED` as strict tools); the reasoning fields as a ReasoningSetting where it is exact (reasoningOf);
84
+ * sampling; stream; the abort signal. Not mapped: tool options inside a
85
+ * `config.tools` entry, any other `config.tools` entry, `includeThoughts`,
86
+ * and the generateContentConfig fields the contract leaves out (topK, seed,
87
+ * penalties, safetySettings).
88
+ *
89
+ * THE RESPONSE (modelResponseToLlmResponse). Parts back to a `model`
90
+ * content; a partial as `partial: true`, the final with `turnComplete:
91
+ * true`; usage in Gemini's meanings (usageToMetadata); error as `errorCode`
92
+ * and `errorMessage`, key-shaped text scrubbed, with `retryable` and
93
+ * `status` as customMetadata['error.retryable'] and ['error.status']
94
+ * (withRetryVerdict, lib/models/errorResponse.ts), the only place
95
+ * FallbackLlm reads whether a fallback may answer; finishReason as
96
+ * Gemini's, except that an error whose code is one of Gemini's finish
97
+ * reasons (the Gemini adapters keep it verbatim: MALFORMED_FUNCTION_CALL,
98
+ * RECITATION, ...) gets that reason itself, as ADK's Gemini reports both, so
99
+ * self-correction retries a malformed call and the stored event reads the
100
+ * same on either runtime (ADR 0088); grounding as the `webSearchQueries` and
101
+ * `groundingChunks[].web` that lib/grounding.ts reads. Not carried, since
102
+ * an LlmResponse has no field for them: `cacheWriteTokens`, a citation's
103
+ * span and cited text, and which native tool ran a query.
104
+ *
105
+ * THE REVERSE: a contract adapter over an ADK BaseLlm (lib/models/
106
+ * adkGeminiAdapter.ts, the wrapper over ADK's Gemini) maps the ModelRequest
107
+ * it is given to an LlmRequest, and each LlmResponse back.
108
+ *
109
+ * modelRequestToLlmRequest builds the LlmRequest ADK's path builds for a
110
+ * Gemini model: the system prompt as `systemInstruction`; the messages as
111
+ * above; client tools as `functionDeclarations` with the lowercase schema
112
+ * in `parametersJsonSchema`, and in `toolsDict` as declaration-only
113
+ * entries, where llmRequestToModelRequest and the ADK-path adapters read
114
+ * them; native tools as Gemini's tool objects, in the request's order;
115
+ * toolChoice and strict as the function-calling mode, sent only beside
116
+ * declarations; the output schema as `responseJsonSchema`, and
117
+ * `outputFormat: 'json'` without one as `responseMimeType:
118
+ * 'application/json'`; reasoning
119
+ * through reasoningConfig (lib/models/reasoning.ts), as the compiler maps
120
+ * it; sampling; the signal as `config.abortSignal`. What does not come
121
+ * back the same through llmRequestToModelRequest: `google_search` reads as
122
+ * `web_search`; `x_search` and `collections_search` are left out (Gemini
123
+ * has no tool for them, and ADK adds their sentinels only for xAI); `auto`
124
+ * reads as absent, and a choice without tools is not sent; one strict tool
125
+ * makes every tool strict, and strict is lost beside a forced choice; a
126
+ * level a model renders as a budget or another word reads back as that
127
+ * rendering; `outputFormat: 'json'` beside a schema reads back as the
128
+ * schema alone; `stream` is not an LlmRequest field. System messages stay
129
+ * `system` contents, which Gemini refuses: the caller folds them into the
130
+ * system prompt.
131
+ *
132
+ * llmResponseToModelResponse reads one LlmResponse. A partial is its text
133
+ * and thinking deltas. Anything else is a final: its parts as an assistant
134
+ * message's, minus thinking, whose signature moves to the next part (or
135
+ * stays with the last part when none follows); `model`, when given, on
136
+ * every thought_signature state, as GeminiAdapter writes it; ids minted
137
+ * from `index`, the answer's place in its conversation; `errorCode` as the
138
+ * error, with the verdict read back from customMetadata and key-shaped text
139
+ * scrubbed, except ADK's `STOP`, which is no error; Gemini's finish reason
140
+ * as the contract's (`tool_call` whenever a call is there); usage under
141
+ * the contract's meanings; grounding as the cited pages and the search
142
+ * queries, attributed to `searchTool`. Not read back: citation spans and
143
+ * cited text, `cacheWriteTokens`, and any thought text in a final, which is
144
+ * display only (the caller yields it as a partial).
145
+ *
146
+ * USAGE MEANINGS. usageToMetadata and usageFromMetadata use Gemini's: its
147
+ * `candidatesTokenCount` excludes the thinking in `thoughtsTokenCount`. The
148
+ * ADK-path GPT and chat-completions adapters write `candidatesTokenCount`
149
+ * with reasoning included, so usageFromMetadata on an event they stored
150
+ * counts that reasoning twice in `outputTokens`.
151
+ */
152
+ import { FinishReason, FunctionCallingConfigMode } from '@google/genai';
153
+ import { ERROR_RETRYABLE_KEY, ERROR_STATUS_KEY, errorText, withRetryVerdict } from './errorResponse.js';
154
+ import { GEMINI_PROVIDER, MINTED_CALL_ID_PREFIX, THOUGHT_SIGNATURE_KIND } from './geminiState.js';
155
+ import { reasoningConfig } from './reasoning.js';
156
+ import { contractToolDeclaration, nativeToolOf, toContractJsonSchema } from './schemaNormalize.js';
157
+ /**
158
+ * The provider id the mapping writes its own state under (only a Gemini
159
+ * adapter replays it), and the providerState kind for a Gemini
160
+ * `thoughtSignature`, whose payload is the signature. Defined once in
161
+ * lib/models/geminiState.ts, so the Gemini adapter spells them the same.
162
+ */
163
+ export { GEMINI_PROVIDER, THOUGHT_SIGNATURE_KIND };
164
+ /** providerState kind for a genai part the contract cannot hold exactly; the payload is the part. */
165
+ export const GENAI_PART_KIND = 'genai_part';
166
+ /** The prefix of an id the mapping made for a call or result that had none (defined in lib/models/geminiState.ts). */
167
+ export { MINTED_CALL_ID_PREFIX };
168
+ const ALLOWED = {
169
+ system: new Set(['text']),
170
+ user: new Set(['text', 'blob']),
171
+ assistant: new Set(['text', 'thinking', 'toolCall', 'blob']),
172
+ tool: new Set(['toolResult']),
173
+ };
174
+ const GENAI_ROLE = { system: 'system', user: 'user', assistant: 'model', tool: 'user' };
175
+ function isObject(value) {
176
+ return !!value && typeof value === 'object' && !Array.isArray(value);
177
+ }
178
+ /** The keys that carry a value: JSON drops undefined, so the mapping does too. */
179
+ function keysOf(o) {
180
+ return Object.keys(o).filter((k) => o[k] !== undefined);
181
+ }
182
+ function hasOnly(o, allowed) {
183
+ return keysOf(o).every((k) => allowed.includes(k));
184
+ }
185
+ export function isMintedCallId(id) {
186
+ return typeof id === 'string' && id.startsWith(MINTED_CALL_ID_PREFIX);
187
+ }
188
+ /** An id that may pass through as it is: absent, or a string the mapping did not make. */
189
+ function isOwnId(id) {
190
+ return id === undefined || (typeof id === 'string' && !isMintedCallId(id));
191
+ }
192
+ function json(value) {
193
+ try {
194
+ return JSON.stringify(value) ?? String(value);
195
+ }
196
+ catch {
197
+ return String(value);
198
+ }
199
+ }
200
+ function slotOf(content) {
201
+ if (content.role === 'model')
202
+ return 'assistant';
203
+ if (content.role === 'system')
204
+ return 'system';
205
+ const parts = content.parts ?? [];
206
+ if (content.role === 'user' && parts.length > 0 && parts.every((p) => isObject(p) && isObject(p.functionResponse))) {
207
+ return 'tool';
208
+ }
209
+ return 'user';
210
+ }
211
+ /** A response as a result: see the header for the rule and its one ambiguity. */
212
+ function resultOf(response) {
213
+ const keys = keysOf(response);
214
+ if (keys.length === 1 && keys[0] === 'error' && response.error !== null && response.error !== false) {
215
+ return { result: response.error, isError: true };
216
+ }
217
+ if (keys.length === 1 && keys[0] === 'result' && !isObject(response.result))
218
+ return { result: response.result };
219
+ return { result: response };
220
+ }
221
+ /** The genai part's own data, without its signature and state, as the nearest contract part. */
222
+ function project(rest) {
223
+ const { functionResponse: fr, functionCall: fc, inlineData: inline, fileData: file, text, thought } = rest;
224
+ const keys = keysOf(rest);
225
+ const only = (key) => keys.length === 1 && keys[0] === key;
226
+ const textual = (exact) => ({
227
+ part: thought === true ? { type: 'thinking', text: text } : { type: 'text', text: text },
228
+ exact,
229
+ });
230
+ // Text alone is exact; text beside a call, a result or a blob ranks below them.
231
+ if (typeof text === 'string' && hasOnly(rest, ['text', 'thought']) && (thought === undefined || thought === true))
232
+ return textual(true);
233
+ if (isObject(fr)) {
234
+ const exact = only('functionResponse') && hasOnly(fr, ['id', 'name', 'response']) && typeof fr.name === 'string' && isObject(fr.response) && isOwnId(fr.id);
235
+ const r = isObject(fr.response) ? resultOf(fr.response) : { result: fr.response ?? null };
236
+ const id = typeof fr.id === 'string' && !isMintedCallId(fr.id) ? fr.id : undefined;
237
+ return {
238
+ part: { type: 'toolResult', id: id ?? '', name: typeof fr.name === 'string' ? fr.name : '', ...r },
239
+ exact,
240
+ needsId: id === undefined,
241
+ };
242
+ }
243
+ if (isObject(fc)) {
244
+ const exact = only('functionCall') && hasOnly(fc, ['name', 'args', 'id']) && typeof fc.name === 'string' && isObject(fc.args) && isOwnId(fc.id);
245
+ const id = typeof fc.id === 'string' && !isMintedCallId(fc.id) ? fc.id : undefined;
246
+ return {
247
+ part: { type: 'toolCall', id: id ?? '', name: typeof fc.name === 'string' ? fc.name : '', args: isObject(fc.args) ? fc.args : {} },
248
+ exact,
249
+ needsId: id === undefined,
250
+ };
251
+ }
252
+ if (isObject(inline) && typeof inline.data === 'string') {
253
+ const exact = only('inlineData') && hasOnly(inline, ['mimeType', 'data']) && typeof inline.mimeType === 'string';
254
+ const mimeType = typeof inline.mimeType === 'string' ? inline.mimeType : 'application/octet-stream';
255
+ return { part: { type: 'blob', mimeType, data: inline.data }, exact };
256
+ }
257
+ if (isObject(file) && typeof file.fileUri === 'string') {
258
+ const exact = only('fileData') && hasOnly(file, ['mimeType', 'fileUri']) && typeof file.mimeType === 'string';
259
+ const mimeType = typeof file.mimeType === 'string' ? file.mimeType : 'application/octet-stream';
260
+ return { part: { type: 'blob', mimeType, url: file.fileUri }, exact };
261
+ }
262
+ if (typeof text === 'string')
263
+ return textual(false);
264
+ if (isObject(rest.executableCode)) {
265
+ const { code, language } = rest.executableCode;
266
+ const lang = typeof language === 'string' && language !== 'LANGUAGE_UNSPECIFIED' ? language.toLowerCase() : '';
267
+ return { part: { type: 'text', text: `\`\`\`${lang}\n${typeof code === 'string' ? code : ''}\n\`\`\`` }, exact: false };
268
+ }
269
+ if (isObject(rest.codeExecutionResult)) {
270
+ const { outcome, output } = rest.codeExecutionResult;
271
+ const failed = typeof outcome === 'string' && outcome !== 'OUTCOME_OK' ? ` (${outcome})` : '';
272
+ return { part: { type: 'text', text: `Output${failed}:\n${typeof output === 'string' ? output : ''}` }, exact: false };
273
+ }
274
+ return { part: { type: 'text', text: '' }, exact: false };
275
+ }
276
+ /** Text standing in for a part its message cannot hold. */
277
+ function describe(part) {
278
+ switch (part.type) {
279
+ case 'text':
280
+ case 'thinking':
281
+ return part.text;
282
+ case 'toolCall':
283
+ return `[${part.name} called with ${json(part.args)}]`;
284
+ case 'toolResult':
285
+ return `[${part.name} ${part.isError ? 'failed' : 'returned'} ${json(part.result)}]`;
286
+ case 'blob':
287
+ return `[${part.mimeType}${part.url !== undefined ? ` ${part.url}` : ''}]`;
288
+ }
289
+ }
290
+ /**
291
+ * Another adapter's state, which may ride through as it is. The mapping's
292
+ * own kinds are not: back, they would be read as a signature or a part.
293
+ */
294
+ function isPassThroughState(state) {
295
+ if (!isObject(state) || typeof state.provider !== 'string' || typeof state.kind !== 'string')
296
+ return false;
297
+ if (state.model !== undefined && typeof state.model !== 'string')
298
+ return false;
299
+ return !(state.provider === GEMINI_PROVIDER && (state.kind === THOUGHT_SIGNATURE_KIND || state.kind === GENAI_PART_KIND));
300
+ }
301
+ function withState(part, state) {
302
+ return { ...part, providerState: state };
303
+ }
304
+ function partFromGenai(raw, slot, index, ids) {
305
+ if (!isObject(raw)) {
306
+ const part = { type: 'text', text: typeof raw === 'string' ? raw : '' };
307
+ return withState(part, { provider: GEMINI_PROVIDER, kind: GENAI_PART_KIND, payload: raw });
308
+ }
309
+ const { thoughtSignature, providerState, ...rest } = raw;
310
+ const projection = project(rest);
311
+ let { part, exact } = projection;
312
+ if (!ALLOWED[slot].has(part.type)) {
313
+ part = { type: 'text', text: describe(part) };
314
+ exact = false;
315
+ }
316
+ else if (projection.needsId && part.type === 'toolCall') {
317
+ part = { ...part, id: ids.call(part.name, index) };
318
+ }
319
+ else if (projection.needsId && part.type === 'toolResult') {
320
+ part = { ...part, id: ids.result(part.name, index) };
321
+ }
322
+ if (exact && thoughtSignature === undefined && providerState === undefined)
323
+ return part;
324
+ if (exact && typeof thoughtSignature === 'string' && providerState === undefined) {
325
+ return withState(part, { provider: GEMINI_PROVIDER, kind: THOUGHT_SIGNATURE_KIND, payload: thoughtSignature });
326
+ }
327
+ if (exact && thoughtSignature === undefined && isPassThroughState(providerState))
328
+ return withState(part, providerState);
329
+ return withState(part, { provider: GEMINI_PROVIDER, kind: GENAI_PART_KIND, payload: raw });
330
+ }
331
+ function idContext(content, open) {
332
+ const mint = (part) => `${MINTED_CALL_ID_PREFIX}${content}-${part}`;
333
+ return {
334
+ call(name, part) {
335
+ const id = mint(part);
336
+ open.push({ name, id, content });
337
+ return id;
338
+ },
339
+ result(name, part) {
340
+ // The latest content's calls first, in their order: parallel calls to
341
+ // one tool are answered in the order they were made.
342
+ let match = -1;
343
+ for (let i = 0; i < open.length; i++) {
344
+ if (open[i].name === name && (match < 0 || open[i].content > open[match].content))
345
+ match = i;
346
+ }
347
+ if (match < 0)
348
+ return mint(part);
349
+ return open.splice(match, 1)[0].id;
350
+ },
351
+ };
352
+ }
353
+ function mapContent(content, index, open) {
354
+ const slot = slotOf(content);
355
+ const ids = idContext(index, open);
356
+ const parts = (content.parts ?? []).map((p, i) => partFromGenai(p, slot, i, ids));
357
+ return { role: slot, parts };
358
+ }
359
+ /**
360
+ * One genai content as a contract message. A call or result without an id
361
+ * gets one minted from `index` (the content's position in its history);
362
+ * results are paired with calls only by contentsToMessages.
363
+ */
364
+ export function contentToMessage(content, index = 0) {
365
+ return mapContent(content, index, []);
366
+ }
367
+ /**
368
+ * The text of a system instruction in any of genai's spellings, parts joined
369
+ * by newlines, or undefined when it has none (an empty string included).
370
+ */
371
+ export function systemText(instruction) {
372
+ if (instruction === undefined || instruction === null)
373
+ return undefined;
374
+ if (typeof instruction === 'string')
375
+ return instruction || undefined;
376
+ const parts = Array.isArray(instruction)
377
+ ? instruction
378
+ : isObject(instruction) && Array.isArray(instruction.parts)
379
+ ? instruction.parts
380
+ : [instruction];
381
+ const texts = parts
382
+ .map((p) => (typeof p === 'string' ? p : isObject(p) && typeof p.text === 'string' && p.thought !== true ? p.text : undefined))
383
+ .filter((t) => t !== undefined);
384
+ return texts.length > 0 ? texts.join('\n') : undefined;
385
+ }
386
+ /**
387
+ * A genai history (contents plus an optional system instruction) as the
388
+ * contract holds it. Calls and results without ids are paired here.
389
+ */
390
+ export function contentsToMessages(contents, systemInstruction) {
391
+ const open = [];
392
+ const messages = contents.map((content, index) => mapContent(content, index, open));
393
+ const system = systemText(systemInstruction);
394
+ return system === undefined ? { messages } : { system, messages };
395
+ }
396
+ // ── Contract → genai ─────────────────────────────────────────────────────────
397
+ /** A tool result as a genai `response`: see the header. */
398
+ function responseOf(result, isError) {
399
+ if (isError)
400
+ return { error: result };
401
+ return isObject(result) ? result : { result };
402
+ }
403
+ /** One contract part as the genai part it maps to. */
404
+ export function partToGenai(part) {
405
+ const state = part.providerState;
406
+ if (state?.provider === GEMINI_PROVIDER && state.kind === GENAI_PART_KIND)
407
+ return state.payload;
408
+ let out;
409
+ switch (part.type) {
410
+ case 'text':
411
+ out = { text: part.text };
412
+ break;
413
+ case 'thinking':
414
+ out = { text: part.text, thought: true };
415
+ break;
416
+ case 'toolCall':
417
+ out = { functionCall: { name: part.name, args: part.args, ...(isMintedCallId(part.id) ? {} : { id: part.id }) } };
418
+ break;
419
+ case 'toolResult':
420
+ out = { functionResponse: { ...(isMintedCallId(part.id) ? {} : { id: part.id }), name: part.name, response: responseOf(part.result, part.isError) } };
421
+ break;
422
+ case 'blob':
423
+ out = part.url !== undefined ? { fileData: { mimeType: part.mimeType, fileUri: part.url } } : { inlineData: { mimeType: part.mimeType, data: part.data } };
424
+ break;
425
+ }
426
+ if (state?.provider === GEMINI_PROVIDER && state.kind === THOUGHT_SIGNATURE_KIND && typeof state.payload === 'string') {
427
+ out.thoughtSignature = state.payload;
428
+ }
429
+ else if (state !== undefined) {
430
+ out.providerState = state;
431
+ }
432
+ return out;
433
+ }
434
+ /** One contract message as a genai content. */
435
+ export function messageToContent(message) {
436
+ return { role: GENAI_ROLE[message.role], parts: message.parts.map(partToGenai) };
437
+ }
438
+ /** The inverse of contentsToMessages; the system instruction comes back as a string. */
439
+ export function messagesToContents(history) {
440
+ const contents = history.messages.map(messageToContent);
441
+ return history.system === undefined ? { contents } : { systemInstruction: history.system, contents };
442
+ }
443
+ // Maps, not object literals: a config value such as `constructor` must read as nothing.
444
+ const THINKING_LEVELS = new Map([['MINIMAL', 'none'], ['LOW', 'low'], ['MEDIUM', 'medium'], ['HIGH', 'high']]);
445
+ const EFFORT_LEVELS = new Map([['none', 'none'], ['minimal', 'none'], ['low', 'low'], ['medium', 'medium'], ['high', 'high']]);
446
+ /**
447
+ * The ReasoningSetting a generateContentConfig asks for, where one is exact:
448
+ * - `thinkingConfig.thinkingLevel`: `MINIMAL` is `none` (ADR 0047's
449
+ * Gemini 3 rendering of it), `LOW`, `MEDIUM`, `HIGH` their levels.
450
+ * - else `thinkingConfig.thinkingBudget` n ≥ 0: `{ budget_tokens: n }`,
451
+ * which every adapter maps to what the budget maps to today (0 included).
452
+ * - else `reasoningEffort`: a level word is that level, and `minimal` is
453
+ * `none` (ADR 0047's rendering of it on the first GPT-5 generation).
454
+ * Lossy, and absent: a budget of -1 (Gemini's dynamic thinking, which is
455
+ * the provider's default), `THINKING_LEVEL_UNSPECIFIED`, the effort words
456
+ * `xhigh` and `max`, and `includeThoughts`. The compiler writes the effort
457
+ * word beside thinkingConfig from one setting, so preferring thinkingConfig
458
+ * loses nothing it wrote; only the older spelling can set the two apart.
459
+ */
460
+ export function reasoningOf(config) {
461
+ const cfg = (config ?? {});
462
+ const thinking = cfg.thinkingConfig;
463
+ if (isObject(thinking)) {
464
+ const level = THINKING_LEVELS.get(thinking.thinkingLevel);
465
+ if (level)
466
+ return level;
467
+ const budget = thinking.thinkingBudget;
468
+ if (typeof budget === 'number' && Number.isInteger(budget) && budget >= 0)
469
+ return { budget_tokens: budget };
470
+ }
471
+ return EFFORT_LEVELS.get(cfg.reasoningEffort);
472
+ }
473
+ function nativeToolsOf(llmRequest) {
474
+ const tools = new Set();
475
+ // The sentinels a non-Gemini request carries in toolsDict (web_search,
476
+ // x_search, collections_search): tools that declare no function.
477
+ for (const tool of Object.values(llmRequest.toolsDict ?? {})) {
478
+ const native = nativeToolOf(tool);
479
+ if (native)
480
+ tools.add(native);
481
+ }
482
+ // Gemini's own tool objects. `google_search` and `web_search` both become
483
+ // `{ googleSearch: {} }` on Gemini, so the mapping reads it as the neutral name.
484
+ for (const entry of (llmRequest.config?.tools ?? [])) {
485
+ if (!isObject(entry))
486
+ continue;
487
+ if (entry.googleSearch !== undefined || entry.googleSearchRetrieval !== undefined)
488
+ tools.add('web_search');
489
+ if (entry.urlContext !== undefined)
490
+ tools.add('url_context');
491
+ if (entry.codeExecution !== undefined)
492
+ tools.add('code_execution');
493
+ }
494
+ return [...tools];
495
+ }
496
+ /** The function-calling mode as a ToolChoice, and VALIDATED as strict tools. The native request builder reads an agent's config through it too. */
497
+ export function toolChoiceOf(config) {
498
+ const fcc = config?.toolConfig?.functionCallingConfig;
499
+ if (!isObject(fcc))
500
+ return {};
501
+ const allowed = Array.isArray(fcc.allowedFunctionNames) ? fcc.allowedFunctionNames : [];
502
+ switch (String(fcc.mode)) {
503
+ case 'NONE':
504
+ return { toolChoice: 'none' };
505
+ case 'ANY':
506
+ // Several allowed names have no contract form: any tool is required.
507
+ return { toolChoice: allowed.length === 1 && typeof allowed[0] === 'string' ? { name: allowed[0] } : 'required' };
508
+ case 'VALIDATED':
509
+ return { strict: true };
510
+ default:
511
+ return {};
512
+ }
513
+ }
514
+ /** The sampling fields the contract carries, or undefined when none is set. The native request builder reads an agent's config through it too. */
515
+ export function samplingOf(config) {
516
+ const sampling = {};
517
+ if (typeof config?.temperature === 'number')
518
+ sampling.temperature = config.temperature;
519
+ if (typeof config?.topP === 'number')
520
+ sampling.topP = config.topP;
521
+ if (typeof config?.maxOutputTokens === 'number')
522
+ sampling.maxOutputTokens = config.maxOutputTokens;
523
+ if (Array.isArray(config?.stopSequences) && config.stopSequences.length > 0)
524
+ sampling.stop = [...config.stopSequences];
525
+ return Object.keys(sampling).length > 0 ? sampling : undefined;
526
+ }
527
+ /** An ADK LlmRequest as a ModelRequest (see the header for what is not mapped). */
528
+ export function llmRequestToModelRequest(llmRequest, options = {}) {
529
+ const model = options.model ?? llmRequest.model;
530
+ if (!model)
531
+ throw new TypeError('llmRequestToModelRequest: the request names no model, and none was given');
532
+ const config = llmRequest.config;
533
+ const { messages } = contentsToMessages(llmRequest.contents ?? []);
534
+ const system = systemText(config?.systemInstruction);
535
+ const { toolChoice, strict } = toolChoiceOf(config);
536
+ // VALIDATED asks Gemini to enforce the schema as declared, so the flag is
537
+ // set on the plain declaration rather than asking for the strict form.
538
+ const tools = [];
539
+ for (const tool of Object.values(llmRequest.toolsDict ?? {})) {
540
+ const declaration = contractToolDeclaration(tool);
541
+ if (declaration)
542
+ tools.push(strict ? { ...declaration, strict } : declaration);
543
+ }
544
+ const nativeTools = nativeToolsOf(llmRequest);
545
+ const schema = config?.responseJsonSchema ?? config?.responseSchema;
546
+ // JSON mode: the MIME type alone, with no schema to say more (ADR 0061).
547
+ const jsonMode = !isObject(schema) && config?.responseMimeType === 'application/json';
548
+ const reasoning = reasoningOf(config);
549
+ const sampling = samplingOf(config);
550
+ const signal = options.signal ?? config?.abortSignal;
551
+ return {
552
+ model,
553
+ ...(system !== undefined ? { system } : {}),
554
+ messages,
555
+ ...(tools.length > 0 ? { tools } : {}),
556
+ ...(nativeTools.length > 0 ? { nativeTools } : {}),
557
+ ...(toolChoice !== undefined ? { toolChoice } : {}),
558
+ ...(isObject(schema) ? { outputSchema: toContractJsonSchema(schema) } : {}),
559
+ ...(jsonMode ? { outputFormat: 'json' } : {}),
560
+ ...(reasoning !== undefined ? { reasoning } : {}),
561
+ ...(sampling ? { sampling } : {}),
562
+ ...(options.stream !== undefined ? { stream: options.stream } : {}),
563
+ ...(signal ? { signal } : {}),
564
+ };
565
+ }
566
+ // ── ModelResponse → LlmResponse ──────────────────────────────────────────────
567
+ /**
568
+ * Usage in Gemini's meanings: `candidatesTokenCount` excludes the thinking
569
+ * that `thoughtsTokenCount` counts, and `totalTokenCount` is input plus
570
+ * output. `cacheWriteTokens` has no genai field.
571
+ */
572
+ export function usageToMetadata(usage) {
573
+ const thinking = usage.thinkingTokens ?? 0;
574
+ return {
575
+ promptTokenCount: usage.inputTokens,
576
+ candidatesTokenCount: Math.max(0, usage.outputTokens - thinking),
577
+ ...(usage.thinkingTokens !== undefined ? { thoughtsTokenCount: usage.thinkingTokens } : {}),
578
+ ...(usage.cacheReadTokens !== undefined ? { cachedContentTokenCount: usage.cacheReadTokens } : {}),
579
+ totalTokenCount: usage.inputTokens + usage.outputTokens,
580
+ };
581
+ }
582
+ /** Gemini's usageMetadata as contract Usage, or undefined when it counts nothing. */
583
+ export function usageFromMetadata(meta) {
584
+ if (!meta)
585
+ return undefined;
586
+ const counted = [meta.promptTokenCount, meta.toolUsePromptTokenCount, meta.candidatesTokenCount, meta.thoughtsTokenCount];
587
+ if (counted.every((n) => n === undefined))
588
+ return undefined;
589
+ return {
590
+ inputTokens: (meta.promptTokenCount ?? 0) + (meta.toolUsePromptTokenCount ?? 0),
591
+ outputTokens: (meta.candidatesTokenCount ?? 0) + (meta.thoughtsTokenCount ?? 0),
592
+ ...(meta.thoughtsTokenCount !== undefined ? { thinkingTokens: meta.thoughtsTokenCount } : {}),
593
+ ...(meta.cachedContentTokenCount !== undefined ? { cacheReadTokens: meta.cachedContentTokenCount } : {}),
594
+ };
595
+ }
596
+ /** `content_filter` cannot say which of Gemini's policy reasons it was; it becomes SAFETY. */
597
+ const FINISH_REASONS = {
598
+ stop: FinishReason.STOP,
599
+ tool_call: FinishReason.STOP,
600
+ max_tokens: FinishReason.MAX_TOKENS,
601
+ content_filter: FinishReason.SAFETY,
602
+ other: FinishReason.OTHER,
603
+ error: undefined,
604
+ };
605
+ /** The groundingMetadata lib/grounding.ts reads: the queries, and each cited URL once. */
606
+ function groundingToMetadata(grounding) {
607
+ const webSearchQueries = (grounding.searchQueries ?? []).map((q) => q.query).filter((q) => q);
608
+ const seen = new Set();
609
+ const groundingChunks = [];
610
+ for (const c of grounding.citations ?? []) {
611
+ if (seen.has(c.url))
612
+ continue;
613
+ seen.add(c.url);
614
+ groundingChunks.push({ web: { uri: c.url, ...(c.title !== undefined ? { title: c.title } : {}) } });
615
+ }
616
+ if (webSearchQueries.length === 0 && groundingChunks.length === 0)
617
+ return undefined;
618
+ return {
619
+ ...(webSearchQueries.length > 0 ? { webSearchQueries } : {}),
620
+ ...(groundingChunks.length > 0 ? { groundingChunks } : {}),
621
+ };
622
+ }
623
+ /** Gemini's finish reasons that end a call short of an answer: every one but STOP and the unspecified one. */
624
+ const GEMINI_FINISH_REASONS = new Set(Object.values(FinishReason).filter((r) => r !== FinishReason.STOP && r !== FinishReason.FINISH_REASON_UNSPECIFIED));
625
+ /** An error code that is one of Gemini's finish reasons, as the finish reason ADK's Gemini reports beside it (ADR 0088). */
626
+ function finishReasonOfCode(code) {
627
+ return code !== undefined && GEMINI_FINISH_REASONS.has(code) ? code : undefined;
628
+ }
629
+ /** A ModelResponse as the LlmResponse ADK expects from a model (see the header). */
630
+ export function modelResponseToLlmResponse(response) {
631
+ const parts = response.parts.map(partToGenai);
632
+ if (response.partial)
633
+ return { content: { role: 'model', parts }, partial: true };
634
+ const finishReason = finishReasonOfCode(response.error?.code) ?? FINISH_REASONS[response.finishReason];
635
+ const groundingMetadata = response.grounding ? groundingToMetadata(response.grounding) : undefined;
636
+ const llmResponse = {
637
+ ...(parts.length > 0 ? { content: { role: 'model', parts } } : {}),
638
+ ...(response.error ? { errorCode: response.error.code, errorMessage: response.error.message } : {}),
639
+ ...(finishReason !== undefined ? { finishReason } : {}),
640
+ ...(response.usage ? { usageMetadata: usageToMetadata(response.usage) } : {}),
641
+ ...(groundingMetadata ? { groundingMetadata } : {}),
642
+ turnComplete: true,
643
+ };
644
+ // FallbackLlm reads the verdict from customMetadata alone (ADR 0044): an
645
+ // error without it would be passed on, never answered by the fallback.
646
+ if (!response.error)
647
+ return llmResponse;
648
+ const { retryable, status } = response.error;
649
+ return withRetryVerdict(llmResponse, { retryable, ...(status !== undefined ? { status } : {}) });
650
+ }
651
+ // ── ModelRequest → LlmRequest ────────────────────────────────────────────────
652
+ /** The Gemini tool object each native tool becomes. `x_search` and `collections_search` have none. */
653
+ const GEMINI_NATIVE_TOOLS = {
654
+ web_search: () => ({ googleSearch: {} }),
655
+ google_search: () => ({ googleSearch: {} }),
656
+ url_context: () => ({ urlContext: {} }),
657
+ code_execution: () => ({ codeExecution: {} }),
658
+ };
659
+ /** The native tools in `tools` that Gemini has no tool for, each once: modelRequestToLlmRequest leaves them out. */
660
+ export function nativeToolsWithoutGeminiTool(tools = []) {
661
+ return [...new Set(tools)].filter((tool) => !GEMINI_NATIVE_TOOLS[tool]);
662
+ }
663
+ /**
664
+ * A toolsDict entry that only declares. `_getDeclaration()` gives the
665
+ * declaration with its lowercase schema as `parameters`, where
666
+ * llmRequestToModelRequest (contractToolDeclaration) and the ADK-path
667
+ * adapters (toolDeclarationFor) read it. It is never run: ADK's Gemini sends
668
+ * `config.tools`, and nothing calls a tool out of a request.
669
+ */
670
+ function declaredTool({ name, description, parameters }) {
671
+ return { name, description, isLongRunning: false, _getDeclaration: () => ({ name, description, parameters }) };
672
+ }
673
+ /** The function-calling mode for the request's tool choice and strict tools, sent only beside declarations. */
674
+ function toolConfigOf(request) {
675
+ if (!request.tools?.length)
676
+ return undefined;
677
+ const choice = request.toolChoice;
678
+ if (typeof choice === 'object') {
679
+ return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY, allowedFunctionNames: [choice.name] } };
680
+ }
681
+ const mode = choice === 'none'
682
+ ? FunctionCallingConfigMode.NONE
683
+ : choice === 'required'
684
+ ? FunctionCallingConfigMode.ANY
685
+ : request.tools.some((t) => t.strict)
686
+ ? FunctionCallingConfigMode.VALIDATED
687
+ : choice === 'auto'
688
+ ? FunctionCallingConfigMode.AUTO
689
+ : undefined;
690
+ return mode ? { functionCallingConfig: { mode } } : undefined;
691
+ }
692
+ /** A ModelRequest as the LlmRequest ADK's path builds for a Gemini model (see the header for what does not come back the same). */
693
+ export function modelRequestToLlmRequest(request) {
694
+ const config = {};
695
+ if (request.system)
696
+ config.systemInstruction = request.system;
697
+ const declarations = request.tools ?? [];
698
+ const tools = [];
699
+ if (declarations.length > 0) {
700
+ tools.push({
701
+ functionDeclarations: declarations.map(({ name, description, parameters }) => ({ name, description, parametersJsonSchema: parameters })),
702
+ });
703
+ }
704
+ // In the request's order, so llmRequestToModelRequest reads them back in it.
705
+ let searching = false;
706
+ for (const native of new Set(request.nativeTools ?? [])) {
707
+ const tool = GEMINI_NATIVE_TOOLS[native];
708
+ if (!tool)
709
+ continue;
710
+ if (native === 'web_search' || native === 'google_search') {
711
+ if (searching)
712
+ continue;
713
+ searching = true;
714
+ }
715
+ tools.push(tool());
716
+ }
717
+ if (tools.length > 0)
718
+ config.tools = tools;
719
+ const toolConfig = toolConfigOf(request);
720
+ if (toolConfig)
721
+ config.toolConfig = toolConfig;
722
+ if (request.outputSchema) {
723
+ config.responseMimeType = 'application/json';
724
+ config.responseJsonSchema = request.outputSchema;
725
+ }
726
+ else if (request.outputFormat === 'json') {
727
+ config.responseMimeType = 'application/json';
728
+ }
729
+ // The fields the compiler writes for the model (ADR 0047): a thinking
730
+ // level or budget on Gemini, and the effort word every other adapter reads.
731
+ if (request.reasoning !== undefined)
732
+ Object.assign(config, reasoningConfig(request.model, request.reasoning));
733
+ const sampling = request.sampling;
734
+ if (sampling?.temperature !== undefined)
735
+ config.temperature = sampling.temperature;
736
+ if (sampling?.topP !== undefined)
737
+ config.topP = sampling.topP;
738
+ if (sampling?.maxOutputTokens !== undefined)
739
+ config.maxOutputTokens = sampling.maxOutputTokens;
740
+ if (sampling?.stop?.length)
741
+ config.stopSequences = [...sampling.stop];
742
+ if (request.signal)
743
+ config.abortSignal = request.signal;
744
+ return {
745
+ model: request.model,
746
+ contents: messagesToContents({ messages: request.messages }).contents,
747
+ config,
748
+ liveConnectConfig: {},
749
+ toolsDict: Object.fromEntries(declarations.map((d) => [d.name, declaredTool(d)])),
750
+ };
751
+ }
752
+ /** Gemini's reasons for withholding an answer, or blocking a prompt, on policy grounds. */
753
+ const POLICY_REASONS = new Set(['SAFETY', 'RECITATION', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII', 'MODEL_ARMOR', 'JAILBREAK']);
754
+ function isPolicyReason(reason) {
755
+ return POLICY_REASONS.has(reason) || reason.startsWith('IMAGE_');
756
+ }
757
+ /** Gemini's finish reason as the contract's (the Gemini table of wiki/models/model-contract.md). */
758
+ function finishReasonOf(reason) {
759
+ if (reason === 'STOP')
760
+ return 'stop';
761
+ if (reason === 'MAX_TOKENS')
762
+ return 'max_tokens';
763
+ return isPolicyReason(reason) ? 'content_filter' : 'other';
764
+ }
765
+ function isSignatureState(state) {
766
+ return state?.provider === GEMINI_PROVIDER && state.kind === THOUGHT_SIGNATURE_KIND;
767
+ }
768
+ /**
769
+ * An assistant message's parts as a final's: thinking left out, a signature
770
+ * it carried moved to the next part when that part has no state of its own,
771
+ * or kept by the last part when no part follows; `model` set on every
772
+ * signature that names none.
773
+ */
774
+ function outputPartsOf(parts, model) {
775
+ const out = [];
776
+ let carried;
777
+ for (const part of parts) {
778
+ if (part.type === 'thinking') {
779
+ if (isSignatureState(part.providerState))
780
+ carried = part.providerState;
781
+ continue;
782
+ }
783
+ if (part.type === 'toolResult')
784
+ continue; // an assistant message holds none
785
+ out.push(carried && part.providerState === undefined ? { ...part, providerState: carried } : part);
786
+ carried = undefined;
787
+ }
788
+ const last = out.at(-1);
789
+ if (carried && last && last.providerState === undefined)
790
+ out[out.length - 1] = { ...last, providerState: carried };
791
+ if (model === undefined)
792
+ return out;
793
+ return out.map((p) => (isSignatureState(p.providerState) && p.providerState.model === undefined ? { ...p, providerState: { ...p.providerState, model } } : p));
794
+ }
795
+ /** An ADK error code as the contract's error, with the verdict the adapter stamped (withRetryVerdict). `STOP` is no error. */
796
+ function errorOf(response) {
797
+ const code = response.errorCode;
798
+ if (!code || code === 'STOP')
799
+ return undefined;
800
+ const status = response.customMetadata?.[ERROR_STATUS_KEY];
801
+ return {
802
+ code,
803
+ message: errorText(response.errorMessage || `The model call ended with ${code}.`),
804
+ retryable: response.customMetadata?.[ERROR_RETRYABLE_KEY] === true,
805
+ ...(typeof status === 'number' ? { status } : {}),
806
+ };
807
+ }
808
+ /** Each cited page once, with its title, and the queries run: what groundingToMetadata writes. Citation spans are not read. */
809
+ function groundingOf(meta, tool) {
810
+ if (!meta)
811
+ return undefined;
812
+ const citations = [];
813
+ const seen = new Set();
814
+ for (const chunk of meta.groundingChunks ?? []) {
815
+ const url = chunk.web?.uri;
816
+ if (!url || seen.has(url))
817
+ continue;
818
+ seen.add(url);
819
+ citations.push(chunk.web?.title ? { url, title: chunk.web.title } : { url });
820
+ }
821
+ const searchQueries = (meta.webSearchQueries ?? []).filter((q) => typeof q === 'string' && q).map((query) => ({ tool, query }));
822
+ if (citations.length === 0 && searchQueries.length === 0)
823
+ return undefined;
824
+ return { ...(citations.length > 0 ? { citations } : {}), ...(searchQueries.length > 0 ? { searchQueries } : {}) };
825
+ }
826
+ /** One LlmResponse as a ModelResponse (see the header). A caller folding a stream of them into one final keeps the thinking itself. */
827
+ export function llmResponseToModelResponse(response, options = {}) {
828
+ const raw = response.content?.parts ?? [];
829
+ if (response.partial === true) {
830
+ const parts = [];
831
+ for (const p of raw) {
832
+ if (!isObject(p) || typeof p.text !== 'string' || p.text === '')
833
+ continue;
834
+ parts.push(p.thought === true ? { type: 'thinking', text: p.text } : { type: 'text', text: p.text });
835
+ }
836
+ return { partial: true, parts };
837
+ }
838
+ const message = contentToMessage({ role: 'model', parts: raw }, options.index ?? 0);
839
+ const parts = outputPartsOf(message.parts, options.model);
840
+ const error = errorOf(response);
841
+ const reason = typeof response.finishReason === 'string' && response.finishReason ? response.finishReason : undefined;
842
+ const finishReason = parts.some((p) => p.type === 'toolCall')
843
+ ? 'tool_call'
844
+ : reason
845
+ ? finishReasonOf(reason)
846
+ : error
847
+ ? isPolicyReason(error.code)
848
+ ? 'content_filter'
849
+ : 'error'
850
+ : 'stop';
851
+ const usage = usageFromMetadata(response.usageMetadata);
852
+ const grounding = groundingOf(response.groundingMetadata, options.searchTool ?? 'web_search');
853
+ const final = {
854
+ partial: false,
855
+ parts,
856
+ finishReason,
857
+ ...(usage ? { usage } : {}),
858
+ ...(grounding ? { grounding } : {}),
859
+ ...(error ? { error } : {}),
860
+ };
861
+ return final;
862
+ }