@bitkyc08/opencodex 2.79.0 → 2.81.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/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- 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 +7 -2
- 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/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- 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/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +10 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- 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/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- 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/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/codex/auth-api/account-list.ts +11 -3
- 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 +1 -0
- package/src/codex/autostart-health.ts +3 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +347 -199
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- 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 +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -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/upstream-retry.ts +17 -5
- 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 +51 -17
- 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/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- 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 +67 -9
- 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/derive.ts +2 -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-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- 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/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- 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/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +16 -16
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- 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/config-routes.ts +27 -7
- 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/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 +86 -41
- package/src/server/messages-native-scope.ts +4 -1
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/combo-requested-effort.ts +14 -0
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +21 -18
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/desktop-startup.ts +100 -3
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- 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/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Ownership captured before a physical send, independent of subsequent cooldown observations. */
|
|
2
|
+
import type { OAuthAccessSnapshot } from "./index";
|
|
3
|
+
import { credentialGeneration, getAccountSet } from "./store";
|
|
4
|
+
import { isAnthropicInstanceId, type AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
5
|
+
import { anthropicCooldownRecoveryFor } from "../providers/quota/anthropic-cooldown-recovery";
|
|
6
|
+
import { captureProviderAccountQuotaEpoch } from "../providers/quota/account-cache";
|
|
7
|
+
|
|
8
|
+
export type AnthropicPhysicalSendOwnership = Readonly<{
|
|
9
|
+
provider: AnthropicInstanceId;
|
|
10
|
+
accountId: string;
|
|
11
|
+
accessToken: string;
|
|
12
|
+
generation: string;
|
|
13
|
+
accountIncarnation: number;
|
|
14
|
+
loginId?: string;
|
|
15
|
+
addedAt?: number;
|
|
16
|
+
quotaEpoch: number;
|
|
17
|
+
}>;
|
|
18
|
+
|
|
19
|
+
/** Namespace API: config/target admission remains with the config-aware physical caller. */
|
|
20
|
+
export function captureAnthropicPhysicalSendOwnership(snapshot: OAuthAccessSnapshot): AnthropicPhysicalSendOwnership | null {
|
|
21
|
+
if (!isAnthropicInstanceId(snapshot.provider)) return null;
|
|
22
|
+
const row = getAccountSet(snapshot.provider)?.accounts.find(account => account.id === snapshot.accountId);
|
|
23
|
+
if (!row || row.paused || row.needsReauth || row.credential.access !== snapshot.accessToken
|
|
24
|
+
|| credentialGeneration(row.credential) !== snapshot.generation) return null;
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
provider: snapshot.provider, accountId: snapshot.accountId,
|
|
27
|
+
accessToken: snapshot.accessToken, generation: snapshot.generation,
|
|
28
|
+
accountIncarnation: anthropicCooldownRecoveryFor(snapshot.provider).reserveAnthropicAccountIncarnation(snapshot.accountId),
|
|
29
|
+
loginId: row.loginId, addedAt: row.addedAt,
|
|
30
|
+
quotaEpoch: captureProviderAccountQuotaEpoch(snapshot.provider),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Pure ownership read: never adopt or reserve the replacement account's current incarnation. */
|
|
35
|
+
export function anthropicPhysicalSendOwnershipIsCurrent(owner: AnthropicPhysicalSendOwnership): boolean {
|
|
36
|
+
const row = getAccountSet(owner.provider)?.accounts.find(account => account.id === owner.accountId);
|
|
37
|
+
return !!row && row.loginId === owner.loginId && row.addedAt === owner.addedAt
|
|
38
|
+
&& row.credential.access === owner.accessToken && credentialGeneration(row.credential) === owner.generation
|
|
39
|
+
&& anthropicCooldownRecoveryFor(owner.provider).anthropicAccountIncarnation(owner.accountId) === owner.accountIncarnation
|
|
40
|
+
&& captureProviderAccountQuotaEpoch(owner.provider) === owner.quotaEpoch;
|
|
41
|
+
}
|
package/src/oauth/anthropic.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { bindAnthropicIdentity, resolveAnthropicAccountIdentity } from "./anthro
|
|
|
4
4
|
import { generatePKCE } from "./pkce";
|
|
5
5
|
import type { LocalTokenImportMode, OAuthController, OAuthCredentials } from "./types";
|
|
6
6
|
import { outboundProxyConfigured, startupOutboundProxyConfigured } from "../lib/proxy-env";
|
|
7
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
8
|
+
import { AnthropicLocalCliImportError } from "./store-anthropic-instance";
|
|
7
9
|
|
|
8
10
|
const CLIENT_ID = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
9
11
|
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
@@ -178,9 +180,10 @@ export class AnthropicOAuthFlow extends OAuthCallbackFlow {
|
|
|
178
180
|
|
|
179
181
|
export async function loginAnthropic(
|
|
180
182
|
ctrl: OAuthController,
|
|
181
|
-
opts?: { importLocal?: LocalTokenImportMode },
|
|
183
|
+
opts?: { importLocal?: LocalTokenImportMode; instance?: AnthropicInstanceId },
|
|
182
184
|
): Promise<OAuthCredentials> {
|
|
183
185
|
const importLocal = opts?.importLocal ?? "off";
|
|
186
|
+
if (opts?.instance === "anthropic2" && importLocal !== "off") throw new AnthropicLocalCliImportError();
|
|
184
187
|
if (importLocal !== "off") {
|
|
185
188
|
const { detectClaudeCodeToken } = await import("./local-token-detect");
|
|
186
189
|
const local = detectClaudeCodeToken();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBuiltinAnthropicInstanceRow } from "../providers/anthropic-instance";
|
|
1
2
|
/**
|
|
2
3
|
* Generic OAuth multi-account 429 failover (#2568).
|
|
3
4
|
*
|
|
@@ -157,7 +158,8 @@ export function kiroAutoSelection(
|
|
|
157
158
|
|
|
158
159
|
/** True when this provider participates in generic rotation at all. */
|
|
159
160
|
export function isGenericFailoverProvider(providerName: string, provider: OcxProviderConfig): boolean {
|
|
160
|
-
return provider.authMode === "oauth" && !EXCLUDED_PROVIDERS.has(providerName)
|
|
161
|
+
return provider.authMode === "oauth" && !EXCLUDED_PROVIDERS.has(providerName)
|
|
162
|
+
&& !isBuiltinAnthropicInstanceRow(providerName, provider);
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
/**
|
package/src/oauth/health.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
1
2
|
import type { MainAccountHardLockStatus } from "../codex/main-account-hard-lock";
|
|
2
3
|
import type { MainAccountExternalUsageWarning } from "../codex/main-account-external-usage";
|
|
3
4
|
import { getCodexAccountHealthSnapshot, type CodexCooldownSource } from "../codex/routing";
|
|
4
|
-
import {
|
|
5
|
+
import { anthropicRoutingFor } from "./anthropic-routing";
|
|
5
6
|
import { isAccountNeedsReauth } from "../codex/account-runtime-state";
|
|
6
7
|
import { getCodexAccountCredential, listCodexAccountIds, readCodexAccountRecord } from "../codex/account-store";
|
|
7
8
|
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
|
|
@@ -10,7 +11,7 @@ import { LOCAL_MANAGEMENT_READ_PATHS } from "../lib/local-management-capability"
|
|
|
10
11
|
import { maskAccountId } from "../lib/privacy";
|
|
11
12
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
12
13
|
import { fetchBoundLocalManagementRead } from "../server/local-management-read-client";
|
|
13
|
-
import { loadAuthStore, peekAuthStore, peekOAuthRefreshIntent, readOAuthRefreshIntent } from "./store";
|
|
14
|
+
import { accountNeedsReauthForStatus, loadAuthStore, peekAuthStore, peekOAuthRefreshIntent, readOAuthRefreshIntent } from "./store";
|
|
14
15
|
import type { ProviderAccount } from "./types";
|
|
15
16
|
|
|
16
17
|
export type OAuthAccountHealth =
|
|
@@ -207,12 +208,13 @@ export function projectStoredOAuthAccountHealth(
|
|
|
207
208
|
now = Date.now(),
|
|
208
209
|
opts: { observeOnly?: boolean } = {},
|
|
209
210
|
): OAuthAccountHealth {
|
|
210
|
-
const anthropicSnap = provider
|
|
211
|
-
? getAnthropicAccountHealthSnapshot(account.id, now)
|
|
211
|
+
const anthropicSnap = isAnthropicInstanceId(provider)
|
|
212
|
+
? anthropicRoutingFor(provider).getAnthropicAccountHealthSnapshot(account.id, now)
|
|
212
213
|
: null;
|
|
214
|
+
const needsReauth = accountNeedsReauthForStatus(provider, account, now);
|
|
213
215
|
return projectOAuthAccountHealth({
|
|
214
|
-
needsReauth
|
|
215
|
-
reauthReason:
|
|
216
|
+
needsReauth,
|
|
217
|
+
reauthReason: needsReauth
|
|
216
218
|
? (account.needsReauthReason ?? "refresh_failed")
|
|
217
219
|
: undefined,
|
|
218
220
|
cooldownUntilMs: anthropicSnap?.cooldownUntil,
|
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,11 +27,15 @@ 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
41
|
import { loginChatGPT, refreshChatGPTToken, type ChatGPTLoginFlow } from "./chatgpt";
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -739,131 +740,6 @@ function definitivelyAnswered(error: unknown): boolean {
|
|
|
739
740
|
return false;
|
|
740
741
|
}
|
|
741
742
|
|
|
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
743
|
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
744
|
function merged(fresh: OAuthCredentials, previous: OAuthCredentials): OAuthCredentials {
|
|
869
745
|
return {
|
|
@@ -932,6 +808,7 @@ export async function refreshAnthropicAccountWithLock(
|
|
|
932
808
|
callerCredential: OAuthCredentials,
|
|
933
809
|
deps: AnthropicRefreshDeps = {},
|
|
934
810
|
): Promise<string> {
|
|
811
|
+
if (!isAnthropicOAuthInstance(provider)) throw new UnsupportedOAuthProviderError(provider);
|
|
935
812
|
const writerGeneration = captureConfigGeneration();
|
|
936
813
|
const now = deps.now ?? Date.now;
|
|
937
814
|
const guard = await (deps.intentLock ?? createOAuthRefreshIntentLock(provider, accountId)).acquire();
|
|
@@ -941,10 +818,10 @@ export async function refreshAnthropicAccountWithLock(
|
|
|
941
818
|
if (!accountSet || !account) throw new OAuthLoginRequiredError(provider);
|
|
942
819
|
if (account.paused) throw new OAuthAccountPausedError();
|
|
943
820
|
const stored = account.credential;
|
|
944
|
-
const owns =
|
|
821
|
+
const owns = captureAnthropicCredentialOwnerForInstance(provider, accountSet, accountId);
|
|
945
822
|
const generation = credentialGeneration(stored);
|
|
946
823
|
let pendingIntent = readOAuthRefreshIntent(provider, accountId);
|
|
947
|
-
const observed = await
|
|
824
|
+
const observed = await newerClaudeCredentialForInstance(provider, stored, now(), deps.signal, deps.resolveIdentity);
|
|
948
825
|
const assertOwner = (store: AuthStore, checkDisk = true) => {
|
|
949
826
|
if (observed.kind !== "absent" && deps.signal?.aborted) throw new OAuthTokenRefreshStaleError();
|
|
950
827
|
if (!owns(store, checkDisk && observed.kind !== "absent" ? observed.diskGeneration : undefined)) {
|
|
@@ -1116,6 +993,10 @@ export async function refreshGenericAccountWithLock(
|
|
|
1116
993
|
return fresh.access;
|
|
1117
994
|
} catch (error) {
|
|
1118
995
|
if (error instanceof OAuthMutationBusyError) throw error;
|
|
996
|
+
if (provider === "kiro" && error instanceof KiroTokenRefreshError && error.refreshAttention) {
|
|
997
|
+
await markAccountRefreshAttentionIfGeneration(provider, accountId, generation, writerGeneration);
|
|
998
|
+
throw error;
|
|
999
|
+
}
|
|
1119
1000
|
if (!terminal(error)) throw error;
|
|
1120
1001
|
// Nous-specific failure-atomicity: a terminal refresh error that carries
|
|
1121
1002
|
// an already-issued rotated refresh token (e.g. the access JWT lacked the
|
|
@@ -1177,20 +1058,22 @@ async function refreshAndPersistAccessToken(
|
|
|
1177
1058
|
replacedStaleFlight?: OAuthRefreshFlightEvidence,
|
|
1178
1059
|
): Promise<string> {
|
|
1179
1060
|
if (provider === "xai") return refreshXaiAccountWithLock(provider, accountId, def, cred, { signal });
|
|
1180
|
-
if (provider
|
|
1061
|
+
if (isAnthropicOAuthInstance(provider)) return refreshAnthropicAccountWithLock(provider, accountId, def, cred, { signal, flight, replacedStaleFlight });
|
|
1181
1062
|
return refreshGenericAccountWithLock(provider, accountId, def, cred, { signal });
|
|
1182
1063
|
}
|
|
1183
1064
|
|
|
1184
|
-
/**
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1065
|
+
/** Resolve discovery credentials only for the caller-owned configured OAuth row. */
|
|
1066
|
+
export async function getModelsOAuthAccessSnapshot(name: string, prov: OcxProviderConfig | undefined): Promise<OAuthAccessSnapshot> {
|
|
1067
|
+
if (!mayResolveModelsOAuth(name, prov)) throw new OAuthLoginRequiredError(name);
|
|
1068
|
+
return getValidAccessTokenSnapshot(name);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/** Shared /models auth resolver; custom key rows keep their own key, including omitted authMode. */
|
|
1189
1072
|
export async function resolveModelsAuthToken(name: string, prov: OcxProviderConfig): Promise<string | undefined> {
|
|
1190
1073
|
if (prov.authMode === "forward") return undefined;
|
|
1191
1074
|
if (prov.authMode === "oauth") {
|
|
1192
1075
|
try {
|
|
1193
|
-
return await
|
|
1076
|
+
return (await getModelsOAuthAccessSnapshot(name, prov)).accessToken;
|
|
1194
1077
|
} catch {
|
|
1195
1078
|
return undefined;
|
|
1196
1079
|
}
|
|
@@ -1235,6 +1118,8 @@ export function buildModelsRequest(
|
|
|
1235
1118
|
providerName = "",
|
|
1236
1119
|
observedAuth?: ModelsRequestObservedAuth,
|
|
1237
1120
|
): { method?: "POST"; url: string; headers: Record<string, string> } {
|
|
1121
|
+
const authorizedTarget = captureModelsOAuthTarget(providerName, prov);
|
|
1122
|
+
if (prov.authMode === "oauth" && !mayResolveModelsOAuth(providerName, prov)) apiKey = undefined;
|
|
1238
1123
|
const transportSeed = modelDiscoveryTransportSeed(providerName, prov);
|
|
1239
1124
|
const copilotApiBaseUrl = observedAuth === undefined
|
|
1240
1125
|
? (providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(providerName) : undefined)
|
|
@@ -1261,16 +1146,18 @@ export function buildModelsRequest(
|
|
|
1261
1146
|
effectiveProvider.baseUrl,
|
|
1262
1147
|
defaultUrl,
|
|
1263
1148
|
);
|
|
1149
|
+
const guarded = <T extends { url: string; headers: Record<string, string> }>(request: T): T =>
|
|
1150
|
+
guardModelsOAuthRequest(providerName, prov, request, authorizedTarget);
|
|
1264
1151
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
|
|
1265
1152
|
headers.Accept = "application/json";
|
|
1266
1153
|
headers["Content-Type"] = "application/json";
|
|
1267
1154
|
headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
|
|
1268
1155
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
1269
|
-
return {
|
|
1270
|
-
method: "POST",
|
|
1156
|
+
return guarded({
|
|
1157
|
+
method: "POST" as const,
|
|
1271
1158
|
url: discoveryUrl(`${effectiveProvider.baseUrl.replace(/\/+$/, "")}/v1internal:fetchAvailableModels`),
|
|
1272
1159
|
headers,
|
|
1273
|
-
};
|
|
1160
|
+
});
|
|
1274
1161
|
}
|
|
1275
1162
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "ai-studio") {
|
|
1276
1163
|
// Generative Language API: API key goes in x-goog-api-key (never Authorization: Bearer),
|
|
@@ -1278,7 +1165,7 @@ export function buildModelsRequest(
|
|
|
1278
1165
|
// enough to list everything without a pageToken loop. Vertex/antigravity keep the
|
|
1279
1166
|
// generic branch (they fall back to their static model lists).
|
|
1280
1167
|
if (apiKey) headers["x-goog-api-key"] = apiKey;
|
|
1281
|
-
return { url: discoveryUrl(`${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`), headers };
|
|
1168
|
+
return guarded({ url: discoveryUrl(`${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`), headers });
|
|
1282
1169
|
}
|
|
1283
1170
|
if (effectiveProvider.adapter === "anthropic") {
|
|
1284
1171
|
const base = effectiveProvider.baseUrl.replace(/\/v1\/?$/, "");
|
|
@@ -1290,10 +1177,10 @@ export function buildModelsRequest(
|
|
|
1290
1177
|
if (effectiveProvider.apiKeyTransport === "bearer") headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1291
1178
|
else headers["x-api-key"] = apiKey;
|
|
1292
1179
|
}
|
|
1293
|
-
return { url: discoveryUrl(`${base}/v1/models?limit=1000`), headers };
|
|
1180
|
+
return guarded({ url: discoveryUrl(`${base}/v1/models?limit=1000`), headers });
|
|
1294
1181
|
}
|
|
1295
1182
|
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1296
|
-
return { url: discoveryUrl(providerModelsUrl(effectiveProvider.baseUrl)), headers };
|
|
1183
|
+
return guarded({ url: discoveryUrl(providerModelsUrl(effectiveProvider.baseUrl)), headers });
|
|
1297
1184
|
}
|
|
1298
1185
|
|
|
1299
1186
|
/**
|
|
@@ -1452,6 +1339,7 @@ function projectOAuthProviderReconciliation(config: OcxConfig): OAuthReconcilePr
|
|
|
1452
1339
|
const touchedAntigravityVersion = projected.googleAntigravityStaticCatalogVersion !== beforeAntigravityVersion;
|
|
1453
1340
|
|
|
1454
1341
|
for (const [name, prov] of Object.entries(projected.providers)) {
|
|
1342
|
+
if (name === "anthropic2" && !anthropicInstanceRowShapeMatches(name, prov)) continue;
|
|
1455
1343
|
const beforeProvider = JSON.stringify(prov);
|
|
1456
1344
|
const def = OAUTH_PROVIDERS[name];
|
|
1457
1345
|
if (name === "command-code" && isLegacyCommandCodeStaticCatalog(prov)) {
|
|
@@ -1588,6 +1476,7 @@ const OAUTH_LOGIN_OWNED_PROVIDER_FIELDS = [
|
|
|
1588
1476
|
|
|
1589
1477
|
/** Add/refresh only an OAuth provider's login-owned config fields (does not persist). */
|
|
1590
1478
|
export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
1479
|
+
assertAnthropicInstanceLoginConfig(config, provider);
|
|
1591
1480
|
if (provider === "chatgpt") return;
|
|
1592
1481
|
const def = OAUTH_PROVIDERS[provider];
|
|
1593
1482
|
if (!def) return;
|
|
@@ -1656,6 +1545,7 @@ interface RunLoginDeps {
|
|
|
1656
1545
|
saveAccountCredential?: typeof saveAccountCredential;
|
|
1657
1546
|
loadConfig?: typeof loadConfig;
|
|
1658
1547
|
saveConfig?: typeof saveConfig;
|
|
1548
|
+
mutatePersistedConfig?: typeof mutatePersistedConfig;
|
|
1659
1549
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
1660
1550
|
rollbackCredentialWrite?: typeof rollbackCredentialWriteIfMatch;
|
|
1661
1551
|
assertCurrentOwner?: () => void;
|
|
@@ -1672,14 +1562,28 @@ export async function runLogin(
|
|
|
1672
1562
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1673
1563
|
const loadLatestConfig = deps.loadConfig ?? loadConfig;
|
|
1674
1564
|
const saveLatestConfig = deps.saveConfig ?? saveConfig;
|
|
1675
|
-
const
|
|
1565
|
+
const initialBConfigState = provider === "anthropic2" ? observeInitialConfigState() : undefined;
|
|
1566
|
+
let preflightConfig = provider !== "chatgpt" ? loadLatestConfig() : undefined;
|
|
1567
|
+
if (provider === "anthropic2" && preflightConfig && initialBConfigState === "missing") {
|
|
1568
|
+
// Explicit first-login onboarding may create the ordinary defaults, without a B row.
|
|
1569
|
+
// The create-only owner never replaces a concurrent file. Reload its winner before login;
|
|
1570
|
+
// once the browser starts, later deletion remains a publication refusal, not recreation.
|
|
1571
|
+
if (initializePersistedConfigIfMissing(getDefaultConfig()) === "invalid") {
|
|
1572
|
+
throw new OAuthProviderPublicationError(provider, false);
|
|
1573
|
+
}
|
|
1574
|
+
preflightConfig = loadLatestConfig();
|
|
1575
|
+
}
|
|
1676
1576
|
if (preflightConfig) {
|
|
1577
|
+
assertAnthropicInstanceLoginConfig(preflightConfig, provider);
|
|
1677
1578
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(
|
|
1678
1579
|
preflightConfig.codexAccountNamespaces,
|
|
1679
1580
|
provider,
|
|
1680
1581
|
);
|
|
1681
1582
|
if (namespaceCollision) throw new Error(namespaceCollision);
|
|
1682
1583
|
}
|
|
1584
|
+
if (provider === "anthropic2" && observeInitialConfigState() !== "exists") {
|
|
1585
|
+
throw new OAuthProviderPublicationError(provider, false);
|
|
1586
|
+
}
|
|
1683
1587
|
// loginKiro keys its pending CLI-session transaction by object identity. Keep this exact object
|
|
1684
1588
|
// for settlement even when source normalization below creates a derived credential object.
|
|
1685
1589
|
const shouldRollbackKiroAccounts = provider === "kiro" && opts?.forceLogin === true;
|
|
@@ -1693,6 +1597,7 @@ export async function runLogin(
|
|
|
1693
1597
|
}
|
|
1694
1598
|
const rawCred = await def.login(ctrl, opts, loginProviderConfig);
|
|
1695
1599
|
const cred: OAuthCredentials = rawCred.source ? rawCred : { ...rawCred, source: "oauth" };
|
|
1600
|
+
if (isAnthropicOAuthInstance(provider)) assertAnthropicCredentialSource(provider, cred);
|
|
1696
1601
|
const settleKiroTransaction = deps.settleKiroLoginTransaction ?? settleKiroLoginTransaction;
|
|
1697
1602
|
try {
|
|
1698
1603
|
deps.assertCurrentOwner?.();
|
|
@@ -1738,16 +1643,28 @@ export async function runLogin(
|
|
|
1738
1643
|
if (provider !== "chatgpt") {
|
|
1739
1644
|
// Re-run against post-credential state so same-provider API-key additions, removals,
|
|
1740
1645
|
// and active-key switches survive. A late namespace claim wins over provider creation.
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1646
|
+
if (provider === "anthropic2") {
|
|
1647
|
+
const publication = (deps.mutatePersistedConfig ?? mutatePersistedConfig)(latestConfig => {
|
|
1648
|
+
assertAnthropicInstanceLoginConfig(latestConfig, provider, true);
|
|
1649
|
+
if (codexAccountNamespaceProviderCollisionError(latestConfig.codexAccountNamespaces, provider)) {
|
|
1650
|
+
throw new OAuthProviderPublicationError(provider);
|
|
1651
|
+
}
|
|
1652
|
+
upsertOAuthProvider(latestConfig, provider);
|
|
1653
|
+
return { changed: true, value: undefined };
|
|
1654
|
+
});
|
|
1655
|
+
if (publication.status === "unavailable") throw new OAuthProviderPublicationError(provider);
|
|
1656
|
+
} else {
|
|
1657
|
+
const latestConfig = loadLatestConfig();
|
|
1658
|
+
const lateCollision = codexAccountNamespaceProviderCollisionError(
|
|
1659
|
+
latestConfig.codexAccountNamespaces,
|
|
1660
|
+
provider,
|
|
1661
|
+
);
|
|
1662
|
+
if (lateCollision) {
|
|
1663
|
+
throw new OAuthProviderPublicationError();
|
|
1664
|
+
}
|
|
1665
|
+
upsertOAuthProvider(latestConfig, provider);
|
|
1666
|
+
saveLatestConfig(latestConfig);
|
|
1748
1667
|
}
|
|
1749
|
-
upsertOAuthProvider(latestConfig, provider);
|
|
1750
|
-
saveLatestConfig(latestConfig);
|
|
1751
1668
|
}
|
|
1752
1669
|
} catch (error) {
|
|
1753
1670
|
const errors: unknown[] = [error];
|
|
@@ -1840,7 +1757,7 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1840
1757
|
...(a.alias ? { alias: a.alias } : {}),
|
|
1841
1758
|
email: projectEmail(a.credential.email, maskEmails) ?? undefined,
|
|
1842
1759
|
active: a.id === set.activeAccountId,
|
|
1843
|
-
...(a
|
|
1760
|
+
...(accountNeedsReauthForStatus(provider, a) ? { needsReauth: true } : {}),
|
|
1844
1761
|
...(a.needsReauth && a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
1845
1762
|
expiresAt: a.credential.expires,
|
|
1846
1763
|
// Explicitly null rather than omitted — see OAuthAccountSummary.plan. No OAuth provider
|
|
@@ -1852,11 +1769,11 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1852
1769
|
|
|
1853
1770
|
// A stored credential counts as "logged in" when it exists and is not marked for
|
|
1854
1771
|
// re-authentication. An expired access token with a valid refresh token is still
|
|
1855
|
-
// logged in
|
|
1772
|
+
// logged in unless refresh-attention evidence applies; refresh eligibility stays internal.
|
|
1856
1773
|
// Invalid/unknown local-import expiries are handled at parse/adoption time
|
|
1857
1774
|
// (local-token-detect.ts), never by over-reporting login state here.
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1775
|
+
const activeAccount = set?.accounts.find(a => a.id === set.activeAccountId);
|
|
1776
|
+
const activeNeedsReauth = activeAccount && accountNeedsReauthForStatus(provider, activeAccount);
|
|
1860
1777
|
return {
|
|
1861
1778
|
loggedIn: !!cred && !activeNeedsReauth,
|
|
1862
1779
|
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
|
}
|