@mastra/code-sdk 1.13.0 → 1.14.0-alpha.1
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/dist/agents/mastracode-gateway.d.ts.map +1 -1
- package/dist/agents/mastracode-gateway.js +18 -44
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/model.d.ts +5 -2
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/model.js +7 -7
- package/dist/agents/model.js.map +1 -1
- package/dist/auth/account-rotation-processor.d.ts +2 -0
- package/dist/auth/account-rotation-processor.d.ts.map +1 -1
- package/dist/auth/account-rotation-processor.js +4 -3
- package/dist/auth/account-rotation-processor.js.map +1 -1
- package/dist/auth/storage.js +1 -1
- package/dist/auth/storage.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/github-copilot-gateway.d.ts +35 -0
- package/dist/providers/github-copilot-gateway.d.ts.map +1 -0
- package/dist/providers/github-copilot-gateway.js +61 -0
- package/dist/providers/github-copilot-gateway.js.map +1 -0
- package/dist/providers/kimi-coding.d.ts +0 -1
- package/dist/providers/kimi-coding.d.ts.map +1 -1
- package/dist/providers/kimi-coding.js +1 -7
- package/dist/providers/kimi-coding.js.map +1 -1
- package/dist/providers/model-ids.d.ts +18 -0
- package/dist/providers/model-ids.d.ts.map +1 -1
- package/dist/providers/model-ids.js +39 -1
- package/dist/providers/model-ids.js.map +1 -1
- package/dist/providers/provider-thinking.d.ts +5 -0
- package/dist/providers/provider-thinking.d.ts.map +1 -1
- package/dist/providers/provider-thinking.js +32 -2
- package/dist/providers/provider-thinking.js.map +1 -1
- package/dist/providers/thinking-request.d.ts +14 -0
- package/dist/providers/thinking-request.d.ts.map +1 -0
- package/dist/providers/thinking-request.js +90 -0
- package/dist/providers/thinking-request.js.map +1 -0
- package/dist/providers/xai.d.ts +2 -2
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js.map +1 -1
- package/dist/thinking.d.ts +1 -0
- package/dist/thinking.d.ts.map +1 -1
- package/dist/thinking.js +23 -5
- package/dist/thinking.js.map +1 -1
- package/package.json +11 -11
- package/dist/providers/deepseek-thinking.d.ts +0 -4
- package/dist/providers/deepseek-thinking.d.ts.map +0 -1
- package/dist/providers/deepseek-thinking.js +0 -16
- package/dist/providers/deepseek-thinking.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getOAuthProviders } from "./storage.js";
|
|
2
|
+
import { getCredentialProviderId, normalizeLegacyModelId } from "../providers/model-ids.js";
|
|
2
3
|
import { ProviderAuthRequiredError } from "./provider-auth-error.js";
|
|
3
4
|
import { resolveCredentialStore } from "../agents/credential-resolver.js";
|
|
4
5
|
import { getRequestAccountSelection, isRequestAccountRoutingExhausted, markRequestAccountRoutingExhausted, setRequestAccountSelection } from "./account-routing-context.js";
|
|
@@ -149,8 +150,8 @@ function classifyRotationError(error) {
|
|
|
149
150
|
}
|
|
150
151
|
/** Provider id off a router model id: `mastracode/<provider>/<model>` or `<provider>/<model>`. */
|
|
151
152
|
function providerFromModelId(modelId) {
|
|
152
|
-
const
|
|
153
|
-
|
|
153
|
+
const segments = normalizeLegacyModelId(modelId).replace(/^mastracode\//, "").split("/");
|
|
154
|
+
const providerId = segments[0] ? getCredentialProviderId(segments[0]) : void 0;
|
|
154
155
|
return providerId && KNOWN_PROVIDER_IDS.has(providerId) ? providerId : void 0;
|
|
155
156
|
}
|
|
156
157
|
function providerFromHost(hostname) {
|
|
@@ -438,7 +439,7 @@ var AccountRotationProcessor = class {
|
|
|
438
439
|
}
|
|
439
440
|
const thinkingLevel = resolveRequestThinkingLevel({
|
|
440
441
|
state: { thinkingLevel: state?.thinkingLevel },
|
|
441
|
-
session: { modeId: typeof controller?.session?.modeId === "string" ? controller.session.modeId : "build" }
|
|
442
|
+
...!this.options.modeless && { session: { modeId: typeof controller?.session?.modeId === "string" ? controller.session.modeId : "build" } }
|
|
442
443
|
}, this.options.settingsPath);
|
|
443
444
|
const entries = [];
|
|
444
445
|
for (const entry of route.slice(startIndex)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-rotation-processor.js","names":[],"sources":["../../src/auth/account-rotation-processor.ts"],"sourcesContent":["/**\n * Error-driven rotation across a provider's OAuth account registry.\n *\n * When the active account cannot serve a request (rate limit, quota\n * exhaustion, dead token, persistent outage), the next account in insertion\n * order is selected for the request and the request retried. OAuth fetch\n * wrappers read that request-scoped selection on every HTTP attempt, so\n * concurrent runs can't swap each other's credentials. Every switch is\n * persisted as a non-transient `data-mastracode-account-switch` part so it\n * shows in the transcript and survives history reload.\n *\n * Two classes, two lanes — the split is load-bearing. The processor runner\n * walks `[...inputProcessors, ...outputProcessors, ...errorProcessors]` when\n * running `processAPIError`, so a processor registered in the input lane that\n * also implemented `processAPIError` would run BEFORE\n * `StreamErrorRetryProcessor` and hop on 5xx errors before transient retries\n * had a chance. `AccountRotationProcessor` therefore implements\n * `processAPIError` only and is registered in `errorProcessors` after\n * `StreamErrorRetryProcessor`; `AccountStartNoticeProcessor` implements\n * `processInput` only and is registered in `inputProcessors`.\n */\nimport { TripWire } from '@mastra/core/agent';\nimport type { ProcessAPIErrorArgs, ProcessInputArgs, ProcessInputResult, Processor } from '@mastra/core/processors';\n\nimport { resolveCredentialStore } from '../agents/credential-resolver.js';\nimport { resolveModel, resolveRequestThinkingLevel } from '../agents/model.js';\nimport {\n getRequestAccountSelection,\n isRequestAccountRoutingExhausted,\n markRequestAccountRoutingExhausted,\n setRequestAccountSelection,\n} from './account-routing-context.js';\nimport { ProviderAuthRequiredError, PROVIDER_AUTH_REQUIRED_ERROR } from './provider-auth-error.js';\nimport { getOAuthProviders } from './storage.js';\nimport type { CredentialStore, OAuthAccountRecord } from './types.js';\n\nexport const ACCOUNT_SWITCH_PART_TYPE = 'data-mastracode-account-switch';\n\n/** Labels and instance ids only — never token material (do-not list). */\nexport interface AccountSwitchPartData {\n provider: string;\n from: { id: string; label: string } | null;\n to: { id: string; label: string } | null;\n reason:\n | 'rate-limit'\n | 'quota-exhausted'\n | 'auth-failed'\n | 'pool-exhausted'\n | 'persistent-outage'\n | 'preferred-routing';\n at: string;\n /**\n * Set on a `to: null` part when a route pinned to a single account reported\n * that account unusable. The provider's other subscriptions were never\n * consulted (A12), so \"all accounts unavailable\" would misreport what\n * happened.\n */\n exclusive?: boolean;\n}\n\n/** The store surface rotation needs; `AuthStorage` satisfies it structurally. */\nexport type RotationCredentialStore = CredentialStore & {\n /**\n * Force one refresh of the active account's tokens regardless of expiry\n * (401/403 path — a rejected-but-unexpired token usually means clock skew\n * or a refresh race). `AuthStorage` provides it; deployed per-tenant stores\n * don't, and the auth path degrades to plain rotation (which itself no-ops\n * without registry methods).\n */\n forceRefreshActiveAccount?(providerId: string, accountInstanceId?: string): Promise<string | undefined>;\n};\n\n/** Rotatable providers = the OAuth provider registry; API-key-only providers never rotate. */\nconst KNOWN_PROVIDER_IDS = new Set(getOAuthProviders().map(provider => provider.id));\n\n/** Hostname suffixes → provider ids, for `APICallError.url`. */\nconst PROVIDER_HOST_PATTERNS: Array<[RegExp, string]> = [\n [/(^|\\.)api\\.anthropic\\.com$/i, 'anthropic'],\n [/(^|\\.)api\\.kimi\\.com$/i, 'kimi-for-coding'],\n [/(^|\\.)api\\.x\\.ai$/i, 'xai'],\n [/(^|\\.)chatgpt\\.com$/i, 'openai-codex'],\n [/(^|\\.)backend-api\\.codex\\.ai$/i, 'openai-codex'],\n [/(^|\\.)githubcopilot\\.com$/i, 'github-copilot'],\n [/(^|\\.)api\\.github\\.com$/i, 'github-copilot'],\n];\n\n/**\n * Provider usage/quota-limit wording (locked Q7 bucket 1). Statuses 429/402\n * are matched before this; some providers deliver quota errors as plain 400s,\n * which only the message reveals. Deliberately restricted to unambiguous\n * quota phrases — a generic \"exceeded your … limit\" can appear on non-quota\n * client errors (request size, context length) that must never rotate.\n */\nconst USAGE_LIMIT_MESSAGE_PATTERN =\n /usage (?:limit|cap)|insufficient[_ ]quota|quota (?:exceeded|exhausted|reached)|exceeded (?:your )?current quota|insufficient (?:balance|credits?)|credits? exhausted|rate limit (?:exceeded|reached)|weekly limit|monthly limit/i;\n\nconst NETWORK_ERROR_CODES = new Set([\n 'ECONNRESET',\n 'EPIPE',\n 'ECONNREFUSED',\n 'ENOTFOUND',\n 'ETIMEDOUT',\n 'EAI_AGAIN',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n]);\n\nconst NETWORK_MESSAGE_PATTERN =\n /fetch failed|network error|socket hang up|getaddrinfo|econnrefused|enotfound|etimedout|connection (?:refused|reset|closed|timed out)|other side closed|write epipe/i;\n\nconst MAX_CAUSE_DEPTH = 4;\n\n/** Persisted data-part type for a model-route hop. */\nexport const MODEL_FALLBACK_PART_TYPE = 'data-mastracode-model-fallback' as const;\n\n/** Non-secret transcript payload for a model-route hop. */\nexport interface ModelFallbackPartData {\n from: { entryId: string; label: string };\n to: { entryId: string; label: string };\n reason: 'pool-exhausted' | 'persistent-outage';\n at: string;\n}\n\n/** Session-state key set when account rotation advances the model route. */\nexport const MODEL_FALLBACK_STATE_KEY = 'mastracodePendingModelFallback' as const;\n\n/** Payload written to session state under MODEL_FALLBACK_STATE_KEY. */\nexport interface PendingModelFallback {\n fromEntryId: string;\n toEntryId: string;\n toModelId: string;\n threadId?: string;\n reason: 'pool-exhausted' | 'persistent-outage';\n at: string;\n}\n\nexport type RotationClassification =\n | { kind: 'rotate'; reason: 'rate-limit' | 'quota-exhausted' | 'auth-failed' }\n | { kind: 'hop' }\n | { kind: 'never' };\n\nfunction isErrorWithCode(error: unknown): error is { code?: unknown } {\n return typeof error === 'object' && error !== null && 'code' in error;\n}\n\nfunction readErrorStatus(error: unknown): number | undefined {\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (typeof current !== 'object') break;\n const { statusCode, status } = current as { statusCode?: unknown; status?: unknown };\n if (typeof statusCode === 'number') return statusCode;\n if (typeof status === 'number') return status;\n current = (current as { cause?: unknown }).cause;\n }\n return undefined;\n}\n\nfunction isProviderAuthRequiredError(error: unknown): boolean {\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (\n current instanceof ProviderAuthRequiredError ||\n (current instanceof Error && current.name === PROVIDER_AUTH_REQUIRED_ERROR)\n ) {\n return true;\n }\n if (typeof current !== 'object') break;\n current = (current as { cause?: unknown }).cause;\n }\n return false;\n}\n\n/** Concatenate the error's message with its cause chain (bounded). */\nfunction collectErrorText(error: unknown): string {\n const parts: string[] = [];\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (current instanceof Error) {\n parts.push(current.message);\n current = current.cause;\n } else if (typeof current === 'object' && 'message' in current) {\n const message = (current as { message?: unknown }).message;\n if (typeof message === 'string') parts.push(message);\n current = (current as { cause?: unknown }).cause;\n } else {\n break;\n }\n }\n return parts.join(' | ');\n}\n\nfunction isNetworkErrorLike(error: unknown): boolean {\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (\n isErrorWithCode(current) &&\n typeof current.code === 'string' &&\n NETWORK_ERROR_CODES.has(current.code.toUpperCase())\n ) {\n return true;\n }\n if (current instanceof Error) {\n if (NETWORK_MESSAGE_PATTERN.test(current.message)) return true;\n current = current.cause;\n } else if (typeof current === 'object') {\n current = (current as { cause?: unknown }).cause;\n } else {\n break;\n }\n }\n return false;\n}\n\n/**\n * The locked Q7 taxonomy. 429/402/usage-limit rotate immediately (429 is not\n * covered by any transient matcher, so it arrives here on first occurrence);\n * 401/403 and `ProviderAuthRequiredError` rotate after one forced refresh;\n * 5xx/network hop only when the transient retry budget is already spent (they\n * reach this processor because `StreamErrorRetryProcessor` declined); 400 and\n * everything unknown never touch the registry.\n */\nexport function classifyRotationError(error: unknown): RotationClassification {\n if (isProviderAuthRequiredError(error)) {\n return { kind: 'rotate', reason: 'auth-failed' };\n }\n\n const status = readErrorStatus(error);\n\n if (status === 429) return { kind: 'rotate', reason: 'rate-limit' };\n if (status === 402) return { kind: 'rotate', reason: 'quota-exhausted' };\n if (status === 401 || status === 403) return { kind: 'rotate', reason: 'auth-failed' };\n if (status !== undefined && status >= 500 && status < 600) return { kind: 'hop' };\n\n // Quota wording only rotates when it co-occurs with a statusless or 400\n // error — the shapes providers actually use for message-only quota\n // failures. Any other status keeps its own classification above (401/403\n // auth, 5xx outage hop, 404/422 never), so limit-ish text riding a\n // non-quota client error can no longer burn the whole pool.\n if (status === undefined || status === 400) {\n if (USAGE_LIMIT_MESSAGE_PATTERN.test(collectErrorText(error))) {\n return { kind: 'rotate', reason: 'quota-exhausted' };\n }\n }\n if (isNetworkErrorLike(error)) return { kind: 'hop' };\n\n return { kind: 'never' };\n}\n\n/** Provider id off a router model id: `mastracode/<provider>/<model>` or `<provider>/<model>`. */\nexport function providerFromModelId(modelId: string): string | undefined {\n const segments = modelId.replace(/^mastracode\\//, '').split('/');\n const providerId = segments[0];\n if (providerId === 'openai') return 'openai-codex';\n return providerId && KNOWN_PROVIDER_IDS.has(providerId) ? providerId : undefined;\n}\n\nfunction providerFromHost(hostname: string): string | undefined {\n for (const [pattern, providerId] of PROVIDER_HOST_PATTERNS) {\n if (pattern.test(hostname)) return providerId;\n }\n return undefined;\n}\n\n/**\n * Identify the provider an error came from: the request URL's hostname\n * (`APICallError.url`, cause chain included), falling back to the router\n * model id. Unknown → undefined (caller treats as never-rotate).\n */\nexport function providerFromError(error: unknown): string | undefined {\n const causeChain: object[] = [];\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (typeof current !== 'object') break;\n causeChain.push(current);\n current = (current as { cause?: unknown }).cause;\n }\n\n // A request URL identifies the provider that actually handled the failed\n // call. Check every supported URL field throughout the cause chain before\n // falling back to router model metadata, which may describe the original\n // session model rather than the active cascade entry.\n for (const item of causeChain) {\n const urls = [\n (item as { url?: unknown }).url,\n (item as { requestURL?: unknown }).requestURL,\n (item as { requestUrl?: unknown }).requestUrl,\n ];\n for (const url of urls) {\n if (typeof url !== 'string') continue;\n try {\n const providerId = providerFromHost(new URL(url).hostname);\n if (providerId) return providerId;\n } catch {\n // Not a parseable URL — try the other fields and causes.\n }\n }\n }\n\n for (const item of causeChain) {\n const modelId = (item as { modelId?: unknown }).modelId;\n if (typeof modelId !== 'string') continue;\n const providerId = providerFromModelId(modelId);\n if (providerId) return providerId;\n }\n return undefined;\n}\n\nfunction getTriedInstances(state: Record<string, unknown>): Set<string> {\n const existing = state.triedInstances;\n if (existing instanceof Set) return existing;\n const created = new Set<string>();\n state.triedInstances = created;\n return created;\n}\n\nfunction getForcedRefreshInstances(state: Record<string, unknown>): Set<string> {\n const existing = state.forcedAuthRefresh;\n if (existing instanceof Set) return existing;\n const created = new Set<string>();\n state.forcedAuthRefresh = created;\n return created;\n}\n\n/** Friendly names for the part/notice copy (display only — never auth data). */\nexport const PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n anthropic: 'Anthropic',\n 'kimi-for-coding': 'Kimi',\n 'openai-codex': 'Codex',\n 'github-copilot': 'GitHub Copilot',\n xai: 'xAI',\n};\n\nconst REASON_TEXT: Record<AccountSwitchPartData['reason'], string> = {\n 'rate-limit': 'rate limit',\n 'quota-exhausted': 'quota exhausted',\n 'auth-failed': 'auth failed',\n 'pool-exhausted': 'pool exhausted',\n 'persistent-outage': 'persistent outage',\n 'preferred-routing': 'subscription routing',\n};\n\n/** Validates untrusted `reason` values (e.g. persisted message parts). */\nexport function isAccountSwitchReason(value: unknown): value is AccountSwitchPartData['reason'] {\n // Own-property check: `in` would accept prototype names like 'toString',\n // which would then interpolate the inherited function into the notice text.\n return typeof value === 'string' && Object.prototype.hasOwnProperty.call(REASON_TEXT, value);\n}\n\n/**\n * One-line transcript copy for a switch part. Shared by the live `info`\n * controller event (the TUI shows controller `info` events as transcript\n * lines, and no message event carries data parts mid-run) and by the TUI's\n * history rendering of the persisted part — one formatter, identical text.\n */\nexport function accountSwitchNoticeText(data: AccountSwitchPartData): string {\n const provider = PROVIDER_DISPLAY_NAMES[data.provider] ?? data.provider;\n const reason = REASON_TEXT[data.reason];\n if (data.to === null) {\n // A12: a targeted route reports *its* account, not the pool — the other\n // subscriptions were never tried, so \"all accounts unavailable\" would be\n // wrong and would read as if the pool had been walked.\n if (data.exclusive) {\n return `Pinned ${provider} account unavailable (${reason})`;\n }\n return `All ${provider} accounts unavailable (${reason})`;\n }\n const from = data.from?.label ?? 'unknown';\n return `Switched ${provider} account: ${from} → ${data.to.label} (${reason})`;\n}\n\n/** Transcript line for a model-route hop; shared by the live info event and the TUI's history render. */\nexport function modelFallbackNoticeText(data: ModelFallbackPartData): string {\n const reason = REASON_TEXT[data.reason] ?? data.reason;\n return `Switched model route: ${data.from.label} → ${data.to.label} (${reason})`;\n}\n\n/**\n * Whether a persisted model-fallback reason is one this build understands.\n * `REASON_TEXT` also covers the account-switch reasons, so the notice text\n * alone would happily render a foreign value such as `rate-limit` as a\n * model-fallback reason; history parsed from another build's part must be\n * rejected instead.\n */\nexport function isModelFallbackReason(value: unknown): value is ModelFallbackPartData['reason'] {\n return value === 'pool-exhausted' || value === 'persistent-outage';\n}\n\nasync function emitAccountSwitchPart(\n args: Pick<ProcessAPIErrorArgs, 'writer' | 'requestContext'> | Pick<ProcessInputArgs, 'writer' | 'requestContext'>,\n data: AccountSwitchPartData,\n): Promise<void> {\n // Non-transient on purpose (contrast the transient tool-progress parts):\n // switches must persist into the assistant message and render from history.\n await args.writer?.custom({\n type: ACCOUNT_SWITCH_PART_TYPE,\n data,\n });\n // Data parts surface in the TUI only on history reload; emit the same line\n // as a controller `info` event so the switch is visible live (precedent:\n // emitTransientRetry emits controller events from the retry matcher).\n const controllerContext = args.requestContext?.get('controller') as\n | { emitEvent?: (event: { type: 'info'; message: string }) => void }\n | undefined;\n controllerContext?.emitEvent?.({ type: 'info', message: accountSwitchNoticeText(data) });\n}\n\n/**\n * Provider of the model the controller session is currently running — the\n * fallback identification path for errors that carry neither a request URL\n * nor a model id (e.g. `ProviderAuthRequiredError` thrown by a fetch wrapper\n * before any HTTP request exists).\n */\nfunction providerFromSession(\n args: Pick<ProcessAPIErrorArgs, 'requestContext'> | Pick<ProcessInputArgs, 'requestContext'>,\n): string | undefined {\n const controller = args.requestContext?.get('controller') as { session?: { modelId?: unknown } } | undefined;\n const modelId = controller?.session?.modelId;\n return typeof modelId === 'string' ? providerFromModelId(modelId) : undefined;\n}\n\n/**\n * Rotates the active OAuth account on classified API errors.\n *\n * Registered in `errorProcessors` AFTER `StreamErrorRetryProcessor` — a\n * transient error reaching this processor means the transient budget is\n * spent, which is exactly the hop condition. Implements `processAPIError`\n * ONLY (see the file header for the runner-order rationale).\n */\n/** Cached per-request model route cascade. */\ninterface ModelRouteCascade {\n entries: Array<{ id: string; label: string; modelId: string; accountId?: string }>;\n position: number;\n}\n\ninterface AccountRoute {\n entryId: string;\n modelId: string;\n providerId: string;\n accountId?: string;\n}\n\ntype RoutingProcessorArgs = Pick<ProcessAPIErrorArgs, 'requestContext' | 'state' | 'writer'> | ProcessInputArgs;\n\nexport type RoutingControllerContext = {\n session?: { modelId?: unknown; modeId?: unknown };\n threadId?: unknown;\n getState?: () => Record<string, unknown>;\n setState?: (updates: Record<string, unknown>) => Promise<void>;\n getThreadSetting?: (key: string) => Promise<unknown>;\n setThreadSetting?: (setting: { key: string; value: unknown }) => Promise<void>;\n isThreadActive?: () => boolean;\n};\n\nfunction getRoutingController(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n): RoutingControllerContext | undefined {\n return args.requestContext?.get('controller') as RoutingControllerContext | undefined;\n}\n\n/** A targeted route is exclusive; an entry without accountId uses automatic pool rotation. */\nfunction getRouteTargetAccountId(route: AccountRoute | null): string | undefined {\n return route?.accountId;\n}\n\nfunction getRequestActiveAccount(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n store: CredentialStore,\n providerId: string,\n) {\n if (isRequestAccountRoutingExhausted(args.requestContext, providerId)) return undefined;\n const selectedId = getRequestAccountSelection(args.requestContext, providerId);\n if (selectedId) return store.listAccounts?.(providerId).find(account => account.id === selectedId);\n return store.getActiveAccount?.(providerId) ?? store.listAccounts?.(providerId).find(account => account.active);\n}\n\nfunction resolveAccountRoute(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n explicit?: { id: string; modelId: string; accountId?: string },\n): AccountRoute | null {\n const controller = getRoutingController(args);\n const state = controller?.getState?.();\n const pending = state?.mastracodePendingModelFallback as\n | { toEntryId?: unknown; toModelId?: unknown; threadId?: unknown }\n | null\n | undefined;\n const pendingMatchesThread =\n pending &&\n typeof pending.toEntryId === 'string' &&\n typeof pending.toModelId === 'string' &&\n (pending.threadId === undefined || pending.threadId === controller?.threadId);\n const routeEntries = Array.isArray((state?.modelRoute as { entries?: unknown } | undefined)?.entries)\n ? ((state?.modelRoute as { entries: Array<{ id?: unknown; modelId?: unknown; accountId?: unknown }> }).entries ??\n [])\n : [];\n const entry = explicit\n ? explicit\n : pendingMatchesThread\n ? routeEntries.find(candidate => candidate.id === pending.toEntryId && candidate.modelId === pending.toModelId)\n : routeEntries.find(candidate => candidate.modelId === controller?.session?.modelId);\n if (!entry || typeof entry.id !== 'string' || typeof entry.modelId !== 'string') return null;\n const providerId = providerFromModelId(entry.modelId);\n if (!providerId) return null;\n return {\n entryId: entry.id,\n modelId: entry.modelId,\n providerId,\n ...(typeof entry.accountId === 'string' ? { accountId: entry.accountId } : {}),\n };\n}\n\n/**\n * A14: `Automatic` rotation starts *at* the persisted active account and walks\n * insertion order from there, wrapping once. The active account is the cursor\n * rotation last left at, so a turn that starts at index 0 regardless would\n * re-activate and re-try an account the previous turn already moved past —\n * which is the whole job the removed sticky-exclusion set was doing badly.\n * With no persisted active account the order is plain insertion order.\n */\nfunction orderAccountsFromActive(accounts: OAuthAccountRecord[], activeId: string | undefined) {\n const index = activeId === undefined ? -1 : accounts.findIndex(account => account.id === activeId);\n if (index <= 0) return accounts;\n return [...accounts.slice(index), ...accounts.slice(0, index)];\n}\n\nasync function applyPreferredAccountRoute(\n args: RoutingProcessorArgs,\n store: CredentialStore,\n route: AccountRoute,\n): Promise<boolean> {\n store.reload();\n const preferredId = getRouteTargetAccountId(route);\n const accounts = store.listAccounts?.(route.providerId) ?? [];\n if (accounts.length === 0 && preferredId === undefined) return false;\n const tried = getTriedInstances(args.state);\n const unavailable = new Set(accounts.filter(account => tried.has(account.id)).map(account => account.id));\n const active = getRequestActiveAccount(args, store, route.providerId) ?? store.getActiveAccount?.(route.providerId);\n const selected = // A12: a targeted route may use only the account it names. `Automatic`\n // keeps insertion order with full pool rotation (A10), starting from the\n // account the cursor already points at (A14).\n (\n preferredId\n ? accounts.filter(account => account.id === preferredId)\n : orderAccountsFromActive(accounts, active?.id)\n ).find(account => !unavailable.has(account.id));\n\n for (const accountId of unavailable) tried.add(accountId);\n if (!selected) {\n markRequestAccountRoutingExhausted(args.requestContext, route.providerId);\n return false;\n }\n\n if (active?.id === selected.id) {\n setRequestAccountSelection(args.requestContext, route.providerId, selected.id);\n return false;\n }\n // Activate before recording the request-scoped selection: a failed\n // activation must not leave the selection claiming an account that never\n // became the provider's active credential.\n const activated = store.activateAccount ? store.activateAccount(route.providerId, selected.id) : selected;\n if (!activated) {\n // Only a targeted route is out of options here. The exhausted marker is\n // request-wide and fails every credential read closed, so setting it for\n // `Automatic` would stop error recovery from retrying on a survivor.\n if (preferredId !== undefined) markRequestAccountRoutingExhausted(args.requestContext, route.providerId);\n return false;\n }\n setRequestAccountSelection(args.requestContext, route.providerId, activated.id);\n\n // Applying an explicit account choice is routine, not a failover notice.\n if (preferredId === undefined) {\n await emitAccountSwitchPart(args, {\n provider: route.providerId,\n from: active ? { id: active.id, label: active.label } : null,\n to: { id: activated.id, label: activated.label },\n reason: 'preferred-routing',\n at: new Date().toISOString(),\n });\n }\n return true;\n}\n\nexport class AccountRotationProcessor implements Processor {\n readonly id = 'mastracode-account-rotation' as const;\n\n constructor(\n private readonly options: {\n credentialStore: RotationCredentialStore;\n maxProcessorRetries: number;\n settingsPath?: string;\n },\n ) {}\n\n async processAPIError(args: ProcessAPIErrorArgs): Promise<{ retry: boolean }> {\n const { error, state } = args;\n\n // Core runs error processors even after the shared retry budget is spent\n // and silently discards `retry: true` then (llm-execution-step.ts\n // canRetryError) — rotating the cursor or emitting a switch part for a\n // retry that will never run would lie to both auth.json and the\n // transcript. With a fallback chain configured, a bare `retry: false`\n // here would silently advance the route for an error we never classified, so the\n // chain gate surfaces the error instead.\n if (args.retryCount >= this.options.maxProcessorRetries) {\n await this.gateChainHop(args, error);\n return { retry: false };\n }\n\n // Classify before attributing: an error from a provider outside the OAuth\n // registry (API-key/router providers are valid route entries) still hops on\n // rotate/hop classes — there is just no account cursor to advance.\n const classification = classifyRotationError(error);\n // Q14: 400/unknown errors never rotate and never advance routes. With a chain\n // active, core's fallback array would still advance on a bare\n // `retry: false` (it advances on any thrown non-TripWire error), so the\n // gate converts that into a surfaced error instead of a silent hop.\n if (classification.kind === 'never') {\n await this.gateChainHop(args, error);\n return { retry: false };\n }\n\n const cascade = await this.getModelRouteCascade(args);\n const currentEntry = cascade?.entries[cascade.position];\n const providerId =\n providerFromError(error) ??\n (currentEntry ? providerFromModelId(currentEntry.modelId) : undefined) ??\n (cascade ? undefined : providerFromSession(args));\n if (!providerId) {\n await this.emitModelFallbackPart(args, classification.kind === 'hop' ? 'persistent-outage' : 'pool-exhausted');\n return { retry: false };\n }\n\n // Deployed requests resolve the tenant-scoped store; local mode keeps the\n // host storage. Reading the host registry here would rotate or announce\n // host accounts for a tenant request. Annotated as the rotation surface:\n // `resolveCredentialStore` returns a plain CredentialStore, and the 401\n // path below uses the optional `forceRefreshActiveAccount` when the host\n // provides it.\n const store: RotationCredentialStore = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n store.reload();\n const accounts = store.listAccounts?.(providerId) ?? [];\n const active = getRequestActiveAccount(args, store, providerId);\n const route = currentEntry ? resolveAccountRoute(args, currentEntry) : resolveAccountRoute(args);\n\n // Q7 bucket 2: force one refresh of the active instance before rotating.\n // A 401 usually means a fresh-but-rejected token; the forced refresh\n // covers server-side clock skew and refresh-token races. Success retries\n // the same account — no rotation, no part. With no active account (e.g.\n // `ProviderAuthRequiredError` from an empty registry) a refresh can never\n // succeed, so skip it and fall through to the retry:false surface below,\n // which rethrows the original auth error instead of a generic pool\n // exhaustion message.\n if (classification.kind === 'rotate' && classification.reason === 'auth-failed' && active) {\n const forced = getForcedRefreshInstances(state);\n const refreshKey = `${providerId}:${active?.id ?? 'active'}`;\n if (!forced.has(refreshKey) && typeof store.forceRefreshActiveAccount === 'function') {\n forced.add(refreshKey);\n const token = await store.forceRefreshActiveAccount(providerId, active?.id);\n if (token !== undefined) {\n return { retry: true };\n }\n }\n }\n\n // A12: only a pin on the provider that actually failed makes this failure\n // exclusive. `route.providerId` is derived from the route's model while\n // `providerId` comes from the failed request's URL, which during a cascade\n // can still describe the session model rather than the entry being retried;\n // when they differ the pin belongs to an unrelated route, so the provider\n // that failed keeps its normal pool handling.\n const targetAccountId = route?.providerId === providerId ? getRouteTargetAccountId(route) : undefined;\n\n if (classification.kind === 'hop') {\n return this.declarePoolUnavailable(args, providerId, 'persistent-outage', targetAccountId !== undefined);\n }\n\n // A12: a targeted route never activates a sibling subscription. A\n // rotate-classified failure on the account the route selected proceeds to\n // the model route instead — the request hops, it does not spill\n // onto another subscription's quota. A targeted route has nothing to\n // rotate to at any pool size. A stale id for an account the user removed\n // reads the same way: fail closed rather than silently land on a sibling.\n if (targetAccountId !== undefined) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted', true);\n }\n\n // Count only this provider's instances: `tried` spans the whole request\n // (a cascade can visit several providers), so comparing its raw size to\n // this provider's account count would falsely report exhaustion when an\n // earlier provider in the same request already used up accounts.\n const tried = getTriedInstances(state);\n if (active) tried.add(active.id);\n // The tried-set is request-global and shared across providers after a\n // route hop, so exhaustion must count only this provider's ids.\n const triedForProvider = accounts.filter(account => tried.has(account.id)).length;\n if (triedForProvider >= accounts.length) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted');\n }\n\n // If the selected account was removed elsewhere, even a single survivor\n // is still untried. Resume from the freshly loaded global cursor.\n const candidates = active ? accounts : orderAccountsFromActive(accounts, store.getActiveAccount?.(providerId)?.id);\n const nextCandidate = candidates.find(account => !tried.has(account.id));\n\n const next = nextCandidate ? store.activateAccount?.(providerId, nextCandidate.id) : undefined;\n if (!next) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted');\n }\n setRequestAccountSelection(args.requestContext, providerId, next.id);\n tried.add(next.id);\n\n // The retry replays the request from scratch (Q10): rotate the assistant\n // message id so the retried response starts a fresh message instead of\n // appending to the partially streamed one.\n args.rotateResponseMessageId?.();\n\n await emitAccountSwitchPart(args, {\n provider: providerId,\n from: active ? { id: active.id, label: active.label } : null,\n to: { id: next.id, label: next.label },\n reason: classification.reason,\n at: new Date().toISOString(),\n });\n\n return { retry: true };\n }\n\n /**\n * Resolve the request's host-supplied model route once and cache the reachable\n * prefix in processor state. Resolution mirrors getDynamicModel so a hop is\n * never announced for an entry core cannot reach.\n */\n private async getModelRouteCascade(args: ProcessAPIErrorArgs): Promise<ModelRouteCascade | null> {\n if (args.state.modelRouteCascade !== undefined) {\n return (args.state.modelRouteCascade as ModelRouteCascade | null) ?? null;\n }\n const controller = getRoutingController(args);\n const state = controller?.getState?.();\n const route = (\n state?.modelRoute as\n | { entries?: Array<{ id?: unknown; label?: unknown; modelId?: unknown; accountId?: unknown }> }\n | undefined\n )?.entries;\n const pending = state?.mastracodePendingModelFallback as\n | { toEntryId?: unknown; threadId?: unknown }\n | null\n | undefined;\n const pendingMatchesThread =\n pending &&\n typeof pending.toEntryId === 'string' &&\n (pending.threadId === undefined || pending.threadId === controller?.threadId);\n const startIndex = pendingMatchesThread\n ? route?.findIndex(entry => entry.id === pending.toEntryId)\n : route?.findIndex(entry => entry.modelId === controller?.session?.modelId);\n if (!route || startIndex === undefined || startIndex < 0 || route.length - startIndex < 2) {\n args.state.modelRouteCascade = null;\n return null;\n }\n\n const thinkingLevel = resolveRequestThinkingLevel(\n {\n state: { thinkingLevel: state?.thinkingLevel },\n session: { modeId: typeof controller?.session?.modeId === 'string' ? controller.session.modeId : 'build' },\n },\n this.options.settingsPath,\n );\n const entries: ModelRouteCascade['entries'] = [];\n for (const entry of route.slice(startIndex)) {\n if (\n typeof entry.id !== 'string' ||\n entry.id.length === 0 ||\n typeof entry.modelId !== 'string' ||\n entry.modelId.length === 0\n ) {\n break;\n }\n if (entries.length > 0) {\n try {\n resolveModel(entry.modelId, {\n thinkingLevel,\n remapForCodexOAuth: true,\n requestContext: args.requestContext,\n });\n } catch {\n break;\n }\n }\n entries.push({\n id: entry.id,\n label: typeof entry.label === 'string' && entry.label.length > 0 ? entry.label : entry.id,\n modelId: entry.modelId,\n ...(typeof entry.accountId === 'string' ? { accountId: entry.accountId } : {}),\n });\n }\n if (entries.length < 2) {\n args.state.modelRouteCascade = null;\n return null;\n }\n const cascade: ModelRouteCascade = { entries, position: 0 };\n args.state.modelRouteCascade = cascade;\n return cascade;\n }\n\n /**\n * Q14 gate: 400/unknown/unattributable errors never advance routes. Core's\n * fallback array advances on ANY thrown error except TripWire\n * (llm-execution-step.ts), so when the session route has another entry and the\n * current entry is non-last, a bare `retry: false` would silently hop on an\n * error class the user excluded from hop triggers. TripWire is the only\n * no-advance escape: the runner rethrows it and the fallback loop declines\n * to advance. No chain (or already on the last entry) → return and let the\n * plain `retry: false` surface the error exactly as before.\n */\n private async gateChainHop(args: ProcessAPIErrorArgs, error: unknown): Promise<void> {\n const cascade = await this.getModelRouteCascade(args);\n if (!cascade) return;\n if (cascade.position >= cascade.entries.length - 1) return;\n const reason = error instanceof Error ? error.message : String(error);\n throw new TripWire(reason, {}, this.id);\n }\n\n /** Announce and persist the next model-route hop before core advances. */\n private async emitModelFallbackPart(\n args: ProcessAPIErrorArgs,\n reason: 'pool-exhausted' | 'persistent-outage',\n ): Promise<void> {\n const cascade = await this.getModelRouteCascade(args);\n if (!cascade) return;\n\n const from = cascade.entries[cascade.position];\n const to = cascade.entries[cascade.position + 1];\n if (!from || !to) return;\n\n const controller = args.requestContext?.get('controller') as\n | {\n threadId?: unknown;\n setState?: (updates: Record<string, unknown>) => Promise<void>;\n setThreadSetting?: (setting: { key: string; value: unknown }) => Promise<void>;\n emitEvent?: (event: { type: 'info'; message: string }) => void;\n }\n | undefined;\n const at = new Date().toISOString();\n const threadId = typeof controller?.threadId === 'string' ? controller.threadId : undefined;\n const pending = {\n fromEntryId: from.id,\n toEntryId: to.id,\n toModelId: to.modelId,\n ...(threadId ? { threadId } : {}),\n reason,\n at,\n } satisfies PendingModelFallback;\n await controller?.setThreadSetting?.({ key: MODEL_FALLBACK_STATE_KEY, value: pending });\n const data: ModelFallbackPartData = {\n from: { entryId: from.id, label: from.label },\n to: { entryId: to.id, label: to.label },\n reason,\n at,\n };\n try {\n await args.writer?.custom({ type: MODEL_FALLBACK_PART_TYPE, data });\n } catch (error) {\n await controller?.setThreadSetting?.({ key: MODEL_FALLBACK_STATE_KEY, value: undefined });\n throw error;\n }\n await controller?.setState?.({ [MODEL_FALLBACK_STATE_KEY]: pending });\n args.state.startNoticeEmitted = false;\n const targetRoute = resolveAccountRoute(args, to);\n if (targetRoute) {\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n await applyPreferredAccountRoute(args, store, targetRoute).catch(() => undefined);\n }\n controller?.emitEvent?.({ type: 'info', message: modelFallbackNoticeText(data) });\n cascade.position++;\n }\n\n /**\n * Pool done for this request: every instance recorded in the tried-set and\n * a `to: null` part emitted. Returns `retry: false` so core's fallback\n * array can hop to the next route entry (announced by `emitModelFallbackPart`).\n * Silent on the account part when the provider has no registry at all —\n * there are no accounts to declare unavailable, but the hop still applies.\n */\n private async declarePoolUnavailable(\n args: ProcessAPIErrorArgs,\n providerId: string,\n reason: 'pool-exhausted' | 'persistent-outage',\n exclusive = false,\n ): Promise<{ retry: boolean }> {\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n const accounts = store.listAccounts?.(providerId) ?? [];\n if (accounts.length === 0) {\n // No account registry (API-key-only provider): nothing rotated, but the\n // hop still happens — announce the model fallback before core advances.\n await this.emitModelFallbackPart(args, reason);\n return { retry: false };\n }\n\n const tried = getTriedInstances(args.state);\n for (const account of accounts) tried.add(account.id);\n\n const active = getRequestActiveAccount(args, store, providerId);\n\n await emitAccountSwitchPart(args, {\n provider: providerId,\n from: active ? { id: active.id, label: active.label } : null,\n to: null,\n reason,\n at: new Date().toISOString(),\n ...(exclusive ? { exclusive: true } : {}),\n });\n\n await this.emitModelFallbackPart(args, reason);\n\n return { retry: false };\n }\n}\n\n/**\n * Applies the pack's preferred account route once per request, before the\n * first model call. Without a route it only reloads the credential store so\n * the request sees accounts changed by other processes.\n *\n * TYPE CONSTRAINT: this class must NEVER implement `processAPIError`. It\n * lives in `inputProcessors`; the runner walks input processors first when\n * running `processAPIError`, so adding the method here would classify and\n * rotate errors BEFORE `StreamErrorRetryProcessor`'s transient retries run.\n */\nexport class AccountStartNoticeProcessor implements Processor {\n readonly id = 'mastracode-account-start-notice' as const;\n\n constructor(private readonly options: { credentialStore: CredentialStore }) {}\n\n async processInput(args: ProcessInputArgs): Promise<ProcessInputResult> {\n if (args.state.startNoticeEmitted) return args.messageList;\n\n // Deployed requests resolve the tenant-scoped store so host accounts are\n // never listed, activated, or announced for a tenant run. Local mode keeps\n // the constructor's storage.\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n\n const route = resolveAccountRoute(args);\n if (!route) {\n store.reload();\n return args.messageList;\n }\n\n // A14: a route whose accounts are all unavailable is not a pre-emptive\n // abort. `applyPreferredAccountRoute` marks the provider on the request so\n // credential reads fail closed rather than falling through to the active\n // account, and the request then runs and fails at the provider — which is\n // what lets the error lane classify it and hand the turn to the route's\n // configured fallback chain. The removed persisted set threw here instead,\n // before any socket opened, so the chain was never reached.\n const switched = await applyPreferredAccountRoute(args, store, route);\n if (switched || getRouteTargetAccountId(route) !== undefined) {\n args.state.startNoticeEmitted = true;\n }\n return args.messageList;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"account-rotation-processor.js","names":[],"sources":["../../src/auth/account-rotation-processor.ts"],"sourcesContent":["/**\n * Error-driven rotation across a provider's OAuth account registry.\n *\n * When the active account cannot serve a request (rate limit, quota\n * exhaustion, dead token, persistent outage), the next account in insertion\n * order is selected for the request and the request retried. OAuth fetch\n * wrappers read that request-scoped selection on every HTTP attempt, so\n * concurrent runs can't swap each other's credentials. Every switch is\n * persisted as a non-transient `data-mastracode-account-switch` part so it\n * shows in the transcript and survives history reload.\n *\n * Two classes, two lanes — the split is load-bearing. The processor runner\n * walks `[...inputProcessors, ...outputProcessors, ...errorProcessors]` when\n * running `processAPIError`, so a processor registered in the input lane that\n * also implemented `processAPIError` would run BEFORE\n * `StreamErrorRetryProcessor` and hop on 5xx errors before transient retries\n * had a chance. `AccountRotationProcessor` therefore implements\n * `processAPIError` only and is registered in `errorProcessors` after\n * `StreamErrorRetryProcessor`; `AccountStartNoticeProcessor` implements\n * `processInput` only and is registered in `inputProcessors`.\n */\nimport { TripWire } from '@mastra/core/agent';\nimport type { ProcessAPIErrorArgs, ProcessInputArgs, ProcessInputResult, Processor } from '@mastra/core/processors';\n\nimport { resolveCredentialStore } from '../agents/credential-resolver.js';\nimport { resolveModel, resolveRequestThinkingLevel } from '../agents/model.js';\nimport { getCredentialProviderId, normalizeLegacyModelId } from '../providers/model-ids.js';\nimport {\n getRequestAccountSelection,\n isRequestAccountRoutingExhausted,\n markRequestAccountRoutingExhausted,\n setRequestAccountSelection,\n} from './account-routing-context.js';\nimport { ProviderAuthRequiredError, PROVIDER_AUTH_REQUIRED_ERROR } from './provider-auth-error.js';\nimport { getOAuthProviders } from './storage.js';\nimport type { CredentialStore, OAuthAccountRecord } from './types.js';\n\nexport const ACCOUNT_SWITCH_PART_TYPE = 'data-mastracode-account-switch';\n\n/** Labels and instance ids only — never token material (do-not list). */\nexport interface AccountSwitchPartData {\n provider: string;\n from: { id: string; label: string } | null;\n to: { id: string; label: string } | null;\n reason:\n | 'rate-limit'\n | 'quota-exhausted'\n | 'auth-failed'\n | 'pool-exhausted'\n | 'persistent-outage'\n | 'preferred-routing';\n at: string;\n /**\n * Set on a `to: null` part when a route pinned to a single account reported\n * that account unusable. The provider's other subscriptions were never\n * consulted (A12), so \"all accounts unavailable\" would misreport what\n * happened.\n */\n exclusive?: boolean;\n}\n\n/** The store surface rotation needs; `AuthStorage` satisfies it structurally. */\nexport type RotationCredentialStore = CredentialStore & {\n /**\n * Force one refresh of the active account's tokens regardless of expiry\n * (401/403 path — a rejected-but-unexpired token usually means clock skew\n * or a refresh race). `AuthStorage` provides it; deployed per-tenant stores\n * don't, and the auth path degrades to plain rotation (which itself no-ops\n * without registry methods).\n */\n forceRefreshActiveAccount?(providerId: string, accountInstanceId?: string): Promise<string | undefined>;\n};\n\n/** Rotatable providers = the OAuth provider registry; API-key-only providers never rotate. */\nconst KNOWN_PROVIDER_IDS = new Set(getOAuthProviders().map(provider => provider.id));\n\n/** Hostname suffixes → provider ids, for `APICallError.url`. */\nconst PROVIDER_HOST_PATTERNS: Array<[RegExp, string]> = [\n [/(^|\\.)api\\.anthropic\\.com$/i, 'anthropic'],\n [/(^|\\.)api\\.kimi\\.com$/i, 'kimi-for-coding'],\n [/(^|\\.)api\\.x\\.ai$/i, 'xai'],\n [/(^|\\.)chatgpt\\.com$/i, 'openai-codex'],\n [/(^|\\.)backend-api\\.codex\\.ai$/i, 'openai-codex'],\n [/(^|\\.)githubcopilot\\.com$/i, 'github-copilot'],\n [/(^|\\.)api\\.github\\.com$/i, 'github-copilot'],\n];\n\n/**\n * Provider usage/quota-limit wording (locked Q7 bucket 1). Statuses 429/402\n * are matched before this; some providers deliver quota errors as plain 400s,\n * which only the message reveals. Deliberately restricted to unambiguous\n * quota phrases — a generic \"exceeded your … limit\" can appear on non-quota\n * client errors (request size, context length) that must never rotate.\n */\nconst USAGE_LIMIT_MESSAGE_PATTERN =\n /usage (?:limit|cap)|insufficient[_ ]quota|quota (?:exceeded|exhausted|reached)|exceeded (?:your )?current quota|insufficient (?:balance|credits?)|credits? exhausted|rate limit (?:exceeded|reached)|weekly limit|monthly limit/i;\n\nconst NETWORK_ERROR_CODES = new Set([\n 'ECONNRESET',\n 'EPIPE',\n 'ECONNREFUSED',\n 'ENOTFOUND',\n 'ETIMEDOUT',\n 'EAI_AGAIN',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n]);\n\nconst NETWORK_MESSAGE_PATTERN =\n /fetch failed|network error|socket hang up|getaddrinfo|econnrefused|enotfound|etimedout|connection (?:refused|reset|closed|timed out)|other side closed|write epipe/i;\n\nconst MAX_CAUSE_DEPTH = 4;\n\n/** Persisted data-part type for a model-route hop. */\nexport const MODEL_FALLBACK_PART_TYPE = 'data-mastracode-model-fallback' as const;\n\n/** Non-secret transcript payload for a model-route hop. */\nexport interface ModelFallbackPartData {\n from: { entryId: string; label: string };\n to: { entryId: string; label: string };\n reason: 'pool-exhausted' | 'persistent-outage';\n at: string;\n}\n\n/** Session-state key set when account rotation advances the model route. */\nexport const MODEL_FALLBACK_STATE_KEY = 'mastracodePendingModelFallback' as const;\n\n/** Payload written to session state under MODEL_FALLBACK_STATE_KEY. */\nexport interface PendingModelFallback {\n fromEntryId: string;\n toEntryId: string;\n toModelId: string;\n threadId?: string;\n reason: 'pool-exhausted' | 'persistent-outage';\n at: string;\n}\n\nexport type RotationClassification =\n | { kind: 'rotate'; reason: 'rate-limit' | 'quota-exhausted' | 'auth-failed' }\n | { kind: 'hop' }\n | { kind: 'never' };\n\nfunction isErrorWithCode(error: unknown): error is { code?: unknown } {\n return typeof error === 'object' && error !== null && 'code' in error;\n}\n\nfunction readErrorStatus(error: unknown): number | undefined {\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (typeof current !== 'object') break;\n const { statusCode, status } = current as { statusCode?: unknown; status?: unknown };\n if (typeof statusCode === 'number') return statusCode;\n if (typeof status === 'number') return status;\n current = (current as { cause?: unknown }).cause;\n }\n return undefined;\n}\n\nfunction isProviderAuthRequiredError(error: unknown): boolean {\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (\n current instanceof ProviderAuthRequiredError ||\n (current instanceof Error && current.name === PROVIDER_AUTH_REQUIRED_ERROR)\n ) {\n return true;\n }\n if (typeof current !== 'object') break;\n current = (current as { cause?: unknown }).cause;\n }\n return false;\n}\n\n/** Concatenate the error's message with its cause chain (bounded). */\nfunction collectErrorText(error: unknown): string {\n const parts: string[] = [];\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (current instanceof Error) {\n parts.push(current.message);\n current = current.cause;\n } else if (typeof current === 'object' && 'message' in current) {\n const message = (current as { message?: unknown }).message;\n if (typeof message === 'string') parts.push(message);\n current = (current as { cause?: unknown }).cause;\n } else {\n break;\n }\n }\n return parts.join(' | ');\n}\n\nfunction isNetworkErrorLike(error: unknown): boolean {\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (\n isErrorWithCode(current) &&\n typeof current.code === 'string' &&\n NETWORK_ERROR_CODES.has(current.code.toUpperCase())\n ) {\n return true;\n }\n if (current instanceof Error) {\n if (NETWORK_MESSAGE_PATTERN.test(current.message)) return true;\n current = current.cause;\n } else if (typeof current === 'object') {\n current = (current as { cause?: unknown }).cause;\n } else {\n break;\n }\n }\n return false;\n}\n\n/**\n * The locked Q7 taxonomy. 429/402/usage-limit rotate immediately (429 is not\n * covered by any transient matcher, so it arrives here on first occurrence);\n * 401/403 and `ProviderAuthRequiredError` rotate after one forced refresh;\n * 5xx/network hop only when the transient retry budget is already spent (they\n * reach this processor because `StreamErrorRetryProcessor` declined); 400 and\n * everything unknown never touch the registry.\n */\nexport function classifyRotationError(error: unknown): RotationClassification {\n if (isProviderAuthRequiredError(error)) {\n return { kind: 'rotate', reason: 'auth-failed' };\n }\n\n const status = readErrorStatus(error);\n\n if (status === 429) return { kind: 'rotate', reason: 'rate-limit' };\n if (status === 402) return { kind: 'rotate', reason: 'quota-exhausted' };\n if (status === 401 || status === 403) return { kind: 'rotate', reason: 'auth-failed' };\n if (status !== undefined && status >= 500 && status < 600) return { kind: 'hop' };\n\n // Quota wording only rotates when it co-occurs with a statusless or 400\n // error — the shapes providers actually use for message-only quota\n // failures. Any other status keeps its own classification above (401/403\n // auth, 5xx outage hop, 404/422 never), so limit-ish text riding a\n // non-quota client error can no longer burn the whole pool.\n if (status === undefined || status === 400) {\n if (USAGE_LIMIT_MESSAGE_PATTERN.test(collectErrorText(error))) {\n return { kind: 'rotate', reason: 'quota-exhausted' };\n }\n }\n if (isNetworkErrorLike(error)) return { kind: 'hop' };\n\n return { kind: 'never' };\n}\n\n/** Provider id off a router model id: `mastracode/<provider>/<model>` or `<provider>/<model>`. */\nexport function providerFromModelId(modelId: string): string | undefined {\n const segments = normalizeLegacyModelId(modelId)\n .replace(/^mastracode\\//, '')\n .split('/');\n const providerId = segments[0] ? getCredentialProviderId(segments[0]) : undefined;\n return providerId && KNOWN_PROVIDER_IDS.has(providerId) ? providerId : undefined;\n}\n\nfunction providerFromHost(hostname: string): string | undefined {\n for (const [pattern, providerId] of PROVIDER_HOST_PATTERNS) {\n if (pattern.test(hostname)) return providerId;\n }\n return undefined;\n}\n\n/**\n * Identify the provider an error came from: the request URL's hostname\n * (`APICallError.url`, cause chain included), falling back to the router\n * model id. Unknown → undefined (caller treats as never-rotate).\n */\nexport function providerFromError(error: unknown): string | undefined {\n const causeChain: object[] = [];\n let current: unknown = error;\n for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {\n if (typeof current !== 'object') break;\n causeChain.push(current);\n current = (current as { cause?: unknown }).cause;\n }\n\n // A request URL identifies the provider that actually handled the failed\n // call. Check every supported URL field throughout the cause chain before\n // falling back to router model metadata, which may describe the original\n // session model rather than the active cascade entry.\n for (const item of causeChain) {\n const urls = [\n (item as { url?: unknown }).url,\n (item as { requestURL?: unknown }).requestURL,\n (item as { requestUrl?: unknown }).requestUrl,\n ];\n for (const url of urls) {\n if (typeof url !== 'string') continue;\n try {\n const providerId = providerFromHost(new URL(url).hostname);\n if (providerId) return providerId;\n } catch {\n // Not a parseable URL — try the other fields and causes.\n }\n }\n }\n\n for (const item of causeChain) {\n const modelId = (item as { modelId?: unknown }).modelId;\n if (typeof modelId !== 'string') continue;\n const providerId = providerFromModelId(modelId);\n if (providerId) return providerId;\n }\n return undefined;\n}\n\nfunction getTriedInstances(state: Record<string, unknown>): Set<string> {\n const existing = state.triedInstances;\n if (existing instanceof Set) return existing;\n const created = new Set<string>();\n state.triedInstances = created;\n return created;\n}\n\nfunction getForcedRefreshInstances(state: Record<string, unknown>): Set<string> {\n const existing = state.forcedAuthRefresh;\n if (existing instanceof Set) return existing;\n const created = new Set<string>();\n state.forcedAuthRefresh = created;\n return created;\n}\n\n/** Friendly names for the part/notice copy (display only — never auth data). */\nexport const PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n anthropic: 'Anthropic',\n 'kimi-for-coding': 'Kimi',\n 'openai-codex': 'Codex',\n 'github-copilot': 'GitHub Copilot',\n xai: 'xAI',\n};\n\nconst REASON_TEXT: Record<AccountSwitchPartData['reason'], string> = {\n 'rate-limit': 'rate limit',\n 'quota-exhausted': 'quota exhausted',\n 'auth-failed': 'auth failed',\n 'pool-exhausted': 'pool exhausted',\n 'persistent-outage': 'persistent outage',\n 'preferred-routing': 'subscription routing',\n};\n\n/** Validates untrusted `reason` values (e.g. persisted message parts). */\nexport function isAccountSwitchReason(value: unknown): value is AccountSwitchPartData['reason'] {\n // Own-property check: `in` would accept prototype names like 'toString',\n // which would then interpolate the inherited function into the notice text.\n return typeof value === 'string' && Object.prototype.hasOwnProperty.call(REASON_TEXT, value);\n}\n\n/**\n * One-line transcript copy for a switch part. Shared by the live `info`\n * controller event (the TUI shows controller `info` events as transcript\n * lines, and no message event carries data parts mid-run) and by the TUI's\n * history rendering of the persisted part — one formatter, identical text.\n */\nexport function accountSwitchNoticeText(data: AccountSwitchPartData): string {\n const provider = PROVIDER_DISPLAY_NAMES[data.provider] ?? data.provider;\n const reason = REASON_TEXT[data.reason];\n if (data.to === null) {\n // A12: a targeted route reports *its* account, not the pool — the other\n // subscriptions were never tried, so \"all accounts unavailable\" would be\n // wrong and would read as if the pool had been walked.\n if (data.exclusive) {\n return `Pinned ${provider} account unavailable (${reason})`;\n }\n return `All ${provider} accounts unavailable (${reason})`;\n }\n const from = data.from?.label ?? 'unknown';\n return `Switched ${provider} account: ${from} → ${data.to.label} (${reason})`;\n}\n\n/** Transcript line for a model-route hop; shared by the live info event and the TUI's history render. */\nexport function modelFallbackNoticeText(data: ModelFallbackPartData): string {\n const reason = REASON_TEXT[data.reason] ?? data.reason;\n return `Switched model route: ${data.from.label} → ${data.to.label} (${reason})`;\n}\n\n/**\n * Whether a persisted model-fallback reason is one this build understands.\n * `REASON_TEXT` also covers the account-switch reasons, so the notice text\n * alone would happily render a foreign value such as `rate-limit` as a\n * model-fallback reason; history parsed from another build's part must be\n * rejected instead.\n */\nexport function isModelFallbackReason(value: unknown): value is ModelFallbackPartData['reason'] {\n return value === 'pool-exhausted' || value === 'persistent-outage';\n}\n\nasync function emitAccountSwitchPart(\n args: Pick<ProcessAPIErrorArgs, 'writer' | 'requestContext'> | Pick<ProcessInputArgs, 'writer' | 'requestContext'>,\n data: AccountSwitchPartData,\n): Promise<void> {\n // Non-transient on purpose (contrast the transient tool-progress parts):\n // switches must persist into the assistant message and render from history.\n await args.writer?.custom({\n type: ACCOUNT_SWITCH_PART_TYPE,\n data,\n });\n // Data parts surface in the TUI only on history reload; emit the same line\n // as a controller `info` event so the switch is visible live (precedent:\n // emitTransientRetry emits controller events from the retry matcher).\n const controllerContext = args.requestContext?.get('controller') as\n | { emitEvent?: (event: { type: 'info'; message: string }) => void }\n | undefined;\n controllerContext?.emitEvent?.({ type: 'info', message: accountSwitchNoticeText(data) });\n}\n\n/**\n * Provider of the model the controller session is currently running — the\n * fallback identification path for errors that carry neither a request URL\n * nor a model id (e.g. `ProviderAuthRequiredError` thrown by a fetch wrapper\n * before any HTTP request exists).\n */\nfunction providerFromSession(\n args: Pick<ProcessAPIErrorArgs, 'requestContext'> | Pick<ProcessInputArgs, 'requestContext'>,\n): string | undefined {\n const controller = args.requestContext?.get('controller') as { session?: { modelId?: unknown } } | undefined;\n const modelId = controller?.session?.modelId;\n return typeof modelId === 'string' ? providerFromModelId(modelId) : undefined;\n}\n\n/**\n * Rotates the active OAuth account on classified API errors.\n *\n * Registered in `errorProcessors` AFTER `StreamErrorRetryProcessor` — a\n * transient error reaching this processor means the transient budget is\n * spent, which is exactly the hop condition. Implements `processAPIError`\n * ONLY (see the file header for the runner-order rationale).\n */\n/** Cached per-request model route cascade. */\ninterface ModelRouteCascade {\n entries: Array<{ id: string; label: string; modelId: string; accountId?: string }>;\n position: number;\n}\n\ninterface AccountRoute {\n entryId: string;\n modelId: string;\n providerId: string;\n accountId?: string;\n}\n\ntype RoutingProcessorArgs = Pick<ProcessAPIErrorArgs, 'requestContext' | 'state' | 'writer'> | ProcessInputArgs;\n\nexport type RoutingControllerContext = {\n session?: { modelId?: unknown; modeId?: unknown };\n threadId?: unknown;\n getState?: () => Record<string, unknown>;\n setState?: (updates: Record<string, unknown>) => Promise<void>;\n getThreadSetting?: (key: string) => Promise<unknown>;\n setThreadSetting?: (setting: { key: string; value: unknown }) => Promise<void>;\n isThreadActive?: () => boolean;\n};\n\nfunction getRoutingController(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n): RoutingControllerContext | undefined {\n return args.requestContext?.get('controller') as RoutingControllerContext | undefined;\n}\n\n/** A targeted route is exclusive; an entry without accountId uses automatic pool rotation. */\nfunction getRouteTargetAccountId(route: AccountRoute | null): string | undefined {\n return route?.accountId;\n}\n\nfunction getRequestActiveAccount(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n store: CredentialStore,\n providerId: string,\n) {\n if (isRequestAccountRoutingExhausted(args.requestContext, providerId)) return undefined;\n const selectedId = getRequestAccountSelection(args.requestContext, providerId);\n if (selectedId) return store.listAccounts?.(providerId).find(account => account.id === selectedId);\n return store.getActiveAccount?.(providerId) ?? store.listAccounts?.(providerId).find(account => account.active);\n}\n\nfunction resolveAccountRoute(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n explicit?: { id: string; modelId: string; accountId?: string },\n): AccountRoute | null {\n const controller = getRoutingController(args);\n const state = controller?.getState?.();\n const pending = state?.mastracodePendingModelFallback as\n | { toEntryId?: unknown; toModelId?: unknown; threadId?: unknown }\n | null\n | undefined;\n const pendingMatchesThread =\n pending &&\n typeof pending.toEntryId === 'string' &&\n typeof pending.toModelId === 'string' &&\n (pending.threadId === undefined || pending.threadId === controller?.threadId);\n const routeEntries = Array.isArray((state?.modelRoute as { entries?: unknown } | undefined)?.entries)\n ? ((state?.modelRoute as { entries: Array<{ id?: unknown; modelId?: unknown; accountId?: unknown }> }).entries ??\n [])\n : [];\n const entry = explicit\n ? explicit\n : pendingMatchesThread\n ? routeEntries.find(candidate => candidate.id === pending.toEntryId && candidate.modelId === pending.toModelId)\n : routeEntries.find(candidate => candidate.modelId === controller?.session?.modelId);\n if (!entry || typeof entry.id !== 'string' || typeof entry.modelId !== 'string') return null;\n const providerId = providerFromModelId(entry.modelId);\n if (!providerId) return null;\n return {\n entryId: entry.id,\n modelId: entry.modelId,\n providerId,\n ...(typeof entry.accountId === 'string' ? { accountId: entry.accountId } : {}),\n };\n}\n\n/**\n * A14: `Automatic` rotation starts *at* the persisted active account and walks\n * insertion order from there, wrapping once. The active account is the cursor\n * rotation last left at, so a turn that starts at index 0 regardless would\n * re-activate and re-try an account the previous turn already moved past —\n * which is the whole job the removed sticky-exclusion set was doing badly.\n * With no persisted active account the order is plain insertion order.\n */\nfunction orderAccountsFromActive(accounts: OAuthAccountRecord[], activeId: string | undefined) {\n const index = activeId === undefined ? -1 : accounts.findIndex(account => account.id === activeId);\n if (index <= 0) return accounts;\n return [...accounts.slice(index), ...accounts.slice(0, index)];\n}\n\nasync function applyPreferredAccountRoute(\n args: RoutingProcessorArgs,\n store: CredentialStore,\n route: AccountRoute,\n): Promise<boolean> {\n store.reload();\n const preferredId = getRouteTargetAccountId(route);\n const accounts = store.listAccounts?.(route.providerId) ?? [];\n if (accounts.length === 0 && preferredId === undefined) return false;\n const tried = getTriedInstances(args.state);\n const unavailable = new Set(accounts.filter(account => tried.has(account.id)).map(account => account.id));\n const active = getRequestActiveAccount(args, store, route.providerId) ?? store.getActiveAccount?.(route.providerId);\n const selected = // A12: a targeted route may use only the account it names. `Automatic`\n // keeps insertion order with full pool rotation (A10), starting from the\n // account the cursor already points at (A14).\n (\n preferredId\n ? accounts.filter(account => account.id === preferredId)\n : orderAccountsFromActive(accounts, active?.id)\n ).find(account => !unavailable.has(account.id));\n\n for (const accountId of unavailable) tried.add(accountId);\n if (!selected) {\n markRequestAccountRoutingExhausted(args.requestContext, route.providerId);\n return false;\n }\n\n if (active?.id === selected.id) {\n setRequestAccountSelection(args.requestContext, route.providerId, selected.id);\n return false;\n }\n // Activate before recording the request-scoped selection: a failed\n // activation must not leave the selection claiming an account that never\n // became the provider's active credential.\n const activated = store.activateAccount ? store.activateAccount(route.providerId, selected.id) : selected;\n if (!activated) {\n // Only a targeted route is out of options here. The exhausted marker is\n // request-wide and fails every credential read closed, so setting it for\n // `Automatic` would stop error recovery from retrying on a survivor.\n if (preferredId !== undefined) markRequestAccountRoutingExhausted(args.requestContext, route.providerId);\n return false;\n }\n setRequestAccountSelection(args.requestContext, route.providerId, activated.id);\n\n // Applying an explicit account choice is routine, not a failover notice.\n if (preferredId === undefined) {\n await emitAccountSwitchPart(args, {\n provider: route.providerId,\n from: active ? { id: active.id, label: active.label } : null,\n to: { id: activated.id, label: activated.label },\n reason: 'preferred-routing',\n at: new Date().toISOString(),\n });\n }\n return true;\n}\n\nexport class AccountRotationProcessor implements Processor {\n readonly id = 'mastracode-account-rotation' as const;\n\n constructor(\n private readonly options: {\n credentialStore: RotationCredentialStore;\n maxProcessorRetries: number;\n settingsPath?: string;\n /** The host has no modes, so per-mode thinking defaults never apply. */\n modeless?: boolean;\n },\n ) {}\n\n async processAPIError(args: ProcessAPIErrorArgs): Promise<{ retry: boolean }> {\n const { error, state } = args;\n\n // Core runs error processors even after the shared retry budget is spent\n // and silently discards `retry: true` then (llm-execution-step.ts\n // canRetryError) — rotating the cursor or emitting a switch part for a\n // retry that will never run would lie to both auth.json and the\n // transcript. With a fallback chain configured, a bare `retry: false`\n // here would silently advance the route for an error we never classified, so the\n // chain gate surfaces the error instead.\n if (args.retryCount >= this.options.maxProcessorRetries) {\n await this.gateChainHop(args, error);\n return { retry: false };\n }\n\n // Classify before attributing: an error from a provider outside the OAuth\n // registry (API-key/router providers are valid route entries) still hops on\n // rotate/hop classes — there is just no account cursor to advance.\n const classification = classifyRotationError(error);\n // Q14: 400/unknown errors never rotate and never advance routes. With a chain\n // active, core's fallback array would still advance on a bare\n // `retry: false` (it advances on any thrown non-TripWire error), so the\n // gate converts that into a surfaced error instead of a silent hop.\n if (classification.kind === 'never') {\n await this.gateChainHop(args, error);\n return { retry: false };\n }\n\n const cascade = await this.getModelRouteCascade(args);\n const currentEntry = cascade?.entries[cascade.position];\n const providerId =\n providerFromError(error) ??\n (currentEntry ? providerFromModelId(currentEntry.modelId) : undefined) ??\n (cascade ? undefined : providerFromSession(args));\n if (!providerId) {\n await this.emitModelFallbackPart(args, classification.kind === 'hop' ? 'persistent-outage' : 'pool-exhausted');\n return { retry: false };\n }\n\n // Deployed requests resolve the tenant-scoped store; local mode keeps the\n // host storage. Reading the host registry here would rotate or announce\n // host accounts for a tenant request. Annotated as the rotation surface:\n // `resolveCredentialStore` returns a plain CredentialStore, and the 401\n // path below uses the optional `forceRefreshActiveAccount` when the host\n // provides it.\n const store: RotationCredentialStore = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n store.reload();\n const accounts = store.listAccounts?.(providerId) ?? [];\n const active = getRequestActiveAccount(args, store, providerId);\n const route = currentEntry ? resolveAccountRoute(args, currentEntry) : resolveAccountRoute(args);\n\n // Q7 bucket 2: force one refresh of the active instance before rotating.\n // A 401 usually means a fresh-but-rejected token; the forced refresh\n // covers server-side clock skew and refresh-token races. Success retries\n // the same account — no rotation, no part. With no active account (e.g.\n // `ProviderAuthRequiredError` from an empty registry) a refresh can never\n // succeed, so skip it and fall through to the retry:false surface below,\n // which rethrows the original auth error instead of a generic pool\n // exhaustion message.\n if (classification.kind === 'rotate' && classification.reason === 'auth-failed' && active) {\n const forced = getForcedRefreshInstances(state);\n const refreshKey = `${providerId}:${active?.id ?? 'active'}`;\n if (!forced.has(refreshKey) && typeof store.forceRefreshActiveAccount === 'function') {\n forced.add(refreshKey);\n const token = await store.forceRefreshActiveAccount(providerId, active?.id);\n if (token !== undefined) {\n return { retry: true };\n }\n }\n }\n\n // A12: only a pin on the provider that actually failed makes this failure\n // exclusive. `route.providerId` is derived from the route's model while\n // `providerId` comes from the failed request's URL, which during a cascade\n // can still describe the session model rather than the entry being retried;\n // when they differ the pin belongs to an unrelated route, so the provider\n // that failed keeps its normal pool handling.\n const targetAccountId = route?.providerId === providerId ? getRouteTargetAccountId(route) : undefined;\n\n if (classification.kind === 'hop') {\n return this.declarePoolUnavailable(args, providerId, 'persistent-outage', targetAccountId !== undefined);\n }\n\n // A12: a targeted route never activates a sibling subscription. A\n // rotate-classified failure on the account the route selected proceeds to\n // the model route instead — the request hops, it does not spill\n // onto another subscription's quota. A targeted route has nothing to\n // rotate to at any pool size. A stale id for an account the user removed\n // reads the same way: fail closed rather than silently land on a sibling.\n if (targetAccountId !== undefined) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted', true);\n }\n\n // Count only this provider's instances: `tried` spans the whole request\n // (a cascade can visit several providers), so comparing its raw size to\n // this provider's account count would falsely report exhaustion when an\n // earlier provider in the same request already used up accounts.\n const tried = getTriedInstances(state);\n if (active) tried.add(active.id);\n // The tried-set is request-global and shared across providers after a\n // route hop, so exhaustion must count only this provider's ids.\n const triedForProvider = accounts.filter(account => tried.has(account.id)).length;\n if (triedForProvider >= accounts.length) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted');\n }\n\n // If the selected account was removed elsewhere, even a single survivor\n // is still untried. Resume from the freshly loaded global cursor.\n const candidates = active ? accounts : orderAccountsFromActive(accounts, store.getActiveAccount?.(providerId)?.id);\n const nextCandidate = candidates.find(account => !tried.has(account.id));\n\n const next = nextCandidate ? store.activateAccount?.(providerId, nextCandidate.id) : undefined;\n if (!next) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted');\n }\n setRequestAccountSelection(args.requestContext, providerId, next.id);\n tried.add(next.id);\n\n // The retry replays the request from scratch (Q10): rotate the assistant\n // message id so the retried response starts a fresh message instead of\n // appending to the partially streamed one.\n args.rotateResponseMessageId?.();\n\n await emitAccountSwitchPart(args, {\n provider: providerId,\n from: active ? { id: active.id, label: active.label } : null,\n to: { id: next.id, label: next.label },\n reason: classification.reason,\n at: new Date().toISOString(),\n });\n\n return { retry: true };\n }\n\n /**\n * Resolve the request's host-supplied model route once and cache the reachable\n * prefix in processor state. Resolution mirrors getDynamicModel so a hop is\n * never announced for an entry core cannot reach.\n */\n private async getModelRouteCascade(args: ProcessAPIErrorArgs): Promise<ModelRouteCascade | null> {\n if (args.state.modelRouteCascade !== undefined) {\n return (args.state.modelRouteCascade as ModelRouteCascade | null) ?? null;\n }\n const controller = getRoutingController(args);\n const state = controller?.getState?.();\n const route = (\n state?.modelRoute as\n | { entries?: Array<{ id?: unknown; label?: unknown; modelId?: unknown; accountId?: unknown }> }\n | undefined\n )?.entries;\n const pending = state?.mastracodePendingModelFallback as\n | { toEntryId?: unknown; threadId?: unknown }\n | null\n | undefined;\n const pendingMatchesThread =\n pending &&\n typeof pending.toEntryId === 'string' &&\n (pending.threadId === undefined || pending.threadId === controller?.threadId);\n const startIndex = pendingMatchesThread\n ? route?.findIndex(entry => entry.id === pending.toEntryId)\n : route?.findIndex(entry => entry.modelId === controller?.session?.modelId);\n if (!route || startIndex === undefined || startIndex < 0 || route.length - startIndex < 2) {\n args.state.modelRouteCascade = null;\n return null;\n }\n\n const thinkingLevel = resolveRequestThinkingLevel(\n {\n state: { thinkingLevel: state?.thinkingLevel },\n ...(!this.options.modeless && {\n session: { modeId: typeof controller?.session?.modeId === 'string' ? controller.session.modeId : 'build' },\n }),\n },\n this.options.settingsPath,\n );\n const entries: ModelRouteCascade['entries'] = [];\n for (const entry of route.slice(startIndex)) {\n if (\n typeof entry.id !== 'string' ||\n entry.id.length === 0 ||\n typeof entry.modelId !== 'string' ||\n entry.modelId.length === 0\n ) {\n break;\n }\n if (entries.length > 0) {\n try {\n resolveModel(entry.modelId, {\n thinkingLevel,\n remapForCodexOAuth: true,\n requestContext: args.requestContext,\n });\n } catch {\n break;\n }\n }\n entries.push({\n id: entry.id,\n label: typeof entry.label === 'string' && entry.label.length > 0 ? entry.label : entry.id,\n modelId: entry.modelId,\n ...(typeof entry.accountId === 'string' ? { accountId: entry.accountId } : {}),\n });\n }\n if (entries.length < 2) {\n args.state.modelRouteCascade = null;\n return null;\n }\n const cascade: ModelRouteCascade = { entries, position: 0 };\n args.state.modelRouteCascade = cascade;\n return cascade;\n }\n\n /**\n * Q14 gate: 400/unknown/unattributable errors never advance routes. Core's\n * fallback array advances on ANY thrown error except TripWire\n * (llm-execution-step.ts), so when the session route has another entry and the\n * current entry is non-last, a bare `retry: false` would silently hop on an\n * error class the user excluded from hop triggers. TripWire is the only\n * no-advance escape: the runner rethrows it and the fallback loop declines\n * to advance. No chain (or already on the last entry) → return and let the\n * plain `retry: false` surface the error exactly as before.\n */\n private async gateChainHop(args: ProcessAPIErrorArgs, error: unknown): Promise<void> {\n const cascade = await this.getModelRouteCascade(args);\n if (!cascade) return;\n if (cascade.position >= cascade.entries.length - 1) return;\n const reason = error instanceof Error ? error.message : String(error);\n throw new TripWire(reason, {}, this.id);\n }\n\n /** Announce and persist the next model-route hop before core advances. */\n private async emitModelFallbackPart(\n args: ProcessAPIErrorArgs,\n reason: 'pool-exhausted' | 'persistent-outage',\n ): Promise<void> {\n const cascade = await this.getModelRouteCascade(args);\n if (!cascade) return;\n\n const from = cascade.entries[cascade.position];\n const to = cascade.entries[cascade.position + 1];\n if (!from || !to) return;\n\n const controller = args.requestContext?.get('controller') as\n | {\n threadId?: unknown;\n setState?: (updates: Record<string, unknown>) => Promise<void>;\n setThreadSetting?: (setting: { key: string; value: unknown }) => Promise<void>;\n emitEvent?: (event: { type: 'info'; message: string }) => void;\n }\n | undefined;\n const at = new Date().toISOString();\n const threadId = typeof controller?.threadId === 'string' ? controller.threadId : undefined;\n const pending = {\n fromEntryId: from.id,\n toEntryId: to.id,\n toModelId: to.modelId,\n ...(threadId ? { threadId } : {}),\n reason,\n at,\n } satisfies PendingModelFallback;\n await controller?.setThreadSetting?.({ key: MODEL_FALLBACK_STATE_KEY, value: pending });\n const data: ModelFallbackPartData = {\n from: { entryId: from.id, label: from.label },\n to: { entryId: to.id, label: to.label },\n reason,\n at,\n };\n try {\n await args.writer?.custom({ type: MODEL_FALLBACK_PART_TYPE, data });\n } catch (error) {\n await controller?.setThreadSetting?.({ key: MODEL_FALLBACK_STATE_KEY, value: undefined });\n throw error;\n }\n await controller?.setState?.({ [MODEL_FALLBACK_STATE_KEY]: pending });\n args.state.startNoticeEmitted = false;\n const targetRoute = resolveAccountRoute(args, to);\n if (targetRoute) {\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n await applyPreferredAccountRoute(args, store, targetRoute).catch(() => undefined);\n }\n controller?.emitEvent?.({ type: 'info', message: modelFallbackNoticeText(data) });\n cascade.position++;\n }\n\n /**\n * Pool done for this request: every instance recorded in the tried-set and\n * a `to: null` part emitted. Returns `retry: false` so core's fallback\n * array can hop to the next route entry (announced by `emitModelFallbackPart`).\n * Silent on the account part when the provider has no registry at all —\n * there are no accounts to declare unavailable, but the hop still applies.\n */\n private async declarePoolUnavailable(\n args: ProcessAPIErrorArgs,\n providerId: string,\n reason: 'pool-exhausted' | 'persistent-outage',\n exclusive = false,\n ): Promise<{ retry: boolean }> {\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n const accounts = store.listAccounts?.(providerId) ?? [];\n if (accounts.length === 0) {\n // No account registry (API-key-only provider): nothing rotated, but the\n // hop still happens — announce the model fallback before core advances.\n await this.emitModelFallbackPart(args, reason);\n return { retry: false };\n }\n\n const tried = getTriedInstances(args.state);\n for (const account of accounts) tried.add(account.id);\n\n const active = getRequestActiveAccount(args, store, providerId);\n\n await emitAccountSwitchPart(args, {\n provider: providerId,\n from: active ? { id: active.id, label: active.label } : null,\n to: null,\n reason,\n at: new Date().toISOString(),\n ...(exclusive ? { exclusive: true } : {}),\n });\n\n await this.emitModelFallbackPart(args, reason);\n\n return { retry: false };\n }\n}\n\n/**\n * Applies the pack's preferred account route once per request, before the\n * first model call. Without a route it only reloads the credential store so\n * the request sees accounts changed by other processes.\n *\n * TYPE CONSTRAINT: this class must NEVER implement `processAPIError`. It\n * lives in `inputProcessors`; the runner walks input processors first when\n * running `processAPIError`, so adding the method here would classify and\n * rotate errors BEFORE `StreamErrorRetryProcessor`'s transient retries run.\n */\nexport class AccountStartNoticeProcessor implements Processor {\n readonly id = 'mastracode-account-start-notice' as const;\n\n constructor(private readonly options: { credentialStore: CredentialStore }) {}\n\n async processInput(args: ProcessInputArgs): Promise<ProcessInputResult> {\n if (args.state.startNoticeEmitted) return args.messageList;\n\n // Deployed requests resolve the tenant-scoped store so host accounts are\n // never listed, activated, or announced for a tenant run. Local mode keeps\n // the constructor's storage.\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n\n const route = resolveAccountRoute(args);\n if (!route) {\n store.reload();\n return args.messageList;\n }\n\n // A14: a route whose accounts are all unavailable is not a pre-emptive\n // abort. `applyPreferredAccountRoute` marks the provider on the request so\n // credential reads fail closed rather than falling through to the active\n // account, and the request then runs and fails at the provider — which is\n // what lets the error lane classify it and hand the turn to the route's\n // configured fallback chain. The removed persisted set threw here instead,\n // before any socket opened, so the chain was never reached.\n const switched = await applyPreferredAccountRoute(args, store, route);\n if (switched || getRouteTargetAccountId(route) !== undefined) {\n args.state.startNoticeEmitted = true;\n }\n return args.messageList;\n }\n}\n"],"mappings":"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"}
|
package/dist/auth/storage.js
CHANGED
|
@@ -20,7 +20,7 @@ const PROVIDER_DEFAULT_MODELS = {
|
|
|
20
20
|
anthropic: "anthropic/claude-fable-5",
|
|
21
21
|
"openai-codex": "openai/gpt-5.6-sol",
|
|
22
22
|
"github-copilot": "github-copilot/gpt-4.1",
|
|
23
|
-
"kimi-for-coding": "kimi-
|
|
23
|
+
"kimi-for-coding": "kimi-code-plan-cn/kimi-for-coding",
|
|
24
24
|
xai: "xai/grok-4.5"
|
|
25
25
|
};
|
|
26
26
|
const oauthProviderRegistry = /* @__PURE__ */ new Map([
|
package/dist/auth/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","names":[],"sources":["../../src/auth/storage.ts"],"sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n */\n\nimport { createHash } from 'node:crypto';\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppDataDir } from '../utils/project.js';\nimport { anthropicOAuthProvider } from './providers/anthropic.js';\nimport { githubCopilotOAuthProvider } from './providers/github-copilot.js';\nimport { kimiCodingOAuthProvider } from './providers/kimi-coding.js';\nimport { openaiCodexOAuthProvider } from './providers/openai-codex.js';\nimport { xaiOAuthProvider } from './providers/xai.js';\nimport type {\n AuthCredential,\n AuthStorageData,\n OAuthAccountRecord,\n OAuthCredential,\n OAuthCredentialSnapshot,\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderId,\n OAuthProviderInterface,\n} from './types.js';\n\n/**\n * Best/default models for each OAuth provider.\n * Used when auto-selecting a model after login.\n */\nexport const PROVIDER_DEFAULT_MODELS: Record<OAuthProviderId, string> = {\n anthropic: 'anthropic/claude-fable-5',\n 'openai-codex': 'openai/gpt-5.6-sol',\n // gpt-4.1 routes through `/chat/completions` (which our OpenAI-compatible\n // adapter handles); Anthropic-shaped Copilot models (Claude on `/v1/messages`)\n // are not yet wired up, so picking one as the post-login default would error.\n 'github-copilot': 'github-copilot/gpt-4.1',\n 'kimi-for-coding': 'kimi-for-coding/kimi-for-coding',\n xai: 'xai/grok-4.5',\n};\n\n// Provider registry\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>([\n [anthropicOAuthProvider.id, anthropicOAuthProvider],\n [openaiCodexOAuthProvider.id, openaiCodexOAuthProvider],\n [githubCopilotOAuthProvider.id, githubCopilotOAuthProvider],\n [kimiCodingOAuthProvider.id, kimiCodingOAuthProvider],\n [xaiOAuthProvider.id, xaiOAuthProvider],\n]);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n return oauthProviderRegistry.get(id);\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n return Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * Mint an account instance id: `${providerId}:${randomUUID()}`.\n *\n * Assigned once, when the account is created, and never derived from\n * credentials. Deriving it from the refresh token (the pre-A13 scheme) looked\n * stable but was not: `persistRefreshedCredential` keeps an entry's key while\n * writing rotated tokens onto it, so every refresh left the id describing a\n * credential the account no longer had — and re-adding that same subscription\n * then failed to match its own entry and registered a second one for it.\n */\nfunction mintAccountId(providerId: string): string {\n return `${providerId}:${globalThis.crypto.randomUUID()}`;\n}\n\n/**\n * The pre-A13 id scheme: `${providerId}:${sha256(refresh).slice(0,8)}`.\n *\n * No longer used to assign ids, but still needed to recognize registries\n * written before A13 — both to match a re-authorized account onto its existing\n * entry, and to recognize entries whose fields the A13 migration must backfill.\n */\nfunction legacyAccountIdFor(providerId: string, refreshToken: string): string {\n return `${providerId}:${createHash('sha256').update(refreshToken).digest('hex').slice(0, 8)}`;\n}\n\n/**\n * Stable identity for the *subscription* behind an account, when the provider\n * exposes one (an account id, an email — anything that survives a refresh and\n * a re-authorization). Used to decide whether an added account is one we\n * already have. Providers whose credentials carry no such identifier return\n * undefined; for those, a re-authorization is indistinguishable from a new\n * account without spending a request.\n */\nfunction providerIdentity(provider: OAuthProviderInterface | undefined, creds: OAuthCredentials): string | undefined {\n try {\n const identity = provider?.getAccountIdentity?.(creds);\n return typeof identity === 'string' && identity.length > 0 ? identity : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction isOAuthAccountRecord(value: unknown): value is OAuthAccountRecord {\n if (!value || typeof value !== 'object') return false;\n const v = value as Partial<OAuthAccountRecord>;\n return (\n v.type === 'oauth-account' &&\n typeof v.id === 'string' &&\n typeof v.label === 'string' &&\n typeof v.addedAt === 'string' &&\n typeof v.active === 'boolean' &&\n typeof v.refresh === 'string' &&\n typeof v.access === 'string' &&\n typeof v.expires === 'number'\n );\n}\n\n/** The credential fields of an account record — everything but registry identity. */\nfunction credentialFieldsOf(record: OAuthAccountRecord): OAuthCredentials {\n const {\n type: _type,\n id: _id,\n label: _label,\n addedAt: _addedAt,\n active: _active,\n identity: _identity,\n ...creds\n } = record;\n return creds;\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n private data: AuthStorageData = {};\n private refreshPromises = new Map<string, Promise<OAuthCredentials | undefined>>();\n\n constructor(private authPath: string = join(getAppDataDir(), 'auth.json')) {\n this.reload();\n }\n\n /**\n * Reload credentials from disk.\n */\n reload(): void {\n if (!existsSync(this.authPath)) {\n this.data = {};\n return;\n }\n try {\n this.data = JSON.parse(readFileSync(this.authPath, 'utf-8'));\n } catch {\n this.data = {};\n }\n this.migrate();\n }\n\n /**\n * Bring auth.json up to the multi-account registry format. Runs on every\n * load; saves only when something actually changed.\n *\n * 1. A legacy OAuth slot with no registry gets one adopted active entry\n * (the slot's tokens stay put — the entry mirrors them).\n * 2. When the slot's refresh token differs from the active entry's, an\n * external writer (older build, another worktree) owns the slot — the\n * slot wins and its tokens are adopted onto the active entry.\n * 3. A registry with no active entry (hand-edited) self-heals to its first\n * entry; malformed `accounts:` values are skipped, never fatal.\n * 4. A registry whose legacy slot disappeared (partial write, an external\n * hand-edit) self-heals by mirroring the active entry's tokens back into\n * the slot, so `isLoggedIn()`/`getOAuthCredential()` keep agreeing with\n * `listAccounts()`. `remove()`/`logout()` clear the registry with the\n * slot, so they never leave the shape this heals.\n */\n private migrate(): void {\n let changed = false;\n for (const key of Object.keys(this.data)) {\n const slot = this.data[key];\n if (!slot || slot.type !== 'oauth') continue;\n const providerId = key;\n const entries = this.accountEntries(providerId);\n if (entries.length === 0) {\n this.adoptSlot(providerId, slot);\n changed = true;\n continue;\n }\n const active = entries.find(entry => entry.active);\n if (!active) {\n this.data[this.accountKeyFor(entries[0]!.id)] = { ...entries[0]!, active: true };\n changed = true;\n continue;\n }\n if (active.refresh !== slot.refresh) {\n // An external writer (older build, another worktree) owns the slot.\n // If its tokens belong to a different registered account, that\n // account is now the active one — re-point activation to it instead\n // of cloning the refresh token onto the current active entry.\n const matching = entries.find(entry => entry.id !== active.id && entry.refresh === slot.refresh);\n if (matching) {\n const { type: _t, ...slotCreds } = slot;\n this.data[this.accountKeyFor(matching.id)] = {\n ...matching,\n ...slotCreds,\n type: 'oauth-account',\n active: true,\n };\n this.data[this.accountKeyFor(active.id)] = { ...active, active: false };\n changed = true;\n } else {\n const { type: _type, ...slotCreds } = slot;\n this.data[this.accountKeyFor(active.id)] = { ...active, ...slotCreds, type: 'oauth-account' };\n changed = true;\n }\n }\n }\n\n // Case 4: registry entries without a legacy slot (the slot loop above\n // only ever sees providers that still have one).\n const registeredProviders = new Set<string>();\n for (const key of Object.keys(this.data)) {\n if (!key.startsWith('accounts:')) continue;\n const providerId = key.slice('accounts:'.length).split(':')[0];\n if (providerId) registeredProviders.add(providerId);\n }\n for (const providerId of registeredProviders) {\n if (this.data[providerId]?.type === 'oauth') continue;\n const entries = this.accountEntries(providerId);\n if (entries.length === 0) continue;\n let active = entries.find(entry => entry.active);\n if (!active) {\n active = entries[0]!;\n this.data[this.accountKeyFor(active.id)] = { ...active, active: true };\n }\n this.data[providerId] = { type: 'oauth', ...credentialFieldsOf(active) };\n changed = true;\n }\n\n // Case 5 (A13): backfill the stable account identity on entries that\n // predate it, so adding a subscription we already hold updates its entry\n // instead of registering a second one for it.\n //\n // Ids are deliberately left exactly as they are. Ids are minted once and\n // derive from nothing (A13), so there is nothing to repair — and re-keying\n // a live registry would invalidate every id persisted outside this file\n // (settings routing preferences, per-thread routing state) for no gain.\n for (const providerId of registeredProviders) {\n const provider = getOAuthProvider(providerId as OAuthProviderId);\n if (!provider?.getAccountIdentity) continue;\n for (const entry of this.accountEntries(providerId)) {\n if (entry.identity) continue;\n const identity = providerIdentity(provider, entry);\n if (!identity) continue;\n this.data[this.accountKeyFor(entry.id)] = { ...entry, identity };\n changed = true;\n }\n }\n\n if (changed) this.save();\n }\n\n /**\n * Save credentials to disk.\n */\n private save(): void {\n const dir = dirname(this.authPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n // Write to a sibling file, then atomically replace auth.json. Provider\n // fetch wrappers reload this file on every request, including in older\n // Mastra Code processes; writing in place lets them observe truncated JSON\n // and temporarily treat every provider as logged out.\n const tempPath = `${this.authPath}.${process.pid}.${globalThis.crypto.randomUUID()}.tmp`;\n try {\n writeFileSync(tempPath, JSON.stringify(this.data, null, 2), { encoding: 'utf-8', mode: 0o600 });\n chmodSync(tempPath, 0o600);\n renameSync(tempPath, this.authPath);\n } finally {\n rmSync(tempPath, { force: true });\n }\n }\n\n /**\n * Get credential for a provider. Registry entries (type 'oauth-account')\n * live under `accounts:` keys and never surface here.\n */\n get(provider: string): AuthCredential | undefined {\n const cred = this.data[provider];\n return cred !== undefined && cred.type !== 'oauth-account' ? cred : undefined;\n }\n\n /**\n * Set credential for a provider.\n */\n set(provider: string, credential: AuthCredential): void {\n this.reload();\n this.data[provider] = credential;\n this.save();\n }\n\n /**\n * Remove credential for a provider, including its account registry.\n * Clearing only the slot would leave the registry behind, and the next\n * load's migration would heal the slot back from it — resurrecting the\n * provider the caller just signed out.\n */\n remove(provider: string): void {\n this.reload();\n delete this.data[provider];\n const prefix = this.accountPrefixFor(provider);\n for (const key of Object.keys(this.data)) {\n if (key.startsWith(prefix)) delete this.data[key];\n }\n this.save();\n }\n\n /**\n * List all providers with credentials.\n */\n list(): string[] {\n return Object.keys(this.data);\n }\n\n /**\n * Check if credentials exist for a provider.\n */\n has(provider: string): boolean {\n return provider in this.data;\n }\n\n /**\n * Check if logged in via OAuth for a provider.\n */\n isLoggedIn(provider: string): boolean {\n const cred = this.data[provider];\n return cred?.type === 'oauth';\n }\n\n /**\n * Check if a stored API key exists for a provider.\n * Keys are stored under `apikey:<provider>` in auth.json.\n */\n hasStoredApiKey(provider: string): boolean {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0;\n }\n\n /**\n * Get a stored API key for a provider, if any.\n */\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0 ? cred.key : undefined;\n }\n\n /**\n * Store an API key for a provider.\n * Also sets the corresponding environment variable so model resolution can find it.\n */\n setStoredApiKey(provider: string, key: string, envVar?: string): void {\n this.set(`apikey:${provider}`, { type: 'api_key', key });\n if (envVar) {\n process.env[envVar] = key;\n }\n }\n\n /**\n * Load all stored API keys into process.env.\n * Called at startup so model resolution can find stored keys.\n * Only sets env vars that aren't already set (env vars take precedence).\n */\n loadStoredApiKeysIntoEnv(providerEnvVars: Record<string, string | undefined>): void {\n for (const [key, cred] of Object.entries(this.data)) {\n if (!key.startsWith('apikey:') || cred.type !== 'api_key' || !cred.key) continue;\n const provider = key.substring('apikey:'.length);\n const envVar = providerEnvVars[provider];\n if (envVar && !process.env[envVar]) {\n process.env[envVar] = cred.key;\n }\n }\n }\n\n /**\n * Login to an OAuth provider. Resolves to the registered account record.\n * Pass `activate: false` to add the account without making it active (the\n * first account for a provider is always activated — a provider with\n * accounts must have an active one).\n */\n async login(\n providerId: OAuthProviderId,\n callbacks: OAuthLoginCallbacks,\n opts?: { replaceAccountId?: string; activate?: boolean },\n ): Promise<OAuthAccountRecord> {\n const provider = getOAuthProvider(providerId);\n if (!provider) {\n throw new Error(`Unknown OAuth provider: ${providerId}`);\n }\n\n const credentials = await provider.login(callbacks);\n // Route through the account registry: the new account is appended (or\n // updated in place — by id collision or an explicit replaceAccountId for\n // re-authentication) and becomes the active account unless activate:false.\n return this.addAccount(providerId, credentials, opts);\n }\n\n /**\n * Logout from a provider: remove the legacy slot and every registered account.\n */\n logout(provider: string): void {\n this.remove(provider);\n }\n\n // ---------------------------------------------------------------------------\n // Multi-account registry\n // ---------------------------------------------------------------------------\n\n private accountPrefixFor(providerId: string): string {\n return `accounts:${providerId}:`;\n }\n\n private accountKeyFor(id: string): string {\n return `accounts:${id}`;\n }\n\n /** Registry entries for a provider in JSON insertion order (malformed values skipped). */\n private accountEntries(providerId: string): OAuthAccountRecord[] {\n const prefix = this.accountPrefixFor(providerId);\n const out: OAuthAccountRecord[] = [];\n for (const [key, value] of Object.entries(this.data)) {\n if (key.startsWith(prefix) && isOAuthAccountRecord(value)) out.push(value);\n }\n return out;\n }\n\n /** Adopt a legacy slot credential as the registry's first active entry. */\n private adoptSlot(providerId: string, slot: OAuthCredential): OAuthAccountRecord {\n const id = mintAccountId(providerId);\n const provider = getOAuthProvider(providerId);\n const identity = providerIdentity(provider, slot);\n const record: OAuthAccountRecord = {\n ...slot,\n type: 'oauth-account',\n id,\n ...(identity ? { identity } : {}),\n label: `${provider?.name ?? providerId} account 1`,\n addedAt: new Date().toISOString(),\n active: true,\n };\n this.data[this.accountKeyFor(id)] = record;\n return record;\n }\n\n /**\n * Registered OAuth accounts for a provider, in insertion order. Copies —\n * callers cannot corrupt unsaved storage state by mutating a record.\n */\n listAccounts(providerId: string): OAuthAccountRecord[] {\n return this.accountEntries(providerId).map(entry => ({ ...entry }));\n }\n\n /**\n * The provider's active registry entry, if a registry exists. A copy.\n */\n getActiveAccount(providerId: string): OAuthAccountRecord | undefined {\n const active = this.accountEntries(providerId).find(entry => entry.active);\n return active ? { ...active } : undefined;\n }\n\n /**\n * Register OAuth credentials as an account for a provider, making it active\n * unless `activate: false` is passed.\n *\n * With `replaceAccountId` (re-authentication of a picked account), the\n * target entry's tokens are replaced in place — id re-keyed to the new\n * refresh-token hash, label/position/addedAt preserved — because providers\n * rotate refresh tokens per authorization, so the picked account's old id\n * never matches the new token hash. Re-authentication keeps the target's\n * active state: an inactive account stays inactive and the currently active\n * account keeps the legacy slot. Otherwise, when the new credentials\n * hash to an existing entry's id (same refresh token), that entry is\n * updated in place; a genuinely new account is appended and activated.\n *\n * `activate: false` only applies to the plain add path: the new/updated entry\n * keeps the active state it had, and the previously active account stays\n * active. The first account of a provider is activated regardless.\n */\n async addAccount(\n providerId: string,\n creds: OAuthCredentials,\n opts?: { label?: string; replaceAccountId?: string; activate?: boolean },\n ): Promise<OAuthAccountRecord> {\n this.reload();\n const entries = this.accountEntries(providerId);\n\n if (opts?.replaceAccountId) {\n const target = entries.find(entry => entry.id === opts.replaceAccountId);\n if (!target) {\n throw new Error(`No account ${opts.replaceAccountId} for provider ${providerId}`);\n }\n const provider = getOAuthProvider(providerId);\n const identity = providerIdentity(provider, creds) ?? target.identity;\n // Re-authentication updates the account in place and **keeps its id**:\n // ids are minted once and never derived from credentials (A13), so the\n // entry's insertion position, the settings routing preference and any\n // thread routing state that name this id all stay valid.\n //\n // A stale entry for the *same* subscription is dropped in favor of the\n // picked account. Same-subscription is a matching stable identity, or —\n // for a registry written before A13, whose ids were the refresh-token\n // hash — the credentials' own legacy id, or an entry already holding\n // exactly these credentials (a re-authorization that returns the same\n // tokens, the only signal an identity-less provider gives). The survivor\n // inherits the collided entry's active state, so re-authenticating an\n // inactive account onto the active account's credentials cannot leave the\n // registry with no active entry.\n const legacyId = legacyAccountIdFor(providerId, creds.refresh);\n const collided = entries.find(\n entry =>\n entry.id !== target.id &&\n ((identity !== undefined && entry.identity === identity) ||\n entry.id === legacyId ||\n entry.refresh === creds.refresh),\n );\n // Credential metadata (device id, enterprise URL, …) follows the tokens:\n // when the fresh response omits a field, the collided entry's value is the\n // coherent fallback, not the target's — pairing fresh tokens with another\n // subscription's stale endpoint would misroute requests. Fresh `creds`\n // win last; only the target's registry metadata (id, label, addedAt)\n // carries over.\n const replacement: OAuthAccountRecord = {\n ...target,\n ...(collided ? credentialFieldsOf(collided) : {}),\n ...creds,\n type: 'oauth-account',\n ...(identity ? { identity } : {}),\n label: opts.label ?? target.label,\n };\n const rebuilt: AuthStorageData = {};\n for (const [key, value] of Object.entries(this.data)) {\n if (key === this.accountKeyFor(target.id)) {\n rebuilt[key] = collided?.active ? { ...replacement, active: true } : replacement;\n } else if (collided && key === this.accountKeyFor(collided.id)) {\n continue;\n } else {\n rebuilt[key] = value;\n }\n }\n this.data = rebuilt;\n // Re-authentication preserves the account's active state: fixing a\n // secondary account's tokens must not hijack the active slot. The target\n // is activated when it was already active, when the entry it collided\n // with was active, or when the provider has no active account at all\n // (first account, or a self-healed gap).\n const wasActive =\n target.active === true ||\n collided?.active === true ||\n entries.some(entry => entry.active && entry.id !== target.id) === false;\n if (wasActive) {\n const activated = this.activateInMemory(providerId, target.id);\n if (!activated) {\n throw new Error(`Failed to activate account ${target.id} for provider ${providerId}`);\n }\n this.save();\n return activated;\n }\n // Inactive target: tokens stay on the registry entry, the legacy slot\n // keeps the currently active account's credential untouched.\n const replacementEntry = this.accountEntries(providerId).find(entry => entry.id === target.id);\n if (!replacementEntry) {\n throw new Error(`Failed to store account ${target.id} for provider ${providerId}`);\n }\n this.save();\n return replacementEntry;\n }\n\n // Resolve the label before the final reload+save: the provider hook may\n // hit the network, and a concurrent write during that await must not be\n // clobbered by a stale snapshot.\n const provider = getOAuthProvider(providerId);\n const identity = providerIdentity(provider, creds);\n const label = opts?.label ?? (await provider?.getAccountLabel?.(creds)) ?? null;\n this.reload();\n const freshEntries = this.accountEntries(providerId);\n\n // Is this a subscription we already hold? Match the provider's stable\n // account identity where it exposes one, then the pre-A13 id (the\n // refresh-token hash) so a pre-A13 registry still updates its entry rather\n // than gaining a second one for the same subscription, and finally an\n // entry already holding these exact credentials (a re-add of the same\n // account as returned, which is the only signal an identity-less provider\n // such as Anthropic gives us — a provider that cannot name its accounts\n // cannot say \"this is the same subscription, re-authorized\").\n //\n // Crucially this must not re-derive the id of a *minted* entry: after a\n // refresh such an entry's id no longer hashes its current token, and\n // treating that miss as \"new account\" is exactly how re-adding a\n // subscription used to create a duplicate entry for it.\n const legacyId = legacyAccountIdFor(providerId, creds.refresh);\n const existing =\n (identity ? freshEntries.find(entry => entry.identity === identity) : undefined) ??\n freshEntries.find(entry => entry.id === legacyId) ??\n freshEntries.find(entry => entry.refresh === creds.refresh);\n const id = existing?.id ?? mintAccountId(providerId);\n if (existing) {\n this.data[this.accountKeyFor(id)] = {\n ...existing,\n ...creds,\n type: 'oauth-account',\n id,\n ...(identity ? { identity } : {}),\n active: existing.active,\n };\n } else {\n const resolvedLabel = label ?? `${provider?.name ?? providerId} account ${freshEntries.length + 1}`;\n this.data[this.accountKeyFor(id)] = {\n type: 'oauth-account',\n id,\n ...(identity ? { identity } : {}),\n label: resolvedLabel,\n addedAt: new Date().toISOString(),\n active: false,\n ...creds,\n };\n }\n\n // activate:false (add-another): keep the current active account. The\n // first account of a provider always activates — a non-empty registry\n // must have an active entry. An id collision with the already-active\n // entry also falls through to activateInMemory so its fresh tokens move\n // into the legacy slot (tokens are single-homed).\n if (opts?.activate === false && freshEntries.length > 0 && !existing?.active) {\n this.save();\n return { ...(this.data[this.accountKeyFor(id)] as OAuthAccountRecord) };\n }\n\n const activated = this.activateInMemory(providerId, id);\n if (!activated) {\n throw new Error(`Failed to activate account ${id} for provider ${providerId}`);\n }\n this.save();\n return activated;\n }\n\n /**\n * Activate an account: move the active tokens out of the legacy slot back\n * into the previously active entry, and the target's tokens into the slot\n * (never copy — one home per token set). With no `instanceId`, rotate to\n * the next entry in insertion order, wrapping once to the front; returns\n * undefined when there is no other entry to rotate to.\n */\n activateAccount(providerId: string, instanceId?: string): OAuthAccountRecord | undefined {\n this.reload();\n const activated = this.activateInMemory(providerId, instanceId);\n if (activated) this.save();\n return activated;\n }\n\n /**\n * Activate an account in memory only — the caller owns reload/save. Move the\n * active tokens out of the legacy slot back into the previously active\n * entry, and the target's tokens into the slot (never copy — one home per\n * token set). With no `instanceId`, rotate to the next entry in insertion\n * order, wrapping once to the front; returns undefined when there is no\n * other entry to rotate to.\n */\n private activateInMemory(providerId: string, instanceId?: string): OAuthAccountRecord | undefined {\n const entries = this.accountEntries(providerId);\n if (entries.length === 0) return undefined;\n\n let target: OAuthAccountRecord | undefined;\n if (instanceId !== undefined) {\n target = entries.find(entry => entry.id === instanceId);\n if (!target) return undefined;\n } else {\n if (entries.length <= 1) return undefined;\n const currentIdx = entries.findIndex(entry => entry.active);\n const candidates =\n currentIdx >= 0 ? [...entries.slice(currentIdx + 1), ...entries.slice(0, currentIdx)] : [...entries];\n target = candidates[0]!;\n }\n if (!target) return undefined;\n\n const slot = this.data[providerId];\n const current = entries.find(entry => entry.active && entry.id !== target.id);\n\n // Move the slot's tokens back onto the previously active entry. Skipped\n // when the target is already active — its (possibly re-authenticated)\n // entry already holds the freshest tokens.\n if (current && slot?.type === 'oauth') {\n const { type: _type, ...slotCreds } = slot;\n this.data[this.accountKeyFor(current.id)] = { ...current, ...slotCreds, type: 'oauth-account', active: false };\n }\n\n // Move the target's tokens into the legacy slot. The slot is rebuilt from\n // the target's own credentials only: carrying fields over from the\n // previously active account would pair this account's tokens with that\n // account's metadata (`deviceId`, `enterpriseUrl`, account id), and the\n // providers read those fields off the slot.\n this.data[providerId] = { ...credentialFieldsOf(target), type: 'oauth' };\n\n // Exactly the target stays active (self-heals multi-active states).\n this.data[this.accountKeyFor(target.id)] = { ...target, active: true };\n for (const entry of entries) {\n if (entry.id === target.id || entry.id === current?.id) continue;\n if (entry.active) {\n this.data[this.accountKeyFor(entry.id)] = { ...entry, active: false };\n }\n }\n\n return { ...target, active: true };\n }\n\n /**\n * Remove an account from the registry. If it was active, the next entry in\n * insertion order is activated; when it was the last one, the legacy slot\n * is removed too (full sign-out for that provider).\n */\n removeAccount(providerId: string, instanceId: string): void {\n this.reload();\n const entries = this.accountEntries(providerId);\n const target = entries.find(entry => entry.id === instanceId);\n if (!target) return;\n\n const wasActive = target.active;\n delete this.data[this.accountKeyFor(instanceId)];\n\n if (wasActive) {\n const idx = entries.indexOf(target);\n const next = entries[idx + 1] ?? entries.find(entry => entry.id !== instanceId);\n if (next) {\n // The removed account's tokens die with it — only move tokens in.\n this.data[providerId] = { type: 'oauth', ...credentialFieldsOf(next) };\n this.data[this.accountKeyFor(next.id)] = { ...next, active: true };\n for (const entry of entries) {\n if (entry.id === next.id || entry.id === instanceId) continue;\n if (entry.active) {\n this.data[this.accountKeyFor(entry.id)] = { ...entry, active: false };\n }\n }\n } else {\n delete this.data[providerId];\n }\n }\n\n this.save();\n }\n\n /**\n * Rename an account's label.\n */\n renameAccount(providerId: string, instanceId: string, label: string): void {\n this.reload();\n const key = this.accountKeyFor(instanceId);\n const entry = this.data[key];\n if (!isOAuthAccountRecord(entry)) return;\n this.data[key] = { ...entry, label };\n this.save();\n }\n\n /**\n * Persist refreshed credentials for the account that initiated the refresh.\n * The legacy slot is updated only if that account is still active when the\n * refresh completes; otherwise a delayed refresh could overwrite a newer\n * activation.\n */\n private persistRefreshedCredential(\n providerId: string,\n instanceId: string | undefined,\n creds: OAuthCredentials,\n ): void {\n this.reload();\n if (instanceId) {\n const key = this.accountKeyFor(instanceId);\n const entry = this.data[key];\n if (!isOAuthAccountRecord(entry)) return;\n this.data[key] = { ...entry, ...creds, type: 'oauth-account' };\n if (entry.active) {\n const slot = this.data[providerId];\n this.data[providerId] =\n slot?.type === 'oauth' ? { ...slot, ...creds, type: 'oauth' } : { type: 'oauth', ...creds };\n }\n } else {\n const slot = this.data[providerId];\n this.data[providerId] =\n slot?.type === 'oauth' ? { ...slot, ...creds, type: 'oauth' } : { type: 'oauth', ...creds };\n }\n this.save();\n }\n\n /**\n * Refresh one account instance through the per-instance dedupe map. Used\n * for sibling refreshes during the rotation walk, so a concurrent\n * `getApiKey` that reloads storage mid-walk (after the candidate was\n * activated but before its refresh resolves) joins the same refresh\n * instead of double-spending a single-use refresh token.\n */\n private async refreshInstance(\n providerId: string,\n instanceId: string,\n creds: OAuthCredentials,\n ): Promise<OAuthCredentials | undefined> {\n const provider = getOAuthProvider(providerId);\n if (!provider) return undefined;\n const refreshKey = `${providerId}:${instanceId}`;\n const pending = this.refreshPromises.get(refreshKey);\n if (pending) return pending;\n const refresh = (async () => {\n try {\n const fresh = await provider.refreshToken(creds);\n this.persistRefreshedCredential(providerId, instanceId, fresh);\n return fresh;\n } catch {\n return undefined;\n }\n })();\n this.refreshPromises.set(refreshKey, refresh);\n try {\n return await refresh;\n } finally {\n this.refreshPromises.delete(refreshKey);\n }\n }\n\n /**\n * Get a ready-to-use OAuth credential snapshot, auto-refreshing if needed.\n * A failed refresh leaves the active account unchanged and returns undefined;\n * the account-rotation processor owns switching so it can persist a visible\n * account-switch notice with the request that triggered the change.\n */\n async getOAuthCredential(\n providerId: string,\n accountInstanceId?: string,\n ): Promise<OAuthCredentialSnapshot | undefined> {\n this.reload();\n const activeEntry = this.getActiveAccount(providerId);\n const selectedEntry = accountInstanceId\n ? this.accountEntries(providerId).find(entry => entry.id === accountInstanceId)\n : activeEntry;\n const slot = this.data[providerId];\n let credential: OAuthCredentials | undefined;\n if (selectedEntry) {\n const {\n type: _type,\n id: _id,\n label: _label,\n addedAt: _addedAt,\n active: _active,\n ...accountCredential\n } = selectedEntry;\n credential = accountCredential;\n // The active account's credentials live in both the legacy slot and its\n // registry entry, and a legacy writer (older build, another worktree) can\n // refresh the slot without rotating the refresh token. `migrate()` only\n // reconciles those when the refresh token changes, so the registry entry\n // can hold stale access/expiry. Prefer the slot for the active account\n // and keep the registry entry as the account identity.\n if (selectedEntry.active && slot?.type === 'oauth' && slot.refresh === selectedEntry.refresh) {\n const { type: _slotType, ...slotCredential } = slot;\n credential = { ...credential, ...slotCredential };\n }\n } else if (!accountInstanceId && slot?.type === 'oauth') {\n credential = slot;\n }\n if (!credential) return undefined;\n\n const provider = getOAuthProvider(providerId);\n if (!provider) return undefined;\n const selectedInstanceId = selectedEntry?.id;\n const toSnapshot = (credentials: OAuthCredentials): OAuthCredentialSnapshot => ({\n type: 'oauth',\n ...credentials,\n accountInstanceId: selectedInstanceId,\n });\n\n if (Date.now() < credential.expires) return toSnapshot(credential);\n\n if (selectedInstanceId) {\n const refreshed = await this.refreshInstance(providerId, selectedInstanceId, credential);\n return refreshed ? toSnapshot(refreshed) : undefined;\n }\n\n const pendingRefresh = this.refreshPromises.get(providerId);\n const refresh =\n pendingRefresh ??\n (async (): Promise<OAuthCredentials | undefined> => {\n try {\n const fresh = await provider.refreshToken(credential);\n this.persistRefreshedCredential(providerId, undefined, fresh);\n return fresh;\n } catch {\n return undefined;\n }\n })();\n if (!pendingRefresh) this.refreshPromises.set(providerId, refresh);\n try {\n const refreshed = await refresh;\n return refreshed ? toSnapshot(refreshed) : undefined;\n } finally {\n if (!pendingRefresh) this.refreshPromises.delete(providerId);\n }\n }\n\n /** Get API key for a provider, refreshing OAuth tokens if needed. */\n async getApiKey(providerId: string, accountInstanceId?: string): Promise<string | undefined> {\n this.reload();\n const cred = this.data[providerId];\n // A provider slot holding an API key belongs to the provider, not to any one\n // subscription, so it is only the right credential for a request that did\n // not select an account. A routed request must resolve the account it asked\n // for — or nothing — rather than be served this key.\n if (accountInstanceId === undefined && cred?.type === 'api_key') return cred.key;\n\n const oauth = await this.getOAuthCredential(providerId, accountInstanceId);\n const provider = oauth ? getOAuthProvider(providerId) : undefined;\n return oauth && provider ? provider.getApiKey(oauth) : undefined;\n }\n\n /**\n * Force one refresh of the active OAuth account's tokens, regardless of\n * expiry. The account-rotation error processor calls this when a provider\n * rejects a not-yet-expired token (401/403) — server-side clock skew and\n * refresh-token races surface that way. Returns the fresh access token, or\n * undefined when the refresh fails or there is no OAuth credential.\n * Shares the per-instance refresh dedupe with `getApiKey`.\n */\n async forceRefreshActiveAccount(providerId: string, accountInstanceId?: string): Promise<string | undefined> {\n this.reload();\n const activeEntry = this.getActiveAccount(providerId);\n const selectedEntry = accountInstanceId\n ? this.accountEntries(providerId).find(entry => entry.id === accountInstanceId)\n : activeEntry;\n const slot = this.get(providerId);\n let cred: OAuthCredentials | undefined;\n if (selectedEntry) {\n const {\n type: _type,\n id: _id,\n label: _label,\n addedAt: _addedAt,\n active: _active,\n ...accountCredential\n } = selectedEntry;\n cred = accountCredential;\n } else if (!accountInstanceId && slot?.type === 'oauth') {\n cred = slot;\n }\n if (!cred) return undefined;\n const provider = getOAuthProvider(providerId);\n if (!provider) return undefined;\n\n const refreshKey = selectedEntry ? `${providerId}:${selectedEntry.id}` : providerId;\n const pending = this.refreshPromises.get(refreshKey);\n const refresh =\n pending ??\n (async (): Promise<OAuthCredentials | undefined> => {\n try {\n const fresh = await provider.refreshToken(cred);\n this.persistRefreshedCredential(providerId, selectedEntry?.id, fresh);\n return fresh;\n } catch {\n return undefined;\n }\n })();\n if (!pending) this.refreshPromises.set(refreshKey, refresh);\n try {\n const creds = await refresh;\n return creds ? provider.getApiKey(creds) : undefined;\n } finally {\n if (!pending) this.refreshPromises.delete(refreshKey);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage.js","names":[],"sources":["../../src/auth/storage.ts"],"sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n */\n\nimport { createHash } from 'node:crypto';\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppDataDir } from '../utils/project.js';\nimport { anthropicOAuthProvider } from './providers/anthropic.js';\nimport { githubCopilotOAuthProvider } from './providers/github-copilot.js';\nimport { kimiCodingOAuthProvider } from './providers/kimi-coding.js';\nimport { openaiCodexOAuthProvider } from './providers/openai-codex.js';\nimport { xaiOAuthProvider } from './providers/xai.js';\nimport type {\n AuthCredential,\n AuthStorageData,\n OAuthAccountRecord,\n OAuthCredential,\n OAuthCredentialSnapshot,\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderId,\n OAuthProviderInterface,\n} from './types.js';\n\n/**\n * Best/default models for each OAuth provider.\n * Used when auto-selecting a model after login.\n */\nexport const PROVIDER_DEFAULT_MODELS: Record<OAuthProviderId, string> = {\n anthropic: 'anthropic/claude-fable-5',\n 'openai-codex': 'openai/gpt-5.6-sol',\n // gpt-4.1 routes through `/chat/completions` (which our OpenAI-compatible\n // adapter handles); Anthropic-shaped Copilot models (Claude on `/v1/messages`)\n // are not yet wired up, so picking one as the post-login default would error.\n 'github-copilot': 'github-copilot/gpt-4.1',\n 'kimi-for-coding': 'kimi-code-plan-cn/kimi-for-coding',\n xai: 'xai/grok-4.5',\n};\n\n// Provider registry\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>([\n [anthropicOAuthProvider.id, anthropicOAuthProvider],\n [openaiCodexOAuthProvider.id, openaiCodexOAuthProvider],\n [githubCopilotOAuthProvider.id, githubCopilotOAuthProvider],\n [kimiCodingOAuthProvider.id, kimiCodingOAuthProvider],\n [xaiOAuthProvider.id, xaiOAuthProvider],\n]);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n return oauthProviderRegistry.get(id);\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n return Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * Mint an account instance id: `${providerId}:${randomUUID()}`.\n *\n * Assigned once, when the account is created, and never derived from\n * credentials. Deriving it from the refresh token (the pre-A13 scheme) looked\n * stable but was not: `persistRefreshedCredential` keeps an entry's key while\n * writing rotated tokens onto it, so every refresh left the id describing a\n * credential the account no longer had — and re-adding that same subscription\n * then failed to match its own entry and registered a second one for it.\n */\nfunction mintAccountId(providerId: string): string {\n return `${providerId}:${globalThis.crypto.randomUUID()}`;\n}\n\n/**\n * The pre-A13 id scheme: `${providerId}:${sha256(refresh).slice(0,8)}`.\n *\n * No longer used to assign ids, but still needed to recognize registries\n * written before A13 — both to match a re-authorized account onto its existing\n * entry, and to recognize entries whose fields the A13 migration must backfill.\n */\nfunction legacyAccountIdFor(providerId: string, refreshToken: string): string {\n return `${providerId}:${createHash('sha256').update(refreshToken).digest('hex').slice(0, 8)}`;\n}\n\n/**\n * Stable identity for the *subscription* behind an account, when the provider\n * exposes one (an account id, an email — anything that survives a refresh and\n * a re-authorization). Used to decide whether an added account is one we\n * already have. Providers whose credentials carry no such identifier return\n * undefined; for those, a re-authorization is indistinguishable from a new\n * account without spending a request.\n */\nfunction providerIdentity(provider: OAuthProviderInterface | undefined, creds: OAuthCredentials): string | undefined {\n try {\n const identity = provider?.getAccountIdentity?.(creds);\n return typeof identity === 'string' && identity.length > 0 ? identity : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction isOAuthAccountRecord(value: unknown): value is OAuthAccountRecord {\n if (!value || typeof value !== 'object') return false;\n const v = value as Partial<OAuthAccountRecord>;\n return (\n v.type === 'oauth-account' &&\n typeof v.id === 'string' &&\n typeof v.label === 'string' &&\n typeof v.addedAt === 'string' &&\n typeof v.active === 'boolean' &&\n typeof v.refresh === 'string' &&\n typeof v.access === 'string' &&\n typeof v.expires === 'number'\n );\n}\n\n/** The credential fields of an account record — everything but registry identity. */\nfunction credentialFieldsOf(record: OAuthAccountRecord): OAuthCredentials {\n const {\n type: _type,\n id: _id,\n label: _label,\n addedAt: _addedAt,\n active: _active,\n identity: _identity,\n ...creds\n } = record;\n return creds;\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n private data: AuthStorageData = {};\n private refreshPromises = new Map<string, Promise<OAuthCredentials | undefined>>();\n\n constructor(private authPath: string = join(getAppDataDir(), 'auth.json')) {\n this.reload();\n }\n\n /**\n * Reload credentials from disk.\n */\n reload(): void {\n if (!existsSync(this.authPath)) {\n this.data = {};\n return;\n }\n try {\n this.data = JSON.parse(readFileSync(this.authPath, 'utf-8'));\n } catch {\n this.data = {};\n }\n this.migrate();\n }\n\n /**\n * Bring auth.json up to the multi-account registry format. Runs on every\n * load; saves only when something actually changed.\n *\n * 1. A legacy OAuth slot with no registry gets one adopted active entry\n * (the slot's tokens stay put — the entry mirrors them).\n * 2. When the slot's refresh token differs from the active entry's, an\n * external writer (older build, another worktree) owns the slot — the\n * slot wins and its tokens are adopted onto the active entry.\n * 3. A registry with no active entry (hand-edited) self-heals to its first\n * entry; malformed `accounts:` values are skipped, never fatal.\n * 4. A registry whose legacy slot disappeared (partial write, an external\n * hand-edit) self-heals by mirroring the active entry's tokens back into\n * the slot, so `isLoggedIn()`/`getOAuthCredential()` keep agreeing with\n * `listAccounts()`. `remove()`/`logout()` clear the registry with the\n * slot, so they never leave the shape this heals.\n */\n private migrate(): void {\n let changed = false;\n for (const key of Object.keys(this.data)) {\n const slot = this.data[key];\n if (!slot || slot.type !== 'oauth') continue;\n const providerId = key;\n const entries = this.accountEntries(providerId);\n if (entries.length === 0) {\n this.adoptSlot(providerId, slot);\n changed = true;\n continue;\n }\n const active = entries.find(entry => entry.active);\n if (!active) {\n this.data[this.accountKeyFor(entries[0]!.id)] = { ...entries[0]!, active: true };\n changed = true;\n continue;\n }\n if (active.refresh !== slot.refresh) {\n // An external writer (older build, another worktree) owns the slot.\n // If its tokens belong to a different registered account, that\n // account is now the active one — re-point activation to it instead\n // of cloning the refresh token onto the current active entry.\n const matching = entries.find(entry => entry.id !== active.id && entry.refresh === slot.refresh);\n if (matching) {\n const { type: _t, ...slotCreds } = slot;\n this.data[this.accountKeyFor(matching.id)] = {\n ...matching,\n ...slotCreds,\n type: 'oauth-account',\n active: true,\n };\n this.data[this.accountKeyFor(active.id)] = { ...active, active: false };\n changed = true;\n } else {\n const { type: _type, ...slotCreds } = slot;\n this.data[this.accountKeyFor(active.id)] = { ...active, ...slotCreds, type: 'oauth-account' };\n changed = true;\n }\n }\n }\n\n // Case 4: registry entries without a legacy slot (the slot loop above\n // only ever sees providers that still have one).\n const registeredProviders = new Set<string>();\n for (const key of Object.keys(this.data)) {\n if (!key.startsWith('accounts:')) continue;\n const providerId = key.slice('accounts:'.length).split(':')[0];\n if (providerId) registeredProviders.add(providerId);\n }\n for (const providerId of registeredProviders) {\n if (this.data[providerId]?.type === 'oauth') continue;\n const entries = this.accountEntries(providerId);\n if (entries.length === 0) continue;\n let active = entries.find(entry => entry.active);\n if (!active) {\n active = entries[0]!;\n this.data[this.accountKeyFor(active.id)] = { ...active, active: true };\n }\n this.data[providerId] = { type: 'oauth', ...credentialFieldsOf(active) };\n changed = true;\n }\n\n // Case 5 (A13): backfill the stable account identity on entries that\n // predate it, so adding a subscription we already hold updates its entry\n // instead of registering a second one for it.\n //\n // Ids are deliberately left exactly as they are. Ids are minted once and\n // derive from nothing (A13), so there is nothing to repair — and re-keying\n // a live registry would invalidate every id persisted outside this file\n // (settings routing preferences, per-thread routing state) for no gain.\n for (const providerId of registeredProviders) {\n const provider = getOAuthProvider(providerId as OAuthProviderId);\n if (!provider?.getAccountIdentity) continue;\n for (const entry of this.accountEntries(providerId)) {\n if (entry.identity) continue;\n const identity = providerIdentity(provider, entry);\n if (!identity) continue;\n this.data[this.accountKeyFor(entry.id)] = { ...entry, identity };\n changed = true;\n }\n }\n\n if (changed) this.save();\n }\n\n /**\n * Save credentials to disk.\n */\n private save(): void {\n const dir = dirname(this.authPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n // Write to a sibling file, then atomically replace auth.json. Provider\n // fetch wrappers reload this file on every request, including in older\n // Mastra Code processes; writing in place lets them observe truncated JSON\n // and temporarily treat every provider as logged out.\n const tempPath = `${this.authPath}.${process.pid}.${globalThis.crypto.randomUUID()}.tmp`;\n try {\n writeFileSync(tempPath, JSON.stringify(this.data, null, 2), { encoding: 'utf-8', mode: 0o600 });\n chmodSync(tempPath, 0o600);\n renameSync(tempPath, this.authPath);\n } finally {\n rmSync(tempPath, { force: true });\n }\n }\n\n /**\n * Get credential for a provider. Registry entries (type 'oauth-account')\n * live under `accounts:` keys and never surface here.\n */\n get(provider: string): AuthCredential | undefined {\n const cred = this.data[provider];\n return cred !== undefined && cred.type !== 'oauth-account' ? cred : undefined;\n }\n\n /**\n * Set credential for a provider.\n */\n set(provider: string, credential: AuthCredential): void {\n this.reload();\n this.data[provider] = credential;\n this.save();\n }\n\n /**\n * Remove credential for a provider, including its account registry.\n * Clearing only the slot would leave the registry behind, and the next\n * load's migration would heal the slot back from it — resurrecting the\n * provider the caller just signed out.\n */\n remove(provider: string): void {\n this.reload();\n delete this.data[provider];\n const prefix = this.accountPrefixFor(provider);\n for (const key of Object.keys(this.data)) {\n if (key.startsWith(prefix)) delete this.data[key];\n }\n this.save();\n }\n\n /**\n * List all providers with credentials.\n */\n list(): string[] {\n return Object.keys(this.data);\n }\n\n /**\n * Check if credentials exist for a provider.\n */\n has(provider: string): boolean {\n return provider in this.data;\n }\n\n /**\n * Check if logged in via OAuth for a provider.\n */\n isLoggedIn(provider: string): boolean {\n const cred = this.data[provider];\n return cred?.type === 'oauth';\n }\n\n /**\n * Check if a stored API key exists for a provider.\n * Keys are stored under `apikey:<provider>` in auth.json.\n */\n hasStoredApiKey(provider: string): boolean {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0;\n }\n\n /**\n * Get a stored API key for a provider, if any.\n */\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0 ? cred.key : undefined;\n }\n\n /**\n * Store an API key for a provider.\n * Also sets the corresponding environment variable so model resolution can find it.\n */\n setStoredApiKey(provider: string, key: string, envVar?: string): void {\n this.set(`apikey:${provider}`, { type: 'api_key', key });\n if (envVar) {\n process.env[envVar] = key;\n }\n }\n\n /**\n * Load all stored API keys into process.env.\n * Called at startup so model resolution can find stored keys.\n * Only sets env vars that aren't already set (env vars take precedence).\n */\n loadStoredApiKeysIntoEnv(providerEnvVars: Record<string, string | undefined>): void {\n for (const [key, cred] of Object.entries(this.data)) {\n if (!key.startsWith('apikey:') || cred.type !== 'api_key' || !cred.key) continue;\n const provider = key.substring('apikey:'.length);\n const envVar = providerEnvVars[provider];\n if (envVar && !process.env[envVar]) {\n process.env[envVar] = cred.key;\n }\n }\n }\n\n /**\n * Login to an OAuth provider. Resolves to the registered account record.\n * Pass `activate: false` to add the account without making it active (the\n * first account for a provider is always activated — a provider with\n * accounts must have an active one).\n */\n async login(\n providerId: OAuthProviderId,\n callbacks: OAuthLoginCallbacks,\n opts?: { replaceAccountId?: string; activate?: boolean },\n ): Promise<OAuthAccountRecord> {\n const provider = getOAuthProvider(providerId);\n if (!provider) {\n throw new Error(`Unknown OAuth provider: ${providerId}`);\n }\n\n const credentials = await provider.login(callbacks);\n // Route through the account registry: the new account is appended (or\n // updated in place — by id collision or an explicit replaceAccountId for\n // re-authentication) and becomes the active account unless activate:false.\n return this.addAccount(providerId, credentials, opts);\n }\n\n /**\n * Logout from a provider: remove the legacy slot and every registered account.\n */\n logout(provider: string): void {\n this.remove(provider);\n }\n\n // ---------------------------------------------------------------------------\n // Multi-account registry\n // ---------------------------------------------------------------------------\n\n private accountPrefixFor(providerId: string): string {\n return `accounts:${providerId}:`;\n }\n\n private accountKeyFor(id: string): string {\n return `accounts:${id}`;\n }\n\n /** Registry entries for a provider in JSON insertion order (malformed values skipped). */\n private accountEntries(providerId: string): OAuthAccountRecord[] {\n const prefix = this.accountPrefixFor(providerId);\n const out: OAuthAccountRecord[] = [];\n for (const [key, value] of Object.entries(this.data)) {\n if (key.startsWith(prefix) && isOAuthAccountRecord(value)) out.push(value);\n }\n return out;\n }\n\n /** Adopt a legacy slot credential as the registry's first active entry. */\n private adoptSlot(providerId: string, slot: OAuthCredential): OAuthAccountRecord {\n const id = mintAccountId(providerId);\n const provider = getOAuthProvider(providerId);\n const identity = providerIdentity(provider, slot);\n const record: OAuthAccountRecord = {\n ...slot,\n type: 'oauth-account',\n id,\n ...(identity ? { identity } : {}),\n label: `${provider?.name ?? providerId} account 1`,\n addedAt: new Date().toISOString(),\n active: true,\n };\n this.data[this.accountKeyFor(id)] = record;\n return record;\n }\n\n /**\n * Registered OAuth accounts for a provider, in insertion order. Copies —\n * callers cannot corrupt unsaved storage state by mutating a record.\n */\n listAccounts(providerId: string): OAuthAccountRecord[] {\n return this.accountEntries(providerId).map(entry => ({ ...entry }));\n }\n\n /**\n * The provider's active registry entry, if a registry exists. A copy.\n */\n getActiveAccount(providerId: string): OAuthAccountRecord | undefined {\n const active = this.accountEntries(providerId).find(entry => entry.active);\n return active ? { ...active } : undefined;\n }\n\n /**\n * Register OAuth credentials as an account for a provider, making it active\n * unless `activate: false` is passed.\n *\n * With `replaceAccountId` (re-authentication of a picked account), the\n * target entry's tokens are replaced in place — id re-keyed to the new\n * refresh-token hash, label/position/addedAt preserved — because providers\n * rotate refresh tokens per authorization, so the picked account's old id\n * never matches the new token hash. Re-authentication keeps the target's\n * active state: an inactive account stays inactive and the currently active\n * account keeps the legacy slot. Otherwise, when the new credentials\n * hash to an existing entry's id (same refresh token), that entry is\n * updated in place; a genuinely new account is appended and activated.\n *\n * `activate: false` only applies to the plain add path: the new/updated entry\n * keeps the active state it had, and the previously active account stays\n * active. The first account of a provider is activated regardless.\n */\n async addAccount(\n providerId: string,\n creds: OAuthCredentials,\n opts?: { label?: string; replaceAccountId?: string; activate?: boolean },\n ): Promise<OAuthAccountRecord> {\n this.reload();\n const entries = this.accountEntries(providerId);\n\n if (opts?.replaceAccountId) {\n const target = entries.find(entry => entry.id === opts.replaceAccountId);\n if (!target) {\n throw new Error(`No account ${opts.replaceAccountId} for provider ${providerId}`);\n }\n const provider = getOAuthProvider(providerId);\n const identity = providerIdentity(provider, creds) ?? target.identity;\n // Re-authentication updates the account in place and **keeps its id**:\n // ids are minted once and never derived from credentials (A13), so the\n // entry's insertion position, the settings routing preference and any\n // thread routing state that name this id all stay valid.\n //\n // A stale entry for the *same* subscription is dropped in favor of the\n // picked account. Same-subscription is a matching stable identity, or —\n // for a registry written before A13, whose ids were the refresh-token\n // hash — the credentials' own legacy id, or an entry already holding\n // exactly these credentials (a re-authorization that returns the same\n // tokens, the only signal an identity-less provider gives). The survivor\n // inherits the collided entry's active state, so re-authenticating an\n // inactive account onto the active account's credentials cannot leave the\n // registry with no active entry.\n const legacyId = legacyAccountIdFor(providerId, creds.refresh);\n const collided = entries.find(\n entry =>\n entry.id !== target.id &&\n ((identity !== undefined && entry.identity === identity) ||\n entry.id === legacyId ||\n entry.refresh === creds.refresh),\n );\n // Credential metadata (device id, enterprise URL, …) follows the tokens:\n // when the fresh response omits a field, the collided entry's value is the\n // coherent fallback, not the target's — pairing fresh tokens with another\n // subscription's stale endpoint would misroute requests. Fresh `creds`\n // win last; only the target's registry metadata (id, label, addedAt)\n // carries over.\n const replacement: OAuthAccountRecord = {\n ...target,\n ...(collided ? credentialFieldsOf(collided) : {}),\n ...creds,\n type: 'oauth-account',\n ...(identity ? { identity } : {}),\n label: opts.label ?? target.label,\n };\n const rebuilt: AuthStorageData = {};\n for (const [key, value] of Object.entries(this.data)) {\n if (key === this.accountKeyFor(target.id)) {\n rebuilt[key] = collided?.active ? { ...replacement, active: true } : replacement;\n } else if (collided && key === this.accountKeyFor(collided.id)) {\n continue;\n } else {\n rebuilt[key] = value;\n }\n }\n this.data = rebuilt;\n // Re-authentication preserves the account's active state: fixing a\n // secondary account's tokens must not hijack the active slot. The target\n // is activated when it was already active, when the entry it collided\n // with was active, or when the provider has no active account at all\n // (first account, or a self-healed gap).\n const wasActive =\n target.active === true ||\n collided?.active === true ||\n entries.some(entry => entry.active && entry.id !== target.id) === false;\n if (wasActive) {\n const activated = this.activateInMemory(providerId, target.id);\n if (!activated) {\n throw new Error(`Failed to activate account ${target.id} for provider ${providerId}`);\n }\n this.save();\n return activated;\n }\n // Inactive target: tokens stay on the registry entry, the legacy slot\n // keeps the currently active account's credential untouched.\n const replacementEntry = this.accountEntries(providerId).find(entry => entry.id === target.id);\n if (!replacementEntry) {\n throw new Error(`Failed to store account ${target.id} for provider ${providerId}`);\n }\n this.save();\n return replacementEntry;\n }\n\n // Resolve the label before the final reload+save: the provider hook may\n // hit the network, and a concurrent write during that await must not be\n // clobbered by a stale snapshot.\n const provider = getOAuthProvider(providerId);\n const identity = providerIdentity(provider, creds);\n const label = opts?.label ?? (await provider?.getAccountLabel?.(creds)) ?? null;\n this.reload();\n const freshEntries = this.accountEntries(providerId);\n\n // Is this a subscription we already hold? Match the provider's stable\n // account identity where it exposes one, then the pre-A13 id (the\n // refresh-token hash) so a pre-A13 registry still updates its entry rather\n // than gaining a second one for the same subscription, and finally an\n // entry already holding these exact credentials (a re-add of the same\n // account as returned, which is the only signal an identity-less provider\n // such as Anthropic gives us — a provider that cannot name its accounts\n // cannot say \"this is the same subscription, re-authorized\").\n //\n // Crucially this must not re-derive the id of a *minted* entry: after a\n // refresh such an entry's id no longer hashes its current token, and\n // treating that miss as \"new account\" is exactly how re-adding a\n // subscription used to create a duplicate entry for it.\n const legacyId = legacyAccountIdFor(providerId, creds.refresh);\n const existing =\n (identity ? freshEntries.find(entry => entry.identity === identity) : undefined) ??\n freshEntries.find(entry => entry.id === legacyId) ??\n freshEntries.find(entry => entry.refresh === creds.refresh);\n const id = existing?.id ?? mintAccountId(providerId);\n if (existing) {\n this.data[this.accountKeyFor(id)] = {\n ...existing,\n ...creds,\n type: 'oauth-account',\n id,\n ...(identity ? { identity } : {}),\n active: existing.active,\n };\n } else {\n const resolvedLabel = label ?? `${provider?.name ?? providerId} account ${freshEntries.length + 1}`;\n this.data[this.accountKeyFor(id)] = {\n type: 'oauth-account',\n id,\n ...(identity ? { identity } : {}),\n label: resolvedLabel,\n addedAt: new Date().toISOString(),\n active: false,\n ...creds,\n };\n }\n\n // activate:false (add-another): keep the current active account. The\n // first account of a provider always activates — a non-empty registry\n // must have an active entry. An id collision with the already-active\n // entry also falls through to activateInMemory so its fresh tokens move\n // into the legacy slot (tokens are single-homed).\n if (opts?.activate === false && freshEntries.length > 0 && !existing?.active) {\n this.save();\n return { ...(this.data[this.accountKeyFor(id)] as OAuthAccountRecord) };\n }\n\n const activated = this.activateInMemory(providerId, id);\n if (!activated) {\n throw new Error(`Failed to activate account ${id} for provider ${providerId}`);\n }\n this.save();\n return activated;\n }\n\n /**\n * Activate an account: move the active tokens out of the legacy slot back\n * into the previously active entry, and the target's tokens into the slot\n * (never copy — one home per token set). With no `instanceId`, rotate to\n * the next entry in insertion order, wrapping once to the front; returns\n * undefined when there is no other entry to rotate to.\n */\n activateAccount(providerId: string, instanceId?: string): OAuthAccountRecord | undefined {\n this.reload();\n const activated = this.activateInMemory(providerId, instanceId);\n if (activated) this.save();\n return activated;\n }\n\n /**\n * Activate an account in memory only — the caller owns reload/save. Move the\n * active tokens out of the legacy slot back into the previously active\n * entry, and the target's tokens into the slot (never copy — one home per\n * token set). With no `instanceId`, rotate to the next entry in insertion\n * order, wrapping once to the front; returns undefined when there is no\n * other entry to rotate to.\n */\n private activateInMemory(providerId: string, instanceId?: string): OAuthAccountRecord | undefined {\n const entries = this.accountEntries(providerId);\n if (entries.length === 0) return undefined;\n\n let target: OAuthAccountRecord | undefined;\n if (instanceId !== undefined) {\n target = entries.find(entry => entry.id === instanceId);\n if (!target) return undefined;\n } else {\n if (entries.length <= 1) return undefined;\n const currentIdx = entries.findIndex(entry => entry.active);\n const candidates =\n currentIdx >= 0 ? [...entries.slice(currentIdx + 1), ...entries.slice(0, currentIdx)] : [...entries];\n target = candidates[0]!;\n }\n if (!target) return undefined;\n\n const slot = this.data[providerId];\n const current = entries.find(entry => entry.active && entry.id !== target.id);\n\n // Move the slot's tokens back onto the previously active entry. Skipped\n // when the target is already active — its (possibly re-authenticated)\n // entry already holds the freshest tokens.\n if (current && slot?.type === 'oauth') {\n const { type: _type, ...slotCreds } = slot;\n this.data[this.accountKeyFor(current.id)] = { ...current, ...slotCreds, type: 'oauth-account', active: false };\n }\n\n // Move the target's tokens into the legacy slot. The slot is rebuilt from\n // the target's own credentials only: carrying fields over from the\n // previously active account would pair this account's tokens with that\n // account's metadata (`deviceId`, `enterpriseUrl`, account id), and the\n // providers read those fields off the slot.\n this.data[providerId] = { ...credentialFieldsOf(target), type: 'oauth' };\n\n // Exactly the target stays active (self-heals multi-active states).\n this.data[this.accountKeyFor(target.id)] = { ...target, active: true };\n for (const entry of entries) {\n if (entry.id === target.id || entry.id === current?.id) continue;\n if (entry.active) {\n this.data[this.accountKeyFor(entry.id)] = { ...entry, active: false };\n }\n }\n\n return { ...target, active: true };\n }\n\n /**\n * Remove an account from the registry. If it was active, the next entry in\n * insertion order is activated; when it was the last one, the legacy slot\n * is removed too (full sign-out for that provider).\n */\n removeAccount(providerId: string, instanceId: string): void {\n this.reload();\n const entries = this.accountEntries(providerId);\n const target = entries.find(entry => entry.id === instanceId);\n if (!target) return;\n\n const wasActive = target.active;\n delete this.data[this.accountKeyFor(instanceId)];\n\n if (wasActive) {\n const idx = entries.indexOf(target);\n const next = entries[idx + 1] ?? entries.find(entry => entry.id !== instanceId);\n if (next) {\n // The removed account's tokens die with it — only move tokens in.\n this.data[providerId] = { type: 'oauth', ...credentialFieldsOf(next) };\n this.data[this.accountKeyFor(next.id)] = { ...next, active: true };\n for (const entry of entries) {\n if (entry.id === next.id || entry.id === instanceId) continue;\n if (entry.active) {\n this.data[this.accountKeyFor(entry.id)] = { ...entry, active: false };\n }\n }\n } else {\n delete this.data[providerId];\n }\n }\n\n this.save();\n }\n\n /**\n * Rename an account's label.\n */\n renameAccount(providerId: string, instanceId: string, label: string): void {\n this.reload();\n const key = this.accountKeyFor(instanceId);\n const entry = this.data[key];\n if (!isOAuthAccountRecord(entry)) return;\n this.data[key] = { ...entry, label };\n this.save();\n }\n\n /**\n * Persist refreshed credentials for the account that initiated the refresh.\n * The legacy slot is updated only if that account is still active when the\n * refresh completes; otherwise a delayed refresh could overwrite a newer\n * activation.\n */\n private persistRefreshedCredential(\n providerId: string,\n instanceId: string | undefined,\n creds: OAuthCredentials,\n ): void {\n this.reload();\n if (instanceId) {\n const key = this.accountKeyFor(instanceId);\n const entry = this.data[key];\n if (!isOAuthAccountRecord(entry)) return;\n this.data[key] = { ...entry, ...creds, type: 'oauth-account' };\n if (entry.active) {\n const slot = this.data[providerId];\n this.data[providerId] =\n slot?.type === 'oauth' ? { ...slot, ...creds, type: 'oauth' } : { type: 'oauth', ...creds };\n }\n } else {\n const slot = this.data[providerId];\n this.data[providerId] =\n slot?.type === 'oauth' ? { ...slot, ...creds, type: 'oauth' } : { type: 'oauth', ...creds };\n }\n this.save();\n }\n\n /**\n * Refresh one account instance through the per-instance dedupe map. Used\n * for sibling refreshes during the rotation walk, so a concurrent\n * `getApiKey` that reloads storage mid-walk (after the candidate was\n * activated but before its refresh resolves) joins the same refresh\n * instead of double-spending a single-use refresh token.\n */\n private async refreshInstance(\n providerId: string,\n instanceId: string,\n creds: OAuthCredentials,\n ): Promise<OAuthCredentials | undefined> {\n const provider = getOAuthProvider(providerId);\n if (!provider) return undefined;\n const refreshKey = `${providerId}:${instanceId}`;\n const pending = this.refreshPromises.get(refreshKey);\n if (pending) return pending;\n const refresh = (async () => {\n try {\n const fresh = await provider.refreshToken(creds);\n this.persistRefreshedCredential(providerId, instanceId, fresh);\n return fresh;\n } catch {\n return undefined;\n }\n })();\n this.refreshPromises.set(refreshKey, refresh);\n try {\n return await refresh;\n } finally {\n this.refreshPromises.delete(refreshKey);\n }\n }\n\n /**\n * Get a ready-to-use OAuth credential snapshot, auto-refreshing if needed.\n * A failed refresh leaves the active account unchanged and returns undefined;\n * the account-rotation processor owns switching so it can persist a visible\n * account-switch notice with the request that triggered the change.\n */\n async getOAuthCredential(\n providerId: string,\n accountInstanceId?: string,\n ): Promise<OAuthCredentialSnapshot | undefined> {\n this.reload();\n const activeEntry = this.getActiveAccount(providerId);\n const selectedEntry = accountInstanceId\n ? this.accountEntries(providerId).find(entry => entry.id === accountInstanceId)\n : activeEntry;\n const slot = this.data[providerId];\n let credential: OAuthCredentials | undefined;\n if (selectedEntry) {\n const {\n type: _type,\n id: _id,\n label: _label,\n addedAt: _addedAt,\n active: _active,\n ...accountCredential\n } = selectedEntry;\n credential = accountCredential;\n // The active account's credentials live in both the legacy slot and its\n // registry entry, and a legacy writer (older build, another worktree) can\n // refresh the slot without rotating the refresh token. `migrate()` only\n // reconciles those when the refresh token changes, so the registry entry\n // can hold stale access/expiry. Prefer the slot for the active account\n // and keep the registry entry as the account identity.\n if (selectedEntry.active && slot?.type === 'oauth' && slot.refresh === selectedEntry.refresh) {\n const { type: _slotType, ...slotCredential } = slot;\n credential = { ...credential, ...slotCredential };\n }\n } else if (!accountInstanceId && slot?.type === 'oauth') {\n credential = slot;\n }\n if (!credential) return undefined;\n\n const provider = getOAuthProvider(providerId);\n if (!provider) return undefined;\n const selectedInstanceId = selectedEntry?.id;\n const toSnapshot = (credentials: OAuthCredentials): OAuthCredentialSnapshot => ({\n type: 'oauth',\n ...credentials,\n accountInstanceId: selectedInstanceId,\n });\n\n if (Date.now() < credential.expires) return toSnapshot(credential);\n\n if (selectedInstanceId) {\n const refreshed = await this.refreshInstance(providerId, selectedInstanceId, credential);\n return refreshed ? toSnapshot(refreshed) : undefined;\n }\n\n const pendingRefresh = this.refreshPromises.get(providerId);\n const refresh =\n pendingRefresh ??\n (async (): Promise<OAuthCredentials | undefined> => {\n try {\n const fresh = await provider.refreshToken(credential);\n this.persistRefreshedCredential(providerId, undefined, fresh);\n return fresh;\n } catch {\n return undefined;\n }\n })();\n if (!pendingRefresh) this.refreshPromises.set(providerId, refresh);\n try {\n const refreshed = await refresh;\n return refreshed ? toSnapshot(refreshed) : undefined;\n } finally {\n if (!pendingRefresh) this.refreshPromises.delete(providerId);\n }\n }\n\n /** Get API key for a provider, refreshing OAuth tokens if needed. */\n async getApiKey(providerId: string, accountInstanceId?: string): Promise<string | undefined> {\n this.reload();\n const cred = this.data[providerId];\n // A provider slot holding an API key belongs to the provider, not to any one\n // subscription, so it is only the right credential for a request that did\n // not select an account. A routed request must resolve the account it asked\n // for — or nothing — rather than be served this key.\n if (accountInstanceId === undefined && cred?.type === 'api_key') return cred.key;\n\n const oauth = await this.getOAuthCredential(providerId, accountInstanceId);\n const provider = oauth ? getOAuthProvider(providerId) : undefined;\n return oauth && provider ? provider.getApiKey(oauth) : undefined;\n }\n\n /**\n * Force one refresh of the active OAuth account's tokens, regardless of\n * expiry. The account-rotation error processor calls this when a provider\n * rejects a not-yet-expired token (401/403) — server-side clock skew and\n * refresh-token races surface that way. Returns the fresh access token, or\n * undefined when the refresh fails or there is no OAuth credential.\n * Shares the per-instance refresh dedupe with `getApiKey`.\n */\n async forceRefreshActiveAccount(providerId: string, accountInstanceId?: string): Promise<string | undefined> {\n this.reload();\n const activeEntry = this.getActiveAccount(providerId);\n const selectedEntry = accountInstanceId\n ? this.accountEntries(providerId).find(entry => entry.id === accountInstanceId)\n : activeEntry;\n const slot = this.get(providerId);\n let cred: OAuthCredentials | undefined;\n if (selectedEntry) {\n const {\n type: _type,\n id: _id,\n label: _label,\n addedAt: _addedAt,\n active: _active,\n ...accountCredential\n } = selectedEntry;\n cred = accountCredential;\n } else if (!accountInstanceId && slot?.type === 'oauth') {\n cred = slot;\n }\n if (!cred) return undefined;\n const provider = getOAuthProvider(providerId);\n if (!provider) return undefined;\n\n const refreshKey = selectedEntry ? `${providerId}:${selectedEntry.id}` : providerId;\n const pending = this.refreshPromises.get(refreshKey);\n const refresh =\n pending ??\n (async (): Promise<OAuthCredentials | undefined> => {\n try {\n const fresh = await provider.refreshToken(cred);\n this.persistRefreshedCredential(providerId, selectedEntry?.id, fresh);\n return fresh;\n } catch {\n return undefined;\n }\n })();\n if (!pending) this.refreshPromises.set(refreshKey, refresh);\n try {\n const creds = await refresh;\n return creds ? provider.getApiKey(creds) : undefined;\n } finally {\n if (!pending) this.refreshPromises.delete(refreshKey);\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,8 +28,12 @@ export interface MastraCodeConfig {
|
|
|
28
28
|
cwd?: string;
|
|
29
29
|
/** Home directory for global config discovery. Default: os.homedir() */
|
|
30
30
|
homeDir?: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Override modes (model IDs, colors, which modes exist). Default: build/plan/fast.
|
|
33
|
+
* `false` runs every session as the single Build agent with no mode switching:
|
|
34
|
+
* threads saved in another mode reopen as Build, and per-mode thinking defaults never apply.
|
|
35
|
+
*/
|
|
36
|
+
modes?: AgentControllerMode[] | false;
|
|
33
37
|
/** Replace subagent definitions. Default: explore/plan/execute. An empty array disables subagents. */
|
|
34
38
|
subagents?: AgentControllerSubagent[];
|
|
35
39
|
/** Extra tools merged into the dynamic tool set. Can be a static record or a (sync or async) function that receives requestContext. */
|