@agentionai/agents 1.12.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/Agent.d.ts +1 -1
- package/dist/agents/AgentConfig.d.ts +34 -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 +169 -0
- package/dist/agents/openai/CodexAgent.js +222 -0
- package/dist/agents/openai/OpenAiAgent.d.ts +133 -6
- package/dist/agents/openai/OpenAiAgent.js +219 -17
- package/dist/agents/openai/codex-auth.d.ts +166 -0
- package/dist/agents/openai/codex-auth.js +264 -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 +5 -0
- package/dist/index.js +15 -1
- package/dist/openai.d.ts +6 -1
- package/dist/openai.js +19 -1
- package/package.json +1 -1
package/dist/agents/Agent.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ type OpenRouterAgentConfig = Omit<BaseAgentConfig, "vendor" | "model"> & OpenRou
|
|
|
42
42
|
};
|
|
43
43
|
type AgentConfig = ClaudeAgentConfig | OpenAIAgentConfig | GeminiAgentConfig | MistralAgentConfig | OllamaAgentConfig | LlamaCppAgentConfig | OpenRouterAgentConfig;
|
|
44
44
|
export declare class Agent {
|
|
45
|
-
static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | MistralAgent | OllamaAgent | LlamaCppAgent | OpenRouterAgent | OpenAiAgent<(string & {}) | "gpt-5-pro" | "gpt-5.2-pro" | "gpt-5.4-pro" | "gpt-5.5-pro" | "o1" | "o1-pro" | "o3" | "o3-mini" | "o4-mini" | "gpt-5" | "gpt-5-mini" | "gpt-5-nano" | "gpt-5.1" | "gpt-5.2" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-nano" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna" | "gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1-preview" | "o1-mini">;
|
|
45
|
+
static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | MistralAgent | OllamaAgent | LlamaCppAgent | OpenRouterAgent | OpenAiAgent<(string & {}) | "gpt-5-pro" | "gpt-5.2-pro" | "gpt-5.4-pro" | "gpt-5.5-pro" | "o1" | "o1-pro" | "o3" | "o3-mini" | "o4-mini" | "gpt-5" | "gpt-5-mini" | "gpt-5-nano" | "gpt-5.1" | "gpt-5.2" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-nano" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna" | "gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1-preview" | "o1-mini", import("openai/resources/models").Model>;
|
|
46
46
|
}
|
|
47
47
|
export {};
|
|
48
48
|
//# sourceMappingURL=Agent.d.ts.map
|
|
@@ -137,6 +137,40 @@ 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";
|
|
151
|
+
/**
|
|
152
|
+
* Override the API base URL. Defaults to `api.openai.com/v1`; `CodexAgent`
|
|
153
|
+
* defaults it to `https://chatgpt.com/backend-api/codex`, and setting it
|
|
154
|
+
* there points at a Codex proxy instead.
|
|
155
|
+
*/
|
|
156
|
+
baseURL?: string;
|
|
157
|
+
/**
|
|
158
|
+
* `CodexAgent` only: ChatGPT workspace to bill, sent as the
|
|
159
|
+
* `chatgpt-account-id` header.
|
|
160
|
+
*/
|
|
161
|
+
accountId?: string;
|
|
162
|
+
/**
|
|
163
|
+
* `CodexAgent` only: client identifier sent as the `originator` header.
|
|
164
|
+
* OpenAI varies the model catalog by originator.
|
|
165
|
+
*
|
|
166
|
+
* @default "codex_cli_rs"
|
|
167
|
+
*/
|
|
168
|
+
originator?: string;
|
|
169
|
+
/**
|
|
170
|
+
* `client_version` sent to the Codex models endpoint, which requires one and
|
|
171
|
+
* hides models newer than the version claimed.
|
|
172
|
+
*/
|
|
173
|
+
clientVersion?: string;
|
|
140
174
|
}
|
|
141
175
|
/**
|
|
142
176
|
* Vendor-specific configuration for Mistral
|
|
@@ -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),
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { History } from "../../history/History";
|
|
2
|
+
import { ModelInfo } from "../BaseAgent";
|
|
3
|
+
import { ResponseInputItem } from "openai/resources/responses/responses";
|
|
4
|
+
import { OpenAIModel } from "../model-types";
|
|
5
|
+
import { AgentConfig as OpenAiAgentConfig, OpenAiAgent } from "./OpenAiAgent";
|
|
6
|
+
import { CodexCredentials, CodexModelCard, CodexTokenProviderOptions } from "./codex-auth";
|
|
7
|
+
import { CodexUsageLimits } from "./codex-usage";
|
|
8
|
+
/**
|
|
9
|
+
* Models the ChatGPT-backed Codex backend serves.
|
|
10
|
+
*
|
|
11
|
+
* A different namespace from the platform API's — every platform id
|
|
12
|
+
* (`gpt-5.6`, `gpt-4.1-mini`, even `gpt-5.1-codex`) is rejected here with
|
|
13
|
+
* *"model is not supported when using Codex with a ChatGPT account"*. Probed
|
|
14
|
+
* live on 2026-09-09; `(string & {})` keeps a newer model usable without a
|
|
15
|
+
* release, while still autocompleting the known ones.
|
|
16
|
+
*/
|
|
17
|
+
export type CodexModel = "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.5" | "gpt-6-astra" | "codex-auto-review" | (string & Record<never, never>);
|
|
18
|
+
/**
|
|
19
|
+
* Reasoning efforts the Codex models accept — a different set from the platform
|
|
20
|
+
* API's, and uniform across these models rather than per-family. Each model's
|
|
21
|
+
* live list is on `ModelInfo.raw.supported_reasoning_levels`.
|
|
22
|
+
*/
|
|
23
|
+
export type CodexReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
24
|
+
export type CodexAgentConfig = Omit<OpenAiAgentConfig, "model" | "reasoningEffort" | "fetch" | "vendor"> & {
|
|
25
|
+
/** @default "gpt-5.6-luna" */
|
|
26
|
+
model?: CodexModel;
|
|
27
|
+
/**
|
|
28
|
+
* How hard the model should think. `medium` if unset — the backend's own
|
|
29
|
+
* default for these models.
|
|
30
|
+
*/
|
|
31
|
+
reasoningEffort?: CodexReasoningEffort;
|
|
32
|
+
/**
|
|
33
|
+
* ChatGPT workspace to bill, sent as the `chatgpt-account-id` header. Take it
|
|
34
|
+
* from {@link loadCodexCredentials}; the backend may reject a request without
|
|
35
|
+
* one.
|
|
36
|
+
*/
|
|
37
|
+
accountId?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Client identifier sent as `originator`. OpenAI gates parts of the model
|
|
40
|
+
* catalog on this, so the default mirrors the Codex CLI.
|
|
41
|
+
*
|
|
42
|
+
* @default CODEX_ORIGINATOR
|
|
43
|
+
*/
|
|
44
|
+
originator?: string;
|
|
45
|
+
/**
|
|
46
|
+
* `client_version` for the models endpoint, which 400s without one and hides
|
|
47
|
+
* models newer than the version claimed.
|
|
48
|
+
*
|
|
49
|
+
* @default CODEX_CLIENT_VERSION
|
|
50
|
+
*/
|
|
51
|
+
clientVersion?: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Agent for OpenAI models reached through a **ChatGPT subscription** rather
|
|
55
|
+
* than a platform API key.
|
|
56
|
+
*
|
|
57
|
+
* Talks to `https://chatgpt.com/backend-api/codex`, the endpoint OpenAI's Codex
|
|
58
|
+
* CLI uses, so calls are billed against the subscription instead of an API
|
|
59
|
+
* account. It speaks the Responses API, hence the `OpenAiAgent` base — but the
|
|
60
|
+
* two are far enough apart that mixing them in one class meant lying to the
|
|
61
|
+
* type system about which models exist:
|
|
62
|
+
*
|
|
63
|
+
* | | platform | Codex |
|
|
64
|
+
* |---|---|---|
|
|
65
|
+
* | models | `gpt-5.6`, `gpt-4.1-mini`, … | `gpt-5.6-luna/sol/terra`, … — disjoint sets |
|
|
66
|
+
* | body | as written | `instructions` required, `stream: true`, no `max_output_tokens` |
|
|
67
|
+
* | errors | `{error: {…}}` | `{detail: …}` |
|
|
68
|
+
* | terminal event | `output` populated | `output: []`; content arrives as items |
|
|
69
|
+
* | models endpoint | `/v1/models` | `/models?client_version=…` |
|
|
70
|
+
*
|
|
71
|
+
* Nothing here is a documented public API and OpenAI can change it without
|
|
72
|
+
* notice; {@link OpenAiAgent} with a platform key remains the supported path.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* // Reads the credentials `codex login` stored, and keeps the token fresh.
|
|
77
|
+
* const agent = await CodexAgent.fromCodexCli({
|
|
78
|
+
* id: "1",
|
|
79
|
+
* name: "Assistant",
|
|
80
|
+
* description: "A helpful assistant",
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* console.log(await agent.execute("Hello!"));
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare class CodexAgent extends OpenAiAgent<OpenAIModel, CodexModelCard> {
|
|
87
|
+
private readonly accountId?;
|
|
88
|
+
private readonly originator;
|
|
89
|
+
private readonly clientVersion;
|
|
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;
|
|
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;
|
|
125
|
+
/**
|
|
126
|
+
* Build an agent from the credentials `codex login` stored, wrapped in a
|
|
127
|
+
* provider that refreshes the access token as it ages out.
|
|
128
|
+
*
|
|
129
|
+
* @throws if no credentials are present — run `codex login` first.
|
|
130
|
+
*/
|
|
131
|
+
static fromCodexCli(config: Omit<CodexAgentConfig, "apiKey" | "accountId"> & {
|
|
132
|
+
/** Read `auth.json` from somewhere other than `$CODEX_HOME`. */
|
|
133
|
+
codexHome?: string;
|
|
134
|
+
/** Forwarded to {@link createCodexTokenProvider}. */
|
|
135
|
+
tokenOptions?: CodexTokenProviderOptions;
|
|
136
|
+
}, history?: History): Promise<CodexAgent>;
|
|
137
|
+
/** Build an agent from credentials obtained however you like. */
|
|
138
|
+
static fromCredentials(credentials: CodexCredentials, config: Omit<CodexAgentConfig, "apiKey" | "accountId"> & {
|
|
139
|
+
tokenOptions?: CodexTokenProviderOptions;
|
|
140
|
+
}, history?: History): CodexAgent;
|
|
141
|
+
/** This backend refuses `stream: false` outright. */
|
|
142
|
+
protected get forceStreaming(): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Satisfy the backend's extra body validations, each of which is otherwise a
|
|
145
|
+
* bare `400`: *"Instructions are required"*, *"Input must be a list"*,
|
|
146
|
+
* *"Store must be set to false"*, *"Stream must be set to true"*,
|
|
147
|
+
* *"Unsupported parameter: max_output_tokens"*.
|
|
148
|
+
*
|
|
149
|
+
* `store: false` and a list-shaped `input` already hold at every call site in
|
|
150
|
+
* the base class.
|
|
151
|
+
*/
|
|
152
|
+
protected transformRequestParams<T extends {
|
|
153
|
+
input: ResponseInputItem[];
|
|
154
|
+
}>(params: T): T;
|
|
155
|
+
/**
|
|
156
|
+
* List the models this ChatGPT account may drive.
|
|
157
|
+
*
|
|
158
|
+
* Neither the endpoint nor the shape matches the platform API's
|
|
159
|
+
* `/v1/models`: it needs a `client_version` query parameter (400s without
|
|
160
|
+
* one) and returns richer cards — the context window for this account's
|
|
161
|
+
* plan, the reasoning efforts the model accepts, the plans it is available
|
|
162
|
+
* in. The SDK has no method for it, so this goes out through `fetch`.
|
|
163
|
+
*
|
|
164
|
+
* Models whose `minimal_client_version` exceeds {@link clientVersion} are
|
|
165
|
+
* omitted by the server, not here.
|
|
166
|
+
*/
|
|
167
|
+
listModels(): Promise<ModelInfo<CodexModelCard>[]>;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=CodexAgent.d.ts.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CodexAgent = void 0;
|
|
4
|
+
const AgentEvent_1 = require("../AgentEvent");
|
|
5
|
+
const AgentError_1 = require("../errors/AgentError");
|
|
6
|
+
const OpenAiAgent_1 = require("./OpenAiAgent");
|
|
7
|
+
const codex_auth_1 = require("./codex-auth");
|
|
8
|
+
const codex_usage_1 = require("./codex-usage");
|
|
9
|
+
/**
|
|
10
|
+
* Agent for OpenAI models reached through a **ChatGPT subscription** rather
|
|
11
|
+
* than a platform API key.
|
|
12
|
+
*
|
|
13
|
+
* Talks to `https://chatgpt.com/backend-api/codex`, the endpoint OpenAI's Codex
|
|
14
|
+
* CLI uses, so calls are billed against the subscription instead of an API
|
|
15
|
+
* account. It speaks the Responses API, hence the `OpenAiAgent` base — but the
|
|
16
|
+
* two are far enough apart that mixing them in one class meant lying to the
|
|
17
|
+
* type system about which models exist:
|
|
18
|
+
*
|
|
19
|
+
* | | platform | Codex |
|
|
20
|
+
* |---|---|---|
|
|
21
|
+
* | models | `gpt-5.6`, `gpt-4.1-mini`, … | `gpt-5.6-luna/sol/terra`, … — disjoint sets |
|
|
22
|
+
* | body | as written | `instructions` required, `stream: true`, no `max_output_tokens` |
|
|
23
|
+
* | errors | `{error: {…}}` | `{detail: …}` |
|
|
24
|
+
* | terminal event | `output` populated | `output: []`; content arrives as items |
|
|
25
|
+
* | models endpoint | `/v1/models` | `/models?client_version=…` |
|
|
26
|
+
*
|
|
27
|
+
* Nothing here is a documented public API and OpenAI can change it without
|
|
28
|
+
* notice; {@link OpenAiAgent} with a platform key remains the supported path.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Reads the credentials `codex login` stored, and keeps the token fresh.
|
|
33
|
+
* const agent = await CodexAgent.fromCodexCli({
|
|
34
|
+
* id: "1",
|
|
35
|
+
* name: "Assistant",
|
|
36
|
+
* description: "A helpful assistant",
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* console.log(await agent.execute("Hello!"));
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
class CodexAgent extends OpenAiAgent_1.OpenAiAgent {
|
|
43
|
+
constructor(config, history) {
|
|
44
|
+
const vendorConfig = config.vendorConfig?.openai ?? {};
|
|
45
|
+
const accountId = config.accountId ?? vendorConfig.accountId;
|
|
46
|
+
const originator = config.originator ?? vendorConfig.originator ?? codex_auth_1.CODEX_ORIGINATOR;
|
|
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 = {};
|
|
51
|
+
// Everything host-specific is passed *into* the base constructor rather
|
|
52
|
+
// than supplied by an override: the base runs before this class's fields
|
|
53
|
+
// are assigned, so an override could not read them.
|
|
54
|
+
super({
|
|
55
|
+
...config,
|
|
56
|
+
model: config.model ?? "gpt-5.6-luna",
|
|
57
|
+
reasoningEffort: config.reasoningEffort,
|
|
58
|
+
baseURL,
|
|
59
|
+
defaultHeaders: {
|
|
60
|
+
...(accountId ? { "chatgpt-account-id": accountId } : {}),
|
|
61
|
+
"OpenAI-Beta": "responses=experimental",
|
|
62
|
+
originator,
|
|
63
|
+
// Every Codex request is a stream; the SDK would send
|
|
64
|
+
// `application/json`, which no reference client does.
|
|
65
|
+
Accept: "text/event-stream",
|
|
66
|
+
...config.defaultHeaders,
|
|
67
|
+
},
|
|
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)()),
|
|
79
|
+
// Cast: the codex-specific keys (accountId, originator, clientVersion)
|
|
80
|
+
// are not part of the base config, and `vendor` is supplied by it.
|
|
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);
|
|
86
|
+
this.accountId = accountId;
|
|
87
|
+
this.originator = originator;
|
|
88
|
+
this.clientVersion =
|
|
89
|
+
config.clientVersion ?? vendorConfig.clientVersion ?? codex_auth_1.CODEX_CLIENT_VERSION;
|
|
90
|
+
this.codexBaseURL = baseURL;
|
|
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
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build an agent from the credentials `codex login` stored, wrapped in a
|
|
120
|
+
* provider that refreshes the access token as it ages out.
|
|
121
|
+
*
|
|
122
|
+
* @throws if no credentials are present — run `codex login` first.
|
|
123
|
+
*/
|
|
124
|
+
static async fromCodexCli(config, history) {
|
|
125
|
+
const credentials = await (0, codex_auth_1.loadCodexCredentials)(config.codexHome);
|
|
126
|
+
return CodexAgent.fromCredentials(credentials, config, history);
|
|
127
|
+
}
|
|
128
|
+
/** Build an agent from credentials obtained however you like. */
|
|
129
|
+
static fromCredentials(credentials, config, history) {
|
|
130
|
+
const tokens = (0, codex_auth_1.createCodexTokenProvider)(credentials, config.tokenOptions);
|
|
131
|
+
return new CodexAgent({
|
|
132
|
+
...config,
|
|
133
|
+
// The function form: the SDK re-invokes it before every request, so a
|
|
134
|
+
// long run outlives the ~1h token.
|
|
135
|
+
apiKey: tokens.getToken,
|
|
136
|
+
accountId: credentials.accountId,
|
|
137
|
+
}, history);
|
|
138
|
+
}
|
|
139
|
+
/** This backend refuses `stream: false` outright. */
|
|
140
|
+
get forceStreaming() {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Satisfy the backend's extra body validations, each of which is otherwise a
|
|
145
|
+
* bare `400`: *"Instructions are required"*, *"Input must be a list"*,
|
|
146
|
+
* *"Store must be set to false"*, *"Stream must be set to true"*,
|
|
147
|
+
* *"Unsupported parameter: max_output_tokens"*.
|
|
148
|
+
*
|
|
149
|
+
* `store: false` and a list-shaped `input` already hold at every call site in
|
|
150
|
+
* the base class.
|
|
151
|
+
*/
|
|
152
|
+
transformRequestParams(params) {
|
|
153
|
+
// Read from history rather than getSystemMessage(): that is the message
|
|
154
|
+
// being stripped from `input` below, and a caller may have replaced it.
|
|
155
|
+
const systemMessage = this.history.getSystemMessage() ?? this.getSystemMessage();
|
|
156
|
+
// Rejected outright, so it cannot merely be left undefined when the caller
|
|
157
|
+
// set `maxTokens`.
|
|
158
|
+
const { max_output_tokens: _dropped, ...rest } = params;
|
|
159
|
+
return {
|
|
160
|
+
...rest,
|
|
161
|
+
// Must be present and non-empty.
|
|
162
|
+
instructions: systemMessage?.trim()
|
|
163
|
+
? systemMessage
|
|
164
|
+
: "You are a helpful assistant.",
|
|
165
|
+
// The system prompt travels in `instructions` now, so drop the copy the
|
|
166
|
+
// transformer put in `input` rather than sending it twice.
|
|
167
|
+
input: params.input.filter((item) => !(typeof item === "object" &&
|
|
168
|
+
item !== null &&
|
|
169
|
+
"role" in item &&
|
|
170
|
+
item.role === "system")),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* List the models this ChatGPT account may drive.
|
|
175
|
+
*
|
|
176
|
+
* Neither the endpoint nor the shape matches the platform API's
|
|
177
|
+
* `/v1/models`: it needs a `client_version` query parameter (400s without
|
|
178
|
+
* one) and returns richer cards — the context window for this account's
|
|
179
|
+
* plan, the reasoning efforts the model accepts, the plans it is available
|
|
180
|
+
* in. The SDK has no method for it, so this goes out through `fetch`.
|
|
181
|
+
*
|
|
182
|
+
* Models whose `minimal_client_version` exceeds {@link clientVersion} are
|
|
183
|
+
* omitted by the server, not here.
|
|
184
|
+
*/
|
|
185
|
+
async listModels() {
|
|
186
|
+
try {
|
|
187
|
+
const token = await this.resolveApiKey();
|
|
188
|
+
const url = `${this.codexBaseURL}/models?client_version=${encodeURIComponent(this.clientVersion)}`;
|
|
189
|
+
const res = await fetch(url, {
|
|
190
|
+
headers: {
|
|
191
|
+
Authorization: `Bearer ${token}`,
|
|
192
|
+
...(this.accountId ? { "chatgpt-account-id": this.accountId } : {}),
|
|
193
|
+
originator: this.originator,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
if (!res.ok) {
|
|
197
|
+
const body = await res.text().catch(() => "");
|
|
198
|
+
throw new Error(`${res.status} ${res.statusText}${body ? `: ${body.slice(0, 300)}` : ""}`);
|
|
199
|
+
}
|
|
200
|
+
const data = (await res.json());
|
|
201
|
+
return (data.models ?? []).map((model) => ({
|
|
202
|
+
id: model.slug,
|
|
203
|
+
displayName: model.display_name,
|
|
204
|
+
// The plan's window, not the model's ceiling — `max_context_window` is
|
|
205
|
+
// on `raw` for anyone who needs the larger number.
|
|
206
|
+
contextLength: model.context_window,
|
|
207
|
+
capabilities: {
|
|
208
|
+
chat: true,
|
|
209
|
+
tools: true,
|
|
210
|
+
vision: model.input_modalities?.includes("image"),
|
|
211
|
+
thinking: (model.supported_reasoning_levels?.length ?? 0) > 0,
|
|
212
|
+
},
|
|
213
|
+
raw: model,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw new AgentError_1.ExecutionError(`Failed to list Codex models: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.CodexAgent = CodexAgent;
|
|
222
|
+
//# sourceMappingURL=CodexAgent.js.map
|