@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,155 @@
1
+ /**
2
+ * What opencodex remembers about a client between operations.
3
+ *
4
+ * Two hashes, because the two questions are genuinely independent: the FILE
5
+ * hash identifies the exact result for restore and for clients whose writer
6
+ * re-serializes the whole document. The BLOCK hash identifies OMP's surgically
7
+ * patched fragment and detects catalog drift. One hash cannot safely answer
8
+ * both questions in a shared client config.
9
+ *
10
+ * Design of record: devlog/_fin/260802_client_toggle_api/021 §2.
11
+ */
12
+ import { createHash } from "node:crypto";
13
+ import { mkdirSync, readFileSync } from "node:fs";
14
+ import { dirname, join } from "node:path";
15
+ import type { ManagedContribution, ManagedFragment } from "../clients/config-export";
16
+ import { atomicWriteFile, getConfigDir } from "../config";
17
+ import type { IntegrationClientId } from "./registry";
18
+
19
+ /** 16 hex chars — the same shape as the Claude Desktop applied fingerprint. */
20
+ export function fingerprint(text: string): string {
21
+ return createHash("sha256").update(text).digest("hex").slice(0, 16);
22
+ }
23
+
24
+ /**
25
+ * Canonicalize JSON object members recursively for semantic comparisons.
26
+ * Arrays stay ordered because their position can carry configuration meaning.
27
+ */
28
+ function semanticJsonValue(value: unknown): unknown {
29
+ if (Array.isArray(value)) return value.map(semanticJsonValue);
30
+ if (value === null || typeof value !== "object") return value;
31
+
32
+ const record = value as Record<string, unknown>;
33
+ return Object.fromEntries(
34
+ Object.keys(record)
35
+ .sort()
36
+ .map(key => [key, semanticJsonValue(record[key])]),
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Stable semantic bytes of a contribution. Third-party clients may
42
+ * re-serialize JSON object members in a different order; that formatting-only
43
+ * rewrite must not look like a protected-value edit.
44
+ */
45
+ export function semanticContribution(contribution: ManagedContribution): string {
46
+ const sorted = [...contribution.fragments].sort((a, b) => {
47
+ const left = a.path.join("\u0000");
48
+ const right = b.path.join("\u0000");
49
+ return left < right ? -1 : left > right ? 1 : 0;
50
+ });
51
+ return JSON.stringify(sorted.map(fragment => [fragment.path, semanticJsonValue(fragment.value)]));
52
+ }
53
+
54
+ /**
55
+ * Legacy-compatible bytes used by existing persisted fingerprints. Fragment
56
+ * paths are stable, while nested object insertion order remains exact.
57
+ */
58
+ export function canonicalContribution(contribution: ManagedContribution): string {
59
+ const sorted = [...contribution.fragments].sort((a, b) => {
60
+ const left = a.path.join("\u0000");
61
+ const right = b.path.join("\u0000");
62
+ return left < right ? -1 : left > right ? 1 : 0;
63
+ });
64
+ return JSON.stringify(sorted.map(fragment => [fragment.path, fragment.value]));
65
+ }
66
+
67
+ export interface OwnershipRecord {
68
+ clientId: IntegrationClientId;
69
+ configPath: string;
70
+ /** Hash of the WHOLE file as we left it — protects whole-file restore. */
71
+ fileFingerprint: string;
72
+ /** Hash of our contribution — detects catalog/port drift. */
73
+ blockFingerprint: string;
74
+ /** Key-order-independent companion for JSON clients that normalize objects. */
75
+ semanticBlockFingerprint?: string;
76
+ /**
77
+ * Hash of the fields the client must not rewrite. Present only when a
78
+ * client has explicitly declared runtime-derived paths below.
79
+ */
80
+ protectedBlockFingerprint?: string;
81
+ /** Key-order-independent companion to `protectedBlockFingerprint`. */
82
+ semanticProtectedBlockFingerprint?: string;
83
+ /**
84
+ * Exact document paths a client may derive after apply. These are recorded
85
+ * per operation so later catalog changes cannot widen an older grant.
86
+ */
87
+ refreshablePaths?: readonly (readonly string[])[];
88
+ /** The exact paths we own. Removal touches these and nothing else. */
89
+ fragmentPaths: readonly (readonly string[])[];
90
+ /**
91
+ * Containers this apply had to CREATE, `\0`-joined.
92
+ *
93
+ * Kimi's `models` map exists only because our aliases need somewhere to
94
+ * live. Without this, disable left it behind as an empty container in a file
95
+ * the user never asked us to restructure — while a `providers: {}` the user
96
+ * wrote themselves must survive. Optional because records written before
97
+ * this field existed simply prune nothing, which is the old behavior.
98
+ */
99
+ createdContainers?: readonly string[];
100
+ appliedAt: string;
101
+ opId: string;
102
+ }
103
+
104
+ /**
105
+ * The integrations directory itself. Every primitive takes THIS path, never a
106
+ * config root, so a caller cannot accidentally produce
107
+ * `<root>/integrations/integrations` by passing an already-resolved value.
108
+ */
109
+ export function integrationsDir(configDir: string = getConfigDir()): string {
110
+ return join(configDir, "integrations");
111
+ }
112
+
113
+ /** `atomicWriteFile` does not create parents. */
114
+ export function ensureDir(filePath: string): void {
115
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
116
+ }
117
+
118
+ function recordsPath(dir: string): string {
119
+ return join(dir, "records.json");
120
+ }
121
+
122
+ export function readRecords(
123
+ dir: string = integrationsDir(),
124
+ ): Partial<Record<IntegrationClientId, OwnershipRecord>> {
125
+ try {
126
+ const parsed: unknown = JSON.parse(readFileSync(recordsPath(dir), "utf8"));
127
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
128
+ return parsed as Partial<Record<IntegrationClientId, OwnershipRecord>>;
129
+ }
130
+ } catch {
131
+ // Missing or corrupt records mean "we remember nothing", which the
132
+ // classifier reads as a conflict for an existing block. Fail closed: an
133
+ // unreadable memory is never permission to delete.
134
+ }
135
+ return {};
136
+ }
137
+
138
+ export function writeRecord(record: OwnershipRecord, dir: string = integrationsDir()): void {
139
+ const all = readRecords(dir);
140
+ all[record.clientId] = record;
141
+ ensureDir(recordsPath(dir));
142
+ atomicWriteFile(recordsPath(dir), `${JSON.stringify(all, null, 2)}\n`);
143
+ }
144
+
145
+ export function deleteRecord(clientId: IntegrationClientId, dir: string = integrationsDir()): void {
146
+ const all = readRecords(dir);
147
+ if (!(clientId in all)) return;
148
+ delete all[clientId];
149
+ ensureDir(recordsPath(dir));
150
+ atomicWriteFile(recordsPath(dir), `${JSON.stringify(all, null, 2)}\n`);
151
+ }
152
+
153
+ export function fragmentPathsOf(contribution: ManagedContribution): readonly (readonly string[])[] {
154
+ return contribution.fragments.map((fragment: ManagedFragment) => fragment.path);
155
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Where each file-toggle client keeps its config, and what we are allowed to do
3
+ * to it.
4
+ *
5
+ * The export registry (src/clients/config-export.ts) says how to RENDER a
6
+ * client's config. This one says where it lives, how to tell whether the client
7
+ * is installed at all, and whether a remote bind is safe for it.
8
+ *
9
+ * Design of record: devlog/_fin/260802_client_toggle_api/021 §1.
10
+ */
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ import {
14
+ EXPORT_CLIENTS,
15
+ clineConfigPath,
16
+ clineHomeDir,
17
+ dshConfigPath,
18
+ dshHomeDir,
19
+ gajaeConfigPath,
20
+ gajaeHomeDir,
21
+ hermesConfigPath,
22
+ hermesHomeDir,
23
+ kimiConfigPath,
24
+ kimiHomeDir,
25
+ mcodeConfigPath,
26
+ mcodeHomeDir,
27
+ ompAgentDir,
28
+ ompModelsConfigPath,
29
+ opencodeGlobalConfigPath,
30
+ openclawConfigPath,
31
+ openclawHomeDir,
32
+ piAgentDir,
33
+ piConfigPath,
34
+ primeAgentDir,
35
+ primeConfigPath,
36
+ zcodeConfigPath,
37
+ zcodeHomeDir,
38
+ type ExportClientId,
39
+ } from "../clients/config-export";
40
+
41
+ /**
42
+ * Readability alias. WP1 owns the type; this never introduces a second one, so
43
+ * the dependency only ever points backwards.
44
+ */
45
+ export type IntegrationClientId = ExportClientId;
46
+
47
+ export interface IntegrationClientSpec {
48
+ id: IntegrationClientId;
49
+ /** The client's config file, honoring that client's own environment override. */
50
+ configPath: (env?: NodeJS.ProcessEnv, home?: string) => string;
51
+ /** Directory whose existence is the cheap "is it installed?" signal. */
52
+ detectDir: (env?: NodeJS.ProcessEnv, home?: string) => string;
53
+ /** Patch only this block-map YAML leaf; never re-render the shared file. */
54
+ sourcePreservingYaml?: { path: readonly string[] };
55
+ /** Coordinate the complete mutation through a sibling config lock. */
56
+ writerLock?: { suffix: ".lock" };
57
+ }
58
+
59
+ /**
60
+ * True when the client has nowhere to put the dedicated admission header a
61
+ * non-loopback bind requires, so a generated config would simply be rejected.
62
+ *
63
+ * Read from the export registry rather than restated here: two lists of the
64
+ * same fact drift, and this one decides whether we write a file that 401s.
65
+ */
66
+ export function isLoopbackOnly(clientId: IntegrationClientId): boolean {
67
+ return EXPORT_CLIENTS[clientId].loopbackOnly;
68
+ }
69
+
70
+ function xdgConfigHome(env: NodeJS.ProcessEnv, home: string): string {
71
+ const xdg = env.XDG_CONFIG_HOME;
72
+ return xdg && xdg.length > 0 ? xdg : join(home, ".config");
73
+ }
74
+
75
+ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientSpec> = {
76
+ opencode: {
77
+ id: "opencode",
78
+ // These take `home` explicitly. The export registry's `destination` reads
79
+ // the real home directory, which is right for telling a user where their
80
+ // file lives and wrong for a writer that a test must be able to redirect.
81
+ configPath: (env = process.env, home = homedir()) => opencodeGlobalConfigPath(env, home),
82
+ detectDir: (env = process.env, home = homedir()) => join(xdgConfigHome(env, home), "opencode"),
83
+ },
84
+ pi: {
85
+ id: "pi",
86
+ configPath: (env = process.env, home = homedir()) => piConfigPath(env, home),
87
+ /*
88
+ * Deliberately not `piAgentDir` unconditionally. Without an override the
89
+ * install signal stays `~/.pi`, which is what it has always been: narrowing
90
+ * it to `~/.pi/agent` would flip a user who has the former without the
91
+ * latter from installed to absent, and this change is about honoring the
92
+ * override, not about redefining detection. With an override there is no
93
+ * parent worth testing — the variable names the agent directory itself — so
94
+ * that directory becomes the signal.
95
+ */
96
+ detectDir: (env = process.env, home = homedir()) =>
97
+ env.PI_CODING_AGENT_DIR?.trim() ? piAgentDir(env, home) : join(home, ".pi"),
98
+ },
99
+ omp: {
100
+ id: "omp",
101
+ configPath: (env = process.env, home = homedir()) => ompModelsConfigPath(env, home),
102
+ detectDir: (env = process.env, home = homedir()) => ompAgentDir(env, home),
103
+ sourcePreservingYaml: { path: ["providers", "opencodex"] },
104
+ },
105
+ hermes: {
106
+ id: "hermes",
107
+ configPath: (env = process.env, home = homedir()) => hermesConfigPath(env, home),
108
+ detectDir: (env = process.env, home = homedir()) => hermesHomeDir(env, home),
109
+ },
110
+ openclaw: {
111
+ id: "openclaw",
112
+ configPath: (env = process.env, home = homedir()) => openclawConfigPath(env, home),
113
+ detectDir: (env = process.env, home = homedir()) => openclawHomeDir(env, home),
114
+ },
115
+ kimi: {
116
+ id: "kimi",
117
+ configPath: (env = process.env, home = homedir()) => kimiConfigPath(env, home),
118
+ detectDir: (env = process.env, home = homedir()) => kimiHomeDir(env, home),
119
+ // Kimi reads credentials only from config.toml — it never consults the
120
+ // environment — so there is no way to point it at a remote bind without
121
+ // serializing the user's key.
122
+ },
123
+ gajae: {
124
+ id: "gajae",
125
+ configPath: (env = process.env, home = homedir()) => gajaeConfigPath(env, home),
126
+ detectDir: (env = process.env, home = homedir()) => gajaeHomeDir(env, home),
127
+ },
128
+ dsh: {
129
+ id: "dsh",
130
+ configPath: (env = process.env, home = homedir()) => dshConfigPath(env, home),
131
+ detectDir: (env = process.env, home = homedir()) => dshHomeDir(env, home),
132
+ sourcePreservingYaml: { path: ["llm-pi-ai", "providers", "opencodex"] },
133
+ writerLock: { suffix: ".lock" },
134
+ },
135
+ mcode: {
136
+ id: "mcode",
137
+ configPath: (env = process.env, home = homedir()) => mcodeConfigPath(env, home),
138
+ detectDir: (env = process.env, home = homedir()) => mcodeHomeDir(env, home),
139
+ writerLock: { suffix: ".lock" },
140
+ },
141
+ zcode: {
142
+ id: "zcode",
143
+ configPath: (env = process.env, home = homedir()) => zcodeConfigPath(env, home),
144
+ detectDir: (env = process.env, home = homedir()) => zcodeHomeDir(env, home),
145
+ },
146
+ prime: {
147
+ id: "prime",
148
+ configPath: (env = process.env, home = homedir()) => primeConfigPath(env, home),
149
+ // The agent directory, not its parent: `PRIME_AGENT_CODING_AGENT_DIR` names
150
+ // that directory directly, so there is no parent to test when the override
151
+ // is set. Same choice as OMP, whose detect signal is `ompAgentDir`.
152
+ detectDir: (env = process.env, home = homedir()) => primeAgentDir(env, home),
153
+ },
154
+ cline: {
155
+ id: "cline",
156
+ configPath: (env = process.env, home = homedir()) => clineConfigPath(env, home),
157
+ detectDir: (env = process.env, home = homedir()) => clineHomeDir(env, home),
158
+ },
159
+ };
160
+
161
+ export const INTEGRATION_CLIENT_IDS: readonly IntegrationClientId[] =
162
+ Object.keys(INTEGRATION_CLIENTS) as IntegrationClientId[];
163
+
164
+ export function isIntegrationClientId(value: string): value is IntegrationClientId {
165
+ return Object.prototype.hasOwnProperty.call(INTEGRATION_CLIENTS, value);
166
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Text serializers for client config documents.
3
+ *
4
+ * Bun 1.3.14 gives us JSON and JSON5 stringify natively, but the two formats a
5
+ * client config actually needs beyond JSON are hand-rendered here:
6
+ *
7
+ * - **TOML** has no `Bun.TOML.stringify` at all.
8
+ * - **YAML** has `Bun.YAML.stringify`, but it emits flow style with no trailing
9
+ * newline (`{a: 1,b: {c: d}}`). That is valid YAML and completely wrong for a
10
+ * file a user opens, so we render block style ourselves.
11
+ *
12
+ * The renderers originally covered only the shallow shapes our builders emit.
13
+ * That was fine while they only ever saw generated documents — but the
14
+ * integration writer parses the USER's whole config and re-serializes it, so a
15
+ * legitimate `null` or a numeric array in a file we never wrote threw straight
16
+ * out of the writer and surfaced as a 500. They now cover the value domains
17
+ * their formats actually allow.
18
+ *
19
+ * What they still refuse is genuinely unrenderable: a function, a symbol, a
20
+ * non-finite number. Those must fail loudly rather than produce bytes a client
21
+ * might misread — and the writer turns the failure into a structured refusal
22
+ * rather than an exception.
23
+ *
24
+ * Design of record: devlog/_fin/260802_client_toggle_api/011_wp1_builders.md.
25
+ */
26
+
27
+ export type ConfigFormat = "json" | "yaml" | "toml" | "json5";
28
+
29
+ /** Thrown when a document holds a value its format cannot represent. */
30
+ export class UnserializableValueError extends Error {}
31
+
32
+ export const FORMAT_MEDIA_TYPE: Record<ConfigFormat, string> = {
33
+ json: "application/json",
34
+ yaml: "application/yaml",
35
+ toml: "application/toml",
36
+ json5: "application/json5",
37
+ };
38
+
39
+ /**
40
+ * Type guard, not a boolean check — the renderers walk `unknown`, so without
41
+ * the predicate every narrowed branch stays `unknown`.
42
+ */
43
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+
47
+ function isYamlScalar(value: unknown): value is string | number | boolean | null {
48
+ return value === null
49
+ || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
50
+ }
51
+
52
+ /**
53
+ * Keep only unambiguous, single-token strings plain. Everything else uses a
54
+ * JSON-compatible double-quoted scalar, which gives deterministic escaping —
55
+ * JSON string escaping is valid YAML 1.2 string escaping.
56
+ */
57
+ function yamlString(value: string): string {
58
+ const plainSafe = value.length > 0
59
+ && value.trim() === value
60
+ && /^[A-Za-z_./][A-Za-z0-9_./-]*$/u.test(value)
61
+ && !/^(?:null|true|false|yes|no|on|off|~|\.nan|[-+]?\.inf)$/iu.test(value);
62
+ return plainSafe ? value : JSON.stringify(value);
63
+ }
64
+
65
+ function yamlScalar(value: string | number | boolean | null): string {
66
+ // `null` renders as the explicit `null` token rather than an empty value:
67
+ // a bare `key:` is also null in YAML, but round-tripping it through other
68
+ // parsers is less predictable than saying so.
69
+ if (value === null) return "null";
70
+ if (typeof value === "string") return yamlString(value);
71
+ if (typeof value === "boolean") return value ? "true" : "false";
72
+ if (Number.isFinite(value)) return String(value);
73
+ throw new UnserializableValueError(`YAML cannot represent the number ${String(value)}`);
74
+ }
75
+
76
+ function yamlEmptyCollection(value: unknown): "[]" | "{}" | undefined {
77
+ if (Array.isArray(value) && value.length === 0) return "[]";
78
+ if (isPlainRecord(value) && Object.keys(value).length === 0) return "{}";
79
+ return undefined;
80
+ }
81
+
82
+ function yamlMapEntryLines(key: string, value: unknown, indent: number): string[] {
83
+ const padding = " ".repeat(indent);
84
+ const renderedKey = yamlString(key);
85
+ if (isYamlScalar(value)) return [`${padding}${renderedKey}: ${yamlScalar(value)}`];
86
+ const empty = yamlEmptyCollection(value);
87
+ if (empty !== undefined) return [`${padding}${renderedKey}: ${empty}`];
88
+ if (Array.isArray(value) || isPlainRecord(value)) {
89
+ return [`${padding}${renderedKey}:`, ...yamlLines(value, indent + 2)];
90
+ }
91
+ throw new UnserializableValueError(`YAML cannot represent the value at key ${JSON.stringify(key)}: ${describeValue(value)}`);
92
+ }
93
+
94
+ /** A map inside a sequence: the first key rides the dash, the rest indent under it. */
95
+ function yamlArrayMapLines(value: Record<string, unknown>, indent: number): string[] {
96
+ const entries = Object.entries(value);
97
+ const padding = " ".repeat(indent);
98
+ if (entries.length === 0) return [`${padding}- {}`];
99
+
100
+ const [first, ...rest] = entries;
101
+ const [firstKey, firstValue] = first!;
102
+ const renderedFirstKey = yamlString(firstKey);
103
+ const firstEmpty = yamlEmptyCollection(firstValue);
104
+ const lines: string[] = [];
105
+ if (isYamlScalar(firstValue)) {
106
+ lines.push(`${padding}- ${renderedFirstKey}: ${yamlScalar(firstValue)}`);
107
+ } else if (firstEmpty !== undefined) {
108
+ lines.push(`${padding}- ${renderedFirstKey}: ${firstEmpty}`);
109
+ } else if (Array.isArray(firstValue) || isPlainRecord(firstValue)) {
110
+ lines.push(`${padding}- ${renderedFirstKey}:`);
111
+ lines.push(...yamlLines(firstValue, indent + 4));
112
+ } else {
113
+ throw new UnserializableValueError(`YAML cannot represent the value at key ${JSON.stringify(firstKey)}: ${describeValue(firstValue)}`);
114
+ }
115
+ for (const [key, child] of rest) lines.push(...yamlMapEntryLines(key, child, indent + 2));
116
+ return lines;
117
+ }
118
+
119
+ function yamlLines(value: unknown, indent: number): string[] {
120
+ const padding = " ".repeat(indent);
121
+ if (isYamlScalar(value)) return [`${padding}${yamlScalar(value)}`];
122
+ if (Array.isArray(value)) {
123
+ if (value.length === 0) return [`${padding}[]`];
124
+ return value.flatMap(item => {
125
+ if (isYamlScalar(item)) return [`${padding}- ${yamlScalar(item)}`];
126
+ if (isPlainRecord(item)) return yamlArrayMapLines(item, indent);
127
+ /*
128
+ * A nested sequence is ordinary YAML (`matrix: [[1,2],[3,4]]`). This
129
+ * used to throw a plain Error carrying `String(item)` — untyped, so the
130
+ * writer could not turn it into a refusal and it surfaced as a 500, and
131
+ * carrying the value's CONTENTS into the message, which a config may
132
+ * not want repeated.
133
+ */
134
+ if (Array.isArray(item)) {
135
+ if (item.length === 0) return [`${padding}- []`];
136
+ return [`${padding}-`, ...yamlLines(item, indent + 2)];
137
+ }
138
+ throw new UnserializableValueError(
139
+ `YAML cannot represent this sequence item: ${describeValue(item)}`,
140
+ );
141
+ });
142
+ }
143
+ if (isPlainRecord(value)) {
144
+ const entries = Object.entries(value);
145
+ if (entries.length === 0) return [`${padding}{}`];
146
+ return entries.flatMap(([key, child]) => yamlMapEntryLines(key, child, indent));
147
+ }
148
+ throw new UnserializableValueError(`YAML cannot represent this value: ${describeValue(value)}`);
149
+ }
150
+
151
+ /** Block-style YAML for the shallow shapes we emit. Throws on anything else. */
152
+ export function renderYaml(value: unknown, indent = 0): string {
153
+ if (!Number.isInteger(indent) || indent < 0) {
154
+ throw new Error(`YAML indent must be a non-negative integer: ${String(indent)}`);
155
+ }
156
+ return `${yamlLines(value, indent).join("\n")}\n`;
157
+ }
158
+
159
+ /** A short, safe description for an error message — never the value's contents. */
160
+ function describeValue(value: unknown): string {
161
+ if (value === null) return "null";
162
+ if (Array.isArray(value)) return "an array";
163
+ if (typeof value === "object") return "an object";
164
+ return typeof value;
165
+ }
166
+
167
+ /** TOML basic-string escape. Mirrors `tomlString` in src/grok/inject.ts. */
168
+ export function tomlString(value: string): string {
169
+ return JSON.stringify(value);
170
+ }
171
+
172
+ /** Bare key when safe, basic-string key otherwise (TOML 1.0 §Keys). */
173
+ export function quoteTomlKey(key: string): string {
174
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : tomlString(key);
175
+ }
176
+
177
+ function tomlScalar(value: unknown): string {
178
+ if (typeof value === "string") return tomlString(value);
179
+ if (typeof value === "boolean") return value ? "true" : "false";
180
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
181
+ /*
182
+ * Arrays of ANY scalar, not just strings. The string-only check was written
183
+ * against our own builder output; a user's config legitimately holds
184
+ * `ports = [1, 2]`, and refusing it threw out of the writer as a 500.
185
+ * TOML permits mixed-type arrays since 1.0, so the elements are rendered
186
+ * individually rather than required to share a type.
187
+ */
188
+ if (Array.isArray(value)) {
189
+ return `[${value.map(item => tomlScalar(item)).join(", ")}]`;
190
+ }
191
+ /*
192
+ * An inline table is valid TOML (`items = [{ x = 1 }]`), and rendering one
193
+ * inline is straightforward — but only where the whole array can be
194
+ * rendered, so this stays a scalar-position renderer rather than silently
195
+ * flattening a table into a string.
196
+ */
197
+ if (isPlainRecord(value)) {
198
+ const entries = Object.entries(value);
199
+ return `{ ${entries.map(([k, v]) => `${quoteTomlKey(k)} = ${tomlScalar(v)}`).join(", ")} }`;
200
+ }
201
+ throw new UnserializableValueError(`TOML cannot represent this value: ${describeValue(value)}`);
202
+ }
203
+
204
+ /**
205
+ * Render `{ [table]: { key: scalar } }` as TOML. Scalars come first, then
206
+ * tables in insertion order, so two calls with the same document produce
207
+ * identical bytes — the stability guarantee `normalizeExportModels` exists to
208
+ * protect.
209
+ */
210
+ export function renderToml(document: Record<string, unknown>, prefix = ""): string {
211
+ const scalars: string[] = [];
212
+ const tables: string[] = [];
213
+ for (const [key, value] of Object.entries(document)) {
214
+ const path = prefix ? `${prefix}.${quoteTomlKey(key)}` : quoteTomlKey(key);
215
+ if (isPlainRecord(value)) {
216
+ tables.push(`[${path}]\n${renderToml(value, path)}`.trimEnd());
217
+ } else {
218
+ scalars.push(`${quoteTomlKey(key)} = ${tomlScalar(value)}`);
219
+ }
220
+ }
221
+ return `${[scalars.join("\n"), tables.join("\n\n")].filter(Boolean).join("\n\n")}\n`;
222
+ }
223
+
224
+ /**
225
+ * Ceiling on container nesting for json documents, shared by the parse-time
226
+ * scanner (config-io.ts) and the serializer walk below. One constant on
227
+ * purpose: the walk must accept every document the scanner admits, or a file
228
+ * the classifier reported as recoverable would refuse at rewrite time. Real
229
+ * configs nest a handful of levels.
230
+ */
231
+ export const MAX_JSON_NESTING = 1000;
232
+
233
+ /** Error messages carry the path to the offending value; keep them readable. */
234
+ function clampPath(path: string): string {
235
+ return path.length > 200 ? `${path.slice(0, 100)}…${path.slice(-100)}` : path;
236
+ }
237
+
238
+ /**
239
+ * JSON.stringify writes a non-finite number as `null` and -0 as `0`; any
240
+ * other finite double round-trips value-exactly (literal-level rounding is
241
+ * the parse-time scanner's concern), so those two are exactly what this walk
242
+ * refuses — refusing more turned a state the classifier had promised as
243
+ * recoverable into a permanent refusal. Documents read from disk are already
244
+ * guarded at parse time, and the writer's merge layer JSON-clones documents —
245
+ * normalizing these values — before serializing, so on the apply/disable path
246
+ * this walk is unreachable for them: it guards the direct serializers
247
+ * (preview/export builders), same posture as the YAML and TOML renderers
248
+ * above, and enforces the nesting ceiling for every json caller before the
249
+ * recursive JSON.stringify can turn depth into a RangeError.
250
+ *
251
+ * Iterative frames instead of recursion or a node stack: depth AND size of
252
+ * the document are inputs under the writer of the config file. Recursion made
253
+ * a deep file a RangeError-500; materializing every node with its path made a
254
+ * wide file allocate a large multiple of its size. Frames keep memory
255
+ * proportional to nesting depth, and path strings exist only for the
256
+ * containers on the current path plus the failing value itself.
257
+ */
258
+ function assertJsonNumbersRoundTrip(document: unknown, rootPath: string): void {
259
+ const refuse = (value: number, path: string): never => {
260
+ throw new UnserializableValueError(Object.is(value, -0)
261
+ ? `JSON cannot rewrite -0 at ${clampPath(path)} without changing it to 0`
262
+ : `JSON cannot rewrite the number at ${clampPath(path)} without changing it to null`);
263
+ };
264
+ if (typeof document === "number" && (!Number.isFinite(document) || Object.is(document, -0))) {
265
+ refuse(document, rootPath);
266
+ }
267
+ type Frame = { container: unknown; keys: string[] | null; index: number; prefix: string };
268
+ const frames: Frame[] = [];
269
+ const pushContainer = (value: unknown, prefix: string) => {
270
+ if (Array.isArray(value)) frames.push({ container: value, keys: null, index: 0, prefix });
271
+ else if (isPlainRecord(value)) frames.push({ container: value, keys: Object.keys(value), index: 0, prefix });
272
+ };
273
+ pushContainer(document, rootPath);
274
+ while (frames.length > 0) {
275
+ const frame = frames[frames.length - 1]!;
276
+ const length = frame.keys ? frame.keys.length : (frame.container as unknown[]).length;
277
+ if (frame.index >= length) { frames.pop(); continue; }
278
+ const i = frame.index;
279
+ frame.index += 1;
280
+ const child = frame.keys
281
+ ? (frame.container as Record<string, unknown>)[frame.keys[i]!]
282
+ : (frame.container as unknown[])[i];
283
+ const childPath = () => frame.keys
284
+ ? (frame.prefix === "$" ? frame.keys[i]! : `${frame.prefix}.${frame.keys[i]!}`)
285
+ : `${frame.prefix}[${i}]`;
286
+ if (typeof child === "number") {
287
+ if (!Number.isFinite(child) || Object.is(child, -0)) refuse(child, childPath());
288
+ continue;
289
+ }
290
+ if (typeof child === "object" && child !== null) {
291
+ if (frames.length >= MAX_JSON_NESTING) {
292
+ throw new UnserializableValueError(
293
+ `the document nests deeper than ${MAX_JSON_NESTING} levels at ${clampPath(childPath())}, which JSON serialization cannot rewrite safely`);
294
+ }
295
+ pushContainer(child, childPath());
296
+ }
297
+ }
298
+ }
299
+
300
+ /** Every serializer returns text ending in exactly one newline. */
301
+ export function serializeDocument(document: unknown, format: ConfigFormat): string {
302
+ switch (format) {
303
+ case "json": {
304
+ assertJsonNumbersRoundTrip(document, "$");
305
+ return `${JSON.stringify(document, null, 2)}\n`;
306
+ }
307
+ case "json5": return `${Bun.JSON5.stringify(document, null, 2)}\n`;
308
+ case "yaml": return renderYaml(document);
309
+ case "toml": {
310
+ if (!isPlainRecord(document)) throw new Error("TOML root must be a table");
311
+ return renderToml(document);
312
+ }
313
+ }
314
+ }