@mastra/code-sdk 0.2.0-alpha.4 → 0.2.0-alpha.8
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/CHANGELOG.md +93 -0
- package/dist/agents/credential-resolver.d.ts +51 -0
- package/dist/agents/credential-resolver.d.ts.map +1 -0
- package/dist/agents/credential-resolver.js +41 -0
- package/dist/agents/credential-resolver.js.map +1 -0
- package/dist/agents/instructions.d.ts.map +1 -1
- package/dist/agents/instructions.js.map +1 -1
- package/dist/agents/mastracode-gateway.d.ts +11 -4
- package/dist/agents/mastracode-gateway.d.ts.map +1 -1
- package/dist/agents/mastracode-gateway.js +51 -30
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/model.d.ts +2 -0
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/model.js +13 -2
- package/dist/agents/model.js.map +1 -1
- package/dist/agents/workspace.d.ts +11 -1
- package/dist/agents/workspace.d.ts.map +1 -1
- package/dist/agents/workspace.js +31 -13
- package/dist/agents/workspace.js.map +1 -1
- package/dist/auth/authorization-input.d.ts +16 -0
- package/dist/auth/authorization-input.d.ts.map +1 -0
- package/dist/auth/authorization-input.js +28 -0
- package/dist/auth/authorization-input.js.map +1 -0
- package/dist/auth/device-code.d.ts +93 -0
- package/dist/auth/device-code.d.ts.map +1 -0
- package/dist/auth/device-code.js +97 -0
- package/dist/auth/device-code.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 +3 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/providers/anthropic.d.ts +24 -1
- package/dist/auth/providers/anthropic.d.ts.map +1 -1
- package/dist/auth/providers/anthropic.js +21 -8
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/github-copilot.d.ts +54 -0
- package/dist/auth/providers/github-copilot.d.ts.map +1 -1
- package/dist/auth/providers/github-copilot.js +88 -61
- package/dist/auth/providers/github-copilot.js.map +1 -1
- package/dist/auth/providers/openai-codex.d.ts +44 -0
- package/dist/auth/providers/openai-codex.d.ts.map +1 -1
- package/dist/auth/providers/openai-codex.js +79 -69
- package/dist/auth/providers/openai-codex.js.map +1 -1
- package/dist/auth/providers/xai.d.ts +64 -0
- package/dist/auth/providers/xai.d.ts.map +1 -0
- package/dist/auth/providers/xai.js +171 -0
- package/dist/auth/providers/xai.js.map +1 -0
- package/dist/auth/storage.d.ts.map +1 -1
- package/dist/auth/storage.js +5 -2
- package/dist/auth/storage.js.map +1 -1
- package/dist/auth/types.d.ts +21 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/types.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -35
- package/dist/index.js.map +1 -1
- package/dist/providers/amazon-bedrock-gateway.d.ts +7 -0
- package/dist/providers/amazon-bedrock-gateway.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock-gateway.js +66 -2
- package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
- package/dist/providers/claude-max.d.ts +3 -1
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/claude-max.js +1 -1
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot.d.ts +3 -1
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +1 -1
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/openai-codex.d.ts +3 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +1 -1
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/xai.d.ts +33 -0
- package/dist/providers/xai.d.ts.map +1 -0
- package/dist/providers/xai.js +63 -0
- package/dist/providers/xai.js.map +1 -0
- package/dist/tools/request-sandbox-access.d.ts.map +1 -1
- package/dist/tools/request-sandbox-access.js.map +1 -1
- package/package.json +13 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/auth/providers/openai-codex.ts"],"sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * Inspired by pi-mono's OAuth implementation:\n * https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/openai-codex.ts\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _randomBytes: ((size: number) => Buffer) | null = null;\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nlet _cryptoPromise: Promise<typeof import('node:crypto')> | null = null;\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nlet _httpPromise: Promise<typeof import('node:http')> | null = null;\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nlet _http: typeof import('node:http') | null = null;\ntype HttpServer = {\n off: (event: 'error' | 'listening', listener: (...args: any[]) => void) => HttpServer;\n once: (event: 'error' | 'listening', listener: (...args: any[]) => void) => HttpServer;\n listen: (port: number, hostname: string) => HttpServer;\n close: () => void;\n};\nif (typeof process !== 'undefined' && (process.versions?.node || process.versions?.bun)) {\n _cryptoPromise = import('node:crypto').then(m => {\n _randomBytes = m.randomBytes;\n return m;\n });\n _httpPromise = import('node:http').then(m => {\n _http = m;\n return m;\n });\n}\n\nimport { generatePKCE } from '../pkce.js';\nimport type { AuthMode, OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProviderInterface } from '../types.js';\n\nexport const OPENAI_CODEX_AUTH_MODES: ReadonlyArray<AuthMode> = [\n {\n id: 'browser',\n name: 'Browser (local callback)',\n description: 'Opens the browser and waits for the OAuth callback on localhost.',\n },\n {\n id: 'device',\n name: 'Device code (headless)',\n description: 'Shows a code to enter at openai.com — for SSH, remote, or no-browser environments.',\n },\n];\n\nconst CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';\nconst ISSUER = 'https://auth.openai.com';\nconst AUTHORIZE_URL = `${ISSUER}/oauth/authorize`;\nconst TOKEN_URL = `${ISSUER}/oauth/token`;\nconst DEVICE_USER_CODE_URL = `${ISSUER}/api/accounts/deviceauth/usercode`;\nconst DEVICE_TOKEN_URL = `${ISSUER}/api/accounts/deviceauth/token`;\nconst DEVICE_AUTHORIZE_URL = `${ISSUER}/codex/device`;\nconst DEVICE_REDIRECT_URI = `${ISSUER}/deviceauth/callback`;\nconst DEFAULT_CALLBACK_PORT = 1455;\nconst FALLBACK_CALLBACK_PORT = 1457;\nconst DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 3600;\nconst DEVICE_AUTH_TIMEOUT_MS = 15 * 60 * 1000;\nconst SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke';\nconst JWT_CLAIM_PATH = 'https://api.openai.com/auth';\n\nconst SUCCESS_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Authentication successful</title>\n</head>\n<body>\n <p>Authentication successful. Return to your terminal to continue.</p>\n</body>\n</html>`;\n\ntype TokenSuccess = {\n type: 'success';\n access: string;\n refresh: string;\n expires: number;\n idToken?: string;\n};\ntype TokenFailure = { type: 'failed' };\ntype TokenResult = TokenSuccess | TokenFailure;\n\ntype JwtPayload = {\n chatgpt_account_id?: string;\n [JWT_CLAIM_PATH]?: {\n chatgpt_account_id?: string;\n };\n [key: string]: unknown;\n};\n\nasync function createState(): Promise<string> {\n const randomBytes = await getRandomBytes();\n return randomBytes(16).toString('hex');\n}\n\nfunction parseAuthorizationInput(input: string): {\n code?: string;\n state?: string;\n} {\n const value = input.trim();\n if (!value) return {};\n\n try {\n const url = new URL(value);\n return {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n };\n } catch {\n // not a URL\n }\n\n if (value.includes('#')) {\n const [code, state] = value.split('#', 2);\n return { code, state };\n }\n\n if (value.includes('code=')) {\n const params = new URLSearchParams(value);\n return {\n code: params.get('code') ?? undefined,\n state: params.get('state') ?? undefined,\n };\n }\n\n return { code: value };\n}\n\nfunction decodeJwt(token: string): JwtPayload | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const payload = parts[1] ?? '';\n const padded = payload\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(Math.ceil(payload.length / 4) * 4, '=');\n const decoded = atob(padded);\n return JSON.parse(decoded) as JwtPayload;\n } catch {\n return null;\n }\n}\n\nfunction extractAccountIdFromClaims(payload: JwtPayload | null | undefined): string | null {\n if (!payload) return null;\n const accountId = payload.chatgpt_account_id ?? payload[JWT_CLAIM_PATH]?.chatgpt_account_id;\n return typeof accountId === 'string' && accountId.length > 0 ? accountId : null;\n}\n\nfunction getAccountId(tokens: { idToken?: string; access: string }, fallback?: string): string | undefined {\n const fromIdToken = tokens.idToken ? extractAccountIdFromClaims(decodeJwt(tokens.idToken)) : null;\n if (fromIdToken) return fromIdToken;\n\n const fromAccessToken = extractAccountIdFromClaims(decodeJwt(tokens.access));\n if (fromAccessToken) return fromAccessToken;\n\n return fallback;\n}\n\nfunction requireAccountId(tokens: { idToken?: string; access: string }, fallback?: string): string {\n const accountId = getAccountId(tokens, fallback);\n if (!accountId) {\n throw new Error('Failed to extract ChatGPT account id from OpenAI Codex token');\n }\n return accountId;\n}\n\ntype TokenResponseJson = {\n id_token?: string;\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n};\n\nfunction tokenResponseToResult(json: TokenResponseJson, logPrefix: string): TokenResult {\n if (!json.access_token || !json.refresh_token) {\n console.error(`[openai-codex] ${logPrefix} response missing fields:`, json);\n return { type: 'failed' };\n }\n\n return {\n type: 'success',\n access: json.access_token,\n refresh: json.refresh_token,\n expires: Date.now() + (json.expires_in ?? DEFAULT_TOKEN_EXPIRES_IN_SECONDS) * 1000,\n idToken: json.id_token,\n };\n}\n\nasync function exchangeAuthorizationCode(code: string, verifier: string, redirectUri: string): Promise<TokenResult> {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n console.error('[openai-codex] code->token failed:', response.status, text);\n return { type: 'failed' };\n }\n\n return tokenResponseToResult((await response.json()) as TokenResponseJson, 'token');\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<TokenResult> {\n try {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: CLIENT_ID,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n console.error('[openai-codex] Token refresh failed:', response.status, text);\n return { type: 'failed' };\n }\n\n return tokenResponseToResult((await response.json()) as TokenResponseJson, 'Token refresh');\n } catch (error) {\n console.error('[openai-codex] Token refresh error:', error);\n return { type: 'failed' };\n }\n}\n\nasync function getRandomBytes() {\n if (!_randomBytes && _cryptoPromise) {\n _randomBytes = (await _cryptoPromise).randomBytes;\n }\n if (!_randomBytes) {\n throw new Error('OpenAI Codex OAuth is only available in Node.js environments');\n }\n return _randomBytes;\n}\n\nasync function createAuthorizationFlow(\n redirectUri: string,\n state: string,\n originator: string = 'mastracode',\n): Promise<{ verifier: string; url: string }> {\n const { verifier, challenge } = await generatePKCE();\n\n const url = new URL(AUTHORIZE_URL);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', CLIENT_ID);\n url.searchParams.set('redirect_uri', redirectUri);\n url.searchParams.set('scope', SCOPE);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', state);\n url.searchParams.set('id_token_add_organizations', 'true');\n url.searchParams.set('codex_cli_simplified_flow', 'true');\n url.searchParams.set('originator', originator);\n\n return { verifier, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n redirectUri: string;\n warning?: string;\n close: () => void;\n cancelWait: () => void;\n waitForCode: () => Promise<{ code: string } | null>;\n};\n\ntype CallbackPorts = {\n defaultPort: number;\n fallbackPort: number;\n};\n\nconst CODEX_CALLBACK_PORTS: CallbackPorts = {\n defaultPort: DEFAULT_CALLBACK_PORT,\n fallbackPort: FALLBACK_CALLBACK_PORT,\n};\n\nasync function requestCancel(port: number): Promise<void> {\n try {\n await fetch(`http://127.0.0.1:${port}/cancel`, { signal: AbortSignal.timeout(200) });\n } catch {\n // The existing listener might not be a Codex auth server.\n }\n}\n\nfunction listen(server: HttpServer, port: number): Promise<boolean> {\n return new Promise(resolve => {\n const onError = () => {\n server.off('listening', onListening);\n resolve(false);\n };\n const onListening = () => {\n server.off('error', onError);\n resolve(true);\n };\n\n server.once('error', onError);\n server.once('listening', onListening);\n server.listen(port, '127.0.0.1');\n });\n}\n\nasync function bindOAuthServer(server: HttpServer, ports: CallbackPorts): Promise<number | null> {\n await requestCancel(ports.defaultPort);\n if (await listen(server, ports.defaultPort)) return ports.defaultPort;\n if (await listen(server, ports.fallbackPort)) return ports.fallbackPort;\n\n return null;\n}\n\nasync function getHttpModule() {\n if (!_http && _httpPromise) {\n _http = await _httpPromise;\n }\n if (!_http) {\n throw new Error('OpenAI Codex OAuth is only available in Node.js environments');\n }\n return _http;\n}\n\nasync function startLocalOAuthServer(\n state: string,\n ports: CallbackPorts = CODEX_CALLBACK_PORTS,\n): Promise<OAuthServerInfo> {\n const http = await getHttpModule();\n let lastCode: string | null = null;\n let cancelled = false;\n const server = http.createServer((req, res) => {\n try {\n const url = new URL(req.url || '', 'http://localhost');\n if (url.pathname === '/cancel') {\n cancelled = true;\n res.statusCode = 200;\n res.end('Cancelled');\n return;\n }\n if (url.pathname !== '/auth/callback') {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n if (url.searchParams.get('state') !== state) {\n res.statusCode = 400;\n res.end('State mismatch');\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n res.statusCode = 400;\n res.end('Missing authorization code');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(SUCCESS_HTML);\n lastCode = code;\n } catch {\n res.statusCode = 500;\n res.end('Internal error');\n }\n });\n\n return new Promise(resolve => {\n bindOAuthServer(server, ports).then(port => {\n if (!port) {\n resolve({\n redirectUri: `http://localhost:${ports.fallbackPort}/auth/callback`,\n warning: `OpenAI Codex OAuth requires localhost port ${ports.defaultPort} or ${ports.fallbackPort}, but both are in use. Automatic browser callback will not work until one is freed.`,\n close: () => {\n try {\n server.close();\n } catch {\n // ignore\n }\n },\n cancelWait: () => {},\n waitForCode: async () => null,\n });\n return;\n }\n\n resolve({\n redirectUri: `http://localhost:${port}/auth/callback`,\n close: () => server.close(),\n cancelWait: () => {\n cancelled = true;\n },\n waitForCode: async () => {\n const sleep = () => new Promise(r => setTimeout(r, 100));\n for (let i = 0; i < 600; i += 1) {\n if (lastCode) return { code: lastCode };\n if (cancelled) return null;\n await sleep();\n }\n return null;\n },\n });\n });\n });\n}\n\nasync function loginOpenAICodexDevice(options: {\n onAuth: (info: { url: string; instructions?: string }) => void;\n onProgress?: (message: string) => void;\n signal?: AbortSignal;\n sleep?: (ms: number) => Promise<void>;\n}): Promise<OAuthCredentials> {\n const response = await fetch(DEVICE_USER_CODE_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'mastracode',\n },\n body: JSON.stringify({ client_id: CLIENT_ID, originator: 'mastracode' }),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to initiate OpenAI Codex device authorization: ${response.status}`);\n }\n\n const deviceData = (await response.json()) as {\n device_auth_id?: string;\n user_code?: string;\n usercode?: string;\n interval?: string | number;\n };\n\n const userCode = deviceData.user_code ?? deviceData.usercode;\n\n if (!deviceData.device_auth_id || !userCode) {\n throw new Error('OpenAI Codex device authorization response missing required fields');\n }\n\n const intervalSeconds =\n typeof deviceData.interval === 'number' ? deviceData.interval : Number.parseInt(deviceData.interval ?? '', 10) || 5;\n const pollDelayMs = Math.max(intervalSeconds, 1) * 1000;\n const sleep = options.sleep ?? (ms => new Promise<void>(resolve => setTimeout(resolve, ms)));\n const startedAt = Date.now();\n\n options.onAuth({\n url: DEVICE_AUTHORIZE_URL,\n instructions: `Enter code: ${userCode}`,\n });\n\n while (true) {\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n if (Date.now() - startedAt >= DEVICE_AUTH_TIMEOUT_MS) {\n throw new Error('OpenAI Codex device authorization timed out after 15 minutes');\n }\n\n const pollResponse = await fetch(DEVICE_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'mastracode',\n },\n body: JSON.stringify({\n device_auth_id: deviceData.device_auth_id,\n user_code: userCode,\n }),\n signal: options.signal,\n });\n\n if (pollResponse.ok) {\n const data = (await pollResponse.json()) as {\n authorization_code?: string;\n code_verifier?: string;\n };\n\n if (!data.authorization_code || !data.code_verifier) {\n throw new Error('OpenAI Codex device token response missing required fields');\n }\n\n const tokenResult = await exchangeAuthorizationCode(\n data.authorization_code,\n data.code_verifier,\n DEVICE_REDIRECT_URI,\n );\n if (tokenResult.type !== 'success') {\n throw new Error('Token exchange failed');\n }\n\n const accountId = requireAccountId(tokenResult);\n return {\n access: tokenResult.access,\n refresh: tokenResult.refresh,\n expires: tokenResult.expires,\n accountId,\n };\n }\n\n if (pollResponse.status !== 403 && pollResponse.status !== 404) {\n const text = await pollResponse.text().catch(() => '');\n throw new Error(`OpenAI Codex device authorization failed: ${pollResponse.status}${text ? ` ${text}` : ''}`);\n }\n\n options.onProgress?.('Waiting for OpenAI Codex device authorization...');\n await sleep(pollDelayMs);\n }\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n * @param options.originator - OAuth originator parameter (defaults to \"mastracode\")\n */\nexport async function loginOpenAICodex(options: {\n onAuth: (info: { url: string; instructions?: string }) => void;\n onPrompt: (prompt: OAuthPrompt) => Promise<string>;\n onProgress?: (message: string) => void;\n onManualCodeInput?: () => Promise<string>;\n signal?: AbortSignal;\n originator?: string;\n mode?: 'browser' | 'device';\n}): Promise<OAuthCredentials> {\n const envMode =\n typeof process !== 'undefined' && process.env?.MASTRACODE_OPENAI_CODEX_AUTH_MODE === 'device'\n ? 'device'\n : undefined;\n const mode = options.mode ?? envMode ?? 'browser';\n if (mode === 'device') {\n return loginOpenAICodexDevice({\n onAuth: options.onAuth,\n onProgress: options.onProgress,\n signal: options.signal,\n });\n }\n\n const state = await createState();\n const server = await startLocalOAuthServer(state);\n if (server.warning) {\n options.onProgress?.(server.warning);\n }\n const { verifier, url } = await createAuthorizationFlow(\n server.redirectUri,\n state,\n options.originator ?? 'mastracode',\n );\n\n options.onAuth({\n url,\n instructions: server.warning\n ? `${server.warning} You can still paste the authorization code or full redirect URL manually.`\n : 'A browser window should open. Complete login to finish.',\n });\n\n let code: string | undefined;\n try {\n if (options.onManualCodeInput) {\n // Race between browser callback and manual input\n let manualCode: string | undefined;\n let manualError: Error | undefined;\n const manualPromise = options\n .onManualCodeInput()\n .then(input => {\n manualCode = input;\n server.cancelWait();\n })\n .catch(err => {\n manualError = err instanceof Error ? err : new Error(String(err));\n server.cancelWait();\n });\n\n const result = await server.waitForCode();\n\n // If manual input was cancelled, throw that error\n if (manualError) {\n throw manualError;\n }\n\n if (result?.code) {\n // Browser callback won\n code = result.code;\n } else if (manualCode) {\n // Manual input won (or callback timed out and user had entered code)\n const parsed = parseAuthorizationInput(manualCode);\n if (parsed.state && parsed.state !== state) {\n throw new Error('State mismatch');\n }\n code = parsed.code;\n }\n\n // If still no code, wait for manual promise to complete and try that\n if (!code) {\n await manualPromise;\n if (manualError) {\n throw manualError;\n }\n if (manualCode) {\n const parsed = parseAuthorizationInput(manualCode);\n if (parsed.state && parsed.state !== state) {\n throw new Error('State mismatch');\n }\n code = parsed.code;\n }\n }\n } else {\n // Original flow: wait for callback, then prompt if needed\n const result = await server.waitForCode();\n if (result?.code) {\n code = result.code;\n }\n }\n\n // Fallback to onPrompt if still no code\n if (!code) {\n const input = await options.onPrompt({\n message: 'Paste the authorization code (or full redirect URL):',\n });\n const parsed = parseAuthorizationInput(input);\n if (parsed.state && parsed.state !== state) {\n throw new Error('State mismatch');\n }\n code = parsed.code;\n }\n\n if (!code) {\n throw new Error('Missing authorization code');\n }\n\n const tokenResult = await exchangeAuthorizationCode(code, verifier, server.redirectUri);\n if (tokenResult.type !== 'success') {\n throw new Error('Token exchange failed');\n }\n\n const accountId = requireAccountId(tokenResult);\n\n return {\n access: tokenResult.access,\n refresh: tokenResult.refresh,\n expires: tokenResult.expires,\n accountId,\n };\n } finally {\n server.close();\n }\n}\n\nexport const __testing = {\n createAuthorizationFlow,\n decodeJwt,\n extractAccountIdFromClaims,\n getAccountId,\n loginOpenAICodexDevice,\n requireAccountId,\n startLocalOAuthServer,\n};\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(\n refreshToken: string,\n previousAccountId?: string,\n): Promise<OAuthCredentials> {\n const result = await refreshAccessToken(refreshToken);\n if (result.type !== 'success') {\n throw new Error('Failed to refresh OpenAI Codex token');\n }\n\n const accountId = requireAccountId(result, previousAccountId);\n\n return {\n access: result.access,\n refresh: result.refresh,\n expires: result.expires,\n accountId,\n };\n}\n\nexport const openaiCodexOAuthProvider: OAuthProviderInterface = {\n id: 'openai-codex',\n name: 'ChatGPT Plus/Pro (Codex Subscription)',\n usesCallbackServer: true,\n authModes: OPENAI_CODEX_AUTH_MODES,\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const mode = callbacks.authMode === 'device' || callbacks.authMode === 'browser' ? callbacks.authMode : undefined;\n return loginOpenAICodex({\n onAuth: callbacks.onAuth,\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n onManualCodeInput: callbacks.onManualCodeInput,\n signal: callbacks.signal,\n mode,\n });\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n return refreshOpenAICodexToken(credentials.refresh, credentials.accountId as string | undefined);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/auth/providers/openai-codex.ts"],"sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * Inspired by pi-mono's OAuth implementation:\n * https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/openai-codex.ts\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _randomBytes: ((size: number) => Buffer) | null = null;\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nlet _cryptoPromise: Promise<typeof import('node:crypto')> | null = null;\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nlet _httpPromise: Promise<typeof import('node:http')> | null = null;\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nlet _http: typeof import('node:http') | null = null;\ntype HttpServer = {\n off: (event: 'error' | 'listening', listener: (...args: any[]) => void) => HttpServer;\n once: (event: 'error' | 'listening', listener: (...args: any[]) => void) => HttpServer;\n listen: (port: number, hostname: string) => HttpServer;\n close: () => void;\n};\nif (typeof process !== 'undefined' && (process.versions?.node || process.versions?.bun)) {\n _cryptoPromise = import('node:crypto').then(m => {\n _randomBytes = m.randomBytes;\n return m;\n });\n _httpPromise = import('node:http').then(m => {\n _http = m;\n return m;\n });\n}\n\nimport { parseAuthorizationInput } from '../authorization-input.js';\nimport { generatePKCE } from '../pkce.js';\nimport type { AuthMode, OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProviderInterface } from '../types.js';\n\nexport const OPENAI_CODEX_AUTH_MODES: ReadonlyArray<AuthMode> = [\n {\n id: 'browser',\n name: 'Browser (local callback)',\n description: 'Opens the browser and waits for the OAuth callback on localhost.',\n },\n {\n id: 'device',\n name: 'Device code (headless)',\n description: 'Shows a code to enter at openai.com — for SSH, remote, or no-browser environments.',\n },\n];\n\nconst CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';\nconst ISSUER = 'https://auth.openai.com';\nconst AUTHORIZE_URL = `${ISSUER}/oauth/authorize`;\nconst TOKEN_URL = `${ISSUER}/oauth/token`;\nconst DEVICE_USER_CODE_URL = `${ISSUER}/api/accounts/deviceauth/usercode`;\nconst DEVICE_TOKEN_URL = `${ISSUER}/api/accounts/deviceauth/token`;\nconst DEVICE_AUTHORIZE_URL = `${ISSUER}/codex/device`;\nconst DEVICE_REDIRECT_URI = `${ISSUER}/deviceauth/callback`;\nconst DEFAULT_CALLBACK_PORT = 1455;\nconst FALLBACK_CALLBACK_PORT = 1457;\nconst DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 3600;\nconst DEVICE_AUTH_TIMEOUT_MS = 15 * 60 * 1000;\nconst SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke';\nconst JWT_CLAIM_PATH = 'https://api.openai.com/auth';\n\nconst SUCCESS_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Authentication successful</title>\n</head>\n<body>\n <p>Authentication successful. Return to your terminal to continue.</p>\n</body>\n</html>`;\n\ntype TokenSuccess = {\n type: 'success';\n access: string;\n refresh: string;\n expires: number;\n idToken?: string;\n};\ntype TokenFailure = { type: 'failed' };\ntype TokenResult = TokenSuccess | TokenFailure;\n\ntype JwtPayload = {\n chatgpt_account_id?: string;\n [JWT_CLAIM_PATH]?: {\n chatgpt_account_id?: string;\n };\n [key: string]: unknown;\n};\n\nasync function createState(): Promise<string> {\n const randomBytes = await getRandomBytes();\n return randomBytes(16).toString('hex');\n}\n\nfunction decodeJwt(token: string): JwtPayload | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const payload = parts[1] ?? '';\n const padded = payload\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(Math.ceil(payload.length / 4) * 4, '=');\n const decoded = atob(padded);\n return JSON.parse(decoded) as JwtPayload;\n } catch {\n return null;\n }\n}\n\nfunction extractAccountIdFromClaims(payload: JwtPayload | null | undefined): string | null {\n if (!payload) return null;\n const accountId = payload.chatgpt_account_id ?? payload[JWT_CLAIM_PATH]?.chatgpt_account_id;\n return typeof accountId === 'string' && accountId.length > 0 ? accountId : null;\n}\n\nfunction getAccountId(tokens: { idToken?: string; access: string }, fallback?: string): string | undefined {\n const fromIdToken = tokens.idToken ? extractAccountIdFromClaims(decodeJwt(tokens.idToken)) : null;\n if (fromIdToken) return fromIdToken;\n\n const fromAccessToken = extractAccountIdFromClaims(decodeJwt(tokens.access));\n if (fromAccessToken) return fromAccessToken;\n\n return fallback;\n}\n\nfunction requireAccountId(tokens: { idToken?: string; access: string }, fallback?: string): string {\n const accountId = getAccountId(tokens, fallback);\n if (!accountId) {\n throw new Error('Failed to extract ChatGPT account id from OpenAI Codex token');\n }\n return accountId;\n}\n\ntype TokenResponseJson = {\n id_token?: string;\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n};\n\nfunction tokenResponseToResult(json: TokenResponseJson, logPrefix: string): TokenResult {\n if (!json.access_token || !json.refresh_token) {\n console.error(`[openai-codex] ${logPrefix} response missing fields:`, json);\n return { type: 'failed' };\n }\n\n return {\n type: 'success',\n access: json.access_token,\n refresh: json.refresh_token,\n expires: Date.now() + (json.expires_in ?? DEFAULT_TOKEN_EXPIRES_IN_SECONDS) * 1000,\n idToken: json.id_token,\n };\n}\n\nasync function exchangeAuthorizationCode(code: string, verifier: string, redirectUri: string): Promise<TokenResult> {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n console.error('[openai-codex] code->token failed:', response.status, text);\n return { type: 'failed' };\n }\n\n return tokenResponseToResult((await response.json()) as TokenResponseJson, 'token');\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<TokenResult> {\n try {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: CLIENT_ID,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n console.error('[openai-codex] Token refresh failed:', response.status, text);\n return { type: 'failed' };\n }\n\n return tokenResponseToResult((await response.json()) as TokenResponseJson, 'Token refresh');\n } catch (error) {\n console.error('[openai-codex] Token refresh error:', error);\n return { type: 'failed' };\n }\n}\n\nasync function getRandomBytes() {\n if (!_randomBytes && _cryptoPromise) {\n _randomBytes = (await _cryptoPromise).randomBytes;\n }\n if (!_randomBytes) {\n throw new Error('OpenAI Codex OAuth is only available in Node.js environments');\n }\n return _randomBytes;\n}\n\nasync function createAuthorizationFlow(\n redirectUri: string,\n state: string,\n originator: string = 'mastracode',\n): Promise<{ verifier: string; url: string }> {\n const { verifier, challenge } = await generatePKCE();\n\n const url = new URL(AUTHORIZE_URL);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', CLIENT_ID);\n url.searchParams.set('redirect_uri', redirectUri);\n url.searchParams.set('scope', SCOPE);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', state);\n url.searchParams.set('id_token_add_organizations', 'true');\n url.searchParams.set('codex_cli_simplified_flow', 'true');\n url.searchParams.set('originator', originator);\n\n return { verifier, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n redirectUri: string;\n warning?: string;\n close: () => void;\n cancelWait: () => void;\n waitForCode: () => Promise<{ code: string } | null>;\n};\n\ntype CallbackPorts = {\n defaultPort: number;\n fallbackPort: number;\n};\n\nconst CODEX_CALLBACK_PORTS: CallbackPorts = {\n defaultPort: DEFAULT_CALLBACK_PORT,\n fallbackPort: FALLBACK_CALLBACK_PORT,\n};\n\nasync function requestCancel(port: number): Promise<void> {\n try {\n await fetch(`http://127.0.0.1:${port}/cancel`, { signal: AbortSignal.timeout(200) });\n } catch {\n // The existing listener might not be a Codex auth server.\n }\n}\n\nfunction listen(server: HttpServer, port: number): Promise<boolean> {\n return new Promise(resolve => {\n const onError = () => {\n server.off('listening', onListening);\n resolve(false);\n };\n const onListening = () => {\n server.off('error', onError);\n resolve(true);\n };\n\n server.once('error', onError);\n server.once('listening', onListening);\n server.listen(port, '127.0.0.1');\n });\n}\n\nasync function bindOAuthServer(server: HttpServer, ports: CallbackPorts): Promise<number | null> {\n await requestCancel(ports.defaultPort);\n if (await listen(server, ports.defaultPort)) return ports.defaultPort;\n if (await listen(server, ports.fallbackPort)) return ports.fallbackPort;\n\n return null;\n}\n\nasync function getHttpModule() {\n if (!_http && _httpPromise) {\n _http = await _httpPromise;\n }\n if (!_http) {\n throw new Error('OpenAI Codex OAuth is only available in Node.js environments');\n }\n return _http;\n}\n\nasync function startLocalOAuthServer(\n state: string,\n ports: CallbackPorts = CODEX_CALLBACK_PORTS,\n): Promise<OAuthServerInfo> {\n const http = await getHttpModule();\n let lastCode: string | null = null;\n let cancelled = false;\n const server = http.createServer((req, res) => {\n try {\n const url = new URL(req.url || '', 'http://localhost');\n if (url.pathname === '/cancel') {\n cancelled = true;\n res.statusCode = 200;\n res.end('Cancelled');\n return;\n }\n if (url.pathname !== '/auth/callback') {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n if (url.searchParams.get('state') !== state) {\n res.statusCode = 400;\n res.end('State mismatch');\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n res.statusCode = 400;\n res.end('Missing authorization code');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(SUCCESS_HTML);\n lastCode = code;\n } catch {\n res.statusCode = 500;\n res.end('Internal error');\n }\n });\n\n return new Promise(resolve => {\n bindOAuthServer(server, ports).then(port => {\n if (!port) {\n resolve({\n redirectUri: `http://localhost:${ports.fallbackPort}/auth/callback`,\n warning: `OpenAI Codex OAuth requires localhost port ${ports.defaultPort} or ${ports.fallbackPort}, but both are in use. Automatic browser callback will not work until one is freed.`,\n close: () => {\n try {\n server.close();\n } catch {\n // ignore\n }\n },\n cancelWait: () => {},\n waitForCode: async () => null,\n });\n return;\n }\n\n resolve({\n redirectUri: `http://localhost:${port}/auth/callback`,\n close: () => server.close(),\n cancelWait: () => {\n cancelled = true;\n },\n waitForCode: async () => {\n const sleep = () => new Promise(r => setTimeout(r, 100));\n for (let i = 0; i < 600; i += 1) {\n if (lastCode) return { code: lastCode };\n if (cancelled) return null;\n await sleep();\n }\n return null;\n },\n });\n });\n });\n}\n\n/**\n * Serializable pending state for a Codex device-code login. Safe to persist\n * (e.g. a `pending jsonb` column) so polling can span HTTP requests — the\n * device token response carries the `code_verifier`, so no PKCE state needs\n * to be kept client-side.\n */\nexport interface CodexDeviceLoginPending {\n deviceAuthId: string;\n userCode: string;\n /** Verification URL for the user to open. */\n url: string;\n instructions: string;\n /** Poll interval in ms suggested by the server. */\n intervalMs: number;\n /** ms epoch after which the device authorization expires. */\n deadlineAt: number;\n}\n\nexport type CodexDevicePollResult =\n | { status: 'complete'; credentials: OAuthCredentials }\n | { status: 'pending'; nextPollMs: number }\n | { status: 'failed'; error: string };\n\n/**\n * Start a Codex device-code login: request a user code and return the\n * serializable pending state for subsequent polls.\n */\nexport async function startCodexDeviceLogin(options?: { signal?: AbortSignal }): Promise<CodexDeviceLoginPending> {\n const response = await fetch(DEVICE_USER_CODE_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'mastracode',\n },\n body: JSON.stringify({ client_id: CLIENT_ID, originator: 'mastracode' }),\n signal: options?.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to initiate OpenAI Codex device authorization: ${response.status}`);\n }\n\n const deviceData = (await response.json()) as {\n device_auth_id?: string;\n user_code?: string;\n usercode?: string;\n interval?: string | number;\n };\n\n const userCode = deviceData.user_code ?? deviceData.usercode;\n\n if (!deviceData.device_auth_id || !userCode) {\n throw new Error('OpenAI Codex device authorization response missing required fields');\n }\n\n const intervalSeconds =\n typeof deviceData.interval === 'number' ? deviceData.interval : Number.parseInt(deviceData.interval ?? '', 10) || 5;\n\n return {\n deviceAuthId: deviceData.device_auth_id,\n userCode,\n url: DEVICE_AUTHORIZE_URL,\n instructions: `Enter code: ${userCode}`,\n intervalMs: Math.max(intervalSeconds, 1) * 1000,\n deadlineAt: Date.now() + DEVICE_AUTH_TIMEOUT_MS,\n };\n}\n\n/**\n * Perform exactly one upstream poll for a pending Codex device login.\n * The Codex device endpoint signals \"still pending\" via HTTP 403/404 (it is\n * not an RFC 8628 error-JSON endpoint); on success it returns the\n * authorization code plus server-held PKCE verifier, which we exchange\n * immediately for credentials. Never throws for flow-level conditions.\n */\nexport async function pollCodexDeviceLogin(\n pending: CodexDeviceLoginPending,\n options?: { signal?: AbortSignal },\n): Promise<CodexDevicePollResult> {\n if (Date.now() >= pending.deadlineAt) {\n return { status: 'failed', error: 'OpenAI Codex device authorization timed out after 15 minutes' };\n }\n\n const pollResponse = await fetch(DEVICE_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'mastracode',\n },\n body: JSON.stringify({\n device_auth_id: pending.deviceAuthId,\n user_code: pending.userCode,\n }),\n signal: options?.signal,\n });\n\n if (pollResponse.ok) {\n const data = (await pollResponse.json()) as {\n authorization_code?: string;\n code_verifier?: string;\n };\n\n if (!data.authorization_code || !data.code_verifier) {\n return { status: 'failed', error: 'OpenAI Codex device token response missing required fields' };\n }\n\n const tokenResult = await exchangeAuthorizationCode(\n data.authorization_code,\n data.code_verifier,\n DEVICE_REDIRECT_URI,\n );\n if (tokenResult.type !== 'success') {\n return { status: 'failed', error: 'Token exchange failed' };\n }\n\n let accountId: string;\n try {\n accountId = requireAccountId(tokenResult);\n } catch (error) {\n return { status: 'failed', error: error instanceof Error ? error.message : String(error) };\n }\n\n return {\n status: 'complete',\n credentials: {\n access: tokenResult.access,\n refresh: tokenResult.refresh,\n expires: tokenResult.expires,\n accountId,\n },\n };\n }\n\n if (pollResponse.status !== 403 && pollResponse.status !== 404) {\n const text = await pollResponse.text().catch(() => '');\n return {\n status: 'failed',\n error: `OpenAI Codex device authorization failed: ${pollResponse.status}${text ? ` ${text}` : ''}`,\n };\n }\n\n return { status: 'pending', nextPollMs: pending.intervalMs };\n}\n\nasync function loginOpenAICodexDevice(options: {\n onAuth: (info: { url: string; instructions?: string }) => void;\n onProgress?: (message: string) => void;\n signal?: AbortSignal;\n sleep?: (ms: number) => Promise<void>;\n}): Promise<OAuthCredentials> {\n const pending = await startCodexDeviceLogin({ signal: options.signal });\n const sleep = options.sleep ?? (ms => new Promise<void>(resolve => setTimeout(resolve, ms)));\n\n options.onAuth({\n url: pending.url,\n instructions: pending.instructions,\n });\n\n await sleep(pending.intervalMs);\n\n while (true) {\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n const result = await pollCodexDeviceLogin(pending, { signal: options.signal });\n if (result.status === 'complete') {\n return result.credentials;\n }\n if (result.status === 'failed') {\n throw new Error(result.error);\n }\n\n options.onProgress?.('Waiting for OpenAI Codex device authorization...');\n await sleep(result.nextPollMs);\n }\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n * @param options.originator - OAuth originator parameter (defaults to \"mastracode\")\n */\nexport async function loginOpenAICodex(options: {\n onAuth: (info: { url: string; instructions?: string }) => void;\n onPrompt: (prompt: OAuthPrompt) => Promise<string>;\n onProgress?: (message: string) => void;\n onManualCodeInput?: () => Promise<string>;\n signal?: AbortSignal;\n originator?: string;\n mode?: 'browser' | 'device';\n}): Promise<OAuthCredentials> {\n const envMode =\n typeof process !== 'undefined' && process.env?.MASTRACODE_OPENAI_CODEX_AUTH_MODE === 'device'\n ? 'device'\n : undefined;\n const mode = options.mode ?? envMode ?? 'browser';\n if (mode === 'device') {\n return loginOpenAICodexDevice({\n onAuth: options.onAuth,\n onProgress: options.onProgress,\n signal: options.signal,\n });\n }\n\n const state = await createState();\n const server = await startLocalOAuthServer(state);\n if (server.warning) {\n options.onProgress?.(server.warning);\n }\n const { verifier, url } = await createAuthorizationFlow(\n server.redirectUri,\n state,\n options.originator ?? 'mastracode',\n );\n\n options.onAuth({\n url,\n instructions: server.warning\n ? `${server.warning} You can still paste the authorization code or full redirect URL manually.`\n : 'A browser window should open. Complete login to finish.',\n });\n\n let code: string | undefined;\n try {\n if (options.onManualCodeInput) {\n // Race between browser callback and manual input\n let manualCode: string | undefined;\n let manualError: Error | undefined;\n const manualPromise = options\n .onManualCodeInput()\n .then(input => {\n manualCode = input;\n server.cancelWait();\n })\n .catch(err => {\n manualError = err instanceof Error ? err : new Error(String(err));\n server.cancelWait();\n });\n\n const result = await server.waitForCode();\n\n // If manual input was cancelled, throw that error\n if (manualError) {\n throw manualError;\n }\n\n if (result?.code) {\n // Browser callback won\n code = result.code;\n } else if (manualCode) {\n // Manual input won (or callback timed out and user had entered code)\n const parsed = parseAuthorizationInput(manualCode);\n if (parsed.state && parsed.state !== state) {\n throw new Error('State mismatch');\n }\n code = parsed.code;\n }\n\n // If still no code, wait for manual promise to complete and try that\n if (!code) {\n await manualPromise;\n if (manualError) {\n throw manualError;\n }\n if (manualCode) {\n const parsed = parseAuthorizationInput(manualCode);\n if (parsed.state && parsed.state !== state) {\n throw new Error('State mismatch');\n }\n code = parsed.code;\n }\n }\n } else {\n // Original flow: wait for callback, then prompt if needed\n const result = await server.waitForCode();\n if (result?.code) {\n code = result.code;\n }\n }\n\n // Fallback to onPrompt if still no code\n if (!code) {\n const input = await options.onPrompt({\n message: 'Paste the authorization code (or full redirect URL):',\n });\n const parsed = parseAuthorizationInput(input);\n if (parsed.state && parsed.state !== state) {\n throw new Error('State mismatch');\n }\n code = parsed.code;\n }\n\n if (!code) {\n throw new Error('Missing authorization code');\n }\n\n const tokenResult = await exchangeAuthorizationCode(code, verifier, server.redirectUri);\n if (tokenResult.type !== 'success') {\n throw new Error('Token exchange failed');\n }\n\n const accountId = requireAccountId(tokenResult);\n\n return {\n access: tokenResult.access,\n refresh: tokenResult.refresh,\n expires: tokenResult.expires,\n accountId,\n };\n } finally {\n server.close();\n }\n}\n\nexport const __testing = {\n createAuthorizationFlow,\n decodeJwt,\n extractAccountIdFromClaims,\n getAccountId,\n loginOpenAICodexDevice,\n requireAccountId,\n startLocalOAuthServer,\n};\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(\n refreshToken: string,\n previousAccountId?: string,\n): Promise<OAuthCredentials> {\n const result = await refreshAccessToken(refreshToken);\n if (result.type !== 'success') {\n throw new Error('Failed to refresh OpenAI Codex token');\n }\n\n const accountId = requireAccountId(result, previousAccountId);\n\n return {\n access: result.access,\n refresh: result.refresh,\n expires: result.expires,\n accountId,\n };\n}\n\nexport const openaiCodexOAuthProvider: OAuthProviderInterface = {\n id: 'openai-codex',\n name: 'ChatGPT Plus/Pro (Codex Subscription)',\n usesCallbackServer: true,\n authModes: OPENAI_CODEX_AUTH_MODES,\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const mode = callbacks.authMode === 'device' || callbacks.authMode === 'browser' ? callbacks.authMode : undefined;\n return loginOpenAICodex({\n onAuth: callbacks.onAuth,\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n onManualCodeInput: callbacks.onManualCodeInput,\n signal: callbacks.signal,\n mode,\n });\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n return refreshOpenAICodexToken(credentials.refresh, credentials.accountId as string | undefined);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xAI OAuth flow (Grok)
|
|
3
|
+
*
|
|
4
|
+
* Ported from pi-mono's xAI OAuth implementation:
|
|
5
|
+
* https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/xai.ts
|
|
6
|
+
*
|
|
7
|
+
* xAI uses a standard RFC 8628 device authorization grant, so the flow needs
|
|
8
|
+
* no inbound connection to the server. The primitives are split into
|
|
9
|
+
* `startXAIDeviceLogin()` / `pollXAIDeviceLogin()` so web routes can persist
|
|
10
|
+
* the JSON-serializable pending state between HTTP requests; the blocking
|
|
11
|
+
* `loginXAI()` wraps them for TUI use.
|
|
12
|
+
*/
|
|
13
|
+
import type { DeviceCodePollState } from '../device-code.js';
|
|
14
|
+
import type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Serializable pending state for an xAI device-code login. Safe to persist
|
|
17
|
+
* (e.g. a `pending jsonb` column) so polling can span HTTP requests.
|
|
18
|
+
*/
|
|
19
|
+
export interface XAIDeviceLoginPending {
|
|
20
|
+
deviceCode: string;
|
|
21
|
+
userCode: string;
|
|
22
|
+
/** Verification URL for the user to open (https-only, validated). */
|
|
23
|
+
url: string;
|
|
24
|
+
instructions: string;
|
|
25
|
+
/** RFC 8628 poll-loop state (interval growth, deadline, slow_down count). */
|
|
26
|
+
state: DeviceCodePollState;
|
|
27
|
+
}
|
|
28
|
+
export type XAIDevicePollResult = {
|
|
29
|
+
status: 'complete';
|
|
30
|
+
credentials: OAuthCredentials;
|
|
31
|
+
} | {
|
|
32
|
+
status: 'pending';
|
|
33
|
+
nextPollMs: number;
|
|
34
|
+
pending: XAIDeviceLoginPending;
|
|
35
|
+
} | {
|
|
36
|
+
status: 'failed';
|
|
37
|
+
error: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Start an xAI device-code login: request a user code and return the
|
|
41
|
+
* serializable pending state for subsequent polls.
|
|
42
|
+
*/
|
|
43
|
+
export declare function startXAIDeviceLogin(options?: {
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}): Promise<XAIDeviceLoginPending>;
|
|
46
|
+
/**
|
|
47
|
+
* Perform exactly one upstream poll for a pending xAI device login.
|
|
48
|
+
* Returns the updated pending state so callers (e.g. web routes) can persist
|
|
49
|
+
* slow_down interval growth between polls. Never throws for flow-level
|
|
50
|
+
* conditions.
|
|
51
|
+
*/
|
|
52
|
+
export declare function pollXAIDeviceLogin(pending: XAIDeviceLoginPending, options?: {
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}): Promise<XAIDevicePollResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Login with xAI OAuth (device-code flow), blocking until authorized.
|
|
57
|
+
*/
|
|
58
|
+
export declare function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
|
|
59
|
+
/**
|
|
60
|
+
* Refresh xAI OAuth token
|
|
61
|
+
*/
|
|
62
|
+
export declare function refreshXAIToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials>;
|
|
63
|
+
export declare const xaiOAuthProvider: OAuthProviderInterface;
|
|
64
|
+
//# sourceMappingURL=xai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/xai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAyB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AA8DjG;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,mBAAmB,CAAC;CAC5B;AAED,MAAM,MAAM,mBAAmB,GAC3B;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,qBAAqB,CAAA;CAAE,GACzE;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAiC5G;AAmDD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAgB9B;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAWxF;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiB3G;AAED,eAAO,MAAM,gBAAgB,EAAE,sBAe9B,CAAC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createDeviceCodePollState, pollDeviceCodeUntilComplete, stepDeviceCodePoll } from "../device-code.js";
|
|
2
|
+
const CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
3
|
+
const DEVICE_CODE_URL = "https://auth.x.ai/oauth2/device/code";
|
|
4
|
+
const TOKEN_URL = "https://auth.x.ai/oauth2/token";
|
|
5
|
+
const SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
6
|
+
const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
7
|
+
const DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 3600;
|
|
8
|
+
const REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
9
|
+
async function postForm(url, params, signal) {
|
|
10
|
+
return fetch(url, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
13
|
+
body: new URLSearchParams(params).toString(),
|
|
14
|
+
signal
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function validateVerificationUri(raw) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = new URL(raw);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error(`xAI device authorization returned an invalid verification_uri: ${raw}`);
|
|
23
|
+
}
|
|
24
|
+
if (parsed.protocol !== "https:") {
|
|
25
|
+
throw new Error(`xAI device authorization returned a non-https verification_uri: ${raw}`);
|
|
26
|
+
}
|
|
27
|
+
return parsed.toString();
|
|
28
|
+
}
|
|
29
|
+
function credentialsFromTokenResponse(data, previousRefreshToken) {
|
|
30
|
+
const record = data ?? {};
|
|
31
|
+
const access = record.access_token;
|
|
32
|
+
if (typeof access !== "string" || access.length === 0) {
|
|
33
|
+
throw new Error("xAI token response missing access_token");
|
|
34
|
+
}
|
|
35
|
+
const refresh = typeof record.refresh_token === "string" && record.refresh_token.length > 0 ? record.refresh_token : previousRefreshToken;
|
|
36
|
+
if (!refresh) {
|
|
37
|
+
throw new Error("xAI token response missing refresh_token");
|
|
38
|
+
}
|
|
39
|
+
const expiresIn = typeof record.expires_in === "number" && record.expires_in > 0 ? record.expires_in : DEFAULT_TOKEN_EXPIRES_IN_SECONDS;
|
|
40
|
+
return {
|
|
41
|
+
access,
|
|
42
|
+
refresh,
|
|
43
|
+
expires: Date.now() + expiresIn * 1e3 - REFRESH_SKEW_MS
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function startXAIDeviceLogin(options) {
|
|
47
|
+
const response = await postForm(DEVICE_CODE_URL, { client_id: CLIENT_ID, scope: SCOPE }, options?.signal);
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const text = await response.text().catch(() => "");
|
|
50
|
+
throw new Error(`Failed to initiate xAI device authorization: ${response.status}${text ? ` ${text}` : ""}`);
|
|
51
|
+
}
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
54
|
+
throw new Error("xAI device authorization response missing required fields");
|
|
55
|
+
}
|
|
56
|
+
const url = validateVerificationUri(data.verification_uri_complete ?? data.verification_uri);
|
|
57
|
+
return {
|
|
58
|
+
deviceCode: data.device_code,
|
|
59
|
+
userCode: data.user_code,
|
|
60
|
+
url,
|
|
61
|
+
instructions: `Enter code: ${data.user_code}`,
|
|
62
|
+
state: createDeviceCodePollState({
|
|
63
|
+
intervalSeconds: data.interval,
|
|
64
|
+
expiresInSeconds: typeof data.expires_in === "number" && data.expires_in > 0 ? data.expires_in : 600
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function pollXAITokenOnce(pending, signal) {
|
|
69
|
+
const response = await postForm(
|
|
70
|
+
TOKEN_URL,
|
|
71
|
+
{
|
|
72
|
+
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
73
|
+
device_code: pending.deviceCode,
|
|
74
|
+
client_id: CLIENT_ID
|
|
75
|
+
},
|
|
76
|
+
signal
|
|
77
|
+
);
|
|
78
|
+
if (response.ok) {
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
try {
|
|
81
|
+
return { status: "complete", result: credentialsFromTokenResponse(data) };
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return { status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const text = await response.text().catch(() => "");
|
|
87
|
+
let body = {};
|
|
88
|
+
try {
|
|
89
|
+
body = JSON.parse(text);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
switch (body.error) {
|
|
93
|
+
case "authorization_pending":
|
|
94
|
+
return { status: "pending", intervalSeconds: body.interval };
|
|
95
|
+
case "slow_down":
|
|
96
|
+
return { status: "slow_down", intervalSeconds: body.interval };
|
|
97
|
+
case "access_denied":
|
|
98
|
+
case "authorization_denied":
|
|
99
|
+
return { status: "failed", error: "xAI authorization was denied" };
|
|
100
|
+
case "expired_token":
|
|
101
|
+
return { status: "failed", error: "xAI device code expired before authorization completed" };
|
|
102
|
+
default:
|
|
103
|
+
return {
|
|
104
|
+
status: "failed",
|
|
105
|
+
error: `xAI device authorization failed: ${response.status}${text ? ` ${text}` : ""}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function pollXAIDeviceLogin(pending, options) {
|
|
110
|
+
const step = await stepDeviceCodePoll(pending.state, () => pollXAITokenOnce(pending, options?.signal));
|
|
111
|
+
switch (step.status) {
|
|
112
|
+
case "complete":
|
|
113
|
+
return { status: "complete", credentials: step.result };
|
|
114
|
+
case "failed":
|
|
115
|
+
return { status: "failed", error: step.error };
|
|
116
|
+
case "pending":
|
|
117
|
+
case "slow_down":
|
|
118
|
+
return {
|
|
119
|
+
status: "pending",
|
|
120
|
+
nextPollMs: step.nextPollMs,
|
|
121
|
+
pending: { ...pending, state: step.state }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function loginXAI(callbacks) {
|
|
126
|
+
const pending = await startXAIDeviceLogin({ signal: callbacks.signal });
|
|
127
|
+
callbacks.onAuth({ url: pending.url, instructions: pending.instructions });
|
|
128
|
+
callbacks.onProgress?.("Waiting for xAI device authorization...");
|
|
129
|
+
return pollDeviceCodeUntilComplete({
|
|
130
|
+
state: pending.state,
|
|
131
|
+
pollOnce: () => pollXAITokenOnce(pending, callbacks.signal),
|
|
132
|
+
signal: callbacks.signal
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function refreshXAIToken(refreshToken, signal) {
|
|
136
|
+
const response = await postForm(
|
|
137
|
+
TOKEN_URL,
|
|
138
|
+
{
|
|
139
|
+
grant_type: "refresh_token",
|
|
140
|
+
client_id: CLIENT_ID,
|
|
141
|
+
refresh_token: refreshToken
|
|
142
|
+
},
|
|
143
|
+
signal
|
|
144
|
+
);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const text = await response.text().catch(() => "");
|
|
147
|
+
throw new Error(`xAI token refresh failed: ${response.status}${text ? ` ${text}` : ""}`);
|
|
148
|
+
}
|
|
149
|
+
return credentialsFromTokenResponse(await response.json(), refreshToken);
|
|
150
|
+
}
|
|
151
|
+
const xaiOAuthProvider = {
|
|
152
|
+
id: "xai",
|
|
153
|
+
name: "xAI (Grok)",
|
|
154
|
+
async login(callbacks) {
|
|
155
|
+
return loginXAI(callbacks);
|
|
156
|
+
},
|
|
157
|
+
async refreshToken(credentials) {
|
|
158
|
+
return refreshXAIToken(credentials.refresh);
|
|
159
|
+
},
|
|
160
|
+
getApiKey(credentials) {
|
|
161
|
+
return credentials.access;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
export {
|
|
165
|
+
loginXAI,
|
|
166
|
+
pollXAIDeviceLogin,
|
|
167
|
+
refreshXAIToken,
|
|
168
|
+
startXAIDeviceLogin,
|
|
169
|
+
xaiOAuthProvider
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=xai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/auth/providers/xai.ts"],"sourcesContent":["/**\n * xAI OAuth flow (Grok)\n *\n * Ported from pi-mono's xAI OAuth implementation:\n * https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/xai.ts\n *\n * xAI uses a standard RFC 8628 device authorization grant, so the flow needs\n * no inbound connection to the server. The primitives are split into\n * `startXAIDeviceLogin()` / `pollXAIDeviceLogin()` so web routes can persist\n * the JSON-serializable pending state between HTTP requests; the blocking\n * `loginXAI()` wraps them for TUI use.\n */\n\nimport { createDeviceCodePollState, pollDeviceCodeUntilComplete, stepDeviceCodePoll } from '../device-code.js';\nimport type { DeviceCodePollOutcome, DeviceCodePollState } from '../device-code.js';\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nconst CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828';\nconst DEVICE_CODE_URL = 'https://auth.x.ai/oauth2/device/code';\nconst TOKEN_URL = 'https://auth.x.ai/oauth2/token';\nconst SCOPE = 'openid profile email offline_access grok-cli:access api:access';\nconst DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';\nconst DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 3600;\n// Refresh 5 minutes before actual expiry (same skew as Anthropic).\nconst REFRESH_SKEW_MS = 5 * 60 * 1000;\n\nasync function postForm(url: string, params: Record<string, string>, signal?: AbortSignal): Promise<Response> {\n return fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams(params).toString(),\n signal,\n });\n}\n\n/** The verification URI is opened by the user; only accept https URLs. */\nfunction validateVerificationUri(raw: string): string {\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`xAI device authorization returned an invalid verification_uri: ${raw}`);\n }\n if (parsed.protocol !== 'https:') {\n throw new Error(`xAI device authorization returned a non-https verification_uri: ${raw}`);\n }\n return parsed.toString();\n}\n\nfunction credentialsFromTokenResponse(data: unknown, previousRefreshToken?: string): OAuthCredentials {\n const record = (data ?? {}) as Record<string, unknown>;\n const access = record.access_token;\n if (typeof access !== 'string' || access.length === 0) {\n throw new Error('xAI token response missing access_token');\n }\n\n // xAI may not rotate the refresh token on refresh; keep the previous one.\n const refresh =\n typeof record.refresh_token === 'string' && record.refresh_token.length > 0\n ? record.refresh_token\n : previousRefreshToken;\n if (!refresh) {\n throw new Error('xAI token response missing refresh_token');\n }\n\n const expiresIn =\n typeof record.expires_in === 'number' && record.expires_in > 0\n ? record.expires_in\n : DEFAULT_TOKEN_EXPIRES_IN_SECONDS;\n\n return {\n access,\n refresh,\n expires: Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS,\n };\n}\n\n/**\n * Serializable pending state for an xAI device-code login. Safe to persist\n * (e.g. a `pending jsonb` column) so polling can span HTTP requests.\n */\nexport interface XAIDeviceLoginPending {\n deviceCode: string;\n userCode: string;\n /** Verification URL for the user to open (https-only, validated). */\n url: string;\n instructions: string;\n /** RFC 8628 poll-loop state (interval growth, deadline, slow_down count). */\n state: DeviceCodePollState;\n}\n\nexport type XAIDevicePollResult =\n | { status: 'complete'; credentials: OAuthCredentials }\n | { status: 'pending'; nextPollMs: number; pending: XAIDeviceLoginPending }\n | { status: 'failed'; error: string };\n\n/**\n * Start an xAI device-code login: request a user code and return the\n * serializable pending state for subsequent polls.\n */\nexport async function startXAIDeviceLogin(options?: { signal?: AbortSignal }): Promise<XAIDeviceLoginPending> {\n const response = await postForm(DEVICE_CODE_URL, { client_id: CLIENT_ID, scope: SCOPE }, options?.signal);\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`Failed to initiate xAI device authorization: ${response.status}${text ? ` ${text}` : ''}`);\n }\n\n const data = (await response.json()) as {\n device_code?: string;\n user_code?: string;\n verification_uri?: string;\n verification_uri_complete?: string;\n interval?: number;\n expires_in?: number;\n };\n\n if (!data.device_code || !data.user_code || !data.verification_uri) {\n throw new Error('xAI device authorization response missing required fields');\n }\n\n const url = validateVerificationUri(data.verification_uri_complete ?? data.verification_uri);\n\n return {\n deviceCode: data.device_code,\n userCode: data.user_code,\n url,\n instructions: `Enter code: ${data.user_code}`,\n state: createDeviceCodePollState({\n intervalSeconds: data.interval,\n expiresInSeconds: typeof data.expires_in === 'number' && data.expires_in > 0 ? data.expires_in : 600,\n }),\n };\n}\n\nasync function pollXAITokenOnce(\n pending: XAIDeviceLoginPending,\n signal?: AbortSignal,\n): Promise<DeviceCodePollOutcome<OAuthCredentials>> {\n const response = await postForm(\n TOKEN_URL,\n {\n grant_type: DEVICE_CODE_GRANT_TYPE,\n device_code: pending.deviceCode,\n client_id: CLIENT_ID,\n },\n signal,\n );\n\n if (response.ok) {\n const data = (await response.json()) as unknown;\n try {\n return { status: 'complete', result: credentialsFromTokenResponse(data) };\n } catch (error) {\n return { status: 'failed', error: error instanceof Error ? error.message : String(error) };\n }\n }\n\n const text = await response.text().catch(() => '');\n let body: { error?: string; interval?: number } = {};\n try {\n body = JSON.parse(text) as { error?: string; interval?: number };\n } catch {\n // Non-JSON error body — fail loudly below with the raw text.\n }\n\n switch (body.error) {\n case 'authorization_pending':\n return { status: 'pending', intervalSeconds: body.interval };\n case 'slow_down':\n return { status: 'slow_down', intervalSeconds: body.interval };\n case 'access_denied':\n case 'authorization_denied':\n return { status: 'failed', error: 'xAI authorization was denied' };\n case 'expired_token':\n return { status: 'failed', error: 'xAI device code expired before authorization completed' };\n default:\n return {\n status: 'failed',\n error: `xAI device authorization failed: ${response.status}${text ? ` ${text}` : ''}`,\n };\n }\n}\n\n/**\n * Perform exactly one upstream poll for a pending xAI device login.\n * Returns the updated pending state so callers (e.g. web routes) can persist\n * slow_down interval growth between polls. Never throws for flow-level\n * conditions.\n */\nexport async function pollXAIDeviceLogin(\n pending: XAIDeviceLoginPending,\n options?: { signal?: AbortSignal },\n): Promise<XAIDevicePollResult> {\n const step = await stepDeviceCodePoll(pending.state, () => pollXAITokenOnce(pending, options?.signal));\n\n switch (step.status) {\n case 'complete':\n return { status: 'complete', credentials: step.result };\n case 'failed':\n return { status: 'failed', error: step.error };\n case 'pending':\n case 'slow_down':\n return {\n status: 'pending',\n nextPollMs: step.nextPollMs,\n pending: { ...pending, state: step.state },\n };\n }\n}\n\n/**\n * Login with xAI OAuth (device-code flow), blocking until authorized.\n */\nexport async function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const pending = await startXAIDeviceLogin({ signal: callbacks.signal });\n\n callbacks.onAuth({ url: pending.url, instructions: pending.instructions });\n callbacks.onProgress?.('Waiting for xAI device authorization...');\n\n return pollDeviceCodeUntilComplete({\n state: pending.state,\n pollOnce: () => pollXAITokenOnce(pending, callbacks.signal),\n signal: callbacks.signal,\n });\n}\n\n/**\n * Refresh xAI OAuth token\n */\nexport async function refreshXAIToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials> {\n const response = await postForm(\n TOKEN_URL,\n {\n grant_type: 'refresh_token',\n client_id: CLIENT_ID,\n refresh_token: refreshToken,\n },\n signal,\n );\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`xAI token refresh failed: ${response.status}${text ? ` ${text}` : ''}`);\n }\n\n return credentialsFromTokenResponse((await response.json()) as unknown, refreshToken);\n}\n\nexport const xaiOAuthProvider: OAuthProviderInterface = {\n id: 'xai',\n name: 'xAI (Grok)',\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n return loginXAI(callbacks);\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n return refreshXAIToken(credentials.refresh);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/auth/storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/auth/storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,OAAO,KAAK,EACV,cAAc,EAEd,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAQnE,CAAC;AAUF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAED;;GAEG;AACH,qBAAa,WAAW;IAGV,OAAO,CAAC,QAAQ;IAF5B,OAAO,CAAC,IAAI,CAAuB;gBAEf,QAAQ,GAAE,MAA2C;IAIzE;;OAEG;IACH,MAAM,IAAI,IAAI;IAYd;;OAEG;IACH,OAAO,CAAC,IAAI;IASZ;;OAEG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIjD;;OAEG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI;IAKvD;;OAEG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK9B;;OAEG;IACH,IAAI,IAAI,MAAM,EAAE;IAIhB;;OAEG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI9B;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKrC;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAK1C;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrD;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrE;;;;OAIG;IACH,wBAAwB,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI;IAWnF;;OAEG;IACG,KAAK,CAAC,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvF;;OAEG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI9B;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CA8BjE"}
|
package/dist/auth/storage.js
CHANGED
|
@@ -4,18 +4,21 @@ import { getAppDataDir } from "../utils/project.js";
|
|
|
4
4
|
import { anthropicOAuthProvider } from "./providers/anthropic.js";
|
|
5
5
|
import { githubCopilotOAuthProvider } from "./providers/github-copilot.js";
|
|
6
6
|
import { openaiCodexOAuthProvider } from "./providers/openai-codex.js";
|
|
7
|
+
import { xaiOAuthProvider } from "./providers/xai.js";
|
|
7
8
|
const PROVIDER_DEFAULT_MODELS = {
|
|
8
9
|
anthropic: "anthropic/claude-opus-4-6",
|
|
9
10
|
"openai-codex": "openai/gpt-5.5",
|
|
10
11
|
// gpt-4.1 routes through `/chat/completions` (which our OpenAI-compatible
|
|
11
12
|
// adapter handles); Anthropic-shaped Copilot models (Claude on `/v1/messages`)
|
|
12
13
|
// are not yet wired up, so picking one as the post-login default would error.
|
|
13
|
-
"github-copilot": "github-copilot/gpt-4.1"
|
|
14
|
+
"github-copilot": "github-copilot/gpt-4.1",
|
|
15
|
+
xai: "xai/grok-4.5"
|
|
14
16
|
};
|
|
15
17
|
const oauthProviderRegistry = /* @__PURE__ */ new Map([
|
|
16
18
|
[anthropicOAuthProvider.id, anthropicOAuthProvider],
|
|
17
19
|
[openaiCodexOAuthProvider.id, openaiCodexOAuthProvider],
|
|
18
|
-
[githubCopilotOAuthProvider.id, githubCopilotOAuthProvider]
|
|
20
|
+
[githubCopilotOAuthProvider.id, githubCopilotOAuthProvider],
|
|
21
|
+
[xaiOAuthProvider.id, xaiOAuthProvider]
|
|
19
22
|
]);
|
|
20
23
|
function getOAuthProvider(id) {
|
|
21
24
|
return oauthProviderRegistry.get(id);
|
package/dist/auth/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/storage.ts"],"sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppDataDir } from '../utils/project.js';\nimport { anthropicOAuthProvider } from './providers/anthropic.js';\nimport { githubCopilotOAuthProvider } from './providers/github-copilot.js';\nimport { openaiCodexOAuthProvider } from './providers/openai-codex.js';\nimport type {\n AuthCredential,\n AuthStorageData,\n OAuthLoginCallbacks,\n OAuthProviderId,\n OAuthProviderInterface,\n} from './types.js';\n\n/**\n * Best/default models for each OAuth provider.\n * Used when auto-selecting a model after login.\n */\nexport const PROVIDER_DEFAULT_MODELS: Record<OAuthProviderId, string> = {\n anthropic: 'anthropic/claude-opus-4-6',\n 'openai-codex': 'openai/gpt-5.5',\n // gpt-4.1 routes through `/chat/completions` (which our OpenAI-compatible\n // adapter handles); Anthropic-shaped Copilot models (Claude on `/v1/messages`)\n // are not yet wired up, so picking one as the post-login default would error.\n 'github-copilot': 'github-copilot/gpt-4.1',\n};\n\n// Provider registry\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>([\n [anthropicOAuthProvider.id, anthropicOAuthProvider],\n [openaiCodexOAuthProvider.id, openaiCodexOAuthProvider],\n [githubCopilotOAuthProvider.id, githubCopilotOAuthProvider],\n]);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n return oauthProviderRegistry.get(id);\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n return Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n private data: AuthStorageData = {};\n\n constructor(private authPath: string = join(getAppDataDir(), 'auth.json')) {\n this.reload();\n }\n\n /**\n * Reload credentials from disk.\n */\n reload(): void {\n if (!existsSync(this.authPath)) {\n this.data = {};\n return;\n }\n try {\n this.data = JSON.parse(readFileSync(this.authPath, 'utf-8'));\n } catch {\n this.data = {};\n }\n }\n\n /**\n * Save credentials to disk.\n */\n private save(): void {\n const dir = dirname(this.authPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n writeFileSync(this.authPath, JSON.stringify(this.data, null, 2), 'utf-8');\n chmodSync(this.authPath, 0o600);\n }\n\n /**\n * Get credential for a provider.\n */\n get(provider: string): AuthCredential | undefined {\n return this.data[provider] ?? undefined;\n }\n\n /**\n * Set credential for a provider.\n */\n set(provider: string, credential: AuthCredential): void {\n this.data[provider] = credential;\n this.save();\n }\n\n /**\n * Remove credential for a provider.\n */\n remove(provider: string): void {\n delete this.data[provider];\n this.save();\n }\n\n /**\n * List all providers with credentials.\n */\n list(): string[] {\n return Object.keys(this.data);\n }\n\n /**\n * Check if credentials exist for a provider.\n */\n has(provider: string): boolean {\n return provider in this.data;\n }\n\n /**\n * Check if logged in via OAuth for a provider.\n */\n isLoggedIn(provider: string): boolean {\n const cred = this.data[provider];\n return cred?.type === 'oauth';\n }\n\n /**\n * Check if a stored API key exists for a provider.\n * Keys are stored under `apikey:<provider>` in auth.json.\n */\n hasStoredApiKey(provider: string): boolean {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0;\n }\n\n /**\n * Get a stored API key for a provider, if any.\n */\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0 ? cred.key : undefined;\n }\n\n /**\n * Store an API key for a provider.\n * Also sets the corresponding environment variable so model resolution can find it.\n */\n setStoredApiKey(provider: string, key: string, envVar?: string): void {\n this.set(`apikey:${provider}`, { type: 'api_key', key });\n if (envVar) {\n process.env[envVar] = key;\n }\n }\n\n /**\n * Load all stored API keys into process.env.\n * Called at startup so model resolution can find stored keys.\n * Only sets env vars that aren't already set (env vars take precedence).\n */\n loadStoredApiKeysIntoEnv(providerEnvVars: Record<string, string | undefined>): void {\n for (const [key, cred] of Object.entries(this.data)) {\n if (!key.startsWith('apikey:') || cred.type !== 'api_key' || !cred.key) continue;\n const provider = key.substring('apikey:'.length);\n const envVar = providerEnvVars[provider];\n if (envVar && !process.env[envVar]) {\n process.env[envVar] = cred.key;\n }\n }\n }\n\n /**\n * Login to an OAuth provider.\n */\n async login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n const provider = getOAuthProvider(providerId);\n if (!provider) {\n throw new Error(`Unknown OAuth provider: ${providerId}`);\n }\n\n const credentials = await provider.login(callbacks);\n this.set(providerId, { type: 'oauth', ...credentials });\n }\n\n /**\n * Logout from a provider.\n */\n logout(provider: string): void {\n this.remove(provider);\n }\n\n /**\n * Get API key for a provider, auto-refreshing OAuth tokens if needed.\n */\n async getApiKey(providerId: string): Promise<string | undefined> {\n const cred = this.data[providerId];\n\n if (cred?.type === 'api_key') {\n return cred.key;\n }\n\n if (cred?.type === 'oauth') {\n const provider = getOAuthProvider(providerId);\n if (!provider) {\n return undefined;\n }\n\n // Check if token needs refresh\n if (Date.now() >= cred.expires) {\n try {\n const newCreds = await provider.refreshToken(cred);\n this.set(providerId, { type: 'oauth', ...newCreds });\n return provider.getApiKey(newCreds);\n } catch {\n // Refresh failed - user needs to re-login\n return undefined;\n }\n }\n\n return provider.getApiKey(cred);\n }\n\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/storage.ts"],"sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppDataDir } from '../utils/project.js';\nimport { anthropicOAuthProvider } from './providers/anthropic.js';\nimport { githubCopilotOAuthProvider } from './providers/github-copilot.js';\nimport { openaiCodexOAuthProvider } from './providers/openai-codex.js';\nimport { xaiOAuthProvider } from './providers/xai.js';\nimport type {\n AuthCredential,\n AuthStorageData,\n OAuthLoginCallbacks,\n OAuthProviderId,\n OAuthProviderInterface,\n} from './types.js';\n\n/**\n * Best/default models for each OAuth provider.\n * Used when auto-selecting a model after login.\n */\nexport const PROVIDER_DEFAULT_MODELS: Record<OAuthProviderId, string> = {\n anthropic: 'anthropic/claude-opus-4-6',\n 'openai-codex': 'openai/gpt-5.5',\n // gpt-4.1 routes through `/chat/completions` (which our OpenAI-compatible\n // adapter handles); Anthropic-shaped Copilot models (Claude on `/v1/messages`)\n // are not yet wired up, so picking one as the post-login default would error.\n 'github-copilot': 'github-copilot/gpt-4.1',\n xai: 'xai/grok-4.5',\n};\n\n// Provider registry\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>([\n [anthropicOAuthProvider.id, anthropicOAuthProvider],\n [openaiCodexOAuthProvider.id, openaiCodexOAuthProvider],\n [githubCopilotOAuthProvider.id, githubCopilotOAuthProvider],\n [xaiOAuthProvider.id, xaiOAuthProvider],\n]);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n return oauthProviderRegistry.get(id);\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n return Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n private data: AuthStorageData = {};\n\n constructor(private authPath: string = join(getAppDataDir(), 'auth.json')) {\n this.reload();\n }\n\n /**\n * Reload credentials from disk.\n */\n reload(): void {\n if (!existsSync(this.authPath)) {\n this.data = {};\n return;\n }\n try {\n this.data = JSON.parse(readFileSync(this.authPath, 'utf-8'));\n } catch {\n this.data = {};\n }\n }\n\n /**\n * Save credentials to disk.\n */\n private save(): void {\n const dir = dirname(this.authPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n writeFileSync(this.authPath, JSON.stringify(this.data, null, 2), 'utf-8');\n chmodSync(this.authPath, 0o600);\n }\n\n /**\n * Get credential for a provider.\n */\n get(provider: string): AuthCredential | undefined {\n return this.data[provider] ?? undefined;\n }\n\n /**\n * Set credential for a provider.\n */\n set(provider: string, credential: AuthCredential): void {\n this.data[provider] = credential;\n this.save();\n }\n\n /**\n * Remove credential for a provider.\n */\n remove(provider: string): void {\n delete this.data[provider];\n this.save();\n }\n\n /**\n * List all providers with credentials.\n */\n list(): string[] {\n return Object.keys(this.data);\n }\n\n /**\n * Check if credentials exist for a provider.\n */\n has(provider: string): boolean {\n return provider in this.data;\n }\n\n /**\n * Check if logged in via OAuth for a provider.\n */\n isLoggedIn(provider: string): boolean {\n const cred = this.data[provider];\n return cred?.type === 'oauth';\n }\n\n /**\n * Check if a stored API key exists for a provider.\n * Keys are stored under `apikey:<provider>` in auth.json.\n */\n hasStoredApiKey(provider: string): boolean {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0;\n }\n\n /**\n * Get a stored API key for a provider, if any.\n */\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.data[`apikey:${provider}`];\n return cred?.type === 'api_key' && cred.key.length > 0 ? cred.key : undefined;\n }\n\n /**\n * Store an API key for a provider.\n * Also sets the corresponding environment variable so model resolution can find it.\n */\n setStoredApiKey(provider: string, key: string, envVar?: string): void {\n this.set(`apikey:${provider}`, { type: 'api_key', key });\n if (envVar) {\n process.env[envVar] = key;\n }\n }\n\n /**\n * Load all stored API keys into process.env.\n * Called at startup so model resolution can find stored keys.\n * Only sets env vars that aren't already set (env vars take precedence).\n */\n loadStoredApiKeysIntoEnv(providerEnvVars: Record<string, string | undefined>): void {\n for (const [key, cred] of Object.entries(this.data)) {\n if (!key.startsWith('apikey:') || cred.type !== 'api_key' || !cred.key) continue;\n const provider = key.substring('apikey:'.length);\n const envVar = providerEnvVars[provider];\n if (envVar && !process.env[envVar]) {\n process.env[envVar] = cred.key;\n }\n }\n }\n\n /**\n * Login to an OAuth provider.\n */\n async login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n const provider = getOAuthProvider(providerId);\n if (!provider) {\n throw new Error(`Unknown OAuth provider: ${providerId}`);\n }\n\n const credentials = await provider.login(callbacks);\n this.set(providerId, { type: 'oauth', ...credentials });\n }\n\n /**\n * Logout from a provider.\n */\n logout(provider: string): void {\n this.remove(provider);\n }\n\n /**\n * Get API key for a provider, auto-refreshing OAuth tokens if needed.\n */\n async getApiKey(providerId: string): Promise<string | undefined> {\n const cred = this.data[providerId];\n\n if (cred?.type === 'api_key') {\n return cred.key;\n }\n\n if (cred?.type === 'oauth') {\n const provider = getOAuthProvider(providerId);\n if (!provider) {\n return undefined;\n }\n\n // Check if token needs refresh\n if (Date.now() >= cred.expires) {\n try {\n const newCreds = await provider.refreshToken(cred);\n this.set(providerId, { type: 'oauth', ...newCreds });\n return provider.getApiKey(newCreds);\n } catch {\n // Refresh failed - user needs to re-login\n return undefined;\n }\n }\n\n return provider.getApiKey(cred);\n }\n\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -65,4 +65,25 @@ export type OAuthCredential = {
|
|
|
65
65
|
} & OAuthCredentials;
|
|
66
66
|
export type AuthCredential = ApiKeyCredential | OAuthCredential;
|
|
67
67
|
export type AuthStorageData = Record<string, AuthCredential>;
|
|
68
|
+
/**
|
|
69
|
+
* The read surface model resolution and the OAuth fetch wrappers need from a
|
|
70
|
+
* credential source. `AuthStorage` satisfies it structurally (file-backed,
|
|
71
|
+
* server-global); deployed web injects a per-tenant implementation backed by
|
|
72
|
+
* the app database so each caller's own credentials are used.
|
|
73
|
+
*/
|
|
74
|
+
export interface CredentialStore {
|
|
75
|
+
/** Whether model resolution may fall back to process environment credentials. */
|
|
76
|
+
readonly allowEnvironmentFallback?: boolean;
|
|
77
|
+
/** Refresh any cached view (no-op for sources that are always fresh). */
|
|
78
|
+
reload(): void;
|
|
79
|
+
/** Credential in the provider's main slot (`anthropic`, `openai-codex`, …). */
|
|
80
|
+
get(provider: string): AuthCredential | undefined;
|
|
81
|
+
/** Dedicated stored API key for a provider, if any. */
|
|
82
|
+
getStoredApiKey(provider: string): string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Ready-to-use key/token for a provider, refreshing expired OAuth
|
|
85
|
+
* credentials first. Implementations own refresh serialization.
|
|
86
|
+
*/
|
|
87
|
+
getApiKey(provider: string): Promise<string | undefined>;
|
|
88
|
+
}
|
|
68
89
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/auth/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,gFAAgF;IAChF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAEtC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAE7C,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEjE,yEAAyE;IACzE,YAAY,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,SAAS,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;CAClD;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,gBAAgB,CAAC;AAErB,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,gFAAgF;IAChF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAEtC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAE7C,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEjE,yEAAyE;IACzE,YAAY,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,SAAS,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;CAClD;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,gBAAgB,CAAC;AAErB,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,yEAAyE;IACzE,MAAM,IAAI,IAAI,CAAC;IACf,+EAA+E;IAC/E,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IAClD,uDAAuD;IACvD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,MAAM,aAAa,GACrB,YAAY,GACZ,aAAa,GACb,MAAM,GACN,kBAAkB,GAClB,cAAc,GACd,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,UAAU,GACV,mBAAmB,GACnB,kBAAkB,GAClB,WAAW,GACX,eAAe,GACf,aAAa,CAAC;AAElB,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,MAAM,GAAG,kBAAkB,CAAC;AAE3E,yFAAyF;AACzF,MAAM,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"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"}
|