@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
package/src/oauth/login-cli.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as readline from "node:readline";
|
|
|
2
2
|
import { modelSelectionGuidance } from "../cli/model-selection-guidance";
|
|
3
3
|
import { initializeProviderModelSelection } from "../providers/initial-model-selection";
|
|
4
4
|
import { openUrl } from "../lib/open-url";
|
|
5
|
+
import { createBrowserLaunchReport } from "../lib/browser-launch-notice";
|
|
5
6
|
import { loadConfig, saveConfig } from "../config";
|
|
6
7
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
7
8
|
import {
|
|
@@ -14,6 +15,41 @@ import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
|
14
15
|
import { configuredAdminToken } from "../lib/admin-secrets";
|
|
15
16
|
import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Seams a test drives in place of a browser, a terminal and a real provider. Production passes
|
|
20
|
+
* none of them.
|
|
21
|
+
*
|
|
22
|
+
* They exist because the thing worth proving here is an ORDER — that a browser which did not
|
|
23
|
+
* open is on screen before the question that assumes it did — and an order is only observable
|
|
24
|
+
* from something that records both events. Spawning a launcher and attaching to stdin to find
|
|
25
|
+
* that out would test the operating system instead.
|
|
26
|
+
*/
|
|
27
|
+
export interface LoginCliDeps {
|
|
28
|
+
runLogin?: typeof runLogin;
|
|
29
|
+
openUrl?: typeof openUrl;
|
|
30
|
+
warn?: (message: string) => void;
|
|
31
|
+
/** Ask one question, read one line. Defaults to a readline prompt that owns its own lifetime. */
|
|
32
|
+
ask?: (question: string) => Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run `body` with a line reader, creating and closing a real one only when the caller did not
|
|
37
|
+
* supply its own. A readline interface attaches to stdin, so building one that nothing will ask
|
|
38
|
+
* a question keeps the process alive for no reason.
|
|
39
|
+
*/
|
|
40
|
+
async function withPrompt<T>(
|
|
41
|
+
supplied: ((question: string) => Promise<string>) | undefined,
|
|
42
|
+
body: (ask: (question: string) => Promise<string>) => Promise<T>,
|
|
43
|
+
): Promise<T> {
|
|
44
|
+
if (supplied) return await body(supplied);
|
|
45
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
46
|
+
try {
|
|
47
|
+
return await body(question => new Promise<string>(resolve => rl.question(question, resolve)));
|
|
48
|
+
} finally {
|
|
49
|
+
rl.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
17
53
|
const LIVE_RELOAD_PROVIDERS = new Set<string>([
|
|
18
54
|
...listOAuthProviders(),
|
|
19
55
|
...Object.keys(KEY_LOGIN_PROVIDERS),
|
|
@@ -83,7 +119,7 @@ export function loginUsageMessage(): string {
|
|
|
83
119
|
+ ` API-key login: ${Object.keys(KEY_LOGIN_PROVIDERS).join(", ")}`;
|
|
84
120
|
}
|
|
85
121
|
|
|
86
|
-
export async function handleLogin(provider?: string): Promise<void> {
|
|
122
|
+
export async function handleLogin(provider?: string, deps: LoginCliDeps = {}): Promise<void> {
|
|
87
123
|
const name = (provider ?? "").trim().toLowerCase();
|
|
88
124
|
// A removed provider id reached through its alias still logs in — the merged
|
|
89
125
|
// successor owns the flow. Warn rather than silently reroute so scripts and
|
|
@@ -91,30 +127,40 @@ export async function handleLogin(provider?: string): Promise<void> {
|
|
|
91
127
|
const alias = DEPRECATED_OAUTH_PROVIDER_ALIASES[name];
|
|
92
128
|
if (alias) {
|
|
93
129
|
console.error(`${name} is deprecated; logging in as ${alias}`);
|
|
94
|
-
return handleOAuthLogin(alias);
|
|
130
|
+
return handleOAuthLogin(alias, deps);
|
|
95
131
|
}
|
|
96
|
-
if (isPublicOAuthProvider(name)) return handleOAuthLogin(name);
|
|
97
|
-
if (isKeyLoginProvider(name)) return handleKeyLogin(name);
|
|
132
|
+
if (isPublicOAuthProvider(name)) return handleOAuthLogin(name, deps);
|
|
133
|
+
if (isKeyLoginProvider(name)) return handleKeyLogin(name, deps);
|
|
98
134
|
console.error(loginUsageMessage());
|
|
99
135
|
process.exit(1);
|
|
100
136
|
}
|
|
101
137
|
|
|
102
|
-
async function handleOAuthLogin(name: string): Promise<void> {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
138
|
+
export async function handleOAuthLogin(name: string, deps: LoginCliDeps = {}): Promise<void> {
|
|
139
|
+
const login = deps.runLogin ?? runLogin;
|
|
140
|
+
const launch = deps.openUrl ?? openUrl;
|
|
141
|
+
const browser = createBrowserLaunchReport(deps.warn);
|
|
142
|
+
await withPrompt(deps.ask, async (ask) => {
|
|
143
|
+
await login(name, {
|
|
106
144
|
onAuth: ({ url, instructions }) => {
|
|
107
145
|
console.log(`\n🔐 Opening browser for ${name} login...\n${url}\n`);
|
|
108
146
|
if (instructions) console.log(instructions);
|
|
109
|
-
|
|
147
|
+
// The controller does not await onAuth, so the launcher's answer cannot be reported from
|
|
148
|
+
// here — this returns long before it arrives. It reports itself instead, and the one
|
|
149
|
+
// thing that could collide with it waits below (#5261).
|
|
150
|
+
browser.track(launch(url));
|
|
110
151
|
},
|
|
111
152
|
onProgress: (m) => console.log(` ${m}`),
|
|
112
|
-
onManualCodeInput: () =>
|
|
113
|
-
|
|
153
|
+
onManualCodeInput: async () => {
|
|
154
|
+
// "or wait for browser" is a lie if nothing opened, and a warning printed after readline
|
|
155
|
+
// has drawn the prompt lands on the line the user is typing on.
|
|
156
|
+
await browser.settled();
|
|
157
|
+
return await ask("Paste redirect URL or code (or wait for browser): ");
|
|
158
|
+
},
|
|
114
159
|
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
160
|
+
});
|
|
161
|
+
// A device or polling provider never prompts, so nothing above waited on the launcher. It is
|
|
162
|
+
// still owed an answer before this claims the login worked.
|
|
163
|
+
await browser.settled();
|
|
118
164
|
const reload = await notifyRunningProxyAfterOAuthLogin(name);
|
|
119
165
|
console.log(`\n✅ Logged in to ${name}. Try: ocx sync`);
|
|
120
166
|
for (const line of modelSelectionGuidance(name)) console.log(line);
|
|
@@ -192,8 +238,9 @@ export async function commitKeyLoginProvider(
|
|
|
192
238
|
return mergedProvider;
|
|
193
239
|
}
|
|
194
240
|
|
|
195
|
-
async function handleKeyLogin(name: string): Promise<void> {
|
|
241
|
+
export async function handleKeyLogin(name: string, deps: LoginCliDeps = {}): Promise<void> {
|
|
196
242
|
const def = KEY_LOGIN_PROVIDERS[name];
|
|
243
|
+
const launch = deps.openUrl ?? openUrl;
|
|
197
244
|
const preflightConfig = loadConfig();
|
|
198
245
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(preflightConfig.codexAccountNamespaces, name);
|
|
199
246
|
if (namespaceCollision) {
|
|
@@ -201,21 +248,25 @@ async function handleKeyLogin(name: string): Promise<void> {
|
|
|
201
248
|
process.exit(1);
|
|
202
249
|
}
|
|
203
250
|
console.log(`\n🔑 ${def.label} — opening ${def.dashboardUrl} so you can create/copy an API key...`);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
251
|
+
const browser = createBrowserLaunchReport(deps.warn);
|
|
252
|
+
browser.track(launch(def.dashboardUrl));
|
|
253
|
+
// The next question asks for a key the user gets FROM that page, so a page that never opened
|
|
254
|
+
// has to be on screen before the question rather than underneath it (#5261).
|
|
255
|
+
await browser.settled();
|
|
256
|
+
const { key, baseUrl } = await withPrompt(deps.ask, async (ask) => {
|
|
257
|
+
const entered = (await ask(`Paste your ${def.label} API key: `)).trim();
|
|
258
|
+
// Template URL with placeholders needs resolution before saving.
|
|
259
|
+
let resolvedBaseUrl = def.baseUrl;
|
|
260
|
+
if (/\{[^}]*\}/.test(resolvedBaseUrl)) {
|
|
261
|
+
const resolved = (await ask(`Your endpoint URL (${resolvedBaseUrl}): `)).trim();
|
|
262
|
+
if (!resolved) {
|
|
263
|
+
console.error("A resolved URL is required — replace the {placeholder} with your actual value.");
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
resolvedBaseUrl = resolved;
|
|
215
267
|
}
|
|
216
|
-
baseUrl
|
|
217
|
-
}
|
|
218
|
-
rl.close();
|
|
268
|
+
return { key: entered, baseUrl: resolvedBaseUrl };
|
|
269
|
+
});
|
|
219
270
|
if (!key) {
|
|
220
271
|
console.error("No key entered.");
|
|
221
272
|
process.exit(1);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { parseCallbackInput } from "./callback-server";
|
|
2
|
+
import { retainedUtf8Bytes } from "../lib/admission";
|
|
3
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* In-flight login flow state and the manual paste slot that feeds it.
|
|
7
|
+
*
|
|
8
|
+
* Split out of `index.ts` when that file crossed the repository file-size ratchet at 2009
|
|
9
|
+
* lines. The cut follows a seam rather than a line count: everything here is bookkeeping for
|
|
10
|
+
* a login that has started and not yet settled, and none of it reads or writes a stored
|
|
11
|
+
* credential. `index.ts` re-exports the two public names, so existing importers are unaffected.
|
|
12
|
+
*/
|
|
13
|
+
export const loginState = new Map<string, { error?: string; done: boolean }>();
|
|
14
|
+
export const loginAbort = new Map<string, AbortController>();
|
|
15
|
+
export const kiroLoginSettling = new Set<string>();
|
|
16
|
+
|
|
17
|
+
/** Pending paste for a login in progress: either a waiter or a stashed early submission. */
|
|
18
|
+
export interface ManualCodeSlot {
|
|
19
|
+
pendingInput?: string;
|
|
20
|
+
resolve?: (value: string) => void;
|
|
21
|
+
/** Registered by the callback flow so submits can validate state synchronously. */
|
|
22
|
+
expectedState?: string;
|
|
23
|
+
}
|
|
24
|
+
const loginManual = new Map<string, ManualCodeSlot>();
|
|
25
|
+
const OAUTH_PENDING_CODE_MAX_BYTES = 4 * 1024;
|
|
26
|
+
let lastOAuthFlowReconciledGeneration = 0;
|
|
27
|
+
|
|
28
|
+
export function reconcileOAuthFlowState(context: GenerationContext): number {
|
|
29
|
+
if (context.generation <= lastOAuthFlowReconciledGeneration) return 0;
|
|
30
|
+
let removed = 0;
|
|
31
|
+
for (const [provider, state] of loginState) {
|
|
32
|
+
if (context.providerNames.has(provider) || !state.done || loginAbort.has(provider)) continue;
|
|
33
|
+
if (loginState.delete(provider)) removed += 1;
|
|
34
|
+
if (loginManual.delete(provider)) removed += 1;
|
|
35
|
+
if (loginAbort.delete(provider)) removed += 1;
|
|
36
|
+
}
|
|
37
|
+
lastOAuthFlowReconciledGeneration = context.generation;
|
|
38
|
+
return removed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clearManualCodeSlot(provider: string): void {
|
|
42
|
+
loginManual.delete(provider);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ensureManualCodeSlot(provider: string): ManualCodeSlot {
|
|
46
|
+
let slot = loginManual.get(provider);
|
|
47
|
+
if (!slot) {
|
|
48
|
+
slot = {};
|
|
49
|
+
loginManual.set(provider, slot);
|
|
50
|
+
}
|
|
51
|
+
return slot;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Wait for a GUI/CLI paste of the OAuth redirect URL or code (or return a stashed early submit). */
|
|
55
|
+
export function waitForManualLoginCode(provider: string, signal: AbortSignal, expectedState?: string): Promise<string> {
|
|
56
|
+
if (signal.aborted) {
|
|
57
|
+
return Promise.reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
58
|
+
}
|
|
59
|
+
const slot = ensureManualCodeSlot(provider);
|
|
60
|
+
if (expectedState !== undefined) slot.expectedState = expectedState;
|
|
61
|
+
if (slot.pendingInput !== undefined) {
|
|
62
|
+
const value = slot.pendingInput;
|
|
63
|
+
slot.pendingInput = undefined;
|
|
64
|
+
return Promise.resolve(value);
|
|
65
|
+
}
|
|
66
|
+
return new Promise<string>((resolve, reject) => {
|
|
67
|
+
const onAbort = () => {
|
|
68
|
+
if (slot.resolve === resolve) slot.resolve = undefined;
|
|
69
|
+
reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
70
|
+
};
|
|
71
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
72
|
+
slot.resolve = (value: string) => {
|
|
73
|
+
signal.removeEventListener("abort", onAbort);
|
|
74
|
+
if (slot.resolve === resolve) slot.resolve = undefined;
|
|
75
|
+
resolve(value);
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Feed a pasted redirect URL or authorization code into an in-progress GUI login.
|
|
82
|
+
* Returns ok:false when no login is waiting (or input is empty). Invalid pastes are accepted
|
|
83
|
+
* here and re-prompted by the OAuth callback loop if they cannot be parsed / fail state checks.
|
|
84
|
+
*/
|
|
85
|
+
export function submitManualLoginCode(provider: string, input: string): { ok: true } | { ok: false; error: string } {
|
|
86
|
+
const trimmed = input.trim();
|
|
87
|
+
if (!trimmed) return { ok: false, error: "empty code" };
|
|
88
|
+
if (retainedUtf8Bytes(trimmed) > OAUTH_PENDING_CODE_MAX_BYTES) return { ok: false, error: "code too large" };
|
|
89
|
+
const st = loginState.get(provider);
|
|
90
|
+
if (!st || st.done) return { ok: false, error: "no login in progress" };
|
|
91
|
+
const slot = ensureManualCodeSlot(provider);
|
|
92
|
+
// Synchronous validation (validated request/ack): reject un-parseable input and
|
|
93
|
+
// authorization responses (url/query kind) whose state is missing or mismatched
|
|
94
|
+
// once the flow has registered its expected state. Raw codes stay in-session-PKCE
|
|
95
|
+
// protected — but a raw paste carrying an explicit #state suffix is state-bearing
|
|
96
|
+
// and checked too. Early posts (flow not yet waiting, no expectedState) are
|
|
97
|
+
// stashed and re-validated by the callback loop.
|
|
98
|
+
const parsed = parseCallbackInput(trimmed);
|
|
99
|
+
// Command Code's manual fallback accepts a pasted JSON callback payload
|
|
100
|
+
// (`{ apiKey, state, ... }`) which has no `code` param. Let it through the
|
|
101
|
+
// shared gate so the provider-specific parser can validate it.
|
|
102
|
+
const isCommandCodeJson = provider === "command-code" && trimmed.startsWith("{") && !parsed.code;
|
|
103
|
+
if (!parsed.code && !isCommandCodeJson) return { ok: false, error: "no authorization code found in input" };
|
|
104
|
+
// A raw paste carrying an explicit code#state suffix is state-bearing too: it
|
|
105
|
+
// must match the expected state rather than bypass validation.
|
|
106
|
+
const stateBearing = parsed.kind !== "raw" || parsed.state !== undefined;
|
|
107
|
+
if (stateBearing && slot.expectedState !== undefined) {
|
|
108
|
+
if (parsed.state === undefined) return { ok: false, error: "redirect URL is missing the state parameter" };
|
|
109
|
+
if (parsed.state !== slot.expectedState) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: parsed.kind === "raw"
|
|
113
|
+
? "state mismatch — paste the bare code, or the correct code#state from THIS login attempt"
|
|
114
|
+
: "state mismatch — paste the redirect URL from THIS login attempt",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (slot.resolve) {
|
|
119
|
+
const resolve = slot.resolve;
|
|
120
|
+
slot.resolve = undefined;
|
|
121
|
+
resolve(trimmed);
|
|
122
|
+
} else {
|
|
123
|
+
// Race: GUI may POST before the flow reaches onManualCodeInput — stash for the waiter.
|
|
124
|
+
slot.pendingInput = trimmed;
|
|
125
|
+
}
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-path for provider key material: env references, keychain references, or the literal
|
|
3
|
+
* value.
|
|
4
|
+
*
|
|
5
|
+
* Leaf module on purpose: reasoning-metadata.ts imports resolveProviderApiKey() here so a
|
|
6
|
+
* learned-refusal identity can hash the same credential the wire sends. key-store.ts needs
|
|
7
|
+
* the ../config barrel for the write path (saveConfigPreservingClaudeCode), and importing it
|
|
8
|
+
* from reasoning-metadata would close the cycle the reasoning-metadata header warns about.
|
|
9
|
+
* resolveEnvValue comes from ../config/proxy-env for the same reason.
|
|
10
|
+
*
|
|
11
|
+
* `config.json` keeps only a reference (`keychain:<provider>` for the active key,
|
|
12
|
+
* `keychain:<provider>/<pool id>` for pool entries); the secret lives in the OS credential store
|
|
13
|
+
* under one service name. Reads are synchronous on purpose: `routedProviderConfig` and the
|
|
14
|
+
* quota/compaction/catalog callers are all sync, and `@napi-rs/keyring` ships a sync `Entry`.
|
|
15
|
+
*
|
|
16
|
+
* Policy: a reference that cannot be resolved fails closed (no key) and is warned once per
|
|
17
|
+
* account; nothing ever rewrites plaintext into config or its backups.
|
|
18
|
+
*/
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { resolveEnvValue } from "../config/proxy-env";
|
|
21
|
+
import type { OcxProviderConfig } from "../types";
|
|
22
|
+
|
|
23
|
+
export const KEYCHAIN_REFERENCE_PREFIX = "keychain:";
|
|
24
|
+
export const PROVIDER_KEYCHAIN_SERVICE = "opencodex.provider-api-key.v1";
|
|
25
|
+
|
|
26
|
+
export interface ProviderKeychainEntry {
|
|
27
|
+
getPassword(): string | null;
|
|
28
|
+
setPassword(password: string): void;
|
|
29
|
+
deletePassword(): boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ProviderKeychainEntryFactory = (service: string, account: string) => ProviderKeychainEntry;
|
|
33
|
+
|
|
34
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
35
|
+
|
|
36
|
+
function defaultEntryFactory(service: string, account: string): ProviderKeychainEntry {
|
|
37
|
+
const { Entry } = nodeRequire("@napi-rs/keyring") as { Entry: new (s: string, a: string) => ProviderKeychainEntry };
|
|
38
|
+
return new Entry(service, account);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let entryFactory: ProviderKeychainEntryFactory = defaultEntryFactory;
|
|
42
|
+
const resolvedCache = new Map<string, string>();
|
|
43
|
+
const warnedAccounts = new Set<string>();
|
|
44
|
+
|
|
45
|
+
/** Test seam: swap the OS entry for an in-memory one and drop caches. */
|
|
46
|
+
export function setProviderKeychainEntryFactoryForTests(factory: ProviderKeychainEntryFactory | null): void {
|
|
47
|
+
entryFactory = factory ?? defaultEntryFactory;
|
|
48
|
+
resolvedCache.clear();
|
|
49
|
+
warnedAccounts.clear();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Write-path seam: a store/restore mutated secrets, so cached reads and warnings are stale. */
|
|
53
|
+
export function invalidateResolvedProviderKeyCache(): void {
|
|
54
|
+
resolvedCache.clear();
|
|
55
|
+
warnedAccounts.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isKeychainReference(value: string | undefined): value is string {
|
|
59
|
+
return typeof value === "string" && value.startsWith(KEYCHAIN_REFERENCE_PREFIX) && value.length > KEYCHAIN_REFERENCE_PREFIX.length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function keychainAccount(reference: string): string {
|
|
63
|
+
return reference.slice(KEYCHAIN_REFERENCE_PREFIX.length);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A reference belongs to `name` only when its account is that provider's own active account
|
|
68
|
+
* or one of its pool accounts. `storeProviderKeyInKeychain` writes exactly those two shapes,
|
|
69
|
+
* so anything else in a provider's config names another provider's secret.
|
|
70
|
+
*/
|
|
71
|
+
export function keychainReferenceBelongsToProvider(reference: string, name: string): boolean {
|
|
72
|
+
const account = keychainAccount(reference);
|
|
73
|
+
return account === name || account.startsWith(`${name}/`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Entry for `account` under the provider-key service; read and write paths share the factory. */
|
|
77
|
+
export function providerKeychainEntry(account: string): ProviderKeychainEntry {
|
|
78
|
+
return entryFactory(PROVIDER_KEYCHAIN_SERVICE, account);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readKeychain(account: string): string | undefined {
|
|
82
|
+
const cached = resolvedCache.get(account);
|
|
83
|
+
if (cached !== undefined) return cached;
|
|
84
|
+
try {
|
|
85
|
+
const value = providerKeychainEntry(account).getPassword();
|
|
86
|
+
if (typeof value === "string" && value.trim()) {
|
|
87
|
+
resolvedCache.set(account, value);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// fall through to the single warning below
|
|
92
|
+
}
|
|
93
|
+
if (!warnedAccounts.has(account)) {
|
|
94
|
+
warnedAccounts.add(account);
|
|
95
|
+
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)`);
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Single resolver for provider key material: env references, keychain references, or the
|
|
102
|
+
* literal value. Every request-time read of `apiKey` goes through here.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveProviderApiKey(value: string | undefined): string | undefined {
|
|
105
|
+
if (!value) return undefined;
|
|
106
|
+
if (isKeychainReference(value)) return readKeychain(keychainAccount(value));
|
|
107
|
+
return resolveEnvValue(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type ProviderKeyStoreKind = "keychain" | "env" | "file" | "none";
|
|
111
|
+
|
|
112
|
+
export function providerKeyStoreKind(provider: Pick<OcxProviderConfig, "apiKey"> | undefined): ProviderKeyStoreKind {
|
|
113
|
+
const key = provider?.apiKey;
|
|
114
|
+
if (!key) return "none";
|
|
115
|
+
if (isKeychainReference(key)) return "keychain";
|
|
116
|
+
if (/^\$\{?\w+\}?$/.test(key)) return "env";
|
|
117
|
+
return "file";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Probe the OS keychain with a throwaway account: write, read back, delete. */
|
|
121
|
+
export function probeProviderKeychain(): { available: true } | { available: false; reason: string } {
|
|
122
|
+
const account = `probe-${process.pid}-${Date.now()}`;
|
|
123
|
+
try {
|
|
124
|
+
const entry = providerKeychainEntry(account);
|
|
125
|
+
entry.setPassword("ok");
|
|
126
|
+
const back = entry.getPassword();
|
|
127
|
+
try { entry.deletePassword(); } catch { /* best effort */ }
|
|
128
|
+
if (back !== "ok") return { available: false, reason: "keychain read-back did not match" };
|
|
129
|
+
return { available: true };
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return { available: false, reason: error instanceof Error ? error.message : "keychain unavailable" };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mutatePersistedConfig } from "../config";
|
|
3
3
|
import { publishAccountSelection } from "../lib/account-selection-events";
|
|
4
|
+
import { clearModelCache } from "../codex/model-cache";
|
|
4
5
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
5
6
|
import type { ProviderApiKeySelection } from "../types/provider";
|
|
6
7
|
import { routedProviderConfig } from "../router";
|
|
@@ -100,6 +101,9 @@ export function commitProviderApiKeySelection<T>(
|
|
|
100
101
|
if (outcome.status === "unavailable") return { status: "unavailable" };
|
|
101
102
|
const committed = outcome.value;
|
|
102
103
|
if (committed.status !== "unavailable") config.providers[name] = structuredClone(committed.provider);
|
|
103
|
-
if (committed.status === "committed" && committed.notify)
|
|
104
|
+
if (committed.status === "committed" && committed.notify) {
|
|
105
|
+
clearModelCache(name);
|
|
106
|
+
publishAccountSelection(name, "api-key");
|
|
107
|
+
}
|
|
104
108
|
return committed;
|
|
105
109
|
}
|
package/src/providers/derive.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CodexAccountMode, OcxProviderConfig } from "../types";
|
|
2
2
|
import { cloneFastWire } from "./fastwire";
|
|
3
|
+
import { resolveModelPolicy } from "./resolved-model-policy";
|
|
3
4
|
import {
|
|
4
5
|
PROVIDER_REGISTRY,
|
|
5
6
|
registryEntryForProviderDestination,
|
|
@@ -220,6 +221,14 @@ export function applyDirectReasoningEffortContracts(
|
|
|
220
221
|
*/
|
|
221
222
|
export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderConfig {
|
|
222
223
|
const liveModels = registryEntrySupportsLiveModelDiscovery(entry) ? entry.liveModels : false;
|
|
224
|
+
const staticPolicy = resolveModelPolicy({
|
|
225
|
+
providerName: entry.id,
|
|
226
|
+
modelId: entry.defaultModel ?? entry.models?.[0] ?? "__provider_seed__",
|
|
227
|
+
provider: { adapter: entry.adapter, baseUrl: entry.baseUrl, authMode: entry.authKind },
|
|
228
|
+
registryEntry: entry,
|
|
229
|
+
transportMatchedRegistry: true,
|
|
230
|
+
effectiveAuth: { authMode: entry.authKind },
|
|
231
|
+
}).provider;
|
|
223
232
|
return {
|
|
224
233
|
adapter: entry.adapter,
|
|
225
234
|
baseUrl: entry.baseUrl,
|
|
@@ -239,17 +248,17 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
239
248
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
240
249
|
...(liveModels !== undefined ? { liveModels } : {}),
|
|
241
250
|
...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
|
|
242
|
-
...(
|
|
243
|
-
...(
|
|
244
|
-
...(
|
|
245
|
-
...(
|
|
251
|
+
...(staticPolicy.modelContextWindows ? { modelContextWindows: { ...staticPolicy.modelContextWindows } } : {}),
|
|
252
|
+
...(staticPolicy.modelDisplayNames ? { modelDisplayNames: { ...staticPolicy.modelDisplayNames } } : {}),
|
|
253
|
+
...(staticPolicy.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(staticPolicy.modelInputModalities) } : {}),
|
|
254
|
+
...(staticPolicy.modelMaxInputTokens ? { modelMaxInputTokens: { ...staticPolicy.modelMaxInputTokens } } : {}),
|
|
246
255
|
...(entry.defaultMaxOutputTokens !== undefined ? { defaultMaxOutputTokens: entry.defaultMaxOutputTokens } : {}),
|
|
247
|
-
...(
|
|
256
|
+
...(staticPolicy.modelMaxOutputTokens ? { modelMaxOutputTokens: { ...staticPolicy.modelMaxOutputTokens } } : {}),
|
|
248
257
|
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
249
|
-
...(
|
|
250
|
-
...(
|
|
251
|
-
...(
|
|
252
|
-
...(
|
|
258
|
+
...(staticPolicy.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(staticPolicy.modelReasoningEfforts) } : {}),
|
|
259
|
+
...(staticPolicy.modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts: { ...staticPolicy.modelDefaultReasoningEfforts } } : {}),
|
|
260
|
+
...(staticPolicy.reasoningEffortMap ? { reasoningEffortMap: { ...staticPolicy.reasoningEffortMap } } : {}),
|
|
261
|
+
...(staticPolicy.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(staticPolicy.modelReasoningEffortMap) } : {}),
|
|
253
262
|
...(entry.reasoningWireFormat ? { reasoningWireFormat: entry.reasoningWireFormat } : {}),
|
|
254
263
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
255
264
|
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
@@ -447,13 +456,16 @@ function applyVerbosityDefaults(prov: OcxProviderConfig, entry: ProviderRegistry
|
|
|
447
456
|
* was skipped and the reasoning ladder was advertised without summary support — exactly the
|
|
448
457
|
* inconsistency that makes Codex drop the inbound reasoning object.
|
|
449
458
|
*
|
|
450
|
-
* Deliberately narrow: reasoning-summary and
|
|
459
|
+
* Deliberately narrow: reasoning-summary, effort, and replay-compatibility metadata only, via
|
|
451
460
|
* `registryEntryForProviderDestination`, which matches fixed key destinations and refuses
|
|
452
461
|
* templated or overridable base URLs. A custom row keeps its own identity for everything else.
|
|
453
462
|
*/
|
|
454
463
|
function enrichReasoningMetadataByDestination(prov: OcxProviderConfig): void {
|
|
455
464
|
const destination = registryEntryForProviderDestination(prov);
|
|
456
465
|
applyReasoningSummaryDefaults(prov, destination?.modelSupportsReasoningSummaries);
|
|
466
|
+
if (prov.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined) {
|
|
467
|
+
prov.dropResponsesReasoningItems = destination.dropResponsesReasoningItems;
|
|
468
|
+
}
|
|
457
469
|
if (destination?.modelReasoningEfforts) {
|
|
458
470
|
prov.modelReasoningEfforts = fillRecordOfArrays(destination.modelReasoningEfforts, prov.modelReasoningEfforts);
|
|
459
471
|
}
|
|
@@ -491,6 +503,14 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
491
503
|
modelReasoningEffortMap: prov.modelReasoningEffortMap,
|
|
492
504
|
};
|
|
493
505
|
const seed = providerConfigSeed(entry);
|
|
506
|
+
const resolvedStatic = resolveModelPolicy({
|
|
507
|
+
providerName: name,
|
|
508
|
+
modelId: prov.defaultModel ?? entry.defaultModel ?? "__provider_enrich__",
|
|
509
|
+
provider: prov,
|
|
510
|
+
registryEntry: entry,
|
|
511
|
+
transportMatchedRegistry: true,
|
|
512
|
+
...(prov.authMode ? { effectiveAuth: { authMode: prov.authMode } } : {}),
|
|
513
|
+
}).provider;
|
|
494
514
|
repairStaticModelCatalogProvider(name, prov);
|
|
495
515
|
if (prov.apiKeyTransport === undefined && seed.apiKeyTransport !== undefined) prov.apiKeyTransport = seed.apiKeyTransport;
|
|
496
516
|
if (!prov.defaultModel && seed.defaultModel) prov.defaultModel = seed.defaultModel;
|
|
@@ -504,10 +524,8 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
504
524
|
if (!prov.modelContextWindows && seed.modelContextWindows) prov.modelContextWindows = { ...seed.modelContextWindows };
|
|
505
525
|
// Per-model fill, not all-or-nothing: an operator who renamed ONE model must still receive
|
|
506
526
|
// labels for the rest, and an existing install must pick up newly seeded rows on enrich.
|
|
507
|
-
if (
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
if (seed.modelInputModalities) prov.modelInputModalities = fillRecordOfArrays(seed.modelInputModalities, prov.modelInputModalities);
|
|
527
|
+
if (resolvedStatic.modelDisplayNames) prov.modelDisplayNames = { ...resolvedStatic.modelDisplayNames };
|
|
528
|
+
if (resolvedStatic.modelInputModalities) prov.modelInputModalities = cloneRecordOfArrays(resolvedStatic.modelInputModalities);
|
|
511
529
|
if (prov.defaultMaxOutputTokens === undefined && seed.defaultMaxOutputTokens !== undefined) prov.defaultMaxOutputTokens = seed.defaultMaxOutputTokens;
|
|
512
530
|
if (!prov.modelMaxOutputTokens && seed.modelMaxOutputTokens) prov.modelMaxOutputTokens = { ...seed.modelMaxOutputTokens };
|
|
513
531
|
if ((!prov.reasoningEfforts || hasLegacyClinePassReasoningEfforts(name, prov)) && seed.reasoningEfforts) {
|
|
@@ -518,9 +536,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
518
536
|
// provider. That split the two planes apart — routing merges these maps per key
|
|
519
537
|
// (mergeRecordFill in src/router.ts), so the wire honored the effort while /v1/models and
|
|
520
538
|
// every client export showed no effort control at all.
|
|
521
|
-
if (
|
|
522
|
-
prov.modelReasoningEfforts = fillRecordOfArrays(seed.modelReasoningEfforts, prov.modelReasoningEfforts);
|
|
523
|
-
}
|
|
539
|
+
if (resolvedStatic.modelReasoningEfforts) prov.modelReasoningEfforts = cloneRecordOfArrays(resolvedStatic.modelReasoningEfforts);
|
|
524
540
|
if (!prov.modelDefaultReasoningEfforts && seed.modelDefaultReasoningEfforts) prov.modelDefaultReasoningEfforts = { ...seed.modelDefaultReasoningEfforts };
|
|
525
541
|
if (!prov.reasoningEffortMap && seed.reasoningEffortMap) prov.reasoningEffortMap = { ...seed.reasoningEffortMap };
|
|
526
542
|
if (!prov.modelReasoningEffortMap && seed.modelReasoningEffortMap) prov.modelReasoningEffortMap = cloneNestedRecord(seed.modelReasoningEffortMap);
|
|
@@ -563,6 +579,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
563
579
|
prov.supportsResponsesCustomTools = entry.supportsResponsesCustomTools;
|
|
564
580
|
}
|
|
565
581
|
if (prov.preserveResponsesReasoningContent === undefined && entry.preserveResponsesReasoningContent !== undefined) prov.preserveResponsesReasoningContent = entry.preserveResponsesReasoningContent;
|
|
582
|
+
if (prov.dropResponsesReasoningItems === undefined && entry.dropResponsesReasoningItems !== undefined) prov.dropResponsesReasoningItems = entry.dropResponsesReasoningItems;
|
|
566
583
|
applyReasoningSummaryDefaults(prov, entry.modelSupportsReasoningSummaries);
|
|
567
584
|
applyServiceTierModelDefaults(prov, serviceTierModelDefaultsFor(entry, prov));
|
|
568
585
|
applyVerbosityDefaults(prov, entry);
|
|
@@ -12,14 +12,12 @@
|
|
|
12
12
|
* key: the `devin-cli` registry entry is gone, so the PROVIDER_REGISTRY lookup
|
|
13
13
|
* that gated the rewrite here could never fire again.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* adapter is rewritten to `devin`, whatever the row is called, and each
|
|
22
|
-
* rewrite is reported.
|
|
15
|
+
* Custom-named rows fail closed. The retired ACP transport used the installed
|
|
16
|
+
* CLI's identity and ignored both configured and caller credentials, so
|
|
17
|
+
* silently changing one of those rows into a cloud provider could send a
|
|
18
|
+
* credential with different provenance to Cognition. The warning tells the
|
|
19
|
+
* operator to reconfigure it explicitly; retaining the unknown adapter id
|
|
20
|
+
* prevents requests from reaching any upstream in the meantime.
|
|
23
21
|
*
|
|
24
22
|
* A row carrying the retired ACP identity URL is repointed at the api-server
|
|
25
23
|
* in the same pass. That URL was never a destination — it existed only so
|
|
@@ -55,10 +53,16 @@ export function projectDevinCliAuthMode(config: OcxConfig): DevinCliAuthModeProj
|
|
|
55
53
|
const warnings: string[] = [];
|
|
56
54
|
let changed = false;
|
|
57
55
|
|
|
58
|
-
// Every row, not only the registry id: a custom-named row had no registry pin,
|
|
59
|
-
// so after the ACP removal it is the one that cannot construct an adapter.
|
|
60
56
|
for (const [name, row] of Object.entries(config.providers ?? {})) {
|
|
61
57
|
if (!row || row.adapter !== RETIRED_ACP_ADAPTER) continue;
|
|
58
|
+
if (name !== RETIRED_ACP_ADAPTER) {
|
|
59
|
+
warnings.push(
|
|
60
|
+
`left custom provider "${name}" unchanged because adapter ${RETIRED_ACP_ADAPTER} was removed: `
|
|
61
|
+
+ "the row was not migrated; switch its adapter to devin and configure Devin authentication "
|
|
62
|
+
+ "explicitly before using this provider.",
|
|
63
|
+
);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
62
66
|
row.adapter = "devin";
|
|
63
67
|
changed = true;
|
|
64
68
|
let detail = "";
|