mixdog 0.8.1 → 0.9.1

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 (299) hide show
  1. package/README.md +47 -23
  2. package/package.json +34 -28
  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 +205 -33
  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 +1222 -90
  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 -29
  62. package/src/lib/plugin-paths.cjs +1 -7
  63. package/src/lib/rules-builder.cjs +242 -98
  64. package/src/lib/text-utils.cjs +1 -1
  65. package/src/mixdog-session-runtime.mjs +2211 -446
  66. package/src/output-styles/default.md +12 -28
  67. package/src/output-styles/extreme-simple.md +9 -6
  68. package/src/output-styles/simple.md +22 -9
  69. package/src/repl.mjs +108 -59
  70. package/src/rules/agent/00-common.md +15 -0
  71. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  72. package/src/rules/agent/30-explorer.md +22 -0
  73. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  75. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  76. package/src/rules/lead/01-general.md +9 -5
  77. package/src/rules/lead/04-workflow.md +51 -12
  78. package/src/rules/lead/lead-tool.md +6 -0
  79. package/src/rules/shared/01-tool.md +12 -1
  80. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  81. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  85. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  86. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  87. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  88. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  89. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  90. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  92. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  93. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  94. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  95. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
  97. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  98. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  100. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  101. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  102. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  107. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
  110. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  111. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  112. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
  113. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  114. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  115. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  116. package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
  117. package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
  118. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  119. package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
  120. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  121. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  122. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  123. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
  124. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
  125. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
  126. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  127. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  130. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  133. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  138. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  139. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  143. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  146. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  147. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  148. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  149. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  150. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  151. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  152. package/src/runtime/channels/index.mjs +149 -51
  153. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  154. package/src/runtime/channels/lib/config.mjs +0 -1
  155. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  156. package/src/runtime/channels/lib/executor.mjs +0 -3
  157. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  158. package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
  159. package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
  160. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  161. package/src/runtime/channels/lib/session-discovery.mjs +3 -6
  162. package/src/runtime/channels/lib/tool-format.mjs +0 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
  164. package/src/runtime/channels/lib/webhook.mjs +1 -1
  165. package/src/runtime/channels/tool-defs.mjs +29 -29
  166. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  167. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  168. package/src/runtime/memory/index.mjs +519 -118
  169. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  170. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  171. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  172. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  173. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  174. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  175. package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
  176. package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
  177. package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
  178. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
  179. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  180. package/src/runtime/memory/lib/memory.mjs +88 -9
  181. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  182. package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
  183. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  184. package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
  185. package/src/runtime/memory/lib/session-ingest.mjs +196 -0
  186. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  187. package/src/runtime/memory/tool-defs.mjs +46 -37
  188. package/src/runtime/search/index.mjs +104 -473
  189. package/src/runtime/search/lib/config.mjs +0 -4
  190. package/src/runtime/search/lib/state.mjs +1 -15
  191. package/src/runtime/search/lib/web-tools.mjs +44 -25
  192. package/src/runtime/search/tool-defs.mjs +16 -23
  193. package/src/runtime/shared/abort-controller.mjs +1 -1
  194. package/src/runtime/shared/atomic-file.mjs +4 -3
  195. package/src/runtime/shared/background-tasks.mjs +122 -11
  196. package/src/runtime/shared/child-spawn-gate.mjs +139 -0
  197. package/src/runtime/shared/config.mjs +7 -4
  198. package/src/runtime/shared/err-text.mjs +131 -4
  199. package/src/runtime/shared/llm/cost.mjs +2 -2
  200. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  201. package/src/runtime/shared/llm/index.mjs +34 -11
  202. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  203. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  204. package/src/runtime/shared/singleton-owner.mjs +104 -0
  205. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  206. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  207. package/src/runtime/shared/tool-surface.mjs +624 -95
  208. package/src/runtime/shared/user-data-guard.mjs +0 -2
  209. package/src/standalone/agent-task-status.mjs +203 -0
  210. package/src/standalone/agent-task-status.test.mjs +76 -0
  211. package/src/standalone/agent-tool.mjs +1913 -0
  212. package/src/standalone/channel-worker.mjs +370 -14
  213. package/src/standalone/explore-tool.mjs +165 -70
  214. package/src/standalone/folder-dialog.mjs +314 -0
  215. package/src/standalone/hook-bus.mjs +898 -22
  216. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  217. package/src/standalone/projects.mjs +226 -0
  218. package/src/standalone/provider-admin.mjs +41 -24
  219. package/src/standalone/seeds.mjs +1 -78
  220. package/src/standalone/usage-dashboard.mjs +96 -8
  221. package/src/tui/App.jsx +4800 -2140
  222. package/src/tui/components/AnsiText.jsx +39 -28
  223. package/src/tui/components/ContextPanel.jsx +14 -4
  224. package/src/tui/components/Markdown.jsx +43 -77
  225. package/src/tui/components/MarkdownTable.jsx +9 -184
  226. package/src/tui/components/Message.jsx +28 -11
  227. package/src/tui/components/Picker.jsx +95 -56
  228. package/src/tui/components/PromptInput.jsx +428 -239
  229. package/src/tui/components/QueuedCommands.jsx +1 -1
  230. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  231. package/src/tui/components/Spinner.jsx +67 -38
  232. package/src/tui/components/StatusLine.jsx +606 -38
  233. package/src/tui/components/TextEntryPanel.jsx +128 -9
  234. package/src/tui/components/ToolExecution.jsx +597 -362
  235. package/src/tui/components/TurnDone.jsx +3 -3
  236. package/src/tui/components/UsagePanel.jsx +3 -5
  237. package/src/tui/components/tool-output-format.mjs +499 -0
  238. package/src/tui/components/tool-output-format.test.mjs +312 -0
  239. package/src/tui/dist/index.mjs +9192 -2399
  240. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  241. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  242. package/src/tui/engine.mjs +1455 -279
  243. package/src/tui/figures.mjs +21 -40
  244. package/src/tui/index.jsx +75 -31
  245. package/src/tui/input-editing.mjs +25 -0
  246. package/src/tui/markdown/format-token.mjs +671 -69
  247. package/src/tui/markdown/format-token.test.mjs +312 -0
  248. package/src/tui/markdown/render-ansi.mjs +94 -0
  249. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  250. package/src/tui/markdown/stream-fence.mjs +34 -0
  251. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  252. package/src/tui/markdown/table-layout.mjs +250 -0
  253. package/src/tui/paste-attachments.mjs +0 -7
  254. package/src/tui/spinner-verbs.mjs +1 -2
  255. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  256. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  257. package/src/tui/theme.mjs +756 -24
  258. package/src/tui/time-format.mjs +1 -1
  259. package/src/tui/transcript-tool-failures.mjs +67 -0
  260. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  261. package/src/ui/ansi.mjs +1 -2
  262. package/src/ui/markdown.mjs +85 -26
  263. package/src/ui/markdown.test.mjs +70 -0
  264. package/src/ui/model-display.mjs +121 -0
  265. package/src/ui/session-stats.mjs +44 -0
  266. package/src/ui/statusline-context-label.test.mjs +15 -0
  267. package/src/ui/statusline.mjs +386 -178
  268. package/src/ui/tool-card.mjs +2 -13
  269. package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
  270. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  271. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  272. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  273. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  274. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  275. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  276. package/src/workflows/default/WORKFLOW.md +12 -5
  277. package/src/workflows/default/workflow.json +0 -1
  278. package/src/workflows/solo/WORKFLOW.md +15 -0
  279. package/src/workflows/solo/workflow.json +7 -0
  280. package/vendor/ink/build/ink.js +54 -8
  281. package/vendor/ink/build/output.js +6 -1
  282. package/src/agents/scheduler-task.md +0 -3
  283. package/src/agents/web-researcher/AGENT.md +0 -3
  284. package/src/agents/web-researcher/agent.json +0 -6
  285. package/src/agents/webhook-handler.md +0 -3
  286. package/src/hooks/lib/permission-rules.cjs +0 -170
  287. package/src/hooks/lib/settings-loader.cjs +0 -112
  288. package/src/lib/hook-pipe-path.cjs +0 -10
  289. package/src/rules/bridge/00-common.md +0 -5
  290. package/src/rules/bridge/30-explorer.md +0 -4
  291. package/src/rules/lead/00-tool-lead.md +0 -5
  292. package/src/rules/shared/00-language.md +0 -3
  293. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  294. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  295. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  296. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  297. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  298. package/src/standalone/bridge-tool.mjs +0 -1414
  299. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -6,6 +6,7 @@ import {
6
6
  providerTimeoutError,
7
7
  } from '../stall-policy.mjs';
8
8
  import { populateHttpStatusFromMessage } from './retry-classifier.mjs';
9
+ import { customToolCallFromResponseItem } from './custom-tool-wire.mjs';
9
10
 
10
11
  function truncatedCompatStreamError(label, detail) {
11
12
  return Object.assign(
@@ -14,245 +15,30 @@ function truncatedCompatStreamError(label, detail) {
14
15
  );
15
16
  }
16
17
 
17
- // Permanent (non-retryable) tool-call arguments parse failure. Unlike a
18
- // truncated stream, this is raised only once a completion/finish signal has
19
- // been observed for the call the model emitted complete-but-malformed JSON,
20
- // which is deterministic: re-requesting the same turn yields the same bad
21
- // payload. Mark unsafeToRetry so classifyError() routes it permanent and the
22
- // shared retry wrapper never reissues.
23
- function badToolCallArgumentsError(label, detail) {
24
- return Object.assign(
25
- new Error(`${label} tool_call arguments JSON parse failed${detail ? `: ${detail}` : ''}`),
26
- { name: 'ToolCallArgumentsParseError', code: 'TOOL_CALL_ARGS_PARSE', unsafeToRetry: true },
27
- );
28
- }
29
-
30
- // Salvage malformed tool_call argument JSON emitted by weaker models.
31
- //
32
- // Observed failure mode (deepseek-v4-flash and similar): a string value is
33
- // emitted as an unquoted scalar with the surrounding quotes dropped, e.g.
34
- // {"pattern": dispatchAiWrapped, "path": "src/agent"}
35
- // ^^^^^^^^^^^^^^^^^ should be "dispatchAiWrapped"
36
- // {"query": claude-code PromptInput borderRight, "site": "github.com"}
37
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ should be one string
38
- // The structure is otherwise well-formed. This is deterministic and common in
39
- // code-search workloads where the model passes identifiers/symbols as values.
40
- //
41
- // Strategy: walk the text tracking object/array context and JSON strings
42
- // (honoring backslash escapes). OUTSIDE a string, only value slots are repaired.
43
- // A value slot is the token after `:` in an object, after `[` in an array, after
44
- // `,` in an array, or after `,` followed by the next object key. If the value is
45
- // not already a complete JSON scalar/container, quote the whole unquoted scalar
46
- // up to the next syntactic value boundary. Characters inside string literals are
47
- // never touched, so a `:` or `,` appearing inside a legitimately-quoted value
48
- // can't trigger a rewrite.
49
- //
50
- // Returns the parsed object on success, or null if salvage did not produce
51
- // valid JSON (caller then falls through to the original error path).
52
- function salvageBarewordJson(text) {
53
- if (typeof text !== 'string' || !text.length) return null;
54
- let out = '';
55
- let inStr = false;
56
- let i = 0;
57
- const n = text.length;
58
- const stack = [];
59
- // expectValue=true means the next non-space char begins a value slot
60
- // (just after `:` `[` `,` or at the very start).
61
- let expectValue = true;
62
- const matchingClose = (open) => open === '{' ? '}' : (open === '[' ? ']' : '');
63
- const skipWs = (pos) => {
64
- while (pos < n && /\s/.test(text[pos])) pos++;
65
- return pos;
66
- };
67
- const scanStringEnd = (pos) => {
68
- if (text[pos] !== '"') return -1;
69
- for (let j = pos + 1; j < n; j++) {
70
- if (text[j] === '\\') { j++; continue; }
71
- if (text[j] === '"') return j;
72
- }
73
- return -1;
74
- };
75
- const isBoundaryAfter = (pos) => {
76
- const j = skipWs(pos);
77
- return j >= n || text[j] === ',' || text[j] === '}' || text[j] === ']';
78
- };
79
- const quotedKeyAfterComma = (commaPos) => {
80
- let j = skipWs(commaPos + 1);
81
- if (text[j] !== '"') return false;
82
- const end = scanStringEnd(j);
83
- if (end < 0) return false;
84
- j = skipWs(end + 1);
85
- return text[j] === ':';
86
- };
87
- const quotedKeyAt = (pos) => {
88
- const end = scanStringEnd(pos);
89
- if (end < 0) return false;
90
- return text[skipWs(end + 1)] === ':';
91
- };
92
- const structuralEnd = (pos) => {
93
- const open = text[pos];
94
- const close = matchingClose(open);
95
- if (!close) return -1;
96
- const local = [open];
97
- let localInStr = false;
98
- for (let j = pos + 1; j < n; j++) {
99
- const ch = text[j];
100
- if (localInStr) {
101
- if (ch === '\\') { j++; continue; }
102
- if (ch === '"') localInStr = false;
103
- continue;
104
- }
105
- if (ch === '"') { localInStr = true; continue; }
106
- if (ch === '{' || ch === '[') { local.push(ch); continue; }
107
- if (ch === '}' || ch === ']') {
108
- if (matchingClose(local[local.length - 1]) !== ch) return -1;
109
- local.pop();
110
- if (!local.length) return j;
111
- }
112
- }
113
- return -1;
114
- };
115
- // Find where the unquoted scalar value at `pos` ends. Returns the boundary
116
- // index, or -1 when the surrounding object structure itself is malformed
117
- // (so salvage must fail rather than quote over it and change structure).
118
- //
119
- // Bracket/brace depth is tracked so a `,` or `:` that belongs to regex/glob
120
- // value text (e.g. `[,:]`, `{a,b,c}`) is correctly kept INSIDE the scalar,
121
- // while depth-0 object-member separators are treated as structure. Quotes are
122
- // NOT treated as string delimiters here: we are inside an unquoted value, so
123
- // any `"` is literal value text (e.g. a regex char class `["']`), and the
124
- // whole run is re-quoted via JSON.stringify by appendQuotedScalar.
125
- const scalarEnd = (pos) => {
126
- const ctx = stack[stack.length - 1] || '';
127
- let depth = 0;
128
- for (let j = pos; j < n; j++) {
129
- const ch = text[j];
130
- if (ch === '[' || ch === '{') { depth++; continue; }
131
- if (ch === ']' || ch === '}') {
132
- if (depth > 0) { depth--; continue; }
133
- return j; // depth-0 closer ends our container, so ends the value
134
- }
135
- if (depth > 0) continue; // inside nested brackets — part of the value
136
- // A depth-0 quoted key before any separator means a comma is missing
137
- // between members: malformed object structure, not value text.
138
- if (ctx === '{' && ch === '"' && quotedKeyAt(j)) return -1;
139
- if (ctx === '{' && ch === ':') {
140
- const before = text.slice(pos, j);
141
- const next = skipWs(j + 1);
142
- // A depth-0 colon with whitespace on either side looks like an
143
- // object member separator that was swallowed into the scalar
144
- // because a comma is missing (e.g. `foo b: 1`, `foo : 1`).
145
- // Keep compact colon-bearing scalars such as `C:/x`, `http://x`,
146
- // or `key:value` recoverable.
147
- if (!before.trim() || /\s/.test(before) || next !== j + 1) return -1;
148
- }
149
- if (ch === ',') {
150
- if (ctx === '[') return j; // array element separator
151
- if (ctx === '{') return quotedKeyAfterComma(j) ? j : -1;
152
- return j;
153
- }
154
- }
155
- return n;
156
- };
157
- const appendQuotedScalar = (start, end) => {
158
- const run = text.slice(start, end);
159
- const leadLen = run.length - run.trimStart().length;
160
- const trailLen = run.length - run.trimEnd().length;
161
- const lead = run.slice(0, leadLen);
162
- const body = run.slice(leadLen, run.length - trailLen);
163
- const trail = trailLen ? run.slice(run.length - trailLen) : '';
164
- if (!body) return false;
165
- out += lead + JSON.stringify(body) + trail;
166
- return true;
167
- };
168
- const readJsonScalar = (pos) => {
169
- const tail = text.slice(pos);
170
- const literal = /^(?:true|false|null)\b/.exec(tail);
171
- if (literal && isBoundaryAfter(pos + literal[0].length)) return literal[0].length;
172
- const number = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(tail);
173
- if (number && isBoundaryAfter(pos + number[0].length)) return number[0].length;
174
- return 0;
18
+ // Invalid-tool-args marker. Native-provider convergence (openai-oauth /
19
+ // opencode): completed-but-malformed tool_call arguments JSON must NOT throw
20
+ // (kills the turn) NOR be silently swallowed to `{}`. Instead the parse
21
+ // failure is carried as data on the tool call's `arguments` slot so the
22
+ // dispatch loop can turn it into an is_error tool_result and let the model
23
+ // re-issue the call with valid JSON in the SAME turn (follow-up retry).
24
+ // { __invalidToolArgs: true, __rawArguments: <raw string>, __parseError: <msg> }
25
+ export function makeInvalidToolArgsMarker(rawArguments, parseError) {
26
+ return {
27
+ __invalidToolArgs: true,
28
+ __rawArguments: typeof rawArguments === 'string' ? rawArguments : String(rawArguments ?? ''),
29
+ __parseError: typeof parseError === 'string' ? parseError : String(parseError ?? 'parse error'),
175
30
  };
176
- while (i < n) {
177
- const c = text[i];
178
- if (inStr) {
179
- out += c;
180
- if (c === '\\') { // copy the escaped char verbatim
181
- if (i + 1 < n) { out += text[i + 1]; i += 2; continue; }
182
- } else if (c === '"') {
183
- inStr = false;
184
- }
185
- i++;
186
- continue;
187
- }
188
- if (c === '"') { inStr = true; out += c; expectValue = false; i++; continue; }
189
- if (c === ':') { out += c; expectValue = true; i++; continue; }
190
- if (c === '{') {
191
- stack.push(c);
192
- out += c;
193
- expectValue = false;
194
- i++;
195
- continue;
196
- }
197
- if (c === '[') {
198
- if (expectValue) {
199
- const end = structuralEnd(i);
200
- if (end >= 0 && isBoundaryAfter(end + 1)) {
201
- const segment = text.slice(i, end + 1);
202
- try { JSON.parse(segment); out += segment; i = end + 1; expectValue = false; continue; } catch {}
203
- }
204
- const valueEnd = scalarEnd(i);
205
- if (valueEnd < 0) return null;
206
- if (!appendQuotedScalar(i, valueEnd)) return null;
207
- i = valueEnd;
208
- expectValue = false;
209
- continue;
210
- }
211
- stack.push(c);
212
- out += c;
213
- expectValue = c === '[';
214
- i++;
215
- continue;
216
- }
217
- if (c === ',') {
218
- out += c;
219
- expectValue = stack[stack.length - 1] === '[' || quotedKeyAfterComma(i);
220
- i++;
221
- continue;
222
- }
223
- if (c === '}' || c === ']') {
224
- if (matchingClose(stack[stack.length - 1]) === c) stack.pop();
225
- out += c;
226
- expectValue = false;
227
- i++;
228
- continue;
229
- }
230
- if (/\s/.test(c)) { out += c; i++; continue; }
231
- // Non-space, non-structural char in a value slot.
232
- if (expectValue) {
233
- const scalarLen = readJsonScalar(i);
234
- if (scalarLen) {
235
- out += text.slice(i, i + scalarLen);
236
- i += scalarLen;
237
- expectValue = false;
238
- continue;
239
- }
240
- const end = scalarEnd(i);
241
- if (end < 0) return null;
242
- if (!appendQuotedScalar(i, end)) return null;
243
- i = end;
244
- expectValue = false;
245
- continue;
246
- }
247
- out += c;
248
- i++;
249
- }
250
- if (inStr) return null; // unbalanced string — don't risk a bad parse
251
- try {
252
- return JSON.parse(out);
253
- } catch {
254
- return null;
255
- }
31
+ }
32
+ export function isInvalidToolArgsMarker(value) {
33
+ return !!value && typeof value === 'object' && value.__invalidToolArgs === true;
34
+ }
35
+ /** Model-facing tool_result text for a tool call whose arguments failed to
36
+ * parse. Mirrors opencode `The arguments provided to the tool are invalid` and
37
+ * `failed to parse function arguments` — instructs an in-turn retry. */
38
+ export function formatInvalidToolArgsResult(call) {
39
+ const name = call?.name || 'tool';
40
+ const detail = call?.arguments?.__parseError || 'arguments were not valid JSON';
41
+ return `The arguments provided to \`${name}\` are invalid JSON and could not be parsed: ${detail}. Re-issue this tool call with valid JSON arguments.`;
256
42
  }
257
43
 
258
44
  /** Completed tool_call.arguments must be valid JSON; empty/missing → {}.
@@ -267,15 +53,7 @@ export function parseCompletedToolCallArgumentsJson(raw, label, meta) {
267
53
  const src = text === '' ? '{}' : text;
268
54
  try {
269
55
  return JSON.parse(src);
270
- } catch {
271
- // Salvage the common weak-model failure: a string value emitted as a
272
- // bare (unquoted) word. Deterministic and structure-preserving, so a
273
- // successful salvage yields the exact arguments the model intended.
274
- const salvaged = salvageBarewordJson(text);
275
- if (salvaged !== null) {
276
- try { process.stderr.write(`[toolcall-salvage] label=${label} recovered bareword JSON (len=${text.length})\n`); } catch {}
277
- return salvaged;
278
- }
56
+ } catch (err) {
279
57
  const preview = text.length <= 64
280
58
  ? text
281
59
  : text.slice(0, 32) + '...' + text.slice(-32);
@@ -291,10 +69,13 @@ export function parseCompletedToolCallArgumentsJson(raw, label, meta) {
291
69
  // Invariant: a completion/finish signal was observed for this tool call
292
70
  // (finish_reason present, or a per-call/response "done" event fired), so
293
71
  // the arguments are NOT mid-stream-truncated — they are complete but
294
- // malformed. Surface a permanent parse error; only an unfinished stream
295
- // (no finishReason) is the retryable truncation case.
72
+ // malformed. Native convergence: return an invalid-args MARKER (not a
73
+ // throw) so the dispatch loop feeds the parse error back to the model as
74
+ // a tool_result and the model self-corrects in the same turn. Only an
75
+ // unfinished stream (no finishReason) stays the retryable truncation
76
+ // case — that transient behavior is deliberately preserved.
296
77
  if (meta?.finishReason) {
297
- throw badToolCallArgumentsError(label, detailParts.join(' '));
78
+ return makeInvalidToolArgsMarker(text, err instanceof Error ? err.message : String(err));
298
79
  }
299
80
  throw truncatedCompatStreamError(label, detailParts.join(' '));
300
81
  }
@@ -591,6 +372,27 @@ export async function consumeCompatChatCompletionStream(stream, { signal, label,
591
372
 
592
373
  function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesToolCalls, responseOutputText, onStreamDelta, onToolCall, onTextDelta }) {
593
374
  if (!event || typeof event.type !== 'string') return;
375
+ const pushToolSearchCall = (item) => {
376
+ if (!item || item.type !== 'tool_search_call') return;
377
+ const callId = item.call_id || item.id || '';
378
+ if (!callId || state.toolCalls.some((call) => call.id === callId)) return;
379
+ const call = {
380
+ id: callId,
381
+ name: 'tool_search',
382
+ arguments: item.arguments && typeof item.arguments === 'object'
383
+ ? item.arguments
384
+ : parseCompletedToolCallArgumentsJson(item.arguments || '{}', label, { id: callId, name: 'tool_search', finishReason: 'done' }),
385
+ nativeType: 'tool_search_call',
386
+ };
387
+ state.toolCalls.push(call);
388
+ emitCompatToolCallOnce(state, call, onToolCall);
389
+ };
390
+ const pushCustomToolCall = (item) => {
391
+ const call = customToolCallFromResponseItem(item);
392
+ if (!call || state.toolCalls.some((existing) => existing.id === call.id)) return;
393
+ state.toolCalls.push(call);
394
+ emitCompatToolCallOnce(state, call, onToolCall);
395
+ };
594
396
  switch (event.type) {
595
397
  case 'response.created':
596
398
  if (event.response?.model) state.model = event.response.model;
@@ -617,6 +419,9 @@ function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesT
617
419
  case 'response.function_call_arguments.delta':
618
420
  try { onStreamDelta?.(); } catch {}
619
421
  break;
422
+ case 'response.custom_tool_call_input.delta':
423
+ try { onStreamDelta?.(); } catch {}
424
+ break;
620
425
  case 'response.function_call_arguments.done': {
621
426
  const itemId = event.item_id || '';
622
427
  const pending = state.pendingCalls.get(itemId);
@@ -654,6 +459,10 @@ function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesT
654
459
  state.toolCalls.push(call);
655
460
  emitCompatToolCallOnce(state, call, onToolCall);
656
461
  }
462
+ } else if (item.type === 'tool_search_call') {
463
+ pushToolSearchCall(item);
464
+ } else if (item.type === 'custom_tool_call') {
465
+ pushCustomToolCall(item);
657
466
  }
658
467
  try { onStreamDelta?.(); } catch {}
659
468
  break;
@@ -665,14 +474,29 @@ function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesT
665
474
  if (!state.model && resp.model) state.model = resp.model;
666
475
  if (!state.responseId && resp.id) state.responseId = resp.id;
667
476
  if (!state.content) state.content = responseOutputText(resp);
668
- if (!state.toolCalls.length) {
669
- const parsed = parseResponsesToolCalls(resp, label);
670
- if (parsed?.length) {
671
- for (const parsedCall of parsed) {
672
- const call = { ...parsedCall };
477
+ for (const item of resp.output || []) {
478
+ if (item?.type === 'function_call') {
479
+ const itemId = item.id || '';
480
+ const tc = state.toolCalls.find(t => t._pendingItemId === itemId)
481
+ || (item.call_id ? state.toolCalls.find(t => t.id === item.call_id) : null);
482
+ if (tc) {
483
+ if (!tc.id && item.call_id) tc.id = item.call_id;
484
+ if (!tc.name && item.name) tc.name = item.name;
485
+ if (tc.id && tc.name) delete tc._pendingItemId;
486
+ emitCompatToolCallOnce(state, tc, onToolCall);
487
+ } else if (item.call_id && item.name) {
488
+ const call = {
489
+ id: item.call_id,
490
+ name: item.name,
491
+ arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id, name: item.name, finishReason: 'done' }),
492
+ };
673
493
  state.toolCalls.push(call);
674
494
  emitCompatToolCallOnce(state, call, onToolCall);
675
495
  }
496
+ } else if (item?.type === 'tool_search_call') {
497
+ pushToolSearchCall(item);
498
+ } else if (item?.type === 'custom_tool_call') {
499
+ pushCustomToolCall(item);
676
500
  }
677
501
  }
678
502
  try { onStreamDelta?.(); } catch {}