@bitkyc08/opencodex 2.86.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/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.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 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- 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/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google.ts +35 -8
- 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-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 +4 -2
- 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 +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -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/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +33 -0
- 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/outbound.ts +38 -13
- 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 +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- 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 +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -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 +28 -8
- 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 +2 -1
- package/src/clients/config-export.ts +18 -44
- 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/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 +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -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 +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +12 -13
- 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 +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- 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/main-account-hard-lock.ts +30 -1
- 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 +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- 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 +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- 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/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -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 +608 -284
- 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/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -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/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- 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 +33 -7
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- 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/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- 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 +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- 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 +37 -8
- 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 +50 -5
- 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/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- 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 +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- 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 +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/management/advisor-routes.ts +15 -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 +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- 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/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 +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +101 -21
- 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 +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- 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 +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- package/src/server/responses/core.ts +2 -115
- 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 +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/request-prepare.ts +76 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- 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/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- 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/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -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/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
package/src/router.ts
CHANGED
|
@@ -14,25 +14,16 @@ import {
|
|
|
14
14
|
import type { NormalizedComboConfig } from "./combos/types";
|
|
15
15
|
import { hasOwnProvider } from "./config/provider-name";
|
|
16
16
|
import { anthropicInstanceRowShapeMatches } from "./providers/anthropic-instance-id";
|
|
17
|
-
import {
|
|
18
|
-
import { captureProviderApiKeySelection } from "./providers/api-key-selection-capture";
|
|
17
|
+
import { reconcileRouterWarningMemos, routedProviderConfig } from "./providers/routed-config";
|
|
19
18
|
import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
20
|
-
import { redactSecretString, redactUrlForLog } from "./lib/redact";
|
|
21
19
|
import {
|
|
22
20
|
PROVIDER_REGISTRY,
|
|
23
21
|
providerCodexAccountMode,
|
|
24
|
-
registryEntryForProviderDestination,
|
|
25
22
|
} from "./providers/registry";
|
|
26
23
|
// Imported from the module directly rather than through the registry facade, which is at its
|
|
27
24
|
// file-size cap.
|
|
28
25
|
import { registryModelIdKeys } from "./providers/registry/model-ids";
|
|
29
|
-
import {
|
|
30
|
-
import { cloneFastWire } from "./providers/fastwire";
|
|
31
|
-
import { fastSwitchOff } from "./providers/fast-opt-in";
|
|
32
|
-
import {
|
|
33
|
-
providerMatchesRegistryTransportWithStaticGuards,
|
|
34
|
-
providerSupportsLiveModelDiscovery,
|
|
35
|
-
} from "./providers/static-model-discovery";
|
|
26
|
+
import { providerMatchesRegistryTransportWithStaticGuards } from "./providers/static-model-discovery";
|
|
36
27
|
import {
|
|
37
28
|
isCanonicalOpenAiForwardProvider,
|
|
38
29
|
LEGACY_CHATGPT_PROVIDER_ID,
|
|
@@ -202,82 +193,6 @@ export function knownModelIdsForProvider(
|
|
|
202
193
|
return [...ids];
|
|
203
194
|
}
|
|
204
195
|
|
|
205
|
-
/** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
|
|
206
|
-
function isSameEndpoint(a: string, b: string): boolean {
|
|
207
|
-
return a.trim().replace(/\/+$/, "") === b.trim().replace(/\/+$/, "");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Origin of a user-configured URL, with the path withheld.
|
|
212
|
-
*
|
|
213
|
-
* A configured `baseUrl` is user-controlled and its path may itself be the credential — an
|
|
214
|
-
* account-scoped route token such as `https://proxy.example/v1/8fK2mP7qR4nV6x` is opaque and
|
|
215
|
-
* high-entropy, so it matches none of the prefix patterns in `redactSecretString`. Pattern
|
|
216
|
-
* redaction cannot be trusted for this value, so no path segment is logged at all. `URL.origin`
|
|
217
|
-
* also excludes userinfo, query and fragment.
|
|
218
|
-
*
|
|
219
|
-
* `…/…` marks that a path was present without revealing it, so a reader can tell an origin-only
|
|
220
|
-
* config apart from one whose path was dropped.
|
|
221
|
-
*/
|
|
222
|
-
function configuredOriginForLog(url: string): string {
|
|
223
|
-
try {
|
|
224
|
-
const parsed = new URL(url.trim());
|
|
225
|
-
// "null" is what URL.origin yields for non-special schemes; treat it as unusable.
|
|
226
|
-
if (!parsed.origin || parsed.origin === "null") return "(unloggable URL)";
|
|
227
|
-
const hasPath = parsed.pathname !== "" && parsed.pathname !== "/";
|
|
228
|
-
return hasPath ? `${parsed.origin}/…` : parsed.origin;
|
|
229
|
-
} catch {
|
|
230
|
-
return "(unparseable URL)";
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// `routedProviderConfig` runs per request, so warn once per (provider, discarded, effective) triple.
|
|
235
|
-
// Keyed by the URLs too: editing config.json to a different wrong value warns again.
|
|
236
|
-
const discardedBaseUrlWarnings = new Set<string>();
|
|
237
|
-
let lastWarningReconciledGeneration = 0;
|
|
238
|
-
|
|
239
|
-
export function reconcileRouterWarningMemos(generation: number): number {
|
|
240
|
-
if (generation <= lastWarningReconciledGeneration) return 0;
|
|
241
|
-
const removed = discardedBaseUrlWarnings.size;
|
|
242
|
-
discardedBaseUrlWarnings.clear();
|
|
243
|
-
lastWarningReconciledGeneration = generation;
|
|
244
|
-
return removed;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* A pinned registry entry — non-template `baseUrl`, no `allowBaseUrlOverride` — outranks a saved
|
|
249
|
-
* `baseUrl`. Dropping it silently is a footgun: requests go to an endpoint the user never
|
|
250
|
-
* configured, and a wrong-region or wrong-account URL then surfaces only as a 401 with nothing
|
|
251
|
-
* pointing back at the discarded setting.
|
|
252
|
-
*
|
|
253
|
-
* Warns rather than throws. The effective route is exactly what it was before, so a hard error
|
|
254
|
-
* here would break configs that route fine today (a stale `baseUrl` left over from an earlier
|
|
255
|
-
* provider is harmless whenever it names the same endpoint the registry pins).
|
|
256
|
-
*/
|
|
257
|
-
function warnIfBaseUrlDiscarded(providerName: string, userBaseUrl: string, effectiveBaseUrl: string): void {
|
|
258
|
-
if (isSameEndpoint(userBaseUrl, effectiveBaseUrl)) return;
|
|
259
|
-
// Asymmetric on purpose. Past the guard above, `effectiveBaseUrl` is necessarily
|
|
260
|
-
// `registryEntry.baseUrl`: the caller passes the resolved URL, and whenever that resolution
|
|
261
|
-
// kept the user's value the two are equal and we have already returned. So the effective side
|
|
262
|
-
// is a constant from this repo's registry and safe to print in full — it is also the useful
|
|
263
|
-
// half, naming the endpoint requests will actually use. The configured side is untrusted.
|
|
264
|
-
const discarded = configuredOriginForLog(userBaseUrl);
|
|
265
|
-
const effective = redactSecretString(redactUrlForLog(effectiveBaseUrl));
|
|
266
|
-
// Key off the logged forms: no raw credential is retained for the process lifetime, and
|
|
267
|
-
// rotating a key embedded in the URL no longer re-warns about the same endpoint mismatch.
|
|
268
|
-
// Coarser than the raw URLs — two bad paths on one host warn once, which is the right grain.
|
|
269
|
-
const key = `${providerName} | ${discarded} | ${effective}`;
|
|
270
|
-
if (discardedBaseUrlWarnings.has(key)) return;
|
|
271
|
-
discardedBaseUrlWarnings.add(key);
|
|
272
|
-
console.warn(
|
|
273
|
-
// Routing is what this warning speaks for: an adapter may adjust the endpoint again
|
|
274
|
-
// downstream (kiro re-derives the region), so do not promise where the request lands.
|
|
275
|
-
`⚠️ config.json provider "${providerName}": configured baseUrl ${discarded} is ignored`
|
|
276
|
-
+ ` because this provider's endpoint is fixed at ${effective}. A URL saved for a different`
|
|
277
|
-
+ ` account or region is a common cause of 401s here — drop it, or use the provider whose endpoint matches.`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
196
|
/**
|
|
282
197
|
* One notice per provider id: the destination is an operator-configured key
|
|
283
198
|
* (never a caller-supplied string), so the log carries no request content.
|
|
@@ -297,239 +212,10 @@ export function resetCompactionFallbackWarningsForTests(): void {
|
|
|
297
212
|
compactionFallbackWarnings.clear();
|
|
298
213
|
}
|
|
299
214
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function routedProviderConfig(providerName: string, provider: OcxProviderConfig): OcxProviderConfig {
|
|
306
|
-
provider = { ...provider, _apiKeyAttempt: provider._apiKeyAttempt ?? captureProviderApiKeySelection(provider) };
|
|
307
|
-
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
|
|
308
|
-
if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
|
|
309
|
-
assertProviderDestinationAllowed(providerName, provider);
|
|
310
|
-
// A row whose adapter no longer matches its registry entry still reaches the Responses
|
|
311
|
-
// adapter when one model opts in through `modelAdapters` — a Volcengine Coding Plan config
|
|
312
|
-
// saved on Chat, for instance. The replay-drop flag belongs to the DESTINATION rather than
|
|
313
|
-
// to the provider-wide wire, so it is filled here too; without it that continuation forwards
|
|
314
|
-
// the reasoning item the upstream answers 400 to. Destination matching refuses templated and
|
|
315
|
-
// overridable base URLs, so this cannot follow a retargeted row, and an explicit value wins.
|
|
316
|
-
const destination = registryEntryForProviderDestination(provider);
|
|
317
|
-
return {
|
|
318
|
-
...provider,
|
|
319
|
-
apiKey: usableResolvedApiKey(provider.apiKey),
|
|
320
|
-
...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
|
|
321
|
-
? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
|
|
322
|
-
: {}),
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
|
|
326
|
-
const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
|
|
327
|
-
const repairLegacyMimoFreeAuth = providerName === "mimo-free"
|
|
328
|
-
&& staticModelCatalog
|
|
329
|
-
&& (provider.authMode === undefined || provider.authMode === "local");
|
|
330
|
-
const explicitKeyOverride = providerUsesKeyAuthOverride(registryEntry, provider, resolvedApiKey);
|
|
331
|
-
const canonicalAuthMode = explicitKeyOverride
|
|
332
|
-
? "key"
|
|
333
|
-
: repairLegacyMimoFreeAuth
|
|
334
|
-
? "key"
|
|
335
|
-
: registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
|
|
336
|
-
? registryEntry.authKind
|
|
337
|
-
: provider.authMode === "forward" ? undefined : provider.authMode;
|
|
338
|
-
const staticPolicy = resolveModelPolicy({
|
|
339
|
-
providerName,
|
|
340
|
-
modelId: "__provider_static__",
|
|
341
|
-
provider,
|
|
342
|
-
registryEntry,
|
|
343
|
-
transportMatchedRegistry: true,
|
|
344
|
-
...(canonicalAuthMode ? { effectiveAuth: { authMode: canonicalAuthMode } } : {}),
|
|
345
|
-
}).provider;
|
|
346
|
-
const reasoningEffortMap = staticPolicy.reasoningEffortMap;
|
|
347
|
-
const modelReasoningEffortMap = staticPolicy.modelReasoningEffortMap;
|
|
348
|
-
const modelReasoningEfforts = staticPolicy.modelReasoningEfforts;
|
|
349
|
-
const modelDefaultReasoningEfforts = staticPolicy.modelDefaultReasoningEfforts;
|
|
350
|
-
const modelContextWindows = staticPolicy.modelContextWindows;
|
|
351
|
-
const modelInputModalities = staticPolicy.modelInputModalities;
|
|
352
|
-
// Registry static headers are documented as applying to every upstream request, so they are
|
|
353
|
-
// filled at resolve time rather than only at seed time: a config written before a header
|
|
354
|
-
// existed, or one carrying any header of its own, would otherwise never receive it. User
|
|
355
|
-
// headers win, matched case-insensitively so an override replaces rather than duplicates.
|
|
356
|
-
const headers = staticPolicy.headers;
|
|
357
|
-
const modelMaxInputTokens = staticPolicy.modelMaxInputTokens;
|
|
358
|
-
const modelMaxOutputTokens = staticPolicy.modelMaxOutputTokens;
|
|
359
|
-
const modelSupportsServiceTier = staticPolicy.modelSupportsServiceTier;
|
|
360
|
-
const modelSupportsVerbosity = staticPolicy.modelSupportsVerbosity;
|
|
361
|
-
const noVisionModels = staticPolicy.noVisionModels;
|
|
362
|
-
const noReasoningModels = staticPolicy.noReasoningModels;
|
|
363
|
-
const noTemperatureModels = staticPolicy.noTemperatureModels;
|
|
364
|
-
const noTopPModels = staticPolicy.noTopPModels;
|
|
365
|
-
const noStopModels = staticPolicy.noStopModels;
|
|
366
|
-
const noPenaltyModels = staticPolicy.noPenaltyModels;
|
|
367
|
-
const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
|
|
368
|
-
const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
|
|
369
|
-
const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
|
|
370
|
-
const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
|
|
371
|
-
const reasoningSplitModels = staticPolicy.reasoningSplitModels;
|
|
372
|
-
const inlineThinkTagModels = staticPolicy.inlineThinkTagModels;
|
|
373
|
-
const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
|
|
374
|
-
const thinkingToggleModels = staticPolicy.thinkingToggleModels;
|
|
375
|
-
const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
|
|
376
|
-
const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
|
|
377
|
-
const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
|
|
378
|
-
const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
|
|
379
|
-
if (registryEntry.allowBaseUrlOverride && !userBaseUrlIsResolved) {
|
|
380
|
-
throw new Error(`Invalid baseUrl for provider "${providerName}": expected a nonblank URL without unresolved placeholders`);
|
|
381
|
-
}
|
|
382
|
-
// Registry template URLs are presets; local/self-hosted entries opt in explicitly.
|
|
383
|
-
const baseUrl = (registryBaseUrlIsTemplate || registryEntry.allowBaseUrlOverride) && userBaseUrlIsResolved
|
|
384
|
-
? userBaseUrl
|
|
385
|
-
: registryEntry.baseUrl;
|
|
386
|
-
if (userBaseUrlIsResolved) warnIfBaseUrlDiscarded(providerName, userBaseUrl, baseUrl);
|
|
387
|
-
assertProviderDestinationAllowed(providerName, { baseUrl, allowPrivateNetwork: provider.allowPrivateNetwork });
|
|
388
|
-
|
|
389
|
-
const resolved: OcxProviderConfig = {
|
|
390
|
-
...provider,
|
|
391
|
-
adapter: registryEntry.adapter,
|
|
392
|
-
baseUrl,
|
|
393
|
-
...(provider.responsesPath === undefined && registryEntry.responsesPath !== undefined
|
|
394
|
-
? { responsesPath: registryEntry.responsesPath }
|
|
395
|
-
: {}),
|
|
396
|
-
...(provider.chatCompletionsPath === undefined && registryEntry.chatCompletionsPath !== undefined
|
|
397
|
-
? { chatCompletionsPath: registryEntry.chatCompletionsPath }
|
|
398
|
-
: {}),
|
|
399
|
-
...(provider.requiresAdjacentResponsesToolResults === undefined
|
|
400
|
-
&& registryEntry.requiresAdjacentResponsesToolResults !== undefined
|
|
401
|
-
? { requiresAdjacentResponsesToolResults: registryEntry.requiresAdjacentResponsesToolResults }
|
|
402
|
-
: {}),
|
|
403
|
-
...(provider.requiresPairedResponsesToolResults === undefined
|
|
404
|
-
&& registryEntry.requiresPairedResponsesToolResults !== undefined
|
|
405
|
-
? { requiresPairedResponsesToolResults: registryEntry.requiresPairedResponsesToolResults }
|
|
406
|
-
: {}),
|
|
407
|
-
...(provider.annotateEmptyToolOutputs === undefined
|
|
408
|
-
&& registryEntry.annotateEmptyToolOutputs !== undefined
|
|
409
|
-
? { annotateEmptyToolOutputs: registryEntry.annotateEmptyToolOutputs }
|
|
410
|
-
: {}),
|
|
411
|
-
...(provider.fastWire === undefined && registryEntry.fastWire !== undefined
|
|
412
|
-
? {
|
|
413
|
-
fastWire: cloneFastWire(registryEntry.fastWire),
|
|
414
|
-
}
|
|
415
|
-
: {}),
|
|
416
|
-
...(provider.supportsServiceTier === undefined && registryEntry.supportsServiceTier !== undefined
|
|
417
|
-
? { supportsServiceTier: registryEntry.supportsServiceTier }
|
|
418
|
-
: {}),
|
|
419
|
-
// An off Fast switch is a provider-wide denial on the runtime provider, so a Fast policy
|
|
420
|
-
// resolved without the provider name still refuses (providerFastSwitchOff).
|
|
421
|
-
...(fastSwitchOff(provider, registryEntry) ? { supportsServiceTier: false } : {}),
|
|
422
|
-
// Registry-only web-search capability: without this backfill a saved provider row reaches
|
|
423
|
-
// the Responses adapter with the flag `undefined`, so the capability gate added in #2262
|
|
424
|
-
// reads "unclassified" and forwards Codex's OpenAI-only `web_search` config fields. xAI
|
|
425
|
-
// rejects the whole request before inference ("Argument not supported:
|
|
426
|
-
// external_web_access"), which killed every routed Grok turn on the Responses lane.
|
|
427
|
-
// enrichProviderFromRegistry() already fills this, but the request path resolves through
|
|
428
|
-
// routedProviderConfig() and never called it.
|
|
429
|
-
...(provider.supportsOpenAiWebSearchToolFields === undefined
|
|
430
|
-
&& registryEntry.supportsOpenAiWebSearchToolFields !== undefined
|
|
431
|
-
? { supportsOpenAiWebSearchToolFields: registryEntry.supportsOpenAiWebSearchToolFields }
|
|
432
|
-
: {}),
|
|
433
|
-
...(provider.supportsResponsesCustomTools === undefined && registryEntry.supportsResponsesCustomTools !== undefined
|
|
434
|
-
? { supportsResponsesCustomTools: registryEntry.supportsResponsesCustomTools }
|
|
435
|
-
: {}),
|
|
436
|
-
...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
|
|
437
|
-
? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
|
|
438
|
-
: {}),
|
|
439
|
-
...(provider.preserveResponsesInputItemIds === undefined && registryEntry.preserveResponsesInputItemIds !== undefined
|
|
440
|
-
? { preserveResponsesInputItemIds: registryEntry.preserveResponsesInputItemIds }
|
|
441
|
-
: {}),
|
|
442
|
-
...(provider.preserveResponsesMessageMetadata === undefined && registryEntry.preserveResponsesMessageMetadata !== undefined
|
|
443
|
-
? { preserveResponsesMessageMetadata: registryEntry.preserveResponsesMessageMetadata }
|
|
444
|
-
: {}),
|
|
445
|
-
...(provider.dropResponsesReasoningItems === undefined && registryEntry.dropResponsesReasoningItems !== undefined
|
|
446
|
-
? { dropResponsesReasoningItems: registryEntry.dropResponsesReasoningItems }
|
|
447
|
-
: {}),
|
|
448
|
-
// The request path resolves through routedProviderConfig() and never calls
|
|
449
|
-
// enrichProviderFromRegistry(), so a saved provider row written before the
|
|
450
|
-
// registry learned this flag must be backfilled here or route.provider never
|
|
451
|
-
// carries it and the showThinkingSummary opt-in stays dead.
|
|
452
|
-
...(provider.showThinkingSummary === undefined && registryEntry.showThinkingSummary !== undefined
|
|
453
|
-
? { showThinkingSummary: registryEntry.showThinkingSummary }
|
|
454
|
-
: {}),
|
|
455
|
-
// Registry-only client-facing repair policy (#938): fill only when the
|
|
456
|
-
// saved provider has no explicit policy; clone so runtime never aliases
|
|
457
|
-
// the registry constant.
|
|
458
|
-
...(provider.responsesItemIdRepair === undefined && registryEntry.responsesItemIdRepair
|
|
459
|
-
? {
|
|
460
|
-
responsesItemIdRepair: {
|
|
461
|
-
...(registryEntry.responsesItemIdRepair.message ? { message: [...registryEntry.responsesItemIdRepair.message] } : {}),
|
|
462
|
-
...(registryEntry.responsesItemIdRepair.reasoning ? { reasoning: [...registryEntry.responsesItemIdRepair.reasoning] } : {}),
|
|
463
|
-
...(registryEntry.responsesItemIdRepair.repairMissingTerminalIds !== undefined
|
|
464
|
-
? { repairMissingTerminalIds: registryEntry.responsesItemIdRepair.repairMissingTerminalIds }
|
|
465
|
-
: {}),
|
|
466
|
-
...(registryEntry.responsesItemIdRepair.repairInvalidIds !== undefined
|
|
467
|
-
? { repairInvalidIds: registryEntry.responsesItemIdRepair.repairInvalidIds }
|
|
468
|
-
: {}),
|
|
469
|
-
},
|
|
470
|
-
}
|
|
471
|
-
: {}),
|
|
472
|
-
authMode: canonicalAuthMode,
|
|
473
|
-
apiKey: resolvedApiKey,
|
|
474
|
-
...(staticModelCatalog ? { liveModels: false } : {}),
|
|
475
|
-
...(headers ? { headers } : {}),
|
|
476
|
-
// Backfill the Google wire mode + Vertex project/location from the registry when the user
|
|
477
|
-
// config omits them, so a minimal `google-vertex`/`google-antigravity` entry still routes
|
|
478
|
-
// through the correct branch (CCA/Vertex) instead of falling back to AI Studio.
|
|
479
|
-
...(provider.googleMode === undefined && registryEntry.googleMode !== undefined ? { googleMode: registryEntry.googleMode } : {}),
|
|
480
|
-
...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
|
|
481
|
-
...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
|
|
482
|
-
...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
|
|
483
|
-
...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
|
|
484
|
-
&& registryEntry.reasoningEfforts !== undefined
|
|
485
|
-
? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
|
|
486
|
-
: {}),
|
|
487
|
-
...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
|
|
488
|
-
...(provider.keyOptional === undefined && registryEntry.keyOptional !== undefined ? { keyOptional: registryEntry.keyOptional } : {}),
|
|
489
|
-
...(provider.modelSuffixBracketStrip === undefined && registryEntry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: registryEntry.modelSuffixBracketStrip } : {}),
|
|
490
|
-
// Scalar backfill: a persisted config created before the flag shipped inherits the registry
|
|
491
|
-
// opt-in, while an explicit user `false` keeps overriding registry `true`.
|
|
492
|
-
...(provider.parallelToolCalls === undefined && registryEntry.parallelToolCalls !== undefined ? { parallelToolCalls: registryEntry.parallelToolCalls } : {}),
|
|
493
|
-
...(provider.promptCacheKey === undefined && registryEntry.promptCacheKey !== undefined ? { promptCacheKey: registryEntry.promptCacheKey } : {}),
|
|
494
|
-
...(provider.chatServiceTier === undefined && registryEntry.chatServiceTier !== undefined ? { chatServiceTier: registryEntry.chatServiceTier } : {}),
|
|
495
|
-
...(provider.openaiChatEofTolerance === undefined && registryEntry.openaiChatEofTolerance !== undefined
|
|
496
|
-
? { openaiChatEofTolerance: registryEntry.openaiChatEofTolerance }
|
|
497
|
-
: {}),
|
|
498
|
-
...(provider.reasoningWireFormat === undefined && registryEntry.reasoningWireFormat !== undefined
|
|
499
|
-
? { reasoningWireFormat: registryEntry.reasoningWireFormat }
|
|
500
|
-
: {}),
|
|
501
|
-
...(provider.defaultMaxOutputTokens === undefined && registryEntry.defaultMaxOutputTokens !== undefined
|
|
502
|
-
? { defaultMaxOutputTokens: registryEntry.defaultMaxOutputTokens }
|
|
503
|
-
: {}),
|
|
504
|
-
...(modelContextWindows ? { modelContextWindows } : {}),
|
|
505
|
-
...(modelInputModalities ? { modelInputModalities } : {}),
|
|
506
|
-
...(modelMaxInputTokens ? { modelMaxInputTokens } : {}),
|
|
507
|
-
...(modelMaxOutputTokens ? { modelMaxOutputTokens } : {}),
|
|
508
|
-
...(modelSupportsServiceTier ? { modelSupportsServiceTier } : {}),
|
|
509
|
-
...(modelSupportsVerbosity ? { modelSupportsVerbosity } : {}),
|
|
510
|
-
...(modelReasoningEfforts ? { modelReasoningEfforts } : {}),
|
|
511
|
-
...(modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts } : {}),
|
|
512
|
-
...(reasoningEffortMap ? { reasoningEffortMap } : {}),
|
|
513
|
-
...(modelReasoningEffortMap ? { modelReasoningEffortMap } : {}),
|
|
514
|
-
...(noVisionModels ? { noVisionModels } : {}),
|
|
515
|
-
...(noReasoningModels ? { noReasoningModels } : {}),
|
|
516
|
-
...(noTemperatureModels ? { noTemperatureModels } : {}),
|
|
517
|
-
...(noTopPModels ? { noTopPModels } : {}),
|
|
518
|
-
...(noStopModels ? { noStopModels } : {}),
|
|
519
|
-
...(noPenaltyModels ? { noPenaltyModels } : {}),
|
|
520
|
-
...(noJsonSchemaModels ? { noJsonSchemaModels } : {}),
|
|
521
|
-
...(autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels } : {}),
|
|
522
|
-
...(preserveReasoningContentModels ? { preserveReasoningContentModels } : {}),
|
|
523
|
-
...(requiresReasoningPlaceholderModels ? { requiresReasoningPlaceholderModels } : {}),
|
|
524
|
-
...(reasoningSplitModels ? { reasoningSplitModels } : {}),
|
|
525
|
-
...(inlineThinkTagModels ? { inlineThinkTagModels } : {}),
|
|
526
|
-
...(reasoningDetailsModels ? { reasoningDetailsModels } : {}),
|
|
527
|
-
...(thinkingToggleModels ? { thinkingToggleModels } : {}),
|
|
528
|
-
...(thinkingBudgetModels ? { thinkingBudgetModels } : {}),
|
|
529
|
-
};
|
|
530
|
-
applyDirectReasoningEffortContracts(registryEntry, resolved, provider);
|
|
531
|
-
return resolved;
|
|
532
|
-
}
|
|
215
|
+
// `routedProviderConfig` and its warning memo moved to `providers/routed-config.ts`: the
|
|
216
|
+
// request-time resolver needs no routing state, and `providers/api-key-selection.ts` importing
|
|
217
|
+
// it through this file created an import cycle (router -> quota -> api-keys -> api-key-selection).
|
|
218
|
+
export { reconcileRouterWarningMemos, routedProviderConfig } from "./providers/routed-config";
|
|
533
219
|
|
|
534
220
|
/**
|
|
535
221
|
* Rows eligible for bare-model inference (configured default model, model list, model alias).
|
|
@@ -622,7 +308,8 @@ function crossProviderRedirectTarget(config: OcxConfig, providerName: string, va
|
|
|
622
308
|
}
|
|
623
309
|
}
|
|
624
310
|
|
|
625
|
-
|
|
311
|
+
/** Exported for the main-account hard-lock fallback: only this family is ever redirected. */
|
|
312
|
+
export function isBareOpenAiFamilyModel(modelId: string): boolean {
|
|
626
313
|
return !modelId.includes("/")
|
|
627
314
|
&& (/^(?:gpt-|o1-|o3-|o4-)/.test(modelId) || CODEX_INTERNAL_OPENAI_MODELS.has(modelId));
|
|
628
315
|
}
|
package/src/routing/health.ts
CHANGED
|
@@ -94,7 +94,6 @@ interface HealthSample {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
interface HealthRow extends HealthSample {
|
|
97
|
-
attemptCount?: number;
|
|
98
97
|
rowJson?: string | null;
|
|
99
98
|
}
|
|
100
99
|
|
|
@@ -103,33 +102,53 @@ interface HealthRow extends HealthSample {
|
|
|
103
102
|
* Combo/failover requests store each upstream try in `entry.attempts` while
|
|
104
103
|
* the top-level row records the final outcome; a provider/model that failed
|
|
105
104
|
* as a non-final attempt must still contribute its own health samples.
|
|
105
|
+
*
|
|
106
|
+
* Only physical sends are evidence: an attempt answered locally or refused
|
|
107
|
+
* before it left the proxy says nothing about the target. The row's terminal
|
|
108
|
+
* facts belong to its last physical send. Returns `undefined` when the row
|
|
109
|
+
* itself is the sample (no attempts, a single attempt, or none named for this
|
|
110
|
+
* candidate) and `[]` when the row sent nothing upstream at all.
|
|
106
111
|
*/
|
|
107
112
|
function attemptSamplesFor(
|
|
108
|
-
row: Pick<HealthRow, "timestamp" | "
|
|
113
|
+
row: Pick<HealthRow, "timestamp" | "rowJson">,
|
|
109
114
|
provider: string,
|
|
110
115
|
model: string,
|
|
111
|
-
): HealthSample[] {
|
|
112
|
-
if (!row.rowJson
|
|
116
|
+
): HealthSample[] | undefined {
|
|
117
|
+
if (!row.rowJson) return undefined;
|
|
113
118
|
try {
|
|
114
|
-
const parsed = JSON.parse(row.rowJson) as {
|
|
115
|
-
|
|
119
|
+
const parsed = JSON.parse(row.rowJson) as {
|
|
120
|
+
attempts?: unknown;
|
|
121
|
+
closeReason?: unknown;
|
|
122
|
+
terminalStatus?: unknown;
|
|
123
|
+
failureCause?: unknown;
|
|
124
|
+
};
|
|
125
|
+
// A local refusal logged before any attempt began never reached a target.
|
|
126
|
+
if (!Array.isArray(parsed.attempts)) return parsed.failureCause === "local-refusal" ? [] : undefined;
|
|
127
|
+
const attempts = parsed.attempts.filter(
|
|
128
|
+
(attempt): attempt is Record<string, unknown> =>
|
|
129
|
+
!!attempt && typeof attempt === "object" && !Array.isArray(attempt),
|
|
130
|
+
);
|
|
131
|
+
const sent = attempts.filter(attempt => attempt.locallyAnswered !== true && attempt.sendCount !== 0);
|
|
132
|
+
if (sent.length === 0) return [];
|
|
133
|
+
const mine = attempts.filter(attempt => attempt.provider === provider && attempt.model === model);
|
|
134
|
+
if (attempts.length <= 1 || mine.length === 0) return undefined;
|
|
135
|
+
const finalSend = sent.at(-1);
|
|
116
136
|
const samples: HealthSample[] = [];
|
|
117
|
-
for (const
|
|
118
|
-
if (!
|
|
119
|
-
const record = attempt as Record<string, unknown>;
|
|
120
|
-
if (record.provider !== provider || record.model !== model) continue;
|
|
137
|
+
for (const record of mine) {
|
|
138
|
+
if (!sent.includes(record)) continue;
|
|
121
139
|
if (typeof record.status !== "number" || typeof record.durationMs !== "number") continue;
|
|
140
|
+
const isFinal = record === finalSend;
|
|
122
141
|
samples.push({
|
|
123
142
|
status: record.status,
|
|
124
|
-
closeReason: null,
|
|
125
|
-
terminalStatus: null,
|
|
143
|
+
closeReason: isFinal && typeof parsed.closeReason === "string" ? parsed.closeReason : null,
|
|
144
|
+
terminalStatus: isFinal && typeof parsed.terminalStatus === "string" ? parsed.terminalStatus : null,
|
|
126
145
|
durationMs: record.durationMs,
|
|
127
146
|
timestamp: row.timestamp,
|
|
128
147
|
});
|
|
129
148
|
}
|
|
130
149
|
return samples;
|
|
131
150
|
} catch {
|
|
132
|
-
return
|
|
151
|
+
return undefined;
|
|
133
152
|
}
|
|
134
153
|
}
|
|
135
154
|
|
|
@@ -243,8 +262,7 @@ function computeHistoricalHealthEvidence(
|
|
|
243
262
|
}
|
|
244
263
|
const rows = handle.query(
|
|
245
264
|
`SELECT status, close_reason AS closeReason, terminal_status AS terminalStatus,
|
|
246
|
-
duration_ms AS durationMs, timestamp,
|
|
247
|
-
attempt_count AS attemptCount, row_json AS rowJson
|
|
265
|
+
duration_ms AS durationMs, timestamp, row_json AS rowJson
|
|
248
266
|
FROM requests WHERE ${where.join(" AND ")}
|
|
249
267
|
ORDER BY timestamp DESC LIMIT ?`,
|
|
250
268
|
).all(...values, HEALTH_MAX_SAMPLES) as HealthRow[];
|
|
@@ -254,7 +272,7 @@ function computeHistoricalHealthEvidence(
|
|
|
254
272
|
// rows that cannot contribute samples for this candidate.
|
|
255
273
|
const escapeLike = (value: string): string => value.replace(/[\\%_]/g, match => `\\${match}`);
|
|
256
274
|
const attemptRows = handle.query(
|
|
257
|
-
`SELECT timestamp,
|
|
275
|
+
`SELECT timestamp, row_json AS rowJson
|
|
258
276
|
FROM requests WHERE timestamp >= ? AND attempt_count > 1
|
|
259
277
|
AND row_json LIKE ? ESCAPE '\\'
|
|
260
278
|
AND row_json LIKE ? ESCAPE '\\'
|
|
@@ -267,17 +285,14 @@ function computeHistoricalHealthEvidence(
|
|
|
267
285
|
input.provider,
|
|
268
286
|
input.model,
|
|
269
287
|
HEALTH_MAX_SAMPLES,
|
|
270
|
-
) as Array<
|
|
271
|
-
Pick<HealthRow, "timestamp" | "attemptCount" | "rowJson">
|
|
272
|
-
>;
|
|
288
|
+
) as Array<Pick<HealthRow, "timestamp" | "rowJson">>;
|
|
273
289
|
|
|
274
290
|
const samples: HealthSample[] = [];
|
|
275
291
|
for (const row of rows) {
|
|
276
|
-
|
|
277
|
-
samples.push(...(attemptSamples.length > 0 ? attemptSamples : [row]));
|
|
292
|
+
samples.push(...(attemptSamplesFor(row, input.provider, input.model) ?? [row]));
|
|
278
293
|
}
|
|
279
294
|
for (const row of attemptRows) {
|
|
280
|
-
samples.push(...attemptSamplesFor(row, input.provider, input.model));
|
|
295
|
+
samples.push(...(attemptSamplesFor(row, input.provider, input.model) ?? []));
|
|
281
296
|
}
|
|
282
297
|
// Newest first for the consecutive-failure walk; attempt samples inherit
|
|
283
298
|
// their row's timestamp.
|
|
@@ -195,17 +195,18 @@ function readStoredMetaField(dbHandle: Database, key: string): string {
|
|
|
195
195
|
return metaValue(dbHandle, key) ?? "";
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
function sourceIdentityMatches(dbHandle: Database, revision: UsageLogRevision | null): boolean {
|
|
198
|
+
export function sourceIdentityMatches(dbHandle: Database, revision: UsageLogRevision | null): boolean {
|
|
199
199
|
const stored = readIndexedMeta(dbHandle);
|
|
200
200
|
if (revision === null) return stored.sourceSize === 0;
|
|
201
201
|
const storedPath = readStoredMetaField(dbHandle, HISTORY_META_KEYS.sourcePath);
|
|
202
202
|
if (!storedPath) return false;
|
|
203
|
-
|
|
204
|
-
const
|
|
203
|
+
// Compared as exact decimal strings; see UsageLogRevision for why ids are not Numbers.
|
|
204
|
+
const storedDev = readStoredMetaField(dbHandle, HISTORY_META_KEYS.sourceDev);
|
|
205
|
+
const storedIno = readStoredMetaField(dbHandle, HISTORY_META_KEYS.sourceIno);
|
|
205
206
|
const storedBirthtimeMs = Number(readStoredMetaField(dbHandle, HISTORY_META_KEYS.sourceBirthtimeMs));
|
|
206
207
|
return storedPath === revision.path
|
|
207
|
-
&& storedDev ===
|
|
208
|
-
&& storedIno ===
|
|
208
|
+
&& storedDev === revision.dev
|
|
209
|
+
&& storedIno === revision.ino
|
|
209
210
|
&& storedBirthtimeMs === Number(revision.birthtimeMs);
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -10,6 +10,7 @@ import type { Database } from "bun:sqlite";
|
|
|
10
10
|
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
11
11
|
import {
|
|
12
12
|
encodePersistedRequestedModel,
|
|
13
|
+
exactFileIdentity,
|
|
13
14
|
type PersistedUsageEntry,
|
|
14
15
|
type UsageLogRevision,
|
|
15
16
|
} from "../../usage/log";
|
|
@@ -177,8 +178,11 @@ export function ingestSourceTail(
|
|
|
177
178
|
fd = openSync(path, "r");
|
|
178
179
|
const stat = fstatSync(fd);
|
|
179
180
|
const expected = options.expected;
|
|
180
|
-
if (expected
|
|
181
|
-
|
|
181
|
+
if (expected) {
|
|
182
|
+
const opened = exactFileIdentity(fd);
|
|
183
|
+
if (opened.dev !== expected.dev || opened.ino !== expected.ino) {
|
|
184
|
+
throw new RequestHistoryIndexStaleError("usage log changed before indexing");
|
|
185
|
+
}
|
|
182
186
|
}
|
|
183
187
|
const size = options.endOffset === undefined
|
|
184
188
|
? Number(stat.size)
|
package/src/routing/profile.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
OcxRoutingUnknownCostCapMode,
|
|
13
13
|
} from "../types";
|
|
14
14
|
import { codexAccountNamespaceEntries } from "../codex/account-namespaces";
|
|
15
|
-
import {
|
|
15
|
+
import { resolveComboId } from "../combos/identifiers";
|
|
16
16
|
import { hasOwnProvider } from "../config/provider-name";
|
|
17
17
|
import { MAX_COMPATIBILITY_REQUIRED_SUITES } from "./compatibility/types";
|
|
18
18
|
import { POLICY_NAMESPACE } from "./profile-namespace";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DataPlaneAdmission } from "./auth-cors";
|
|
2
|
+
import { isApiAuthRequired } from "./auth-cors";
|
|
2
3
|
import type { OcxApiKeyEntry, OcxConfig } from "../types";
|
|
3
4
|
import { markResponseNonReplayable } from "../lib/upstream-retry";
|
|
4
5
|
|
|
@@ -77,6 +78,22 @@ export function resolveAdmissionModelScope(
|
|
|
77
78
|
return entry ? admissionModelScopeOf(entry) : undefined;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/** Consent comes from stored operator config, never request headers. Use the full listener config. */
|
|
82
|
+
export function admissionMayUseMeteredComboTargets(
|
|
83
|
+
config: Pick<OcxConfig, "apiKeys" | "hostname">,
|
|
84
|
+
admission: DataPlaneAdmission | undefined,
|
|
85
|
+
combo?: { allowMeteredTargets?: boolean },
|
|
86
|
+
): boolean {
|
|
87
|
+
if (admission?.kind === "configured") {
|
|
88
|
+
const entry = (config.apiKeys ?? []).find(key => key.id === admission.keyId);
|
|
89
|
+
return entry?.allowMeteredComboTargets === true;
|
|
90
|
+
}
|
|
91
|
+
// Linked ingress always carries keyed admission, even when the primary bind is loopback.
|
|
92
|
+
// A secondary loopback listener on a public proxy does not qualify either.
|
|
93
|
+
return (!admission || admission.kind === "loopback")
|
|
94
|
+
&& !isApiAuthRequired(config) && combo?.allowMeteredTargets === true;
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
/**
|
|
81
98
|
* Does this scope admit this resolved destination?
|
|
82
99
|
*
|
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,6 +924,9 @@ 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
|
}
|
|
@@ -1053,9 +1061,12 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
1053
1061
|
preserveResponsesReasoningContent: "editor",
|
|
1054
1062
|
preserveResponsesInputItemIds: "editor",
|
|
1055
1063
|
preserveResponsesMessageMetadata: "editor",
|
|
1064
|
+
projectResponsesLiteHostedSearchHistory: "editor",
|
|
1056
1065
|
dropResponsesReasoningItems: "editor",
|
|
1057
1066
|
modelReasoningEffortsAuthoritative: "editor",
|
|
1058
1067
|
decodesNativeCompactionBlobs: "editor",
|
|
1068
|
+
supportsNativeCompactionTrigger: "editor",
|
|
1069
|
+
supportsNativeCompactEndpoint: "editor",
|
|
1059
1070
|
allowEncryptedV2AgentTasks: "editor",
|
|
1060
1071
|
allowPrivateNetwork: "editor",
|
|
1061
1072
|
// A proxy URL routinely embeds `user:password@`, so it never reaches the dashboard DTO and
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
} from "./request-log";
|
|
55
55
|
import { createFinalRequestLog } from "./inference/final-log";
|
|
56
56
|
import { clientWireLogOf, clientWireOf } from "./inference/client-wire";
|
|
57
|
+
import { attachAdvisorReasoning } from "./inference/advisor-diagnostics";
|
|
57
58
|
import { directEncodersApply } from "./inference/client-encoder-delivery";
|
|
58
59
|
import { responseWithDeferredRequestLog } from "./relay";
|
|
59
60
|
import { handleResponses } from "./responses";
|
|
@@ -114,8 +115,10 @@ export async function handleChatCompletions(
|
|
|
114
115
|
): Promise<Response> {
|
|
115
116
|
const translatorBudget = createTranslatorBudget();
|
|
116
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);
|
|
117
120
|
return finalizeTranslatorBudgetResponse(
|
|
118
|
-
|
|
121
|
+
response,
|
|
119
122
|
translatorBudget,
|
|
120
123
|
req.signal,
|
|
121
124
|
);
|
|
@@ -601,6 +604,9 @@ async function handleChatCompletionsWithBudget(
|
|
|
601
604
|
// below could never restore this one -- the translated client was told the provider
|
|
602
605
|
// throttled the turn, and handed a two-second wait to send it again.
|
|
603
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";
|
|
604
610
|
} else if (upstreamCode === "model_not_found") {
|
|
605
611
|
// Structured model_not_found must win over classifyError's generic remaps.
|
|
606
612
|
classified.code = "model_not_found";
|