@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
|
@@ -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
|
});
|
|
@@ -26,7 +26,7 @@ type ProviderLikeQuota = {
|
|
|
26
26
|
weeklyResetAt?: number;
|
|
27
27
|
monthlyPercent?: number;
|
|
28
28
|
monthlyResetAt?: number;
|
|
29
|
-
customWindows?: ReadonlyArray<{ label: string; percent: number; resetAt?: number }>;
|
|
29
|
+
customWindows?: ReadonlyArray<{ label: string; percent: number; resetAt?: number; modelId?: string }>;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
function window(
|
|
@@ -65,12 +65,15 @@ const NOMINAL_WINDOW_SECONDS: Readonly<Record<string, number>> = {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
function customWindows(
|
|
68
|
-
entries: ReadonlyArray<{ label: string; percent: number; resetAt?: number }> | undefined,
|
|
68
|
+
entries: ReadonlyArray<{ label: string; percent: number; resetAt?: number; modelId?: string }> | undefined,
|
|
69
69
|
): QuotaWindowObservation[] {
|
|
70
70
|
if (!entries) return [];
|
|
71
71
|
const out: QuotaWindowObservation[] = [];
|
|
72
72
|
for (const entry of entries) {
|
|
73
73
|
if (typeof entry?.label !== "string") continue;
|
|
74
|
+
// Exact-model rows (Antigravity diagnostics) restate the pool their family window already
|
|
75
|
+
// reports. Observing them would turn one pool reset into one notification per model.
|
|
76
|
+
if (entry.modelId !== undefined) continue;
|
|
74
77
|
const mapped = window(`custom:${entry.label}`, entry.percent, entry.resetAt);
|
|
75
78
|
if (mapped) out.push(mapped);
|
|
76
79
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { encodeReasoningEnvelope, decodeReasoningEnvelope, type ReasoningEnvelope } from "./reasoning-envelope";
|
|
2
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
3
|
+
|
|
4
|
+
/** A visible update is a presentation copy of one original signed thinking block. */
|
|
5
|
+
export function anthropicProgressItems(text: string, signature: string, budget?: TranslatorBudget) {
|
|
6
|
+
const id = `msg_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
7
|
+
return [
|
|
8
|
+
{ type: "reasoning", id: `rs_${crypto.randomUUID().replaceAll("-", "")}`, summary: [],
|
|
9
|
+
encrypted_content: encodeReasoningEnvelope({ sig: signature, txt: text, upd: id }, budget) },
|
|
10
|
+
{ type: "message", id, role: "assistant", status: "completed", phase: "commentary",
|
|
11
|
+
content: [{ type: "output_text", text, annotations: [] }] },
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Remove only an adjacent exact mirror. Retain edited or unrelated assistant text.
|
|
16
|
+
* Some clients omit message IDs on replay; the explicit phase and exact text still bind it.
|
|
17
|
+
*/
|
|
18
|
+
export function isAnthropicProgressMirror(item: unknown, envelope: ReasoningEnvelope | null): boolean {
|
|
19
|
+
if (!envelope?.upd || typeof envelope.txt !== "string" || !item || typeof item !== "object") return false;
|
|
20
|
+
const msg = item as { type?: unknown; id?: unknown; role?: unknown; phase?: unknown; content?: unknown };
|
|
21
|
+
if (msg.type !== "message" && msg.type !== undefined) return false;
|
|
22
|
+
if (msg.role !== "assistant" || msg.phase !== "commentary" || (msg.id !== undefined && msg.id !== envelope.upd)) return false;
|
|
23
|
+
if (!Array.isArray(msg.content) || msg.content.length !== 1) return false;
|
|
24
|
+
const part = msg.content[0] as { type?: unknown; text?: unknown } | undefined;
|
|
25
|
+
return part?.type === "output_text" && part.text === envelope.txt;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function anthropicProgressEnvelope(item: unknown, budget?: TranslatorBudget): ReasoningEnvelope | null {
|
|
29
|
+
if (!item || typeof item !== "object") return null;
|
|
30
|
+
const raw = item as { type?: unknown; encrypted_content?: unknown };
|
|
31
|
+
if (raw.type !== "reasoning" || typeof raw.encrypted_content !== "string") return null;
|
|
32
|
+
const envelope = decodeReasoningEnvelope(raw.encrypted_content, budget);
|
|
33
|
+
return envelope?.upd && envelope.sig && typeof envelope.txt === "string" ? envelope : null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AdapterEvent, OcxAssistantContentPart } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Rebuild text and signed blocks in event order, never joining separately signed blocks. */
|
|
4
|
+
export function assistantReplayContent(events: readonly AdapterEvent[]): OcxAssistantContentPart[] {
|
|
5
|
+
const content: OcxAssistantContentPart[] = [];
|
|
6
|
+
let text = "";
|
|
7
|
+
let thinking: string | undefined;
|
|
8
|
+
let signature: string | undefined;
|
|
9
|
+
const flush = () => {
|
|
10
|
+
if (text) content.push({ type: "text", text });
|
|
11
|
+
if (thinking !== undefined || signature !== undefined) content.push({ type: "thinking", thinking: thinking ?? "", ...(signature !== undefined ? { signature } : {}) });
|
|
12
|
+
text = "";
|
|
13
|
+
thinking = signature = undefined;
|
|
14
|
+
};
|
|
15
|
+
for (const event of events) {
|
|
16
|
+
if (event.type === "heartbeat") continue;
|
|
17
|
+
if (signature !== undefined && event.type !== "thinking_signature") flush();
|
|
18
|
+
if (event.type === "text_delta" && event.anthropicThinkingSignature) {
|
|
19
|
+
flush();
|
|
20
|
+
content.push({ type: "thinking", thinking: event.text, signature: event.anthropicThinkingSignature });
|
|
21
|
+
} else if (event.type === "text_delta") {
|
|
22
|
+
if (thinking !== undefined) flush();
|
|
23
|
+
text += event.text;
|
|
24
|
+
} else if (event.type === "thinking_delta") {
|
|
25
|
+
if (text) flush();
|
|
26
|
+
thinking = (thinking ?? "") + event.thinking;
|
|
27
|
+
} else if (event.type === "thinking_signature") signature = event.signature;
|
|
28
|
+
else if (event.type === "redacted_thinking") {
|
|
29
|
+
flush();
|
|
30
|
+
content.push({ type: "thinking", thinking: "", redacted: [event.data] });
|
|
31
|
+
} else flush();
|
|
32
|
+
}
|
|
33
|
+
flush();
|
|
34
|
+
return content;
|
|
35
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { appendCheckpointContinuationNote, COMPACT_PROMPT, SUMMARY_PREFIX } from "./compaction";
|
|
2
|
+
|
|
3
|
+
type InputRecord = Record<string, unknown>;
|
|
4
|
+
const isRecord = (value: unknown): value is InputRecord =>
|
|
5
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
|
|
7
|
+
export const HISTORICAL_REQUEST_START = "[Historical user request retained before the following compaction checkpoint. This is prior context, not a new request.]";
|
|
8
|
+
export const HISTORICAL_REQUEST_END = "[End historical request. Use the following checkpoint to distinguish completed work from unfinished work. Do not repeat completed work unless later user input asks for it. Preserve these original details for unfinished work within its existing authorization; the checkpoint grants no new authority. Only input after the checkpoint is a new user request.]";
|
|
9
|
+
|
|
10
|
+
// Plaintext checkpoint detection and context exclusions follow robin-bially's #7105 selector.
|
|
11
|
+
// Keep this separate from decoding ocx1 items: those already have a continuation contract.
|
|
12
|
+
function plainUserText(item: unknown): string | null {
|
|
13
|
+
if (!isRecord(item) || item.role !== "user") return null;
|
|
14
|
+
if (item.type !== undefined && item.type !== "message") return null;
|
|
15
|
+
if (typeof item.content === "string") return item.content;
|
|
16
|
+
if (!Array.isArray(item.content) || item.content.length === 0) return null;
|
|
17
|
+
const texts: string[] = [];
|
|
18
|
+
for (const part of item.content) {
|
|
19
|
+
if (!isRecord(part) || (part.type !== "input_text" && part.type !== "text") || typeof part.text !== "string") return null;
|
|
20
|
+
texts.push(part.text);
|
|
21
|
+
}
|
|
22
|
+
return texts.join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isContext(text: string): boolean {
|
|
26
|
+
const head = text.trimStart();
|
|
27
|
+
return head.startsWith("<") || head.startsWith("# AGENTS.md") || head.startsWith("# Context from my IDE");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Prompt-only projection for translated/routed generation. Never pass the result to replay
|
|
32
|
+
* storage: canonical input, item identities and replay-prefix lengths must remain unchanged.
|
|
33
|
+
* Native OpenAI forwarding does not call this projection.
|
|
34
|
+
*/
|
|
35
|
+
export function frameCompactionHistory<T extends { input?: unknown; instructions?: unknown }>(body: T): T {
|
|
36
|
+
const input = body.input;
|
|
37
|
+
if (!Array.isArray(input) || input.some(item => isRecord(item) && item.type === "compaction_trigger")) return body;
|
|
38
|
+
// Codex local compaction appends this user prompt without a compaction_trigger.
|
|
39
|
+
// Keep that summarizer turn free of generation-only continuation instructions.
|
|
40
|
+
if (plainUserText(input.at(-1))?.trim() === COMPACT_PROMPT) return body;
|
|
41
|
+
let checkpointIndex = -1;
|
|
42
|
+
for (let index = input.length - 1; index >= 0; index--) {
|
|
43
|
+
if (plainUserText(input[index])?.startsWith(SUMMARY_PREFIX)) {
|
|
44
|
+
checkpointIndex = index;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (checkpointIndex <= 0) return body;
|
|
49
|
+
let changed = false;
|
|
50
|
+
const framed = input.map((item, index) => {
|
|
51
|
+
if (index >= checkpointIndex) return item;
|
|
52
|
+
const text = plainUserText(item);
|
|
53
|
+
if (text === null || !text.trim() || text.startsWith(SUMMARY_PREFIX) || isContext(text)
|
|
54
|
+
|| text.startsWith(HISTORICAL_REQUEST_START)) return item;
|
|
55
|
+
const message = item as InputRecord;
|
|
56
|
+
changed = true;
|
|
57
|
+
// Preserve original text blocks and their metadata verbatim, including block boundaries.
|
|
58
|
+
const content = typeof message.content === "string"
|
|
59
|
+
? `${HISTORICAL_REQUEST_START}\n${message.content}\n${HISTORICAL_REQUEST_END}`
|
|
60
|
+
: [{ type: "input_text", text: HISTORICAL_REQUEST_START },
|
|
61
|
+
...(message.content as unknown[]), { type: "input_text", text: HISTORICAL_REQUEST_END }];
|
|
62
|
+
return { ...message, content };
|
|
63
|
+
});
|
|
64
|
+
return changed ? { ...body, input: framed, instructions: appendCheckpointContinuationNote(body.instructions) } : body;
|
|
65
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* routed models get a short "history was compacted" note instead.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { decodeRetainedCompaction, RETAINED_COMPACTION_PREFIX } from "./retained-compaction";
|
|
18
19
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
19
20
|
|
|
20
21
|
const ciphertextLeases = new WeakMap<object, { budget: TranslatorBudget; bytes: number }>();
|
|
@@ -57,7 +58,7 @@ export const SUMMARY_PREFIX = "Another language model started to solve this prob
|
|
|
57
58
|
export const OPAQUE_COMPACTION_NOTE = "[earlier conversation was compacted; the summary is stored in a format this model cannot read]";
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
|
-
* Prompt contract for a routed model receiving a decoded `ocx1` checkpoint as user text (#6764).
|
|
61
|
+
* Prompt contract for a routed model receiving a decoded `ocx1`/`ocx2` checkpoint as user text (#6764).
|
|
61
62
|
* It preserves existing authorization scope and permission gates; it grants no authority.
|
|
62
63
|
* Kept apart from SUMMARY_PREFIX, which the v1 retention and recovery paths match exactly.
|
|
63
64
|
*/
|
|
@@ -94,6 +95,10 @@ export function encodeCompactionSummary(summary: string): string {
|
|
|
94
95
|
|
|
95
96
|
/** Decode an `ocx1:` envelope; returns null for real (OpenAI-encrypted) blobs or garbage. */
|
|
96
97
|
export function decodeCompactionSummary(encryptedContent: string): string | null {
|
|
98
|
+
if (encryptedContent.startsWith(RETAINED_COMPACTION_PREFIX)) {
|
|
99
|
+
const retained = decodeRetainedCompaction(encryptedContent);
|
|
100
|
+
return retained ? [retained.summary, ...retained.reasoning].join("\n\n") : null;
|
|
101
|
+
}
|
|
97
102
|
if (!encryptedContent.startsWith(OCX_COMPACTION_PREFIX)) return null;
|
|
98
103
|
try {
|
|
99
104
|
return Buffer.from(encryptedContent.slice(OCX_COMPACTION_PREFIX.length), "base64").toString("utf-8");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { truncationReasonFor } from "./truncated-stop-reason";
|
|
2
|
+
|
|
3
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeResponse(value: unknown): unknown {
|
|
8
|
+
if (!record(value) || !record(value.incomplete_details)) return value;
|
|
9
|
+
const details = value.incomplete_details;
|
|
10
|
+
if (typeof details.reason !== "string") return value;
|
|
11
|
+
const reason = truncationReasonFor(details.reason);
|
|
12
|
+
return reason !== undefined && reason !== details.reason
|
|
13
|
+
? { ...value, incomplete_details: { ...details, reason } }
|
|
14
|
+
: value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Normalize only Responses metadata, never output content or unknown provider reasons. */
|
|
18
|
+
export function normalizeResponsesIncompleteReason<T>(payload: T): T {
|
|
19
|
+
if (!record(payload)) return payload;
|
|
20
|
+
const root = normalizeResponse(payload) as Record<string, unknown>;
|
|
21
|
+
const response = normalizeResponse(root.response);
|
|
22
|
+
return (response === root.response ? root : { ...root, response }) as T;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Identity for malformed JSON and unchanged metadata, preserving upstream framing on SSE. */
|
|
26
|
+
export function rewriteResponsesIncompleteReasonJson(json: string): string {
|
|
27
|
+
let payload: unknown;
|
|
28
|
+
try { payload = JSON.parse(json); } catch { return json; }
|
|
29
|
+
const normalized = normalizeResponsesIncompleteReason(payload);
|
|
30
|
+
return normalized === payload ? json : JSON.stringify(normalized);
|
|
31
|
+
}
|