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
@@ -1,7 +1,7 @@
1
1
  import OpenAI from 'openai';
2
2
  import { createHash } from 'crypto';
3
3
  import { loadConfig } from '../config.mjs';
4
- import { withRetry } from './retry-classifier.mjs';
4
+ import { shouldFallbackTransport, withRetry } from './retry-classifier.mjs';
5
5
  import { sendViaWebSocket } from './openai-oauth-ws.mjs';
6
6
  import {
7
7
  consumeCompatChatCompletionStream,
@@ -9,11 +9,11 @@ import {
9
9
  parseCompletedToolCallArgumentsJson,
10
10
  } from './openai-compat-stream.mjs';
11
11
  import { enrichModels, getModelMetadataSync } from './model-catalog.mjs';
12
- import { appendBridgeTrace, traceBridgeUsage } from '../bridge-trace.mjs';
12
+ import { appendAgentTrace, traceAgentUsage } from '../agent-trace.mjs';
13
13
  import {
14
14
  resolveProviderCacheKey,
15
15
  resolveProviderPromptCacheLane,
16
- } from '../smart-bridge/cache-strategy.mjs';
16
+ } from '../agent-runtime/cache-strategy.mjs';
17
17
  import {
18
18
  PROVIDER_FIRST_BYTE_TIMEOUT_MS,
19
19
  PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
@@ -23,42 +23,16 @@ import {
23
23
  } from '../stall-policy.mjs';
24
24
  import { traceHash, stableTraceStringify, summarizeTraceTools, traceTextShape } from './trace-utils.mjs';
25
25
  import {
26
- contentHasImage,
27
26
  normalizeContentForOpenAIChat,
28
27
  normalizeContentForOpenAIResponses,
29
28
  splitToolContentForOpenAIChat,
30
29
  splitToolContentForOpenAIResponses,
31
30
  } from './media-normalization.mjs';
32
- // OPENAI_COMPAT_PRESETS — self-declaring list of compat provider names and
33
- // their base URLs / defaults. registry.mjs imports this export so there is no
34
- // parallel OPENAI_COMPAT_PROVIDERS list to maintain separately.
35
- export const OPENAI_COMPAT_PRESETS = {
36
- deepseek: {
37
- baseURL: 'https://api.deepseek.com',
38
- defaultModel: 'deepseek-v4-pro',
39
- },
40
- xai: {
41
- baseURL: 'https://api.x.ai/v1',
42
- defaultModel: 'grok-4.3',
43
- },
44
- // OpenCode Go — low-cost coding-model subscription gateway. GLM / Kimi /
45
- // DeepSeek / MiMo use the OpenAI-compatible chat surface; MiniMax / Qwen
46
- // use the Anthropic-compatible messages surface. opencode-go.mjs provides
47
- // the dual transport while this preset remains the shared base URL/key
48
- // source. Auth is a single OPENCODE_API_KEY (Bearer).
49
- 'opencode-go': {
50
- baseURL: 'https://opencode.ai/zen/go/v1',
51
- defaultModel: 'glm-5.2',
52
- },
53
- ollama: {
54
- baseURL: 'http://localhost:11434/v1',
55
- defaultModel: 'llama3.3:latest',
56
- },
57
- lmstudio: {
58
- baseURL: 'http://localhost:1234/v1',
59
- defaultModel: 'default',
60
- },
61
- };
31
+ import {
32
+ customToolCallFromResponseItem,
33
+ } from './custom-tool-wire.mjs';
34
+ import { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
35
+ export { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
62
36
  const PRESETS = OPENAI_COMPAT_PRESETS;
63
37
  const MODEL_LIST_TIMEOUT_MS = resolveTimeoutMs(
64
38
  'MIXDOG_COMPAT_MODEL_LIST_TIMEOUT_MS',
@@ -266,6 +240,22 @@ function useXaiResponsesWebSocket(opts, config) {
266
240
  return !['0', 'false', 'off', 'http', 'https', 'responses-http', 'sdk'].includes(transport);
267
241
  }
268
242
 
243
+ function _envFlag(name, fallback = true) {
244
+ const raw = process.env[name];
245
+ if (raw == null || raw === '') return fallback;
246
+ return !['0', 'false', 'off', 'no'].includes(String(raw).toLowerCase());
247
+ }
248
+
249
+ // xAI WS→HTTP transport fallback → shared shouldFallbackTransport
250
+ // (retry-classifier.mjs). Identical deny-order + allow-list; the per-provider
251
+ // env flag is computed here and passed via `enabled`.
252
+ function _shouldFallbackXaiWsToHttp(err, signal) {
253
+ return shouldFallbackTransport(err, {
254
+ signal,
255
+ enabled: _envFlag('MIXDOG_XAI_WS_HTTP_FALLBACK', true),
256
+ });
257
+ }
258
+
269
259
  function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseId, instructions, rawTools }) {
270
260
  if (previousResponseId) return false;
271
261
  const raw = opts?.xaiResponsesWarmup
@@ -282,7 +272,7 @@ function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseId, inst
282
272
  return String(instructions || '').length >= 2048 || (Array.isArray(rawTools) && rawTools.length >= 10);
283
273
  }
284
274
 
285
- // Match OpenAI/Codex cache-lane semantics: default to 12 stable shards (via
275
+ // Match OpenAI OAuth/API cache-lane semantics: default to 12 stable shards (via
286
276
  // resolveProviderPromptCacheLane) and serialize each final shard. This gives
287
277
  // Grok/xAI 10+ worker fanout without concurrent same-key cache contention.
288
278
  const XAI_RESPONSES_CACHE_LANE_DEFAULT_MAX_IN_FLIGHT = 1;
@@ -511,7 +501,7 @@ function acquireXaiResponsesCacheLane({ key, maxInFlight, signal, timeoutMs }) {
511
501
  function traceXaiCacheLane(opts, payload) {
512
502
  if (!compatCacheTraceEnabled('xai')) return;
513
503
  try {
514
- appendBridgeTrace({
504
+ appendAgentTrace({
515
505
  sessionId: opts?.sessionId || opts?.session?.id || null,
516
506
  iteration: Number.isFinite(Number(opts?.iteration)) ? Number(opts.iteration) : null,
517
507
  kind: 'cache_lane',
@@ -784,7 +774,7 @@ function traceXaiResponsesCacheContext(args) {
784
774
  const payload = xaiResponsesFingerprintPayload(args);
785
775
  const sessionId = args?.opts?.sessionId || args?.opts?.session?.id || null;
786
776
  const iteration = Number.isFinite(Number(args?.opts?.iteration)) ? Number(args.opts.iteration) : null;
787
- appendBridgeTrace({
777
+ appendAgentTrace({
788
778
  sessionId,
789
779
  iteration,
790
780
  kind: 'cache_context',
@@ -797,7 +787,7 @@ function traceXaiResponsesCacheContext(args) {
797
787
  anomaly: 'xai_mid_turn_cold_cache',
798
788
  reason: 'previous_response_id_present_but_cached_tokens_low',
799
789
  };
800
- appendBridgeTrace({
790
+ appendAgentTrace({
801
791
  sessionId,
802
792
  iteration,
803
793
  kind: 'cache_anomaly',
@@ -942,10 +932,6 @@ function toOpenAIMessages(messages, providerName, options = {}) {
942
932
  return out;
943
933
  }
944
934
 
945
- function messagesHaveImageContent(messages) {
946
- return (messages || []).some((m) => contentHasImage(m?.content));
947
- }
948
-
949
935
  function toOpenAITools(tools) {
950
936
  return tools.map((t) => ({
951
937
  type: 'function',
@@ -957,14 +943,34 @@ function toOpenAITools(tools) {
957
943
  }));
958
944
  }
959
945
  function toResponsesTools(tools) {
960
- return tools.map((t) => ({
961
- type: 'function',
962
- name: t.name,
963
- description: t.description,
964
- parameters: t.inputSchema,
965
- }));
946
+ return tools.map((t) => {
947
+ if (t?.name === 'tool_search') {
948
+ return {
949
+ type: 'tool_search',
950
+ execution: 'client',
951
+ description: t.description,
952
+ parameters: t.inputSchema,
953
+ };
954
+ }
955
+ // xAI/Grok Responses rejects the OpenAI-only `type:'custom'` freeform
956
+ // variant ("unknown variant 'custom'"). Serialize freeform/grammar
957
+ // tools (e.g. apply_patch) as ordinary function tools instead. Grammar
958
+ // tools may carry no usable inputSchema, so fall back to a permissive
959
+ // object schema so grok still registers a valid function tool.
960
+ return {
961
+ type: 'function',
962
+ name: t.name,
963
+ description: t.description,
964
+ parameters: t.inputSchema || { type: 'object', additionalProperties: true },
965
+ };
966
+ });
966
967
  }
967
- function parseToolCalls(choice, label) {
968
+ function nativeResponsesTools(opts) {
969
+ return Array.isArray(opts?.nativeTools)
970
+ ? opts.nativeTools.filter(t => t && typeof t === 'object')
971
+ : [];
972
+ }
973
+ export function parseToolCalls(choice, label) {
968
974
  const calls = choice.message?.tool_calls;
969
975
  if (!calls?.length)
970
976
  return undefined;
@@ -979,18 +985,31 @@ function parseToolCalls(choice, label) {
979
985
  arguments: parseCompletedToolCallArgumentsJson(tc.function.arguments, label, { id: tc.id, name: tc.function.name, finishReason }),
980
986
  }));
981
987
  }
982
- function parseResponsesToolCalls(response, label) {
988
+ export function parseResponsesToolCalls(response, label) {
983
989
  const out = [];
984
990
  // A Responses tool call is only parsed off a completed/done item, so any
985
991
  // malformed-JSON failure here is deterministic, not mid-stream truncation.
986
992
  const finishReason = response?.status || 'completed';
987
993
  for (const item of response?.output || []) {
988
- if (item?.type !== 'function_call') continue;
989
- out.push({
990
- id: item.call_id || item.id,
991
- name: item.name,
992
- arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id || item.id, name: item.name, finishReason }),
993
- });
994
+ if (item?.type === 'function_call') {
995
+ out.push({
996
+ id: item.call_id || item.id,
997
+ name: item.name,
998
+ arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id || item.id, name: item.name, finishReason }),
999
+ });
1000
+ } else if (item?.type === 'custom_tool_call') {
1001
+ const call = customToolCallFromResponseItem(item);
1002
+ if (call) out.push(call);
1003
+ } else if (item?.type === 'tool_search_call') {
1004
+ out.push({
1005
+ id: item.call_id || item.id,
1006
+ name: 'tool_search',
1007
+ arguments: item.arguments && typeof item.arguments === 'object'
1008
+ ? item.arguments
1009
+ : parseCompletedToolCallArgumentsJson(item.arguments || '{}', label, { id: item.call_id || item.id, name: 'tool_search', finishReason }),
1010
+ nativeType: 'tool_search_call',
1011
+ });
1012
+ }
994
1013
  }
995
1014
  return out.length ? out : undefined;
996
1015
  }
@@ -1005,9 +1024,71 @@ function responseOutputText(response) {
1005
1024
  }
1006
1025
  return chunks.join('');
1007
1026
  }
1008
- function toResponsesInputMessage(m, pendingToolMedia = null) {
1027
+ function collectCompatResponseSearchSources(response) {
1028
+ const citations = [];
1029
+ const webSearchCalls = [];
1030
+ const seen = new Set();
1031
+ const addCitation = (source, fallback = {}) => {
1032
+ if (!source) return;
1033
+ if (typeof source === 'string') {
1034
+ const url = source.trim();
1035
+ if (!url || seen.has(url)) return;
1036
+ seen.add(url);
1037
+ citations.push({ title: url, url, snippet: '', source: fallback.source || 'citation', provider: 'xai' });
1038
+ return;
1039
+ }
1040
+ if (typeof source !== 'object') return;
1041
+ const url = String(
1042
+ source.url
1043
+ || source.uri
1044
+ || source.href
1045
+ || source.source_url
1046
+ || source.url_citation?.url
1047
+ || '',
1048
+ ).trim();
1049
+ if (!url || seen.has(url)) return;
1050
+ seen.add(url);
1051
+ citations.push({
1052
+ title: String(source.title || source.name || source.query || source.url_citation?.title || fallback.title || url).trim(),
1053
+ url,
1054
+ snippet: String(source.snippet || source.text || source.description || '').trim(),
1055
+ source: source.source || fallback.source || 'citation',
1056
+ provider: source.provider || 'xai',
1057
+ });
1058
+ };
1059
+ for (const citation of Array.isArray(response?.citations) ? response.citations : []) addCitation(citation);
1060
+ for (const item of Array.isArray(response?.output) ? response.output : []) {
1061
+ if (item?.type === 'web_search_call') {
1062
+ webSearchCalls.push({ id: item.id || '', status: item.status || '', action: item.action || null });
1063
+ const action = item.action || {};
1064
+ for (const source of Array.isArray(action.sources) ? action.sources : []) addCitation(source, { title: action.query || '', source: 'web_search_call' });
1065
+ if (action.url) addCitation({ url: action.url, title: action.query || '' }, { source: 'web_search_call' });
1066
+ for (const url of Array.isArray(action.urls) ? action.urls : []) addCitation({ url, title: action.query || '' }, { source: 'web_search_call' });
1067
+ }
1068
+ for (const citation of Array.isArray(item?.citations) ? item.citations : []) addCitation(citation);
1069
+ for (const part of Array.isArray(item?.content) ? item.content : []) {
1070
+ for (const annotation of Array.isArray(part?.annotations) ? part.annotations : []) {
1071
+ addCitation(annotation, { source: 'annotation' });
1072
+ }
1073
+ }
1074
+ }
1075
+ return { citations, webSearchCalls };
1076
+ }
1077
+ function toResponsesInputMessage(m, pendingToolMedia = null, customToolCallNameById = null) {
1009
1078
  if (m.role === 'tool') {
1079
+ if (Array.isArray(m.nativeToolSearch?.openaiTools)) {
1080
+ return {
1081
+ type: 'tool_search_output',
1082
+ call_id: m.toolCallId || '',
1083
+ status: 'completed',
1084
+ execution: 'client',
1085
+ tools: m.nativeToolSearch.openaiTools,
1086
+ };
1087
+ }
1010
1088
  const { output, mediaContent } = splitToolContentForOpenAIResponses(m.content);
1089
+ // xai path: never emit `custom_tool_call_output` (the `custom` variant
1090
+ // is rejected by grok). Replay prior tool outputs as the standard
1091
+ // `function_call_output` item regardless of original native type.
1011
1092
  const item = {
1012
1093
  type: 'function_call_output',
1013
1094
  call_id: m.toolCallId || '',
@@ -1020,12 +1101,25 @@ function toResponsesInputMessage(m, pendingToolMedia = null) {
1020
1101
  const items = [];
1021
1102
  if (m.content) items.push({ role: 'assistant', content: normalizeContentForOpenAIResponses(m.content, { role: 'assistant' }) });
1022
1103
  for (const tc of m.toolCalls) {
1023
- items.push({
1024
- type: 'function_call',
1025
- call_id: tc.id,
1026
- name: tc.name,
1027
- arguments: JSON.stringify(tc.arguments || {}),
1028
- });
1104
+ if (tc.nativeType === 'tool_search_call' || tc.name === 'tool_search') {
1105
+ items.push({
1106
+ type: 'tool_search_call',
1107
+ call_id: tc.id,
1108
+ execution: 'client',
1109
+ arguments: tc.arguments || {},
1110
+ });
1111
+ } else {
1112
+ // xai path: prior native `custom_tool_call` history is replayed
1113
+ // as a standard `function_call` (grok rejects the `custom`
1114
+ // variant). tc.arguments already holds the recovered object
1115
+ // form, so the same stringify path as regular calls applies.
1116
+ items.push({
1117
+ type: 'function_call',
1118
+ call_id: tc.id,
1119
+ name: tc.name,
1120
+ arguments: JSON.stringify(tc.arguments || {}),
1121
+ });
1122
+ }
1029
1123
  }
1030
1124
  return items;
1031
1125
  }
@@ -1062,6 +1156,7 @@ function toXaiResponsesInput(messages, providerState, options = {}) {
1062
1156
  }
1063
1157
  const input = [];
1064
1158
  const pendingToolMedia = [];
1159
+ const customToolCallNameById = new Map();
1065
1160
  const flushToolMedia = () => {
1066
1161
  if (!pendingToolMedia.length) return;
1067
1162
  input.push({ role: 'user', content: pendingToolMedia.splice(0) });
@@ -1069,7 +1164,7 @@ function toXaiResponsesInput(messages, providerState, options = {}) {
1069
1164
  for (const m of messages.slice(startIndex)) {
1070
1165
  if (!includeSystem && m.role === 'system') continue;
1071
1166
  if (m.role !== 'tool') flushToolMedia();
1072
- const converted = toResponsesInputMessage(m, pendingToolMedia);
1167
+ const converted = toResponsesInputMessage(m, pendingToolMedia, customToolCallNameById);
1073
1168
  if (Array.isArray(converted)) input.push(...converted);
1074
1169
  else input.push(converted);
1075
1170
  }
@@ -1145,8 +1240,35 @@ export class OpenAICompatProvider {
1145
1240
  const useModel = model || this.defaultModel;
1146
1241
  const opts = sendOpts || {};
1147
1242
  if (this.name === 'xai' && useXaiResponsesApi(opts, this.config)) {
1148
- if (useXaiResponsesWebSocket(opts, this.config) && !messagesHaveImageContent(messages)) {
1149
- return await this._doSendXaiResponsesWebSocket(messages, useModel, tools, opts);
1243
+ if (useXaiResponsesWebSocket(opts, this.config)) {
1244
+ try {
1245
+ return await this._doSendXaiResponsesWebSocket(messages, useModel, tools, opts);
1246
+ } catch (err) {
1247
+ if (_shouldFallbackXaiWsToHttp(err, opts.signal)) {
1248
+ const reason = err?.midstreamClassifier || err?.retryClassifier || err?.code || err?.message || 'ws_failed';
1249
+ process.stderr.write(`[xai:responses] WebSocket unhealthy (${reason}); falling back to HTTP/SSE\n`);
1250
+ try {
1251
+ appendAgentTrace({
1252
+ sessionId: opts?.sessionId || opts?.session?.id || null,
1253
+ iteration: Number.isFinite(Number(opts?.iteration)) ? Number(opts.iteration) : null,
1254
+ kind: 'transport_fallback',
1255
+ provider: 'xai',
1256
+ model: useModel,
1257
+ transport: 'http',
1258
+ payload: {
1259
+ from: 'websocket',
1260
+ to: 'http',
1261
+ reason,
1262
+ error_code: err?.code || null,
1263
+ error_http_status: Number(err?.httpStatus || 0) || null,
1264
+ error_classifier: err?.retryClassifier || err?.midstreamClassifier || null,
1265
+ },
1266
+ });
1267
+ } catch {}
1268
+ return await this._doSendXaiResponses(messages, useModel, tools, opts);
1269
+ }
1270
+ throw err;
1271
+ }
1150
1272
  }
1151
1273
  return await this._doSendXaiResponses(messages, useModel, tools, opts);
1152
1274
  }
@@ -1283,7 +1405,7 @@ export class OpenAICompatProvider {
1283
1405
  if (response.usage) {
1284
1406
  const inputTokens = Number(response.usage.prompt_tokens ?? response.usage.input_tokens ?? 0);
1285
1407
  const cachedTokens = extractCompatCachedTokens(response.usage);
1286
- traceBridgeUsage({
1408
+ traceAgentUsage({
1287
1409
  sessionId: opts.sessionId || opts.session?.id || null,
1288
1410
  iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1289
1411
  inputTokens,
@@ -1357,9 +1479,10 @@ export class OpenAICompatProvider {
1357
1479
  prompt_cache_key: cacheRouting.key,
1358
1480
  };
1359
1481
  if (previousResponseId) params.previous_response_id = previousResponseId;
1360
- if (tools?.length) params.tools = toResponsesTools(tools);
1482
+ const nativeTools = nativeResponsesTools(opts);
1483
+ if (tools?.length || nativeTools.length) params.tools = [...nativeTools, ...toResponsesTools(tools || [])];
1361
1484
  // SSE transport: report 'requesting' until the stream opens, then
1362
- // per-chunk onStreamDelta feeds the bridge stall watchdog.
1485
+ // per-chunk onStreamDelta feeds the agent stall watchdog.
1363
1486
  try { opts.onStageChange?.('requesting'); } catch { /* heartbeat best-effort */ }
1364
1487
  const reasoningEffort = normalizeXaiReasoningEffort(opts.xaiReasoningEffort
1365
1488
  ?? opts.effort
@@ -1453,7 +1576,7 @@ export class OpenAICompatProvider {
1453
1576
  if (response.usage) {
1454
1577
  const inputTokens = Number(response.usage.input_tokens ?? response.usage.prompt_tokens ?? 0);
1455
1578
  const cachedTokens = extractCompatCachedTokens(response.usage);
1456
- traceBridgeUsage({
1579
+ traceAgentUsage({
1457
1580
  sessionId: opts.sessionId || opts.session?.id || null,
1458
1581
  iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1459
1582
  inputTokens,
@@ -1469,11 +1592,14 @@ export class OpenAICompatProvider {
1469
1592
  });
1470
1593
  }
1471
1594
  const nextPreviousResponseId = streamed.stopReason === 'length' ? null : response.id;
1595
+ const searchSources = collectCompatResponseSearchSources(response);
1472
1596
  return {
1473
1597
  content: streamed.content,
1474
1598
  model: response.model || useModel,
1475
1599
  toolCalls,
1476
1600
  stopReason: streamed.stopReason || null,
1601
+ citations: searchSources.citations.length ? searchSources.citations : undefined,
1602
+ webSearchCalls: searchSources.webSearchCalls.length ? searchSources.webSearchCalls : undefined,
1477
1603
  providerState: {
1478
1604
  ...(opts.providerState || {}),
1479
1605
  xaiResponses: {
@@ -1529,7 +1655,8 @@ export class OpenAICompatProvider {
1529
1655
  // xAI rejects instructions together with previous_response_id; the
1530
1656
  // first response already anchors instructions for the continuation.
1531
1657
  else if (instructions) params.instructions = instructions;
1532
- if (tools?.length) params.tools = toResponsesTools(tools);
1658
+ const nativeTools = nativeResponsesTools(opts);
1659
+ if (tools?.length || nativeTools.length) params.tools = [...nativeTools, ...toResponsesTools(tools || [])];
1533
1660
  const reasoningEffort = normalizeXaiReasoningEffort(opts.xaiReasoningEffort
1534
1661
  ?? opts.effort
1535
1662
  ?? this.config?.reasoningEffort
@@ -1640,6 +1767,8 @@ export class OpenAICompatProvider {
1640
1767
  ...result.usage,
1641
1768
  ...(costUsd != null ? { costUsd } : {}),
1642
1769
  } : undefined,
1770
+ citations: Array.isArray(result.citations) && result.citations.length ? result.citations : undefined,
1771
+ webSearchCalls: Array.isArray(result.webSearchCalls) && result.webSearchCalls.length ? result.webSearchCalls : undefined,
1643
1772
  };
1644
1773
  }
1645
1774
  async _fetchModelItems() {
@@ -1717,3 +1846,6 @@ export class OpenAICompatProvider {
1717
1846
  return null;
1718
1847
  }
1719
1848
  }
1849
+
1850
+ export const _toResponsesToolsForTest = toResponsesTools;
1851
+ export const _toXaiResponsesInputForTest = toXaiResponsesInput;