@mastra/code-sdk 0.2.0-alpha.4 → 0.2.0-alpha.9

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 (81) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/agents/credential-resolver.d.ts +51 -0
  3. package/dist/agents/credential-resolver.d.ts.map +1 -0
  4. package/dist/agents/credential-resolver.js +41 -0
  5. package/dist/agents/credential-resolver.js.map +1 -0
  6. package/dist/agents/instructions.d.ts.map +1 -1
  7. package/dist/agents/instructions.js.map +1 -1
  8. package/dist/agents/mastracode-gateway.d.ts +11 -4
  9. package/dist/agents/mastracode-gateway.d.ts.map +1 -1
  10. package/dist/agents/mastracode-gateway.js +51 -30
  11. package/dist/agents/mastracode-gateway.js.map +1 -1
  12. package/dist/agents/model.d.ts +2 -0
  13. package/dist/agents/model.d.ts.map +1 -1
  14. package/dist/agents/model.js +13 -2
  15. package/dist/agents/model.js.map +1 -1
  16. package/dist/agents/workspace.d.ts +11 -1
  17. package/dist/agents/workspace.d.ts.map +1 -1
  18. package/dist/agents/workspace.js +31 -13
  19. package/dist/agents/workspace.js.map +1 -1
  20. package/dist/auth/authorization-input.d.ts +16 -0
  21. package/dist/auth/authorization-input.d.ts.map +1 -0
  22. package/dist/auth/authorization-input.js +28 -0
  23. package/dist/auth/authorization-input.js.map +1 -0
  24. package/dist/auth/device-code.d.ts +93 -0
  25. package/dist/auth/device-code.d.ts.map +1 -0
  26. package/dist/auth/device-code.js +97 -0
  27. package/dist/auth/device-code.js.map +1 -0
  28. package/dist/auth/index.d.ts +1 -0
  29. package/dist/auth/index.d.ts.map +1 -1
  30. package/dist/auth/index.js +3 -1
  31. package/dist/auth/index.js.map +1 -1
  32. package/dist/auth/providers/anthropic.d.ts +24 -1
  33. package/dist/auth/providers/anthropic.d.ts.map +1 -1
  34. package/dist/auth/providers/anthropic.js +21 -8
  35. package/dist/auth/providers/anthropic.js.map +1 -1
  36. package/dist/auth/providers/github-copilot.d.ts +54 -0
  37. package/dist/auth/providers/github-copilot.d.ts.map +1 -1
  38. package/dist/auth/providers/github-copilot.js +88 -61
  39. package/dist/auth/providers/github-copilot.js.map +1 -1
  40. package/dist/auth/providers/openai-codex.d.ts +44 -0
  41. package/dist/auth/providers/openai-codex.d.ts.map +1 -1
  42. package/dist/auth/providers/openai-codex.js +79 -69
  43. package/dist/auth/providers/openai-codex.js.map +1 -1
  44. package/dist/auth/providers/xai.d.ts +64 -0
  45. package/dist/auth/providers/xai.d.ts.map +1 -0
  46. package/dist/auth/providers/xai.js +171 -0
  47. package/dist/auth/providers/xai.js.map +1 -0
  48. package/dist/auth/storage.d.ts.map +1 -1
  49. package/dist/auth/storage.js +5 -2
  50. package/dist/auth/storage.js.map +1 -1
  51. package/dist/auth/types.d.ts +21 -0
  52. package/dist/auth/types.d.ts.map +1 -1
  53. package/dist/hooks/types.d.ts.map +1 -1
  54. package/dist/hooks/types.js.map +1 -1
  55. package/dist/index.d.ts +12 -11
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +49 -35
  58. package/dist/index.js.map +1 -1
  59. package/dist/providers/amazon-bedrock-gateway.d.ts +7 -0
  60. package/dist/providers/amazon-bedrock-gateway.d.ts.map +1 -1
  61. package/dist/providers/amazon-bedrock-gateway.js +66 -2
  62. package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
  63. package/dist/providers/claude-max.d.ts +3 -1
  64. package/dist/providers/claude-max.d.ts.map +1 -1
  65. package/dist/providers/claude-max.js +1 -1
  66. package/dist/providers/claude-max.js.map +1 -1
  67. package/dist/providers/github-copilot.d.ts +3 -1
  68. package/dist/providers/github-copilot.d.ts.map +1 -1
  69. package/dist/providers/github-copilot.js +1 -1
  70. package/dist/providers/github-copilot.js.map +1 -1
  71. package/dist/providers/openai-codex.d.ts +3 -1
  72. package/dist/providers/openai-codex.d.ts.map +1 -1
  73. package/dist/providers/openai-codex.js +1 -1
  74. package/dist/providers/openai-codex.js.map +1 -1
  75. package/dist/providers/xai.d.ts +33 -0
  76. package/dist/providers/xai.d.ts.map +1 -0
  77. package/dist/providers/xai.js +63 -0
  78. package/dist/providers/xai.js.map +1 -0
  79. package/dist/tools/request-sandbox-access.d.ts.map +1 -1
  80. package/dist/tools/request-sandbox-access.js.map +1 -1
  81. package/package.json +11 -11
@@ -105,60 +105,76 @@ function abortableSleep(ms, signal) {
105
105
  signal?.addEventListener("abort", onAbort, { once: true });
106
106
  });
107
107
  }
108
- async function pollForGitHubAccessToken(domain, deviceCode, intervalSeconds, expiresIn, signal) {
109
- const urls = getUrls(domain);
110
- const deadline = Date.now() + expiresIn * 1e3;
111
- let intervalMs = Math.max(1e3, Math.floor(intervalSeconds * 1e3));
112
- let intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;
113
- let slowDownResponses = 0;
114
- while (Date.now() < deadline) {
115
- if (signal?.aborted) {
116
- throw new Error("Login cancelled");
108
+ function copilotNextPollDelayMs(pending) {
109
+ return Math.ceil(pending.intervalMs * pending.intervalMultiplier);
110
+ }
111
+ async function startGitHubCopilotDeviceLogin(enterpriseDomain, options) {
112
+ const domain = enterpriseDomain || "github.com";
113
+ const device = await startDeviceFlow(domain, options?.signal);
114
+ return {
115
+ domain,
116
+ ...enterpriseDomain ? { enterpriseDomain } : {},
117
+ deviceCode: device.device_code,
118
+ userCode: device.user_code,
119
+ url: device.verification_uri,
120
+ instructions: `Enter code: ${device.user_code}`,
121
+ intervalMs: Math.max(1e3, Math.floor(device.interval * 1e3)),
122
+ intervalMultiplier: INITIAL_POLL_INTERVAL_MULTIPLIER,
123
+ slowDownCount: 0,
124
+ deadlineAt: Date.now() + device.expires_in * 1e3
125
+ };
126
+ }
127
+ async function pollGitHubCopilotDeviceLogin(pending, options) {
128
+ if (Date.now() >= pending.deadlineAt) {
129
+ if (pending.slowDownCount > 0) {
130
+ return {
131
+ status: "failed",
132
+ error: "Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again."
133
+ };
117
134
  }
118
- const remainingMs = deadline - Date.now();
119
- const waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);
120
- await abortableSleep(waitMs, signal);
121
- const raw = await fetchJson(
122
- urls.accessTokenUrl,
123
- {
124
- method: "POST",
125
- headers: {
126
- Accept: "application/json",
127
- "Content-Type": "application/x-www-form-urlencoded",
128
- "User-Agent": COPILOT_USER_AGENT
129
- },
130
- body: new URLSearchParams({
131
- client_id: CLIENT_ID,
132
- device_code: deviceCode,
133
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
134
- })
135
+ return { status: "failed", error: "Device flow timed out" };
136
+ }
137
+ const urls = getUrls(pending.domain);
138
+ const raw = await fetchJson(
139
+ urls.accessTokenUrl,
140
+ {
141
+ method: "POST",
142
+ headers: {
143
+ Accept: "application/json",
144
+ "Content-Type": "application/x-www-form-urlencoded",
145
+ "User-Agent": COPILOT_USER_AGENT
135
146
  },
136
- signal
137
- );
138
- if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
139
- return raw.access_token;
147
+ body: new URLSearchParams({
148
+ client_id: CLIENT_ID,
149
+ device_code: pending.deviceCode,
150
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
151
+ })
152
+ },
153
+ options?.signal
154
+ );
155
+ if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
156
+ const githubAccessToken = raw.access_token;
157
+ options?.onProgress?.("Fetching Copilot token...");
158
+ const credentials = await refreshGitHubCopilotToken(githubAccessToken, pending.enterpriseDomain, options?.signal);
159
+ return { status: "complete", credentials };
160
+ }
161
+ if (raw && typeof raw === "object" && typeof raw.error === "string") {
162
+ const { error, error_description: description, interval } = raw;
163
+ if (error === "slow_down") {
164
+ const next = {
165
+ ...pending,
166
+ slowDownCount: pending.slowDownCount + 1,
167
+ intervalMs: typeof interval === "number" && interval > 0 ? interval * 1e3 : Math.max(1e3, pending.intervalMs + 5e3),
168
+ intervalMultiplier: SLOW_DOWN_POLL_INTERVAL_MULTIPLIER
169
+ };
170
+ return { status: "pending", pending: next, nextPollMs: copilotNextPollDelayMs(next) };
140
171
  }
141
- if (raw && typeof raw === "object" && typeof raw.error === "string") {
142
- const { error, error_description: description, interval } = raw;
143
- if (error === "authorization_pending") {
144
- continue;
145
- }
146
- if (error === "slow_down") {
147
- slowDownResponses += 1;
148
- intervalMs = typeof interval === "number" && interval > 0 ? interval * 1e3 : Math.max(1e3, intervalMs + 5e3);
149
- intervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;
150
- continue;
151
- }
172
+ if (error !== "authorization_pending") {
152
173
  const descriptionSuffix = description ? `: ${description}` : "";
153
- throw new Error(`Device flow failed: ${error}${descriptionSuffix}`);
174
+ return { status: "failed", error: `Device flow failed: ${error}${descriptionSuffix}` };
154
175
  }
155
176
  }
156
- if (slowDownResponses > 0) {
157
- throw new Error(
158
- "Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again."
159
- );
160
- }
161
- throw new Error("Device flow timed out");
177
+ return { status: "pending", pending, nextPollMs: copilotNextPollDelayMs(pending) };
162
178
  }
163
179
  async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain, signal) {
164
180
  const domain = enterpriseDomain || "github.com";
@@ -208,18 +224,26 @@ async function loginGitHubCopilot(options) {
208
224
  if (trimmed && !enterpriseDomain) {
209
225
  throw new Error("Invalid GitHub Enterprise URL/domain");
210
226
  }
211
- const domain = enterpriseDomain || "github.com";
212
- const device = await startDeviceFlow(domain, options.signal);
213
- options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
214
- const githubAccessToken = await pollForGitHubAccessToken(
215
- domain,
216
- device.device_code,
217
- device.interval,
218
- device.expires_in,
219
- options.signal
220
- );
221
- options.onProgress?.("Fetching Copilot token...");
222
- return refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? void 0, options.signal);
227
+ let pending = await startGitHubCopilotDeviceLogin(enterpriseDomain ?? void 0, { signal: options.signal });
228
+ options.onAuth(pending.url, pending.instructions);
229
+ while (true) {
230
+ if (options.signal?.aborted) {
231
+ throw new Error("Login cancelled");
232
+ }
233
+ const remainingMs = Math.max(pending.deadlineAt - Date.now(), 0);
234
+ await abortableSleep(Math.min(copilotNextPollDelayMs(pending), remainingMs), options.signal);
235
+ const result = await pollGitHubCopilotDeviceLogin(pending, {
236
+ signal: options.signal,
237
+ onProgress: options.onProgress
238
+ });
239
+ if (result.status === "complete") {
240
+ return result.credentials;
241
+ }
242
+ if (result.status === "failed") {
243
+ throw new Error(result.error);
244
+ }
245
+ pending = result.pending;
246
+ }
223
247
  }
224
248
  async function fetchCopilotModels(opts) {
225
249
  const url = `${opts.baseUrl.replace(/\/$/, "")}/models`;
@@ -292,11 +316,14 @@ const githubCopilotOAuthProvider = {
292
316
  export {
293
317
  COPILOT_HEADERS,
294
318
  COPILOT_USER_AGENT,
319
+ copilotNextPollDelayMs,
295
320
  fetchCopilotModels,
296
321
  getGitHubCopilotBaseUrl,
297
322
  githubCopilotOAuthProvider,
298
323
  loginGitHubCopilot,
299
324
  normalizeDomain,
300
- refreshGitHubCopilotToken
325
+ pollGitHubCopilotDeviceLogin,
326
+ refreshGitHubCopilotToken,
327
+ startGitHubCopilotDeviceLogin
301
328
  };
302
329
  //# sourceMappingURL=github-copilot.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/auth/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth flow\n *\n * Inspired by:\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n *\n * Storage layout in AuthStorage:\n * - `refresh` holds the long-lived GitHub OAuth access token (used to mint Copilot tokens).\n * - `access` holds the short-lived Copilot bearer token (~30 minutes).\n * - `expires` is the Copilot bearer expiry (ms epoch, with a 5-minute safety buffer).\n * - `enterpriseUrl` is the GitHub Enterprise hostname, or undefined for github.com.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nexport type GitHubCopilotCredentials = OAuthCredentials & {\n enterpriseUrl?: string;\n};\n\nconst decode = (s: string): string => atob(s);\n// Copilot's public client ID, encoded to mirror the upstream references.\nconst CLIENT_ID = decode('SXYxLmI1MDdhMDhjODdlY2ZlOTg=');\n\nexport const COPILOT_USER_AGENT = 'GitHubCopilotChat/0.35.0';\n\n/** Headers that Copilot endpoints expect from a VS Code-like client. */\nexport const COPILOT_HEADERS = {\n 'User-Agent': COPILOT_USER_AGENT,\n 'Editor-Version': 'vscode/1.107.0',\n 'Editor-Plugin-Version': 'copilot-chat/0.35.0',\n 'Copilot-Integration-Id': 'vscode-chat',\n} as const;\n\nconst INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;\nconst SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;\n\ntype DeviceCodeResponse = {\n device_code: string;\n user_code: string;\n verification_uri: string;\n interval: number;\n expires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n access_token: string;\n token_type?: string;\n scope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n error: string;\n error_description?: string;\n interval?: number;\n};\n\n/**\n * Normalize a user-entered Enterprise URL/domain into a hostname (e.g. `company.ghe.com`).\n * Returns null for an empty input or an unparseable URL.\n */\nexport function normalizeDomain(input: string): string | null {\n const trimmed = input.trim();\n if (!trimmed) return null;\n try {\n const url = trimmed.includes('://') ? new URL(trimmed) : new URL(`https://${trimmed}`);\n return url.hostname || null;\n } catch {\n return null;\n }\n}\n\nfunction getUrls(domain: string): {\n deviceCodeUrl: string;\n accessTokenUrl: string;\n copilotTokenUrl: string;\n} {\n return {\n deviceCodeUrl: `https://${domain}/login/device/code`,\n accessTokenUrl: `https://${domain}/login/oauth/access_token`,\n copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n };\n}\n\n/**\n * Parse the `proxy-ep` segment from a Copilot bearer token and convert to its API base URL.\n * Token shape: `tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...`\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n const match = token.match(/proxy-ep=([^;]+)/);\n if (!match) return null;\n const proxyHost = match[1]!;\n const apiHost = proxyHost.replace(/^proxy\\./, 'api.');\n return `https://${apiHost}`;\n}\n\n/**\n * Resolve the Copilot API base URL.\n * Prefers the `proxy-ep` parsed from the bearer token, then falls back to enterprise/individual defaults.\n */\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n if (token) {\n const fromToken = getBaseUrlFromToken(token);\n if (fromToken) return fromToken;\n }\n if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n return 'https://api.individual.githubcopilot.com';\n}\n\nasync function fetchJson(url: string, init: RequestInit, signal?: AbortSignal): Promise<unknown> {\n const response = await fetch(url, signal ? { ...init, signal } : init);\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`${response.status} ${response.statusText}: ${text}`);\n }\n return response.json();\n}\n\nasync function startDeviceFlow(domain: string, signal?: AbortSignal): Promise<DeviceCodeResponse> {\n const urls = getUrls(domain);\n const data = await fetchJson(\n urls.deviceCodeUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n scope: 'read:user',\n }),\n },\n signal,\n );\n\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid device code response');\n }\n\n const obj = data as Record<string, unknown>;\n const deviceCode = obj.device_code;\n const userCode = obj.user_code;\n const verificationUri = obj.verification_uri;\n const interval = obj.interval;\n const expiresIn = obj.expires_in;\n\n if (\n typeof deviceCode !== 'string' ||\n typeof userCode !== 'string' ||\n typeof verificationUri !== 'string' ||\n typeof interval !== 'number' ||\n typeof expiresIn !== 'number'\n ) {\n throw new Error('Invalid device code response fields');\n }\n\n return {\n device_code: deviceCode,\n user_code: userCode,\n verification_uri: verificationUri,\n interval,\n expires_in: expiresIn,\n };\n}\n\n/** Sleep that can be interrupted by an AbortSignal. */\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Login cancelled'));\n return;\n }\n\n let timeout: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeout);\n reject(new Error('Login cancelled'));\n };\n\n timeout = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nasync function pollForGitHubAccessToken(\n domain: string,\n deviceCode: string,\n intervalSeconds: number,\n expiresIn: number,\n signal?: AbortSignal,\n): Promise<string> {\n const urls = getUrls(domain);\n const deadline = Date.now() + expiresIn * 1000;\n let intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));\n let intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;\n let slowDownResponses = 0;\n\n while (Date.now() < deadline) {\n if (signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n const remainingMs = deadline - Date.now();\n const waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);\n await abortableSleep(waitMs, signal);\n\n const raw = await fetchJson(\n urls.accessTokenUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n device_code: deviceCode,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }),\n },\n signal,\n );\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenSuccessResponse).access_token === 'string') {\n return (raw as DeviceTokenSuccessResponse).access_token;\n }\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenErrorResponse).error === 'string') {\n const { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;\n if (error === 'authorization_pending') {\n continue;\n }\n\n if (error === 'slow_down') {\n slowDownResponses += 1;\n intervalMs = typeof interval === 'number' && interval > 0 ? interval * 1000 : Math.max(1000, intervalMs + 5000);\n intervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;\n continue;\n }\n\n const descriptionSuffix = description ? `: ${description}` : '';\n throw new Error(`Device flow failed: ${error}${descriptionSuffix}`);\n }\n }\n\n if (slowDownResponses > 0) {\n throw new Error(\n 'Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.',\n );\n }\n\n throw new Error('Device flow timed out');\n}\n\n/**\n * Refresh the short-lived Copilot bearer token using the long-lived GitHub OAuth token.\n *\n * `refreshToken` here is the GitHub OAuth access token; the Copilot endpoint returns a\n * fresh bearer token (`token`) and absolute expiry (`expires_at`, seconds since epoch).\n */\nexport async function refreshGitHubCopilotToken(\n refreshToken: string,\n enterpriseDomain?: string,\n signal?: AbortSignal,\n): Promise<GitHubCopilotCredentials> {\n const domain = enterpriseDomain || 'github.com';\n const urls = getUrls(domain);\n\n const raw = await fetchJson(\n urls.copilotTokenUrl,\n {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${refreshToken}`,\n ...COPILOT_HEADERS,\n },\n },\n signal,\n );\n\n if (!raw || typeof raw !== 'object') {\n throw new Error('Invalid Copilot token response');\n }\n\n const obj = raw as Record<string, unknown>;\n const token = obj.token;\n const expiresAt = obj.expires_at;\n\n if (typeof token !== 'string' || typeof expiresAt !== 'number') {\n throw new Error('Invalid Copilot token response fields');\n }\n\n const credentials: GitHubCopilotCredentials = {\n refresh: refreshToken,\n access: token,\n // expires_at is seconds; subtract 5 minutes so we refresh before actual expiry.\n expires: expiresAt * 1000 - 5 * 60 * 1000,\n };\n if (enterpriseDomain) {\n credentials.enterpriseUrl = enterpriseDomain;\n }\n return credentials;\n}\n\n/**\n * Login with GitHub Copilot OAuth (device-code flow).\n *\n * Prompts for an optional GitHub Enterprise URL/domain, performs the device-code flow,\n * then exchanges the GitHub OAuth token for a Copilot bearer token.\n */\nexport async function loginGitHubCopilot(options: {\n onAuth: (url: string, instructions?: string) => void;\n onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n onProgress?: (message: string) => void;\n signal?: AbortSignal;\n}): Promise<GitHubCopilotCredentials> {\n const input = await options.onPrompt({\n message: 'GitHub Enterprise URL/domain (blank for github.com)',\n placeholder: 'company.ghe.com',\n allowEmpty: true,\n });\n\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n const trimmed = input.trim();\n const enterpriseDomain = normalizeDomain(input);\n if (trimmed && !enterpriseDomain) {\n throw new Error('Invalid GitHub Enterprise URL/domain');\n }\n const domain = enterpriseDomain || 'github.com';\n\n const device = await startDeviceFlow(domain, options.signal);\n options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);\n\n const githubAccessToken = await pollForGitHubAccessToken(\n domain,\n device.device_code,\n device.interval,\n device.expires_in,\n options.signal,\n );\n\n options.onProgress?.('Fetching Copilot token...');\n return refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined, options.signal);\n}\n\n/**\n * Filtered, normalized entry from the Copilot `/models` API.\n *\n * The full API payload includes a lot of capability metadata; we only keep the bits\n * we need to expose models in `listAvailableModels()` and route requests sensibly.\n */\nexport type CopilotModelEntry = {\n /** Stable model id (e.g. `claude-sonnet-4.5`, `gpt-4.1`). */\n id: string;\n /** Human-readable display name (e.g. `Claude Sonnet 4.5`). */\n name: string;\n /** Vendor field from the API (e.g. `Anthropic`, `OpenAI`). */\n vendor: string;\n /**\n * Endpoints the model exposes (e.g. `/chat/completions`, `/responses`, `/v1/messages`).\n * Empty when the API didn't return `supported_endpoints` for this entry.\n */\n supportedEndpoints: string[];\n /** True when `supported_endpoints` includes `/v1/messages` (Anthropic-shaped Copilot model). */\n isAnthropicShaped: boolean;\n /** True when `capabilities.supports.vision` is true. */\n supportsVision: boolean;\n /** True when the model supports tool calling. */\n supportsToolCalls: boolean;\n};\n\n/**\n * Fetch the Copilot model list available to the current subscription.\n *\n * Hits `${baseURL}/models` with the Copilot bearer token, filters to\n * `model_picker_enabled === true && policy.state !== 'disabled'`, and returns\n * a normalized list. Mirrors opencode's filtering rules.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/models.ts\n */\nexport async function fetchCopilotModels(opts: {\n baseUrl: string;\n bearerToken: string;\n signal?: AbortSignal;\n}): Promise<CopilotModelEntry[]> {\n const url = `${opts.baseUrl.replace(/\\/$/, '')}/models`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${opts.bearerToken}`,\n ...COPILOT_HEADERS,\n },\n signal: opts.signal,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`Failed to fetch Copilot models: ${response.status} ${response.statusText}: ${text}`);\n }\n\n const json = await response.json().catch(() => null);\n if (!json || typeof json !== 'object' || !Array.isArray((json as { data?: unknown }).data)) {\n throw new Error('Invalid Copilot models response: missing `data` array');\n }\n\n const data = (json as { data: unknown[] }).data;\n const result: CopilotModelEntry[] = [];\n\n for (const item of data) {\n if (!item || typeof item !== 'object') continue;\n const obj = item as Record<string, unknown>;\n\n if (obj.model_picker_enabled !== true) continue;\n\n const policy = obj.policy as Record<string, unknown> | undefined;\n if (policy && policy.state === 'disabled') continue;\n\n const id = obj.id;\n if (typeof id !== 'string' || !id) continue;\n\n const name = typeof obj.name === 'string' ? obj.name : id;\n const vendor = typeof obj.vendor === 'string' ? obj.vendor : '';\n\n const capabilities = obj.capabilities as Record<string, unknown> | undefined;\n const supports = capabilities?.supports as Record<string, unknown> | undefined;\n const supportsVision = supports?.vision === true;\n const supportsToolCalls = supports?.tool_calls === true;\n\n const rawEndpoints = obj.supported_endpoints;\n const supportedEndpoints = Array.isArray(rawEndpoints)\n ? rawEndpoints.filter((e): e is string => typeof e === 'string')\n : [];\n const isAnthropicShaped = supportedEndpoints.includes('/v1/messages');\n\n result.push({\n id,\n name,\n vendor,\n supportedEndpoints,\n isAnthropicShaped,\n supportsVision,\n supportsToolCalls,\n });\n }\n\n return result;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n id: 'github-copilot',\n name: 'GitHub Copilot',\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n return loginGitHubCopilot({\n onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n signal: callbacks.signal,\n });\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const creds = credentials as GitHubCopilotCredentials;\n return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/auth/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth flow\n *\n * Inspired by:\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n *\n * Storage layout in AuthStorage:\n * - `refresh` holds the long-lived GitHub OAuth access token (used to mint Copilot tokens).\n * - `access` holds the short-lived Copilot bearer token (~30 minutes).\n * - `expires` is the Copilot bearer expiry (ms epoch, with a 5-minute safety buffer).\n * - `enterpriseUrl` is the GitHub Enterprise hostname, or undefined for github.com.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../types.js';\n\nexport type GitHubCopilotCredentials = OAuthCredentials & {\n enterpriseUrl?: string;\n};\n\nconst decode = (s: string): string => atob(s);\n// Copilot's public client ID, encoded to mirror the upstream references.\nconst CLIENT_ID = decode('SXYxLmI1MDdhMDhjODdlY2ZlOTg=');\n\nexport const COPILOT_USER_AGENT = 'GitHubCopilotChat/0.35.0';\n\n/** Headers that Copilot endpoints expect from a VS Code-like client. */\nexport const COPILOT_HEADERS = {\n 'User-Agent': COPILOT_USER_AGENT,\n 'Editor-Version': 'vscode/1.107.0',\n 'Editor-Plugin-Version': 'copilot-chat/0.35.0',\n 'Copilot-Integration-Id': 'vscode-chat',\n} as const;\n\nconst INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;\nconst SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;\n\ntype DeviceCodeResponse = {\n device_code: string;\n user_code: string;\n verification_uri: string;\n interval: number;\n expires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n access_token: string;\n token_type?: string;\n scope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n error: string;\n error_description?: string;\n interval?: number;\n};\n\n/**\n * Normalize a user-entered Enterprise URL/domain into a hostname (e.g. `company.ghe.com`).\n * Returns null for an empty input or an unparseable URL.\n */\nexport function normalizeDomain(input: string): string | null {\n const trimmed = input.trim();\n if (!trimmed) return null;\n try {\n const url = trimmed.includes('://') ? new URL(trimmed) : new URL(`https://${trimmed}`);\n return url.hostname || null;\n } catch {\n return null;\n }\n}\n\nfunction getUrls(domain: string): {\n deviceCodeUrl: string;\n accessTokenUrl: string;\n copilotTokenUrl: string;\n} {\n return {\n deviceCodeUrl: `https://${domain}/login/device/code`,\n accessTokenUrl: `https://${domain}/login/oauth/access_token`,\n copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n };\n}\n\n/**\n * Parse the `proxy-ep` segment from a Copilot bearer token and convert to its API base URL.\n * Token shape: `tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...`\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n const match = token.match(/proxy-ep=([^;]+)/);\n if (!match) return null;\n const proxyHost = match[1]!;\n const apiHost = proxyHost.replace(/^proxy\\./, 'api.');\n return `https://${apiHost}`;\n}\n\n/**\n * Resolve the Copilot API base URL.\n * Prefers the `proxy-ep` parsed from the bearer token, then falls back to enterprise/individual defaults.\n */\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n if (token) {\n const fromToken = getBaseUrlFromToken(token);\n if (fromToken) return fromToken;\n }\n if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n return 'https://api.individual.githubcopilot.com';\n}\n\nasync function fetchJson(url: string, init: RequestInit, signal?: AbortSignal): Promise<unknown> {\n const response = await fetch(url, signal ? { ...init, signal } : init);\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`${response.status} ${response.statusText}: ${text}`);\n }\n return response.json();\n}\n\nasync function startDeviceFlow(domain: string, signal?: AbortSignal): Promise<DeviceCodeResponse> {\n const urls = getUrls(domain);\n const data = await fetchJson(\n urls.deviceCodeUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n scope: 'read:user',\n }),\n },\n signal,\n );\n\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid device code response');\n }\n\n const obj = data as Record<string, unknown>;\n const deviceCode = obj.device_code;\n const userCode = obj.user_code;\n const verificationUri = obj.verification_uri;\n const interval = obj.interval;\n const expiresIn = obj.expires_in;\n\n if (\n typeof deviceCode !== 'string' ||\n typeof userCode !== 'string' ||\n typeof verificationUri !== 'string' ||\n typeof interval !== 'number' ||\n typeof expiresIn !== 'number'\n ) {\n throw new Error('Invalid device code response fields');\n }\n\n return {\n device_code: deviceCode,\n user_code: userCode,\n verification_uri: verificationUri,\n interval,\n expires_in: expiresIn,\n };\n}\n\n/** Sleep that can be interrupted by an AbortSignal. */\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Login cancelled'));\n return;\n }\n\n let timeout: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeout);\n reject(new Error('Login cancelled'));\n };\n\n timeout = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * Serializable pending state for a Copilot device-code login. Safe to persist\n * (e.g. a `pending jsonb` column) so polling can span HTTP requests/replicas.\n */\nexport interface CopilotDeviceLoginPending {\n /** GitHub host performing the flow (`github.com` or an Enterprise hostname). */\n domain: string;\n /** Enterprise hostname, or undefined for github.com. Recorded on the final credentials. */\n enterpriseDomain?: string;\n deviceCode: string;\n userCode: string;\n /** Verification URL for the user to open. */\n url: string;\n instructions: string;\n /** Current base poll interval in ms (grows on slow_down). */\n intervalMs: number;\n /** Safety margin applied to intervalMs when computing the next wait. */\n intervalMultiplier: number;\n /** Number of slow_down responses seen (used for the clock-drift hint). */\n slowDownCount: number;\n /** ms epoch after which the device authorization expires. */\n deadlineAt: number;\n}\n\nexport type CopilotDevicePollResult =\n | { status: 'complete'; credentials: GitHubCopilotCredentials }\n | { status: 'pending'; pending: CopilotDeviceLoginPending; nextPollMs: number }\n | { status: 'failed'; error: string };\n\n/** Delay to wait before the next upstream poll for a pending Copilot device login. */\nexport function copilotNextPollDelayMs(pending: CopilotDeviceLoginPending): number {\n return Math.ceil(pending.intervalMs * pending.intervalMultiplier);\n}\n\n/**\n * Start a Copilot device-code login: request a user code and return the\n * serializable pending state for subsequent polls. Wait `copilotNextPollDelayMs`\n * before the first poll.\n */\nexport async function startGitHubCopilotDeviceLogin(\n enterpriseDomain?: string,\n options?: { signal?: AbortSignal },\n): Promise<CopilotDeviceLoginPending> {\n const domain = enterpriseDomain || 'github.com';\n const device = await startDeviceFlow(domain, options?.signal);\n\n return {\n domain,\n ...(enterpriseDomain ? { enterpriseDomain } : {}),\n deviceCode: device.device_code,\n userCode: device.user_code,\n url: device.verification_uri,\n instructions: `Enter code: ${device.user_code}`,\n intervalMs: Math.max(1000, Math.floor(device.interval * 1000)),\n intervalMultiplier: INITIAL_POLL_INTERVAL_MULTIPLIER,\n slowDownCount: 0,\n deadlineAt: Date.now() + device.expires_in * 1000,\n };\n}\n\n/**\n * Perform exactly one upstream poll for a pending Copilot device login.\n * On success, exchanges the GitHub OAuth token for a Copilot bearer token and\n * returns full credentials. Never throws for flow-level conditions; network\n * errors from fetch still propagate.\n */\nexport async function pollGitHubCopilotDeviceLogin(\n pending: CopilotDeviceLoginPending,\n options?: { signal?: AbortSignal; onProgress?: (message: string) => void },\n): Promise<CopilotDevicePollResult> {\n if (Date.now() >= pending.deadlineAt) {\n if (pending.slowDownCount > 0) {\n return {\n status: 'failed',\n error:\n 'Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.',\n };\n }\n return { status: 'failed', error: 'Device flow timed out' };\n }\n\n const urls = getUrls(pending.domain);\n const raw = await fetchJson(\n urls.accessTokenUrl,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': COPILOT_USER_AGENT,\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n device_code: pending.deviceCode,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }),\n },\n options?.signal,\n );\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenSuccessResponse).access_token === 'string') {\n const githubAccessToken = (raw as DeviceTokenSuccessResponse).access_token;\n options?.onProgress?.('Fetching Copilot token...');\n const credentials = await refreshGitHubCopilotToken(githubAccessToken, pending.enterpriseDomain, options?.signal);\n return { status: 'complete', credentials };\n }\n\n if (raw && typeof raw === 'object' && typeof (raw as DeviceTokenErrorResponse).error === 'string') {\n const { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;\n\n if (error === 'slow_down') {\n const next: CopilotDeviceLoginPending = {\n ...pending,\n slowDownCount: pending.slowDownCount + 1,\n intervalMs:\n typeof interval === 'number' && interval > 0 ? interval * 1000 : Math.max(1000, pending.intervalMs + 5000),\n intervalMultiplier: SLOW_DOWN_POLL_INTERVAL_MULTIPLIER,\n };\n return { status: 'pending', pending: next, nextPollMs: copilotNextPollDelayMs(next) };\n }\n\n if (error !== 'authorization_pending') {\n const descriptionSuffix = description ? `: ${description}` : '';\n return { status: 'failed', error: `Device flow failed: ${error}${descriptionSuffix}` };\n }\n }\n\n // authorization_pending or an unrecognized-but-OK response: keep polling.\n return { status: 'pending', pending, nextPollMs: copilotNextPollDelayMs(pending) };\n}\n\n/**\n * Refresh the short-lived Copilot bearer token using the long-lived GitHub OAuth token.\n *\n * `refreshToken` here is the GitHub OAuth access token; the Copilot endpoint returns a\n * fresh bearer token (`token`) and absolute expiry (`expires_at`, seconds since epoch).\n */\nexport async function refreshGitHubCopilotToken(\n refreshToken: string,\n enterpriseDomain?: string,\n signal?: AbortSignal,\n): Promise<GitHubCopilotCredentials> {\n const domain = enterpriseDomain || 'github.com';\n const urls = getUrls(domain);\n\n const raw = await fetchJson(\n urls.copilotTokenUrl,\n {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${refreshToken}`,\n ...COPILOT_HEADERS,\n },\n },\n signal,\n );\n\n if (!raw || typeof raw !== 'object') {\n throw new Error('Invalid Copilot token response');\n }\n\n const obj = raw as Record<string, unknown>;\n const token = obj.token;\n const expiresAt = obj.expires_at;\n\n if (typeof token !== 'string' || typeof expiresAt !== 'number') {\n throw new Error('Invalid Copilot token response fields');\n }\n\n const credentials: GitHubCopilotCredentials = {\n refresh: refreshToken,\n access: token,\n // expires_at is seconds; subtract 5 minutes so we refresh before actual expiry.\n expires: expiresAt * 1000 - 5 * 60 * 1000,\n };\n if (enterpriseDomain) {\n credentials.enterpriseUrl = enterpriseDomain;\n }\n return credentials;\n}\n\n/**\n * Login with GitHub Copilot OAuth (device-code flow).\n *\n * Prompts for an optional GitHub Enterprise URL/domain, performs the device-code flow,\n * then exchanges the GitHub OAuth token for a Copilot bearer token.\n */\nexport async function loginGitHubCopilot(options: {\n onAuth: (url: string, instructions?: string) => void;\n onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n onProgress?: (message: string) => void;\n signal?: AbortSignal;\n}): Promise<GitHubCopilotCredentials> {\n const input = await options.onPrompt({\n message: 'GitHub Enterprise URL/domain (blank for github.com)',\n placeholder: 'company.ghe.com',\n allowEmpty: true,\n });\n\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n const trimmed = input.trim();\n const enterpriseDomain = normalizeDomain(input);\n if (trimmed && !enterpriseDomain) {\n throw new Error('Invalid GitHub Enterprise URL/domain');\n }\n let pending = await startGitHubCopilotDeviceLogin(enterpriseDomain ?? undefined, { signal: options.signal });\n options.onAuth(pending.url, pending.instructions);\n\n while (true) {\n if (options.signal?.aborted) {\n throw new Error('Login cancelled');\n }\n\n // Wait before polling (safety margin over the server interval), clamped to the deadline.\n const remainingMs = Math.max(pending.deadlineAt - Date.now(), 0);\n await abortableSleep(Math.min(copilotNextPollDelayMs(pending), remainingMs), options.signal);\n\n const result = await pollGitHubCopilotDeviceLogin(pending, {\n signal: options.signal,\n onProgress: options.onProgress,\n });\n if (result.status === 'complete') {\n return result.credentials;\n }\n if (result.status === 'failed') {\n throw new Error(result.error);\n }\n pending = result.pending;\n }\n}\n\n/**\n * Filtered, normalized entry from the Copilot `/models` API.\n *\n * The full API payload includes a lot of capability metadata; we only keep the bits\n * we need to expose models in `listAvailableModels()` and route requests sensibly.\n */\nexport type CopilotModelEntry = {\n /** Stable model id (e.g. `claude-sonnet-4.5`, `gpt-4.1`). */\n id: string;\n /** Human-readable display name (e.g. `Claude Sonnet 4.5`). */\n name: string;\n /** Vendor field from the API (e.g. `Anthropic`, `OpenAI`). */\n vendor: string;\n /**\n * Endpoints the model exposes (e.g. `/chat/completions`, `/responses`, `/v1/messages`).\n * Empty when the API didn't return `supported_endpoints` for this entry.\n */\n supportedEndpoints: string[];\n /** True when `supported_endpoints` includes `/v1/messages` (Anthropic-shaped Copilot model). */\n isAnthropicShaped: boolean;\n /** True when `capabilities.supports.vision` is true. */\n supportsVision: boolean;\n /** True when the model supports tool calling. */\n supportsToolCalls: boolean;\n};\n\n/**\n * Fetch the Copilot model list available to the current subscription.\n *\n * Hits `${baseURL}/models` with the Copilot bearer token, filters to\n * `model_picker_enabled === true && policy.state !== 'disabled'`, and returns\n * a normalized list. Mirrors opencode's filtering rules.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/models.ts\n */\nexport async function fetchCopilotModels(opts: {\n baseUrl: string;\n bearerToken: string;\n signal?: AbortSignal;\n}): Promise<CopilotModelEntry[]> {\n const url = `${opts.baseUrl.replace(/\\/$/, '')}/models`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${opts.bearerToken}`,\n ...COPILOT_HEADERS,\n },\n signal: opts.signal,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(`Failed to fetch Copilot models: ${response.status} ${response.statusText}: ${text}`);\n }\n\n const json = await response.json().catch(() => null);\n if (!json || typeof json !== 'object' || !Array.isArray((json as { data?: unknown }).data)) {\n throw new Error('Invalid Copilot models response: missing `data` array');\n }\n\n const data = (json as { data: unknown[] }).data;\n const result: CopilotModelEntry[] = [];\n\n for (const item of data) {\n if (!item || typeof item !== 'object') continue;\n const obj = item as Record<string, unknown>;\n\n if (obj.model_picker_enabled !== true) continue;\n\n const policy = obj.policy as Record<string, unknown> | undefined;\n if (policy && policy.state === 'disabled') continue;\n\n const id = obj.id;\n if (typeof id !== 'string' || !id) continue;\n\n const name = typeof obj.name === 'string' ? obj.name : id;\n const vendor = typeof obj.vendor === 'string' ? obj.vendor : '';\n\n const capabilities = obj.capabilities as Record<string, unknown> | undefined;\n const supports = capabilities?.supports as Record<string, unknown> | undefined;\n const supportsVision = supports?.vision === true;\n const supportsToolCalls = supports?.tool_calls === true;\n\n const rawEndpoints = obj.supported_endpoints;\n const supportedEndpoints = Array.isArray(rawEndpoints)\n ? rawEndpoints.filter((e): e is string => typeof e === 'string')\n : [];\n const isAnthropicShaped = supportedEndpoints.includes('/v1/messages');\n\n result.push({\n id,\n name,\n vendor,\n supportedEndpoints,\n isAnthropicShaped,\n supportsVision,\n supportsToolCalls,\n });\n }\n\n return result;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n id: 'github-copilot',\n name: 'GitHub Copilot',\n\n async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n return loginGitHubCopilot({\n onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n signal: callbacks.signal,\n });\n },\n\n async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const creds = credentials as GitHubCopilotCredentials;\n return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n },\n\n getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n },\n};\n"],"mappings":"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","names":[]}
@@ -45,6 +45,50 @@ type CallbackPorts = {
45
45
  fallbackPort: number;
46
46
  };
47
47
  declare function startLocalOAuthServer(state: string, ports?: CallbackPorts): Promise<OAuthServerInfo>;
48
+ /**
49
+ * Serializable pending state for a Codex device-code login. Safe to persist
50
+ * (e.g. a `pending jsonb` column) so polling can span HTTP requests — the
51
+ * device token response carries the `code_verifier`, so no PKCE state needs
52
+ * to be kept client-side.
53
+ */
54
+ export interface CodexDeviceLoginPending {
55
+ deviceAuthId: string;
56
+ userCode: string;
57
+ /** Verification URL for the user to open. */
58
+ url: string;
59
+ instructions: string;
60
+ /** Poll interval in ms suggested by the server. */
61
+ intervalMs: number;
62
+ /** ms epoch after which the device authorization expires. */
63
+ deadlineAt: number;
64
+ }
65
+ export type CodexDevicePollResult = {
66
+ status: 'complete';
67
+ credentials: OAuthCredentials;
68
+ } | {
69
+ status: 'pending';
70
+ nextPollMs: number;
71
+ } | {
72
+ status: 'failed';
73
+ error: string;
74
+ };
75
+ /**
76
+ * Start a Codex device-code login: request a user code and return the
77
+ * serializable pending state for subsequent polls.
78
+ */
79
+ export declare function startCodexDeviceLogin(options?: {
80
+ signal?: AbortSignal;
81
+ }): Promise<CodexDeviceLoginPending>;
82
+ /**
83
+ * Perform exactly one upstream poll for a pending Codex device login.
84
+ * The Codex device endpoint signals "still pending" via HTTP 403/404 (it is
85
+ * not an RFC 8628 error-JSON endpoint); on success it returns the
86
+ * authorization code plus server-held PKCE verifier, which we exchange
87
+ * immediately for credentials. Never throws for flow-level conditions.
88
+ */
89
+ export declare function pollCodexDeviceLogin(pending: CodexDeviceLoginPending, options?: {
90
+ signal?: AbortSignal;
91
+ }): Promise<CodexDevicePollResult>;
48
92
  declare function loginOpenAICodexDevice(options: {
49
93
  onAuth: (info: {
50
94
  url: string;
@@ -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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"}
@@ -12,6 +12,7 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
12
12
  return m;
13
13
  });
14
14
  }
15
+ import { parseAuthorizationInput } from "../authorization-input.js";
15
16
  import { generatePKCE } from "../pkce.js";
16
17
  const OPENAI_CODEX_AUTH_MODES = [
17
18
  {
@@ -54,30 +55,6 @@ async function createState() {
54
55
  const randomBytes = await getRandomBytes();
55
56
  return randomBytes(16).toString("hex");
56
57
  }
57
- function parseAuthorizationInput(input) {
58
- const value = input.trim();
59
- if (!value) return {};
60
- try {
61
- const url = new URL(value);
62
- return {
63
- code: url.searchParams.get("code") ?? void 0,
64
- state: url.searchParams.get("state") ?? void 0
65
- };
66
- } catch {
67
- }
68
- if (value.includes("#")) {
69
- const [code, state] = value.split("#", 2);
70
- return { code, state };
71
- }
72
- if (value.includes("code=")) {
73
- const params = new URLSearchParams(value);
74
- return {
75
- code: params.get("code") ?? void 0,
76
- state: params.get("state") ?? void 0
77
- };
78
- }
79
- return { code: value };
80
- }
81
58
  function decodeJwt(token) {
82
59
  try {
83
60
  const parts = token.split(".");
@@ -302,7 +279,7 @@ async function startLocalOAuthServer(state, ports = CODEX_CALLBACK_PORTS) {
302
279
  });
303
280
  });
304
281
  }
305
- async function loginOpenAICodexDevice(options) {
282
+ async function startCodexDeviceLogin(options) {
306
283
  const response = await fetch(DEVICE_USER_CODE_URL, {
307
284
  method: "POST",
308
285
  headers: {
@@ -310,7 +287,7 @@ async function loginOpenAICodexDevice(options) {
310
287
  "User-Agent": "mastracode"
311
288
  },
312
289
  body: JSON.stringify({ client_id: CLIENT_ID, originator: "mastracode" }),
313
- signal: options.signal
290
+ signal: options?.signal
314
291
  });
315
292
  if (!response.ok) {
316
293
  throw new Error(`Failed to initiate OpenAI Codex device authorization: ${response.status}`);
@@ -321,59 +298,90 @@ async function loginOpenAICodexDevice(options) {
321
298
  throw new Error("OpenAI Codex device authorization response missing required fields");
322
299
  }
323
300
  const intervalSeconds = typeof deviceData.interval === "number" ? deviceData.interval : Number.parseInt(deviceData.interval ?? "", 10) || 5;
324
- const pollDelayMs = Math.max(intervalSeconds, 1) * 1e3;
325
- const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
326
- const startedAt = Date.now();
327
- options.onAuth({
301
+ return {
302
+ deviceAuthId: deviceData.device_auth_id,
303
+ userCode,
328
304
  url: DEVICE_AUTHORIZE_URL,
329
- instructions: `Enter code: ${userCode}`
305
+ instructions: `Enter code: ${userCode}`,
306
+ intervalMs: Math.max(intervalSeconds, 1) * 1e3,
307
+ deadlineAt: Date.now() + DEVICE_AUTH_TIMEOUT_MS
308
+ };
309
+ }
310
+ async function pollCodexDeviceLogin(pending, options) {
311
+ if (Date.now() >= pending.deadlineAt) {
312
+ return { status: "failed", error: "OpenAI Codex device authorization timed out after 15 minutes" };
313
+ }
314
+ const pollResponse = await fetch(DEVICE_TOKEN_URL, {
315
+ method: "POST",
316
+ headers: {
317
+ "Content-Type": "application/json",
318
+ "User-Agent": "mastracode"
319
+ },
320
+ body: JSON.stringify({
321
+ device_auth_id: pending.deviceAuthId,
322
+ user_code: pending.userCode
323
+ }),
324
+ signal: options?.signal
330
325
  });
331
- while (true) {
332
- if (options.signal?.aborted) {
333
- throw new Error("Login cancelled");
326
+ if (pollResponse.ok) {
327
+ const data = await pollResponse.json();
328
+ if (!data.authorization_code || !data.code_verifier) {
329
+ return { status: "failed", error: "OpenAI Codex device token response missing required fields" };
334
330
  }
335
- if (Date.now() - startedAt >= DEVICE_AUTH_TIMEOUT_MS) {
336
- throw new Error("OpenAI Codex device authorization timed out after 15 minutes");
331
+ const tokenResult = await exchangeAuthorizationCode(
332
+ data.authorization_code,
333
+ data.code_verifier,
334
+ DEVICE_REDIRECT_URI
335
+ );
336
+ if (tokenResult.type !== "success") {
337
+ return { status: "failed", error: "Token exchange failed" };
337
338
  }
338
- const pollResponse = await fetch(DEVICE_TOKEN_URL, {
339
- method: "POST",
340
- headers: {
341
- "Content-Type": "application/json",
342
- "User-Agent": "mastracode"
343
- },
344
- body: JSON.stringify({
345
- device_auth_id: deviceData.device_auth_id,
346
- user_code: userCode
347
- }),
348
- signal: options.signal
349
- });
350
- if (pollResponse.ok) {
351
- const data = await pollResponse.json();
352
- if (!data.authorization_code || !data.code_verifier) {
353
- throw new Error("OpenAI Codex device token response missing required fields");
354
- }
355
- const tokenResult = await exchangeAuthorizationCode(
356
- data.authorization_code,
357
- data.code_verifier,
358
- DEVICE_REDIRECT_URI
359
- );
360
- if (tokenResult.type !== "success") {
361
- throw new Error("Token exchange failed");
362
- }
363
- const accountId = requireAccountId(tokenResult);
364
- return {
339
+ let accountId;
340
+ try {
341
+ accountId = requireAccountId(tokenResult);
342
+ } catch (error) {
343
+ return { status: "failed", error: error instanceof Error ? error.message : String(error) };
344
+ }
345
+ return {
346
+ status: "complete",
347
+ credentials: {
365
348
  access: tokenResult.access,
366
349
  refresh: tokenResult.refresh,
367
350
  expires: tokenResult.expires,
368
351
  accountId
369
- };
352
+ }
353
+ };
354
+ }
355
+ if (pollResponse.status !== 403 && pollResponse.status !== 404) {
356
+ const text = await pollResponse.text().catch(() => "");
357
+ return {
358
+ status: "failed",
359
+ error: `OpenAI Codex device authorization failed: ${pollResponse.status}${text ? ` ${text}` : ""}`
360
+ };
361
+ }
362
+ return { status: "pending", nextPollMs: pending.intervalMs };
363
+ }
364
+ async function loginOpenAICodexDevice(options) {
365
+ const pending = await startCodexDeviceLogin({ signal: options.signal });
366
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
367
+ options.onAuth({
368
+ url: pending.url,
369
+ instructions: pending.instructions
370
+ });
371
+ await sleep(pending.intervalMs);
372
+ while (true) {
373
+ if (options.signal?.aborted) {
374
+ throw new Error("Login cancelled");
375
+ }
376
+ const result = await pollCodexDeviceLogin(pending, { signal: options.signal });
377
+ if (result.status === "complete") {
378
+ return result.credentials;
370
379
  }
371
- if (pollResponse.status !== 403 && pollResponse.status !== 404) {
372
- const text = await pollResponse.text().catch(() => "");
373
- throw new Error(`OpenAI Codex device authorization failed: ${pollResponse.status}${text ? ` ${text}` : ""}`);
380
+ if (result.status === "failed") {
381
+ throw new Error(result.error);
374
382
  }
375
383
  options.onProgress?.("Waiting for OpenAI Codex device authorization...");
376
- await sleep(pollDelayMs);
384
+ await sleep(result.nextPollMs);
377
385
  }
378
386
  }
379
387
  async function loginOpenAICodex(options) {
@@ -522,6 +530,8 @@ export {
522
530
  __testing,
523
531
  loginOpenAICodex,
524
532
  openaiCodexOAuthProvider,
525
- refreshOpenAICodexToken
533
+ pollCodexDeviceLogin,
534
+ refreshOpenAICodexToken,
535
+ startCodexDeviceLogin
526
536
  };
527
537
  //# sourceMappingURL=openai-codex.js.map