@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Cross-process reconciliation for user cost overlays.
3
+ *
4
+ * `refreshUserCostOverlays` is process-local by design: `ocx config set`
5
+ * executes `saveConfig()` in the CLI process, and a direct `config.json` edit
6
+ * runs no in-process code at all. A running proxy therefore keeps its previous
7
+ * `activeUserCostOverlays()`/version until restart or another in-process
8
+ * refresh.
9
+ *
10
+ * This module gives the long-lived server a lightweight stat-based poller:
11
+ * when `config.json` changes on disk, it re-reads the persisted config, mirrors
12
+ * the disk `modelCosts` rows into the live provider rows (so a later in-process
13
+ * save cannot erase the external edit), and refreshes the overlay registry so
14
+ * Logs/Usage estimates follow the edit without a restart.
15
+ *
16
+ * The reconciler is owner-scoped: every `startServer` acquires its own lease
17
+ * (with its own live config), and the shared timer is torn down only when the
18
+ * LAST owner stops. This mirrors `acquireServerBackgroundLifecycle` so stopping
19
+ * one of several servers cannot kill reconciliation for the others.
20
+ */
21
+ import { statSync } from "node:fs";
22
+
23
+ import { getConfigPath, readConfigDiagnostics } from "../config";
24
+ import type { OcxConfig, OcxProviderConfig } from "../types";
25
+ import {
26
+ refreshPreservedProviderOwner,
27
+ refreshUserCostOverlays,
28
+ registerPreservedProviderOwner,
29
+ setPreservedDiskOnlyProviders,
30
+ unregisterPreservedProviderOwner,
31
+ } from "./user-cost-overlays";
32
+
33
+ /** Default poll cadence for external config edits. */
34
+ export const USER_COST_OVERLAY_RECONCILE_INTERVAL_MS = 5_000;
35
+
36
+ let reconcileTimer: ReturnType<typeof setInterval> | null = null;
37
+ let reconcileTimerMs = 0;
38
+ const owners = new Map<symbol, OcxConfig | null>();
39
+ const ownerIntervals = new Map<symbol, number>();
40
+ let lastStamp: { mtimeMs: number; size: number; ctimeMs: number; ino: number } | null = null;
41
+ let invalidReconcileCount = 0;
42
+
43
+ function configStamp(): { mtimeMs: number; size: number; ctimeMs: number; ino: number } | null {
44
+ try {
45
+ const stat = statSync(getConfigPath());
46
+ return {
47
+ mtimeMs: stat.mtimeMs,
48
+ size: stat.size,
49
+ ctimeMs: stat.ctimeMs,
50
+ ino: Number(stat.ino),
51
+ };
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /** Registered live configs plus an optional one-shot config, deduped by object identity. */
58
+ function liveConfigsForPreservation(extra?: OcxConfig | null): OcxConfig[] {
59
+ const liveConfigs: OcxConfig[] = [];
60
+ const seen = new Set<OcxConfig>();
61
+ if (extra) {
62
+ seen.add(extra);
63
+ liveConfigs.push(extra);
64
+ }
65
+ for (const config of owners.values()) {
66
+ if (!config || seen.has(config)) continue;
67
+ seen.add(config);
68
+ liveConfigs.push(config);
69
+ }
70
+ return liveConfigs;
71
+ }
72
+
73
+ /**
74
+ * Mirror disk `modelCosts` rows into provider rows the live config already
75
+ * knows. Providers added by the external edit are left out of the live config
76
+ * (they would change the routing surface); their overlays still become active
77
+ * because the registry below is refreshed from the disk config.
78
+ */
79
+ function adoptDiskModelCosts(live: OcxConfig, disk: OcxConfig): void {
80
+ if (!live.providers || !disk.providers) return;
81
+ for (const [name, diskProvider] of Object.entries(disk.providers)) {
82
+ const liveProvider = live.providers[name];
83
+ if (!liveProvider) continue;
84
+ if (diskProvider?.modelCosts === undefined) {
85
+ delete liveProvider.modelCosts;
86
+ } else {
87
+ liveProvider.modelCosts = structuredClone(diskProvider.modelCosts);
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Remember provider rows present on disk that at least one live routing
94
+ * config does NOT own. They stay out of the live configs that lack them
95
+ * (adding them would change the routing surface), but
96
+ * `persistConfigUnlocked` merges them back at serialization so ANY owner's
97
+ * unrelated save preserves a provider row that another active owner owns.
98
+ *
99
+ * A row is preserved when it is missing from at least one live config: the
100
+ * writer that lacks it would otherwise erase it. Rows every live config owns
101
+ * need no protection because every writer carries them from its own config.
102
+ */
103
+ function rememberDiskOnlyProviders(liveConfigs: readonly OcxConfig[], disk: OcxConfig): void {
104
+ const preserved: Record<string, OcxProviderConfig> = {};
105
+ if (disk.providers && liveConfigs.length > 0) {
106
+ for (const [name, provider] of Object.entries(disk.providers)) {
107
+ if (!provider) continue;
108
+ const ownedByAll = liveConfigs.every(live => Object.hasOwn(live.providers ?? {}, name));
109
+ if (!ownedByAll) preserved[name] = structuredClone(provider);
110
+ }
111
+ }
112
+ setPreservedDiskOnlyProviders(Object.keys(preserved).length > 0 ? preserved : null);
113
+ // Update only registered server owners. One-shot callers without a lease do
114
+ // not become deletion authorities merely by asking for a reconciliation.
115
+ for (const live of liveConfigs) refreshPreservedProviderOwner(live, disk);
116
+ }
117
+
118
+ /**
119
+ * Re-read the persisted config and recompute the disk-only provider
120
+ * preservation registry against the CURRENT owner set. Used when an owner
121
+ * stops but others remain: a newer owner may have owned a provider that an
122
+ * older owner still treats as disk-only, and the registry must reflect the
123
+ * remaining owners rather than the stale pre-stop snapshot.
124
+ *
125
+ * A missing or transiently invalid file leaves the registry untouched so a
126
+ * bad write cannot erase preservation state.
127
+ */
128
+ function recomputePreservedDiskOnlyProviders(): void {
129
+ const diagnostics = readConfigDiagnostics();
130
+ if (diagnostics.source !== "file") return;
131
+ const liveConfigs = liveConfigsForPreservation();
132
+ if (liveConfigs.length > 0) {
133
+ rememberDiskOnlyProviders(liveConfigs, diagnostics.config);
134
+ } else {
135
+ // No live routing configuration remains to protect disk-only providers;
136
+ // clear the cache so a later save cannot resurrect externally deleted rows.
137
+ setPreservedDiskOnlyProviders(null);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Re-read the persisted config and make external overlay edits live.
143
+ *
144
+ * Returns `false` (and leaves the registry untouched) when the file is missing
145
+ * or invalid, so a transient bad write cannot wipe display-only prices.
146
+ */
147
+ export function reconcileUserCostOverlaysFromDisk(liveConfig?: OcxConfig | null): boolean {
148
+ const diagnostics = readConfigDiagnostics();
149
+ if (diagnostics.source !== "file") return false;
150
+ const disk = diagnostics.config;
151
+ if (liveConfig) {
152
+ adoptDiskModelCosts(liveConfig, disk);
153
+ // A one-shot caller is not the whole process. Existing server owners still
154
+ // participate in global preservation or this call can drop protection for
155
+ // a provider absent from an older live projection.
156
+ rememberDiskOnlyProviders(liveConfigsForPreservation(liveConfig), disk);
157
+ } else {
158
+ // No live routing config was supplied: mirror the owners path so a stale
159
+ // preservation registry cannot resurrect externally deleted providers on
160
+ // the next saveConfig. Registered owners still protect disk-only rows;
161
+ // without any, preservation is cleared.
162
+ const liveConfigs = liveConfigsForPreservation();
163
+ if (liveConfigs.length > 0) {
164
+ for (const live of liveConfigs) adoptDiskModelCosts(live, disk);
165
+ rememberDiskOnlyProviders(liveConfigs, disk);
166
+ } else {
167
+ setPreservedDiskOnlyProviders(null);
168
+ }
169
+ }
170
+ // Refresh from the DISK config: overlays for providers only added by the
171
+ // external edit are display-only and must still resolve for historical rows.
172
+ refreshUserCostOverlays(disk);
173
+ return true;
174
+ }
175
+
176
+ function reconcileForOwners(): void {
177
+ const diagnostics = readConfigDiagnostics();
178
+ if (diagnostics.source !== "file") {
179
+ if (diagnostics.source === "fallback") invalidReconcileCount += 1;
180
+ return;
181
+ }
182
+ const disk = diagnostics.config;
183
+ const liveConfigs = liveConfigsForPreservation();
184
+ if (liveConfigs.length > 0) {
185
+ for (const live of liveConfigs) adoptDiskModelCosts(live, disk);
186
+ rememberDiskOnlyProviders(liveConfigs, disk);
187
+ } else {
188
+ // Overlay-only refresh with no live routing configuration: do not treat
189
+ // every disk provider as protected, and do not let a stale preservation
190
+ // cache resurrect providers that were intentionally removed.
191
+ setPreservedDiskOnlyProviders(null);
192
+ }
193
+ refreshUserCostOverlays(disk);
194
+ }
195
+
196
+ /** Test-only observation that proves the timer actually read an invalid config fallback. */
197
+ export function userCostOverlayInvalidReconcileCountForTests(): number {
198
+ return invalidReconcileCount;
199
+ }
200
+
201
+ /** Smallest poll interval across all active owners (the effective cadence). */
202
+ function effectiveIntervalMs(): number {
203
+ let min = Number.POSITIVE_INFINITY;
204
+ for (const interval of ownerIntervals.values()) {
205
+ if (interval < min) min = interval;
206
+ }
207
+ return min === Number.POSITIVE_INFINITY ? USER_COST_OVERLAY_RECONCILE_INTERVAL_MS : min;
208
+ }
209
+
210
+ /**
211
+ * Keep the shared timer on the effective minimum cadence across owners. A
212
+ * later owner may request a smaller interval, which restarts the timer; when
213
+ * an owner stops, the cadence relaxes back up to the smallest still-active
214
+ * owner's request. The timer therefore never outlives or ignores an owner's
215
+ * faster polling need, and never polls faster than the active set requires.
216
+ */
217
+ function syncReconcileTimer(): void {
218
+ const intervalMs = effectiveIntervalMs();
219
+ if (reconcileTimer && reconcileTimerMs === intervalMs) return;
220
+ if (reconcileTimer) clearInterval(reconcileTimer);
221
+ reconcileTimer = setInterval(() => {
222
+ const stamp = configStamp();
223
+ if (!stamp) return;
224
+ if (
225
+ lastStamp
226
+ && lastStamp.mtimeMs === stamp.mtimeMs
227
+ && lastStamp.size === stamp.size
228
+ && lastStamp.ctimeMs === stamp.ctimeMs
229
+ && lastStamp.ino === stamp.ino
230
+ ) return;
231
+ lastStamp = stamp;
232
+ try {
233
+ reconcileForOwners();
234
+ } catch {
235
+ // Display-only reconciliation must never take the proxy down.
236
+ }
237
+ }, intervalMs);
238
+ reconcileTimer.unref?.();
239
+ reconcileTimerMs = intervalMs;
240
+ }
241
+
242
+ /**
243
+ * Start the stat-based reconciler. Each call registers an owner lease; the
244
+ * shared timer starts with the first owner and stops when the last owner's
245
+ * `stop()` releases it.
246
+ */
247
+ export function startUserCostOverlayReconciler(
248
+ options: { intervalMs?: number; liveConfig?: OcxConfig | null } = {},
249
+ ): { stop(): void } {
250
+ const token = Symbol("user-cost-overlay-reconciler");
251
+ const liveConfig = options.liveConfig ?? null;
252
+ owners.set(token, liveConfig);
253
+ ownerIntervals.set(token, options.intervalMs ?? USER_COST_OVERLAY_RECONCILE_INTERVAL_MS);
254
+ if (liveConfig) registerPreservedProviderOwner(liveConfig);
255
+ syncReconcileTimer();
256
+ return {
257
+ stop() {
258
+ const ownedConfig = owners.get(token) ?? null;
259
+ owners.delete(token);
260
+ ownerIntervals.delete(token);
261
+ if (ownedConfig) unregisterPreservedProviderOwner(ownedConfig);
262
+ if (owners.size === 0) {
263
+ if (reconcileTimer) clearInterval(reconcileTimer);
264
+ reconcileTimer = null;
265
+ reconcileTimerMs = 0;
266
+ lastStamp = null;
267
+ // No live routing config remains, so nothing can keep disk-only
268
+ // providers alive: clear the cache to prevent a later save from
269
+ // resurrecting an externally deleted provider.
270
+ setPreservedDiskOnlyProviders(null);
271
+ return;
272
+ }
273
+ syncReconcileTimer();
274
+ // Other owners remain: recompute preservation against their live
275
+ // configs. A newer owner may have owned providers that an older owner
276
+ // still sees as disk-only; without this recompute the older owner's next
277
+ // unrelated save could erase them.
278
+ try {
279
+ recomputePreservedDiskOnlyProviders();
280
+ } catch {
281
+ // Preservation is display-only; a failed recompute must not break stop.
282
+ }
283
+ },
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Process-wide stop: releases EVERY owner's lease and the shared timer.
289
+ * Intended for tests and full process teardown. A per-server shutdown must
290
+ * call its own owner-scoped `stop()` handle instead — this function would
291
+ * otherwise tear down reconciliation (and disk-only preservation) for every
292
+ * other server still running in the same process.
293
+ */
294
+ export function stopUserCostOverlayReconciler(): void {
295
+ for (const config of owners.values()) {
296
+ if (config) unregisterPreservedProviderOwner(config);
297
+ }
298
+ owners.clear();
299
+ ownerIntervals.clear();
300
+ if (reconcileTimer) clearInterval(reconcileTimer);
301
+ reconcileTimer = null;
302
+ reconcileTimerMs = 0;
303
+ lastStamp = null;
304
+ // No live routing config remains; drop preservation so a later save cannot
305
+ // resurrect externally deleted providers.
306
+ setPreservedDiskOnlyProviders(null);
307
+ }
308
+
309
+ /** Test-only reset for module-global reconciler state. */
310
+ export function resetUserCostOverlayReconcilerForTests(): void {
311
+ stopUserCostOverlayReconciler();
312
+ invalidReconcileCount = 0;
313
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Runtime registry for user-configured provider cost overlays
3
+ * (`providers.<name>.modelCosts` in config.json — per-model prices in ocx's
4
+ * flat `modelXxx` convention, mirroring opencode's per-model pricing).
5
+ *
6
+ * The usage cost estimator stays pure: it receives overlays as parameters and
7
+ * defaults to this registry, which is refreshed at the config chokepoints
8
+ * (loadConfig and every persist path). A refresh that actually changes the
9
+ * rows replaces the active array with a NEW identity and bumps a version
10
+ * counter, so the estimator's memo and the /api/usage summary cache skip stale
11
+ * rows without cross-module invalidation. Refreshes with byte-identical rows
12
+ * are no-ops: config is reloaded at many chokepoints and an unchanged reload
13
+ * must not churn the version (see refreshUserCostOverlays). The configured
14
+ * provider-name set is part of the change identity: adding or removing a
15
+ * provider changes which names may collapse to a label base in the resolver,
16
+ * so it bumps the version even when no overlay row changed.
17
+ *
18
+ * Display-time estimation only — these rows never affect billing.
19
+ */
20
+ import type { OcxConfig, OcxProviderConfig, ProviderCostOverlay } from "../types";
21
+ import { MAX_COST4_RATE, type ExpectedPriceOverlay } from "./expected-prices";
22
+ import { redactSecretString } from "../lib/redact";
23
+
24
+ const EMPTY: readonly ExpectedPriceOverlay[] = [];
25
+
26
+ let active: readonly ExpectedPriceOverlay[] = EMPTY;
27
+ let activeSignature = "";
28
+ let activeConfigured = new Set<string>();
29
+ let version = 0;
30
+ let preservedDiskOnlyProviders: Record<string, OcxProviderConfig> | null = null;
31
+
32
+ /**
33
+ * Preservation owner metadata rides through the shallow config projections in
34
+ * config.ts via an enumerable SYMBOL key. JSON.stringify ignores symbol keys,
35
+ * so the tag is process-local only and can never reach config.json or a DTO.
36
+ */
37
+ const PRESERVATION_OWNER_STATE = Symbol("opencodex.user-cost-overlay-preservation-owner");
38
+ const PERSISTED_PROVIDER_DELETIONS = Symbol("opencodex.persisted-provider-deletions");
39
+
40
+ type PreservationOwnerState = {
41
+ refs: number;
42
+ config: OcxConfig;
43
+ ownedProviders: Set<string>;
44
+ };
45
+
46
+ type PreservationTaggedConfig = OcxConfig & {
47
+ [PRESERVATION_OWNER_STATE]?: PreservationOwnerState;
48
+ [PERSISTED_PROVIDER_DELETIONS]?: readonly string[];
49
+ };
50
+
51
+ const preservationOwnerStates = new Set<PreservationOwnerState>();
52
+
53
+ function providerNames(config: OcxConfig): Set<string> {
54
+ return new Set(Object.keys(config.providers ?? {}));
55
+ }
56
+
57
+ /** Register one active live-config owner. Multiple server leases may share one config object. */
58
+ export function registerPreservedProviderOwner(config: OcxConfig): void {
59
+ const tagged = config as PreservationTaggedConfig;
60
+ const existing = tagged[PRESERVATION_OWNER_STATE];
61
+ if (existing && preservationOwnerStates.has(existing)) {
62
+ existing.refs += 1;
63
+ existing.ownedProviders = providerNames(config);
64
+ return;
65
+ }
66
+ const state: PreservationOwnerState = {
67
+ refs: 1,
68
+ config,
69
+ ownedProviders: providerNames(config),
70
+ };
71
+ // Enumerable is deliberate: projectCustomModelCatalogMigration and the live
72
+ // binding guard use object spread, which must carry this process-local tag to
73
+ // the serialization view. Symbol keys are still omitted by JSON.stringify.
74
+ Object.defineProperty(tagged, PRESERVATION_OWNER_STATE, {
75
+ value: state,
76
+ enumerable: true,
77
+ configurable: true,
78
+ });
79
+ preservationOwnerStates.add(state);
80
+ }
81
+
82
+ /**
83
+ * Refresh a registered owner's provider snapshot from a successful disk read.
84
+ *
85
+ * A provider still present on disk remains owned even if the live object
86
+ * temporarily omits it: management DELETE has an async-import gap between
87
+ * mutating the live config and committing the write, and a reconciler tick in
88
+ * that gap must not erase the deletion authority. Conversely, providers that
89
+ * disappeared from disk are no longer considered owned, and newly present
90
+ * providers are adopted only when this live config actually has the row.
91
+ */
92
+ export function refreshPreservedProviderOwner(config: OcxConfig, disk: OcxConfig): void {
93
+ const state = (config as PreservationTaggedConfig)[PRESERVATION_OWNER_STATE];
94
+ if (!state || !preservationOwnerStates.has(state)) return;
95
+ const diskNames = providerNames(disk);
96
+ for (const name of [...state.ownedProviders]) {
97
+ if (!diskNames.has(name)) state.ownedProviders.delete(name);
98
+ }
99
+ for (const name of Object.keys(config.providers ?? {})) {
100
+ if (diskNames.has(name)) state.ownedProviders.add(name);
101
+ }
102
+ }
103
+
104
+ /** Release one active live-config owner lease. */
105
+ export function unregisterPreservedProviderOwner(config: OcxConfig): void {
106
+ const tagged = config as PreservationTaggedConfig;
107
+ const state = tagged[PRESERVATION_OWNER_STATE];
108
+ if (!state || !preservationOwnerStates.has(state)) return;
109
+ state.refs -= 1;
110
+ if (state.refs > 0) return;
111
+ preservationOwnerStates.delete(state);
112
+ if (tagged[PRESERVATION_OWNER_STATE] === state) {
113
+ delete tagged[PRESERVATION_OWNER_STATE];
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Remember provider rows that exist on disk but are intentionally absent from
119
+ * the live routing config (added by an external editor after the proxy booted).
120
+ * They are merged back at the config serialization boundary so an unrelated
121
+ * in-process save cannot erase the external provider and its overlay.
122
+ */
123
+ export function setPreservedDiskOnlyProviders(
124
+ providers: Record<string, OcxProviderConfig> | null,
125
+ ): void {
126
+ preservedDiskOnlyProviders = providers;
127
+ }
128
+
129
+ /**
130
+ * Commit an explicit provider deletion only after the persisted serialization
131
+ * view has been accepted by the config write path. This clears the stale
132
+ * preservation row and removes the provider from every active live projection,
133
+ * so a second owner cannot resurrect it on a later unrelated save.
134
+ */
135
+ function commitPersistedProviderDeletions(config: OcxConfig): void {
136
+ const tagged = config as PreservationTaggedConfig;
137
+ const deletions = tagged[PERSISTED_PROVIDER_DELETIONS];
138
+ if (!deletions || deletions.length === 0) return;
139
+
140
+ const deleted = new Set(deletions);
141
+ if (preservedDiskOnlyProviders) {
142
+ const next = { ...preservedDiskOnlyProviders };
143
+ for (const name of deleted) delete next[name];
144
+ preservedDiskOnlyProviders = Object.keys(next).length > 0 ? next : null;
145
+ }
146
+
147
+ for (const state of preservationOwnerStates) {
148
+ for (const name of deleted) {
149
+ state.ownedProviders.delete(name);
150
+ if (state.config.providers) delete state.config.providers[name];
151
+ }
152
+ }
153
+ delete tagged[PERSISTED_PROVIDER_DELETIONS];
154
+ }
155
+
156
+ /**
157
+ * A serialization view of `config` that keeps externally added providers on
158
+ * disk without adding them to live routing state. Live providers win when a
159
+ * name exists in both maps.
160
+ *
161
+ * A registered owner also carries the provider names it previously owned. If
162
+ * that owner now omits one of those providers, the omission is an intentional
163
+ * in-process deletion rather than an old projection that never knew the row.
164
+ * Suppress only those names from preservation for this write. The suppression
165
+ * is committed globally only after refreshUserCostOverlays receives the
166
+ * successfully persisted view, so a failed atomic write cannot destroy the
167
+ * preservation safety net.
168
+ */
169
+ export function withPreservedDiskOnlyProviders(config: OcxConfig): OcxConfig {
170
+ const tagged = config as PreservationTaggedConfig;
171
+ const owner = tagged[PRESERVATION_OWNER_STATE];
172
+ const deletedProviders = owner && preservationOwnerStates.has(owner)
173
+ ? [...owner.ownedProviders].filter(name => !Object.hasOwn(config.providers ?? {}, name))
174
+ : [];
175
+ const deletedSet = new Set(deletedProviders);
176
+
177
+ let preserved: Record<string, OcxProviderConfig> | null = null;
178
+ if (preservedDiskOnlyProviders) {
179
+ const filtered = Object.entries(preservedDiskOnlyProviders)
180
+ .filter(([name]) => !deletedSet.has(name));
181
+ if (filtered.length > 0) preserved = Object.fromEntries(filtered);
182
+ }
183
+
184
+ let persisted: OcxConfig;
185
+ if (preserved && Object.keys(preserved).length > 0) {
186
+ persisted = {
187
+ ...config,
188
+ providers: {
189
+ ...preserved,
190
+ ...config.providers,
191
+ },
192
+ };
193
+ } else if (deletedProviders.length > 0) {
194
+ // Return a distinct object so the persisted-deletion marker remains scoped
195
+ // to this serialization attempt rather than the long-lived live config.
196
+ persisted = { ...config };
197
+ } else {
198
+ return config;
199
+ }
200
+
201
+ if (deletedProviders.length > 0) {
202
+ Object.defineProperty(persisted as PreservationTaggedConfig, PERSISTED_PROVIDER_DELETIONS, {
203
+ value: deletedProviders,
204
+ enumerable: false,
205
+ configurable: true,
206
+ });
207
+ }
208
+ return persisted;
209
+ }
210
+
211
+ /** Test-only reset for the preserved disk-only provider registry. */
212
+ export function resetPreservedDiskOnlyProvidersForTests(): void {
213
+ preservedDiskOnlyProviders = null;
214
+ preservationOwnerStates.clear();
215
+ }
216
+
217
+ /** Exact four fields accepted for a user-configured cost tuple. */
218
+ export const COST4_RATE_KEYS = ["input", "output", "cacheRead", "cacheWrite"] as const;
219
+
220
+ /** Shared per-rate predicate used by config validation, display sanitization, and runtime lifting. */
221
+ export function isValidCost4Rate(rate: unknown): rate is number {
222
+ return typeof rate === "number"
223
+ && Number.isFinite(rate)
224
+ && rate >= 0
225
+ && rate <= MAX_COST4_RATE;
226
+ }
227
+
228
+ /** True when `value` is a complete cost entry: all four rates are non-negative finite numbers. */
229
+ function validCost4(value: unknown): value is ProviderCostOverlay {
230
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
231
+ const entry = value as Record<string, unknown>;
232
+ return COST4_RATE_KEYS.every(key => isValidCost4Rate(entry[key]));
233
+ }
234
+
235
+ /**
236
+ * Rebuild the active user-overlay rows from the current config. Malformed rows
237
+ * are skipped (config validation reports them separately); a provider with no
238
+ * overlay contributes nothing.
239
+ */
240
+ export function refreshUserCostOverlays(config: OcxConfig): void {
241
+ // Only persisted serialization views carry PERSISTED_PROVIDER_DELETIONS.
242
+ // Committing here means changed writes update owner state only after the
243
+ // atomic write succeeds, while byte-identical successful saves also converge.
244
+ commitPersistedProviderDeletions(config);
245
+
246
+ const rows: ExpectedPriceOverlay[] = [];
247
+ const providers = config.providers;
248
+ if (providers) {
249
+ for (const [providerName, provider] of Object.entries(providers)) {
250
+ const costs = provider?.modelCosts;
251
+ if (!costs || typeof costs !== "object" || Array.isArray(costs)) continue;
252
+ for (const [modelId, cost4] of Object.entries(costs)) {
253
+ if (!modelId.trim() || !validCost4(cost4)) continue;
254
+ rows.push({
255
+ provider: providerName,
256
+ modelId,
257
+ // Copy ONLY the four validated rate fields: a hand-edited row may
258
+ // carry extra properties (e.g. a misplaced apiKey) that must never
259
+ // reach display estimates or /api/logs through the registry.
260
+ cost4: {
261
+ input: cost4.input,
262
+ output: cost4.output,
263
+ cacheRead: cost4.cacheRead,
264
+ cacheWrite: cost4.cacheWrite,
265
+ },
266
+ // Display provenance only: redact token-shaped provider/model ids so
267
+ // the source string can never echo a pasted key. Matching still uses
268
+ // the raw fields, and the change-detection signature below MUST keep
269
+ // them raw — distinct ids would otherwise collapse to "[REDACTED]"
270
+ // and skip the version bump.
271
+ source: `config:providers.${redactSecretString(providerName)}.modelCosts[${redactSecretString(modelId)}]`,
272
+ verifiedAt: "user-configured",
273
+ status: "verified",
274
+ });
275
+ }
276
+ }
277
+ }
278
+ // Config is re-loaded at many chokepoints (server start, migrations, persist
279
+ // paths). Bumping the version on every load — even when nothing changed —
280
+ // would invalidate the /api/usage summary cache on unrelated reloads and
281
+ // churn the cost memo. Only a real overlay change bumps the version, so the
282
+ // cache survives reloads of an unchanged config.
283
+ // The signature MUST compare the raw matching fields: two different
284
+ // secret-shaped ids would both redact to "[REDACTED]" and falsely look
285
+ // unchanged, skipping the version bump and serving stale estimates. The
286
+ // signature is process-local state and is never serialized to a response;
287
+ // only the display `source` above is redacted.
288
+ // The configured provider-name set is part of the identity too: adding or
289
+ // removing a provider (even one without an overlay) changes which names are
290
+ // allowed to collapse to a label base, so the resolver memo and the
291
+ // /api/usage summary cache must be invalidated on that change as well.
292
+ const configuredNames = Object.keys(providers ?? {}).sort();
293
+ const signature = `${JSON.stringify(configuredNames)}\u0000${JSON.stringify(rows)}`;
294
+ if (signature === activeSignature) return;
295
+ activeSignature = signature;
296
+ active = rows;
297
+ activeConfigured = new Set(configuredNames);
298
+ version++;
299
+ }
300
+
301
+ /** Active user-configured overlay rows (stable identity until the next refresh). */
302
+ export function activeUserCostOverlays(): readonly ExpectedPriceOverlay[] {
303
+ return active;
304
+ }
305
+
306
+ /** Monotonic version bumped on every refresh; used by the estimator memo key. */
307
+ export function userCostOverlayVersion(): number {
308
+ return version;
309
+ }
310
+
311
+ /** Configured provider names from the last refresh (pricing-namespace identity). */
312
+ export function activeConfiguredProviders(): ReadonlySet<string> {
313
+ return activeConfigured;
314
+ }
@@ -3,7 +3,7 @@ import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fin
3
3
  import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
4
4
  import { sidecarEnter } from "../lib/sidecar-tracker";
5
5
  import { fetchWithResetRetry } from "../lib/upstream-retry";
6
- import { getValidAccessToken } from "../oauth";
6
+ import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
7
7
  import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
8
8
  import type { DescribeOutcome, VisionSettings } from "./describe";
9
9
 
@@ -67,8 +67,8 @@ export async function parseAnthropicVisionSSE(res: Response): Promise<DescribeOu
67
67
  const delta = isRecord(data.delta) ? data.delta : {};
68
68
  if (delta.type === "text_delta" && typeof delta.text === "string") text += delta.text;
69
69
  } else if (data.type === "error") {
70
- const error = isRecord(data.error) ? data.error : {};
71
- terminalError = typeof error.message === "string" ? error.message : "anthropic vision sidecar stream error";
70
+ // Provider-authored stream errors can contain credentials, paths, or response bodies.
71
+ terminalError = "anthropic vision sidecar stream error";
72
72
  }
73
73
  };
74
74
 
@@ -116,7 +116,7 @@ export async function describeImageAnthropic(
116
116
  try {
117
117
  token = await getValidAccessToken(providerName);
118
118
  } catch (error) {
119
- return { text: "", error: `anthropic vision sidecar auth failed: ${error instanceof Error ? error.message : String(error)}` };
119
+ return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(error)}` };
120
120
  }
121
121
 
122
122
  const headers: Record<string, string> = {
@@ -166,7 +166,11 @@ export async function describeImageAnthropic(
166
166
  if (!res.ok) {
167
167
  const responseText = await res.text().catch(() => "");
168
168
  console.warn(`[vision] anthropic sidecar HTTP ${res.status} (${Date.now() - startedAt}ms)`);
169
- return { text: "", error: `anthropic vision sidecar HTTP ${res.status}: ${responseText.slice(0, 200)}` };
169
+ if (res.status === 401) {
170
+ return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(responseText))}` };
171
+ }
172
+ // Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
173
+ return { text: "", error: `anthropic vision sidecar HTTP ${res.status}` };
170
174
  }
171
175
  const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
172
176
  try {
@@ -177,7 +181,7 @@ export async function describeImageAnthropic(
177
181
  } catch (error) {
178
182
  const kind = error instanceof Error && error.name === "TimeoutError" ? "timeout" : "connect_error";
179
183
  console.warn(`[vision] anthropic sidecar ${kind} (${Date.now() - startedAt}ms)`);
180
- return { text: "", error: error instanceof Error ? error.message : String(error) };
184
+ return { text: "", error: `anthropic vision sidecar ${kind}` };
181
185
  } finally {
182
186
  sidecarExit();
183
187
  linkedSignal.cleanup();