@agentionai/agents 1.13.0 → 1.15.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,38 @@ 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 — `"in_memory"` expires them within
148
+ * minutes, `"24h"` keeps them up to a day. Left unset the default follows
149
+ * your organization's data-retention policy (`"24h"` without ZDR,
150
+ * `"in_memory"` with it), so set it explicitly if you care either way.
151
+ * Ignored by the ChatGPT/Codex backend, which manages its own cache.
152
+ */
153
+ promptCacheRetention?: "in_memory" | "24h";
154
+ /**
155
+ * Request each turn's reasoning as an encrypted blob and replay it on every
156
+ * later request of the conversation, so a reasoning model keeps its own
157
+ * thinking across tool hops and turns instead of re-deriving it.
158
+ *
159
+ * Setting it to `false` stops both halves — nothing is requested, and blobs
160
+ * already in the history are not sent either, which is what makes it a way
161
+ * out of the model-switch rejection below.
162
+ *
163
+ * Defaults to on for models known to reason on OpenAI's own API, off
164
+ * otherwise — including behind a custom `baseURL`, where the host may not
165
+ * support the parameter. `CodexAgent` defaults it to on for every model.
166
+ *
167
+ * Reasoning blobs are tied to the model that produced them: switching models
168
+ * mid-conversation with a history full of them is rejected. Clear the history
169
+ * or turn this off when doing that.
170
+ */
171
+ includeEncryptedReasoning?: boolean;
140
172
  /**
141
173
  * Override the API base URL. Defaults to `api.openai.com/v1`; `CodexAgent`
142
174
  * defaults it to `https://chatgpt.com/backend-api/codex`, and setting it
@@ -148,6 +180,14 @@ export interface OpenAISpecificConfig {
148
180
  * `chatgpt-account-id` header.
149
181
  */
150
182
  accountId?: string;
183
+ /**
184
+ * `CodexAgent` only: conversation id sent as the `session_id` header, which
185
+ * is what routes requests to a shared prompt cache on that backend — setting
186
+ * it is how you opt into caching there. Unset by default, so the header is
187
+ * omitted and the request stays byte-identical; `randomUUID()` per agent
188
+ * gives a per-run cache, a stable id shares one across runs.
189
+ */
190
+ sessionId?: string;
151
191
  /**
152
192
  * `CodexAgent` only: client identifier sent as the `originator` header.
153
193
  * OpenAI varies the model catalog by originator.
@@ -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
  *
@@ -48,7 +49,57 @@ export type CodexAgentConfig = Omit<OpenAiAgentConfig, "model" | "reasoningEffor
48
49
  * @default CODEX_CLIENT_VERSION
49
50
  */
50
51
  clientVersion?: string;
52
+ /**
53
+ * Conversation identifier sent as the `session_id` header, as the Codex CLI
54
+ * does — **this is what makes prompt caching work on this backend**, and
55
+ * setting it is how you opt into that caching.
56
+ *
57
+ * Requests carrying the same value are routed together and reuse each other's
58
+ * cached prefix; without it the backend caches essentially nothing, however
59
+ * identical the prefix. Measured on 2026-09-10 with a ~9K-token prefix
60
+ * repeated 8 times: 0/14 calls hit the cache with no header, 12/14 hit
61
+ * (~98% of the prefix) with one. `prompt_cache_key`, the platform API's
62
+ * lever, makes no difference here.
63
+ *
64
+ * **Unset by default**, so the header is omitted entirely and a request stays
65
+ * byte-identical to what earlier versions sent — the same opt-in rule as
66
+ * {@link AgentConfig.promptCacheKey} and
67
+ * {@link AgentConfig.promptCacheRetention}. Caching is not free of side
68
+ * effects: it groups your requests server-side under an id you chose, so it
69
+ * is yours to turn on rather than the agent's to assume.
70
+ *
71
+ * Any stable string works; the value is opaque and only its stability
72
+ * matters. One id per conversation is the usual grain — `randomUUID()` per
73
+ * agent instance reproduces the old default:
74
+ *
75
+ * ```typescript
76
+ * new CodexAgent({ …, sessionId: randomUUID() }) // cache within this run
77
+ * new CodexAgent({ …, sessionId: conversationId }) // cache across runs
78
+ * ```
79
+ *
80
+ * Note it is a *header*: `session_id` in the request body is rejected with
81
+ * *"Unsupported parameter: session_id"*.
82
+ */
83
+ sessionId?: string;
51
84
  };
85
+ /**
86
+ * Configuration a factory hands to the constructor, before the credentials it
87
+ * resolves are merged in.
88
+ */
89
+ type CodexFactoryConfig = Omit<CodexAgentConfig, "apiKey" | "accountId"> & {
90
+ tokenOptions?: CodexTokenProviderOptions;
91
+ };
92
+ /**
93
+ * What a static factory needs of the class it was called on: a constructor for
94
+ * the concrete subclass — which is where `T` is inferred from — intersected
95
+ * with the sibling factory it delegates to, so an override of that runs too.
96
+ *
97
+ * `Pick<typeof CodexAgent, …>` rather than a hand-written signature: the
98
+ * sibling's own `this` parameter is what carries `T` through the delegation,
99
+ * and restating it here would erase that and pin every subclass back to the
100
+ * base type.
101
+ */
102
+ type CodexAgentClass<T extends CodexAgent> = (new (config: CodexAgentConfig, history?: History) => T) & Pick<typeof CodexAgent, "fromCredentials">;
52
103
  /**
53
104
  * Agent for OpenAI models reached through a **ChatGPT subscription** rather
54
105
  * than a platform API key.
@@ -87,23 +138,77 @@ export declare class CodexAgent extends OpenAiAgent<OpenAIModel, CodexModelCard>
87
138
  private readonly originator;
88
139
  private readonly clientVersion;
89
140
  private readonly codexBaseURL;
141
+ /**
142
+ * The `session_id` this agent sends on every request, or `undefined` when the
143
+ * header is not being sent — the key the backend's prompt cache is routed by.
144
+ *
145
+ * `undefined` means prompt caching is effectively off for this agent; set
146
+ * {@link CodexSpecificConfig.sessionId} to opt in. Read it back to pin a
147
+ * later agent to the same cache.
148
+ */
149
+ readonly sessionId?: string;
150
+ /**
151
+ * Quota state written by the fetch wrapper installed in the constructor.
152
+ *
153
+ * A holder object rather than a field because the wrapper is built *before*
154
+ * `super()` — the base constructor creates the SDK client, so the wrapper has
155
+ * to exist by then, and `this` is not available yet. `notify` is attached
156
+ * afterwards, once emitting is possible.
157
+ */
158
+ private readonly limits;
90
159
  constructor(config: CodexAgentConfig, history?: History);
160
+ /**
161
+ * What the most recent response said about the subscription's remaining
162
+ * allowance — this backend's answer to "what did that cost?".
163
+ *
164
+ * A ChatGPT subscription is not priced per request, so
165
+ * `lastTokenUsage.cost_usd` is undefined here and always will be. What a call
166
+ * spends is plan allowance, reported as two rolling windows (5-hourly and
167
+ * weekly) plus the credit balance that takes over once they are used up.
168
+ *
169
+ * Unlike `lastTokenUsage`, this is **not** reset per run: it describes the
170
+ * account, not the turn, so it keeps the last value seen until another call
171
+ * updates it. `undefined` before the first call, and after calls that carried
172
+ * no quota headers — `listModels()` is one, so only `execute()` /
173
+ * `executeStream()` refresh it. `AgentEvent.USAGE_LIMITS` fires on every
174
+ * update, including the ones on a failed request.
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * await agent.execute("Hello!");
179
+ * const limits = agent.lastUsageLimits;
180
+ * console.log(`${limits?.primary?.usedPercent}% of the 5h window used`);
181
+ * ```
182
+ */
183
+ get lastUsageLimits(): CodexUsageLimits | undefined;
91
184
  /**
92
185
  * Build an agent from the credentials `codex login` stored, wrapped in a
93
186
  * provider that refreshes the access token as it ages out.
94
187
  *
188
+ * Constructs `this`, so `MyCodexAgent.fromCodexCli(…)` returns a
189
+ * `MyCodexAgent` — see {@link CodexAgent.fromCredentials}.
190
+ *
95
191
  * @throws if no credentials are present — run `codex login` first.
96
192
  */
97
- static fromCodexCli(config: Omit<CodexAgentConfig, "apiKey" | "accountId"> & {
193
+ static fromCodexCli<T extends CodexAgent>(this: CodexAgentClass<T>, config: CodexFactoryConfig & {
98
194
  /** Read `auth.json` from somewhere other than `$CODEX_HOME`. */
99
195
  codexHome?: string;
100
- /** Forwarded to {@link createCodexTokenProvider}. */
101
- tokenOptions?: CodexTokenProviderOptions;
102
- }, history?: History): Promise<CodexAgent>;
103
- /** Build an agent from credentials obtained however you like. */
104
- static fromCredentials(credentials: CodexCredentials, config: Omit<CodexAgentConfig, "apiKey" | "accountId"> & {
105
- tokenOptions?: CodexTokenProviderOptions;
106
- }, history?: History): CodexAgent;
196
+ }, history?: History): Promise<T>;
197
+ /**
198
+ * Build an agent from credentials obtained however you like.
199
+ *
200
+ * Instantiates `this` rather than `CodexAgent`, so a subclass gets its own
201
+ * type back and its overrides actually run. Hard-coding the class here made
202
+ * `class MyCodexAgent extends CodexAgent` silently produce a plain
203
+ * `CodexAgent` — no error, no override, and nothing to see until an
204
+ * experiment came back saying the change under test had no effect.
205
+ */
206
+ static fromCredentials<T extends CodexAgent>(this: new (config: CodexAgentConfig, history?: History) => T, credentials: CodexCredentials, config: CodexFactoryConfig, history?: History): T;
207
+ /**
208
+ * Every model on this backend reasons, and this is what the Codex CLI itself
209
+ * does, so the encrypted-reasoning round trip is on unless turned off.
210
+ */
211
+ protected defaultIncludeEncryptedReasoning(): boolean;
107
212
  /** This backend refuses `stream: false` outright. */
108
213
  protected get forceStreaming(): boolean;
109
214
  /**
@@ -132,4 +237,5 @@ export declare class CodexAgent extends OpenAiAgent<OpenAIModel, CodexModelCard>
132
237
  */
133
238
  listModels(): Promise<ModelInfo<CodexModelCard>[]>;
134
239
  }
240
+ export {};
135
241
  //# sourceMappingURL=CodexAgent.d.ts.map
@@ -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,12 @@ 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
+ // No generated fallback: caching is opt-in, so an unset id means the header
49
+ // is not sent at all.
50
+ const sessionId = config.sessionId ?? vendorConfig.sessionId;
51
+ // Filled by the fetch wrapper below and adopted as `this.limits` once
52
+ // `super()` has run.
53
+ const limits = {};
46
54
  // Everything host-specific is passed *into* the base constructor rather
47
55
  // than supplied by an override: the base runs before this class's fields
48
56
  // are assigned, so an override could not read them.
@@ -55,37 +63,96 @@ class CodexAgent extends OpenAiAgent_1.OpenAiAgent {
55
63
  ...(accountId ? { "chatgpt-account-id": accountId } : {}),
56
64
  "OpenAI-Beta": "responses=experimental",
57
65
  originator,
66
+ // Opt-in. Stable for the life of the agent: the backend keys its
67
+ // prompt cache on this, and drops to ~0% hit rate without it. Omitted
68
+ // entirely when unset, so the request is byte-identical to one sent
69
+ // before this existed.
70
+ ...(sessionId ? { session_id: sessionId } : {}),
58
71
  // Every Codex request is a stream; the SDK would send
59
72
  // `application/json`, which no reference client does.
60
73
  Accept: "text/event-stream",
61
74
  ...config.defaultHeaders,
62
75
  },
63
- // This backend reports failures as `{detail: …}`, which the SDK drops
64
- // on the floor — see wrapErrorBodyFetch().
65
- fetch: (0, OpenAiAgent_1.wrapErrorBodyFetch)(),
76
+ // Two wrappers, innermost first: normalise this backend's `{detail: …}`
77
+ // error bodies (which the SDK otherwise drops on the floor — see
78
+ // wrapErrorBodyFetch()), then read the `x-codex-*` quota headers off
79
+ // every response on the way back out.
80
+ fetch: (0, codex_usage_1.observeHeadersFetch)((headers) => {
81
+ const parsed = (0, codex_usage_1.parseCodexUsageLimits)(headers);
82
+ if (!parsed)
83
+ return;
84
+ limits.latest = parsed;
85
+ limits.notify?.(parsed);
86
+ }, (0, OpenAiAgent_1.wrapErrorBodyFetch)()),
66
87
  // Cast: the codex-specific keys (accountId, originator, clientVersion)
67
88
  // are not part of the base config, and `vendor` is supplied by it.
68
89
  }, history);
90
+ this.limits = limits;
91
+ // Only now can the wrapper emit; anything parsed before this point is still
92
+ // on `limits.latest`.
93
+ limits.notify = (usageLimits) => this.emit(AgentEvent_1.AgentEvent.USAGE_LIMITS, usageLimits);
69
94
  this.accountId = accountId;
70
95
  this.originator = originator;
71
96
  this.clientVersion =
72
- config.clientVersion ?? vendorConfig.clientVersion ?? codex_auth_1.CODEX_CLIENT_VERSION;
97
+ config.clientVersion ??
98
+ vendorConfig.clientVersion ??
99
+ codex_auth_1.CODEX_CLIENT_VERSION;
73
100
  this.codexBaseURL = baseURL;
101
+ this.sessionId = sessionId;
102
+ }
103
+ /**
104
+ * What the most recent response said about the subscription's remaining
105
+ * allowance — this backend's answer to "what did that cost?".
106
+ *
107
+ * A ChatGPT subscription is not priced per request, so
108
+ * `lastTokenUsage.cost_usd` is undefined here and always will be. What a call
109
+ * spends is plan allowance, reported as two rolling windows (5-hourly and
110
+ * weekly) plus the credit balance that takes over once they are used up.
111
+ *
112
+ * Unlike `lastTokenUsage`, this is **not** reset per run: it describes the
113
+ * account, not the turn, so it keeps the last value seen until another call
114
+ * updates it. `undefined` before the first call, and after calls that carried
115
+ * no quota headers — `listModels()` is one, so only `execute()` /
116
+ * `executeStream()` refresh it. `AgentEvent.USAGE_LIMITS` fires on every
117
+ * update, including the ones on a failed request.
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * await agent.execute("Hello!");
122
+ * const limits = agent.lastUsageLimits;
123
+ * console.log(`${limits?.primary?.usedPercent}% of the 5h window used`);
124
+ * ```
125
+ */
126
+ get lastUsageLimits() {
127
+ return this.limits.latest;
74
128
  }
75
129
  /**
76
130
  * Build an agent from the credentials `codex login` stored, wrapped in a
77
131
  * provider that refreshes the access token as it ages out.
78
132
  *
133
+ * Constructs `this`, so `MyCodexAgent.fromCodexCli(…)` returns a
134
+ * `MyCodexAgent` — see {@link CodexAgent.fromCredentials}.
135
+ *
79
136
  * @throws if no credentials are present — run `codex login` first.
80
137
  */
81
138
  static async fromCodexCli(config, history) {
82
139
  const credentials = await (0, codex_auth_1.loadCodexCredentials)(config.codexHome);
83
- return CodexAgent.fromCredentials(credentials, config, history);
140
+ // `this`, not `CodexAgent`: routed through the subclass so an override of
141
+ // `fromCredentials` is not skipped either.
142
+ return this.fromCredentials(credentials, config, history);
84
143
  }
85
- /** Build an agent from credentials obtained however you like. */
144
+ /**
145
+ * Build an agent from credentials obtained however you like.
146
+ *
147
+ * Instantiates `this` rather than `CodexAgent`, so a subclass gets its own
148
+ * type back and its overrides actually run. Hard-coding the class here made
149
+ * `class MyCodexAgent extends CodexAgent` silently produce a plain
150
+ * `CodexAgent` — no error, no override, and nothing to see until an
151
+ * experiment came back saying the change under test had no effect.
152
+ */
86
153
  static fromCredentials(credentials, config, history) {
87
154
  const tokens = (0, codex_auth_1.createCodexTokenProvider)(credentials, config.tokenOptions);
88
- return new CodexAgent({
155
+ return new this({
89
156
  ...config,
90
157
  // The function form: the SDK re-invokes it before every request, so a
91
158
  // long run outlives the ~1h token.
@@ -93,6 +160,13 @@ class CodexAgent extends OpenAiAgent_1.OpenAiAgent {
93
160
  accountId: credentials.accountId,
94
161
  }, history);
95
162
  }
163
+ /**
164
+ * Every model on this backend reasons, and this is what the Codex CLI itself
165
+ * does, so the encrypted-reasoning round trip is on unless turned off.
166
+ */
167
+ defaultIncludeEncryptedReasoning() {
168
+ return true;
169
+ }
96
170
  /** This backend refuses `stream: false` outright. */
97
171
  get forceStreaming() {
98
172
  return true;
@@ -46,6 +46,69 @@ 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
+ * Ask for each turn's reasoning to come back as an encrypted, replayable
63
+ * blob (`include: ["reasoning.encrypted_content"]`), and send those blobs
64
+ * back on every later request of the conversation.
65
+ *
66
+ * Both halves matter, and both happen here: requesting the blobs without
67
+ * replaying them changes nothing, and replaying is only possible because the
68
+ * agent runs with `store: false`, which leaves the provider holding no
69
+ * reasoning of its own.
70
+ *
71
+ * Why it is worth the bytes on a reasoning model: the model's own thinking is
72
+ * part of what it saw when it decided to call a tool, so dropping it between
73
+ * hops makes the model re-derive it — and, because the replayed prefix no
74
+ * longer matches what was processed last turn, breaks the prompt cache from
75
+ * the first turn onward.
76
+ *
77
+ * Setting it to `false` stops both halves too: nothing is requested, and
78
+ * blobs already sitting in the history are not replayed either. That is what
79
+ * makes turning it off a way out of the model-switch rejection below, rather
80
+ * than a half-measure that keeps sending the old model's reasoning.
81
+ *
82
+ * Defaults to on for models known to reason (see
83
+ * {@link OPENAI_REASONING_SUPPORT}) *on OpenAI's own API*, and off otherwise
84
+ * — including behind a custom `baseURL`, since a model name says nothing
85
+ * about whether the host serving it accepts the parameter. So a non-reasoning
86
+ * model such as `gpt-4.1-mini`, and any gateway or local server, sends a
87
+ * byte-identical request. Set it explicitly for a model too new to be in that
88
+ * table, or for a compatible host.
89
+ *
90
+ * Reasoning blobs are tied to the model that produced them: switching models
91
+ * mid-conversation with a history full of them is rejected. Clear the history
92
+ * or turn this off when doing that.
93
+ */
94
+ includeEncryptedReasoning?: boolean;
95
+ /**
96
+ * How long cached prefixes stay warm. `"in_memory"` expires a prefix after
97
+ * minutes of inactivity (an hour at the outside); `"24h"` keeps it up to a
98
+ * day.
99
+ *
100
+ * Left unset, the default is your organization's data-retention policy, not
101
+ * a fixed value: orgs *without* ZDR default to `"24h"`, orgs *with* ZDR to
102
+ * `"in_memory"`. Set it explicitly to stop prefixes being retained for a day
103
+ * without having to enable ZDR account-wide.
104
+ *
105
+ * `gpt-5.5` and later accept only `"24h"` here, and the field is deprecated
106
+ * upstream in favour of `prompt_cache_options.ttl` — the two are independent
107
+ * (this is a *maximum* retention policy, `ttl` a *minimum* lifetime).
108
+ *
109
+ * Ignored by the ChatGPT/Codex backend, which manages its own cache.
110
+ */
111
+ promptCacheRetention?: "in_memory" | "24h";
49
112
  };
50
113
  /**
51
114
  * Lowest `reasoning.effort` the given model accepts, used to resolve
@@ -63,6 +126,24 @@ export type AgentConfig<M extends OpenAIModel = OpenAIModel> = Omit<BaseAgentCon
63
126
  * `reasoningEffort` explicitly to override.
64
127
  */
65
128
  export declare function lowestReasoningEffort(model: string | undefined): ReasoningEffort | undefined;
129
+ /**
130
+ * `usage.input_tokens_details` as the wire actually carries it.
131
+ *
132
+ * The SDK declares both counts on `ResponseUsage.InputTokensDetails` as of
133
+ * `openai` 7.x (6.x had `cached_tokens` alone, though the ChatGPT/Codex backend
134
+ * already reported `cache_write_tokens` — observed live on 2026-09-10). What it
135
+ * gets wrong for our purposes is that it types them as *required*: a
136
+ * non-OpenAI host behind this SDK — llama.cpp, vLLM, a gateway — may report
137
+ * one, the other, or neither. Restating them as optional keeps `parseUsage`
138
+ * from trusting a number that isn't there, and keeps `undefined` ("provider
139
+ * said nothing") distinct from `0` ("nothing was cached").
140
+ */
141
+ export type OpenAIInputTokensDetails = {
142
+ /** Prompt tokens served from cache. */
143
+ cached_tokens?: number;
144
+ /** Prompt tokens written to cache. On `gpt-5.6`+ these bill at 1.25x input. */
145
+ cache_write_tokens?: number;
146
+ };
66
147
  /**
67
148
  * `fetch` wrapper that rewrites a non-OpenAI-shaped error body into the shape
68
149
  * the SDK can read.
@@ -195,6 +276,72 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel, TModelCard
195
276
  * emits `response.reasoning_summary_text.delta` events when it is set.
196
277
  */
197
278
  private buildReasoningParams;
279
+ /**
280
+ * Whether {@link AgentConfig.includeEncryptedReasoning} defaults to on, when
281
+ * the caller has not said either way.
282
+ *
283
+ * On only for OpenAI's own API *and* a model the reasoning table knows
284
+ * about. Asking a non-reasoning model for reasoning blobs would add a
285
+ * parameter it has nothing to put in; asking a third-party host behind a
286
+ * custom `baseURL` — a gateway, vLLM, llama.cpp — would silently start
287
+ * sending it a parameter it never received before, which is the opposite of
288
+ * the byte-identical request this default exists to preserve. A model name
289
+ * says nothing about the host serving it, so an OpenAI-shaped name on a proxy
290
+ * must not be enough on its own. Set the flag explicitly for a host that does
291
+ * support the round trip.
292
+ *
293
+ * `CodexAgent` overrides this: it always runs against a custom `baseURL`, and
294
+ * every model on that backend reasons.
295
+ *
296
+ * Called from the base constructor, so an override must depend on nothing but
297
+ * its arguments: the subclass's own fields are not assigned yet.
298
+ */
299
+ protected defaultIncludeEncryptedReasoning(model: string | undefined, baseURL: string | undefined): boolean;
300
+ /**
301
+ * The `include` field, asking for reasoning to come back in a form that can
302
+ * be replayed on the next request.
303
+ *
304
+ * Omitted entirely when off, so requests stay byte-identical to what earlier
305
+ * versions sent. The other half of this — putting the returned items back
306
+ * into `input` — is `openAiTransformer`'s, fed by
307
+ * {@link OpenAiAgent.replayableReasoning}.
308
+ */
309
+ private buildIncludeParams;
310
+ /**
311
+ * The `reasoning` items of a response that are worth keeping.
312
+ *
313
+ * Only items carrying `encrypted_content` qualify: the agent always sends
314
+ * `store: false`, so the provider has retained nothing, and an item replayed
315
+ * without its payload cannot be resolved — the request fails rather than
316
+ * silently ignoring it. A summary-only item is therefore dropped, exactly as
317
+ * it was before this existed.
318
+ *
319
+ * A turn that also used a **built-in tool** keeps its reasoning too. Those
320
+ * items (`web_search_call` and friends) are not stored, so the replayed turn
321
+ * is `[reasoning, reasoning, message]` where the model emitted
322
+ * `[reasoning, web_search_call, reasoning, message]`. That was expected to be
323
+ * rejected — "reasoning item without its required following item" — but it is
324
+ * not: probed live on 2026-09-10 against `gpt-5-nano` and `gpt-5.4-mini`, the
325
+ * API accepted that shape, the same reasoning item twice in a row, a
326
+ * reasoning item followed only by the next *user* turn, and a dangling
327
+ * reasoning item last in `input` with nothing after it at all. That ordering
328
+ * rule appears to govern the `store: true` / `previous_response_id` flow, not
329
+ * this one, which sends `store: false` and an explicit `input`.
330
+ *
331
+ * So the reasoning is kept. Dropping it would cost every `builtInTools` user
332
+ * their prompt-cache continuity to avoid a rejection that does not happen.
333
+ * Revisit if the API tightens.
334
+ */
335
+ protected replayableReasoning(response: Response): unknown[];
336
+ /**
337
+ * Prompt-caching parameters, omitted entirely when unconfigured so a request
338
+ * stays byte-identical to what earlier versions sent.
339
+ *
340
+ * Caching itself is automatic and needs no opt-in — these only influence
341
+ * which cache a request is routed to and how long the prefix stays warm. What
342
+ * was actually reused comes back on `lastTokenUsage.cache_read_tokens`.
343
+ */
344
+ private buildCacheParams;
198
345
  protected process(_input: string): Promise<string>;
199
346
  execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
200
347
  protected handleResponse(response: Response, options?: ExecuteOptions): Promise<string>;