@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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/AGENTS_INSTALL.md +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -786,6 +786,58 @@ function catalogStatusFromProcesses(
|
|
|
786
786
|
return { state: stale ? "stale" : "fresh", processes: withStarts, catalogMtimeMs };
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
+
interface ComputedCodexAppServerCatalogStatus {
|
|
790
|
+
status: CodexAppServerCatalogStatus;
|
|
791
|
+
processes: CodexAppServerProcess[];
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Compute one catalog-state observation while retaining the command lines from the
|
|
796
|
+
* same process enumeration. The public collector deliberately exposes only the
|
|
797
|
+
* identity and timestamp projection; post-write warning code also needs the matched
|
|
798
|
+
* process records, and re-enumerating there creates a race between classification and
|
|
799
|
+
* reporting (as well as a second expensive Windows CIM walk).
|
|
800
|
+
*/
|
|
801
|
+
function computeCodexAppServerCatalogStatus(
|
|
802
|
+
io: CodexAppServerProcessIo,
|
|
803
|
+
): ComputedCodexAppServerCatalogStatus {
|
|
804
|
+
const platform = io.platform ?? process.platform;
|
|
805
|
+
const getuid = io.getuid ?? (() => {
|
|
806
|
+
try {
|
|
807
|
+
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
808
|
+
} catch {
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
let snapshots: ProcessSnapshot[];
|
|
813
|
+
let enumerationFailed = false;
|
|
814
|
+
const enumerate = io.listSnapshots ?? (() => defaultListSnapshots(platform, getuid));
|
|
815
|
+
try {
|
|
816
|
+
snapshots = enumerate();
|
|
817
|
+
} catch {
|
|
818
|
+
// A failed process read is unknown, never proof that nothing is running.
|
|
819
|
+
snapshots = [];
|
|
820
|
+
enumerationFailed = true;
|
|
821
|
+
}
|
|
822
|
+
const processes = codexAppServerProcessesFromSnapshots(snapshots);
|
|
823
|
+
if (processes.length === 0) {
|
|
824
|
+
return {
|
|
825
|
+
processes,
|
|
826
|
+
status: enumerationFailed
|
|
827
|
+
? { state: "unknown", processes: [], catalogMtimeMs: null }
|
|
828
|
+
: { state: "not_running", processes: [], catalogMtimeMs: null },
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
const catalogMtimeMs = (io.catalogMtimeMs ?? defaultCatalogMtimeMs)();
|
|
832
|
+
const starts = io.readStartMs
|
|
833
|
+
? new Map(processes.map(proc => [proc.pid, io.readStartMs!(proc.pid)] as const))
|
|
834
|
+
: readProcessStartMsBatch(processes.map(proc => proc.pid), platform);
|
|
835
|
+
return {
|
|
836
|
+
processes,
|
|
837
|
+
status: catalogStatusFromProcesses(processes, catalogMtimeMs, starts),
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
789
841
|
// Short TTL: process listing + stat run once per window even under per-turn
|
|
790
842
|
// guidance calls (#857).
|
|
791
843
|
let catalogStateCache: { atMs: number; status: CodexAppServerCatalogStatus } | null = null;
|
|
@@ -889,41 +941,7 @@ export function collectCodexAppServerCatalogState(
|
|
|
889
941
|
&& now - catalogStateCache.atMs < catalogStateTtlMs(catalogStateCache.status.state)) {
|
|
890
942
|
return catalogStateCache.status;
|
|
891
943
|
}
|
|
892
|
-
const
|
|
893
|
-
const platform = io.platform ?? process.platform;
|
|
894
|
-
const getuid = io.getuid ?? (() => {
|
|
895
|
-
try {
|
|
896
|
-
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
897
|
-
} catch {
|
|
898
|
-
return undefined;
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
let snapshots: ProcessSnapshot[];
|
|
902
|
-
let enumerationFailed = false;
|
|
903
|
-
const enumerate = io.listSnapshots ?? (() => defaultListSnapshots(platform, getuid));
|
|
904
|
-
try {
|
|
905
|
-
snapshots = enumerate();
|
|
906
|
-
} catch {
|
|
907
|
-
// Enumeration failure must never read as "nothing running" — that would let
|
|
908
|
-
// positive model guidance through on guesswork (#857). The injected seam gets
|
|
909
|
-
// the same contract as the default path: whoever enumerates, a failure to read
|
|
910
|
-
// the process list is unknown, not an empty machine.
|
|
911
|
-
snapshots = [];
|
|
912
|
-
enumerationFailed = true;
|
|
913
|
-
}
|
|
914
|
-
const processes = codexAppServerProcessesFromSnapshots(snapshots);
|
|
915
|
-
if (processes.length === 0) {
|
|
916
|
-
return enumerationFailed
|
|
917
|
-
? { state: "unknown", processes: [], catalogMtimeMs: null }
|
|
918
|
-
: { state: "not_running", processes: [], catalogMtimeMs: null };
|
|
919
|
-
}
|
|
920
|
-
const catalogMtimeMs = (io.catalogMtimeMs ?? defaultCatalogMtimeMs)();
|
|
921
|
-
const starts = io.readStartMs
|
|
922
|
-
? new Map(processes.map(proc => [proc.pid, io.readStartMs!(proc.pid)] as const))
|
|
923
|
-
: readProcessStartMsBatch(processes.map(proc => proc.pid), platform);
|
|
924
|
-
return catalogStatusFromProcesses(processes, catalogMtimeMs, starts);
|
|
925
|
-
};
|
|
926
|
-
const status = compute();
|
|
944
|
+
const status = computeCodexAppServerCatalogStatus(io).status;
|
|
927
945
|
if (fullyDefault) {
|
|
928
946
|
catalogStateCache = { atMs: now, status };
|
|
929
947
|
}
|
|
@@ -1228,16 +1246,30 @@ export function afterCatalogWriteHandleAppServers(
|
|
|
1228
1246
|
options: AfterCatalogWriteAppServerOptions,
|
|
1229
1247
|
): AfterCatalogWriteAppServerResult {
|
|
1230
1248
|
const excluded = new Set(options.excludePids ?? []);
|
|
1249
|
+
const hint = STALE_CODEX_APP_SERVER_HINT;
|
|
1250
|
+
if (!options.restart) {
|
|
1251
|
+
// A running process is not necessarily stale. Classify the exact process
|
|
1252
|
+
// enumeration that supplies the warning, and stay quiet when freshness cannot be
|
|
1253
|
+
// established rather than presenting an unknown observation as a known mismatch.
|
|
1254
|
+
const observed = computeCodexAppServerCatalogStatus(options.io ?? {});
|
|
1255
|
+
const starts = new Map(observed.status.processes.map(process => [process.pid, process.startedAtMs]));
|
|
1256
|
+
const catalogMtimeMs = observed.status.catalogMtimeMs;
|
|
1257
|
+
const processes = observed.processes.filter(process => !excluded.has(process.pid));
|
|
1258
|
+
const staleProcesses = catalogMtimeMs === null
|
|
1259
|
+
? []
|
|
1260
|
+
: processes.filter(process => {
|
|
1261
|
+
const startedAtMs = starts.get(process.pid);
|
|
1262
|
+
return startedAtMs !== null && startedAtMs !== undefined && startedAtMs <= catalogMtimeMs;
|
|
1263
|
+
});
|
|
1264
|
+
if (staleProcesses.length === 0) return { processes, warned: false, hint };
|
|
1265
|
+
options.log?.error(formatStaleCodexAppServerWarning(staleProcesses));
|
|
1266
|
+
return { processes: staleProcesses, warned: true, hint };
|
|
1267
|
+
}
|
|
1231
1268
|
const processes = listCodexAppServerProcesses(options.io)
|
|
1232
1269
|
.filter(process => !excluded.has(process.pid));
|
|
1233
|
-
const hint = STALE_CODEX_APP_SERVER_HINT;
|
|
1234
1270
|
if (processes.length === 0) {
|
|
1235
1271
|
return { processes, warned: false, hint };
|
|
1236
1272
|
}
|
|
1237
|
-
if (!options.restart) {
|
|
1238
|
-
options.log?.error(formatStaleCodexAppServerWarning(processes));
|
|
1239
|
-
return { processes, warned: true, hint };
|
|
1240
|
-
}
|
|
1241
1273
|
options.log?.log(
|
|
1242
1274
|
`Stopping Codex app-server process(es): ${processes.map(process => process.pid).join(", ")} `
|
|
1243
1275
|
+ "(active turns may be interrupted).",
|
|
@@ -82,9 +82,9 @@ export function mainQuotaWithCarriedResetCredits(
|
|
|
82
82
|
/**
|
|
83
83
|
* Why an account needs the operator. `missing_credential`, `refresh_failed`, and
|
|
84
84
|
* `quota_unauthorized` are the three causes this surface tells apart on its own. `unauthorized`
|
|
85
|
-
* and `forbidden`
|
|
86
|
-
* `
|
|
87
|
-
*
|
|
85
|
+
* and `forbidden` come from the shared health projection: `projectCodexAccountHealth` maps a
|
|
86
|
+
* stored verification failure's `http_status:401`/`http_status:403` to them, so the union has
|
|
87
|
+
* to accept every reason the projection can emit rather than silently dropping one.
|
|
88
88
|
*/
|
|
89
89
|
export type CodexAccountReauthReason =
|
|
90
90
|
| "missing_credential"
|
|
@@ -105,18 +105,26 @@ export function poolAccountDto(
|
|
|
105
105
|
const plan = codexPlanValue(account.plan);
|
|
106
106
|
const quota = quotaForPlan(quotaResult.quota, plan);
|
|
107
107
|
const runtimeReauth = isAccountNeedsReauth(account.id);
|
|
108
|
+
const rawReauthReason: CodexAccountReauthReason | undefined = !hasCredential
|
|
109
|
+
? "missing_credential"
|
|
110
|
+
: quotaResult.reauthReason;
|
|
108
111
|
const needsReauth = !hasCredential || quotaResult.needsReauth || runtimeReauth;
|
|
109
|
-
|
|
112
|
+
// An in-memory reauth mark carries no cause of its own, so it must not name one: passing
|
|
113
|
+
// a caller reason here would outrank the stored verdict's http_status inside the
|
|
114
|
+
// projection and hide unauthorized/forbidden until the mark is gone. With no caller
|
|
115
|
+
// reason the projection falls back to the persisted cause, then to refresh_failed.
|
|
116
|
+
const healthReason = rawReauthReason === "quota_unauthorized" || rawReauthReason === "missing_credential"
|
|
117
|
+
? "unauthorized"
|
|
118
|
+
: rawReauthReason;
|
|
119
|
+
const health = projectCodexAccountHealth({
|
|
120
|
+
accountId: account.id,
|
|
121
|
+
needsReauth,
|
|
122
|
+
reauthReason: needsReauth ? healthReason : undefined,
|
|
123
|
+
});
|
|
110
124
|
// `needsReauth` is an OR of three independent causes plus a persisted verdict resolved inside the
|
|
111
125
|
// health projection. Emitting only the boolean is what left #4212's reporter guessing which
|
|
112
126
|
// account took their model away and why, so name the cause they actually have to act on.
|
|
113
|
-
const reauthReason: CodexAccountReauthReason | undefined =
|
|
114
|
-
? "missing_credential"
|
|
115
|
-
: runtimeReauth
|
|
116
|
-
? "refresh_failed"
|
|
117
|
-
: quotaResult.needsReauth
|
|
118
|
-
? "quota_unauthorized"
|
|
119
|
-
: health.status === "reauth_required" ? health.reason : undefined;
|
|
127
|
+
const reauthReason: CodexAccountReauthReason | undefined = rawReauthReason ?? (health.status === "reauth_required" ? health.reason : undefined);
|
|
120
128
|
return {
|
|
121
129
|
id: account.id,
|
|
122
130
|
email: projectEmail(account.email, maskEmails) ?? account.email,
|
|
@@ -226,9 +226,12 @@ export async function handleCodexAuthLoginStart(req: Request, config: OcxConfig,
|
|
|
226
226
|
// machine. Opening it on the hub host is useless at best, and on a
|
|
227
227
|
// headless host it fails. `deviceCode` is the same signal the generic
|
|
228
228
|
// OAuth login route uses to make this decision.
|
|
229
|
+
// Reported to the caller rather than discarded (#5261): a login whose browser never opened
|
|
230
|
+
// is indistinguishable from one that did, so it reads as success while nothing happens.
|
|
231
|
+
let browserLaunch: "started" | "failed" | "skipped" = "skipped";
|
|
229
232
|
if (result.url && !result.deviceCode && shouldOpenBrowserForLogin(body.openBrowser, runtimeConfig)) {
|
|
230
233
|
const { openUrl } = await import("../../lib/open-url");
|
|
231
|
-
openUrl(result.url);
|
|
234
|
+
browserLaunch = (await openUrl(result.url)).status === "started" ? "started" : "failed";
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
(async () => {
|
|
@@ -488,6 +491,8 @@ export async function handleCodexAuthLoginStart(req: Request, config: OcxConfig,
|
|
|
488
491
|
flowId,
|
|
489
492
|
url: result.url,
|
|
490
493
|
instructions: result.instructions,
|
|
494
|
+
// Never fatal: the URL is still a valid thing to open by hand, and the flow stays live.
|
|
495
|
+
browserLaunch,
|
|
491
496
|
// Dropped before #3366: every device-code surface renders this field,
|
|
492
497
|
// so withholding it left the GUI and CLI with no code to show.
|
|
493
498
|
...(result.deviceCode ? { deviceCode: result.deviceCode } : {}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { capturePoolQuotaWriter, getValidCodexToken, isCodexAccountGenerationLive, forceRefreshCodexPoolToken, markCodexAccountValidated, markCodexAccountValidationFailed, readCodexAccountRecord, CodexCredentialGenerationConflictError, CodexCredentialRefreshLockTimeoutError, CodexCredentialRefreshBusyError, CodexCredentialRefreshStaleError, TokenRefreshError } from "../account-store";
|
|
1
|
+
import { capturePoolQuotaWriter, getValidCodexToken, isCodexAccountGenerationLive, forceRefreshCodexPoolToken, markCodexAccountValidated, markCodexAccountValidationFailed, readCodexAccountRecord, isTerminalCodexPoolRefreshFailure, CodexCredentialGenerationConflictError, CodexCredentialRefreshLockTimeoutError, CodexCredentialRefreshBusyError, CodexCredentialRefreshStaleError, TokenRefreshError } from "../account-store";
|
|
2
2
|
import type { PoolQuotaWriter } from "../quota-types";
|
|
3
3
|
import { isValidWhamHistoryObservation, getAccountQuota, isCompleteCodexQuotaRecoverySnapshot, parseUsageQuota, setAccountQuotaFromParsed } from "../quota";
|
|
4
4
|
import type { StoredAccountQuota, WhamUsageResponse } from "../quota";
|
|
@@ -46,6 +46,8 @@ export interface PoolQuotaResult {
|
|
|
46
46
|
resetRefreshLineage?: ManualResetRefreshLineage;
|
|
47
47
|
quota: StoredAccountQuota | null;
|
|
48
48
|
needsReauth: boolean;
|
|
49
|
+
/** Failure source observed while obtaining the quota, when reauthentication is required. */
|
|
50
|
+
reauthReason?: "refresh_failed" | "quota_unauthorized";
|
|
49
51
|
/** Credential generation whose cache or network result this DTO state belongs to. */
|
|
50
52
|
credentialGeneration?: number;
|
|
51
53
|
/** Present only when this call freshly parsed a WHAM usage response. */
|
|
@@ -178,7 +180,7 @@ export async function recoverPoolQuotaFrom401(ctx: {
|
|
|
178
180
|
// the credential it condemned, so a late terminal response arriving after the operator
|
|
179
181
|
// re-authenticated would quarantine the replacement.
|
|
180
182
|
markAccountNeedsReauth(accountId, captureConfigGeneration(), rejectedGeneration);
|
|
181
|
-
return { quota: existing ?? null, needsReauth: true, credentialGeneration: rejectedGeneration };
|
|
183
|
+
return { quota: existing ?? null, needsReauth: true, reauthReason: "quota_unauthorized", credentialGeneration: rejectedGeneration };
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
const claim = claimQuotaRecovery(accountId, rejectedGeneration);
|
|
@@ -186,7 +188,7 @@ export async function recoverPoolQuotaFrom401(ctx: {
|
|
|
186
188
|
// A lineage fenced by a TERMINAL refresh failure stays terminal. Without this, the
|
|
187
189
|
// budget being used would make the next bare 401 report a dead credential as healthy.
|
|
188
190
|
if (quotaRecoveryTerminalFor(accountId, rejectedGeneration)) {
|
|
189
|
-
return { quota: existing ?? null, needsReauth: true, credentialGeneration: rejectedGeneration };
|
|
191
|
+
return { quota: existing ?? null, needsReauth: true, reauthReason: "refresh_failed", credentialGeneration: rejectedGeneration };
|
|
190
192
|
}
|
|
191
193
|
// Otherwise: this lineage spent its attempt, another caller is mid-refresh, or a
|
|
192
194
|
// transient failure is backing off. Report transient and let the next poll try —
|
|
@@ -225,8 +227,15 @@ export async function recoverPoolQuotaFrom401(ctx: {
|
|
|
225
227
|
// A refresh that failed terminally is the one case where the credential really is gone.
|
|
226
228
|
// Everything else is unknown, and unknown is not proof.
|
|
227
229
|
if (e instanceof TokenRefreshError && isTerminalRefreshError(e)) {
|
|
230
|
+
// Persist the same verdict the token guardian writes: the in-memory mark dies with
|
|
231
|
+
// this process, and only the stored terminal failure keeps a cached listing from
|
|
232
|
+
// calling the dead grant healthy after a restart.
|
|
233
|
+
markCodexAccountValidationFailed(accountId, `refresh_${e.reason}`, {
|
|
234
|
+
expectedGeneration: rejectedGeneration,
|
|
235
|
+
terminal: true,
|
|
236
|
+
});
|
|
228
237
|
markAccountNeedsReauth(accountId, captureConfigGeneration(), rejectedGeneration);
|
|
229
|
-
return { quota: existing ?? null, needsReauth: true, credentialGeneration: rejectedGeneration };
|
|
238
|
+
return { quota: existing ?? null, needsReauth: true, reauthReason: "refresh_failed", credentialGeneration: rejectedGeneration };
|
|
230
239
|
}
|
|
231
240
|
return { quota: existing ?? null, needsReauth: false, credentialGeneration: rejectedGeneration };
|
|
232
241
|
}
|
|
@@ -258,7 +267,7 @@ export async function recoverPoolQuotaFrom401(ctx: {
|
|
|
258
267
|
// let the next poll call a dead credential healthy. The evidence is about the
|
|
259
268
|
// REFRESHED credential, which is what the replay used.
|
|
260
269
|
markAccountNeedsReauth(accountId, writerGeneration, refreshed.generation);
|
|
261
|
-
return { quota: existing ?? null, needsReauth: true, credentialGeneration: refreshed.generation };
|
|
270
|
+
return { quota: existing ?? null, needsReauth: true, reauthReason: "quota_unauthorized", credentialGeneration: refreshed.generation };
|
|
262
271
|
}
|
|
263
272
|
return { quota: existing ?? null, needsReauth: false, credentialGeneration: refreshed.generation };
|
|
264
273
|
}
|
|
@@ -399,9 +408,23 @@ export async function fetchFreshPoolAccountQuota(
|
|
|
399
408
|
quotaProbeSkipped: true,
|
|
400
409
|
}, quotaProbeEvidence);
|
|
401
410
|
}
|
|
402
|
-
|
|
411
|
+
// Terminal means the grant itself is dead or missing; an `unknown` refresh failure (a
|
|
412
|
+
// token-endpoint 5xx, a transport blip) may clear, so it reports transient like the
|
|
413
|
+
// 401-recovery path instead of quarantining a healthy account (#2887). A revoked or
|
|
414
|
+
// expired grant is also written to the record as a terminal validation failure, the same
|
|
415
|
+
// verdict the token guardian persists: the in-memory mark dies with this process, and
|
|
416
|
+
// the stored verdict is what keeps a cached listing from calling the dead grant healthy
|
|
417
|
+
// after a restart.
|
|
418
|
+
if (isTerminalCodexPoolRefreshFailure(e)) {
|
|
419
|
+
if (e instanceof TokenRefreshError && isTerminalRefreshError(e)) {
|
|
420
|
+
markCodexAccountValidationFailed(accountId, `refresh_${e.reason}`, {
|
|
421
|
+
expectedGeneration: requestCredentialGeneration,
|
|
422
|
+
terminal: true,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
markAccountNeedsReauth(accountId, captureConfigGeneration(), requestCredentialGeneration);
|
|
403
426
|
return withQuotaProbeEvidence(
|
|
404
|
-
{ quota: existing ?? null, needsReauth: true, credentialGeneration: requestCredentialGeneration },
|
|
427
|
+
{ quota: existing ?? null, needsReauth: true, reauthReason: "refresh_failed", credentialGeneration: requestCredentialGeneration },
|
|
405
428
|
quotaProbeEvidence,
|
|
406
429
|
);
|
|
407
430
|
}
|
|
@@ -154,6 +154,34 @@ export function startupHealthSummary(health: StartupHealth): string {
|
|
|
154
154
|
return action ? `${summary}; ${action}` : summary;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* What to say at the end of setup, once routing is on disk and the autostart choice is made.
|
|
159
|
+
*
|
|
160
|
+
* #5261: applying the Codex integration does not install a background service, and on Windows
|
|
161
|
+
* the scheduled task that a separate install would create is logon-triggered rather than
|
|
162
|
+
* boot-triggered. So "routing written, nothing listening" is an ordinary state after a restart
|
|
163
|
+
* rather than a corruption — and the user is never told, because setup ends on a success line.
|
|
164
|
+
*
|
|
165
|
+
* A boot trigger is not the missing piece and would be a false reassurance. The task runs as
|
|
166
|
+
* the interactive user, so before logon there is no session for it to run in; making it truly
|
|
167
|
+
* pre-logon means a different principal and a different service backend, not another trigger.
|
|
168
|
+
* What is actually missing is that nobody says the dependency exists, which is cheap to fix and
|
|
169
|
+
* true on every platform.
|
|
170
|
+
*
|
|
171
|
+
* Reuses the existing health model rather than re-deriving the condition, so this cannot drift
|
|
172
|
+
* from what `ocx status` and `ocx doctor` report about the same install.
|
|
173
|
+
*/
|
|
174
|
+
export function injectedRoutingRestartWarningLines(health: StartupHealth): string[] {
|
|
175
|
+
if (health.status !== "at-risk") return [];
|
|
176
|
+
return [
|
|
177
|
+
// Deliberately not "nothing will restart the proxy": a healthy launcher shim does restart it,
|
|
178
|
+
// for CLI launches only, and is still at-risk. The summary line below says which case this is.
|
|
179
|
+
"⚠️ Codex routing is written to disk and survives a restart; keeping the proxy running is a separate matter.",
|
|
180
|
+
` ${startupHealthSummary(health)}`,
|
|
181
|
+
" While the proxy is down Codex cannot sign in or reach a model. 'ocx restore' undoes the routing without needing it.",
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
|
|
157
185
|
function classifyStartupHealthSummary(health: StartupHealth): string {
|
|
158
186
|
if (health.status === "native") return health.routingKind === "custom-remote"
|
|
159
187
|
? "custom remote Codex routing (no local restart dependency)"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CODEX_REASONING_LEVELS } from "../../reasoning-effort";
|
|
1
|
+
import { CODEX_REASONING_LEVELS, type CodexReasoningLevel } from "../../reasoning-effort";
|
|
2
2
|
import { clearModelCache } from "../model-cache";
|
|
3
3
|
import { routedSlug, slugEquivalenceKey } from "../../providers/slug-codec";
|
|
4
4
|
import { COMBO_NAMESPACE } from "../../combos";
|
|
@@ -872,7 +872,7 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
872
872
|
// (luna: no ultra) stay intact.
|
|
873
873
|
if (!freshCustomEntries.has(m) && !exactCombo && !reserveProjection && !String(e.slug ?? "").startsWith("opencode-go/")) {
|
|
874
874
|
const levels = Array.isArray(e.supported_reasoning_levels)
|
|
875
|
-
? e.supported_reasoning_levels as Array<
|
|
875
|
+
? e.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
876
876
|
: [];
|
|
877
877
|
if (levels.length > 0
|
|
878
878
|
&& !suppressedSyntheticMaxSlugs.has(String(e.slug ?? ""))
|
|
@@ -8,7 +8,7 @@ import { clearModelCache, DEFAULT_MODEL_CACHE_TTL_MS, getFreshCached, getStaleCa
|
|
|
8
8
|
import { buildModelsRequest, resolveModelsAuthToken } from "../../oauth";
|
|
9
9
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
10
10
|
import { modelInList } from "../../types";
|
|
11
|
-
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
11
|
+
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts, type CodexReasoningLevel } from "../../reasoning-effort";
|
|
12
12
|
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
13
13
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
14
14
|
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
@@ -286,7 +286,7 @@ export function isGpt56NativeSlug(slug: string): boolean {
|
|
|
286
286
|
|
|
287
287
|
export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
288
288
|
const levels = Array.isArray(entry.supported_reasoning_levels)
|
|
289
|
-
? entry.supported_reasoning_levels as Array<
|
|
289
|
+
? entry.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
290
290
|
: [];
|
|
291
291
|
const out = [...levels];
|
|
292
292
|
// max is a real native rung on the 5.6 family — always restored; ultra always advertised.
|
|
@@ -300,7 +300,7 @@ export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
|
300
300
|
|
|
301
301
|
export function ensureUltraReasoningLevel(entry: RawEntry): void {
|
|
302
302
|
const levels = Array.isArray(entry.supported_reasoning_levels)
|
|
303
|
-
? entry.supported_reasoning_levels as Array<
|
|
303
|
+
? entry.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
304
304
|
: [];
|
|
305
305
|
if (levels.length === 0) return;
|
|
306
306
|
const wanted = ["max", "ultra"];
|
|
@@ -100,8 +100,9 @@ import type {
|
|
|
100
100
|
CatalogSourceEvidence,
|
|
101
101
|
CatalogTrustedOpenAiApiPolicySnapshot,
|
|
102
102
|
} from "../convergence-types";
|
|
103
|
-
import {
|
|
103
|
+
import { modelCapabilities, modelInputModalities } from "./model-hints";
|
|
104
104
|
import { configuredComboTargetModelsByProvider } from "./combo-member";
|
|
105
|
+
import { resolveModelPolicy } from "../../providers/resolved-model-policy";
|
|
105
106
|
|
|
106
107
|
/** Concurrent gatherRoutedModels callers with the same catalog identity share one live discovery.
|
|
107
108
|
* Keyed by gatherFlightKey so a different config cannot join or evict the wrong flight. */
|
|
@@ -344,8 +345,26 @@ export function captureProviderGather(
|
|
|
344
345
|
): CapturedProviderGather {
|
|
345
346
|
const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured));
|
|
346
347
|
enrichProviderFromRegistry(name, enriched);
|
|
347
|
-
applyRegistryCapabilitySeedFill(name, enriched);
|
|
348
348
|
const registryTransportMatch = providerMatchesRegistryTransport(name, enriched);
|
|
349
|
+
const registryEntry = registryTransportMatch ? getProviderRegistryEntry(name) : undefined;
|
|
350
|
+
const staticProvider = resolveModelPolicy({
|
|
351
|
+
providerName: name,
|
|
352
|
+
modelId: enriched.defaultModel ?? "__catalog_capture__",
|
|
353
|
+
provider: enriched,
|
|
354
|
+
registryEntry,
|
|
355
|
+
transportMatchedRegistry: registryTransportMatch,
|
|
356
|
+
...(enriched.authMode ? { effectiveAuth: { authMode: enriched.authMode } } : {}),
|
|
357
|
+
}).provider;
|
|
358
|
+
for (const key of [
|
|
359
|
+
"modelContextWindows", "modelInputModalities", "modelMaxInputTokens", "modelMaxOutputTokens",
|
|
360
|
+
"modelReasoningEfforts", "modelSupportsReasoningSummaries",
|
|
361
|
+
"modelSupportsVerbosity", "modelSupportsServiceTier",
|
|
362
|
+
] as const) {
|
|
363
|
+
if (staticProvider[key] !== undefined) enriched[key] = detachedClone(staticProvider[key]) as never;
|
|
364
|
+
}
|
|
365
|
+
// A present modelDefaultReasoningEfforts object is a whole-map catalog authority, including
|
|
366
|
+
// explicit {}. enrichProviderFromRegistry already preserves/fills that contract; replacing it
|
|
367
|
+
// with the resolver's per-key registry fill would turn "no configured default" into a seed default.
|
|
349
368
|
const provider = recursivelyFreeze(enriched);
|
|
350
369
|
const fastPolicyAuthority = captureFastPolicyAuthority(
|
|
351
370
|
name,
|
|
@@ -90,6 +90,7 @@ import type { CatalogModel } from "./parsing";
|
|
|
90
90
|
import { disabledNativeSlugs, hasComboTargets, hasNativeOpenAiCapabilityMetadata, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeOpenAiCapabilityDisplayName, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiMaxOutputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
91
91
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
92
92
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
93
|
+
import { clampObservedModelLimits, resolveModelPolicy } from "../../providers/resolved-model-policy";
|
|
93
94
|
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
94
95
|
import type {
|
|
95
96
|
CatalogAdmissionSnapshot,
|
|
@@ -246,22 +247,6 @@ export function configuredReasoningSummarySupport(prov: OcxProviderConfig | unde
|
|
|
246
247
|
return modelRecordValue(prov.modelReasoningSummaryDelivery, id) !== undefined ? true : undefined;
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
function configuredVerbositySupport(name: string, prov: OcxProviderConfig | undefined, id: string): boolean | undefined {
|
|
250
|
-
const explicit = prov ? modelRecordValue(prov.modelSupportsVerbosity, id) : undefined;
|
|
251
|
-
if (explicit !== undefined) return explicit;
|
|
252
|
-
if (!prov) return undefined;
|
|
253
|
-
void name;
|
|
254
|
-
// Provider-wide fallback for ids the per-model map does not enumerate — a live-discovered
|
|
255
|
-
// model would otherwise re-advertise a control the upstream accepts and ignores.
|
|
256
|
-
//
|
|
257
|
-
// Read from the PROVIDER CONFIG, never from PROVIDER_REGISTRY. A gather flight captures its
|
|
258
|
-
// registry authority up front and forbids any later registry read, so consulting the registry
|
|
259
|
-
// here made a custom-destination flight fall back to "configured" instead of serving its own
|
|
260
|
-
// discovery result (tests/codex-integration/codex-gather-authority.test.ts). `applyVerbosityDefaults` in
|
|
261
|
-
// providers/derive.ts materializes the registry default into the config at seed/enrich time.
|
|
262
|
-
return prov.supportsVerbosity;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
250
|
export function applyProviderConfigHints(
|
|
266
251
|
name: string,
|
|
267
252
|
prov: OcxProviderConfig,
|
|
@@ -270,6 +255,15 @@ export function applyProviderConfigHints(
|
|
|
270
255
|
metadataModelIdCaseFold?: boolean,
|
|
271
256
|
effectiveAlias?: string | null,
|
|
272
257
|
): CatalogModel {
|
|
258
|
+
const staticPolicy = resolveModelPolicy({
|
|
259
|
+
providerName: name,
|
|
260
|
+
modelId: model.id,
|
|
261
|
+
provider: prov,
|
|
262
|
+
transportMatchedRegistry: false,
|
|
263
|
+
modelCapabilities: prov.modelCapabilities?.[model.id],
|
|
264
|
+
...(prov.authMode ? { effectiveAuth: { authMode: prov.authMode } } : {}),
|
|
265
|
+
...(effectiveAlias !== undefined ? { effectiveAlias } : {}),
|
|
266
|
+
});
|
|
273
267
|
const displayName = configuredModelDisplayName(prov, model.id);
|
|
274
268
|
// The alias decision is resolved once at flight admission (captureProviderGather) and threaded
|
|
275
269
|
// through as `effectiveAlias`. Re-deriving it here would read PROVIDER_REGISTRY after admission,
|
|
@@ -279,11 +273,15 @@ export function applyProviderConfigHints(
|
|
|
279
273
|
const providerAlias = typeof effectiveAlias === "string" || effectiveAlias === null
|
|
280
274
|
? effectiveAlias
|
|
281
275
|
: model.providerAlias;
|
|
282
|
-
const configuredCap = configuredContextWindow(prov, model.id);
|
|
283
|
-
const configuredMaxInput =
|
|
276
|
+
const configuredCap = staticPolicy.model.contextWindow ?? configuredContextWindow(prov, model.id);
|
|
277
|
+
const configuredMaxInput = staticPolicy.model.maxInputTokens;
|
|
284
278
|
const maxOutputTokens = routedMaxOutputTokens(name, prov, model, model.id, metadataModelIdCaseFold);
|
|
285
279
|
const configuredAutoCompact = configuredAutoCompactTokenLimit(prov, model.id);
|
|
286
|
-
|
|
280
|
+
// The resolver owns exact capability precedence and legacy exact/colon-family/case-fold fallback.
|
|
281
|
+
// Removing an exact capability row therefore returns this projection to legacy-map inference.
|
|
282
|
+
let inputModalities = staticPolicy.model.inputModalities
|
|
283
|
+
? [...staticPolicy.model.inputModalities]
|
|
284
|
+
: undefined;
|
|
287
285
|
// The shared vision-sidecar consumer predicate keeps catalog advertisement and request-time
|
|
288
286
|
// planning aligned. The catalog must still advertise image input — the Codex app
|
|
289
287
|
// gates attachments client-side on input_modalities, and a text-only entry would block images
|
|
@@ -297,10 +295,12 @@ export function applyProviderConfigHints(
|
|
|
297
295
|
}
|
|
298
296
|
const reasoningEfforts = configuredReasoningEfforts(prov, model.id);
|
|
299
297
|
const suppressSyntheticMax = modelRecordValue(prov.modelSuppressSyntheticMax, model.id) === true;
|
|
300
|
-
const defaultReasoningEffort =
|
|
301
|
-
const supportsReasoningSummaries =
|
|
302
|
-
const supportsVerbosity =
|
|
298
|
+
const defaultReasoningEffort = staticPolicy.model.defaultReasoningEffort ?? model.defaultReasoningEffort;
|
|
299
|
+
const supportsReasoningSummaries = staticPolicy.model.supportsReasoningSummaries;
|
|
300
|
+
const supportsVerbosity = staticPolicy.model.supportsVerbosity;
|
|
303
301
|
const fastPolicy = fastPolicyForModel(prov, model.id, name);
|
|
302
|
+
// Frozen gather providers retain the captured Fast authority. That late/captured eligibility
|
|
303
|
+
// owns catalog publication, including an explicit provider-level false.
|
|
304
304
|
const supportsServiceTier = serviceTierSupportFromPolicy(fastPolicy);
|
|
305
305
|
const {
|
|
306
306
|
supportsServiceTier: _staleServiceTier,
|
|
@@ -313,9 +313,12 @@ export function applyProviderConfigHints(
|
|
|
313
313
|
const discoveredWindow = typeof model.contextWindow === "number" && model.contextWindow > 0
|
|
314
314
|
? model.contextWindow
|
|
315
315
|
: undefined;
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
const projectedLimits = clampObservedModelLimits(staticPolicy.model, {
|
|
317
|
+
...(discoveredWindow !== undefined ? { contextWindow: discoveredWindow } : {}),
|
|
318
|
+
...(typeof model.maxInputTokens === "number" && model.maxInputTokens > 0 ? { maxInputTokens: model.maxInputTokens } : {}),
|
|
319
|
+
});
|
|
320
|
+
const hintedWindow = projectedLimits.contextWindow
|
|
321
|
+
?? (providerCap !== undefined ? resolveUnknownRoutedContextWindow(providerCap) : undefined);
|
|
319
322
|
const hinted = {
|
|
320
323
|
...modelWithoutServiceTier,
|
|
321
324
|
...(displayName !== undefined ? { displayName } : {}),
|
|
@@ -326,9 +329,7 @@ export function applyProviderConfigHints(
|
|
|
326
329
|
...(suppressSyntheticMax ? { suppressSyntheticMax: true } : {}),
|
|
327
330
|
...(configuredMaxInput !== undefined
|
|
328
331
|
? {
|
|
329
|
-
maxInputTokens:
|
|
330
|
-
? Math.min(model.maxInputTokens, configuredMaxInput)
|
|
331
|
-
: configuredMaxInput,
|
|
332
|
+
maxInputTokens: projectedLimits.maxInputTokens ?? configuredMaxInput,
|
|
332
333
|
}
|
|
333
334
|
: {}),
|
|
334
335
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
@@ -803,6 +803,13 @@ export function normalizeRoutedCatalogEntry(
|
|
|
803
803
|
delete entry.multi_agent_reasoning_effort;
|
|
804
804
|
delete entry.use_responses_lite;
|
|
805
805
|
delete entry.supports_websockets;
|
|
806
|
+
// Routed rows cloned from native templates must not inherit OpenAI-only experimental context
|
|
807
|
+
// delivery. Codex reads the flag as "this model accepts experimental context history" and drives
|
|
808
|
+
// its context-management cadence from it, so a third-party provider that never negotiated it gets
|
|
809
|
+
// a compact-after-every-step loop instead (observed on a routed DeepSeek row: 1,600+ compactions
|
|
810
|
+
// in a single thread). Nothing re-applies the field from provider metadata during this step, so
|
|
811
|
+
// the row leaves this normalization without it.
|
|
812
|
+
delete entry.supports_experimental_context;
|
|
806
813
|
/*
|
|
807
814
|
* Tier metadata is stripped from routed rows because a row cloned from a native template
|
|
808
815
|
* would otherwise hand a third-party provider OpenAI's tiers.
|