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