@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/server/auth-cors.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
providerForwardClientHeadersConfigError,
|
|
31
31
|
reasoningSummaryDeliveryRecordConfigError,
|
|
32
32
|
upstreamHttpVersionConfigError,
|
|
33
|
+
credentialPinnedProviderConfigError,
|
|
33
34
|
} from "../config/provider-validation";
|
|
34
35
|
import { providerDestinationConfigError } from "../lib/destination-policy";
|
|
35
36
|
import { providerEgressConfigError } from "../lib/provider-egress";
|
|
@@ -784,6 +785,8 @@ export function providerManagementConfigError(
|
|
|
784
785
|
delete canonicalCandidate.pinnedReasoningEffort;
|
|
785
786
|
delete canonicalCandidate.modelPinnedReasoningEfforts;
|
|
786
787
|
delete canonicalCandidate.responsesSnapshotRepair;
|
|
788
|
+
// Explicit history-loss opt-in changes request content, never auth or transport identity.
|
|
789
|
+
delete canonicalCandidate.projectResponsesLiteHostedSearchHistory;
|
|
787
790
|
// modelCosts is a user-owned display overlay, not part of the canonical
|
|
788
791
|
// forward seed; it is validated separately below (providerModelCostsConfigError).
|
|
789
792
|
delete canonicalCandidate.modelCosts;
|
|
@@ -912,6 +915,8 @@ export function providerManagementConfigError(
|
|
|
912
915
|
if (reasoningSummaryDeliveryError) return `provider ${name} ${reasoningSummaryDeliveryError}`;
|
|
913
916
|
const modelAdaptersError = modelAdapterRecordConfigError(raw.modelAdapters, "modelAdapters", name, typed);
|
|
914
917
|
if (modelAdaptersError) return `provider ${name} ${modelAdaptersError}`;
|
|
918
|
+
const credentialPinError = credentialPinnedProviderConfigError(name, typed);
|
|
919
|
+
if (credentialPinError) return `provider ${name} ${credentialPinError}`;
|
|
915
920
|
const preferHostedToolsError = modelPreferHostedToolsConfigError(
|
|
916
921
|
raw.modelPreferHostedTools,
|
|
917
922
|
"modelPreferHostedTools",
|
|
@@ -919,12 +924,17 @@ export function providerManagementConfigError(
|
|
|
919
924
|
typed,
|
|
920
925
|
);
|
|
921
926
|
if (preferHostedToolsError) return `provider ${name} ${preferHostedToolsError}`;
|
|
927
|
+
if (raw.projectResponsesLiteHostedSearchHistory !== undefined && typeof raw.projectResponsesLiteHostedSearchHistory !== "boolean") {
|
|
928
|
+
return `provider ${name} projectResponsesLiteHostedSearchHistory must be a boolean`;
|
|
929
|
+
}
|
|
922
930
|
if (raw.responsesSnapshotRepair !== undefined && typeof raw.responsesSnapshotRepair !== "boolean") {
|
|
923
931
|
return `provider ${name} responsesSnapshotRepair must be a boolean`;
|
|
924
932
|
}
|
|
925
933
|
if (raw.xaiResponsesXSearch !== undefined && typeof raw.xaiResponsesXSearch !== "boolean") {
|
|
926
934
|
return `provider ${name} xaiResponsesXSearch must be a boolean`;
|
|
927
935
|
}
|
|
936
|
+
const minMaxOutputError = positiveIntegerConfigError(raw.minMaxOutputTokens, "minMaxOutputTokens");
|
|
937
|
+
if (minMaxOutputError) return `provider ${name} ${minMaxOutputError}`;
|
|
928
938
|
const defaultMaxOutputError = positiveIntegerConfigError(raw.defaultMaxOutputTokens, "defaultMaxOutputTokens");
|
|
929
939
|
if (defaultMaxOutputError) return `provider ${name} ${defaultMaxOutputError}`;
|
|
930
940
|
const maxOutputError = positiveIntegerRecordConfigError(raw.modelMaxOutputTokens, "modelMaxOutputTokens");
|
|
@@ -1051,9 +1061,12 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
1051
1061
|
preserveResponsesReasoningContent: "editor",
|
|
1052
1062
|
preserveResponsesInputItemIds: "editor",
|
|
1053
1063
|
preserveResponsesMessageMetadata: "editor",
|
|
1064
|
+
projectResponsesLiteHostedSearchHistory: "editor",
|
|
1054
1065
|
dropResponsesReasoningItems: "editor",
|
|
1055
1066
|
modelReasoningEffortsAuthoritative: "editor",
|
|
1056
1067
|
decodesNativeCompactionBlobs: "editor",
|
|
1068
|
+
supportsNativeCompactionTrigger: "editor",
|
|
1069
|
+
supportsNativeCompactEndpoint: "editor",
|
|
1057
1070
|
allowEncryptedV2AgentTasks: "editor",
|
|
1058
1071
|
allowPrivateNetwork: "editor",
|
|
1059
1072
|
// A proxy URL routinely embeds `user:password@`, so it never reaches the dashboard DTO and
|
|
@@ -1085,6 +1098,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
1085
1098
|
newModelPolicy: "editor",
|
|
1086
1099
|
modelPreset: "editor",
|
|
1087
1100
|
contextWindow: "editor",
|
|
1101
|
+
inferContextWindow: "editor",
|
|
1088
1102
|
modelContextWindows: "editor",
|
|
1089
1103
|
modelContextTiers: "editor",
|
|
1090
1104
|
modelInputModalities: "editor",
|
|
@@ -1092,6 +1106,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
1092
1106
|
modelMaxInputTokens: "runtime",
|
|
1093
1107
|
modelAutoCompactTokenLimits: "editor",
|
|
1094
1108
|
defaultMaxOutputTokens: "editor",
|
|
1109
|
+
minMaxOutputTokens: "editor",
|
|
1095
1110
|
modelMaxOutputTokens: "editor",
|
|
1096
1111
|
modelCosts: "editor",
|
|
1097
1112
|
headers: "redacted",
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
assertChatCompletionsRoutingBody,
|
|
11
11
|
ChatCompletionsRequestError,
|
|
12
12
|
} from "../chat/inbound";
|
|
13
|
-
import { chatToResponsesBody } from "../protocols/codecs/chat";
|
|
13
|
+
import { chatSettingsBody, chatToResponsesBody } from "../protocols/codecs/chat";
|
|
14
|
+
import { chatConversationToIr, chatIrRouteEvidence, withDirectChatConversation, type ChatIrConversation } from "../protocols/codecs/chat-ir";
|
|
15
|
+
import { antigravityDirectDeclineReason } from "./antigravity-direct-eligibility";
|
|
14
16
|
import { normalizeChatImageParts } from "../chat/image-parts";
|
|
15
17
|
import {
|
|
16
18
|
chatCompletionsErrorResponse,
|
|
@@ -19,7 +21,8 @@ import {
|
|
|
19
21
|
responsesJsonToChatCompletion,
|
|
20
22
|
responsesSseToChatCompletionsSse,
|
|
21
23
|
} from "../chat/outbound";
|
|
22
|
-
import {
|
|
24
|
+
import { ADVISOR_INTERNAL_CAPABILITY_HEADER, isInternalCallCapability } from "../lib/local-internal-call-capability";
|
|
25
|
+
import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
|
|
23
26
|
import { redactSecretString } from "../lib/redact";
|
|
24
27
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
25
28
|
import {
|
|
@@ -44,12 +47,14 @@ import type { OcxConfig } from "../types";
|
|
|
44
47
|
import { readJsonRequestBody, resolveInboundBodyLimitBytes } from "./request-decompress";
|
|
45
48
|
import {
|
|
46
49
|
addFinalRequestLog,
|
|
50
|
+
cacheDiagnosticsObserving,
|
|
47
51
|
httpStatusForRequestLogTerminal,
|
|
48
52
|
recordFirstOutput,
|
|
49
53
|
type RequestLogContext,
|
|
50
54
|
} from "./request-log";
|
|
51
55
|
import { createFinalRequestLog } from "./inference/final-log";
|
|
52
56
|
import { clientWireLogOf, clientWireOf } from "./inference/client-wire";
|
|
57
|
+
import { attachAdvisorReasoning } from "./inference/advisor-diagnostics";
|
|
53
58
|
import { directEncodersApply } from "./inference/client-encoder-delivery";
|
|
54
59
|
import { responseWithDeferredRequestLog } from "./relay";
|
|
55
60
|
import { handleResponses } from "./responses";
|
|
@@ -110,8 +115,10 @@ export async function handleChatCompletions(
|
|
|
110
115
|
): Promise<Response> {
|
|
111
116
|
const translatorBudget = createTranslatorBudget();
|
|
112
117
|
try {
|
|
118
|
+
let response = await handleChatCompletionsWithBudget(req, config, logCtx, translatorBudget, logIds);
|
|
119
|
+
if (isInternalCallCapability(req.headers.get(ADVISOR_INTERNAL_CAPABILITY_HEADER))) response = attachAdvisorReasoning(response, logCtx);
|
|
113
120
|
return finalizeTranslatorBudgetResponse(
|
|
114
|
-
|
|
121
|
+
response,
|
|
115
122
|
translatorBudget,
|
|
116
123
|
req.signal,
|
|
117
124
|
);
|
|
@@ -272,6 +279,12 @@ async function handleChatCompletionsWithBudget(
|
|
|
272
279
|
const protocolSettings = resolveProtocolSettings(config);
|
|
273
280
|
const nativeChatCombos = protocolSettings.rollout.nativeChatCombos
|
|
274
281
|
&& settledRoute?.combo !== undefined && !effortRow;
|
|
282
|
+
// Antigravity direct lane (off by default): the conversation is decoded straight into the IR
|
|
283
|
+
// and the answer is encoded straight into Chat, with no internal Responses body either way.
|
|
284
|
+
const antigravityDecline = settledRoute && !chatNativeRoute
|
|
285
|
+
? antigravityDirectDeclineReason(settledRoute, chatBody, config, { cacheDiagnostics: cacheDiagnosticsObserving() })
|
|
286
|
+
: undefined;
|
|
287
|
+
const directRoute = settledRoute && !chatNativeRoute && antigravityDecline === undefined ? settledRoute : null;
|
|
275
288
|
const envelope = protocolSettings.unrepresentable === "reject" || nativeChatCombos
|
|
276
289
|
? createProtocolEnvelope({ inbound: "chat", body: chatBody, translatorBudget })
|
|
277
290
|
: undefined;
|
|
@@ -281,7 +294,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
281
294
|
inbound: "chat",
|
|
282
295
|
requestPath: chatNativeRoute
|
|
283
296
|
? requestPathForLane("chat", "native", "chat")
|
|
284
|
-
: requestPathForLane("chat", "bridge", upstreamWireForAdapter(settledRoute.provider.adapter)),
|
|
297
|
+
: requestPathForLane("chat", directRoute ? "direct" : "bridge", upstreamWireForAdapter(settledRoute.provider.adapter)),
|
|
285
298
|
features: envelope.features(),
|
|
286
299
|
policy: "reject",
|
|
287
300
|
});
|
|
@@ -294,8 +307,15 @@ async function handleChatCompletionsWithBudget(
|
|
|
294
307
|
}
|
|
295
308
|
markProtocolEntry(logCtx, {
|
|
296
309
|
inbound: "chat",
|
|
297
|
-
lane: chatNativeRoute ? "native" : "bridge",
|
|
298
|
-
reasonCodes:
|
|
310
|
+
lane: chatNativeRoute ? "native" : directRoute ? "direct" : "bridge",
|
|
311
|
+
reasonCodes: [
|
|
312
|
+
...(!chatNativeRoute && !directRoute && nativeDecline ? [nativeDecline] : []),
|
|
313
|
+
// Only a decline that says something about this route: with the switch off or a
|
|
314
|
+
// non-Antigravity target the path reason already explains the bridge.
|
|
315
|
+
...(antigravityDecline && antigravityDecline !== "rollout-disabled" && antigravityDecline !== "not-migrated"
|
|
316
|
+
? [antigravityDecline]
|
|
317
|
+
: []),
|
|
318
|
+
],
|
|
299
319
|
features: envelope ? () => envelope.features() : () => featuresFromChatBody(chatBody),
|
|
300
320
|
});
|
|
301
321
|
recordProtocolShadowPlan(logCtx, config, { inbound: "chat", model: requestedModel });
|
|
@@ -314,10 +334,18 @@ async function handleChatCompletionsWithBudget(
|
|
|
314
334
|
}
|
|
315
335
|
|
|
316
336
|
let internalBody: Rec;
|
|
337
|
+
let directConversation: ChatIrConversation | undefined;
|
|
317
338
|
try {
|
|
318
339
|
// Validate the full Chat boundary after routing. Native Chat keeps `chatBody` as
|
|
319
340
|
// its wire source; this Responses projection is used only by the fallback path.
|
|
320
|
-
|
|
341
|
+
// The direct lane validates the same boundary while decoding the conversation into the IR,
|
|
342
|
+
// and sends the core only the request settings.
|
|
343
|
+
if (directRoute) {
|
|
344
|
+
directConversation = chatConversationToIr(chatBody, chatBody.model as string);
|
|
345
|
+
internalBody = chatSettingsBody(chatBody);
|
|
346
|
+
} else {
|
|
347
|
+
internalBody = chatToResponsesBody(chatBody);
|
|
348
|
+
}
|
|
321
349
|
if (effortRow) {
|
|
322
350
|
internalBody.reasoning = {
|
|
323
351
|
...(isRec(internalBody.reasoning) ? internalBody.reasoning : {}),
|
|
@@ -370,6 +398,17 @@ async function handleChatCompletionsWithBudget(
|
|
|
370
398
|
}
|
|
371
399
|
|
|
372
400
|
const visionDescribeTerminal = req.headers.get("x-opencodex-vision-describe") === "1";
|
|
401
|
+
// Terminal advisor marker: the advisor sidecar's own loopback consultation re-enters through
|
|
402
|
+
// this surface. The bridge rebuilds headers from the FORWARD_HEADERS allowlist, which would
|
|
403
|
+
// drop the raw marker header — so the fact is detected here and carried as an option flag
|
|
404
|
+
// (same structure as the vision-describe fence above, depth cap 1).
|
|
405
|
+
//
|
|
406
|
+
// The header value is NOT evidence by itself: any external caller can send it, and the peer
|
|
407
|
+
// address proves nothing (Docker/WSL/tunnels/port-forwarding end on loopback). Internal
|
|
408
|
+
// authority comes from a process-owned capability minted at random per process and compared in
|
|
409
|
+
// constant time, so a forged header — or a token captured from an older process — is treated
|
|
410
|
+
// as an ordinary external request.
|
|
411
|
+
const advisorInternal = isInternalCallCapability(req.headers.get(ADVISOR_INTERNAL_CAPABILITY_HEADER));
|
|
373
412
|
// Concrete helper targets must fail before optional stored-main credential enrichment.
|
|
374
413
|
// Unresolved combos are checked after their concrete child route is selected in Responses.
|
|
375
414
|
if (settledRoute && !settledRoute.combo && isCanonicalOpenAiForwardProvider(settledRoute.provider)
|
|
@@ -455,6 +494,10 @@ async function handleChatCompletionsWithBudget(
|
|
|
455
494
|
abortSignal: req.signal,
|
|
456
495
|
// Body is Responses-shaped by now, but the client spoke Chat Completions.
|
|
457
496
|
inboundWire: "chat",
|
|
497
|
+
// A Chat caller may send an explicit empty `tools` array — a deny-all declaration the
|
|
498
|
+
// translation drops along with the whole catalog. Carried separately so the
|
|
499
|
+
// undeclared-tool guard can still enforce it; absent/partial catalogs stay deferred.
|
|
500
|
+
clientDeclaredEmptyToolCatalog: Array.isArray(chatBody.tools) && chatBody.tools.length === 0,
|
|
458
501
|
...((settledRoute?.combo || settledRoute?.routeKind === "policy") && droidDefaultEffort
|
|
459
502
|
? { droidDefaultEffort }
|
|
460
503
|
: {}),
|
|
@@ -468,13 +511,35 @@ async function handleChatCompletionsWithBudget(
|
|
|
468
511
|
// headers from the FORWARD_HEADERS allowlist, which would drop the raw
|
|
469
512
|
// header — so the fact is detected here and carried as an option flag.
|
|
470
513
|
...(visionDescribeTerminal ? { visionDescribeTerminal: true } : {}),
|
|
514
|
+
...(advisorInternal ? { advisorInternal: true } : {}),
|
|
471
515
|
translatorBudget,
|
|
472
516
|
...(logIds ? { onFirstOutput: () => recordFirstOutput(logCtx, logIds.start) } : {}),
|
|
473
517
|
onNativePassthroughTerminal: status => finalizeNativeLog(httpStatusForRequestLogTerminal(status, logCtx), { terminalStatus: status, closeReason: "terminal" }),
|
|
474
518
|
onNativePassthroughCancel: () => finalizeNativeLog(499, { closeReason: "client_cancel" }),
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
519
|
+
// The direct lane answers through the direct encoder whatever `directEncoders` says: the
|
|
520
|
+
// point of the lane is that neither direction passes through the internal Responses body.
|
|
521
|
+
...(directRoute && directConversation
|
|
522
|
+
? {
|
|
523
|
+
clientEncoder: { protocol: "chat" as const, stream, model: requestedModel, requestLane: "direct" as const },
|
|
524
|
+
directRequest: {
|
|
525
|
+
protocol: "chat" as const,
|
|
526
|
+
providerName: directRoute.providerName,
|
|
527
|
+
modelId: directRoute.modelId,
|
|
528
|
+
evidence: chatIrRouteEvidence(directConversation),
|
|
529
|
+
decode: base => withDirectChatConversation(base, chatBody, directConversation!),
|
|
530
|
+
bridgeConversation: () => {
|
|
531
|
+
const bridge = chatToResponsesBody(chatBody);
|
|
532
|
+
return {
|
|
533
|
+
input: bridge.input,
|
|
534
|
+
...(bridge.instructions !== undefined ? { instructions: bridge.instructions } : {}),
|
|
535
|
+
...(bridge.tools !== undefined ? { tools: bridge.tools } : {}),
|
|
536
|
+
};
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
}
|
|
540
|
+
: directEncodersApply(config, settledRoute)
|
|
541
|
+
? { clientEncoder: { protocol: "chat" as const, stream, model: requestedModel } }
|
|
542
|
+
: {}),
|
|
478
543
|
});
|
|
479
544
|
// Already in the Chat wire: no conversion. A direct-encoder body (PF-09) reports its own log
|
|
480
545
|
// facts to the deferred request log. A native combo child (PF-07) carries none: its row is
|
|
@@ -539,10 +604,17 @@ async function handleChatCompletionsWithBudget(
|
|
|
539
604
|
// below could never restore this one -- the translated client was told the provider
|
|
540
605
|
// throttled the turn, and handed a two-second wait to send it again.
|
|
541
606
|
classified.code = UPSTREAM_RESET_REPLAY_REFUSED_CODE;
|
|
607
|
+
} else if (upstreamCode === "claude_subscription_cooldown") {
|
|
608
|
+
classified.code = "claude_subscription_cooldown";
|
|
609
|
+
classified.type = "rate_limit_error";
|
|
542
610
|
} else if (upstreamCode === "model_not_found") {
|
|
543
611
|
// Structured model_not_found must win over classifyError's generic remaps.
|
|
544
612
|
classified.code = "model_not_found";
|
|
545
613
|
classified.type = "invalid_request_error";
|
|
614
|
+
} else if (upstreamCode === UNDECLARED_TOOL_CALL_ERROR_CODE) {
|
|
615
|
+
// The guard's deterministic refusal keeps its wire identity — same code the
|
|
616
|
+
// streamed `response.failed` frame and the Anthropic surface carry.
|
|
617
|
+
classified.code = UNDECLARED_TOOL_CALL_ERROR_CODE;
|
|
546
618
|
} else if (upstreamCode !== undefined && upstreamCode !== null && classified.code == null) {
|
|
547
619
|
classified.code = upstreamCode;
|
|
548
620
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { OcxProviderConfig, OcxReasoningReplayScopeRef } from "../types";
|
|
2
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
3
|
+
import { chatReasoningReplayPolicy, isChatRecord } from "../adapters/openai-chat/reasoning-replay";
|
|
4
|
+
import { bindReasoningReplayScope, reasoningReplayDestinationIdentity,
|
|
5
|
+
reasoningReplayKeyCredentialIdentity, rememberReasoningForCall } from "../responses/reasoning-replay-cache";
|
|
6
|
+
import { reasoningReplayConversationIdFromResponsesRequest, sessionIdHeaderFromRequest } from "./request-log-conversation";
|
|
7
|
+
|
|
8
|
+
type Rec = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export function nativeChatReplayScope(headers: Headers): OcxReasoningReplayScopeRef | undefined {
|
|
11
|
+
const clientThreadId = reasoningReplayConversationIdFromResponsesRequest({
|
|
12
|
+
threadIdHeader: headers.get("thread-id"), sessionIdHeader: sessionIdHeaderFromRequest(headers),
|
|
13
|
+
});
|
|
14
|
+
return clientThreadId ? { clientThreadId } : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Rebind before EVERY build: a key rotation must not retrieve another credential's reasoning. */
|
|
18
|
+
export function bindNativeChatReplayScope(
|
|
19
|
+
scope: OcxReasoningReplayScopeRef | undefined,
|
|
20
|
+
provider: OcxProviderConfig,
|
|
21
|
+
providerName: string,
|
|
22
|
+
modelId: string,
|
|
23
|
+
): void {
|
|
24
|
+
const providerDestinationIdentity = reasoningReplayDestinationIdentity(provider.baseUrl);
|
|
25
|
+
const credentialIdentity = reasoningReplayKeyCredentialIdentity(provider);
|
|
26
|
+
bindReasoningReplayScope(scope, providerDestinationIdentity && credentialIdentity ? {
|
|
27
|
+
providerName, modelId, adapterName: "openai-chat",
|
|
28
|
+
providerDestinationIdentity, credentialIdentity,
|
|
29
|
+
} : undefined);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Observe the native wire, never translate it or retain tool arguments/content. */
|
|
33
|
+
export function nativeChatReasoningCapture(
|
|
34
|
+
provider: OcxProviderConfig,
|
|
35
|
+
modelId: string,
|
|
36
|
+
scope: OcxReasoningReplayScopeRef | undefined,
|
|
37
|
+
budget: TranslatorBudget,
|
|
38
|
+
) {
|
|
39
|
+
const choices = new Map<number, { text: string; calls: Map<number, string> }>();
|
|
40
|
+
const accounting = { kind: "retained_collectors" as const };
|
|
41
|
+
// This optional fallback cannot retain more than the cache it feeds (256 KiB / 64 calls).
|
|
42
|
+
const maxBytes = 256 * 1024;
|
|
43
|
+
let bytes = 0;
|
|
44
|
+
let callCount = 0;
|
|
45
|
+
let disabled = !scope?.current || !chatReasoningReplayPolicy(provider, modelId).preserve;
|
|
46
|
+
const discard = () => {
|
|
47
|
+
budget.releaseRetained(bytes, accounting);
|
|
48
|
+
bytes = 0;
|
|
49
|
+
choices.clear();
|
|
50
|
+
disabled = true;
|
|
51
|
+
};
|
|
52
|
+
const remember = (text: string, calls: Iterable<string>) => {
|
|
53
|
+
for (const id of calls) rememberReasoningForCall(id, text, scope);
|
|
54
|
+
};
|
|
55
|
+
const observe = (value: Rec) => {
|
|
56
|
+
if (disabled || !Array.isArray(value.choices)) return;
|
|
57
|
+
for (const [ordinal, raw] of value.choices.entries()) {
|
|
58
|
+
if (!isChatRecord(raw) || !isChatRecord(raw.delta)) continue;
|
|
59
|
+
const index = typeof raw.index === "number" ? raw.index : ordinal;
|
|
60
|
+
if (!choices.has(index)) {
|
|
61
|
+
if (choices.size >= 64) { discard(); return; }
|
|
62
|
+
choices.set(index, { text: "", calls: new Map() });
|
|
63
|
+
}
|
|
64
|
+
const state = choices.get(index)!;
|
|
65
|
+
const delta = raw.delta;
|
|
66
|
+
const append = (previous: string, fragment: string): string | undefined => {
|
|
67
|
+
const added = Buffer.byteLength(fragment) * 2;
|
|
68
|
+
if (bytes + added > maxBytes) { discard(); return undefined; }
|
|
69
|
+
// Account the new retained fragment before concatenation; the transient reservation
|
|
70
|
+
// covers the old string while its replacement is allocated.
|
|
71
|
+
const reservation = budget.reserveTransient(Buffer.byteLength(previous) * 2, accounting);
|
|
72
|
+
try {
|
|
73
|
+
budget.chargeRetained(added, accounting);
|
|
74
|
+
bytes += added;
|
|
75
|
+
return previous + fragment;
|
|
76
|
+
} finally { reservation.release(); }
|
|
77
|
+
};
|
|
78
|
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
79
|
+
const text = append(state.text, delta.reasoning_content);
|
|
80
|
+
if (text === undefined) return;
|
|
81
|
+
state.text = text;
|
|
82
|
+
}
|
|
83
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
84
|
+
for (const [toolOrdinal, tool] of delta.tool_calls.entries()) {
|
|
85
|
+
if (!isChatRecord(tool) || typeof tool.id !== "string" || tool.id.length === 0) continue;
|
|
86
|
+
const toolIndex = typeof tool.index === "number" ? tool.index : toolOrdinal;
|
|
87
|
+
if (!state.calls.has(toolIndex)) {
|
|
88
|
+
if (callCount >= 64) { discard(); return; }
|
|
89
|
+
callCount += 1;
|
|
90
|
+
}
|
|
91
|
+
const previous = state.calls.get(toolIndex) ?? "";
|
|
92
|
+
if (previous === tool.id) continue;
|
|
93
|
+
const id = append(previous, tool.id);
|
|
94
|
+
if (id === undefined) return;
|
|
95
|
+
state.calls.set(toolIndex, id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// The completed choice is valid even if a caller dispatches its tool before [DONE].
|
|
99
|
+
if (typeof raw.finish_reason === "string" && raw.finish_reason.length > 0) {
|
|
100
|
+
remember(state.text, state.calls.values());
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
observe,
|
|
106
|
+
finish(success: boolean) {
|
|
107
|
+
if (success && !disabled) {
|
|
108
|
+
for (const state of choices.values()) remember(state.text, state.calls.values());
|
|
109
|
+
}
|
|
110
|
+
discard();
|
|
111
|
+
},
|
|
112
|
+
json(value: Rec) {
|
|
113
|
+
if (disabled || !Array.isArray(value.choices)) return;
|
|
114
|
+
for (const choice of value.choices) {
|
|
115
|
+
if (!isChatRecord(choice) || !isChatRecord(choice.message)) continue;
|
|
116
|
+
const message = choice.message;
|
|
117
|
+
if (typeof message.reasoning_content !== "string" || !Array.isArray(message.tool_calls)) continue;
|
|
118
|
+
remember(message.reasoning_content, message.tool_calls.filter(isChatRecord)
|
|
119
|
+
.flatMap(tool => typeof tool.id === "string" ? [tool.id] : []));
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { chatCompletionsErrorBody } from "../chat/outbound";
|
|
2
|
-
import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
|
|
2
|
+
import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
|
|
3
3
|
import { redactSecretString } from "../lib/redact";
|
|
4
4
|
import {
|
|
5
5
|
isTranslatorBudgetExceededError,
|
|
@@ -16,6 +16,39 @@ function isRec(value: unknown): value is Rec {
|
|
|
16
16
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export const UNDECLARED_CHAT_TOOL_MESSAGE = "Upstream emitted a client tool call outside this request's declared tool catalog.";
|
|
20
|
+
|
|
21
|
+
function* chatToolCalls(value: Rec): Generator<{ key: string; name: string; snapshot: boolean; invalid?: boolean }> {
|
|
22
|
+
const choices = Array.isArray(value.choices) ? value.choices : [];
|
|
23
|
+
for (const [position, choice] of choices.entries()) {
|
|
24
|
+
if (!isRec(choice)) continue;
|
|
25
|
+
const choiceIndex = typeof choice.index === "number" ? choice.index : position;
|
|
26
|
+
for (const [payload, snapshot] of [[choice.delta, false], [choice.message, true]] as const) {
|
|
27
|
+
if (!isRec(payload)) continue;
|
|
28
|
+
const tools = Array.isArray(payload.tool_calls) ? payload.tool_calls : [];
|
|
29
|
+
for (const [index, tool] of tools.entries()) {
|
|
30
|
+
const fn = isRec(tool) && isRec(tool.function) ? tool.function : {};
|
|
31
|
+
const toolIndex = isRec(tool) && typeof tool.index === "number" ? tool.index : index;
|
|
32
|
+
yield {
|
|
33
|
+
key: `${choiceIndex}:tool:${toolIndex}`, name: typeof fn.name === "string" ? fn.name : "", snapshot,
|
|
34
|
+
invalid: !isRec(tool) || (tool.type !== undefined && tool.type !== "function"),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (payload.function_call !== undefined && payload.function_call !== null) {
|
|
38
|
+
const fn = isRec(payload.function_call) ? payload.function_call : {};
|
|
39
|
+
yield { key: `${choiceIndex}:legacy`, name: typeof fn.name === "string" ? fn.name : "", snapshot };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function chatResponseHasUndeclaredToolCalls(value: Rec, declared: ReadonlySet<string>): boolean {
|
|
46
|
+
for (const call of chatToolCalls(value)) {
|
|
47
|
+
if (call.invalid || !declared.has(call.name)) return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
19
52
|
export function usageFromChat(value: unknown): OcxUsage | undefined {
|
|
20
53
|
if (!isRec(value)) return undefined;
|
|
21
54
|
const prompt = typeof value.prompt_tokens === "number" ? value.prompt_tokens : undefined;
|
|
@@ -134,8 +167,11 @@ interface NativeChatSseOptions {
|
|
|
134
167
|
* `stallTimeoutSec` then resolves to disabled so a slow local model is not cut mid-turn.
|
|
135
168
|
*/
|
|
136
169
|
localUpstream?: boolean;
|
|
170
|
+
clientDeclaredEmptyToolCatalog?: boolean;
|
|
171
|
+
declaredLegacyFunctionNames?: ReadonlySet<string>;
|
|
137
172
|
onFirstOutput?: () => void;
|
|
138
173
|
onUsage: (usage: OcxUsage) => void;
|
|
174
|
+
onChunk?: (value: Rec) => void;
|
|
139
175
|
onTerminal?: (status: number, message?: string) => void;
|
|
140
176
|
onCancel?: () => void;
|
|
141
177
|
}
|
|
@@ -154,6 +190,29 @@ export function nativeChatSse(
|
|
|
154
190
|
}
|
|
155
191
|
});
|
|
156
192
|
let queuedBytes = 0;
|
|
193
|
+
const declared = options.declaredLegacyFunctionNames ?? new Set<string>();
|
|
194
|
+
const toolNames = new Map<string, string>();
|
|
195
|
+
const pendingToolNames = new Set<string>();
|
|
196
|
+
const heldFrames: string[] = [];
|
|
197
|
+
let heldBytes = 0;
|
|
198
|
+
let toolNameBytes = 0;
|
|
199
|
+
const hasPendingToolName = () => pendingToolNames.size > 0;
|
|
200
|
+
const inspectToolCalls = (value: Rec): boolean => {
|
|
201
|
+
for (const call of chatToolCalls(value)) {
|
|
202
|
+
if (call.invalid) return false;
|
|
203
|
+
const previous = toolNames.get(call.key);
|
|
204
|
+
const name = call.snapshot ? call.name : (previous ?? "") + call.name;
|
|
205
|
+
if (!declared.has(name) && ![...declared].some(allowed => allowed.startsWith(name))) return false;
|
|
206
|
+
const bytes = previous === undefined ? 128 + Buffer.byteLength(call.key) * 2 + Buffer.byteLength(name) * 2
|
|
207
|
+
: Math.max(0, Buffer.byteLength(name) - Buffer.byteLength(previous)) * 2;
|
|
208
|
+
options.translatorBudget.chargeRetained(bytes, scope);
|
|
209
|
+
toolNameBytes += bytes;
|
|
210
|
+
toolNames.set(call.key, name);
|
|
211
|
+
if (declared.has(name)) pendingToolNames.delete(call.key);
|
|
212
|
+
else pendingToolNames.add(call.key);
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
};
|
|
157
216
|
let sawFinish = false;
|
|
158
217
|
let sawDone = false;
|
|
159
218
|
let firstOutput = false;
|
|
@@ -191,7 +250,31 @@ export function nativeChatSse(
|
|
|
191
250
|
throw error;
|
|
192
251
|
}
|
|
193
252
|
};
|
|
194
|
-
const
|
|
253
|
+
const releaseHeld = () => {
|
|
254
|
+
options.translatorBudget.releaseRetained(heldBytes, scope);
|
|
255
|
+
heldBytes = 0;
|
|
256
|
+
heldFrames.length = 0;
|
|
257
|
+
};
|
|
258
|
+
const relay = (controller: ReadableStreamDefaultController<Uint8Array>, text: string) => {
|
|
259
|
+
if (hasPendingToolName()) {
|
|
260
|
+
const bytes = Buffer.byteLength(text) * 2;
|
|
261
|
+
options.translatorBudget.chargeRetained(bytes, scope);
|
|
262
|
+
heldBytes += bytes;
|
|
263
|
+
heldFrames.push(text);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
for (const frame of heldFrames) enqueue(controller, frame);
|
|
267
|
+
releaseHeld();
|
|
268
|
+
enqueue(controller, text);
|
|
269
|
+
};
|
|
270
|
+
const releaseBuffer = () => {
|
|
271
|
+
buffer.clear();
|
|
272
|
+
releaseHeld();
|
|
273
|
+
options.translatorBudget.releaseRetained(toolNameBytes, scope);
|
|
274
|
+
toolNameBytes = 0;
|
|
275
|
+
toolNames.clear();
|
|
276
|
+
pendingToolNames.clear();
|
|
277
|
+
};
|
|
195
278
|
const settle = (status: number, message?: string) => {
|
|
196
279
|
if (settled) return;
|
|
197
280
|
settled = true;
|
|
@@ -206,12 +289,12 @@ export function nativeChatSse(
|
|
|
206
289
|
type = "upstream_error",
|
|
207
290
|
) => {
|
|
208
291
|
const safeMessage = redactSecretString(message);
|
|
292
|
+
releaseBuffer();
|
|
209
293
|
try {
|
|
210
294
|
const frame = `data: ${JSON.stringify(chatCompletionsErrorBody(status, safeMessage, type, code))}\n\n`;
|
|
211
295
|
if (code === "translation_buffer_limit") controller.enqueue(encoder.encode(frame));
|
|
212
296
|
else enqueue(controller, frame);
|
|
213
297
|
} catch { /* controller may already be closed */ }
|
|
214
|
-
releaseBuffer();
|
|
215
298
|
settle(status, safeMessage);
|
|
216
299
|
try { void reader.cancel(new Error(safeMessage)).catch(() => {}); } catch { /* already closed */ }
|
|
217
300
|
try { controller.close(); } catch { /* already closed */ }
|
|
@@ -224,16 +307,20 @@ export function nativeChatSse(
|
|
|
224
307
|
const payload = sseDataPayload(block);
|
|
225
308
|
if (payload === null) {
|
|
226
309
|
if (performance.now() >= pullDeadline) throw stalled;
|
|
227
|
-
|
|
310
|
+
relay(controller, block + delimiter);
|
|
228
311
|
return;
|
|
229
312
|
}
|
|
230
313
|
const trimmed = payload.trim();
|
|
231
314
|
if (trimmed === "") {
|
|
232
315
|
if (performance.now() >= pullDeadline) throw stalled;
|
|
233
|
-
|
|
316
|
+
relay(controller, block + delimiter);
|
|
234
317
|
return;
|
|
235
318
|
}
|
|
236
319
|
if (trimmed === "[DONE]") {
|
|
320
|
+
if (hasPendingToolName()) {
|
|
321
|
+
fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
237
324
|
sawDone = true;
|
|
238
325
|
enqueue(controller, replaceSseDataPayload(block, "[DONE]") + delimiter);
|
|
239
326
|
releaseBuffer();
|
|
@@ -272,9 +359,20 @@ export function nativeChatSse(
|
|
|
272
359
|
return;
|
|
273
360
|
}
|
|
274
361
|
const usage = usageFromChat(parsed.usage);
|
|
362
|
+
options.onChunk?.(parsed);
|
|
275
363
|
if (usage) options.onUsage(usage);
|
|
364
|
+
if (options.clientDeclaredEmptyToolCatalog && !inspectToolCalls(parsed)) {
|
|
365
|
+
fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
276
368
|
const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
|
|
277
369
|
const finished = choices.some(choice => isRec(choice) && typeof choice.finish_reason === "string" && choice.finish_reason.length > 0);
|
|
370
|
+
if (hasPendingToolName() && choices.some((choice, position) => isRec(choice)
|
|
371
|
+
&& typeof choice.finish_reason === "string" && choice.finish_reason.length > 0
|
|
372
|
+
&& [...pendingToolNames].some(key => key.startsWith(`${typeof choice.index === "number" ? choice.index : position}:`)))) {
|
|
373
|
+
fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
278
376
|
if (finished) sawFinish = true;
|
|
279
377
|
const hasText = choices.some(choice => {
|
|
280
378
|
if (!isRec(choice) || !isRec(choice.delta)) return false;
|
|
@@ -302,11 +400,11 @@ export function nativeChatSse(
|
|
|
302
400
|
const hasProgress = hasText || hasTool;
|
|
303
401
|
if (hasProgress || finished) pullDeadline = performance.now() + stallMs;
|
|
304
402
|
else if (performance.now() >= pullDeadline) throw stalled;
|
|
305
|
-
if (!firstOutput && hasProgress) {
|
|
403
|
+
if (!firstOutput && hasProgress && !hasPendingToolName()) {
|
|
306
404
|
firstOutput = true;
|
|
307
405
|
options.onFirstOutput?.();
|
|
308
406
|
}
|
|
309
|
-
|
|
407
|
+
relay(controller, replaceSseDataPayload(block, JSON.stringify(normalizedChunk(parsed, options.requestedModel))) + delimiter);
|
|
310
408
|
};
|
|
311
409
|
|
|
312
410
|
function onAbort() {
|
|
@@ -336,6 +434,7 @@ export function nativeChatSse(
|
|
|
336
434
|
if (next) {
|
|
337
435
|
buffer.compact();
|
|
338
436
|
processBlock(controller, next.block, next.delimiter);
|
|
437
|
+
if (heldFrames.length > 0 && !settled) continue;
|
|
339
438
|
return;
|
|
340
439
|
}
|
|
341
440
|
if (performance.now() >= pullDeadline) throw stalled;
|
|
@@ -370,6 +469,10 @@ export function nativeChatSse(
|
|
|
370
469
|
continue;
|
|
371
470
|
}
|
|
372
471
|
buffer.append(decoder.decode());
|
|
472
|
+
if (hasPendingToolName()) {
|
|
473
|
+
fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
373
476
|
if (buffer.tail().trim().length > 0) {
|
|
374
477
|
fail(controller, "upstream SSE ended with an unterminated event", "upstream_sse_unterminated");
|
|
375
478
|
return;
|