@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
|
@@ -2,14 +2,30 @@ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent"
|
|
|
2
2
|
import { ExecuteOptions } from "../cancellation";
|
|
3
3
|
import { History, MessageContent } from "../../history/History";
|
|
4
4
|
import { type BuiltInTool } from "../../tools/BuiltInTool";
|
|
5
|
-
import { Tool, Response, ResponseUsage } from "openai/resources/responses/responses";
|
|
5
|
+
import { Tool, Response, ResponseInputItem, ResponseUsage } from "openai/resources/responses/responses";
|
|
6
6
|
import type { Model as OpenAIModelCard } from "openai/resources/models";
|
|
7
7
|
import { OpenAIModel, ReasoningEffort, ReasoningEffortFor } from "../model-types";
|
|
8
8
|
import { StreamChunk } from "../openai-compatible/OpenAICompatibleAgent";
|
|
9
|
-
type AgentConfig<M extends OpenAIModel = OpenAIModel> = BaseAgentConfig & {
|
|
10
|
-
|
|
9
|
+
export type AgentConfig<M extends OpenAIModel = OpenAIModel> = Omit<BaseAgentConfig, "apiKey"> & {
|
|
10
|
+
/**
|
|
11
|
+
* Platform API key, or an async function returning one.
|
|
12
|
+
*
|
|
13
|
+
* A function is re-invoked before every request, so a rotating or refreshed
|
|
14
|
+
* credential stays current across a long run — which is how `CodexAgent`
|
|
15
|
+
* keeps a ChatGPT OAuth token alive.
|
|
16
|
+
*/
|
|
17
|
+
apiKey: string | (() => Promise<string>);
|
|
11
18
|
model?: M;
|
|
12
19
|
maxTokens?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Override the API base URL. Defaults to the SDK's `api.openai.com/v1`.
|
|
22
|
+
*/
|
|
23
|
+
baseURL?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Replace the `fetch` used for every request — for interception, proxying, or
|
|
26
|
+
* normalising a non-OpenAI host's error bodies (see {@link wrapErrorBodyFetch}).
|
|
27
|
+
*/
|
|
28
|
+
fetch?: typeof fetch;
|
|
13
29
|
disableParallelToolUse?: boolean;
|
|
14
30
|
/**
|
|
15
31
|
* Ask for the least reasoning the configured model supports (e.g. `minimal` on
|
|
@@ -30,6 +46,26 @@ type AgentConfig<M extends OpenAIModel = OpenAIModel> = BaseAgentConfig & {
|
|
|
30
46
|
* @see lib/tools/BuiltInTool.ts
|
|
31
47
|
*/
|
|
32
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";
|
|
33
69
|
};
|
|
34
70
|
/**
|
|
35
71
|
* Lowest `reasoning.effort` the given model accepts, used to resolve
|
|
@@ -47,6 +83,52 @@ type AgentConfig<M extends OpenAIModel = OpenAIModel> = BaseAgentConfig & {
|
|
|
47
83
|
* `reasoningEffort` explicitly to override.
|
|
48
84
|
*/
|
|
49
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
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* `fetch` wrapper that rewrites a non-OpenAI-shaped error body into the shape
|
|
103
|
+
* the SDK can read.
|
|
104
|
+
*
|
|
105
|
+
* `APIError.generate` takes the message from `body.error` and throws the rest
|
|
106
|
+
* away (`openai/core/error.js`), so a backend that reports failures as
|
|
107
|
+
* `{"detail": "..."}` — which the ChatGPT Codex endpoint does, for all four of
|
|
108
|
+
* its body validations plus auth failures — surfaces as the useless
|
|
109
|
+
* `400 status code (no body)`. Nesting the original body under `error` puts the
|
|
110
|
+
* real reason back in the thrown error.
|
|
111
|
+
*
|
|
112
|
+
* Only touches error responses; successful (streaming) responses pass straight
|
|
113
|
+
* through untouched.
|
|
114
|
+
*/
|
|
115
|
+
export declare function wrapErrorBodyFetch(baseFetch?: typeof fetch): typeof fetch;
|
|
116
|
+
/**
|
|
117
|
+
* Pull a human-readable message out of an OpenAI-shaped error.
|
|
118
|
+
*
|
|
119
|
+
* `api.openai.com` answers with `{ error: { message, code } }`, but not every
|
|
120
|
+
* host behind this SDK does — the ChatGPT Codex backend reports its validation
|
|
121
|
+
* failures as `{ detail: "Instructions are required" }`. Reading
|
|
122
|
+
* `error.error.message` blindly turns those into a `TypeError` that hides the
|
|
123
|
+
* real cause, so every field is probed defensively and the SDK's own `message`
|
|
124
|
+
* is the last resort.
|
|
125
|
+
*/
|
|
126
|
+
export declare function describeOpenAIError(error: unknown): {
|
|
127
|
+
message: string;
|
|
128
|
+
code?: string;
|
|
129
|
+
status?: number;
|
|
130
|
+
body?: unknown;
|
|
131
|
+
};
|
|
50
132
|
/**
|
|
51
133
|
* Agent for OpenAI models using the Responses API.
|
|
52
134
|
*
|
|
@@ -62,7 +144,7 @@ export declare function lowestReasoningEffort(model: string | undefined): Reason
|
|
|
62
144
|
* const response = await agent.execute("Hello!");
|
|
63
145
|
* ```
|
|
64
146
|
*/
|
|
65
|
-
export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends BaseAgent {
|
|
147
|
+
export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel, TModelCard = OpenAIModelCard> extends BaseAgent {
|
|
66
148
|
private client;
|
|
67
149
|
/**
|
|
68
150
|
* Resolved runtime config. Deliberately not narrowed by `M` — the constructor
|
|
@@ -75,6 +157,19 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
|
|
|
75
157
|
private vizEventId?;
|
|
76
158
|
/** Count of tool calls in current execution */
|
|
77
159
|
private currentToolCallCount;
|
|
160
|
+
/**
|
|
161
|
+
* Whether a non-streaming call must be issued as a stream and collapsed.
|
|
162
|
+
* `false` here; `CodexAgent` overrides it, since that backend refuses
|
|
163
|
+
* `stream: false` outright.
|
|
164
|
+
*/
|
|
165
|
+
protected get forceStreaming(): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Last chance to reshape a request body before it goes out. Identity here —
|
|
168
|
+
* `CodexAgent` overrides it to satisfy that backend's extra validations.
|
|
169
|
+
*/
|
|
170
|
+
protected transformRequestParams<T extends {
|
|
171
|
+
input: ResponseInputItem[];
|
|
172
|
+
}>(params: T): T;
|
|
78
173
|
constructor(config: Omit<AgentConfig<M>, "vendor">, history?: History);
|
|
79
174
|
/**
|
|
80
175
|
* List the models available to this API key.
|
|
@@ -83,7 +178,9 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
|
|
|
83
178
|
* image models alike — so filter by `id` if you only want the ones this
|
|
84
179
|
* agent can drive.
|
|
85
180
|
*/
|
|
86
|
-
listModels(): Promise<ModelInfo<
|
|
181
|
+
listModels(): Promise<ModelInfo<TModelCard>[]>;
|
|
182
|
+
/** The configured key, resolving the function form if that is what was given. */
|
|
183
|
+
protected resolveApiKey(): Promise<string>;
|
|
87
184
|
protected getToolDefinitions(): Tool[];
|
|
88
185
|
/**
|
|
89
186
|
* Combine locally-executed tool definitions with provider-defined
|
|
@@ -94,6 +191,28 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
|
|
|
94
191
|
* uses for its own `ToolUnion[]`.
|
|
95
192
|
*/
|
|
96
193
|
protected getAllToolDefinitions(): Tool[];
|
|
194
|
+
/**
|
|
195
|
+
* Rebuild a terminal response's `output` from the items streamed alongside it.
|
|
196
|
+
*
|
|
197
|
+
* The Codex backend sends `response.completed` with `output: []` and no
|
|
198
|
+
* `output_text`, unlike the platform API which fills both in — the content
|
|
199
|
+
* only ever arrives as `response.output_item.done` events. Everything
|
|
200
|
+
* downstream (tool-call detection, the text written to history) reads
|
|
201
|
+
* `output`, so without this a Codex turn silently commits an empty assistant
|
|
202
|
+
* message and drops every tool call.
|
|
203
|
+
*
|
|
204
|
+
* A no-op wherever `output` is already populated, so the platform path is
|
|
205
|
+
* untouched.
|
|
206
|
+
*/
|
|
207
|
+
private repairStreamedOutput;
|
|
208
|
+
/**
|
|
209
|
+
* Issue a non-streaming Responses API call.
|
|
210
|
+
*
|
|
211
|
+
* When {@link forceStreaming} is set the request is streamed and the terminal
|
|
212
|
+
* event's `response` handed back instead — giving callers the same `Response`
|
|
213
|
+
* either way, at the cost of buffering the turn.
|
|
214
|
+
*/
|
|
215
|
+
private createResponse;
|
|
97
216
|
/**
|
|
98
217
|
* Build the `reasoning` field for a Responses API request, as an object to
|
|
99
218
|
* spread into the request params.
|
|
@@ -111,6 +230,15 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
|
|
|
111
230
|
* emits `response.reasoning_summary_text.delta` events when it is set.
|
|
112
231
|
*/
|
|
113
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;
|
|
114
242
|
protected process(_input: string): Promise<string>;
|
|
115
243
|
execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
|
|
116
244
|
protected handleResponse(response: Response, options?: ExecuteOptions): Promise<string>;
|
|
@@ -131,5 +259,4 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
|
|
|
131
259
|
private streamTurn;
|
|
132
260
|
protected parseUsage(input: ResponseUsage): TokenUsage;
|
|
133
261
|
}
|
|
134
|
-
export {};
|
|
135
262
|
//# sourceMappingURL=OpenAiAgent.d.ts.map
|
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.OpenAiAgent = void 0;
|
|
7
7
|
exports.lowestReasoningEffort = lowestReasoningEffort;
|
|
8
|
+
exports.wrapErrorBodyFetch = wrapErrorBodyFetch;
|
|
9
|
+
exports.describeOpenAIError = describeOpenAIError;
|
|
8
10
|
const openai_1 = __importDefault(require("openai"));
|
|
9
11
|
const BaseAgent_1 = require("../BaseAgent");
|
|
10
12
|
const AgentEvent_1 = require("../AgentEvent");
|
|
@@ -38,6 +40,81 @@ function lowestReasoningEffort(model) {
|
|
|
38
40
|
const group = model_types_1.OPENAI_REASONING_SUPPORT.find((entry) => entry.models.includes(base));
|
|
39
41
|
return group?.efforts[0];
|
|
40
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* `fetch` wrapper that rewrites a non-OpenAI-shaped error body into the shape
|
|
45
|
+
* the SDK can read.
|
|
46
|
+
*
|
|
47
|
+
* `APIError.generate` takes the message from `body.error` and throws the rest
|
|
48
|
+
* away (`openai/core/error.js`), so a backend that reports failures as
|
|
49
|
+
* `{"detail": "..."}` — which the ChatGPT Codex endpoint does, for all four of
|
|
50
|
+
* its body validations plus auth failures — surfaces as the useless
|
|
51
|
+
* `400 status code (no body)`. Nesting the original body under `error` puts the
|
|
52
|
+
* real reason back in the thrown error.
|
|
53
|
+
*
|
|
54
|
+
* Only touches error responses; successful (streaming) responses pass straight
|
|
55
|
+
* through untouched.
|
|
56
|
+
*/
|
|
57
|
+
function wrapErrorBodyFetch(baseFetch = fetch) {
|
|
58
|
+
return async (input, init) => {
|
|
59
|
+
const res = await baseFetch(input, init);
|
|
60
|
+
if (res.ok)
|
|
61
|
+
return res;
|
|
62
|
+
const text = await res.text().catch(() => "");
|
|
63
|
+
let body = text;
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(text);
|
|
66
|
+
if (parsed && typeof parsed === "object" && !("error" in parsed)) {
|
|
67
|
+
body = JSON.stringify({
|
|
68
|
+
error: {
|
|
69
|
+
message: typeof parsed.detail === "string"
|
|
70
|
+
? parsed.detail
|
|
71
|
+
: JSON.stringify(parsed),
|
|
72
|
+
...parsed,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Not JSON (an HTML error page, say) — hand the text back unchanged so
|
|
79
|
+
// the SDK reports it as the message.
|
|
80
|
+
}
|
|
81
|
+
// Reading the body consumed it, so the Response has to be rebuilt. Drop the
|
|
82
|
+
// length/encoding headers, which no longer describe the new payload.
|
|
83
|
+
const headers = new Headers(res.headers);
|
|
84
|
+
headers.delete("content-length");
|
|
85
|
+
headers.delete("content-encoding");
|
|
86
|
+
// `globalThis.Response`, not `Response`: this module imports the Responses
|
|
87
|
+
// API's `Response` *type*, which shadows the global class name here.
|
|
88
|
+
return new globalThis.Response(body, {
|
|
89
|
+
status: res.status,
|
|
90
|
+
statusText: res.statusText,
|
|
91
|
+
headers,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Pull a human-readable message out of an OpenAI-shaped error.
|
|
97
|
+
*
|
|
98
|
+
* `api.openai.com` answers with `{ error: { message, code } }`, but not every
|
|
99
|
+
* host behind this SDK does — the ChatGPT Codex backend reports its validation
|
|
100
|
+
* failures as `{ detail: "Instructions are required" }`. Reading
|
|
101
|
+
* `error.error.message` blindly turns those into a `TypeError` that hides the
|
|
102
|
+
* real cause, so every field is probed defensively and the SDK's own `message`
|
|
103
|
+
* is the last resort.
|
|
104
|
+
*/
|
|
105
|
+
function describeOpenAIError(error) {
|
|
106
|
+
const err = error;
|
|
107
|
+
const body = err?.error;
|
|
108
|
+
const fromBody = typeof body === "string"
|
|
109
|
+
? body
|
|
110
|
+
: (body?.message ?? body?.detail ?? undefined);
|
|
111
|
+
return {
|
|
112
|
+
message: fromBody ?? err?.detail ?? err?.message ?? "Unknown error",
|
|
113
|
+
code: typeof body === "object" ? body?.code : undefined,
|
|
114
|
+
status: err?.status,
|
|
115
|
+
body: body ?? err?.detail,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
41
118
|
/**
|
|
42
119
|
* Agent for OpenAI models using the Responses API.
|
|
43
120
|
*
|
|
@@ -54,17 +131,39 @@ function lowestReasoningEffort(model) {
|
|
|
54
131
|
* ```
|
|
55
132
|
*/
|
|
56
133
|
class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
134
|
+
/**
|
|
135
|
+
* Whether a non-streaming call must be issued as a stream and collapsed.
|
|
136
|
+
* `false` here; `CodexAgent` overrides it, since that backend refuses
|
|
137
|
+
* `stream: false` outright.
|
|
138
|
+
*/
|
|
139
|
+
get forceStreaming() {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Last chance to reshape a request body before it goes out. Identity here —
|
|
144
|
+
* `CodexAgent` overrides it to satisfy that backend's extra validations.
|
|
145
|
+
*/
|
|
146
|
+
transformRequestParams(params) {
|
|
147
|
+
return params;
|
|
148
|
+
}
|
|
57
149
|
constructor(config, history) {
|
|
150
|
+
// Cast: `BaseAgentConfig.apiKey` is `string`, while this agent also accepts
|
|
151
|
+
// a token-returning function. BaseAgent never reads the field — it only
|
|
152
|
+
// declares it — so widening the base config for one provider would be the
|
|
153
|
+
// more invasive fix.
|
|
58
154
|
super({ ...config, vendor: "openai" }, history);
|
|
59
155
|
/** Count of tool calls in current execution */
|
|
60
156
|
this.currentToolCallCount = 0;
|
|
157
|
+
// Merge flat config (deprecated) with nested vendorConfig
|
|
158
|
+
// Flat config takes precedence for backward compatibility
|
|
159
|
+
const vendorConfig = config.vendorConfig?.openai || {};
|
|
160
|
+
const baseURL = config.baseURL ?? vendorConfig.baseURL;
|
|
61
161
|
this.client = new openai_1.default({
|
|
62
162
|
apiKey: config.apiKey,
|
|
163
|
+
baseURL,
|
|
63
164
|
defaultHeaders: config.defaultHeaders,
|
|
165
|
+
fetch: config.fetch,
|
|
64
166
|
});
|
|
65
|
-
// Merge flat config (deprecated) with nested vendorConfig
|
|
66
|
-
// Flat config takes precedence for backward compatibility
|
|
67
|
-
const vendorConfig = config.vendorConfig?.openai || {};
|
|
68
167
|
const disableParallelToolUse = config.disableParallelToolUse ??
|
|
69
168
|
vendorConfig.disableParallelToolUse ??
|
|
70
169
|
false;
|
|
@@ -72,6 +171,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
72
171
|
const reasoningEffort = config.reasoningEffort ?? vendorConfig.reasoningEffort;
|
|
73
172
|
const user = config.user ?? vendorConfig.user;
|
|
74
173
|
const builtInTools = config.builtInTools ?? vendorConfig.builtInTools;
|
|
174
|
+
const promptCacheKey = config.promptCacheKey ?? vendorConfig.promptCacheKey;
|
|
175
|
+
const promptCacheRetention = config.promptCacheRetention ?? vendorConfig.promptCacheRetention;
|
|
75
176
|
this.config = {
|
|
76
177
|
model: config.model || "gpt-4.1-mini",
|
|
77
178
|
// No default. `max_output_tokens` is optional on the Responses API, and
|
|
@@ -86,7 +187,10 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
86
187
|
reasoningEffort,
|
|
87
188
|
user,
|
|
88
189
|
builtInTools,
|
|
190
|
+
promptCacheKey,
|
|
191
|
+
promptCacheRetention,
|
|
89
192
|
apiKey: config.apiKey,
|
|
193
|
+
baseURL,
|
|
90
194
|
temperature: config.temperature,
|
|
91
195
|
topP: config.topP,
|
|
92
196
|
seed: config.seed,
|
|
@@ -111,6 +215,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
111
215
|
id: model.id,
|
|
112
216
|
created: model.created ? new Date(model.created * 1000) : undefined,
|
|
113
217
|
ownedBy: model.owned_by,
|
|
218
|
+
// Cast: this implementation always returns OpenAI's own cards; a
|
|
219
|
+
// subclass that reports a different shape overrides the whole method.
|
|
114
220
|
raw: model,
|
|
115
221
|
}));
|
|
116
222
|
}
|
|
@@ -118,6 +224,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
118
224
|
throw new AgentError_1.ExecutionError(`Failed to list OpenAI models: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
119
225
|
}
|
|
120
226
|
}
|
|
227
|
+
/** The configured key, resolving the function form if that is what was given. */
|
|
228
|
+
async resolveApiKey() {
|
|
229
|
+
const key = this.config.apiKey;
|
|
230
|
+
return typeof key === "function" ? await key() : (key ?? "");
|
|
231
|
+
}
|
|
121
232
|
getToolDefinitions() {
|
|
122
233
|
return Array.from(this.tools.values()).map((tool) => {
|
|
123
234
|
const prompt = tool.getPrompt();
|
|
@@ -152,6 +263,66 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
152
263
|
...(this.config.builtInTools ?? []),
|
|
153
264
|
];
|
|
154
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Rebuild a terminal response's `output` from the items streamed alongside it.
|
|
268
|
+
*
|
|
269
|
+
* The Codex backend sends `response.completed` with `output: []` and no
|
|
270
|
+
* `output_text`, unlike the platform API which fills both in — the content
|
|
271
|
+
* only ever arrives as `response.output_item.done` events. Everything
|
|
272
|
+
* downstream (tool-call detection, the text written to history) reads
|
|
273
|
+
* `output`, so without this a Codex turn silently commits an empty assistant
|
|
274
|
+
* message and drops every tool call.
|
|
275
|
+
*
|
|
276
|
+
* A no-op wherever `output` is already populated, so the platform path is
|
|
277
|
+
* untouched.
|
|
278
|
+
*/
|
|
279
|
+
repairStreamedOutput(response, streamedItems) {
|
|
280
|
+
if (response.output?.length || streamedItems.length === 0)
|
|
281
|
+
return response;
|
|
282
|
+
const output = streamedItems;
|
|
283
|
+
const outputText = output
|
|
284
|
+
.filter((item) => item.type === "message")
|
|
285
|
+
.flatMap((item) => ("content" in item ? (item.content ?? []) : []))
|
|
286
|
+
.filter((part) => part?.type === "output_text")
|
|
287
|
+
.map((part) => ("text" in part ? part.text : ""))
|
|
288
|
+
.join("");
|
|
289
|
+
return { ...response, output, output_text: outputText };
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Issue a non-streaming Responses API call.
|
|
293
|
+
*
|
|
294
|
+
* When {@link forceStreaming} is set the request is streamed and the terminal
|
|
295
|
+
* event's `response` handed back instead — giving callers the same `Response`
|
|
296
|
+
* either way, at the cost of buffering the turn.
|
|
297
|
+
*/
|
|
298
|
+
async createResponse(params, requestOptions) {
|
|
299
|
+
const body = this.transformRequestParams(params);
|
|
300
|
+
if (!this.forceStreaming) {
|
|
301
|
+
return this.client.responses.create({ ...body, stream: false }, requestOptions);
|
|
302
|
+
}
|
|
303
|
+
const stream = (await this.client.responses.create({ ...body, stream: true }, requestOptions));
|
|
304
|
+
let terminal;
|
|
305
|
+
const streamedItems = [];
|
|
306
|
+
for await (const event of stream) {
|
|
307
|
+
// Collected because the Codex backend leaves `output` empty on the
|
|
308
|
+
// terminal event — see repairStreamedOutput().
|
|
309
|
+
if (event.type === "response.output_item.done") {
|
|
310
|
+
streamedItems.push(event.item);
|
|
311
|
+
}
|
|
312
|
+
// `incomplete` and `failed` carry a Response too — handleResponse()
|
|
313
|
+
// already reads `status` off it, so let it report the reason rather than
|
|
314
|
+
// failing here with a vaguer message.
|
|
315
|
+
if (event.type === "response.completed" ||
|
|
316
|
+
event.type === "response.incomplete" ||
|
|
317
|
+
event.type === "response.failed") {
|
|
318
|
+
terminal = event.response;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (!terminal) {
|
|
322
|
+
throw new AgentError_1.ExecutionError("OpenAI stream ended without a terminal response event");
|
|
323
|
+
}
|
|
324
|
+
return this.repairStreamedOutput(terminal, streamedItems);
|
|
325
|
+
}
|
|
155
326
|
/**
|
|
156
327
|
* Build the `reasoning` field for a Responses API request, as an object to
|
|
157
328
|
* spread into the request params.
|
|
@@ -183,6 +354,24 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
183
354
|
},
|
|
184
355
|
};
|
|
185
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
|
+
}
|
|
186
375
|
async process(_input) {
|
|
187
376
|
return "";
|
|
188
377
|
}
|
|
@@ -217,7 +406,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
217
406
|
try {
|
|
218
407
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
219
408
|
this.startTurnTimer();
|
|
220
|
-
const response = await this.
|
|
409
|
+
const response = await this.createResponse({
|
|
221
410
|
model: this.config.model,
|
|
222
411
|
max_output_tokens: this.config.maxTokens,
|
|
223
412
|
input: inputMessages,
|
|
@@ -228,6 +417,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
228
417
|
// Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
|
|
229
418
|
user: this.config.user,
|
|
230
419
|
...this.buildReasoningParams(),
|
|
420
|
+
...this.buildCacheParams(),
|
|
231
421
|
}, { signal: options?.signal });
|
|
232
422
|
this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
|
|
233
423
|
return await this.handleResponse(response, options);
|
|
@@ -242,16 +432,16 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
242
432
|
throw abortError;
|
|
243
433
|
}
|
|
244
434
|
if (error && typeof error === "object" && "error" in error) {
|
|
245
|
-
const openAIError = error;
|
|
246
|
-
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.
|
|
247
|
-
if (openAIError.
|
|
435
|
+
const openAIError = describeOpenAIError(error);
|
|
436
|
+
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.message}`, openAIError.status, openAIError.body);
|
|
437
|
+
if (openAIError.code === "insufficient_quota") {
|
|
248
438
|
apiError.message =
|
|
249
439
|
"OpenAI API quota exceeded. Please check your billing details.";
|
|
250
440
|
}
|
|
251
441
|
this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
|
|
252
442
|
// Report error to viz
|
|
253
443
|
if (this.vizEventId) {
|
|
254
|
-
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.
|
|
444
|
+
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.code === "rate_limit_exceeded");
|
|
255
445
|
this.vizEventId = undefined;
|
|
256
446
|
}
|
|
257
447
|
throw apiError;
|
|
@@ -344,7 +534,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
344
534
|
try {
|
|
345
535
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
346
536
|
this.startTurnTimer();
|
|
347
|
-
const newResponse = await this.
|
|
537
|
+
const newResponse = await this.createResponse({
|
|
348
538
|
model: this.config.model,
|
|
349
539
|
max_output_tokens: this.config.maxTokens,
|
|
350
540
|
input: inputMessages,
|
|
@@ -355,14 +545,15 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
355
545
|
// Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
|
|
356
546
|
user: this.config.user,
|
|
357
547
|
...this.buildReasoningParams(),
|
|
548
|
+
...this.buildCacheParams(),
|
|
358
549
|
}, { signal: options?.signal });
|
|
359
550
|
this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
|
|
360
551
|
return this.handleResponse(newResponse, options);
|
|
361
552
|
}
|
|
362
553
|
catch (error) {
|
|
363
554
|
if (error && typeof error === "object" && "error" in error) {
|
|
364
|
-
const openAIError = error;
|
|
365
|
-
const apiError = new AgentError_1.ApiError(`OpenAI API error during tool response: ${openAIError.
|
|
555
|
+
const openAIError = describeOpenAIError(error);
|
|
556
|
+
const apiError = new AgentError_1.ApiError(`OpenAI API error during tool response: ${openAIError.message}`, openAIError.status, openAIError.body);
|
|
366
557
|
this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
|
|
367
558
|
throw apiError;
|
|
368
559
|
}
|
|
@@ -501,11 +692,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
501
692
|
throw this.withPartialTurn(error);
|
|
502
693
|
}
|
|
503
694
|
if (error && typeof error === "object" && "error" in error) {
|
|
504
|
-
const openAIError = error;
|
|
505
|
-
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.
|
|
695
|
+
const openAIError = describeOpenAIError(error);
|
|
696
|
+
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.message}`, openAIError.status, openAIError.body);
|
|
506
697
|
this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
|
|
507
698
|
if (this.vizEventId) {
|
|
508
|
-
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.
|
|
699
|
+
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.code === "rate_limit_exceeded");
|
|
509
700
|
this.vizEventId = undefined;
|
|
510
701
|
}
|
|
511
702
|
throw this.withPartialTurn(apiError);
|
|
@@ -525,7 +716,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
525
716
|
async *streamTurn(options) {
|
|
526
717
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
527
718
|
this.startTurnTimer();
|
|
528
|
-
const stream = await this.client.responses.create({
|
|
719
|
+
const stream = await this.client.responses.create(this.transformRequestParams({
|
|
529
720
|
model: this.config.model,
|
|
530
721
|
max_output_tokens: this.config.maxTokens,
|
|
531
722
|
input: inputMessages,
|
|
@@ -536,8 +727,10 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
536
727
|
top_p: this.config.topP,
|
|
537
728
|
user: this.config.user,
|
|
538
729
|
...this.buildReasoningParams("auto"),
|
|
539
|
-
|
|
730
|
+
...this.buildCacheParams(),
|
|
731
|
+
}), { signal: options?.signal });
|
|
540
732
|
let completedEvent = null;
|
|
733
|
+
const streamedItems = [];
|
|
541
734
|
// The Responses API builds the committed turn out of `response.completed`,
|
|
542
735
|
// which only arrives on success, so the deltas are mirrored here as well:
|
|
543
736
|
// without them a stream that dies mid-flight leaves nothing behind at all,
|
|
@@ -580,6 +773,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
580
773
|
if (acc)
|
|
581
774
|
acc.arguments += event.delta;
|
|
582
775
|
}
|
|
776
|
+
if (event.type === "response.output_item.done") {
|
|
777
|
+
// The Codex backend leaves `output` empty on the terminal event, so
|
|
778
|
+
// the finished items are kept here — see repairStreamedOutput().
|
|
779
|
+
streamedItems.push(event.item);
|
|
780
|
+
}
|
|
583
781
|
if (event.type === "response.completed") {
|
|
584
782
|
completedEvent = event;
|
|
585
783
|
if (event.response.usage) {
|
|
@@ -597,7 +795,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
597
795
|
if (!completedEvent) {
|
|
598
796
|
throw new AgentError_1.ExecutionError("OpenAI stream ended without a completed event");
|
|
599
797
|
}
|
|
600
|
-
const response = completedEvent.response;
|
|
798
|
+
const response = this.repairStreamedOutput(completedEvent.response, streamedItems);
|
|
601
799
|
const toolCalls = response.output.filter((o) => o.type === "function_call");
|
|
602
800
|
if (toolCalls.length > 0) {
|
|
603
801
|
// As in handleResponse(): bail out before the assistant turn is written,
|
|
@@ -659,12 +857,16 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
659
857
|
}
|
|
660
858
|
}
|
|
661
859
|
parseUsage(input) {
|
|
860
|
+
const inputDetails = input.input_tokens_details;
|
|
662
861
|
return {
|
|
663
862
|
input_tokens: input.input_tokens,
|
|
664
863
|
output_tokens: input.output_tokens,
|
|
665
864
|
total_tokens: input.total_tokens,
|
|
666
865
|
// Reasoning tokens are already counted inside `output_tokens`.
|
|
667
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,
|
|
668
870
|
};
|
|
669
871
|
}
|
|
670
872
|
}
|