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
@@ -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,41 @@ 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
+ function _shouldFallbackXaiWsToHttp(err, signal) {
250
+ if (!_envFlag('MIXDOG_XAI_WS_HTTP_FALLBACK', true)) return false;
251
+ if (signal?.aborted) return false;
252
+ if (err?.liveTextEmitted === true) return false;
253
+ if (err?.emittedToolCall === true || err?.unsafeToRetry === true) return false;
254
+ const status = Number(err?.httpStatus || err?.status || 0);
255
+ if (status === 401 || status === 403 || status === 404 || status === 429) return false;
256
+ if (status >= 500 && status < 600) return true;
257
+ const code = String(err?.code || '');
258
+ if (['EWSACQUIRETIMEOUT', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'ECONNRESET', 'EAI_AGAIN', 'ENOTFOUND', 'EAI_NODATA', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'EPIPE'].includes(code)) {
259
+ return true;
260
+ }
261
+ const classifier = String(err?.retryClassifier || err?.midstreamClassifier || '');
262
+ if ([
263
+ 'timeout', 'reset', 'dns', 'refused', 'network', 'acquire_timeout', 'http_5xx',
264
+ 'first_byte_timeout', 'first_meaningful_timeout',
265
+ 'ws_1006', 'ws_1011', 'ws_1012', 'ws_1000', 'ws_4000', 'agent_stall', 'stream_stalled',
266
+ 'response_failed_disconnected', 'response_failed_network', 'response_failed_auth_expired',
267
+ 'ws_send_failed',
268
+ ].includes(classifier)) {
269
+ return true;
270
+ }
271
+ if (/^http_5\d\d$/.test(classifier)) return true;
272
+ if (err?.firstByteTimeout) return true;
273
+ if (err?.firstMeaningfulTimeout) return true;
274
+ const msg = String(err?.message || '');
275
+ return /opening handshake has timed out|socket hang up|acquire timed out|no first server event|no meaningful output/i.test(msg);
276
+ }
277
+
269
278
  function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseId, instructions, rawTools }) {
270
279
  if (previousResponseId) return false;
271
280
  const raw = opts?.xaiResponsesWarmup
@@ -282,7 +291,7 @@ function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseId, inst
282
291
  return String(instructions || '').length >= 2048 || (Array.isArray(rawTools) && rawTools.length >= 10);
283
292
  }
284
293
 
285
- // Match OpenAI/Codex cache-lane semantics: default to 12 stable shards (via
294
+ // Match OpenAI OAuth/API cache-lane semantics: default to 12 stable shards (via
286
295
  // resolveProviderPromptCacheLane) and serialize each final shard. This gives
287
296
  // Grok/xAI 10+ worker fanout without concurrent same-key cache contention.
288
297
  const XAI_RESPONSES_CACHE_LANE_DEFAULT_MAX_IN_FLIGHT = 1;
@@ -511,7 +520,7 @@ function acquireXaiResponsesCacheLane({ key, maxInFlight, signal, timeoutMs }) {
511
520
  function traceXaiCacheLane(opts, payload) {
512
521
  if (!compatCacheTraceEnabled('xai')) return;
513
522
  try {
514
- appendBridgeTrace({
523
+ appendAgentTrace({
515
524
  sessionId: opts?.sessionId || opts?.session?.id || null,
516
525
  iteration: Number.isFinite(Number(opts?.iteration)) ? Number(opts.iteration) : null,
517
526
  kind: 'cache_lane',
@@ -784,7 +793,7 @@ function traceXaiResponsesCacheContext(args) {
784
793
  const payload = xaiResponsesFingerprintPayload(args);
785
794
  const sessionId = args?.opts?.sessionId || args?.opts?.session?.id || null;
786
795
  const iteration = Number.isFinite(Number(args?.opts?.iteration)) ? Number(args.opts.iteration) : null;
787
- appendBridgeTrace({
796
+ appendAgentTrace({
788
797
  sessionId,
789
798
  iteration,
790
799
  kind: 'cache_context',
@@ -797,7 +806,7 @@ function traceXaiResponsesCacheContext(args) {
797
806
  anomaly: 'xai_mid_turn_cold_cache',
798
807
  reason: 'previous_response_id_present_but_cached_tokens_low',
799
808
  };
800
- appendBridgeTrace({
809
+ appendAgentTrace({
801
810
  sessionId,
802
811
  iteration,
803
812
  kind: 'cache_anomaly',
@@ -942,10 +951,6 @@ function toOpenAIMessages(messages, providerName, options = {}) {
942
951
  return out;
943
952
  }
944
953
 
945
- function messagesHaveImageContent(messages) {
946
- return (messages || []).some((m) => contentHasImage(m?.content));
947
- }
948
-
949
954
  function toOpenAITools(tools) {
950
955
  return tools.map((t) => ({
951
956
  type: 'function',
@@ -957,14 +962,34 @@ function toOpenAITools(tools) {
957
962
  }));
958
963
  }
959
964
  function toResponsesTools(tools) {
960
- return tools.map((t) => ({
961
- type: 'function',
962
- name: t.name,
963
- description: t.description,
964
- parameters: t.inputSchema,
965
- }));
965
+ return tools.map((t) => {
966
+ if (t?.name === 'tool_search') {
967
+ return {
968
+ type: 'tool_search',
969
+ execution: 'client',
970
+ description: t.description,
971
+ parameters: t.inputSchema,
972
+ };
973
+ }
974
+ // xAI/Grok Responses rejects the OpenAI-only `type:'custom'` freeform
975
+ // variant ("unknown variant 'custom'"). Serialize freeform/grammar
976
+ // tools (e.g. apply_patch) as ordinary function tools instead. Grammar
977
+ // tools may carry no usable inputSchema, so fall back to a permissive
978
+ // object schema so grok still registers a valid function tool.
979
+ return {
980
+ type: 'function',
981
+ name: t.name,
982
+ description: t.description,
983
+ parameters: t.inputSchema || { type: 'object', additionalProperties: true },
984
+ };
985
+ });
966
986
  }
967
- function parseToolCalls(choice, label) {
987
+ function nativeResponsesTools(opts) {
988
+ return Array.isArray(opts?.nativeTools)
989
+ ? opts.nativeTools.filter(t => t && typeof t === 'object')
990
+ : [];
991
+ }
992
+ export function parseToolCalls(choice, label) {
968
993
  const calls = choice.message?.tool_calls;
969
994
  if (!calls?.length)
970
995
  return undefined;
@@ -979,18 +1004,31 @@ function parseToolCalls(choice, label) {
979
1004
  arguments: parseCompletedToolCallArgumentsJson(tc.function.arguments, label, { id: tc.id, name: tc.function.name, finishReason }),
980
1005
  }));
981
1006
  }
982
- function parseResponsesToolCalls(response, label) {
1007
+ export function parseResponsesToolCalls(response, label) {
983
1008
  const out = [];
984
1009
  // A Responses tool call is only parsed off a completed/done item, so any
985
1010
  // malformed-JSON failure here is deterministic, not mid-stream truncation.
986
1011
  const finishReason = response?.status || 'completed';
987
1012
  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
- });
1013
+ if (item?.type === 'function_call') {
1014
+ out.push({
1015
+ id: item.call_id || item.id,
1016
+ name: item.name,
1017
+ arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id || item.id, name: item.name, finishReason }),
1018
+ });
1019
+ } else if (item?.type === 'custom_tool_call') {
1020
+ const call = customToolCallFromResponseItem(item);
1021
+ if (call) out.push(call);
1022
+ } else if (item?.type === 'tool_search_call') {
1023
+ out.push({
1024
+ id: item.call_id || item.id,
1025
+ name: 'tool_search',
1026
+ arguments: item.arguments && typeof item.arguments === 'object'
1027
+ ? item.arguments
1028
+ : parseCompletedToolCallArgumentsJson(item.arguments || '{}', label, { id: item.call_id || item.id, name: 'tool_search', finishReason }),
1029
+ nativeType: 'tool_search_call',
1030
+ });
1031
+ }
994
1032
  }
995
1033
  return out.length ? out : undefined;
996
1034
  }
@@ -1005,9 +1043,71 @@ function responseOutputText(response) {
1005
1043
  }
1006
1044
  return chunks.join('');
1007
1045
  }
1008
- function toResponsesInputMessage(m, pendingToolMedia = null) {
1046
+ function collectCompatResponseSearchSources(response) {
1047
+ const citations = [];
1048
+ const webSearchCalls = [];
1049
+ const seen = new Set();
1050
+ const addCitation = (source, fallback = {}) => {
1051
+ if (!source) return;
1052
+ if (typeof source === 'string') {
1053
+ const url = source.trim();
1054
+ if (!url || seen.has(url)) return;
1055
+ seen.add(url);
1056
+ citations.push({ title: url, url, snippet: '', source: fallback.source || 'citation', provider: 'xai' });
1057
+ return;
1058
+ }
1059
+ if (typeof source !== 'object') return;
1060
+ const url = String(
1061
+ source.url
1062
+ || source.uri
1063
+ || source.href
1064
+ || source.source_url
1065
+ || source.url_citation?.url
1066
+ || '',
1067
+ ).trim();
1068
+ if (!url || seen.has(url)) return;
1069
+ seen.add(url);
1070
+ citations.push({
1071
+ title: String(source.title || source.name || source.query || source.url_citation?.title || fallback.title || url).trim(),
1072
+ url,
1073
+ snippet: String(source.snippet || source.text || source.description || '').trim(),
1074
+ source: source.source || fallback.source || 'citation',
1075
+ provider: source.provider || 'xai',
1076
+ });
1077
+ };
1078
+ for (const citation of Array.isArray(response?.citations) ? response.citations : []) addCitation(citation);
1079
+ for (const item of Array.isArray(response?.output) ? response.output : []) {
1080
+ if (item?.type === 'web_search_call') {
1081
+ webSearchCalls.push({ id: item.id || '', status: item.status || '', action: item.action || null });
1082
+ const action = item.action || {};
1083
+ for (const source of Array.isArray(action.sources) ? action.sources : []) addCitation(source, { title: action.query || '', source: 'web_search_call' });
1084
+ if (action.url) addCitation({ url: action.url, title: action.query || '' }, { source: 'web_search_call' });
1085
+ for (const url of Array.isArray(action.urls) ? action.urls : []) addCitation({ url, title: action.query || '' }, { source: 'web_search_call' });
1086
+ }
1087
+ for (const citation of Array.isArray(item?.citations) ? item.citations : []) addCitation(citation);
1088
+ for (const part of Array.isArray(item?.content) ? item.content : []) {
1089
+ for (const annotation of Array.isArray(part?.annotations) ? part.annotations : []) {
1090
+ addCitation(annotation, { source: 'annotation' });
1091
+ }
1092
+ }
1093
+ }
1094
+ return { citations, webSearchCalls };
1095
+ }
1096
+ function toResponsesInputMessage(m, pendingToolMedia = null, customToolCallNameById = null) {
1009
1097
  if (m.role === 'tool') {
1098
+ if (Array.isArray(m.nativeToolSearch?.openaiTools)) {
1099
+ return {
1100
+ type: 'tool_search_output',
1101
+ call_id: m.toolCallId || '',
1102
+ status: 'completed',
1103
+ execution: 'client',
1104
+ tools: m.nativeToolSearch.openaiTools,
1105
+ };
1106
+ }
1010
1107
  const { output, mediaContent } = splitToolContentForOpenAIResponses(m.content);
1108
+ // xai path: never emit `custom_tool_call_output` (the `custom` variant
1109
+ // is rejected by grok). Replay prior tool outputs as the standard
1110
+ // `function_call_output` item regardless of original native type.
1011
1111
  const item = {
1012
1112
  type: 'function_call_output',
1013
1113
  call_id: m.toolCallId || '',
@@ -1020,12 +1120,25 @@ function toResponsesInputMessage(m, pendingToolMedia = null) {
1020
1120
  const items = [];
1021
1121
  if (m.content) items.push({ role: 'assistant', content: normalizeContentForOpenAIResponses(m.content, { role: 'assistant' }) });
1022
1122
  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
- });
1123
+ if (tc.nativeType === 'tool_search_call' || tc.name === 'tool_search') {
1124
+ items.push({
1125
+ type: 'tool_search_call',
1126
+ call_id: tc.id,
1127
+ execution: 'client',
1128
+ arguments: tc.arguments || {},
1129
+ });
1130
+ } else {
1131
+ // xai path: prior native `custom_tool_call` history is replayed
1132
+ // as a standard `function_call` (grok rejects the `custom`
1133
+ // variant). tc.arguments already holds the recovered object
1134
+ // form, so the same stringify path as regular calls applies.
1135
+ items.push({
1136
+ type: 'function_call',
1137
+ call_id: tc.id,
1138
+ name: tc.name,
1139
+ arguments: JSON.stringify(tc.arguments || {}),
1140
+ });
1141
+ }
1029
1142
  }
1030
1143
  return items;
1031
1144
  }
@@ -1062,6 +1175,7 @@ function toXaiResponsesInput(messages, providerState, options = {}) {
1062
1175
  }
1063
1176
  const input = [];
1064
1177
  const pendingToolMedia = [];
1178
+ const customToolCallNameById = new Map();
1065
1179
  const flushToolMedia = () => {
1066
1180
  if (!pendingToolMedia.length) return;
1067
1181
  input.push({ role: 'user', content: pendingToolMedia.splice(0) });
@@ -1069,7 +1183,7 @@ function toXaiResponsesInput(messages, providerState, options = {}) {
1069
1183
  for (const m of messages.slice(startIndex)) {
1070
1184
  if (!includeSystem && m.role === 'system') continue;
1071
1185
  if (m.role !== 'tool') flushToolMedia();
1072
- const converted = toResponsesInputMessage(m, pendingToolMedia);
1186
+ const converted = toResponsesInputMessage(m, pendingToolMedia, customToolCallNameById);
1073
1187
  if (Array.isArray(converted)) input.push(...converted);
1074
1188
  else input.push(converted);
1075
1189
  }
@@ -1145,8 +1259,35 @@ export class OpenAICompatProvider {
1145
1259
  const useModel = model || this.defaultModel;
1146
1260
  const opts = sendOpts || {};
1147
1261
  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);
1262
+ if (useXaiResponsesWebSocket(opts, this.config)) {
1263
+ try {
1264
+ return await this._doSendXaiResponsesWebSocket(messages, useModel, tools, opts);
1265
+ } catch (err) {
1266
+ if (_shouldFallbackXaiWsToHttp(err, opts.signal)) {
1267
+ const reason = err?.midstreamClassifier || err?.retryClassifier || err?.code || err?.message || 'ws_failed';
1268
+ process.stderr.write(`[xai:responses] WebSocket unhealthy (${reason}); falling back to HTTP/SSE\n`);
1269
+ try {
1270
+ appendAgentTrace({
1271
+ sessionId: opts?.sessionId || opts?.session?.id || null,
1272
+ iteration: Number.isFinite(Number(opts?.iteration)) ? Number(opts.iteration) : null,
1273
+ kind: 'transport_fallback',
1274
+ provider: 'xai',
1275
+ model: useModel,
1276
+ transport: 'http',
1277
+ payload: {
1278
+ from: 'websocket',
1279
+ to: 'http',
1280
+ reason,
1281
+ error_code: err?.code || null,
1282
+ error_http_status: Number(err?.httpStatus || 0) || null,
1283
+ error_classifier: err?.retryClassifier || err?.midstreamClassifier || null,
1284
+ },
1285
+ });
1286
+ } catch {}
1287
+ return await this._doSendXaiResponses(messages, useModel, tools, opts);
1288
+ }
1289
+ throw err;
1290
+ }
1150
1291
  }
1151
1292
  return await this._doSendXaiResponses(messages, useModel, tools, opts);
1152
1293
  }
@@ -1283,7 +1424,7 @@ export class OpenAICompatProvider {
1283
1424
  if (response.usage) {
1284
1425
  const inputTokens = Number(response.usage.prompt_tokens ?? response.usage.input_tokens ?? 0);
1285
1426
  const cachedTokens = extractCompatCachedTokens(response.usage);
1286
- traceBridgeUsage({
1427
+ traceAgentUsage({
1287
1428
  sessionId: opts.sessionId || opts.session?.id || null,
1288
1429
  iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1289
1430
  inputTokens,
@@ -1357,9 +1498,10 @@ export class OpenAICompatProvider {
1357
1498
  prompt_cache_key: cacheRouting.key,
1358
1499
  };
1359
1500
  if (previousResponseId) params.previous_response_id = previousResponseId;
1360
- if (tools?.length) params.tools = toResponsesTools(tools);
1501
+ const nativeTools = nativeResponsesTools(opts);
1502
+ if (tools?.length || nativeTools.length) params.tools = [...nativeTools, ...toResponsesTools(tools || [])];
1361
1503
  // SSE transport: report 'requesting' until the stream opens, then
1362
- // per-chunk onStreamDelta feeds the bridge stall watchdog.
1504
+ // per-chunk onStreamDelta feeds the agent stall watchdog.
1363
1505
  try { opts.onStageChange?.('requesting'); } catch { /* heartbeat best-effort */ }
1364
1506
  const reasoningEffort = normalizeXaiReasoningEffort(opts.xaiReasoningEffort
1365
1507
  ?? opts.effort
@@ -1453,7 +1595,7 @@ export class OpenAICompatProvider {
1453
1595
  if (response.usage) {
1454
1596
  const inputTokens = Number(response.usage.input_tokens ?? response.usage.prompt_tokens ?? 0);
1455
1597
  const cachedTokens = extractCompatCachedTokens(response.usage);
1456
- traceBridgeUsage({
1598
+ traceAgentUsage({
1457
1599
  sessionId: opts.sessionId || opts.session?.id || null,
1458
1600
  iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
1459
1601
  inputTokens,
@@ -1469,11 +1611,14 @@ export class OpenAICompatProvider {
1469
1611
  });
1470
1612
  }
1471
1613
  const nextPreviousResponseId = streamed.stopReason === 'length' ? null : response.id;
1614
+ const searchSources = collectCompatResponseSearchSources(response);
1472
1615
  return {
1473
1616
  content: streamed.content,
1474
1617
  model: response.model || useModel,
1475
1618
  toolCalls,
1476
1619
  stopReason: streamed.stopReason || null,
1620
+ citations: searchSources.citations.length ? searchSources.citations : undefined,
1621
+ webSearchCalls: searchSources.webSearchCalls.length ? searchSources.webSearchCalls : undefined,
1477
1622
  providerState: {
1478
1623
  ...(opts.providerState || {}),
1479
1624
  xaiResponses: {
@@ -1529,7 +1674,8 @@ export class OpenAICompatProvider {
1529
1674
  // xAI rejects instructions together with previous_response_id; the
1530
1675
  // first response already anchors instructions for the continuation.
1531
1676
  else if (instructions) params.instructions = instructions;
1532
- if (tools?.length) params.tools = toResponsesTools(tools);
1677
+ const nativeTools = nativeResponsesTools(opts);
1678
+ if (tools?.length || nativeTools.length) params.tools = [...nativeTools, ...toResponsesTools(tools || [])];
1533
1679
  const reasoningEffort = normalizeXaiReasoningEffort(opts.xaiReasoningEffort
1534
1680
  ?? opts.effort
1535
1681
  ?? this.config?.reasoningEffort
@@ -1640,6 +1786,8 @@ export class OpenAICompatProvider {
1640
1786
  ...result.usage,
1641
1787
  ...(costUsd != null ? { costUsd } : {}),
1642
1788
  } : undefined,
1789
+ citations: Array.isArray(result.citations) && result.citations.length ? result.citations : undefined,
1790
+ webSearchCalls: Array.isArray(result.webSearchCalls) && result.webSearchCalls.length ? result.webSearchCalls : undefined,
1643
1791
  };
1644
1792
  }
1645
1793
  async _fetchModelItems() {
@@ -1717,3 +1865,6 @@ export class OpenAICompatProvider {
1717
1865
  return null;
1718
1866
  }
1719
1867
  }
1868
+
1869
+ export const _toResponsesToolsForTest = toResponsesTools;
1870
+ export const _toXaiResponsesInputForTest = toXaiResponsesInput;