@agentionai/agents 1.13.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.
@@ -137,6 +137,17 @@ export interface OpenAISpecificConfig {
137
137
  * see `lib/tools/BuiltInTool.ts`.
138
138
  */
139
139
  builtInTools?: BuiltInTool[];
140
+ /**
141
+ * Cache-routing key sent as `prompt_cache_key`. Requests sharing a key are
142
+ * steered to the same prompt cache, raising the hit rate for a long
143
+ * conversation or a fleet of agents sharing a system prompt and tool belt.
144
+ */
145
+ promptCacheKey?: string;
146
+ /**
147
+ * How long cached prefixes stay warm — `"24h"` opts into extended retention.
148
+ * Ignored by the ChatGPT/Codex backend, which manages its own cache.
149
+ */
150
+ promptCacheRetention?: "in-memory" | "24h";
140
151
  /**
141
152
  * Override the API base URL. Defaults to `api.openai.com/v1`; `CodexAgent`
142
153
  * defaults it to `https://chatgpt.com/backend-api/codex`, and setting it
@@ -17,6 +17,12 @@ export declare class AgentEvent {
17
17
  * to history. The listener receives the `PartialTurn` that was salvaged.
18
18
  */
19
19
  static PARTIAL_TURN: string;
20
+ /**
21
+ * The provider reported how much of the account's allowance is left. Emitted
22
+ * by `CodexAgent`, whose backend returns rate-limit and credit headers on
23
+ * every response; the listener receives a `CodexUsageLimits`.
24
+ */
25
+ static USAGE_LIMITS: string;
20
26
  private defaultPrevented;
21
27
  constructor(target: BaseAgent<any>);
22
28
  preventDefault(): void;
@@ -30,4 +30,10 @@ AgentEvent.REASONING_CHUNK = "reasoning_chunk";
30
30
  * to history. The listener receives the `PartialTurn` that was salvaged.
31
31
  */
32
32
  AgentEvent.PARTIAL_TURN = "partial_turn";
33
+ /**
34
+ * The provider reported how much of the account's allowance is left. Emitted
35
+ * by `CodexAgent`, whose backend returns rate-limit and credit headers on
36
+ * every response; the listener receives a `CodexUsageLimits`.
37
+ */
38
+ AgentEvent.USAGE_LIMITS = "usage_limits";
33
39
  //# sourceMappingURL=AgentEvent.js.map
@@ -41,6 +41,24 @@ export type TokenUsage = {
41
41
  * for instance, folds thinking tokens into `output_tokens`.
42
42
  */
43
43
  reasoning_tokens?: number;
44
+ /**
45
+ * Prompt tokens the provider served from its own cache instead of processing
46
+ * afresh — a subset of `input_tokens`, not an addition to them, and normally
47
+ * billed at a discount.
48
+ *
49
+ * `0` is a real answer ("nothing hit the cache"); `undefined` means the
50
+ * provider said nothing about caching at all.
51
+ */
52
+ cache_read_tokens?: number;
53
+ /**
54
+ * Prompt tokens this call wrote *into* the provider's cache, where the
55
+ * provider reports writes separately from reads. Also a subset of
56
+ * `input_tokens` on the OpenAI-shaped providers.
57
+ *
58
+ * Rarer than `cache_read_tokens` — of the providers wired here only the
59
+ * ChatGPT/Codex backend reports it, and the platform Responses API does not.
60
+ */
61
+ cache_write_tokens?: number;
44
62
  /**
45
63
  * USD billed for this usage, straight from the provider's own accounting —
46
64
  * not derived from a local price table. Undefined where the provider
@@ -253,6 +253,8 @@ class BaseAgent extends events_1.default {
253
253
  output_tokens: previous.output_tokens + timed.output_tokens,
254
254
  total_tokens: previous.total_tokens + timed.total_tokens,
255
255
  reasoning_tokens: sumOptional(previous.reasoning_tokens, timed.reasoning_tokens),
256
+ cache_read_tokens: sumOptional(previous.cache_read_tokens, timed.cache_read_tokens),
257
+ cache_write_tokens: sumOptional(previous.cache_write_tokens, timed.cache_write_tokens),
256
258
  cost_usd: sumOptional(previous.cost_usd, timed.cost_usd),
257
259
  timeToFirstTokenMs: sumOptional(previous.timeToFirstTokenMs, timed.timeToFirstTokenMs),
258
260
  generationMs: sumOptional(previous.generationMs, timed.generationMs),
@@ -4,6 +4,7 @@ import { ResponseInputItem } from "openai/resources/responses/responses";
4
4
  import { OpenAIModel } from "../model-types";
5
5
  import { AgentConfig as OpenAiAgentConfig, OpenAiAgent } from "./OpenAiAgent";
6
6
  import { CodexCredentials, CodexModelCard, CodexTokenProviderOptions } from "./codex-auth";
7
+ import { CodexUsageLimits } from "./codex-usage";
7
8
  /**
8
9
  * Models the ChatGPT-backed Codex backend serves.
9
10
  *
@@ -87,7 +88,40 @@ export declare class CodexAgent extends OpenAiAgent<OpenAIModel, CodexModelCard>
87
88
  private readonly originator;
88
89
  private readonly clientVersion;
89
90
  private readonly codexBaseURL;
91
+ /**
92
+ * Quota state written by the fetch wrapper installed in the constructor.
93
+ *
94
+ * A holder object rather than a field because the wrapper is built *before*
95
+ * `super()` — the base constructor creates the SDK client, so the wrapper has
96
+ * to exist by then, and `this` is not available yet. `notify` is attached
97
+ * afterwards, once emitting is possible.
98
+ */
99
+ private readonly limits;
90
100
  constructor(config: CodexAgentConfig, history?: History);
101
+ /**
102
+ * What the most recent response said about the subscription's remaining
103
+ * allowance — this backend's answer to "what did that cost?".
104
+ *
105
+ * A ChatGPT subscription is not priced per request, so
106
+ * `lastTokenUsage.cost_usd` is undefined here and always will be. What a call
107
+ * spends is plan allowance, reported as two rolling windows (5-hourly and
108
+ * weekly) plus the credit balance that takes over once they are used up.
109
+ *
110
+ * Unlike `lastTokenUsage`, this is **not** reset per run: it describes the
111
+ * account, not the turn, so it keeps the last value seen until another call
112
+ * updates it. `undefined` before the first call, and after calls that carried
113
+ * no quota headers — `listModels()` is one, so only `execute()` /
114
+ * `executeStream()` refresh it. `AgentEvent.USAGE_LIMITS` fires on every
115
+ * update, including the ones on a failed request.
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * await agent.execute("Hello!");
120
+ * const limits = agent.lastUsageLimits;
121
+ * console.log(`${limits?.primary?.usedPercent}% of the 5h window used`);
122
+ * ```
123
+ */
124
+ get lastUsageLimits(): CodexUsageLimits | undefined;
91
125
  /**
92
126
  * Build an agent from the credentials `codex login` stored, wrapped in a
93
127
  * provider that refreshes the access token as it ages out.
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CodexAgent = void 0;
4
+ const AgentEvent_1 = require("../AgentEvent");
4
5
  const AgentError_1 = require("../errors/AgentError");
5
6
  const OpenAiAgent_1 = require("./OpenAiAgent");
6
7
  const codex_auth_1 = require("./codex-auth");
8
+ const codex_usage_1 = require("./codex-usage");
7
9
  /**
8
10
  * Agent for OpenAI models reached through a **ChatGPT subscription** rather
9
11
  * than a platform API key.
@@ -43,6 +45,9 @@ class CodexAgent extends OpenAiAgent_1.OpenAiAgent {
43
45
  const accountId = config.accountId ?? vendorConfig.accountId;
44
46
  const originator = config.originator ?? vendorConfig.originator ?? codex_auth_1.CODEX_ORIGINATOR;
45
47
  const baseURL = config.baseURL ?? vendorConfig.baseURL ?? codex_auth_1.CODEX_BASE_URL;
48
+ // Filled by the fetch wrapper below and adopted as `this.limits` once
49
+ // `super()` has run.
50
+ const limits = {};
46
51
  // Everything host-specific is passed *into* the base constructor rather
47
52
  // than supplied by an override: the base runs before this class's fields
48
53
  // are assigned, so an override could not read them.
@@ -60,18 +65,56 @@ class CodexAgent extends OpenAiAgent_1.OpenAiAgent {
60
65
  Accept: "text/event-stream",
61
66
  ...config.defaultHeaders,
62
67
  },
63
- // This backend reports failures as `{detail: …}`, which the SDK drops
64
- // on the floor — see wrapErrorBodyFetch().
65
- fetch: (0, OpenAiAgent_1.wrapErrorBodyFetch)(),
68
+ // Two wrappers, innermost first: normalise this backend's `{detail: …}`
69
+ // error bodies (which the SDK otherwise drops on the floor — see
70
+ // wrapErrorBodyFetch()), then read the `x-codex-*` quota headers off
71
+ // every response on the way back out.
72
+ fetch: (0, codex_usage_1.observeHeadersFetch)((headers) => {
73
+ const parsed = (0, codex_usage_1.parseCodexUsageLimits)(headers);
74
+ if (!parsed)
75
+ return;
76
+ limits.latest = parsed;
77
+ limits.notify?.(parsed);
78
+ }, (0, OpenAiAgent_1.wrapErrorBodyFetch)()),
66
79
  // Cast: the codex-specific keys (accountId, originator, clientVersion)
67
80
  // are not part of the base config, and `vendor` is supplied by it.
68
81
  }, history);
82
+ this.limits = limits;
83
+ // Only now can the wrapper emit; anything parsed before this point is still
84
+ // on `limits.latest`.
85
+ limits.notify = (usageLimits) => this.emit(AgentEvent_1.AgentEvent.USAGE_LIMITS, usageLimits);
69
86
  this.accountId = accountId;
70
87
  this.originator = originator;
71
88
  this.clientVersion =
72
89
  config.clientVersion ?? vendorConfig.clientVersion ?? codex_auth_1.CODEX_CLIENT_VERSION;
73
90
  this.codexBaseURL = baseURL;
74
91
  }
92
+ /**
93
+ * What the most recent response said about the subscription's remaining
94
+ * allowance — this backend's answer to "what did that cost?".
95
+ *
96
+ * A ChatGPT subscription is not priced per request, so
97
+ * `lastTokenUsage.cost_usd` is undefined here and always will be. What a call
98
+ * spends is plan allowance, reported as two rolling windows (5-hourly and
99
+ * weekly) plus the credit balance that takes over once they are used up.
100
+ *
101
+ * Unlike `lastTokenUsage`, this is **not** reset per run: it describes the
102
+ * account, not the turn, so it keeps the last value seen until another call
103
+ * updates it. `undefined` before the first call, and after calls that carried
104
+ * no quota headers — `listModels()` is one, so only `execute()` /
105
+ * `executeStream()` refresh it. `AgentEvent.USAGE_LIMITS` fires on every
106
+ * update, including the ones on a failed request.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * await agent.execute("Hello!");
111
+ * const limits = agent.lastUsageLimits;
112
+ * console.log(`${limits?.primary?.usedPercent}% of the 5h window used`);
113
+ * ```
114
+ */
115
+ get lastUsageLimits() {
116
+ return this.limits.latest;
117
+ }
75
118
  /**
76
119
  * Build an agent from the credentials `codex login` stored, wrapped in a
77
120
  * provider that refreshes the access token as it ages out.
@@ -46,6 +46,26 @@ export type AgentConfig<M extends OpenAIModel = OpenAIModel> = Omit<BaseAgentCon
46
46
  * @see lib/tools/BuiltInTool.ts
47
47
  */
48
48
  builtInTools?: BuiltInTool[];
49
+ /**
50
+ * Cache-routing key sent as `prompt_cache_key`. Requests sharing a key are
51
+ * steered to the same cache, which raises the prompt-cache hit rate for a
52
+ * long conversation or a fleet of agents that share a system prompt and tool
53
+ * belt. Any stable string works — a conversation id is the usual choice.
54
+ *
55
+ * Left unset by default: caching still happens without it, this only
56
+ * improves the routing.
57
+ *
58
+ * @see https://platform.openai.com/docs/guides/prompt-caching
59
+ */
60
+ promptCacheKey?: string;
61
+ /**
62
+ * How long cached prefixes stay warm. `"24h"` opts into extended retention;
63
+ * the default (`undefined`, i.e. the API's `in-memory`) expires a prefix
64
+ * within minutes.
65
+ *
66
+ * Ignored by the ChatGPT/Codex backend, which manages its own cache.
67
+ */
68
+ promptCacheRetention?: "in-memory" | "24h";
49
69
  };
50
70
  /**
51
71
  * Lowest `reasoning.effort` the given model accepts, used to resolve
@@ -63,6 +83,21 @@ export type AgentConfig<M extends OpenAIModel = OpenAIModel> = Omit<BaseAgentCon
63
83
  * `reasoningEffort` explicitly to override.
64
84
  */
65
85
  export declare function lowestReasoningEffort(model: string | undefined): ReasoningEffort | undefined;
86
+ /**
87
+ * `usage.input_tokens_details` as the wire actually carries it.
88
+ *
89
+ * The SDK's `ResponseUsage.InputTokensDetails` declares `cached_tokens` alone,
90
+ * but the ChatGPT/Codex backend also reports `cache_write_tokens` (observed
91
+ * live on 2026-09-10). Declared here rather than cast at the use site, and
92
+ * every field optional because a non-OpenAI host behind this SDK may report
93
+ * neither.
94
+ */
95
+ export type OpenAIInputTokensDetails = {
96
+ /** Prompt tokens served from cache. */
97
+ cached_tokens?: number;
98
+ /** Prompt tokens written to cache. Codex backend only. */
99
+ cache_write_tokens?: number;
100
+ };
66
101
  /**
67
102
  * `fetch` wrapper that rewrites a non-OpenAI-shaped error body into the shape
68
103
  * the SDK can read.
@@ -195,6 +230,15 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel, TModelCard
195
230
  * emits `response.reasoning_summary_text.delta` events when it is set.
196
231
  */
197
232
  private buildReasoningParams;
233
+ /**
234
+ * Prompt-caching parameters, omitted entirely when unconfigured so a request
235
+ * stays byte-identical to what earlier versions sent.
236
+ *
237
+ * Caching itself is automatic and needs no opt-in — these only influence
238
+ * which cache a request is routed to and how long the prefix stays warm. What
239
+ * was actually reused comes back on `lastTokenUsage.cache_read_tokens`.
240
+ */
241
+ private buildCacheParams;
198
242
  protected process(_input: string): Promise<string>;
199
243
  execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
200
244
  protected handleResponse(response: Response, options?: ExecuteOptions): Promise<string>;
@@ -171,6 +171,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
171
171
  const reasoningEffort = config.reasoningEffort ?? vendorConfig.reasoningEffort;
172
172
  const user = config.user ?? vendorConfig.user;
173
173
  const builtInTools = config.builtInTools ?? vendorConfig.builtInTools;
174
+ const promptCacheKey = config.promptCacheKey ?? vendorConfig.promptCacheKey;
175
+ const promptCacheRetention = config.promptCacheRetention ?? vendorConfig.promptCacheRetention;
174
176
  this.config = {
175
177
  model: config.model || "gpt-4.1-mini",
176
178
  // No default. `max_output_tokens` is optional on the Responses API, and
@@ -185,6 +187,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
185
187
  reasoningEffort,
186
188
  user,
187
189
  builtInTools,
190
+ promptCacheKey,
191
+ promptCacheRetention,
188
192
  apiKey: config.apiKey,
189
193
  baseURL,
190
194
  temperature: config.temperature,
@@ -350,6 +354,24 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
350
354
  },
351
355
  };
352
356
  }
357
+ /**
358
+ * Prompt-caching parameters, omitted entirely when unconfigured so a request
359
+ * stays byte-identical to what earlier versions sent.
360
+ *
361
+ * Caching itself is automatic and needs no opt-in — these only influence
362
+ * which cache a request is routed to and how long the prefix stays warm. What
363
+ * was actually reused comes back on `lastTokenUsage.cache_read_tokens`.
364
+ */
365
+ buildCacheParams() {
366
+ return {
367
+ ...(this.config.promptCacheKey
368
+ ? { prompt_cache_key: this.config.promptCacheKey }
369
+ : {}),
370
+ ...(this.config.promptCacheRetention
371
+ ? { prompt_cache_retention: this.config.promptCacheRetention }
372
+ : {}),
373
+ };
374
+ }
353
375
  async process(_input) {
354
376
  return "";
355
377
  }
@@ -395,6 +417,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
395
417
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
396
418
  user: this.config.user,
397
419
  ...this.buildReasoningParams(),
420
+ ...this.buildCacheParams(),
398
421
  }, { signal: options?.signal });
399
422
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
400
423
  return await this.handleResponse(response, options);
@@ -522,6 +545,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
522
545
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
523
546
  user: this.config.user,
524
547
  ...this.buildReasoningParams(),
548
+ ...this.buildCacheParams(),
525
549
  }, { signal: options?.signal });
526
550
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
527
551
  return this.handleResponse(newResponse, options);
@@ -703,6 +727,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
703
727
  top_p: this.config.topP,
704
728
  user: this.config.user,
705
729
  ...this.buildReasoningParams("auto"),
730
+ ...this.buildCacheParams(),
706
731
  }), { signal: options?.signal });
707
732
  let completedEvent = null;
708
733
  const streamedItems = [];
@@ -832,12 +857,16 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
832
857
  }
833
858
  }
834
859
  parseUsage(input) {
860
+ const inputDetails = input.input_tokens_details;
835
861
  return {
836
862
  input_tokens: input.input_tokens,
837
863
  output_tokens: input.output_tokens,
838
864
  total_tokens: input.total_tokens,
839
865
  // Reasoning tokens are already counted inside `output_tokens`.
840
866
  reasoning_tokens: input.output_tokens_details?.reasoning_tokens,
867
+ // Cache counts are part of `input_tokens`, not extra on top of it.
868
+ cache_read_tokens: inputDetails?.cached_tokens,
869
+ cache_write_tokens: inputDetails?.cache_write_tokens,
841
870
  };
842
871
  }
843
872
  }
@@ -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
@@ -3,6 +3,7 @@ export * from "./agents/anthropic/ClaudeAgent";
3
3
  export { OpenAiAgent } from "./agents/openai/OpenAiAgent";
4
4
  export { CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_ORIGINATOR, CODEX_TOKEN_URL, codexAuthFilePath, createCodexTokenProvider, loadCodexCredentials, refreshCodexCredentials, } from "./agents/openai/codex-auth";
5
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";
6
7
  export { CodexAgent } from "./agents/openai/CodexAgent";
7
8
  export type { CodexAgentConfig, CodexModel, CodexReasoningEffort, } from "./agents/openai/CodexAgent";
8
9
  export { MistralAgent } from "./agents/mistral/MistralAgent";
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.openRouterTransformer = exports.chatCompletionsTransformer = exports.ollamaTransformer = exports.geminiTransformer = exports.mistralTransformer = exports.openAiTransformer = exports.anthropicTransformer = exports.OpenRouterAgent = exports.OpenAICompatibleAgent = exports.LlamaCppAgent = exports.OllamaAgent = exports.GEMINI_RETIRED_MODELS = exports.GeminiAgent = exports.MistralAgent = exports.CodexAgent = exports.refreshCodexCredentials = exports.loadCodexCredentials = exports.createCodexTokenProvider = exports.codexAuthFilePath = exports.CODEX_TOKEN_URL = exports.CODEX_ORIGINATOR = exports.CODEX_CLIENT_ID = exports.CODEX_BASE_URL = exports.OpenAiAgent = void 0;
25
+ exports.openRouterTransformer = exports.chatCompletionsTransformer = exports.ollamaTransformer = exports.geminiTransformer = exports.mistralTransformer = exports.openAiTransformer = exports.anthropicTransformer = exports.OpenRouterAgent = exports.OpenAICompatibleAgent = exports.LlamaCppAgent = exports.OllamaAgent = exports.GEMINI_RETIRED_MODELS = exports.GeminiAgent = exports.MistralAgent = exports.CodexAgent = exports.observeHeadersFetch = exports.parseCodexUsageLimits = exports.refreshCodexCredentials = exports.loadCodexCredentials = exports.createCodexTokenProvider = exports.codexAuthFilePath = exports.CODEX_TOKEN_URL = exports.CODEX_ORIGINATOR = exports.CODEX_CLIENT_ID = exports.CODEX_BASE_URL = exports.OpenAiAgent = void 0;
26
26
  // Agents
27
27
  __exportStar(require("./agents/BaseAgent"), exports);
28
28
  __exportStar(require("./agents/anthropic/ClaudeAgent"), exports);
@@ -37,6 +37,9 @@ Object.defineProperty(exports, "codexAuthFilePath", { enumerable: true, get: fun
37
37
  Object.defineProperty(exports, "createCodexTokenProvider", { enumerable: true, get: function () { return codex_auth_1.createCodexTokenProvider; } });
38
38
  Object.defineProperty(exports, "loadCodexCredentials", { enumerable: true, get: function () { return codex_auth_1.loadCodexCredentials; } });
39
39
  Object.defineProperty(exports, "refreshCodexCredentials", { enumerable: true, get: function () { return codex_auth_1.refreshCodexCredentials; } });
40
+ var codex_usage_1 = require("./agents/openai/codex-usage");
41
+ Object.defineProperty(exports, "parseCodexUsageLimits", { enumerable: true, get: function () { return codex_usage_1.parseCodexUsageLimits; } });
42
+ Object.defineProperty(exports, "observeHeadersFetch", { enumerable: true, get: function () { return codex_usage_1.observeHeadersFetch; } });
40
43
  var CodexAgent_1 = require("./agents/openai/CodexAgent");
41
44
  Object.defineProperty(exports, "CodexAgent", { enumerable: true, get: function () { return CodexAgent_1.CodexAgent; } });
42
45
  var MistralAgent_1 = require("./agents/mistral/MistralAgent");
package/dist/openai.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export * from "./core";
2
2
  export { OpenAiAgent, describeOpenAIError, wrapErrorBodyFetch, } from "./agents/openai/OpenAiAgent";
3
+ export type { OpenAIInputTokensDetails } from "./agents/openai/OpenAiAgent";
3
4
  export { CodexAgent } from "./agents/openai/CodexAgent";
4
5
  export type { CodexAgentConfig, CodexModel, CodexReasoningEffort, } from "./agents/openai/CodexAgent";
5
6
  export { openAiTransformer } from "./history/transformers";
6
7
  export { CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_ORIGINATOR, CODEX_TOKEN_URL, codexAuthFilePath, createCodexTokenProvider, decodeJwtClaims, jwtExpiry, loadCodexCredentials, refreshCodexCredentials, type CodexCredentials, type CodexTokenProvider, type CodexTokenProviderOptions, type CodexModelCard, } from "./agents/openai/codex-auth";
8
+ export { parseCodexUsageLimits, observeHeadersFetch, type CodexUsageLimits, type CodexRateLimitWindow, type CodexCredits, } from "./agents/openai/codex-usage";
7
9
  //# sourceMappingURL=openai.d.ts.map
package/dist/openai.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.refreshCodexCredentials = exports.loadCodexCredentials = exports.jwtExpiry = exports.decodeJwtClaims = exports.createCodexTokenProvider = exports.codexAuthFilePath = exports.CODEX_TOKEN_URL = exports.CODEX_ORIGINATOR = exports.CODEX_CLIENT_ID = exports.CODEX_BASE_URL = exports.openAiTransformer = exports.CodexAgent = exports.wrapErrorBodyFetch = exports.describeOpenAIError = exports.OpenAiAgent = void 0;
17
+ exports.observeHeadersFetch = exports.parseCodexUsageLimits = exports.refreshCodexCredentials = exports.loadCodexCredentials = exports.jwtExpiry = exports.decodeJwtClaims = exports.createCodexTokenProvider = exports.codexAuthFilePath = exports.CODEX_TOKEN_URL = exports.CODEX_ORIGINATOR = exports.CODEX_CLIENT_ID = exports.CODEX_BASE_URL = exports.openAiTransformer = exports.CodexAgent = exports.wrapErrorBodyFetch = exports.describeOpenAIError = exports.OpenAiAgent = void 0;
18
18
  // OpenAI Agent Entry Point
19
19
  __exportStar(require("./core"), exports);
20
20
  var OpenAiAgent_1 = require("./agents/openai/OpenAiAgent");
@@ -36,4 +36,7 @@ Object.defineProperty(exports, "decodeJwtClaims", { enumerable: true, get: funct
36
36
  Object.defineProperty(exports, "jwtExpiry", { enumerable: true, get: function () { return codex_auth_1.jwtExpiry; } });
37
37
  Object.defineProperty(exports, "loadCodexCredentials", { enumerable: true, get: function () { return codex_auth_1.loadCodexCredentials; } });
38
38
  Object.defineProperty(exports, "refreshCodexCredentials", { enumerable: true, get: function () { return codex_auth_1.refreshCodexCredentials; } });
39
+ var codex_usage_1 = require("./agents/openai/codex-usage");
40
+ Object.defineProperty(exports, "parseCodexUsageLimits", { enumerable: true, get: function () { return codex_usage_1.parseCodexUsageLimits; } });
41
+ Object.defineProperty(exports, "observeHeadersFetch", { enumerable: true, get: function () { return codex_usage_1.observeHeadersFetch; } });
39
42
  //# sourceMappingURL=openai.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentionai/agents",
3
3
  "author": "Laurent Zuijdwijk",
4
- "version": "1.13.0",
4
+ "version": "1.14.0",
5
5
  "description": "Agent Library",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",