mixdog 0.8.0 → 0.9.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 (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -56,7 +56,7 @@ function cacheKey(provider) {
56
56
  function freshSnapshot(snapshot, ttlMs) {
57
57
  const cachedAt = num(snapshot?.cachedAt, 0);
58
58
  if (!cachedAt || Date.now() - cachedAt > ttlMs) return null;
59
- if (snapshot?.balance || Array.isArray(snapshot?.quotaWindows)) return snapshot;
59
+ if (snapshot?.balance || Array.isArray(snapshot?.quotaWindows) || snapshot?.tokenUsage || snapshot?.unavailable) return snapshot;
60
60
  return null;
61
61
  }
62
62
 
@@ -175,6 +175,39 @@ function parseOpenAICosts(data) {
175
175
  };
176
176
  }
177
177
 
178
+ function parseOpenAICompletionUsage(data) {
179
+ const buckets = Array.isArray(data?.data) ? data.data : [];
180
+ let inputTokens = 0;
181
+ let outputTokens = 0;
182
+ let cachedInputTokens = 0;
183
+ let requests = 0;
184
+ let seen = false;
185
+ for (const bucket of buckets) {
186
+ const results = Array.isArray(bucket?.results) ? bucket.results : [];
187
+ for (const result of results) {
188
+ inputTokens += num(result?.input_tokens, 0);
189
+ outputTokens += num(result?.output_tokens, 0);
190
+ cachedInputTokens += num(result?.input_cached_tokens, 0);
191
+ requests += num(result?.num_model_requests, 0);
192
+ seen = true;
193
+ }
194
+ }
195
+ if (!seen && Array.isArray(data?.data)) seen = true;
196
+ if (!seen) return null;
197
+ return {
198
+ provider: 'openai',
199
+ source: 'openai-organization-usage-completions',
200
+ tokenUsage: {
201
+ inputTokens,
202
+ outputTokens,
203
+ cachedInputTokens,
204
+ requests,
205
+ source: 'openai-organization-usage-completions',
206
+ },
207
+ cachedAt: Date.now(),
208
+ };
209
+ }
210
+
178
211
  function parseOpenAICreditGrants(data) {
179
212
  const remainingUsd = num(data?.total_available ?? data?.totalAvailable ?? data?.available, null);
180
213
  if (remainingUsd === null) return null;
@@ -205,6 +238,13 @@ function parseOpenAICreditGrants(data) {
205
238
  };
206
239
  }
207
240
 
241
+ function ymd(value) {
242
+ const d = new Date(value);
243
+ if (!Number.isFinite(d.getTime())) return '';
244
+ const pad = (n) => String(n).padStart(2, '0');
245
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
246
+ }
247
+
208
248
  function uniqueValues(values) {
209
249
  const seen = new Set();
210
250
  const out = [];
@@ -237,6 +277,49 @@ async function fetchOpenAICreditGrantSnapshot(apiKey) {
237
277
  return null;
238
278
  }
239
279
 
280
+ async function fetchOpenAIDashboardUsageSnapshot(sessionKey) {
281
+ if (!sessionKey) return null;
282
+ const headers = authHeaders(sessionKey, { Accept: 'application/json' });
283
+ let subscription = null;
284
+ let usage = null;
285
+ try {
286
+ subscription = await fetchJson('https://api.openai.com/dashboard/billing/subscription', { headers });
287
+ } catch {
288
+ subscription = null;
289
+ }
290
+ try {
291
+ const endDate = ymd(Date.now());
292
+ const startDate = ymd(Date.now() - 31 * 24 * 60 * 60_000);
293
+ usage = await fetchJson(`https://api.openai.com/dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, { headers });
294
+ } catch {
295
+ usage = null;
296
+ }
297
+ const totalUsageCents = num(usage?.total_usage ?? usage?.totalUsage, null);
298
+ const usedUsd = totalUsageCents === null ? null : totalUsageCents / 100;
299
+ const limitUsd = num(
300
+ subscription?.hard_limit_usd
301
+ ?? subscription?.hardLimitUsd
302
+ ?? subscription?.system_hard_limit_usd
303
+ ?? subscription?.systemHardLimitUsd
304
+ ?? subscription?.soft_limit_usd
305
+ ?? subscription?.softLimitUsd,
306
+ null,
307
+ );
308
+ if (usedUsd === null && limitUsd === null) return null;
309
+ const balance = {
310
+ source: 'openai-dashboard-billing',
311
+ };
312
+ if (usedUsd !== null) balance.usedUsd = round(usedUsd, 4);
313
+ if (limitUsd !== null) balance.limitUsd = round(limitUsd, 4);
314
+ if (usedUsd !== null && limitUsd !== null) balance.remainingUsd = round(Math.max(0, limitUsd - usedUsd), 4);
315
+ return {
316
+ provider: 'openai',
317
+ source: 'openai-dashboard-billing',
318
+ balance,
319
+ cachedAt: Date.now(),
320
+ };
321
+ }
322
+
240
323
  async function fetchOpenAICostSnapshot(apiKey) {
241
324
  if (!apiKey) return null;
242
325
  const start = Math.floor((Date.now() - 31 * 24 * 60 * 60_000) / 1000);
@@ -249,11 +332,115 @@ async function fetchOpenAICostSnapshot(apiKey) {
249
332
  return parseOpenAICosts(data);
250
333
  }
251
334
 
335
+ async function fetchOpenAICompletionUsageSnapshot(apiKey) {
336
+ if (!apiKey) return null;
337
+ const start = Math.floor((Date.now() - 31 * 24 * 60 * 60_000) / 1000);
338
+ const url = new URL('https://api.openai.com/v1/organization/usage/completions');
339
+ url.searchParams.set('start_time', String(start));
340
+ url.searchParams.set('bucket_width', '1d');
341
+ url.searchParams.set('limit', '31');
342
+ const data = await fetchJson(url, {
343
+ headers: authHeaders(apiKey, { Accept: 'application/json' }),
344
+ });
345
+ return parseOpenAICompletionUsage(data);
346
+ }
347
+
252
348
  async function fetchOpenAIUsageSnapshot() {
253
349
  const key = getAgentApiKey('openai');
254
350
  const credit = await fetchOpenAICreditGrantSnapshot(key);
255
351
  if (credit) return credit;
256
- return await fetchOpenAICostSnapshot(key);
352
+ const dashboard = await fetchOpenAIDashboardUsageSnapshot(getOpenAIUsageSessionKey());
353
+ if (dashboard) return dashboard;
354
+ try {
355
+ const costs = await fetchOpenAICostSnapshot(key);
356
+ if (costs) return costs;
357
+ } catch (err) {
358
+ const status = num(err?.status, null);
359
+ if (status !== 401 && status !== 403 && status !== 404) throw err;
360
+ }
361
+ return await fetchOpenAICompletionUsageSnapshot(key);
362
+ }
363
+
364
+ function anthropicAdminKeys() {
365
+ return uniqueValues([
366
+ process.env.ANTHROPIC_ADMIN_API_KEY,
367
+ process.env.MIXDOG_ANTHROPIC_ADMIN_API_KEY,
368
+ process.env.ANTHROPIC_ADMIN_KEY,
369
+ getAgentApiKey('anthropic'),
370
+ ]);
371
+ }
372
+
373
+ function parseAnthropicCostReport(data) {
374
+ const buckets = Array.isArray(data?.data) ? data.data : [];
375
+ let usedUsd = 0;
376
+ let seen = false;
377
+ for (const bucket of buckets) {
378
+ const results = Array.isArray(bucket?.results) ? bucket.results : [];
379
+ for (const item of results) {
380
+ const currency = String(item?.currency || 'USD').toUpperCase();
381
+ if (currency && currency !== 'USD') continue;
382
+ const cents = num(item?.amount, null);
383
+ if (cents === null) continue;
384
+ usedUsd += cents / 100;
385
+ seen = true;
386
+ }
387
+ }
388
+ if (!seen && Array.isArray(data?.data)) seen = true;
389
+ if (!seen) return null;
390
+ return {
391
+ provider: 'anthropic',
392
+ source: 'anthropic-cost-report',
393
+ balance: {
394
+ usedUsd: round(usedUsd, 4),
395
+ source: 'anthropic-cost-report',
396
+ },
397
+ cachedAt: Date.now(),
398
+ };
399
+ }
400
+
401
+ async function fetchAnthropicCostSnapshot() {
402
+ const keys = anthropicAdminKeys();
403
+ if (!keys.length) return unavailableSnapshot('anthropic', { message: 'Set ANTHROPIC_ADMIN_API_KEY for usage/cost report' });
404
+ const start = new Date(Date.now() - 31 * 24 * 60 * 60_000).toISOString();
405
+ const url = new URL('https://api.anthropic.com/v1/organizations/cost_report');
406
+ url.searchParams.set('starting_at', start);
407
+ url.searchParams.set('bucket_width', '1d');
408
+ url.searchParams.set('limit', '31');
409
+ let firstAuthError = null;
410
+ for (const key of keys) {
411
+ for (const authMode of ['bearer', 'x-api-key']) {
412
+ try {
413
+ const headers = {
414
+ Accept: 'application/json',
415
+ 'anthropic-version': '2023-06-01',
416
+ ...(authMode === 'bearer' ? { Authorization: `Bearer ${key}` } : { 'x-api-key': key }),
417
+ };
418
+ const data = await fetchJson(url, { headers });
419
+ return parseAnthropicCostReport(data) || unavailableSnapshot('anthropic', { message: 'Anthropic cost report returned no usage data' });
420
+ } catch (err) {
421
+ const status = num(err?.status, null);
422
+ if (status === 401 || status === 403 || status === 404) {
423
+ firstAuthError ||= err;
424
+ continue;
425
+ }
426
+ throw err;
427
+ }
428
+ }
429
+ }
430
+ return unavailableSnapshot('anthropic', firstAuthError || { message: 'Anthropic Admin API usage/cost auth failed' });
431
+ }
432
+
433
+ async function fetchGeminiUsageSnapshot() {
434
+ const key = getAgentApiKey('gemini');
435
+ if (!key) return null;
436
+ try {
437
+ await fetchJson(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}`, {
438
+ headers: { Accept: 'application/json' },
439
+ });
440
+ return unavailableSnapshot('gemini', { message: 'Gemini API key valid; balance/usage is AI Studio or Cloud Billing only' });
441
+ } catch (err) {
442
+ return unavailableSnapshot('gemini', err);
443
+ }
257
444
  }
258
445
 
259
446
  function managementKey(provider) {
@@ -285,6 +472,28 @@ function moneyNumber(value) {
285
472
  return null;
286
473
  }
287
474
 
475
+ function errorMessage(err) {
476
+ const body = err?.body;
477
+ const value = body?.error?.message ?? body?.error ?? body?.message ?? err?.message;
478
+ if (typeof value === 'string') return value.trim();
479
+ if (value && typeof value === 'object') {
480
+ try { return JSON.stringify(value).slice(0, 300); } catch {}
481
+ }
482
+ return '';
483
+ }
484
+
485
+ function unavailableSnapshot(provider, err) {
486
+ return {
487
+ provider,
488
+ source: 'provider-api-unavailable',
489
+ unavailable: {
490
+ status: num(err?.status, null),
491
+ message: errorMessage(err),
492
+ },
493
+ cachedAt: Date.now(),
494
+ };
495
+ }
496
+
288
497
  function parseXaiPrepaidBalance(data) {
289
498
  const remainingUsd = moneyNumber(
290
499
  data?.total
@@ -311,13 +520,34 @@ function parseXaiPrepaidBalance(data) {
311
520
  async function fetchXaiUsageSnapshot() {
312
521
  const key = managementKey('xai');
313
522
  const team = teamId('xai');
314
- if (!key || !team) return null;
523
+ if (!key || !team) return await fetchXaiPublicUsageSnapshot() || unavailableSnapshot('xai', { message: 'Set XAI_MANAGEMENT_API_KEY and XAI_TEAM_ID for prepaid balance' });
315
524
  const data = await fetchJson(`https://management-api.x.ai/v1/billing/teams/${encodeURIComponent(team)}/prepaid/balance`, {
316
525
  headers: authHeaders(key, { Accept: 'application/json' }),
317
526
  });
318
527
  return parseXaiPrepaidBalance(data);
319
528
  }
320
529
 
530
+ async function fetchXaiPublicUsageSnapshot() {
531
+ const key = getAgentApiKey('xai');
532
+ if (!key) return null;
533
+ const urls = [
534
+ 'https://api.x.ai/v1/usage',
535
+ 'https://api.x.ai/v1/rate_limits',
536
+ ];
537
+ let firstError = null;
538
+ for (const url of urls) {
539
+ try {
540
+ const data = await fetchJson(url, { headers: authHeaders(key, { Accept: 'application/json' }) });
541
+ const parsed = parseXaiPrepaidBalance(data);
542
+ if (parsed) return { ...parsed, source: 'xai-public-usage-probe', balance: { ...parsed.balance, source: 'xai-public-usage-probe' } };
543
+ return unavailableSnapshot('xai', { message: `xAI public usage probe returned unsupported data from ${url}` });
544
+ } catch (err) {
545
+ firstError ||= err;
546
+ }
547
+ }
548
+ return unavailableSnapshot('xai', firstError || { message: 'xAI public usage probes failed' });
549
+ }
550
+
321
551
  export async function fetchApiUsageSnapshot(provider, { force = false } = {}) {
322
552
  const id = cacheKey(provider);
323
553
  if (!force) {
@@ -329,10 +559,12 @@ export async function fetchApiUsageSnapshot(provider, { force = false } = {}) {
329
559
  try {
330
560
  if (id === 'deepseek') snapshot = await fetchDeepSeekUsageSnapshot();
331
561
  else if (id === 'openai') snapshot = await fetchOpenAIUsageSnapshot();
562
+ else if (id === 'anthropic') snapshot = await fetchAnthropicCostSnapshot();
563
+ else if (id === 'gemini') snapshot = await fetchGeminiUsageSnapshot();
332
564
  else if (id === 'xai') snapshot = await fetchXaiUsageSnapshot();
333
565
  } catch (err) {
334
566
  const status = num(err?.status, null);
335
- if (status === 401 || status === 403 || status === 404) return null;
567
+ if (status === 401 || status === 403 || status === 404) return unavailableSnapshot(id, err);
336
568
  throw err;
337
569
  }
338
570
 
@@ -0,0 +1,49 @@
1
+ export function isResponsesFreeformTool(tool) {
2
+ return !!(tool?.freeform
3
+ && tool.freeform.type === 'grammar'
4
+ && typeof tool.freeform.syntax === 'string'
5
+ && typeof tool.freeform.definition === 'string');
6
+ }
7
+
8
+ export function toResponsesCustomTool(tool) {
9
+ return {
10
+ type: 'custom',
11
+ name: tool.name,
12
+ description: tool.freeformDescription || tool.description,
13
+ format: tool.freeform,
14
+ };
15
+ }
16
+
17
+ export function customToolInputFromArguments(name, args) {
18
+ if (typeof args === 'string') return args;
19
+ if (name === 'apply_patch' && typeof args?.patch === 'string') return args.patch;
20
+ if (typeof args?.input === 'string') return args.input;
21
+ try { return JSON.stringify(args ?? {}); } catch { return String(args ?? ''); }
22
+ }
23
+
24
+ export function customToolArgumentsFromInput(name, input) {
25
+ const text = typeof input === 'string'
26
+ ? input
27
+ : (() => {
28
+ try { return JSON.stringify(input ?? ''); } catch { return String(input ?? ''); }
29
+ })();
30
+ if (name === 'apply_patch') return { patch: text };
31
+ return { input: text };
32
+ }
33
+
34
+ export function customToolCallFromResponseItem(item) {
35
+ if (!item || item.type !== 'custom_tool_call') return null;
36
+ const id = item.call_id || item.id || '';
37
+ const name = item.name || '';
38
+ if (!id || !name) return null;
39
+ return {
40
+ id,
41
+ name,
42
+ arguments: customToolArgumentsFromInput(name, item.input ?? ''),
43
+ nativeType: 'custom_tool_call',
44
+ };
45
+ }
46
+
47
+ export function isCustomToolCallRecord(call) {
48
+ return call?.nativeType === 'custom_tool_call';
49
+ }
@@ -2,7 +2,7 @@ import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai';
2
2
  import { loadConfig } from '../config.mjs';
3
3
  import { makeModelCache } from './model-cache.mjs';
4
4
  import { withRetry } from './retry-classifier.mjs';
5
- import { traceBridgeUsage, appendBridgeTrace } from '../bridge-trace.mjs';
5
+ import { traceAgentUsage, appendAgentTrace } from '../agent-trace.mjs';
6
6
  import {
7
7
  PROVIDER_FIRST_BYTE_TIMEOUT_MS,
8
8
  PROVIDER_MAX_BEFORE_WARN_MS,
@@ -822,6 +822,41 @@ function toGeminiTools(tools) {
822
822
  })),
823
823
  };
824
824
  }
825
+ function toGeminiNativeTools(nativeTools) {
826
+ if (!Array.isArray(nativeTools)) return [];
827
+ const out = [];
828
+ for (const tool of nativeTools) {
829
+ const type = String(tool?.type || '').trim().toLowerCase();
830
+ if (type === 'google_search' || type === 'google_search_retrieval') {
831
+ out.push({ googleSearch: {} });
832
+ }
833
+ }
834
+ return out;
835
+ }
836
+
837
+ function collectGeminiGroundingSources(candidate) {
838
+ const out = [];
839
+ const seen = new Set();
840
+ const add = (source) => {
841
+ if (!source || typeof source !== 'object') return;
842
+ const web = source.web && typeof source.web === 'object' ? source.web : source;
843
+ const url = String(web.uri || web.url || source.uri || source.url || '').trim();
844
+ if (!url || seen.has(url)) return;
845
+ seen.add(url);
846
+ out.push({
847
+ title: String(web.title || source.title || url).trim(),
848
+ url,
849
+ snippet: '',
850
+ source: 'gemini-grounding',
851
+ provider: 'gemini',
852
+ });
853
+ };
854
+ const grounding = candidate?.groundingMetadata || {};
855
+ for (const chunk of Array.isArray(grounding.groundingChunks) ? grounding.groundingChunks : []) add(chunk);
856
+ const citationMetadata = candidate?.citationMetadata || {};
857
+ for (const source of Array.isArray(citationMetadata.citationSources) ? citationMetadata.citationSources : []) add(source);
858
+ return out;
859
+ }
825
860
 
826
861
  // Map the orchestrator-level toolChoice to Gemini's functionCallingConfig.
827
862
  // auto -> AUTO
@@ -908,7 +943,7 @@ function toGeminiContents(messages) {
908
943
  return contents;
909
944
  }
910
945
 
911
- function parseToolCalls(parts) {
946
+ export function parseToolCalls(parts) {
912
947
  const calls = parts.filter((p) => 'functionCall' in p && !!p.functionCall);
913
948
  if (!calls.length)
914
949
  return undefined;
@@ -998,6 +1033,7 @@ export class GeminiProvider {
998
1033
  // tokens/hour) is dwarfed by the 75% input-price discount on hits beyond
999
1034
  // a few iterations.
1000
1035
  async _ensureGeminiCache({ apiKey, model, systemInstruction, geminiTools, contents, opts }) {
1036
+ if (Array.isArray(opts?.nativeTools) && opts.nativeTools.length) return null;
1001
1037
  const state = opts.providerState?.gemini || null;
1002
1038
  const now = Date.now();
1003
1039
  const currentIter = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : 1;
@@ -1029,7 +1065,7 @@ export class GeminiProvider {
1029
1065
  const canAttachState = !!state?.cacheName && cacheLiveMs > 0 && modelMatches && prefixMatches;
1030
1066
  const canReuseState = canAttachState && cacheLiveMs > 6 * 60 * 1000 && itersSinceCreate < refreshEveryN;
1031
1067
  try {
1032
- appendBridgeTrace({
1068
+ appendAgentTrace({
1033
1069
  sessionId: opts.sessionId || opts.session?.id || null,
1034
1070
  iteration: currentIter,
1035
1071
  kind: 'gemini_cache_decision',
@@ -1063,7 +1099,7 @@ export class GeminiProvider {
1063
1099
  const estimatedTokens = _estimateGeminiCacheTokens(systemInstruction, geminiTools, contents);
1064
1100
  if (estimatedTokens < minTokens) {
1065
1101
  try {
1066
- appendBridgeTrace({
1102
+ appendAgentTrace({
1067
1103
  sessionId: opts.sessionId || opts.session?.id || null,
1068
1104
  iteration: currentIter,
1069
1105
  kind: 'gemini_cache_skip',
@@ -1095,7 +1131,7 @@ export class GeminiProvider {
1095
1131
  const globalCache = _getGeminiGlobalCache(globalCacheKey, now);
1096
1132
  if (globalCache) {
1097
1133
  try {
1098
- appendBridgeTrace({
1134
+ appendAgentTrace({
1099
1135
  sessionId: opts.sessionId || opts.session?.id || null,
1100
1136
  iteration: currentIter,
1101
1137
  kind: 'gemini_cache_global_hit',
@@ -1115,7 +1151,7 @@ export class GeminiProvider {
1115
1151
  const created = await inFlightCreate.catch(() => null);
1116
1152
  if (created?.cacheName) {
1117
1153
  try {
1118
- appendBridgeTrace({
1154
+ appendAgentTrace({
1119
1155
  sessionId: opts.sessionId || opts.session?.id || null,
1120
1156
  iteration: currentIter,
1121
1157
  kind: 'gemini_cache_global_wait_hit',
@@ -1169,7 +1205,7 @@ export class GeminiProvider {
1169
1205
  if (!res.ok) {
1170
1206
  const text = await res.text().catch(() => '');
1171
1207
  try {
1172
- appendBridgeTrace({
1208
+ appendAgentTrace({
1173
1209
  sessionId: opts.sessionId || opts.session?.id || null,
1174
1210
  iteration: currentIter,
1175
1211
  kind: 'gemini_cache_create_fail',
@@ -1189,7 +1225,7 @@ export class GeminiProvider {
1189
1225
  if (!cacheName) return null;
1190
1226
  const cacheTokenSize = Number(data?.usageMetadata?.totalTokenCount || 0) || 0;
1191
1227
  try {
1192
- appendBridgeTrace({
1228
+ appendAgentTrace({
1193
1229
  sessionId: opts.sessionId || opts.session?.id || null,
1194
1230
  iteration: currentIter,
1195
1231
  kind: 'gemini_cache_create_ok',
@@ -1242,6 +1278,9 @@ export class GeminiProvider {
1242
1278
  }
1243
1279
 
1244
1280
  async send(messages, model, tools, sendOpts) {
1281
+ // Re-warm a kept-alive socket before the turn (TTL-gated no-op while
1282
+ // hot) so a post-idle request skips the cold TLS handshake.
1283
+ preconnect('https://generativelanguage.googleapis.com');
1245
1284
  try {
1246
1285
  return await this._doSend(messages, model, tools, sendOpts);
1247
1286
  } catch (err) {
@@ -1275,8 +1314,12 @@ export class GeminiProvider {
1275
1314
  if (!contents.length)
1276
1315
  throw new Error('No messages to send');
1277
1316
 
1278
- const geminiTools = tools?.length ? [toGeminiTools(tools)] : undefined;
1279
- const toolConfig = geminiTools ? toGeminiToolConfig(opts.toolChoice) : undefined;
1317
+ const nativeGeminiTools = toGeminiNativeTools(opts.nativeTools);
1318
+ const functionGeminiTools = tools?.length ? [toGeminiTools(tools)] : [];
1319
+ const geminiTools = nativeGeminiTools.length || functionGeminiTools.length
1320
+ ? [...nativeGeminiTools, ...functionGeminiTools]
1321
+ : undefined;
1322
+ const toolConfig = functionGeminiTools.length ? toGeminiToolConfig(opts.toolChoice) : undefined;
1280
1323
  try { opts.onStageChange?.('requesting'); } catch {}
1281
1324
 
1282
1325
  // Explicit cachedContents (system + tools + prior-turn transcript).
@@ -1478,6 +1521,7 @@ export class GeminiProvider {
1478
1521
  const textParts = candidate?.content?.parts?.filter(p => 'text' in p) ?? [];
1479
1522
  const content = textParts.map(p => 'text' in p ? p.text : '').join('');
1480
1523
  const toolCalls = parseToolCalls(candidate?.content?.parts ?? []);
1524
+ const citations = collectGeminiGroundingSources(candidate);
1481
1525
  emitGeminiToolCalls(toolCalls, onToolCall);
1482
1526
  // Inspect candidate.finishReason — Gemini reports terminal status here.
1483
1527
  // Only STOP (and the legacy "FINISH_REASON_STOP") plus tool/function-
@@ -1537,7 +1581,7 @@ export class GeminiProvider {
1537
1581
  const outputTokens = (um.candidatesTokenCount || 0) + (um.thoughtsTokenCount || 0);
1538
1582
  if (cachedContent && inputTokens > 0 && cachedTokens <= 0) {
1539
1583
  try {
1540
- appendBridgeTrace({
1584
+ appendAgentTrace({
1541
1585
  sessionId: opts.sessionId || opts.session?.id || null,
1542
1586
  iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1543
1587
  kind: 'gemini_cache_anomaly',
@@ -1553,7 +1597,7 @@ export class GeminiProvider {
1553
1597
  });
1554
1598
  } catch {}
1555
1599
  }
1556
- traceBridgeUsage({
1600
+ traceAgentUsage({
1557
1601
  sessionId: opts.sessionId || opts.session?.id || null,
1558
1602
  iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1559
1603
  inputTokens,
@@ -1571,6 +1615,7 @@ export class GeminiProvider {
1571
1615
  content,
1572
1616
  model: useModel,
1573
1617
  toolCalls,
1618
+ citations: citations.length ? citations : undefined,
1574
1619
  providerState: opts.providerState,
1575
1620
  usage: um ? (() => {
1576
1621
  const input = um.promptTokenCount || um.totalTokenCount || 0;
@@ -1580,7 +1625,7 @@ export class GeminiProvider {
1580
1625
  // Use the already-computed cachedTokens (with
1581
1626
  // cache-create fallback applied) rather than the raw
1582
1627
  // metadata field, so the returned usage matches what
1583
- // traceBridgeUsage recorded for this same call.
1628
+ // traceAgentUsage recorded for this same call.
1584
1629
  cachedTokens,
1585
1630
  // Gemini promptTokenCount is total (cachedContentTokenCount
1586
1631
  // is a subset). Alias directly into promptTokens.