@avadisabelle/ava-pi-ai 0.61.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1229 -0
- package/dist/api-registry.d.ts +20 -0
- package/dist/api-registry.d.ts.map +1 -0
- package/dist/api-registry.js +44 -0
- package/dist/api-registry.js.map +1 -0
- package/dist/bedrock-provider.d.ts +5 -0
- package/dist/bedrock-provider.d.ts.map +1 -0
- package/dist/bedrock-provider.js +6 -0
- package/dist/bedrock-provider.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/env-api-keys.d.ts +9 -0
- package/dist/env-api-keys.d.ts.map +1 -0
- package/dist/env-api-keys.js +107 -0
- package/dist/env-api-keys.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +24 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +14368 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +14153 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +55 -0
- package/dist/models.js.map +1 -0
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +2 -0
- package/dist/oauth.js.map +1 -0
- package/dist/providers/amazon-bedrock.d.ts +15 -0
- package/dist/providers/amazon-bedrock.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock.js +622 -0
- package/dist/providers/amazon-bedrock.js.map +1 -0
- package/dist/providers/anthropic.d.ts +40 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +743 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/azure-openai-responses.d.ts +15 -0
- package/dist/providers/azure-openai-responses.d.ts.map +1 -0
- package/dist/providers/azure-openai-responses.js +187 -0
- package/dist/providers/azure-openai-responses.js.map +1 -0
- package/dist/providers/github-copilot-headers.d.ts +8 -0
- package/dist/providers/github-copilot-headers.d.ts.map +1 -0
- package/dist/providers/github-copilot-headers.js +29 -0
- package/dist/providers/github-copilot-headers.js.map +1 -0
- package/dist/providers/google-gemini-cli.d.ts +74 -0
- package/dist/providers/google-gemini-cli.d.ts.map +1 -0
- package/dist/providers/google-gemini-cli.js +760 -0
- package/dist/providers/google-gemini-cli.js.map +1 -0
- package/dist/providers/google-shared.d.ts +65 -0
- package/dist/providers/google-shared.d.ts.map +1 -0
- package/dist/providers/google-shared.js +312 -0
- package/dist/providers/google-shared.js.map +1 -0
- package/dist/providers/google-vertex.d.ts +15 -0
- package/dist/providers/google-vertex.d.ts.map +1 -0
- package/dist/providers/google-vertex.js +402 -0
- package/dist/providers/google-vertex.js.map +1 -0
- package/dist/providers/google.d.ts +13 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +358 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/mistral.d.ts +22 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/mistral.js +501 -0
- package/dist/providers/mistral.js.map +1 -0
- package/dist/providers/openai-codex-responses.d.ts +9 -0
- package/dist/providers/openai-codex-responses.d.ts.map +1 -0
- package/dist/providers/openai-codex-responses.js +741 -0
- package/dist/providers/openai-codex-responses.js.map +1 -0
- package/dist/providers/openai-completions.d.ts +15 -0
- package/dist/providers/openai-completions.d.ts.map +1 -0
- package/dist/providers/openai-completions.js +736 -0
- package/dist/providers/openai-completions.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +17 -0
- package/dist/providers/openai-responses-shared.d.ts.map +1 -0
- package/dist/providers/openai-responses-shared.js +453 -0
- package/dist/providers/openai-responses-shared.js.map +1 -0
- package/dist/providers/openai-responses.d.ts +13 -0
- package/dist/providers/openai-responses.d.ts.map +1 -0
- package/dist/providers/openai-responses.js +201 -0
- package/dist/providers/openai-responses.js.map +1 -0
- package/dist/providers/register-builtins.d.ts +38 -0
- package/dist/providers/register-builtins.d.ts.map +1 -0
- package/dist/providers/register-builtins.js +261 -0
- package/dist/providers/register-builtins.js.map +1 -0
- package/dist/providers/simple-options.d.ts +8 -0
- package/dist/providers/simple-options.d.ts.map +1 -0
- package/dist/providers/simple-options.js +35 -0
- package/dist/providers/simple-options.js.map +1 -0
- package/dist/providers/transform-messages.d.ts +8 -0
- package/dist/providers/transform-messages.d.ts.map +1 -0
- package/dist/providers/transform-messages.js +155 -0
- package/dist/providers/transform-messages.js.map +1 -0
- package/dist/stream.d.ts +8 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +27 -0
- package/dist/stream.js.map +1 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/event-stream.d.ts +21 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +81 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/json-parse.d.ts +9 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +29 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +25 -0
- package/dist/utils/oauth/anthropic.d.ts.map +1 -0
- package/dist/utils/oauth/anthropic.js +335 -0
- package/dist/utils/oauth/anthropic.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +30 -0
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
- package/dist/utils/oauth/github-copilot.js +292 -0
- package/dist/utils/oauth/github-copilot.js.map +1 -0
- package/dist/utils/oauth/google-antigravity.d.ts +26 -0
- package/dist/utils/oauth/google-antigravity.d.ts.map +1 -0
- package/dist/utils/oauth/google-antigravity.js +376 -0
- package/dist/utils/oauth/google-antigravity.js.map +1 -0
- package/dist/utils/oauth/google-gemini-cli.d.ts +26 -0
- package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -0
- package/dist/utils/oauth/google-gemini-cli.js +481 -0
- package/dist/utils/oauth/google-gemini-cli.js.map +1 -0
- package/dist/utils/oauth/index.d.ts +61 -0
- package/dist/utils/oauth/index.d.ts.map +1 -0
- package/dist/utils/oauth/index.js +131 -0
- package/dist/utils/oauth/index.js.map +1 -0
- package/dist/utils/oauth/oauth-page.d.ts +3 -0
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
- package/dist/utils/oauth/oauth-page.js +105 -0
- package/dist/utils/oauth/oauth-page.js.map +1 -0
- package/dist/utils/oauth/openai-codex.d.ts +34 -0
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
- package/dist/utils/oauth/openai-codex.js +373 -0
- package/dist/utils/oauth/openai-codex.js.map +1 -0
- package/dist/utils/oauth/pkce.d.ts +13 -0
- package/dist/utils/oauth/pkce.d.ts.map +1 -0
- package/dist/utils/oauth/pkce.js +31 -0
- package/dist/utils/oauth/pkce.js.map +1 -0
- package/dist/utils/oauth/types.d.ts +47 -0
- package/dist/utils/oauth/types.d.ts.map +1 -0
- package/dist/utils/oauth/types.js +2 -0
- package/dist/utils/oauth/types.js.map +1 -0
- package/dist/utils/overflow.d.ts +52 -0
- package/dist/utils/overflow.d.ts.map +1 -0
- package/dist/utils/overflow.js +117 -0
- package/dist/utils/overflow.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +26 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/typebox-helpers.d.ts +17 -0
- package/dist/utils/typebox-helpers.d.ts.map +1 -0
- package/dist/utils/typebox-helpers.js +21 -0
- package/dist/utils/typebox-helpers.js.map +1 -0
- package/dist/utils/validation.d.ts +18 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +80 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +114 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth credential management for AI providers.
|
|
3
|
+
*
|
|
4
|
+
* This module handles login, token refresh, and credential storage
|
|
5
|
+
* for OAuth-based providers:
|
|
6
|
+
* - Anthropic (Claude Pro/Max)
|
|
7
|
+
* - GitHub Copilot
|
|
8
|
+
* - Google Cloud Code Assist (Gemini CLI)
|
|
9
|
+
* - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
10
|
+
*/
|
|
11
|
+
// Anthropic
|
|
12
|
+
export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
|
|
13
|
+
// GitHub Copilot
|
|
14
|
+
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
|
|
15
|
+
// Google Antigravity
|
|
16
|
+
export { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from "./google-antigravity.js";
|
|
17
|
+
// Google Gemini CLI
|
|
18
|
+
export { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli.js";
|
|
19
|
+
// OpenAI Codex (ChatGPT OAuth)
|
|
20
|
+
export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.js";
|
|
21
|
+
export * from "./types.js";
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Provider Registry
|
|
24
|
+
// ============================================================================
|
|
25
|
+
import { anthropicOAuthProvider } from "./anthropic.js";
|
|
26
|
+
import { githubCopilotOAuthProvider } from "./github-copilot.js";
|
|
27
|
+
import { antigravityOAuthProvider } from "./google-antigravity.js";
|
|
28
|
+
import { geminiCliOAuthProvider } from "./google-gemini-cli.js";
|
|
29
|
+
import { openaiCodexOAuthProvider } from "./openai-codex.js";
|
|
30
|
+
const BUILT_IN_OAUTH_PROVIDERS = [
|
|
31
|
+
anthropicOAuthProvider,
|
|
32
|
+
githubCopilotOAuthProvider,
|
|
33
|
+
geminiCliOAuthProvider,
|
|
34
|
+
antigravityOAuthProvider,
|
|
35
|
+
openaiCodexOAuthProvider,
|
|
36
|
+
];
|
|
37
|
+
const oauthProviderRegistry = new Map(BUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]));
|
|
38
|
+
/**
|
|
39
|
+
* Get an OAuth provider by ID
|
|
40
|
+
*/
|
|
41
|
+
export function getOAuthProvider(id) {
|
|
42
|
+
return oauthProviderRegistry.get(id);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Register a custom OAuth provider
|
|
46
|
+
*/
|
|
47
|
+
export function registerOAuthProvider(provider) {
|
|
48
|
+
oauthProviderRegistry.set(provider.id, provider);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Unregister an OAuth provider.
|
|
52
|
+
*
|
|
53
|
+
* If the provider is built-in, restores the built-in implementation.
|
|
54
|
+
* Custom providers are removed completely.
|
|
55
|
+
*/
|
|
56
|
+
export function unregisterOAuthProvider(id) {
|
|
57
|
+
const builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);
|
|
58
|
+
if (builtInProvider) {
|
|
59
|
+
oauthProviderRegistry.set(id, builtInProvider);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
oauthProviderRegistry.delete(id);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Reset OAuth providers to built-ins.
|
|
66
|
+
*/
|
|
67
|
+
export function resetOAuthProviders() {
|
|
68
|
+
oauthProviderRegistry.clear();
|
|
69
|
+
for (const provider of BUILT_IN_OAUTH_PROVIDERS) {
|
|
70
|
+
oauthProviderRegistry.set(provider.id, provider);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get all registered OAuth providers
|
|
75
|
+
*/
|
|
76
|
+
export function getOAuthProviders() {
|
|
77
|
+
return Array.from(oauthProviderRegistry.values());
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]
|
|
81
|
+
*/
|
|
82
|
+
export function getOAuthProviderInfoList() {
|
|
83
|
+
return getOAuthProviders().map((p) => ({
|
|
84
|
+
id: p.id,
|
|
85
|
+
name: p.name,
|
|
86
|
+
available: true,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// High-level API (uses provider registry)
|
|
91
|
+
// ============================================================================
|
|
92
|
+
/**
|
|
93
|
+
* Refresh token for any OAuth provider.
|
|
94
|
+
* @deprecated Use getOAuthProvider(id).refreshToken() instead
|
|
95
|
+
*/
|
|
96
|
+
export async function refreshOAuthToken(providerId, credentials) {
|
|
97
|
+
const provider = getOAuthProvider(providerId);
|
|
98
|
+
if (!provider) {
|
|
99
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
100
|
+
}
|
|
101
|
+
return provider.refreshToken(credentials);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get API key for a provider from OAuth credentials.
|
|
105
|
+
* Automatically refreshes expired tokens.
|
|
106
|
+
*
|
|
107
|
+
* @returns API key string and updated credentials, or null if no credentials
|
|
108
|
+
* @throws Error if refresh fails
|
|
109
|
+
*/
|
|
110
|
+
export async function getOAuthApiKey(providerId, credentials) {
|
|
111
|
+
const provider = getOAuthProvider(providerId);
|
|
112
|
+
if (!provider) {
|
|
113
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
114
|
+
}
|
|
115
|
+
let creds = credentials[providerId];
|
|
116
|
+
if (!creds) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
// Refresh if expired
|
|
120
|
+
if (Date.now() >= creds.expires) {
|
|
121
|
+
try {
|
|
122
|
+
creds = await provider.refreshToken(creds);
|
|
123
|
+
}
|
|
124
|
+
catch (_error) {
|
|
125
|
+
throw new Error(`Failed to refresh OAuth token for ${providerId}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const apiKey = provider.getApiKey(creds);
|
|
129
|
+
return { newCredentials: creds, apiKey };
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.js\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.js\";\nimport { antigravityOAuthProvider } from \"./google-antigravity.js\";\nimport { geminiCliOAuthProvider } from \"./google-gemini-cli.js\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.js\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.js\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\tgeminiCliOAuthProvider,\n\tantigravityOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-page.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/oauth-page.ts"],"names":[],"mappings":"AA6FA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAOxE","sourcesContent":["const LOGO_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 800 800\" aria-hidden=\"true\"><path fill=\"#fff\" fill-rule=\"evenodd\" d=\"M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z\"/><path fill=\"#fff\" d=\"M517.36 400 H634.72 V634.72 H517.36 Z\"/></svg>`;\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\")\n\t\t.replaceAll(\"'\", \"'\");\n}\n\nfunction renderPage(options: { title: string; heading: string; message: string; details?: string }): string {\n\tconst title = escapeHtml(options.title);\n\tconst heading = escapeHtml(options.heading);\n\tconst message = escapeHtml(options.message);\n\tconst details = options.details ? escapeHtml(options.details) : undefined;\n\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n <style>\n :root {\n --text: #fafafa;\n --text-dim: #a1a1aa;\n --page-bg: #09090b;\n --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n }\n * { box-sizing: border-box; }\n html { color-scheme: dark; }\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: var(--page-bg);\n color: var(--text);\n font-family: var(--font-sans);\n text-align: center;\n }\n main {\n width: 100%;\n max-width: 560px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n }\n .logo {\n width: 72px;\n height: 72px;\n display: block;\n margin-bottom: 24px;\n }\n h1 {\n margin: 0 0 10px;\n font-size: 28px;\n line-height: 1.15;\n font-weight: 650;\n color: var(--text);\n }\n p {\n margin: 0;\n line-height: 1.7;\n color: var(--text-dim);\n font-size: 15px;\n }\n .details {\n margin-top: 16px;\n font-family: var(--font-mono);\n font-size: 13px;\n color: var(--text-dim);\n white-space: pre-wrap;\n word-break: break-word;\n }\n </style>\n</head>\n<body>\n <main>\n <div class=\"logo\">${LOGO_SVG}</div>\n <h1>${heading}</h1>\n <p>${message}</p>\n ${details ? `<div class=\"details\">${details}</div>` : \"\"}\n </main>\n</body>\n</html>`;\n}\n\nexport function oauthSuccessHtml(message: string): string {\n\treturn renderPage({\n\t\ttitle: \"Authentication successful\",\n\t\theading: \"Authentication successful\",\n\t\tmessage,\n\t});\n}\n\nexport function oauthErrorHtml(message: string, details?: string): string {\n\treturn renderPage({\n\t\ttitle: \"Authentication failed\",\n\t\theading: \"Authentication failed\",\n\t\tmessage,\n\t\tdetails,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;
|
|
2
|
+
function escapeHtml(value) {
|
|
3
|
+
return value
|
|
4
|
+
.replaceAll("&", "&")
|
|
5
|
+
.replaceAll("<", "<")
|
|
6
|
+
.replaceAll(">", ">")
|
|
7
|
+
.replaceAll('"', """)
|
|
8
|
+
.replaceAll("'", "'");
|
|
9
|
+
}
|
|
10
|
+
function renderPage(options) {
|
|
11
|
+
const title = escapeHtml(options.title);
|
|
12
|
+
const heading = escapeHtml(options.heading);
|
|
13
|
+
const message = escapeHtml(options.message);
|
|
14
|
+
const details = options.details ? escapeHtml(options.details) : undefined;
|
|
15
|
+
return `<!doctype html>
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="utf-8" />
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
20
|
+
<title>${title}</title>
|
|
21
|
+
<style>
|
|
22
|
+
:root {
|
|
23
|
+
--text: #fafafa;
|
|
24
|
+
--text-dim: #a1a1aa;
|
|
25
|
+
--page-bg: #09090b;
|
|
26
|
+
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
27
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
28
|
+
}
|
|
29
|
+
* { box-sizing: border-box; }
|
|
30
|
+
html { color-scheme: dark; }
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
padding: 24px;
|
|
38
|
+
background: var(--page-bg);
|
|
39
|
+
color: var(--text);
|
|
40
|
+
font-family: var(--font-sans);
|
|
41
|
+
text-align: center;
|
|
42
|
+
}
|
|
43
|
+
main {
|
|
44
|
+
width: 100%;
|
|
45
|
+
max-width: 560px;
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
}
|
|
51
|
+
.logo {
|
|
52
|
+
width: 72px;
|
|
53
|
+
height: 72px;
|
|
54
|
+
display: block;
|
|
55
|
+
margin-bottom: 24px;
|
|
56
|
+
}
|
|
57
|
+
h1 {
|
|
58
|
+
margin: 0 0 10px;
|
|
59
|
+
font-size: 28px;
|
|
60
|
+
line-height: 1.15;
|
|
61
|
+
font-weight: 650;
|
|
62
|
+
color: var(--text);
|
|
63
|
+
}
|
|
64
|
+
p {
|
|
65
|
+
margin: 0;
|
|
66
|
+
line-height: 1.7;
|
|
67
|
+
color: var(--text-dim);
|
|
68
|
+
font-size: 15px;
|
|
69
|
+
}
|
|
70
|
+
.details {
|
|
71
|
+
margin-top: 16px;
|
|
72
|
+
font-family: var(--font-mono);
|
|
73
|
+
font-size: 13px;
|
|
74
|
+
color: var(--text-dim);
|
|
75
|
+
white-space: pre-wrap;
|
|
76
|
+
word-break: break-word;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
79
|
+
</head>
|
|
80
|
+
<body>
|
|
81
|
+
<main>
|
|
82
|
+
<div class="logo">${LOGO_SVG}</div>
|
|
83
|
+
<h1>${heading}</h1>
|
|
84
|
+
<p>${message}</p>
|
|
85
|
+
${details ? `<div class="details">${details}</div>` : ""}
|
|
86
|
+
</main>
|
|
87
|
+
</body>
|
|
88
|
+
</html>`;
|
|
89
|
+
}
|
|
90
|
+
export function oauthSuccessHtml(message) {
|
|
91
|
+
return renderPage({
|
|
92
|
+
title: "Authentication successful",
|
|
93
|
+
heading: "Authentication successful",
|
|
94
|
+
message,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export function oauthErrorHtml(message, details) {
|
|
98
|
+
return renderPage({
|
|
99
|
+
title: "Authentication failed",
|
|
100
|
+
heading: "Authentication failed",
|
|
101
|
+
message,
|
|
102
|
+
details,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=oauth-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-page.js","sourceRoot":"","sources":["../../../src/utils/oauth/oauth-page.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,uSAAuS,CAAC;AAEzT,SAAS,UAAU,CAAC,KAAa,EAAU;IAC1C,OAAO,KAAK;SACV,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAAA,CAC3B;AAED,SAAS,UAAU,CAAC,OAA8E,EAAU;IAC3G,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE1E,OAAO;;;;;WAKG,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA8DQ,QAAQ;UACtB,OAAO;SACR,OAAO;MACV,OAAO,CAAC,CAAC,CAAC,wBAAwB,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE;;;QAGpD,CAAC;AAAA,CACR;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAU;IACzD,OAAO,UAAU,CAAC;QACjB,KAAK,EAAE,2BAA2B;QAClC,OAAO,EAAE,2BAA2B;QACpC,OAAO;KACP,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,OAAgB,EAAU;IACzE,OAAO,UAAU,CAAC;QACjB,KAAK,EAAE,uBAAuB;QAC9B,OAAO,EAAE,uBAAuB;QAChC,OAAO;QACP,OAAO;KACP,CAAC,CAAC;AAAA,CACH","sourcesContent":["const LOGO_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 800 800\" aria-hidden=\"true\"><path fill=\"#fff\" fill-rule=\"evenodd\" d=\"M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z\"/><path fill=\"#fff\" d=\"M517.36 400 H634.72 V634.72 H517.36 Z\"/></svg>`;\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\")\n\t\t.replaceAll(\"'\", \"'\");\n}\n\nfunction renderPage(options: { title: string; heading: string; message: string; details?: string }): string {\n\tconst title = escapeHtml(options.title);\n\tconst heading = escapeHtml(options.heading);\n\tconst message = escapeHtml(options.message);\n\tconst details = options.details ? escapeHtml(options.details) : undefined;\n\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n <style>\n :root {\n --text: #fafafa;\n --text-dim: #a1a1aa;\n --page-bg: #09090b;\n --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n }\n * { box-sizing: border-box; }\n html { color-scheme: dark; }\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: var(--page-bg);\n color: var(--text);\n font-family: var(--font-sans);\n text-align: center;\n }\n main {\n width: 100%;\n max-width: 560px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n }\n .logo {\n width: 72px;\n height: 72px;\n display: block;\n margin-bottom: 24px;\n }\n h1 {\n margin: 0 0 10px;\n font-size: 28px;\n line-height: 1.15;\n font-weight: 650;\n color: var(--text);\n }\n p {\n margin: 0;\n line-height: 1.7;\n color: var(--text-dim);\n font-size: 15px;\n }\n .details {\n margin-top: 16px;\n font-family: var(--font-mono);\n font-size: 13px;\n color: var(--text-dim);\n white-space: pre-wrap;\n word-break: break-word;\n }\n </style>\n</head>\n<body>\n <main>\n <div class=\"logo\">${LOGO_SVG}</div>\n <h1>${heading}</h1>\n <p>${message}</p>\n ${details ? `<div class=\"details\">${details}</div>` : \"\"}\n </main>\n</body>\n</html>`;\n}\n\nexport function oauthSuccessHtml(message: string): string {\n\treturn renderPage({\n\t\ttitle: \"Authentication successful\",\n\t\theading: \"Authentication successful\",\n\t\tmessage,\n\t});\n}\n\nexport function oauthErrorHtml(message: string, details?: string): string {\n\treturn renderPage({\n\t\ttitle: \"Authentication failed\",\n\t\theading: \"Authentication failed\",\n\t\tmessage,\n\t\tdetails,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex (ChatGPT OAuth) flow
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This module uses Node.js crypto and http for the OAuth callback.
|
|
5
|
+
* It is only intended for CLI use, not browser environments.
|
|
6
|
+
*/
|
|
7
|
+
import type { OAuthCredentials, OAuthPrompt, OAuthProviderInterface } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Login with OpenAI Codex OAuth
|
|
10
|
+
*
|
|
11
|
+
* @param options.onAuth - Called with URL and instructions when auth starts
|
|
12
|
+
* @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)
|
|
13
|
+
* @param options.onProgress - Optional progress messages
|
|
14
|
+
* @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.
|
|
15
|
+
* Races with browser callback - whichever completes first wins.
|
|
16
|
+
* Useful for showing paste input immediately alongside browser flow.
|
|
17
|
+
* @param options.originator - OAuth originator parameter (defaults to "pi")
|
|
18
|
+
*/
|
|
19
|
+
export declare function loginOpenAICodex(options: {
|
|
20
|
+
onAuth: (info: {
|
|
21
|
+
url: string;
|
|
22
|
+
instructions?: string;
|
|
23
|
+
}) => void;
|
|
24
|
+
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
|
25
|
+
onProgress?: (message: string) => void;
|
|
26
|
+
onManualCodeInput?: () => Promise<string>;
|
|
27
|
+
originator?: string;
|
|
28
|
+
}): Promise<OAuthCredentials>;
|
|
29
|
+
/**
|
|
30
|
+
* Refresh OpenAI Codex OAuth token
|
|
31
|
+
*/
|
|
32
|
+
export declare function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials>;
|
|
33
|
+
export declare const openaiCodexOAuthProvider: OAuthProviderInterface;
|
|
34
|
+
//# sourceMappingURL=openai-codex.d.ts.map
|
|
@@ -0,0 +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 = \"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, \"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 \"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\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"]}
|