@earendil-works/pi-ai 0.80.6 → 0.80.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -24
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +95 -46
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +14 -7
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/lazy.d.ts.map +1 -1
- package/dist/api/lazy.js +9 -10
- package/dist/api/lazy.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts +1 -0
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +9 -5
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +12 -3
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts +3 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +51 -0
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.d.ts +1 -0
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +26 -8
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.d.ts +97 -0
- package/dist/api/pi-messages.d.ts.map +1 -0
- package/dist/api/pi-messages.js +309 -0
- package/dist/api/pi-messages.js.map +1 -0
- package/dist/api/pi-messages.lazy.d.ts +3 -0
- package/dist/api/pi-messages.lazy.d.ts.map +1 -0
- package/dist/api/pi-messages.lazy.js +3 -0
- package/dist/api/pi-messages.lazy.js.map +1 -0
- package/dist/auth/credential-store.d.ts +2 -1
- package/dist/auth/credential-store.d.ts.map +1 -1
- package/dist/auth/credential-store.js +3 -0
- package/dist/auth/credential-store.js.map +1 -1
- package/dist/auth/helpers.d.ts.map +1 -1
- package/dist/auth/helpers.js +3 -3
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/oauth/anthropic.d.ts +9 -0
- package/dist/auth/oauth/anthropic.d.ts.map +1 -0
- package/dist/{utils → auth}/oauth/anthropic.js +51 -116
- package/dist/auth/oauth/anthropic.js.map +1 -0
- package/dist/auth/oauth/device-code.d.ts.map +1 -0
- package/dist/auth/oauth/device-code.js.map +1 -0
- package/dist/auth/oauth/github-copilot.d.ts +6 -0
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -0
- package/dist/{utils → auth}/oauth/github-copilot.js +18 -77
- package/dist/auth/oauth/github-copilot.js.map +1 -0
- package/dist/auth/oauth/load.d.ts +23 -0
- package/dist/auth/oauth/load.d.ts.map +1 -0
- package/dist/auth/oauth/load.js +49 -0
- package/dist/auth/oauth/load.js.map +1 -0
- package/dist/auth/oauth/oauth-page.d.ts.map +1 -0
- package/dist/auth/oauth/oauth-page.js.map +1 -0
- package/dist/auth/oauth/openai-codex.d.ts +9 -0
- package/dist/auth/oauth/openai-codex.d.ts.map +1 -0
- package/dist/{utils → auth}/oauth/openai-codex.js +61 -155
- package/dist/auth/oauth/openai-codex.js.map +1 -0
- package/dist/auth/oauth/pkce.d.ts.map +1 -0
- package/dist/auth/oauth/pkce.js.map +1 -0
- package/dist/auth/oauth/radius.d.ts +16 -0
- package/dist/auth/oauth/radius.d.ts.map +1 -0
- package/dist/auth/oauth/radius.js +318 -0
- package/dist/auth/oauth/radius.js.map +1 -0
- package/dist/auth/oauth/xai.d.ts +6 -0
- package/dist/auth/oauth/xai.d.ts.map +1 -0
- package/dist/auth/oauth/xai.js +184 -0
- package/dist/auth/oauth/xai.js.map +1 -0
- package/dist/auth/resolve.d.ts +2 -4
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +9 -7
- package/dist/auth/resolve.js.map +1 -1
- package/dist/auth/types.d.ts +47 -10
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/bun-oauth.d.ts +3 -0
- package/dist/bun-oauth.d.ts.map +1 -0
- package/dist/bun-oauth.js +17 -0
- package/dist/bun-oauth.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +58 -76
- package/dist/cli.js.map +1 -1
- package/dist/compat/extension-oauth-types.d.ts +39 -0
- package/dist/compat/extension-oauth-types.d.ts.map +1 -0
- package/dist/compat/extension-oauth-types.js +2 -0
- package/dist/compat/extension-oauth-types.js.map +1 -0
- package/dist/compat.d.ts +2 -0
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +25 -8
- package/dist/compat.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -0
- package/dist/env-api-keys.js.map +1 -1
- package/dist/images-models.d.ts +4 -2
- package/dist/images-models.d.ts.map +1 -1
- package/dist/images-models.js +5 -4
- package/dist/images-models.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models-store.d.ts +25 -0
- package/dist/models-store.d.ts.map +1 -0
- package/dist/models-store.js +14 -0
- package/dist/models-store.js.map +1 -0
- package/dist/models.d.ts +66 -30
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +570 -163
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.js +214 -29
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +2 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +1 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/all.d.ts +11 -6
- package/dist/providers/all.d.ts.map +1 -1
- package/dist/providers/all.js +3 -0
- package/dist/providers/all.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +51 -6
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/amazon-bedrock.models.d.ts +60 -0
- package/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.models.js +54 -0
- package/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.models.d.ts +17 -0
- package/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.models.js +17 -0
- package/dist/providers/azure-openai-responses.models.js.map +1 -1
- package/dist/providers/cerebras.models.d.ts.map +1 -1
- package/dist/providers/cerebras.models.js +1 -1
- package/dist/providers/cerebras.models.js.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.d.ts.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.js +4 -3
- package/dist/providers/cloudflare-ai-gateway.js.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.d.ts +92 -0
- package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js +72 -0
- package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
- package/dist/providers/cloudflare-auth.d.ts.map +1 -1
- package/dist/providers/cloudflare-auth.js +22 -26
- package/dist/providers/cloudflare-auth.js.map +1 -1
- package/dist/providers/cloudflare-stream.d.ts +8 -0
- package/dist/providers/cloudflare-stream.d.ts.map +1 -0
- package/dist/providers/cloudflare-stream.js +21 -0
- package/dist/providers/cloudflare-stream.js.map +1 -0
- package/dist/providers/cloudflare-workers-ai.d.ts.map +1 -1
- package/dist/providers/cloudflare-workers-ai.js +2 -1
- package/dist/providers/cloudflare-workers-ai.js.map +1 -1
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +11 -1
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/github-copilot.models.d.ts +93 -6
- package/dist/providers/github-copilot.models.d.ts.map +1 -1
- package/dist/providers/github-copilot.models.js +60 -3
- package/dist/providers/github-copilot.models.js.map +1 -1
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +56 -5
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/kimi-coding.models.d.ts +20 -0
- package/dist/providers/kimi-coding.models.d.ts.map +1 -1
- package/dist/providers/kimi-coding.models.js +18 -0
- package/dist/providers/kimi-coding.models.js.map +1 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +1 -1
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/openai-codex.models.d.ts +18 -0
- package/dist/providers/openai-codex.models.d.ts.map +1 -1
- package/dist/providers/openai-codex.models.js +6 -0
- package/dist/providers/openai-codex.models.js.map +1 -1
- package/dist/providers/openai.models.d.ts +38 -0
- package/dist/providers/openai.models.d.ts.map +1 -1
- package/dist/providers/openai.models.js +24 -0
- package/dist/providers/openai.models.js.map +1 -1
- package/dist/providers/opencode.models.d.ts +123 -0
- package/dist/providers/opencode.models.d.ts.map +1 -1
- package/dist/providers/opencode.models.js +74 -1
- package/dist/providers/opencode.models.js.map +1 -1
- package/dist/providers/openrouter.models.d.ts +28 -43
- package/dist/providers/openrouter.models.d.ts.map +1 -1
- package/dist/providers/openrouter.models.js +186 -203
- package/dist/providers/openrouter.models.js.map +1 -1
- package/dist/providers/radius-config.d.ts +26 -0
- package/dist/providers/radius-config.d.ts.map +1 -0
- package/dist/providers/radius-config.js +59 -0
- package/dist/providers/radius-config.js.map +1 -0
- package/dist/providers/radius.d.ts +9 -0
- package/dist/providers/radius.d.ts.map +1 -0
- package/dist/providers/radius.js +54 -0
- package/dist/providers/radius.js.map +1 -0
- package/dist/providers/vercel-ai-gateway.models.d.ts +71 -106
- package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.js +89 -136
- package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +11 -3
- package/dist/providers/xai.js.map +1 -1
- package/dist/providers/xai.models.d.ts +6 -4
- package/dist/providers/xai.models.d.ts.map +1 -1
- package/dist/providers/xai.models.js +3 -2
- package/dist/providers/xai.models.js.map +1 -1
- package/dist/types.d.ts +25 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/deferred-tools.d.ts +9 -0
- package/dist/utils/deferred-tools.d.ts.map +1 -0
- package/dist/utils/deferred-tools.js +36 -0
- package/dist/utils/deferred-tools.js.map +1 -0
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +18 -5
- package/dist/utils/estimate.js.map +1 -1
- package/package.json +6 -1
- package/dist/utils/oauth/anthropic.d.ts +0 -27
- package/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/dist/utils/oauth/anthropic.js.map +0 -1
- package/dist/utils/oauth/device-code.d.ts.map +0 -1
- package/dist/utils/oauth/device-code.js.map +0 -1
- package/dist/utils/oauth/github-copilot.d.ts +0 -32
- package/dist/utils/oauth/github-copilot.d.ts.map +0 -1
- package/dist/utils/oauth/github-copilot.js.map +0 -1
- package/dist/utils/oauth/index.d.ts +0 -58
- package/dist/utils/oauth/index.d.ts.map +0 -1
- package/dist/utils/oauth/index.js +0 -122
- package/dist/utils/oauth/index.js.map +0 -1
- package/dist/utils/oauth/load.d.ts +0 -5
- package/dist/utils/oauth/load.d.ts.map +0 -1
- package/dist/utils/oauth/load.js +0 -22
- package/dist/utils/oauth/load.js.map +0 -1
- package/dist/utils/oauth/oauth-page.d.ts.map +0 -1
- package/dist/utils/oauth/oauth-page.js.map +0 -1
- package/dist/utils/oauth/openai-codex.d.ts +0 -45
- package/dist/utils/oauth/openai-codex.d.ts.map +0 -1
- package/dist/utils/oauth/openai-codex.js.map +0 -1
- package/dist/utils/oauth/pkce.d.ts.map +0 -1
- package/dist/utils/oauth/pkce.js.map +0 -1
- package/dist/utils/oauth/types.d.ts +0 -64
- package/dist/utils/oauth/types.d.ts.map +0 -1
- package/dist/utils/oauth/types.js +0 -2
- package/dist/utils/oauth/types.js.map +0 -1
- /package/dist/{utils → auth}/oauth/device-code.d.ts +0 -0
- /package/dist/{utils → auth}/oauth/device-code.js +0 -0
- /package/dist/{utils → auth}/oauth/oauth-page.d.ts +0 -0
- /package/dist/{utils → auth}/oauth/oauth-page.js +0 -0
- /package/dist/{utils → auth}/oauth/pkce.d.ts +0 -0
- /package/dist/{utils → auth}/oauth/pkce.js +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic OAuth flow (Claude Pro/Max)
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This module uses Node.js http.createServer for the OAuth callback server.
|
|
5
|
+
* It is only intended for CLI use, not browser environments.
|
|
6
|
+
*/
|
|
7
|
+
import type { OAuthAuth } from "../types.ts";
|
|
8
|
+
export declare const anthropicOAuth: OAuthAuth;
|
|
9
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAmB,SAAS,EAAmB,MAAM,aAAa,CAAC;AA4U/E,eAAO,MAAM,cAAc,EAAE,SAQ5B,CAAC","sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback server.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport type { Server } from \"node:http\";\nimport { getProviderEnvValue } from \"../../utils/provider-env.ts\";\nimport type { AuthInteraction, OAuthAuth, OAuthCredential } from \"../types.ts\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\n\ntype CallbackServerInfo = {\n\tserver: Server;\n\tredirectUri: string;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string; state: string } | null>;\n};\n\ntype NodeApis = {\n\tcreateServer: typeof import(\"node:http\").createServer;\n};\n\nlet nodeApis: NodeApis | null = null;\nlet nodeApisPromise: Promise<NodeApis> | null = null;\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl\");\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://platform.claude.com/v1/oauth/token\";\nconst CALLBACK_HOST = getProviderEnvValue(\"PI_OAUTH_CALLBACK_HOST\") || \"127.0.0.1\";\nconst CALLBACK_PORT = 53692;\nconst CALLBACK_PATH = \"/callback\";\nconst REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;\nconst SCOPES =\n\t\"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload\";\nasync function getNodeApis(): Promise<NodeApis> {\n\tif (nodeApis) return nodeApis;\n\tif (!nodeApisPromise) {\n\t\tif (typeof process === \"undefined\" || (!process.versions?.node && !process.versions?.bun)) {\n\t\t\tthrow new Error(\"Anthropic OAuth is only available in Node.js environments\");\n\t\t}\n\t\tnodeApisPromise = import(\"node:http\").then((httpModule) => ({\n\t\t\tcreateServer: httpModule.createServer,\n\t\t}));\n\t}\n\tnodeApis = await nodeApisPromise;\n\treturn nodeApis;\n}\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction formatErrorDetails(error: unknown): string {\n\tif (error instanceof Error) {\n\t\tconst details: string[] = [`${error.name}: ${error.message}`];\n\t\tconst errorWithCode = error as Error & { code?: string; errno?: number | string; cause?: unknown };\n\t\tif (errorWithCode.code) details.push(`code=${errorWithCode.code}`);\n\t\tif (typeof errorWithCode.errno !== \"undefined\") details.push(`errno=${String(errorWithCode.errno)}`);\n\t\tif (typeof error.cause !== \"undefined\") {\n\t\t\tdetails.push(`cause=${formatErrorDetails(error.cause)}`);\n\t\t}\n\t\tif (error.stack) {\n\t\t\tdetails.push(`stack=${error.stack}`);\n\t\t}\n\t\treturn details.join(\"; \");\n\t}\n\treturn String(error);\n}\n\nasync function startCallbackServer(expectedState: string): Promise<CallbackServerInfo> {\n\tconst { createServer } = await getNodeApis();\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settleWait: ((value: { code: string; state: string } | null) => void) | undefined;\n\t\tconst waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {\n\t\t\tlet settled = false;\n\t\t\tsettleWait = (value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolveWait(value);\n\t\t\t};\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Anthropic authentication did not complete.\", `Error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!code || !state) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (state !== expectedState) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\tres.end(oauthSuccessHtml(\"Anthropic authentication completed. You can close this window.\"));\n\t\t\t\tsettleWait?.({ code, state });\n\t\t\t} catch {\n\t\t\t\tres.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n\t\t\t\tres.end(\"Internal error\");\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(CALLBACK_PORT, CALLBACK_HOST, () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tredirectUri: REDIRECT_URI,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t},\n\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\nasync function postJson(url: string, body: Record<string, string | number>): Promise<string> {\n\tconst response = await fetch(url, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\n\tconst responseBody = await response.text();\n\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);\n\t}\n\n\treturn responseBody;\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tstate: string,\n\tverifier: string,\n\tredirectUri: string,\n): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(TOKEN_URL, {\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tstate,\n\t\t\tredirect_uri: redirectUri,\n\t\t\tcode_verifier: verifier,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\tlet tokenData: { access_token: string; refresh_token: string; expires_in: number };\n\ttry {\n\t\ttokenData = JSON.parse(responseBody) as { access_token: string; refresh_token: string; expires_in: number };\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nasync function loginAnthropic(interaction: AuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst server = await startCallbackServer(verifier);\n\tconst manualAbort = new AbortController();\n\tlet code: string | undefined;\n\tlet state: string | undefined;\n\tlet manualInput: string | undefined;\n\tlet manualError: Error | undefined;\n\n\ttry {\n\t\tconst authParams = new URLSearchParams({\n\t\t\tcode: \"true\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES,\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t});\n\t\tinteraction.notify({\n\t\t\ttype: \"auth_url\",\n\t\t\turl: `${AUTHORIZE_URL}?${authParams.toString()}`,\n\t\t\tinstructions:\n\t\t\t\t\"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.\",\n\t\t});\n\n\t\tconst manualPromise = interaction\n\t\t\t.prompt({\n\t\t\t\ttype: \"manual_code\",\n\t\t\t\tmessage: \"Complete login in your browser, or paste the authorization code / redirect URL here:\",\n\t\t\t\tplaceholder: REDIRECT_URI,\n\t\t\t\tsignal: manualAbort.signal,\n\t\t\t})\n\t\t\t.then((input) => {\n\t\t\t\tmanualInput = input;\n\t\t\t\tserver.cancelWait();\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tmanualError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\tserver.cancelWait();\n\t\t\t});\n\n\t\tconst result = await server.waitForCode();\n\t\tif (manualError) throw manualError;\n\t\tif (result?.code) {\n\t\t\tcode = result.code;\n\t\t\tstate = result.state;\n\t\t} else if (manualInput) {\n\t\t\tconst parsed = parseAuthorizationInput(manualInput);\n\t\t\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\t\t\tcode = parsed.code;\n\t\t\tstate = parsed.state ?? verifier;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tawait manualPromise;\n\t\t\tif (manualError) throw manualError;\n\t\t\tif (manualInput) {\n\t\t\t\tconst parsed = parseAuthorizationInput(manualInput);\n\t\t\t\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\t\t\t\tcode = parsed.code;\n\t\t\t\tstate = parsed.state ?? verifier;\n\t\t\t}\n\t\t}\n\n\t\tif (!code) throw new Error(\"Missing authorization code\");\n\t\tif (!state) throw new Error(\"Missing OAuth state\");\n\t\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\t\treturn exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI);\n\t} finally {\n\t\tmanualAbort.abort();\n\t\tserver.server.close();\n\t}\n}\n\n/**\n * Refresh Anthropic OAuth token\n */\nasync function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(TOKEN_URL, {\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);\n\t}\n\n\tlet data: { access_token: string; refresh_token: string; expires_in: number; scope?: string };\n\ttry {\n\t\tdata = JSON.parse(responseBody) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t\tscope?: string;\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: data.refresh_token,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nexport const anthropicOAuth: OAuthAuth = {\n\tname: \"Anthropic (Claude Pro/Max)\",\n\tlogin: loginAnthropic,\n\trefresh: (credential) => refreshAnthropicToken(credential.refresh),\n\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* NOTE: This module uses Node.js http.createServer for the OAuth callback server.
|
|
5
5
|
* It is only intended for CLI use, not browser environments.
|
|
6
6
|
*/
|
|
7
|
-
import { getProviderEnvValue } from "
|
|
7
|
+
import { getProviderEnvValue } from "../../utils/provider-env.js";
|
|
8
8
|
import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
|
|
9
9
|
import { generatePKCE } from "./pkce.js";
|
|
10
10
|
let nodeApis = null;
|
|
@@ -179,20 +179,20 @@ async function exchangeAuthorizationCode(code, state, verifier, redirectUri) {
|
|
|
179
179
|
throw new Error(`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`);
|
|
180
180
|
}
|
|
181
181
|
return {
|
|
182
|
+
type: "oauth",
|
|
182
183
|
refresh: tokenData.refresh_token,
|
|
183
184
|
access: tokenData.access_token,
|
|
184
185
|
expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
-
* Login with Anthropic OAuth (authorization code + PKCE)
|
|
189
|
-
*/
|
|
190
|
-
export async function loginAnthropic(options) {
|
|
188
|
+
async function loginAnthropic(interaction) {
|
|
191
189
|
const { verifier, challenge } = await generatePKCE();
|
|
192
190
|
const server = await startCallbackServer(verifier);
|
|
191
|
+
const manualAbort = new AbortController();
|
|
193
192
|
let code;
|
|
194
193
|
let state;
|
|
195
|
-
let
|
|
194
|
+
let manualInput;
|
|
195
|
+
let manualError;
|
|
196
196
|
try {
|
|
197
197
|
const authParams = new URLSearchParams({
|
|
198
198
|
code: "true",
|
|
@@ -204,92 +204,68 @@ export async function loginAnthropic(options) {
|
|
|
204
204
|
code_challenge_method: "S256",
|
|
205
205
|
state: verifier,
|
|
206
206
|
});
|
|
207
|
-
|
|
207
|
+
interaction.notify({
|
|
208
|
+
type: "auth_url",
|
|
208
209
|
url: `${AUTHORIZE_URL}?${authParams.toString()}`,
|
|
209
210
|
instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.",
|
|
210
211
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
212
|
+
const manualPromise = interaction
|
|
213
|
+
.prompt({
|
|
214
|
+
type: "manual_code",
|
|
215
|
+
message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
|
|
216
|
+
placeholder: REDIRECT_URI,
|
|
217
|
+
signal: manualAbort.signal,
|
|
218
|
+
})
|
|
219
|
+
.then((input) => {
|
|
220
|
+
manualInput = input;
|
|
221
|
+
server.cancelWait();
|
|
222
|
+
})
|
|
223
|
+
.catch((error) => {
|
|
224
|
+
manualError = error instanceof Error ? error : new Error(String(error));
|
|
225
|
+
server.cancelWait();
|
|
226
|
+
});
|
|
227
|
+
const result = await server.waitForCode();
|
|
228
|
+
if (manualError)
|
|
229
|
+
throw manualError;
|
|
230
|
+
if (result?.code) {
|
|
231
|
+
code = result.code;
|
|
232
|
+
state = result.state;
|
|
233
|
+
}
|
|
234
|
+
else if (manualInput) {
|
|
235
|
+
const parsed = parseAuthorizationInput(manualInput);
|
|
236
|
+
if (parsed.state && parsed.state !== verifier)
|
|
237
|
+
throw new Error("OAuth state mismatch");
|
|
238
|
+
code = parsed.code;
|
|
239
|
+
state = parsed.state ?? verifier;
|
|
240
|
+
}
|
|
241
|
+
if (!code) {
|
|
242
|
+
await manualPromise;
|
|
243
|
+
if (manualError)
|
|
226
244
|
throw manualError;
|
|
227
|
-
|
|
228
|
-
if (result?.code) {
|
|
229
|
-
code = result.code;
|
|
230
|
-
state = result.state;
|
|
231
|
-
redirectUriForExchange = REDIRECT_URI;
|
|
232
|
-
}
|
|
233
|
-
else if (manualInput) {
|
|
245
|
+
if (manualInput) {
|
|
234
246
|
const parsed = parseAuthorizationInput(manualInput);
|
|
235
|
-
if (parsed.state && parsed.state !== verifier)
|
|
247
|
+
if (parsed.state && parsed.state !== verifier)
|
|
236
248
|
throw new Error("OAuth state mismatch");
|
|
237
|
-
}
|
|
238
249
|
code = parsed.code;
|
|
239
250
|
state = parsed.state ?? verifier;
|
|
240
251
|
}
|
|
241
|
-
if (!code) {
|
|
242
|
-
await manualPromise;
|
|
243
|
-
if (manualError) {
|
|
244
|
-
throw manualError;
|
|
245
|
-
}
|
|
246
|
-
if (manualInput) {
|
|
247
|
-
const parsed = parseAuthorizationInput(manualInput);
|
|
248
|
-
if (parsed.state && parsed.state !== verifier) {
|
|
249
|
-
throw new Error("OAuth state mismatch");
|
|
250
|
-
}
|
|
251
|
-
code = parsed.code;
|
|
252
|
-
state = parsed.state ?? verifier;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
const result = await server.waitForCode();
|
|
258
|
-
if (result?.code) {
|
|
259
|
-
code = result.code;
|
|
260
|
-
state = result.state;
|
|
261
|
-
redirectUriForExchange = REDIRECT_URI;
|
|
262
|
-
}
|
|
263
252
|
}
|
|
264
|
-
if (!code)
|
|
265
|
-
const input = await options.onPrompt({
|
|
266
|
-
message: "Paste the authorization code or full redirect URL:",
|
|
267
|
-
placeholder: REDIRECT_URI,
|
|
268
|
-
});
|
|
269
|
-
const parsed = parseAuthorizationInput(input);
|
|
270
|
-
if (parsed.state && parsed.state !== verifier) {
|
|
271
|
-
throw new Error("OAuth state mismatch");
|
|
272
|
-
}
|
|
273
|
-
code = parsed.code;
|
|
274
|
-
state = parsed.state ?? verifier;
|
|
275
|
-
}
|
|
276
|
-
if (!code) {
|
|
253
|
+
if (!code)
|
|
277
254
|
throw new Error("Missing authorization code");
|
|
278
|
-
|
|
279
|
-
if (!state) {
|
|
255
|
+
if (!state)
|
|
280
256
|
throw new Error("Missing OAuth state");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return exchangeAuthorizationCode(code, state, verifier, redirectUriForExchange);
|
|
257
|
+
interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
|
|
258
|
+
return exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI);
|
|
284
259
|
}
|
|
285
260
|
finally {
|
|
261
|
+
manualAbort.abort();
|
|
286
262
|
server.server.close();
|
|
287
263
|
}
|
|
288
264
|
}
|
|
289
265
|
/**
|
|
290
266
|
* Refresh Anthropic OAuth token
|
|
291
267
|
*/
|
|
292
|
-
|
|
268
|
+
async function refreshAnthropicToken(refreshToken) {
|
|
293
269
|
let responseBody;
|
|
294
270
|
try {
|
|
295
271
|
responseBody = await postJson(TOKEN_URL, {
|
|
@@ -309,6 +285,7 @@ export async function refreshAnthropicToken(refreshToken) {
|
|
|
309
285
|
throw new Error(`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`);
|
|
310
286
|
}
|
|
311
287
|
return {
|
|
288
|
+
type: "oauth",
|
|
312
289
|
refresh: data.refresh_token,
|
|
313
290
|
access: data.access_token,
|
|
314
291
|
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
@@ -316,52 +293,10 @@ export async function refreshAnthropicToken(refreshToken) {
|
|
|
316
293
|
}
|
|
317
294
|
export const anthropicOAuth = {
|
|
318
295
|
name: "Anthropic (Claude Pro/Max)",
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// the flow settles so the UI can dismiss the pending input.
|
|
322
|
-
const manualAbort = new AbortController();
|
|
323
|
-
try {
|
|
324
|
-
const credentials = await loginAnthropic({
|
|
325
|
-
onAuth: (info) => callbacks.notify({ type: "auth_url", url: info.url, instructions: info.instructions }),
|
|
326
|
-
onProgress: (message) => callbacks.notify({ type: "progress", message }),
|
|
327
|
-
onPrompt: (prompt) => callbacks.prompt({ type: "text", message: prompt.message, placeholder: prompt.placeholder }),
|
|
328
|
-
onManualCodeInput: () => callbacks.prompt({
|
|
329
|
-
type: "manual_code",
|
|
330
|
-
message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
|
|
331
|
-
placeholder: REDIRECT_URI,
|
|
332
|
-
signal: manualAbort.signal,
|
|
333
|
-
}),
|
|
334
|
-
});
|
|
335
|
-
return { ...credentials, type: "oauth" };
|
|
336
|
-
}
|
|
337
|
-
finally {
|
|
338
|
-
manualAbort.abort();
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
async refresh(credential) {
|
|
342
|
-
return { ...(await refreshAnthropicToken(credential.refresh)), type: "oauth" };
|
|
343
|
-
},
|
|
296
|
+
login: loginAnthropic,
|
|
297
|
+
refresh: (credential) => refreshAnthropicToken(credential.refresh),
|
|
344
298
|
async toAuth(credential) {
|
|
345
299
|
return { apiKey: credential.access };
|
|
346
300
|
},
|
|
347
301
|
};
|
|
348
|
-
export const anthropicOAuthProvider = {
|
|
349
|
-
id: "anthropic",
|
|
350
|
-
name: "Anthropic (Claude Pro/Max)",
|
|
351
|
-
usesCallbackServer: true,
|
|
352
|
-
async login(callbacks) {
|
|
353
|
-
return loginAnthropic({
|
|
354
|
-
onAuth: callbacks.onAuth,
|
|
355
|
-
onPrompt: callbacks.onPrompt,
|
|
356
|
-
onProgress: callbacks.onProgress,
|
|
357
|
-
onManualCodeInput: callbacks.onManualCodeInput,
|
|
358
|
-
});
|
|
359
|
-
},
|
|
360
|
-
async refreshToken(credentials) {
|
|
361
|
-
return refreshAnthropicToken(credentials.refresh);
|
|
362
|
-
},
|
|
363
|
-
getApiKey(credentials) {
|
|
364
|
-
return credentials.access;
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
302
|
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback server.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport type { Server } from \"node:http\";\nimport { getProviderEnvValue } from \"../../utils/provider-env.ts\";\nimport type { AuthInteraction, OAuthAuth, OAuthCredential } from \"../types.ts\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\n\ntype CallbackServerInfo = {\n\tserver: Server;\n\tredirectUri: string;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string; state: string } | null>;\n};\n\ntype NodeApis = {\n\tcreateServer: typeof import(\"node:http\").createServer;\n};\n\nlet nodeApis: NodeApis | null = null;\nlet nodeApisPromise: Promise<NodeApis> | null = null;\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl\");\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://platform.claude.com/v1/oauth/token\";\nconst CALLBACK_HOST = getProviderEnvValue(\"PI_OAUTH_CALLBACK_HOST\") || \"127.0.0.1\";\nconst CALLBACK_PORT = 53692;\nconst CALLBACK_PATH = \"/callback\";\nconst REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;\nconst SCOPES =\n\t\"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload\";\nasync function getNodeApis(): Promise<NodeApis> {\n\tif (nodeApis) return nodeApis;\n\tif (!nodeApisPromise) {\n\t\tif (typeof process === \"undefined\" || (!process.versions?.node && !process.versions?.bun)) {\n\t\t\tthrow new Error(\"Anthropic OAuth is only available in Node.js environments\");\n\t\t}\n\t\tnodeApisPromise = import(\"node:http\").then((httpModule) => ({\n\t\t\tcreateServer: httpModule.createServer,\n\t\t}));\n\t}\n\tnodeApis = await nodeApisPromise;\n\treturn nodeApis;\n}\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction formatErrorDetails(error: unknown): string {\n\tif (error instanceof Error) {\n\t\tconst details: string[] = [`${error.name}: ${error.message}`];\n\t\tconst errorWithCode = error as Error & { code?: string; errno?: number | string; cause?: unknown };\n\t\tif (errorWithCode.code) details.push(`code=${errorWithCode.code}`);\n\t\tif (typeof errorWithCode.errno !== \"undefined\") details.push(`errno=${String(errorWithCode.errno)}`);\n\t\tif (typeof error.cause !== \"undefined\") {\n\t\t\tdetails.push(`cause=${formatErrorDetails(error.cause)}`);\n\t\t}\n\t\tif (error.stack) {\n\t\t\tdetails.push(`stack=${error.stack}`);\n\t\t}\n\t\treturn details.join(\"; \");\n\t}\n\treturn String(error);\n}\n\nasync function startCallbackServer(expectedState: string): Promise<CallbackServerInfo> {\n\tconst { createServer } = await getNodeApis();\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settleWait: ((value: { code: string; state: string } | null) => void) | undefined;\n\t\tconst waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {\n\t\t\tlet settled = false;\n\t\t\tsettleWait = (value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolveWait(value);\n\t\t\t};\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Anthropic authentication did not complete.\", `Error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!code || !state) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (state !== expectedState) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\tres.end(oauthSuccessHtml(\"Anthropic authentication completed. You can close this window.\"));\n\t\t\t\tsettleWait?.({ code, state });\n\t\t\t} catch {\n\t\t\t\tres.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n\t\t\t\tres.end(\"Internal error\");\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(CALLBACK_PORT, CALLBACK_HOST, () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tredirectUri: REDIRECT_URI,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t},\n\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\nasync function postJson(url: string, body: Record<string, string | number>): Promise<string> {\n\tconst response = await fetch(url, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\n\tconst responseBody = await response.text();\n\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);\n\t}\n\n\treturn responseBody;\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tstate: string,\n\tverifier: string,\n\tredirectUri: string,\n): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(TOKEN_URL, {\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tstate,\n\t\t\tredirect_uri: redirectUri,\n\t\t\tcode_verifier: verifier,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\tlet tokenData: { access_token: string; refresh_token: string; expires_in: number };\n\ttry {\n\t\ttokenData = JSON.parse(responseBody) as { access_token: string; refresh_token: string; expires_in: number };\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nasync function loginAnthropic(interaction: AuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst server = await startCallbackServer(verifier);\n\tconst manualAbort = new AbortController();\n\tlet code: string | undefined;\n\tlet state: string | undefined;\n\tlet manualInput: string | undefined;\n\tlet manualError: Error | undefined;\n\n\ttry {\n\t\tconst authParams = new URLSearchParams({\n\t\t\tcode: \"true\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES,\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t});\n\t\tinteraction.notify({\n\t\t\ttype: \"auth_url\",\n\t\t\turl: `${AUTHORIZE_URL}?${authParams.toString()}`,\n\t\t\tinstructions:\n\t\t\t\t\"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.\",\n\t\t});\n\n\t\tconst manualPromise = interaction\n\t\t\t.prompt({\n\t\t\t\ttype: \"manual_code\",\n\t\t\t\tmessage: \"Complete login in your browser, or paste the authorization code / redirect URL here:\",\n\t\t\t\tplaceholder: REDIRECT_URI,\n\t\t\t\tsignal: manualAbort.signal,\n\t\t\t})\n\t\t\t.then((input) => {\n\t\t\t\tmanualInput = input;\n\t\t\t\tserver.cancelWait();\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tmanualError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\tserver.cancelWait();\n\t\t\t});\n\n\t\tconst result = await server.waitForCode();\n\t\tif (manualError) throw manualError;\n\t\tif (result?.code) {\n\t\t\tcode = result.code;\n\t\t\tstate = result.state;\n\t\t} else if (manualInput) {\n\t\t\tconst parsed = parseAuthorizationInput(manualInput);\n\t\t\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\t\t\tcode = parsed.code;\n\t\t\tstate = parsed.state ?? verifier;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tawait manualPromise;\n\t\t\tif (manualError) throw manualError;\n\t\t\tif (manualInput) {\n\t\t\t\tconst parsed = parseAuthorizationInput(manualInput);\n\t\t\t\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\t\t\t\tcode = parsed.code;\n\t\t\t\tstate = parsed.state ?? verifier;\n\t\t\t}\n\t\t}\n\n\t\tif (!code) throw new Error(\"Missing authorization code\");\n\t\tif (!state) throw new Error(\"Missing OAuth state\");\n\t\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\t\treturn exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI);\n\t} finally {\n\t\tmanualAbort.abort();\n\t\tserver.server.close();\n\t}\n}\n\n/**\n * Refresh Anthropic OAuth token\n */\nasync function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(TOKEN_URL, {\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);\n\t}\n\n\tlet data: { access_token: string; refresh_token: string; expires_in: number; scope?: string };\n\ttry {\n\t\tdata = JSON.parse(responseBody) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t\tscope?: string;\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: data.refresh_token,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nexport const anthropicOAuth: OAuthAuth = {\n\tname: \"Anthropic (Claude Pro/Max)\",\n\tlogin: loginAnthropic,\n\trefresh: (credential) => refreshAnthropicToken(credential.refresh),\n\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/device-code.ts"],"names":[],"mappings":"AAUA,KAAK,mCAAmC,GACrC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,mCAAmC,GAAG;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAElH,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAsBF,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,OAAO,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAoDnG","sourcesContent":["const CANCEL_MESSAGE = \"Login cancelled\";\nconst TIMEOUT_MESSAGE = \"Device flow timed out\";\nconst SLOW_DOWN_TIMEOUT_MESSAGE =\n\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.\";\nconst MINIMUM_INTERVAL_MS = 1000;\n// RFC 8628 section 3.2: if the authorization server omits `interval`, the client must use 5 seconds.\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\n// RFC 8628 section 3.5: `slow_down` means the polling interval must increase by 5 seconds.\nconst SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000;\n\ntype OAuthDeviceCodeIncompletePollResult =\n\t| { status: \"pending\" }\n\t| { status: \"slow_down\"; intervalSeconds?: number }\n\t| { status: \"failed\"; message: string };\n\nexport type OAuthDeviceCodePollResult<T> = OAuthDeviceCodeIncompletePollResult | { status: \"complete\"; value: T };\n\nexport type OAuthDeviceCodePollOptions<T> = {\n\tintervalSeconds?: number;\n\texpiresInSeconds?: number;\n\twaitBeforeFirstPoll?: boolean;\n\tpoll: () => Promise<OAuthDeviceCodePollResult<T>>;\n\tsignal?: AbortSignal;\n};\n\nfunction abortableSleep(ms: number, signal: AbortSignal | undefined, cancelMessage: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(cancelMessage));\n\t\t\treturn;\n\t\t}\n\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(cancelMessage));\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nexport async function pollOAuthDeviceCodeFlow<T>(options: OAuthDeviceCodePollOptions<T>): Promise<T> {\n\tconst deadline =\n\t\ttypeof options.expiresInSeconds === \"number\"\n\t\t\t? Date.now() + options.expiresInSeconds * 1000\n\t\t\t: Number.POSITIVE_INFINITY;\n\tlet intervalMs = Math.max(\n\t\tMINIMUM_INTERVAL_MS,\n\t\tMath.floor((options.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000),\n\t);\n\n\tlet slowDownResponses = 0;\n\tif (options.waitBeforeFirstPoll) {\n\t\tconst remainingMs = deadline - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tawait abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);\n\t\t}\n\t}\n\n\twhile (Date.now() < deadline) {\n\t\tif (options.signal?.aborted) {\n\t\t\tthrow new Error(CANCEL_MESSAGE);\n\t\t}\n\n\t\tconst result = await options.poll();\n\t\tif (result.status === \"complete\") {\n\t\t\treturn result.value;\n\t\t}\n\t\tif (result.status === \"failed\") {\n\t\t\tthrow new Error(result.message);\n\t\t}\n\t\tif (result.status === \"slow_down\") {\n\t\t\tslowDownResponses += 1;\n\t\t\t// Use the server-provided interval when given (GitHub reports the new required minimum\n\t\t\t// in `interval`); trusting only a client-tracked value risks polling early forever under\n\t\t\t// WSL/VM clock drift. Otherwise apply RFC 8628 section 3.5: increase by 5 seconds.\n\t\t\tintervalMs =\n\t\t\t\ttypeof result.intervalSeconds === \"number\" &&\n\t\t\t\tNumber.isFinite(result.intervalSeconds) &&\n\t\t\t\tresult.intervalSeconds > 0\n\t\t\t\t\t? Math.max(MINIMUM_INTERVAL_MS, Math.floor(result.intervalSeconds * 1000))\n\t\t\t\t\t: Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);\n\t\t}\n\n\t\tconst remainingMs = deadline - Date.now();\n\t\tif (remainingMs <= 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tawait abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);\n\t}\n\n\tthrow new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../../src/auth/oauth/device-code.ts"],"names":[],"mappings":"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","sourcesContent":["const CANCEL_MESSAGE = \"Login cancelled\";\nconst TIMEOUT_MESSAGE = \"Device flow timed out\";\nconst SLOW_DOWN_TIMEOUT_MESSAGE =\n\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.\";\nconst MINIMUM_INTERVAL_MS = 1000;\n// RFC 8628 section 3.2: if the authorization server omits `interval`, the client must use 5 seconds.\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\n// RFC 8628 section 3.5: `slow_down` means the polling interval must increase by 5 seconds.\nconst SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000;\n\ntype OAuthDeviceCodeIncompletePollResult =\n\t| { status: \"pending\" }\n\t| { status: \"slow_down\"; intervalSeconds?: number }\n\t| { status: \"failed\"; message: string };\n\nexport type OAuthDeviceCodePollResult<T> = OAuthDeviceCodeIncompletePollResult | { status: \"complete\"; value: T };\n\nexport type OAuthDeviceCodePollOptions<T> = {\n\tintervalSeconds?: number;\n\texpiresInSeconds?: number;\n\twaitBeforeFirstPoll?: boolean;\n\tpoll: () => Promise<OAuthDeviceCodePollResult<T>>;\n\tsignal?: AbortSignal;\n};\n\nfunction abortableSleep(ms: number, signal: AbortSignal | undefined, cancelMessage: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(cancelMessage));\n\t\t\treturn;\n\t\t}\n\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(cancelMessage));\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nexport async function pollOAuthDeviceCodeFlow<T>(options: OAuthDeviceCodePollOptions<T>): Promise<T> {\n\tconst deadline =\n\t\ttypeof options.expiresInSeconds === \"number\"\n\t\t\t? Date.now() + options.expiresInSeconds * 1000\n\t\t\t: Number.POSITIVE_INFINITY;\n\tlet intervalMs = Math.max(\n\t\tMINIMUM_INTERVAL_MS,\n\t\tMath.floor((options.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000),\n\t);\n\n\tlet slowDownResponses = 0;\n\tif (options.waitBeforeFirstPoll) {\n\t\tconst remainingMs = deadline - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tawait abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);\n\t\t}\n\t}\n\n\twhile (Date.now() < deadline) {\n\t\tif (options.signal?.aborted) {\n\t\t\tthrow new Error(CANCEL_MESSAGE);\n\t\t}\n\n\t\tconst result = await options.poll();\n\t\tif (result.status === \"complete\") {\n\t\t\treturn result.value;\n\t\t}\n\t\tif (result.status === \"failed\") {\n\t\t\tthrow new Error(result.message);\n\t\t}\n\t\tif (result.status === \"slow_down\") {\n\t\t\tslowDownResponses += 1;\n\t\t\t// Use the server-provided interval when given (GitHub reports the new required minimum\n\t\t\t// in `interval`); trusting only a client-tracked value risks polling early forever under\n\t\t\t// WSL/VM clock drift. Otherwise apply RFC 8628 section 3.5: increase by 5 seconds.\n\t\t\tintervalMs =\n\t\t\t\ttypeof result.intervalSeconds === \"number\" &&\n\t\t\t\tNumber.isFinite(result.intervalSeconds) &&\n\t\t\t\tresult.intervalSeconds > 0\n\t\t\t\t\t? Math.max(MINIMUM_INTERVAL_MS, Math.floor(result.intervalSeconds * 1000))\n\t\t\t\t\t: Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);\n\t\t}\n\n\t\tconst remainingMs = deadline - Date.now();\n\t\tif (remainingMs <= 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tawait abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);\n\t}\n\n\tthrow new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/github-copilot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAmB,SAAS,EAAmB,MAAM,aAAa,CAAC;AAyW/E,eAAO,MAAM,kBAAkB,EAAE,SAYhC,CAAC","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { GITHUB_COPILOT_MODELS } from \"../../providers/github-copilot.models.ts\";\nimport type { AuthInteraction, OAuthAuth, OAuthCredential } from \"../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\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;\nconst COPILOT_API_VERSION = \"2026-06-01\";\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\nfunction 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\nfunction 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\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn value && typeof value === \"object\" ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction isSelectableCopilotModel(item: Record<string, unknown>): boolean {\n\tconst policy = asRecord(item.policy);\n\tconst capabilities = asRecord(item.capabilities);\n\tconst supports = asRecord(capabilities?.supports);\n\treturn item.model_picker_enabled === true && policy?.state !== \"disabled\" && supports?.tool_calls !== false;\n}\n\nfunction parseAvailableCopilotModelIds(raw: unknown): string[] {\n\tconst data = asRecord(raw)?.data;\n\tif (!Array.isArray(data)) {\n\t\tthrow new Error(\"Invalid Copilot models response\");\n\t}\n\n\tconst ids: string[] = [];\n\tfor (const rawItem of data) {\n\t\tconst item = asRecord(rawItem);\n\t\tconst id = item?.id;\n\t\tif (typeof id === \"string\" && item && isSelectableCopilotModel(item)) {\n\t\t\tids.push(id);\n\t\t}\n\t}\n\treturn ids;\n}\n\nasync function fetchAvailableGitHubCopilotModelIds(copilotToken: string, enterpriseDomain?: string): Promise<string[]> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(copilotToken, enterpriseDomain);\n\tconst raw = await fetchJson(`${baseUrl}/models`, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${copilotToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t\t\"X-GitHub-Api-Version\": COPILOT_API_VERSION,\n\t\t},\n\t\tsignal: AbortSignal.timeout(5000),\n\t});\n\treturn parseAvailableCopilotModelIds(raw);\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\t// The verification URI is opened in the user's browser and to prevent `open` from\n\t// opening an executable or similar, we force it to be a URL.\n\tlet parsedUri: URL;\n\ttry {\n\t\tparsedUri = new URL(verificationUri);\n\t} catch {\n\t\tthrow new Error(\"Untrusted verification_uri in device code response\");\n\t}\n\tif (parsedUri.protocol !== \"https:\" && parsedUri.protocol !== \"http:\") {\n\t\tthrow new Error(\"Untrusted verification_uri in device code response\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: parsedUri.href,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\nasync function pollForGitHubAccessToken(\n\tdomain: string,\n\tdevice: DeviceCodeResponse,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\tconst urls = getUrls(domain);\n\treturn pollOAuthDeviceCodeFlow<string>({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t\twaitBeforeFirstPoll: true,\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\", value: (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, interval } = 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\", intervalSeconds: typeof interval === \"number\" ? interval : undefined };\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\nasync function refreshGitHubCopilotAccessToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredential> {\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\ttype: \"oauth\",\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 * Refresh GitHub Copilot token\n */\nasync function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string): Promise<OAuthCredential> {\n\tconst credentials = await refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain);\n\treturn {\n\t\t...credentials,\n\t\tavailableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, 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(token: string, enterpriseDomain?: string): Promise<void> {\n\tconst models = Object.values(GITHUB_COPILOT_MODELS);\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tawait enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t}),\n\t);\n}\n\nasync function loginGitHubCopilot(interaction: AuthInteraction): Promise<OAuthCredential> {\n\tconst input = await interaction.prompt({\n\t\ttype: \"text\",\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t});\n\tif (interaction.signal?.aborted) throw new Error(\"Login cancelled\");\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) throw new Error(\"Invalid GitHub Enterprise URL/domain\");\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\tinteraction.notify({\n\t\ttype: \"device_code\",\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, interaction.signal);\n\tconst credentials = await refreshGitHubCopilotAccessToken(githubAccessToken, enterpriseDomain ?? undefined);\n\tinteraction.notify({ type: \"progress\", message: \"Enabling models...\" });\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn {\n\t\t...credentials,\n\t\tavailableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, enterpriseDomain ?? undefined),\n\t};\n}\n\nfunction copilotEnterpriseDomain(credential: OAuthCredential): string | undefined {\n\tconst enterpriseUrl = credential.enterpriseUrl;\n\tif (typeof enterpriseUrl !== \"string\" || !enterpriseUrl) return undefined;\n\treturn normalizeDomain(enterpriseUrl) ?? undefined;\n}\n\nexport const githubCopilotOAuth: OAuthAuth = {\n\tname: \"GitHub Copilot\",\n\tlogin: loginGitHubCopilot,\n\trefresh: (credential) => refreshGitHubCopilotToken(credential.refresh, copilotEnterpriseDomain(credential)),\n\n\t/** Derive the credential-specific proxy endpoint for each request. */\n\tasync toAuth(credential) {\n\t\treturn {\n\t\t\tapiKey: credential.access,\n\t\t\tbaseUrl: getGitHubCopilotBaseUrl(credential.access, copilotEnterpriseDomain(credential)),\n\t\t};\n\t},\n};\n"]}
|
|
@@ -12,7 +12,7 @@ const COPILOT_HEADERS = {
|
|
|
12
12
|
"Copilot-Integration-Id": "vscode-chat",
|
|
13
13
|
};
|
|
14
14
|
const COPILOT_API_VERSION = "2026-06-01";
|
|
15
|
-
|
|
15
|
+
function normalizeDomain(input) {
|
|
16
16
|
const trimmed = input.trim();
|
|
17
17
|
if (!trimmed)
|
|
18
18
|
return null;
|
|
@@ -45,7 +45,7 @@ function getBaseUrlFromToken(token) {
|
|
|
45
45
|
const apiHost = proxyHost.replace(/^proxy\./, "api.");
|
|
46
46
|
return `https://${apiHost}`;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
function getGitHubCopilotBaseUrl(token, enterpriseDomain) {
|
|
49
49
|
// If we have a token, extract the base URL from proxy-ep
|
|
50
50
|
if (token) {
|
|
51
51
|
const urlFromToken = getBaseUrlFromToken(token);
|
|
@@ -209,6 +209,7 @@ async function refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain) {
|
|
|
209
209
|
throw new Error("Invalid Copilot token response fields");
|
|
210
210
|
}
|
|
211
211
|
return {
|
|
212
|
+
type: "oauth",
|
|
212
213
|
refresh: refreshToken,
|
|
213
214
|
access: token,
|
|
214
215
|
expires: expiresAt * 1000 - 5 * 60 * 1000,
|
|
@@ -218,7 +219,7 @@ async function refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain) {
|
|
|
218
219
|
/**
|
|
219
220
|
* Refresh GitHub Copilot token
|
|
220
221
|
*/
|
|
221
|
-
|
|
222
|
+
async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain) {
|
|
222
223
|
const credentials = await refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain);
|
|
223
224
|
return {
|
|
224
225
|
...credentials,
|
|
@@ -254,50 +255,37 @@ async function enableGitHubCopilotModel(token, modelId, enterpriseDomain) {
|
|
|
254
255
|
* Enable all known GitHub Copilot models that may require policy acceptance.
|
|
255
256
|
* Called after successful login to ensure all models are available.
|
|
256
257
|
*/
|
|
257
|
-
async function enableAllGitHubCopilotModels(token, enterpriseDomain
|
|
258
|
+
async function enableAllGitHubCopilotModels(token, enterpriseDomain) {
|
|
258
259
|
const models = Object.values(GITHUB_COPILOT_MODELS);
|
|
259
260
|
await Promise.all(models.map(async (model) => {
|
|
260
|
-
|
|
261
|
-
onProgress?.(model.id, success);
|
|
261
|
+
await enableGitHubCopilotModel(token, model.id, enterpriseDomain);
|
|
262
262
|
}));
|
|
263
263
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
* @param options.onDeviceCode - Callback with URL and user code
|
|
268
|
-
* @param options.onPrompt - Callback to prompt user for input
|
|
269
|
-
* @param options.onProgress - Optional progress callback
|
|
270
|
-
* @param options.signal - Optional AbortSignal for cancellation
|
|
271
|
-
*/
|
|
272
|
-
export async function loginGitHubCopilot(options) {
|
|
273
|
-
const input = await options.onPrompt({
|
|
264
|
+
async function loginGitHubCopilot(interaction) {
|
|
265
|
+
const input = await interaction.prompt({
|
|
266
|
+
type: "text",
|
|
274
267
|
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
275
268
|
placeholder: "company.ghe.com",
|
|
276
|
-
allowEmpty: true,
|
|
277
269
|
});
|
|
278
|
-
if (
|
|
270
|
+
if (interaction.signal?.aborted)
|
|
279
271
|
throw new Error("Login cancelled");
|
|
280
|
-
}
|
|
281
272
|
const trimmed = input.trim();
|
|
282
273
|
const enterpriseDomain = normalizeDomain(input);
|
|
283
|
-
if (trimmed && !enterpriseDomain)
|
|
274
|
+
if (trimmed && !enterpriseDomain)
|
|
284
275
|
throw new Error("Invalid GitHub Enterprise URL/domain");
|
|
285
|
-
}
|
|
286
276
|
const domain = enterpriseDomain || "github.com";
|
|
287
277
|
const device = await startDeviceFlow(domain);
|
|
288
|
-
|
|
278
|
+
interaction.notify({
|
|
279
|
+
type: "device_code",
|
|
289
280
|
userCode: device.user_code,
|
|
290
281
|
verificationUri: device.verification_uri,
|
|
291
282
|
intervalSeconds: device.interval,
|
|
292
283
|
expiresInSeconds: device.expires_in,
|
|
293
284
|
});
|
|
294
|
-
const githubAccessToken = await pollForGitHubAccessToken(domain, device,
|
|
285
|
+
const githubAccessToken = await pollForGitHubAccessToken(domain, device, interaction.signal);
|
|
295
286
|
const credentials = await refreshGitHubCopilotAccessToken(githubAccessToken, enterpriseDomain ?? undefined);
|
|
296
|
-
|
|
297
|
-
options.onProgress?.("Enabling models...");
|
|
287
|
+
interaction.notify({ type: "progress", message: "Enabling models..." });
|
|
298
288
|
await enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);
|
|
299
|
-
// Fetch availability after policy enable so newly enabled models are included,
|
|
300
|
-
// while unavailable models are still filtered out.
|
|
301
289
|
return {
|
|
302
290
|
...credentials,
|
|
303
291
|
availableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, enterpriseDomain ?? undefined),
|
|
@@ -311,22 +299,9 @@ function copilotEnterpriseDomain(credential) {
|
|
|
311
299
|
}
|
|
312
300
|
export const githubCopilotOAuth = {
|
|
313
301
|
name: "GitHub Copilot",
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
onPrompt: (prompt) => callbacks.prompt({ type: "text", message: prompt.message, placeholder: prompt.placeholder }),
|
|
318
|
-
onProgress: (message) => callbacks.notify({ type: "progress", message }),
|
|
319
|
-
signal: callbacks.signal,
|
|
320
|
-
});
|
|
321
|
-
return { ...credentials, type: "oauth" };
|
|
322
|
-
},
|
|
323
|
-
async refresh(credential) {
|
|
324
|
-
return {
|
|
325
|
-
...(await refreshGitHubCopilotToken(credential.refresh, copilotEnterpriseDomain(credential))),
|
|
326
|
-
type: "oauth",
|
|
327
|
-
};
|
|
328
|
-
},
|
|
329
|
-
/** Per-credential baseUrl from the token's proxy endpoint replaces the old `modifyModels` rewriting. */
|
|
302
|
+
login: loginGitHubCopilot,
|
|
303
|
+
refresh: (credential) => refreshGitHubCopilotToken(credential.refresh, copilotEnterpriseDomain(credential)),
|
|
304
|
+
/** Derive the credential-specific proxy endpoint for each request. */
|
|
330
305
|
async toAuth(credential) {
|
|
331
306
|
return {
|
|
332
307
|
apiKey: credential.access,
|
|
@@ -334,38 +309,4 @@ export const githubCopilotOAuth = {
|
|
|
334
309
|
};
|
|
335
310
|
},
|
|
336
311
|
};
|
|
337
|
-
export const githubCopilotOAuthProvider = {
|
|
338
|
-
id: "github-copilot",
|
|
339
|
-
name: "GitHub Copilot",
|
|
340
|
-
async login(callbacks) {
|
|
341
|
-
return loginGitHubCopilot({
|
|
342
|
-
onDeviceCode: callbacks.onDeviceCode,
|
|
343
|
-
onPrompt: callbacks.onPrompt,
|
|
344
|
-
onProgress: callbacks.onProgress,
|
|
345
|
-
signal: callbacks.signal,
|
|
346
|
-
});
|
|
347
|
-
},
|
|
348
|
-
async refreshToken(credentials) {
|
|
349
|
-
const creds = credentials;
|
|
350
|
-
return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);
|
|
351
|
-
},
|
|
352
|
-
getApiKey(credentials) {
|
|
353
|
-
return credentials.access;
|
|
354
|
-
},
|
|
355
|
-
modifyModels(models, credentials) {
|
|
356
|
-
const creds = credentials;
|
|
357
|
-
const domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;
|
|
358
|
-
const baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);
|
|
359
|
-
// Older stored Pi auth entries do not have account-specific model IDs yet;
|
|
360
|
-
// keep their existing generated-catalog behavior until the next refresh/login.
|
|
361
|
-
const availableModelIds = "availableModelIds" in creds ? new Set(creds.availableModelIds) : undefined;
|
|
362
|
-
return models.flatMap((m) => {
|
|
363
|
-
if (m.provider !== "github-copilot")
|
|
364
|
-
return [m];
|
|
365
|
-
if (availableModelIds && !availableModelIds.has(m.id))
|
|
366
|
-
return [];
|
|
367
|
-
return [{ ...m, baseUrl }];
|
|
368
|
-
});
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
312
|
//# sourceMappingURL=github-copilot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-copilot.js","sourceRoot":"","sources":["../../../src/auth/oauth/github-copilot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { GITHUB_COPILOT_MODELS } from \"../../providers/github-copilot.models.ts\";\nimport type { AuthInteraction, OAuthAuth, OAuthCredential } from \"../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\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;\nconst COPILOT_API_VERSION = \"2026-06-01\";\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\nfunction 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\nfunction 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\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn value && typeof value === \"object\" ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction isSelectableCopilotModel(item: Record<string, unknown>): boolean {\n\tconst policy = asRecord(item.policy);\n\tconst capabilities = asRecord(item.capabilities);\n\tconst supports = asRecord(capabilities?.supports);\n\treturn item.model_picker_enabled === true && policy?.state !== \"disabled\" && supports?.tool_calls !== false;\n}\n\nfunction parseAvailableCopilotModelIds(raw: unknown): string[] {\n\tconst data = asRecord(raw)?.data;\n\tif (!Array.isArray(data)) {\n\t\tthrow new Error(\"Invalid Copilot models response\");\n\t}\n\n\tconst ids: string[] = [];\n\tfor (const rawItem of data) {\n\t\tconst item = asRecord(rawItem);\n\t\tconst id = item?.id;\n\t\tif (typeof id === \"string\" && item && isSelectableCopilotModel(item)) {\n\t\t\tids.push(id);\n\t\t}\n\t}\n\treturn ids;\n}\n\nasync function fetchAvailableGitHubCopilotModelIds(copilotToken: string, enterpriseDomain?: string): Promise<string[]> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(copilotToken, enterpriseDomain);\n\tconst raw = await fetchJson(`${baseUrl}/models`, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${copilotToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t\t\"X-GitHub-Api-Version\": COPILOT_API_VERSION,\n\t\t},\n\t\tsignal: AbortSignal.timeout(5000),\n\t});\n\treturn parseAvailableCopilotModelIds(raw);\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\t// The verification URI is opened in the user's browser and to prevent `open` from\n\t// opening an executable or similar, we force it to be a URL.\n\tlet parsedUri: URL;\n\ttry {\n\t\tparsedUri = new URL(verificationUri);\n\t} catch {\n\t\tthrow new Error(\"Untrusted verification_uri in device code response\");\n\t}\n\tif (parsedUri.protocol !== \"https:\" && parsedUri.protocol !== \"http:\") {\n\t\tthrow new Error(\"Untrusted verification_uri in device code response\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: parsedUri.href,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\nasync function pollForGitHubAccessToken(\n\tdomain: string,\n\tdevice: DeviceCodeResponse,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\tconst urls = getUrls(domain);\n\treturn pollOAuthDeviceCodeFlow<string>({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t\twaitBeforeFirstPoll: true,\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\", value: (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, interval } = 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\", intervalSeconds: typeof interval === \"number\" ? interval : undefined };\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\nasync function refreshGitHubCopilotAccessToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredential> {\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\ttype: \"oauth\",\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 * Refresh GitHub Copilot token\n */\nasync function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string): Promise<OAuthCredential> {\n\tconst credentials = await refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain);\n\treturn {\n\t\t...credentials,\n\t\tavailableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, 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(token: string, enterpriseDomain?: string): Promise<void> {\n\tconst models = Object.values(GITHUB_COPILOT_MODELS);\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tawait enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t}),\n\t);\n}\n\nasync function loginGitHubCopilot(interaction: AuthInteraction): Promise<OAuthCredential> {\n\tconst input = await interaction.prompt({\n\t\ttype: \"text\",\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t});\n\tif (interaction.signal?.aborted) throw new Error(\"Login cancelled\");\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) throw new Error(\"Invalid GitHub Enterprise URL/domain\");\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\tinteraction.notify({\n\t\ttype: \"device_code\",\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, interaction.signal);\n\tconst credentials = await refreshGitHubCopilotAccessToken(githubAccessToken, enterpriseDomain ?? undefined);\n\tinteraction.notify({ type: \"progress\", message: \"Enabling models...\" });\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn {\n\t\t...credentials,\n\t\tavailableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, enterpriseDomain ?? undefined),\n\t};\n}\n\nfunction copilotEnterpriseDomain(credential: OAuthCredential): string | undefined {\n\tconst enterpriseUrl = credential.enterpriseUrl;\n\tif (typeof enterpriseUrl !== \"string\" || !enterpriseUrl) return undefined;\n\treturn normalizeDomain(enterpriseUrl) ?? undefined;\n}\n\nexport const githubCopilotOAuth: OAuthAuth = {\n\tname: \"GitHub Copilot\",\n\tlogin: loginGitHubCopilot,\n\trefresh: (credential) => refreshGitHubCopilotToken(credential.refresh, copilotEnterpriseDomain(credential)),\n\n\t/** Derive the credential-specific proxy endpoint for each request. */\n\tasync toAuth(credential) {\n\t\treturn {\n\t\t\tapiKey: credential.access,\n\t\t\tbaseUrl: getGitHubCopilotBaseUrl(credential.access, copilotEnterpriseDomain(credential)),\n\t\t};\n\t},\n};\n"]}
|