@mastra/code-sdk 1.7.3-alpha.0 → 1.8.0-alpha.10
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/agent-connections/tools.d.ts.map +1 -1
- package/dist/agent-connections/tools.js +26 -8
- package/dist/agent-connections/tools.js.map +1 -1
- package/dist/agents/background-completion-events.d.ts +16 -0
- package/dist/agents/background-completion-events.d.ts.map +1 -0
- package/dist/agents/background-completion-events.js +19 -0
- package/dist/agents/background-completion-events.js.map +1 -0
- package/dist/agents/background-completion.d.ts +9 -0
- package/dist/agents/background-completion.d.ts.map +1 -0
- package/dist/agents/background-completion.js +59 -0
- package/dist/agents/background-completion.js.map +1 -0
- package/dist/agents/mastracode-gateway.d.ts +2 -0
- package/dist/agents/mastracode-gateway.d.ts.map +1 -1
- package/dist/agents/mastracode-gateway.js +29 -10
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/memory.d.ts +1 -1
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/memory.js +32 -25
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/model.d.ts +41 -1
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/model.js +141 -7
- package/dist/agents/model.js.map +1 -1
- package/dist/agents/tool-availability.d.ts +2 -0
- package/dist/agents/tool-availability.d.ts.map +1 -1
- package/dist/agents/tool-availability.js +33 -4
- package/dist/agents/tool-availability.js.map +1 -1
- package/dist/agents/tools.d.ts +1 -1
- package/dist/agents/tools.d.ts.map +1 -1
- package/dist/agents/tools.js +30 -2
- package/dist/agents/tools.js.map +1 -1
- package/dist/agents/workspace.d.ts +2 -1
- package/dist/agents/workspace.d.ts.map +1 -1
- package/dist/agents/workspace.js +3 -3
- package/dist/agents/workspace.js.map +1 -1
- package/dist/auth/account-rotation-processor.d.ts +207 -0
- package/dist/auth/account-rotation-processor.d.ts.map +1 -0
- package/dist/auth/account-rotation-processor.js +669 -0
- package/dist/auth/account-rotation-processor.js.map +1 -0
- package/dist/auth/account-routing-context.d.ts +13 -0
- package/dist/auth/account-routing-context.d.ts.map +1 -0
- package/dist/auth/account-routing-context.js +41 -0
- package/dist/auth/account-routing-context.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/providers/anthropic.d.ts.map +1 -1
- package/dist/auth/providers/anthropic.js +2 -8
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/github-copilot.d.ts.map +1 -1
- package/dist/auth/providers/github-copilot.js +34 -12
- package/dist/auth/providers/github-copilot.js.map +1 -1
- package/dist/auth/providers/kimi-coding.d.ts.map +1 -1
- package/dist/auth/providers/kimi-coding.js +2 -6
- package/dist/auth/providers/kimi-coding.js.map +1 -1
- package/dist/auth/providers/openai-codex.d.ts +3 -1
- package/dist/auth/providers/openai-codex.d.ts.map +1 -1
- package/dist/auth/providers/openai-codex.js +50 -24
- package/dist/auth/providers/openai-codex.js.map +1 -1
- package/dist/auth/providers/xai.d.ts +1 -1
- package/dist/auth/providers/xai.d.ts.map +1 -1
- package/dist/auth/providers/xai.js +36 -16
- package/dist/auth/providers/xai.js.map +1 -1
- package/dist/auth/storage.d.ts +130 -8
- package/dist/auth/storage.d.ts.map +1 -1
- package/dist/auth/storage.js +602 -39
- package/dist/auth/storage.js.map +1 -1
- package/dist/auth/types.d.ts +69 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/goal-signal.d.ts +1 -1
- package/dist/goal-signal.d.ts.map +1 -1
- package/dist/goal-signal.js +1 -1
- package/dist/goal-signal.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -38
- package/dist/index.js.map +1 -1
- package/dist/mcp/config.d.ts +17 -2
- package/dist/mcp/config.d.ts.map +1 -1
- package/dist/mcp/config.js +18 -3
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/manager.d.ts +2 -1
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +13 -8
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/types.d.ts +15 -3
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/onboarding/custom-packs.d.ts.map +1 -1
- package/dist/onboarding/custom-packs.js +3 -0
- package/dist/onboarding/custom-packs.js.map +1 -1
- package/dist/onboarding/index.js +1 -1
- package/dist/onboarding/packs.d.ts +44 -0
- package/dist/onboarding/packs.d.ts.map +1 -1
- package/dist/onboarding/packs.js +80 -2
- package/dist/onboarding/packs.js.map +1 -1
- package/dist/onboarding/settings.d.ts +33 -0
- package/dist/onboarding/settings.d.ts.map +1 -1
- package/dist/onboarding/settings.js +88 -8
- package/dist/onboarding/settings.js.map +1 -1
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +2 -1
- package/dist/plugins/loader.js.map +1 -1
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/claude-max.js +4 -5
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot.d.ts +1 -1
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +89 -37
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/kimi-coding.d.ts.map +1 -1
- package/dist/providers/kimi-coding.js +16 -2
- package/dist/providers/kimi-coding.js.map +1 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +42 -27
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/schema.d.ts +28 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -0
- package/dist/schema.js.map +1 -1
- package/dist/tools/workflows/save-workflow.d.ts +1 -1
- package/dist/tools/workflows/save-workflow.d.ts.map +1 -1
- package/dist/workflows/register-primitives.js +2 -2
- package/dist/workflows/register-primitives.js.map +1 -1
- package/package.json +17 -17
|
@@ -0,0 +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 { listResolvableModePacks, resolveModel, resolveRequestThinkingLevel } from '../agents/model.js';\nimport { resolveModePackFallbackChain } from '../onboarding/packs.js';\nimport { findModePackForModel, loadSettings, resolveModePackModels } from '../onboarding/settings.js';\nimport {\n getRequestAccountSelection,\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 | 'starting-on-account';\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 fallback-pack hop. */\nexport const PACK_FALLBACK_PART_TYPE = 'data-mastracode-pack-fallback' as const;\n\n/**\n * Payload of a pack-fallback part: the pack the cascade is leaving, the pack\n * core's fallback array advances to, and why. Labels and pack ids only —\n * never credential material.\n */\nexport interface PackFallbackPartData {\n from: { packId: string; label: string };\n to: { packId: string; label: string };\n reason: 'pool-exhausted' | 'persistent-outage';\n at: string;\n}\n\n/**\n * Session-state key the processor sets when a pack hop happens. The TUI\n * listens for it on the typed `state_changed` controller event (data parts\n * never ride controller message events) and applies thread stickiness.\n */\nexport const PACK_FALLBACK_STATE_KEY = 'mastracodePendingPackFallback' as const;\n\n/** Payload written to session state under PACK_FALLBACK_STATE_KEY. */\nexport interface PendingPackFallback {\n fromPackId: string;\n toPackId: string;\n /** Landed pack's model for the mode the cascade is serving. */\n toModelId: string;\n /** Originating thread. Absent only on pending hops written by older clients. */\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<Exclude<AccountSwitchPartData['reason'], 'starting-on-account'>, 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 return (\n value === 'starting-on-account' ||\n // Own-property check: `in` would accept prototype names like 'toString',\n // which would then interpolate the inherited function into the notice text.\n (typeof value === 'string' && Object.prototype.hasOwnProperty.call(REASON_TEXT, value))\n );\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 if (data.reason === 'starting-on-account' && data.to) {\n return `Starting on ${provider} account: ${data.to.label}`;\n }\n const reason = data.reason === 'starting-on-account' ? data.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 pack-fallback hop; shared by the live info event and the TUI's history render. */\nexport function packFallbackNoticeText(data: PackFallbackPartData): string {\n const reason = REASON_TEXT[data.reason] ?? data.reason;\n return `Switched model pack: ${data.from.label} → ${data.to.label} (${reason})`;\n}\n\n/**\n * Whether a persisted pack-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 * pack-fallback reason; history parsed from another build's part must be\n * rejected instead.\n */\nexport function isPackFallbackReason(value: unknown): value is PackFallbackPartData['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 pack cascade (state.packCascade). */\ninterface PackCascade {\n packs: Array<{ packId: string; label: string }>;\n models: Record<string, Record<string, string>>;\n modeId: string;\n position: number;\n}\n\ninterface AccountRoute {\n packId: string;\n modelId: string;\n providerId: string;\n}\n\ntype RoutingProcessorArgs = Pick<ProcessAPIErrorArgs, 'requestContext' | 'state' | 'writer'> | ProcessInputArgs;\n\n/**\n * Controller-shaped accessors for a thread's routing metadata. Core supplies this\n * via `requestContext.get('controller')`; the TUI builds the same shape from its\n * session so both writers share one serialization key and one read source.\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/**\n * A12: the account a pack/model route targets, or `undefined` when the route\n * is `Automatic` (no entry — insertion order, full pool rotation).\n *\n * A targeted route is *exclusive*: only that account may serve the route, so\n * rotation must never spill the request onto a sibling subscription's quota.\n * A heavy model on one subscription can burn that subscription's quota in\n * hours; spilling it into a second subscription defeats the point of having\n * one.\n */\nfunction getRouteTargetAccountId(settingsPath: string | undefined, route: AccountRoute | null): string | undefined {\n if (!route) return undefined;\n return loadSettings(settingsPath).models.packAccountPreferences?.[route.packId]?.[route.modelId];\n}\n\nfunction getRequestActiveAccount(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n store: CredentialStore,\n providerId: string,\n) {\n const selectedId = getRequestAccountSelection(args.requestContext, providerId);\n return (\n (selectedId ? store.listAccounts?.(providerId).find(account => account.id === selectedId) : undefined) ??\n store.getActiveAccount?.(providerId) ??\n store.listAccounts?.(providerId).find(account => account.active)\n );\n}\n\nfunction resolveAccountRoute(\n args: Pick<RoutingProcessorArgs, 'requestContext'>,\n settingsPath?: string,\n explicit?: { packId: string; modelId: string },\n): AccountRoute | null {\n const controller = getRoutingController(args);\n const state = controller?.getState?.();\n const pending = state?.mastracodePendingPackFallback as\n | { toPackId?: unknown; toModelId?: unknown; threadId?: unknown }\n | null\n | undefined;\n const pendingMatchesThread =\n pending &&\n typeof pending.toPackId === 'string' &&\n typeof pending.toModelId === 'string' &&\n (pending.threadId === undefined || pending.threadId === controller?.threadId);\n const resolvedModelId =\n explicit?.modelId ??\n (pendingMatchesThread ? pending.toModelId : undefined) ??\n (typeof controller?.session?.modelId === 'string' ? controller.session.modelId : undefined);\n if (typeof resolvedModelId !== 'string' || resolvedModelId.length === 0) return null;\n const modelId = resolvedModelId;\n\n const settings = loadSettings(settingsPath);\n const packs = listResolvableModePacks(settings);\n const modeId =\n typeof controller?.session?.modeId === 'string' && controller.session.modeId.length > 0\n ? controller.session.modeId\n : 'build';\n const explicitPackId =\n explicit?.packId ??\n (pendingMatchesThread ? pending.toPackId : undefined) ??\n (typeof state?.activeModelPackId === 'string' ? state.activeModelPackId : settings.models.activeModelPackId);\n const pack = explicitPackId\n ? packs.find(candidate => candidate.id === explicitPackId)\n : findModePackForModel(settings, packs, modelId, modeId, undefined);\n if (!pack || resolveModePackModels(settings, pack)[modeId] !== modelId) return null;\n\n const providerId = providerFromModelId(modelId);\n return providerId ? { packId: pack.id, modelId, providerId } : null;\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 settingsPath: string | undefined,\n route: AccountRoute,\n): Promise<boolean> {\n const preferredId = getRouteTargetAccountId(settingsPath, route);\n const accounts = store.listAccounts?.(route.providerId) ?? [];\n if (accounts.length === 0) 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);\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?.(route.providerId, selected.id) ?? selected;\n setRequestAccountSelection(args.requestContext, route.providerId, activated.id);\n\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 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 hop packs 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 pack members) 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 hop packs. 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.getPackCascade(args);\n const currentPack = cascade?.packs[cascade.position];\n const cascadeModelId = currentPack ? cascade.models[currentPack.packId]?.[cascade.modeId] : undefined;\n const providerId =\n providerFromError(error) ??\n (typeof cascadeModelId === 'string' ? providerFromModelId(cascadeModelId) : undefined) ??\n (cascade ? undefined : providerFromSession(args));\n if (!providerId) {\n await this.emitPackFallbackPart(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 const accounts = store.listAccounts?.(providerId) ?? [];\n const active = getRequestActiveAccount(args, store, providerId);\n const route =\n currentPack && typeof cascadeModelId === 'string'\n ? resolveAccountRoute(args, this.options.settingsPath, { packId: currentPack.packId, modelId: cascadeModelId })\n : resolveAccountRoute(args, this.options.settingsPath);\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 =\n route?.providerId === providerId ? getRouteTargetAccountId(this.options.settingsPath, 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 pack's fallback chain instead — the request hops, it does not spill\n // onto another subscription's quota. `Automatic` keeps rotating, so this\n // check sits above the pool-size test: 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 // A pool smaller than 2 has nothing to rotate to — it is exhausted by\n // definition once a rotate-classified error arrives. Route through the\n // pool-exhausted path so the notices (and any pack hop) still fire; with\n // no registry at all this announces only a configured pack hop.\n if (accounts.length < 2) {\n return this.declarePoolUnavailable(args, providerId, 'pool-exhausted');\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 // pack 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 const nextCandidate = accounts.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 * The request's pack cascade, computed once from the session's pack and\n * cached in processor state. Truncated at the first pack that lacks the\n * session mode's model — mirroring getDynamicModel's truncation — so the\n * cascade never promises a hop core's fallback array cannot make. The\n * position advances per hop, so a second hop in the same turn reports B→C\n * even though the session modelId still points at pack A (stickiness lands\n * TUI-side only after the part renders). Returns null when the session has\n * no active pack or the pack has no fallback chain.\n */\n private async getPackCascade(args: ProcessAPIErrorArgs): Promise<PackCascade | null> {\n if (args.state.packCascade !== undefined) {\n return (args.state.packCascade as PackCascade | null) ?? null;\n }\n const controller = args.requestContext?.get('controller') as\n | {\n session?: { modelId?: unknown; modeId?: unknown };\n getState?: () => { activeModelPackId?: unknown; thinkingLevel?: unknown };\n }\n | undefined;\n const modelId = controller?.session?.modelId;\n if (typeof modelId !== 'string' || modelId.length === 0) {\n args.state.packCascade = null;\n return null;\n }\n const modeId =\n typeof controller?.session?.modeId === 'string' && controller.session.modeId.length > 0\n ? controller.session.modeId\n : 'build';\n const settings = loadSettings(this.options.settingsPath);\n const packs = listResolvableModePacks(settings);\n const controllerState = controller?.getState?.();\n const statePackId = controllerState?.activeModelPackId ?? settings.models.activeModelPackId;\n const activePack = findModePackForModel(\n settings,\n packs,\n modelId,\n modeId,\n typeof statePackId === 'string' ? statePackId : undefined,\n );\n const chain = activePack\n ? resolveModePackFallbackChain(settings.models.packFallbacks ?? {}, activePack.id, settings.customModelPacks)\n : [];\n if (chain.length < 2) {\n args.state.packCascade = null;\n return null;\n }\n const resolvedPacks: Array<{ packId: string; label: string }> = [];\n const models: Record<string, Record<string, string>> = {};\n for (const packId of chain) {\n const pack = packs.find(candidate => candidate.id === packId);\n const packModels = pack ? resolveModePackModels(settings, pack) : {};\n if (resolvedPacks.length > 0) {\n const entryModelId = packModels[modeId];\n if (!entryModelId) break;\n // Mirror getDynamicModel's truncation: an entry whose model cannot\n // resolve (e.g. unconnected provider in deployed fail-closed mode)\n // ends the cascade here so a later hop is never announced for a pack\n // core's fallback array cannot reach. Resolved with the exact options\n // getDynamicModel uses (agents/model.ts) — including thinkingLevel —\n // so the probe can't pass where the chain builder fails.\n try {\n resolveModel(entryModelId, {\n thinkingLevel: resolveRequestThinkingLevel(\n {\n state: { thinkingLevel: controllerState?.thinkingLevel },\n session: { modeId },\n },\n this.options.settingsPath,\n ),\n remapForCodexOAuth: true,\n requestContext: args.requestContext,\n });\n } catch {\n break;\n }\n }\n resolvedPacks.push({ packId, label: pack?.name ?? packId });\n models[packId] = packModels;\n }\n if (resolvedPacks.length < 2) {\n args.state.packCascade = null;\n return null;\n }\n const cascade: PackCascade = { packs: resolvedPacks, models, modeId, position: 0 };\n args.state.packCascade = cascade;\n return cascade;\n }\n\n /**\n * Q14 gate: 400/unknown/unattributable errors never hop packs. Core's\n * fallback array advances on ANY thrown error except TripWire\n * (llm-execution-step.ts), so when the session's pack has a chain 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.getPackCascade(args);\n if (!cascade) return;\n if (cascade.position >= cascade.packs.length - 1) return;\n const reason = error instanceof Error ? error.message : String(error);\n throw new TripWire(reason, {}, this.id);\n }\n\n /**\n * Emit the pack-fallback part when a configured chain has a next pack and\n * queue the thread stickiness trigger. Core's fallback array does the hop\n * itself on `retry: false`; this only announces it. The cascade is\n * truncated exactly like getDynamicModel's chain (missing mode model or\n * unresolvable model), so an announced hop is always one core can make;\n * the toModelId guard stays as defense against a stale cache.\n */\n private async emitPackFallbackPart(\n args: ProcessAPIErrorArgs,\n reason: 'pool-exhausted' | 'persistent-outage',\n ): Promise<void> {\n const cascade = await this.getPackCascade(args);\n if (!cascade) return;\n\n const from = cascade.packs[cascade.position];\n const to = cascade.packs[cascade.position + 1];\n if (!from || !to) return;\n const toModelId = cascade.models[to.packId]?.[cascade.modeId];\n if (!toModelId) return;\n\n const controller = args.requestContext?.get('controller') as\n | {\n session?: { modeId?: unknown };\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 fromPackId: from.packId,\n toPackId: to.packId,\n toModelId,\n ...(threadId ? { threadId } : {}),\n reason,\n at,\n } satisfies PendingPackFallback;\n // Persist the pending hop before announcing it. The TUI clears this marker\n // only after applying every model/pack write, so a crash or immediate\n // retrigger resumes on the landed fallback instead of the failed pack.\n await controller?.setThreadSetting?.({ key: PACK_FALLBACK_STATE_KEY, value: pending });\n const data: PackFallbackPartData = { from, to, reason, at };\n try {\n // Persist the transcript notice before notifying live state listeners.\n // Session events do not await async handlers, so reversing these writes\n // can apply a pack hop that never receives its required transcript part.\n await args.writer?.custom({ type: PACK_FALLBACK_PART_TYPE, data });\n } catch (error) {\n await controller?.setThreadSetting?.({ key: PACK_FALLBACK_STATE_KEY, value: undefined });\n throw error;\n }\n // Keep the durable marker if live state persistence fails: the transcript\n // already records the hop and thread hydration can safely resume it.\n await controller?.setState?.({ [PACK_FALLBACK_STATE_KEY]: pending });\n // Re-arm the start notice so the retried attempt on the target pack\n // re-applies its preferred routing even if the attempt below fails —\n // the flag may already be set by a switch on the original pack.\n args.state.startNoticeEmitted = false;\n // Activate the target pack's preferred account only after the hop is\n // durable. A failure here must not strand a provider-global account\n // switch with no durable fallback state — the start-notice processor\n // re-applies routing when the retried request begins on the target pack.\n const targetRoute = resolveAccountRoute(args, this.options.settingsPath, { packId: to.packId, modelId: toModelId });\n if (targetRoute) {\n // Deployed requests must hop on the tenant store: activating the target\n // pack's preferred account on the host registry would mutate a local\n // account to serve a tenant request.\n const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;\n await applyPreferredAccountRoute(args, store, this.options.settingsPath, targetRoute).catch(() => undefined);\n }\n // Live visibility: data parts never ride controller message events, so\n // emit the same line as an info event (see emitAccountSwitchPart).\n controller?.emitEvent?.({ type: 'info', message: packFallbackNoticeText(data) });\n // Advance only after the part and stickiness landed — a throw mid-emit\n // must not desync the cascade from what the transcript shows.\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 pack (announced by `emitPackFallbackPart`).\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 pack fallback before core advances.\n await this.emitPackFallbackPart(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.emitPackFallbackPart(args, reason);\n\n return { retry: false };\n }\n}\n\n/**\n * Emits the \"starting on a non-default account\" notice (Q19) once per\n * request when the active registry account is not the first entry.\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; settingsPath?: string }) {}\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, this.options.settingsPath);\n if (route) {\n const switched = await applyPreferredAccountRoute(args, store, this.options.settingsPath, route);\n if (switched) {\n args.state.startNoticeEmitted = true;\n return args.messageList;\n }\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 pack'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 providerId = route?.providerId ?? providerFromSession(args);\n if (!providerId) return args.messageList;\n\n const accounts = store.listAccounts?.(providerId) ?? [];\n if (accounts.length < 2) return args.messageList;\n\n const active = store.getActiveAccount?.(providerId);\n const first = accounts[0];\n if (!active || !first || active.id === first.id) return args.messageList;\n\n args.state.startNoticeEmitted = true;\n\n await emitAccountSwitchPart(args, {\n provider: providerId,\n from: null,\n to: { id: active.id, label: active.label },\n reason: 'starting-on-account',\n at: new Date().toISOString(),\n });\n\n return args.messageList;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RequestContext } from '@mastra/core/request-context';
|
|
2
|
+
export declare function getRequestAccountSelection(requestContext: RequestContext | undefined, providerId: string): string | undefined;
|
|
3
|
+
export declare function setRequestAccountSelection(requestContext: RequestContext | undefined, providerId: string, accountInstanceId: string): void;
|
|
4
|
+
/**
|
|
5
|
+
* Record that routing found no usable account for this provider on this
|
|
6
|
+
* request. Kept separate from the account selection: a sentinel *id* would be
|
|
7
|
+
* handed to credential lookup, where an unknown id makes `get()` fall back to
|
|
8
|
+
* the provider's active account — the very account routing just rejected.
|
|
9
|
+
*/
|
|
10
|
+
export declare function markRequestAccountRoutingExhausted(requestContext: RequestContext | undefined, providerId: string): void;
|
|
11
|
+
/** Whether routing already rejected every account for this provider on this request. */
|
|
12
|
+
export declare function isRequestAccountRoutingExhausted(requestContext: RequestContext | undefined, providerId: string): boolean;
|
|
13
|
+
//# sourceMappingURL=account-routing-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-routing-context.d.ts","sourceRoot":"","sources":["../../src/auth/account-routing-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,cAAc,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,sBAExG;AAED,wBAAgB,0BAA0B,CACxC,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,GACxB,IAAI,CAMN;AASD;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,UAAU,EAAE,MAAM,GACjB,IAAI,CAKN;AAED,wFAAwF;AACxF,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,UAAU,EAAE,MAAM,GACjB,OAAO,CAET"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/auth/account-routing-context.ts
|
|
2
|
+
const ACCOUNT_ROUTING_SELECTIONS_KEY = "mastracodeAccountRoutingSelections";
|
|
3
|
+
function getSelections(requestContext) {
|
|
4
|
+
const value = requestContext?.get(ACCOUNT_ROUTING_SELECTIONS_KEY);
|
|
5
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6
|
+
}
|
|
7
|
+
function getRequestAccountSelection(requestContext, providerId) {
|
|
8
|
+
return getSelections(requestContext)?.[providerId];
|
|
9
|
+
}
|
|
10
|
+
function setRequestAccountSelection(requestContext, providerId, accountInstanceId) {
|
|
11
|
+
if (!requestContext || typeof requestContext.set !== "function") return;
|
|
12
|
+
requestContext.set(ACCOUNT_ROUTING_SELECTIONS_KEY, {
|
|
13
|
+
...getSelections(requestContext),
|
|
14
|
+
[providerId]: accountInstanceId
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const ACCOUNT_ROUTING_EXHAUSTED_KEY = "mastracodeAccountRoutingExhaustedProviders";
|
|
18
|
+
function getExhaustedProviders(requestContext) {
|
|
19
|
+
const value = requestContext?.get(ACCOUNT_ROUTING_EXHAUSTED_KEY);
|
|
20
|
+
return Array.isArray(value) ? value : [];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Record that routing found no usable account for this provider on this
|
|
24
|
+
* request. Kept separate from the account selection: a sentinel *id* would be
|
|
25
|
+
* handed to credential lookup, where an unknown id makes `get()` fall back to
|
|
26
|
+
* the provider's active account — the very account routing just rejected.
|
|
27
|
+
*/
|
|
28
|
+
function markRequestAccountRoutingExhausted(requestContext, providerId) {
|
|
29
|
+
if (!requestContext || typeof requestContext.set !== "function") return;
|
|
30
|
+
const exhausted = getExhaustedProviders(requestContext);
|
|
31
|
+
if (exhausted.includes(providerId)) return;
|
|
32
|
+
requestContext.set(ACCOUNT_ROUTING_EXHAUSTED_KEY, [...exhausted, providerId]);
|
|
33
|
+
}
|
|
34
|
+
/** Whether routing already rejected every account for this provider on this request. */
|
|
35
|
+
function isRequestAccountRoutingExhausted(requestContext, providerId) {
|
|
36
|
+
return getExhaustedProviders(requestContext).includes(providerId);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { getRequestAccountSelection, isRequestAccountRoutingExhausted, markRequestAccountRoutingExhausted, setRequestAccountSelection };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=account-routing-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-routing-context.js","names":[],"sources":["../../src/auth/account-routing-context.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\n\nconst ACCOUNT_ROUTING_SELECTIONS_KEY = 'mastracodeAccountRoutingSelections';\n\ntype AccountRoutingSelections = Record<string, string>;\n\nfunction getSelections(requestContext?: RequestContext): AccountRoutingSelections | undefined {\n const value = requestContext?.get(ACCOUNT_ROUTING_SELECTIONS_KEY);\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as AccountRoutingSelections) : undefined;\n}\n\nexport function getRequestAccountSelection(requestContext: RequestContext | undefined, providerId: string) {\n return getSelections(requestContext)?.[providerId];\n}\n\nexport function setRequestAccountSelection(\n requestContext: RequestContext | undefined,\n providerId: string,\n accountInstanceId: string,\n): void {\n if (!requestContext || typeof requestContext.set !== 'function') return;\n requestContext.set(ACCOUNT_ROUTING_SELECTIONS_KEY, {\n ...getSelections(requestContext),\n [providerId]: accountInstanceId,\n });\n}\n\nconst ACCOUNT_ROUTING_EXHAUSTED_KEY = 'mastracodeAccountRoutingExhaustedProviders';\n\nfunction getExhaustedProviders(requestContext?: RequestContext): string[] {\n const value = requestContext?.get(ACCOUNT_ROUTING_EXHAUSTED_KEY);\n return Array.isArray(value) ? (value as string[]) : [];\n}\n\n/**\n * Record that routing found no usable account for this provider on this\n * request. Kept separate from the account selection: a sentinel *id* would be\n * handed to credential lookup, where an unknown id makes `get()` fall back to\n * the provider's active account — the very account routing just rejected.\n */\nexport function markRequestAccountRoutingExhausted(\n requestContext: RequestContext | undefined,\n providerId: string,\n): void {\n if (!requestContext || typeof requestContext.set !== 'function') return;\n const exhausted = getExhaustedProviders(requestContext);\n if (exhausted.includes(providerId)) return;\n requestContext.set(ACCOUNT_ROUTING_EXHAUSTED_KEY, [...exhausted, providerId]);\n}\n\n/** Whether routing already rejected every account for this provider on this request. */\nexport function isRequestAccountRoutingExhausted(\n requestContext: RequestContext | undefined,\n providerId: string,\n): boolean {\n return getExhaustedProviders(requestContext).includes(providerId);\n}\n"],"mappings":";AAEA,MAAM,iCAAiC;AAIvC,SAAS,cAAc,gBAAuE;CAC5F,MAAM,QAAQ,gBAAgB,IAAI,8BAA8B;CAChE,OAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAK,QAAqC,KAAA;AAC7G;AAEA,SAAgB,2BAA2B,gBAA4C,YAAoB;CACzG,OAAO,cAAc,cAAc,CAAC,GAAG;AACzC;AAEA,SAAgB,2BACd,gBACA,YACA,mBACM;CACN,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY;CACjE,eAAe,IAAI,gCAAgC;EACjD,GAAG,cAAc,cAAc;GAC9B,aAAa;CAChB,CAAC;AACH;AAEA,MAAM,gCAAgC;AAEtC,SAAS,sBAAsB,gBAA2C;CACxE,MAAM,QAAQ,gBAAgB,IAAI,6BAA6B;CAC/D,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAqB,CAAC;AACvD;;;;;;;AAQA,SAAgB,mCACd,gBACA,YACM;CACN,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY;CACjE,MAAM,YAAY,sBAAsB,cAAc;CACtD,IAAI,UAAU,SAAS,UAAU,GAAG;CACpC,eAAe,IAAI,+BAA+B,CAAC,GAAG,WAAW,UAAU,CAAC;AAC9E;;AAGA,SAAgB,iCACd,gBACA,YACS;CACT,OAAO,sBAAsB,cAAc,CAAC,CAAC,SAAS,UAAU;AAClE"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export * from './types.js';
|
|
5
5
|
export * from './provider-auth-error.js';
|
|
6
6
|
export * from './storage.js';
|
|
7
|
+
export * from './account-rotation-processor.js';
|
|
7
8
|
export { anthropicOAuthProvider } from './providers/anthropic.js';
|
|
8
9
|
export { githubCopilotOAuthProvider } from './providers/github-copilot.js';
|
|
9
10
|
export { kimiCodingOAuthProvider } from './providers/kimi-coding.js';
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,iCAAiC,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/auth/index.js
CHANGED
|
@@ -5,4 +5,5 @@ import { openaiCodexOAuthProvider } from "./providers/openai-codex.js";
|
|
|
5
5
|
import { xaiOAuthProvider } from "./providers/xai.js";
|
|
6
6
|
import { AuthStorage, PROVIDER_DEFAULT_MODELS, getOAuthProvider, getOAuthProviders } from "./storage.js";
|
|
7
7
|
import { PROVIDER_AUTH_REQUIRED_ERROR, ProviderAuthRequiredError } from "./provider-auth-error.js";
|
|
8
|
-
|
|
8
|
+
import { ACCOUNT_SWITCH_PART_TYPE, AccountRotationProcessor, AccountStartNoticeProcessor, PACK_FALLBACK_PART_TYPE, PACK_FALLBACK_STATE_KEY, PROVIDER_DISPLAY_NAMES, accountSwitchNoticeText, classifyRotationError, isAccountSwitchReason, isPackFallbackReason, packFallbackNoticeText, providerFromError, providerFromModelId } from "./account-rotation-processor.js";
|
|
9
|
+
export { ACCOUNT_SWITCH_PART_TYPE, AccountRotationProcessor, AccountStartNoticeProcessor, AuthStorage, PACK_FALLBACK_PART_TYPE, PACK_FALLBACK_STATE_KEY, PROVIDER_AUTH_REQUIRED_ERROR, PROVIDER_DEFAULT_MODELS, PROVIDER_DISPLAY_NAMES, ProviderAuthRequiredError, accountSwitchNoticeText, anthropicOAuthProvider, classifyRotationError, getOAuthProvider, getOAuthProviders, githubCopilotOAuthProvider, isAccountSwitchReason, isPackFallbackReason, kimiCodingOAuthProvider, openaiCodexOAuthProvider, packFallbackNoticeText, providerFromError, providerFromModelId, xaiOAuthProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAYjG,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAexE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAYjG,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAexE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8CvG;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAU3B;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2B3F;AAED,eAAO,MAAM,sBAAsB,EAAE,sBAkBpC,CAAC"}
|
|
@@ -62,10 +62,7 @@ async function completeAnthropicLogin(input, verifier) {
|
|
|
62
62
|
code_verifier: verifier
|
|
63
63
|
})
|
|
64
64
|
});
|
|
65
|
-
if (!tokenResponse.ok) {
|
|
66
|
-
const error = await tokenResponse.text();
|
|
67
|
-
throw new Error(`Token exchange failed: ${error}`);
|
|
68
|
-
}
|
|
65
|
+
if (!tokenResponse.ok) throw new Error(`Token exchange failed: ${tokenResponse.status}`);
|
|
69
66
|
const tokenData = await tokenResponse.json();
|
|
70
67
|
const expiresAt = Date.now() + tokenData.expires_in * 1e3 - 300 * 1e3;
|
|
71
68
|
return {
|
|
@@ -96,10 +93,7 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
96
93
|
refresh_token: refreshToken
|
|
97
94
|
})
|
|
98
95
|
});
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
const error = await response.text();
|
|
101
|
-
throw new Error(`Anthropic token refresh failed: ${error}`);
|
|
102
|
-
}
|
|
96
|
+
if (!response.ok) throw new Error(`Anthropic token refresh failed: ${response.status}`);
|
|
103
97
|
const data = await response.json();
|
|
104
98
|
return {
|
|
105
99
|
refresh: data.refresh_token,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.js","names":[],"sources":["../../../src/auth/providers/anthropic.ts"],"sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * Inspired by pi-mono's OAuth implementation:\n * https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/anthropic.ts\n *\n * The flow is a paste-code PKCE flow: the redirect lands on Anthropic's hosted\n * callback page which displays `code#state` for the user to paste back. That\n * makes it deployable without any inbound connection to the server, so the\n * primitives are split into `startAnthropicLogin()` / `completeAnthropicLogin()`\n * which can span separate HTTP requests (only the PKCE verifier needs to be\n * persisted in between).\n */\n\nimport { parseAuthorizationInput } from '../authorization-input.js';\nimport { generatePKCE } from '../pkce.js';\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode('OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl');\nconst AUTHORIZE_URL = 'https://claude.ai/oauth/authorize';\n// pi-mono uses `https://platform.claude.com/v1/oauth/token` with extra scopes\n// (user:sessions:claude_code, user:mcp_servers, user:file_upload); we keep the\n// console.anthropic.com endpoints that are known to work for our scope set.\nconst TOKEN_URL = 'https://console.anthropic.com/v1/oauth/token';\nconst REDIRECT_URI = 'https://console.anthropic.com/oauth/code/callback';\nconst SCOPES = 'org:create_api_key user:profile user:inference';\n\nexport interface AnthropicLoginStart {\n /** Authorization URL for the user to open. */\n url: string;\n /** PKCE code verifier — persist it to complete the login later. */\n verifier: string;\n}\n\n/**\n * Start an Anthropic login: generate PKCE state and build the authorization URL.\n */\nexport async function startAnthropicLogin(): Promise<AnthropicLoginStart> {\n const { verifier, challenge } = await generatePKCE();\n\n const authParams = new URLSearchParams({\n code: 'true',\n client_id: CLIENT_ID,\n response_type: 'code',\n redirect_uri: REDIRECT_URI,\n scope: SCOPES,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n state: verifier,\n });\n\n return { url: `${AUTHORIZE_URL}?${authParams.toString()}`, verifier };\n}\n\n/**\n * Complete an Anthropic login: parse the pasted authorization input\n * (full URL, `code#state`, or query string), validate its state, and exchange\n * it for tokens using the verifier from `startAnthropicLogin()`.\n */\nexport async function completeAnthropicLogin(input: string, verifier: string): Promise<OAuthCredentials> {\n const { code, state } = parseAuthorizationInput(input);\n if (!code) {\n throw new Error('Missing authorization code');\n }\n if (!state || state !== verifier) {\n throw new Error('Invalid authorization state');\n }\n\n const tokenResponse = await fetch(TOKEN_URL, {\n method: 'POST',\n // Bound the OAuth exchange so an unresponsive upstream cannot pin the\n // caller (and, in the shipyard server, the containing project lock)\n // indefinitely. See 2025-07-23 shipyard latency incident.\n signal: AbortSignal.timeout(15_000),\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n state,\n redirect_uri: REDIRECT_URI,\n code_verifier: verifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n
|
|
1
|
+
{"version":3,"file":"anthropic.js","names":[],"sources":["../../../src/auth/providers/anthropic.ts"],"sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * Inspired by pi-mono's OAuth implementation:\n * https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/anthropic.ts\n *\n * The flow is a paste-code PKCE flow: the redirect lands on Anthropic's hosted\n * callback page which displays `code#state` for the user to paste back. That\n * makes it deployable without any inbound connection to the server, so the\n * primitives are split into `startAnthropicLogin()` / `completeAnthropicLogin()`\n * which can span separate HTTP requests (only the PKCE verifier needs to be\n * persisted in between).\n */\n\nimport { parseAuthorizationInput } from '../authorization-input.js';\nimport { generatePKCE } from '../pkce.js';\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode('OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl');\nconst AUTHORIZE_URL = 'https://claude.ai/oauth/authorize';\n// pi-mono uses `https://platform.claude.com/v1/oauth/token` with extra scopes\n// (user:sessions:claude_code, user:mcp_servers, user:file_upload); we keep the\n// console.anthropic.com endpoints that are known to work for our scope set.\nconst TOKEN_URL = 'https://console.anthropic.com/v1/oauth/token';\nconst REDIRECT_URI = 'https://console.anthropic.com/oauth/code/callback';\nconst SCOPES = 'org:create_api_key user:profile user:inference';\n\nexport interface AnthropicLoginStart {\n /** Authorization URL for the user to open. */\n url: string;\n /** PKCE code verifier — persist it to complete the login later. */\n verifier: string;\n}\n\n/**\n * Start an Anthropic login: generate PKCE state and build the authorization URL.\n */\nexport async function startAnthropicLogin(): Promise<AnthropicLoginStart> {\n const { verifier, challenge } = await generatePKCE();\n\n const authParams = new URLSearchParams({\n code: 'true',\n client_id: CLIENT_ID,\n response_type: 'code',\n redirect_uri: REDIRECT_URI,\n scope: SCOPES,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n state: verifier,\n });\n\n return { url: `${AUTHORIZE_URL}?${authParams.toString()}`, verifier };\n}\n\n/**\n * Complete an Anthropic login: parse the pasted authorization input\n * (full URL, `code#state`, or query string), validate its state, and exchange\n * it for tokens using the verifier from `startAnthropicLogin()`.\n */\nexport async function completeAnthropicLogin(input: string, verifier: string): Promise<OAuthCredentials> {\n const { code, state } = parseAuthorizationInput(input);\n if (!code) {\n throw new Error('Missing authorization code');\n }\n if (!state || state !== verifier) {\n throw new Error('Invalid authorization state');\n }\n\n const tokenResponse = await fetch(TOKEN_URL, {\n method: 'POST',\n // Bound the OAuth exchange so an unresponsive upstream cannot pin the\n // caller (and, in the shipyard server, the containing project lock)\n // indefinitely. See 2025-07-23 shipyard latency incident.\n signal: AbortSignal.timeout(15_000),\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n state,\n redirect_uri: REDIRECT_URI,\n code_verifier: verifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n throw new Error(`Token exchange failed: ${tokenResponse.status}`);\n }\n\n const tokenData = (await tokenResponse.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n // Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n return {\n refresh: tokenData.refresh_token,\n access: tokenData.access_token,\n expires: expiresAt,\n };\n}\n\n/**\n * Login with Anthropic OAuth (paste-code flow), blocking on the prompt callback.\n */\nexport async function loginAnthropic(\n onAuthUrl: (url: string) => void,\n onPromptCode: () => Promise<string>,\n): Promise<OAuthCredentials> {\n const { url, verifier } = await startAnthropicLogin();\n\n // Notify caller with URL to open\n onAuthUrl(url);\n\n // Wait for user to paste authorization code (format: code#state)\n const authCode = await onPromptCode();\n\n return completeAnthropicLogin(authCode, verifier);\n}\n\n/**\n * Refresh Anthropic OAuth token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(15_000),\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: CLIENT_ID,\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Anthropic token refresh failed: ${response.status}`);\n }\n\n const data = (await response.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n return {\n refresh: data.refresh_token,\n access: data.access_token,\n expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n };\n}\n\nexport const anthropicOAuthProvider: OAuthProviderInterface = {\n id: 'anthropic',\n name: 'Anthropic (Claude Pro/Max)',\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n return loginAnthropic(\n url => callbacks.onAuth({ url }),\n () => callbacks.onPrompt({ message: 'Paste the authorization code:' }),\n );\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n return refreshAnthropicToken(credentials.refresh);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAM,UAAU,MAAc,KAAK,CAAC;AACpC,MAAM,YAAY,OAAO,kDAAkD;AAC3E,MAAM,gBAAgB;AAItB,MAAM,YAAY;AAClB,MAAM,eAAe;AACrB,MAAM,SAAS;;;;AAYf,eAAsB,sBAAoD;CACxE,MAAM,EAAE,UAAU,cAAc,MAAM,aAAa;CAEnD,MAAM,aAAa,IAAI,gBAAgB;EACrC,MAAM;EACN,WAAW;EACX,eAAe;EACf,cAAc;EACd,OAAO;EACP,gBAAgB;EAChB,uBAAuB;EACvB,OAAO;CACT,CAAC;CAED,OAAO;EAAE,KAAK,GAAG,cAAc,GAAG,WAAW,SAAS;EAAK;CAAS;AACtE;;;;;;AAOA,eAAsB,uBAAuB,OAAe,UAA6C;CACvG,MAAM,EAAE,MAAM,UAAU,wBAAwB,KAAK;CACrD,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,4BAA4B;CAE9C,IAAI,CAAC,SAAS,UAAU,UACtB,MAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAM,gBAAgB,MAAM,MAAM,WAAW;EAC3C,QAAQ;EAIR,QAAQ,YAAY,QAAQ,IAAM;EAClC,SAAS,EACP,gBAAgB,mBAClB;EACA,MAAM,KAAK,UAAU;GACnB,YAAY;GACZ,WAAW;GACX;GACA;GACA,cAAc;GACd,eAAe;EACjB,CAAC;CACH,CAAC;CAED,IAAI,CAAC,cAAc,IACjB,MAAM,IAAI,MAAM,0BAA0B,cAAc,QAAQ;CAGlE,MAAM,YAAa,MAAM,cAAc,KAAK;CAO5C,MAAM,YAAY,KAAK,IAAI,IAAI,UAAU,aAAa,MAAO,MAAS;CAEtE,OAAO;EACL,SAAS,UAAU;EACnB,QAAQ,UAAU;EAClB,SAAS;CACX;AACF;;;;AAKA,eAAsB,eACpB,WACA,cAC2B;CAC3B,MAAM,EAAE,KAAK,aAAa,MAAM,oBAAoB;CAGpD,UAAU,GAAG;CAKb,OAAO,uBAAuB,MAFP,aAAa,GAEI,QAAQ;AAClD;;;;AAKA,eAAsB,sBAAsB,cAAiD;CAC3F,MAAM,WAAW,MAAM,MAAM,WAAW;EACtC,QAAQ;EACR,QAAQ,YAAY,QAAQ,IAAM;EAClC,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GACnB,YAAY;GACZ,WAAW;GACX,eAAe;EACjB,CAAC;CACH,CAAC;CAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,mCAAmC,SAAS,QAAQ;CAGtE,MAAM,OAAQ,MAAM,SAAS,KAAK;CAMlC,OAAO;EACL,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,SAAS,KAAK,IAAI,IAAI,KAAK,aAAa,MAAO,MAAS;CAC1D;AACF;AAEA,MAAa,yBAAiD;CAC5D,IAAI;CACJ,MAAM;CAEN,MAAM,MAAM,WAA2D;EACrE,OAAO,gBACL,QAAO,UAAU,OAAO,EAAE,IAAI,CAAC,SACzB,UAAU,SAAS,EAAE,SAAS,gCAAgC,CAAC,CACvE;CACF;CAEA,MAAM,aAAa,aAA0D;EAC3E,OAAO,sBAAsB,YAAY,OAAO;CAClD;CAEA,UAAU,aAAuC;EAC/C,OAAO,YAAY;CACrB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/github-copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEjG,MAAM,MAAM,wBAAwB,GAAG,gBAAgB,GAAG;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAMF,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,wEAAwE;AACxE,eAAO,MAAM,eAAe;;;;;CAKlB,CAAC;
|
|
1
|
+
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/github-copilot.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,11 @@ const COPILOT_HEADERS = {
|
|
|
11
11
|
};
|
|
12
12
|
const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
|
|
13
13
|
const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
|
|
14
|
+
const OAUTH_REQUEST_TIMEOUT_MS = 15e3;
|
|
15
|
+
function requestSignal(signal) {
|
|
16
|
+
const timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
17
|
+
return signal ? AbortSignal.any([timeout, signal]) : timeout;
|
|
18
|
+
}
|
|
14
19
|
/**
|
|
15
20
|
* Normalize a user-entered Enterprise URL/domain into a hostname (e.g. `company.ghe.com`).
|
|
16
21
|
* Returns null for an empty input or an unparseable URL.
|
|
@@ -53,14 +58,11 @@ function getGitHubCopilotBaseUrl(token, enterpriseDomain) {
|
|
|
53
58
|
return "https://api.individual.githubcopilot.com";
|
|
54
59
|
}
|
|
55
60
|
async function fetchJson(url, init, signal) {
|
|
56
|
-
const response = await fetch(url,
|
|
61
|
+
const response = await fetch(url, {
|
|
57
62
|
...init,
|
|
58
|
-
signal
|
|
59
|
-
}
|
|
60
|
-
if (!response.ok) {
|
|
61
|
-
const text = await response.text().catch(() => "");
|
|
62
|
-
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
63
|
-
}
|
|
63
|
+
signal: requestSignal(signal)
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`.trim());
|
|
64
66
|
return response.json();
|
|
65
67
|
}
|
|
66
68
|
async function startDeviceFlow(domain, signal) {
|
|
@@ -274,12 +276,9 @@ async function fetchCopilotModels(opts) {
|
|
|
274
276
|
Authorization: `Bearer ${opts.bearerToken}`,
|
|
275
277
|
...COPILOT_HEADERS
|
|
276
278
|
},
|
|
277
|
-
signal: opts.signal
|
|
279
|
+
signal: requestSignal(opts.signal)
|
|
278
280
|
});
|
|
279
|
-
if (!response.ok) {
|
|
280
|
-
const text = await response.text().catch(() => "");
|
|
281
|
-
throw new Error(`Failed to fetch Copilot models: ${response.status} ${response.statusText}: ${text}`);
|
|
282
|
-
}
|
|
281
|
+
if (!response.ok) throw new Error(`Failed to fetch Copilot models: ${response.status} ${response.statusText}`.trim());
|
|
283
282
|
const json = await response.json().catch(() => null);
|
|
284
283
|
if (!json || typeof json !== "object" || !Array.isArray(json.data)) throw new Error("Invalid Copilot models response: missing `data` array");
|
|
285
284
|
const data = json.data;
|
|
@@ -332,6 +331,29 @@ const githubCopilotOAuthProvider = {
|
|
|
332
331
|
},
|
|
333
332
|
getApiKey(credentials) {
|
|
334
333
|
return credentials.access;
|
|
334
|
+
},
|
|
335
|
+
/**
|
|
336
|
+
* The stored refresh token is the long-lived GitHub OAuth token — use it to
|
|
337
|
+
* resolve the account's GitHub login for the account manager. Enterprise
|
|
338
|
+
* tokens don't work against api.github.com, which just falls through to the
|
|
339
|
+
* label prompt/default.
|
|
340
|
+
*/
|
|
341
|
+
async getAccountLabel(credentials) {
|
|
342
|
+
if (credentials.enterpriseUrl) return void 0;
|
|
343
|
+
try {
|
|
344
|
+
const response = await fetch("https://api.github.com/user", {
|
|
345
|
+
headers: {
|
|
346
|
+
Authorization: `Bearer ${credentials.refresh}`,
|
|
347
|
+
Accept: "application/vnd.github+json"
|
|
348
|
+
},
|
|
349
|
+
signal: requestSignal()
|
|
350
|
+
});
|
|
351
|
+
if (!response.ok) return void 0;
|
|
352
|
+
const user = await response.json();
|
|
353
|
+
return typeof user.login === "string" && user.login.length > 0 ? user.login : void 0;
|
|
354
|
+
} catch {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
335
357
|
}
|
|
336
358
|
};
|
|
337
359
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.js","names":[],"sources":["../../../src/auth/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth flow\n *\n * Inspired by:\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n *\n * Storage layout in AuthStorage:\n * - `refresh` holds the long-lived GitHub OAuth access token (used to mint Copilot tokens).\n * - `access` holds the short-lived Copilot bearer token (~30 minutes).\n * - `expires` is the Copilot bearer expiry (ms epoch, with a 5-minute safety buffer).\n * - `enterpriseUrl` is the GitHub Enterprise hostname, or undefined for github.com.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nexport type GitHubCopilotCredentials = OAuthCredentials & {\n enterpriseUrl?: string;\n};\n\nconst decode = (s: string): string => atob(s);\n// Copilot's public client ID, encoded to mirror the upstream references.\nconst CLIENT_ID = decode('SXYxLmI1MDdhMDhjODdlY2ZlOTg=');\n\nexport const COPILOT_USER_AGENT = 'GitHubCopilotChat/0.35.0';\n\n/** Headers that Copilot endpoints expect from a VS Code-like client. */\nexport const COPILOT_HEADERS = {\n 'User-Agent': COPILOT_USER_AGENT,\n 'Editor-Version': 'vscode/1.107.0',\n 'Editor-Plugin-Version': 'copilot-chat/0.35.0',\n 'Copilot-Integration-Id': 'vscode-chat',\n} as const;\n\nconst INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;\nconst SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;\n\ntype DeviceCodeResponse = {\n device_code: string;\n user_code: string;\n verification_uri: string;\n interval: number;\n expires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n access_token: string;\n token_type?: string;\n scope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n error: string;\n error_description?: string;\n interval?: number;\n};\n\n/**\n * Normalize a user-entered Enterprise URL/domain into a hostname (e.g. `company.ghe.com`).\n * Returns null for an empty input or an unparseable URL.\n */\nexport function normalizeDomain(input: string): string | null {\n const trimmed = input.trim();\n if (!trimmed) return null;\n try {\n const url = trimmed.includes('://') ? new URL(trimmed) : new URL(`https://${trimmed}`);\n return url.hostname || null;\n } catch {\n return null;\n }\n}\n\nfunction getUrls(domain: string): {\n deviceCodeUrl: string;\n accessTokenUrl: string;\n copilotTokenUrl: string;\n} {\n return {\n deviceCodeUrl: `https://${domain}/login/device/code`,\n accessTokenUrl: `https://${domain}/login/oauth/access_token`,\n copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n };\n}\n\n/**\n * Parse the `proxy-ep` segment from a Copilot bearer token and convert to its API base URL.\n * Token shape: `tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...`\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n const match = token.match(/proxy-ep=([^;]+)/);\n if (!match) return null;\n const proxyHost = match[1]!;\n const apiHost = proxyHost.replace(/^proxy\\./, 'api.');\n return `https://${apiHost}`;\n}\n\n/**\n * Resolve the Copilot API base URL.\n * Prefers the `proxy-ep` parsed from the bearer token, then falls back to enterprise/individual defaults.\n */\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n if (token) {\n const fromToken = getBaseUrlFromToken(token);\n if (fromToken) return fromToken;\n }\n if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n return 'https://api.individual.githubcopilot.com';\n}\n\nasync function fetchJson(url: string, init: RequestInit, signal?: AbortSignal): Promise<unknown> {\n const response = await fetch(url, signal ? { ...init, signal } : init);\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`${response.status} ${response.statusText}: ${text}`);\n }\n return response.json();\n}\n\nasync function startDeviceFlow(domain: string, signal?: AbortSignal): Promise<DeviceCodeResponse> {\n const urls = getUrls(domain);\n const data = await fetchJson(\n urls.deviceCodeUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n scope: 'read:user',\n }),\n },\n signal,\n );\n\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid device code response');\n }\n\n const obj = data as Record<string, unknown>;\n const deviceCode = obj.device_code;\n const userCode = obj.user_code;\n const verificationUri = obj.verification_uri;\n const interval = obj.interval;\n const expiresIn = obj.expires_in;\n\n if (\n typeof deviceCode !== 'string' ||\n typeof userCode !== 'string' ||\n typeof verificationUri !== 'string' ||\n typeof interval !== 'number' ||\n typeof expiresIn !== 'number'\n ) {\n throw new Error('Invalid device code response fields');\n }\n\n return {\n device_code: deviceCode,\n user_code: userCode,\n verification_uri: verificationUri,\n interval,\n expires_in: expiresIn,\n };\n}\n\n/** Sleep that can be interrupted by an AbortSignal. */\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Login cancelled'));\n return;\n }\n\n let timeout: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeout);\n reject(new Error('Login cancelled'));\n };\n\n timeout = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * Serializable pending state for a Copilot device-code login. Safe to persist\n * (e.g. a `pending jsonb` column) so polling can span HTTP requests/replicas.\n */\nexport interface CopilotDeviceLoginPending {\n /** GitHub host performing the flow (`github.com` or an Enterprise hostname). */\n domain: string;\n /** Enterprise hostname, or undefined for github.com. Recorded on the final credentials. */\n enterpriseDomain?: string;\n deviceCode: string;\n userCode: string;\n /** Verification URL for the user to open. */\n url: string;\n instructions: string;\n /** Current base poll interval in ms (grows on slow_down). */\n intervalMs: number;\n /** Safety margin applied to intervalMs when computing the next wait. */\n intervalMultiplier: number;\n /** Number of slow_down responses seen (used for the clock-drift hint). */\n slowDownCount: number;\n /** ms epoch after which the device authorization expires. */\n deadlineAt: number;\n}\n\nexport type CopilotDevicePollResult =\n | { status: 'complete'; credentials: GitHubCopilotCredentials }\n | { status: 'pending'; pending: CopilotDeviceLoginPending; nextPollMs: number }\n | { status: 'failed'; error: string };\n\n/** Delay to wait before the next upstream poll for a pending Copilot device login. */\nexport function copilotNextPollDelayMs(pending: CopilotDeviceLoginPending): number {\n return Math.ceil(pending.intervalMs * pending.intervalMultiplier);\n}\n\n/**\n * Start a Copilot device-code login: request a user code and return the\n * serializable pending state for subsequent polls. Wait `copilotNextPollDelayMs`\n * before the first poll.\n */\nexport async function startGitHubCopilotDeviceLogin(\n enterpriseDomain?: string,\n options?: { signal?: AbortSignal },\n): Promise<CopilotDeviceLoginPending> {\n const domain = enterpriseDomain || 'github.com';\n const device = await startDeviceFlow(domain, options?.signal);\n\n return {\n domain,\n ...(enterpriseDomain ? { enterpriseDomain } : {}),\n deviceCode: device.device_code,\n userCode: device.user_code,\n url: device.verification_uri,\n instructions: `Enter code: ${device.user_code}`,\n intervalMs: Math.max(1000, Math.floor(device.interval * 1000)),\n intervalMultiplier: INITIAL_POLL_INTERVAL_MULTIPLIER,\n slowDownCount: 0,\n deadlineAt: Date.now() + device.expires_in * 1000,\n };\n}\n\n/**\n * Perform exactly one upstream poll for a pending Copilot device login.\n * On success, exchanges the GitHub OAuth token for a Copilot bearer token and\n * returns full credentials. Never throws for flow-level conditions; network\n * errors from fetch still propagate.\n */\nexport async function pollGitHubCopilotDeviceLogin(\n pending: CopilotDeviceLoginPending,\n options?: { signal?: AbortSignal; onProgress?: (message: string) => void },\n): Promise<CopilotDevicePollResult> {\n if (Date.now() >= pending.deadlineAt) {\n if (pending.slowDownCount > 0) {\n return {\n status: 'failed',\n error:\n 'Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.',\n };\n }\n return { status: 'failed', error: 'Device flow timed out' };\n }\n\n const urls = getUrls(pending.domain);\n const raw = await fetchJson(\n urls.accessTokenUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n device_code: pending.deviceCode,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }),\n },\n options?.signal,\n );\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenSuccessResponse).access_token === 'string') {\n const githubAccessToken = (raw as DeviceTokenSuccessResponse).access_token;\n options?.onProgress?.('Fetching Copilot token...');\n const credentials = await refreshGitHubCopilotToken(githubAccessToken, pending.enterpriseDomain, options?.signal);\n return { status: 'complete', credentials };\n }\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenErrorResponse).error === 'string') {\n const { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;\n\n if (error === 'slow_down') {\n const next: CopilotDeviceLoginPending = {\n ...pending,\n slowDownCount: pending.slowDownCount + 1,\n intervalMs:\n typeof interval === 'number' && interval > 0 ? interval * 1000 : Math.max(1000, pending.intervalMs + 5000),\n intervalMultiplier: SLOW_DOWN_POLL_INTERVAL_MULTIPLIER,\n };\n return { status: 'pending', pending: next, nextPollMs: copilotNextPollDelayMs(next) };\n }\n\n if (error !== 'authorization_pending') {\n const descriptionSuffix = description ? `: ${description}` : '';\n return { status: 'failed', error: `Device flow failed: ${error}${descriptionSuffix}` };\n }\n }\n\n // authorization_pending or an unrecognized-but-OK response: keep polling.\n return { status: 'pending', pending, nextPollMs: copilotNextPollDelayMs(pending) };\n}\n\n/**\n * Refresh the short-lived Copilot bearer token using the long-lived GitHub OAuth token.\n *\n * `refreshToken` here is the GitHub OAuth access token; the Copilot endpoint returns a\n * fresh bearer token (`token`) and absolute expiry (`expires_at`, seconds since epoch).\n */\nexport async function refreshGitHubCopilotToken(\n refreshToken: string,\n enterpriseDomain?: string,\n signal?: AbortSignal,\n): Promise<GitHubCopilotCredentials> {\n const domain = enterpriseDomain || 'github.com';\n const urls = getUrls(domain);\n\n const raw = await fetchJson(\n urls.copilotTokenUrl,\n {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${refreshToken}`,\n ...COPILOT_HEADERS,\n },\n },\n signal,\n );\n\n if (!raw || typeof raw !== 'object') {\n throw new Error('Invalid Copilot token response');\n }\n\n const obj = raw as Record<string, unknown>;\n const token = obj.token;\n const expiresAt = obj.expires_at;\n\n if (typeof token !== 'string' || typeof expiresAt !== 'number') {\n throw new Error('Invalid Copilot token response fields');\n }\n\n const credentials: GitHubCopilotCredentials = {\n refresh: refreshToken,\n access: token,\n // expires_at is seconds; subtract 5 minutes so we refresh before actual expiry.\n expires: expiresAt * 1000 - 5 * 60 * 1000,\n };\n if (enterpriseDomain) {\n credentials.enterpriseUrl = enterpriseDomain;\n }\n return credentials;\n}\n\n/**\n * Login with GitHub Copilot OAuth (device-code flow).\n *\n * Prompts for an optional GitHub Enterprise URL/domain, performs the device-code flow,\n * then exchanges the GitHub OAuth token for a Copilot bearer token.\n */\nexport async function loginGitHubCopilot(options: {\n onAuth: (url: string, instructions?: string) => void;\n onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n onProgress?: (message: string) => void;\n signal?: AbortSignal;\n}): Promise<GitHubCopilotCredentials> {\n const input = await options.onPrompt({\n message: 'GitHub Enterprise URL/domain (blank for github.com)',\n placeholder: 'company.ghe.com',\n allowEmpty: true,\n });\n\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n const trimmed = input.trim();\n const enterpriseDomain = normalizeDomain(input);\n if (trimmed && !enterpriseDomain) {\n throw new Error('Invalid GitHub Enterprise URL/domain');\n }\n let pending = await startGitHubCopilotDeviceLogin(enterpriseDomain ?? undefined, { signal: options.signal });\n options.onAuth(pending.url, pending.instructions);\n\n while (true) {\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n // Wait before polling (safety margin over the server interval), clamped to the deadline.\n const remainingMs = Math.max(pending.deadlineAt - Date.now(), 0);\n await abortableSleep(Math.min(copilotNextPollDelayMs(pending), remainingMs), options.signal);\n\n const result = await pollGitHubCopilotDeviceLogin(pending, {\n signal: options.signal,\n onProgress: options.onProgress,\n });\n if (result.status === 'complete') {\n return result.credentials;\n }\n if (result.status === 'failed') {\n throw new Error(result.error);\n }\n pending = result.pending;\n }\n}\n\n/**\n * Filtered, normalized entry from the Copilot `/models` API.\n *\n * The full API payload includes a lot of capability metadata; we only keep the bits\n * we need to expose models in `listAvailableModels()` and route requests sensibly.\n */\nexport type CopilotModelEntry = {\n /** Stable model id (e.g. `claude-sonnet-4.5`, `gpt-4.1`). */\n id: string;\n /** Human-readable display name (e.g. `Claude Sonnet 4.5`). */\n name: string;\n /** Vendor field from the API (e.g. `Anthropic`, `OpenAI`). */\n vendor: string;\n /**\n * Endpoints the model exposes (e.g. `/chat/completions`, `/responses`, `/v1/messages`).\n * Empty when the API didn't return `supported_endpoints` for this entry.\n */\n supportedEndpoints: string[];\n /** True when `supported_endpoints` includes `/v1/messages` (Anthropic-shaped Copilot model). */\n isAnthropicShaped: boolean;\n /** True when `capabilities.supports.vision` is true. */\n supportsVision: boolean;\n /** True when the model supports tool calling. */\n supportsToolCalls: boolean;\n};\n\n/**\n * Fetch the Copilot model list available to the current subscription.\n *\n * Hits `${baseURL}/models` with the Copilot bearer token, filters to\n * `model_picker_enabled === true && policy.state !== 'disabled'`, and returns\n * a normalized list. Mirrors opencode's filtering rules.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/models.ts\n */\nexport async function fetchCopilotModels(opts: {\n baseUrl: string;\n bearerToken: string;\n signal?: AbortSignal;\n}): Promise<CopilotModelEntry[]> {\n const url = `${opts.baseUrl.replace(/\\/$/, '')}/models`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${opts.bearerToken}`,\n ...COPILOT_HEADERS,\n },\n signal: opts.signal,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`Failed to fetch Copilot models: ${response.status} ${response.statusText}: ${text}`);\n }\n\n const json = await response.json().catch(() => null);\n if (!json || typeof json !== 'object' || !Array.isArray((json as { data?: unknown }).data)) {\n throw new Error('Invalid Copilot models response: missing `data` array');\n }\n\n const data = (json as { data: unknown[] }).data;\n const result: CopilotModelEntry[] = [];\n\n for (const item of data) {\n if (!item || typeof item !== 'object') continue;\n const obj = item as Record<string, unknown>;\n\n if (obj.model_picker_enabled !== true) continue;\n\n const policy = obj.policy as Record<string, unknown> | undefined;\n if (policy && policy.state === 'disabled') continue;\n\n const id = obj.id;\n if (typeof id !== 'string' || !id) continue;\n\n const name = typeof obj.name === 'string' ? obj.name : id;\n const vendor = typeof obj.vendor === 'string' ? obj.vendor : '';\n\n const capabilities = obj.capabilities as Record<string, unknown> | undefined;\n const supports = capabilities?.supports as Record<string, unknown> | undefined;\n const supportsVision = supports?.vision === true;\n const supportsToolCalls = supports?.tool_calls === true;\n\n const rawEndpoints = obj.supported_endpoints;\n const supportedEndpoints = Array.isArray(rawEndpoints)\n ? rawEndpoints.filter((e): e is string => typeof e === 'string')\n : [];\n const isAnthropicShaped = supportedEndpoints.includes('/v1/messages');\n\n result.push({\n id,\n name,\n vendor,\n supportedEndpoints,\n isAnthropicShaped,\n supportsVision,\n supportsToolCalls,\n });\n }\n\n return result;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n id: 'github-copilot',\n name: 'GitHub Copilot',\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n return loginGitHubCopilot({\n onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n signal: callbacks.signal,\n });\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const creds = credentials as GitHubCopilotCredentials;\n return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"github-copilot.js","names":[],"sources":["../../../src/auth/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth flow\n *\n * Inspired by:\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n *\n * Storage layout in AuthStorage:\n * - `refresh` holds the long-lived GitHub OAuth access token (used to mint Copilot tokens).\n * - `access` holds the short-lived Copilot bearer token (~30 minutes).\n * - `expires` is the Copilot bearer expiry (ms epoch, with a 5-minute safety buffer).\n * - `enterpriseUrl` is the GitHub Enterprise hostname, or undefined for github.com.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nexport type GitHubCopilotCredentials = OAuthCredentials & {\n enterpriseUrl?: string;\n};\n\nconst decode = (s: string): string => atob(s);\n// Copilot's public client ID, encoded to mirror the upstream references.\nconst CLIENT_ID = decode('SXYxLmI1MDdhMDhjODdlY2ZlOTg=');\n\nexport const COPILOT_USER_AGENT = 'GitHubCopilotChat/0.35.0';\n\n/** Headers that Copilot endpoints expect from a VS Code-like client. */\nexport const COPILOT_HEADERS = {\n 'User-Agent': COPILOT_USER_AGENT,\n 'Editor-Version': 'vscode/1.107.0',\n 'Editor-Plugin-Version': 'copilot-chat/0.35.0',\n 'Copilot-Integration-Id': 'vscode-chat',\n} as const;\n\nconst INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;\nconst SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;\nconst OAUTH_REQUEST_TIMEOUT_MS = 15_000;\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n const timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);\n return signal ? AbortSignal.any([timeout, signal]) : timeout;\n}\n\ntype DeviceCodeResponse = {\n device_code: string;\n user_code: string;\n verification_uri: string;\n interval: number;\n expires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n access_token: string;\n token_type?: string;\n scope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n error: string;\n error_description?: string;\n interval?: number;\n};\n\n/**\n * Normalize a user-entered Enterprise URL/domain into a hostname (e.g. `company.ghe.com`).\n * Returns null for an empty input or an unparseable URL.\n */\nexport function normalizeDomain(input: string): string | null {\n const trimmed = input.trim();\n if (!trimmed) return null;\n try {\n const url = trimmed.includes('://') ? new URL(trimmed) : new URL(`https://${trimmed}`);\n return url.hostname || null;\n } catch {\n return null;\n }\n}\n\nfunction getUrls(domain: string): {\n deviceCodeUrl: string;\n accessTokenUrl: string;\n copilotTokenUrl: string;\n} {\n return {\n deviceCodeUrl: `https://${domain}/login/device/code`,\n accessTokenUrl: `https://${domain}/login/oauth/access_token`,\n copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n };\n}\n\n/**\n * Parse the `proxy-ep` segment from a Copilot bearer token and convert to its API base URL.\n * Token shape: `tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...`\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n const match = token.match(/proxy-ep=([^;]+)/);\n if (!match) return null;\n const proxyHost = match[1]!;\n const apiHost = proxyHost.replace(/^proxy\\./, 'api.');\n return `https://${apiHost}`;\n}\n\n/**\n * Resolve the Copilot API base URL.\n * Prefers the `proxy-ep` parsed from the bearer token, then falls back to enterprise/individual defaults.\n */\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n if (token) {\n const fromToken = getBaseUrlFromToken(token);\n if (fromToken) return fromToken;\n }\n if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n return 'https://api.individual.githubcopilot.com';\n}\n\nasync function fetchJson(url: string, init: RequestInit, signal?: AbortSignal): Promise<unknown> {\n const response = await fetch(url, { ...init, signal: requestSignal(signal) });\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}`.trim());\n }\n return response.json();\n}\n\nasync function startDeviceFlow(domain: string, signal?: AbortSignal): Promise<DeviceCodeResponse> {\n const urls = getUrls(domain);\n const data = await fetchJson(\n urls.deviceCodeUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n scope: 'read:user',\n }),\n },\n signal,\n );\n\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid device code response');\n }\n\n const obj = data as Record<string, unknown>;\n const deviceCode = obj.device_code;\n const userCode = obj.user_code;\n const verificationUri = obj.verification_uri;\n const interval = obj.interval;\n const expiresIn = obj.expires_in;\n\n if (\n typeof deviceCode !== 'string' ||\n typeof userCode !== 'string' ||\n typeof verificationUri !== 'string' ||\n typeof interval !== 'number' ||\n typeof expiresIn !== 'number'\n ) {\n throw new Error('Invalid device code response fields');\n }\n\n return {\n device_code: deviceCode,\n user_code: userCode,\n verification_uri: verificationUri,\n interval,\n expires_in: expiresIn,\n };\n}\n\n/** Sleep that can be interrupted by an AbortSignal. */\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Login cancelled'));\n return;\n }\n\n let timeout: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeout);\n reject(new Error('Login cancelled'));\n };\n\n timeout = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * Serializable pending state for a Copilot device-code login. Safe to persist\n * (e.g. a `pending jsonb` column) so polling can span HTTP requests/replicas.\n */\nexport interface CopilotDeviceLoginPending {\n /** GitHub host performing the flow (`github.com` or an Enterprise hostname). */\n domain: string;\n /** Enterprise hostname, or undefined for github.com. Recorded on the final credentials. */\n enterpriseDomain?: string;\n deviceCode: string;\n userCode: string;\n /** Verification URL for the user to open. */\n url: string;\n instructions: string;\n /** Current base poll interval in ms (grows on slow_down). */\n intervalMs: number;\n /** Safety margin applied to intervalMs when computing the next wait. */\n intervalMultiplier: number;\n /** Number of slow_down responses seen (used for the clock-drift hint). */\n slowDownCount: number;\n /** ms epoch after which the device authorization expires. */\n deadlineAt: number;\n}\n\nexport type CopilotDevicePollResult =\n | { status: 'complete'; credentials: GitHubCopilotCredentials }\n | { status: 'pending'; pending: CopilotDeviceLoginPending; nextPollMs: number }\n | { status: 'failed'; error: string };\n\n/** Delay to wait before the next upstream poll for a pending Copilot device login. */\nexport function copilotNextPollDelayMs(pending: CopilotDeviceLoginPending): number {\n return Math.ceil(pending.intervalMs * pending.intervalMultiplier);\n}\n\n/**\n * Start a Copilot device-code login: request a user code and return the\n * serializable pending state for subsequent polls. Wait `copilotNextPollDelayMs`\n * before the first poll.\n */\nexport async function startGitHubCopilotDeviceLogin(\n enterpriseDomain?: string,\n options?: { signal?: AbortSignal },\n): Promise<CopilotDeviceLoginPending> {\n const domain = enterpriseDomain || 'github.com';\n const device = await startDeviceFlow(domain, options?.signal);\n\n return {\n domain,\n ...(enterpriseDomain ? { enterpriseDomain } : {}),\n deviceCode: device.device_code,\n userCode: device.user_code,\n url: device.verification_uri,\n instructions: `Enter code: ${device.user_code}`,\n intervalMs: Math.max(1000, Math.floor(device.interval * 1000)),\n intervalMultiplier: INITIAL_POLL_INTERVAL_MULTIPLIER,\n slowDownCount: 0,\n deadlineAt: Date.now() + device.expires_in * 1000,\n };\n}\n\n/**\n * Perform exactly one upstream poll for a pending Copilot device login.\n * On success, exchanges the GitHub OAuth token for a Copilot bearer token and\n * returns full credentials. Never throws for flow-level conditions; network\n * errors from fetch still propagate.\n */\nexport async function pollGitHubCopilotDeviceLogin(\n pending: CopilotDeviceLoginPending,\n options?: { signal?: AbortSignal; onProgress?: (message: string) => void },\n): Promise<CopilotDevicePollResult> {\n if (Date.now() >= pending.deadlineAt) {\n if (pending.slowDownCount > 0) {\n return {\n status: 'failed',\n error:\n 'Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.',\n };\n }\n return { status: 'failed', error: 'Device flow timed out' };\n }\n\n const urls = getUrls(pending.domain);\n const raw = await fetchJson(\n urls.accessTokenUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n device_code: pending.deviceCode,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }),\n },\n options?.signal,\n );\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenSuccessResponse).access_token === 'string') {\n const githubAccessToken = (raw as DeviceTokenSuccessResponse).access_token;\n options?.onProgress?.('Fetching Copilot token...');\n const credentials = await refreshGitHubCopilotToken(githubAccessToken, pending.enterpriseDomain, options?.signal);\n return { status: 'complete', credentials };\n }\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenErrorResponse).error === 'string') {\n const { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;\n\n if (error === 'slow_down') {\n const next: CopilotDeviceLoginPending = {\n ...pending,\n slowDownCount: pending.slowDownCount + 1,\n intervalMs:\n typeof interval === 'number' && interval > 0 ? interval * 1000 : Math.max(1000, pending.intervalMs + 5000),\n intervalMultiplier: SLOW_DOWN_POLL_INTERVAL_MULTIPLIER,\n };\n return { status: 'pending', pending: next, nextPollMs: copilotNextPollDelayMs(next) };\n }\n\n if (error !== 'authorization_pending') {\n const descriptionSuffix = description ? `: ${description}` : '';\n return { status: 'failed', error: `Device flow failed: ${error}${descriptionSuffix}` };\n }\n }\n\n // authorization_pending or an unrecognized-but-OK response: keep polling.\n return { status: 'pending', pending, nextPollMs: copilotNextPollDelayMs(pending) };\n}\n\n/**\n * Refresh the short-lived Copilot bearer token using the long-lived GitHub OAuth token.\n *\n * `refreshToken` here is the GitHub OAuth access token; the Copilot endpoint returns a\n * fresh bearer token (`token`) and absolute expiry (`expires_at`, seconds since epoch).\n */\nexport async function refreshGitHubCopilotToken(\n refreshToken: string,\n enterpriseDomain?: string,\n signal?: AbortSignal,\n): Promise<GitHubCopilotCredentials> {\n const domain = enterpriseDomain || 'github.com';\n const urls = getUrls(domain);\n\n const raw = await fetchJson(\n urls.copilotTokenUrl,\n {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${refreshToken}`,\n ...COPILOT_HEADERS,\n },\n },\n signal,\n );\n\n if (!raw || typeof raw !== 'object') {\n throw new Error('Invalid Copilot token response');\n }\n\n const obj = raw as Record<string, unknown>;\n const token = obj.token;\n const expiresAt = obj.expires_at;\n\n if (typeof token !== 'string' || typeof expiresAt !== 'number') {\n throw new Error('Invalid Copilot token response fields');\n }\n\n const credentials: GitHubCopilotCredentials = {\n refresh: refreshToken,\n access: token,\n // expires_at is seconds; subtract 5 minutes so we refresh before actual expiry.\n expires: expiresAt * 1000 - 5 * 60 * 1000,\n };\n if (enterpriseDomain) {\n credentials.enterpriseUrl = enterpriseDomain;\n }\n return credentials;\n}\n\n/**\n * Login with GitHub Copilot OAuth (device-code flow).\n *\n * Prompts for an optional GitHub Enterprise URL/domain, performs the device-code flow,\n * then exchanges the GitHub OAuth token for a Copilot bearer token.\n */\nexport async function loginGitHubCopilot(options: {\n onAuth: (url: string, instructions?: string) => void;\n onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n onProgress?: (message: string) => void;\n signal?: AbortSignal;\n}): Promise<GitHubCopilotCredentials> {\n const input = await options.onPrompt({\n message: 'GitHub Enterprise URL/domain (blank for github.com)',\n placeholder: 'company.ghe.com',\n allowEmpty: true,\n });\n\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n const trimmed = input.trim();\n const enterpriseDomain = normalizeDomain(input);\n if (trimmed && !enterpriseDomain) {\n throw new Error('Invalid GitHub Enterprise URL/domain');\n }\n let pending = await startGitHubCopilotDeviceLogin(enterpriseDomain ?? undefined, { signal: options.signal });\n options.onAuth(pending.url, pending.instructions);\n\n while (true) {\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n // Wait before polling (safety margin over the server interval), clamped to the deadline.\n const remainingMs = Math.max(pending.deadlineAt - Date.now(), 0);\n await abortableSleep(Math.min(copilotNextPollDelayMs(pending), remainingMs), options.signal);\n\n const result = await pollGitHubCopilotDeviceLogin(pending, {\n signal: options.signal,\n onProgress: options.onProgress,\n });\n if (result.status === 'complete') {\n return result.credentials;\n }\n if (result.status === 'failed') {\n throw new Error(result.error);\n }\n pending = result.pending;\n }\n}\n\n/**\n * Filtered, normalized entry from the Copilot `/models` API.\n *\n * The full API payload includes a lot of capability metadata; we only keep the bits\n * we need to expose models in `listAvailableModels()` and route requests sensibly.\n */\nexport type CopilotModelEntry = {\n /** Stable model id (e.g. `claude-sonnet-4.5`, `gpt-4.1`). */\n id: string;\n /** Human-readable display name (e.g. `Claude Sonnet 4.5`). */\n name: string;\n /** Vendor field from the API (e.g. `Anthropic`, `OpenAI`). */\n vendor: string;\n /**\n * Endpoints the model exposes (e.g. `/chat/completions`, `/responses`, `/v1/messages`).\n * Empty when the API didn't return `supported_endpoints` for this entry.\n */\n supportedEndpoints: string[];\n /** True when `supported_endpoints` includes `/v1/messages` (Anthropic-shaped Copilot model). */\n isAnthropicShaped: boolean;\n /** True when `capabilities.supports.vision` is true. */\n supportsVision: boolean;\n /** True when the model supports tool calling. */\n supportsToolCalls: boolean;\n};\n\n/**\n * Fetch the Copilot model list available to the current subscription.\n *\n * Hits `${baseURL}/models` with the Copilot bearer token, filters to\n * `model_picker_enabled === true && policy.state !== 'disabled'`, and returns\n * a normalized list. Mirrors opencode's filtering rules.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/models.ts\n */\nexport async function fetchCopilotModels(opts: {\n baseUrl: string;\n bearerToken: string;\n signal?: AbortSignal;\n}): Promise<CopilotModelEntry[]> {\n const url = `${opts.baseUrl.replace(/\\/$/, '')}/models`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${opts.bearerToken}`,\n ...COPILOT_HEADERS,\n },\n signal: requestSignal(opts.signal),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch Copilot models: ${response.status} ${response.statusText}`.trim());\n }\n\n const json = await response.json().catch(() => null);\n if (!json || typeof json !== 'object' || !Array.isArray((json as { data?: unknown }).data)) {\n throw new Error('Invalid Copilot models response: missing `data` array');\n }\n\n const data = (json as { data: unknown[] }).data;\n const result: CopilotModelEntry[] = [];\n\n for (const item of data) {\n if (!item || typeof item !== 'object') continue;\n const obj = item as Record<string, unknown>;\n\n if (obj.model_picker_enabled !== true) continue;\n\n const policy = obj.policy as Record<string, unknown> | undefined;\n if (policy && policy.state === 'disabled') continue;\n\n const id = obj.id;\n if (typeof id !== 'string' || !id) continue;\n\n const name = typeof obj.name === 'string' ? obj.name : id;\n const vendor = typeof obj.vendor === 'string' ? obj.vendor : '';\n\n const capabilities = obj.capabilities as Record<string, unknown> | undefined;\n const supports = capabilities?.supports as Record<string, unknown> | undefined;\n const supportsVision = supports?.vision === true;\n const supportsToolCalls = supports?.tool_calls === true;\n\n const rawEndpoints = obj.supported_endpoints;\n const supportedEndpoints = Array.isArray(rawEndpoints)\n ? rawEndpoints.filter((e): e is string => typeof e === 'string')\n : [];\n const isAnthropicShaped = supportedEndpoints.includes('/v1/messages');\n\n result.push({\n id,\n name,\n vendor,\n supportedEndpoints,\n isAnthropicShaped,\n supportsVision,\n supportsToolCalls,\n });\n }\n\n return result;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n id: 'github-copilot',\n name: 'GitHub Copilot',\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n return loginGitHubCopilot({\n onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n signal: callbacks.signal,\n });\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const creds = credentials as GitHubCopilotCredentials;\n return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n\n /**\n * The stored refresh token is the long-lived GitHub OAuth token — use it to\n * resolve the account's GitHub login for the account manager. Enterprise\n * tokens don't work against api.github.com, which just falls through to the\n * label prompt/default.\n */\n async getAccountLabel(credentials: OAuthCredentials): Promise<string | undefined> {\n // Enterprise credentials authenticate against the enterprise host, not\n // GitHub.com — sending that token to api.github.com would disclose it.\n // Enterprise accounts fall through to the label prompt/default instead.\n if ((credentials as GitHubCopilotCredentials).enterpriseUrl) return undefined;\n try {\n const response = await fetch('https://api.github.com/user', {\n headers: {\n Authorization: `Bearer ${credentials.refresh}`,\n Accept: 'application/vnd.github+json',\n },\n signal: requestSignal(),\n });\n if (!response.ok) return undefined;\n const user = (await response.json()) as { login?: unknown };\n return typeof user.login === 'string' && user.login.length > 0 ? user.login : undefined;\n } catch {\n return undefined;\n }\n },\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kimi-coding.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/kimi-coding.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAyB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAuBjG,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEpE;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKnF;AA6CD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC;CAC5B;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,gBAAgB,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,4BAA4B,CAAA;CAAE,GAChF;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE;IACzD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,4BAA4B,CAAC,
|
|
1
|
+
{"version":3,"file":"kimi-coding.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/kimi-coding.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAyB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAuBjG,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEpE;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKnF;AA6CD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC;CAC5B;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,gBAAgB,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,4BAA4B,CAAA;CAAE,GAChF;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE;IACzD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAqDxC;AA+CD,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,4BAA4B,EACrC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACjC,OAAO,CAAC,0BAA0B,CAAC,CASrC;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAS/F;AAED,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC,gBAAgB,CAAC,CAoC3B;AAED,eAAO,MAAM,uBAAuB,EAAE,sBASrC,CAAC"}
|
|
@@ -80,10 +80,7 @@ async function startKimiCodingDeviceLogin(options) {
|
|
|
80
80
|
body: new URLSearchParams({ client_id: clientId }).toString(),
|
|
81
81
|
signal: requestSignal(options?.signal)
|
|
82
82
|
});
|
|
83
|
-
if (!response.ok) {
|
|
84
|
-
const text = await response.text().catch(() => "");
|
|
85
|
-
throw new Error(`Kimi For Coding device authorization failed: ${response.status}${text ? ` ${text}` : ""}`);
|
|
86
|
-
}
|
|
83
|
+
if (!response.ok) throw new Error(`Kimi For Coding device authorization failed: ${response.status}`);
|
|
87
84
|
const data = await readJson(response);
|
|
88
85
|
const deviceCode = data?.device_code;
|
|
89
86
|
const userCode = data?.user_code;
|
|
@@ -146,10 +143,9 @@ async function pollKimiCodingTokenOnce(pending, signal) {
|
|
|
146
143
|
status: "failed",
|
|
147
144
|
error: "Kimi For Coding login was denied."
|
|
148
145
|
};
|
|
149
|
-
const description = typeof data?.error_description === "string" ? `: ${data.error_description}` : "";
|
|
150
146
|
return {
|
|
151
147
|
status: "failed",
|
|
152
|
-
error: `Kimi For Coding token request failed: ${response.status}
|
|
148
|
+
error: `Kimi For Coding token request failed: ${response.status}`
|
|
153
149
|
};
|
|
154
150
|
}
|
|
155
151
|
async function pollKimiCodingDeviceLogin(pending, options) {
|