@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.
@@ -107,7 +107,7 @@ function describeOpenAIError(error) {
107
107
  const body = err?.error;
108
108
  const fromBody = typeof body === "string"
109
109
  ? body
110
- : (body?.message ?? body?.detail ?? undefined);
110
+ : body?.message ?? body?.detail ?? undefined;
111
111
  return {
112
112
  message: fromBody ?? err?.detail ?? err?.message ?? "Unknown error",
113
113
  code: typeof body === "object" ? body?.code : undefined,
@@ -171,6 +171,11 @@ 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;
176
+ const includeEncryptedReasoning = config.includeEncryptedReasoning ??
177
+ vendorConfig.includeEncryptedReasoning ??
178
+ this.defaultIncludeEncryptedReasoning(config.model, baseURL);
174
179
  this.config = {
175
180
  model: config.model || "gpt-4.1-mini",
176
181
  // No default. `max_output_tokens` is optional on the Responses API, and
@@ -185,6 +190,9 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
185
190
  reasoningEffort,
186
191
  user,
187
192
  builtInTools,
193
+ promptCacheKey,
194
+ promptCacheRetention,
195
+ includeEncryptedReasoning,
188
196
  apiKey: config.apiKey,
189
197
  baseURL,
190
198
  temperature: config.temperature,
@@ -223,7 +231,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
223
231
  /** The configured key, resolving the function form if that is what was given. */
224
232
  async resolveApiKey() {
225
233
  const key = this.config.apiKey;
226
- return typeof key === "function" ? await key() : (key ?? "");
234
+ return typeof key === "function" ? await key() : key ?? "";
227
235
  }
228
236
  getToolDefinitions() {
229
237
  return Array.from(this.tools.values()).map((tool) => {
@@ -278,7 +286,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
278
286
  const output = streamedItems;
279
287
  const outputText = output
280
288
  .filter((item) => item.type === "message")
281
- .flatMap((item) => ("content" in item ? (item.content ?? []) : []))
289
+ .flatMap((item) => ("content" in item ? item.content ?? [] : []))
282
290
  .filter((part) => part?.type === "output_text")
283
291
  .map((part) => ("text" in part ? part.text : ""))
284
292
  .join("");
@@ -350,6 +358,95 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
350
358
  },
351
359
  };
352
360
  }
361
+ /**
362
+ * Whether {@link AgentConfig.includeEncryptedReasoning} defaults to on, when
363
+ * the caller has not said either way.
364
+ *
365
+ * On only for OpenAI's own API *and* a model the reasoning table knows
366
+ * about. Asking a non-reasoning model for reasoning blobs would add a
367
+ * parameter it has nothing to put in; asking a third-party host behind a
368
+ * custom `baseURL` — a gateway, vLLM, llama.cpp — would silently start
369
+ * sending it a parameter it never received before, which is the opposite of
370
+ * the byte-identical request this default exists to preserve. A model name
371
+ * says nothing about the host serving it, so an OpenAI-shaped name on a proxy
372
+ * must not be enough on its own. Set the flag explicitly for a host that does
373
+ * support the round trip.
374
+ *
375
+ * `CodexAgent` overrides this: it always runs against a custom `baseURL`, and
376
+ * every model on that backend reasons.
377
+ *
378
+ * Called from the base constructor, so an override must depend on nothing but
379
+ * its arguments: the subclass's own fields are not assigned yet.
380
+ */
381
+ defaultIncludeEncryptedReasoning(model, baseURL) {
382
+ return baseURL === undefined && lowestReasoningEffort(model) !== undefined;
383
+ }
384
+ /**
385
+ * The `include` field, asking for reasoning to come back in a form that can
386
+ * be replayed on the next request.
387
+ *
388
+ * Omitted entirely when off, so requests stay byte-identical to what earlier
389
+ * versions sent. The other half of this — putting the returned items back
390
+ * into `input` — is `openAiTransformer`'s, fed by
391
+ * {@link OpenAiAgent.replayableReasoning}.
392
+ */
393
+ buildIncludeParams() {
394
+ return this.config.includeEncryptedReasoning
395
+ ? { include: ["reasoning.encrypted_content"] }
396
+ : {};
397
+ }
398
+ /**
399
+ * The `reasoning` items of a response that are worth keeping.
400
+ *
401
+ * Only items carrying `encrypted_content` qualify: the agent always sends
402
+ * `store: false`, so the provider has retained nothing, and an item replayed
403
+ * without its payload cannot be resolved — the request fails rather than
404
+ * silently ignoring it. A summary-only item is therefore dropped, exactly as
405
+ * it was before this existed.
406
+ *
407
+ * A turn that also used a **built-in tool** keeps its reasoning too. Those
408
+ * items (`web_search_call` and friends) are not stored, so the replayed turn
409
+ * is `[reasoning, reasoning, message]` where the model emitted
410
+ * `[reasoning, web_search_call, reasoning, message]`. That was expected to be
411
+ * rejected — "reasoning item without its required following item" — but it is
412
+ * not: probed live on 2026-09-10 against `gpt-5-nano` and `gpt-5.4-mini`, the
413
+ * API accepted that shape, the same reasoning item twice in a row, a
414
+ * reasoning item followed only by the next *user* turn, and a dangling
415
+ * reasoning item last in `input` with nothing after it at all. That ordering
416
+ * rule appears to govern the `store: true` / `previous_response_id` flow, not
417
+ * this one, which sends `store: false` and an explicit `input`.
418
+ *
419
+ * So the reasoning is kept. Dropping it would cost every `builtInTools` user
420
+ * their prompt-cache continuity to avoid a rejection that does not happen.
421
+ * Revisit if the API tightens.
422
+ */
423
+ replayableReasoning(response) {
424
+ if (!this.config.includeEncryptedReasoning)
425
+ return [];
426
+ return (response.output ?? []).filter((item) => typeof item === "object" &&
427
+ item !== null &&
428
+ item.type === "reasoning" &&
429
+ typeof item.encrypted_content ===
430
+ "string");
431
+ }
432
+ /**
433
+ * Prompt-caching parameters, omitted entirely when unconfigured so a request
434
+ * stays byte-identical to what earlier versions sent.
435
+ *
436
+ * Caching itself is automatic and needs no opt-in — these only influence
437
+ * which cache a request is routed to and how long the prefix stays warm. What
438
+ * was actually reused comes back on `lastTokenUsage.cache_read_tokens`.
439
+ */
440
+ buildCacheParams() {
441
+ return {
442
+ ...(this.config.promptCacheKey
443
+ ? { prompt_cache_key: this.config.promptCacheKey }
444
+ : {}),
445
+ ...(this.config.promptCacheRetention
446
+ ? { prompt_cache_retention: this.config.promptCacheRetention }
447
+ : {}),
448
+ };
449
+ }
353
450
  async process(_input) {
354
451
  return "";
355
452
  }
@@ -382,7 +479,9 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
382
479
  // mid-loop. endExecution() in the finally block enforces limits once.
383
480
  this.history.beginExecution();
384
481
  try {
385
- const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
482
+ const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries(), {
483
+ replayReasoning: this.config.includeEncryptedReasoning,
484
+ });
386
485
  this.startTurnTimer();
387
486
  const response = await this.createResponse({
388
487
  model: this.config.model,
@@ -395,6 +494,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
395
494
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
396
495
  user: this.config.user,
397
496
  ...this.buildReasoningParams(),
497
+ ...this.buildCacheParams(),
498
+ ...this.buildIncludeParams(),
398
499
  }, { signal: options?.signal });
399
500
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
400
501
  return await this.handleResponse(response, options);
@@ -471,7 +572,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
471
572
  messageOutput.type === "message" &&
472
573
  messageOutput.status === "completed") {
473
574
  // Normal text response - add to history in normalized format
474
- const entry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text);
575
+ const entry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text, undefined, this.replayableReasoning(response));
475
576
  this.addToHistory(entry);
476
577
  this.emit(AgentEvent_1.AgentEvent.DONE, response, this.lastTokenUsage);
477
578
  // Report completion to viz
@@ -499,7 +600,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
499
600
  name: tc.name,
500
601
  arguments: tc.arguments,
501
602
  }));
502
- const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls);
603
+ const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls,
604
+ // The thinking that led to these calls: replayed on the follow-up so
605
+ // the model does not have to re-derive it, and so the prefix the
606
+ // provider caches still matches what it processed.
607
+ this.replayableReasoning(response));
503
608
  this.addToHistory(assistantEntry);
504
609
  const toolResponses = await this.handleToolUse(toolCalls, options);
505
610
  // Add tool results to history (normalized)
@@ -509,7 +614,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
509
614
  }
510
615
  // Continue conversation
511
616
  try {
512
- const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
617
+ const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries(), { replayReasoning: this.config.includeEncryptedReasoning });
513
618
  this.startTurnTimer();
514
619
  const newResponse = await this.createResponse({
515
620
  model: this.config.model,
@@ -522,6 +627,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
522
627
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
523
628
  user: this.config.user,
524
629
  ...this.buildReasoningParams(),
630
+ ...this.buildCacheParams(),
631
+ ...this.buildIncludeParams(),
525
632
  }, { signal: options?.signal });
526
633
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
527
634
  return this.handleResponse(newResponse, options);
@@ -690,9 +797,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
690
797
  }
691
798
  }
692
799
  async *streamTurn(options) {
693
- const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
800
+ const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries(), {
801
+ replayReasoning: this.config.includeEncryptedReasoning,
802
+ });
694
803
  this.startTurnTimer();
695
- const stream = await this.client.responses.create(this.transformRequestParams({
804
+ const stream = (await this.client.responses.create(this.transformRequestParams({
696
805
  model: this.config.model,
697
806
  max_output_tokens: this.config.maxTokens,
698
807
  input: inputMessages,
@@ -703,7 +812,9 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
703
812
  top_p: this.config.topP,
704
813
  user: this.config.user,
705
814
  ...this.buildReasoningParams("auto"),
706
- }), { signal: options?.signal });
815
+ ...this.buildCacheParams(),
816
+ ...this.buildIncludeParams(),
817
+ }), { signal: options?.signal }));
707
818
  let completedEvent = null;
708
819
  const streamedItems = [];
709
820
  // The Responses API builds the committed turn out of `response.completed`,
@@ -784,7 +895,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
784
895
  name: tc.name,
785
896
  arguments: tc.arguments,
786
897
  }));
787
- const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls);
898
+ const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls, this.replayableReasoning(response));
788
899
  this.addToHistory(assistantEntry);
789
900
  committed = true;
790
901
  const toolResults = await this.handleToolUse(toolCalls, options);
@@ -795,7 +906,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
795
906
  }
796
907
  else {
797
908
  const textContent = response.output_text || "";
798
- const entry = transformers_1.openAiTransformer.fromProviderMessage("assistant", textContent);
909
+ const entry = transformers_1.openAiTransformer.fromProviderMessage("assistant", textContent, undefined, this.replayableReasoning(response));
799
910
  this.addToHistory(entry);
800
911
  committed = true;
801
912
  this.emit(AgentEvent_1.AgentEvent.DONE, response, this.lastTokenUsage);
@@ -832,12 +943,16 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
832
943
  }
833
944
  }
834
945
  parseUsage(input) {
946
+ const inputDetails = input.input_tokens_details;
835
947
  return {
836
948
  input_tokens: input.input_tokens,
837
949
  output_tokens: input.output_tokens,
838
950
  total_tokens: input.total_tokens,
839
951
  // Reasoning tokens are already counted inside `output_tokens`.
840
952
  reasoning_tokens: input.output_tokens_details?.reasoning_tokens,
953
+ // Cache counts are part of `input_tokens`, not extra on top of it.
954
+ cache_read_tokens: inputDetails?.cached_tokens,
955
+ cache_write_tokens: inputDetails?.cache_write_tokens,
841
956
  };
842
957
  }
843
958
  }
@@ -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
@@ -3,8 +3,8 @@ import { HistoryEntry, MessageRole, MessageContent } from "./types";
3
3
  import type { ReduceOptions } from "./types";
4
4
  /** @internal — exposed for test teardown only */
5
5
  export declare function resetTokenxCache(): void;
6
- export type { HistoryEntry, MessageRole, MessageContent, ReduceOptions, ImageMimeType, ImageUrlContent, ImageBase64Content, ThinkingContent, } from "./types";
7
- export { text, toolUse, toolResult, thinking, textMessage, imageUrl, imageBase64, isTextContent, isToolUseContent, isToolResultContent, isThinkingContent, isImageUrlContent, isImageBase64Content, isImageContent, } from "./types";
6
+ export type { HistoryEntry, MessageRole, MessageContent, ReduceOptions, ImageMimeType, ImageUrlContent, ImageBase64Content, ThinkingContent, ReasoningDetailsFormat, } from "./types";
7
+ export { text, toolUse, toolResult, thinking, textMessage, imageUrl, imageBase64, isTextContent, isToolUseContent, isToolResultContent, isThinkingContent, isImageUrlContent, isImageBase64Content, isImageContent, reasoningDetailsFormatOf, } from "./types";
8
8
  /**
9
9
  * Metadata stored alongside each history entry.
10
10
  * Extended with summary tracking fields for the compression plugin.
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.History = exports.isImageContent = exports.isImageBase64Content = exports.isImageUrlContent = exports.isThinkingContent = exports.isToolResultContent = exports.isToolUseContent = exports.isTextContent = exports.imageBase64 = exports.imageUrl = exports.textMessage = exports.thinking = exports.toolResult = exports.toolUse = exports.text = void 0;
39
+ exports.History = exports.reasoningDetailsFormatOf = exports.isImageContent = exports.isImageBase64Content = exports.isImageUrlContent = exports.isThinkingContent = exports.isToolResultContent = exports.isToolUseContent = exports.isTextContent = exports.imageBase64 = exports.imageUrl = exports.textMessage = exports.thinking = exports.toolResult = exports.toolUse = exports.text = void 0;
40
40
  exports.resetTokenxCache = resetTokenxCache;
41
41
  const events_1 = __importDefault(require("events"));
42
42
  const types_1 = require("./types");
@@ -53,9 +53,22 @@ void Promise.resolve().then(() => __importStar(require("tokenx"))).then((mod) =>
53
53
  function resetTokenxCache() {
54
54
  _estimateTokenCount = (t) => Math.ceil(t.length / 4);
55
55
  }
56
+ /**
57
+ * Chars per token for an opaque reasoning payload.
58
+ *
59
+ * `ThinkingContent.reasoningDetails` holds ciphertext — OpenAI's
60
+ * `encrypted_content`, OpenRouter's `reasoning.encrypted` — which stands in for
61
+ * the reasoning tokens the provider decrypts it back into, and is far longer
62
+ * than the tokens it represents. Running it through the text estimator counted
63
+ * it at roughly three times its weight, which trimmed history early for no
64
+ * reason. Measured on a Codex turn: 1892 chars of blob carried 156 reasoning
65
+ * tokens, i.e. ~12 chars per token against the ~4 a plain string averages.
66
+ */
67
+ const CHARS_PER_ENCRYPTED_REASONING_TOKEN = 12;
56
68
  /**
57
69
  * Estimate token count for a content block array.
58
70
  * Image blocks use a flat 1000-token estimate (resolution-independent conservative value).
71
+ * Encrypted reasoning payloads get their own ratio, see above.
59
72
  * Text and tool blocks fall through to the tokenx estimator.
60
73
  */
61
74
  function estimateContentTokens(content) {
@@ -63,6 +76,13 @@ function estimateContentTokens(content) {
63
76
  if ((0, types_1.isImageContent)(block)) {
64
77
  return sum + 1000;
65
78
  }
79
+ if ((0, types_1.isThinkingContent)(block) && block.reasoningDetails?.length) {
80
+ const { reasoningDetails, ...rest } = block;
81
+ return (sum +
82
+ _estimateTokenCount(JSON.stringify(rest)) +
83
+ Math.ceil(JSON.stringify(reasoningDetails).length /
84
+ CHARS_PER_ENCRYPTED_REASONING_TOKEN));
85
+ }
66
86
  return sum + _estimateTokenCount(JSON.stringify(block));
67
87
  }, 0);
68
88
  }
@@ -81,6 +101,7 @@ Object.defineProperty(exports, "isThinkingContent", { enumerable: true, get: fun
81
101
  Object.defineProperty(exports, "isImageUrlContent", { enumerable: true, get: function () { return types_2.isImageUrlContent; } });
82
102
  Object.defineProperty(exports, "isImageBase64Content", { enumerable: true, get: function () { return types_2.isImageBase64Content; } });
83
103
  Object.defineProperty(exports, "isImageContent", { enumerable: true, get: function () { return types_2.isImageContent; } });
104
+ Object.defineProperty(exports, "reasoningDetailsFormatOf", { enumerable: true, get: function () { return types_2.reasoningDetailsFormatOf; } });
84
105
  /**
85
106
  * Manages conversation history in a provider-agnostic format.
86
107
  *
@@ -433,7 +454,8 @@ class History extends events_1.default {
433
454
  applyTrimming() {
434
455
  if (this._executing)
435
456
  return;
436
- if (this.options.maxLength && this._entries.length > this.options.maxLength) {
457
+ if (this.options.maxLength &&
458
+ this._entries.length > this.options.maxLength) {
437
459
  this._entries = this._entries.slice(this._entries.length - this.options.maxLength);
438
460
  this.sanitizeToolPairs();
439
461
  }
@@ -1,5 +1,5 @@
1
1
  export { History, resetTokenxCache, type EntryMetadata, type ReducibleEntry, type HistoryPlugin, } from "./History";
2
2
  export { RedisHistory } from "./RedisHistory";
3
- export type { HistoryEntry, MessageRole, MessageContent, TextContent, ToolUseContent, ToolResultContent, ThinkingContent, ProviderMeta, ReduceOptions, } from "./types";
4
- export { text, toolUse, toolResult, thinking, textMessage, isTextContent, isToolUseContent, isToolResultContent, isThinkingContent, } from "./types";
3
+ export type { HistoryEntry, MessageRole, MessageContent, TextContent, ToolUseContent, ToolResultContent, ThinkingContent, ReasoningDetailsFormat, ProviderMeta, ReduceOptions, } from "./types";
4
+ export { text, toolUse, toolResult, thinking, textMessage, isTextContent, isToolUseContent, isToolResultContent, isThinkingContent, reasoningDetailsFormatOf, } from "./types";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isThinkingContent = exports.isToolResultContent = exports.isToolUseContent = exports.isTextContent = exports.textMessage = exports.thinking = exports.toolResult = exports.toolUse = exports.text = exports.RedisHistory = exports.resetTokenxCache = exports.History = void 0;
3
+ exports.reasoningDetailsFormatOf = exports.isThinkingContent = exports.isToolResultContent = exports.isToolUseContent = exports.isTextContent = exports.textMessage = exports.thinking = exports.toolResult = exports.toolUse = exports.text = exports.RedisHistory = exports.resetTokenxCache = exports.History = void 0;
4
4
  var History_1 = require("./History");
5
5
  Object.defineProperty(exports, "History", { enumerable: true, get: function () { return History_1.History; } });
6
6
  Object.defineProperty(exports, "resetTokenxCache", { enumerable: true, get: function () { return History_1.resetTokenxCache; } });
@@ -16,4 +16,5 @@ Object.defineProperty(exports, "isTextContent", { enumerable: true, get: functio
16
16
  Object.defineProperty(exports, "isToolUseContent", { enumerable: true, get: function () { return types_1.isToolUseContent; } });
17
17
  Object.defineProperty(exports, "isToolResultContent", { enumerable: true, get: function () { return types_1.isToolResultContent; } });
18
18
  Object.defineProperty(exports, "isThinkingContent", { enumerable: true, get: function () { return types_1.isThinkingContent; } });
19
+ Object.defineProperty(exports, "reasoningDetailsFormatOf", { enumerable: true, get: function () { return types_1.reasoningDetailsFormatOf; } });
19
20
  //# sourceMappingURL=index.js.map
@@ -23,9 +23,18 @@ export declare const anthropicTransformer: {
23
23
  };
24
24
  export declare const openAiTransformer: {
25
25
  /**
26
- * Convert normalized entries to OpenAI ResponseInputItem format
26
+ * Convert normalized entries to OpenAI ResponseInputItem format.
27
+ *
28
+ * `replayReasoning` defaults to on and mirrors
29
+ * `AgentConfig.includeEncryptedReasoning`: requesting the blobs and sending
30
+ * them back are one feature, so switching it off has to stop *both*.
31
+ * Otherwise a history that already holds blobs keeps replaying them — which
32
+ * is exactly the situation the flag is turned off to escape, since reasoning
33
+ * is tied to the model that produced it and switching models is rejected.
27
34
  */
28
- toProvider(entries: HistoryEntry[]): ResponseInputItem[];
35
+ toProvider(entries: HistoryEntry[], options?: {
36
+ replayReasoning?: boolean;
37
+ }): ResponseInputItem[];
29
38
  /**
30
39
  * Convert OpenAI response to normalized HistoryEntry
31
40
  */
@@ -34,7 +43,7 @@ export declare const openAiTransformer: {
34
43
  call_id: string;
35
44
  name: string;
36
45
  arguments: string;
37
- }>): HistoryEntry;
46
+ }>, reasoningItems?: unknown[]): HistoryEntry;
38
47
  /**
39
48
  * Create a tool result entry from OpenAI function call output
40
49
  */