@alvin0/ai-agent-sdk-provider-codex 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alvin0 (chaulamdinhai) <chaulamdinhai@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # @alvin0/ai-agent-sdk-provider-codex
2
+
3
+ Runtime: **Universal** (Edge/Worker, browser, Deno, Bun, and Node with an injected auth store).
4
+
5
+ ```sh
6
+ pnpm add @alvin0/ai-agent-sdk-core @alvin0/ai-agent-sdk-provider-codex
7
+ ```
8
+
9
+ Universal Codex adapter, OAuth flows, memory/custom auth stores, and transactional provider plugin. A `CodexAuthStore` must be injected; filesystem and environment defaults belong to the Node auth package.
10
+
11
+ ```ts
12
+ import { ModelRegistry } from '@alvin0/ai-agent-sdk-core'
13
+ import { codexPlugin } from '@alvin0/ai-agent-sdk-provider-codex'
14
+
15
+ const registry = new ModelRegistry()
16
+ registry.install(codexPlugin({ authStore: mySecretManagerStore }))
17
+ ```
18
+
19
+ Use `codexAdapter()` for manual route registration. The store contract is
20
+ Universal; a browser, Worker, secret manager, or Node package owns persistence.
21
+ Credential and catalog observation excludes OAuth tokens, account details, store
22
+ locations, and raw authentication errors.
23
+
24
+ Composition: `runtime.providers`. Lifecycle: `inert-runtime-owned-registration`;
25
+ the runtime owns registration while the injected credential store remains
26
+ caller-owned.
@@ -0,0 +1,280 @@
1
+ import { AgentSdkError, ModelProviderPlugin, RetryPolicyConfig } from "@alvin0/ai-agent-sdk-core";
2
+ import { ComposableModelProviderPlugin, CredentialStore, ModelTarget } from "@alvin0/ai-agent-sdk-core/provider";
3
+ import { HttpModelAdapter, ProviderCatalogModel, ProviderRequestLogger } from "@alvin0/ai-agent-sdk-provider-http";
4
+ import { ResponsesDialect, openAiResponsesProtocol } from "@alvin0/ai-agent-sdk-protocol-responses";
5
+ //#region src/common/store-types.d.ts
6
+ /** OAuth tokens as stored by Codex authentication. */
7
+ interface CodexTokens {
8
+ id_token: string;
9
+ access_token: string;
10
+ refresh_token: string;
11
+ account_id?: string | null;
12
+ }
13
+ /** Persisted Codex authentication document. */
14
+ interface CodexAuthFile {
15
+ auth_mode?: string;
16
+ OPENAI_API_KEY?: string | null;
17
+ tokens?: CodexTokens | null;
18
+ last_refresh?: string | null;
19
+ }
20
+ /** @deprecated Marker-free storage contract retained for compatibility. */
21
+ interface CodexAuthStore {
22
+ readonly location: string;
23
+ read(): Promise<CodexAuthFile | undefined>;
24
+ write(file: CodexAuthFile): Promise<void>;
25
+ }
26
+ /** Revision-aware credential storage used by normal runtime composition. */
27
+ type CodexCredentialStore = CredentialStore<CodexAuthFile>;
28
+ //#endregion
29
+ //#region src/auth.d.ts
30
+ /** An in-memory {@link CodexAuthStore}, for tests. */
31
+ declare function memoryCodexAuthStore(initial?: CodexAuthFile): CodexAuthStore;
32
+ /** In-memory compare-and-swap store for deterministic runtime/tests. */
33
+ declare function memoryCodexCredentialStore(initial?: CodexAuthFile): CodexCredentialStore;
34
+ /** Claims this SDK reads out of a Codex JWT. */
35
+ interface CodexJwtClaims {
36
+ exp?: number;
37
+ email?: string;
38
+ accountId?: string;
39
+ planType?: string;
40
+ isFedramp: boolean;
41
+ }
42
+ /**
43
+ * Read the claims this SDK cares about out of a JWT.
44
+ *
45
+ * The signature is NOT verified, and does not need to be: this token is being
46
+ * read to decide which account id to send and whether to refresh, not to grant
47
+ * anything. The issuer verifies it.
48
+ * @param jwt - a compact-serialization JWT.
49
+ * @returns the claims, or `undefined` when the token is unreadable.
50
+ */
51
+ declare function readJwtClaims(jwt: string): CodexJwtClaims | undefined;
52
+ /**
53
+ * Resolve the account id to send as `ChatGPT-Account-ID`.
54
+ *
55
+ * Prefers the stored value and falls back to the `id_token` claim, because the
56
+ * stored field is legitimately null for personal accounts.
57
+ * @param tokens - the stored tokens.
58
+ * @returns the account id, or `undefined` when neither source has one.
59
+ */
60
+ declare function resolveAccountId(tokens: CodexTokens): string | undefined;
61
+ /** Whether this account must be routed through the FedRAMP edge. */
62
+ declare function isFedrampAccount(tokens: CodexTokens): boolean;
63
+ /** Refresh this long before the access token actually expires. */
64
+ declare const ACCESS_TOKEN_REFRESH_WINDOW_MS: number;
65
+ /** Fallback staleness bound, used only when `exp` cannot be read. */
66
+ declare const LAST_REFRESH_MAX_AGE_MS: number;
67
+ /**
68
+ * Whether the access token should be refreshed before the next request.
69
+ *
70
+ * Primary signal is the token's own `exp`, with a five-minute margin so a request
71
+ * cannot expire in flight. The `last_refresh` age is only a fallback for a token
72
+ * whose `exp` is unreadable — matching how the Codex CLI decides.
73
+ * @param file - the credential file.
74
+ * @param now - current time in epoch milliseconds; injectable for tests.
75
+ * @returns true when a refresh is due.
76
+ */
77
+ declare function shouldRefresh(file: CodexAuthFile, now?: number): boolean;
78
+ /**
79
+ * Require usable ChatGPT tokens, with a message that says how to get them.
80
+ * @param file - the credential file, or `undefined` when absent.
81
+ * @param location - the path checked, named in the diagnostic.
82
+ * @returns the tokens.
83
+ */
84
+ declare function requireTokens(file: CodexAuthFile | undefined, location: string): CodexTokens;
85
+ //#endregion
86
+ //#region src/oauth.d.ts
87
+ /** OpenAI's auth issuer. */
88
+ declare const DEFAULT_CODEX_ISSUER = "https://auth.openai.com";
89
+ /** The public OAuth client id the Codex CLI uses; not a secret. */
90
+ declare const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
91
+ /** Shared settings for the OAuth calls. */
92
+ interface CodexOAuthOptions {
93
+ /** Auth issuer base URL; defaults to {@link DEFAULT_CODEX_ISSUER}. */
94
+ issuer?: string;
95
+ /** OAuth client id; defaults to {@link CODEX_CLIENT_ID}. */
96
+ clientId?: string;
97
+ /** Cancellation for the whole flow. */
98
+ signal?: AbortSignal;
99
+ /** HTTP implementation for tests and non-browser runtimes. */
100
+ fetch?: typeof fetch;
101
+ /** Deadline for each auth HTTP request. Defaults to 30 seconds. */
102
+ requestTimeoutMs?: number;
103
+ /** Maximum auth response bytes retained or parsed. Defaults to 1 MiB. */
104
+ maxResponseBytes?: number;
105
+ /** Maximum auth response chunks accepted. Defaults to 10,000. */
106
+ maxResponseChunks?: number;
107
+ /** Permit an http:// issuer for a trusted local test endpoint. Defaults to false. */
108
+ allowInsecureIssuer?: boolean;
109
+ }
110
+ /** A pending device authorization the user has to approve. */
111
+ interface CodexDeviceCode {
112
+ /** URL to open in a browser. */
113
+ verificationUrl: string;
114
+ /** One-time code the user types there. */
115
+ userCode: string;
116
+ /** Opaque server-side handle for this authorization. */
117
+ deviceAuthId: string;
118
+ /** Seconds to wait between polls. */
119
+ intervalSeconds: number;
120
+ }
121
+ /** Progress reported while a device-code login runs. */
122
+ interface CodexLoginProgress {
123
+ /** The code is ready; show it to the user. */
124
+ onPrompt?: (code: CodexDeviceCode) => void;
125
+ /** Called before each poll, so a CLI can show that it is still waiting. */
126
+ onPoll?: (elapsedMs: number) => void;
127
+ }
128
+ /**
129
+ * Start a device authorization.
130
+ * @param options - issuer, client id, cancellation.
131
+ * @returns the code and URL to show the user.
132
+ */
133
+ declare function requestDeviceCode(options?: CodexOAuthOptions): Promise<CodexDeviceCode>;
134
+ /** Result of a completed device-code login. */
135
+ interface CodexLoginResult {
136
+ /** Where the credentials were written. */
137
+ location: string;
138
+ /** Signed-in account email, when the token discloses one. */
139
+ email: string | undefined;
140
+ /** Workspace/account id that requests will carry. */
141
+ accountId: string | undefined;
142
+ /** Plan type, when disclosed. */
143
+ planType: string | undefined;
144
+ }
145
+ /**
146
+ * Run a full device-code login and persist the result.
147
+ * @param store - where to write the credentials.
148
+ * @param options - issuer, client id, cancellation.
149
+ * @param progress - prompt and poll notifications for a CLI to render.
150
+ * @returns a summary of who signed in and where it was stored.
151
+ */
152
+ declare function runDeviceCodeLogin(store: CodexCredentialStore, options?: CodexOAuthOptions, progress?: CodexLoginProgress): Promise<CodexLoginResult>;
153
+ declare function runDeviceCodeLogin(store: CodexAuthStore, options?: CodexOAuthOptions, progress?: CodexLoginProgress): Promise<CodexLoginResult>;
154
+ /** Why a refresh failed, which decides whether re-login is required. */
155
+ type RefreshFailureKind = 'permanent' | 'transient';
156
+ /** A refresh that did not succeed. */
157
+ declare class CodexRefreshError extends AgentSdkError {
158
+ readonly kind: RefreshFailureKind;
159
+ constructor(message: string, kind: RefreshFailureKind, options?: ErrorOptions);
160
+ }
161
+ /**
162
+ * Exchange a refresh token for a fresh token set and persist it.
163
+ *
164
+ * Refresh tokens are SINGLE USE and rotate on every call, which is why this
165
+ * writes the result immediately: losing the new token means the next refresh
166
+ * replays a spent one and permanently fails. It is also why this SDK must not
167
+ * share a credential file with the Codex CLI.
168
+ * @param store - the credential store to update in place.
169
+ * @param options - issuer, client id, cancellation.
170
+ * @returns the refreshed tokens.
171
+ */
172
+ declare function refreshCodexTokens(store: CodexCredentialStore, options?: CodexOAuthOptions): Promise<CodexTokens>;
173
+ declare function refreshCodexTokens(store: CodexAuthStore, options?: CodexOAuthOptions): Promise<CodexTokens>;
174
+ //#endregion
175
+ //#region src/adapter.d.ts
176
+ /** The ChatGPT-backed Codex API base. */
177
+ declare const CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
178
+ /** Client identifier this endpoint expects. See the module note. */
179
+ declare const CODEX_ORIGINATOR = "codex_cli_rs";
180
+ /**
181
+ * Client version sent when listing models.
182
+ *
183
+ * NOT cosmetic: the model catalog is gated on it, and an older value returns a
184
+ * shorter list or an empty one. Verified against a live account — `0.45.0` returns
185
+ * `{"models":[]}` while `1.0.0` returns the full set.
186
+ */
187
+ declare const CODEX_CLIENT_VERSION = "1.0.0";
188
+ /** Options for {@link codexAdapter}. */
189
+ interface CodexAdapterOptions {
190
+ /**
191
+ * Where the credentials live.
192
+ *
193
+ * Required injection. Filesystem/env defaults belong to the Node auth wrapper.
194
+ */
195
+ authStore: CodexAuthStore;
196
+ /** Endpoint base; defaults to {@link CODEX_BASE_URL}. */
197
+ baseUrl?: string;
198
+ /** Client identifier; defaults to {@link CODEX_ORIGINATOR}. */
199
+ originator?: string;
200
+ /**
201
+ * Model catalog.
202
+ *
203
+ * Left undefined, the adapter DISCOVERS it from the endpoint, which is the right
204
+ * default here: the available models depend on the account's plan and on
205
+ * {@link CODEX_CLIENT_VERSION}, so no hardcoded list could be correct for
206
+ * everyone. Discovery also supplies `input_modalities`, without which every model
207
+ * would be assumed text-only and image input silently stripped.
208
+ */
209
+ models?: readonly ProviderCatalogModel[];
210
+ /** Client version used for catalog discovery; defaults to {@link CODEX_CLIENT_VERSION}. */
211
+ clientVersion?: string;
212
+ /** Maximum raw model-catalog response bytes. Defaults to 4 MiB. */
213
+ maxCatalogBytes?: number;
214
+ /** Maximum model entries accepted from discovery. Defaults to 2,048. */
215
+ maxCatalogModels?: number;
216
+ /** Maximum response chunks accepted during discovery. Defaults to 10,000. */
217
+ maxCatalogChunks?: number;
218
+ /** Model-catalog request deadline. Defaults to 30 seconds. */
219
+ catalogTimeoutMs?: number;
220
+ catalogTtlMs?: number;
221
+ catalogStaleTtlMs?: number;
222
+ catalogFailureBackoffMs?: number;
223
+ /** Output cap when neither caller nor catalog names one. */
224
+ defaultMaxTokens?: number;
225
+ /** Context capacity assumed for an uncatalogued model. */
226
+ defaultContextWindow?: number;
227
+ /** Idle bound while a stream read is outstanding. */
228
+ streamIdleTimeoutMs?: number;
229
+ requestTimeoutMs?: number;
230
+ maxRequestBytes?: number;
231
+ maxResponseBytes?: number;
232
+ maxResponseChunks?: number;
233
+ maxSseEvents?: number;
234
+ maxSseEventChars?: number;
235
+ maxErrorBodyBytes?: number;
236
+ requestLoggerTimeoutMs?: number;
237
+ /** Retry policy this route owns. */
238
+ retryPolicy?: RetryPolicyConfig;
239
+ /** Optional exact wire-request logger; credentials/account ids are redacted. */
240
+ requestLogger?: ProviderRequestLogger;
241
+ /** Issuer and client id overrides for token refresh. */
242
+ oauth?: CodexOAuthOptions;
243
+ /**
244
+ * Stable key letting the provider reuse a cached prompt prefix across turns.
245
+ *
246
+ * Defaults to one id captured by the adapter/provider-plugin instance. Every
247
+ * conversation routed through that same instance shares the key. Use separate
248
+ * plugin instances (and routes) when cache identity must be isolated; this is
249
+ * not a conversation- or tenant-scoped setting.
250
+ */
251
+ promptCacheKey?: string;
252
+ fetch?: typeof globalThis.fetch;
253
+ }
254
+ interface CodexRevisionedAdapterOptions extends Omit<CodexAdapterOptions, 'authStore'> {
255
+ readonly authStore: CodexCredentialStore;
256
+ }
257
+ /**
258
+ * Create a Codex adapter.
259
+ * @param options - credential store, endpoint, and catalog overrides.
260
+ * @returns the adapter, ready to register.
261
+ */
262
+ declare function codexAdapter(options: CodexRevisionedAdapterOptions): HttpModelAdapter;
263
+ declare function codexAdapter(options: CodexAdapterOptions): HttpModelAdapter;
264
+ interface CodexPluginOptions extends CodexAdapterOptions {
265
+ /** Registry routes installed by the plugin. Defaults to `['codex']`. */
266
+ readonly routes?: readonly string[];
267
+ }
268
+ interface CodexProviderOptions extends CodexRevisionedAdapterOptions {
269
+ readonly defaultModel?: string | ModelTarget;
270
+ readonly id?: string;
271
+ readonly routes?: readonly string[];
272
+ }
273
+ /** Preferred transactional plugin for installing the Universal Codex provider. */
274
+ declare function codexPlugin(options: CodexProviderOptions): ComposableModelProviderPlugin & {
275
+ readonly family: 'codex';
276
+ };
277
+ declare function codexPlugin(options: CodexPluginOptions): ModelProviderPlugin;
278
+ //#endregion
279
+ export { ACCESS_TOKEN_REFRESH_WINDOW_MS, CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR, type CodexAdapterOptions, type CodexAuthFile, type CodexAuthStore, type CodexCredentialStore, type CodexDeviceCode, type CodexJwtClaims, type CodexLoginProgress, type CodexLoginResult, type CodexOAuthOptions, type CodexPluginOptions, type CodexProviderOptions, CodexRefreshError, type CodexRevisionedAdapterOptions, type CodexTokens, DEFAULT_CODEX_ISSUER, LAST_REFRESH_MAX_AGE_MS, type RefreshFailureKind, type ResponsesDialect, codexAdapter, codexPlugin, isFedrampAccount, memoryCodexAuthStore, memoryCodexCredentialStore, openAiResponsesProtocol, readJwtClaims, refreshCodexTokens, requestDeviceCode, requireTokens, resolveAccountId, runDeviceCodeLogin, shouldRefresh };
280
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/common/store-types.ts","../src/auth.ts","../src/oauth.ts","../src/adapter.ts"],"mappings":";;;;;;UAGiB;EACf;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA,SAAS;EACT;;;UAIe;WACN;EACT,QAAQ,QAAQ;EAChB,MAAM,MAAM,gBAAgB;;;KAIlB,uBAAuB,gBAAgB;;;;iBCLnC,qBAAqB,UAAU,gBAAgB;;iBAa/C,2BAA2B,UAAU,gBAAgB;;UAgCpD;EACf;EACA;EACA;EACA;EACA;;;;;;;;;;;iBAuBc,cAAc,cAAc;;;;;;;;;iBAmC5B,iBAAiB,QAAQ;;iBAOzB,iBAAiB,QAAQ;;cAK5B;;cAGA;;;;;;;;;;;iBAYG,cAAc,MAAM,eAAe;;;;;;;iBAiBnC,cACd,MAAM,2BACN,mBACC;;;;cC7HU;;cAGA;;UASI;;EAEf;;EAEA;;EAEA,SAAS;;EAET,eAAe;;EAEf;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf,YAAY,MAAM;;EAElB,UAAU;;;;;;;iBA6HU,kBACpB,UAAS,oBACR,QAAQ;;UAoKM;;EAEf;;EAEA;;EAEA;;EAEA;;;;;;;;;iBAUc,mBACd,OAAO,sBACP,UAAU,mBACV,WAAW,qBACV,QAAQ;iBACK,mBACd,OAAO,gBACP,UAAU,mBACV,WAAW,qBACV,QAAQ;;KA0BC;;cAGC,0BAA0B;WAC5B,MAAM;EAEf,YAAY,iBAAiB,MAAM,oBAAoB,UAAU;;;;;;;;;;;;;iBA0CnD,mBACd,OAAO,sBACP,UAAU,oBACT,QAAQ;iBACK,mBACd,OAAO,gBACP,UAAU,oBACT,QAAQ;;;;cChbE;;cAGA;;;;;;;;cASA;;UAkBI;;;;;;EAMf,WAAW;;EAEX;;EAEA;;;;;;;;;;EAUA,kBAAkB;;EAElB;;EAEA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA,cAAc;;EAEd,gBAAgB;;EAEhB,QAAQ;;;;;;;;;EASR;EACA,eAAe,WAAW;;UAGX,sCAAsC,KAAK;WACjD,WAAW;;;;;;;iBA4EN,aAAa,SAAS,gCAAgC;iBACtD,aAAa,SAAS,sBAAsB;UAmM3C,2BAA2B;;WAEjC;;UAGM,6BAA6B;WACnC,wBAAwB;WACxB;WACA;;;iBAIK,YACd,SAAS,uBACR;WAA2C;;iBAC9B,YAAY,SAAS,qBAAqB"}