@agentionai/agents 1.11.0 → 1.13.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 +23 -0
- package/dist/agents/AgentEvent.d.ts +5 -0
- package/dist/agents/AgentEvent.js +5 -0
- package/dist/agents/BaseAgent.d.ts +107 -1
- package/dist/agents/BaseAgent.js +56 -0
- package/dist/agents/anthropic/ClaudeAgent.js +157 -117
- package/dist/agents/errors/AgentError.d.ts +12 -0
- package/dist/agents/openai/CodexAgent.d.ts +135 -0
- package/dist/agents/openai/CodexAgent.js +179 -0
- package/dist/agents/openai/OpenAiAgent.d.ts +89 -6
- package/dist/agents/openai/OpenAiAgent.js +305 -75
- package/dist/agents/openai/codex-auth.d.ts +166 -0
- package/dist/agents/openai/codex-auth.js +264 -0
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +140 -107
- package/dist/agents/openrouter/OpenRouterAgent.js +128 -95
- package/dist/agents/reasoning-text.d.ts +44 -0
- package/dist/agents/reasoning-text.js +43 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +13 -1
- package/dist/openai.d.ts +4 -1
- package/dist/openai.js +16 -1
- package/package.json +3 -3
|
@@ -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;
|
|
@@ -87,6 +186,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
87
186
|
user,
|
|
88
187
|
builtInTools,
|
|
89
188
|
apiKey: config.apiKey,
|
|
189
|
+
baseURL,
|
|
90
190
|
temperature: config.temperature,
|
|
91
191
|
topP: config.topP,
|
|
92
192
|
seed: config.seed,
|
|
@@ -111,6 +211,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
111
211
|
id: model.id,
|
|
112
212
|
created: model.created ? new Date(model.created * 1000) : undefined,
|
|
113
213
|
ownedBy: model.owned_by,
|
|
214
|
+
// Cast: this implementation always returns OpenAI's own cards; a
|
|
215
|
+
// subclass that reports a different shape overrides the whole method.
|
|
114
216
|
raw: model,
|
|
115
217
|
}));
|
|
116
218
|
}
|
|
@@ -118,6 +220,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
118
220
|
throw new AgentError_1.ExecutionError(`Failed to list OpenAI models: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
119
221
|
}
|
|
120
222
|
}
|
|
223
|
+
/** The configured key, resolving the function form if that is what was given. */
|
|
224
|
+
async resolveApiKey() {
|
|
225
|
+
const key = this.config.apiKey;
|
|
226
|
+
return typeof key === "function" ? await key() : (key ?? "");
|
|
227
|
+
}
|
|
121
228
|
getToolDefinitions() {
|
|
122
229
|
return Array.from(this.tools.values()).map((tool) => {
|
|
123
230
|
const prompt = tool.getPrompt();
|
|
@@ -152,6 +259,66 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
152
259
|
...(this.config.builtInTools ?? []),
|
|
153
260
|
];
|
|
154
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Rebuild a terminal response's `output` from the items streamed alongside it.
|
|
264
|
+
*
|
|
265
|
+
* The Codex backend sends `response.completed` with `output: []` and no
|
|
266
|
+
* `output_text`, unlike the platform API which fills both in — the content
|
|
267
|
+
* only ever arrives as `response.output_item.done` events. Everything
|
|
268
|
+
* downstream (tool-call detection, the text written to history) reads
|
|
269
|
+
* `output`, so without this a Codex turn silently commits an empty assistant
|
|
270
|
+
* message and drops every tool call.
|
|
271
|
+
*
|
|
272
|
+
* A no-op wherever `output` is already populated, so the platform path is
|
|
273
|
+
* untouched.
|
|
274
|
+
*/
|
|
275
|
+
repairStreamedOutput(response, streamedItems) {
|
|
276
|
+
if (response.output?.length || streamedItems.length === 0)
|
|
277
|
+
return response;
|
|
278
|
+
const output = streamedItems;
|
|
279
|
+
const outputText = output
|
|
280
|
+
.filter((item) => item.type === "message")
|
|
281
|
+
.flatMap((item) => ("content" in item ? (item.content ?? []) : []))
|
|
282
|
+
.filter((part) => part?.type === "output_text")
|
|
283
|
+
.map((part) => ("text" in part ? part.text : ""))
|
|
284
|
+
.join("");
|
|
285
|
+
return { ...response, output, output_text: outputText };
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Issue a non-streaming Responses API call.
|
|
289
|
+
*
|
|
290
|
+
* When {@link forceStreaming} is set the request is streamed and the terminal
|
|
291
|
+
* event's `response` handed back instead — giving callers the same `Response`
|
|
292
|
+
* either way, at the cost of buffering the turn.
|
|
293
|
+
*/
|
|
294
|
+
async createResponse(params, requestOptions) {
|
|
295
|
+
const body = this.transformRequestParams(params);
|
|
296
|
+
if (!this.forceStreaming) {
|
|
297
|
+
return this.client.responses.create({ ...body, stream: false }, requestOptions);
|
|
298
|
+
}
|
|
299
|
+
const stream = (await this.client.responses.create({ ...body, stream: true }, requestOptions));
|
|
300
|
+
let terminal;
|
|
301
|
+
const streamedItems = [];
|
|
302
|
+
for await (const event of stream) {
|
|
303
|
+
// Collected because the Codex backend leaves `output` empty on the
|
|
304
|
+
// terminal event — see repairStreamedOutput().
|
|
305
|
+
if (event.type === "response.output_item.done") {
|
|
306
|
+
streamedItems.push(event.item);
|
|
307
|
+
}
|
|
308
|
+
// `incomplete` and `failed` carry a Response too — handleResponse()
|
|
309
|
+
// already reads `status` off it, so let it report the reason rather than
|
|
310
|
+
// failing here with a vaguer message.
|
|
311
|
+
if (event.type === "response.completed" ||
|
|
312
|
+
event.type === "response.incomplete" ||
|
|
313
|
+
event.type === "response.failed") {
|
|
314
|
+
terminal = event.response;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!terminal) {
|
|
318
|
+
throw new AgentError_1.ExecutionError("OpenAI stream ended without a terminal response event");
|
|
319
|
+
}
|
|
320
|
+
return this.repairStreamedOutput(terminal, streamedItems);
|
|
321
|
+
}
|
|
155
322
|
/**
|
|
156
323
|
* Build the `reasoning` field for a Responses API request, as an object to
|
|
157
324
|
* spread into the request params.
|
|
@@ -190,6 +357,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
190
357
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
191
358
|
// Reset token usage for this execution
|
|
192
359
|
this.resetTokenUsage();
|
|
360
|
+
this.resetPartialTurn();
|
|
193
361
|
this.currentToolCallCount = 0;
|
|
194
362
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
195
363
|
// Start visualization reporting
|
|
@@ -216,7 +384,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
216
384
|
try {
|
|
217
385
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
218
386
|
this.startTurnTimer();
|
|
219
|
-
const response = await this.
|
|
387
|
+
const response = await this.createResponse({
|
|
220
388
|
model: this.config.model,
|
|
221
389
|
max_output_tokens: this.config.maxTokens,
|
|
222
390
|
input: inputMessages,
|
|
@@ -241,16 +409,16 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
241
409
|
throw abortError;
|
|
242
410
|
}
|
|
243
411
|
if (error && typeof error === "object" && "error" in error) {
|
|
244
|
-
const openAIError = error;
|
|
245
|
-
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.
|
|
246
|
-
if (openAIError.
|
|
412
|
+
const openAIError = describeOpenAIError(error);
|
|
413
|
+
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.message}`, openAIError.status, openAIError.body);
|
|
414
|
+
if (openAIError.code === "insufficient_quota") {
|
|
247
415
|
apiError.message =
|
|
248
416
|
"OpenAI API quota exceeded. Please check your billing details.";
|
|
249
417
|
}
|
|
250
418
|
this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
|
|
251
419
|
// Report error to viz
|
|
252
420
|
if (this.vizEventId) {
|
|
253
|
-
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.
|
|
421
|
+
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.code === "rate_limit_exceeded");
|
|
254
422
|
this.vizEventId = undefined;
|
|
255
423
|
}
|
|
256
424
|
throw apiError;
|
|
@@ -343,7 +511,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
343
511
|
try {
|
|
344
512
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
345
513
|
this.startTurnTimer();
|
|
346
|
-
const newResponse = await this.
|
|
514
|
+
const newResponse = await this.createResponse({
|
|
347
515
|
model: this.config.model,
|
|
348
516
|
max_output_tokens: this.config.maxTokens,
|
|
349
517
|
input: inputMessages,
|
|
@@ -360,8 +528,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
360
528
|
}
|
|
361
529
|
catch (error) {
|
|
362
530
|
if (error && typeof error === "object" && "error" in error) {
|
|
363
|
-
const openAIError = error;
|
|
364
|
-
const apiError = new AgentError_1.ApiError(`OpenAI API error during tool response: ${openAIError.
|
|
531
|
+
const openAIError = describeOpenAIError(error);
|
|
532
|
+
const apiError = new AgentError_1.ApiError(`OpenAI API error during tool response: ${openAIError.message}`, openAIError.status, openAIError.body);
|
|
365
533
|
this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
|
|
366
534
|
throw apiError;
|
|
367
535
|
}
|
|
@@ -461,6 +629,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
461
629
|
async *executeStream(input, options) {
|
|
462
630
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
463
631
|
this.resetTokenUsage();
|
|
632
|
+
this.resetPartialTurn();
|
|
464
633
|
this.currentToolCallCount = 0;
|
|
465
634
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
466
635
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -488,7 +657,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
488
657
|
VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
|
|
489
658
|
this.vizEventId = undefined;
|
|
490
659
|
}
|
|
491
|
-
throw abortError;
|
|
660
|
+
throw this.withPartialTurn(abortError);
|
|
492
661
|
}
|
|
493
662
|
if (error instanceof AgentError_1.AgentError) {
|
|
494
663
|
this.emit(AgentEvent_1.AgentEvent.ERROR, error);
|
|
@@ -496,17 +665,17 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
496
665
|
VizReporter_1.vizReporter.agentError(this.vizEventId, error.constructor.name, error.message, false);
|
|
497
666
|
this.vizEventId = undefined;
|
|
498
667
|
}
|
|
499
|
-
throw error;
|
|
668
|
+
throw this.withPartialTurn(error);
|
|
500
669
|
}
|
|
501
670
|
if (error && typeof error === "object" && "error" in error) {
|
|
502
|
-
const openAIError = error;
|
|
503
|
-
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.
|
|
671
|
+
const openAIError = describeOpenAIError(error);
|
|
672
|
+
const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.message}`, openAIError.status, openAIError.body);
|
|
504
673
|
this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
|
|
505
674
|
if (this.vizEventId) {
|
|
506
|
-
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.
|
|
675
|
+
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, openAIError.code === "rate_limit_exceeded");
|
|
507
676
|
this.vizEventId = undefined;
|
|
508
677
|
}
|
|
509
|
-
throw apiError;
|
|
678
|
+
throw this.withPartialTurn(apiError);
|
|
510
679
|
}
|
|
511
680
|
const executionError = new AgentError_1.ExecutionError(`OpenAI error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
512
681
|
this.emit(AgentEvent_1.AgentEvent.ERROR, executionError);
|
|
@@ -514,7 +683,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
514
683
|
VizReporter_1.vizReporter.agentError(this.vizEventId, "ExecutionError", executionError.message, false);
|
|
515
684
|
this.vizEventId = undefined;
|
|
516
685
|
}
|
|
517
|
-
throw executionError;
|
|
686
|
+
throw this.withPartialTurn(executionError);
|
|
518
687
|
}
|
|
519
688
|
finally {
|
|
520
689
|
this.history.endExecution();
|
|
@@ -523,7 +692,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
523
692
|
async *streamTurn(options) {
|
|
524
693
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
525
694
|
this.startTurnTimer();
|
|
526
|
-
const stream = await this.client.responses.create({
|
|
695
|
+
const stream = await this.client.responses.create(this.transformRequestParams({
|
|
527
696
|
model: this.config.model,
|
|
528
697
|
max_output_tokens: this.config.maxTokens,
|
|
529
698
|
input: inputMessages,
|
|
@@ -534,70 +703,131 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
534
703
|
top_p: this.config.topP,
|
|
535
704
|
user: this.config.user,
|
|
536
705
|
...this.buildReasoningParams("auto"),
|
|
537
|
-
}, { signal: options?.signal });
|
|
706
|
+
}), { signal: options?.signal });
|
|
538
707
|
let completedEvent = null;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
708
|
+
const streamedItems = [];
|
|
709
|
+
// The Responses API builds the committed turn out of `response.completed`,
|
|
710
|
+
// which only arrives on success, so the deltas are mirrored here as well:
|
|
711
|
+
// without them a stream that dies mid-flight leaves nothing behind at all,
|
|
712
|
+
// and a reasoning summary can be minutes of generation.
|
|
713
|
+
let textDelta = "";
|
|
714
|
+
let reasoningDelta = "";
|
|
715
|
+
const partialCalls = new Map();
|
|
716
|
+
// Set once this frame's assistant message reaches history.
|
|
717
|
+
let committed = false;
|
|
718
|
+
let failure;
|
|
719
|
+
try {
|
|
720
|
+
for await (const event of stream) {
|
|
721
|
+
if (event.type === "response.output_text.delta") {
|
|
722
|
+
this.markFirstToken();
|
|
723
|
+
// Accumulated as well as yielded purely so the `finally` below can hand
|
|
724
|
+
// it back if the stream dies: the committed turn is rebuilt from
|
|
725
|
+
// `response.completed`, which never arrives on a failure.
|
|
726
|
+
textDelta += event.delta;
|
|
727
|
+
this.emit(AgentEvent_1.AgentEvent.CHUNK, event.delta);
|
|
728
|
+
yield { type: "text", content: event.delta };
|
|
729
|
+
}
|
|
730
|
+
if (event.type === "response.reasoning_summary_text.delta") {
|
|
731
|
+
this.markFirstToken();
|
|
732
|
+
reasoningDelta += event.delta;
|
|
733
|
+
this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, event.delta);
|
|
734
|
+
yield { type: "reasoning", content: event.delta };
|
|
735
|
+
}
|
|
736
|
+
if (event.type === "response.output_item.added") {
|
|
737
|
+
const item = event.item;
|
|
738
|
+
if (item.type === "function_call") {
|
|
739
|
+
partialCalls.set(event.output_index, {
|
|
740
|
+
id: item.call_id || item.id || "",
|
|
741
|
+
name: item.name ?? "",
|
|
742
|
+
arguments: "",
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (event.type === "response.function_call_arguments.delta") {
|
|
747
|
+
const acc = partialCalls.get(event.output_index);
|
|
748
|
+
if (acc)
|
|
749
|
+
acc.arguments += event.delta;
|
|
750
|
+
}
|
|
751
|
+
if (event.type === "response.output_item.done") {
|
|
752
|
+
// The Codex backend leaves `output` empty on the terminal event, so
|
|
753
|
+
// the finished items are kept here — see repairStreamedOutput().
|
|
754
|
+
streamedItems.push(event.item);
|
|
755
|
+
}
|
|
756
|
+
if (event.type === "response.completed") {
|
|
757
|
+
completedEvent = event;
|
|
758
|
+
if (event.response.usage) {
|
|
759
|
+
this.accumulateUsage(this.parseUsage(event.response.usage));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (event.type === "response.incomplete") {
|
|
763
|
+
throw new AgentError_1.MaxTokensExceededError("Response incomplete: max tokens reached", this.config.maxTokens);
|
|
764
|
+
}
|
|
544
765
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
766
|
+
// The SDK's stream iterator swallows the abort and simply stops yielding.
|
|
767
|
+
// Without this the turn would fail as a malformed stream instead of a
|
|
768
|
+
// cancellation — checked here so the tokens already spent are reported.
|
|
769
|
+
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
770
|
+
if (!completedEvent) {
|
|
771
|
+
throw new AgentError_1.ExecutionError("OpenAI stream ended without a completed event");
|
|
549
772
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
773
|
+
const response = this.repairStreamedOutput(completedEvent.response, streamedItems);
|
|
774
|
+
const toolCalls = response.output.filter((o) => o.type === "function_call");
|
|
775
|
+
if (toolCalls.length > 0) {
|
|
776
|
+
// As in handleResponse(): bail out before the assistant turn is written,
|
|
777
|
+
// so a cancelled run leaves no unanswered function call in history.
|
|
778
|
+
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
779
|
+
this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
|
|
780
|
+
this.currentToolCallCount += toolCalls.length;
|
|
781
|
+
const functionCalls = toolCalls.map((tc) => ({
|
|
782
|
+
id: tc.id || tc.call_id,
|
|
783
|
+
call_id: tc.call_id,
|
|
784
|
+
name: tc.name,
|
|
785
|
+
arguments: tc.arguments,
|
|
786
|
+
}));
|
|
787
|
+
const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls);
|
|
788
|
+
this.addToHistory(assistantEntry);
|
|
789
|
+
committed = true;
|
|
790
|
+
const toolResults = await this.handleToolUse(toolCalls, options);
|
|
791
|
+
for (const result of toolResults) {
|
|
792
|
+
this.addToHistory(transformers_1.openAiTransformer.toolResultEntry(result.call_id, result.output, false));
|
|
554
793
|
}
|
|
794
|
+
yield* this.streamTurn(options);
|
|
555
795
|
}
|
|
556
|
-
|
|
557
|
-
|
|
796
|
+
else {
|
|
797
|
+
const textContent = response.output_text || "";
|
|
798
|
+
const entry = transformers_1.openAiTransformer.fromProviderMessage("assistant", textContent);
|
|
799
|
+
this.addToHistory(entry);
|
|
800
|
+
committed = true;
|
|
801
|
+
this.emit(AgentEvent_1.AgentEvent.DONE, response, this.lastTokenUsage);
|
|
802
|
+
if (this.vizEventId) {
|
|
803
|
+
VizReporter_1.vizReporter.agentComplete(this.vizEventId, {
|
|
804
|
+
input: this.lastTokenUsage?.input_tokens || 0,
|
|
805
|
+
output: this.lastTokenUsage?.output_tokens || 0,
|
|
806
|
+
total: this.lastTokenUsage?.total_tokens || 0,
|
|
807
|
+
}, "end_turn", this.currentToolCallCount > 0, this.currentToolCallCount, textContent);
|
|
808
|
+
this.vizEventId = undefined;
|
|
809
|
+
}
|
|
558
810
|
}
|
|
559
811
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
564
|
-
if (!completedEvent) {
|
|
565
|
-
throw new AgentError_1.ExecutionError("OpenAI stream ended without a completed event");
|
|
566
|
-
}
|
|
567
|
-
const response = completedEvent.response;
|
|
568
|
-
const toolCalls = response.output.filter((o) => o.type === "function_call");
|
|
569
|
-
if (toolCalls.length > 0) {
|
|
570
|
-
// As in handleResponse(): bail out before the assistant turn is written,
|
|
571
|
-
// so a cancelled run leaves no unanswered function call in history.
|
|
572
|
-
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
573
|
-
this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
|
|
574
|
-
this.currentToolCallCount += toolCalls.length;
|
|
575
|
-
const functionCalls = toolCalls.map((tc) => ({
|
|
576
|
-
id: tc.id || tc.call_id,
|
|
577
|
-
call_id: tc.call_id,
|
|
578
|
-
name: tc.name,
|
|
579
|
-
arguments: tc.arguments,
|
|
580
|
-
}));
|
|
581
|
-
const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls);
|
|
582
|
-
this.addToHistory(assistantEntry);
|
|
583
|
-
const toolResults = await this.handleToolUse(toolCalls, options);
|
|
584
|
-
for (const result of toolResults) {
|
|
585
|
-
this.addToHistory(transformers_1.openAiTransformer.toolResultEntry(result.call_id, result.output, false));
|
|
586
|
-
}
|
|
587
|
-
yield* this.streamTurn(options);
|
|
812
|
+
catch (error) {
|
|
813
|
+
failure = error;
|
|
814
|
+
throw error;
|
|
588
815
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
816
|
+
finally {
|
|
817
|
+
if (!committed) {
|
|
818
|
+
this.capturePartialTurn({
|
|
819
|
+
text: textDelta,
|
|
820
|
+
reasoning: reasoningDelta,
|
|
821
|
+
toolCalls: Array.from(partialCalls.entries())
|
|
822
|
+
.sort(([a], [b]) => a - b)
|
|
823
|
+
.map(([, tc]) => ({
|
|
824
|
+
id: tc.id,
|
|
825
|
+
name: tc.name,
|
|
826
|
+
arguments: tc.arguments,
|
|
827
|
+
})),
|
|
828
|
+
reason: this.partialTurnReason(failure, options?.signal),
|
|
829
|
+
error: failure,
|
|
830
|
+
});
|
|
601
831
|
}
|
|
602
832
|
}
|
|
603
833
|
}
|