@agentionai/agents 1.6.0 → 1.8.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.
Files changed (41) hide show
  1. package/README.md +4 -2
  2. package/dist/agents/Agent.d.ts +9 -2
  3. package/dist/agents/Agent.js +4 -0
  4. package/dist/agents/AgentConfig.d.ts +76 -2
  5. package/dist/agents/BaseAgent.d.ts +24 -2
  6. package/dist/agents/BaseAgent.js +17 -0
  7. package/dist/agents/anthropic/ClaudeAgent.d.ts +4 -3
  8. package/dist/agents/anthropic/ClaudeAgent.js +47 -17
  9. package/dist/agents/cancellation.d.ts +55 -0
  10. package/dist/agents/cancellation.js +72 -0
  11. package/dist/agents/errors/AgentError.d.ts +50 -2
  12. package/dist/agents/errors/AgentError.js +57 -1
  13. package/dist/agents/google/GeminiAgent.d.ts +3 -2
  14. package/dist/agents/google/GeminiAgent.js +34 -11
  15. package/dist/agents/mistral/MistralAgent.d.ts +3 -2
  16. package/dist/agents/mistral/MistralAgent.js +33 -13
  17. package/dist/agents/ollama/OllamaAgent.d.ts +17 -3
  18. package/dist/agents/ollama/OllamaAgent.js +69 -19
  19. package/dist/agents/openai/OpenAiAgent.d.ts +4 -3
  20. package/dist/agents/openai/OpenAiAgent.js +52 -17
  21. package/dist/agents/openai-compatible/OpenAICompatibleAgent.d.ts +4 -3
  22. package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +48 -19
  23. package/dist/agents/openrouter/OpenRouterAgent.d.ts +234 -0
  24. package/dist/agents/openrouter/OpenRouterAgent.js +711 -0
  25. package/dist/agents/openrouter/types.d.ts +164 -0
  26. package/dist/agents/openrouter/types.js +15 -0
  27. package/dist/core.d.ts +1 -0
  28. package/dist/core.js +1 -0
  29. package/dist/history/transformers.d.ts +80 -0
  30. package/dist/history/transformers.js +156 -1
  31. package/dist/history/types.d.ts +22 -2
  32. package/dist/history/types.js +13 -2
  33. package/dist/index.d.ts +5 -1
  34. package/dist/index.js +5 -1
  35. package/dist/mcp/MCPClient.js +4 -2
  36. package/dist/openrouter.d.ts +6 -0
  37. package/dist/openrouter.js +24 -0
  38. package/dist/tools/Tool.d.ts +13 -3
  39. package/dist/tools/Tool.js +18 -4
  40. package/dist/viz/types.d.ts +1 -1
  41. package/package.json +10 -1
@@ -22,12 +22,44 @@ export declare class ApiError extends AgentError {
22
22
  response?: any | undefined;
23
23
  constructor(message: string, statusCode?: number | undefined, response?: any | undefined);
24
24
  }
25
+ /**
26
+ * Error thrown when the provider rate limits a request and the agent's retry
27
+ * policy did not manage to get past it.
28
+ *
29
+ * Extends {@link ApiError}, so existing `catch (e) { if (e instanceof ApiError) }`
30
+ * handling keeps working; the extra fields carry what the provider said about
31
+ * when to come back.
32
+ */
33
+ export declare class RateLimitError extends ApiError {
34
+ retryAfterMs?: number | undefined;
35
+ limit?: number | undefined;
36
+ remaining?: number | undefined;
37
+ resetAt?: Date | undefined;
38
+ /**
39
+ * @param message Error message
40
+ * @param retryAfterMs How long the provider asked you to wait, in
41
+ * milliseconds, from its `Retry-After` header. Undefined
42
+ * when it sent no hint.
43
+ * @param limit Requests permitted in the window (`X-RateLimit-Limit`).
44
+ * @param remaining Requests left in the window (`X-RateLimit-Remaining`).
45
+ * @param resetAt When the window resets (`X-RateLimit-Reset`).
46
+ * @param response The underlying provider error.
47
+ */
48
+ constructor(message: string, retryAfterMs?: number | undefined, limit?: number | undefined, remaining?: number | undefined, resetAt?: Date | undefined, response?: unknown);
49
+ }
25
50
  /**
26
51
  * Error thrown when maximum token limit is exceeded
27
52
  */
28
53
  export declare class MaxTokensExceededError extends AgentError {
29
- tokenLimit: number;
30
- constructor(message: string, tokenLimit: number);
54
+ tokenLimit?: number | undefined;
55
+ /**
56
+ * @param message Error message
57
+ * @param tokenLimit The configured `maxTokens` that was hit. Undefined when
58
+ * no limit was configured and the model's own output cap
59
+ * is what ended the response — reporting a number the
60
+ * caller never set would just be misleading.
61
+ */
62
+ constructor(message: string, tokenLimit?: number | undefined);
31
63
  }
32
64
  /**
33
65
  * Error thrown when maximum retries are exceeded
@@ -36,6 +68,22 @@ export declare class MaxRetriesExceededError extends AgentError {
36
68
  maxRetries: number;
37
69
  constructor(message: string, maxRetries: number);
38
70
  }
71
+ /**
72
+ * Error thrown when an execution is cancelled through the `AbortSignal` passed
73
+ * to `execute()` / `executeStream()`.
74
+ *
75
+ * `name` is `"AbortError"`, matching the platform convention, so callers that
76
+ * already check `err.name === "AbortError"` keep working.
77
+ */
78
+ export declare class AbortError extends AgentError {
79
+ reason?: unknown | undefined;
80
+ /**
81
+ * @param message Error message
82
+ * @param reason The signal's `reason`, or the underlying provider error when
83
+ * the signal did not carry one.
84
+ */
85
+ constructor(message: string, reason?: unknown | undefined);
86
+ }
39
87
  /**
40
88
  * Error thrown when tool execution fails
41
89
  */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ToolExecutionError = exports.MaxRetriesExceededError = exports.MaxTokensExceededError = exports.ApiError = exports.ExecutionError = exports.AgentError = void 0;
3
+ exports.ToolExecutionError = exports.AbortError = exports.MaxRetriesExceededError = exports.MaxTokensExceededError = exports.RateLimitError = exports.ApiError = exports.ExecutionError = exports.AgentError = void 0;
4
4
  /**
5
5
  * Base error class for all agent-related errors
6
6
  */
@@ -37,10 +37,46 @@ class ApiError extends AgentError {
37
37
  }
38
38
  }
39
39
  exports.ApiError = ApiError;
40
+ /**
41
+ * Error thrown when the provider rate limits a request and the agent's retry
42
+ * policy did not manage to get past it.
43
+ *
44
+ * Extends {@link ApiError}, so existing `catch (e) { if (e instanceof ApiError) }`
45
+ * handling keeps working; the extra fields carry what the provider said about
46
+ * when to come back.
47
+ */
48
+ class RateLimitError extends ApiError {
49
+ /**
50
+ * @param message Error message
51
+ * @param retryAfterMs How long the provider asked you to wait, in
52
+ * milliseconds, from its `Retry-After` header. Undefined
53
+ * when it sent no hint.
54
+ * @param limit Requests permitted in the window (`X-RateLimit-Limit`).
55
+ * @param remaining Requests left in the window (`X-RateLimit-Remaining`).
56
+ * @param resetAt When the window resets (`X-RateLimit-Reset`).
57
+ * @param response The underlying provider error.
58
+ */
59
+ constructor(message, retryAfterMs, limit, remaining, resetAt, response) {
60
+ super(message, 429, response);
61
+ this.retryAfterMs = retryAfterMs;
62
+ this.limit = limit;
63
+ this.remaining = remaining;
64
+ this.resetAt = resetAt;
65
+ this.name = "RateLimitError";
66
+ }
67
+ }
68
+ exports.RateLimitError = RateLimitError;
40
69
  /**
41
70
  * Error thrown when maximum token limit is exceeded
42
71
  */
43
72
  class MaxTokensExceededError extends AgentError {
73
+ /**
74
+ * @param message Error message
75
+ * @param tokenLimit The configured `maxTokens` that was hit. Undefined when
76
+ * no limit was configured and the model's own output cap
77
+ * is what ended the response — reporting a number the
78
+ * caller never set would just be misleading.
79
+ */
44
80
  constructor(message, tokenLimit) {
45
81
  super(message);
46
82
  this.tokenLimit = tokenLimit;
@@ -59,6 +95,26 @@ class MaxRetriesExceededError extends AgentError {
59
95
  }
60
96
  }
61
97
  exports.MaxRetriesExceededError = MaxRetriesExceededError;
98
+ /**
99
+ * Error thrown when an execution is cancelled through the `AbortSignal` passed
100
+ * to `execute()` / `executeStream()`.
101
+ *
102
+ * `name` is `"AbortError"`, matching the platform convention, so callers that
103
+ * already check `err.name === "AbortError"` keep working.
104
+ */
105
+ class AbortError extends AgentError {
106
+ /**
107
+ * @param message Error message
108
+ * @param reason The signal's `reason`, or the underlying provider error when
109
+ * the signal did not carry one.
110
+ */
111
+ constructor(message, reason) {
112
+ super(message);
113
+ this.reason = reason;
114
+ this.name = "AbortError";
115
+ }
116
+ }
117
+ exports.AbortError = AbortError;
62
118
  /**
63
119
  * Error thrown when tool execution fails
64
120
  */
@@ -1,5 +1,6 @@
1
1
  import { FunctionDeclarationsTool, GenerateContentResult, Schema } from "@google/generative-ai";
2
2
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
3
+ import { ExecuteOptions } from "../cancellation";
3
4
  import { History, MessageContent } from "../../history/History";
4
5
  import { GeminiModel } from "../model-types";
5
6
  /**
@@ -123,8 +124,8 @@ export declare class GeminiAgent extends BaseAgent {
123
124
  */
124
125
  private mapJsonSchemaTypeToGemini;
125
126
  protected process(_input: string): Promise<string>;
126
- execute(input: string | MessageContent[]): Promise<string>;
127
- protected handleResponse(response: GenerateContentResult): Promise<string>;
127
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
128
+ protected handleResponse(response: GenerateContentResult, options?: ExecuteOptions): Promise<string>;
128
129
  private handleFunctionCalls;
129
130
  protected parseUsage(input: {
130
131
  promptTokenCount?: number;
@@ -4,6 +4,7 @@ exports.GeminiAgent = exports.GEMINI_RETIRED_MODELS = void 0;
4
4
  const generative_ai_1 = require("@google/generative-ai");
5
5
  const BaseAgent_1 = require("../BaseAgent");
6
6
  const AgentEvent_1 = require("../AgentEvent");
7
+ const cancellation_1 = require("../cancellation");
7
8
  const AgentError_1 = require("../errors/AgentError");
8
9
  const transformers_1 = require("../../history/transformers");
9
10
  const VizReporter_1 = require("../../viz/VizReporter");
@@ -66,7 +67,12 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
66
67
  const responseSchema = config.responseSchema ?? vendorConfig.responseSchema;
67
68
  this.config = {
68
69
  model: config.model || "gemini-flash-latest",
69
- maxTokens: config.maxTokens || 1024,
70
+ // No default: `maxOutputTokens` is optional on Gemini, and omitting it
71
+ // lets the model use its full output budget. A default here silently
72
+ // truncated every response — and on thinking models it was worse, since
73
+ // thoughts count against the same budget and a small cap could be spent
74
+ // entirely on them.
75
+ maxTokens: config.maxTokens,
70
76
  apiKey: config.apiKey,
71
77
  temperature: config.temperature,
72
78
  topP: config.topP,
@@ -251,7 +257,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
251
257
  async process(_input) {
252
258
  return "";
253
259
  }
254
- async execute(input) {
260
+ async execute(input, options) {
255
261
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
256
262
  // Reset token usage for this execution
257
263
  this.resetTokenUsage();
@@ -297,11 +303,23 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
297
303
  responseMimeType: this.config.responseMimeType,
298
304
  responseSchema: this.config.responseSchema,
299
305
  },
300
- });
306
+ },
307
+ // Note: an abort only stops the client from waiting — Google still
308
+ // runs and bills the request. Merged over the model's own request
309
+ // options, so `customHeaders` set at construction survive.
310
+ { signal: options?.signal });
301
311
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
302
- return await this.handleResponse(response);
312
+ return await this.handleResponse(response, options);
303
313
  }
304
314
  catch (error) {
315
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
316
+ const abortError = this.abortError(error, options?.signal);
317
+ if (this.vizEventId) {
318
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
319
+ this.vizEventId = undefined;
320
+ }
321
+ throw abortError;
322
+ }
305
323
  const err = error;
306
324
  if (err.status) {
307
325
  const apiError = new AgentError_1.ApiError(`Gemini API error: ${err.message || "Unknown error"}`, err.status, error);
@@ -328,7 +346,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
328
346
  this.history.endExecution();
329
347
  }
330
348
  }
331
- async handleResponse(response) {
349
+ async handleResponse(response, options) {
332
350
  const result = response.response;
333
351
  // Parse and track usage
334
352
  if (result.usageMetadata) {
@@ -342,7 +360,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
342
360
  throw error;
343
361
  }
344
362
  if (candidate.finishReason === "MAX_TOKENS") {
345
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
363
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
346
364
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
347
365
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
348
366
  // Report error to viz
@@ -377,11 +395,16 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
377
395
  }
378
396
  // Handle function calls
379
397
  try {
398
+ // Stop before the assistant turn is written: nothing else would notice a
399
+ // cancellation until the next provider call, and bailing out here avoids
400
+ // both running the tools' side effects and leaving a functionCall in
401
+ // history with no functionResponse to answer it.
402
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
380
403
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, functionCalls);
381
404
  // Add assistant response with function calls to history (normalized)
382
405
  const assistantEntry = transformers_1.geminiTransformer.fromProviderContent("assistant", parts);
383
406
  this.addToHistory(assistantEntry);
384
- const toolResults = await this.handleFunctionCalls(functionCalls);
407
+ const toolResults = await this.handleFunctionCalls(functionCalls, options);
385
408
  // Add tool results to history (normalized)
386
409
  for (const tr of toolResults) {
387
410
  const resultEntry = transformers_1.geminiTransformer.toolResultEntry(tr.name, tr.response);
@@ -407,9 +430,9 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
407
430
  responseMimeType: this.config.responseMimeType,
408
431
  responseSchema: this.config.responseSchema,
409
432
  },
410
- });
433
+ }, { signal: options?.signal });
411
434
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
412
- return this.handleResponse(newResponse);
435
+ return this.handleResponse(newResponse, options);
413
436
  }
414
437
  catch (error) {
415
438
  const err = error;
@@ -438,7 +461,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
438
461
  throw toolError;
439
462
  }
440
463
  }
441
- async handleFunctionCalls(functionCalls) {
464
+ async handleFunctionCalls(functionCalls, options) {
442
465
  if (!functionCalls.length) {
443
466
  throw new AgentError_1.ExecutionError("No function calls found in response");
444
467
  }
@@ -462,7 +485,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
462
485
  try {
463
486
  const args = (fc.args || {});
464
487
  const result = await tool.execute(this.getId(), this.getName(), args, toolName, // Gemini uses function name as ID
465
- this.config.model, "gemini");
488
+ this.config.model, "gemini", { signal: options?.signal });
466
489
  return {
467
490
  name: toolName,
468
491
  response: JSON.stringify(result),
@@ -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 { ChatCompletionResponse, ModelList, Tool, UsageInfo } from "@mistralai/mistralai/models/components";
4
5
  import { MistralModel } from "../model-types";
@@ -66,8 +67,8 @@ export declare class MistralAgent extends BaseAgent {
66
67
  listModels(): Promise<ModelInfo<MistralModelCard>[]>;
67
68
  protected getToolDefinitions(): Tool[];
68
69
  protected process(_input: string): Promise<string>;
69
- execute(input: string | MessageContent[]): Promise<string>;
70
- protected handleResponse(response: ChatCompletionResponse): Promise<string>;
70
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
71
+ protected handleResponse(response: ChatCompletionResponse, options?: ExecuteOptions): Promise<string>;
71
72
  private handleToolCalls;
72
73
  protected parseUsage(input: UsageInfo): TokenUsage;
73
74
  }
@@ -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");
@@ -67,7 +68,10 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
67
68
  const rateLimitDelay = config.rateLimitDelay ?? vendorConfig.rateLimitDelay ?? 1500;
68
69
  this.config = {
69
70
  model: config.model || "mistral-small-latest",
70
- maxTokens: config.maxTokens || 1024,
71
+ // No default: `maxTokens` is optional on Mistral's API, and omitting it
72
+ // lets the model use its full output budget. A default here silently
73
+ // truncated every response.
74
+ maxTokens: config.maxTokens,
71
75
  disableParallelToolUse,
72
76
  safePrompt,
73
77
  randomSeed,
@@ -129,7 +133,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
129
133
  async process(_input) {
130
134
  return "";
131
135
  }
132
- async execute(input) {
136
+ async execute(input, options) {
133
137
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
134
138
  // Reset token usage for this execution
135
139
  this.resetTokenUsage();
@@ -169,11 +173,19 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
169
173
  randomSeed: this.config.randomSeed,
170
174
  safePrompt: this.config.safePrompt,
171
175
  stop: this.config.stopSequences,
172
- });
176
+ }, { signal: options?.signal });
173
177
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
174
- return await this.handleResponse(response);
178
+ return await this.handleResponse(response, options);
175
179
  }
176
180
  catch (error) {
181
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
182
+ const abortError = this.abortError(error, options?.signal);
183
+ if (this.vizEventId) {
184
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
185
+ this.vizEventId = undefined;
186
+ }
187
+ throw abortError;
188
+ }
177
189
  const err = error;
178
190
  if (err.status) {
179
191
  const apiError = new AgentError_1.ApiError(`Mistral API error: ${err.message || "Unknown error"}`, err.status, error);
@@ -200,7 +212,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
200
212
  this.history.endExecution();
201
213
  }
202
214
  }
203
- async handleResponse(response) {
215
+ async handleResponse(response, options) {
204
216
  if (!response.choices || response.choices.length === 0) {
205
217
  const error = new AgentError_1.ExecutionError("Empty response from Mistral API");
206
218
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
@@ -210,7 +222,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
210
222
  // Track token usage
211
223
  const usage = this.accumulateUsage(this.parseUsage(response.usage));
212
224
  if (choice.finishReason === "length") {
213
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
225
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
214
226
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
215
227
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
216
228
  // Report error to viz
@@ -255,18 +267,26 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
255
267
  }
256
268
  else if (choice.finishReason === "tool_calls" || message.toolCalls) {
257
269
  try {
270
+ // Stop before the assistant turn is written: nothing else would notice
271
+ // a cancellation until the next provider call, and bailing out here
272
+ // avoids both running the tools' side effects and leaving a tool call
273
+ // in history with no tool message to answer it.
274
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
258
275
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, message.toolCalls);
259
276
  // Add assistant message with tool calls to history (normalized)
260
277
  const assistantEntry = transformers_1.mistralTransformer.fromProviderMessage(message);
261
278
  this.addToHistory(assistantEntry);
262
- const toolResults = await this.handleToolCalls(message.toolCalls || []);
279
+ const toolResults = await this.handleToolCalls(message.toolCalls || [], options);
263
280
  // Add tool results to history (normalized)
264
281
  for (const result of toolResults) {
265
282
  const resultEntry = transformers_1.mistralTransformer.toolResultEntry(result.toolCallId, result.name, result.content);
266
283
  this.addToHistory(resultEntry);
267
284
  }
268
- // Rate limiting delay for Mistral
269
- await (0, promises_1.setTimeout)(this.config.rateLimitDelay || 1500);
285
+ // Rate limiting delay for Mistral. Aborting during the wait rejects
286
+ // immediately rather than sitting out the full delay first.
287
+ await (0, promises_1.setTimeout)(this.config.rateLimitDelay || 1500, undefined, {
288
+ signal: options?.signal,
289
+ });
270
290
  // Continue conversation
271
291
  try {
272
292
  const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
@@ -281,9 +301,9 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
281
301
  randomSeed: this.config.randomSeed,
282
302
  safePrompt: this.config.safePrompt,
283
303
  stop: this.config.stopSequences,
284
- });
304
+ }, { signal: options?.signal });
285
305
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
286
- return this.handleResponse(newResponse);
306
+ return this.handleResponse(newResponse, options);
287
307
  }
288
308
  catch (error) {
289
309
  const err = error;
@@ -316,7 +336,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
316
336
  }
317
337
  throw error;
318
338
  }
319
- async handleToolCalls(toolCalls) {
339
+ async handleToolCalls(toolCalls, options) {
320
340
  if (!toolCalls.length) {
321
341
  throw new AgentError_1.ExecutionError("No tool calls found in response");
322
342
  }
@@ -346,7 +366,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
346
366
  else {
347
367
  args = toolCall.function.arguments;
348
368
  }
349
- const result = await tool.execute(this.getId(), this.getName(), args, toolCallId, this.config.model, "mistral");
369
+ const result = await tool.execute(this.getId(), this.getName(), args, toolCallId, this.config.model, "mistral", { signal: options?.signal });
350
370
  return {
351
371
  name: toolName,
352
372
  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