@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
|
@@ -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
|
package/dist/agents/BaseAgent.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
//
|
|
64
|
-
// on the floor — see
|
|
65
|
-
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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>;
|