@earendil-works/pi-ai 0.75.4 → 0.76.0

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 (62) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +14 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/models.generated.d.ts +364 -687
  8. package/dist/models.generated.d.ts.map +1 -1
  9. package/dist/models.generated.js +407 -749
  10. package/dist/models.generated.js.map +1 -1
  11. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  12. package/dist/providers/amazon-bedrock.js +2 -1
  13. package/dist/providers/amazon-bedrock.js.map +1 -1
  14. package/dist/providers/anthropic.d.ts +22 -5
  15. package/dist/providers/anthropic.d.ts.map +1 -1
  16. package/dist/providers/anthropic.js +8 -22
  17. package/dist/providers/anthropic.js.map +1 -1
  18. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  19. package/dist/providers/azure-openai-responses.js +1 -1
  20. package/dist/providers/azure-openai-responses.js.map +1 -1
  21. package/dist/providers/images/openrouter.d.ts.map +1 -1
  22. package/dist/providers/images/openrouter.js +1 -1
  23. package/dist/providers/images/openrouter.js.map +1 -1
  24. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  25. package/dist/providers/openai-codex-responses.js +148 -76
  26. package/dist/providers/openai-codex-responses.js.map +1 -1
  27. package/dist/providers/openai-completions.d.ts.map +1 -1
  28. package/dist/providers/openai-completions.js +1 -1
  29. package/dist/providers/openai-completions.js.map +1 -1
  30. package/dist/providers/openai-responses.d.ts.map +1 -1
  31. package/dist/providers/openai-responses.js +1 -1
  32. package/dist/providers/openai-responses.js.map +1 -1
  33. package/dist/providers/simple-options.d.ts.map +1 -1
  34. package/dist/providers/simple-options.js +1 -0
  35. package/dist/providers/simple-options.js.map +1 -1
  36. package/dist/types.d.ts +16 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js.map +1 -1
  39. package/dist/utils/abort-signals.d.ts +6 -0
  40. package/dist/utils/abort-signals.d.ts.map +1 -0
  41. package/dist/utils/abort-signals.js +34 -0
  42. package/dist/utils/abort-signals.js.map +1 -0
  43. package/dist/utils/oauth/device-code.d.ts +19 -0
  44. package/dist/utils/oauth/device-code.d.ts.map +1 -0
  45. package/dist/utils/oauth/device-code.js +55 -0
  46. package/dist/utils/oauth/device-code.js.map +1 -0
  47. package/dist/utils/oauth/github-copilot.d.ts +3 -3
  48. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  49. package/dist/utils/oauth/github-copilot.js +45 -69
  50. package/dist/utils/oauth/github-copilot.js.map +1 -1
  51. package/dist/utils/oauth/index.d.ts +1 -0
  52. package/dist/utils/oauth/index.d.ts.map +1 -1
  53. package/dist/utils/oauth/index.js +1 -0
  54. package/dist/utils/oauth/index.js.map +1 -1
  55. package/dist/utils/oauth/types.d.ts +8 -1
  56. package/dist/utils/oauth/types.d.ts.map +1 -1
  57. package/dist/utils/oauth/types.js.map +1 -1
  58. package/dist/utils/overflow.d.ts +2 -1
  59. package/dist/utils/overflow.d.ts.map +1 -1
  60. package/dist/utils/overflow.js +5 -2
  61. package/dist/utils/overflow.js.map +1 -1
  62. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/github-copilot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAuChG,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D;AA4BD,wBAAgB,uBAAuB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CASzF;AAkJD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,YAAY,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AA8CD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvG,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkC5B;AAED,eAAO,MAAM,0BAA0B,EAAE,sBA4BxC,CAAC","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { getModels } from \"../../models.ts\";\nimport type { Api, Model } from \"../../types.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\ntype CopilotCredentials = OAuthCredentials & {\n\tenterpriseUrl?: string;\n};\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"SXYxLmI1MDdhMDhjODdlY2ZlOTg=\");\n\nconst COPILOT_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"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\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval: number;\n\texpires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n\taccess_token: string;\n\ttoken_type?: string;\n\tscope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n\terror: string;\n\terror_description?: string;\n\tinterval?: number;\n};\n\nexport function normalizeDomain(input: string): string | null {\n\tconst trimmed = input.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = trimmed.includes(\"://\") ? new URL(trimmed) : new URL(`https://${trimmed}`);\n\t\treturn url.hostname;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getUrls(domain: string): {\n\tdeviceCodeUrl: string;\n\taccessTokenUrl: string;\n\tcopilotTokenUrl: string;\n} {\n\treturn {\n\t\tdeviceCodeUrl: `https://${domain}/login/device/code`,\n\t\taccessTokenUrl: `https://${domain}/login/oauth/access_token`,\n\t\tcopilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n\t};\n}\n\n/**\n * Parse the proxy-ep from a Copilot token and convert to API base URL.\n * Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...\n * Returns API URL like https://api.individual.githubcopilot.com\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n\tconst match = token.match(/proxy-ep=([^;]+)/);\n\tif (!match) return null;\n\tconst proxyHost = match[1];\n\t// Convert proxy.xxx to api.xxx\n\tconst apiHost = proxyHost.replace(/^proxy\\./, \"api.\");\n\treturn `https://${apiHost}`;\n}\n\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n\t// If we have a token, extract the base URL from proxy-ep\n\tif (token) {\n\t\tconst urlFromToken = getBaseUrlFromToken(token);\n\t\tif (urlFromToken) return urlFromToken;\n\t}\n\t// Fallback for enterprise or if token parsing fails\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\treturn \"https://api.individual.githubcopilot.com\";\n}\n\nasync function fetchJson(url: string, init: RequestInit): Promise<unknown> {\n\tconst response = await fetch(url, init);\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`${response.status} ${response.statusText}: ${text}`);\n\t}\n\treturn response.json();\n}\n\nasync function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {\n\tconst urls = getUrls(domain);\n\tconst data = await fetchJson(urls.deviceCodeUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"read:user\",\n\t\t}),\n\t});\n\n\tif (!data || typeof data !== \"object\") {\n\t\tthrow new Error(\"Invalid device code response\");\n\t}\n\n\tconst deviceCode = (data as Record<string, unknown>).device_code;\n\tconst userCode = (data as Record<string, unknown>).user_code;\n\tconst verificationUri = (data as Record<string, unknown>).verification_uri;\n\tconst interval = (data as Record<string, unknown>).interval;\n\tconst expiresIn = (data as Record<string, unknown>).expires_in;\n\n\tif (\n\t\ttypeof deviceCode !== \"string\" ||\n\t\ttypeof userCode !== \"string\" ||\n\t\ttypeof verificationUri !== \"string\" ||\n\t\ttypeof interval !== \"number\" ||\n\t\ttypeof expiresIn !== \"number\"\n\t) {\n\t\tthrow new Error(\"Invalid device code response fields\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: verificationUri,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\n/**\n * Sleep that can be interrupted by an AbortSignal\n */\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeout = setTimeout(resolve, ms);\n\n\t\tsignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\nasync function pollForGitHubAccessToken(\n\tdomain: string,\n\tdeviceCode: string,\n\tintervalSeconds: number,\n\texpiresIn: number,\n\tsignal?: AbortSignal,\n) {\n\tconst urls = getUrls(domain);\n\tconst deadline = Date.now() + expiresIn * 1000;\n\tlet intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));\n\tlet intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;\n\tlet slowDownResponses = 0;\n\n\twhile (Date.now() < deadline) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\n\t\tconst remainingMs = deadline - Date.now();\n\t\tconst waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);\n\t\tawait abortableSleep(waitMs, signal);\n\n\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t}),\n\t\t});\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\treturn (raw as DeviceTokenSuccessResponse).access_token;\n\t\t}\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\tconst { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;\n\t\t\tif (error === \"authorization_pending\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (error === \"slow_down\") {\n\t\t\t\tslowDownResponses += 1;\n\t\t\t\tintervalMs =\n\t\t\t\t\ttypeof interval === \"number\" && interval > 0 ? interval * 1000 : Math.max(1000, intervalMs + 5000);\n\t\t\t\tintervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst descriptionSuffix = description ? `: ${description}` : \"\";\n\t\t\tthrow new Error(`Device flow failed: ${error}${descriptionSuffix}`);\n\t\t}\n\t}\n\n\tif (slowDownResponses > 0) {\n\t\tthrow new Error(\n\t\t\t\"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\t\t);\n\t}\n\n\tthrow new Error(\"Device flow timed out\");\n}\n\n/**\n * Refresh GitHub Copilot token\n */\nexport async function refreshGitHubCopilotToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredentials> {\n\tconst domain = enterpriseDomain || \"github.com\";\n\tconst urls = getUrls(domain);\n\n\tconst raw = await fetchJson(urls.copilotTokenUrl, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${refreshToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t},\n\t});\n\n\tif (!raw || typeof raw !== \"object\") {\n\t\tthrow new Error(\"Invalid Copilot token response\");\n\t}\n\n\tconst token = (raw as Record<string, unknown>).token;\n\tconst expiresAt = (raw as Record<string, unknown>).expires_at;\n\n\tif (typeof token !== \"string\" || typeof expiresAt !== \"number\") {\n\t\tthrow new Error(\"Invalid Copilot token response fields\");\n\t}\n\n\treturn {\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires: expiresAt * 1000 - 5 * 60 * 1000,\n\t\tenterpriseUrl: enterpriseDomain,\n\t};\n}\n\n/**\n * Enable a model for the user's GitHub Copilot account.\n * This is required for some models (like Claude, Grok) before they can be used.\n */\nasync function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);\n\tconst url = `${baseUrl}/models/${modelId}/policy`;\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t...COPILOT_HEADERS,\n\t\t\t\t\"openai-intent\": \"chat-policy\",\n\t\t\t\t\"x-interaction-type\": \"chat-policy\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ state: \"enabled\" }),\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Enable all known GitHub Copilot models that may require policy acceptance.\n * Called after successful login to ensure all models are available.\n */\nasync function enableAllGitHubCopilotModels(\n\ttoken: string,\n\tenterpriseDomain?: string,\n\tonProgress?: (model: string, success: boolean) => void,\n): Promise<void> {\n\tconst models = getModels(\"github-copilot\");\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tconst success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t\tonProgress?.(model.id, success);\n\t\t}),\n\t);\n}\n\n/**\n * Login with GitHub Copilot OAuth (device code flow)\n *\n * @param options.onAuth - Callback with URL and optional instructions (user code)\n * @param options.onPrompt - Callback to prompt user for input\n * @param options.onProgress - Optional progress callback\n * @param options.signal - Optional AbortSignal for cancellation\n */\nexport async function loginGitHubCopilot(options: {\n\tonAuth: (url: string, instructions?: string) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst input = await options.onPrompt({\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t\tallowEmpty: true,\n\t});\n\n\tif (options.signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) {\n\t\tthrow new Error(\"Invalid GitHub Enterprise URL/domain\");\n\t}\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\toptions.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(\n\t\tdomain,\n\t\tdevice.device_code,\n\t\tdevice.interval,\n\t\tdevice.expires_in,\n\t\toptions.signal,\n\t);\n\tconst credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);\n\n\t// Enable all models after successful login\n\toptions.onProgress?.(\"Enabling models...\");\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn credentials;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGitHubCopilot({\n\t\t\tonAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\treturn refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n\n\tmodifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[] {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\tconst domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;\n\t\tconst baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);\n\t\treturn models.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t},\n};\n"]}
1
+ {"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/github-copilot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAmCrH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D;AA4BD,wBAAgB,uBAAuB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CASzF;AAmGD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,YAAY,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AA8CD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,YAAY,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvG,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiC5B;AAED,eAAO,MAAM,0BAA0B,EAAE,sBA4BxC,CAAC","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { getModels } from \"../../models.ts\";\nimport type { Api, Model } from \"../../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthDeviceCodeInfo, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\ntype CopilotCredentials = OAuthCredentials & {\n\tenterpriseUrl?: string;\n};\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"SXYxLmI1MDdhMDhjODdlY2ZlOTg=\");\n\nconst COPILOT_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n} as const;\n\ntype DeviceCodeResponse = {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval?: number;\n\texpires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n\taccess_token: string;\n\ttoken_type?: string;\n\tscope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n\terror: string;\n\terror_description?: string;\n};\n\nexport function normalizeDomain(input: string): string | null {\n\tconst trimmed = input.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = trimmed.includes(\"://\") ? new URL(trimmed) : new URL(`https://${trimmed}`);\n\t\treturn url.hostname;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getUrls(domain: string): {\n\tdeviceCodeUrl: string;\n\taccessTokenUrl: string;\n\tcopilotTokenUrl: string;\n} {\n\treturn {\n\t\tdeviceCodeUrl: `https://${domain}/login/device/code`,\n\t\taccessTokenUrl: `https://${domain}/login/oauth/access_token`,\n\t\tcopilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n\t};\n}\n\n/**\n * Parse the proxy-ep from a Copilot token and convert to API base URL.\n * Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...\n * Returns API URL like https://api.individual.githubcopilot.com\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n\tconst match = token.match(/proxy-ep=([^;]+)/);\n\tif (!match) return null;\n\tconst proxyHost = match[1];\n\t// Convert proxy.xxx to api.xxx\n\tconst apiHost = proxyHost.replace(/^proxy\\./, \"api.\");\n\treturn `https://${apiHost}`;\n}\n\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n\t// If we have a token, extract the base URL from proxy-ep\n\tif (token) {\n\t\tconst urlFromToken = getBaseUrlFromToken(token);\n\t\tif (urlFromToken) return urlFromToken;\n\t}\n\t// Fallback for enterprise or if token parsing fails\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\treturn \"https://api.individual.githubcopilot.com\";\n}\n\nasync function fetchJson(url: string, init: RequestInit): Promise<unknown> {\n\tconst response = await fetch(url, init);\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`${response.status} ${response.statusText}: ${text}`);\n\t}\n\treturn response.json();\n}\n\nasync function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {\n\tconst urls = getUrls(domain);\n\tconst data = await fetchJson(urls.deviceCodeUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"read:user\",\n\t\t}),\n\t});\n\n\tif (!data || typeof data !== \"object\") {\n\t\tthrow new Error(\"Invalid device code response\");\n\t}\n\n\tconst deviceCode = (data as Record<string, unknown>).device_code;\n\tconst userCode = (data as Record<string, unknown>).user_code;\n\tconst verificationUri = (data as Record<string, unknown>).verification_uri;\n\tconst interval = (data as Record<string, unknown>).interval;\n\tconst expiresIn = (data as Record<string, unknown>).expires_in;\n\n\tif (\n\t\ttypeof deviceCode !== \"string\" ||\n\t\ttypeof userCode !== \"string\" ||\n\t\ttypeof verificationUri !== \"string\" ||\n\t\t(interval !== undefined && typeof interval !== \"number\") ||\n\t\ttypeof expiresIn !== \"number\"\n\t) {\n\t\tthrow new Error(\"Invalid device code response fields\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: verificationUri,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\nasync function pollForGitHubAccessToken(domain: string, device: DeviceCodeResponse, signal?: AbortSignal) {\n\tconst urls = getUrls(domain);\n\treturn pollOAuthDeviceCodeFlow({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t\t},\n\t\t\t\tbody: new URLSearchParams({\n\t\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\t\tdevice_code: device.device_code,\n\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\t\treturn { status: \"complete\", accessToken: (raw as DeviceTokenSuccessResponse).access_token };\n\t\t\t}\n\n\t\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\t\tconst { error, error_description: description } = raw as DeviceTokenErrorResponse;\n\t\t\t\tif (error === \"authorization_pending\") {\n\t\t\t\t\treturn { status: \"pending\" };\n\t\t\t\t}\n\n\t\t\t\tif (error === \"slow_down\") {\n\t\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t\t}\n\n\t\t\t\tconst descriptionSuffix = description ? `: ${description}` : \"\";\n\t\t\t\treturn { status: \"failed\", message: `Device flow failed: ${error}${descriptionSuffix}` };\n\t\t\t}\n\n\t\t\treturn { status: \"failed\", message: \"Invalid device token response\" };\n\t\t},\n\t});\n}\n\n/**\n * Refresh GitHub Copilot token\n */\nexport async function refreshGitHubCopilotToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredentials> {\n\tconst domain = enterpriseDomain || \"github.com\";\n\tconst urls = getUrls(domain);\n\n\tconst raw = await fetchJson(urls.copilotTokenUrl, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${refreshToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t},\n\t});\n\n\tif (!raw || typeof raw !== \"object\") {\n\t\tthrow new Error(\"Invalid Copilot token response\");\n\t}\n\n\tconst token = (raw as Record<string, unknown>).token;\n\tconst expiresAt = (raw as Record<string, unknown>).expires_at;\n\n\tif (typeof token !== \"string\" || typeof expiresAt !== \"number\") {\n\t\tthrow new Error(\"Invalid Copilot token response fields\");\n\t}\n\n\treturn {\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires: expiresAt * 1000 - 5 * 60 * 1000,\n\t\tenterpriseUrl: enterpriseDomain,\n\t};\n}\n\n/**\n * Enable a model for the user's GitHub Copilot account.\n * This is required for some models (like Claude, Grok) before they can be used.\n */\nasync function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);\n\tconst url = `${baseUrl}/models/${modelId}/policy`;\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t...COPILOT_HEADERS,\n\t\t\t\t\"openai-intent\": \"chat-policy\",\n\t\t\t\t\"x-interaction-type\": \"chat-policy\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ state: \"enabled\" }),\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Enable all known GitHub Copilot models that may require policy acceptance.\n * Called after successful login to ensure all models are available.\n */\nasync function enableAllGitHubCopilotModels(\n\ttoken: string,\n\tenterpriseDomain?: string,\n\tonProgress?: (model: string, success: boolean) => void,\n): Promise<void> {\n\tconst models = getModels(\"github-copilot\");\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tconst success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t\tonProgress?.(model.id, success);\n\t\t}),\n\t);\n}\n\n/**\n * Login with GitHub Copilot OAuth (device code flow)\n *\n * @param options.onDeviceCode - Callback with URL and user code\n * @param options.onPrompt - Callback to prompt user for input\n * @param options.onProgress - Optional progress callback\n * @param options.signal - Optional AbortSignal for cancellation\n */\nexport async function loginGitHubCopilot(options: {\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst input = await options.onPrompt({\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t\tallowEmpty: true,\n\t});\n\n\tif (options.signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) {\n\t\tthrow new Error(\"Invalid GitHub Enterprise URL/domain\");\n\t}\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\toptions.onDeviceCode({\n\t\tuserCode: device.user_code,\n\t\tverificationUri: device.verification_uri,\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t});\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);\n\tconst credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);\n\n\t// Enable all models after successful login\n\toptions.onProgress?.(\"Enabling models...\");\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn credentials;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGitHubCopilot({\n\t\t\tonDeviceCode: callbacks.onDeviceCode,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\treturn refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n\n\tmodifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[] {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\tconst domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;\n\t\tconst baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);\n\t\treturn models.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t},\n};\n"]}
@@ -2,6 +2,7 @@
2
2
  * GitHub Copilot OAuth flow
3
3
  */
4
4
  import { getModels } from "../../models.js";
5
+ import { pollOAuthDeviceCodeFlow } from "./device-code.js";
5
6
  const decode = (s) => atob(s);
6
7
  const CLIENT_ID = decode("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
7
8
  const COPILOT_HEADERS = {
@@ -10,8 +11,6 @@ const COPILOT_HEADERS = {
10
11
  "Editor-Plugin-Version": "copilot-chat/0.35.0",
11
12
  "Copilot-Integration-Id": "vscode-chat",
12
13
  };
13
- const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
14
- const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
15
14
  export function normalizeDomain(input) {
16
15
  const trimmed = input.trim();
17
16
  if (!trimmed)
@@ -90,7 +89,7 @@ async function startDeviceFlow(domain) {
90
89
  if (typeof deviceCode !== "string" ||
91
90
  typeof userCode !== "string" ||
92
91
  typeof verificationUri !== "string" ||
93
- typeof interval !== "number" ||
92
+ (interval !== undefined && typeof interval !== "number") ||
94
93
  typeof expiresIn !== "number") {
95
94
  throw new Error("Invalid device code response fields");
96
95
  }
@@ -102,71 +101,43 @@ async function startDeviceFlow(domain) {
102
101
  expires_in: expiresIn,
103
102
  };
104
103
  }
105
- /**
106
- * Sleep that can be interrupted by an AbortSignal
107
- */
108
- function abortableSleep(ms, signal) {
109
- return new Promise((resolve, reject) => {
110
- if (signal?.aborted) {
111
- reject(new Error("Login cancelled"));
112
- return;
113
- }
114
- const timeout = setTimeout(resolve, ms);
115
- signal?.addEventListener("abort", () => {
116
- clearTimeout(timeout);
117
- reject(new Error("Login cancelled"));
118
- }, { once: true });
119
- });
120
- }
121
- async function pollForGitHubAccessToken(domain, deviceCode, intervalSeconds, expiresIn, signal) {
104
+ async function pollForGitHubAccessToken(domain, device, signal) {
122
105
  const urls = getUrls(domain);
123
- const deadline = Date.now() + expiresIn * 1000;
124
- let intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));
125
- let intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;
126
- let slowDownResponses = 0;
127
- while (Date.now() < deadline) {
128
- if (signal?.aborted) {
129
- throw new Error("Login cancelled");
130
- }
131
- const remainingMs = deadline - Date.now();
132
- const waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);
133
- await abortableSleep(waitMs, signal);
134
- const raw = await fetchJson(urls.accessTokenUrl, {
135
- method: "POST",
136
- headers: {
137
- Accept: "application/json",
138
- "Content-Type": "application/x-www-form-urlencoded",
139
- "User-Agent": "GitHubCopilotChat/0.35.0",
140
- },
141
- body: new URLSearchParams({
142
- client_id: CLIENT_ID,
143
- device_code: deviceCode,
144
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
145
- }),
146
- });
147
- if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
148
- return raw.access_token;
149
- }
150
- if (raw && typeof raw === "object" && typeof raw.error === "string") {
151
- const { error, error_description: description, interval } = raw;
152
- if (error === "authorization_pending") {
153
- continue;
106
+ return pollOAuthDeviceCodeFlow({
107
+ intervalSeconds: device.interval,
108
+ expiresInSeconds: device.expires_in,
109
+ signal,
110
+ poll: async () => {
111
+ const raw = await fetchJson(urls.accessTokenUrl, {
112
+ method: "POST",
113
+ headers: {
114
+ Accept: "application/json",
115
+ "Content-Type": "application/x-www-form-urlencoded",
116
+ "User-Agent": "GitHubCopilotChat/0.35.0",
117
+ },
118
+ body: new URLSearchParams({
119
+ client_id: CLIENT_ID,
120
+ device_code: device.device_code,
121
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
122
+ }),
123
+ });
124
+ if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
125
+ return { status: "complete", accessToken: raw.access_token };
154
126
  }
155
- if (error === "slow_down") {
156
- slowDownResponses += 1;
157
- intervalMs =
158
- typeof interval === "number" && interval > 0 ? interval * 1000 : Math.max(1000, intervalMs + 5000);
159
- intervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;
160
- continue;
127
+ if (raw && typeof raw === "object" && typeof raw.error === "string") {
128
+ const { error, error_description: description } = raw;
129
+ if (error === "authorization_pending") {
130
+ return { status: "pending" };
131
+ }
132
+ if (error === "slow_down") {
133
+ return { status: "slow_down" };
134
+ }
135
+ const descriptionSuffix = description ? `: ${description}` : "";
136
+ return { status: "failed", message: `Device flow failed: ${error}${descriptionSuffix}` };
161
137
  }
162
- const descriptionSuffix = description ? `: ${description}` : "";
163
- throw new Error(`Device flow failed: ${error}${descriptionSuffix}`);
164
- }
165
- }
166
- if (slowDownResponses > 0) {
167
- throw new 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.");
168
- }
169
- throw new Error("Device flow timed out");
138
+ return { status: "failed", message: "Invalid device token response" };
139
+ },
140
+ });
170
141
  }
171
142
  /**
172
143
  * Refresh GitHub Copilot token
@@ -235,7 +206,7 @@ async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress)
235
206
  /**
236
207
  * Login with GitHub Copilot OAuth (device code flow)
237
208
  *
238
- * @param options.onAuth - Callback with URL and optional instructions (user code)
209
+ * @param options.onDeviceCode - Callback with URL and user code
239
210
  * @param options.onPrompt - Callback to prompt user for input
240
211
  * @param options.onProgress - Optional progress callback
241
212
  * @param options.signal - Optional AbortSignal for cancellation
@@ -256,8 +227,13 @@ export async function loginGitHubCopilot(options) {
256
227
  }
257
228
  const domain = enterpriseDomain || "github.com";
258
229
  const device = await startDeviceFlow(domain);
259
- options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
260
- const githubAccessToken = await pollForGitHubAccessToken(domain, device.device_code, device.interval, device.expires_in, options.signal);
230
+ options.onDeviceCode({
231
+ userCode: device.user_code,
232
+ verificationUri: device.verification_uri,
233
+ intervalSeconds: device.interval,
234
+ expiresInSeconds: device.expires_in,
235
+ });
236
+ const githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);
261
237
  const credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);
262
238
  // Enable all models after successful login
263
239
  options.onProgress?.("Enabling models...");
@@ -269,7 +245,7 @@ export const githubCopilotOAuthProvider = {
269
245
  name: "GitHub Copilot",
270
246
  async login(callbacks) {
271
247
  return loginGitHubCopilot({
272
- onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),
248
+ onDeviceCode: callbacks.onDeviceCode,
273
249
  onPrompt: callbacks.onPrompt,
274
250
  onProgress: callbacks.onProgress,
275
251
  signal: callbacks.signal,
@@ -1 +1 @@
1
- {"version":3,"file":"github-copilot.js","sourceRoot":"","sources":["../../../src/utils/oauth/github-copilot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { getModels } from \"../../models.ts\";\nimport type { Api, Model } from \"../../types.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\ntype CopilotCredentials = OAuthCredentials & {\n\tenterpriseUrl?: string;\n};\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"SXYxLmI1MDdhMDhjODdlY2ZlOTg=\");\n\nconst COPILOT_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"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\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval: number;\n\texpires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n\taccess_token: string;\n\ttoken_type?: string;\n\tscope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n\terror: string;\n\terror_description?: string;\n\tinterval?: number;\n};\n\nexport function normalizeDomain(input: string): string | null {\n\tconst trimmed = input.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = trimmed.includes(\"://\") ? new URL(trimmed) : new URL(`https://${trimmed}`);\n\t\treturn url.hostname;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getUrls(domain: string): {\n\tdeviceCodeUrl: string;\n\taccessTokenUrl: string;\n\tcopilotTokenUrl: string;\n} {\n\treturn {\n\t\tdeviceCodeUrl: `https://${domain}/login/device/code`,\n\t\taccessTokenUrl: `https://${domain}/login/oauth/access_token`,\n\t\tcopilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n\t};\n}\n\n/**\n * Parse the proxy-ep from a Copilot token and convert to API base URL.\n * Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...\n * Returns API URL like https://api.individual.githubcopilot.com\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n\tconst match = token.match(/proxy-ep=([^;]+)/);\n\tif (!match) return null;\n\tconst proxyHost = match[1];\n\t// Convert proxy.xxx to api.xxx\n\tconst apiHost = proxyHost.replace(/^proxy\\./, \"api.\");\n\treturn `https://${apiHost}`;\n}\n\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n\t// If we have a token, extract the base URL from proxy-ep\n\tif (token) {\n\t\tconst urlFromToken = getBaseUrlFromToken(token);\n\t\tif (urlFromToken) return urlFromToken;\n\t}\n\t// Fallback for enterprise or if token parsing fails\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\treturn \"https://api.individual.githubcopilot.com\";\n}\n\nasync function fetchJson(url: string, init: RequestInit): Promise<unknown> {\n\tconst response = await fetch(url, init);\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`${response.status} ${response.statusText}: ${text}`);\n\t}\n\treturn response.json();\n}\n\nasync function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {\n\tconst urls = getUrls(domain);\n\tconst data = await fetchJson(urls.deviceCodeUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"read:user\",\n\t\t}),\n\t});\n\n\tif (!data || typeof data !== \"object\") {\n\t\tthrow new Error(\"Invalid device code response\");\n\t}\n\n\tconst deviceCode = (data as Record<string, unknown>).device_code;\n\tconst userCode = (data as Record<string, unknown>).user_code;\n\tconst verificationUri = (data as Record<string, unknown>).verification_uri;\n\tconst interval = (data as Record<string, unknown>).interval;\n\tconst expiresIn = (data as Record<string, unknown>).expires_in;\n\n\tif (\n\t\ttypeof deviceCode !== \"string\" ||\n\t\ttypeof userCode !== \"string\" ||\n\t\ttypeof verificationUri !== \"string\" ||\n\t\ttypeof interval !== \"number\" ||\n\t\ttypeof expiresIn !== \"number\"\n\t) {\n\t\tthrow new Error(\"Invalid device code response fields\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: verificationUri,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\n/**\n * Sleep that can be interrupted by an AbortSignal\n */\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeout = setTimeout(resolve, ms);\n\n\t\tsignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\nasync function pollForGitHubAccessToken(\n\tdomain: string,\n\tdeviceCode: string,\n\tintervalSeconds: number,\n\texpiresIn: number,\n\tsignal?: AbortSignal,\n) {\n\tconst urls = getUrls(domain);\n\tconst deadline = Date.now() + expiresIn * 1000;\n\tlet intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));\n\tlet intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;\n\tlet slowDownResponses = 0;\n\n\twhile (Date.now() < deadline) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\n\t\tconst remainingMs = deadline - Date.now();\n\t\tconst waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);\n\t\tawait abortableSleep(waitMs, signal);\n\n\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t}),\n\t\t});\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\treturn (raw as DeviceTokenSuccessResponse).access_token;\n\t\t}\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\tconst { error, error_description: description, interval } = raw as DeviceTokenErrorResponse;\n\t\t\tif (error === \"authorization_pending\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (error === \"slow_down\") {\n\t\t\t\tslowDownResponses += 1;\n\t\t\t\tintervalMs =\n\t\t\t\t\ttypeof interval === \"number\" && interval > 0 ? interval * 1000 : Math.max(1000, intervalMs + 5000);\n\t\t\t\tintervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst descriptionSuffix = description ? `: ${description}` : \"\";\n\t\t\tthrow new Error(`Device flow failed: ${error}${descriptionSuffix}`);\n\t\t}\n\t}\n\n\tif (slowDownResponses > 0) {\n\t\tthrow new Error(\n\t\t\t\"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\t\t);\n\t}\n\n\tthrow new Error(\"Device flow timed out\");\n}\n\n/**\n * Refresh GitHub Copilot token\n */\nexport async function refreshGitHubCopilotToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredentials> {\n\tconst domain = enterpriseDomain || \"github.com\";\n\tconst urls = getUrls(domain);\n\n\tconst raw = await fetchJson(urls.copilotTokenUrl, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${refreshToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t},\n\t});\n\n\tif (!raw || typeof raw !== \"object\") {\n\t\tthrow new Error(\"Invalid Copilot token response\");\n\t}\n\n\tconst token = (raw as Record<string, unknown>).token;\n\tconst expiresAt = (raw as Record<string, unknown>).expires_at;\n\n\tif (typeof token !== \"string\" || typeof expiresAt !== \"number\") {\n\t\tthrow new Error(\"Invalid Copilot token response fields\");\n\t}\n\n\treturn {\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires: expiresAt * 1000 - 5 * 60 * 1000,\n\t\tenterpriseUrl: enterpriseDomain,\n\t};\n}\n\n/**\n * Enable a model for the user's GitHub Copilot account.\n * This is required for some models (like Claude, Grok) before they can be used.\n */\nasync function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);\n\tconst url = `${baseUrl}/models/${modelId}/policy`;\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t...COPILOT_HEADERS,\n\t\t\t\t\"openai-intent\": \"chat-policy\",\n\t\t\t\t\"x-interaction-type\": \"chat-policy\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ state: \"enabled\" }),\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Enable all known GitHub Copilot models that may require policy acceptance.\n * Called after successful login to ensure all models are available.\n */\nasync function enableAllGitHubCopilotModels(\n\ttoken: string,\n\tenterpriseDomain?: string,\n\tonProgress?: (model: string, success: boolean) => void,\n): Promise<void> {\n\tconst models = getModels(\"github-copilot\");\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tconst success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t\tonProgress?.(model.id, success);\n\t\t}),\n\t);\n}\n\n/**\n * Login with GitHub Copilot OAuth (device code flow)\n *\n * @param options.onAuth - Callback with URL and optional instructions (user code)\n * @param options.onPrompt - Callback to prompt user for input\n * @param options.onProgress - Optional progress callback\n * @param options.signal - Optional AbortSignal for cancellation\n */\nexport async function loginGitHubCopilot(options: {\n\tonAuth: (url: string, instructions?: string) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst input = await options.onPrompt({\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t\tallowEmpty: true,\n\t});\n\n\tif (options.signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) {\n\t\tthrow new Error(\"Invalid GitHub Enterprise URL/domain\");\n\t}\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\toptions.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(\n\t\tdomain,\n\t\tdevice.device_code,\n\t\tdevice.interval,\n\t\tdevice.expires_in,\n\t\toptions.signal,\n\t);\n\tconst credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);\n\n\t// Enable all models after successful login\n\toptions.onProgress?.(\"Enabling models...\");\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn credentials;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGitHubCopilot({\n\t\t\tonAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\treturn refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n\n\tmodifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[] {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\tconst domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;\n\t\tconst baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);\n\t\treturn models.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t},\n};\n"]}
1
+ {"version":3,"file":"github-copilot.js","sourceRoot":"","sources":["../../../src/utils/oauth/github-copilot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { getModels } from \"../../models.ts\";\nimport type { Api, Model } from \"../../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthDeviceCodeInfo, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\ntype CopilotCredentials = OAuthCredentials & {\n\tenterpriseUrl?: string;\n};\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"SXYxLmI1MDdhMDhjODdlY2ZlOTg=\");\n\nconst COPILOT_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n} as const;\n\ntype DeviceCodeResponse = {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval?: number;\n\texpires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n\taccess_token: string;\n\ttoken_type?: string;\n\tscope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n\terror: string;\n\terror_description?: string;\n};\n\nexport function normalizeDomain(input: string): string | null {\n\tconst trimmed = input.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = trimmed.includes(\"://\") ? new URL(trimmed) : new URL(`https://${trimmed}`);\n\t\treturn url.hostname;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getUrls(domain: string): {\n\tdeviceCodeUrl: string;\n\taccessTokenUrl: string;\n\tcopilotTokenUrl: string;\n} {\n\treturn {\n\t\tdeviceCodeUrl: `https://${domain}/login/device/code`,\n\t\taccessTokenUrl: `https://${domain}/login/oauth/access_token`,\n\t\tcopilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n\t};\n}\n\n/**\n * Parse the proxy-ep from a Copilot token and convert to API base URL.\n * Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...\n * Returns API URL like https://api.individual.githubcopilot.com\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n\tconst match = token.match(/proxy-ep=([^;]+)/);\n\tif (!match) return null;\n\tconst proxyHost = match[1];\n\t// Convert proxy.xxx to api.xxx\n\tconst apiHost = proxyHost.replace(/^proxy\\./, \"api.\");\n\treturn `https://${apiHost}`;\n}\n\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n\t// If we have a token, extract the base URL from proxy-ep\n\tif (token) {\n\t\tconst urlFromToken = getBaseUrlFromToken(token);\n\t\tif (urlFromToken) return urlFromToken;\n\t}\n\t// Fallback for enterprise or if token parsing fails\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\treturn \"https://api.individual.githubcopilot.com\";\n}\n\nasync function fetchJson(url: string, init: RequestInit): Promise<unknown> {\n\tconst response = await fetch(url, init);\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`${response.status} ${response.statusText}: ${text}`);\n\t}\n\treturn response.json();\n}\n\nasync function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {\n\tconst urls = getUrls(domain);\n\tconst data = await fetchJson(urls.deviceCodeUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"read:user\",\n\t\t}),\n\t});\n\n\tif (!data || typeof data !== \"object\") {\n\t\tthrow new Error(\"Invalid device code response\");\n\t}\n\n\tconst deviceCode = (data as Record<string, unknown>).device_code;\n\tconst userCode = (data as Record<string, unknown>).user_code;\n\tconst verificationUri = (data as Record<string, unknown>).verification_uri;\n\tconst interval = (data as Record<string, unknown>).interval;\n\tconst expiresIn = (data as Record<string, unknown>).expires_in;\n\n\tif (\n\t\ttypeof deviceCode !== \"string\" ||\n\t\ttypeof userCode !== \"string\" ||\n\t\ttypeof verificationUri !== \"string\" ||\n\t\t(interval !== undefined && typeof interval !== \"number\") ||\n\t\ttypeof expiresIn !== \"number\"\n\t) {\n\t\tthrow new Error(\"Invalid device code response fields\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: verificationUri,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\nasync function pollForGitHubAccessToken(domain: string, device: DeviceCodeResponse, signal?: AbortSignal) {\n\tconst urls = getUrls(domain);\n\treturn pollOAuthDeviceCodeFlow({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t\t},\n\t\t\t\tbody: new URLSearchParams({\n\t\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\t\tdevice_code: device.device_code,\n\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\t\treturn { status: \"complete\", accessToken: (raw as DeviceTokenSuccessResponse).access_token };\n\t\t\t}\n\n\t\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\t\tconst { error, error_description: description } = raw as DeviceTokenErrorResponse;\n\t\t\t\tif (error === \"authorization_pending\") {\n\t\t\t\t\treturn { status: \"pending\" };\n\t\t\t\t}\n\n\t\t\t\tif (error === \"slow_down\") {\n\t\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t\t}\n\n\t\t\t\tconst descriptionSuffix = description ? `: ${description}` : \"\";\n\t\t\t\treturn { status: \"failed\", message: `Device flow failed: ${error}${descriptionSuffix}` };\n\t\t\t}\n\n\t\t\treturn { status: \"failed\", message: \"Invalid device token response\" };\n\t\t},\n\t});\n}\n\n/**\n * Refresh GitHub Copilot token\n */\nexport async function refreshGitHubCopilotToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredentials> {\n\tconst domain = enterpriseDomain || \"github.com\";\n\tconst urls = getUrls(domain);\n\n\tconst raw = await fetchJson(urls.copilotTokenUrl, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${refreshToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t},\n\t});\n\n\tif (!raw || typeof raw !== \"object\") {\n\t\tthrow new Error(\"Invalid Copilot token response\");\n\t}\n\n\tconst token = (raw as Record<string, unknown>).token;\n\tconst expiresAt = (raw as Record<string, unknown>).expires_at;\n\n\tif (typeof token !== \"string\" || typeof expiresAt !== \"number\") {\n\t\tthrow new Error(\"Invalid Copilot token response fields\");\n\t}\n\n\treturn {\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires: expiresAt * 1000 - 5 * 60 * 1000,\n\t\tenterpriseUrl: enterpriseDomain,\n\t};\n}\n\n/**\n * Enable a model for the user's GitHub Copilot account.\n * This is required for some models (like Claude, Grok) before they can be used.\n */\nasync function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);\n\tconst url = `${baseUrl}/models/${modelId}/policy`;\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t...COPILOT_HEADERS,\n\t\t\t\t\"openai-intent\": \"chat-policy\",\n\t\t\t\t\"x-interaction-type\": \"chat-policy\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ state: \"enabled\" }),\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Enable all known GitHub Copilot models that may require policy acceptance.\n * Called after successful login to ensure all models are available.\n */\nasync function enableAllGitHubCopilotModels(\n\ttoken: string,\n\tenterpriseDomain?: string,\n\tonProgress?: (model: string, success: boolean) => void,\n): Promise<void> {\n\tconst models = getModels(\"github-copilot\");\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tconst success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t\tonProgress?.(model.id, success);\n\t\t}),\n\t);\n}\n\n/**\n * Login with GitHub Copilot OAuth (device code flow)\n *\n * @param options.onDeviceCode - Callback with URL and user code\n * @param options.onPrompt - Callback to prompt user for input\n * @param options.onProgress - Optional progress callback\n * @param options.signal - Optional AbortSignal for cancellation\n */\nexport async function loginGitHubCopilot(options: {\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst input = await options.onPrompt({\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t\tallowEmpty: true,\n\t});\n\n\tif (options.signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) {\n\t\tthrow new Error(\"Invalid GitHub Enterprise URL/domain\");\n\t}\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\toptions.onDeviceCode({\n\t\tuserCode: device.user_code,\n\t\tverificationUri: device.verification_uri,\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t});\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);\n\tconst credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);\n\n\t// Enable all models after successful login\n\toptions.onProgress?.(\"Enabling models...\");\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn credentials;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGitHubCopilot({\n\t\t\tonDeviceCode: callbacks.onDeviceCode,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\treturn refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n\n\tmodifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[] {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\tconst domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;\n\t\tconst baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);\n\t\treturn models.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t},\n};\n"]}
@@ -7,6 +7,7 @@
7
7
  * - GitHub Copilot
8
8
  */
9
9
  export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.ts";
10
+ export * from "./device-code.ts";
10
11
  export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.ts";
11
12
  export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.ts";
12
13
  export * from "./types.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE/F,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAS3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAY/G;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,iBAAiB,EAAE,CAM9D;AAMD;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsBtE","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.ts\";\n\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAS3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAY/G;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,iBAAiB,EAAE,CAM9D;AAMD;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsBtE","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\nexport * from \"./device-code.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.ts\";\n\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
@@ -8,6 +8,7 @@
8
8
  */
9
9
  // Anthropic
10
10
  export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
11
+ export * from "./device-code.js";
11
12
  // GitHub Copilot
12
13
  export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
13
14
  // OpenAI Codex (ChatGPT OAuth)
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.ts\";\n\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\nexport * from \"./device-code.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.ts\";\n\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
@@ -17,6 +17,12 @@ export type OAuthAuthInfo = {
17
17
  url: string;
18
18
  instructions?: string;
19
19
  };
20
+ export type OAuthDeviceCodeInfo = {
21
+ userCode: string;
22
+ verificationUri: string;
23
+ intervalSeconds?: number;
24
+ expiresInSeconds?: number;
25
+ };
20
26
  export type OAuthSelectOption = {
21
27
  id: string;
22
28
  label: string;
@@ -27,11 +33,12 @@ export type OAuthSelectPrompt = {
27
33
  };
28
34
  export interface OAuthLoginCallbacks {
29
35
  onAuth: (info: OAuthAuthInfo) => void;
36
+ onDeviceCode: (info: OAuthDeviceCodeInfo) => void;
30
37
  onPrompt: (prompt: OAuthPrompt) => Promise<string>;
31
38
  onProgress?: (message: string) => void;
32
39
  onManualCodeInput?: () => Promise<string>;
33
40
  /** Show an interactive selector and return the selected option id, or undefined on cancel. */
34
- onSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;
41
+ onSelect: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;
35
42
  signal?: AbortSignal;
36
43
  }
37
44
  export interface OAuthProviderInterface {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEjE,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,yEAAyE;IACzE,YAAY,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,SAAS,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;IAEjD,uEAAuE;IACvE,YAAY,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;CACjF;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB","sourcesContent":["import type { Api, Model } from \"../../types.ts\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n};\n\nexport type OAuthProviderId = string;\n\n/** @deprecated Use OAuthProviderId instead */\nexport type OAuthProvider = OAuthProviderId;\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport type OAuthSelectOption = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type OAuthSelectPrompt = {\n\tmessage: string;\n\toptions: OAuthSelectOption[];\n};\n\nexport interface OAuthLoginCallbacks {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\t/** Show an interactive selector and return the selected option id, or undefined on cancel. */\n\tonSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;\n\tsignal?: AbortSignal;\n}\n\nexport interface OAuthProviderInterface {\n\treadonly id: OAuthProviderId;\n\treadonly name: string;\n\n\t/** Run the login flow, return credentials to persist */\n\tlogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n\n\t/** Whether login uses a local callback server and supports manual code input. */\n\tusesCallbackServer?: boolean;\n\n\t/** Refresh expired credentials, return updated credentials to persist */\n\trefreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;\n\n\t/** Convert credentials to API key string for the provider */\n\tgetApiKey(credentials: OAuthCredentials): string;\n\n\t/** Optional: modify models for this provider (e.g., update baseUrl) */\n\tmodifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];\n}\n\n/** @deprecated Use OAuthProviderInterface instead */\nexport interface OAuthProviderInfo {\n\tid: OAuthProviderId;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,8FAA8F;IAC9F,QAAQ,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrE,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEjE,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,yEAAyE;IACzE,YAAY,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,SAAS,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;IAEjD,uEAAuE;IACvE,YAAY,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;CACjF;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB","sourcesContent":["import type { Api, Model } from \"../../types.ts\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n};\n\nexport type OAuthProviderId = string;\n\n/** @deprecated Use OAuthProviderId instead */\nexport type OAuthProvider = OAuthProviderId;\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport type OAuthDeviceCodeInfo = {\n\tuserCode: string;\n\tverificationUri: string;\n\tintervalSeconds?: number;\n\texpiresInSeconds?: number;\n};\n\nexport type OAuthSelectOption = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type OAuthSelectPrompt = {\n\tmessage: string;\n\toptions: OAuthSelectOption[];\n};\n\nexport interface OAuthLoginCallbacks {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\t/** Show an interactive selector and return the selected option id, or undefined on cancel. */\n\tonSelect: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;\n\tsignal?: AbortSignal;\n}\n\nexport interface OAuthProviderInterface {\n\treadonly id: OAuthProviderId;\n\treadonly name: string;\n\n\t/** Run the login flow, return credentials to persist */\n\tlogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n\n\t/** Whether login uses a local callback server and supports manual code input. */\n\tusesCallbackServer?: boolean;\n\n\t/** Refresh expired credentials, return updated credentials to persist */\n\trefreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;\n\n\t/** Convert credentials to API key string for the provider */\n\tgetApiKey(credentials: OAuthCredentials): string;\n\n\t/** Optional: modify models for this provider (e.g., update baseUrl) */\n\tmodifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];\n}\n\n/** @deprecated Use OAuthProviderInterface instead */\nexport interface OAuthProviderInfo {\n\tid: OAuthProviderId;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Api, Model } from \"../../types.ts\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n};\n\nexport type OAuthProviderId = string;\n\n/** @deprecated Use OAuthProviderId instead */\nexport type OAuthProvider = OAuthProviderId;\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport type OAuthSelectOption = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type OAuthSelectPrompt = {\n\tmessage: string;\n\toptions: OAuthSelectOption[];\n};\n\nexport interface OAuthLoginCallbacks {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\t/** Show an interactive selector and return the selected option id, or undefined on cancel. */\n\tonSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;\n\tsignal?: AbortSignal;\n}\n\nexport interface OAuthProviderInterface {\n\treadonly id: OAuthProviderId;\n\treadonly name: string;\n\n\t/** Run the login flow, return credentials to persist */\n\tlogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n\n\t/** Whether login uses a local callback server and supports manual code input. */\n\tusesCallbackServer?: boolean;\n\n\t/** Refresh expired credentials, return updated credentials to persist */\n\trefreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;\n\n\t/** Convert credentials to API key string for the provider */\n\tgetApiKey(credentials: OAuthCredentials): string;\n\n\t/** Optional: modify models for this provider (e.g., update baseUrl) */\n\tmodifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];\n}\n\n/** @deprecated Use OAuthProviderInterface instead */\nexport interface OAuthProviderInfo {\n\tid: OAuthProviderId;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Api, Model } from \"../../types.ts\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n};\n\nexport type OAuthProviderId = string;\n\n/** @deprecated Use OAuthProviderId instead */\nexport type OAuthProvider = OAuthProviderId;\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport type OAuthDeviceCodeInfo = {\n\tuserCode: string;\n\tverificationUri: string;\n\tintervalSeconds?: number;\n\texpiresInSeconds?: number;\n};\n\nexport type OAuthSelectOption = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type OAuthSelectPrompt = {\n\tmessage: string;\n\toptions: OAuthSelectOption[];\n};\n\nexport interface OAuthLoginCallbacks {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\t/** Show an interactive selector and return the selected option id, or undefined on cancel. */\n\tonSelect: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;\n\tsignal?: AbortSignal;\n}\n\nexport interface OAuthProviderInterface {\n\treadonly id: OAuthProviderId;\n\treadonly name: string;\n\n\t/** Run the login flow, return credentials to persist */\n\tlogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n\n\t/** Whether login uses a local callback server and supports manual code input. */\n\tusesCallbackServer?: boolean;\n\n\t/** Refresh expired credentials, return updated credentials to persist */\n\trefreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;\n\n\t/** Convert credentials to API key string for the provider */\n\tgetApiKey(credentials: OAuthCredentials): string;\n\n\t/** Optional: modify models for this provider (e.g., update baseUrl) */\n\tmodifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];\n}\n\n/** @deprecated Use OAuthProviderInterface instead */\nexport interface OAuthProviderInfo {\n\tid: OAuthProviderId;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
@@ -18,7 +18,8 @@ import type { AssistantMessage } from "../types.ts";
18
18
  * - Groq: "reduce the length of the messages"
19
19
  * - Cerebras: 400/413 status code (no body)
20
20
  * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
21
- * - OpenRouter (all backends): "maximum context length is X tokens"
21
+ * - OpenRouter (most backends): "maximum context length is X tokens"
22
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
22
23
  * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
23
24
  * - llama.cpp: "exceeds the available context size"
24
25
  * - LM Studio: "greater than the context length"
@@ -1 +1 @@
1
- {"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAwEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CA6B5F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C","sourcesContent":["import type { AssistantMessage } from \"../types.ts\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - OpenAI/LiteLLM: \"Requested token count exceeds the model's maximum context length of 131072 tokens\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - Together AI: \"The input (X tokens) is longer than the model's context length (Y tokens).\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt is too long/i, // Anthropic token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/exceeds (?:the )?(?:model'?s )?maximum context length of [\\d,]+ tokens?/i, // OpenAI-compatible proxies (LiteLLM)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/input \\(\\d+ tokens\\) is longer than the model'?s context length \\(\\d+ tokens\\)/i, // Together AI\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t/^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i, // Cerebras: 400/413 with no body\n];\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\" or \"exceeds the model's maximum context length of X tokens\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - Together AI: \"The input (X tokens) is longer than the model's context length (Y tokens).\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\t// Case 1: Check error message patterns\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 2: Silent overflow (z.ai style) - successful but usage exceeds context\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
1
+ {"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA0EpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CA6B5F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C","sourcesContent":["import type { AssistantMessage } from \"../types.ts\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - OpenAI/LiteLLM: \"Requested token count exceeds the model's maximum context length of 131072 tokens\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - OpenRouter/Poolside: \"Input length X exceeds the maximum allowed input length of Y tokens.\"\n * - Together AI: \"The input (X tokens) is longer than the model's context length (Y tokens).\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt is too long/i, // Anthropic token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/exceeds (?:the )?(?:model'?s )?maximum context length of [\\d,]+ tokens?/i, // OpenAI-compatible proxies (LiteLLM)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (most backends)\n\t/exceeds (?:the )?maximum allowed input length of [\\d,]+ tokens?/i, // OpenRouter/Poolside\n\t/input \\(\\d+ tokens\\) is longer than the model'?s context length \\(\\d+ tokens\\)/i, // Together AI\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t/^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i, // Cerebras: 400/413 with no body\n];\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\" or \"exceeds the model's maximum context length of X tokens\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (most backends): \"maximum context length is X tokens\"\n * - OpenRouter/Poolside: \"Input length X exceeds the maximum allowed input length of Y tokens.\"\n * - Together AI: \"The input (X tokens) is longer than the model's context length (Y tokens).\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\t// Case 1: Check error message patterns\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 2: Silent overflow (z.ai style) - successful but usage exceeds context\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
@@ -14,6 +14,7 @@
14
14
  * - xAI: "This model's maximum prompt length is 131072 but the request contains 537812 tokens"
15
15
  * - Groq: "Please reduce the length of the messages or completion"
16
16
  * - OpenRouter: "This endpoint's maximum context length is X tokens. However, you requested about Y tokens"
17
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
17
18
  * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
18
19
  * - llama.cpp: "the request exceeds the available context size, try increasing it"
19
20
  * - LM Studio: "tokens to keep from the initial prompt is greater than the context length"
@@ -37,7 +38,8 @@ const OVERFLOW_PATTERNS = [
37
38
  /input token count.*exceeds the maximum/i, // Google (Gemini)
38
39
  /maximum prompt length is \d+/i, // xAI (Grok)
39
40
  /reduce the length of the messages/i, // Groq
40
- /maximum context length is \d+ tokens/i, // OpenRouter (all backends)
41
+ /maximum context length is \d+ tokens/i, // OpenRouter (most backends)
42
+ /exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i, // OpenRouter/Poolside
41
43
  /input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i, // Together AI
42
44
  /exceeds the limit of \d+/i, // GitHub Copilot
43
45
  /exceeds the available context size/i, // llama.cpp server
@@ -85,7 +87,8 @@ const NON_OVERFLOW_PATTERNS = [
85
87
  * - Groq: "reduce the length of the messages"
86
88
  * - Cerebras: 400/413 status code (no body)
87
89
  * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
88
- * - OpenRouter (all backends): "maximum context length is X tokens"
90
+ * - OpenRouter (most backends): "maximum context length is X tokens"
91
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
89
92
  * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
90
93
  * - llama.cpp: "exceeds the available context size"
91
94
  * - LM Studio: "greater than the context length"