@bitkyc08/opencodex 2.84.0 → 2.87.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +44 -3
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +41 -9
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
package/src/bridge/sse.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { anthropicProgressItems } from "../responses/anthropic-progress";
|
|
1
2
|
import type {
|
|
2
3
|
AdapterEvent,
|
|
3
4
|
OcxMessagePhase,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
classifyError,
|
|
13
14
|
cyberPolicyErrorType,
|
|
14
15
|
CYBER_POLICY_ERROR_CODE,
|
|
15
|
-
|
|
16
|
+
isTerminalRefusalCode,
|
|
16
17
|
type OcxErrorPayload,
|
|
17
18
|
} from "../lib/errors";
|
|
18
19
|
import { redactSecretString } from "../lib/redact";
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
} from "../lib/translator-budget";
|
|
51
52
|
import { adapterFailureFromEvent, emptyChunks, joinChunks, ownedBudgetAbandonedMs, responsesUsage, toolCallArgumentsCouldBeJson, toolCallArgumentsUsable, uuid, webSearchAction } from "./internal";
|
|
52
53
|
import type { OutputItem, StringChunks } from "./internal";
|
|
54
|
+
import { terminalRefusalFailure } from "./terminal-refusal";
|
|
53
55
|
|
|
54
56
|
function sseEvent(name: string, data: Record<string, unknown>): string {
|
|
55
57
|
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
@@ -88,6 +90,7 @@ export function bridgeToResponsesSSE(
|
|
|
88
90
|
* response.completed — codex-rs collect_compaction_output requires exactly one.
|
|
89
91
|
*/
|
|
90
92
|
compaction?: boolean;
|
|
93
|
+
compactionRetention?: { encode(summary: string): string; commit(): void };
|
|
91
94
|
/** One-shot: first non-empty text/thinking/raw-reasoning/tool-input delta observed. */
|
|
92
95
|
onFirstOutput?: () => void;
|
|
93
96
|
onTerminal?: (status: ResponsesTerminalStatus) => void;
|
|
@@ -120,6 +123,12 @@ export function bridgeToResponsesSSE(
|
|
|
120
123
|
* would reach the client under the invented name instead of the normalized one.
|
|
121
124
|
*/
|
|
122
125
|
enforceDeclaredToolNames?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Deliver a non-retryable upstream refusal as `response.failed` with a terminal-refusal code
|
|
128
|
+
* instead of `response.incomplete` (see `./terminal-refusal`). Set only for the Responses
|
|
129
|
+
* inbound wire: the chat and Anthropic encoders read the incomplete to report a filter stop.
|
|
130
|
+
*/
|
|
131
|
+
terminalRefusalAsFailed?: boolean;
|
|
123
132
|
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
124
133
|
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
125
134
|
/**
|
|
@@ -676,12 +685,15 @@ export function bridgeToResponsesSSE(
|
|
|
676
685
|
// leaves a "Searching the web" spinner spinning forever.
|
|
677
686
|
// `sources` rides on the done item (additive field; codex-rs serde ignores unknown fields) so
|
|
678
687
|
// downstream translators (claude outbound) can fill web_search_tool_result content.
|
|
679
|
-
|
|
688
|
+
// `fetch` (Anthropic's server-side web_fetch) makes the cell a page open and rides its result
|
|
689
|
+
// verbatim as `fetch_result`, for the same claude outbound translator.
|
|
690
|
+
const closeCurrentWebSearch = (status: "completed" | "failed", queries: string[], sources?: { url: string; title?: string }[], fetch?: { url: string; result: unknown }) => {
|
|
680
691
|
if (!currentWebSearch) return;
|
|
681
692
|
const item = {
|
|
682
693
|
type: "web_search_call", id: currentWebSearch.itemId, status,
|
|
683
|
-
action: webSearchAction(queries),
|
|
694
|
+
action: webSearchAction(queries, fetch),
|
|
684
695
|
...(sources && sources.length > 0 ? { sources } : {}),
|
|
696
|
+
...(fetch ? { fetch_result: fetch.result } : {}),
|
|
685
697
|
};
|
|
686
698
|
emit("response.output_item.done", { output_index: currentWebSearch.outputIndex, item });
|
|
687
699
|
retainFinishedItem(item as OutputItem);
|
|
@@ -841,6 +853,7 @@ export function bridgeToResponsesSSE(
|
|
|
841
853
|
// its compaction UI renders nothing mid-turn, so nothing is lost visually.
|
|
842
854
|
if (options?.compaction) {
|
|
843
855
|
if (event.type === "text_delta") {
|
|
856
|
+
if (event.anthropicThinkingSignature) continue;
|
|
844
857
|
compaction = appendString(
|
|
845
858
|
compaction,
|
|
846
859
|
event.text,
|
|
@@ -871,6 +884,29 @@ export function bridgeToResponsesSSE(
|
|
|
871
884
|
break;
|
|
872
885
|
}
|
|
873
886
|
case "text_delta": {
|
|
887
|
+
if (event.anthropicThinkingSignature) {
|
|
888
|
+
rawReasoningForNextToolCall = "";
|
|
889
|
+
closeCurrentMessage("commentary");
|
|
890
|
+
closeCurrentReasoning();
|
|
891
|
+
closeCurrentRawReasoning();
|
|
892
|
+
flushHiddenRawReasoning();
|
|
893
|
+
flushHiddenReasoningEnvelope();
|
|
894
|
+
if (currentToolCall) closeCurrentToolCall();
|
|
895
|
+
for (const item of anthropicProgressItems(event.text, event.anthropicThinkingSignature, budget)) {
|
|
896
|
+
emit("response.output_item.added", { output_index: outputIndex, item: { ...item, ...(item.type === "message" ? { status: "in_progress", content: [] } : {}) } });
|
|
897
|
+
if (item.type === "message") {
|
|
898
|
+
const part = item.content![0];
|
|
899
|
+
emit("response.content_part.added", { item_id: item.id, output_index: outputIndex, content_index: 0, part: { ...part, text: "" } });
|
|
900
|
+
emit("response.output_text.delta", { item_id: item.id, output_index: outputIndex, content_index: 0, delta: event.text });
|
|
901
|
+
emit("response.output_text.done", { item_id: item.id, output_index: outputIndex, content_index: 0, text: event.text });
|
|
902
|
+
emit("response.content_part.done", { item_id: item.id, output_index: outputIndex, content_index: 0, part });
|
|
903
|
+
}
|
|
904
|
+
emit("response.output_item.done", { output_index: outputIndex, item });
|
|
905
|
+
retainFinishedItem(item as OutputItem);
|
|
906
|
+
outputIndex++;
|
|
907
|
+
}
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
874
910
|
if (currentReasoning) closeCurrentReasoning();
|
|
875
911
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
876
912
|
flushHiddenRawReasoning();
|
|
@@ -1195,7 +1231,7 @@ export function bridgeToResponsesSSE(
|
|
|
1195
1231
|
currentWebSearch = { itemId: wsItemId2, eventId: event.id, outputIndex };
|
|
1196
1232
|
}
|
|
1197
1233
|
const safeSources = safeWebSearchSources(event.sources);
|
|
1198
|
-
closeCurrentWebSearch(event.status ?? "completed", event.queries, safeSources);
|
|
1234
|
+
closeCurrentWebSearch(event.status ?? "completed", event.queries, safeSources, event.fetch);
|
|
1199
1235
|
// Queue this search's sources for the next assistant message (dedup by URL).
|
|
1200
1236
|
if (safeSources.length > 0) {
|
|
1201
1237
|
for (const source of safeSources) {
|
|
@@ -1233,7 +1269,7 @@ export function bridgeToResponsesSSE(
|
|
|
1233
1269
|
// Exactly one compaction item per turn; codex-rs takes the first and fatals on 0.
|
|
1234
1270
|
const item = {
|
|
1235
1271
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
1236
|
-
encrypted_content: event.compactionEncryptedContent ?? encodeCompactionSummary(joinChunks(compaction)),
|
|
1272
|
+
encrypted_content: event.compactionEncryptedContent ?? (options?.compactionRetention?.encode(joinChunks(compaction)) ?? encodeCompactionSummary(joinChunks(compaction))),
|
|
1237
1273
|
};
|
|
1238
1274
|
emit("response.output_item.done", { output_index: outputIndex, item });
|
|
1239
1275
|
retainFinishedItem(item as OutputItem, event.compactionEncryptedContent ? 0 : compaction.bytes);
|
|
@@ -1271,6 +1307,7 @@ export function bridgeToResponsesSSE(
|
|
|
1271
1307
|
emit("response.completed", {
|
|
1272
1308
|
response,
|
|
1273
1309
|
});
|
|
1310
|
+
if (!closed && !terminated && options?.compaction && !event.compactionEncryptedContent) options.compactionRetention?.commit();
|
|
1274
1311
|
reportTerminal("completed");
|
|
1275
1312
|
}
|
|
1276
1313
|
terminalEvent = true;
|
|
@@ -1287,12 +1324,25 @@ export function bridgeToResponsesSSE(
|
|
|
1287
1324
|
flushHiddenReasoningEnvelope();
|
|
1288
1325
|
options?.onUsage?.(event.usage);
|
|
1289
1326
|
await awaitThoughtSignatureDurability();
|
|
1327
|
+
const refusal = options?.terminalRefusalAsFailed ? terminalRefusalFailure(event) : undefined;
|
|
1328
|
+
if (refusal) {
|
|
1329
|
+
emit("response.failed", {
|
|
1330
|
+
response: {
|
|
1331
|
+
...responseSnapshot("failed", finishedItems, event.endTurn),
|
|
1332
|
+
usage: responsesUsage(event.usage),
|
|
1333
|
+
...refusal,
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1336
|
+
reportTerminal("failed");
|
|
1337
|
+
terminalEvent = true;
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1290
1340
|
emit("response.incomplete", {
|
|
1291
1341
|
response: {
|
|
1292
1342
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
1293
1343
|
usage: responsesUsage(event.usage),
|
|
1294
1344
|
incomplete_details: {
|
|
1295
|
-
reason: event.reason,
|
|
1345
|
+
reason: truncationReasonFor(event.reason) ?? event.reason,
|
|
1296
1346
|
...(event.message ? { message: event.message } : {}),
|
|
1297
1347
|
...(event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
1298
1348
|
},
|
|
@@ -1325,7 +1375,7 @@ export function bridgeToResponsesSSE(
|
|
|
1325
1375
|
...(event.usage ? { usage: responsesUsage(event.usage) } : {}),
|
|
1326
1376
|
error: failure.error,
|
|
1327
1377
|
last_error: failure.error,
|
|
1328
|
-
...(
|
|
1378
|
+
...(isTerminalRefusalCode(failure.error.code)
|
|
1329
1379
|
? { retryable: false }
|
|
1330
1380
|
: event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
1331
1381
|
},
|
|
@@ -1366,7 +1416,7 @@ export function bridgeToResponsesSSE(
|
|
|
1366
1416
|
...responseSnapshot("failed", finishedItems),
|
|
1367
1417
|
error: failure,
|
|
1368
1418
|
last_error: failure,
|
|
1369
|
-
...(
|
|
1419
|
+
...(isTerminalRefusalCode(failure.code) ? { retryable: false } : {}),
|
|
1370
1420
|
},
|
|
1371
1421
|
});
|
|
1372
1422
|
reportTerminal("failed");
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AdapterEvent } from "../types";
|
|
2
|
+
import type { OcxErrorPayload } from "../lib/errors";
|
|
3
|
+
import { redactSecretString } from "../lib/redact";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The terminal-refusal code Codex ends a turn on. It is a member of `TERMINAL_REFUSAL_CODES`
|
|
7
|
+
* in `src/lib/errors.ts`, and the one that says "this prompt, as sent, is refused" without
|
|
8
|
+
* claiming a specific policy family the upstream never named.
|
|
9
|
+
*/
|
|
10
|
+
const TERMINAL_REFUSAL_FAILED_CODE = "invalid_prompt";
|
|
11
|
+
|
|
12
|
+
interface TerminalRefusalFailure {
|
|
13
|
+
error: OcxErrorPayload;
|
|
14
|
+
last_error: OcxErrorPayload;
|
|
15
|
+
retryable: false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Client-visible copy: what happened, and that resending the same context cannot help. It points
|
|
20
|
+
* at a new conversation rather than a new turn, because a later turn replays the refused context.
|
|
21
|
+
*/
|
|
22
|
+
function terminalRefusalMessage(detail: string | undefined): string {
|
|
23
|
+
const reason = detail?.trim() ? ` (${redactSecretString(detail.trim())})` : "";
|
|
24
|
+
return `The upstream model refused this request${reason}. `
|
|
25
|
+
+ "Retrying with the same conversation context will not succeed; change the request or start a new conversation.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An adapter `incomplete` that is a non-retryable upstream refusal: a content filter carrying an
|
|
30
|
+
* explicit `retryable: false` verdict. A filter without the verdict, an output cap, a stall and an
|
|
31
|
+
* adapter EOF do not qualify. The sidecar loops read this too: a refused iteration ends the turn,
|
|
32
|
+
* so a sidecar call it carried is neither run nor answered with another model request.
|
|
33
|
+
*/
|
|
34
|
+
export function isTerminalRefusalIncomplete(event: AdapterEvent | undefined): boolean {
|
|
35
|
+
return event?.type === "incomplete" && event.reason === "content_filter" && event.retryable === false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `response.failed` fields for an adapter `incomplete` that is a non-retryable upstream refusal
|
|
40
|
+
* (#4312), or `undefined` when the event is anything else. The Anthropic and Kiro adapters are
|
|
41
|
+
* the ones that emit it today.
|
|
42
|
+
*
|
|
43
|
+
* codex-rs maps every `response.incomplete` to a retryable stream error and never reads
|
|
44
|
+
* `incomplete_details.retryable`, so a refusal delivered that way is re-sent through the whole
|
|
45
|
+
* stream retry budget. It only ends the turn on `response.failed` carrying a terminal-refusal
|
|
46
|
+
* `error.code`. Only an explicit `retryable: false` content filter qualifies: a filter without
|
|
47
|
+
* the verdict, an output cap, a stall and an adapter EOF stay `response.incomplete`.
|
|
48
|
+
*/
|
|
49
|
+
export function terminalRefusalFailure(
|
|
50
|
+
event: Extract<AdapterEvent, { type: "incomplete" }>,
|
|
51
|
+
): TerminalRefusalFailure | undefined {
|
|
52
|
+
if (!isTerminalRefusalIncomplete(event)) return undefined;
|
|
53
|
+
const error: OcxErrorPayload = {
|
|
54
|
+
type: "invalid_request_error",
|
|
55
|
+
code: TERMINAL_REFUSAL_FAILED_CODE,
|
|
56
|
+
message: terminalRefusalMessage(event.message),
|
|
57
|
+
};
|
|
58
|
+
return { error, last_error: error, retryable: false };
|
|
59
|
+
}
|
package/src/chat/inbound.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function assertChatCompletionsRoutingBody(raw: unknown): asserts raw is C
|
|
|
36
36
|
const OUTPUT_CONFIG_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
37
37
|
const OUTPUT_CONFIG_SUMMARIES = new Set(["auto", "concise", "detailed", "none"]);
|
|
38
38
|
|
|
39
|
-
function contentToText(content: unknown): string {
|
|
39
|
+
export function contentToText(content: unknown): string {
|
|
40
40
|
if (typeof content === "string") return content;
|
|
41
41
|
if (!Array.isArray(content)) return "";
|
|
42
42
|
const parts: string[] = [];
|
|
@@ -62,7 +62,7 @@ const imageUrlFromPart = chatImageUrlFromPart;
|
|
|
62
62
|
* understanding is requested by (#3271). The object form is the only one that
|
|
63
63
|
* can carry it — `video_url` as a bare string has nowhere to put it.
|
|
64
64
|
*/
|
|
65
|
-
function videoFromPart(part: Rec): { url: string; processing?: string } | null {
|
|
65
|
+
export function videoFromPart(part: Rec): { url: string; processing?: string } | null {
|
|
66
66
|
if (part.type !== "video_url") return null;
|
|
67
67
|
const videoUrl = part.video_url;
|
|
68
68
|
if (typeof videoUrl === "string" && videoUrl.length > 0) return { url: videoUrl };
|
|
@@ -123,7 +123,7 @@ function userContentToBlocks(content: unknown): Rec[] {
|
|
|
123
123
|
* was dropped in silence (#5212). A part with no inline bytes is still not translatable and is
|
|
124
124
|
* left to the untranslated-media refusal, which runs before this loop.
|
|
125
125
|
*/
|
|
126
|
-
function fileFromPart(part: Rec): Rec | null {
|
|
126
|
+
export function fileFromPart(part: Rec): Rec | null {
|
|
127
127
|
if (part.type !== "file" && part.type !== "input_file") return null;
|
|
128
128
|
const file = isRec(part.file) ? part.file : part;
|
|
129
129
|
const fileData = file.file_data;
|
|
@@ -143,7 +143,7 @@ function fileFromPart(part: Rec): Rec | null {
|
|
|
143
143
|
* Only representable plaintext is read. No signature, encrypted payload or
|
|
144
144
|
* provider-issued item id is reconstructed — see the reasoning item built below.
|
|
145
145
|
*/
|
|
146
|
-
function assistantReasoningText(msg: Rec): string | undefined {
|
|
146
|
+
export function assistantReasoningText(msg: Rec): string | undefined {
|
|
147
147
|
if (typeof msg.reasoning_content === "string" && msg.reasoning_content.length > 0) {
|
|
148
148
|
return msg.reasoning_content;
|
|
149
149
|
}
|
|
@@ -601,6 +601,32 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
|
|
|
601
601
|
...(legacyFunctionsToResponses(raw.functions) ?? []),
|
|
602
602
|
];
|
|
603
603
|
if (tools.length > 0) body.tools = tools;
|
|
604
|
+
applyChatSettings(raw, body);
|
|
605
|
+
return body;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* The request settings of a Chat body as a Responses body with no conversation: `input` is
|
|
610
|
+
* empty and there are no `instructions` or `tools`. The Antigravity direct lane hands this to
|
|
611
|
+
* the Responses core so routing, effort policy, tier and logging keep reading the fields they
|
|
612
|
+
* read today, while the conversation is decoded straight into the IR
|
|
613
|
+
* (`src/protocols/codecs/chat-ir.ts`). Field order and validation are the bridge body's own:
|
|
614
|
+
* both are built by `applyChatSettings`.
|
|
615
|
+
*/
|
|
616
|
+
export function chatSettingsToResponsesBody(raw: unknown): Rec {
|
|
617
|
+
assertChatCompletionsRoutingBody(raw);
|
|
618
|
+
const body: Rec = {
|
|
619
|
+
model: raw.model,
|
|
620
|
+
input: [],
|
|
621
|
+
stream: raw.stream === true,
|
|
622
|
+
store: false,
|
|
623
|
+
};
|
|
624
|
+
applyChatSettings(raw, body);
|
|
625
|
+
return body;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Everything after the conversation and tool catalog: choice, limits, sampling, reasoning, format. */
|
|
629
|
+
function applyChatSettings(raw: ChatCompletionsRoutingBody, body: Rec): void {
|
|
604
630
|
if (raw.tool_choice !== undefined) toolChoiceToResponses(raw.tool_choice, body);
|
|
605
631
|
else legacyFunctionChoiceToResponses(raw.function_call, body);
|
|
606
632
|
|
|
@@ -637,6 +663,4 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
|
|
|
637
663
|
|
|
638
664
|
const text = responseFormatToText(raw.response_format);
|
|
639
665
|
if (text) body.text = text;
|
|
640
|
-
|
|
641
|
-
return body;
|
|
642
666
|
}
|
package/src/chat/outbound.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
CYBER_POLICY_ERROR_CODE,
|
|
20
20
|
isCyberPolicyCode,
|
|
21
21
|
isCyberPolicyMessage,
|
|
22
|
+
UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
22
23
|
} from "../lib/errors";
|
|
23
24
|
import { redactSecretString } from "../lib/redact";
|
|
24
25
|
import { createSseBlockBuffer, sseDataPayload } from "../server/sse-payload-rewrite";
|
|
@@ -196,6 +197,10 @@ export function chatCompletionsStreamErrorPayload(
|
|
|
196
197
|
} else if (isCyberPolicyCode(details?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
197
198
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
198
199
|
classified.type = cyberPolicyErrorType(details?.type);
|
|
200
|
+
} else if (details?.code === UNDECLARED_TOOL_CALL_ERROR_CODE) {
|
|
201
|
+
// The guard's deterministic refusal keeps its wire identity past classifyError's
|
|
202
|
+
// generic 5xx remap — same code the buffered Chat error and the SSE frame carry.
|
|
203
|
+
classified.code = UNDECLARED_TOOL_CALL_ERROR_CODE;
|
|
199
204
|
} else if (details?.code !== undefined && details.code !== null && !classified.code) {
|
|
200
205
|
classified.code = details.code;
|
|
201
206
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, type BigIntStats } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { claudeDesktopConfigLibraryDir, resolveConfigLibraryDir } from "./desktop-3p-paths";
|
|
@@ -44,9 +44,60 @@ export interface Desktop3pMetadata {
|
|
|
44
44
|
|
|
45
45
|
export const DESKTOP_PICKER_ENTRY_NAME = "opencodex-picker";
|
|
46
46
|
|
|
47
|
+
/** A path that no longer names the observed file was swapped; a same-path failure keeps its own errno. */
|
|
48
|
+
function pathStillNamesExpectedFile(path: string, expected: BigIntStats): boolean {
|
|
49
|
+
try {
|
|
50
|
+
const now = lstatSync(path, { bigint: true });
|
|
51
|
+
return now.isFile() && now.dev === expected.dev && now.ino === expected.ino
|
|
52
|
+
&& now.ctimeNs === expected.ctimeNs;
|
|
53
|
+
} catch { return false; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Read regular config files only; a path swap must not turn an observation into a FIFO wait. */
|
|
57
|
+
export function readDesktopLibraryBytes(path: string): Buffer {
|
|
58
|
+
const expected = lstatSync(path, { bigint: true });
|
|
59
|
+
if (!expected.isFile()) throw new Error("desktop_config_not_regular");
|
|
60
|
+
// POSIX open of a FIFO can itself block, before fstat is possible. Windows has no POSIX
|
|
61
|
+
// FIFO entry; retain descriptor identity validation there without unsupported open flags.
|
|
62
|
+
const flags = constants.O_RDONLY | (process.platform === "win32" ? 0 : constants.O_NONBLOCK | constants.O_NOFOLLOW);
|
|
63
|
+
let fd: number;
|
|
64
|
+
try {
|
|
65
|
+
fd = openSync(path, flags);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// A swap can fail at open — ELOOP on a symlink, ENXIO on a socket — before fstat can
|
|
68
|
+
// classify it. Re-observing the path reports every swap as the documented refusal.
|
|
69
|
+
if (pathStillNamesExpectedFile(path, expected)) throw error;
|
|
70
|
+
throw new Error("desktop_config_not_regular");
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const opened = fstatSync(fd, { bigint: true });
|
|
74
|
+
if (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino
|
|
75
|
+
|| opened.ctimeNs !== expected.ctimeNs) throw new Error("desktop_config_not_regular");
|
|
76
|
+
return readFileSync(fd);
|
|
77
|
+
} finally {
|
|
78
|
+
closeSync(fd);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Text decoding stays identical to node:fs readFileSync(path, "utf8"). */
|
|
83
|
+
export function readDesktopLibraryText(path: string): string {
|
|
84
|
+
return readDesktopLibraryBytes(path).toString("utf8");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Only an absent directory entry is optional; dangling links and failed reads still refuse. */
|
|
88
|
+
export function readOptionalDesktopLibraryBytes(path: string): Buffer | undefined {
|
|
89
|
+
try { return readDesktopLibraryBytes(path); }
|
|
90
|
+
catch (error) {
|
|
91
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Read optional verified metadata; a present file must contain an entries array. */
|
|
47
97
|
export function parseMetadata(path: string): Desktop3pMetadata {
|
|
48
|
-
|
|
49
|
-
|
|
98
|
+
const bytes = readOptionalDesktopLibraryBytes(path);
|
|
99
|
+
if (bytes === undefined) return { entries: [] };
|
|
100
|
+
const parsed = JSON.parse(bytes.toString("utf8")) as Partial<Desktop3pMetadata>;
|
|
50
101
|
if (!Array.isArray(parsed.entries)) throw new Error("Claude Desktop 3P _meta.json has no entries array");
|
|
51
102
|
return { ...parsed, entries: parsed.entries };
|
|
52
103
|
}
|
|
@@ -80,11 +131,25 @@ export const OPENCODEX_DESKTOP_PROFILE_KEYS = new Set([
|
|
|
80
131
|
"inferenceModels",
|
|
81
132
|
]);
|
|
82
133
|
|
|
83
|
-
export
|
|
84
|
-
|
|
85
|
-
|
|
134
|
+
export interface DesktopProfileSnapshot {
|
|
135
|
+
/** Verified bytes of the existing profile; undefined only when the entry is absent. */
|
|
136
|
+
bytes?: Buffer;
|
|
137
|
+
foreign: Record<string, unknown>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Capture verified bytes for key preservation, backup and rollback; publication revalidates them. */
|
|
141
|
+
export function readDesktopProfileSnapshot(path: string): DesktopProfileSnapshot {
|
|
142
|
+
const bytes = readOptionalDesktopLibraryBytes(path);
|
|
143
|
+
if (bytes === undefined) return { foreign: {} };
|
|
144
|
+
const parsed = JSON.parse(bytes.toString("utf8")) as unknown;
|
|
86
145
|
if (!isRecord(parsed)) throw new Error("Claude Desktop 3P profile is not a JSON object");
|
|
87
|
-
|
|
146
|
+
const foreign = Object.fromEntries(
|
|
88
147
|
Object.entries(parsed).filter(([key]) => !OPENCODEX_DESKTOP_PROFILE_KEYS.has(key)),
|
|
89
148
|
);
|
|
149
|
+
return { bytes, foreign };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Preserve settings outside the gateway-owned keys from a verified profile. */
|
|
153
|
+
export function readDesktopProfileForeignKeys(path: string): Record<string, unknown> {
|
|
154
|
+
return readDesktopProfileSnapshot(path).foreign;
|
|
90
155
|
}
|
package/src/claude/desktop-3p.ts
CHANGED
|
@@ -5,21 +5,23 @@ import { applyRemoteDesktopStore, inspectRemoteDesktopCleanup, restoreRemoteDesk
|
|
|
5
5
|
import { canonicalDirectory } from "./desktop-remote-store-io";
|
|
6
6
|
import {
|
|
7
7
|
resolveDesktop3pConfigLibraryPath, parseMetadata, SAFE_DESKTOP_PROFILE_ID, isRecord,
|
|
8
|
-
isOwnedDesktopEntry, isOwnedDesktopGatewayEntry, profilePath,
|
|
8
|
+
isOwnedDesktopEntry, isOwnedDesktopGatewayEntry, profilePath, readDesktopProfileSnapshot, readDesktopLibraryText, readDesktopLibraryBytes,
|
|
9
|
+
readOptionalDesktopLibraryBytes,
|
|
9
10
|
type Desktop3pConfigLibraryOptions, type Desktop3pMetadata, type Desktop3pMetadataEntry,
|
|
10
11
|
} from "./desktop-3p-library";
|
|
11
12
|
export { resolveDesktop3pConfigLibraryPath, type Desktop3pConfigLibraryOptions } from "./desktop-3p-library";
|
|
12
13
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
|
-
import {
|
|
14
|
+
import { existsSync, mkdirSync, lstatSync, unlinkSync, writeFileSync } from "node:fs";
|
|
14
15
|
import { join } from "node:path";
|
|
15
16
|
import { atomicWriteFile, readConfigDiagnostics, withConfigMutationLockSync } from "../config";
|
|
16
|
-
import { atomicWriteFileNoFollow } from "../config/atomic-write";
|
|
17
|
+
import { atomicWriteFileNoFollow, atomicWriteFileStreamed } from "../config/atomic-write";
|
|
17
18
|
import { claudeDesktopIntegrationEnabled } from "../codex/desired-state";
|
|
18
19
|
import type { OcxClaudeDesktopProfile } from "../types";
|
|
19
20
|
import {
|
|
20
21
|
reconcileDesktopProfile,
|
|
21
22
|
renderDesktopProfile,
|
|
22
23
|
validDateAlias,
|
|
24
|
+
legacyDesktopProfileWireAlias,
|
|
23
25
|
type DesktopProfileModel,
|
|
24
26
|
} from "./desktop-profile";
|
|
25
27
|
import { nativeOpenAiContextWindow, type NativeContextLimitsInput } from "../codex/catalog";
|
|
@@ -223,7 +225,7 @@ function collectDesktop3pModels(
|
|
|
223
225
|
...(model.supports1m ? { supports1m: true, prefer1m: true } : {}),
|
|
224
226
|
});
|
|
225
227
|
}
|
|
226
|
-
//
|
|
228
|
+
// Previous profile IDs and both hash generations are compatibility-only and can
|
|
227
229
|
// collide. Bind them in stable route order so changing a family default or
|
|
228
230
|
// rendered ordering can never silently rebind an old Desktop id.
|
|
229
231
|
for (const model of [...rendered].sort((a, b) => a.route.localeCompare(b.route))) {
|
|
@@ -231,7 +233,8 @@ function collectDesktop3pModels(
|
|
|
231
233
|
const providerEnd = model.route.indexOf("/");
|
|
232
234
|
const provider = model.route.slice(0, providerEnd);
|
|
233
235
|
const id = model.route.slice(providerEnd + 1);
|
|
234
|
-
|
|
236
|
+
const storedAlias = reconciled.assignments[model.route]!.alias;
|
|
237
|
+
for (const legacy of [legacyDesktopProfileWireAlias(storedAlias), desktop3pAlias(provider, id), legacyDesktop3pAlias(provider, id)]) {
|
|
235
238
|
if (realAnthropicIds.has(legacy)) {
|
|
236
239
|
console.warn(`[opencodex] Claude Desktop legacy alias ${legacy} for ${model.route} conflicts with a real Anthropic model; ignoring the alias`);
|
|
237
240
|
continue;
|
|
@@ -360,6 +363,11 @@ export function isKnownDesktop3pModelId(id: string): boolean {
|
|
|
360
363
|
return desktop3pRegistry.has(id) || desktop3pRealAnthropicIds.has(id);
|
|
361
364
|
}
|
|
362
365
|
|
|
366
|
+
/** Real catalog IDs retain identity even when their spelling resembles an alias. */
|
|
367
|
+
export function isRealDesktop3pModelId(id: string): boolean {
|
|
368
|
+
return desktop3pRealAnthropicIds.has(id);
|
|
369
|
+
}
|
|
370
|
+
|
|
363
371
|
/** Only missing IDs in the emitted Desktop namespaces are managed-alias errors. */
|
|
364
372
|
export function isUnresolvedDesktop3pAlias(id: string): boolean {
|
|
365
373
|
if (isKnownDesktop3pModelId(id)) return false;
|
|
@@ -369,7 +377,7 @@ export function isUnresolvedDesktop3pAlias(id: string): boolean {
|
|
|
369
377
|
if (isKnownDesktop3pModelId(base)) return false;
|
|
370
378
|
return validDateAlias(base)
|
|
371
379
|
|| /^claude-opus-4-(?:8-)?[a-z][a-z0-9]{2}$/.test(base)
|
|
372
|
-
|| /^claude-opus-4-8-p[0-9a-z]{3}$/.test(base);
|
|
380
|
+
|| /^claude-(?:opus-4-8|fable-5)-p[0-9a-z]{3}$/.test(base);
|
|
373
381
|
}
|
|
374
382
|
|
|
375
383
|
/** Alias selected by the installed profile registry, falling back to the legacy hash shape. */
|
|
@@ -377,6 +385,13 @@ export function activeDesktop3pAlias(provider: string, modelId: string): string
|
|
|
377
385
|
return desktop3pAliasesByRoute.get(`${provider}/${modelId}`) ?? desktop3pAlias(provider, modelId);
|
|
378
386
|
}
|
|
379
387
|
|
|
388
|
+
/** Discovery cannot offer a skipped alias or one owned by a different route. */
|
|
389
|
+
export function resolvableDesktop3pAlias(provider: string, modelId: string): string | undefined {
|
|
390
|
+
if (provider === "anthropic" && modelId.startsWith("claude-")) return modelId;
|
|
391
|
+
const alias = activeDesktop3pAlias(provider, modelId);
|
|
392
|
+
return resolveDesktop3pAlias(alias) === `${provider}/${modelId}` ? alias : undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
380
395
|
/**
|
|
381
396
|
* Generate the complete Claude Desktop 3P gateway config.
|
|
382
397
|
*
|
|
@@ -476,7 +491,7 @@ export function inspectDesktop3pConfigLibrary(
|
|
|
476
491
|
let profile: Record<string, unknown>;
|
|
477
492
|
let fingerprint: string;
|
|
478
493
|
try {
|
|
479
|
-
const source =
|
|
494
|
+
const source = readDesktopLibraryText(selectedProfilePath);
|
|
480
495
|
const parsed = JSON.parse(source) as unknown;
|
|
481
496
|
if (!isRecord(parsed)) return { kind: "broken", libraryPath, selectedProfilePath, appliedId, residualPaths, ownedProfileActive };
|
|
482
497
|
profile = parsed;
|
|
@@ -709,7 +724,7 @@ export function writeDesktop3pConfig(
|
|
|
709
724
|
const metadata = parseMetadata(join(inspected.libraryPath, "_meta.json"));
|
|
710
725
|
if (!isOwnedDesktopGatewayEntry(metadata.entries.find(entry => entry.id === inspected.appliedId))) return refused;
|
|
711
726
|
try {
|
|
712
|
-
const selected = JSON.parse(
|
|
727
|
+
const selected = JSON.parse(readDesktopLibraryText(inspected.selectedProfilePath)) as Record<string, unknown>;
|
|
713
728
|
mode = selected.modelDiscoveryEnabled !== true ? "static"
|
|
714
729
|
: Array.isArray(selected.inferenceModels) ? "hybrid" : "discovery";
|
|
715
730
|
} catch { return refused; }
|
|
@@ -786,6 +801,7 @@ function desktopRefreshPathsSafe(libraryPath: string, configPath?: string): bool
|
|
|
786
801
|
} catch { return false; }
|
|
787
802
|
}
|
|
788
803
|
|
|
804
|
+
/** Publish a gateway profile from a verified snapshot, rolling it back if metadata fails. */
|
|
789
805
|
function writeDesktop3pConfigWithGenerator(
|
|
790
806
|
generate: () => object,
|
|
791
807
|
refreshOnly?: { requiredId: string; expectedFingerprint: string; admit: () => boolean },
|
|
@@ -814,8 +830,8 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
814
830
|
: [...metadata.entries, entry];
|
|
815
831
|
|
|
816
832
|
const generated = generate();
|
|
817
|
-
const
|
|
818
|
-
const configJson = JSON.stringify({ ...
|
|
833
|
+
const previous = readDesktopProfileSnapshot(configPath);
|
|
834
|
+
const configJson = JSON.stringify({ ...previous.foreign, ...generated }, null, 2) + "\n";
|
|
819
835
|
const fingerprint = createHash("sha256").update(configJson).digest("hex").slice(0, 16);
|
|
820
836
|
if (refreshOnly) {
|
|
821
837
|
atomicWriteFileNoFollow(configPath, configJson, undefined, {
|
|
@@ -825,7 +841,7 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
825
841
|
const freshMetadata = parseMetadata(metadataPath);
|
|
826
842
|
if (freshMetadata.appliedId !== refreshOnly.requiredId
|
|
827
843
|
|| !isOwnedDesktopGatewayEntry(freshMetadata.entries.find(entry => entry.id === refreshOnly.requiredId))
|
|
828
|
-
|| createHash("sha256").update(
|
|
844
|
+
|| createHash("sha256").update(readDesktopLibraryBytes(configPath)).digest("hex").slice(0, 16) !== refreshOnly.expectedFingerprint) {
|
|
829
845
|
throw new Error();
|
|
830
846
|
}
|
|
831
847
|
} catch { throw new Error("desktop_refresh_only_skipped"); }
|
|
@@ -833,12 +849,31 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
833
849
|
});
|
|
834
850
|
return { written: true, path: configPath, fingerprint };
|
|
835
851
|
}
|
|
836
|
-
|
|
852
|
+
atomicReplaceDesktopConfig(configPath, configJson, previous.bytes);
|
|
837
853
|
try {
|
|
838
854
|
atomicWriteFile(metadataPath, JSON.stringify({ ...metadata, appliedId: id, entries }, null, 2) + "\n");
|
|
839
855
|
} catch (metaError) {
|
|
840
|
-
//
|
|
841
|
-
|
|
856
|
+
// Roll back from the verified bytes in memory: reopening the backup could meet a swapped
|
|
857
|
+
// entry and leave the new profile beside the old metadata. A profile this call created is
|
|
858
|
+
// unreferenced once metadata failed, so remove it rather than orphan one per retry.
|
|
859
|
+
// Rollback is ownership-aware: it acts only while the live profile still holds the bytes
|
|
860
|
+
// this call published, checked by the same comparison that guarded publication.
|
|
861
|
+
const base = metaError instanceof Error ? metaError.message : String(metaError);
|
|
862
|
+
const published = Buffer.from(configJson);
|
|
863
|
+
let owned = false;
|
|
864
|
+
const stillOwned = () => { owned = false; assertDesktopProfileUnchanged(configPath, published); owned = true; };
|
|
865
|
+
try {
|
|
866
|
+
stillOwned();
|
|
867
|
+
if (previous.bytes !== undefined) writeDesktopConfigBytes(configPath, previous.bytes, stillOwned);
|
|
868
|
+
else unlinkSync(configPath);
|
|
869
|
+
} catch (rollbackError) {
|
|
870
|
+
if (!owned) {
|
|
871
|
+
// Another writer replaced the profile after publication; its bytes win over our rollback.
|
|
872
|
+
throw new Error(`${base} (profile rollback skipped: profile changed after publish)`, { cause: metaError });
|
|
873
|
+
}
|
|
874
|
+
const detail = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
|
|
875
|
+
throw new AggregateError([metaError, rollbackError], `${base} (profile rollback also failed: ${detail})`, { cause: metaError });
|
|
876
|
+
}
|
|
842
877
|
throw metaError;
|
|
843
878
|
}
|
|
844
879
|
return { written: true, path: configPath, fingerprint };
|
|
@@ -848,14 +883,35 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
848
883
|
}
|
|
849
884
|
}
|
|
850
885
|
|
|
851
|
-
/**
|
|
886
|
+
/**
|
|
887
|
+
* Compare the live profile with the caller's verified bytes before backup and before replacement.
|
|
888
|
+
* Refuse drift without retrying; backup and rollback continue to use the original verified bytes.
|
|
889
|
+
* `previous` is undefined only for an absent profile, which leaves any older backup alone.
|
|
890
|
+
* Exported for failure-path tests.
|
|
891
|
+
*/
|
|
852
892
|
export function atomicReplaceDesktopConfig(
|
|
853
893
|
path: string,
|
|
854
894
|
content: string,
|
|
855
|
-
|
|
895
|
+
previous: Buffer | undefined,
|
|
896
|
+
writer: typeof atomicWriteFileNoFollow = atomicWriteFileNoFollow,
|
|
856
897
|
): { backupPath?: string } {
|
|
898
|
+
const validateBeforeRename = () => assertDesktopProfileUnchanged(path, previous);
|
|
899
|
+
validateBeforeRename();
|
|
857
900
|
const backupPath = `${path}.bak`;
|
|
858
|
-
if (
|
|
859
|
-
writer(path, content);
|
|
860
|
-
return
|
|
901
|
+
if (previous !== undefined) writeDesktopConfigBytes(backupPath, previous);
|
|
902
|
+
writer(path, content, undefined, { validateBeforeRename });
|
|
903
|
+
return previous === undefined ? {} : { backupPath };
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/** Single compare-and-swap check: the live profile must still hold `expected` (undefined = absent). */
|
|
907
|
+
function assertDesktopProfileUnchanged(path: string, expected: Buffer | undefined): void {
|
|
908
|
+
const current = readOptionalDesktopLibraryBytes(path);
|
|
909
|
+
if (expected === undefined ? current !== undefined : current === undefined || !expected.equals(current)) {
|
|
910
|
+
throw new Error("desktop_profile_changed");
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** Publish the verified bytes without reopening the source or decoding its backup contents. */
|
|
915
|
+
function writeDesktopConfigBytes(path: string, bytes: Buffer, validateBeforeRename?: () => void): void {
|
|
916
|
+
atomicWriteFileStreamed(path, descriptor => { writeFileSync(descriptor, bytes); }, validateBeforeRename ? { validateBeforeRename } : {});
|
|
861
917
|
}
|
|
@@ -25,7 +25,7 @@ import type { OcxConfig } from "../types";
|
|
|
25
25
|
import { claudeConfigDir } from "./auth-detect";
|
|
26
26
|
import { inspectDesktop3pConfigLibrary } from "./desktop-3p";
|
|
27
27
|
import { claudeInterceptCaCertPath, ensureLocalInterceptCa } from "./intercept/local-ca";
|
|
28
|
-
import { claudeInterceptEnabled, claudeInterceptProxyPort } from "./intercept/
|
|
28
|
+
import { claudeInterceptEnabled, claudeInterceptProxyPort } from "./intercept/port-policy";
|
|
29
29
|
import { ensureClaudeInterceptProxyToken, readClaudeInterceptProxyToken } from "./intercept/proxy-auth";
|
|
30
30
|
import {
|
|
31
31
|
applyClaudeInterceptSettings,
|