mixdog 0.7.17 → 0.8.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 (836) hide show
  1. package/README.md +37 -331
  2. package/package.json +67 -99
  3. package/scripts/boot-smoke.mjs +94 -0
  4. package/scripts/build-tui.mjs +52 -0
  5. package/scripts/compact-smoke.mjs +199 -0
  6. package/scripts/lead-workflow-smoke.mjs +598 -0
  7. package/scripts/live-worker-smoke.mjs +239 -0
  8. package/scripts/output-style-smoke.mjs +101 -0
  9. package/scripts/smoke-loop-report.mjs +221 -0
  10. package/scripts/smoke-loop.mjs +201 -0
  11. package/scripts/smoke.mjs +113 -0
  12. package/scripts/tool-failures.mjs +143 -0
  13. package/scripts/tool-smoke.mjs +456 -0
  14. package/src/agents/debugger/AGENT.md +3 -0
  15. package/src/agents/debugger/agent.json +6 -0
  16. package/src/agents/explore/AGENT.md +4 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/agents/heavy-worker/AGENT.md +3 -0
  19. package/src/agents/heavy-worker/agent.json +6 -0
  20. package/src/agents/maintainer/AGENT.md +3 -0
  21. package/src/agents/maintainer/agent.json +6 -0
  22. package/src/agents/reviewer/AGENT.md +3 -0
  23. package/src/agents/reviewer/agent.json +6 -0
  24. package/src/agents/scheduler-task.md +3 -0
  25. package/src/agents/web-researcher/AGENT.md +3 -0
  26. package/src/agents/web-researcher/agent.json +6 -0
  27. package/src/agents/webhook-handler.md +3 -0
  28. package/src/agents/worker/AGENT.md +3 -0
  29. package/src/agents/worker/agent.json +6 -0
  30. package/src/app.mjs +90 -0
  31. package/src/cli.mjs +11 -0
  32. package/src/defaults/hidden-roles.json +72 -0
  33. package/src/defaults/mixdog-config.template.json +15 -0
  34. package/src/hooks/lib/permission-evaluator.cjs +488 -0
  35. package/src/hooks/lib/settings-loader.cjs +112 -0
  36. package/src/lib/keychain-cjs.cjs +332 -0
  37. package/src/lib/plugin-paths.cjs +28 -0
  38. package/src/lib/rules-builder.cjs +315 -0
  39. package/src/mixdog-session-runtime.mjs +3704 -0
  40. package/src/output-styles/default.md +38 -0
  41. package/src/output-styles/extreme-simple.md +17 -0
  42. package/src/output-styles/simple.md +17 -0
  43. package/src/repl.mjs +322 -0
  44. package/src/rules/bridge/00-common.md +5 -0
  45. package/src/rules/bridge/20-skip-protocol.md +11 -0
  46. package/src/rules/bridge/30-explorer.md +4 -0
  47. package/src/rules/bridge/40-cycle1-agent.md +28 -0
  48. package/src/rules/bridge/41-cycle2-agent.md +59 -0
  49. package/src/rules/lead/00-tool-lead.md +5 -0
  50. package/src/rules/lead/01-general.md +5 -0
  51. package/src/rules/lead/02-channels.md +3 -0
  52. package/src/rules/lead/04-workflow.md +12 -0
  53. package/src/rules/shared/00-language.md +3 -0
  54. package/src/rules/shared/01-tool.md +3 -0
  55. package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
  56. package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
  57. package/src/runtime/agent/orchestrator/config.mjs +446 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
  59. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
  60. package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
  65. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
  66. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
  67. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
  68. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
  69. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
  73. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
  77. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
  78. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
  79. package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
  80. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
  81. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
  83. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  84. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
  85. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
  86. package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
  88. package/src/runtime/agent/orchestrator/session/loop.mjs +2320 -0
  89. package/src/runtime/agent/orchestrator/session/manager.mjs +2960 -0
  90. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  91. package/src/runtime/agent/orchestrator/session/store.mjs +663 -0
  92. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  93. package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
  94. package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
  95. package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
  96. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
  97. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
  100. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
  118. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  119. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
  120. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  121. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
  122. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
  123. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
  124. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
  125. package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
  128. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
  129. package/src/runtime/channels/backends/discord.mjs +781 -0
  130. package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
  131. package/src/runtime/channels/index.mjs +3309 -0
  132. package/src/runtime/channels/lib/config.mjs +285 -0
  133. package/src/runtime/channels/lib/drop-trace.mjs +71 -0
  134. package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
  135. package/src/runtime/channels/lib/holidays.mjs +138 -0
  136. package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
  137. package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
  138. package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
  139. package/src/runtime/channels/lib/scheduler.mjs +710 -0
  140. package/src/runtime/channels/lib/session-discovery.mjs +102 -0
  141. package/src/runtime/channels/lib/state-file.mjs +68 -0
  142. package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
  143. package/src/runtime/channels/lib/tool-format.mjs +124 -0
  144. package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
  146. package/src/runtime/channels/lib/webhook.mjs +1288 -0
  147. package/src/runtime/channels/tool-defs.mjs +177 -0
  148. package/src/runtime/lib/keychain-cjs.cjs +289 -0
  149. package/src/runtime/memory/data/runtime-manifest.json +40 -0
  150. package/src/runtime/memory/index.mjs +3600 -0
  151. package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
  152. package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
  153. package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
  154. package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
  155. package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
  156. package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
  157. package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
  158. package/src/runtime/memory/lib/memory-embed.mjs +300 -0
  159. package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
  160. package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
  161. package/src/runtime/memory/lib/memory.mjs +418 -0
  162. package/src/runtime/memory/lib/pg/adapter.mjs +314 -0
  163. package/src/runtime/memory/lib/pg/process.mjs +366 -0
  164. package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
  165. package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
  166. package/src/runtime/memory/lib/trace-store.mjs +734 -0
  167. package/src/runtime/memory/tool-defs.mjs +79 -0
  168. package/src/runtime/search/index.mjs +925 -0
  169. package/src/runtime/search/lib/config.mjs +61 -0
  170. package/src/runtime/search/lib/web-tools.mjs +1278 -0
  171. package/src/runtime/search/tool-defs.mjs +64 -0
  172. package/src/runtime/shared/atomic-file.mjs +435 -0
  173. package/src/runtime/shared/background-tasks.mjs +376 -0
  174. package/src/runtime/shared/child-guardian.mjs +98 -0
  175. package/src/runtime/shared/config.mjs +393 -0
  176. package/src/runtime/shared/err-text.mjs +121 -0
  177. package/src/runtime/shared/launcher-control.mjs +259 -0
  178. package/src/runtime/shared/llm/cost.mjs +66 -0
  179. package/src/runtime/shared/llm/http-agent.mjs +129 -0
  180. package/src/runtime/shared/open-url.mjs +37 -0
  181. package/src/runtime/shared/plugin-paths.mjs +25 -0
  182. package/src/runtime/shared/process-shutdown.mjs +147 -0
  183. package/src/runtime/shared/schedules-store.mjs +70 -0
  184. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  185. package/src/runtime/shared/tool-surface.mjs +950 -0
  186. package/src/runtime/shared/user-cwd.mjs +221 -0
  187. package/src/runtime/shared/user-data-guard.mjs +232 -0
  188. package/src/runtime/shared/workspace-router.mjs +259 -0
  189. package/src/standalone/bridge-tool.mjs +1414 -0
  190. package/src/standalone/channel-admin.mjs +366 -0
  191. package/src/standalone/channel-worker-preload.cjs +3 -0
  192. package/src/standalone/channel-worker.mjs +353 -0
  193. package/src/standalone/explore-tool.mjs +233 -0
  194. package/src/standalone/hook-bus.mjs +246 -0
  195. package/src/standalone/plugin-admin.mjs +247 -0
  196. package/src/standalone/provider-admin.mjs +338 -0
  197. package/src/standalone/seeds.mjs +94 -0
  198. package/src/standalone/usage-dashboard.mjs +510 -0
  199. package/src/tui/App.jsx +5438 -0
  200. package/src/tui/components/AnsiText.jsx +199 -0
  201. package/src/tui/components/ContextPanel.jsx +217 -0
  202. package/src/tui/components/Markdown.jsx +205 -0
  203. package/src/tui/components/MarkdownTable.jsx +204 -0
  204. package/src/tui/components/Message.jsx +103 -0
  205. package/src/tui/components/Picker.jsx +317 -0
  206. package/src/tui/components/PromptInput.jsx +584 -0
  207. package/src/tui/components/QueuedCommands.jsx +47 -0
  208. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  209. package/src/tui/components/Spinner.jsx +317 -0
  210. package/src/tui/components/StatusLine.jsx +87 -0
  211. package/src/tui/components/TextEntryPanel.jsx +323 -0
  212. package/src/tui/components/ToolExecution.jsx +772 -0
  213. package/src/tui/components/TurnDone.jsx +78 -0
  214. package/src/tui/components/UsagePanel.jsx +331 -0
  215. package/src/tui/dist/index.mjs +12359 -0
  216. package/src/tui/engine.mjs +2410 -0
  217. package/src/tui/figures.mjs +50 -0
  218. package/src/tui/hooks/useEngine.mjs +16 -0
  219. package/src/tui/index.jsx +254 -0
  220. package/src/tui/input-editing.mjs +242 -0
  221. package/src/tui/markdown/format-token.mjs +194 -0
  222. package/src/tui/paste-attachments.mjs +198 -0
  223. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  224. package/src/tui/spinner-verbs.mjs +45 -0
  225. package/src/tui/theme.mjs +67 -0
  226. package/src/tui/time-format.mjs +53 -0
  227. package/src/ui/ansi.mjs +115 -0
  228. package/src/ui/markdown.mjs +195 -0
  229. package/src/ui/statusline.mjs +730 -0
  230. package/src/ui/tool-card.mjs +101 -0
  231. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  232. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  233. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  234. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  235. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  236. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  237. package/src/workflows/default/WORKFLOW.md +7 -0
  238. package/src/workflows/default/workflow.json +14 -0
  239. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  240. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  241. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  242. package/vendor/ink/build/colorize.d.ts +3 -0
  243. package/vendor/ink/build/colorize.js +48 -0
  244. package/vendor/ink/build/colorize.js.map +1 -0
  245. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  246. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  247. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  248. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  249. package/vendor/ink/build/components/AnimationContext.js +13 -0
  250. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  251. package/vendor/ink/build/components/App.d.ts +24 -0
  252. package/vendor/ink/build/components/App.js +554 -0
  253. package/vendor/ink/build/components/App.js.map +1 -0
  254. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  255. package/vendor/ink/build/components/AppContext.js +25 -0
  256. package/vendor/ink/build/components/AppContext.js.map +1 -0
  257. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  258. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  259. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  260. package/vendor/ink/build/components/Box.d.ts +130 -0
  261. package/vendor/ink/build/components/Box.js +34 -0
  262. package/vendor/ink/build/components/Box.js.map +1 -0
  263. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  264. package/vendor/ink/build/components/CursorContext.js +8 -0
  265. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  266. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  267. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  268. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  269. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  270. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  271. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  272. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  273. package/vendor/ink/build/components/FocusContext.js +17 -0
  274. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  275. package/vendor/ink/build/components/Newline.d.ts +13 -0
  276. package/vendor/ink/build/components/Newline.js +8 -0
  277. package/vendor/ink/build/components/Newline.js.map +1 -0
  278. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  279. package/vendor/ink/build/components/Spacer.js +11 -0
  280. package/vendor/ink/build/components/Spacer.js.map +1 -0
  281. package/vendor/ink/build/components/Static.d.ts +24 -0
  282. package/vendor/ink/build/components/Static.js +28 -0
  283. package/vendor/ink/build/components/Static.js.map +1 -0
  284. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  285. package/vendor/ink/build/components/StderrContext.js +13 -0
  286. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  287. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  288. package/vendor/ink/build/components/StdinContext.js +20 -0
  289. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  290. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  291. package/vendor/ink/build/components/StdoutContext.js +13 -0
  292. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  293. package/vendor/ink/build/components/Text.d.ts +55 -0
  294. package/vendor/ink/build/components/Text.js +50 -0
  295. package/vendor/ink/build/components/Text.js.map +1 -0
  296. package/vendor/ink/build/components/Transform.d.ts +16 -0
  297. package/vendor/ink/build/components/Transform.js +15 -0
  298. package/vendor/ink/build/components/Transform.js.map +1 -0
  299. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  300. package/vendor/ink/build/cursor-helpers.js +62 -0
  301. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  302. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  303. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  304. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  305. package/vendor/ink/build/devtools.d.ts +1 -0
  306. package/vendor/ink/build/devtools.js +36 -0
  307. package/vendor/ink/build/devtools.js.map +1 -0
  308. package/vendor/ink/build/dom.d.ts +62 -0
  309. package/vendor/ink/build/dom.js +143 -0
  310. package/vendor/ink/build/dom.js.map +1 -0
  311. package/vendor/ink/build/get-max-width.d.ts +3 -0
  312. package/vendor/ink/build/get-max-width.js +10 -0
  313. package/vendor/ink/build/get-max-width.js.map +1 -0
  314. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  315. package/vendor/ink/build/hooks/use-animation.js +87 -0
  316. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  317. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  318. package/vendor/ink/build/hooks/use-app.js +8 -0
  319. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  320. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  321. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  322. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  323. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  324. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  325. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  326. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  327. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  328. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  329. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  330. package/vendor/ink/build/hooks/use-focus.js +43 -0
  331. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  332. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  333. package/vendor/ink/build/hooks/use-input.js +126 -0
  334. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  335. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  336. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  337. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  338. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  339. package/vendor/ink/build/hooks/use-paste.js +62 -0
  340. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  341. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  342. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  343. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  344. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  345. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  346. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  347. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  348. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  349. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  350. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  351. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  352. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  353. package/vendor/ink/build/index.d.ts +42 -0
  354. package/vendor/ink/build/index.js +24 -0
  355. package/vendor/ink/build/index.js.map +1 -0
  356. package/vendor/ink/build/ink.d.ts +146 -0
  357. package/vendor/ink/build/ink.js +1022 -0
  358. package/vendor/ink/build/ink.js.map +1 -0
  359. package/vendor/ink/build/input-parser.d.ts +10 -0
  360. package/vendor/ink/build/input-parser.js +194 -0
  361. package/vendor/ink/build/input-parser.js.map +1 -0
  362. package/vendor/ink/build/instances.d.ts +3 -0
  363. package/vendor/ink/build/instances.js +8 -0
  364. package/vendor/ink/build/instances.js.map +1 -0
  365. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  366. package/vendor/ink/build/kitty-keyboard.js +32 -0
  367. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  368. package/vendor/ink/build/log-update.d.ts +20 -0
  369. package/vendor/ink/build/log-update.js +261 -0
  370. package/vendor/ink/build/log-update.js.map +1 -0
  371. package/vendor/ink/build/measure-element.d.ts +20 -0
  372. package/vendor/ink/build/measure-element.js +13 -0
  373. package/vendor/ink/build/measure-element.js.map +1 -0
  374. package/vendor/ink/build/measure-text.d.ts +6 -0
  375. package/vendor/ink/build/measure-text.js +21 -0
  376. package/vendor/ink/build/measure-text.js.map +1 -0
  377. package/vendor/ink/build/output.d.ts +35 -0
  378. package/vendor/ink/build/output.js +328 -0
  379. package/vendor/ink/build/output.js.map +1 -0
  380. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  381. package/vendor/ink/build/parse-keypress.js +495 -0
  382. package/vendor/ink/build/parse-keypress.js.map +1 -0
  383. package/vendor/ink/build/reconciler.d.ts +4 -0
  384. package/vendor/ink/build/reconciler.js +306 -0
  385. package/vendor/ink/build/reconciler.js.map +1 -0
  386. package/vendor/ink/build/render-background.d.ts +4 -0
  387. package/vendor/ink/build/render-background.js +25 -0
  388. package/vendor/ink/build/render-background.js.map +1 -0
  389. package/vendor/ink/build/render-border.d.ts +4 -0
  390. package/vendor/ink/build/render-border.js +84 -0
  391. package/vendor/ink/build/render-border.js.map +1 -0
  392. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  393. package/vendor/ink/build/render-node-to-output.js +162 -0
  394. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  395. package/vendor/ink/build/render-to-string.d.ts +38 -0
  396. package/vendor/ink/build/render-to-string.js +116 -0
  397. package/vendor/ink/build/render-to-string.js.map +1 -0
  398. package/vendor/ink/build/render.d.ts +176 -0
  399. package/vendor/ink/build/render.js +71 -0
  400. package/vendor/ink/build/render.js.map +1 -0
  401. package/vendor/ink/build/renderer.d.ts +8 -0
  402. package/vendor/ink/build/renderer.js +64 -0
  403. package/vendor/ink/build/renderer.js.map +1 -0
  404. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  405. package/vendor/ink/build/sanitize-ansi.js +27 -0
  406. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  407. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  408. package/vendor/ink/build/squash-text-nodes.js +36 -0
  409. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  410. package/vendor/ink/build/styles.d.ts +302 -0
  411. package/vendor/ink/build/styles.js +303 -0
  412. package/vendor/ink/build/styles.js.map +1 -0
  413. package/vendor/ink/build/utils.d.ts +9 -0
  414. package/vendor/ink/build/utils.js +19 -0
  415. package/vendor/ink/build/utils.js.map +1 -0
  416. package/vendor/ink/build/wrap-text.d.ts +3 -0
  417. package/vendor/ink/build/wrap-text.js +38 -0
  418. package/vendor/ink/build/wrap-text.js.map +1 -0
  419. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  420. package/vendor/ink/build/write-synchronized.js +9 -0
  421. package/vendor/ink/build/write-synchronized.js.map +1 -0
  422. package/vendor/ink/license +10 -0
  423. package/vendor/ink/package.json +137 -0
  424. package/.claude-plugin/marketplace.json +0 -34
  425. package/.claude-plugin/plugin.json +0 -20
  426. package/.gitattributes +0 -34
  427. package/.mcp.json +0 -14
  428. package/ARCHITECTURE.md +0 -77
  429. package/CHANGELOG.md +0 -30
  430. package/CONTRIBUTING.md +0 -45
  431. package/DATA-FLOW.md +0 -79
  432. package/LICENSE +0 -21
  433. package/SECURITY.md +0 -138
  434. package/UNINSTALL.md +0 -112
  435. package/agents/maintenance.md +0 -5
  436. package/agents/memory-classification.md +0 -30
  437. package/agents/scheduler-task.md +0 -18
  438. package/agents/webhook-handler.md +0 -27
  439. package/agents/worker.md +0 -24
  440. package/bin/bridge +0 -133
  441. package/bin/statusline-launcher.mjs +0 -82
  442. package/bin/statusline-lib.mjs +0 -558
  443. package/bin/statusline.mjs +0 -615
  444. package/bun.lock +0 -927
  445. package/commands/config.md +0 -16
  446. package/commands/doctor.md +0 -13
  447. package/commands/setup.md +0 -17
  448. package/defaults/hidden-roles.json +0 -68
  449. package/defaults/memory-chunk-prompt.md +0 -63
  450. package/defaults/mixdog-config.template.json +0 -27
  451. package/defaults/user-workflow.json +0 -8
  452. package/defaults/user-workflow.md +0 -17
  453. package/hooks/hooks.json +0 -73
  454. package/hooks/lib/active-instance.cjs +0 -77
  455. package/hooks/lib/permission-evaluator.cjs +0 -411
  456. package/hooks/lib/permission-route.cjs +0 -63
  457. package/hooks/lib/settings-loader.cjs +0 -117
  458. package/hooks/post-tool-use.cjs +0 -84
  459. package/hooks/pre-mcp-sandbox.cjs +0 -158
  460. package/hooks/pre-tool-subagent.cjs +0 -258
  461. package/hooks/session-start.cjs +0 -1479
  462. package/hooks/shim-launcher.cjs +0 -65
  463. package/hooks/turn-timer.cjs +0 -82
  464. package/lib/claude-md-writer.cjs +0 -386
  465. package/lib/keychain-cjs.cjs +0 -290
  466. package/lib/plugin-paths.cjs +0 -69
  467. package/lib/rules-builder.cjs +0 -241
  468. package/native/README.md +0 -117
  469. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  470. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  471. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  472. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  473. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  474. package/prompts/code-review.txt +0 -16
  475. package/prompts/security-audit.txt +0 -17
  476. package/rules/bridge/00-common.md +0 -39
  477. package/rules/bridge/20-skip-protocol.md +0 -18
  478. package/rules/bridge/30-explorer.md +0 -33
  479. package/rules/bridge/40-cycle1-agent.md +0 -52
  480. package/rules/bridge/41-cycle2-agent.md +0 -62
  481. package/rules/lead/00-tool-lead.md +0 -61
  482. package/rules/lead/01-general.md +0 -23
  483. package/rules/lead/02-channels.md +0 -49
  484. package/rules/lead/03-team.md +0 -27
  485. package/rules/lead/04-workflow.md +0 -20
  486. package/rules/shared/00-language.md +0 -14
  487. package/rules/shared/01-tool.md +0 -138
  488. package/scripts/bootstrap.mjs +0 -130
  489. package/scripts/bridge-unify-smoke.mjs +0 -308
  490. package/scripts/build-runtime-linux.sh +0 -348
  491. package/scripts/build-runtime-macos.sh +0 -217
  492. package/scripts/build-runtime-windows.ps1 +0 -242
  493. package/scripts/builtin-utils-smoke.mjs +0 -398
  494. package/scripts/bump.mjs +0 -80
  495. package/scripts/check-json.mjs +0 -45
  496. package/scripts/check-syntax-changed.mjs +0 -102
  497. package/scripts/check-syntax.mjs +0 -58
  498. package/scripts/code-graph-batch.test.mjs +0 -33
  499. package/scripts/config-preserve-smoke.mjs +0 -180
  500. package/scripts/doctor.mjs +0 -489
  501. package/scripts/edit-normalize-fuzz.mjs +0 -130
  502. package/scripts/edit-normalize-smoke.mjs +0 -401
  503. package/scripts/edit-operation-smoke.mjs +0 -369
  504. package/scripts/edit2-smoke.mjs +0 -63
  505. package/scripts/ensure-deps.mjs +0 -259
  506. package/scripts/fuzzy-e2e.mjs +0 -28
  507. package/scripts/fuzzy-smoke.mjs +0 -26
  508. package/scripts/generate-runtime-manifest.mjs +0 -166
  509. package/scripts/guard-smoke.mjs +0 -66
  510. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  511. package/scripts/hook-routing-smoke.mjs +0 -29
  512. package/scripts/inject-input.ps1 +0 -204
  513. package/scripts/io-complex-smoke.mjs +0 -667
  514. package/scripts/io-explore-bench.mjs +0 -424
  515. package/scripts/io-guardrails-smoke.mjs +0 -205
  516. package/scripts/io-mini-bench-baseline.json +0 -11
  517. package/scripts/io-mini-bench.mjs +0 -216
  518. package/scripts/io-route-harness.mjs +0 -933
  519. package/scripts/io-telemetry-report.mjs +0 -691
  520. package/scripts/mutation-bench.mjs +0 -564
  521. package/scripts/mutation-io-smoke.mjs +0 -1097
  522. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  523. package/scripts/native-patch-smoke.mjs +0 -304
  524. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  525. package/scripts/patch-interior-context-smoke.mjs +0 -49
  526. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  527. package/scripts/perf-hook-smoke.mjs +0 -71
  528. package/scripts/permission-eval-smoke.mjs +0 -443
  529. package/scripts/prep-patch.mjs +0 -53
  530. package/scripts/prep-shim.mjs +0 -96
  531. package/scripts/provider-cache-smoke.mjs +0 -687
  532. package/scripts/report-runtime-health.mjs +0 -132
  533. package/scripts/resolve-bun.mjs +0 -60
  534. package/scripts/run-mcp.mjs +0 -1448
  535. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  536. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  537. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  538. package/scripts/smoke-runtime-negative.ps1 +0 -100
  539. package/scripts/smoke-runtime-negative.sh +0 -95
  540. package/scripts/stall-policy-smoke.mjs +0 -50
  541. package/scripts/start-memory-worker.mjs +0 -23
  542. package/scripts/statusline-launcher-smoke.mjs +0 -82
  543. package/scripts/stress-atomic-write.mjs +0 -1028
  544. package/scripts/test-fault-inject.mjs +0 -164
  545. package/scripts/test-large-file.mjs +0 -174
  546. package/scripts/tool-edge-smoke.mjs +0 -209
  547. package/scripts/uninstall.mjs +0 -201
  548. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  549. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  550. package/server-main.mjs +0 -3109
  551. package/server.mjs +0 -468
  552. package/setup/config-merge.mjs +0 -246
  553. package/setup/install.mjs +0 -574
  554. package/setup/launch-core.mjs +0 -617
  555. package/setup/launch.mjs +0 -101
  556. package/setup/locate-claude.mjs +0 -56
  557. package/setup/mixdog-cli.mjs +0 -122
  558. package/setup/setup-server.mjs +0 -3305
  559. package/setup/setup.html +0 -3740
  560. package/setup/tui.mjs +0 -325
  561. package/skills/retro-skill-proposer/SKILL.md +0 -92
  562. package/skills/schedule-add/SKILL.md +0 -77
  563. package/skills/setup/SKILL.md +0 -356
  564. package/skills/webhook-add/SKILL.md +0 -81
  565. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  566. package/src/agent/index.mjs +0 -2138
  567. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  568. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  569. package/src/agent/orchestrator/bridge-trace.mjs +0 -583
  570. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  571. package/src/agent/orchestrator/config.mjs +0 -405
  572. package/src/agent/orchestrator/context/collect.mjs +0 -651
  573. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  574. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  575. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  576. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  577. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  578. package/src/agent/orchestrator/jobs.mjs +0 -116
  579. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  580. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1881
  581. package/src/agent/orchestrator/providers/anthropic.mjs +0 -594
  582. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  583. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  584. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  585. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  586. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  587. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  588. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  589. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  590. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  591. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  592. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  593. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  594. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  595. package/src/agent/orchestrator/session/loop.mjs +0 -1478
  596. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  597. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  598. package/src/agent/orchestrator/session/store.mjs +0 -632
  599. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  600. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  601. package/src/agent/orchestrator/session/trim.mjs +0 -491
  602. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  603. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  604. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  605. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  606. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  607. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  608. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  609. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  610. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  611. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  612. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -455
  613. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  614. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  615. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  616. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  617. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  618. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  619. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  620. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  621. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  622. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  623. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  624. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  625. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  626. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  627. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  628. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  629. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  630. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  631. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  632. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  633. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  634. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  635. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  636. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  637. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  638. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  639. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  640. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  641. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  642. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  643. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  644. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  645. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  646. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  647. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  648. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  649. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  650. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  651. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  652. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  653. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  654. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  655. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  656. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  657. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  658. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  659. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  660. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  661. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  662. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  663. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  664. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  665. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  666. package/src/agent/tool-defs.mjs +0 -103
  667. package/src/channels/backends/discord.mjs +0 -784
  668. package/src/channels/data/voice-runtime-manifest.json +0 -138
  669. package/src/channels/index.mjs +0 -3268
  670. package/src/channels/lib/config.mjs +0 -292
  671. package/src/channels/lib/drop-trace.mjs +0 -71
  672. package/src/channels/lib/event-pipeline.mjs +0 -81
  673. package/src/channels/lib/holidays.mjs +0 -138
  674. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  675. package/src/channels/lib/output-forwarder.mjs +0 -765
  676. package/src/channels/lib/runtime-paths.mjs +0 -517
  677. package/src/channels/lib/scheduler.mjs +0 -723
  678. package/src/channels/lib/session-discovery.mjs +0 -103
  679. package/src/channels/lib/state-file.mjs +0 -68
  680. package/src/channels/lib/status-snapshot.mjs +0 -219
  681. package/src/channels/lib/tool-format.mjs +0 -140
  682. package/src/channels/lib/transcript-discovery.mjs +0 -195
  683. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  684. package/src/channels/lib/webhook.mjs +0 -1318
  685. package/src/channels/tool-defs.mjs +0 -170
  686. package/src/daemon/host.mjs +0 -118
  687. package/src/daemon/mcp-transport.mjs +0 -47
  688. package/src/daemon/session.mjs +0 -100
  689. package/src/daemon/thin-client.mjs +0 -71
  690. package/src/daemon/transport.mjs +0 -163
  691. package/src/memory/data/runtime-manifest.json +0 -40
  692. package/src/memory/index.mjs +0 -3332
  693. package/src/memory/lib/core-memory-store.mjs +0 -330
  694. package/src/memory/lib/embedding-provider.mjs +0 -269
  695. package/src/memory/lib/embedding-worker.mjs +0 -323
  696. package/src/memory/lib/memory-cycle1.mjs +0 -645
  697. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  698. package/src/memory/lib/memory-cycle3.mjs +0 -540
  699. package/src/memory/lib/memory-embed.mjs +0 -299
  700. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  701. package/src/memory/lib/memory-recall-store.mjs +0 -638
  702. package/src/memory/lib/memory.mjs +0 -412
  703. package/src/memory/lib/pg/adapter.mjs +0 -308
  704. package/src/memory/lib/pg/process.mjs +0 -360
  705. package/src/memory/lib/pg/supervisor.mjs +0 -396
  706. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  707. package/src/memory/lib/trace-store.mjs +0 -728
  708. package/src/memory/tool-defs.mjs +0 -79
  709. package/src/search/index.mjs +0 -1173
  710. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  711. package/src/search/lib/backends/exa.mjs +0 -50
  712. package/src/search/lib/backends/firecrawl.mjs +0 -61
  713. package/src/search/lib/backends/gemini-api.mjs +0 -83
  714. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  715. package/src/search/lib/backends/index.mjs +0 -150
  716. package/src/search/lib/backends/openai-api.mjs +0 -144
  717. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  718. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  719. package/src/search/lib/backends/tavily.mjs +0 -55
  720. package/src/search/lib/backends/xai-api.mjs +0 -113
  721. package/src/search/lib/config.mjs +0 -192
  722. package/src/search/lib/provider-usage.mjs +0 -67
  723. package/src/search/lib/providers.mjs +0 -47
  724. package/src/search/lib/search-intent.mjs +0 -109
  725. package/src/search/lib/setup-handler.mjs +0 -261
  726. package/src/search/lib/web-tools.mjs +0 -1219
  727. package/src/search/tool-defs.mjs +0 -83
  728. package/src/setup/defender-exclusion.mjs +0 -183
  729. package/src/shared/atomic-file.mjs +0 -436
  730. package/src/shared/config.mjs +0 -372
  731. package/src/shared/daemon-recycle.mjs +0 -108
  732. package/src/shared/disable-claude-builtins.mjs +0 -91
  733. package/src/shared/err-text.mjs +0 -12
  734. package/src/shared/llm/cost.mjs +0 -66
  735. package/src/shared/llm/http-agent.mjs +0 -123
  736. package/src/shared/open-url.mjs +0 -62
  737. package/src/shared/plugin-paths.mjs +0 -58
  738. package/src/shared/schedules-store.mjs +0 -70
  739. package/src/shared/seed.mjs +0 -136
  740. package/src/shared/user-cwd.mjs +0 -225
  741. package/src/shared/user-data-guard.mjs +0 -244
  742. package/src/status/aggregator.mjs +0 -584
  743. package/src/status/server.mjs +0 -413
  744. package/tools.json +0 -1653
  745. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  746. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  747. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  748. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  749. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  750. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  751. /package/{lib → src/lib}/text-utils.cjs +0 -0
  752. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  753. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  754. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  755. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  756. /package/src/{agent → runtime/agent}/orchestrator/session/cache/post-edit-marks.mjs +0 -0
  757. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  758. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  759. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  760. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  761. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  762. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  763. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/advisory-lock.mjs +0 -0
  764. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  805. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  806. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  807. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  808. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  809. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  810. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  811. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  812. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  813. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  814. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  815. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  816. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  817. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  818. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  819. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  820. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  821. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  822. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  823. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  824. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  829. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  830. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  831. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  832. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  833. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  834. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  835. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  836. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -0,0 +1,1679 @@
1
+ import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai';
2
+ import { loadConfig } from '../config.mjs';
3
+ import { makeModelCache } from './model-cache.mjs';
4
+ import { withRetry } from './retry-classifier.mjs';
5
+ import { traceBridgeUsage, appendBridgeTrace } from '../bridge-trace.mjs';
6
+ import {
7
+ PROVIDER_FIRST_BYTE_TIMEOUT_MS,
8
+ PROVIDER_MAX_BEFORE_WARN_MS,
9
+ PROVIDER_SSE_IDLE_TIMEOUT_MS,
10
+ PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
11
+ providerTimeoutError,
12
+ resolveTimeoutMs,
13
+ createTimeoutSignal,
14
+ createPassthroughSignal,
15
+ } from '../stall-policy.mjs';
16
+ import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
17
+ import { traceHash, stableTraceStringify, summarizeTraceTools, traceTextShape } from './trace-utils.mjs';
18
+ import { normalizeContentForGeminiParts, splitToolContentForGemini } from './media-normalization.mjs';
19
+
20
+ const MODELS = [
21
+ { id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash Preview', provider: 'gemini', contextWindow: 1048576 },
22
+ { id: 'gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro Preview', provider: 'gemini', contextWindow: 1048576 },
23
+ { id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro Preview', provider: 'gemini', contextWindow: 1048576 },
24
+ { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', provider: 'gemini', contextWindow: 1048576 },
25
+ { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', provider: 'gemini', contextWindow: 1048576 },
26
+ ];
27
+
28
+ const DEFAULT_MODEL = MODELS[0].id;
29
+
30
+ // --- Model catalog cache (24h disk TTL) ---
31
+ // Gemini's /models has no `created` timestamp, so latest-resolution is
32
+ // VERSION-based (parse gemini-X.Y) rather than release-date based.
33
+ const MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
34
+
35
+ // De-dupes concurrent force-refreshes so they share one HTTP round-trip,
36
+ // mirroring anthropic-oauth's _modelRefreshInFlight.
37
+ let _modelRefreshInFlight = null;
38
+
39
+ const _modelCache = makeModelCache({ fileName: 'gemini-models.json', ttlMs: MODEL_CACHE_TTL_MS });
40
+
41
+ // Mirror of anthropic-oauth.mjs _compareVersion: compare two gemini ids by the
42
+ // X.Y version embedded in the id (gemini-3.5-flash -> [3, 5]). Falls back to a
43
+ // lexicographic tiebreak so ordering is total.
44
+ function _compareVersion(a, b) {
45
+ const na = (a.match(/gemini-(\d+)(?:\.(\d+))?/) || []).slice(1).map(Number);
46
+ const nb = (b.match(/gemini-(\d+)(?:\.(\d+))?/) || []).slice(1).map(Number);
47
+ for (let i = 0; i < Math.max(na.length, nb.length); i++) {
48
+ if ((na[i] || 0) !== (nb[i] || 0)) return (na[i] || 0) - (nb[i] || 0);
49
+ }
50
+ return a.localeCompare(b);
51
+ }
52
+
53
+ // Per family, mark the highest-version model as latest:true.
54
+ function _markLatestGemini(models) {
55
+ const byFamily = new Map();
56
+ for (const m of models) {
57
+ if (!m?.id) continue;
58
+ const cur = byFamily.get(m.family);
59
+ if (!cur || _compareVersion(m.id, cur.id) > 0) {
60
+ byFamily.set(m.family, m);
61
+ }
62
+ }
63
+ for (const m of byFamily.values()) m.latest = true;
64
+ }
65
+
66
+ // Newest chat model by VERSION in the 'gemini-flash' family, read from the
67
+ // on-disk catalog cache. Returns null until cached; callers warm via
68
+ // ensureLatestGeminiModel when null.
69
+ export function resolveLatestGeminiModel() {
70
+ const cached = _modelCache.loadSync();
71
+ if (!Array.isArray(cached)) return null;
72
+ let best = null;
73
+ for (const m of cached) {
74
+ if (!m?.id || m.family !== 'gemini-flash') continue;
75
+ if (!best || _compareVersion(m.id, best.id) > 0) best = m;
76
+ }
77
+ return best?.id || null;
78
+ }
79
+
80
+ export async function ensureLatestGeminiModel(provider) {
81
+ let m = resolveLatestGeminiModel();
82
+ if (m) return m;
83
+ await provider._refreshModelCache();
84
+ m = resolveLatestGeminiModel();
85
+ if (m) return m;
86
+ throw new Error('[gemini] model catalog unavailable after warmup — cannot resolve default model');
87
+ }
88
+
89
+ const GEMINI_FIRST_BYTE_TIMEOUT_MS = resolveTimeoutMs(
90
+ 'MIXDOG_GEMINI_FIRST_BYTE_TIMEOUT_MS',
91
+ PROVIDER_FIRST_BYTE_TIMEOUT_MS,
92
+ { minMs: 30_000, maxMs: PROVIDER_MAX_BEFORE_WARN_MS },
93
+ );
94
+
95
+ function summarizeTracePart(part) {
96
+ if (!part || typeof part !== 'object') return { type: typeof part };
97
+ if ('text' in part) {
98
+ return { type: 'text', ...traceTextShape(part.text) };
99
+ }
100
+ if (part.functionCall) {
101
+ return {
102
+ type: 'functionCall',
103
+ name: part.functionCall.name || null,
104
+ argsHash: traceHash(stableTraceStringify(part.functionCall.args || {})),
105
+ };
106
+ }
107
+ if (part.functionResponse) {
108
+ const response = part.functionResponse.response || {};
109
+ const responseShape = stableTraceStringify(response);
110
+ return {
111
+ type: 'functionResponse',
112
+ name: part.functionResponse.name || null,
113
+ responseChars: responseShape.length,
114
+ responseHash: traceHash(responseShape),
115
+ };
116
+ }
117
+ return { type: Object.keys(part).sort().join('|') || 'unknown' };
118
+ }
119
+
120
+ function summarizeTraceContents(contents) {
121
+ const summaries = (contents || []).map((content, index) => ({
122
+ index,
123
+ role: content?.role || null,
124
+ parts: Array.isArray(content?.parts) ? content.parts.map(summarizeTracePart) : [],
125
+ }));
126
+ if (summaries.length <= 12) return summaries;
127
+ return [
128
+ ...summaries.slice(0, 8),
129
+ { omittedTurns: summaries.length - 12 },
130
+ ...summaries.slice(-4),
131
+ ];
132
+ }
133
+
134
+ // Gemini cachedContents API rejects prefixes below the model-specific minimum
135
+ // (Gemini 2.0/2.5 = 2048 tokens, Gemini 3.x = 4096 tokens) with HTTP 400
136
+ // "Cached content is too small". Estimating chars/4 ≈ tokens lets us skip the
137
+ // roundtrip when the prefix cannot satisfy the invariant. Estimate undercount
138
+ // is harmless (we attempt and get the same 400 we would have skipped); overcount
139
+ // is harmless (we skip, fall back to implicit cache).
140
+ function _estimateGeminiCacheTokens(systemInstruction, geminiTools, contents) {
141
+ let chars = 0;
142
+ if (typeof systemInstruction === 'string') chars += systemInstruction.length;
143
+ if (Array.isArray(geminiTools) && geminiTools.length) {
144
+ try { chars += JSON.stringify(geminiTools).length; } catch {}
145
+ }
146
+ if (Array.isArray(contents) && contents.length > 1) {
147
+ for (let i = 0; i < contents.length - 1; i++) {
148
+ try { chars += JSON.stringify(contents[i]?.parts ?? '').length; } catch {}
149
+ }
150
+ }
151
+ return Math.ceil(chars / 4);
152
+ }
153
+
154
+ function _geminiCacheMinTokens(model) {
155
+ return /^gemini-3/i.test(String(model || '')) ? 4096 : 2048;
156
+ }
157
+
158
+ function _geminiCachePrefixCount(contents) {
159
+ return Array.isArray(contents) && contents.length > 1 ? contents.length - 1 : 0;
160
+ }
161
+
162
+ function _geminiCachePrefixContents(contents, prefixCount) {
163
+ if (!Array.isArray(contents) || prefixCount <= 0) return [];
164
+ return contents.slice(0, prefixCount).map(c => {
165
+ const r = c?.role;
166
+ const safeRole = (r === 'model' || r === 'user') ? r : 'user';
167
+ return {
168
+ role: safeRole,
169
+ parts: Array.isArray(c?.parts) ? c.parts : [],
170
+ };
171
+ });
172
+ }
173
+
174
+ function _geminiCachePrefixHash({ model, systemInstruction, geminiTools, contents, prefixCount }) {
175
+ return traceHash(stableTraceStringify({
176
+ model: model || null,
177
+ systemInstruction: systemInstruction || '',
178
+ tools: geminiTools || [],
179
+ contents: _geminiCachePrefixContents(contents, prefixCount),
180
+ }));
181
+ }
182
+
183
+ const GEMINI_GLOBAL_CACHE_MIN_LIVE_MS = 6 * 60 * 1000;
184
+ const GEMINI_GLOBAL_CACHE_MAX_ENTRIES = 128;
185
+ const geminiGlobalCaches = new Map();
186
+ const geminiGlobalCacheCreates = new Map();
187
+
188
+ function _geminiGlobalCacheKey({ apiKey, model, cachePrefixHash, cachePrefixContentCount }) {
189
+ return traceHash(stableTraceStringify({
190
+ apiKeyHash: traceHash(apiKey || ''),
191
+ model: model || null,
192
+ cachePrefixHash,
193
+ cachePrefixContentCount,
194
+ }));
195
+ }
196
+
197
+ function _pruneGeminiGlobalCaches(now = Date.now()) {
198
+ for (const [key, entry] of geminiGlobalCaches) {
199
+ if (!entry?.cacheName || Number(entry.cacheExpiresAt || 0) <= now) {
200
+ geminiGlobalCaches.delete(key);
201
+ }
202
+ }
203
+ while (geminiGlobalCaches.size > GEMINI_GLOBAL_CACHE_MAX_ENTRIES) {
204
+ const oldest = geminiGlobalCaches.keys().next().value;
205
+ if (oldest === undefined) break;
206
+ geminiGlobalCaches.delete(oldest);
207
+ }
208
+ }
209
+
210
+ function _getGeminiGlobalCache(key, now = Date.now()) {
211
+ _pruneGeminiGlobalCaches(now);
212
+ const entry = geminiGlobalCaches.get(key);
213
+ if (!entry?.cacheName) return null;
214
+ if ((entry.cacheExpiresAt || 0) - now <= GEMINI_GLOBAL_CACHE_MIN_LIVE_MS) {
215
+ geminiGlobalCaches.delete(key);
216
+ return null;
217
+ }
218
+ entry.lastUsedAt = now;
219
+ return entry;
220
+ }
221
+
222
+ function _setGeminiGlobalCache(key, entry) {
223
+ if (!key || !entry?.cacheName) return;
224
+ geminiGlobalCaches.set(key, {
225
+ ...entry,
226
+ lastUsedAt: Date.now(),
227
+ });
228
+ _pruneGeminiGlobalCaches();
229
+ }
230
+
231
+ function _geminiGlobalCacheNameIsLive(cacheName, now = Date.now()) {
232
+ if (!cacheName) return false;
233
+ for (const entry of geminiGlobalCaches.values()) {
234
+ if (entry?.cacheName === cacheName && Number(entry.cacheExpiresAt || 0) > now) return true;
235
+ }
236
+ return false;
237
+ }
238
+
239
+ function _attachGeminiCacheState(opts, entry, currentIter) {
240
+ if (!entry?.cacheName) return;
241
+ opts.providerState = {
242
+ ...(opts.providerState || {}),
243
+ gemini: {
244
+ cacheName: entry.cacheName,
245
+ cacheCreatedAt: entry.cacheCreatedAt,
246
+ cacheCreatedAtIter: currentIter,
247
+ cacheExpiresAt: entry.cacheExpiresAt,
248
+ cacheModel: entry.cacheModel,
249
+ cacheTokenSize: entry.cacheTokenSize,
250
+ cachePrefixContentCount: entry.cachePrefixContentCount,
251
+ cachePrefixHash: entry.cachePrefixHash,
252
+ },
253
+ };
254
+ }
255
+
256
+ function _resolveGeminiCacheUsage({ usageMetadata, cachedContent, providerState }) {
257
+ const inputTokens = Number(usageMetadata?.promptTokenCount || usageMetadata?.totalTokenCount || 0) || 0;
258
+ const reportedCachedTokens = Number(usageMetadata?.cachedContentTokenCount || 0) || 0;
259
+ const cachedFallbackTokens = cachedContent
260
+ ? Number(providerState?.gemini?.cacheTokenSize || 0) || 0
261
+ : 0;
262
+ const rawCachedTokens = reportedCachedTokens > 0 ? reportedCachedTokens : cachedFallbackTokens;
263
+ const cachedTokens = inputTokens > 0 ? Math.min(rawCachedTokens, inputTokens) : rawCachedTokens;
264
+ const cacheTokenSource = reportedCachedTokens > 0
265
+ ? 'usage_metadata'
266
+ : (cachedFallbackTokens > 0 ? 'cache_create_fallback' : 'none');
267
+ return {
268
+ inputTokens,
269
+ reportedCachedTokens,
270
+ cachedFallbackTokens,
271
+ cachedTokens,
272
+ cacheTokenSource,
273
+ };
274
+ }
275
+
276
+ function writeGeminiCacheTrace({ opts, model, systemInstruction, tools, contents, usageMetadata, cachedContent }) {
277
+ if (process.env.MIXDOG_GEMINI_CACHE_TRACE !== '1') return;
278
+ try {
279
+ const session = opts?.session || {};
280
+ const {
281
+ inputTokens,
282
+ reportedCachedTokens,
283
+ cachedFallbackTokens,
284
+ cachedTokens,
285
+ cacheTokenSource,
286
+ } = _resolveGeminiCacheUsage({
287
+ usageMetadata,
288
+ cachedContent,
289
+ providerState: opts?.providerState,
290
+ });
291
+ const toolShape = summarizeTraceTools(tools);
292
+ const trace = {
293
+ event: 'generate',
294
+ provider: 'gemini',
295
+ model,
296
+ owner: session.owner || null,
297
+ role: session.role || null,
298
+ permission: session.permission || null,
299
+ toolPermission: session.toolPermission || null,
300
+ profileId: session.profileId || null,
301
+ sourceType: session.sourceType || null,
302
+ sourceName: session.sourceName || null,
303
+ sessionIdHash: opts?.sessionId ? traceHash(opts.sessionId) : null,
304
+ providerCacheKeyHash: opts?.providerCacheKey ? traceHash(opts.providerCacheKey) : null,
305
+ promptCacheKeyHash: opts?.promptCacheKey ? traceHash(opts.promptCacheKey) : null,
306
+ systemChars: systemInstruction ? systemInstruction.length : 0,
307
+ systemHash: systemInstruction ? traceHash(systemInstruction) : null,
308
+ toolCount: Array.isArray(tools) ? tools.length : 0,
309
+ toolSchemaHash: traceHash(stableTraceStringify(toolShape)),
310
+ contentTurnCount: Array.isArray(contents) ? contents.length : 0,
311
+ contents: summarizeTraceContents(contents),
312
+ inputTokens,
313
+ cachedTokens,
314
+ reportedCachedTokens,
315
+ cachedFallbackTokens,
316
+ cacheTokenSource,
317
+ cacheAttached: !!cachedContent,
318
+ cachePrefixContentCount: opts?.providerState?.gemini?.cachePrefixContentCount ?? null,
319
+ cacheHitRate: inputTokens > 0 ? Number((cachedTokens / inputTokens).toFixed(6)) : null,
320
+ };
321
+ process.stderr.write(`[gemini-cache-trace] ${JSON.stringify(trace)}\n`);
322
+ } catch (err) {
323
+ process.stderr.write(`[gemini-cache-trace] failed: ${err?.message || err}\n`);
324
+ }
325
+ }
326
+
327
+ function geminiTimeoutError(label, timeoutMs) {
328
+ const err = providerTimeoutError(label, timeoutMs);
329
+ err.name = 'GeminiTimeoutError';
330
+ err.code = 'EGEMINITIMEOUT';
331
+ return err;
332
+ }
333
+
334
+ function geminiTruncatedStreamError(message) {
335
+ return Object.assign(
336
+ new Error(message),
337
+ { name: 'TruncatedStreamError', code: 'TRUNCATED_STREAM', truncatedStream: true },
338
+ );
339
+ }
340
+
341
+ /**
342
+ * Aggregate streamed GenerateContentResponse chunks into one response object
343
+ * (same shape as a non-streaming generateContent JSON body).
344
+ * Mirrors @google/generative-ai aggregateResponses().
345
+ */
346
+ function aggregateGeminiStreamChunks(responses) {
347
+ const lastResponse = responses[responses.length - 1];
348
+ const aggregatedResponse = {
349
+ promptFeedback: lastResponse?.promptFeedback,
350
+ };
351
+ for (const response of responses) {
352
+ if (response?.candidates) {
353
+ let candidateIndex = 0;
354
+ for (const candidate of response.candidates) {
355
+ if (!aggregatedResponse.candidates) aggregatedResponse.candidates = [];
356
+ if (!aggregatedResponse.candidates[candidateIndex]) {
357
+ aggregatedResponse.candidates[candidateIndex] = { index: candidateIndex };
358
+ }
359
+ const aggCand = aggregatedResponse.candidates[candidateIndex];
360
+ aggCand.citationMetadata = candidate.citationMetadata;
361
+ aggCand.groundingMetadata = candidate.groundingMetadata;
362
+ aggCand.finishReason = candidate.finishReason;
363
+ aggCand.finishMessage = candidate.finishMessage;
364
+ aggCand.safetyRatings = candidate.safetyRatings;
365
+ if (candidate.content?.parts) {
366
+ if (!aggCand.content) {
367
+ aggCand.content = {
368
+ role: candidate.content.role || 'user',
369
+ parts: [],
370
+ };
371
+ }
372
+ for (const part of candidate.content.parts) {
373
+ const newPart = {};
374
+ if (part.text) newPart.text = part.text;
375
+ if (part.functionCall) newPart.functionCall = part.functionCall;
376
+ if (part.thoughtSignature) newPart.thoughtSignature = part.thoughtSignature;
377
+ if (part.thought_signature) newPart.thought_signature = part.thought_signature;
378
+ if (part.executableCode) newPart.executableCode = part.executableCode;
379
+ if (part.codeExecutionResult) newPart.codeExecutionResult = part.codeExecutionResult;
380
+ if (Object.keys(newPart).length === 0) newPart.text = '';
381
+ aggCand.content.parts.push(newPart);
382
+ }
383
+ }
384
+ candidateIndex++;
385
+ }
386
+ }
387
+ if (response?.usageMetadata) aggregatedResponse.usageMetadata = response.usageMetadata;
388
+ }
389
+ return aggregatedResponse;
390
+ }
391
+
392
+ function assertGeminiStreamCompleted({ sawStreamChunk, finishReason, label }) {
393
+ if (!sawStreamChunk) {
394
+ throw geminiTruncatedStreamError(`${label} truncated: empty stream`);
395
+ }
396
+ if (!finishReason) {
397
+ throw geminiTruncatedStreamError(`${label} truncated: no finishReason`);
398
+ }
399
+ }
400
+
401
+ // Concatenate the text parts of a single streamed Gemini chunk. Used to feed
402
+ // the gateway live-text relay (onTextDelta) with the incremental text payload
403
+ // as each SSE/SDK chunk arrives. Returns '' for tool-call / thought-only /
404
+ // malformed chunks so the caller can skip empty emits.
405
+ function geminiChunkText(chunk) {
406
+ const parts = chunk?.candidates?.[0]?.content?.parts;
407
+ if (!Array.isArray(parts)) return '';
408
+ let text = '';
409
+ for (const p of parts) {
410
+ if (p && typeof p.text === 'string') text += p.text;
411
+ }
412
+ return text;
413
+ }
414
+
415
+ async function consumeGeminiRestStreamResponse(response, { signal, onStreamDelta, onTextDelta, label }) {
416
+ if (!response?.body) throw new Error(`${label}: missing response body`);
417
+ const reader = response.body.getReader();
418
+ const decoder = new TextDecoder();
419
+ let buffer = '';
420
+ const allChunks = [];
421
+ let sawStreamChunk = false;
422
+ let idleTimedOut = false;
423
+ let idleTimer = null;
424
+ let idleReject = null;
425
+
426
+ let firstByteTimer = setTimeout(() => {
427
+ try { reader.cancel('first byte timeout'); } catch {}
428
+ if (idleReject) {
429
+ const e = geminiTimeoutError(`${label} first byte`, GEMINI_FIRST_BYTE_TIMEOUT_MS);
430
+ const r = idleReject; idleReject = null; r(e);
431
+ }
432
+ }, GEMINI_FIRST_BYTE_TIMEOUT_MS);
433
+ if (firstByteTimer.unref) firstByteTimer.unref();
434
+
435
+ const clearFirstByteTimer = () => {
436
+ if (firstByteTimer) {
437
+ clearTimeout(firstByteTimer);
438
+ firstByteTimer = null;
439
+ }
440
+ };
441
+
442
+ const resetIdleTimer = () => {
443
+ if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
444
+ if (idleTimer) clearTimeout(idleTimer);
445
+ idleTimer = setTimeout(() => {
446
+ idleTimedOut = true;
447
+ try { reader.cancel('SSE idle timeout'); } catch {}
448
+ if (idleReject) {
449
+ const e = geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
450
+ const r = idleReject; idleReject = null; r(e);
451
+ }
452
+ }, PROVIDER_SSE_IDLE_TIMEOUT_MS);
453
+ if (idleTimer.unref) idleTimer.unref();
454
+ };
455
+
456
+ const onAbort = () => {
457
+ try {
458
+ const c = reader.cancel('aborted');
459
+ if (c && typeof c.catch === 'function') c.catch(() => {});
460
+ } catch {}
461
+ };
462
+
463
+ if (signal) {
464
+ if (signal.aborted) {
465
+ const reason = signal.reason;
466
+ throw reason instanceof Error ? reason : new Error(`${label} aborted`);
467
+ }
468
+ signal.addEventListener('abort', onAbort, { once: true });
469
+ }
470
+
471
+ try {
472
+ resetIdleTimer();
473
+ while (true) {
474
+ let chunk;
475
+ try {
476
+ chunk = await new Promise((resolve, reject) => {
477
+ idleReject = reject;
478
+ reader.read().then(resolve, reject);
479
+ });
480
+ } catch (err) {
481
+ if (idleTimedOut) {
482
+ throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
483
+ }
484
+ if (signal?.aborted) {
485
+ const reason = signal.reason;
486
+ throw reason instanceof Error ? reason : new Error(`${label} aborted`);
487
+ }
488
+ throw err;
489
+ } finally {
490
+ idleReject = null;
491
+ }
492
+ const { done, value } = chunk;
493
+ if (done) break;
494
+ resetIdleTimer();
495
+ buffer += decoder.decode(value, { stream: true });
496
+ let lineEnd;
497
+ while ((lineEnd = buffer.indexOf('\n')) >= 0) {
498
+ let line = buffer.slice(0, lineEnd);
499
+ buffer = buffer.slice(lineEnd + 1);
500
+ line = line.replace(/\r$/, '');
501
+ if (!line.startsWith('data: ')) continue;
502
+ const data = line.slice(6).trim();
503
+ if (!data || data === '[DONE]') continue;
504
+ let parsed;
505
+ try { parsed = JSON.parse(data); } catch { continue; }
506
+ if (!sawStreamChunk) {
507
+ sawStreamChunk = true;
508
+ clearFirstByteTimer();
509
+ }
510
+ allChunks.push(parsed);
511
+ try { onStreamDelta?.(); } catch {}
512
+ if (onTextDelta) {
513
+ const t = geminiChunkText(parsed);
514
+ if (t) { try { onTextDelta(t); } catch {} }
515
+ }
516
+ }
517
+ }
518
+ if (buffer.trim()) {
519
+ const line = buffer.trim().replace(/\r$/, '');
520
+ if (line.startsWith('data: ')) {
521
+ const data = line.slice(6).trim();
522
+ if (data && data !== '[DONE]') {
523
+ try {
524
+ const parsed = JSON.parse(data);
525
+ if (!sawStreamChunk) {
526
+ sawStreamChunk = true;
527
+ clearFirstByteTimer();
528
+ }
529
+ allChunks.push(parsed);
530
+ try { onStreamDelta?.(); } catch {}
531
+ if (onTextDelta) {
532
+ const t = geminiChunkText(parsed);
533
+ if (t) { try { onTextDelta(t); } catch {} }
534
+ }
535
+ } catch { /* skip malformed tail */ }
536
+ }
537
+ }
538
+ }
539
+ } finally {
540
+ clearFirstByteTimer();
541
+ if (idleTimer) clearTimeout(idleTimer);
542
+ if (signal) signal.removeEventListener('abort', onAbort);
543
+ try { reader.releaseLock(); } catch {}
544
+ }
545
+
546
+ const aggregated = aggregateGeminiStreamChunks(allChunks);
547
+ const finishReason = aggregated.candidates?.[0]?.finishReason || null;
548
+ assertGeminiStreamCompleted({ sawStreamChunk, finishReason, label });
549
+ return aggregated;
550
+ }
551
+
552
+ async function consumeGeminiSdkStream(streamResult, { signal, onStreamDelta, onTextDelta, label }) {
553
+ let sawStreamChunk = false;
554
+ let idleTimedOut = false;
555
+ let idleTimer = null;
556
+ let firstByteReject = null;
557
+ let firstByteTimer = null;
558
+ let inFlightReject = null;
559
+
560
+ const armFirstByteTimer = () => {
561
+ if (firstByteTimer) clearTimeout(firstByteTimer);
562
+ firstByteTimer = setTimeout(() => {
563
+ if (firstByteReject) {
564
+ const e = geminiTimeoutError(`${label} first byte`, GEMINI_FIRST_BYTE_TIMEOUT_MS);
565
+ const r = firstByteReject; firstByteReject = null; r(e);
566
+ }
567
+ }, GEMINI_FIRST_BYTE_TIMEOUT_MS);
568
+ if (firstByteTimer.unref) firstByteTimer.unref();
569
+ };
570
+
571
+ const clearFirstByteTimer = () => {
572
+ if (firstByteTimer) {
573
+ clearTimeout(firstByteTimer);
574
+ firstByteTimer = null;
575
+ }
576
+ firstByteReject = null;
577
+ };
578
+
579
+ const resetIdleTimer = () => {
580
+ if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
581
+ if (idleTimer) clearTimeout(idleTimer);
582
+ idleTimer = setTimeout(() => {
583
+ idleTimedOut = true;
584
+ if (inFlightReject) {
585
+ const e = geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
586
+ const r = inFlightReject; inFlightReject = null; r(e);
587
+ }
588
+ }, PROVIDER_SSE_IDLE_TIMEOUT_MS);
589
+ if (idleTimer.unref) idleTimer.unref();
590
+ };
591
+
592
+ if (signal?.aborted) {
593
+ const reason = signal.reason;
594
+ throw reason instanceof Error ? reason : new Error(`${label} aborted`);
595
+ }
596
+
597
+ const iterator = streamResult.stream[Symbol.asyncIterator]();
598
+
599
+ // Wire the abort signal to actually CANCEL iteration (mirror of the REST
600
+ // consumer's onAbort -> reader.cancel). Without this, a parent / client /
601
+ // gateway abort that fires AFTER the first byte — with the SSE idle
602
+ // watchdog off by default — would leave iterator.next() hanging: the loop
603
+ // below only reads `signal` for error translation, never to interrupt the
604
+ // pending read. Rejecting the in-flight promise and returning the iterator
605
+ // releases it promptly even if the SDK is slow to propagate the underlying
606
+ // request abort.
607
+ let onSignalAbort = null;
608
+ if (signal) {
609
+ onSignalAbort = () => {
610
+ const reason = signal.reason;
611
+ const err = reason instanceof Error ? reason : new Error(`${label} aborted`);
612
+ if (inFlightReject) {
613
+ const r = inFlightReject;
614
+ inFlightReject = null;
615
+ firstByteReject = null;
616
+ r(err);
617
+ }
618
+ try {
619
+ const ret = iterator.return?.();
620
+ if (ret && typeof ret.catch === 'function') ret.catch(() => {});
621
+ } catch {}
622
+ };
623
+ signal.addEventListener('abort', onSignalAbort, { once: true });
624
+ }
625
+
626
+ try {
627
+ armFirstByteTimer();
628
+ resetIdleTimer();
629
+ while (true) {
630
+ if (idleTimedOut) {
631
+ throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
632
+ }
633
+ let step;
634
+ try {
635
+ step = await new Promise((resolve, reject) => {
636
+ inFlightReject = reject;
637
+ if (!sawStreamChunk) firstByteReject = reject;
638
+ iterator.next().then(
639
+ (value) => {
640
+ inFlightReject = null;
641
+ firstByteReject = null;
642
+ resolve(value);
643
+ },
644
+ (err) => {
645
+ inFlightReject = null;
646
+ firstByteReject = null;
647
+ reject(err);
648
+ },
649
+ );
650
+ });
651
+ } catch (err) {
652
+ if (idleTimedOut) {
653
+ throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
654
+ }
655
+ if (signal?.aborted) {
656
+ const reason = signal.reason;
657
+ throw reason instanceof Error ? reason : new Error(`${label} aborted`);
658
+ }
659
+ throw err;
660
+ }
661
+ if (step.done) break;
662
+ if (!sawStreamChunk) {
663
+ sawStreamChunk = true;
664
+ clearFirstByteTimer();
665
+ }
666
+ resetIdleTimer();
667
+ try { onStreamDelta?.(); } catch {}
668
+ if (onTextDelta) {
669
+ const t = geminiChunkText(step.value);
670
+ if (t) { try { onTextDelta(t); } catch {} }
671
+ }
672
+ }
673
+ if (idleTimedOut) {
674
+ throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
675
+ }
676
+ } catch (err) {
677
+ clearFirstByteTimer();
678
+ if (signal?.aborted) {
679
+ const reason = signal.reason;
680
+ throw reason instanceof Error ? reason : new Error(`${label} aborted`);
681
+ }
682
+ throw err;
683
+ } finally {
684
+ clearFirstByteTimer();
685
+ if (idleTimer) clearTimeout(idleTimer);
686
+ if (signal && onSignalAbort) {
687
+ try { signal.removeEventListener('abort', onSignalAbort); } catch {}
688
+ }
689
+ }
690
+
691
+ let response;
692
+ try {
693
+ response = await streamResult.response;
694
+ } catch (err) {
695
+ if (signal?.aborted) {
696
+ const reason = signal.reason;
697
+ throw reason instanceof Error ? reason : new Error(`${label} aborted`);
698
+ }
699
+ throw err;
700
+ }
701
+ const raw = response?.candidates ? response : (response?.response || response);
702
+ const finishReason = raw?.candidates?.[0]?.finishReason || null;
703
+ assertGeminiStreamCompleted({ sawStreamChunk, finishReason, label });
704
+ return raw;
705
+ }
706
+
707
+ /**
708
+ * Convert JSON Schema type string to Gemini SchemaType.
709
+ * Gemini SDK uses its own enum instead of plain strings.
710
+ */
711
+ function toSchemaType(t) {
712
+ const map = {
713
+ string: SchemaType.STRING,
714
+ number: SchemaType.NUMBER,
715
+ integer: SchemaType.INTEGER,
716
+ boolean: SchemaType.BOOLEAN,
717
+ array: SchemaType.ARRAY,
718
+ object: SchemaType.OBJECT,
719
+ };
720
+ return map[t] ?? SchemaType.STRING;
721
+ }
722
+
723
+ /**
724
+ * Recursively convert a JSON Schema object to Gemini's FunctionDeclarationSchema.
725
+ * Gemini requires `type` to be a SchemaType enum, not a plain string, and
726
+ * rejects several JSON Schema fields the API does not understand
727
+ * (additionalProperties, $schema, $ref, const, examples, definitions,
728
+ * patternProperties). We strip those at every level.
729
+ */
730
+ const GEMINI_SCHEMA_STRIP = new Set([
731
+ 'additionalProperties',
732
+ '$schema',
733
+ '$ref',
734
+ 'const',
735
+ 'examples',
736
+ 'definitions',
737
+ 'patternProperties',
738
+ ]);
739
+ function convertSchema(schema) {
740
+ if (!schema || typeof schema !== 'object') return schema;
741
+ const result = {};
742
+ for (const [k, v] of Object.entries(schema)) {
743
+ if (GEMINI_SCHEMA_STRIP.has(k)) continue;
744
+ result[k] = v;
745
+ }
746
+ // Gemini's Schema validator requires every `enum` entry to be a string,
747
+ // even when the parent `type` is integer/number/boolean. Drop the enum in
748
+ // that case rather than emit an invalid typed enum — `type` plus the
749
+ // description still guides the model, and the tool handler revalidates.
750
+ const rawType = typeof result.type === 'string' ? result.type : undefined;
751
+ if (Array.isArray(result.enum) && (rawType === 'integer' || rawType === 'number' || rawType === 'boolean')) {
752
+ if (result.enum.some((item) => typeof item !== 'string')) {
753
+ delete result.enum;
754
+ }
755
+ }
756
+ // Gemini rejects array schemas that omit `items`; fill a permissive
757
+ // default so the declaration validates.
758
+ if (rawType === 'array' && (!result.items || typeof result.items !== 'object')) {
759
+ result.items = { type: 'string' };
760
+ }
761
+ if (typeof result.type === 'string') {
762
+ result.type = toSchemaType(result.type);
763
+ }
764
+ if (result.properties && typeof result.properties === 'object') {
765
+ const props = {};
766
+ for (const [key, val] of Object.entries(result.properties)) {
767
+ props[key] = convertSchema(val);
768
+ }
769
+ result.properties = props;
770
+ }
771
+ if (result.items && typeof result.items === 'object') {
772
+ result.items = convertSchema(result.items);
773
+ }
774
+ // Recurse into JSON Schema combinator keys so disallowed fields
775
+ // (additionalProperties, $schema, etc.) get stripped at every nesting
776
+ // level. Without this, schemas using anyOf/oneOf/allOf/not pass the
777
+ // shallow strip but fail Gemini validation at depth.
778
+ //
779
+ // Two Gemini-specific normalizations are also applied per combinator
780
+ // subschema:
781
+ // 1. Inject `type: OBJECT` when a subschema uses object-only keys
782
+ // (`required` / `properties`) without an explicit type — Gemini
783
+ // rejects `required` outside of OBJECT type.
784
+ // 2. Materialize a local `properties` map from the parent's properties
785
+ // when the subschema only carries `required: [names]` — Gemini
786
+ // validates that every name in `required` exists in *this*
787
+ // subschema's `properties` (it does not inherit from the parent
788
+ // the way JSON Schema's compositional model does).
789
+ for (const combinator of ['anyOf', 'oneOf', 'allOf']) {
790
+ if (Array.isArray(result[combinator])) {
791
+ result[combinator] = result[combinator].map((s) => {
792
+ const sub = convertSchema(s);
793
+ if (sub && typeof sub === 'object') {
794
+ const usesObjectKeys = sub.required !== undefined || sub.properties !== undefined;
795
+ if (usesObjectKeys && sub.type === undefined) {
796
+ sub.type = toSchemaType('object');
797
+ }
798
+ if (Array.isArray(sub.required) && !sub.properties && result.properties) {
799
+ const projected = {};
800
+ for (const k of sub.required) {
801
+ if (result.properties[k]) projected[k] = result.properties[k];
802
+ }
803
+ if (Object.keys(projected).length > 0) sub.properties = projected;
804
+ }
805
+ }
806
+ return sub;
807
+ });
808
+ }
809
+ }
810
+ if (result.not && typeof result.not === 'object') {
811
+ result.not = convertSchema(result.not);
812
+ }
813
+ return result;
814
+ }
815
+
816
+ function toGeminiTools(tools) {
817
+ return {
818
+ functionDeclarations: tools.map((t) => ({
819
+ name: t.name,
820
+ description: t.description,
821
+ parameters: convertSchema(t.inputSchema),
822
+ })),
823
+ };
824
+ }
825
+
826
+ // Map the orchestrator-level toolChoice to Gemini's functionCallingConfig.
827
+ // auto -> AUTO
828
+ // required -> ANY
829
+ // none -> NONE
830
+ // { name } -> ANY + allowedFunctionNames:[name] (specific tool)
831
+ function toGeminiToolConfig(toolChoice) {
832
+ if (toolChoice == null) return undefined;
833
+ if (typeof toolChoice === 'string') {
834
+ if (toolChoice === 'auto') return { functionCallingConfig: { mode: 'AUTO' } };
835
+ if (toolChoice === 'required') return { functionCallingConfig: { mode: 'ANY' } };
836
+ if (toolChoice === 'none') return { functionCallingConfig: { mode: 'NONE' } };
837
+ return undefined;
838
+ }
839
+ if (typeof toolChoice === 'object') {
840
+ const name = toolChoice.name || toolChoice.function?.name;
841
+ if (typeof name === 'string' && name) {
842
+ return { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [name] } };
843
+ }
844
+ }
845
+ return undefined;
846
+ }
847
+
848
+ function toGeminiContent(message, toolNameByCallId) {
849
+ if (!message || message.role === 'system') return null;
850
+ if (message.role === 'assistant' && message.toolCalls?.length) {
851
+ const parts = [];
852
+ if (message.content) parts.push(...normalizeContentForGeminiParts(message.content));
853
+ for (const tc of message.toolCalls) {
854
+ // Gemini 3 thinking models require the original thoughtSignature
855
+ // echoed back on every prior functionCall so the cached thinking
856
+ // prefix stays valid. v1beta places the field at the Part level
857
+ // (sibling of functionCall) — putting it inside functionCall returns
858
+ // 400 "Unknown name". Older models / first turn have no signature.
859
+ const part = { functionCall: { name: tc.name, args: tc.arguments } };
860
+ if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;
861
+ parts.push(part);
862
+ }
863
+ return { role: 'model', parts };
864
+ }
865
+ if (message.role === 'tool') {
866
+ // Tool result content stays byte-identical for cache prefix stability.
867
+ // Gemini accepts functionResponse parts under role 'user' (per docs).
868
+ // Using 'user' keeps tool_result entries byte-identical between
869
+ // cachedContents.create (which rejects role:'function') and
870
+ // generateContent, so the cached prefix actually matches at runtime.
871
+ // functionResponse.name must be the FUNCTION name, not the synthetic
872
+ // toolCallId. Resolve it from the toolCallId->functionName map built
873
+ // from prior assistant tool_calls; fall back to the raw id only when
874
+ // no mapping exists.
875
+ const functionName = (toolNameByCallId && toolNameByCallId.get(message.toolCallId))
876
+ || message.toolCallId
877
+ || '';
878
+ const { response, mediaParts } = splitToolContentForGemini(message.content);
879
+ const parts = [{ functionResponse: { name: functionName, response } }];
880
+ if (mediaParts.length) parts.push(...mediaParts);
881
+ return {
882
+ role: 'user',
883
+ parts,
884
+ };
885
+ }
886
+ return {
887
+ role: message.role === 'assistant' ? 'model' : 'user',
888
+ parts: normalizeContentForGeminiParts(message.content),
889
+ };
890
+ }
891
+
892
+ function toGeminiContents(messages) {
893
+ const contents = [];
894
+ // Map synthetic toolCallId -> function name from prior assistant
895
+ // tool_calls so each functionResponse part carries the real function name.
896
+ const toolNameByCallId = new Map();
897
+ for (const m of messages) {
898
+ if (m?.role === 'assistant' && Array.isArray(m.toolCalls)) {
899
+ for (const tc of m.toolCalls) {
900
+ if (tc?.id && tc?.name) toolNameByCallId.set(tc.id, tc.name);
901
+ }
902
+ }
903
+ }
904
+ for (const message of messages) {
905
+ const content = toGeminiContent(message, toolNameByCallId);
906
+ if (content) contents.push(content);
907
+ }
908
+ return contents;
909
+ }
910
+
911
+ function parseToolCalls(parts) {
912
+ const calls = parts.filter((p) => 'functionCall' in p && !!p.functionCall);
913
+ if (!calls.length)
914
+ return undefined;
915
+ // The @google/generative-ai 0.24.1 SDK predates Gemini 3 thinking — its
916
+ // FunctionCall type only declares { name, args }. The runtime object,
917
+ // however, retains whatever the wire response carried, which means the
918
+ // signature may sit under any of:
919
+ // • part.functionCall.thoughtSignature (camelCase, expected)
920
+ // • part.functionCall.thought_signature (snake_case, raw protobuf)
921
+ // • part.thoughtSignature / part.thought_signature (sibling on Part)
922
+ // Read all four and use the first non-empty hit. Set MIXDOG_DEBUG_GEMINI=1
923
+ // to dump the raw parts so we can confirm the actual key location on the
924
+ // next session and harden the parser.
925
+ if (process.env.MIXDOG_DEBUG_GEMINI === '1') {
926
+ try { process.stderr.write(`[gemini fc raw] ${JSON.stringify(parts)}\n`); } catch {}
927
+ }
928
+ return calls.map((p, i) => {
929
+ const fc = p.functionCall;
930
+ const sig = fc.thoughtSignature
931
+ || fc.thought_signature
932
+ || p.thoughtSignature
933
+ || p.thought_signature
934
+ || null;
935
+ const idHash = traceHash(stableTraceStringify({
936
+ index: i,
937
+ name: fc.name || '',
938
+ args: fc.args ?? {},
939
+ })).slice(0, 16);
940
+ const call = {
941
+ id: `gemini_${idHash}`,
942
+ name: fc.name,
943
+ arguments: (fc.args ?? {}),
944
+ };
945
+ if (sig) call.thoughtSignature = sig;
946
+ return call;
947
+ });
948
+ }
949
+
950
+ function emitGeminiToolCalls(toolCalls, onToolCall) {
951
+ if (typeof onToolCall !== 'function' || !Array.isArray(toolCalls)) return;
952
+ const emitted = new Set();
953
+ for (const call of toolCalls) {
954
+ if (!call?.id || !call?.name || emitted.has(call.id)) continue;
955
+ emitted.add(call.id);
956
+ try { onToolCall(call); } catch {}
957
+ }
958
+ }
959
+
960
+ export class GeminiProvider {
961
+ // promptTokenCount is the total (cachedContentTokenCount is a subset), so
962
+ // input already includes cache. See registry.mjs.
963
+ static inputExcludesCache = false;
964
+ name = 'gemini';
965
+ genAI;
966
+ config;
967
+
968
+ constructor(config) {
969
+ this.config = config;
970
+ const apiKey = config.apiKey || process.env.GEMINI_API_KEY || '';
971
+ this.genAI = new GoogleGenerativeAI(apiKey);
972
+ // Warm a kept-alive socket to the Gemini REST API so the first cache/
973
+ // generateContent request skips the cold TLS handshake. Best-effort.
974
+ preconnect('https://generativelanguage.googleapis.com');
975
+ }
976
+
977
+ reloadApiKey() {
978
+ try {
979
+ const freshConfig = loadConfig();
980
+ const cfg = freshConfig.providers?.gemini;
981
+ const newKey = cfg?.apiKey || process.env.GEMINI_API_KEY;
982
+ if (newKey) {
983
+ this.genAI = new GoogleGenerativeAI(newKey);
984
+ }
985
+ } catch { /* best effort */ }
986
+ }
987
+
988
+ _getApiKey() {
989
+ return this.config?.apiKey || process.env.GEMINI_API_KEY || '';
990
+ }
991
+
992
+ // Explicit cachedContents API. The implicit cache layer on Gemini 3.x
993
+ // does not surface cachedContentTokenCount in usageMetadata, so the only
994
+ // way to obtain measurable + billable cache savings is to register the
995
+ // stable prefix (system + tools) as a CachedContent and pass its name on
996
+ // every generateContent call. TTL is 1h so a single worker session keeps
997
+ // one cache slot warm without re-creation overhead; storage cost (~$0.5/M
998
+ // tokens/hour) is dwarfed by the 75% input-price discount on hits beyond
999
+ // a few iterations.
1000
+ async _ensureGeminiCache({ apiKey, model, systemInstruction, geminiTools, contents, opts }) {
1001
+ const state = opts.providerState?.gemini || null;
1002
+ const now = Date.now();
1003
+ const currentIter = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : 1;
1004
+ const refreshEveryN = Number(process.env.MIXDOG_GEMINI_CACHE_REFRESH_EVERY) > 0
1005
+ ? Number(process.env.MIXDOG_GEMINI_CACHE_REFRESH_EVERY)
1006
+ : 4;
1007
+ const cacheLiveMs = state?.cacheExpiresAt ? state.cacheExpiresAt - now : 0;
1008
+ const itersSinceCreate = state?.cacheCreatedAtIter != null
1009
+ ? currentIter - state.cacheCreatedAtIter
1010
+ : Infinity;
1011
+ const statePrefixContentCount = Number.isFinite(Number(state?.cachePrefixContentCount))
1012
+ ? Math.max(0, Math.trunc(Number(state.cachePrefixContentCount)))
1013
+ : null;
1014
+ const currentStatePrefixHash = statePrefixContentCount != null
1015
+ ? _geminiCachePrefixHash({
1016
+ model,
1017
+ systemInstruction,
1018
+ geminiTools,
1019
+ contents,
1020
+ prefixCount: statePrefixContentCount,
1021
+ })
1022
+ : null;
1023
+ const modelMatches = !!state?.cacheName && state?.cacheModel === model;
1024
+ const prefixMatches = !!state?.cacheName
1025
+ && statePrefixContentCount != null
1026
+ && statePrefixContentCount <= (Array.isArray(contents) ? contents.length : 0)
1027
+ && !!state?.cachePrefixHash
1028
+ && state.cachePrefixHash === currentStatePrefixHash;
1029
+ const canAttachState = !!state?.cacheName && cacheLiveMs > 0 && modelMatches && prefixMatches;
1030
+ const canReuseState = canAttachState && cacheLiveMs > 6 * 60 * 1000 && itersSinceCreate < refreshEveryN;
1031
+ try {
1032
+ appendBridgeTrace({
1033
+ sessionId: opts.sessionId || opts.session?.id || null,
1034
+ iteration: currentIter,
1035
+ kind: 'gemini_cache_decision',
1036
+ payload: {
1037
+ hasState: !!state?.cacheName,
1038
+ stateCacheName: state?.cacheName || null,
1039
+ stateCreatedAtIter: state?.cacheCreatedAtIter ?? null,
1040
+ stateCacheModel: state?.cacheModel || null,
1041
+ statePrefixContentCount,
1042
+ statePrefixHash: state?.cachePrefixHash || null,
1043
+ currentStatePrefixHash,
1044
+ modelMatches,
1045
+ prefixMatches,
1046
+ canAttachState,
1047
+ cacheLiveMs,
1048
+ itersSinceCreate,
1049
+ refreshEveryN,
1050
+ decision: canReuseState ? 'reuse' : 'rebuild',
1051
+ contentsLen: Array.isArray(contents) ? contents.length : 0,
1052
+ },
1053
+ });
1054
+ } catch {}
1055
+ if (canReuseState) {
1056
+ return state.cacheName;
1057
+ }
1058
+ if (!apiKey) return null;
1059
+ // Pre-flight invariant: cachedContents.create rejects prefixes below
1060
+ // the model-specific minimum. Skip the POST entirely when the estimate
1061
+ // is under threshold so we don't spam 400 responses turn-after-turn.
1062
+ const minTokens = _geminiCacheMinTokens(model);
1063
+ const estimatedTokens = _estimateGeminiCacheTokens(systemInstruction, geminiTools, contents);
1064
+ if (estimatedTokens < minTokens) {
1065
+ try {
1066
+ appendBridgeTrace({
1067
+ sessionId: opts.sessionId || opts.session?.id || null,
1068
+ iteration: currentIter,
1069
+ kind: 'gemini_cache_skip',
1070
+ payload: {
1071
+ reason: 'prefix_below_min',
1072
+ estimatedTokens,
1073
+ minTokens,
1074
+ model,
1075
+ },
1076
+ });
1077
+ } catch {}
1078
+ return canAttachState ? state.cacheName : null;
1079
+ }
1080
+ const ttlSeconds = 3600;
1081
+ const cachePrefixContentCount = _geminiCachePrefixCount(contents);
1082
+ const cachePrefixHash = _geminiCachePrefixHash({
1083
+ model,
1084
+ systemInstruction,
1085
+ geminiTools,
1086
+ contents,
1087
+ prefixCount: cachePrefixContentCount,
1088
+ });
1089
+ const globalCacheKey = _geminiGlobalCacheKey({
1090
+ apiKey,
1091
+ model,
1092
+ cachePrefixHash,
1093
+ cachePrefixContentCount,
1094
+ });
1095
+ const globalCache = _getGeminiGlobalCache(globalCacheKey, now);
1096
+ if (globalCache) {
1097
+ try {
1098
+ appendBridgeTrace({
1099
+ sessionId: opts.sessionId || opts.session?.id || null,
1100
+ iteration: currentIter,
1101
+ kind: 'gemini_cache_global_hit',
1102
+ payload: {
1103
+ cacheName: globalCache.cacheName,
1104
+ cacheTokenSize: globalCache.cacheTokenSize,
1105
+ cachePrefixContentCount,
1106
+ cachePrefixHash,
1107
+ },
1108
+ });
1109
+ } catch {}
1110
+ _attachGeminiCacheState(opts, globalCache, currentIter);
1111
+ return globalCache.cacheName;
1112
+ }
1113
+ const inFlightCreate = geminiGlobalCacheCreates.get(globalCacheKey);
1114
+ if (inFlightCreate) {
1115
+ const created = await inFlightCreate.catch(() => null);
1116
+ if (created?.cacheName) {
1117
+ try {
1118
+ appendBridgeTrace({
1119
+ sessionId: opts.sessionId || opts.session?.id || null,
1120
+ iteration: currentIter,
1121
+ kind: 'gemini_cache_global_wait_hit',
1122
+ payload: {
1123
+ cacheName: created.cacheName,
1124
+ cacheTokenSize: created.cacheTokenSize,
1125
+ cachePrefixContentCount,
1126
+ cachePrefixHash,
1127
+ },
1128
+ });
1129
+ } catch {}
1130
+ _attachGeminiCacheState(opts, created, currentIter);
1131
+ return created.cacheName;
1132
+ }
1133
+ }
1134
+ const createTask = (async () => {
1135
+ try {
1136
+ const cachePrefixContents = _geminiCachePrefixContents(contents, cachePrefixContentCount);
1137
+ const body = {
1138
+ model: `models/${model}`,
1139
+ ttl: `${ttlSeconds}s`,
1140
+ };
1141
+ if (systemInstruction) {
1142
+ body.systemInstruction = { parts: [{ text: systemInstruction }] };
1143
+ }
1144
+ if (Array.isArray(geminiTools) && geminiTools.length) {
1145
+ body.tools = geminiTools;
1146
+ }
1147
+ // Capture conversation prefix (everything except the latest user/
1148
+ // tool input that the generateContent call will carry) inside the
1149
+ // cache. cachedContents only accepts role='user' or 'model';
1150
+ // generateContent uses role='function' for tool_result turns, so
1151
+ // collapse that to 'user' (functionResponse parts remain inside).
1152
+ if (cachePrefixContents.length) {
1153
+ body.contents = cachePrefixContents;
1154
+ }
1155
+ const url = `https://generativelanguage.googleapis.com/v1beta/cachedContents?key=${encodeURIComponent(apiKey)}`;
1156
+ // Honor the external session abort signal during cache creation, not
1157
+ // only the 20s ceiling. Without merging opts.signal a session that is
1158
+ // aborted (stall-watchdog / closeSession) mid-cache-create leaves this
1159
+ // preflight request running until its own timeout fires.
1160
+ const res = await fetch(url, {
1161
+ method: 'POST',
1162
+ headers: { 'Content-Type': 'application/json' },
1163
+ body: JSON.stringify(body),
1164
+ signal: opts.signal
1165
+ ? AbortSignal.any([opts.signal, AbortSignal.timeout(20_000)])
1166
+ : AbortSignal.timeout(20_000),
1167
+ dispatcher: getLlmDispatcher(),
1168
+ });
1169
+ if (!res.ok) {
1170
+ const text = await res.text().catch(() => '');
1171
+ try {
1172
+ appendBridgeTrace({
1173
+ sessionId: opts.sessionId || opts.session?.id || null,
1174
+ iteration: currentIter,
1175
+ kind: 'gemini_cache_create_fail',
1176
+ payload: {
1177
+ status: res.status,
1178
+ body: text.slice(0, 500),
1179
+ contentsLen: Array.isArray(contents) ? contents.length : 0,
1180
+ cachePrefixContentCount,
1181
+ canAttachState,
1182
+ },
1183
+ });
1184
+ } catch {}
1185
+ return null;
1186
+ }
1187
+ const data = await res.json();
1188
+ const cacheName = data?.name || null;
1189
+ if (!cacheName) return null;
1190
+ const cacheTokenSize = Number(data?.usageMetadata?.totalTokenCount || 0) || 0;
1191
+ try {
1192
+ appendBridgeTrace({
1193
+ sessionId: opts.sessionId || opts.session?.id || null,
1194
+ iteration: currentIter,
1195
+ kind: 'gemini_cache_create_ok',
1196
+ payload: {
1197
+ cacheName,
1198
+ cacheTokenSize,
1199
+ contentsLen: Array.isArray(contents) ? contents.length : 0,
1200
+ cachePrefixContentCount,
1201
+ cachePrefixHash,
1202
+ },
1203
+ });
1204
+ } catch {}
1205
+ // Best-effort cleanup of the previous cache so storage cost only
1206
+ // accrues on the live revision. Fire-and-forget; TTL expiry covers
1207
+ // any delete failures.
1208
+ const priorCacheName = state?.cacheName || null;
1209
+ if (priorCacheName && priorCacheName !== cacheName && !_geminiGlobalCacheNameIsLive(priorCacheName)) {
1210
+ const delUrl = `https://generativelanguage.googleapis.com/v1beta/${priorCacheName}?key=${encodeURIComponent(apiKey)}`;
1211
+ fetch(delUrl, { method: 'DELETE', signal: AbortSignal.timeout(10_000), dispatcher: getLlmDispatcher() })
1212
+ .catch(() => { /* TTL expiry will reclaim it */ });
1213
+ }
1214
+ const createdAt = Date.now();
1215
+ const entry = {
1216
+ cacheName,
1217
+ cacheCreatedAt: createdAt,
1218
+ cacheExpiresAt: createdAt + ttlSeconds * 1000,
1219
+ cacheModel: model,
1220
+ cacheTokenSize,
1221
+ cachePrefixContentCount,
1222
+ cachePrefixHash,
1223
+ };
1224
+ _setGeminiGlobalCache(globalCacheKey, entry);
1225
+ return entry;
1226
+ } catch (err) {
1227
+ process.stderr.write(`[gemini] cachedContents.create error: ${err?.message || err}\n`);
1228
+ return null;
1229
+ }
1230
+ })();
1231
+ geminiGlobalCacheCreates.set(globalCacheKey, createTask);
1232
+ try {
1233
+ const created = await createTask;
1234
+ if (!created?.cacheName) return canAttachState ? state.cacheName : null;
1235
+ _attachGeminiCacheState(opts, created, currentIter);
1236
+ return created.cacheName;
1237
+ } finally {
1238
+ if (geminiGlobalCacheCreates.get(globalCacheKey) === createTask) {
1239
+ geminiGlobalCacheCreates.delete(globalCacheKey);
1240
+ }
1241
+ }
1242
+ }
1243
+
1244
+ async send(messages, model, tools, sendOpts) {
1245
+ try {
1246
+ return await this._doSend(messages, model, tools, sendOpts);
1247
+ } catch (err) {
1248
+ if (err.message && (err.message.includes('401') || err.message.includes('403'))) {
1249
+ process.stderr.write(`[provider] Auth error, re-reading config...\n`);
1250
+ this.reloadApiKey();
1251
+ return await this._doSend(messages, model, tools, sendOpts);
1252
+ }
1253
+ throw err;
1254
+ }
1255
+ }
1256
+
1257
+ async _doSend(messages, model, tools, sendOpts) {
1258
+ const opts = sendOpts || {};
1259
+ const signal = opts.signal || null;
1260
+ const onStreamDelta = typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null;
1261
+ const onToolCall = typeof opts.onToolCall === 'function' ? opts.onToolCall : null;
1262
+ const onTextDelta = typeof opts.onTextDelta === 'function' ? opts.onTextDelta : null;
1263
+ if (signal?.aborted) {
1264
+ const reason = signal.reason;
1265
+ throw reason instanceof Error ? reason : new Error('Gemini request aborted by session close');
1266
+ }
1267
+
1268
+ const useModel = model || await ensureLatestGeminiModel(this);
1269
+ const systemInstruction = messages
1270
+ .filter(m => m.role === 'system')
1271
+ .map(m => m.content)
1272
+ .join('\n\n') || undefined;
1273
+ const chatMsgs = messages.filter(m => m.role !== 'system');
1274
+ const contents = toGeminiContents(chatMsgs);
1275
+ if (!contents.length)
1276
+ throw new Error('No messages to send');
1277
+
1278
+ const geminiTools = tools?.length ? [toGeminiTools(tools)] : undefined;
1279
+ const toolConfig = geminiTools ? toGeminiToolConfig(opts.toolChoice) : undefined;
1280
+ try { opts.onStageChange?.('requesting'); } catch {}
1281
+
1282
+ // Explicit cachedContents (system + tools + prior-turn transcript).
1283
+ // Per Google docs, `tools` must be supplied on BOTH the cache create
1284
+ // call AND every subsequent generate_content call — the cache stores
1285
+ // the schema for prompt-token credit but the runtime model still
1286
+ // needs the tool schema to actually emit function calls. Sending
1287
+ // cachedContent without tools yields an empty completion (function
1288
+ // calling silently disabled). The contents payload captures the
1289
+ // accumulated prefix; we refresh the cache every N iterations so
1290
+ // recent turns also enter the cached prefix instead of being billed
1291
+ // at full input rates.
1292
+ const cachedContent = await this._ensureGeminiCache({
1293
+ apiKey: this._getApiKey(),
1294
+ model: useModel,
1295
+ systemInstruction,
1296
+ geminiTools,
1297
+ contents,
1298
+ opts,
1299
+ });
1300
+ try { opts.onStageChange?.('requesting'); } catch {}
1301
+
1302
+ // When cachedContent is attached we bypass @google/generative-ai
1303
+ // (deprecated; v1beta v1.x docs explicitly forbid re-sending tools or
1304
+ // systemInstruction once a cache carries them, but the bundled SDK
1305
+ // can't actually issue a tool-less generateContent call). REST direct
1306
+ // sends the v1beta payload Google's new genai client uses, so the
1307
+ // cache owns system/tools and the runtime gets a clean cache hit.
1308
+ let response;
1309
+ if (cachedContent) {
1310
+ const apiKey = this._getApiKey();
1311
+ const genUrl = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(useModel)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(apiKey)}`;
1312
+ const cachedPrefixContentCount = Number.isFinite(Number(opts.providerState?.gemini?.cachePrefixContentCount))
1313
+ ? Math.max(0, Math.min(contents.length, Math.trunc(Number(opts.providerState.gemini.cachePrefixContentCount))))
1314
+ : 0;
1315
+ const deltaContents = contents.slice(cachedPrefixContentCount);
1316
+ // Cache carries the recorded prefix. Send every uncached tail turn,
1317
+ // not just the last message, so reused cachedContents preserve
1318
+ // full conversation context between periodic refreshes.
1319
+ const body = {
1320
+ contents: deltaContents.length ? deltaContents : contents.slice(-1),
1321
+ cachedContent,
1322
+ };
1323
+ if (toolConfig) body.toolConfig = toolConfig;
1324
+ // Option A (mirror anthropic-oauth): no absolute wall-clock cap on a
1325
+ // live streaming turn. A stream that keeps emitting SSE deltas must
1326
+ // not be killed by a fixed total-lifetime timer — that false-aborts
1327
+ // healthy high-reasoning turns still producing tokens. The streaming
1328
+ // phase is bounded instead by the per-attempt first-byte timeout
1329
+ // (truly wedged socket), the external signal (client disconnect /
1330
+ // replaced request), and the SSE idle watchdog. totalSignal is a pure
1331
+ // pass-through of the external signal with no timer.
1332
+ const restPassthrough = createPassthroughSignal(signal);
1333
+ const totalSignal = restPassthrough.signal;
1334
+ try {
1335
+ response = await withRetry(
1336
+ async ({ signal: attemptSignal }) => {
1337
+ try { opts.onStageChange?.('requesting'); } catch {}
1338
+ const openFirstByte = createTimeoutSignal(
1339
+ attemptSignal,
1340
+ GEMINI_FIRST_BYTE_TIMEOUT_MS,
1341
+ 'Gemini REST first byte',
1342
+ );
1343
+ let res;
1344
+ try {
1345
+ res = await fetch(genUrl, {
1346
+ method: 'POST',
1347
+ headers: { 'Content-Type': 'application/json' },
1348
+ body: JSON.stringify(body),
1349
+ signal: openFirstByte.signal,
1350
+ dispatcher: getLlmDispatcher(),
1351
+ });
1352
+ } finally {
1353
+ openFirstByte.cleanup();
1354
+ }
1355
+ if (!res.ok) {
1356
+ const text = await res.text().catch(() => '');
1357
+ const err = new Error(`Gemini REST streamGenerateContent ${res.status}: ${text.slice(0, 300)}`);
1358
+ err.status = res.status;
1359
+ throw err;
1360
+ }
1361
+ return await consumeGeminiRestStreamResponse(res, {
1362
+ signal: attemptSignal,
1363
+ onStreamDelta,
1364
+ onTextDelta,
1365
+ label: 'Gemini REST streamGenerateContent',
1366
+ });
1367
+ },
1368
+ {
1369
+ signal: totalSignal,
1370
+ onRetry: ({ attempt, lastErr }) => {
1371
+ try { opts.onStageChange?.('requesting'); } catch {}
1372
+ process.stderr.write(`[gemini-rest] retry attempt ${attempt + 1} after ${lastErr?.message || lastErr?.code || 'transient error'}\n`);
1373
+ },
1374
+ },
1375
+ );
1376
+ } finally {
1377
+ restPassthrough.cleanup();
1378
+ }
1379
+ } else {
1380
+ const genModel = this.genAI.getGenerativeModel({
1381
+ model: useModel,
1382
+ systemInstruction,
1383
+ tools: geminiTools,
1384
+ ...(toolConfig ? { toolConfig } : {}),
1385
+ });
1386
+ // Option A (mirror anthropic-oauth): pure pass-through of the external
1387
+ // signal, no absolute streaming total cap. See the REST branch above.
1388
+ const sdkPassthrough = createPassthroughSignal(signal);
1389
+ const totalSignal = sdkPassthrough.signal;
1390
+ try {
1391
+ response = await withRetry(
1392
+ async ({ signal: attemptSignal }) => {
1393
+ try { opts.onStageChange?.('requesting'); } catch {}
1394
+ // Mirror the REST branch's signal lifetime: the request
1395
+ // controller stays linked to the parent (attemptSignal)
1396
+ // for the FULL stream — connect AND body — so a parent /
1397
+ // client / gateway abort after first byte still cancels
1398
+ // the underlying SDK request (the SSE idle watchdog is off
1399
+ // by default). The first-byte timer only bounds the
1400
+ // connect phase and is cleared once the stream starts, so
1401
+ // it can never kill a live, still-producing stream.
1402
+ const reqController = new AbortController();
1403
+ let parentAbortListener = null;
1404
+ let firstByteTimer = null;
1405
+ const detachParent = () => {
1406
+ if (parentAbortListener && attemptSignal) {
1407
+ try { attemptSignal.removeEventListener('abort', parentAbortListener); } catch {}
1408
+ parentAbortListener = null;
1409
+ }
1410
+ };
1411
+ const clearConnectTimer = () => {
1412
+ if (firstByteTimer) { clearTimeout(firstByteTimer); firstByteTimer = null; }
1413
+ };
1414
+ if (attemptSignal) {
1415
+ if (attemptSignal.aborted) {
1416
+ try { reqController.abort(attemptSignal.reason); } catch {}
1417
+ } else {
1418
+ parentAbortListener = () => { try { reqController.abort(attemptSignal.reason); } catch {} };
1419
+ attemptSignal.addEventListener('abort', parentAbortListener, { once: true });
1420
+ }
1421
+ }
1422
+ firstByteTimer = setTimeout(() => {
1423
+ try { reqController.abort(geminiTimeoutError('Gemini SDK first byte', GEMINI_FIRST_BYTE_TIMEOUT_MS)); } catch {}
1424
+ }, GEMINI_FIRST_BYTE_TIMEOUT_MS);
1425
+ if (firstByteTimer.unref) firstByteTimer.unref();
1426
+ try {
1427
+ let streamResult;
1428
+ try {
1429
+ streamResult = await genModel.generateContentStream(
1430
+ { contents },
1431
+ { signal: reqController.signal },
1432
+ );
1433
+ } catch (err) {
1434
+ if (reqController.signal.aborted) {
1435
+ throw reqController.signal.reason instanceof Error
1436
+ ? reqController.signal.reason
1437
+ : err;
1438
+ }
1439
+ throw err;
1440
+ }
1441
+ // First byte / headers received: drop the connect-phase
1442
+ // timer but KEEP the parent link attached so a later
1443
+ // abort during streaming still reaches the request.
1444
+ clearConnectTimer();
1445
+ return await consumeGeminiSdkStream(streamResult, {
1446
+ signal: attemptSignal,
1447
+ onStreamDelta,
1448
+ onTextDelta,
1449
+ label: 'Gemini SDK streamGenerateContent',
1450
+ });
1451
+ } finally {
1452
+ clearConnectTimer();
1453
+ detachParent();
1454
+ }
1455
+ },
1456
+ {
1457
+ signal: totalSignal,
1458
+ onRetry: ({ attempt, lastErr }) => {
1459
+ try { opts.onStageChange?.('requesting'); } catch {}
1460
+ process.stderr.write(`[gemini] retry attempt ${attempt + 1} after ${lastErr?.message || lastErr?.code || 'transient error'}\n`);
1461
+ },
1462
+ },
1463
+ );
1464
+ } finally {
1465
+ sdkPassthrough.cleanup();
1466
+ }
1467
+ }
1468
+ writeGeminiCacheTrace({
1469
+ opts,
1470
+ model: useModel,
1471
+ systemInstruction,
1472
+ tools,
1473
+ contents,
1474
+ usageMetadata: response.usageMetadata,
1475
+ cachedContent,
1476
+ });
1477
+ const candidate = response.candidates?.[0] || null;
1478
+ const textParts = candidate?.content?.parts?.filter(p => 'text' in p) ?? [];
1479
+ const content = textParts.map(p => 'text' in p ? p.text : '').join('');
1480
+ const toolCalls = parseToolCalls(candidate?.content?.parts ?? []);
1481
+ emitGeminiToolCalls(toolCalls, onToolCall);
1482
+ // Inspect candidate.finishReason — Gemini reports terminal status here.
1483
+ // Only STOP (and the legacy "FINISH_REASON_STOP") plus tool/function-
1484
+ // call paths represent a fully delivered turn. MAX_TOKENS / SAFETY /
1485
+ // RECITATION / OTHER all mean the candidate was cut off before the
1486
+ // model finished, and surfacing the partial text as final would
1487
+ // silently accept a truncated answer. Convert those into a typed
1488
+ // provider-incomplete error so the loop can decide whether to retry,
1489
+ // nudge, or surface to the user. Missing finishReason (still
1490
+ // streaming / unknown) is left alone — existing success paths for
1491
+ // genuinely complete responses keep working.
1492
+ const finishReason = candidate?.finishReason || null;
1493
+ const incompleteFinishReasons = new Set([
1494
+ 'MAX_TOKENS',
1495
+ 'SAFETY',
1496
+ 'RECITATION',
1497
+ 'OTHER',
1498
+ 'BLOCKLIST',
1499
+ 'PROHIBITED_CONTENT',
1500
+ 'SPII',
1501
+ 'MALFORMED_FUNCTION_CALL',
1502
+ ]);
1503
+ if (finishReason && incompleteFinishReasons.has(finishReason)) {
1504
+ const err = Object.assign(
1505
+ new Error(`Gemini response incomplete: finishReason=${finishReason}`),
1506
+ {
1507
+ name: 'ProviderIncompleteError',
1508
+ code: 'PROVIDER_INCOMPLETE',
1509
+ providerIncomplete: true,
1510
+ finishReason,
1511
+ partialContent: content,
1512
+ partialToolCalls: toolCalls,
1513
+ model: useModel,
1514
+ rawUsage: response.usageMetadata || null,
1515
+ },
1516
+ );
1517
+ throw err;
1518
+ }
1519
+ const um = response.usageMetadata || null;
1520
+ // Hoist cachedTokens so the returned usage block can reuse the
1521
+ // exact value the trace already recorded (including the
1522
+ // cachedFallback when cachedContentTokenCount under-reports).
1523
+ let cachedTokens = 0;
1524
+ if (um) {
1525
+ const {
1526
+ inputTokens,
1527
+ reportedCachedTokens,
1528
+ cachedFallbackTokens,
1529
+ cachedTokens: resolvedCachedTokens,
1530
+ cacheTokenSource,
1531
+ } = _resolveGeminiCacheUsage({
1532
+ usageMetadata: um,
1533
+ cachedContent,
1534
+ providerState: opts.providerState,
1535
+ });
1536
+ cachedTokens = resolvedCachedTokens;
1537
+ const outputTokens = (um.candidatesTokenCount || 0) + (um.thoughtsTokenCount || 0);
1538
+ if (cachedContent && inputTokens > 0 && cachedTokens <= 0) {
1539
+ try {
1540
+ appendBridgeTrace({
1541
+ sessionId: opts.sessionId || opts.session?.id || null,
1542
+ iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1543
+ kind: 'gemini_cache_anomaly',
1544
+ payload: {
1545
+ reason: 'cached_content_attached_but_zero_cached_tokens',
1546
+ inputTokens,
1547
+ reportedCachedTokens,
1548
+ cachedFallbackTokens,
1549
+ cacheTokenSource,
1550
+ cacheName: opts.providerState?.gemini?.cacheName || null,
1551
+ cachePrefixContentCount: opts.providerState?.gemini?.cachePrefixContentCount ?? null,
1552
+ },
1553
+ });
1554
+ } catch {}
1555
+ }
1556
+ traceBridgeUsage({
1557
+ sessionId: opts.sessionId || opts.session?.id || null,
1558
+ iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1559
+ inputTokens,
1560
+ outputTokens,
1561
+ cachedTokens,
1562
+ cacheWriteTokens: 0,
1563
+ promptTokens: inputTokens,
1564
+ model: useModel,
1565
+ modelDisplay: useModel,
1566
+ rawUsage: um,
1567
+ provider: 'gemini',
1568
+ });
1569
+ }
1570
+ return {
1571
+ content,
1572
+ model: useModel,
1573
+ toolCalls,
1574
+ providerState: opts.providerState,
1575
+ usage: um ? (() => {
1576
+ const input = um.promptTokenCount || um.totalTokenCount || 0;
1577
+ return {
1578
+ inputTokens: input,
1579
+ outputTokens: (um.candidatesTokenCount || 0) + (um.thoughtsTokenCount || 0),
1580
+ // Use the already-computed cachedTokens (with
1581
+ // cache-create fallback applied) rather than the raw
1582
+ // metadata field, so the returned usage matches what
1583
+ // traceBridgeUsage recorded for this same call.
1584
+ cachedTokens,
1585
+ // Gemini promptTokenCount is total (cachedContentTokenCount
1586
+ // is a subset). Alias directly into promptTokens.
1587
+ promptTokens: input,
1588
+ };
1589
+ })() : undefined,
1590
+ };
1591
+ }
1592
+
1593
+ async listModels() {
1594
+ const cached = _modelCache.loadSync();
1595
+ if (cached) return cached;
1596
+ // Dynamic lookup via Gemini v1beta /models. Requires API key.
1597
+ const apiKey = this.config.apiKey || process.env.GEMINI_API_KEY;
1598
+ if (!apiKey) return MODELS; // no key — return minimal static list
1599
+ try {
1600
+ return await this._fetchAndCacheModels(apiKey);
1601
+ } catch (err) {
1602
+ process.stderr.write(`[gemini] listModels fetch failed (${err.message})\n`);
1603
+ return MODELS;
1604
+ }
1605
+ }
1606
+
1607
+ // Shared fetch+normalize+enrich+write used by both listModels() (after the
1608
+ // TTL check) and _refreshModelCache() (bypassing it). Throws on failure so
1609
+ // each caller applies its own fallback/logging.
1610
+ async _fetchAndCacheModels(apiKey) {
1611
+ const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`;
1612
+ const listSignal = AbortSignal.timeout(60_000);
1613
+ const res = await fetch(url, { signal: listSignal, dispatcher: getLlmDispatcher() });
1614
+ if (!res.ok) throw new Error(`gemini list_models ${res.status}`);
1615
+ const data = await res.json();
1616
+ const items = Array.isArray(data?.models) ? data.models : [];
1617
+ // Filter to Gemini family; skip embedding/imagen endpoints.
1618
+ const normalized = items
1619
+ .filter(m => (m?.name || '').includes('gemini'))
1620
+ .filter(m => !/embedding|aqa|imagen/.test(m?.name || ''))
1621
+ .map(m => {
1622
+ const id = (m.name || '').replace(/^models\//, '');
1623
+ const family = /flash-lite/.test(id) ? 'gemini-flash-lite'
1624
+ : /flash/.test(id) ? 'gemini-flash'
1625
+ : /pro/.test(id) ? 'gemini-pro'
1626
+ : 'gemini';
1627
+ return {
1628
+ id,
1629
+ display: m.displayName || id,
1630
+ family,
1631
+ provider: 'gemini',
1632
+ contextWindow: m.inputTokenLimit || 1000000,
1633
+ outputTokens: m.outputTokenLimit || 8192,
1634
+ tier: 'version',
1635
+ latest: false,
1636
+ description: m.description || '',
1637
+ };
1638
+ });
1639
+ _markLatestGemini(normalized);
1640
+ // LiteLLM catalog overlays pricing and updated metadata.
1641
+ const { enrichModels } = await import('./model-catalog.mjs');
1642
+ const enriched = await enrichModels(normalized);
1643
+ _modelCache.save(enriched);
1644
+ return enriched;
1645
+ }
1646
+
1647
+ // Force a catalog refresh (ignores the 24h disk TTL). De-duped via
1648
+ // _modelRefreshInFlight so concurrent callers share one HTTP round-trip.
1649
+ // Fire-and-forget context: failures are caught/logged, returning null.
1650
+ async _refreshModelCache() {
1651
+ if (_modelRefreshInFlight) return _modelRefreshInFlight;
1652
+ _modelRefreshInFlight = (async () => {
1653
+ try {
1654
+ const apiKey = this.config.apiKey || process.env.GEMINI_API_KEY;
1655
+ if (!apiKey) return null; // no key — nothing to refresh
1656
+ const enriched = await this._fetchAndCacheModels(apiKey);
1657
+ if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(`[gemini] catalog refreshed (${enriched.length} models)\n`);
1658
+ return enriched;
1659
+ } catch (err) {
1660
+ if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(`[gemini] catalog refresh failed (${err.message})\n`);
1661
+ return null;
1662
+ } finally {
1663
+ _modelRefreshInFlight = null;
1664
+ }
1665
+ })();
1666
+ return _modelRefreshInFlight;
1667
+ }
1668
+
1669
+ async isAvailable() {
1670
+ try {
1671
+ const model = this.genAI.getGenerativeModel({ model: DEFAULT_MODEL });
1672
+ await model.generateContent('hi');
1673
+ return true;
1674
+ }
1675
+ catch {
1676
+ return false;
1677
+ }
1678
+ }
1679
+ }