@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.
- package/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- 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
|
+
}
|