@agentionai/agents 1.6.0 → 1.7.0-beta.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.
@@ -6,6 +6,7 @@ const mistralai_1 = require("@mistralai/mistralai");
6
6
  const http_1 = require("@mistralai/mistralai/lib/http");
7
7
  const BaseAgent_1 = require("../BaseAgent");
8
8
  const AgentEvent_1 = require("../AgentEvent");
9
+ const cancellation_1 = require("../cancellation");
9
10
  const AgentError_1 = require("../errors/AgentError");
10
11
  const transformers_1 = require("../../history/transformers");
11
12
  const components_1 = require("@mistralai/mistralai/models/components");
@@ -129,7 +130,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
129
130
  async process(_input) {
130
131
  return "";
131
132
  }
132
- async execute(input) {
133
+ async execute(input, options) {
133
134
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
134
135
  // Reset token usage for this execution
135
136
  this.resetTokenUsage();
@@ -169,11 +170,19 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
169
170
  randomSeed: this.config.randomSeed,
170
171
  safePrompt: this.config.safePrompt,
171
172
  stop: this.config.stopSequences,
172
- });
173
+ }, { signal: options?.signal });
173
174
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
174
- return await this.handleResponse(response);
175
+ return await this.handleResponse(response, options);
175
176
  }
176
177
  catch (error) {
178
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
179
+ const abortError = this.abortError(error, options?.signal);
180
+ if (this.vizEventId) {
181
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
182
+ this.vizEventId = undefined;
183
+ }
184
+ throw abortError;
185
+ }
177
186
  const err = error;
178
187
  if (err.status) {
179
188
  const apiError = new AgentError_1.ApiError(`Mistral API error: ${err.message || "Unknown error"}`, err.status, error);
@@ -200,7 +209,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
200
209
  this.history.endExecution();
201
210
  }
202
211
  }
203
- async handleResponse(response) {
212
+ async handleResponse(response, options) {
204
213
  if (!response.choices || response.choices.length === 0) {
205
214
  const error = new AgentError_1.ExecutionError("Empty response from Mistral API");
206
215
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
@@ -255,18 +264,26 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
255
264
  }
256
265
  else if (choice.finishReason === "tool_calls" || message.toolCalls) {
257
266
  try {
267
+ // Stop before the assistant turn is written: nothing else would notice
268
+ // a cancellation until the next provider call, and bailing out here
269
+ // avoids both running the tools' side effects and leaving a tool call
270
+ // in history with no tool message to answer it.
271
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
258
272
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, message.toolCalls);
259
273
  // Add assistant message with tool calls to history (normalized)
260
274
  const assistantEntry = transformers_1.mistralTransformer.fromProviderMessage(message);
261
275
  this.addToHistory(assistantEntry);
262
- const toolResults = await this.handleToolCalls(message.toolCalls || []);
276
+ const toolResults = await this.handleToolCalls(message.toolCalls || [], options);
263
277
  // Add tool results to history (normalized)
264
278
  for (const result of toolResults) {
265
279
  const resultEntry = transformers_1.mistralTransformer.toolResultEntry(result.toolCallId, result.name, result.content);
266
280
  this.addToHistory(resultEntry);
267
281
  }
268
- // Rate limiting delay for Mistral
269
- await (0, promises_1.setTimeout)(this.config.rateLimitDelay || 1500);
282
+ // Rate limiting delay for Mistral. Aborting during the wait rejects
283
+ // immediately rather than sitting out the full delay first.
284
+ await (0, promises_1.setTimeout)(this.config.rateLimitDelay || 1500, undefined, {
285
+ signal: options?.signal,
286
+ });
270
287
  // Continue conversation
271
288
  try {
272
289
  const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
@@ -281,9 +298,9 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
281
298
  randomSeed: this.config.randomSeed,
282
299
  safePrompt: this.config.safePrompt,
283
300
  stop: this.config.stopSequences,
284
- });
301
+ }, { signal: options?.signal });
285
302
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
286
- return this.handleResponse(newResponse);
303
+ return this.handleResponse(newResponse, options);
287
304
  }
288
305
  catch (error) {
289
306
  const err = error;
@@ -316,7 +333,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
316
333
  }
317
334
  throw error;
318
335
  }
319
- async handleToolCalls(toolCalls) {
336
+ async handleToolCalls(toolCalls, options) {
320
337
  if (!toolCalls.length) {
321
338
  throw new AgentError_1.ExecutionError("No tool calls found in response");
322
339
  }
@@ -346,7 +363,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
346
363
  else {
347
364
  args = toolCall.function.arguments;
348
365
  }
349
- const result = await tool.execute(this.getId(), this.getName(), args, toolCallId, this.config.model, "mistral");
366
+ const result = await tool.execute(this.getId(), this.getName(), args, toolCallId, this.config.model, "mistral", { signal: options?.signal });
350
367
  return {
351
368
  name: toolName,
352
369
  toolCallId,
@@ -1,4 +1,5 @@
1
1
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
+ import { ExecuteOptions } from "../cancellation";
2
3
  import { History, MessageContent } from "../../history/History";
3
4
  import { OllamaModel } from "../model-types";
4
5
  type AgentConfig = BaseAgentConfig & {
@@ -50,9 +51,22 @@ export declare class OllamaAgent extends BaseAgent {
50
51
  private vizEventId?;
51
52
  /** Count of tool calls in current execution */
52
53
  private currentToolCallCount;
53
- /** Cached Ollama client instance */
54
+ /** Cached signal-less Ollama client instance */
54
55
  private _client;
56
+ /** Cached constructor from the optional `ollama` peer dependency */
57
+ private _clientClass;
55
58
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
59
+ private loadClientClass;
60
+ /**
61
+ * Get an Ollama client.
62
+ *
63
+ * The `ollama` package takes no per-request options — its own `abort()`
64
+ * cancels every streamed request on the client at once, which is too blunt
65
+ * for a per-run signal. A client does accept a `fetch` implementation
66
+ * though, so a run with a signal gets its own client whose `fetch` attaches
67
+ * that signal to each request. Clients are cheap (they open no connection),
68
+ * and the signal-less one is still cached and shared.
69
+ */
56
70
  private getClient;
57
71
  /**
58
72
  * List the models currently available on the Ollama server.
@@ -64,10 +78,10 @@ export declare class OllamaAgent extends BaseAgent {
64
78
  listModels(): Promise<ModelInfo<OllamaModelInfo>[]>;
65
79
  protected getToolDefinitions(): OllamaToolDefinition[];
66
80
  protected process(_input: string): Promise<string>;
67
- execute(input: string | MessageContent[]): Promise<string>;
81
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
68
82
  private buildOptions;
69
83
  private callOllama;
70
- protected handleResponse(response: unknown): Promise<string>;
84
+ protected handleResponse(response: unknown, options?: ExecuteOptions): Promise<string>;
71
85
  private handleToolCalls;
72
86
  protected parseUsage(input: unknown): TokenUsage;
73
87
  }
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.OllamaAgent = void 0;
37
37
  const BaseAgent_1 = require("../BaseAgent");
38
38
  const AgentEvent_1 = require("../AgentEvent");
39
+ const cancellation_1 = require("../cancellation");
39
40
  const AgentError_1 = require("../errors/AgentError");
40
41
  const transformers_1 = require("../../history/transformers");
41
42
  const VizReporter_1 = require("../../viz/VizReporter");
@@ -73,8 +74,10 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
73
74
  super({ ...config, vendor: "ollama" }, history);
74
75
  /** Count of tool calls in current execution */
75
76
  this.currentToolCallCount = 0;
76
- /** Cached Ollama client instance */
77
+ /** Cached signal-less Ollama client instance */
77
78
  this._client = null;
79
+ /** Cached constructor from the optional `ollama` peer dependency */
80
+ this._clientClass = null;
78
81
  const vendorConfig = config.vendorConfig?.ollama || {};
79
82
  const host = config.host ?? vendorConfig.host;
80
83
  this.config = {
@@ -91,8 +94,8 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
91
94
  };
92
95
  this.addSystemMessage(this.getSystemMessage());
93
96
  }
94
- async getClient() {
95
- if (!this._client) {
97
+ async loadClientClass() {
98
+ if (!this._clientClass) {
96
99
  const pkg = "ollama";
97
100
  try {
98
101
  const mod = (await Promise.resolve(`${pkg}`).then(s => __importStar(require(s))));
@@ -100,15 +103,39 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
100
103
  if (!OllamaClass) {
101
104
  throw new Error("Could not find Ollama class in ollama package");
102
105
  }
103
- this._client = new OllamaClass({
104
- host: this.config.host,
105
- headers: this.config.defaultHeaders,
106
- });
106
+ this._clientClass = OllamaClass;
107
107
  }
108
108
  catch (err) {
109
109
  throw new AgentError_1.ExecutionError(`Failed to load 'ollama' package. Install it with: npm install ollama\n${err instanceof Error ? err.message : String(err)}`);
110
110
  }
111
111
  }
112
+ return this._clientClass;
113
+ }
114
+ /**
115
+ * Get an Ollama client.
116
+ *
117
+ * The `ollama` package takes no per-request options — its own `abort()`
118
+ * cancels every streamed request on the client at once, which is too blunt
119
+ * for a per-run signal. A client does accept a `fetch` implementation
120
+ * though, so a run with a signal gets its own client whose `fetch` attaches
121
+ * that signal to each request. Clients are cheap (they open no connection),
122
+ * and the signal-less one is still cached and shared.
123
+ */
124
+ async getClient(signal) {
125
+ if (!signal && this._client)
126
+ return this._client;
127
+ const OllamaClass = await this.loadClientClass();
128
+ if (signal) {
129
+ return new OllamaClass({
130
+ host: this.config.host,
131
+ headers: this.config.defaultHeaders,
132
+ fetch: fetchWithSignal(signal),
133
+ });
134
+ }
135
+ this._client ?? (this._client = new OllamaClass({
136
+ host: this.config.host,
137
+ headers: this.config.defaultHeaders,
138
+ }));
112
139
  return this._client;
113
140
  }
114
141
  /**
@@ -146,7 +173,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
146
173
  async process(_input) {
147
174
  return "";
148
175
  }
149
- async execute(input) {
176
+ async execute(input, options) {
150
177
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
151
178
  this.resetTokenUsage();
152
179
  this.currentToolCallCount = 0;
@@ -167,12 +194,19 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
167
194
  this.history.setSessionAnchor();
168
195
  this.history.beginExecution();
169
196
  try {
170
- await this.getClient(); // ensure client is cached before handleResponse loop
171
- const response = await this.callOllama();
197
+ const response = await this.callOllama(options);
172
198
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
173
- return await this.handleResponse(response);
199
+ return await this.handleResponse(response, options);
174
200
  }
175
201
  catch (error) {
202
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
203
+ const abortError = this.abortError(error, options?.signal);
204
+ if (this.vizEventId) {
205
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
206
+ this.vizEventId = undefined;
207
+ }
208
+ throw abortError;
209
+ }
176
210
  if (error instanceof AgentError_1.ExecutionError || error instanceof AgentError_1.ApiError) {
177
211
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
178
212
  if (this.vizEventId) {
@@ -211,8 +245,8 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
211
245
  opts.think = this.config.think;
212
246
  return opts;
213
247
  }
214
- async callOllama() {
215
- const client = await this.getClient();
248
+ async callOllama(executeOptions) {
249
+ const client = await this.getClient(executeOptions?.signal);
216
250
  const messages = transformers_1.ollamaTransformer.toProvider(this.history.getEntries());
217
251
  const tools = this.tools.size > 0 ? this.getToolDefinitions() : undefined;
218
252
  const options = this.buildOptions();
@@ -226,7 +260,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
226
260
  options: Object.keys(options).length > 0 ? options : undefined,
227
261
  });
228
262
  }
229
- async handleResponse(response) {
263
+ async handleResponse(response, options) {
230
264
  const ollamaResponse = response;
231
265
  const usage = this.accumulateUsage(this.parseUsage(ollamaResponse));
232
266
  if (ollamaResponse.done_reason === "length") {
@@ -258,22 +292,27 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
258
292
  }
259
293
  // Tool calls detected
260
294
  const toolCalls = message.tool_calls;
295
+ // Stop before the assistant turn is written: nothing else would notice a
296
+ // cancellation until the next provider call, and bailing out here avoids
297
+ // both running the tools' side effects and leaving a tool call in history
298
+ // with no tool message to answer it.
299
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
261
300
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
262
301
  this.currentToolCallCount += toolCalls.length;
263
302
  // Generate IDs — Ollama doesn't provide tool call IDs
264
303
  const generatedIds = toolCalls.map((_, i) => `ollama_${Date.now()}_${i}`);
265
304
  const assistantEntry = transformers_1.ollamaTransformer.fromProviderMessage(message, generatedIds);
266
305
  this.addToHistory(assistantEntry);
267
- const toolResults = await this.handleToolCalls(toolCalls, generatedIds);
306
+ const toolResults = await this.handleToolCalls(toolCalls, generatedIds, options);
268
307
  for (const result of toolResults) {
269
308
  const resultEntry = transformers_1.ollamaTransformer.toolResultEntry(result.toolCallId, result.content);
270
309
  this.addToHistory(resultEntry);
271
310
  }
272
311
  // Continue conversation with tool results
273
312
  try {
274
- const newResponse = await this.callOllama();
313
+ const newResponse = await this.callOllama(options);
275
314
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
276
- return this.handleResponse(newResponse);
315
+ return this.handleResponse(newResponse, options);
277
316
  }
278
317
  catch (error) {
279
318
  const executionError = new AgentError_1.ExecutionError(`Ollama error during tool response: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -281,7 +320,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
281
320
  throw executionError;
282
321
  }
283
322
  }
284
- async handleToolCalls(toolCalls, generatedIds) {
323
+ async handleToolCalls(toolCalls, generatedIds, options) {
285
324
  return Promise.all(toolCalls.map(async (toolCall, idx) => {
286
325
  const toolName = toolCall.function.name;
287
326
  const tool = this.tools.get(toolName);
@@ -296,7 +335,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
296
335
  const args = typeof toolCall.function.arguments === "string"
297
336
  ? JSON.parse(toolCall.function.arguments)
298
337
  : toolCall.function.arguments;
299
- const result = await tool.execute(this.getId(), this.getName(), args, toolCallId, this.config.model, "ollama");
338
+ const result = await tool.execute(this.getId(), this.getName(), args, toolCallId, this.config.model, "ollama", { signal: options?.signal });
300
339
  return { toolCallId, content: JSON.stringify(result) };
301
340
  }
302
341
  catch (error) {
@@ -329,4 +368,15 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
329
368
  }
330
369
  }
331
370
  exports.OllamaAgent = OllamaAgent;
371
+ /**
372
+ * Wrap the global `fetch` so every request carries `signal`, keeping any
373
+ * signal the caller already set — the `ollama` package attaches its own to
374
+ * streamed requests so that its `abort()` keeps working.
375
+ */
376
+ function fetchWithSignal(signal) {
377
+ return (input, init) => fetch(input, {
378
+ ...init,
379
+ signal: (0, cancellation_1.combineSignals)(init?.signal ?? undefined, signal),
380
+ });
381
+ }
332
382
  //# sourceMappingURL=OllamaAgent.js.map
@@ -1,4 +1,5 @@
1
1
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
+ import { ExecuteOptions } from "../cancellation";
2
3
  import { History, MessageContent } from "../../history/History";
3
4
  import { Tool, Response, ResponseUsage } from "openai/resources/responses/responses";
4
5
  import type { Model as OpenAIModelCard } from "openai/resources/models";
@@ -95,8 +96,8 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
95
96
  */
96
97
  private buildReasoningParams;
97
98
  protected process(_input: string): Promise<string>;
98
- execute(input: string | MessageContent[]): Promise<string>;
99
- protected handleResponse(response: Response): Promise<string>;
99
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
100
+ protected handleResponse(response: Response, options?: ExecuteOptions): Promise<string>;
100
101
  private handleToolUse;
101
102
  /**
102
103
  * Stream a response as an async generator of `StreamChunk` objects.
@@ -110,7 +111,7 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
110
111
  * }
111
112
  * ```
112
113
  */
113
- executeStream(input: string | MessageContent[]): AsyncGenerator<StreamChunk>;
114
+ executeStream(input: string | MessageContent[], options?: ExecuteOptions): AsyncGenerator<StreamChunk>;
114
115
  private streamTurn;
115
116
  protected parseUsage(input: ResponseUsage): TokenUsage;
116
117
  }
@@ -8,6 +8,7 @@ exports.lowestReasoningEffort = lowestReasoningEffort;
8
8
  const openai_1 = __importDefault(require("openai"));
9
9
  const BaseAgent_1 = require("../BaseAgent");
10
10
  const AgentEvent_1 = require("../AgentEvent");
11
+ const cancellation_1 = require("../cancellation");
11
12
  const AgentError_1 = require("../errors/AgentError");
12
13
  const transformers_1 = require("../../history/transformers");
13
14
  const VizReporter_1 = require("../../viz/VizReporter");
@@ -163,7 +164,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
163
164
  async process(_input) {
164
165
  return "";
165
166
  }
166
- async execute(input) {
167
+ async execute(input, options) {
167
168
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
168
169
  // Reset token usage for this execution
169
170
  this.resetTokenUsage();
@@ -204,11 +205,19 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
204
205
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
205
206
  user: this.config.user,
206
207
  ...this.buildReasoningParams(),
207
- });
208
+ }, { signal: options?.signal });
208
209
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
209
- return await this.handleResponse(response);
210
+ return await this.handleResponse(response, options);
210
211
  }
211
212
  catch (error) {
213
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
214
+ const abortError = this.abortError(error, options?.signal);
215
+ if (this.vizEventId) {
216
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
217
+ this.vizEventId = undefined;
218
+ }
219
+ throw abortError;
220
+ }
212
221
  if (error && typeof error === "object" && "error" in error) {
213
222
  const openAIError = error;
214
223
  const apiError = new AgentError_1.ApiError(`OpenAI API error: ${openAIError.error.message || "Unknown error"}`, openAIError.status, openAIError.error);
@@ -239,7 +248,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
239
248
  this.history.endExecution();
240
249
  }
241
250
  }
242
- async handleResponse(response) {
251
+ async handleResponse(response, options) {
243
252
  if (!response.output || !response.output.length) {
244
253
  const error = new AgentError_1.ExecutionError("Invalid response format: missing output");
245
254
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
@@ -288,6 +297,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
288
297
  }
289
298
  else if (toolCalls.length) {
290
299
  try {
300
+ // Stop before the assistant turn is written: nothing else would notice
301
+ // a cancellation until the next provider call, and bailing out here
302
+ // avoids both running the tools' side effects and leaving a function
303
+ // call in history with no output to answer it.
304
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
291
305
  // Add assistant message with tool calls to history (normalized)
292
306
  const functionCalls = toolCalls.map((tc) => ({
293
307
  id: tc.id || tc.call_id,
@@ -297,7 +311,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
297
311
  }));
298
312
  const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls);
299
313
  this.addToHistory(assistantEntry);
300
- const toolResponses = await this.handleToolUse(toolCalls);
314
+ const toolResponses = await this.handleToolUse(toolCalls, options);
301
315
  // Add tool results to history (normalized)
302
316
  for (const result of toolResponses) {
303
317
  const resultEntry = transformers_1.openAiTransformer.toolResultEntry(result.call_id, result.output, false);
@@ -318,9 +332,9 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
318
332
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
319
333
  user: this.config.user,
320
334
  ...this.buildReasoningParams(),
321
- });
335
+ }, { signal: options?.signal });
322
336
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
323
- return this.handleResponse(newResponse);
337
+ return this.handleResponse(newResponse, options);
324
338
  }
325
339
  catch (error) {
326
340
  if (error && typeof error === "object" && "error" in error) {
@@ -358,7 +372,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
358
372
  throw error;
359
373
  }
360
374
  }
361
- async handleToolUse(content) {
375
+ async handleToolUse(content, options) {
362
376
  if (!content || !content.length) {
363
377
  throw new AgentError_1.ExecutionError("Invalid tool calls content");
364
378
  }
@@ -389,7 +403,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
389
403
  catch (parseError) {
390
404
  throw new AgentError_1.ToolExecutionError(`Invalid tool arguments: ${parseError instanceof Error ? parseError.message : "Parse error"}`, toolName, toolCall.arguments);
391
405
  }
392
- const result = await tool.execute(this.getId(), this.getName(), toolArgs, toolCall.id || "", this.config.model, "openai");
406
+ const result = await tool.execute(this.getId(), this.getName(), toolArgs, toolCall.id || "", this.config.model, "openai", { signal: options?.signal });
393
407
  return {
394
408
  call_id: toolCall.call_id,
395
409
  output: JSON.stringify(result),
@@ -422,7 +436,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
422
436
  * }
423
437
  * ```
424
438
  */
425
- async *executeStream(input) {
439
+ async *executeStream(input, options) {
426
440
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
427
441
  this.resetTokenUsage();
428
442
  this.currentToolCallCount = 0;
@@ -443,9 +457,17 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
443
457
  this.history.setSessionAnchor();
444
458
  this.history.beginExecution();
445
459
  try {
446
- yield* this.streamTurn();
460
+ yield* this.streamTurn(options);
447
461
  }
448
462
  catch (error) {
463
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
464
+ const abortError = this.abortError(error, options?.signal);
465
+ if (this.vizEventId) {
466
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
467
+ this.vizEventId = undefined;
468
+ }
469
+ throw abortError;
470
+ }
449
471
  if (error instanceof AgentError_1.AgentError) {
450
472
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
451
473
  if (this.vizEventId) {
@@ -476,7 +498,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
476
498
  this.history.endExecution();
477
499
  }
478
500
  }
479
- async *streamTurn() {
501
+ async *streamTurn(options) {
480
502
  const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
481
503
  this.startTurnTimer();
482
504
  const stream = await this.client.responses.create({
@@ -490,7 +512,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
490
512
  top_p: this.config.topP,
491
513
  user: this.config.user,
492
514
  ...this.buildReasoningParams("auto"),
493
- });
515
+ }, { signal: options?.signal });
494
516
  let completedEvent = null;
495
517
  for await (const event of stream) {
496
518
  if (event.type === "response.output_text.delta") {
@@ -513,12 +535,19 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
513
535
  throw new AgentError_1.MaxTokensExceededError("Response incomplete: max tokens reached", this.config.maxTokens || 1024);
514
536
  }
515
537
  }
538
+ // The SDK's stream iterator swallows the abort and simply stops yielding.
539
+ // Without this the turn would fail as a malformed stream instead of a
540
+ // cancellation — checked here so the tokens already spent are reported.
541
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
516
542
  if (!completedEvent) {
517
543
  throw new AgentError_1.ExecutionError("OpenAI stream ended without a completed event");
518
544
  }
519
545
  const response = completedEvent.response;
520
546
  const toolCalls = response.output.filter((o) => o.type === "function_call");
521
547
  if (toolCalls.length > 0) {
548
+ // As in handleResponse(): bail out before the assistant turn is written,
549
+ // so a cancelled run leaves no unanswered function call in history.
550
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
522
551
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
523
552
  this.currentToolCallCount += toolCalls.length;
524
553
  const functionCalls = toolCalls.map((tc) => ({
@@ -529,11 +558,11 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
529
558
  }));
530
559
  const assistantEntry = transformers_1.openAiTransformer.fromProviderMessage("assistant", response.output_text || "", functionCalls);
531
560
  this.addToHistory(assistantEntry);
532
- const toolResults = await this.handleToolUse(toolCalls);
561
+ const toolResults = await this.handleToolUse(toolCalls, options);
533
562
  for (const result of toolResults) {
534
563
  this.addToHistory(transformers_1.openAiTransformer.toolResultEntry(result.call_id, result.output, false));
535
564
  }
536
- yield* this.streamTurn();
565
+ yield* this.streamTurn(options);
537
566
  }
538
567
  else {
539
568
  const textContent = response.output_text || "";
@@ -3,6 +3,7 @@ import { ChatCompletion, ChatCompletionTool } from "openai/resources/chat/comple
3
3
  import { Model } from "openai/resources/models";
4
4
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
5
5
  import { AgentVendor } from "../AgentConfig";
6
+ import { ExecuteOptions } from "../cancellation";
6
7
  import { History, MessageContent } from "../../history/History";
7
8
  /**
8
9
  * A single chunk yielded by `executeStream()`.
@@ -50,9 +51,9 @@ export declare abstract class OpenAICompatibleAgent extends BaseAgent {
50
51
  listModels(): Promise<ModelInfo<Model>[]>;
51
52
  protected getToolDefinitions(): ChatCompletionTool[];
52
53
  protected process(_input: string): Promise<string>;
53
- execute(input: string | MessageContent[]): Promise<string>;
54
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
54
55
  private callProvider;
55
- protected handleResponse(response: ChatCompletion): Promise<string>;
56
+ protected handleResponse(response: ChatCompletion, options?: ExecuteOptions): Promise<string>;
56
57
  private handleToolCalls;
57
58
  /**
58
59
  * Stream a response as an async generator of `StreamChunk` objects.
@@ -69,7 +70,7 @@ export declare abstract class OpenAICompatibleAgent extends BaseAgent {
69
70
  * }
70
71
  * ```
71
72
  */
72
- executeStream(input: string | MessageContent[]): AsyncGenerator<StreamChunk>;
73
+ executeStream(input: string | MessageContent[], options?: ExecuteOptions): AsyncGenerator<StreamChunk>;
73
74
  private streamTurn;
74
75
  private accumulateStreamUsage;
75
76
  protected parseUsage(response: ChatCompletion): TokenUsage;