@draht/ai 2026.4.26 → 2026.6.11
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/dist/api-registry.d.ts +1 -1
- package/dist/api-registry.d.ts.map +1 -1
- package/dist/api-registry.js.map +1 -1
- package/dist/bedrock-provider.d.ts +2 -2
- package/dist/bedrock-provider.d.ts.map +1 -1
- package/dist/bedrock-provider.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts +10 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +110 -36
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.d.ts +10 -0
- package/dist/image-models.d.ts.map +1 -0
- package/dist/image-models.generated.d.ts +485 -0
- package/dist/image-models.generated.d.ts.map +1 -0
- package/dist/image-models.generated.js +487 -0
- package/dist/image-models.generated.js.map +1 -0
- package/dist/image-models.js +23 -0
- package/dist/image-models.js.map +1 -0
- package/dist/images-api-registry.d.ts +14 -0
- package/dist/images-api-registry.d.ts.map +1 -0
- package/dist/images-api-registry.js +22 -0
- package/dist/images-api-registry.js.map +1 -0
- package/dist/images.d.ts +4 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +14 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +31 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +5 -8
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +5197 -1721
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +7156 -5016
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +33 -6
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +19 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +278 -89
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +37 -6
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +300 -114
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +1 -1
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +68 -21
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/cloudflare.d.ts +13 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +26 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/faux.d.ts +1 -1
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +1 -0
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +1 -1
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +8 -3
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +34 -17
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +45 -18
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +2 -2
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +9 -6
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts +3 -0
- package/dist/providers/images/openrouter.d.ts.map +1 -0
- package/dist/providers/images/openrouter.js +128 -0
- package/dist/providers/images/openrouter.js.map +1 -0
- package/dist/providers/images/register-builtins.d.ts +4 -0
- package/dist/providers/images/register-builtins.d.ts.map +1 -0
- package/dist/providers/images/register-builtins.js +34 -0
- package/dist/providers/images/register-builtins.js.map +1 -0
- package/dist/providers/mistral.d.ts +4 -1
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +43 -10
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +22 -1
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +542 -111
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +6 -2
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +446 -227
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-prompt-cache.d.ts +3 -0
- package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
- package/dist/providers/openai-prompt-cache.js +10 -0
- package/dist/providers/openai-prompt-cache.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +3 -2
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +41 -15
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +85 -40
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/register-builtins.d.ts +10 -13
- package/dist/providers/register-builtins.d.ts.map +1 -1
- package/dist/providers/register-builtins.js +13 -20
- package/dist/providers/register-builtins.js.map +1 -1
- package/dist/providers/simple-options.d.ts +2 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js +8 -2
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +63 -34
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/stream.d.ts +3 -3
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +14 -2
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +177 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/abort-signals.d.ts +6 -0
- package/dist/utils/abort-signals.d.ts.map +1 -0
- package/dist/utils/abort-signals.js +34 -0
- package/dist/utils/abort-signals.js.map +1 -0
- package/dist/utils/diagnostics.d.ts +19 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/event-stream.d.ts +3 -3
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +2 -2
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +8 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +8 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +89 -5
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/node-http-proxy.d.ts +10 -0
- package/dist/utils/node-http-proxy.d.ts.map +1 -0
- package/dist/utils/node-http-proxy.js +97 -0
- package/dist/utils/node-http-proxy.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +1 -1
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +1 -1
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts +21 -0
- package/dist/utils/oauth/device-code.d.ts.map +1 -0
- package/dist/utils/oauth/device-code.js +56 -0
- package/dist/utils/oauth/device-code.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +3 -3
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +58 -70
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -11
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +2 -11
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +11 -2
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +187 -73
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +18 -1
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/overflow.d.ts +7 -3
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +25 -3
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +1 -1
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +242 -41
- package/dist/utils/validation.js.map +1 -1
- package/package.json +15 -16
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -776
- package/dist/providers/google-gemini-cli.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/openai-codex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,WAAW,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AA2Q7G;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/D,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,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmG5B;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiB7F;AAED,eAAO,MAAM,wBAAwB,EAAE,sBAqBtC,CAAC","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _randomBytes: typeof import(\"node:crypto\").randomBytes | null = null;\nlet _http: typeof import(\"node:http\") | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\timport(\"node:crypto\").then((m) => {\n\t\t_randomBytes = m.randomBytes;\n\t});\n\timport(\"node:http\").then((m) => {\n\t\t_http = m;\n\t});\n}\n\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.js\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProviderInterface } from \"./types.js\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTHORIZE_URL = \"https://auth.openai.com/oauth/authorize\";\nconst TOKEN_URL = \"https://auth.openai.com/oauth/token\";\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\ntype TokenSuccess = { type: \"success\"; access: string; refresh: string; expires: number };\ntype TokenFailure = { type: \"failed\" };\ntype TokenResult = TokenSuccess | TokenFailure;\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\tif (!_randomBytes) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\treturn _randomBytes(16).toString(\"hex\");\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 decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = atob(payload);\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string = REDIRECT_URI,\n): Promise<TokenResult> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tconsole.error(\"[openai-codex] code->token failed:\", response.status, text);\n\t\treturn { type: \"failed\" };\n\t}\n\n\tconst json = (await response.json()) as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t};\n\n\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tconsole.error(\"[openai-codex] token response missing fields:\", json);\n\t\treturn { type: \"failed\" };\n\t}\n\n\treturn {\n\t\ttype: \"success\",\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<TokenResult> {\n\ttry {\n\t\tconst response = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tconsole.error(\"[openai-codex] Token refresh failed:\", response.status, text);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\tconst json = (await response.json()) as {\n\t\t\taccess_token?: string;\n\t\t\trefresh_token?: string;\n\t\t\texpires_in?: number;\n\t\t};\n\n\t\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\t\tconsole.error(\"[openai-codex] Token refresh response missing fields:\", json);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"success\",\n\t\t\taccess: json.access_token,\n\t\t\trefresh: json.refresh_token,\n\t\t\texpires: Date.now() + json.expires_in * 1000,\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"[openai-codex] Token refresh error:\", error);\n\t\treturn { type: \"failed\" };\n\t}\n}\n\nasync function createAuthorizationFlow(\n\toriginator: string = \"draht\",\n): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", originator);\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tif (!_http) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\n\tlet settleWait: ((value: { code: string } | null) => void) | undefined;\n\tconst waitForCodePromise = new Promise<{ code: string } | null>((resolve) => {\n\t\tlet settled = false;\n\t\tsettleWait = (value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresolve(value);\n\t\t};\n\t});\n\n\tconst server = _http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Missing authorization code.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthSuccessHtml(\"OpenAI authentication completed. You can close this window.\"));\n\t\t\tsettleWait?.({ code });\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthErrorHtml(\"Internal error while processing OAuth callback.\"));\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, \"127.0.0.1\", () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (err: NodeJS.ErrnoException) => {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[openai-codex] Failed to bind http://127.0.0.1:1455 (\",\n\t\t\t\t\terr.code,\n\t\t\t\t\t\") Falling back to manual paste.\",\n\t\t\t\t);\n\t\t\t\tsettleWait?.(null);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n * @param options.originator - OAuth originator parameter (defaults to \"draht\")\n */\nexport async function loginOpenAICodex(options: {\n\tonAuth: (info: { url: string; instructions?: string }) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\toriginator?: string;\n}): Promise<OAuthCredentials> {\n\tconst { verifier, state, url } = await createAuthorizationFlow(options.originator);\n\tconst server = await startLocalOAuthServer(state);\n\n\toptions.onAuth({ url, instructions: \"A browser window should open. Complete login to finish.\" });\n\n\tlet code: string | undefined;\n\ttry {\n\t\tif (options.onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualCode: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = options\n\t\t\t\t.onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualCode = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualCode) {\n\t\t\t\t// Manual input won (or callback timed out and user had entered code)\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise to complete and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualCode) {\n\t\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: wait for callback, then prompt if needed\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to onPrompt if still no code\n\t\tif (!code) {\n\t\t\tconst input = await options.onPrompt({\n\t\t\t\tmessage: \"Paste the authorization code (or full redirect URL):\",\n\t\t\t});\n\t\t\tconst parsed = parseAuthorizationInput(input);\n\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t}\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"Missing authorization code\");\n\t\t}\n\n\t\tconst tokenResult = await exchangeAuthorizationCode(code, verifier);\n\t\tif (tokenResult.type !== \"success\") {\n\t\t\tthrow new Error(\"Token exchange failed\");\n\t\t}\n\n\t\tconst accountId = getAccountId(tokenResult.access);\n\t\tif (!accountId) {\n\t\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t\t}\n\n\t\treturn {\n\t\t\taccess: tokenResult.access,\n\t\t\trefresh: tokenResult.refresh,\n\t\t\texpires: tokenResult.expires,\n\t\t\taccountId,\n\t\t};\n\t} finally {\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst result = await refreshAccessToken(refreshToken);\n\tif (result.type !== \"success\") {\n\t\tthrow new Error(\"Failed to refresh OpenAI Codex token\");\n\t}\n\n\tconst accountId = getAccountId(result.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\taccess: result.access,\n\t\trefresh: result.refresh,\n\t\texpires: result.expires,\n\t\taccountId,\n\t};\n}\n\nexport const openaiCodexOAuthProvider: OAuthProviderInterface = {\n\tid: \"openai-codex\",\n\tname: \"ChatGPT Plus/Pro (Codex Subscription)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginOpenAICodex({\n\t\t\tonAuth: callbacks.onAuth,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tonManualCodeInput: callbacks.onManualCodeInput,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\treturn refreshOpenAICodexToken(credentials.refresh);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/openai-codex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH,OAAO,KAAK,EACX,gBAAgB,EAChB,mBAAmB,EAEnB,WAAW,EACX,sBAAsB,EACtB,MAAM,YAAY,CAAC;AAYpB,eAAO,MAAM,iCAAiC,YAAY,CAAC;AAC3D,eAAO,MAAM,qCAAqC,gBAAgB,CAAC;AAoYnE;;GAEG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE;IACzD,YAAY,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/D,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,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAoF5B;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE7F;AAED,eAAO,MAAM,wBAAwB,EAAE,sBA2CtC,CAAC","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds\nlet _randomBytes: typeof import(\"node:crypto\").randomBytes | null = null;\nlet _http: typeof import(\"node:http\") | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\timport(\"node:crypto\").then((m) => {\n\t\t_randomBytes = m.randomBytes;\n\t});\n\timport(\"node:http\").then((m) => {\n\t\t_http = m;\n\t});\n}\n\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\nimport type {\n\tOAuthCredentials,\n\tOAuthDeviceCodeInfo,\n\tOAuthLoginCallbacks,\n\tOAuthPrompt,\n\tOAuthProviderInterface,\n} from \"./types.ts\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTH_BASE_URL = \"https://auth.openai.com\";\nconst AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;\nconst TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;\nconst DEVICE_TOKEN_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;\nconst DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;\nconst DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;\nconst DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;\nexport const OPENAI_CODEX_BROWSER_LOGIN_METHOD = \"browser\";\nexport const OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = \"device_code\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\ntype OAuthToken = { access: string; refresh: string; expires: number };\ntype TokenOperation = \"exchange\" | \"refresh\";\n\nfunction getCallbackHost(): string {\n\treturn typeof process !== \"undefined\" ? process.env.PI_OAUTH_CALLBACK_HOST || \"127.0.0.1\" : \"127.0.0.1\";\n}\n\ntype DeviceAuthInfo = {\n\tdeviceAuthId: string;\n\tuserCode: string;\n\tintervalSeconds: number;\n};\n\ntype DeviceTokenSuccess = {\n\tauthorizationCode: string;\n\tcodeVerifier: string;\n};\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\tif (!_randomBytes) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\treturn _randomBytes(16).toString(\"hex\");\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 decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = atob(payload);\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchWithLoginCancellation(input: string, init: RequestInit): Promise<Response> {\n\ttry {\n\t\treturn await fetch(input, init);\n\t} catch (error) {\n\t\tif (init.signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nasync function readTokenResponse(response: Response, operation: TokenOperation): Promise<OAuthToken> {\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tthrow new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text || response.statusText}`);\n\t}\n\n\tconst rawJson = await response.json();\n\tconst json = rawJson as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t} | null;\n\tif (!json?.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tthrow new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);\n\t}\n\n\treturn {\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string = REDIRECT_URI,\n\tsignal?: AbortSignal,\n): Promise<OAuthToken> {\n\tconst response = await fetchWithLoginCancellation(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t\tsignal,\n\t});\n\n\treturn readTokenResponse(response, \"exchange\");\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<OAuthToken> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\treturn readTokenResponse(response, \"refresh\");\n}\n\nasync function startOpenAICodexDeviceAuth(signal?: AbortSignal): Promise<DeviceAuthInfo> {\n\tconst response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({ client_id: CLIENT_ID }),\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tif (response.status === 404) {\n\t\t\tthrow new Error(\n\t\t\t\t\"OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.\",\n\t\t\t);\n\t\t}\n\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\tthrow new Error(\n\t\t\t`OpenAI Codex device code request failed with status ${response.status}${responseBody ? `: ${responseBody}` : \"\"}`,\n\t\t);\n\t}\n\n\tconst rawJson = await response.json();\n\tconst json = rawJson as {\n\t\tdevice_auth_id?: string;\n\t\tuser_code?: string;\n\t\tinterval?: number | string;\n\t} | null;\n\tconst intervalSeconds = typeof json?.interval === \"string\" ? Number(json.interval.trim()) : json?.interval;\n\tif (\n\t\t!json?.device_auth_id ||\n\t\t!json.user_code ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds < 0\n\t) {\n\t\tthrow new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);\n\t}\n\n\treturn {\n\t\tdeviceAuthId: json.device_auth_id,\n\t\tuserCode: json.user_code,\n\t\tintervalSeconds,\n\t};\n}\n\nasync function pollOpenAICodexDeviceAuth(device: DeviceAuthInfo, signal?: AbortSignal): Promise<DeviceTokenSuccess> {\n\treturn pollOAuthDeviceCodeFlow<DeviceTokenSuccess>({\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst response = await fetchWithLoginCancellation(DEVICE_TOKEN_URL, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tdevice_auth_id: device.deviceAuthId,\n\t\t\t\t\tuser_code: device.userCode,\n\t\t\t\t}),\n\t\t\t\tsignal,\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst rawJson = await response.json();\n\t\t\t\tconst json = rawJson as { authorization_code?: string; code_verifier?: string } | null;\n\t\t\t\tif (!json?.authorization_code || !json.code_verifier) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tmessage: `Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"complete\",\n\t\t\t\t\tvalue: { authorizationCode: json.authorization_code, codeVerifier: json.code_verifier },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (response.status === 403 || response.status === 404) {\n\t\t\t\treturn { status: \"pending\" };\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\t\tlet errorCode: unknown;\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(responseBody) as { error?: string | { code?: string } } | null;\n\t\t\t\tconst error = json?.error;\n\t\t\t\terrorCode = typeof error === \"object\" ? error?.code : error;\n\t\t\t} catch {}\n\n\t\t\tif (errorCode === \"deviceauth_authorization_pending\") {\n\t\t\t\treturn { status: \"pending\" };\n\t\t\t}\n\t\t\tif (errorCode === \"slow_down\") {\n\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tmessage: `OpenAI Codex device auth failed with status ${response.status}${responseBody ? `: ${responseBody}` : \"\"}`,\n\t\t\t};\n\t\t},\n\t});\n}\n\nasync function createAuthorizationFlow(\n\toriginator: string = \"pi\",\n): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", originator);\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tif (!_http) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\n\tlet settleWait: ((value: { code: string } | null) => void) | undefined;\n\tconst waitForCodePromise = new Promise<{ code: string } | null>((resolve) => {\n\t\tlet settled = false;\n\t\tsettleWait = (value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresolve(value);\n\t\t};\n\t});\n\n\tconst server = _http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Missing authorization code.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthSuccessHtml(\"OpenAI authentication completed. You can close this window.\"));\n\t\t\tsettleWait?.({ code });\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthErrorHtml(\"Internal error while processing OAuth callback.\"));\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, getCallbackHost(), () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (_err: NodeJS.ErrnoException) => {\n\t\t\t\tsettleWait?.(null);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\nfunction credentialsFromToken(token: OAuthToken): OAuthCredentials {\n\tconst accountId = getAccountId(token.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\taccess: token.access,\n\t\trefresh: token.refresh,\n\t\texpires: token.expires,\n\t\taccountId,\n\t};\n}\n\nasync function exchangeAuthorizationCodeForCredentials(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string,\n\tsignal?: AbortSignal,\n): Promise<OAuthCredentials> {\n\treturn credentialsFromToken(await exchangeAuthorizationCode(code, verifier, redirectUri, signal));\n}\n\n/**\n * Login with OpenAI Codex OAuth using the Codex device-code flow.\n */\nexport async function loginOpenAICodexDeviceCode(options: {\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst device = await startOpenAICodexDeviceAuth(options.signal);\n\toptions.onDeviceCode({\n\t\tuserCode: device.userCode,\n\t\tverificationUri: DEVICE_VERIFICATION_URI,\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,\n\t});\n\tconst code = await pollOpenAICodexDeviceAuth(device, options.signal);\n\treturn exchangeAuthorizationCodeForCredentials(\n\t\tcode.authorizationCode,\n\t\tcode.codeVerifier,\n\t\tDEVICE_REDIRECT_URI,\n\t\toptions.signal,\n\t);\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n * @param options.originator - OAuth originator parameter (defaults to \"pi\")\n */\nexport async function loginOpenAICodex(options: {\n\tonAuth: (info: { url: string; instructions?: string }) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\toriginator?: string;\n}): Promise<OAuthCredentials> {\n\tconst { verifier, state, url } = await createAuthorizationFlow(options.originator);\n\tconst server = await startLocalOAuthServer(state);\n\n\toptions.onAuth({ url, instructions: \"A browser window should open. Complete login to finish.\" });\n\n\tlet code: string | undefined;\n\ttry {\n\t\tif (options.onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualCode: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = options\n\t\t\t\t.onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualCode = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualCode) {\n\t\t\t\t// Manual input won (or callback timed out and user had entered code)\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise to complete and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualCode) {\n\t\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: wait for callback, then prompt if needed\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to onPrompt if still no code\n\t\tif (!code) {\n\t\t\tconst input = await options.onPrompt({\n\t\t\t\tmessage: \"Paste the authorization code (or full redirect URL):\",\n\t\t\t});\n\t\t\tconst parsed = parseAuthorizationInput(input);\n\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t}\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"Missing authorization code\");\n\t\t}\n\n\t\treturn exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI);\n\t} finally {\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n\treturn credentialsFromToken(await refreshAccessToken(refreshToken));\n}\n\nexport const openaiCodexOAuthProvider: OAuthProviderInterface = {\n\tid: \"openai-codex\",\n\tname: \"ChatGPT Plus/Pro (Codex Subscription)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\tconst loginMethod = await callbacks.onSelect({\n\t\t\tmessage: \"Select OpenAI Codex login method:\",\n\t\t\toptions: [\n\t\t\t\t{ id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: \"Browser login (default)\" },\n\t\t\t\t{ id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: \"Device code login (headless)\" },\n\t\t\t],\n\t\t});\n\t\tif (!loginMethod) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\n\t\tif (loginMethod === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) {\n\t\t\treturn loginOpenAICodexDeviceCode({\n\t\t\t\tonDeviceCode: callbacks.onDeviceCode,\n\t\t\t\tsignal: callbacks.signal,\n\t\t\t});\n\t\t}\n\n\t\tif (loginMethod !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) {\n\t\t\tthrow new Error(`Unknown OpenAI Codex login method: ${loginMethod}`);\n\t\t}\n\n\t\treturn loginOpenAICodex({\n\t\t\tonAuth: callbacks.onAuth,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tonManualCodeInput: callbacks.onManualCodeInput,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\treturn refreshOpenAICodexToken(credentials.refresh);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n};\n"]}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* NOTE: This module uses Node.js crypto and http for the OAuth callback.
|
|
5
5
|
* It is only intended for CLI use, not browser environments.
|
|
6
6
|
*/
|
|
7
|
-
// NEVER convert to top-level imports - breaks browser/Vite builds
|
|
7
|
+
// NEVER convert to top-level imports - breaks browser/Vite builds
|
|
8
8
|
let _randomBytes = null;
|
|
9
9
|
let _http = null;
|
|
10
10
|
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
@@ -15,14 +15,26 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|
|
15
15
|
_http = m;
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
+
import { pollOAuthDeviceCodeFlow } from "./device-code.js";
|
|
18
19
|
import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
|
|
19
20
|
import { generatePKCE } from "./pkce.js";
|
|
20
21
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
21
|
-
const
|
|
22
|
-
const
|
|
22
|
+
const AUTH_BASE_URL = "https://auth.openai.com";
|
|
23
|
+
const AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;
|
|
24
|
+
const TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;
|
|
23
25
|
const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
26
|
+
const DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;
|
|
27
|
+
const DEVICE_TOKEN_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;
|
|
28
|
+
const DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;
|
|
29
|
+
const DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;
|
|
30
|
+
const DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;
|
|
31
|
+
export const OPENAI_CODEX_BROWSER_LOGIN_METHOD = "browser";
|
|
32
|
+
export const OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = "device_code";
|
|
24
33
|
const SCOPE = "openid profile email offline_access";
|
|
25
34
|
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
35
|
+
function getCallbackHost() {
|
|
36
|
+
return typeof process !== "undefined" ? process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1" : "127.0.0.1";
|
|
37
|
+
}
|
|
26
38
|
function createState() {
|
|
27
39
|
if (!_randomBytes) {
|
|
28
40
|
throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
|
|
@@ -69,8 +81,35 @@ function decodeJwt(token) {
|
|
|
69
81
|
return null;
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
|
-
async function
|
|
73
|
-
|
|
84
|
+
async function fetchWithLoginCancellation(input, init) {
|
|
85
|
+
try {
|
|
86
|
+
return await fetch(input, init);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (init.signal?.aborted) {
|
|
90
|
+
throw new Error("Login cancelled");
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function readTokenResponse(response, operation) {
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const text = await response.text().catch(() => "");
|
|
98
|
+
throw new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text || response.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
const rawJson = await response.json();
|
|
101
|
+
const json = rawJson;
|
|
102
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
103
|
+
throw new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
access: json.access_token,
|
|
107
|
+
refresh: json.refresh_token,
|
|
108
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI, signal) {
|
|
112
|
+
const response = await fetchWithLoginCancellation(TOKEN_URL, {
|
|
74
113
|
method: "POST",
|
|
75
114
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
76
115
|
body: new URLSearchParams({
|
|
@@ -80,27 +119,14 @@ async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_
|
|
|
80
119
|
code_verifier: verifier,
|
|
81
120
|
redirect_uri: redirectUri,
|
|
82
121
|
}),
|
|
122
|
+
signal,
|
|
83
123
|
});
|
|
84
|
-
|
|
85
|
-
const text = await response.text().catch(() => "");
|
|
86
|
-
console.error("[openai-codex] code->token failed:", response.status, text);
|
|
87
|
-
return { type: "failed" };
|
|
88
|
-
}
|
|
89
|
-
const json = (await response.json());
|
|
90
|
-
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
91
|
-
console.error("[openai-codex] token response missing fields:", json);
|
|
92
|
-
return { type: "failed" };
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
type: "success",
|
|
96
|
-
access: json.access_token,
|
|
97
|
-
refresh: json.refresh_token,
|
|
98
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
99
|
-
};
|
|
124
|
+
return readTokenResponse(response, "exchange");
|
|
100
125
|
}
|
|
101
126
|
async function refreshAccessToken(refreshToken) {
|
|
127
|
+
let response;
|
|
102
128
|
try {
|
|
103
|
-
|
|
129
|
+
response = await fetch(TOKEN_URL, {
|
|
104
130
|
method: "POST",
|
|
105
131
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
106
132
|
body: new URLSearchParams({
|
|
@@ -109,29 +135,96 @@ async function refreshAccessToken(refreshToken) {
|
|
|
109
135
|
client_id: CLIENT_ID,
|
|
110
136
|
}),
|
|
111
137
|
});
|
|
112
|
-
if (!response.ok) {
|
|
113
|
-
const text = await response.text().catch(() => "");
|
|
114
|
-
console.error("[openai-codex] Token refresh failed:", response.status, text);
|
|
115
|
-
return { type: "failed" };
|
|
116
|
-
}
|
|
117
|
-
const json = (await response.json());
|
|
118
|
-
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
119
|
-
console.error("[openai-codex] Token refresh response missing fields:", json);
|
|
120
|
-
return { type: "failed" };
|
|
121
|
-
}
|
|
122
|
-
return {
|
|
123
|
-
type: "success",
|
|
124
|
-
access: json.access_token,
|
|
125
|
-
refresh: json.refresh_token,
|
|
126
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
127
|
-
};
|
|
128
138
|
}
|
|
129
139
|
catch (error) {
|
|
130
|
-
|
|
131
|
-
return { type: "failed" };
|
|
140
|
+
throw new Error(`OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`);
|
|
132
141
|
}
|
|
142
|
+
return readTokenResponse(response, "refresh");
|
|
143
|
+
}
|
|
144
|
+
async function startOpenAICodexDeviceAuth(signal) {
|
|
145
|
+
const response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "Content-Type": "application/json" },
|
|
148
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
149
|
+
signal,
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
if (response.status === 404) {
|
|
153
|
+
throw new Error("OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.");
|
|
154
|
+
}
|
|
155
|
+
const responseBody = await response.text().catch(() => "");
|
|
156
|
+
throw new Error(`OpenAI Codex device code request failed with status ${response.status}${responseBody ? `: ${responseBody}` : ""}`);
|
|
157
|
+
}
|
|
158
|
+
const rawJson = await response.json();
|
|
159
|
+
const json = rawJson;
|
|
160
|
+
const intervalSeconds = typeof json?.interval === "string" ? Number(json.interval.trim()) : json?.interval;
|
|
161
|
+
if (!json?.device_auth_id ||
|
|
162
|
+
!json.user_code ||
|
|
163
|
+
typeof intervalSeconds !== "number" ||
|
|
164
|
+
!Number.isFinite(intervalSeconds) ||
|
|
165
|
+
intervalSeconds < 0) {
|
|
166
|
+
throw new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
deviceAuthId: json.device_auth_id,
|
|
170
|
+
userCode: json.user_code,
|
|
171
|
+
intervalSeconds,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
async function pollOpenAICodexDeviceAuth(device, signal) {
|
|
175
|
+
return pollOAuthDeviceCodeFlow({
|
|
176
|
+
intervalSeconds: device.intervalSeconds,
|
|
177
|
+
expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,
|
|
178
|
+
signal,
|
|
179
|
+
poll: async () => {
|
|
180
|
+
const response = await fetchWithLoginCancellation(DEVICE_TOKEN_URL, {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "Content-Type": "application/json" },
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
device_auth_id: device.deviceAuthId,
|
|
185
|
+
user_code: device.userCode,
|
|
186
|
+
}),
|
|
187
|
+
signal,
|
|
188
|
+
});
|
|
189
|
+
if (response.ok) {
|
|
190
|
+
const rawJson = await response.json();
|
|
191
|
+
const json = rawJson;
|
|
192
|
+
if (!json?.authorization_code || !json.code_verifier) {
|
|
193
|
+
return {
|
|
194
|
+
status: "failed",
|
|
195
|
+
message: `Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
status: "complete",
|
|
200
|
+
value: { authorizationCode: json.authorization_code, codeVerifier: json.code_verifier },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (response.status === 403 || response.status === 404) {
|
|
204
|
+
return { status: "pending" };
|
|
205
|
+
}
|
|
206
|
+
const responseBody = await response.text().catch(() => "");
|
|
207
|
+
let errorCode;
|
|
208
|
+
try {
|
|
209
|
+
const json = JSON.parse(responseBody);
|
|
210
|
+
const error = json?.error;
|
|
211
|
+
errorCode = typeof error === "object" ? error?.code : error;
|
|
212
|
+
}
|
|
213
|
+
catch { }
|
|
214
|
+
if (errorCode === "deviceauth_authorization_pending") {
|
|
215
|
+
return { status: "pending" };
|
|
216
|
+
}
|
|
217
|
+
if (errorCode === "slow_down") {
|
|
218
|
+
return { status: "slow_down" };
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
status: "failed",
|
|
222
|
+
message: `OpenAI Codex device auth failed with status ${response.status}${responseBody ? `: ${responseBody}` : ""}`,
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
});
|
|
133
226
|
}
|
|
134
|
-
async function createAuthorizationFlow(originator = "
|
|
227
|
+
async function createAuthorizationFlow(originator = "pi") {
|
|
135
228
|
const { verifier, challenge } = await generatePKCE();
|
|
136
229
|
const state = createState();
|
|
137
230
|
const url = new URL(AUTHORIZE_URL);
|
|
@@ -196,7 +289,7 @@ function startLocalOAuthServer(state) {
|
|
|
196
289
|
});
|
|
197
290
|
return new Promise((resolve) => {
|
|
198
291
|
server
|
|
199
|
-
.listen(1455,
|
|
292
|
+
.listen(1455, getCallbackHost(), () => {
|
|
200
293
|
resolve({
|
|
201
294
|
close: () => server.close(),
|
|
202
295
|
cancelWait: () => {
|
|
@@ -205,8 +298,7 @@ function startLocalOAuthServer(state) {
|
|
|
205
298
|
waitForCode: () => waitForCodePromise,
|
|
206
299
|
});
|
|
207
300
|
})
|
|
208
|
-
.on("error", (
|
|
209
|
-
console.error("[openai-codex] Failed to bind http://127.0.0.1:1455 (", err.code, ") Falling back to manual paste.");
|
|
301
|
+
.on("error", (_err) => {
|
|
210
302
|
settleWait?.(null);
|
|
211
303
|
resolve({
|
|
212
304
|
close: () => {
|
|
@@ -229,6 +321,35 @@ function getAccountId(accessToken) {
|
|
|
229
321
|
const accountId = auth?.chatgpt_account_id;
|
|
230
322
|
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
231
323
|
}
|
|
324
|
+
function credentialsFromToken(token) {
|
|
325
|
+
const accountId = getAccountId(token.access);
|
|
326
|
+
if (!accountId) {
|
|
327
|
+
throw new Error("Failed to extract accountId from token");
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
access: token.access,
|
|
331
|
+
refresh: token.refresh,
|
|
332
|
+
expires: token.expires,
|
|
333
|
+
accountId,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
async function exchangeAuthorizationCodeForCredentials(code, verifier, redirectUri, signal) {
|
|
337
|
+
return credentialsFromToken(await exchangeAuthorizationCode(code, verifier, redirectUri, signal));
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Login with OpenAI Codex OAuth using the Codex device-code flow.
|
|
341
|
+
*/
|
|
342
|
+
export async function loginOpenAICodexDeviceCode(options) {
|
|
343
|
+
const device = await startOpenAICodexDeviceAuth(options.signal);
|
|
344
|
+
options.onDeviceCode({
|
|
345
|
+
userCode: device.userCode,
|
|
346
|
+
verificationUri: DEVICE_VERIFICATION_URI,
|
|
347
|
+
intervalSeconds: device.intervalSeconds,
|
|
348
|
+
expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,
|
|
349
|
+
});
|
|
350
|
+
const code = await pollOpenAICodexDeviceAuth(device, options.signal);
|
|
351
|
+
return exchangeAuthorizationCodeForCredentials(code.authorizationCode, code.codeVerifier, DEVICE_REDIRECT_URI, options.signal);
|
|
352
|
+
}
|
|
232
353
|
/**
|
|
233
354
|
* Login with OpenAI Codex OAuth
|
|
234
355
|
*
|
|
@@ -238,7 +359,7 @@ function getAccountId(accessToken) {
|
|
|
238
359
|
* @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.
|
|
239
360
|
* Races with browser callback - whichever completes first wins.
|
|
240
361
|
* Useful for showing paste input immediately alongside browser flow.
|
|
241
|
-
* @param options.originator - OAuth originator parameter (defaults to "
|
|
362
|
+
* @param options.originator - OAuth originator parameter (defaults to "pi")
|
|
242
363
|
*/
|
|
243
364
|
export async function loginOpenAICodex(options) {
|
|
244
365
|
const { verifier, state, url } = await createAuthorizationFlow(options.originator);
|
|
@@ -313,20 +434,7 @@ export async function loginOpenAICodex(options) {
|
|
|
313
434
|
if (!code) {
|
|
314
435
|
throw new Error("Missing authorization code");
|
|
315
436
|
}
|
|
316
|
-
|
|
317
|
-
if (tokenResult.type !== "success") {
|
|
318
|
-
throw new Error("Token exchange failed");
|
|
319
|
-
}
|
|
320
|
-
const accountId = getAccountId(tokenResult.access);
|
|
321
|
-
if (!accountId) {
|
|
322
|
-
throw new Error("Failed to extract accountId from token");
|
|
323
|
-
}
|
|
324
|
-
return {
|
|
325
|
-
access: tokenResult.access,
|
|
326
|
-
refresh: tokenResult.refresh,
|
|
327
|
-
expires: tokenResult.expires,
|
|
328
|
-
accountId,
|
|
329
|
-
};
|
|
437
|
+
return exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI);
|
|
330
438
|
}
|
|
331
439
|
finally {
|
|
332
440
|
server.close();
|
|
@@ -336,26 +444,32 @@ export async function loginOpenAICodex(options) {
|
|
|
336
444
|
* Refresh OpenAI Codex OAuth token
|
|
337
445
|
*/
|
|
338
446
|
export async function refreshOpenAICodexToken(refreshToken) {
|
|
339
|
-
|
|
340
|
-
if (result.type !== "success") {
|
|
341
|
-
throw new Error("Failed to refresh OpenAI Codex token");
|
|
342
|
-
}
|
|
343
|
-
const accountId = getAccountId(result.access);
|
|
344
|
-
if (!accountId) {
|
|
345
|
-
throw new Error("Failed to extract accountId from token");
|
|
346
|
-
}
|
|
347
|
-
return {
|
|
348
|
-
access: result.access,
|
|
349
|
-
refresh: result.refresh,
|
|
350
|
-
expires: result.expires,
|
|
351
|
-
accountId,
|
|
352
|
-
};
|
|
447
|
+
return credentialsFromToken(await refreshAccessToken(refreshToken));
|
|
353
448
|
}
|
|
354
449
|
export const openaiCodexOAuthProvider = {
|
|
355
450
|
id: "openai-codex",
|
|
356
451
|
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
357
452
|
usesCallbackServer: true,
|
|
358
453
|
async login(callbacks) {
|
|
454
|
+
const loginMethod = await callbacks.onSelect({
|
|
455
|
+
message: "Select OpenAI Codex login method:",
|
|
456
|
+
options: [
|
|
457
|
+
{ id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: "Browser login (default)" },
|
|
458
|
+
{ id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: "Device code login (headless)" },
|
|
459
|
+
],
|
|
460
|
+
});
|
|
461
|
+
if (!loginMethod) {
|
|
462
|
+
throw new Error("Login cancelled");
|
|
463
|
+
}
|
|
464
|
+
if (loginMethod === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) {
|
|
465
|
+
return loginOpenAICodexDeviceCode({
|
|
466
|
+
onDeviceCode: callbacks.onDeviceCode,
|
|
467
|
+
signal: callbacks.signal,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (loginMethod !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) {
|
|
471
|
+
throw new Error(`Unknown OpenAI Codex login method: ${loginMethod}`);
|
|
472
|
+
}
|
|
359
473
|
return loginOpenAICodex({
|
|
360
474
|
onAuth: callbacks.onAuth,
|
|
361
475
|
onPrompt: callbacks.onPrompt,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-codex.js","sourceRoot":"","sources":["../../../src/utils/oauth/openai-codex.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _randomBytes: typeof import(\"node:crypto\").randomBytes | null = null;\nlet _http: typeof import(\"node:http\") | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\timport(\"node:crypto\").then((m) => {\n\t\t_randomBytes = m.randomBytes;\n\t});\n\timport(\"node:http\").then((m) => {\n\t\t_http = m;\n\t});\n}\n\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.js\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProviderInterface } from \"./types.js\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTHORIZE_URL = \"https://auth.openai.com/oauth/authorize\";\nconst TOKEN_URL = \"https://auth.openai.com/oauth/token\";\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\ntype TokenSuccess = { type: \"success\"; access: string; refresh: string; expires: number };\ntype TokenFailure = { type: \"failed\" };\ntype TokenResult = TokenSuccess | TokenFailure;\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\tif (!_randomBytes) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\treturn _randomBytes(16).toString(\"hex\");\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 decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = atob(payload);\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string = REDIRECT_URI,\n): Promise<TokenResult> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tconsole.error(\"[openai-codex] code->token failed:\", response.status, text);\n\t\treturn { type: \"failed\" };\n\t}\n\n\tconst json = (await response.json()) as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t};\n\n\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tconsole.error(\"[openai-codex] token response missing fields:\", json);\n\t\treturn { type: \"failed\" };\n\t}\n\n\treturn {\n\t\ttype: \"success\",\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<TokenResult> {\n\ttry {\n\t\tconst response = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tconsole.error(\"[openai-codex] Token refresh failed:\", response.status, text);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\tconst json = (await response.json()) as {\n\t\t\taccess_token?: string;\n\t\t\trefresh_token?: string;\n\t\t\texpires_in?: number;\n\t\t};\n\n\t\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\t\tconsole.error(\"[openai-codex] Token refresh response missing fields:\", json);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"success\",\n\t\t\taccess: json.access_token,\n\t\t\trefresh: json.refresh_token,\n\t\t\texpires: Date.now() + json.expires_in * 1000,\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"[openai-codex] Token refresh error:\", error);\n\t\treturn { type: \"failed\" };\n\t}\n}\n\nasync function createAuthorizationFlow(\n\toriginator: string = \"draht\",\n): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", originator);\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tif (!_http) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\n\tlet settleWait: ((value: { code: string } | null) => void) | undefined;\n\tconst waitForCodePromise = new Promise<{ code: string } | null>((resolve) => {\n\t\tlet settled = false;\n\t\tsettleWait = (value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresolve(value);\n\t\t};\n\t});\n\n\tconst server = _http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Missing authorization code.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthSuccessHtml(\"OpenAI authentication completed. You can close this window.\"));\n\t\t\tsettleWait?.({ code });\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthErrorHtml(\"Internal error while processing OAuth callback.\"));\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, \"127.0.0.1\", () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (err: NodeJS.ErrnoException) => {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[openai-codex] Failed to bind http://127.0.0.1:1455 (\",\n\t\t\t\t\terr.code,\n\t\t\t\t\t\") Falling back to manual paste.\",\n\t\t\t\t);\n\t\t\t\tsettleWait?.(null);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n * @param options.originator - OAuth originator parameter (defaults to \"draht\")\n */\nexport async function loginOpenAICodex(options: {\n\tonAuth: (info: { url: string; instructions?: string }) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\toriginator?: string;\n}): Promise<OAuthCredentials> {\n\tconst { verifier, state, url } = await createAuthorizationFlow(options.originator);\n\tconst server = await startLocalOAuthServer(state);\n\n\toptions.onAuth({ url, instructions: \"A browser window should open. Complete login to finish.\" });\n\n\tlet code: string | undefined;\n\ttry {\n\t\tif (options.onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualCode: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = options\n\t\t\t\t.onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualCode = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualCode) {\n\t\t\t\t// Manual input won (or callback timed out and user had entered code)\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise to complete and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualCode) {\n\t\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: wait for callback, then prompt if needed\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to onPrompt if still no code\n\t\tif (!code) {\n\t\t\tconst input = await options.onPrompt({\n\t\t\t\tmessage: \"Paste the authorization code (or full redirect URL):\",\n\t\t\t});\n\t\t\tconst parsed = parseAuthorizationInput(input);\n\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t}\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"Missing authorization code\");\n\t\t}\n\n\t\tconst tokenResult = await exchangeAuthorizationCode(code, verifier);\n\t\tif (tokenResult.type !== \"success\") {\n\t\t\tthrow new Error(\"Token exchange failed\");\n\t\t}\n\n\t\tconst accountId = getAccountId(tokenResult.access);\n\t\tif (!accountId) {\n\t\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t\t}\n\n\t\treturn {\n\t\t\taccess: tokenResult.access,\n\t\t\trefresh: tokenResult.refresh,\n\t\t\texpires: tokenResult.expires,\n\t\t\taccountId,\n\t\t};\n\t} finally {\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst result = await refreshAccessToken(refreshToken);\n\tif (result.type !== \"success\") {\n\t\tthrow new Error(\"Failed to refresh OpenAI Codex token\");\n\t}\n\n\tconst accountId = getAccountId(result.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\taccess: result.access,\n\t\trefresh: result.refresh,\n\t\texpires: result.expires,\n\t\taccountId,\n\t};\n}\n\nexport const openaiCodexOAuthProvider: OAuthProviderInterface = {\n\tid: \"openai-codex\",\n\tname: \"ChatGPT Plus/Pro (Codex Subscription)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginOpenAICodex({\n\t\t\tonAuth: callbacks.onAuth,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tonManualCodeInput: callbacks.onManualCodeInput,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\treturn refreshOpenAICodexToken(credentials.refresh);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"openai-codex.js","sourceRoot":"","sources":["../../../src/utils/oauth/openai-codex.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds\nlet _randomBytes: typeof import(\"node:crypto\").randomBytes | null = null;\nlet _http: typeof import(\"node:http\") | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\timport(\"node:crypto\").then((m) => {\n\t\t_randomBytes = m.randomBytes;\n\t});\n\timport(\"node:http\").then((m) => {\n\t\t_http = m;\n\t});\n}\n\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\nimport type {\n\tOAuthCredentials,\n\tOAuthDeviceCodeInfo,\n\tOAuthLoginCallbacks,\n\tOAuthPrompt,\n\tOAuthProviderInterface,\n} from \"./types.ts\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTH_BASE_URL = \"https://auth.openai.com\";\nconst AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;\nconst TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;\nconst DEVICE_TOKEN_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;\nconst DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;\nconst DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;\nconst DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;\nexport const OPENAI_CODEX_BROWSER_LOGIN_METHOD = \"browser\";\nexport const OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = \"device_code\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\ntype OAuthToken = { access: string; refresh: string; expires: number };\ntype TokenOperation = \"exchange\" | \"refresh\";\n\nfunction getCallbackHost(): string {\n\treturn typeof process !== \"undefined\" ? process.env.PI_OAUTH_CALLBACK_HOST || \"127.0.0.1\" : \"127.0.0.1\";\n}\n\ntype DeviceAuthInfo = {\n\tdeviceAuthId: string;\n\tuserCode: string;\n\tintervalSeconds: number;\n};\n\ntype DeviceTokenSuccess = {\n\tauthorizationCode: string;\n\tcodeVerifier: string;\n};\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\tif (!_randomBytes) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\treturn _randomBytes(16).toString(\"hex\");\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 decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = atob(payload);\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchWithLoginCancellation(input: string, init: RequestInit): Promise<Response> {\n\ttry {\n\t\treturn await fetch(input, init);\n\t} catch (error) {\n\t\tif (init.signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nasync function readTokenResponse(response: Response, operation: TokenOperation): Promise<OAuthToken> {\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tthrow new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text || response.statusText}`);\n\t}\n\n\tconst rawJson = await response.json();\n\tconst json = rawJson as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t} | null;\n\tif (!json?.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tthrow new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);\n\t}\n\n\treturn {\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string = REDIRECT_URI,\n\tsignal?: AbortSignal,\n): Promise<OAuthToken> {\n\tconst response = await fetchWithLoginCancellation(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t\tsignal,\n\t});\n\n\treturn readTokenResponse(response, \"exchange\");\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<OAuthToken> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\treturn readTokenResponse(response, \"refresh\");\n}\n\nasync function startOpenAICodexDeviceAuth(signal?: AbortSignal): Promise<DeviceAuthInfo> {\n\tconst response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({ client_id: CLIENT_ID }),\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tif (response.status === 404) {\n\t\t\tthrow new Error(\n\t\t\t\t\"OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.\",\n\t\t\t);\n\t\t}\n\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\tthrow new Error(\n\t\t\t`OpenAI Codex device code request failed with status ${response.status}${responseBody ? `: ${responseBody}` : \"\"}`,\n\t\t);\n\t}\n\n\tconst rawJson = await response.json();\n\tconst json = rawJson as {\n\t\tdevice_auth_id?: string;\n\t\tuser_code?: string;\n\t\tinterval?: number | string;\n\t} | null;\n\tconst intervalSeconds = typeof json?.interval === \"string\" ? Number(json.interval.trim()) : json?.interval;\n\tif (\n\t\t!json?.device_auth_id ||\n\t\t!json.user_code ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds < 0\n\t) {\n\t\tthrow new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);\n\t}\n\n\treturn {\n\t\tdeviceAuthId: json.device_auth_id,\n\t\tuserCode: json.user_code,\n\t\tintervalSeconds,\n\t};\n}\n\nasync function pollOpenAICodexDeviceAuth(device: DeviceAuthInfo, signal?: AbortSignal): Promise<DeviceTokenSuccess> {\n\treturn pollOAuthDeviceCodeFlow<DeviceTokenSuccess>({\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst response = await fetchWithLoginCancellation(DEVICE_TOKEN_URL, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tdevice_auth_id: device.deviceAuthId,\n\t\t\t\t\tuser_code: device.userCode,\n\t\t\t\t}),\n\t\t\t\tsignal,\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst rawJson = await response.json();\n\t\t\t\tconst json = rawJson as { authorization_code?: string; code_verifier?: string } | null;\n\t\t\t\tif (!json?.authorization_code || !json.code_verifier) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tmessage: `Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"complete\",\n\t\t\t\t\tvalue: { authorizationCode: json.authorization_code, codeVerifier: json.code_verifier },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (response.status === 403 || response.status === 404) {\n\t\t\t\treturn { status: \"pending\" };\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\t\tlet errorCode: unknown;\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(responseBody) as { error?: string | { code?: string } } | null;\n\t\t\t\tconst error = json?.error;\n\t\t\t\terrorCode = typeof error === \"object\" ? error?.code : error;\n\t\t\t} catch {}\n\n\t\t\tif (errorCode === \"deviceauth_authorization_pending\") {\n\t\t\t\treturn { status: \"pending\" };\n\t\t\t}\n\t\t\tif (errorCode === \"slow_down\") {\n\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tmessage: `OpenAI Codex device auth failed with status ${response.status}${responseBody ? `: ${responseBody}` : \"\"}`,\n\t\t\t};\n\t\t},\n\t});\n}\n\nasync function createAuthorizationFlow(\n\toriginator: string = \"pi\",\n): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", originator);\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tif (!_http) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\n\tlet settleWait: ((value: { code: string } | null) => void) | undefined;\n\tconst waitForCodePromise = new Promise<{ code: string } | null>((resolve) => {\n\t\tlet settled = false;\n\t\tsettleWait = (value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresolve(value);\n\t\t};\n\t});\n\n\tconst server = _http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Missing authorization code.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthSuccessHtml(\"OpenAI authentication completed. You can close this window.\"));\n\t\t\tsettleWait?.({ code });\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthErrorHtml(\"Internal error while processing OAuth callback.\"));\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, getCallbackHost(), () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (_err: NodeJS.ErrnoException) => {\n\t\t\t\tsettleWait?.(null);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\nfunction credentialsFromToken(token: OAuthToken): OAuthCredentials {\n\tconst accountId = getAccountId(token.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\taccess: token.access,\n\t\trefresh: token.refresh,\n\t\texpires: token.expires,\n\t\taccountId,\n\t};\n}\n\nasync function exchangeAuthorizationCodeForCredentials(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string,\n\tsignal?: AbortSignal,\n): Promise<OAuthCredentials> {\n\treturn credentialsFromToken(await exchangeAuthorizationCode(code, verifier, redirectUri, signal));\n}\n\n/**\n * Login with OpenAI Codex OAuth using the Codex device-code flow.\n */\nexport async function loginOpenAICodexDeviceCode(options: {\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst device = await startOpenAICodexDeviceAuth(options.signal);\n\toptions.onDeviceCode({\n\t\tuserCode: device.userCode,\n\t\tverificationUri: DEVICE_VERIFICATION_URI,\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,\n\t});\n\tconst code = await pollOpenAICodexDeviceAuth(device, options.signal);\n\treturn exchangeAuthorizationCodeForCredentials(\n\t\tcode.authorizationCode,\n\t\tcode.codeVerifier,\n\t\tDEVICE_REDIRECT_URI,\n\t\toptions.signal,\n\t);\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n * @param options.originator - OAuth originator parameter (defaults to \"pi\")\n */\nexport async function loginOpenAICodex(options: {\n\tonAuth: (info: { url: string; instructions?: string }) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\toriginator?: string;\n}): Promise<OAuthCredentials> {\n\tconst { verifier, state, url } = await createAuthorizationFlow(options.originator);\n\tconst server = await startLocalOAuthServer(state);\n\n\toptions.onAuth({ url, instructions: \"A browser window should open. Complete login to finish.\" });\n\n\tlet code: string | undefined;\n\ttry {\n\t\tif (options.onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualCode: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = options\n\t\t\t\t.onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualCode = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualCode) {\n\t\t\t\t// Manual input won (or callback timed out and user had entered code)\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise to complete and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualCode) {\n\t\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: wait for callback, then prompt if needed\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to onPrompt if still no code\n\t\tif (!code) {\n\t\t\tconst input = await options.onPrompt({\n\t\t\t\tmessage: \"Paste the authorization code (or full redirect URL):\",\n\t\t\t});\n\t\t\tconst parsed = parseAuthorizationInput(input);\n\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t}\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"Missing authorization code\");\n\t\t}\n\n\t\treturn exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI);\n\t} finally {\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n\treturn credentialsFromToken(await refreshAccessToken(refreshToken));\n}\n\nexport const openaiCodexOAuthProvider: OAuthProviderInterface = {\n\tid: \"openai-codex\",\n\tname: \"ChatGPT Plus/Pro (Codex Subscription)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\tconst loginMethod = await callbacks.onSelect({\n\t\t\tmessage: \"Select OpenAI Codex login method:\",\n\t\t\toptions: [\n\t\t\t\t{ id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: \"Browser login (default)\" },\n\t\t\t\t{ id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: \"Device code login (headless)\" },\n\t\t\t],\n\t\t});\n\t\tif (!loginMethod) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\n\t\tif (loginMethod === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) {\n\t\t\treturn loginOpenAICodexDeviceCode({\n\t\t\t\tonDeviceCode: callbacks.onDeviceCode,\n\t\t\t\tsignal: callbacks.signal,\n\t\t\t});\n\t\t}\n\n\t\tif (loginMethod !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) {\n\t\t\tthrow new Error(`Unknown OpenAI Codex login method: ${loginMethod}`);\n\t\t}\n\n\t\treturn loginOpenAICodex({\n\t\t\tonAuth: callbacks.onAuth,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tonManualCodeInput: callbacks.onManualCodeInput,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\treturn refreshOpenAICodexToken(credentials.refresh);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n};\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Api, Model } from "../../types.
|
|
1
|
+
import type { Api, Model } from "../../types.ts";
|
|
2
2
|
export type OAuthCredentials = {
|
|
3
3
|
refresh: string;
|
|
4
4
|
access: string;
|
|
@@ -17,11 +17,28 @@ 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
|
+
};
|
|
26
|
+
export type OAuthSelectOption = {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
};
|
|
30
|
+
export type OAuthSelectPrompt = {
|
|
31
|
+
message: string;
|
|
32
|
+
options: OAuthSelectOption[];
|
|
33
|
+
};
|
|
20
34
|
export interface OAuthLoginCallbacks {
|
|
21
35
|
onAuth: (info: OAuthAuthInfo) => void;
|
|
36
|
+
onDeviceCode: (info: OAuthDeviceCodeInfo) => void;
|
|
22
37
|
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
|
23
38
|
onProgress?: (message: string) => void;
|
|
24
39
|
onManualCodeInput?: () => Promise<string>;
|
|
40
|
+
/** Show an interactive selector and return the selected option id, or undefined on cancel. */
|
|
41
|
+
onSelect: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;
|
|
25
42
|
signal?: AbortSignal;
|
|
26
43
|
}
|
|
27
44
|
export interface OAuthProviderInterface {
|