@bitkyc08/opencodex 2.80.0 → 2.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
package/src/oauth/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
|
|
2
2
|
import { initializeProviderModelSelection } from "../providers/initial-model-selection";
|
|
3
3
|
import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
|
|
4
|
-
import {
|
|
4
|
+
import { getDefaultConfig, initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig, observeInitialConfigState, saveConfig } from "../config";
|
|
5
5
|
import { resolveProviderApiKey } from "../providers/key-store";
|
|
6
6
|
import { projectEmail } from "../lib/privacy";
|
|
7
7
|
import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro";
|
|
@@ -9,15 +9,13 @@ import {
|
|
|
9
9
|
OAuthMutationBusyError,
|
|
10
10
|
OAuthRefreshIntentIOError,
|
|
11
11
|
clearOAuthRefreshIntent,
|
|
12
|
-
clearOAuthRefreshIntentIfMatch,
|
|
13
12
|
createOAuthRefreshIntentLock,
|
|
14
13
|
credentialGeneration,
|
|
15
14
|
getAccountCredential,
|
|
16
15
|
getAccountCredentialWithStatus,
|
|
17
16
|
getAccountSet,
|
|
18
17
|
getCredential,
|
|
19
|
-
markAccountNeedsReauthIfGeneration,
|
|
20
|
-
markOAuthRefreshIntentCleanupPending,
|
|
18
|
+
markAccountNeedsReauthIfGeneration, accountNeedsReauthForStatus, markAccountRefreshAttentionIfGeneration,
|
|
21
19
|
markOAuthRefreshIntentStaleOwner,
|
|
22
20
|
mergeAccountCredential,
|
|
23
21
|
normalizeAuthStoreBuffer,
|
|
@@ -29,14 +27,18 @@ import {
|
|
|
29
27
|
writeOAuthRefreshIntent,
|
|
30
28
|
type OAuthCredentialWriteReceipt,
|
|
31
29
|
type OAuthRefreshIntent,
|
|
32
|
-
type OAuthRefreshIntentCleanupPending,
|
|
33
30
|
type AuthStore,
|
|
34
31
|
} from "./store";
|
|
35
32
|
import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenRequestError } from "./xai";
|
|
36
|
-
import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError
|
|
33
|
+
import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError } from "./anthropic";
|
|
34
|
+
import { isAnthropicOAuthInstance, anthropicInstanceRowShapeMatches } from "../providers/anthropic-instance";
|
|
35
|
+
import { captureModelsOAuthTarget, guardModelsOAuthRequest, mayResolveModelsOAuth } from "./model-discovery-auth";
|
|
36
|
+
import { anthropicOAuthDefinition, assertAnthropicInstanceLoginConfig, AnthropicInstanceCollisionError,
|
|
37
|
+
clearAnthropicRefreshIntentBestEffort, clearAnthropicRefreshIntentForKnownFailure,
|
|
38
|
+
resumeAnthropicRefreshIntentCleanup, clearObservedAnthropicRefreshIntent } from "./anthropic-oauth-definitions";
|
|
37
39
|
import { loginKimi, refreshKimiToken } from "./kimi";
|
|
38
40
|
import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous";
|
|
39
|
-
import { loginChatGPT, refreshChatGPTToken, type ChatGPTLoginFlow } from "./chatgpt";
|
|
41
|
+
import { ChatGptTokenRequestError, ChatGptTokenError, loginChatGPT, refreshChatGPTToken, type ChatGPTLoginFlow } from "./chatgpt";
|
|
40
42
|
import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
|
|
41
43
|
import { loginCursor, refreshCursorToken } from "./cursor";
|
|
42
44
|
import { loginZed, refreshZedToken } from "./zed";
|
|
@@ -83,8 +85,9 @@ export { OAUTH_REFRESH_LOCK_WAIT_MS, peekAuthStore, peekOAuthRefreshIntent } fro
|
|
|
83
85
|
import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
|
|
84
86
|
|
|
85
87
|
import { REFRESH_SKEW_MS } from "./refresh-policy";
|
|
86
|
-
import {
|
|
88
|
+
import { newerClaudeCredentialForInstance, captureAnthropicCredentialOwnerForInstance } from "./anthropic-continuity";
|
|
87
89
|
import { mergeAnthropicIdentity, type AnthropicIdentityResolver } from "./anthropic-identity";
|
|
90
|
+
import { AnthropicCrossInstanceDuplicateError, AnthropicLocalCliImportError, assertAnthropicCredentialSource } from "./store-anthropic-instance";
|
|
88
91
|
export interface OAuthAccessSnapshot {
|
|
89
92
|
provider: string;
|
|
90
93
|
accountId: string;
|
|
@@ -195,7 +198,7 @@ export interface LoginFlowLifecycle {
|
|
|
195
198
|
onSettled?: () => void | Promise<void>;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
interface OAuthProviderDef {
|
|
201
|
+
export interface OAuthProviderDef {
|
|
199
202
|
login(ctrl: OAuthController, opts?: LoginOpts, providerConfig?: OcxProviderConfig): Promise<OAuthCredentials>;
|
|
200
203
|
refresh(
|
|
201
204
|
refreshToken: string,
|
|
@@ -266,15 +269,8 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
266
269
|
providerConfig: oauthConfig("xai"),
|
|
267
270
|
defaultModel: oauthDefaultModel("xai"),
|
|
268
271
|
},
|
|
269
|
-
anthropic:
|
|
270
|
-
|
|
271
|
-
refresh: refreshAnthropicToken,
|
|
272
|
-
providerConfig: oauthConfig("anthropic"),
|
|
273
|
-
defaultModel: oauthDefaultModel("anthropic"),
|
|
274
|
-
// Anthropic actively server-side-blocks subscription OAuth outside its own clients (Feb 2026).
|
|
275
|
-
// Never generate background refresh traffic for it — grade 20, highest ToS risk.
|
|
276
|
-
defaultRefreshPolicy: "disabled",
|
|
277
|
-
},
|
|
272
|
+
anthropic: anthropicOAuthDefinition("anthropic"),
|
|
273
|
+
anthropic2: anthropicOAuthDefinition("anthropic2"),
|
|
278
274
|
kimi: {
|
|
279
275
|
login: (ctrl) => loginKimi(ctrl),
|
|
280
276
|
refresh: refreshKimiToken,
|
|
@@ -354,7 +350,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
354
350
|
},
|
|
355
351
|
chatgpt: {
|
|
356
352
|
login: (ctrl, opts) => loginChatGPT(ctrl, { forceLogin: opts?.forceLogin, flow: opts?.flow }),
|
|
357
|
-
refresh: (rt) => refreshChatGPTToken(rt),
|
|
353
|
+
refresh: (rt, signal) => refreshChatGPTToken(rt, { signal }),
|
|
358
354
|
providerConfig: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex", authMode: "forward" as const },
|
|
359
355
|
defaultModel: "gpt-5.6-luna",
|
|
360
356
|
},
|
|
@@ -446,8 +442,10 @@ export class OAuthAccountPausedError extends Error {
|
|
|
446
442
|
}
|
|
447
443
|
|
|
448
444
|
export class OAuthProviderPublicationError extends Error {
|
|
449
|
-
constructor() {
|
|
450
|
-
super("OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login."
|
|
445
|
+
constructor(provider?: string, credentialWritten = true) {
|
|
446
|
+
super(provider !== "anthropic2" ? "OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login."
|
|
447
|
+
: credentialWritten ? "Anthropic Pool 2 credential was saved as an orphan auth row, but the provider entry was not written. Repair the persisted config or resolve the provider/account namespace collision, then retry login."
|
|
448
|
+
: "Anthropic Pool 2 login did not start because the config file is missing or could not be written; no credential was saved. Repair the persisted config, then retry login.");
|
|
451
449
|
this.name = "OAuthProviderPublicationError";
|
|
452
450
|
}
|
|
453
451
|
}
|
|
@@ -490,6 +488,9 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
|
490
488
|
|| error instanceof OAuthReauthIdentityUnverifiedError
|
|
491
489
|
|| error instanceof OAuthTokenRefreshBusyError
|
|
492
490
|
|| error instanceof OAuthTokenRefreshStaleError
|
|
491
|
+
|| error instanceof AnthropicInstanceCollisionError
|
|
492
|
+
|| error instanceof AnthropicCrossInstanceDuplicateError
|
|
493
|
+
|| error instanceof AnthropicLocalCliImportError
|
|
493
494
|
) return error.message;
|
|
494
495
|
return "OAuth authentication failed. Check the OpenCodex account status and retry.";
|
|
495
496
|
}
|
|
@@ -711,10 +712,14 @@ function isTerminalRefreshError(err: unknown): boolean {
|
|
|
711
712
|
|| msg.includes("expired_token");
|
|
712
713
|
}
|
|
713
714
|
function terminal(error:unknown):boolean{
|
|
715
|
+
if (error instanceof ChatGptTokenRequestError) return false;
|
|
714
716
|
if(error instanceof XaiTokenRequestError)return ["invalid_grant","refresh_token_reused","revoked_token"].includes(error.oauthError??"");
|
|
715
717
|
if(error instanceof AnthropicTokenError)return (error.httpStatus===400||error.httpStatus===401)&&["invalid_grant","refresh_token_reused","revoked","revoked_token","refresh_token_revoked"].includes(error.oauthError??"");
|
|
716
718
|
if(error instanceof KiroTokenRefreshError)return (error.httpStatus===400||error.httpStatus===401)&&error.oauthError!==undefined;
|
|
717
719
|
if(error instanceof NousTokenError)return error.terminal===true||["invalid_grant","refresh_token_reused","revoked","revoked_token","expired_token"].includes(error.oauthError??"");
|
|
720
|
+
// ChatGPT carries the pool classifier's verdict on the error itself: revoked and
|
|
721
|
+
// expired grants are terminal; unknown/transient failures stay retryable.
|
|
722
|
+
if(error instanceof ChatGptTokenError)return error.terminal;
|
|
718
723
|
// Local durable-write/read/cleanup failures are operational, not credential
|
|
719
724
|
// death: the provider credential was never rejected or consumed. Never mark
|
|
720
725
|
// the account needsReauth for broken local persistence infrastructure.
|
|
@@ -739,131 +744,6 @@ function definitivelyAnswered(error: unknown): boolean {
|
|
|
739
744
|
return false;
|
|
740
745
|
}
|
|
741
746
|
|
|
742
|
-
/**
|
|
743
|
-
* Intent cleanup is secondary to the refresh outcome it protects.
|
|
744
|
-
*
|
|
745
|
-
* Once a credential is already durable, cleanup remains secondary and best-effort. A known
|
|
746
|
-
* failed attempt takes the stricter path below: its retry-safe marker must become durable before
|
|
747
|
-
* the original provider error can be returned.
|
|
748
|
-
*/
|
|
749
|
-
function clearAnthropicRefreshIntentBestEffort(
|
|
750
|
-
provider: string,
|
|
751
|
-
accountId: string,
|
|
752
|
-
expected: OAuthRefreshIntent,
|
|
753
|
-
): boolean {
|
|
754
|
-
try {
|
|
755
|
-
return expected.attemptId
|
|
756
|
-
? clearOAuthRefreshIntentIfMatch(provider, accountId, expected)
|
|
757
|
-
: clearOAuthRefreshIntent(provider, accountId, expected.generation);
|
|
758
|
-
} catch {
|
|
759
|
-
console.warn(
|
|
760
|
-
"[opencodex] Anthropic refresh intent cleanup failed; preserving the durable replay guard.",
|
|
761
|
-
);
|
|
762
|
-
return false;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS = [10, 25, 50] as const;
|
|
767
|
-
|
|
768
|
-
function isConfigMutationLockContention(error: unknown): boolean {
|
|
769
|
-
if (!(error instanceof ConfigMutationLockError)) return false;
|
|
770
|
-
const cause = error.cause;
|
|
771
|
-
const code = cause && typeof cause === "object" && "code" in cause
|
|
772
|
-
? String((cause as { code?: unknown }).code)
|
|
773
|
-
: "";
|
|
774
|
-
return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED";
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
async function clearAnthropicRefreshIntentForKnownFailure(
|
|
778
|
-
provider: string,
|
|
779
|
-
accountId: string,
|
|
780
|
-
expected: OAuthRefreshIntent,
|
|
781
|
-
cleanupPending: OAuthRefreshIntentCleanupPending,
|
|
782
|
-
refreshError: unknown,
|
|
783
|
-
): Promise<boolean> {
|
|
784
|
-
let marked: OAuthRefreshIntent | undefined;
|
|
785
|
-
for (let attempt = 0; attempt <= ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
786
|
-
try {
|
|
787
|
-
marked = markOAuthRefreshIntentCleanupPending(
|
|
788
|
-
provider,
|
|
789
|
-
accountId,
|
|
790
|
-
expected,
|
|
791
|
-
cleanupPending,
|
|
792
|
-
);
|
|
793
|
-
break;
|
|
794
|
-
} catch (cause) {
|
|
795
|
-
const retryDelay = ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS[attempt];
|
|
796
|
-
if (!isConfigMutationLockContention(cause) || retryDelay === undefined) {
|
|
797
|
-
throw new OAuthRefreshIntentIOError(
|
|
798
|
-
"mark-cleanup-pending",
|
|
799
|
-
cause,
|
|
800
|
-
refreshError,
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
// The provider has definitively answered, so caller cancellation no longer changes the
|
|
804
|
-
// settlement obligation. Yield briefly while retaining the per-account refresh lock, then
|
|
805
|
-
// rerun the existing compare-and-swap marker against current disk state.
|
|
806
|
-
await Bun.sleep(retryDelay);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (!marked) {
|
|
810
|
-
throw new OAuthRefreshIntentIOError(
|
|
811
|
-
"mark-cleanup-pending",
|
|
812
|
-
new Error("Anthropic refresh intent changed before safe cleanup"),
|
|
813
|
-
refreshError,
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
let cleared: boolean;
|
|
818
|
-
try {
|
|
819
|
-
cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, marked);
|
|
820
|
-
} catch {
|
|
821
|
-
console.warn(
|
|
822
|
-
"[opencodex] Anthropic refresh intent cleanup failed; retry-safe cleanup remains pending.",
|
|
823
|
-
);
|
|
824
|
-
return false;
|
|
825
|
-
}
|
|
826
|
-
if (!cleared) {
|
|
827
|
-
throw new OAuthRefreshIntentIOError(
|
|
828
|
-
"clear-cleanup-pending",
|
|
829
|
-
new Error("Anthropic refresh intent changed during safe cleanup"),
|
|
830
|
-
refreshError,
|
|
831
|
-
);
|
|
832
|
-
}
|
|
833
|
-
return true;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
function resumeAnthropicRefreshIntentCleanup(
|
|
837
|
-
provider: string,
|
|
838
|
-
accountId: string,
|
|
839
|
-
pendingIntent: OAuthRefreshIntent,
|
|
840
|
-
): void {
|
|
841
|
-
let cleared: boolean;
|
|
842
|
-
try {
|
|
843
|
-
cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent);
|
|
844
|
-
} catch (cause) {
|
|
845
|
-
throw new OAuthRefreshIntentIOError(
|
|
846
|
-
"resume-cleanup",
|
|
847
|
-
cause,
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
if (!cleared) {
|
|
851
|
-
throw new OAuthRefreshIntentIOError(
|
|
852
|
-
"resume-cleanup",
|
|
853
|
-
new Error("Pending Anthropic refresh intent changed before cleanup"),
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function clearObservedAnthropicRefreshIntent(
|
|
859
|
-
provider: string,
|
|
860
|
-
accountId: string,
|
|
861
|
-
pendingIntent: OAuthRefreshIntent,
|
|
862
|
-
): boolean {
|
|
863
|
-
return pendingIntent.attemptId
|
|
864
|
-
? clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent)
|
|
865
|
-
: clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation);
|
|
866
|
-
}
|
|
867
747
|
function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;}
|
|
868
748
|
function merged(fresh: OAuthCredentials, previous: OAuthCredentials): OAuthCredentials {
|
|
869
749
|
return {
|
|
@@ -932,6 +812,7 @@ export async function refreshAnthropicAccountWithLock(
|
|
|
932
812
|
callerCredential: OAuthCredentials,
|
|
933
813
|
deps: AnthropicRefreshDeps = {},
|
|
934
814
|
): Promise<string> {
|
|
815
|
+
if (!isAnthropicOAuthInstance(provider)) throw new UnsupportedOAuthProviderError(provider);
|
|
935
816
|
const writerGeneration = captureConfigGeneration();
|
|
936
817
|
const now = deps.now ?? Date.now;
|
|
937
818
|
const guard = await (deps.intentLock ?? createOAuthRefreshIntentLock(provider, accountId)).acquire();
|
|
@@ -941,10 +822,10 @@ export async function refreshAnthropicAccountWithLock(
|
|
|
941
822
|
if (!accountSet || !account) throw new OAuthLoginRequiredError(provider);
|
|
942
823
|
if (account.paused) throw new OAuthAccountPausedError();
|
|
943
824
|
const stored = account.credential;
|
|
944
|
-
const owns =
|
|
825
|
+
const owns = captureAnthropicCredentialOwnerForInstance(provider, accountSet, accountId);
|
|
945
826
|
const generation = credentialGeneration(stored);
|
|
946
827
|
let pendingIntent = readOAuthRefreshIntent(provider, accountId);
|
|
947
|
-
const observed = await
|
|
828
|
+
const observed = await newerClaudeCredentialForInstance(provider, stored, now(), deps.signal, deps.resolveIdentity);
|
|
948
829
|
const assertOwner = (store: AuthStore, checkDisk = true) => {
|
|
949
830
|
if (observed.kind !== "absent" && deps.signal?.aborted) throw new OAuthTokenRefreshStaleError();
|
|
950
831
|
if (!owns(store, checkDisk && observed.kind !== "absent" ? observed.diskGeneration : undefined)) {
|
|
@@ -1116,6 +997,10 @@ export async function refreshGenericAccountWithLock(
|
|
|
1116
997
|
return fresh.access;
|
|
1117
998
|
} catch (error) {
|
|
1118
999
|
if (error instanceof OAuthMutationBusyError) throw error;
|
|
1000
|
+
if (provider === "kiro" && error instanceof KiroTokenRefreshError && error.refreshAttention) {
|
|
1001
|
+
await markAccountRefreshAttentionIfGeneration(provider, accountId, generation, writerGeneration);
|
|
1002
|
+
throw error;
|
|
1003
|
+
}
|
|
1119
1004
|
if (!terminal(error)) throw error;
|
|
1120
1005
|
// Nous-specific failure-atomicity: a terminal refresh error that carries
|
|
1121
1006
|
// an already-issued rotated refresh token (e.g. the access JWT lacked the
|
|
@@ -1177,20 +1062,22 @@ async function refreshAndPersistAccessToken(
|
|
|
1177
1062
|
replacedStaleFlight?: OAuthRefreshFlightEvidence,
|
|
1178
1063
|
): Promise<string> {
|
|
1179
1064
|
if (provider === "xai") return refreshXaiAccountWithLock(provider, accountId, def, cred, { signal });
|
|
1180
|
-
if (provider
|
|
1065
|
+
if (isAnthropicOAuthInstance(provider)) return refreshAnthropicAccountWithLock(provider, accountId, def, cred, { signal, flight, replacedStaleFlight });
|
|
1181
1066
|
return refreshGenericAccountWithLock(provider, accountId, def, cred, { signal });
|
|
1182
1067
|
}
|
|
1183
1068
|
|
|
1184
|
-
/**
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1069
|
+
/** Resolve discovery credentials only for the caller-owned configured OAuth row. */
|
|
1070
|
+
export async function getModelsOAuthAccessSnapshot(name: string, prov: OcxProviderConfig | undefined): Promise<OAuthAccessSnapshot> {
|
|
1071
|
+
if (!mayResolveModelsOAuth(name, prov)) throw new OAuthLoginRequiredError(name);
|
|
1072
|
+
return getValidAccessTokenSnapshot(name);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/** Shared /models auth resolver; custom key rows keep their own key, including omitted authMode. */
|
|
1189
1076
|
export async function resolveModelsAuthToken(name: string, prov: OcxProviderConfig): Promise<string | undefined> {
|
|
1190
1077
|
if (prov.authMode === "forward") return undefined;
|
|
1191
1078
|
if (prov.authMode === "oauth") {
|
|
1192
1079
|
try {
|
|
1193
|
-
return await
|
|
1080
|
+
return (await getModelsOAuthAccessSnapshot(name, prov)).accessToken;
|
|
1194
1081
|
} catch {
|
|
1195
1082
|
return undefined;
|
|
1196
1083
|
}
|
|
@@ -1235,6 +1122,8 @@ export function buildModelsRequest(
|
|
|
1235
1122
|
providerName = "",
|
|
1236
1123
|
observedAuth?: ModelsRequestObservedAuth,
|
|
1237
1124
|
): { method?: "POST"; url: string; headers: Record<string, string> } {
|
|
1125
|
+
const authorizedTarget = captureModelsOAuthTarget(providerName, prov);
|
|
1126
|
+
if (prov.authMode === "oauth" && !mayResolveModelsOAuth(providerName, prov)) apiKey = undefined;
|
|
1238
1127
|
const transportSeed = modelDiscoveryTransportSeed(providerName, prov);
|
|
1239
1128
|
const copilotApiBaseUrl = observedAuth === undefined
|
|
1240
1129
|
? (providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(providerName) : undefined)
|
|
@@ -1261,16 +1150,18 @@ export function buildModelsRequest(
|
|
|
1261
1150
|
effectiveProvider.baseUrl,
|
|
1262
1151
|
defaultUrl,
|
|
1263
1152
|
);
|
|
1153
|
+
const guarded = <T extends { url: string; headers: Record<string, string> }>(request: T): T =>
|
|
1154
|
+
guardModelsOAuthRequest(providerName, prov, request, authorizedTarget);
|
|
1264
1155
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
|
|
1265
1156
|
headers.Accept = "application/json";
|
|
1266
1157
|
headers["Content-Type"] = "application/json";
|
|
1267
1158
|
headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
|
|
1268
1159
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
1269
|
-
return {
|
|
1270
|
-
method: "POST",
|
|
1160
|
+
return guarded({
|
|
1161
|
+
method: "POST" as const,
|
|
1271
1162
|
url: discoveryUrl(`${effectiveProvider.baseUrl.replace(/\/+$/, "")}/v1internal:fetchAvailableModels`),
|
|
1272
1163
|
headers,
|
|
1273
|
-
};
|
|
1164
|
+
});
|
|
1274
1165
|
}
|
|
1275
1166
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "ai-studio") {
|
|
1276
1167
|
// Generative Language API: API key goes in x-goog-api-key (never Authorization: Bearer),
|
|
@@ -1278,7 +1169,7 @@ export function buildModelsRequest(
|
|
|
1278
1169
|
// enough to list everything without a pageToken loop. Vertex/antigravity keep the
|
|
1279
1170
|
// generic branch (they fall back to their static model lists).
|
|
1280
1171
|
if (apiKey) headers["x-goog-api-key"] = apiKey;
|
|
1281
|
-
return { url: discoveryUrl(`${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`), headers };
|
|
1172
|
+
return guarded({ url: discoveryUrl(`${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`), headers });
|
|
1282
1173
|
}
|
|
1283
1174
|
if (effectiveProvider.adapter === "anthropic") {
|
|
1284
1175
|
const base = effectiveProvider.baseUrl.replace(/\/v1\/?$/, "");
|
|
@@ -1290,10 +1181,10 @@ export function buildModelsRequest(
|
|
|
1290
1181
|
if (effectiveProvider.apiKeyTransport === "bearer") headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1291
1182
|
else headers["x-api-key"] = apiKey;
|
|
1292
1183
|
}
|
|
1293
|
-
return { url: discoveryUrl(`${base}/v1/models?limit=1000`), headers };
|
|
1184
|
+
return guarded({ url: discoveryUrl(`${base}/v1/models?limit=1000`), headers });
|
|
1294
1185
|
}
|
|
1295
1186
|
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1296
|
-
return { url: discoveryUrl(providerModelsUrl(effectiveProvider.baseUrl)), headers };
|
|
1187
|
+
return guarded({ url: discoveryUrl(providerModelsUrl(effectiveProvider.baseUrl)), headers });
|
|
1297
1188
|
}
|
|
1298
1189
|
|
|
1299
1190
|
/**
|
|
@@ -1452,6 +1343,7 @@ function projectOAuthProviderReconciliation(config: OcxConfig): OAuthReconcilePr
|
|
|
1452
1343
|
const touchedAntigravityVersion = projected.googleAntigravityStaticCatalogVersion !== beforeAntigravityVersion;
|
|
1453
1344
|
|
|
1454
1345
|
for (const [name, prov] of Object.entries(projected.providers)) {
|
|
1346
|
+
if (name === "anthropic2" && !anthropicInstanceRowShapeMatches(name, prov)) continue;
|
|
1455
1347
|
const beforeProvider = JSON.stringify(prov);
|
|
1456
1348
|
const def = OAUTH_PROVIDERS[name];
|
|
1457
1349
|
if (name === "command-code" && isLegacyCommandCodeStaticCatalog(prov)) {
|
|
@@ -1588,6 +1480,7 @@ const OAUTH_LOGIN_OWNED_PROVIDER_FIELDS = [
|
|
|
1588
1480
|
|
|
1589
1481
|
/** Add/refresh only an OAuth provider's login-owned config fields (does not persist). */
|
|
1590
1482
|
export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
1483
|
+
assertAnthropicInstanceLoginConfig(config, provider);
|
|
1591
1484
|
if (provider === "chatgpt") return;
|
|
1592
1485
|
const def = OAUTH_PROVIDERS[provider];
|
|
1593
1486
|
if (!def) return;
|
|
@@ -1656,6 +1549,7 @@ interface RunLoginDeps {
|
|
|
1656
1549
|
saveAccountCredential?: typeof saveAccountCredential;
|
|
1657
1550
|
loadConfig?: typeof loadConfig;
|
|
1658
1551
|
saveConfig?: typeof saveConfig;
|
|
1552
|
+
mutatePersistedConfig?: typeof mutatePersistedConfig;
|
|
1659
1553
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
1660
1554
|
rollbackCredentialWrite?: typeof rollbackCredentialWriteIfMatch;
|
|
1661
1555
|
assertCurrentOwner?: () => void;
|
|
@@ -1672,14 +1566,28 @@ export async function runLogin(
|
|
|
1672
1566
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1673
1567
|
const loadLatestConfig = deps.loadConfig ?? loadConfig;
|
|
1674
1568
|
const saveLatestConfig = deps.saveConfig ?? saveConfig;
|
|
1675
|
-
const
|
|
1569
|
+
const initialBConfigState = provider === "anthropic2" ? observeInitialConfigState() : undefined;
|
|
1570
|
+
let preflightConfig = provider !== "chatgpt" ? loadLatestConfig() : undefined;
|
|
1571
|
+
if (provider === "anthropic2" && preflightConfig && initialBConfigState === "missing") {
|
|
1572
|
+
// Explicit first-login onboarding may create the ordinary defaults, without a B row.
|
|
1573
|
+
// The create-only owner never replaces a concurrent file. Reload its winner before login;
|
|
1574
|
+
// once the browser starts, later deletion remains a publication refusal, not recreation.
|
|
1575
|
+
if (initializePersistedConfigIfMissing(getDefaultConfig()) === "invalid") {
|
|
1576
|
+
throw new OAuthProviderPublicationError(provider, false);
|
|
1577
|
+
}
|
|
1578
|
+
preflightConfig = loadLatestConfig();
|
|
1579
|
+
}
|
|
1676
1580
|
if (preflightConfig) {
|
|
1581
|
+
assertAnthropicInstanceLoginConfig(preflightConfig, provider);
|
|
1677
1582
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(
|
|
1678
1583
|
preflightConfig.codexAccountNamespaces,
|
|
1679
1584
|
provider,
|
|
1680
1585
|
);
|
|
1681
1586
|
if (namespaceCollision) throw new Error(namespaceCollision);
|
|
1682
1587
|
}
|
|
1588
|
+
if (provider === "anthropic2" && observeInitialConfigState() !== "exists") {
|
|
1589
|
+
throw new OAuthProviderPublicationError(provider, false);
|
|
1590
|
+
}
|
|
1683
1591
|
// loginKiro keys its pending CLI-session transaction by object identity. Keep this exact object
|
|
1684
1592
|
// for settlement even when source normalization below creates a derived credential object.
|
|
1685
1593
|
const shouldRollbackKiroAccounts = provider === "kiro" && opts?.forceLogin === true;
|
|
@@ -1693,6 +1601,7 @@ export async function runLogin(
|
|
|
1693
1601
|
}
|
|
1694
1602
|
const rawCred = await def.login(ctrl, opts, loginProviderConfig);
|
|
1695
1603
|
const cred: OAuthCredentials = rawCred.source ? rawCred : { ...rawCred, source: "oauth" };
|
|
1604
|
+
if (isAnthropicOAuthInstance(provider)) assertAnthropicCredentialSource(provider, cred);
|
|
1696
1605
|
const settleKiroTransaction = deps.settleKiroLoginTransaction ?? settleKiroLoginTransaction;
|
|
1697
1606
|
try {
|
|
1698
1607
|
deps.assertCurrentOwner?.();
|
|
@@ -1738,16 +1647,28 @@ export async function runLogin(
|
|
|
1738
1647
|
if (provider !== "chatgpt") {
|
|
1739
1648
|
// Re-run against post-credential state so same-provider API-key additions, removals,
|
|
1740
1649
|
// and active-key switches survive. A late namespace claim wins over provider creation.
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1650
|
+
if (provider === "anthropic2") {
|
|
1651
|
+
const publication = (deps.mutatePersistedConfig ?? mutatePersistedConfig)(latestConfig => {
|
|
1652
|
+
assertAnthropicInstanceLoginConfig(latestConfig, provider, true);
|
|
1653
|
+
if (codexAccountNamespaceProviderCollisionError(latestConfig.codexAccountNamespaces, provider)) {
|
|
1654
|
+
throw new OAuthProviderPublicationError(provider);
|
|
1655
|
+
}
|
|
1656
|
+
upsertOAuthProvider(latestConfig, provider);
|
|
1657
|
+
return { changed: true, value: undefined };
|
|
1658
|
+
});
|
|
1659
|
+
if (publication.status === "unavailable") throw new OAuthProviderPublicationError(provider);
|
|
1660
|
+
} else {
|
|
1661
|
+
const latestConfig = loadLatestConfig();
|
|
1662
|
+
const lateCollision = codexAccountNamespaceProviderCollisionError(
|
|
1663
|
+
latestConfig.codexAccountNamespaces,
|
|
1664
|
+
provider,
|
|
1665
|
+
);
|
|
1666
|
+
if (lateCollision) {
|
|
1667
|
+
throw new OAuthProviderPublicationError();
|
|
1668
|
+
}
|
|
1669
|
+
upsertOAuthProvider(latestConfig, provider);
|
|
1670
|
+
saveLatestConfig(latestConfig);
|
|
1748
1671
|
}
|
|
1749
|
-
upsertOAuthProvider(latestConfig, provider);
|
|
1750
|
-
saveLatestConfig(latestConfig);
|
|
1751
1672
|
}
|
|
1752
1673
|
} catch (error) {
|
|
1753
1674
|
const errors: unknown[] = [error];
|
|
@@ -1840,7 +1761,7 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1840
1761
|
...(a.alias ? { alias: a.alias } : {}),
|
|
1841
1762
|
email: projectEmail(a.credential.email, maskEmails) ?? undefined,
|
|
1842
1763
|
active: a.id === set.activeAccountId,
|
|
1843
|
-
...(a
|
|
1764
|
+
...(accountNeedsReauthForStatus(provider, a) ? { needsReauth: true } : {}),
|
|
1844
1765
|
...(a.needsReauth && a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
1845
1766
|
expiresAt: a.credential.expires,
|
|
1846
1767
|
// Explicitly null rather than omitted — see OAuthAccountSummary.plan. No OAuth provider
|
|
@@ -1852,11 +1773,11 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1852
1773
|
|
|
1853
1774
|
// A stored credential counts as "logged in" when it exists and is not marked for
|
|
1854
1775
|
// re-authentication. An expired access token with a valid refresh token is still
|
|
1855
|
-
// logged in
|
|
1776
|
+
// logged in unless refresh-attention evidence applies; refresh eligibility stays internal.
|
|
1856
1777
|
// Invalid/unknown local-import expiries are handled at parse/adoption time
|
|
1857
1778
|
// (local-token-detect.ts), never by over-reporting login state here.
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1779
|
+
const activeAccount = set?.accounts.find(a => a.id === set.activeAccountId);
|
|
1780
|
+
const activeNeedsReauth = activeAccount && accountNeedsReauthForStatus(provider, activeAccount);
|
|
1860
1781
|
return {
|
|
1861
1782
|
loggedIn: !!cred && !activeNeedsReauth,
|
|
1862
1783
|
email: projectEmail(cred?.email, maskEmails) ?? undefined,
|
package/src/oauth/kiro.ts
CHANGED
|
@@ -60,6 +60,7 @@ export class KiroTokenRefreshError extends Error {
|
|
|
60
60
|
constructor(
|
|
61
61
|
readonly httpStatus: number,
|
|
62
62
|
readonly oauthError?: string,
|
|
63
|
+
readonly refreshAttention = false,
|
|
63
64
|
) {
|
|
64
65
|
super(`Kiro token refresh failed: ${httpStatus}${oauthError ? ` (${oauthError})` : ""}`);
|
|
65
66
|
this.name = "KiroTokenRefreshError";
|
|
@@ -530,17 +531,21 @@ export function resolveKiroRequestProfile(
|
|
|
530
531
|
: { profileArn: undefined, builderIdFallback: false };
|
|
531
532
|
}
|
|
532
533
|
|
|
533
|
-
async function kiroTokenRefreshError(response: Response): Promise<KiroTokenRefreshError> {
|
|
534
|
+
async function kiroTokenRefreshError(response: Response, awsSsoRefresh = false): Promise<KiroTokenRefreshError> {
|
|
534
535
|
let oauthError: string | undefined;
|
|
536
|
+
let refreshAttention = false;
|
|
535
537
|
try {
|
|
536
538
|
const payload = await response.json() as { error?: unknown };
|
|
539
|
+
// This input-error code is attention evidence only for our AWS SSO refresh request,
|
|
540
|
+
// never a terminal grant verdict. Do not surface arbitrary upstream descriptions.
|
|
541
|
+
refreshAttention = awsSsoRefresh && response.status === 400 && payload.error === "invalid_request";
|
|
537
542
|
if (typeof payload.error === "string" && KIRO_TERMINAL_REFRESH_ERRORS.has(payload.error)) {
|
|
538
543
|
oauthError = payload.error;
|
|
539
544
|
}
|
|
540
545
|
} catch {
|
|
541
546
|
// Error bodies are untrusted and intentionally excluded from the surfaced message.
|
|
542
547
|
}
|
|
543
|
-
return new KiroTokenRefreshError(response.status, oauthError);
|
|
548
|
+
return new KiroTokenRefreshError(response.status, oauthError, refreshAttention);
|
|
544
549
|
}
|
|
545
550
|
|
|
546
551
|
async function readTokenResponse(res: Response, oldRefresh: string): Promise<OAuthCredentials> {
|
|
@@ -608,7 +613,7 @@ async function refreshAwsSsoOidcToken(
|
|
|
608
613
|
signal: kiroRefreshSignal(signal),
|
|
609
614
|
});
|
|
610
615
|
const res = await run(refresh);
|
|
611
|
-
if (!res.ok) throw await kiroTokenRefreshError(res);
|
|
616
|
+
if (!res.ok) throw await kiroTokenRefreshError(res, true);
|
|
612
617
|
return readTokenResponse(res, refresh);
|
|
613
618
|
}
|
|
614
619
|
|
|
@@ -668,11 +673,19 @@ export async function refreshKiroToken(
|
|
|
668
673
|
const local = matchingRotatedKiroCliCredential(refresh, credential);
|
|
669
674
|
if (!local) throw error;
|
|
670
675
|
const retryMetadata = metadataForRotatedKiroCliCredential(credential, local);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
676
|
+
try {
|
|
677
|
+
const fresh = await refreshAwsSsoOidcToken(local.refresh, signal, {
|
|
678
|
+
...credential,
|
|
679
|
+
refresh: local.refresh,
|
|
680
|
+
kiro: retryMetadata,
|
|
681
|
+
});
|
|
682
|
+
return { ...fresh, kiro: retryMetadata };
|
|
683
|
+
} catch (recoveryError) {
|
|
684
|
+
// A failed CLI fallback may use the desktop endpoint. Retain the original AWS SSO
|
|
685
|
+
// attention evidence on another input rejection; transient recovery stays retryable.
|
|
686
|
+
if (error.refreshAttention && recoveryError instanceof KiroTokenRefreshError
|
|
687
|
+
&& recoveryError.httpStatus === 400 && recoveryError.oauthError === undefined) throw error;
|
|
688
|
+
throw recoveryError;
|
|
689
|
+
}
|
|
677
690
|
}
|
|
678
691
|
}
|
package/src/oauth/login-cli.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { KEY_LOGIN_PROVIDERS, isKeyLoginProvider, validateApiKey, type KeyLoginP
|
|
|
14
14
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
15
15
|
import { configuredAdminToken } from "../lib/admin-secrets";
|
|
16
16
|
import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
|
|
17
|
+
import { assertAnthropicInstanceLoginConfig } from "./store-anthropic-instance";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Seams a test drives in place of a browser, a terminal and a real provider. Production passes
|
|
@@ -136,6 +137,7 @@ export async function handleLogin(provider?: string, deps: LoginCliDeps = {}): P
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
export async function handleOAuthLogin(name: string, deps: LoginCliDeps = {}): Promise<void> {
|
|
140
|
+
if (name === "anthropic2") assertAnthropicInstanceLoginConfig(loadConfig(), name);
|
|
139
141
|
const login = deps.runLogin ?? runLogin;
|
|
140
142
|
const launch = deps.openUrl ?? openUrl;
|
|
141
143
|
const browser = createBrowserLaunchReport(deps.warn);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
import { isBuiltinAnthropicInstanceRow } from "../providers/anthropic-instance";
|
|
3
|
+
import { resolveProviderModelDiscoveryUrl } from "../providers/model-discovery";
|
|
4
|
+
import { resolveProviderTransport } from "../providers/xai-transport";
|
|
5
|
+
|
|
6
|
+
/** Discovery owns its configured row (a frozen capture for catalog gathers), not live config. */
|
|
7
|
+
export function mayResolveModelsOAuth(name: string, provider: OcxProviderConfig | undefined): boolean {
|
|
8
|
+
return name !== "anthropic2" || (provider !== undefined && provider.disabled !== true
|
|
9
|
+
&& isBuiltinAnthropicInstanceRow(name, provider));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Derive authority from configured transport/discovery policy, never from the outgoing request. */
|
|
13
|
+
export function captureModelsOAuthTarget(name: string, provider: OcxProviderConfig | undefined): string | undefined {
|
|
14
|
+
if (name !== "anthropic2" || !mayResolveModelsOAuth(name, provider) || !provider) return undefined;
|
|
15
|
+
try {
|
|
16
|
+
const effective = resolveProviderTransport(name, provider);
|
|
17
|
+
const base = effective.baseUrl.replace(/\/v1\/?$/, "");
|
|
18
|
+
const target = new URL(resolveProviderModelDiscoveryUrl(name, provider, effective.baseUrl, `${base}/v1/models?limit=1000`));
|
|
19
|
+
return !target.username && !target.password && !target.hash ? target.href : undefined;
|
|
20
|
+
} catch { return undefined; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function modelsOAuthTargetMatches(name: string, provider: OcxProviderConfig | undefined, target: string | undefined): boolean {
|
|
24
|
+
return name !== "anthropic2" || (target !== undefined && captureModelsOAuthTarget(name, provider) === target);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A supplied snapshot cannot authorize sending Pool 2's bearer to a custom destination. */
|
|
28
|
+
export function guardModelsOAuthRequest<T extends { url: string; headers: Record<string, string> }>(
|
|
29
|
+
name: string,
|
|
30
|
+
provider: OcxProviderConfig,
|
|
31
|
+
request: T,
|
|
32
|
+
authorizedTarget = captureModelsOAuthTarget(name, provider),
|
|
33
|
+
): T {
|
|
34
|
+
if (name !== "anthropic2" || provider.authMode !== "oauth") return request;
|
|
35
|
+
try {
|
|
36
|
+
const destination = new URL(request.url);
|
|
37
|
+
if (mayResolveModelsOAuth(name, provider) && authorizedTarget !== undefined
|
|
38
|
+
&& authorizedTarget === captureModelsOAuthTarget(name, provider) && destination.href === authorizedTarget
|
|
39
|
+
&& !destination.username && !destination.password && !destination.hash) return request;
|
|
40
|
+
} catch { /* Malformed destinations carry no credential authority. */ }
|
|
41
|
+
return { ...request, headers: Object.fromEntries(Object.entries(request.headers)
|
|
42
|
+
.filter(([header]) => header.toLowerCase() !== "authorization")) };
|
|
43
|
+
}
|