openai-codex-oauth 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jwt.ts"],"sourcesContent":["/**\n * @file jwt.ts\n *\n * JWT (JSON Web Token) parsing utilities for OpenAI OAuth.\n * These functions decode and extract claims from JWTs without\n * verifying signatures (since we trust the OAuth issuer).\n */\n\nimport type { OpenAIOAuthTokens } from './types';\n\n/**\n * Decodes a Base64URL-encoded string to UTF-8 text.\n * Handles both browser (atob) and Node.js (Buffer) environments.\n */\nfunction decodeBase64Url(value: string): string | undefined {\n try {\n const base64 = value.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n\n if (typeof globalThis.atob === 'function') {\n const binary = globalThis.atob(padded);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n }\n\n return Buffer.from(padded, 'base64').toString('utf8');\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Parse the JSON payload from a JWT without verifying the signature. */\nexport function parseJwtClaims(token: string | undefined): Record<string, unknown> | undefined {\n if (!token?.includes('.')) {\n return undefined;\n }\n\n const parts = token.split('.');\n if (parts.length !== 3 || !parts[1]) {\n return undefined;\n }\n\n const payload = decodeBase64Url(parts[1]);\n if (!payload) {\n return undefined;\n }\n\n try {\n const parsed = JSON.parse(payload) as unknown;\n return isRecord(parsed) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Derive the ChatGPT account id from OpenAI JWT claims when present. */\nexport function deriveAccountId(...tokens: Array<string | undefined>): string | undefined {\n for (const token of tokens) {\n const claims = parseJwtClaims(token);\n const auth = claims?.['https://api.openai.com/auth'];\n\n if (isRecord(auth) && typeof auth.chatgpt_account_id === 'string' && auth.chatgpt_account_id.length > 0) {\n return auth.chatgpt_account_id;\n }\n }\n\n return undefined;\n}\n\n/** Derive JWT expiry as epoch milliseconds from the `exp` claim. */\nexport function deriveExpiresAt(token: string | undefined): number | undefined {\n const claims = parseJwtClaims(token);\n return typeof claims?.exp === 'number' ? claims.exp * 1000 : undefined;\n}\n"],"mappings":";AAcA,SAAS,gBAAgB,OAAmC;AAC1D,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACzD,UAAM,SAAS,SAAS,IAAI,QAAQ,IAAK,OAAO,SAAS,KAAM,CAAC;AAEhE,QAAI,OAAO,WAAW,SAAS,YAAY;AACzC,YAAM,SAAS,WAAW,KAAK,MAAM;AACrC,YAAM,QAAQ,WAAW,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC;AAClE,aAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AAAA,IACvC;AAEA,WAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,MAAM;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAGO,SAAS,eAAe,OAAgE;AAC7F,MAAI,CAAC,OAAO,SAAS,GAAG,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,KAAK,CAAC,MAAM,CAAC,GAAG;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,gBAAgB,MAAM,CAAC,CAAC;AACxC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,mBAAmB,QAAuD;AACxF,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,eAAe,KAAK;AACnC,UAAM,OAAO,SAAS,6BAA6B;AAEnD,QAAI,SAAS,IAAI,KAAK,OAAO,KAAK,uBAAuB,YAAY,KAAK,mBAAmB,SAAS,GAAG;AACvG,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,gBAAgB,OAA+C;AAC7E,QAAM,SAAS,eAAe,KAAK;AACnC,SAAO,OAAO,QAAQ,QAAQ,WAAW,OAAO,MAAM,MAAO;AAC/D;","names":[]}
@@ -0,0 +1,147 @@
1
+ import { d as OpenAIOAuthSettings, F as FetchLike, e as OpenAIOAuthTokens, a as OpenAIDeviceFlowOptions, O as OpenAIDeviceFlow, T as TokenStore, c as OpenAIOAuthProviderSettings } from './types-Bv0Lf2Og.js';
2
+ export { b as OpenAIOAuthError } from './types-Bv0Lf2Og.js';
3
+ export { OpenAIOAuthProxyOptions, createOpenAIOAuthProxy } from './proxy.js';
4
+ import { ProviderV3, LanguageModelV3, EmbeddingModelV3, ImageModelV3 } from '@ai-sdk/provider';
5
+
6
+ /**
7
+ * @file codex-fetch.ts
8
+ *
9
+ * Core module that provides the fetch wrapper for authenticated Codex requests.
10
+ * This is the main entry point for making OAuth-authenticated requests to
11
+ * the OpenAI Codex backend.
12
+ *
13
+ * Key features:
14
+ * - Automatic token management and refresh before expiry
15
+ * - Request/response normalization for Codex API compatibility
16
+ * - Browser proxy URL resolution for cross-origin requests
17
+ * - Account ID derivation from JWT claims
18
+ */
19
+
20
+ /** Default OpenAI Codex backend base URL. */
21
+ declare const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
22
+ /**
23
+ * Normalize an OpenAI Responses request body for the Codex backend.
24
+ *
25
+ * Codex expects requests to be stateless by default, so `store` defaults to
26
+ * `false`. Unsupported `max_output_tokens` is removed.
27
+ */
28
+ declare function normalizeCodexResponsesBody(body: Record<string, unknown>, options?: Pick<OpenAIOAuthSettings, 'instructions' | 'store'>): Record<string, unknown>;
29
+ /**
30
+ * Create a fetch implementation that authenticates OpenAI Responses requests
31
+ * with Codex OAuth credentials.
32
+ *
33
+ * The returned function rewrites `/v1/responses` requests to the Codex backend,
34
+ * injects `Authorization` and `ChatGPT-Account-Id`, and refreshes credentials
35
+ * before expiry when a refresh token is available.
36
+ */
37
+ declare function createCodexOAuthFetch(settings?: OpenAIOAuthSettings): FetchLike;
38
+ /** Create a small Codex client around `createCodexOAuthFetch`. */
39
+ declare function createCodexOAuthClient(settings?: OpenAIOAuthSettings): {
40
+ baseURL: string;
41
+ fetch: typeof globalThis.fetch;
42
+ request: (path: string, init?: RequestInit) => Promise<Response>;
43
+ };
44
+
45
+ /**
46
+ * @file device-flow.ts
47
+ *
48
+ * Implements the OAuth 2.0 Device Authorization Grant flow for OpenAI Codex.
49
+ * This flow is designed for CLI tools and headless applications where
50
+ * the user authorizes on a separate device with a browser.
51
+ *
52
+ * The flow:
53
+ * 1. Client requests a device code from the authorization server
54
+ * 2. User visits the authorization URL and enters the displayed code
55
+ * 3. Client polls for authorization completion
56
+ * 4. On success, client exchanges the authorization grant for tokens
57
+ */
58
+
59
+ /** Default OAuth client ID for Codex/ChatGPT. */
60
+ declare const DEFAULT_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
61
+ /** Default OAuth issuer/authorization server URL. */
62
+ declare const DEFAULT_ISSUER = "https://auth.openai.com";
63
+ /**
64
+ * Start OpenAI's Codex device OAuth flow.
65
+ *
66
+ * The returned flow contains a URL and user code for authorization. Call
67
+ * `flow.complete()` after showing those values to poll for authorization and
68
+ * exchange the grant for OAuth tokens.
69
+ */
70
+ declare function startOpenAIDeviceFlow(options?: OpenAIDeviceFlowOptions): Promise<OpenAIDeviceFlow>;
71
+ /**
72
+ * Refresh OpenAI OAuth credentials with a refresh token.
73
+ *
74
+ * Returns `undefined` when refresh is not possible or the server rejects the
75
+ * refresh request. Callers should treat expired credentials as unusable when
76
+ * this returns `undefined`.
77
+ */
78
+ declare function refreshOpenAITokens({ tokens, fetch, clientId, issuer, tokenUrl, now, }: {
79
+ tokens: OpenAIOAuthTokens;
80
+ fetch: FetchLike;
81
+ clientId?: string;
82
+ issuer?: string;
83
+ tokenUrl?: string;
84
+ now?: () => number;
85
+ }): Promise<OpenAIOAuthTokens | undefined>;
86
+
87
+ /**
88
+ * @file jwt.ts
89
+ *
90
+ * JWT (JSON Web Token) parsing utilities for OpenAI OAuth.
91
+ * These functions decode and extract claims from JWTs without
92
+ * verifying signatures (since we trust the OAuth issuer).
93
+ */
94
+ /** Parse the JSON payload from a JWT without verifying the signature. */
95
+ declare function parseJwtClaims(token: string | undefined): Record<string, unknown> | undefined;
96
+ /** Derive the ChatGPT account id from OpenAI JWT claims when present. */
97
+ declare function deriveAccountId(...tokens: Array<string | undefined>): string | undefined;
98
+ /** Derive JWT expiry as epoch milliseconds from the `exp` claim. */
99
+ declare function deriveExpiresAt(token: string | undefined): number | undefined;
100
+
101
+ /**
102
+ * @file memory-token-store.ts
103
+ *
104
+ * In-memory token storage implementation.
105
+ * Useful for testing and short-lived processes that don't need
106
+ * persistent token storage.
107
+ */
108
+
109
+ /**
110
+ * Create an in-memory token store.
111
+ *
112
+ * This is useful for tests and short-lived scripts. It does not persist tokens
113
+ * across process restarts.
114
+ */
115
+ declare function createMemoryTokenStore(initial?: OpenAIOAuthTokens): TokenStore;
116
+
117
+ /**
118
+ * @file provider.ts
119
+ *
120
+ * Vercel AI SDK provider implementation for OpenAI Codex OAuth.
121
+ * This module creates a provider that can be used with the AI SDK's
122
+ * generateText, streamText, and other AI functions.
123
+ *
124
+ * The provider wraps the Codex OAuth fetch implementation and adapts it
125
+ * to the AI SDK provider interface.
126
+ */
127
+
128
+ /** Model ID type for Codex OAuth models. */
129
+ type OpenAIOAuthModelId = string;
130
+ /**
131
+ * AI SDK provider interface for OpenAI Codex OAuth-backed language models.
132
+ * Provides language model, embedding model, and image model factories.
133
+ */
134
+ interface OpenAIOAuthProvider extends ProviderV3 {
135
+ (modelId: OpenAIOAuthModelId): LanguageModelV3;
136
+ languageModel(modelId: OpenAIOAuthModelId): LanguageModelV3;
137
+ responses(modelId: OpenAIOAuthModelId): LanguageModelV3;
138
+ embeddingModel(modelId: string): EmbeddingModelV3;
139
+ imageModel(modelId: string): ImageModelV3;
140
+ }
141
+ /**
142
+ * Create a Vercel AI SDK provider that sends Responses API calls through the
143
+ * OpenAI Codex OAuth backend.
144
+ */
145
+ declare function createOpenAIOAuth(settings?: OpenAIOAuthProviderSettings): OpenAIOAuthProvider;
146
+
147
+ export { DEFAULT_CLIENT_ID, DEFAULT_CODEX_BASE_URL, DEFAULT_ISSUER, FetchLike, OpenAIDeviceFlow, OpenAIDeviceFlowOptions, type OpenAIOAuthModelId, type OpenAIOAuthProvider, OpenAIOAuthProviderSettings, OpenAIOAuthSettings, OpenAIOAuthTokens, TokenStore, createCodexOAuthClient, createCodexOAuthFetch, createMemoryTokenStore, createOpenAIOAuth, deriveAccountId, deriveExpiresAt, normalizeCodexResponsesBody, parseJwtClaims, refreshOpenAITokens, startOpenAIDeviceFlow };
package/dist/index.js ADDED
@@ -0,0 +1,242 @@
1
+ import {
2
+ DEFAULT_CLIENT_ID,
3
+ DEFAULT_CODEX_BASE_URL,
4
+ DEFAULT_ISSUER,
5
+ OpenAIOAuthError,
6
+ createCodexOAuthClient,
7
+ createCodexOAuthFetch,
8
+ createMemoryTokenStore,
9
+ createOpenAIOAuthProxy,
10
+ normalizeCodexResponsesBody,
11
+ refreshOpenAITokens,
12
+ startOpenAIDeviceFlow
13
+ } from "./chunk-DXYYRLYI.js";
14
+ import {
15
+ deriveAccountId,
16
+ deriveExpiresAt,
17
+ parseJwtClaims
18
+ } from "./chunk-SCKIRN2D.js";
19
+
20
+ // src/provider.ts
21
+ import { createOpenAI } from "@ai-sdk/openai";
22
+ import { NoSuchModelError } from "@ai-sdk/provider";
23
+
24
+ // src/stream-to-generate.ts
25
+ var emptyUsage = () => ({
26
+ inputTokens: {
27
+ total: void 0,
28
+ noCache: void 0,
29
+ cacheRead: void 0,
30
+ cacheWrite: void 0
31
+ },
32
+ outputTokens: {
33
+ total: void 0,
34
+ text: void 0,
35
+ reasoning: void 0
36
+ }
37
+ });
38
+ function mergeProviderMetadata(left, right) {
39
+ if (left == null) return right;
40
+ if (right == null) return left;
41
+ const merged = { ...left };
42
+ for (const [provider, value] of Object.entries(right)) {
43
+ const existing = merged[provider];
44
+ merged[provider] = existing == null ? value : { ...existing, ...value };
45
+ }
46
+ return merged;
47
+ }
48
+ async function collectStreamGenerateResult(streamResult) {
49
+ const reader = streamResult.stream.getReader();
50
+ const content = [];
51
+ const warnings = [];
52
+ const activeTextById = /* @__PURE__ */ new Map();
53
+ const activeReasoningById = /* @__PURE__ */ new Map();
54
+ let finishReason = { unified: "other", raw: void 0 };
55
+ let usage = emptyUsage();
56
+ let providerMetadata;
57
+ let responseMetadata;
58
+ try {
59
+ while (true) {
60
+ const { value: part, done } = await reader.read();
61
+ if (done) {
62
+ break;
63
+ }
64
+ switch (part.type) {
65
+ case "stream-start":
66
+ warnings.push(...part.warnings);
67
+ break;
68
+ case "response-metadata":
69
+ responseMetadata = {
70
+ id: part.id,
71
+ timestamp: part.timestamp,
72
+ modelId: part.modelId
73
+ };
74
+ break;
75
+ case "text-start": {
76
+ const textPart = {
77
+ type: "text",
78
+ text: "",
79
+ providerMetadata: part.providerMetadata
80
+ };
81
+ content.push(textPart);
82
+ activeTextById.set(part.id, textPart);
83
+ break;
84
+ }
85
+ case "text-delta": {
86
+ const existing = activeTextById.get(part.id);
87
+ if (existing) {
88
+ existing.text += part.delta;
89
+ existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);
90
+ } else {
91
+ const textPart = {
92
+ type: "text",
93
+ text: part.delta,
94
+ providerMetadata: part.providerMetadata
95
+ };
96
+ content.push(textPart);
97
+ activeTextById.set(part.id, textPart);
98
+ }
99
+ break;
100
+ }
101
+ case "text-end": {
102
+ const existing = activeTextById.get(part.id);
103
+ if (existing) {
104
+ existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);
105
+ activeTextById.delete(part.id);
106
+ }
107
+ break;
108
+ }
109
+ case "reasoning-start": {
110
+ const reasoningPart = {
111
+ type: "reasoning",
112
+ text: "",
113
+ providerMetadata: part.providerMetadata
114
+ };
115
+ content.push(reasoningPart);
116
+ activeReasoningById.set(part.id, reasoningPart);
117
+ break;
118
+ }
119
+ case "reasoning-delta": {
120
+ const existing = activeReasoningById.get(part.id);
121
+ if (existing) {
122
+ existing.text += part.delta;
123
+ existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);
124
+ } else {
125
+ const reasoningPart = {
126
+ type: "reasoning",
127
+ text: part.delta,
128
+ providerMetadata: part.providerMetadata
129
+ };
130
+ content.push(reasoningPart);
131
+ activeReasoningById.set(part.id, reasoningPart);
132
+ }
133
+ break;
134
+ }
135
+ case "reasoning-end": {
136
+ const existing = activeReasoningById.get(part.id);
137
+ if (existing) {
138
+ existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);
139
+ activeReasoningById.delete(part.id);
140
+ }
141
+ break;
142
+ }
143
+ case "tool-call":
144
+ case "tool-result":
145
+ case "tool-approval-request":
146
+ case "file":
147
+ case "source":
148
+ content.push(part);
149
+ break;
150
+ case "finish":
151
+ finishReason = part.finishReason;
152
+ usage = part.usage;
153
+ providerMetadata = part.providerMetadata;
154
+ break;
155
+ case "error":
156
+ throw part.error instanceof Error ? part.error : new Error("Streaming request failed.", { cause: part.error });
157
+ case "tool-input-start":
158
+ case "tool-input-delta":
159
+ case "tool-input-end":
160
+ case "raw":
161
+ break;
162
+ default:
163
+ part;
164
+ }
165
+ }
166
+ } finally {
167
+ reader.releaseLock();
168
+ }
169
+ return {
170
+ content,
171
+ finishReason,
172
+ usage,
173
+ providerMetadata,
174
+ request: streamResult.request,
175
+ response: responseMetadata == null && streamResult.response?.headers == null ? void 0 : {
176
+ ...responseMetadata ?? {},
177
+ ...streamResult.response?.headers == null ? {} : { headers: streamResult.response.headers }
178
+ },
179
+ warnings
180
+ };
181
+ }
182
+ var StreamOnlyLanguageModel = class {
183
+ constructor(inner) {
184
+ this.inner = inner;
185
+ this.provider = inner.provider;
186
+ this.modelId = inner.modelId;
187
+ this.supportedUrls = inner.supportedUrls;
188
+ }
189
+ inner;
190
+ specificationVersion = "v3";
191
+ provider;
192
+ modelId;
193
+ supportedUrls;
194
+ async doGenerate(options) {
195
+ return collectStreamGenerateResult(await this.inner.doStream(options));
196
+ }
197
+ doStream(options) {
198
+ return this.inner.doStream(options);
199
+ }
200
+ };
201
+
202
+ // src/provider.ts
203
+ function createOpenAIOAuth(settings = {}) {
204
+ const providerName = settings.name ?? "openai-oauth";
205
+ const oauthFetch = createCodexOAuthFetch(settings);
206
+ const openai = createOpenAI({
207
+ apiKey: "oauth",
208
+ baseURL: settings.baseURL ?? DEFAULT_CODEX_BASE_URL,
209
+ name: providerName,
210
+ fetch: oauthFetch
211
+ });
212
+ const createLanguageModel = (modelId) => new StreamOnlyLanguageModel(openai.responses(modelId));
213
+ const provider = (modelId) => createLanguageModel(modelId);
214
+ provider.specificationVersion = "v3";
215
+ provider.languageModel = createLanguageModel;
216
+ provider.responses = createLanguageModel;
217
+ provider.embeddingModel = (modelId) => {
218
+ throw new NoSuchModelError({ modelId, modelType: "embeddingModel" });
219
+ };
220
+ provider.imageModel = (modelId) => {
221
+ throw new NoSuchModelError({ modelId, modelType: "imageModel" });
222
+ };
223
+ return provider;
224
+ }
225
+ export {
226
+ DEFAULT_CLIENT_ID,
227
+ DEFAULT_CODEX_BASE_URL,
228
+ DEFAULT_ISSUER,
229
+ OpenAIOAuthError,
230
+ createCodexOAuthClient,
231
+ createCodexOAuthFetch,
232
+ createMemoryTokenStore,
233
+ createOpenAIOAuth,
234
+ createOpenAIOAuthProxy,
235
+ deriveAccountId,
236
+ deriveExpiresAt,
237
+ normalizeCodexResponsesBody,
238
+ parseJwtClaims,
239
+ refreshOpenAITokens,
240
+ startOpenAIDeviceFlow
241
+ };
242
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.ts","../src/stream-to-generate.ts"],"sourcesContent":["/**\n * @file provider.ts\n *\n * Vercel AI SDK provider implementation for OpenAI Codex OAuth.\n * This module creates a provider that can be used with the AI SDK's\n * generateText, streamText, and other AI functions.\n *\n * The provider wraps the Codex OAuth fetch implementation and adapts it\n * to the AI SDK provider interface.\n */\n\nimport { createOpenAI } from '@ai-sdk/openai';\nimport type { EmbeddingModelV3, ImageModelV3, LanguageModelV3, ProviderV3 } from '@ai-sdk/provider';\nimport { NoSuchModelError } from '@ai-sdk/provider';\nimport { createCodexOAuthFetch, DEFAULT_CODEX_BASE_URL } from './codex-fetch';\nimport { StreamOnlyLanguageModel } from './stream-to-generate';\nimport type { OpenAIOAuthProviderSettings } from './types';\n\n/** Model ID type for Codex OAuth models. */\nexport type OpenAIOAuthModelId = string;\n\n/**\n * AI SDK provider interface for OpenAI Codex OAuth-backed language models.\n * Provides language model, embedding model, and image model factories.\n */\nexport interface OpenAIOAuthProvider extends ProviderV3 {\n (modelId: OpenAIOAuthModelId): LanguageModelV3;\n languageModel(modelId: OpenAIOAuthModelId): LanguageModelV3;\n responses(modelId: OpenAIOAuthModelId): LanguageModelV3;\n embeddingModel(modelId: string): EmbeddingModelV3;\n imageModel(modelId: string): ImageModelV3;\n}\n\n/**\n * Create a Vercel AI SDK provider that sends Responses API calls through the\n * OpenAI Codex OAuth backend.\n */\nexport function createOpenAIOAuth(settings: OpenAIOAuthProviderSettings = {}): OpenAIOAuthProvider {\n const providerName = settings.name ?? 'openai-oauth';\n const oauthFetch = createCodexOAuthFetch(settings);\n const openai = createOpenAI({\n apiKey: 'oauth',\n baseURL: settings.baseURL ?? DEFAULT_CODEX_BASE_URL,\n name: providerName,\n fetch: oauthFetch,\n });\n\n const createLanguageModel = (modelId: OpenAIOAuthModelId) => new StreamOnlyLanguageModel(openai.responses(modelId as never));\n const provider = (modelId: OpenAIOAuthModelId) => createLanguageModel(modelId);\n\n provider.specificationVersion = 'v3' as const;\n provider.languageModel = createLanguageModel;\n provider.responses = createLanguageModel;\n provider.embeddingModel = (modelId: string) => {\n throw new NoSuchModelError({ modelId, modelType: 'embeddingModel' });\n };\n provider.imageModel = (modelId: string) => {\n throw new NoSuchModelError({ modelId, modelType: 'imageModel' });\n };\n\n return provider as OpenAIOAuthProvider;\n}\n","/**\n * @file stream-to-generate.ts\n *\n * Provides utilities for converting streaming language model results\n * into non-streaming results. This is useful when the underlying provider\n * only supports streaming but the application needs synchronous generation.\n *\n * The AI SDK v3 provider interface requires both doGenerate and doStream,\n * but some backends (like Codex) only implement streaming. This module\n * bridges that gap by consuming the stream and assembling a complete result.\n */\n\nimport type {\n LanguageModelV3,\n LanguageModelV3Content,\n LanguageModelV3FinishReason,\n LanguageModelV3GenerateResult,\n LanguageModelV3StreamResult,\n LanguageModelV3Usage,\n SharedV3ProviderMetadata,\n SharedV3Warning,\n} from '@ai-sdk/provider';\n\n/** Creates an empty usage object with all fields undefined. */\nconst emptyUsage = (): LanguageModelV3Usage => ({\n inputTokens: {\n total: undefined,\n noCache: undefined,\n cacheRead: undefined,\n cacheWrite: undefined,\n },\n outputTokens: {\n total: undefined,\n text: undefined,\n reasoning: undefined,\n },\n});\n\n/**\n * Merges two provider metadata objects, with right values taking precedence\n * over left values when both are defined.\n */\nfunction mergeProviderMetadata(\n left: SharedV3ProviderMetadata | undefined,\n right: SharedV3ProviderMetadata | undefined,\n): SharedV3ProviderMetadata | undefined {\n if (left == null) return right;\n if (right == null) return left;\n\n const merged: SharedV3ProviderMetadata = { ...left };\n for (const [provider, value] of Object.entries(right)) {\n const existing = merged[provider];\n merged[provider] = existing == null ? value : { ...existing, ...value };\n }\n\n return merged;\n}\n\n/** Collect a LanguageModel stream result into a non-streaming generate result. */\nexport async function collectStreamGenerateResult(\n streamResult: LanguageModelV3StreamResult,\n): Promise<LanguageModelV3GenerateResult> {\n const reader = streamResult.stream.getReader();\n const content: LanguageModelV3Content[] = [];\n const warnings: SharedV3Warning[] = [];\n const activeTextById = new Map<string, Extract<LanguageModelV3Content, { type: 'text' }>>();\n const activeReasoningById = new Map<string, Extract<LanguageModelV3Content, { type: 'reasoning' }>>();\n\n let finishReason: LanguageModelV3FinishReason = { unified: 'other', raw: undefined };\n let usage: LanguageModelV3Usage = emptyUsage();\n let providerMetadata: SharedV3ProviderMetadata | undefined;\n let responseMetadata: LanguageModelV3GenerateResult['response'];\n\n try {\n while (true) {\n const { value: part, done } = await reader.read();\n if (done) {\n break;\n }\n\n switch (part.type) {\n case 'stream-start':\n warnings.push(...part.warnings);\n break;\n case 'response-metadata':\n responseMetadata = {\n id: part.id,\n timestamp: part.timestamp,\n modelId: part.modelId,\n };\n break;\n case 'text-start': {\n const textPart: Extract<LanguageModelV3Content, { type: 'text' }> = {\n type: 'text',\n text: '',\n providerMetadata: part.providerMetadata,\n };\n content.push(textPart);\n activeTextById.set(part.id, textPart);\n break;\n }\n case 'text-delta': {\n const existing = activeTextById.get(part.id);\n if (existing) {\n existing.text += part.delta;\n existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);\n } else {\n const textPart: Extract<LanguageModelV3Content, { type: 'text' }> = {\n type: 'text',\n text: part.delta,\n providerMetadata: part.providerMetadata,\n };\n content.push(textPart);\n activeTextById.set(part.id, textPart);\n }\n break;\n }\n case 'text-end': {\n const existing = activeTextById.get(part.id);\n if (existing) {\n existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);\n activeTextById.delete(part.id);\n }\n break;\n }\n case 'reasoning-start': {\n const reasoningPart: Extract<LanguageModelV3Content, { type: 'reasoning' }> = {\n type: 'reasoning',\n text: '',\n providerMetadata: part.providerMetadata,\n };\n content.push(reasoningPart);\n activeReasoningById.set(part.id, reasoningPart);\n break;\n }\n case 'reasoning-delta': {\n const existing = activeReasoningById.get(part.id);\n if (existing) {\n existing.text += part.delta;\n existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);\n } else {\n const reasoningPart: Extract<LanguageModelV3Content, { type: 'reasoning' }> = {\n type: 'reasoning',\n text: part.delta,\n providerMetadata: part.providerMetadata,\n };\n content.push(reasoningPart);\n activeReasoningById.set(part.id, reasoningPart);\n }\n break;\n }\n case 'reasoning-end': {\n const existing = activeReasoningById.get(part.id);\n if (existing) {\n existing.providerMetadata = mergeProviderMetadata(existing.providerMetadata, part.providerMetadata);\n activeReasoningById.delete(part.id);\n }\n break;\n }\n case 'tool-call':\n case 'tool-result':\n case 'tool-approval-request':\n case 'file':\n case 'source':\n content.push(part);\n break;\n case 'finish':\n finishReason = part.finishReason;\n usage = part.usage;\n providerMetadata = part.providerMetadata;\n break;\n case 'error':\n throw part.error instanceof Error ? part.error : new Error('Streaming request failed.', { cause: part.error });\n case 'tool-input-start':\n case 'tool-input-delta':\n case 'tool-input-end':\n case 'raw':\n break;\n default:\n part satisfies never;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n return {\n content,\n finishReason,\n usage,\n providerMetadata,\n request: streamResult.request,\n response: responseMetadata == null && streamResult.response?.headers == null\n ? undefined\n : {\n ...(responseMetadata ?? {}),\n ...(streamResult.response?.headers == null ? {} : { headers: streamResult.response.headers }),\n },\n warnings,\n };\n}\n\n/** Language model wrapper that implements `doGenerate` by consuming `doStream`. */\nexport class StreamOnlyLanguageModel implements LanguageModelV3 {\n readonly specificationVersion = 'v3';\n readonly provider: string;\n readonly modelId: string;\n readonly supportedUrls: LanguageModelV3['supportedUrls'];\n\n constructor(private readonly inner: LanguageModelV3) {\n this.provider = inner.provider;\n this.modelId = inner.modelId;\n this.supportedUrls = inner.supportedUrls;\n }\n\n async doGenerate(options: Parameters<LanguageModelV3['doGenerate']>[0]): Promise<LanguageModelV3GenerateResult> {\n return collectStreamGenerateResult(await this.inner.doStream(options));\n }\n\n doStream(options: Parameters<LanguageModelV3['doStream']>[0]) {\n return this.inner.doStream(options);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,38 @@
1
+ import { T as TokenStore } from '../types-Bv0Lf2Og.js';
2
+
3
+ /**
4
+ * @file auth-file-store.ts
5
+ *
6
+ * Node.js-specific token store implementation that reads and writes
7
+ * Codex-compatible auth.json files. This is the recommended storage
8
+ * for desktop CLI applications using Codex.
9
+ *
10
+ * The store looks for auth files in multiple standard locations:
11
+ * - $CHATGPT_LOCAL_HOME/auth.json
12
+ * - $CODEX_HOME/auth.json
13
+ * - ~/.chatgpt-local/auth.json
14
+ * - ~/.codex/auth.json
15
+ *
16
+ * When writing, it prefers the environment variable locations or
17
+ * defaults to ~/.codex/auth.json with mode 0600 for security.
18
+ */
19
+
20
+ type CodexAuthFileStoreOptions = {
21
+ /** Explicit auth file path. When omitted, common Codex auth locations are searched. */
22
+ authFilePath?: string;
23
+ };
24
+ /**
25
+ * Returns candidate paths to search for auth.json files.
26
+ * If authFilePath is explicitly provided, returns just that path.
27
+ * Otherwise returns all standard candidate paths based on environment variables.
28
+ */
29
+ declare function resolveAuthFileCandidates(authFilePath?: string): string[];
30
+ /**
31
+ * Create a Node token store backed by Codex-compatible `auth.json` files.
32
+ *
33
+ * The store reads existing Codex/ChatGPT local auth files and writes refreshed
34
+ * tokens back with file mode `0600` where supported by the OS.
35
+ */
36
+ declare function createCodexAuthFileStore(options?: CodexAuthFileStoreOptions): TokenStore;
37
+
38
+ export { type CodexAuthFileStoreOptions, createCodexAuthFileStore, resolveAuthFileCandidates };
@@ -0,0 +1,106 @@
1
+ import {
2
+ deriveAccountId,
3
+ deriveExpiresAt
4
+ } from "../chunk-SCKIRN2D.js";
5
+
6
+ // src/node/auth-file-store.ts
7
+ import { promises as fs } from "fs";
8
+ import os from "os";
9
+ import path from "path";
10
+ var AUTH_FILENAME = "auth.json";
11
+ function unique(values) {
12
+ return [...new Set(values)];
13
+ }
14
+ function resolveAuthFileCandidates(authFilePath) {
15
+ if (authFilePath) {
16
+ return [authFilePath];
17
+ }
18
+ const chatgptHome = process.env.CHATGPT_LOCAL_HOME;
19
+ const codexHome = process.env.CODEX_HOME;
20
+ return unique(
21
+ [
22
+ chatgptHome ? path.join(chatgptHome, AUTH_FILENAME) : void 0,
23
+ codexHome ? path.join(codexHome, AUTH_FILENAME) : void 0,
24
+ path.join(os.homedir(), ".chatgpt-local", AUTH_FILENAME),
25
+ path.join(os.homedir(), ".codex", AUTH_FILENAME)
26
+ ].filter((value) => Boolean(value))
27
+ );
28
+ }
29
+ function resolveWritePath(authFilePath) {
30
+ if (authFilePath) {
31
+ return authFilePath;
32
+ }
33
+ if (process.env.CHATGPT_LOCAL_HOME) {
34
+ return path.join(process.env.CHATGPT_LOCAL_HOME, AUTH_FILENAME);
35
+ }
36
+ if (process.env.CODEX_HOME) {
37
+ return path.join(process.env.CODEX_HOME, AUTH_FILENAME);
38
+ }
39
+ return path.join(os.homedir(), ".codex", AUTH_FILENAME);
40
+ }
41
+ async function readAuthFile(candidates) {
42
+ for (const candidate of candidates) {
43
+ try {
44
+ const raw = await fs.readFile(candidate, "utf8");
45
+ const parsed = JSON.parse(raw);
46
+ return { filePath: candidate, data: parsed };
47
+ } catch {
48
+ }
49
+ }
50
+ return {};
51
+ }
52
+ function toTokens(data) {
53
+ const accessToken = data?.tokens?.access_token;
54
+ if (!accessToken) {
55
+ return void 0;
56
+ }
57
+ const idToken = data?.tokens?.id_token;
58
+ return {
59
+ accessToken,
60
+ refreshToken: data?.tokens?.refresh_token,
61
+ idToken,
62
+ accountId: data?.tokens?.account_id ?? deriveAccountId(idToken, accessToken),
63
+ expiresAt: deriveExpiresAt(accessToken)
64
+ };
65
+ }
66
+ async function writeAuthFile(filePath, data) {
67
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
68
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), {
69
+ encoding: "utf8",
70
+ mode: 384
71
+ });
72
+ }
73
+ function createCodexAuthFileStore(options = {}) {
74
+ let sourcePath;
75
+ let lastData = {};
76
+ return {
77
+ async load() {
78
+ const result = await readAuthFile(resolveAuthFileCandidates(options.authFilePath));
79
+ sourcePath = result.filePath;
80
+ lastData = result.data ?? {};
81
+ return toTokens(result.data);
82
+ },
83
+ async save(tokens) {
84
+ const filePath = sourcePath ?? resolveWritePath(options.authFilePath);
85
+ const data = {
86
+ ...lastData,
87
+ tokens: {
88
+ ...lastData.tokens ?? {},
89
+ access_token: tokens.accessToken,
90
+ refresh_token: tokens.refreshToken,
91
+ id_token: tokens.idToken,
92
+ account_id: tokens.accountId
93
+ },
94
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString()
95
+ };
96
+ await writeAuthFile(filePath, data);
97
+ sourcePath = filePath;
98
+ lastData = data;
99
+ }
100
+ };
101
+ }
102
+ export {
103
+ createCodexAuthFileStore,
104
+ resolveAuthFileCandidates
105
+ };
106
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/node/auth-file-store.ts"],"sourcesContent":["/**\n * @file auth-file-store.ts\n *\n * Node.js-specific token store implementation that reads and writes\n * Codex-compatible auth.json files. This is the recommended storage\n * for desktop CLI applications using Codex.\n *\n * The store looks for auth files in multiple standard locations:\n * - $CHATGPT_LOCAL_HOME/auth.json\n * - $CODEX_HOME/auth.json\n * - ~/.chatgpt-local/auth.json\n * - ~/.codex/auth.json\n *\n * When writing, it prefers the environment variable locations or\n * defaults to ~/.codex/auth.json with mode 0600 for security.\n */\n\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { deriveAccountId, deriveExpiresAt } from '../jwt';\nimport type { OpenAIOAuthTokens, TokenStore } from '../types';\n\n/** Standard filename for Codex/ChatGPT local auth files. */\nconst AUTH_FILENAME = 'auth.json';\n\n/**\n * Shape of the auth.json file stored by Codex/ChatGPT local.\n */\ntype StoredAuthFile = {\n OPENAI_API_KEY?: string;\n tokens?: {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n account_id?: string;\n };\n last_refresh?: string;\n};\n\nexport type CodexAuthFileStoreOptions = {\n /** Explicit auth file path. When omitted, common Codex auth locations are searched. */\n authFilePath?: string;\n};\n\n/** Returns unique values from an array by converting to a Set and back. */\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n\n/**\n * Returns candidate paths to search for auth.json files.\n * If authFilePath is explicitly provided, returns just that path.\n * Otherwise returns all standard candidate paths based on environment variables.\n */\nexport function resolveAuthFileCandidates(authFilePath?: string): string[] {\n if (authFilePath) {\n return [authFilePath];\n }\n\n const chatgptHome = process.env.CHATGPT_LOCAL_HOME;\n const codexHome = process.env.CODEX_HOME;\n\n return unique(\n [\n chatgptHome ? path.join(chatgptHome, AUTH_FILENAME) : undefined,\n codexHome ? path.join(codexHome, AUTH_FILENAME) : undefined,\n path.join(os.homedir(), '.chatgpt-local', AUTH_FILENAME),\n path.join(os.homedir(), '.codex', AUTH_FILENAME),\n ].filter((value): value is string => Boolean(value)),\n );\n}\n\n/**\n * Resolves the path where auth tokens should be written.\n * Prefers explicit path, then environment variables, then defaults to ~/.codex/auth.json\n */\nfunction resolveWritePath(authFilePath?: string): string {\n if (authFilePath) {\n return authFilePath;\n }\n\n if (process.env.CHATGPT_LOCAL_HOME) {\n return path.join(process.env.CHATGPT_LOCAL_HOME, AUTH_FILENAME);\n }\n\n if (process.env.CODEX_HOME) {\n return path.join(process.env.CODEX_HOME, AUTH_FILENAME);\n }\n\n return path.join(os.homedir(), '.codex', AUTH_FILENAME);\n}\n\n/**\n * Attempts to read and parse an auth.json file from the list of candidates.\n * Returns the first successfully parsed file's path and data.\n */\nasync function readAuthFile(candidates: string[]): Promise<{ filePath?: string; data?: StoredAuthFile }> {\n for (const candidate of candidates) {\n try {\n const raw = await fs.readFile(candidate, 'utf8');\n const parsed = JSON.parse(raw) as StoredAuthFile;\n return { filePath: candidate, data: parsed };\n } catch {\n }\n }\n\n return {};\n}\n\n/**\n * Converts stored auth file format to OpenAIOAuthTokens format.\n * Derives account ID and expiry from JWT claims if not explicitly stored.\n */\nfunction toTokens(data: StoredAuthFile | undefined): OpenAIOAuthTokens | undefined {\n const accessToken = data?.tokens?.access_token;\n if (!accessToken) {\n return undefined;\n }\n\n const idToken = data?.tokens?.id_token;\n return {\n accessToken,\n refreshToken: data?.tokens?.refresh_token,\n idToken,\n accountId: data?.tokens?.account_id ?? deriveAccountId(idToken, accessToken),\n expiresAt: deriveExpiresAt(accessToken),\n };\n}\n\n/**\n * Writes auth data to a file, creating parent directories if needed.\n * Sets file mode to 0600 (owner read/write only) for security.\n */\nasync function writeAuthFile(filePath: string, data: StoredAuthFile): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, JSON.stringify(data, null, 2), {\n encoding: 'utf8',\n mode: 0o600,\n });\n}\n\n/**\n * Create a Node token store backed by Codex-compatible `auth.json` files.\n *\n * The store reads existing Codex/ChatGPT local auth files and writes refreshed\n * tokens back with file mode `0600` where supported by the OS.\n */\nexport function createCodexAuthFileStore(options: CodexAuthFileStoreOptions = {}): TokenStore {\n let sourcePath: string | undefined;\n let lastData: StoredAuthFile = {};\n\n return {\n async load() {\n const result = await readAuthFile(resolveAuthFileCandidates(options.authFilePath));\n sourcePath = result.filePath;\n lastData = result.data ?? {};\n return toTokens(result.data);\n },\n async save(tokens) {\n const filePath = sourcePath ?? resolveWritePath(options.authFilePath);\n const data: StoredAuthFile = {\n ...lastData,\n tokens: {\n ...(lastData.tokens ?? {}),\n access_token: tokens.accessToken,\n refresh_token: tokens.refreshToken,\n id_token: tokens.idToken,\n account_id: tokens.accountId,\n },\n last_refresh: new Date().toISOString(),\n };\n\n await writeAuthFile(filePath, data);\n sourcePath = filePath;\n lastData = data;\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,24 @@
1
+ import { d as OpenAIOAuthSettings, F as FetchLike } from './types-Bv0Lf2Og.js';
2
+
3
+ /**
4
+ * @file proxy.ts
5
+ *
6
+ * Server-side request handlers for proxying OpenAI Codex OAuth requests.
7
+ * This module provides framework-agnostic handlers that can be used in
8
+ * various server environments (Node.js, Deno, Cloudflare Workers, etc.)
9
+ * to enable browser clients to make authenticated Codex requests.
10
+ *
11
+ * The proxy validates OAuth credentials (Authorization header and account ID),
12
+ * rewrites requests to the Codex backend, and passes through responses.
13
+ */
14
+
15
+ type OpenAIOAuthProxyOptions = Pick<OpenAIOAuthSettings, 'instructions' | 'originator' | 'store'> & {
16
+ fetch?: FetchLike;
17
+ baseURL?: string;
18
+ };
19
+ /** Create framework-agnostic server handlers for browser-safe Codex OAuth proxying. */
20
+ declare function createOpenAIOAuthProxy(options?: OpenAIOAuthProxyOptions): {
21
+ responses(request: Request): Promise<Response>;
22
+ };
23
+
24
+ export { type OpenAIOAuthProxyOptions, createOpenAIOAuthProxy };
package/dist/proxy.js ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ createOpenAIOAuthProxy
3
+ } from "./chunk-DXYYRLYI.js";
4
+ import "./chunk-SCKIRN2D.js";
5
+ export {
6
+ createOpenAIOAuthProxy
7
+ };
8
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}