@agentionai/agents 1.12.0 → 1.14.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,166 @@
1
+ /**
2
+ * OAuth against a ChatGPT subscription, as used by OpenAI's Codex CLI.
3
+ *
4
+ * This is a different product surface from the platform API: the credentials are
5
+ * a ChatGPT login rather than a `sk-...` platform key, and requests are billed
6
+ * against the subscription instead of an API account. The endpoint differs too —
7
+ * see {@link CODEX_BASE_URL}.
8
+ *
9
+ * None of it is a documented public API. The values here were cross-checked
10
+ * against the Codex CLI's own behaviour and several independent
11
+ * reimplementations, but OpenAI can change them without notice.
12
+ */
13
+ /**
14
+ * Base URL for the ChatGPT-backed Codex Responses API.
15
+ *
16
+ * The SDK appends `/responses`, giving
17
+ * `https://chatgpt.com/backend-api/codex/responses`.
18
+ */
19
+ export declare const CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
20
+ /** Public OAuth client id the Codex CLI uses. Not a secret. */
21
+ export declare const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
22
+ /** Token endpoint used to exchange a refresh token for a fresh access token. */
23
+ export declare const CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token";
24
+ /**
25
+ * Default `originator` header value.
26
+ *
27
+ * OpenAI gates parts of the model catalog on this, so an unrecognised value can
28
+ * quietly change which models an account may reach.
29
+ */
30
+ export declare const CODEX_ORIGINATOR = "codex_cli_rs";
31
+ /**
32
+ * `client_version` for the Codex models endpoint, which 400s without one.
33
+ *
34
+ * Each model also carries a `minimal_client_version`; the backend hides models
35
+ * newer than the version claimed here, so an old value quietly shortens the
36
+ * list rather than erroring.
37
+ */
38
+ export declare const CODEX_CLIENT_VERSION = "0.153.4";
39
+ /**
40
+ * One entry from the Codex `/models` response.
41
+ *
42
+ * Nothing like the platform API's `/v1/models` — richer, and shaped for the
43
+ * Codex client. Only the fields worth relying on are named; the rest come
44
+ * through on `ModelInfo.raw`.
45
+ */
46
+ export interface CodexModelCard {
47
+ slug: string;
48
+ display_name?: string;
49
+ description?: string;
50
+ /** Default context window for this account's plan. */
51
+ context_window?: number;
52
+ /** Largest window the model can be driven at. */
53
+ max_context_window?: number;
54
+ input_modalities?: string[];
55
+ supported_reasoning_levels?: {
56
+ effort: string;
57
+ description?: string;
58
+ }[];
59
+ default_reasoning_level?: string;
60
+ /** Subscription plans that may select this model. */
61
+ available_in_plans?: string[];
62
+ /** `"list"` for models meant to be shown in a picker. */
63
+ visibility?: string;
64
+ supported_in_api?: boolean;
65
+ minimal_client_version?: string;
66
+ supports_parallel_tool_calls?: boolean;
67
+ [key: string]: unknown;
68
+ }
69
+ /**
70
+ * Credentials for the ChatGPT/Codex backend.
71
+ */
72
+ export interface CodexCredentials {
73
+ /** Bearer token sent as `Authorization`. */
74
+ accessToken: string;
75
+ /** Used to mint a new access token once the current one expires. */
76
+ refreshToken?: string;
77
+ /**
78
+ * Workspace/account the request is billed to, sent as the
79
+ * `chatgpt-account-id` header. Read from `auth.json`, or decoded from the
80
+ * `id_token` when absent.
81
+ */
82
+ accountId?: string;
83
+ /** Account e-mail, when the `id_token` carried one. Informational. */
84
+ email?: string;
85
+ /** Subscription tier (`plus`, `pro`, …), when present. Informational. */
86
+ planType?: string;
87
+ }
88
+ /**
89
+ * Decode a JWT's payload without verifying its signature.
90
+ *
91
+ * Verification is the token endpoint's job — we are only reading claims out of a
92
+ * token we were just handed over TLS, never making a trust decision on it.
93
+ * Returns `undefined` for anything that does not parse, so a malformed or
94
+ * opaque token degrades to "no claims" rather than throwing.
95
+ */
96
+ export declare function decodeJwtClaims<T = Record<string, unknown>>(token: string): T | undefined;
97
+ /**
98
+ * Seconds-since-epoch expiry of a JWT, or `undefined` if it has no `exp`.
99
+ */
100
+ export declare function jwtExpiry(token: string): number | undefined;
101
+ /** Default location of Codex's credential file. */
102
+ export declare function codexAuthFilePath(codexHome?: string): string;
103
+ /**
104
+ * Read the credentials the Codex CLI stored at `$CODEX_HOME/auth.json`
105
+ * (`~/.codex/auth.json` by default).
106
+ *
107
+ * Sign in with `codex login` first — this only reads what that wrote, it does
108
+ * not run the OAuth flow itself.
109
+ *
110
+ * @throws if the file is missing, unreadable, not JSON, or holds no access token.
111
+ */
112
+ export declare function loadCodexCredentials(codexHome?: string): Promise<CodexCredentials>;
113
+ /**
114
+ * Exchange a refresh token for a fresh access token.
115
+ *
116
+ * The returned credentials carry the new `refresh_token` when the server
117
+ * rotated it, and the previous one otherwise.
118
+ */
119
+ export declare function refreshCodexCredentials(refreshToken: string, options?: {
120
+ clientId?: string;
121
+ tokenUrl?: string;
122
+ signal?: AbortSignal;
123
+ }): Promise<CodexCredentials>;
124
+ /** Options for {@link createCodexTokenProvider}. */
125
+ export interface CodexTokenProviderOptions {
126
+ /** OAuth client id. Defaults to {@link CODEX_CLIENT_ID}. */
127
+ clientId?: string;
128
+ /** Token endpoint. Defaults to {@link CODEX_TOKEN_URL}. */
129
+ tokenUrl?: string;
130
+ /**
131
+ * Refresh this many seconds before the access token actually expires, so a
132
+ * request is never sent with a token that dies in flight.
133
+ *
134
+ * @default 300
135
+ */
136
+ refreshSkewSeconds?: number;
137
+ /**
138
+ * Called after every successful refresh, e.g. to persist the rotated refresh
139
+ * token. Errors thrown here are ignored — a failed write must not fail the
140
+ * request the token was minted for.
141
+ */
142
+ onRefresh?: (credentials: CodexCredentials) => void | Promise<void>;
143
+ }
144
+ /**
145
+ * A token source that hands out an access token and silently refreshes it.
146
+ *
147
+ * The `getToken` function is shaped for the OpenAI SDK's `apiKey` option, which
148
+ * accepts an async function and calls it before *every* request — so a
149
+ * long-running agent keeps working past the ~1h life of an access token without
150
+ * anyone reaching for the credential file again.
151
+ */
152
+ export interface CodexTokenProvider {
153
+ /** Current access token, refreshed on demand. Pass as the SDK's `apiKey`. */
154
+ getToken: () => Promise<string>;
155
+ /** Latest known credentials, including `accountId`. */
156
+ current: () => CodexCredentials;
157
+ }
158
+ /**
159
+ * Wrap credentials in a self-refreshing token provider.
160
+ *
161
+ * Refreshes lazily — only when a token is actually asked for and the current
162
+ * one is within `refreshSkewSeconds` of expiry. Concurrent callers share a
163
+ * single in-flight refresh rather than each starting their own.
164
+ */
165
+ export declare function createCodexTokenProvider(credentials: CodexCredentials, options?: CodexTokenProviderOptions): CodexTokenProvider;
166
+ //# sourceMappingURL=codex-auth.d.ts.map
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CODEX_CLIENT_VERSION = exports.CODEX_ORIGINATOR = exports.CODEX_TOKEN_URL = exports.CODEX_CLIENT_ID = exports.CODEX_BASE_URL = void 0;
37
+ exports.decodeJwtClaims = decodeJwtClaims;
38
+ exports.jwtExpiry = jwtExpiry;
39
+ exports.codexAuthFilePath = codexAuthFilePath;
40
+ exports.loadCodexCredentials = loadCodexCredentials;
41
+ exports.refreshCodexCredentials = refreshCodexCredentials;
42
+ exports.createCodexTokenProvider = createCodexTokenProvider;
43
+ const fs_1 = require("fs");
44
+ const os = __importStar(require("os"));
45
+ const path = __importStar(require("path"));
46
+ /**
47
+ * OAuth against a ChatGPT subscription, as used by OpenAI's Codex CLI.
48
+ *
49
+ * This is a different product surface from the platform API: the credentials are
50
+ * a ChatGPT login rather than a `sk-...` platform key, and requests are billed
51
+ * against the subscription instead of an API account. The endpoint differs too —
52
+ * see {@link CODEX_BASE_URL}.
53
+ *
54
+ * None of it is a documented public API. The values here were cross-checked
55
+ * against the Codex CLI's own behaviour and several independent
56
+ * reimplementations, but OpenAI can change them without notice.
57
+ */
58
+ /**
59
+ * Base URL for the ChatGPT-backed Codex Responses API.
60
+ *
61
+ * The SDK appends `/responses`, giving
62
+ * `https://chatgpt.com/backend-api/codex/responses`.
63
+ */
64
+ exports.CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
65
+ /** Public OAuth client id the Codex CLI uses. Not a secret. */
66
+ exports.CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
67
+ /** Token endpoint used to exchange a refresh token for a fresh access token. */
68
+ exports.CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token";
69
+ /**
70
+ * Default `originator` header value.
71
+ *
72
+ * OpenAI gates parts of the model catalog on this, so an unrecognised value can
73
+ * quietly change which models an account may reach.
74
+ */
75
+ exports.CODEX_ORIGINATOR = "codex_cli_rs";
76
+ /**
77
+ * `client_version` for the Codex models endpoint, which 400s without one.
78
+ *
79
+ * Each model also carries a `minimal_client_version`; the backend hides models
80
+ * newer than the version claimed here, so an old value quietly shortens the
81
+ * list rather than erroring.
82
+ */
83
+ exports.CODEX_CLIENT_VERSION = "0.153.4";
84
+ /**
85
+ * Decode a JWT's payload without verifying its signature.
86
+ *
87
+ * Verification is the token endpoint's job — we are only reading claims out of a
88
+ * token we were just handed over TLS, never making a trust decision on it.
89
+ * Returns `undefined` for anything that does not parse, so a malformed or
90
+ * opaque token degrades to "no claims" rather than throwing.
91
+ */
92
+ function decodeJwtClaims(token) {
93
+ const payload = token.split(".")[1];
94
+ if (!payload)
95
+ return undefined;
96
+ try {
97
+ const json = Buffer.from(payload, "base64url").toString("utf8");
98
+ const claims = JSON.parse(json);
99
+ return typeof claims === "object" && claims !== null
100
+ ? claims
101
+ : undefined;
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ /**
108
+ * Seconds-since-epoch expiry of a JWT, or `undefined` if it has no `exp`.
109
+ */
110
+ function jwtExpiry(token) {
111
+ const exp = decodeJwtClaims(token)?.exp;
112
+ return typeof exp === "number" ? exp : undefined;
113
+ }
114
+ /** Default location of Codex's credential file. */
115
+ function codexAuthFilePath(codexHome) {
116
+ const home = codexHome ?? process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
117
+ return path.join(home, "auth.json");
118
+ }
119
+ /**
120
+ * Read the credentials the Codex CLI stored at `$CODEX_HOME/auth.json`
121
+ * (`~/.codex/auth.json` by default).
122
+ *
123
+ * Sign in with `codex login` first — this only reads what that wrote, it does
124
+ * not run the OAuth flow itself.
125
+ *
126
+ * @throws if the file is missing, unreadable, not JSON, or holds no access token.
127
+ */
128
+ async function loadCodexCredentials(codexHome) {
129
+ const file = codexAuthFilePath(codexHome);
130
+ let raw;
131
+ try {
132
+ raw = await fs_1.promises.readFile(file, "utf8");
133
+ }
134
+ catch (error) {
135
+ const reason = error?.code === "ENOENT"
136
+ ? "no such file — run `codex login` to sign in with your ChatGPT account"
137
+ : error instanceof Error
138
+ ? error.message
139
+ : "unknown error";
140
+ throw new Error(`Could not read Codex credentials from ${file}: ${reason}`);
141
+ }
142
+ let parsed;
143
+ try {
144
+ parsed = JSON.parse(raw);
145
+ }
146
+ catch {
147
+ throw new Error(`Codex credentials at ${file} are not valid JSON`);
148
+ }
149
+ const tokens = parsed.tokens;
150
+ if (!tokens?.access_token) {
151
+ throw new Error(`Codex credentials at ${file} contain no OAuth access token` +
152
+ (parsed.OPENAI_API_KEY
153
+ ? " — that file holds a platform API key instead, which belongs in `apiKey` with the default `authType: \"apiKey\"`"
154
+ : " — run `codex login` to sign in with your ChatGPT account"));
155
+ }
156
+ return credentialsFromTokens(tokens);
157
+ }
158
+ /** Build {@link CodexCredentials} from an `auth.json` `tokens` object. */
159
+ function credentialsFromTokens(tokens) {
160
+ const claims = tokens.id_token
161
+ ? decodeJwtClaims(tokens.id_token)
162
+ : undefined;
163
+ const auth = claims?.["https://api.openai.com/auth"];
164
+ return {
165
+ accessToken: tokens.access_token,
166
+ refreshToken: tokens.refresh_token,
167
+ // `auth.json` usually carries `account_id`, but not always; the same value
168
+ // is a claim on the id_token, so fall back to that before giving up.
169
+ accountId: tokens.account_id ?? auth?.chatgpt_account_id,
170
+ email: claims?.email ?? claims?.["https://api.openai.com/profile"]?.email,
171
+ planType: auth?.chatgpt_plan_type,
172
+ };
173
+ }
174
+ /**
175
+ * Exchange a refresh token for a fresh access token.
176
+ *
177
+ * The returned credentials carry the new `refresh_token` when the server
178
+ * rotated it, and the previous one otherwise.
179
+ */
180
+ async function refreshCodexCredentials(refreshToken, options = {}) {
181
+ const res = await fetch(options.tokenUrl ?? exports.CODEX_TOKEN_URL, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: JSON.stringify({
185
+ grant_type: "refresh_token",
186
+ refresh_token: refreshToken,
187
+ client_id: options.clientId ?? exports.CODEX_CLIENT_ID,
188
+ }),
189
+ signal: options.signal,
190
+ });
191
+ if (!res.ok) {
192
+ const body = await res.text().catch(() => "");
193
+ throw new Error(`Codex token refresh failed (${res.status} ${res.statusText})${body ? `: ${body.slice(0, 500)}` : ""}`);
194
+ }
195
+ const data = (await res.json());
196
+ if (!data.access_token) {
197
+ throw new Error("Codex token refresh returned no access_token");
198
+ }
199
+ return credentialsFromTokens({
200
+ access_token: data.access_token,
201
+ // The endpoint only returns a refresh token when it rotates one; reuse the
202
+ // current one otherwise, or the next refresh has nothing to present.
203
+ refresh_token: data.refresh_token ?? refreshToken,
204
+ id_token: data.id_token,
205
+ });
206
+ }
207
+ /**
208
+ * Wrap credentials in a self-refreshing token provider.
209
+ *
210
+ * Refreshes lazily — only when a token is actually asked for and the current
211
+ * one is within `refreshSkewSeconds` of expiry. Concurrent callers share a
212
+ * single in-flight refresh rather than each starting their own.
213
+ */
214
+ function createCodexTokenProvider(credentials, options = {}) {
215
+ const skew = options.refreshSkewSeconds ?? 300;
216
+ let current = credentials;
217
+ let expiresAt = jwtExpiry(credentials.accessToken);
218
+ let inFlight;
219
+ const isFresh = () => {
220
+ // An opaque token with no readable `exp` is assumed good: refreshing on
221
+ // every call would be worse than letting a 401 surface.
222
+ if (expiresAt === undefined)
223
+ return true;
224
+ return Date.now() / 1000 < expiresAt - skew;
225
+ };
226
+ const refresh = async () => {
227
+ if (!current.refreshToken) {
228
+ throw new Error("Codex access token has expired and no refresh token is available — run `codex login` again");
229
+ }
230
+ const next = await refreshCodexCredentials(current.refreshToken, {
231
+ clientId: options.clientId,
232
+ tokenUrl: options.tokenUrl,
233
+ });
234
+ current = {
235
+ ...next,
236
+ // A refresh response carries no id_token in some cases, which would drop
237
+ // the account id the `chatgpt-account-id` header needs.
238
+ accountId: next.accountId ?? current.accountId,
239
+ email: next.email ?? current.email,
240
+ planType: next.planType ?? current.planType,
241
+ };
242
+ expiresAt = jwtExpiry(current.accessToken);
243
+ try {
244
+ await options.onRefresh?.(current);
245
+ }
246
+ catch {
247
+ // Persisting is best-effort; the token in hand is still valid.
248
+ }
249
+ return current.accessToken;
250
+ };
251
+ return {
252
+ getToken: async () => {
253
+ if (isFresh())
254
+ return current.accessToken;
255
+ // Collapse concurrent refreshes: the second caller awaits the first.
256
+ inFlight ?? (inFlight = refresh().finally(() => {
257
+ inFlight = undefined;
258
+ }));
259
+ return inFlight;
260
+ },
261
+ current: () => current,
262
+ };
263
+ }
264
+ //# sourceMappingURL=codex-auth.js.map
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Quota accounting for the ChatGPT-backed Codex backend.
3
+ *
4
+ * A ChatGPT subscription is not billed per request, so nothing on this path
5
+ * reports a dollar cost — `TokenUsage.cost_usd` stays undefined here, as it
6
+ * does on every provider that does not price a response itself. What a
7
+ * subscription spends instead is *plan allowance*, and the backend reports that
8
+ * on every `/responses` call as a set of `x-codex-*` headers: two rolling
9
+ * windows (a 5-hour "primary" and a weekly "secondary"), the plan and limit
10
+ * tier in force, and the pay-as-you-go credit balance that takes over once the
11
+ * windows are exhausted.
12
+ *
13
+ * These headers are the only source: there is no usage endpoint (`/usage`,
14
+ * `/rate_limits` and `/limits` all answer `403`), and `/models` returns none of
15
+ * them — so quota state can only be refreshed by making a real call. Values
16
+ * observed live against `chatgpt.com/backend-api/codex` on 2026-09-10; like the
17
+ * rest of that surface they are undocumented and may change without notice,
18
+ * which is why every field here is optional and an unparseable value is dropped
19
+ * rather than guessed at.
20
+ */
21
+ /** One rolling usage window, as the backend reports it. */
22
+ export interface CodexRateLimitWindow {
23
+ /**
24
+ * Percentage of the window's allowance already consumed, `0`–`100`. Requests
25
+ * start failing once this reaches 100 and the other window has nothing left
26
+ * either.
27
+ */
28
+ usedPercent: number;
29
+ /**
30
+ * Length of the rolling window in minutes — `300` (5 hours) for the primary
31
+ * window and `10080` (7 days) for the secondary, on the plans seen so far.
32
+ */
33
+ windowMinutes?: number;
34
+ /** Seconds until the window rolls over and the allowance is restored. */
35
+ resetAfterSeconds?: number;
36
+ /** Wall-clock time the window rolls over. */
37
+ resetAt?: Date;
38
+ }
39
+ /** Pay-as-you-go credit balance, used once the plan windows are exhausted. */
40
+ export interface CodexCredits {
41
+ /** Remaining credits. `0` on an account that has never bought any. */
42
+ balance?: number;
43
+ /** Whether any credits are available to spend. */
44
+ hasCredits?: boolean;
45
+ /** Whether the account's credits are uncapped. */
46
+ unlimited?: boolean;
47
+ }
48
+ /**
49
+ * What one Codex response said about the subscription's remaining allowance —
50
+ * the closest thing this backend has to a cost figure.
51
+ */
52
+ export interface CodexUsageLimits {
53
+ /** Short rolling window; `300` minutes (5 hours) on the plans seen so far. */
54
+ primary?: CodexRateLimitWindow;
55
+ /** Long rolling window; `10080` minutes (7 days) on those same plans. */
56
+ secondary?: CodexRateLimitWindow;
57
+ /** Subscription tier the request was billed against, e.g. `"plus"`. */
58
+ planType?: string;
59
+ /** Limit tier in force for this request, e.g. `"premium"`. */
60
+ activeLimit?: string;
61
+ /** Credit balance backing the account once the windows run dry. */
62
+ credits?: CodexCredits;
63
+ /**
64
+ * How far the primary window may run past the secondary window's pace, as a
65
+ * percentage. `0` where the backend imposes no such allowance.
66
+ */
67
+ primaryOverSecondaryLimitPercent?: number;
68
+ /** When these values were received. */
69
+ at: Date;
70
+ }
71
+ /**
72
+ * Read the quota state out of a Codex response's headers.
73
+ *
74
+ * @returns the limits, or `undefined` when the response carried none — which is
75
+ * every response that is not a `/responses` call, including `/models`
76
+ * and anything Cloudflare answered on the backend's behalf.
77
+ */
78
+ export declare function parseCodexUsageLimits(headers: Headers): CodexUsageLimits | undefined;
79
+ /**
80
+ * `fetch` wrapper that hands every response's headers to `onHeaders` before
81
+ * returning it untouched.
82
+ *
83
+ * The body is never read here — the SDK still consumes the stream itself — so
84
+ * this is safe to stack under `wrapErrorBodyFetch`. A throwing observer
85
+ * is swallowed: quota bookkeeping must never be able to fail a request.
86
+ */
87
+ export declare function observeHeadersFetch(onHeaders: (headers: Headers) => void, baseFetch?: typeof fetch): typeof fetch;
88
+ //# sourceMappingURL=codex-usage.d.ts.map
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * Quota accounting for the ChatGPT-backed Codex backend.
4
+ *
5
+ * A ChatGPT subscription is not billed per request, so nothing on this path
6
+ * reports a dollar cost — `TokenUsage.cost_usd` stays undefined here, as it
7
+ * does on every provider that does not price a response itself. What a
8
+ * subscription spends instead is *plan allowance*, and the backend reports that
9
+ * on every `/responses` call as a set of `x-codex-*` headers: two rolling
10
+ * windows (a 5-hour "primary" and a weekly "secondary"), the plan and limit
11
+ * tier in force, and the pay-as-you-go credit balance that takes over once the
12
+ * windows are exhausted.
13
+ *
14
+ * These headers are the only source: there is no usage endpoint (`/usage`,
15
+ * `/rate_limits` and `/limits` all answer `403`), and `/models` returns none of
16
+ * them — so quota state can only be refreshed by making a real call. Values
17
+ * observed live against `chatgpt.com/backend-api/codex` on 2026-09-10; like the
18
+ * rest of that surface they are undocumented and may change without notice,
19
+ * which is why every field here is optional and an unparseable value is dropped
20
+ * rather than guessed at.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.parseCodexUsageLimits = parseCodexUsageLimits;
24
+ exports.observeHeadersFetch = observeHeadersFetch;
25
+ /** Parse a header that should hold a number, dropping anything that does not. */
26
+ function num(headers, name) {
27
+ const raw = headers.get(name);
28
+ if (raw === null || raw.trim() === "")
29
+ return undefined;
30
+ const value = Number(raw);
31
+ return Number.isFinite(value) ? value : undefined;
32
+ }
33
+ /**
34
+ * Parse a header holding a boolean. The backend writes these Python-style
35
+ * (`True` / `False`), so match case-insensitively and accept the JSON spelling
36
+ * too in case that ever changes.
37
+ */
38
+ function bool(headers, name) {
39
+ const raw = headers.get(name)?.trim().toLowerCase();
40
+ if (raw === "true")
41
+ return true;
42
+ if (raw === "false")
43
+ return false;
44
+ return undefined;
45
+ }
46
+ /** Parse a `-reset-at` header, which carries seconds since the epoch. */
47
+ function resetAt(headers, name) {
48
+ const seconds = num(headers, name);
49
+ return seconds === undefined ? undefined : new Date(seconds * 1000);
50
+ }
51
+ /**
52
+ * Parse one rolling window's headers.
53
+ *
54
+ * Returns `undefined` unless `used-percent` is present: without it there is no
55
+ * window to speak of, only a reset time for one that was never reported.
56
+ */
57
+ function window(headers, prefix) {
58
+ const usedPercent = num(headers, `x-codex-${prefix}-used-percent`);
59
+ if (usedPercent === undefined)
60
+ return undefined;
61
+ return {
62
+ usedPercent,
63
+ windowMinutes: num(headers, `x-codex-${prefix}-window-minutes`),
64
+ resetAfterSeconds: num(headers, `x-codex-${prefix}-reset-after-seconds`),
65
+ resetAt: resetAt(headers, `x-codex-${prefix}-reset-at`),
66
+ };
67
+ }
68
+ /**
69
+ * Read the quota state out of a Codex response's headers.
70
+ *
71
+ * @returns the limits, or `undefined` when the response carried none — which is
72
+ * every response that is not a `/responses` call, including `/models`
73
+ * and anything Cloudflare answered on the backend's behalf.
74
+ */
75
+ function parseCodexUsageLimits(headers) {
76
+ const primary = window(headers, "primary");
77
+ const secondary = window(headers, "secondary");
78
+ const planType = headers.get("x-codex-plan-type") ?? undefined;
79
+ const activeLimit = headers.get("x-codex-active-limit") ?? undefined;
80
+ const balance = num(headers, "x-codex-credits-balance");
81
+ const hasCredits = bool(headers, "x-codex-credits-has-credits");
82
+ const unlimited = bool(headers, "x-codex-credits-unlimited");
83
+ const primaryOverSecondaryLimitPercent = num(headers, "x-codex-primary-over-secondary-limit-percent");
84
+ const credits = balance === undefined && hasCredits === undefined && unlimited === undefined
85
+ ? undefined
86
+ : { balance, hasCredits, unlimited };
87
+ // Nothing recognised: report "no limits seen" rather than a shell of an
88
+ // object timestamped as if it were an answer.
89
+ if (!primary &&
90
+ !secondary &&
91
+ !planType &&
92
+ !activeLimit &&
93
+ !credits &&
94
+ primaryOverSecondaryLimitPercent === undefined) {
95
+ return undefined;
96
+ }
97
+ return {
98
+ primary,
99
+ secondary,
100
+ planType,
101
+ activeLimit,
102
+ credits,
103
+ primaryOverSecondaryLimitPercent,
104
+ at: new Date(),
105
+ };
106
+ }
107
+ /**
108
+ * `fetch` wrapper that hands every response's headers to `onHeaders` before
109
+ * returning it untouched.
110
+ *
111
+ * The body is never read here — the SDK still consumes the stream itself — so
112
+ * this is safe to stack under `wrapErrorBodyFetch`. A throwing observer
113
+ * is swallowed: quota bookkeeping must never be able to fail a request.
114
+ */
115
+ function observeHeadersFetch(onHeaders, baseFetch = fetch) {
116
+ return async (input, init) => {
117
+ const res = await baseFetch(input, init);
118
+ try {
119
+ onHeaders(res.headers);
120
+ }
121
+ catch {
122
+ // Ignored on purpose: see above.
123
+ }
124
+ return res;
125
+ };
126
+ }
127
+ //# sourceMappingURL=codex-usage.js.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export * from "./agents/BaseAgent";
2
2
  export * from "./agents/anthropic/ClaudeAgent";
3
3
  export { OpenAiAgent } from "./agents/openai/OpenAiAgent";
4
+ export { CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_ORIGINATOR, CODEX_TOKEN_URL, codexAuthFilePath, createCodexTokenProvider, loadCodexCredentials, refreshCodexCredentials, } from "./agents/openai/codex-auth";
5
+ export type { CodexModelCard, CodexCredentials, CodexTokenProvider, CodexTokenProviderOptions, } from "./agents/openai/codex-auth";
6
+ export { parseCodexUsageLimits, observeHeadersFetch, type CodexUsageLimits, type CodexRateLimitWindow, type CodexCredits, } from "./agents/openai/codex-usage";
7
+ export { CodexAgent } from "./agents/openai/CodexAgent";
8
+ export type { CodexAgentConfig, CodexModel, CodexReasoningEffort, } from "./agents/openai/CodexAgent";
4
9
  export { MistralAgent } from "./agents/mistral/MistralAgent";
5
10
  export type { MistralModelCard } from "./agents/mistral/MistralAgent";
6
11
  export { GeminiAgent, GEMINI_RETIRED_MODELS, } from "./agents/google/GeminiAgent";