@mastra/code-sdk 1.8.0-alpha.7 → 1.8.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.
Files changed (79) hide show
  1. package/dist/agents/mastracode-gateway.d.ts +2 -0
  2. package/dist/agents/mastracode-gateway.d.ts.map +1 -1
  3. package/dist/agents/mastracode-gateway.js +29 -10
  4. package/dist/agents/mastracode-gateway.js.map +1 -1
  5. package/dist/agents/memory.d.ts +1 -1
  6. package/dist/agents/memory.d.ts.map +1 -1
  7. package/dist/agents/memory.js +32 -25
  8. package/dist/agents/memory.js.map +1 -1
  9. package/dist/agents/model.d.ts +41 -1
  10. package/dist/agents/model.d.ts.map +1 -1
  11. package/dist/agents/model.js +141 -7
  12. package/dist/agents/model.js.map +1 -1
  13. package/dist/auth/account-rotation-processor.d.ts +207 -0
  14. package/dist/auth/account-rotation-processor.d.ts.map +1 -0
  15. package/dist/auth/account-rotation-processor.js +669 -0
  16. package/dist/auth/account-rotation-processor.js.map +1 -0
  17. package/dist/auth/account-routing-context.d.ts +13 -0
  18. package/dist/auth/account-routing-context.d.ts.map +1 -0
  19. package/dist/auth/account-routing-context.js +41 -0
  20. package/dist/auth/account-routing-context.js.map +1 -0
  21. package/dist/auth/index.d.ts +1 -0
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +2 -1
  24. package/dist/auth/providers/anthropic.d.ts.map +1 -1
  25. package/dist/auth/providers/anthropic.js +2 -8
  26. package/dist/auth/providers/anthropic.js.map +1 -1
  27. package/dist/auth/providers/github-copilot.d.ts.map +1 -1
  28. package/dist/auth/providers/github-copilot.js +34 -12
  29. package/dist/auth/providers/github-copilot.js.map +1 -1
  30. package/dist/auth/providers/kimi-coding.d.ts.map +1 -1
  31. package/dist/auth/providers/kimi-coding.js +2 -6
  32. package/dist/auth/providers/kimi-coding.js.map +1 -1
  33. package/dist/auth/providers/openai-codex.d.ts +3 -1
  34. package/dist/auth/providers/openai-codex.d.ts.map +1 -1
  35. package/dist/auth/providers/openai-codex.js +50 -24
  36. package/dist/auth/providers/openai-codex.js.map +1 -1
  37. package/dist/auth/providers/xai.d.ts +1 -1
  38. package/dist/auth/providers/xai.d.ts.map +1 -1
  39. package/dist/auth/providers/xai.js +36 -16
  40. package/dist/auth/providers/xai.js.map +1 -1
  41. package/dist/auth/storage.d.ts +130 -8
  42. package/dist/auth/storage.d.ts.map +1 -1
  43. package/dist/auth/storage.js +602 -39
  44. package/dist/auth/storage.js.map +1 -1
  45. package/dist/auth/types.d.ts +69 -2
  46. package/dist/auth/types.d.ts.map +1 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +88 -23
  49. package/dist/index.js.map +1 -1
  50. package/dist/onboarding/custom-packs.d.ts.map +1 -1
  51. package/dist/onboarding/custom-packs.js +3 -0
  52. package/dist/onboarding/custom-packs.js.map +1 -1
  53. package/dist/onboarding/index.js +1 -1
  54. package/dist/onboarding/packs.d.ts +44 -0
  55. package/dist/onboarding/packs.d.ts.map +1 -1
  56. package/dist/onboarding/packs.js +80 -2
  57. package/dist/onboarding/packs.js.map +1 -1
  58. package/dist/onboarding/settings.d.ts +28 -0
  59. package/dist/onboarding/settings.d.ts.map +1 -1
  60. package/dist/onboarding/settings.js +81 -8
  61. package/dist/onboarding/settings.js.map +1 -1
  62. package/dist/providers/claude-max.d.ts.map +1 -1
  63. package/dist/providers/claude-max.js +4 -5
  64. package/dist/providers/claude-max.js.map +1 -1
  65. package/dist/providers/github-copilot.d.ts +1 -1
  66. package/dist/providers/github-copilot.d.ts.map +1 -1
  67. package/dist/providers/github-copilot.js +89 -37
  68. package/dist/providers/github-copilot.js.map +1 -1
  69. package/dist/providers/kimi-coding.d.ts.map +1 -1
  70. package/dist/providers/kimi-coding.js +16 -2
  71. package/dist/providers/kimi-coding.js.map +1 -1
  72. package/dist/providers/openai-codex.d.ts.map +1 -1
  73. package/dist/providers/openai-codex.js +42 -27
  74. package/dist/providers/openai-codex.js.map +1 -1
  75. package/dist/schema.d.ts +27 -0
  76. package/dist/schema.d.ts.map +1 -1
  77. package/dist/schema.js +9 -0
  78. package/dist/schema.js.map +1 -1
  79. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"kimi-coding.js","names":[],"sources":["../../../src/auth/providers/kimi-coding.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { arch, hostname, platform, release } from 'node:os';\nimport { getCurrentVersion } from '../../utils/update-check.js';\nimport {\n abortableSleep,\n createDeviceCodePollState,\n pollDeviceCodeUntilComplete,\n stepDeviceCodePoll,\n} from '../device-code.js';\nimport type { DeviceCodePollOutcome, DeviceCodePollState } from '../device-code.js';\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nconst CLIENT_ID = '17e5f671-d194-4dfb-9706-5516cb48c098';\nconst OAUTH_HOST = 'https://auth.kimi.com';\nconst DEVICE_ID_PATTERN = /^[0-9a-f]{32}$/;\nconst DEFAULT_EXPIRES_IN_SECONDS = 15 * 60;\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst REFRESH_MAX_RETRIES = 3;\n\nfunction asciiHeaderValue(value: string): string {\n const sanitized = value.replace(/[^\\x20-\\x7E]/g, '').trim();\n return sanitized || 'unknown';\n}\n\nconst KIMI_DEVICE_DETAILS = {\n 'X-Msh-Platform': 'mastracode',\n 'X-Msh-Version': asciiHeaderValue(getCurrentVersion()),\n 'X-Msh-Device-Name': asciiHeaderValue(hostname()),\n 'X-Msh-Device-Model': asciiHeaderValue(`${platform()} ${arch()}`),\n 'X-Msh-Os-Version': asciiHeaderValue(release()),\n};\n\nexport function createKimiCodingDeviceId(): string {\n return randomUUID().replaceAll('-', '');\n}\n\nexport function isKimiCodingDeviceId(value: unknown): value is string {\n return typeof value === 'string' && DEVICE_ID_PATTERN.test(value);\n}\n\nexport function getKimiCodingDeviceHeaders(deviceId: string): Record<string, string> {\n if (!isKimiCodingDeviceId(deviceId)) {\n throw new Error('Kimi For Coding credentials have an invalid device ID. Please reconnect the account.');\n }\n return { ...KIMI_DEVICE_DETAILS, 'X-Msh-Device-Id': deviceId };\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);\n return signal ? AbortSignal.any([timeout, signal]) : timeout;\n}\n\nfunction trustedHttpUrl(value: unknown): string | null {\n if (typeof value !== 'string' || !value) return null;\n try {\n const url = new URL(value);\n return url.protocol === 'https:' ? url.href : null;\n } catch {\n return null;\n }\n}\n\nasync function readJson(response: Response): Promise<Record<string, unknown> | null> {\n try {\n const value = (await response.json()) as unknown;\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n}\n\nfunction credentialsFromTokenResponse(value: unknown, operation: string, deviceId: string): OAuthCredentials {\n const data = (value ?? {}) as Record<string, unknown>;\n const access = data.access_token;\n const refresh = data.refresh_token;\n const expiresIn = data.expires_in;\n if (\n typeof access !== 'string' ||\n !access ||\n typeof refresh !== 'string' ||\n !refresh ||\n typeof expiresIn !== 'number' ||\n !Number.isFinite(expiresIn) ||\n expiresIn <= 0\n ) {\n throw new Error(`Kimi For Coding token ${operation} response missing fields`);\n }\n return { access, refresh, expires: Date.now() + expiresIn * 1000, deviceId };\n}\n\nexport interface KimiCodingDeviceLoginPending {\n clientId: string;\n deviceId: string;\n deviceCode: string;\n userCode: string;\n url: string;\n instructions: string;\n state: DeviceCodePollState;\n}\n\nexport type KimiCodingDevicePollResult =\n | { status: 'complete'; credentials: OAuthCredentials }\n | { status: 'pending'; nextPollMs: number; pending: KimiCodingDeviceLoginPending }\n | { status: 'failed'; error: string };\n\nexport async function startKimiCodingDeviceLogin(options?: {\n signal?: AbortSignal;\n}): Promise<KimiCodingDeviceLoginPending> {\n const clientId = CLIENT_ID;\n const deviceId = createKimiCodingDeviceId();\n const response = await fetch(`${OAUTH_HOST}/api/oauth/device_authorization`, {\n method: 'POST',\n headers: {\n ...getKimiCodingDeviceHeaders(deviceId),\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: new URLSearchParams({ client_id: clientId }).toString(),\n signal: requestSignal(options?.signal),\n });\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`Kimi For Coding device authorization failed: ${response.status}${text ? ` ${text}` : ''}`);\n }\n\n const data = await readJson(response);\n const deviceCode = data?.device_code;\n const userCode = data?.user_code;\n const verificationUri = trustedHttpUrl(data?.verification_uri);\n const verificationUriComplete = trustedHttpUrl(data?.verification_uri_complete);\n if (\n typeof deviceCode !== 'string' ||\n !deviceCode ||\n typeof userCode !== 'string' ||\n !userCode ||\n !verificationUri ||\n !verificationUriComplete\n ) {\n throw new Error('Invalid Kimi For Coding device authorization response');\n }\n\n const interval = data?.interval;\n const expiresIn = data?.expires_in;\n return {\n clientId,\n deviceId,\n deviceCode,\n userCode,\n url: verificationUriComplete,\n instructions: `Enter code: ${userCode}`,\n state: createDeviceCodePollState({\n intervalSeconds:\n typeof interval === 'number' && Number.isFinite(interval) && interval > 0\n ? interval\n : DEFAULT_POLL_INTERVAL_SECONDS,\n expiresInSeconds:\n typeof expiresIn === 'number' && Number.isFinite(expiresIn) && expiresIn > 0\n ? expiresIn\n : DEFAULT_EXPIRES_IN_SECONDS,\n }),\n };\n}\n\nasync function pollKimiCodingTokenOnce(\n pending: KimiCodingDeviceLoginPending,\n signal?: AbortSignal,\n): Promise<DeviceCodePollOutcome<OAuthCredentials>> {\n const response = await fetch(`${OAUTH_HOST}/api/oauth/token`, {\n method: 'POST',\n headers: {\n ...getKimiCodingDeviceHeaders(pending.deviceId),\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: new URLSearchParams({\n client_id: pending.clientId,\n device_code: pending.deviceCode,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }).toString(),\n signal: requestSignal(signal),\n });\n const data = await readJson(response);\n if (response.ok && typeof data?.access_token === 'string') {\n try {\n return { status: 'complete', result: credentialsFromTokenResponse(data, 'poll', pending.deviceId) };\n } catch (error) {\n return { status: 'failed', error: error instanceof Error ? error.message : String(error) };\n }\n }\n\n const error = data?.error;\n if (error === 'authorization_pending') return { status: 'pending' };\n if (error === 'slow_down') {\n return {\n status: 'slow_down',\n intervalSeconds: typeof data?.interval === 'number' && data.interval > 0 ? data.interval : undefined,\n };\n }\n if (error === 'expired_token') {\n return { status: 'failed', error: 'Kimi For Coding authorization expired. Please restart login.' };\n }\n if (error === 'access_denied') return { status: 'failed', error: 'Kimi For Coding login was denied.' };\n const description = typeof data?.error_description === 'string' ? `: ${data.error_description}` : '';\n return {\n status: 'failed',\n error: `Kimi For Coding token request failed: ${response.status}${typeof error === 'string' ? ` ${error}${description}` : ''}`,\n };\n}\n\nexport async function pollKimiCodingDeviceLogin(\n pending: KimiCodingDeviceLoginPending,\n options?: { signal?: AbortSignal },\n): Promise<KimiCodingDevicePollResult> {\n const step = await stepDeviceCodePoll(pending.state, () => pollKimiCodingTokenOnce(pending, options?.signal));\n if (step.status === 'complete') return { status: 'complete', credentials: step.result };\n if (step.status === 'failed') return { status: 'failed', error: step.error };\n return {\n status: 'pending',\n nextPollMs: step.nextPollMs,\n pending: { ...pending, state: step.state },\n };\n}\n\nexport async function loginKimiCoding(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const pending = await startKimiCodingDeviceLogin({ signal: callbacks.signal });\n callbacks.onAuth({ url: pending.url, instructions: pending.instructions });\n callbacks.onProgress?.('Waiting for Kimi For Coding device authorization...');\n return pollDeviceCodeUntilComplete({\n state: pending.state,\n pollOnce: () => pollKimiCodingTokenOnce(pending, callbacks.signal),\n signal: callbacks.signal,\n });\n}\n\nexport async function refreshKimiCodingToken(\n refreshToken: string,\n options: { signal?: AbortSignal; deviceId: string },\n): Promise<OAuthCredentials> {\n const clientId = CLIENT_ID;\n const deviceHeaders = getKimiCodingDeviceHeaders(options.deviceId);\n let lastError: Error | undefined;\n for (let attempt = 0; attempt <= REFRESH_MAX_RETRIES; attempt++) {\n if (attempt > 0) await abortableSleep(1000 * 2 ** (attempt - 1), options.signal);\n let response: Response;\n try {\n response = await fetch(`${OAUTH_HOST}/api/oauth/token`, {\n method: 'POST',\n headers: {\n ...deviceHeaders,\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: new URLSearchParams({\n client_id: clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }).toString(),\n signal: requestSignal(options.signal),\n });\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt === REFRESH_MAX_RETRIES) throw lastError;\n continue;\n }\n const data = await readJson(response);\n if (response.ok) return credentialsFromTokenResponse(data, 'refresh', options.deviceId);\n\n const error = typeof data?.error === 'string' ? ` ${data.error}` : '';\n lastError = new Error(`Kimi For Coding token refresh failed: ${response.status}${error}`);\n const retryable = response.status === 429 || response.status >= 500;\n if (!retryable || attempt === REFRESH_MAX_RETRIES) throw lastError;\n }\n throw lastError ?? new Error('Kimi For Coding token refresh failed');\n}\n\nexport const kimiCodingOAuthProvider: OAuthProviderInterface = {\n id: 'kimi-for-coding',\n name: 'Kimi For Coding',\n login: loginKimiCoding,\n refreshToken: credentials =>\n refreshKimiCodingToken(credentials.refresh, {\n deviceId: typeof credentials.deviceId === 'string' ? credentials.deviceId : '',\n }),\n getApiKey: credentials => credentials.access,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"kimi-coding.js","names":[],"sources":["../../../src/auth/providers/kimi-coding.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { arch, hostname, platform, release } from 'node:os';\nimport { getCurrentVersion } from '../../utils/update-check.js';\nimport {\n abortableSleep,\n createDeviceCodePollState,\n pollDeviceCodeUntilComplete,\n stepDeviceCodePoll,\n} from '../device-code.js';\nimport type { DeviceCodePollOutcome, DeviceCodePollState } from '../device-code.js';\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nconst CLIENT_ID = '17e5f671-d194-4dfb-9706-5516cb48c098';\nconst OAUTH_HOST = 'https://auth.kimi.com';\nconst DEVICE_ID_PATTERN = /^[0-9a-f]{32}$/;\nconst DEFAULT_EXPIRES_IN_SECONDS = 15 * 60;\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst REFRESH_MAX_RETRIES = 3;\n\nfunction asciiHeaderValue(value: string): string {\n const sanitized = value.replace(/[^\\x20-\\x7E]/g, '').trim();\n return sanitized || 'unknown';\n}\n\nconst KIMI_DEVICE_DETAILS = {\n 'X-Msh-Platform': 'mastracode',\n 'X-Msh-Version': asciiHeaderValue(getCurrentVersion()),\n 'X-Msh-Device-Name': asciiHeaderValue(hostname()),\n 'X-Msh-Device-Model': asciiHeaderValue(`${platform()} ${arch()}`),\n 'X-Msh-Os-Version': asciiHeaderValue(release()),\n};\n\nexport function createKimiCodingDeviceId(): string {\n return randomUUID().replaceAll('-', '');\n}\n\nexport function isKimiCodingDeviceId(value: unknown): value is string {\n return typeof value === 'string' && DEVICE_ID_PATTERN.test(value);\n}\n\nexport function getKimiCodingDeviceHeaders(deviceId: string): Record<string, string> {\n if (!isKimiCodingDeviceId(deviceId)) {\n throw new Error('Kimi For Coding credentials have an invalid device ID. Please reconnect the account.');\n }\n return { ...KIMI_DEVICE_DETAILS, 'X-Msh-Device-Id': deviceId };\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);\n return signal ? AbortSignal.any([timeout, signal]) : timeout;\n}\n\nfunction trustedHttpUrl(value: unknown): string | null {\n if (typeof value !== 'string' || !value) return null;\n try {\n const url = new URL(value);\n return url.protocol === 'https:' ? url.href : null;\n } catch {\n return null;\n }\n}\n\nasync function readJson(response: Response): Promise<Record<string, unknown> | null> {\n try {\n const value = (await response.json()) as unknown;\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n}\n\nfunction credentialsFromTokenResponse(value: unknown, operation: string, deviceId: string): OAuthCredentials {\n const data = (value ?? {}) as Record<string, unknown>;\n const access = data.access_token;\n const refresh = data.refresh_token;\n const expiresIn = data.expires_in;\n if (\n typeof access !== 'string' ||\n !access ||\n typeof refresh !== 'string' ||\n !refresh ||\n typeof expiresIn !== 'number' ||\n !Number.isFinite(expiresIn) ||\n expiresIn <= 0\n ) {\n throw new Error(`Kimi For Coding token ${operation} response missing fields`);\n }\n return { access, refresh, expires: Date.now() + expiresIn * 1000, deviceId };\n}\n\nexport interface KimiCodingDeviceLoginPending {\n clientId: string;\n deviceId: string;\n deviceCode: string;\n userCode: string;\n url: string;\n instructions: string;\n state: DeviceCodePollState;\n}\n\nexport type KimiCodingDevicePollResult =\n | { status: 'complete'; credentials: OAuthCredentials }\n | { status: 'pending'; nextPollMs: number; pending: KimiCodingDeviceLoginPending }\n | { status: 'failed'; error: string };\n\nexport async function startKimiCodingDeviceLogin(options?: {\n signal?: AbortSignal;\n}): Promise<KimiCodingDeviceLoginPending> {\n const clientId = CLIENT_ID;\n const deviceId = createKimiCodingDeviceId();\n const response = await fetch(`${OAUTH_HOST}/api/oauth/device_authorization`, {\n method: 'POST',\n headers: {\n ...getKimiCodingDeviceHeaders(deviceId),\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: new URLSearchParams({ client_id: clientId }).toString(),\n signal: requestSignal(options?.signal),\n });\n if (!response.ok) {\n throw new Error(`Kimi For Coding device authorization failed: ${response.status}`);\n }\n\n const data = await readJson(response);\n const deviceCode = data?.device_code;\n const userCode = data?.user_code;\n const verificationUri = trustedHttpUrl(data?.verification_uri);\n const verificationUriComplete = trustedHttpUrl(data?.verification_uri_complete);\n if (\n typeof deviceCode !== 'string' ||\n !deviceCode ||\n typeof userCode !== 'string' ||\n !userCode ||\n !verificationUri ||\n !verificationUriComplete\n ) {\n throw new Error('Invalid Kimi For Coding device authorization response');\n }\n\n const interval = data?.interval;\n const expiresIn = data?.expires_in;\n return {\n clientId,\n deviceId,\n deviceCode,\n userCode,\n url: verificationUriComplete,\n instructions: `Enter code: ${userCode}`,\n state: createDeviceCodePollState({\n intervalSeconds:\n typeof interval === 'number' && Number.isFinite(interval) && interval > 0\n ? interval\n : DEFAULT_POLL_INTERVAL_SECONDS,\n expiresInSeconds:\n typeof expiresIn === 'number' && Number.isFinite(expiresIn) && expiresIn > 0\n ? expiresIn\n : DEFAULT_EXPIRES_IN_SECONDS,\n }),\n };\n}\n\nasync function pollKimiCodingTokenOnce(\n pending: KimiCodingDeviceLoginPending,\n signal?: AbortSignal,\n): Promise<DeviceCodePollOutcome<OAuthCredentials>> {\n const response = await fetch(`${OAUTH_HOST}/api/oauth/token`, {\n method: 'POST',\n headers: {\n ...getKimiCodingDeviceHeaders(pending.deviceId),\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: new URLSearchParams({\n client_id: pending.clientId,\n device_code: pending.deviceCode,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }).toString(),\n signal: requestSignal(signal),\n });\n const data = await readJson(response);\n if (response.ok && typeof data?.access_token === 'string') {\n try {\n return { status: 'complete', result: credentialsFromTokenResponse(data, 'poll', pending.deviceId) };\n } catch (error) {\n return { status: 'failed', error: error instanceof Error ? error.message : String(error) };\n }\n }\n\n const error = data?.error;\n if (error === 'authorization_pending') return { status: 'pending' };\n if (error === 'slow_down') {\n return {\n status: 'slow_down',\n intervalSeconds: typeof data?.interval === 'number' && data.interval > 0 ? data.interval : undefined,\n };\n }\n if (error === 'expired_token') {\n return { status: 'failed', error: 'Kimi For Coding authorization expired. Please restart login.' };\n }\n if (error === 'access_denied') return { status: 'failed', error: 'Kimi For Coding login was denied.' };\n return {\n status: 'failed',\n error: `Kimi For Coding token request failed: ${response.status}`,\n };\n}\n\nexport async function pollKimiCodingDeviceLogin(\n pending: KimiCodingDeviceLoginPending,\n options?: { signal?: AbortSignal },\n): Promise<KimiCodingDevicePollResult> {\n const step = await stepDeviceCodePoll(pending.state, () => pollKimiCodingTokenOnce(pending, options?.signal));\n if (step.status === 'complete') return { status: 'complete', credentials: step.result };\n if (step.status === 'failed') return { status: 'failed', error: step.error };\n return {\n status: 'pending',\n nextPollMs: step.nextPollMs,\n pending: { ...pending, state: step.state },\n };\n}\n\nexport async function loginKimiCoding(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const pending = await startKimiCodingDeviceLogin({ signal: callbacks.signal });\n callbacks.onAuth({ url: pending.url, instructions: pending.instructions });\n callbacks.onProgress?.('Waiting for Kimi For Coding device authorization...');\n return pollDeviceCodeUntilComplete({\n state: pending.state,\n pollOnce: () => pollKimiCodingTokenOnce(pending, callbacks.signal),\n signal: callbacks.signal,\n });\n}\n\nexport async function refreshKimiCodingToken(\n refreshToken: string,\n options: { signal?: AbortSignal; deviceId: string },\n): Promise<OAuthCredentials> {\n const clientId = CLIENT_ID;\n const deviceHeaders = getKimiCodingDeviceHeaders(options.deviceId);\n let lastError: Error | undefined;\n for (let attempt = 0; attempt <= REFRESH_MAX_RETRIES; attempt++) {\n if (attempt > 0) await abortableSleep(1000 * 2 ** (attempt - 1), options.signal);\n let response: Response;\n try {\n response = await fetch(`${OAUTH_HOST}/api/oauth/token`, {\n method: 'POST',\n headers: {\n ...deviceHeaders,\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: new URLSearchParams({\n client_id: clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }).toString(),\n signal: requestSignal(options.signal),\n });\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt === REFRESH_MAX_RETRIES) throw lastError;\n continue;\n }\n const data = await readJson(response);\n if (response.ok) return credentialsFromTokenResponse(data, 'refresh', options.deviceId);\n\n const error = typeof data?.error === 'string' ? ` ${data.error}` : '';\n lastError = new Error(`Kimi For Coding token refresh failed: ${response.status}${error}`);\n const retryable = response.status === 429 || response.status >= 500;\n if (!retryable || attempt === REFRESH_MAX_RETRIES) throw lastError;\n }\n throw lastError ?? new Error('Kimi For Coding token refresh failed');\n}\n\nexport const kimiCodingOAuthProvider: OAuthProviderInterface = {\n id: 'kimi-for-coding',\n name: 'Kimi For Coding',\n login: loginKimiCoding,\n refreshToken: credentials =>\n refreshKimiCodingToken(credentials.refresh, {\n deviceId: typeof credentials.deviceId === 'string' ? credentials.deviceId : '',\n }),\n getApiKey: credentials => credentials.access,\n};\n"],"mappings":"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"}
@@ -12,8 +12,10 @@ export declare const OPENAI_CODEX_AUTH_MODES: ReadonlyArray<AuthMode>;
12
12
  declare const JWT_CLAIM_PATH = "https://api.openai.com/auth";
13
13
  type JwtPayload = {
14
14
  chatgpt_account_id?: string;
15
+ email?: string;
15
16
  [JWT_CLAIM_PATH]?: {
16
17
  chatgpt_account_id?: string;
18
+ email?: string;
17
19
  };
18
20
  [key: string]: unknown;
19
21
  };
@@ -133,7 +135,7 @@ export declare const __testing: {
133
135
  /**
134
136
  * Refresh OpenAI Codex OAuth token
135
137
  */
136
- export declare function refreshOpenAICodexToken(refreshToken: string, previousAccountId?: string): Promise<OAuthCredentials>;
138
+ export declare function refreshOpenAICodexToken(refreshToken: string, previousAccountId?: string, previousEmail?: string): Promise<OAuthCredentials>;
137
139
  export declare const openaiCodexOAuthProvider: OAuthProviderInterface;
138
140
  export {};
139
141
  //# sourceMappingURL=openai-codex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/openai-codex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/openai-codex.ts"],"names":[],"mappings":"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"}
@@ -45,6 +45,7 @@ const DEFAULT_CALLBACK_PORT = 1455;
45
45
  const FALLBACK_CALLBACK_PORT = 1457;
46
46
  const DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 3600;
47
47
  const DEVICE_AUTH_TIMEOUT_MS = 900 * 1e3;
48
+ const OAUTH_REQUEST_TIMEOUT_MS = 15e3;
48
49
  const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
49
50
  const JWT_CLAIM_PATH = "https://api.openai.com/auth";
50
51
  const SUCCESS_HTML = `<!doctype html>
@@ -58,6 +59,10 @@ const SUCCESS_HTML = `<!doctype html>
58
59
  <p>Authentication successful. Return to your terminal to continue.</p>
59
60
  </body>
60
61
  </html>`;
62
+ function requestSignal(signal) {
63
+ const timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
64
+ return signal ? AbortSignal.any([timeout, signal]) : timeout;
65
+ }
61
66
  async function createState() {
62
67
  return (await getRandomBytes())(16).toString("hex");
63
68
  }
@@ -90,9 +95,18 @@ function requireAccountId(tokens, fallback) {
90
95
  if (!accountId) throw new Error("Failed to extract ChatGPT account id from OpenAI Codex token");
91
96
  return accountId;
92
97
  }
98
+ function extractEmailFromClaims(payload) {
99
+ const email = payload.email ?? payload[JWT_CLAIM_PATH]?.email;
100
+ return typeof email === "string" && email.length > 0 ? email : null;
101
+ }
102
+ function getEmailFromTokens(tokens) {
103
+ const fromIdToken = tokens.idToken ? extractEmailFromClaims(decodeJwt(tokens.idToken) ?? {}) : null;
104
+ if (fromIdToken) return fromIdToken;
105
+ return extractEmailFromClaims(decodeJwt(tokens.access) ?? {}) ?? void 0;
106
+ }
93
107
  function tokenResponseToResult(json, logPrefix) {
94
108
  if (!json.access_token || !json.refresh_token) {
95
- console.error(`[openai-codex] ${logPrefix} response missing fields:`, json);
109
+ console.error(`[openai-codex] ${logPrefix} response missing required fields (access_token=${Boolean(json.access_token)}, refresh_token=${Boolean(json.refresh_token)})`);
96
110
  return { type: "failed" };
97
111
  }
98
112
  return {
@@ -103,9 +117,10 @@ function tokenResponseToResult(json, logPrefix) {
103
117
  idToken: json.id_token
104
118
  };
105
119
  }
106
- async function exchangeAuthorizationCode(code, verifier, redirectUri) {
120
+ async function exchangeAuthorizationCode(code, verifier, redirectUri, signal) {
107
121
  const response = await fetch(TOKEN_URL, {
108
122
  method: "POST",
123
+ signal: requestSignal(signal),
109
124
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
110
125
  body: new URLSearchParams({
111
126
  grant_type: "authorization_code",
@@ -116,8 +131,7 @@ async function exchangeAuthorizationCode(code, verifier, redirectUri) {
116
131
  })
117
132
  });
118
133
  if (!response.ok) {
119
- const text = await response.text().catch(() => "");
120
- console.error("[openai-codex] code->token failed:", response.status, text);
134
+ console.error("[openai-codex] code->token failed:", response.status);
121
135
  return { type: "failed" };
122
136
  }
123
137
  return tokenResponseToResult(await response.json(), "token");
@@ -126,6 +140,7 @@ async function refreshAccessToken(refreshToken) {
126
140
  try {
127
141
  const response = await fetch(TOKEN_URL, {
128
142
  method: "POST",
143
+ signal: requestSignal(),
129
144
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
130
145
  body: new URLSearchParams({
131
146
  grant_type: "refresh_token",
@@ -134,13 +149,12 @@ async function refreshAccessToken(refreshToken) {
134
149
  })
135
150
  });
136
151
  if (!response.ok) {
137
- const text = await response.text().catch(() => "");
138
- console.error("[openai-codex] Token refresh failed:", response.status, text);
152
+ console.error("[openai-codex] Token refresh failed:", response.status);
139
153
  return { type: "failed" };
140
154
  }
141
155
  return tokenResponseToResult(await response.json(), "Token refresh");
142
- } catch (error) {
143
- console.error("[openai-codex] Token refresh error:", error);
156
+ } catch {
157
+ console.error("[openai-codex] Token refresh request failed");
144
158
  return { type: "failed" };
145
159
  }
146
160
  }
@@ -290,7 +304,7 @@ async function startCodexDeviceLogin(options) {
290
304
  client_id: CLIENT_ID,
291
305
  originator: "mastracode"
292
306
  }),
293
- signal: options?.signal
307
+ signal: requestSignal(options?.signal)
294
308
  });
295
309
  if (!response.ok) throw new Error(`Failed to initiate OpenAI Codex device authorization: ${response.status}`);
296
310
  const deviceData = await response.json();
@@ -328,7 +342,7 @@ async function pollCodexDeviceLogin(pending, options) {
328
342
  device_auth_id: pending.deviceAuthId,
329
343
  user_code: pending.userCode
330
344
  }),
331
- signal: options?.signal
345
+ signal: requestSignal(options?.signal)
332
346
  });
333
347
  if (pollResponse.ok) {
334
348
  const data = await pollResponse.json();
@@ -336,7 +350,7 @@ async function pollCodexDeviceLogin(pending, options) {
336
350
  status: "failed",
337
351
  error: "OpenAI Codex device token response missing required fields"
338
352
  };
339
- const tokenResult = await exchangeAuthorizationCode(data.authorization_code, data.code_verifier, DEVICE_REDIRECT_URI);
353
+ const tokenResult = await exchangeAuthorizationCode(data.authorization_code, data.code_verifier, DEVICE_REDIRECT_URI, options?.signal);
340
354
  if (tokenResult.type !== "success") return {
341
355
  status: "failed",
342
356
  error: "Token exchange failed"
@@ -356,17 +370,15 @@ async function pollCodexDeviceLogin(pending, options) {
356
370
  access: tokenResult.access,
357
371
  refresh: tokenResult.refresh,
358
372
  expires: tokenResult.expires,
359
- accountId
373
+ accountId,
374
+ email: getEmailFromTokens(tokenResult)
360
375
  }
361
376
  };
362
377
  }
363
- if (pollResponse.status !== 403 && pollResponse.status !== 404) {
364
- const text = await pollResponse.text().catch(() => "");
365
- return {
366
- status: "failed",
367
- error: `OpenAI Codex device authorization failed: ${pollResponse.status}${text ? ` ${text}` : ""}`
368
- };
369
- }
378
+ if (pollResponse.status !== 403 && pollResponse.status !== 404) return {
379
+ status: "failed",
380
+ error: `OpenAI Codex device authorization failed: ${pollResponse.status}`
381
+ };
370
382
  return {
371
383
  status: "pending",
372
384
  nextPollMs: pending.intervalMs
@@ -454,14 +466,15 @@ async function loginOpenAICodex(options) {
454
466
  code = parsed.code;
455
467
  }
456
468
  if (!code) throw new Error("Missing authorization code");
457
- const tokenResult = await exchangeAuthorizationCode(code, verifier, server.redirectUri);
469
+ const tokenResult = await exchangeAuthorizationCode(code, verifier, server.redirectUri, options.signal);
458
470
  if (tokenResult.type !== "success") throw new Error("Token exchange failed");
459
471
  const accountId = requireAccountId(tokenResult);
460
472
  return {
461
473
  access: tokenResult.access,
462
474
  refresh: tokenResult.refresh,
463
475
  expires: tokenResult.expires,
464
- accountId
476
+ accountId,
477
+ email: getEmailFromTokens(tokenResult)
465
478
  };
466
479
  } finally {
467
480
  server.close();
@@ -479,7 +492,7 @@ const __testing = {
479
492
  /**
480
493
  * Refresh OpenAI Codex OAuth token
481
494
  */
482
- async function refreshOpenAICodexToken(refreshToken, previousAccountId) {
495
+ async function refreshOpenAICodexToken(refreshToken, previousAccountId, previousEmail) {
483
496
  const result = await refreshAccessToken(refreshToken);
484
497
  if (result.type !== "success") throw new Error("Failed to refresh OpenAI Codex token");
485
498
  const accountId = requireAccountId(result, previousAccountId);
@@ -487,7 +500,8 @@ async function refreshOpenAICodexToken(refreshToken, previousAccountId) {
487
500
  access: result.access,
488
501
  refresh: result.refresh,
489
502
  expires: result.expires,
490
- accountId
503
+ accountId,
504
+ email: getEmailFromTokens(result) ?? previousEmail
491
505
  };
492
506
  }
493
507
  const openaiCodexOAuthProvider = {
@@ -507,10 +521,22 @@ const openaiCodexOAuthProvider = {
507
521
  });
508
522
  },
509
523
  async refreshToken(credentials) {
510
- return refreshOpenAICodexToken(credentials.refresh, credentials.accountId);
524
+ return refreshOpenAICodexToken(credentials.refresh, credentials.accountId, credentials.email);
511
525
  },
512
526
  getApiKey(credentials) {
513
527
  return credentials.access;
528
+ },
529
+ getAccountLabel(credentials) {
530
+ const email = credentials.email;
531
+ return Promise.resolve(typeof email === "string" && email.length > 0 ? email : void 0);
532
+ },
533
+ /**
534
+ * The ChatGPT account/workspace id from the token, when present — stable
535
+ * across refreshes and re-authorizations of one subscription.
536
+ */
537
+ getAccountIdentity(credentials) {
538
+ const accountId = credentials.accountId;
539
+ return typeof accountId === "string" && accountId.length > 0 ? accountId : void 0;
514
540
  }
515
541
  };
516
542
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"openai-codex.js","names":[],"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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"}
1
+ {"version":3,"file":"openai-codex.js","names":[],"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 OAUTH_REQUEST_TIMEOUT_MS = 15_000;\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 email?: string;\n [JWT_CLAIM_PATH]?: {\n chatgpt_account_id?: string;\n email?: string;\n };\n [key: string]: unknown;\n};\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\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\nfunction extractEmailFromClaims(payload: JwtPayload): string | null {\n const email = payload.email ?? payload[JWT_CLAIM_PATH]?.email;\n return typeof email === 'string' && email.length > 0 ? email : null;\n}\n\nfunction getEmailFromTokens(tokens: { idToken?: string; access: string }): string | undefined {\n const fromIdToken = tokens.idToken ? extractEmailFromClaims(decodeJwt(tokens.idToken) ?? {}) : null;\n if (fromIdToken) return fromIdToken;\n const fromAccessToken = extractEmailFromClaims(decodeJwt(tokens.access) ?? {});\n return fromAccessToken ?? undefined;\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(\n `[openai-codex] ${logPrefix} response missing required fields (access_token=${Boolean(json.access_token)}, refresh_token=${Boolean(json.refresh_token)})`,\n );\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(\n code: string,\n verifier: string,\n redirectUri: string,\n signal?: AbortSignal,\n): Promise<TokenResult> {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n signal: requestSignal(signal),\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 console.error('[openai-codex] code->token failed:', response.status);\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 signal: requestSignal(),\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 console.error('[openai-codex] Token refresh failed:', response.status);\n return { type: 'failed' };\n }\n\n return tokenResponseToResult((await response.json()) as TokenResponseJson, 'Token refresh');\n } catch {\n console.error('[openai-codex] Token refresh request failed');\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: requestSignal(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: requestSignal(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 options?.signal,\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 email: getEmailFromTokens(tokenResult),\n },\n };\n }\n\n if (pollResponse.status !== 403 && pollResponse.status !== 404) {\n return {\n status: 'failed',\n error: `OpenAI Codex device authorization failed: ${pollResponse.status}`,\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, options.signal);\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 email: getEmailFromTokens(tokenResult),\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 previousEmail?: 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 email: getEmailFromTokens(result) ?? previousEmail,\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(\n credentials.refresh,\n credentials.accountId as string | undefined,\n credentials.email as string | undefined,\n );\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n\n getAccountLabel(credentials: OAuthCredentials): Promise<string | undefined> {\n const email = credentials.email;\n return Promise.resolve(typeof email === 'string' && email.length > 0 ? email : undefined);\n },\n\n /**\n * The ChatGPT account/workspace id from the token, when present — stable\n * across refreshes and re-authorizations of one subscription.\n */\n getAccountIdentity(credentials: OAuthCredentials): string | undefined {\n const accountId = credentials.accountId;\n return typeof accountId === 'string' && accountId.length > 0 ? accountId : undefined;\n },\n};\n"],"mappings":"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"}
@@ -59,6 +59,6 @@ export declare function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthC
59
59
  /**
60
60
  * Refresh xAI OAuth token
61
61
  */
62
- export declare function refreshXAIToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials>;
62
+ export declare function refreshXAIToken(refreshToken: string, signal?: AbortSignal, previousIdToken?: string): Promise<OAuthCredentials>;
63
63
  export declare const xaiOAuthProvider: OAuthProviderInterface;
64
64
  //# sourceMappingURL=xai.d.ts.map
@@ -1 +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"}
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;AAwEjG;;;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,CAgC5G;AAsDD;;;;;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,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAgB3B;AAgBD,eAAO,MAAM,gBAAgB,EAAE,sBA2B9B,CAAC"}
@@ -18,13 +18,15 @@ const TOKEN_URL = "https://auth.x.ai/oauth2/token";
18
18
  const SCOPE = "openid profile email offline_access grok-cli:access api:access";
19
19
  const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
20
20
  const DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 3600;
21
+ const REQUEST_TIMEOUT_MS = 3e4;
21
22
  const REFRESH_SKEW_MS = 300 * 1e3;
22
23
  async function postForm(url, params, signal) {
24
+ const timeoutSignal = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
23
25
  return fetch(url, {
24
26
  method: "POST",
25
27
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
26
28
  body: new URLSearchParams(params).toString(),
27
- signal
29
+ signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal
28
30
  });
29
31
  }
30
32
  /** The verification URI is opened by the user; only accept https URLs. */
@@ -33,12 +35,12 @@ function validateVerificationUri(raw) {
33
35
  try {
34
36
  parsed = new URL(raw);
35
37
  } catch {
36
- throw new Error(`xAI device authorization returned an invalid verification_uri: ${raw}`);
38
+ throw new Error("xAI device authorization returned an invalid verification_uri");
37
39
  }
38
- if (parsed.protocol !== "https:") throw new Error(`xAI device authorization returned a non-https verification_uri: ${raw}`);
40
+ if (parsed.protocol !== "https:") throw new Error("xAI device authorization returned a non-https verification_uri");
39
41
  return parsed.toString();
40
42
  }
41
- function credentialsFromTokenResponse(data, previousRefreshToken) {
43
+ function credentialsFromTokenResponse(data, previousRefreshToken, previousIdToken) {
42
44
  const record = data ?? {};
43
45
  const access = record.access_token;
44
46
  if (typeof access !== "string" || access.length === 0) throw new Error("xAI token response missing access_token");
@@ -48,7 +50,8 @@ function credentialsFromTokenResponse(data, previousRefreshToken) {
48
50
  return {
49
51
  access,
50
52
  refresh,
51
- expires: Date.now() + expiresIn * 1e3 - REFRESH_SKEW_MS
53
+ expires: Date.now() + expiresIn * 1e3 - REFRESH_SKEW_MS,
54
+ idToken: typeof record.id_token === "string" && record.id_token.length > 0 ? record.id_token : previousIdToken
52
55
  };
53
56
  }
54
57
  /**
@@ -60,10 +63,7 @@ async function startXAIDeviceLogin(options) {
60
63
  client_id: CLIENT_ID,
61
64
  scope: SCOPE
62
65
  }, options?.signal);
63
- if (!response.ok) {
64
- const text = await response.text().catch(() => "");
65
- throw new Error(`Failed to initiate xAI device authorization: ${response.status}${text ? ` ${text}` : ""}`);
66
- }
66
+ if (!response.ok) throw new Error(`Failed to initiate xAI device authorization: ${response.status}`);
67
67
  const data = await response.json();
68
68
  if (!data.device_code || !data.user_code || !data.verification_uri) throw new Error("xAI device authorization response missing required fields");
69
69
  const url = validateVerificationUri(data.verification_uri_complete ?? data.verification_uri);
@@ -123,7 +123,7 @@ async function pollXAITokenOnce(pending, signal) {
123
123
  };
124
124
  default: return {
125
125
  status: "failed",
126
- error: `xAI device authorization failed: ${response.status}${text ? ` ${text}` : ""}`
126
+ error: `xAI device authorization failed: ${response.status}`
127
127
  };
128
128
  }
129
129
  }
@@ -174,17 +174,27 @@ async function loginXAI(callbacks) {
174
174
  /**
175
175
  * Refresh xAI OAuth token
176
176
  */
177
- async function refreshXAIToken(refreshToken, signal) {
177
+ async function refreshXAIToken(refreshToken, signal, previousIdToken) {
178
178
  const response = await postForm(TOKEN_URL, {
179
179
  grant_type: "refresh_token",
180
180
  client_id: CLIENT_ID,
181
181
  refresh_token: refreshToken
182
182
  }, signal);
183
- if (!response.ok) {
184
- const text = await response.text().catch(() => "");
185
- throw new Error(`xAI token refresh failed: ${response.status}${text ? ` ${text}` : ""}`);
183
+ if (!response.ok) throw new Error(`xAI token refresh failed: ${response.status}`);
184
+ return credentialsFromTokenResponse(await response.json(), refreshToken, previousIdToken);
185
+ }
186
+ /** Decode the email claim out of an id_token JWT payload (no verification). */
187
+ function emailFromIdToken(idToken) {
188
+ if (typeof idToken !== "string") return void 0;
189
+ const parts = idToken.split(".");
190
+ if (parts.length !== 3) return void 0;
191
+ try {
192
+ const padded = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
193
+ const json = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
194
+ return typeof json.email === "string" && json.email.length > 0 ? json.email : void 0;
195
+ } catch {
196
+ return;
186
197
  }
187
- return credentialsFromTokenResponse(await response.json(), refreshToken);
188
198
  }
189
199
  const xaiOAuthProvider = {
190
200
  id: "xai",
@@ -193,10 +203,20 @@ const xaiOAuthProvider = {
193
203
  return loginXAI(callbacks);
194
204
  },
195
205
  async refreshToken(credentials) {
196
- return refreshXAIToken(credentials.refresh);
206
+ return refreshXAIToken(credentials.refresh, void 0, credentials.idToken);
197
207
  },
198
208
  getApiKey(credentials) {
199
209
  return credentials.access;
210
+ },
211
+ getAccountLabel(credentials) {
212
+ return Promise.resolve(emailFromIdToken(credentials.idToken));
213
+ },
214
+ /**
215
+ * The account's email, read from the id token — one subscription per email,
216
+ * so this distinguishes two xAI accounts from a re-authorization of one.
217
+ */
218
+ getAccountIdentity(credentials) {
219
+ return emailFromIdToken(credentials.idToken);
200
220
  }
201
221
  };
202
222
  //#endregion