@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.
- package/dist/agents/AgentConfig.d.ts +40 -0
- package/dist/agents/AgentEvent.d.ts +6 -0
- package/dist/agents/AgentEvent.js +6 -0
- package/dist/agents/BaseAgent.d.ts +18 -0
- package/dist/agents/BaseAgent.js +2 -0
- package/dist/agents/openai/CodexAgent.d.ts +114 -8
- package/dist/agents/openai/CodexAgent.js +81 -7
- package/dist/agents/openai/OpenAiAgent.d.ts +147 -0
- package/dist/agents/openai/OpenAiAgent.js +127 -12
- package/dist/agents/openai/codex-usage.d.ts +88 -0
- package/dist/agents/openai/codex-usage.js +127 -0
- package/dist/history/History.d.ts +2 -2
- package/dist/history/History.js +24 -2
- package/dist/history/index.d.ts +2 -2
- package/dist/history/index.js +2 -1
- package/dist/history/transformers.d.ts +12 -3
- package/dist/history/transformers.js +119 -13
- package/dist/history/types.d.ts +31 -1
- package/dist/history/types.js +15 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/openai.d.ts +2 -0
- package/dist/openai.js +4 -1
- package/package.json +3 -3
|
@@ -107,7 +107,7 @@ function describeOpenAIError(error) {
|
|
|
107
107
|
const body = err?.error;
|
|
108
108
|
const fromBody = typeof body === "string"
|
|
109
109
|
? body
|
|
110
|
-
:
|
|
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() :
|
|
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 ?
|
|
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
|
-
|
|
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.
|
package/dist/history/History.js
CHANGED
|
@@ -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 &&
|
|
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
|
}
|
package/dist/history/index.d.ts
CHANGED
|
@@ -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
|
package/dist/history/index.js
CHANGED
|
@@ -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[]
|
|
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
|
-
}
|
|
46
|
+
}>, reasoningItems?: unknown[]): HistoryEntry;
|
|
38
47
|
/**
|
|
39
48
|
* Create a tool result entry from OpenAI function call output
|
|
40
49
|
*/
|