@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OcxProviderConfig, RequestPacingRule } from "../types";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
3
4
|
import { carryReplayRefusal, isNonReplayableResponse, markResponseNonReplayable } from "../lib/upstream-retry";
|
|
4
5
|
import { redactSecretString } from "../lib/redact";
|
|
@@ -39,7 +40,7 @@ export function registerProviderResponseProvenance(markers: WeakMap<Response, un
|
|
|
39
40
|
responseProvenance.add(markers);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export type RequestPacingQueueOverloadReason = "queue_full" | "queue_expired";
|
|
43
|
+
export type RequestPacingQueueOverloadReason = "queue_full" | "queue_expired" | "keys_exhausted";
|
|
43
44
|
|
|
44
45
|
export class RequestPacingQueueOverloadError extends Error {
|
|
45
46
|
constructor(
|
|
@@ -47,7 +48,7 @@ export class RequestPacingQueueOverloadError extends Error {
|
|
|
47
48
|
public readonly reason: RequestPacingQueueOverloadReason,
|
|
48
49
|
public readonly retryAfterSeconds: number,
|
|
49
50
|
) {
|
|
50
|
-
super(reason === "queue_full"
|
|
51
|
+
super(reason === "keys_exhausted" ? "No eligible API key remains; wait for the provider cooldown before retrying" : reason === "queue_full"
|
|
51
52
|
? `request pacing queue for provider '${providerName}' is full`
|
|
52
53
|
: `request pacing queue for provider '${providerName}' exceeded the maximum queued age`);
|
|
53
54
|
this.name = "RequestPacingQueueOverloadError";
|
|
@@ -62,6 +63,10 @@ export class RequestPacingProviderRemovedError extends Error {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
interface Waiter {
|
|
66
|
+
keyLease: boolean;
|
|
67
|
+
keyId?: string;
|
|
68
|
+
keyIntervalMs: number;
|
|
69
|
+
keyMaxConcurrent: number;
|
|
65
70
|
modelId?: string;
|
|
66
71
|
providerIntervalMs: number;
|
|
67
72
|
modelIntervalMs: number;
|
|
@@ -75,6 +80,9 @@ interface Waiter {
|
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
interface ProviderPacer {
|
|
83
|
+
keyNextStartAt: Map<string, number>;
|
|
84
|
+
keyInFlight: Map<string, number>;
|
|
85
|
+
keyStarts: Map<string, number>;
|
|
78
86
|
queue: Waiter[];
|
|
79
87
|
providerNextStartAt: number;
|
|
80
88
|
modelNextStartAt: Map<string, number>;
|
|
@@ -138,6 +146,7 @@ export interface ProviderRequestPacingStatus {
|
|
|
138
146
|
inFlight?: number;
|
|
139
147
|
lastStartedAt?: number;
|
|
140
148
|
lastModelId?: string;
|
|
149
|
+
keys?: Array<{ id: string; starts: number; inFlight: number; queued: number; nextSlotInMs: number }>;
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
const pacers = new Map<string, ProviderPacer>();
|
|
@@ -165,6 +174,13 @@ function normalizedInterval(rule: RequestPacingRule | undefined): number {
|
|
|
165
174
|
return Math.max(rpmInterval, fixedInterval);
|
|
166
175
|
}
|
|
167
176
|
|
|
177
|
+
function keyBucket(provider: OcxProviderConfig): string | undefined {
|
|
178
|
+
if (provider.authMode === "oauth" || provider.authMode === "forward" || !provider.apiKey) return;
|
|
179
|
+
if (!provider.requestPacing?.enabled || (!provider.requestPacing.keys && provider.requestPacing.keySelection !== "round-robin")) return;
|
|
180
|
+
// Internal only: duplicate references share one bucket; never serialize a key or digest.
|
|
181
|
+
return createHash("sha256").update(provider._apiKeyAttempt?.reference ?? provider.apiKey).digest("hex");
|
|
182
|
+
}
|
|
183
|
+
|
|
168
184
|
function normalizedMaxConcurrent(rule: RequestPacingRule | undefined): number {
|
|
169
185
|
return typeof rule?.maxConcurrentRequests === "number" && rule.maxConcurrentRequests > 0
|
|
170
186
|
// Floor so runtime-injected configs that bypass the integer schema cannot admit
|
|
@@ -178,10 +194,14 @@ export function requestPacingIntervalMs(provider: OcxProviderConfig, modelId?: s
|
|
|
178
194
|
const policy = provider.requestPacing;
|
|
179
195
|
if (!policy?.enabled) return 0;
|
|
180
196
|
const override = modelId ? policy.models?.[modelId] : undefined;
|
|
181
|
-
return Math.max(normalizedInterval(policy), normalizedInterval(override));
|
|
197
|
+
return Math.max(normalizedInterval(policy), normalizedInterval(override), keyBucket(provider) ? normalizedInterval(policy.keys) : 0);
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string): {
|
|
201
|
+
keyLease: boolean;
|
|
202
|
+
keyId?: string;
|
|
203
|
+
keyIntervalMs: number;
|
|
204
|
+
keyMaxConcurrent: number;
|
|
185
205
|
providerIntervalMs: number;
|
|
186
206
|
modelIntervalMs: number;
|
|
187
207
|
providerMaxConcurrent: number;
|
|
@@ -189,9 +209,14 @@ function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string):
|
|
|
189
209
|
} {
|
|
190
210
|
const policy = provider.requestPacing;
|
|
191
211
|
if (!policy?.enabled) {
|
|
192
|
-
return { providerIntervalMs: 0, modelIntervalMs: 0, providerMaxConcurrent: 0, modelMaxConcurrent: 0 };
|
|
212
|
+
return { keyLease: false, keyIntervalMs: 0, keyMaxConcurrent: 0, providerIntervalMs: 0, modelIntervalMs: 0, providerMaxConcurrent: 0, modelMaxConcurrent: 0 };
|
|
193
213
|
}
|
|
214
|
+
const keyId = keyBucket(provider);
|
|
194
215
|
return {
|
|
216
|
+
keyLease: true,
|
|
217
|
+
keyId,
|
|
218
|
+
keyIntervalMs: keyId ? normalizedInterval(policy.keys) : 0,
|
|
219
|
+
keyMaxConcurrent: keyId ? normalizedMaxConcurrent(policy.keys) : 0,
|
|
195
220
|
providerIntervalMs: normalizedInterval(policy),
|
|
196
221
|
modelIntervalMs: modelId ? normalizedInterval(policy.models?.[modelId]) : 0,
|
|
197
222
|
providerMaxConcurrent: normalizedMaxConcurrent(policy),
|
|
@@ -209,13 +234,14 @@ function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string):
|
|
|
209
234
|
*/
|
|
210
235
|
export function requestPacingMaxConcurrentRequests(provider: OcxProviderConfig, modelId?: string): number {
|
|
211
236
|
const limits = requestPacingIntervals(provider, modelId);
|
|
212
|
-
return Math.max(limits.providerMaxConcurrent, limits.modelMaxConcurrent);
|
|
237
|
+
return Math.max(limits.providerMaxConcurrent, limits.modelMaxConcurrent, limits.keyMaxConcurrent);
|
|
213
238
|
}
|
|
214
239
|
|
|
215
|
-
function waiterReadyAt(state: ProviderPacer, modelId: string | undefined): number {
|
|
240
|
+
function waiterReadyAt(state: ProviderPacer, modelId: string | undefined, keyId?: string): number {
|
|
216
241
|
return Math.max(
|
|
217
242
|
state.providerNextStartAt,
|
|
218
243
|
modelId ? (state.modelNextStartAt.get(modelId) ?? 0) : 0,
|
|
244
|
+
keyId ? (state.keyNextStartAt.get(keyId) ?? 0) : 0,
|
|
219
245
|
);
|
|
220
246
|
}
|
|
221
247
|
|
|
@@ -224,8 +250,9 @@ function pacingRetryAfterSeconds(
|
|
|
224
250
|
modelId: string | undefined,
|
|
225
251
|
now: number,
|
|
226
252
|
concurrencyCapped = false,
|
|
253
|
+
keyId?: string,
|
|
227
254
|
): number {
|
|
228
|
-
const intervalSeconds = Math.max(1, Math.ceil(Math.max(0, waiterReadyAt(state, modelId) - now) / 1000));
|
|
255
|
+
const intervalSeconds = Math.max(1, Math.ceil(Math.max(0, waiterReadyAt(state, modelId, keyId) - now) / 1000));
|
|
229
256
|
// A waiter blocked by an in-flight lease has no interval to report: when the wait ends
|
|
230
257
|
// in refusal, the honest answer is that the cap is saturated, not "retry every second".
|
|
231
258
|
return concurrencyCapped
|
|
@@ -240,7 +267,7 @@ function makeProviderRequestSlot(
|
|
|
240
267
|
): BodyTrackableProviderRequestSlot {
|
|
241
268
|
let released = false;
|
|
242
269
|
let bodyTracked = false;
|
|
243
|
-
const leased = waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0;
|
|
270
|
+
const leased = (!!waiter.keyId && waiter.keyLease) || waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0;
|
|
244
271
|
const slot: BodyTrackableProviderRequestSlot = {
|
|
245
272
|
providerName,
|
|
246
273
|
leased,
|
|
@@ -258,6 +285,7 @@ function makeProviderRequestSlot(
|
|
|
258
285
|
released = true;
|
|
259
286
|
waiter.signal?.removeEventListener("abort", releaseOnAbort);
|
|
260
287
|
if (waiter.providerMaxConcurrent > 0 && state.providerInFlight > 0) state.providerInFlight -= 1;
|
|
288
|
+
if (waiter.keyId && waiter.keyLease) state.keyInFlight.set(waiter.keyId, Math.max(0, (state.keyInFlight.get(waiter.keyId) ?? 0) - 1));
|
|
261
289
|
if (waiter.modelId && waiter.modelMaxConcurrent > 0) {
|
|
262
290
|
const current = state.modelInFlight.get(waiter.modelId) ?? 0;
|
|
263
291
|
if (current <= 1) state.modelInFlight.delete(waiter.modelId);
|
|
@@ -293,7 +321,8 @@ function rejectExpiredWaiters(providerName: string, state: ProviderPacer, now: n
|
|
|
293
321
|
state,
|
|
294
322
|
waiter.modelId,
|
|
295
323
|
now,
|
|
296
|
-
waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0,
|
|
324
|
+
waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0 || waiter.keyMaxConcurrent > 0,
|
|
325
|
+
waiter.keyId,
|
|
297
326
|
),
|
|
298
327
|
));
|
|
299
328
|
}
|
|
@@ -327,17 +356,18 @@ function runQueue(providerName: string, state: ProviderPacer): void {
|
|
|
327
356
|
|
|
328
357
|
const providerReadyAt = Math.max(now, state.providerNextStartAt);
|
|
329
358
|
const waiterIndex = state.queue.findIndex(waiter => {
|
|
359
|
+
if (waiter.keyId && waiter.keyMaxConcurrent > 0 && (state.keyInFlight.get(waiter.keyId) ?? 0) >= waiter.keyMaxConcurrent) return false;
|
|
330
360
|
if (waiter.providerMaxConcurrent > 0 && state.providerInFlight >= waiter.providerMaxConcurrent) return false;
|
|
331
361
|
if (waiter.modelId && waiter.modelMaxConcurrent > 0
|
|
332
362
|
&& (state.modelInFlight.get(waiter.modelId) ?? 0) >= waiter.modelMaxConcurrent) return false;
|
|
333
363
|
const modelReadyAt = waiter.modelId ? (state.modelNextStartAt.get(waiter.modelId) ?? 0) : 0;
|
|
334
|
-
return Math.max(providerReadyAt, modelReadyAt) <= now;
|
|
364
|
+
return Math.max(providerReadyAt, modelReadyAt, waiter.keyId ? state.keyNextStartAt.get(waiter.keyId) ?? 0 : 0) <= now;
|
|
335
365
|
});
|
|
336
366
|
if (waiterIndex < 0) {
|
|
337
367
|
let earliestAt = Number.POSITIVE_INFINITY;
|
|
338
368
|
for (const waiter of state.queue) {
|
|
339
369
|
const modelReadyAt = waiter.modelId ? (state.modelNextStartAt.get(waiter.modelId) ?? 0) : 0;
|
|
340
|
-
const readyAt = Math.max(providerReadyAt, modelReadyAt);
|
|
370
|
+
const readyAt = Math.max(providerReadyAt, modelReadyAt, waiter.keyId ? state.keyNextStartAt.get(waiter.keyId) ?? 0 : 0);
|
|
341
371
|
const expiresAt = waiter.queuedAt + maxQueueAgeMs;
|
|
342
372
|
// A waiter whose start time already passed is blocked on an in-flight lease; its release
|
|
343
373
|
// re-runs this queue through a microtask, so the timer only needs its expiry backstop.
|
|
@@ -362,6 +392,11 @@ function runQueue(providerName: string, state: ProviderPacer): void {
|
|
|
362
392
|
state.lastStartedAt = startedAt;
|
|
363
393
|
state.lastModelId = waiter.modelId;
|
|
364
394
|
state.providerNextStartAt = startedAt + waiter.providerIntervalMs;
|
|
395
|
+
if (waiter.keyId) {
|
|
396
|
+
state.keyNextStartAt.set(waiter.keyId, startedAt + waiter.keyIntervalMs);
|
|
397
|
+
if (waiter.keyLease) state.keyInFlight.set(waiter.keyId, (state.keyInFlight.get(waiter.keyId) ?? 0) + 1);
|
|
398
|
+
state.keyStarts.set(waiter.keyId, (state.keyStarts.get(waiter.keyId) ?? 0) + 1);
|
|
399
|
+
}
|
|
365
400
|
if (waiter.modelId && waiter.modelIntervalMs > 0) {
|
|
366
401
|
state.modelNextStartAt.set(waiter.modelId, startedAt + waiter.modelIntervalMs);
|
|
367
402
|
}
|
|
@@ -386,22 +421,34 @@ export async function waitForProviderRequestSlot(
|
|
|
386
421
|
// by interval only, or a follow-up would queue behind the lease its own turn holds.
|
|
387
422
|
const concurrency = options?.concurrency !== false;
|
|
388
423
|
const waiterIntervals = concurrency ? intervals : {
|
|
424
|
+
keyId: intervals.keyId, keyLease: false, keyIntervalMs: intervals.keyIntervalMs, keyMaxConcurrent: 0,
|
|
389
425
|
providerIntervalMs: intervals.providerIntervalMs,
|
|
390
426
|
modelIntervalMs: intervals.modelIntervalMs,
|
|
391
427
|
providerMaxConcurrent: 0,
|
|
392
428
|
modelMaxConcurrent: 0,
|
|
393
429
|
};
|
|
394
430
|
const paced = Math.max(intervals.providerIntervalMs, intervals.modelIntervalMs) > 0
|
|
431
|
+
|| intervals.keyId !== undefined
|
|
395
432
|
|| waiterIntervals.providerMaxConcurrent > 0
|
|
396
433
|
|| waiterIntervals.modelMaxConcurrent > 0;
|
|
397
434
|
if (!paced) return inertProviderRequestSlot;
|
|
398
435
|
if (signal?.aborted) throw abortReason(signal);
|
|
399
436
|
|
|
400
437
|
const state = pacers.get(providerName) ?? {
|
|
438
|
+
keyNextStartAt: new Map<string, number>(), keyInFlight: new Map<string, number>(), keyStarts: new Map<string, number>(),
|
|
401
439
|
queue: [], providerNextStartAt: 0, modelNextStartAt: new Map<string, number>(),
|
|
402
440
|
providerInFlight: 0, modelInFlight: new Map<string, number>(),
|
|
403
441
|
};
|
|
404
442
|
pacers.set(providerName, state);
|
|
443
|
+
// Retire removed credentials only after their queued work, leases and spacing debt end.
|
|
444
|
+
const currentKeys = new Set((provider.apiKeyPool ?? []).map(entry =>
|
|
445
|
+
keyBucket({ ...provider, apiKey: entry.key, _apiKeyAttempt: undefined })));
|
|
446
|
+
currentKeys.add(intervals.keyId);
|
|
447
|
+
for (const key of state.keyStarts.keys()) {
|
|
448
|
+
if (currentKeys.has(key) || (state.keyInFlight.get(key) ?? 0) > 0
|
|
449
|
+
|| (state.keyNextStartAt.get(key) ?? 0) > runtime.now() || state.queue.some(waiter => waiter.keyId === key)) continue;
|
|
450
|
+
state.keyStarts.delete(key); state.keyInFlight.delete(key); state.keyNextStartAt.delete(key);
|
|
451
|
+
}
|
|
405
452
|
|
|
406
453
|
// Give already-eligible or expired waiters a chance to leave before applying the
|
|
407
454
|
// admission bound to the newest request. This preserves FIFO-ish fairness while
|
|
@@ -419,7 +466,8 @@ export async function waitForProviderRequestSlot(
|
|
|
419
466
|
state,
|
|
420
467
|
modelId,
|
|
421
468
|
runtime.now(),
|
|
422
|
-
waiterIntervals.providerMaxConcurrent > 0 || waiterIntervals.modelMaxConcurrent > 0,
|
|
469
|
+
waiterIntervals.providerMaxConcurrent > 0 || waiterIntervals.modelMaxConcurrent > 0 || waiterIntervals.keyMaxConcurrent > 0,
|
|
470
|
+
waiterIntervals.keyId,
|
|
423
471
|
),
|
|
424
472
|
);
|
|
425
473
|
}
|
|
@@ -464,14 +512,13 @@ export async function waitForProviderRequestSlot(
|
|
|
464
512
|
* means the exchange is already finished, so the lease returns immediately. A body that
|
|
465
513
|
* is neither read nor cancelled for REQUEST_PACING_UNCONSUMED_BODY_MS releases its lease
|
|
466
514
|
* and cancels the body, so a dropped Response cannot hold a slot forever. The returned
|
|
467
|
-
* Response preserves status, statusText, headers,
|
|
515
|
+
* Response preserves status, statusText, headers, replay markers, and registered provenance.
|
|
468
516
|
*/
|
|
469
517
|
export function trackProviderRequestSlotBody(
|
|
470
518
|
slot: ProviderRequestSlot | undefined,
|
|
471
519
|
response: Response,
|
|
472
520
|
): Response {
|
|
473
|
-
// An unleased slot has nothing to return on body close
|
|
474
|
-
// would break identity-based markers (the eager WS relay registry is a WeakSet).
|
|
521
|
+
// An unleased slot has nothing to return on body close; preserve the response identity.
|
|
475
522
|
if (!slot?.leased) return response;
|
|
476
523
|
// Slots built outside waitForProviderRequestSlot (test doubles) satisfy only the public
|
|
477
524
|
// ProviderRequestSlot shape; their bodies still own the release through the wrapper below.
|
|
@@ -657,24 +704,35 @@ export function providerRequestPacingStatus(
|
|
|
657
704
|
if (state && state.queue.length > 0) {
|
|
658
705
|
let earliestQueuedSlotAt = Number.POSITIVE_INFINITY;
|
|
659
706
|
for (const waiter of state.queue) {
|
|
660
|
-
earliestQueuedSlotAt = Math.min(earliestQueuedSlotAt, waiterReadyAt(state, waiter.modelId));
|
|
707
|
+
earliestQueuedSlotAt = Math.min(earliestQueuedSlotAt, waiterReadyAt(state, waiter.modelId, waiter.keyId));
|
|
661
708
|
}
|
|
662
709
|
if (Number.isFinite(earliestQueuedSlotAt)) nextSlotAt = earliestQueuedSlotAt;
|
|
663
710
|
}
|
|
664
|
-
const providerConcurrencyCap =
|
|
711
|
+
const providerConcurrencyCap = normalizedMaxConcurrent(provider.requestPacing);
|
|
712
|
+
const hasKeyAccounting = provider.requestPacing?.enabled === true && keyBucket(provider) !== undefined;
|
|
665
713
|
// Gate on enabled so the status surface agrees with enforcement: a disabled requestPacing
|
|
666
714
|
// block must not report in-flight leases alongside enabled: false.
|
|
667
|
-
const anyConcurrencyCap = provider.requestPacing?.enabled === true && (providerConcurrencyCap > 0
|
|
715
|
+
const anyConcurrencyCap = provider.requestPacing?.enabled === true && (hasKeyAccounting || providerConcurrencyCap > 0
|
|
668
716
|
|| Object.values(provider.requestPacing?.models ?? {}).some(rule => normalizedMaxConcurrent(rule) > 0));
|
|
669
717
|
return {
|
|
670
718
|
provider: providerName,
|
|
671
719
|
enabled: provider.requestPacing?.enabled === true,
|
|
672
720
|
queued: state?.queue.length ?? 0,
|
|
673
721
|
nextSlotInMs: Math.max(0, Math.ceil(nextSlotAt - now)),
|
|
722
|
+
...(provider.requestPacing?.enabled && (provider.requestPacing.keys || provider.requestPacing.keySelection === "round-robin") ? {
|
|
723
|
+
keys: (provider.apiKeyPool ?? []).map(entry => {
|
|
724
|
+
const bucket = keyBucket({ ...provider, apiKey: entry.key, _apiKeyAttempt: undefined })!;
|
|
725
|
+
return { id: entry.id, starts: state?.keyStarts.get(bucket) ?? 0, inFlight: state?.keyInFlight.get(bucket) ?? 0,
|
|
726
|
+
queued: state?.queue.filter(waiter => waiter.keyId === bucket).length ?? 0,
|
|
727
|
+
nextSlotInMs: Math.max(0, Math.ceil((state?.keyNextStartAt.get(bucket) ?? 0) - now)) };
|
|
728
|
+
}),
|
|
729
|
+
} : {}),
|
|
674
730
|
// A provider cap counts every paced send in providerInFlight; model-only caps keep
|
|
675
731
|
// providerInFlight at zero, so report the per-model sum for those providers instead.
|
|
676
732
|
...(anyConcurrencyCap ? {
|
|
677
|
-
inFlight:
|
|
733
|
+
inFlight: hasKeyAccounting
|
|
734
|
+
? [...(state?.keyInFlight.values() ?? [])].reduce((sum, count) => sum + count, 0)
|
|
735
|
+
: providerConcurrencyCap > 0
|
|
678
736
|
? state?.providerInFlight ?? 0
|
|
679
737
|
: [...(state?.modelInFlight.values() ?? [])].reduce((sum, count) => sum + count, 0),
|
|
680
738
|
} : {}),
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-time provider resolution: merge a saved config row with its registry entry.
|
|
3
|
+
*
|
|
4
|
+
* This lives outside `src/router.ts` because `providers/api-key-selection.ts` needs exactly
|
|
5
|
+
* one function from the router, and importing the router for it dragged the whole routing
|
|
6
|
+
* graph into an import cycle. The router re-exports both symbols below so every existing
|
|
7
|
+
* import keeps working.
|
|
8
|
+
*/
|
|
9
|
+
import type { OcxProviderConfig } from "../types";
|
|
10
|
+
import { providerUsesKeyAuthOverride, resolveProviderApiKey } from "./key-store";
|
|
11
|
+
import { GCP_CREDENTIAL_MARKER_PREFIX } from "../lib/gcp-adc";
|
|
12
|
+
import { captureProviderApiKeySelection } from "./api-key-selection-capture";
|
|
13
|
+
import { assertProviderDestinationAllowed } from "../lib/destination-policy";
|
|
14
|
+
import { redactSecretString, redactUrlForLog } from "../lib/redact";
|
|
15
|
+
import { PROVIDER_REGISTRY, registryEntryForProviderDestination } from "./registry";
|
|
16
|
+
import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./derive";
|
|
17
|
+
import { cloneFastWire } from "./fastwire";
|
|
18
|
+
import { fastSwitchOff } from "./fast-opt-in";
|
|
19
|
+
import {
|
|
20
|
+
providerMatchesRegistryTransportWithStaticGuards,
|
|
21
|
+
providerSupportsLiveModelDiscovery,
|
|
22
|
+
} from "./static-model-discovery";
|
|
23
|
+
import { resolveModelPolicy } from "./resolved-model-policy";
|
|
24
|
+
|
|
25
|
+
/** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
|
|
26
|
+
function isSameEndpoint(a: string, b: string): boolean {
|
|
27
|
+
return a.trim().replace(/\/+$/, "") === b.trim().replace(/\/+$/, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Origin of a user-configured URL, with the path withheld.
|
|
32
|
+
*
|
|
33
|
+
* A configured `baseUrl` is user-controlled and its path may itself be the credential — an
|
|
34
|
+
* account-scoped route token such as `https://proxy.example/v1/8fK2mP7qR4nV6x` is opaque and
|
|
35
|
+
* high-entropy, so it matches none of the prefix patterns in `redactSecretString`. Pattern
|
|
36
|
+
* redaction cannot be trusted for this value, so no path segment is logged at all. `URL.origin`
|
|
37
|
+
* also excludes userinfo, query and fragment.
|
|
38
|
+
*
|
|
39
|
+
* `…/…` marks that a path was present without revealing it, so a reader can tell an origin-only
|
|
40
|
+
* config apart from one whose path was dropped.
|
|
41
|
+
*/
|
|
42
|
+
function configuredOriginForLog(url: string): string {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = new URL(url.trim());
|
|
45
|
+
// "null" is what URL.origin yields for non-special schemes; treat it as unusable.
|
|
46
|
+
if (!parsed.origin || parsed.origin === "null") return "(unloggable URL)";
|
|
47
|
+
const hasPath = parsed.pathname !== "" && parsed.pathname !== "/";
|
|
48
|
+
return hasPath ? `${parsed.origin}/…` : parsed.origin;
|
|
49
|
+
} catch {
|
|
50
|
+
return "(unparseable URL)";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// `routedProviderConfig` runs per request, so warn once per (provider, discarded, effective) triple.
|
|
55
|
+
// Keyed by the URLs too: editing config.json to a different wrong value warns again.
|
|
56
|
+
const discardedBaseUrlWarnings = new Set<string>();
|
|
57
|
+
let lastWarningReconciledGeneration = 0;
|
|
58
|
+
|
|
59
|
+
export function reconcileRouterWarningMemos(generation: number): number {
|
|
60
|
+
if (generation <= lastWarningReconciledGeneration) return 0;
|
|
61
|
+
const removed = discardedBaseUrlWarnings.size;
|
|
62
|
+
discardedBaseUrlWarnings.clear();
|
|
63
|
+
lastWarningReconciledGeneration = generation;
|
|
64
|
+
return removed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A pinned registry entry — non-template `baseUrl`, no `allowBaseUrlOverride` — outranks a saved
|
|
69
|
+
* `baseUrl`. Dropping it silently is a footgun: requests go to an endpoint the user never
|
|
70
|
+
* configured, and a wrong-region or wrong-account URL then surfaces only as a 401 with nothing
|
|
71
|
+
* pointing back at the discarded setting.
|
|
72
|
+
*
|
|
73
|
+
* Warns rather than throws. The effective route is exactly what it was before, so a hard error
|
|
74
|
+
* here would break configs that route fine today (a stale `baseUrl` left over from an earlier
|
|
75
|
+
* provider is harmless whenever it names the same endpoint the registry pins).
|
|
76
|
+
*/
|
|
77
|
+
function warnIfBaseUrlDiscarded(providerName: string, userBaseUrl: string, effectiveBaseUrl: string): void {
|
|
78
|
+
if (isSameEndpoint(userBaseUrl, effectiveBaseUrl)) return;
|
|
79
|
+
// Asymmetric on purpose. Past the guard above, `effectiveBaseUrl` is necessarily
|
|
80
|
+
// `registryEntry.baseUrl`: the caller passes the resolved URL, and whenever that resolution
|
|
81
|
+
// kept the user's value the two are equal and we have already returned. So the effective side
|
|
82
|
+
// is a constant from this repo's registry and safe to print in full — it is also the useful
|
|
83
|
+
// half, naming the endpoint requests will actually use. The configured side is untrusted.
|
|
84
|
+
const discarded = configuredOriginForLog(userBaseUrl);
|
|
85
|
+
const effective = redactSecretString(redactUrlForLog(effectiveBaseUrl));
|
|
86
|
+
// Key off the logged forms: no raw credential is retained for the process lifetime, and
|
|
87
|
+
// rotating a key embedded in the URL no longer re-warns about the same endpoint mismatch.
|
|
88
|
+
// Coarser than the raw URLs — two bad paths on one host warn once, which is the right grain.
|
|
89
|
+
const key = `${providerName} | ${discarded} | ${effective}`;
|
|
90
|
+
if (discardedBaseUrlWarnings.has(key)) return;
|
|
91
|
+
discardedBaseUrlWarnings.add(key);
|
|
92
|
+
console.warn(
|
|
93
|
+
// Routing is what this warning speaks for: an adapter may adjust the endpoint again
|
|
94
|
+
// downstream (kiro re-derives the region), so do not promise where the request lands.
|
|
95
|
+
`⚠️ config.json provider "${providerName}": configured baseUrl ${discarded} is ignored`
|
|
96
|
+
+ ` because this provider's endpoint is fixed at ${effective}. A URL saved for a different`
|
|
97
|
+
+ ` account or region is a common cause of 401s here — drop it, or use the provider whose endpoint matches.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function usableResolvedApiKey(apiKey: string | undefined): string | undefined {
|
|
102
|
+
// A `gcp-sa:` marker must pass through UNCHANGED: it resolves to `undefined` here (it is not a
|
|
103
|
+
// keychain/env ref, and the marker itself is not a literal), and replacing it would silently
|
|
104
|
+
// drop the marker from the routed config — the vertex adapter would then read
|
|
105
|
+
// `gcpCredentialMarkerAccount(undefined)` and fall back to host ADC instead of the configured
|
|
106
|
+
// service account. Return the marker verbatim so the adapter passes its account per request.
|
|
107
|
+
if (typeof apiKey === "string" && apiKey.startsWith(GCP_CREDENTIAL_MARKER_PREFIX)) return apiKey;
|
|
108
|
+
const resolved = resolveProviderApiKey(apiKey);
|
|
109
|
+
return typeof resolved === "string" && resolved.trim().length > 0 ? resolved : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function routedProviderConfig(providerName: string, provider: OcxProviderConfig): OcxProviderConfig {
|
|
113
|
+
provider = { ...provider, _apiKeyAttempt: provider._apiKeyAttempt ?? captureProviderApiKeySelection(provider) };
|
|
114
|
+
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
|
|
115
|
+
if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
|
|
116
|
+
assertProviderDestinationAllowed(providerName, provider);
|
|
117
|
+
// A row whose adapter no longer matches its registry entry still reaches the Responses
|
|
118
|
+
// adapter when one model opts in through `modelAdapters` — a Volcengine Coding Plan config
|
|
119
|
+
// saved on Chat, for instance. The replay-drop flag belongs to the DESTINATION rather than
|
|
120
|
+
// to the provider-wide wire, so it is filled here too; without it that continuation forwards
|
|
121
|
+
// the reasoning item the upstream answers 400 to. Destination matching refuses templated and
|
|
122
|
+
// overridable base URLs, so this cannot follow a retargeted row, and an explicit value wins.
|
|
123
|
+
const destination = registryEntryForProviderDestination(provider);
|
|
124
|
+
return {
|
|
125
|
+
...provider,
|
|
126
|
+
apiKey: usableResolvedApiKey(provider.apiKey),
|
|
127
|
+
...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
|
|
128
|
+
? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
|
|
129
|
+
: {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
|
|
133
|
+
const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
|
|
134
|
+
const repairLegacyMimoFreeAuth = providerName === "mimo-free"
|
|
135
|
+
&& staticModelCatalog
|
|
136
|
+
&& (provider.authMode === undefined || provider.authMode === "local");
|
|
137
|
+
const explicitKeyOverride = providerUsesKeyAuthOverride(registryEntry, provider, resolvedApiKey);
|
|
138
|
+
const canonicalAuthMode = explicitKeyOverride
|
|
139
|
+
? "key"
|
|
140
|
+
: repairLegacyMimoFreeAuth
|
|
141
|
+
? "key"
|
|
142
|
+
: registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
|
|
143
|
+
? registryEntry.authKind
|
|
144
|
+
: provider.authMode === "forward" ? undefined : provider.authMode;
|
|
145
|
+
const staticPolicy = resolveModelPolicy({
|
|
146
|
+
providerName,
|
|
147
|
+
modelId: "__provider_static__",
|
|
148
|
+
provider,
|
|
149
|
+
registryEntry,
|
|
150
|
+
transportMatchedRegistry: true,
|
|
151
|
+
...(canonicalAuthMode ? { effectiveAuth: { authMode: canonicalAuthMode } } : {}),
|
|
152
|
+
}).provider;
|
|
153
|
+
const reasoningEffortMap = staticPolicy.reasoningEffortMap;
|
|
154
|
+
const modelReasoningEffortMap = staticPolicy.modelReasoningEffortMap;
|
|
155
|
+
const modelReasoningEfforts = staticPolicy.modelReasoningEfforts;
|
|
156
|
+
const modelDefaultReasoningEfforts = staticPolicy.modelDefaultReasoningEfforts;
|
|
157
|
+
const modelContextWindows = staticPolicy.modelContextWindows;
|
|
158
|
+
const modelInputModalities = staticPolicy.modelInputModalities;
|
|
159
|
+
// Registry static headers are documented as applying to every upstream request, so they are
|
|
160
|
+
// filled at resolve time rather than only at seed time: a config written before a header
|
|
161
|
+
// existed, or one carrying any header of its own, would otherwise never receive it. User
|
|
162
|
+
// headers win, matched case-insensitively so an override replaces rather than duplicates.
|
|
163
|
+
const headers = staticPolicy.headers;
|
|
164
|
+
const modelMaxInputTokens = staticPolicy.modelMaxInputTokens;
|
|
165
|
+
const modelMaxOutputTokens = staticPolicy.modelMaxOutputTokens;
|
|
166
|
+
const modelSupportsServiceTier = staticPolicy.modelSupportsServiceTier;
|
|
167
|
+
const modelSupportsVerbosity = staticPolicy.modelSupportsVerbosity;
|
|
168
|
+
const noVisionModels = staticPolicy.noVisionModels;
|
|
169
|
+
const noReasoningModels = staticPolicy.noReasoningModels;
|
|
170
|
+
const noTemperatureModels = staticPolicy.noTemperatureModels;
|
|
171
|
+
const noTopPModels = staticPolicy.noTopPModels;
|
|
172
|
+
const noStopModels = staticPolicy.noStopModels;
|
|
173
|
+
const noPenaltyModels = staticPolicy.noPenaltyModels;
|
|
174
|
+
const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
|
|
175
|
+
const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
|
|
176
|
+
const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
|
|
177
|
+
const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
|
|
178
|
+
const reasoningSplitModels = staticPolicy.reasoningSplitModels;
|
|
179
|
+
const inlineThinkTagModels = staticPolicy.inlineThinkTagModels;
|
|
180
|
+
const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
|
|
181
|
+
const thinkingToggleModels = staticPolicy.thinkingToggleModels;
|
|
182
|
+
const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
|
|
183
|
+
const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
|
|
184
|
+
const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
|
|
185
|
+
const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
|
|
186
|
+
if (registryEntry.allowBaseUrlOverride && !userBaseUrlIsResolved) {
|
|
187
|
+
throw new Error(`Invalid baseUrl for provider "${providerName}": expected a nonblank URL without unresolved placeholders`);
|
|
188
|
+
}
|
|
189
|
+
// Registry template URLs are presets; local/self-hosted entries opt in explicitly.
|
|
190
|
+
const baseUrl = (registryBaseUrlIsTemplate || registryEntry.allowBaseUrlOverride) && userBaseUrlIsResolved
|
|
191
|
+
? userBaseUrl
|
|
192
|
+
: registryEntry.baseUrl;
|
|
193
|
+
if (userBaseUrlIsResolved) warnIfBaseUrlDiscarded(providerName, userBaseUrl, baseUrl);
|
|
194
|
+
assertProviderDestinationAllowed(providerName, { baseUrl, allowPrivateNetwork: provider.allowPrivateNetwork });
|
|
195
|
+
|
|
196
|
+
const resolved: OcxProviderConfig = {
|
|
197
|
+
...provider,
|
|
198
|
+
adapter: registryEntry.adapter,
|
|
199
|
+
baseUrl,
|
|
200
|
+
...(provider.responsesPath === undefined && registryEntry.responsesPath !== undefined
|
|
201
|
+
? { responsesPath: registryEntry.responsesPath }
|
|
202
|
+
: {}),
|
|
203
|
+
...(provider.chatCompletionsPath === undefined && registryEntry.chatCompletionsPath !== undefined
|
|
204
|
+
? { chatCompletionsPath: registryEntry.chatCompletionsPath }
|
|
205
|
+
: {}),
|
|
206
|
+
...(provider.requiresAdjacentResponsesToolResults === undefined
|
|
207
|
+
&& registryEntry.requiresAdjacentResponsesToolResults !== undefined
|
|
208
|
+
? { requiresAdjacentResponsesToolResults: registryEntry.requiresAdjacentResponsesToolResults }
|
|
209
|
+
: {}),
|
|
210
|
+
...(provider.requiresPairedResponsesToolResults === undefined
|
|
211
|
+
&& registryEntry.requiresPairedResponsesToolResults !== undefined
|
|
212
|
+
? { requiresPairedResponsesToolResults: registryEntry.requiresPairedResponsesToolResults }
|
|
213
|
+
: {}),
|
|
214
|
+
...(provider.annotateEmptyToolOutputs === undefined
|
|
215
|
+
&& registryEntry.annotateEmptyToolOutputs !== undefined
|
|
216
|
+
? { annotateEmptyToolOutputs: registryEntry.annotateEmptyToolOutputs }
|
|
217
|
+
: {}),
|
|
218
|
+
...(provider.fastWire === undefined && registryEntry.fastWire !== undefined
|
|
219
|
+
? {
|
|
220
|
+
fastWire: cloneFastWire(registryEntry.fastWire),
|
|
221
|
+
}
|
|
222
|
+
: {}),
|
|
223
|
+
...(provider.supportsServiceTier === undefined && registryEntry.supportsServiceTier !== undefined
|
|
224
|
+
? { supportsServiceTier: registryEntry.supportsServiceTier }
|
|
225
|
+
: {}),
|
|
226
|
+
// An off Fast switch is a provider-wide denial on the runtime provider, so a Fast policy
|
|
227
|
+
// resolved without the provider name still refuses (providerFastSwitchOff).
|
|
228
|
+
...(fastSwitchOff(provider, registryEntry) ? { supportsServiceTier: false } : {}),
|
|
229
|
+
// Registry-only web-search capability: without this backfill a saved provider row reaches
|
|
230
|
+
// the Responses adapter with the flag `undefined`, so the capability gate added in #2262
|
|
231
|
+
// reads "unclassified" and forwards Codex's OpenAI-only `web_search` config fields. xAI
|
|
232
|
+
// rejects the whole request before inference ("Argument not supported:
|
|
233
|
+
// external_web_access"), which killed every routed Grok turn on the Responses lane.
|
|
234
|
+
// enrichProviderFromRegistry() already fills this, but the request path resolves through
|
|
235
|
+
// routedProviderConfig() and never called it.
|
|
236
|
+
...(provider.supportsOpenAiWebSearchToolFields === undefined
|
|
237
|
+
&& registryEntry.supportsOpenAiWebSearchToolFields !== undefined
|
|
238
|
+
? { supportsOpenAiWebSearchToolFields: registryEntry.supportsOpenAiWebSearchToolFields }
|
|
239
|
+
: {}),
|
|
240
|
+
...(provider.supportsResponsesCustomTools === undefined && registryEntry.supportsResponsesCustomTools !== undefined
|
|
241
|
+
? { supportsResponsesCustomTools: registryEntry.supportsResponsesCustomTools }
|
|
242
|
+
: {}),
|
|
243
|
+
...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
|
|
244
|
+
? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
|
|
245
|
+
: {}),
|
|
246
|
+
...(provider.preserveResponsesInputItemIds === undefined && registryEntry.preserveResponsesInputItemIds !== undefined
|
|
247
|
+
? { preserveResponsesInputItemIds: registryEntry.preserveResponsesInputItemIds }
|
|
248
|
+
: {}),
|
|
249
|
+
...(provider.preserveResponsesMessageMetadata === undefined && registryEntry.preserveResponsesMessageMetadata !== undefined
|
|
250
|
+
? { preserveResponsesMessageMetadata: registryEntry.preserveResponsesMessageMetadata }
|
|
251
|
+
: {}),
|
|
252
|
+
...(provider.dropResponsesReasoningItems === undefined && registryEntry.dropResponsesReasoningItems !== undefined
|
|
253
|
+
? { dropResponsesReasoningItems: registryEntry.dropResponsesReasoningItems }
|
|
254
|
+
: {}),
|
|
255
|
+
// The request path resolves through routedProviderConfig() and never calls
|
|
256
|
+
// enrichProviderFromRegistry(), so a saved provider row written before the
|
|
257
|
+
// registry learned this flag must be backfilled here or route.provider never
|
|
258
|
+
// carries it and the showThinkingSummary opt-in stays dead.
|
|
259
|
+
...(provider.showThinkingSummary === undefined && registryEntry.showThinkingSummary !== undefined
|
|
260
|
+
? { showThinkingSummary: registryEntry.showThinkingSummary }
|
|
261
|
+
: {}),
|
|
262
|
+
// Registry-only client-facing repair policy (#938): fill only when the
|
|
263
|
+
// saved provider has no explicit policy; clone so runtime never aliases
|
|
264
|
+
// the registry constant.
|
|
265
|
+
...(provider.responsesItemIdRepair === undefined && registryEntry.responsesItemIdRepair
|
|
266
|
+
? {
|
|
267
|
+
responsesItemIdRepair: {
|
|
268
|
+
...(registryEntry.responsesItemIdRepair.message ? { message: [...registryEntry.responsesItemIdRepair.message] } : {}),
|
|
269
|
+
...(registryEntry.responsesItemIdRepair.reasoning ? { reasoning: [...registryEntry.responsesItemIdRepair.reasoning] } : {}),
|
|
270
|
+
...(registryEntry.responsesItemIdRepair.repairMissingTerminalIds !== undefined
|
|
271
|
+
? { repairMissingTerminalIds: registryEntry.responsesItemIdRepair.repairMissingTerminalIds }
|
|
272
|
+
: {}),
|
|
273
|
+
...(registryEntry.responsesItemIdRepair.repairInvalidIds !== undefined
|
|
274
|
+
? { repairInvalidIds: registryEntry.responsesItemIdRepair.repairInvalidIds }
|
|
275
|
+
: {}),
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
: {}),
|
|
279
|
+
authMode: canonicalAuthMode,
|
|
280
|
+
apiKey: resolvedApiKey,
|
|
281
|
+
...(staticModelCatalog ? { liveModels: false } : {}),
|
|
282
|
+
...(headers ? { headers } : {}),
|
|
283
|
+
// Backfill the Google wire mode + Vertex project/location from the registry when the user
|
|
284
|
+
// config omits them, so a minimal `google-vertex`/`google-antigravity` entry still routes
|
|
285
|
+
// through the correct branch (CCA/Vertex) instead of falling back to AI Studio.
|
|
286
|
+
...(provider.googleMode === undefined && registryEntry.googleMode !== undefined ? { googleMode: registryEntry.googleMode } : {}),
|
|
287
|
+
...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
|
|
288
|
+
...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
|
|
289
|
+
...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
|
|
290
|
+
...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
|
|
291
|
+
&& registryEntry.reasoningEfforts !== undefined
|
|
292
|
+
? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
|
|
293
|
+
: {}),
|
|
294
|
+
...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
|
|
295
|
+
...(provider.keyOptional === undefined && registryEntry.keyOptional !== undefined ? { keyOptional: registryEntry.keyOptional } : {}),
|
|
296
|
+
...(provider.modelSuffixBracketStrip === undefined && registryEntry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: registryEntry.modelSuffixBracketStrip } : {}),
|
|
297
|
+
// Scalar backfill: a persisted config created before the flag shipped inherits the registry
|
|
298
|
+
// opt-in, while an explicit user `false` keeps overriding registry `true`.
|
|
299
|
+
...(provider.parallelToolCalls === undefined && registryEntry.parallelToolCalls !== undefined ? { parallelToolCalls: registryEntry.parallelToolCalls } : {}),
|
|
300
|
+
...(provider.promptCacheKey === undefined && registryEntry.promptCacheKey !== undefined ? { promptCacheKey: registryEntry.promptCacheKey } : {}),
|
|
301
|
+
...(provider.chatServiceTier === undefined && registryEntry.chatServiceTier !== undefined ? { chatServiceTier: registryEntry.chatServiceTier } : {}),
|
|
302
|
+
...(provider.openaiChatEofTolerance === undefined && registryEntry.openaiChatEofTolerance !== undefined
|
|
303
|
+
? { openaiChatEofTolerance: registryEntry.openaiChatEofTolerance }
|
|
304
|
+
: {}),
|
|
305
|
+
...(provider.reasoningWireFormat === undefined && registryEntry.reasoningWireFormat !== undefined
|
|
306
|
+
? { reasoningWireFormat: registryEntry.reasoningWireFormat }
|
|
307
|
+
: {}),
|
|
308
|
+
...(provider.defaultMaxOutputTokens === undefined && registryEntry.defaultMaxOutputTokens !== undefined
|
|
309
|
+
? { defaultMaxOutputTokens: registryEntry.defaultMaxOutputTokens }
|
|
310
|
+
: {}),
|
|
311
|
+
...(modelContextWindows ? { modelContextWindows } : {}),
|
|
312
|
+
...(modelInputModalities ? { modelInputModalities } : {}),
|
|
313
|
+
...(modelMaxInputTokens ? { modelMaxInputTokens } : {}),
|
|
314
|
+
...(modelMaxOutputTokens ? { modelMaxOutputTokens } : {}),
|
|
315
|
+
...(modelSupportsServiceTier ? { modelSupportsServiceTier } : {}),
|
|
316
|
+
...(modelSupportsVerbosity ? { modelSupportsVerbosity } : {}),
|
|
317
|
+
...(modelReasoningEfforts ? { modelReasoningEfforts } : {}),
|
|
318
|
+
...(modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts } : {}),
|
|
319
|
+
...(reasoningEffortMap ? { reasoningEffortMap } : {}),
|
|
320
|
+
...(modelReasoningEffortMap ? { modelReasoningEffortMap } : {}),
|
|
321
|
+
...(noVisionModels ? { noVisionModels } : {}),
|
|
322
|
+
...(noReasoningModels ? { noReasoningModels } : {}),
|
|
323
|
+
...(noTemperatureModels ? { noTemperatureModels } : {}),
|
|
324
|
+
...(noTopPModels ? { noTopPModels } : {}),
|
|
325
|
+
...(noStopModels ? { noStopModels } : {}),
|
|
326
|
+
...(noPenaltyModels ? { noPenaltyModels } : {}),
|
|
327
|
+
...(noJsonSchemaModels ? { noJsonSchemaModels } : {}),
|
|
328
|
+
...(autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels } : {}),
|
|
329
|
+
...(preserveReasoningContentModels ? { preserveReasoningContentModels } : {}),
|
|
330
|
+
...(requiresReasoningPlaceholderModels ? { requiresReasoningPlaceholderModels } : {}),
|
|
331
|
+
...(reasoningSplitModels ? { reasoningSplitModels } : {}),
|
|
332
|
+
...(inlineThinkTagModels ? { inlineThinkTagModels } : {}),
|
|
333
|
+
...(reasoningDetailsModels ? { reasoningDetailsModels } : {}),
|
|
334
|
+
...(thinkingToggleModels ? { thinkingToggleModels } : {}),
|
|
335
|
+
...(thinkingBudgetModels ? { thinkingBudgetModels } : {}),
|
|
336
|
+
};
|
|
337
|
+
applyDirectReasoningEffortContracts(registryEntry, resolved, provider);
|
|
338
|
+
return resolved;
|
|
339
|
+
}
|
|
@@ -33,6 +33,7 @@ type ServiceTierCapabilityProvider = Pick<
|
|
|
33
33
|
| "modelAdapters"
|
|
34
34
|
| "baseUrl"
|
|
35
35
|
| "authMode"
|
|
36
|
+
| "googleMode"
|
|
36
37
|
| "apiKeyTransport"
|
|
37
38
|
| "chatServiceTier"
|
|
38
39
|
| "fastWire"
|
|
@@ -119,7 +120,7 @@ function buildFastPolicyAuthority(
|
|
|
119
120
|
: {}),
|
|
120
121
|
}),
|
|
121
122
|
modelAdapters: Object.freeze({ ...(provider.modelAdapters ?? {}) }),
|
|
122
|
-
hardPins: captureWireAdapterHardPins(providerName),
|
|
123
|
+
hardPins: captureWireAdapterHardPins(providerName, provider),
|
|
123
124
|
hardPinPrefixes: captureWireAdapterHardPinPrefixes(providerName, provider),
|
|
124
125
|
registryWireDefaults: cloneRegistryWireDefaults(registry?.modelWireDefaults),
|
|
125
126
|
});
|