@bitkyc08/opencodex 2.83.0 → 2.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +4 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +55 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +173 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -39,17 +39,25 @@ function defaultEntryFactory(service: string, account: string): ProviderKeychain
|
|
|
39
39
|
let entryFactory: ProviderKeychainEntryFactory = defaultEntryFactory;
|
|
40
40
|
const resolvedCache = new Map<string, string>();
|
|
41
41
|
const warnedAccounts = new Set<string>();
|
|
42
|
+
let resolutionGeneration = 0;
|
|
43
|
+
|
|
44
|
+
/** Advances whenever cached secrets may have changed, so derived snapshots can rebuild. */
|
|
45
|
+
export function providerKeyResolutionGeneration(): number {
|
|
46
|
+
return resolutionGeneration;
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
/** Test seam: swap the OS entry for an in-memory one and drop caches. */
|
|
44
50
|
export function setProviderKeychainEntryFactoryForTests(factory: ProviderKeychainEntryFactory | null): void {
|
|
45
51
|
entryFactory = factory ?? defaultEntryFactory;
|
|
46
52
|
resolvedCache.clear();
|
|
53
|
+
resolutionGeneration++;
|
|
47
54
|
warnedAccounts.clear();
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
/** Write-path seam: a store/restore mutated secrets, so cached reads and warnings are stale. */
|
|
51
58
|
export function invalidateResolvedProviderKeyCache(): void {
|
|
52
59
|
resolvedCache.clear();
|
|
60
|
+
resolutionGeneration++;
|
|
53
61
|
warnedAccounts.clear();
|
|
54
62
|
}
|
|
55
63
|
|
|
@@ -76,7 +84,7 @@ export function providerKeychainEntry(account: string): ProviderKeychainEntry {
|
|
|
76
84
|
return entryFactory(PROVIDER_KEYCHAIN_SERVICE, account);
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
function readKeychain(account: string): string | undefined {
|
|
87
|
+
function readKeychain(account: string, quiet = false): string | undefined {
|
|
80
88
|
const cached = resolvedCache.get(account);
|
|
81
89
|
if (cached !== undefined) return cached;
|
|
82
90
|
try {
|
|
@@ -88,7 +96,7 @@ function readKeychain(account: string): string | undefined {
|
|
|
88
96
|
} catch {
|
|
89
97
|
// fall through to the single warning below
|
|
90
98
|
}
|
|
91
|
-
if (!warnedAccounts.has(account)) {
|
|
99
|
+
if (!quiet && !warnedAccounts.has(account)) {
|
|
92
100
|
warnedAccounts.add(account);
|
|
93
101
|
console.warn(`[opencodex] provider key reference keychain:${account} could not be read from the OS keychain; requests for this provider have no credential until the keychain is available (no plaintext fallback)`);
|
|
94
102
|
}
|
|
@@ -105,6 +113,13 @@ export function resolveProviderApiKey(value: string | undefined): string | undef
|
|
|
105
113
|
return resolveEnvValue(value);
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
/** The same resolution without the unreadable-reference warning, for observe-only callers. */
|
|
117
|
+
export function resolveProviderApiKeyQuietly(value: string | undefined): string | undefined {
|
|
118
|
+
if (!value) return undefined;
|
|
119
|
+
if (isKeychainReference(value)) return readKeychain(keychainAccount(value), true);
|
|
120
|
+
return resolveEnvValue(value);
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
export type ProviderKeyStoreKind = "keychain" | "env" | "file" | "none";
|
|
109
124
|
|
|
110
125
|
export function providerKeyStoreKind(provider: Pick<OcxProviderConfig, "apiKey"> | undefined): ProviderKeyStoreKind {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Modelled after src/codex/routing.ts cooldown logic but scoped to plain API-key pools.
|
|
10
10
|
*/
|
|
11
|
-
import { commitProviderApiKeySelection } from "./api-key-selection";
|
|
11
|
+
import { commitProviderApiKeySelection, providerApiKeySelectionIsCurrent } from "./api-key-selection";
|
|
12
12
|
import type { ProviderApiKeySelection } from "../types/provider";
|
|
13
13
|
import { routedProviderConfig } from "../router";
|
|
14
14
|
import { getProviderRegistryEntry } from "./registry";
|
|
@@ -30,6 +30,16 @@ interface KeyCooldown {
|
|
|
30
30
|
|
|
31
31
|
const DEFAULT_COOLDOWN_MS = 60_000;
|
|
32
32
|
const MAX_COOLDOWN_MS = 10 * 60_000; // cap at 10 min for api-key rotation
|
|
33
|
+
/**
|
|
34
|
+
* Anthropic credit-balance refusal (#6958). An empty prepaid balance is account state, not a
|
|
35
|
+
* rate signal: it clears only when someone buys credits or auto-reload fires, so the
|
|
36
|
+
* ten-minute credential cap would hand a dead key back to rotation six times an hour. One hour
|
|
37
|
+
* keeps it out of replacement and proactive selection while a funded sibling exists, and still
|
|
38
|
+
* notices a top-up without a restart. When every key is held, requests keep using the active
|
|
39
|
+
* key and return Anthropic's original refusal. The hold is in memory; dashboard key management
|
|
40
|
+
* clears it.
|
|
41
|
+
*/
|
|
42
|
+
export const ANTHROPIC_BILLING_COOLDOWN_MS = 60 * 60_000;
|
|
33
43
|
|
|
34
44
|
/**
|
|
35
45
|
* Cap for a cooldown the upstream itself dated, as opposed to one we inferred.
|
|
@@ -688,12 +698,13 @@ export function rateLimitRetryDelayMs(
|
|
|
688
698
|
function rotateKeyAfterFailure(
|
|
689
699
|
config: OcxConfig,
|
|
690
700
|
providerName: string,
|
|
691
|
-
failureStatus: 401 | 429,
|
|
701
|
+
failureStatus: 401 | 429 | "billing",
|
|
692
702
|
retryAfterHeader: string | null | undefined,
|
|
693
703
|
now = Date.now(),
|
|
694
704
|
attemptedKey?: string,
|
|
695
705
|
attemptedSelection?: ProviderApiKeySelection,
|
|
696
706
|
quotaResetAt?: number,
|
|
707
|
+
acceptsFreshProvider?: (provider: OcxProviderConfig) => boolean,
|
|
697
708
|
): OcxProviderConfig | null {
|
|
698
709
|
const provider = config.providers[providerName];
|
|
699
710
|
if (!provider) return null;
|
|
@@ -704,6 +715,11 @@ function rotateKeyAfterFailure(
|
|
|
704
715
|
| { failedId?: string; candidateId?: string }
|
|
705
716
|
| { exhaustedCount: number; failedId?: string };
|
|
706
717
|
const outcome = commitProviderApiKeySelection<Rotation | null>(config, providerName, freshProvider => {
|
|
718
|
+
// Run on every persisted transaction attempt, including a freshness rebase.
|
|
719
|
+
if (acceptsFreshProvider) {
|
|
720
|
+
try { if (!acceptsFreshProvider(freshProvider)) return { changed: false, value: null }; }
|
|
721
|
+
catch { return { changed: false, value: null }; }
|
|
722
|
+
}
|
|
707
723
|
const pool = freshProvider.apiKeyPool;
|
|
708
724
|
if (!pool || pool.length < 2) return { changed: false, value: null };
|
|
709
725
|
|
|
@@ -741,6 +757,8 @@ function rotateKeyAfterFailure(
|
|
|
741
757
|
}, attemptedSelection);
|
|
742
758
|
if (outcome.status === "unavailable") return null;
|
|
743
759
|
if (outcome.status === "superseded") {
|
|
760
|
+
// Billing authority is bound to the sent selection; a newer manual choice grants no replay.
|
|
761
|
+
if (acceptsFreshProvider) return null;
|
|
744
762
|
// A newer manual selection (including A→B→A) owns subsequent dispatch. Reusing the
|
|
745
763
|
// same failed key here would loop forever; preserve its original failure instead.
|
|
746
764
|
return outcome.provider.apiKey !== failedKey ? structuredClone(outcome.provider) : null;
|
|
@@ -750,7 +768,9 @@ function rotateKeyAfterFailure(
|
|
|
750
768
|
// A 401 is a verdict about the credential itself, not a timing signal: the key is rejected
|
|
751
769
|
// until an operator replaces it, and upstreams send no Retry-After for it. Hold it for the
|
|
752
770
|
// full cap instead of the 429 default so a dead key is not re-tried once a minute.
|
|
753
|
-
const cooldownMs = failureStatus ===
|
|
771
|
+
const cooldownMs = failureStatus === "billing"
|
|
772
|
+
? ANTHROPIC_BILLING_COOLDOWN_MS
|
|
773
|
+
: failureStatus === 401
|
|
754
774
|
? MAX_COOLDOWN_MS
|
|
755
775
|
// A reset instant the upstream dated outranks both the header and the
|
|
756
776
|
// default: it is the only one of the three that knows when the quota
|
|
@@ -762,7 +782,7 @@ function rotateKeyAfterFailure(
|
|
|
762
782
|
sweepExpiredOnWrite(now);
|
|
763
783
|
}
|
|
764
784
|
if ("exhaustedCount" in outcome.value) {
|
|
765
|
-
console.warn(`[key-failover] ${providerName}: all ${outcome.value.exhaustedCount} keys in cooldown after ${failureStatus}; returning the upstream status to the client`);
|
|
785
|
+
console.warn(`[key-failover] ${providerName}: all ${outcome.value.exhaustedCount} keys in cooldown after ${failureLabel(failureStatus)}; returning the upstream status to the client`);
|
|
766
786
|
return null;
|
|
767
787
|
}
|
|
768
788
|
|
|
@@ -771,12 +791,16 @@ function rotateKeyAfterFailure(
|
|
|
771
791
|
if (outcome.value.candidateId) {
|
|
772
792
|
console.warn(
|
|
773
793
|
// Log ids only — labels are user-supplied free text and could carry secret material.
|
|
774
|
-
`[key-failover] ${providerName}: ${failureStatus} on key ${outcome.value.failedId ?? "?"}; rotating to key ${outcome.value.candidateId}`,
|
|
794
|
+
`[key-failover] ${providerName}: ${failureLabel(failureStatus)} on key ${outcome.value.failedId ?? "?"}; rotating to key ${outcome.value.candidateId}`,
|
|
775
795
|
);
|
|
776
796
|
}
|
|
777
797
|
return structuredClone(committed);
|
|
778
798
|
}
|
|
779
799
|
|
|
800
|
+
function failureLabel(failureStatus: 401 | 429 | "billing"): string {
|
|
801
|
+
return failureStatus === "billing" ? "credit-balance refusal" : String(failureStatus);
|
|
802
|
+
}
|
|
803
|
+
|
|
780
804
|
export function rotateKeyOn429(
|
|
781
805
|
config: OcxConfig,
|
|
782
806
|
providerName: string,
|
|
@@ -865,6 +889,29 @@ export function rotateProviderTransportOn401(
|
|
|
865
889
|
return applyRotatedTransport(providerName, routedProvider, rotated, options.promptCacheKey);
|
|
866
890
|
}
|
|
867
891
|
|
|
892
|
+
/** Exact Anthropic billing refusal: cool the key for ANTHROPIC_BILLING_COOLDOWN_MS. */
|
|
893
|
+
export function rotateProviderTransportOnAnthropicBilling(
|
|
894
|
+
config: OcxConfig,
|
|
895
|
+
providerName: string,
|
|
896
|
+
routedProvider: OcxProviderTransport,
|
|
897
|
+
options: Omit<RotateProviderTransportOptions, "retryAfter"> = {},
|
|
898
|
+
): OcxProviderTransport | null {
|
|
899
|
+
if (routedProvider.adapter !== "anthropic"
|
|
900
|
+
|| (routedProvider.authMode !== undefined && routedProvider.authMode !== "key")
|
|
901
|
+
|| !hasKeyPoolFailover(routedProvider)) return null;
|
|
902
|
+
const headerShape = (provider: OcxProviderConfig) => JSON.stringify(Object.entries(provider.headers ?? {}).sort(([a], [b]) => a.localeCompare(b)));
|
|
903
|
+
const sentHeaders = headerShape(routedProvider);
|
|
904
|
+
const acceptsFreshProvider = (fresh: OcxProviderConfig): boolean => fresh.adapter === routedProvider.adapter
|
|
905
|
+
&& (fresh.authMode === undefined || fresh.authMode === "key") && hasKeyPoolFailover(fresh)
|
|
906
|
+
&& (fresh.apiKeyTransport ?? "x-api-key") === (routedProvider.apiKeyTransport ?? "x-api-key")
|
|
907
|
+
&& headerShape(fresh) === sentHeaders
|
|
908
|
+
&& providerApiKeySelectionIsCurrent({ ...config, providers: { ...config.providers, [providerName]: fresh } },
|
|
909
|
+
providerName, routedProvider);
|
|
910
|
+
const rotated = rotateKeyAfterFailure(config, providerName, "billing", null, options.now,
|
|
911
|
+
options.attemptedKey, options.attemptedSelection ?? routedProvider._apiKeyAttempt, undefined, acceptsFreshProvider);
|
|
912
|
+
return rotated ? applyRotatedTransport(providerName, routedProvider, rotated, options.promptCacheKey) : null;
|
|
913
|
+
}
|
|
914
|
+
|
|
868
915
|
function applyRotatedTransport(
|
|
869
916
|
providerName: string,
|
|
870
917
|
routedProvider: OcxProviderTransport,
|
|
@@ -59,6 +59,7 @@ export const PROVIDER_MODEL_RENAME_ROLES = {
|
|
|
59
59
|
newModelPolicy: "none",
|
|
60
60
|
modelPreset: "none",
|
|
61
61
|
contextWindow: "none",
|
|
62
|
+
inferContextWindow: "none",
|
|
62
63
|
modelContextWindows: "record",
|
|
63
64
|
modelContextTiers: "record",
|
|
64
65
|
modelInputModalities: "record",
|
|
@@ -66,6 +67,7 @@ export const PROVIDER_MODEL_RENAME_ROLES = {
|
|
|
66
67
|
modelMaxInputTokens: "record",
|
|
67
68
|
modelAutoCompactTokenLimits: "record",
|
|
68
69
|
defaultMaxOutputTokens: "none",
|
|
70
|
+
minMaxOutputTokens: "none",
|
|
69
71
|
modelMaxOutputTokens: "record",
|
|
70
72
|
modelCosts: "record",
|
|
71
73
|
autoReviewModel: "none",
|
|
@@ -124,16 +124,18 @@ export const MODEL_RENAMES: readonly ModelRename[] = [
|
|
|
124
124
|
// Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a
|
|
125
125
|
// dead selection rather than a merely outdated one. Routing already redirects these ids
|
|
126
126
|
// at request time; this migration repairs the saved config so the picker, the allowlist
|
|
127
|
-
// and the capability maps stop naming a model the backend no longer serves.
|
|
127
|
+
// and the capability maps stop naming a model the backend no longer serves. The target is
|
|
128
|
+
// the live 3.8 base: 3.7 left the static lineup on 2026-10-14 (CCA 404s its wire id), and
|
|
129
|
+
// registrySeedsTarget refuses to write an id the registry no longer seeds.
|
|
128
130
|
...(["gemini-3.6-flash", "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high",
|
|
129
131
|
"gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-mid", "gemini-3.5-flash-high",
|
|
130
132
|
"gemini-3-flash-agent"] as const).map(from => ({
|
|
131
133
|
provider: "google-antigravity",
|
|
132
134
|
from,
|
|
133
|
-
to: "gemini-3.
|
|
135
|
+
to: "gemini-3.8-flash",
|
|
134
136
|
reason: "Google retires the previous Antigravity Flash generation from Cloud Code Assist when its successor ships, so the saved id no longer resolves to a live model",
|
|
135
137
|
// The retired Flash tiers were wire ids, so any saved per-model record keyed by one
|
|
136
|
-
// may also hold one as a value. 3.
|
|
138
|
+
// may also hold one as a value. 3.8 resolves tiers from its own suffix ladder instead.
|
|
137
139
|
dropReasoningEffortMap: true,
|
|
138
140
|
})),
|
|
139
141
|
];
|
|
@@ -3,6 +3,7 @@ import { projectModelRenames } from "./model-rename-migration";
|
|
|
3
3
|
import { projectStaleContextWindows } from "./stale-context-window-migration";
|
|
4
4
|
import { projectStaleModelRosters } from "./stale-model-roster-migration";
|
|
5
5
|
import { projectStaleVisionClassifications } from "./stale-vision-classification-migration";
|
|
6
|
+
import { projectStaleNoVisionSeeds } from "./stale-no-vision-seed-migration";
|
|
6
7
|
import { projectDevinCliAuthMode } from "./devin-cli-authmode-migration";
|
|
7
8
|
import type { OcxConfig } from "../types";
|
|
8
9
|
|
|
@@ -17,12 +18,13 @@ export function projectStartupConfigRepairs(config: OcxConfig): ReturnType<typeo
|
|
|
17
18
|
const renames = projectModelRenames(config);
|
|
18
19
|
const windows = projectStaleContextWindows(renames.config);
|
|
19
20
|
const vision = projectStaleVisionClassifications(windows.config);
|
|
20
|
-
const
|
|
21
|
+
const noVision = projectStaleNoVisionSeeds(vision.config);
|
|
22
|
+
const rosters = projectStaleModelRosters(noVision.config);
|
|
21
23
|
const devinCli = projectDevinCliAuthMode(rosters.config);
|
|
22
24
|
return {
|
|
23
25
|
config: devinCli.config,
|
|
24
|
-
changed: renames.changed || windows.changed || vision.changed || rosters.changed || devinCli.changed,
|
|
25
|
-
warnings: [...renames.warnings, ...windows.warnings, ...vision.warnings, ...rosters.warnings, ...devinCli.warnings],
|
|
26
|
+
changed: renames.changed || windows.changed || vision.changed || noVision.changed || rosters.changed || devinCli.changed,
|
|
27
|
+
warnings: [...renames.warnings, ...windows.warnings, ...vision.warnings, ...noVision.warnings, ...rosters.warnings, ...devinCli.warnings],
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -473,6 +473,17 @@ export function clearAccountQuotaCache(provider?: string): void {
|
|
|
473
473
|
persistAccountQuotaCache();
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
/** Drop one account's cached bars and any joined probe so a reset is not ranked from the old window. */
|
|
477
|
+
export function forgetCachedProviderAccountQuota(provider: string, accountId: string): void {
|
|
478
|
+
hydrateAccountQuotaCache();
|
|
479
|
+
const key = accountCacheKey(provider, accountId);
|
|
480
|
+
accountQuotaCache.delete(key);
|
|
481
|
+
for (const flight of [...accountQuotaInflight.keys()]) {
|
|
482
|
+
if (flight === key || flight.startsWith(key + "\u0000")) accountQuotaInflight.delete(flight);
|
|
483
|
+
}
|
|
484
|
+
persistAccountQuotaCache();
|
|
485
|
+
}
|
|
486
|
+
|
|
476
487
|
/**
|
|
477
488
|
* Resolve a bearer for quota probing without silently adopting a newer global
|
|
478
489
|
* Claude CLI credential into a background multiauth slot.
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
*/
|
|
21
21
|
export async function fetchAnthropicAccountQuota(
|
|
22
22
|
instance: AnthropicInstanceId, accountId: string, force: boolean, config: OcxConfig = loadConfig(),
|
|
23
|
+
/** Never join an older account or vendor flight; a reset reread needs post-grant evidence. */
|
|
24
|
+
freshDispatch = false,
|
|
23
25
|
): Promise<AccountQuotaCacheEntry> {
|
|
24
26
|
const unavailable = (): AccountQuotaCacheEntry => ({ ts: Date.now(), quota: null, unavailable: true });
|
|
25
27
|
const target = config.providers[instance];
|
|
@@ -52,7 +54,7 @@ export async function fetchAnthropicAccountQuota(
|
|
|
52
54
|
const flightKey = recovery.anthropicCredentialQuotaFlightKey(key, accountId);
|
|
53
55
|
const flightCurrent = () => ownerCurrent()
|
|
54
56
|
&& recovery.anthropicCredentialQuotaFlightKey(key, accountId) === flightKey;
|
|
55
|
-
const joinable = accountQuotaInflight.get(flightKey);
|
|
57
|
+
const joinable = freshDispatch ? undefined : accountQuotaInflight.get(flightKey);
|
|
56
58
|
if (joinable) {
|
|
57
59
|
const joined = await joinable;
|
|
58
60
|
return ownerCurrent() && (joined.isCurrent?.() ?? flightCurrent()) ? joined : unavailable();
|
|
@@ -78,7 +80,7 @@ export async function fetchAnthropicAccountQuota(
|
|
|
78
80
|
const result = await recovery.probeAnthropicQuotaWithRecovery(accountId, token, fresh => {
|
|
79
81
|
if (!flightCurrent()) throw new AnthropicQuotaProbeOwnershipError("quota flight changed");
|
|
80
82
|
recovery.assertAnthropicQuotaSendAllowed(accountId, token);
|
|
81
|
-
return fetchAnthropicUsageQuotaForInstance(instance, token, fresh);
|
|
83
|
+
return fetchAnthropicUsageQuotaForInstance(instance, token, fresh || freshDispatch);
|
|
82
84
|
}, () => ownerCurrent() && mayCommitAccountQuotaKey(key, writerGeneration));
|
|
83
85
|
if (result && !result.isCurrent()) return unavailable();
|
|
84
86
|
if (!result && !flightCurrent()) return unavailable();
|
|
@@ -344,3 +344,17 @@ export function isProviderQuotaReportCurrent(value: ProviderQuotaReport): boolea
|
|
|
344
344
|
return (generation === undefined || isMainAccountIdentityGenerationLive(generation))
|
|
345
345
|
&& (accountReportCurrent.get(value)?.() ?? true);
|
|
346
346
|
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* The reports this process already holds for the configured providers, for a passive read (#6959):
|
|
350
|
+
* no probe, token refresh, account snapshot or publication into routing evidence. The Home's own
|
|
351
|
+
* dashboard and traffic keep the cache current.
|
|
352
|
+
*/
|
|
353
|
+
export function heldProviderQuotaReports(configuredProviders: readonly string[]): ProviderQuotaResponse {
|
|
354
|
+
const held = getProviderQuotaReportCache()?.response;
|
|
355
|
+
const configured = new Set(configuredProviders);
|
|
356
|
+
return {
|
|
357
|
+
generatedAt: held?.generatedAt ?? Date.now(),
|
|
358
|
+
reports: (held?.reports ?? []).filter(item => configured.has(item.provider) && isProviderQuotaReportCurrent(item)),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
@@ -327,9 +327,9 @@ async function readAnthropicUsageQuota(accessToken: string): Promise<ProviderQuo
|
|
|
327
327
|
* This endpoint reports quota only. Its body carries `five_hour`, `seven_day`, the
|
|
328
328
|
* model-scoped weekly buckets (`seven_day_fable`/`_opus`/`_sonnet`) and a `limits` array,
|
|
329
329
|
* and **no subscription or tier field** — nor does the OAuth token response, which yields only
|
|
330
|
-
* `account.uuid` and `account.email_address` (`src/oauth/anthropic.ts`).
|
|
331
|
-
*
|
|
332
|
-
*
|
|
330
|
+
* `account.uuid` and `account.email_address` (`src/oauth/anthropic.ts`). Subscription labels
|
|
331
|
+
* instead come from the authenticated profile in `src/oauth/anthropic-identity.ts`, never
|
|
332
|
+
* from this quota response.
|
|
333
333
|
*
|
|
334
334
|
* A tier must not be inferred from what is here. Percentages are normalized per account, so a
|
|
335
335
|
* Max x5 seat at 50% is byte-identical to a Max x20 seat at 50%, and the presence of a
|
package/src/providers/quota.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "./kiro-usage";
|
|
16
16
|
import { mapQuotaRoster, readProviderApiKeyQuotas, type ProviderApiKeyQuota } from "./quota-key-accounts";
|
|
17
17
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
18
|
+
import { isPassiveRead } from "../lib/passive-read";
|
|
18
19
|
import type { ProviderQuota, QuotaFailureCode } from "./quota-types";
|
|
19
20
|
import {
|
|
20
21
|
accountReportCurrent,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
captureProviderReportEpochs, providerReportEpochChanged,
|
|
24
25
|
cacheKeyWithAggregationState,
|
|
25
26
|
getProviderQuotaReportCache,
|
|
26
|
-
hasCodexPoolProvider,
|
|
27
|
+
hasCodexPoolProvider, heldProviderQuotaReports,
|
|
27
28
|
inflight,
|
|
28
29
|
invalidationEpoch,
|
|
29
30
|
isBuiltInChatGptForwardProvider,
|
|
@@ -229,6 +230,8 @@ export function flushProviderQuotaObservationsForTests(): Promise<void> {
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh = false): Promise<ProviderQuotaResponse> {
|
|
233
|
+
// A passive read (a Remote Link Child's, #6959) shows the held reports and starts nothing.
|
|
234
|
+
if (isPassiveRead()) return heldProviderQuotaReports(Object.keys(config.providers));
|
|
232
235
|
// A Pool report's cache signature and provider fetch must share one account snapshot.
|
|
233
236
|
// Preserve force semantics when deciding whether that snapshot refreshes upstream data.
|
|
234
237
|
const prefetchedCodexSnapshot = hasCodexPoolProvider(config)
|
|
@@ -32,12 +32,12 @@ import {
|
|
|
32
32
|
OPENAI_API_GPT56_MAX_INPUT_TOKENS,
|
|
33
33
|
OPENAI_API_GPT56_VIRTUAL_MODELS,
|
|
34
34
|
OPENAI_API_GPT56_REASONING_EFFORTS,
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
META_MODEL_API_REASONING_EFFORTS,
|
|
36
|
+
META_MODEL_API_REASONING_EFFORT_MAPS,
|
|
37
37
|
META_MUSE_CODE_REASONING_EFFORTS,
|
|
38
38
|
META_MUSE_CODE_REASONING_EFFORT_MAP,
|
|
39
39
|
META_MUSE_CONTEXT_WINDOW,
|
|
40
|
-
|
|
40
|
+
META_MODEL_API_MODELS,
|
|
41
41
|
OPENAI_DAYBREAK_MODELS,
|
|
42
42
|
OPENAI_DAYBREAK_CONTEXT_WINDOWS,
|
|
43
43
|
OPENAI_DAYBREAK_MAX_INPUT_TOKENS,
|
|
@@ -736,7 +736,7 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
736
736
|
authKind: "key",
|
|
737
737
|
dashboardUrl: "https://dev.meta.ai/docs/authentication",
|
|
738
738
|
defaultModel: "muse-spark-1.3",
|
|
739
|
-
models:
|
|
739
|
+
models: META_MODEL_API_MODELS,
|
|
740
740
|
// Static roster: no authenticated /v1/models payload was ever observed (the only
|
|
741
741
|
// contact was an unauthenticated GET returning 401 invalid_api_key), and Meta serves
|
|
742
742
|
// non-agent families on this same base URL. Turning discovery on would publish an
|
|
@@ -746,20 +746,20 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
746
746
|
// without this, registry transport canonicalization would retarget it and send their
|
|
747
747
|
// saved key to Meta.
|
|
748
748
|
preserveCustomDestination: true,
|
|
749
|
-
modelContextWindows: Object.fromEntries(
|
|
749
|
+
modelContextWindows: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
|
|
750
750
|
// text+image only. Meta also documents video, audio (degraded on 1.3), and PDF, but
|
|
751
751
|
// the catalog modality enum is text/image and over-advertising poisons the exported
|
|
752
752
|
// client config (see tests/codex-integration/catalog-input-modality-enum.test.ts).
|
|
753
|
-
modelInputModalities: Object.fromEntries(
|
|
754
|
-
modelReasoningEfforts:
|
|
755
|
-
modelReasoningEffortMap:
|
|
753
|
+
modelInputModalities: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
|
|
754
|
+
modelReasoningEfforts: META_MODEL_API_REASONING_EFFORTS,
|
|
755
|
+
modelReasoningEffortMap: META_MODEL_API_REASONING_EFFORT_MAPS,
|
|
756
756
|
// No defaultMaxOutputTokens: Meta publishes none. The only number in its docs
|
|
757
757
|
// (131072) appears inside a third-party config sample, and the protocol pages call
|
|
758
758
|
// the real limit "model-dependent".
|
|
759
759
|
// Meta names its variable MODEL_API_KEY, but the env var opencodex reads is derived
|
|
760
760
|
// from the provider id (META_MODEL_API_KEY). Saying only Meta's name would send a
|
|
761
761
|
// user to export a variable this proxy never reads.
|
|
762
|
-
note: "Pay-as-you-go Meta Model API. Get a key at https://dev.meta.ai (Meta calls it MODEL_API_KEY; export it here as META_MODEL_API_KEY) — a Meta developer account needs a payment method before it can serve requests, and every call is metered per token. A Muse Code subscription does NOT work here: Meta scopes that credential to the Muse Code CLI and bills any other key pay-as-you-go (dev.meta.ai/docs/muse-code/subscriptions). The Contributor tier (muse-spark-1.3-contributor) is cheap because Meta trains on your prompts — about 92% off input, 95% off output, 99% off cached input; do not send confidential material through it. Muse Spark is also reachable through resellers: command-code carries both tiers, opencode-go serves only muse-spark-1.3-contributor.",
|
|
762
|
+
note: "Pay-as-you-go Meta Model API. Get a key at https://dev.meta.ai (Meta calls it MODEL_API_KEY; export it here as META_MODEL_API_KEY) — a Meta developer account needs a payment method before it can serve requests, and every call is metered per token. A Muse Code subscription does NOT work here: Meta scopes that credential to the Muse Code CLI and bills any other key pay-as-you-go (dev.meta.ai/docs/muse-code/subscriptions). The Contributor tier (muse-spark-1.3-contributor) is cheap because Meta trains on your prompts — about 92% off input, 95% off output, 99% off cached input; do not send confidential material through it. Muse Wonder (muse-wonder-1.0, preview) is Standard mode only on this API ($4 input / $20 output per 1M tokens, no Contributor tier) and accepts reasoning effort up to max; the Spark rows stop at xhigh. Muse Spark is also reachable through resellers: command-code carries both tiers, opencode-go serves only muse-spark-1.3-contributor.",
|
|
763
763
|
},
|
|
764
764
|
/* [Decision Log]
|
|
765
765
|
- 목적과 의도: Let an operator who already signed the Muse Code CLI in reach Muse Spark with that credential, instead of provisioning a second key.
|
|
@@ -787,14 +787,14 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
787
787
|
oauthId: "meta-muse",
|
|
788
788
|
dashboardUrl: "https://dev.meta.ai",
|
|
789
789
|
defaultModel: "muse-spark-1.3",
|
|
790
|
-
models:
|
|
790
|
+
models: META_MODEL_API_MODELS,
|
|
791
791
|
// Same reason as meta-model: the authenticated roster carries muse-image-1.0 and
|
|
792
792
|
// muse-voice-transcribe-1.0, which this Responses-agent provider cannot drive.
|
|
793
793
|
liveModels: false,
|
|
794
|
-
modelContextWindows: Object.fromEntries(
|
|
795
|
-
modelInputModalities: Object.fromEntries(
|
|
796
|
-
modelReasoningEfforts: Object.fromEntries(
|
|
797
|
-
modelReasoningEffortMap: Object.fromEntries(
|
|
794
|
+
modelContextWindows: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
|
|
795
|
+
modelInputModalities: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
|
|
796
|
+
modelReasoningEfforts: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORTS])),
|
|
797
|
+
modelReasoningEffortMap: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORT_MAP])),
|
|
798
798
|
note: "Signs in to Meta with a browser device code on any platform, then mints the Muse Code subscription key. That grant is reimplemented from the one the Muse Code CLI performs and has NOT been exercised against Meta from OpenCodex, so treat the first login as unverified. If the Muse Code CLI is already signed in on macOS, the existing key is imported instead of starting a new grant. A pasted key from https://dev.meta.ai still works as a fallback when a device login cannot complete, and faces the same format check and live validation. A device login authenticates as Meta own Muse Code client, which is a stronger claim than reusing a key the CLI already minted. Meta scopes that credential to the Muse Code CLI, so this is an UNSUPPORTED use: Meta does not authorize subscription coverage outside its own CLI, how these calls settle is not observable from the API, and you should treat every call as billable against your account. The key, imported or pasted, is copied into OpenCodex's auth store. For an account signed in with the device login, OpenCodex refreshes Meta's subscription windows on demand from the same key endpoint the login uses, at most once every five minutes. For an imported or pasted key there is no endpoint to query them on demand, so OpenCodex reads them from streaming responses and shows the last observed value with its age; refreshing one then requires another streaming turn, and translated (non-passthrough) turns report none. Rate limits apply per team, not per key. For a supported path use the meta-model provider with your own key (export it as META_MODEL_API_KEY).",
|
|
799
799
|
},
|
|
800
800
|
{
|
|
@@ -1140,10 +1140,8 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
1140
1140
|
// https://cf.api.fan/v1 — GET /v1/models answers 401 without a key, so the host is live and
|
|
1141
1141
|
// discovery narrows to what the key's token group allows. Model ids are bare OpenAI-style
|
|
1142
1142
|
// ids (the Codex token group lists gpt-5.5 / gpt-5.1-codex).
|
|
1143
|
-
// Standard sponsor under SPONSORS.md; the dashboardUrl carries their affiliate code.
|
|
1144
1143
|
id: "packycode", label: "PackyCode", adapter: "openai-chat", baseUrl: "https://cf.api.fan/v1",
|
|
1145
|
-
authKind: "key", dashboardUrl: "https://www.packyapi.com
|
|
1146
|
-
sponsor: { tier: "standard", url: "https://www.packyapi.com/register?aff=k5KT" },
|
|
1144
|
+
authKind: "key", dashboardUrl: "https://www.packyapi.com",
|
|
1147
1145
|
defaultModel: "gpt-5.5",
|
|
1148
1146
|
models: ["gpt-5.5", "gpt-5.1-codex"],
|
|
1149
1147
|
liveModels: true,
|
|
@@ -1170,13 +1168,16 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
1170
1168
|
// devlog/_plan/260710_provider_hardening/001_research_frontier.md.
|
|
1171
1169
|
{
|
|
1172
1170
|
id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
|
|
1173
|
-
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.8-flash", "gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview", "gemini-3.7-flash"],
|
|
1174
|
-
modelContextWindows: { "gemini-3.8-flash": 1_048_576, "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576, "gemini-3.7-flash": 1_048_576 },
|
|
1175
|
-
modelInputModalities: { "gemini-3.8-flash": ["text", "image"], "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"], "gemini-3.7-flash": ["text", "image"] },
|
|
1171
|
+
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-4-argon", "gemini-3.8-flash", "gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview", "gemini-3.7-flash"],
|
|
1172
|
+
modelContextWindows: { "gemini-4-argon": 1_048_576, "gemini-3.8-flash": 1_048_576, "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576, "gemini-3.7-flash": 1_048_576 },
|
|
1173
|
+
modelInputModalities: { "gemini-4-argon": ["text", "image"], "gemini-3.8-flash": ["text", "image"], "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"], "gemini-3.7-flash": ["text", "image"] },
|
|
1176
1174
|
modelReasoningEfforts: {
|
|
1177
1175
|
// 3.7 and 3.8 omit `minimal`: Google documents it as a validation error on both model
|
|
1178
1176
|
// pages, so advertising it hands the user a rung the API rejects. 3.5/3.6 keep theirs —
|
|
1179
1177
|
// their pages still list it, and this unit has no evidence to change them.
|
|
1178
|
+
// Gemini 4 Argon (GA 2026-10-13): low/medium(default)/high; `minimal` and thinking_budget
|
|
1179
|
+
// return HTTP 400 per ai.google.dev/gemini-api/docs/models/gemini-4-argon.
|
|
1180
|
+
"gemini-4-argon": ["low", "medium", "high"],
|
|
1180
1181
|
"gemini-3.8-flash": ["low", "medium", "high"],
|
|
1181
1182
|
"gemini-3.7-flash": ["low", "medium", "high"],
|
|
1182
1183
|
"gemini-3.6-flash": ["minimal", "low", "medium", "high"],
|
|
@@ -1247,6 +1247,25 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1247
1247
|
},
|
|
1248
1248
|
note: "OpenAI-compatible API gateway. Create a workspace API key at tokenlab.sh. Live discovery lists tool-capable chat models available to your key and delivery policy.",
|
|
1249
1249
|
},
|
|
1250
|
+
{
|
|
1251
|
+
// Modelflare: OpenAI-compatible relay at https://modelflare.dev/v1 (an Anthropic Messages
|
|
1252
|
+
// surface also answers at https://modelflare.dev/v1/messages). Live-checked 2026-10-13 through
|
|
1253
|
+
// the proxy: chat, streaming and tool calls succeed on Chat for all four seeded models, and a
|
|
1254
|
+
// real `codex exec` turn completed on glm-5.3-flash. GET /v1/models lists ~77 rows mixing chat,
|
|
1255
|
+
// image and video models, and includes deepseek-v4.1-flash, which answers 503 on every call,
|
|
1256
|
+
// so the preset seeds the verified chat models statically instead of trusting live discovery.
|
|
1257
|
+
// The conventional env reference derived from the id is ${MODELFLARE_API_KEY}.
|
|
1258
|
+
id: "modelflare",
|
|
1259
|
+
label: "Modelflare",
|
|
1260
|
+
adapter: "openai-chat",
|
|
1261
|
+
baseUrl: "https://modelflare.dev/v1",
|
|
1262
|
+
authKind: "key",
|
|
1263
|
+
dashboardUrl: "https://modelflare.dev",
|
|
1264
|
+
preserveCustomDestination: true,
|
|
1265
|
+
defaultModel: "glm-5.3-flash",
|
|
1266
|
+
models: ["glm-5.3-flash", "claude-sonnet-5-5", "gpt-6.1-sol", "deepseek-v4-flash"],
|
|
1267
|
+
note: "OpenAI-compatible relay for Claude, GPT, GLM and DeepSeek chat models. The seeded models were verified with chat, streaming and tool calls; deepseek-v4.1-flash is listed upstream but currently answers 503 and is left out. An ordinary key only reaches its own routing group's models; a Smart key reaches all seeded models. Image and video models are not served through this Chat preset. Upstream latency varies widely (a few seconds to about 30 s) with occasional transient 503s.",
|
|
1268
|
+
},
|
|
1250
1269
|
{
|
|
1251
1270
|
id: "opencode-free",
|
|
1252
1271
|
label: "OpenCode Free",
|
|
@@ -1468,7 +1487,6 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1468
1487
|
models: [...QODER_GLOBAL_MODELS],
|
|
1469
1488
|
liveModels: true,
|
|
1470
1489
|
reasoningEfforts: [...QODER_REASONING_EFFORTS],
|
|
1471
|
-
noVisionModels: [...QODER_GLOBAL_MODELS],
|
|
1472
1490
|
note: "Official Qoder Global CLI using QODER_PERSONAL_ACCESS_TOKEN. Models are discovered per account with `qoder --list-models`; the documented roster is a degraded fallback. The CLI runs single-turn with tools, MCP, settings hooks, and session persistence disabled. Requires `npm install -g @qoder-ai/qodercli`.",
|
|
1473
1491
|
},
|
|
1474
1492
|
{
|
|
@@ -255,6 +255,23 @@ export const META_MUSE_CODE_REASONING_EFFORT_MAP: Record<string, string> = Objec
|
|
|
255
255
|
/** Both Muse Spark 1.3 tiers publish a 1,048,576-token window (dev.meta.ai/docs/models). */
|
|
256
256
|
export const META_MUSE_CONTEXT_WINDOW = 1_048_576;
|
|
257
257
|
export const META_MUSE_MODELS = ["muse-spark-1.3", "muse-spark-1.3-contributor"];
|
|
258
|
+
/*
|
|
259
|
+
* Muse Wonder (preview), announced 2026-10-12 (research.meta.ai/blog/muse-wonder). Per
|
|
260
|
+
* dev.meta.ai/docs/models and dev.meta.ai/docs/pricing-rate-limits, verified 2026-10-13:
|
|
261
|
+
* id muse-wonder-1.0, a 1,048,576-token window, reasoning effort "minimal" through "max",
|
|
262
|
+
* and Standard mode only on the Model API (no Contributor variant). Muse Wonder Full is
|
|
263
|
+
* reachable only inside Muse Code subscriptions and has no Model API id, so it is absent.
|
|
264
|
+
*/
|
|
265
|
+
export const META_MUSE_WONDER_MODEL = "muse-wonder-1.0";
|
|
266
|
+
/** Roster both direct Meta providers serve: the Spark 1.3 tiers plus Muse Wonder. */
|
|
267
|
+
export const META_MODEL_API_MODELS = [...META_MUSE_MODELS, META_MUSE_WONDER_MODEL];
|
|
268
|
+
/** Model API ladders: Spark stops at xhigh; Wonder documents minimal..max. */
|
|
269
|
+
export const META_MODEL_API_REASONING_EFFORTS: Record<string, string[]> = Object.fromEntries(
|
|
270
|
+
META_MODEL_API_MODELS.map(id => [id, id === META_MUSE_WONDER_MODEL ? META_MUSE_CODE_REASONING_EFFORTS : META_MUSE_REASONING_EFFORTS]),
|
|
271
|
+
);
|
|
272
|
+
export const META_MODEL_API_REASONING_EFFORT_MAPS: Record<string, Record<string, string>> = Object.fromEntries(
|
|
273
|
+
META_MODEL_API_MODELS.map(id => [id, id === META_MUSE_WONDER_MODEL ? META_MUSE_CODE_REASONING_EFFORT_MAP : META_MUSE_REASONING_EFFORT_MAP]),
|
|
274
|
+
);
|
|
258
275
|
/**
|
|
259
276
|
* Daybreak program aliases. These `-latest` ids are the stable contract: OpenAI repoints
|
|
260
277
|
* them at newer snapshots over time (red -> gpt-5.6-cyber, blue -> gpt-5.6-sol as of
|
|
@@ -33,6 +33,12 @@ export const REQUEST_PACING_CONCURRENCY_RETRY_AFTER_SECONDS = 5;
|
|
|
33
33
|
let maxQueueDepth = REQUEST_PACING_MAX_QUEUE_DEPTH;
|
|
34
34
|
let maxQueueAgeMs = REQUEST_PACING_MAX_QUEUE_AGE_MS;
|
|
35
35
|
|
|
36
|
+
// Optional owners register internal response markers; only this owned body rewrap carries them.
|
|
37
|
+
const responseProvenance = new Set<WeakMap<Response, unknown>>();
|
|
38
|
+
export function registerProviderResponseProvenance(markers: WeakMap<Response, unknown>): void {
|
|
39
|
+
responseProvenance.add(markers);
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
export type RequestPacingQueueOverloadReason = "queue_full" | "queue_expired";
|
|
37
43
|
|
|
38
44
|
export class RequestPacingQueueOverloadError extends Error {
|
|
@@ -588,6 +594,9 @@ export function trackProviderRequestSlotBody(
|
|
|
588
594
|
// Retry helpers mark the response they return from, and recovery decisions key on these
|
|
589
595
|
// identity markers; the rewrap must not make a non-replayable response look replayable.
|
|
590
596
|
if (isNonReplayableResponse(response)) markResponseNonReplayable(wrapped);
|
|
597
|
+
for (const markers of responseProvenance) {
|
|
598
|
+
if (markers.has(response)) markers.set(wrapped, markers.get(response));
|
|
599
|
+
}
|
|
591
600
|
return carryReplayRefusal(response, wrapped);
|
|
592
601
|
}
|
|
593
602
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop a saved `noVisionModels` list that is still, byte for byte, a retired registry seed.
|
|
3
|
+
*
|
|
4
|
+
* `enrichProviderFromRegistry` fills `noVisionModels` all-or-nothing, so a provider row saved
|
|
5
|
+
* while the registry seeded a blanket list keeps that list after the registry stops seeding it.
|
|
6
|
+
* Qoder Global seeded its whole static discovery roster as text-only until the Global adapter
|
|
7
|
+
* began forwarding image input to the Qoder CLI (#6906). The saved copy would keep every image
|
|
8
|
+
* on the vision sidecar path, stripped or captioned before the adapter could forward it.
|
|
9
|
+
*
|
|
10
|
+
* This projection removes one thing: a saved list identical, in order, to a seed this file names,
|
|
11
|
+
* on a row the registry still owns (same adapter, and the registry transport rule enrichment uses),
|
|
12
|
+
* and only while the registry no longer seeds the list at all. Any edited list does not match and
|
|
13
|
+
* is left alone. `modelCapabilities` and `modelInputModalities` are untouched; they are where a
|
|
14
|
+
* deliberate per-model text-only declaration belongs.
|
|
15
|
+
*/
|
|
16
|
+
import { PROVIDER_REGISTRY, providerMatchesRegistryTransport } from "./registry";
|
|
17
|
+
import type { OcxConfig } from "../types";
|
|
18
|
+
|
|
19
|
+
export interface StaleNoVisionSeed {
|
|
20
|
+
/** Registry provider id whose saved rows may carry the retired list. */
|
|
21
|
+
provider: string;
|
|
22
|
+
/** Every list the registry ever seeded, in seed order; only an exact match is removed. */
|
|
23
|
+
seeds: readonly (readonly string[])[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StaleNoVisionSeedProjection {
|
|
27
|
+
config: OcxConfig;
|
|
28
|
+
changed: boolean;
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const STALE_NO_VISION_SEEDS: readonly StaleNoVisionSeed[] = [
|
|
33
|
+
{
|
|
34
|
+
provider: "qoder",
|
|
35
|
+
seeds: [
|
|
36
|
+
// 2026-09-03 (Qoder Global PAT provider) until the DeepSeek V4.1 transition (#4282).
|
|
37
|
+
["Qwen3.8-Max", "Qwen3.7-Max", "Qwen3.7-Plus", "Kimi-K3", "Kimi-K2.7-Code", "GLM-5.3", "GLM-5.2", "DeepSeek-V4-Pro"],
|
|
38
|
+
// #4282 until Global image input (#6906).
|
|
39
|
+
["Qwen3.8-Max", "Qwen3.7-Max", "Qwen3.7-Plus", "Kimi-K3", "Kimi-K2.7-Code", "GLM-5.3", "GLM-5.2"],
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function sameList(current: readonly unknown[], expected: readonly string[]): boolean {
|
|
45
|
+
return current.length === expected.length && current.every((value, index) => value === expected[index]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Pure projection. The caller decides whether to persist. */
|
|
49
|
+
export function projectStaleNoVisionSeeds(
|
|
50
|
+
config: OcxConfig,
|
|
51
|
+
entries: readonly StaleNoVisionSeed[] = STALE_NO_VISION_SEEDS,
|
|
52
|
+
): StaleNoVisionSeedProjection {
|
|
53
|
+
const warnings: string[] = [];
|
|
54
|
+
let changed = false;
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const prov = config.providers?.[entry.provider];
|
|
57
|
+
if (!prov || !Array.isArray(prov.noVisionModels)) continue;
|
|
58
|
+
const registry = PROVIDER_REGISTRY.find(row => row.id === entry.provider);
|
|
59
|
+
// While the registry still seeds a list, the saved one is not stale by this file's rule.
|
|
60
|
+
if (!registry || registry.noVisionModels !== undefined || registry.adapter !== prov.adapter) continue;
|
|
61
|
+
if (!providerMatchesRegistryTransport(entry.provider, prov)) continue;
|
|
62
|
+
const saved = prov.noVisionModels;
|
|
63
|
+
if (!entry.seeds.some(seed => sameList(saved, seed))) continue;
|
|
64
|
+
delete prov.noVisionModels;
|
|
65
|
+
changed = true;
|
|
66
|
+
warnings.push(
|
|
67
|
+
`removed the retired registry noVisionModels seed (${saved.length} model(s)) from "${entry.provider}"; `
|
|
68
|
+
+ "image input now follows the provider's current classification.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return { config, changed, warnings };
|
|
72
|
+
}
|