@tanstack/ai 0.6.3 → 0.8.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 (59) hide show
  1. package/dist/esm/activities/chat/index.d.ts +20 -0
  2. package/dist/esm/activities/chat/index.js +248 -213
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/middleware/compose.d.ts +66 -0
  5. package/dist/esm/activities/chat/middleware/compose.js +327 -0
  6. package/dist/esm/activities/chat/middleware/compose.js.map +1 -0
  7. package/dist/esm/activities/chat/middleware/index.d.ts +2 -0
  8. package/dist/esm/activities/chat/middleware/tool-cache-middleware.d.ts +89 -0
  9. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +76 -0
  10. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -0
  11. package/dist/esm/activities/chat/middleware/types.d.ts +307 -0
  12. package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -1
  13. package/dist/esm/activities/chat/tools/tool-calls.js +148 -64
  14. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  15. package/dist/esm/activities/generateImage/index.js +1 -1
  16. package/dist/esm/activities/generateImage/index.js.map +1 -1
  17. package/dist/esm/activities/generateSpeech/index.js +1 -1
  18. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  19. package/dist/esm/activities/generateTranscription/index.js +1 -1
  20. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  21. package/dist/esm/activities/generateVideo/index.js +1 -1
  22. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  23. package/dist/esm/activities/summarize/index.js +1 -1
  24. package/dist/esm/activities/summarize/index.js.map +1 -1
  25. package/dist/esm/index.d.ts +3 -1
  26. package/dist/esm/index.js +2 -2
  27. package/dist/esm/middlewares/content-guard.d.ts +77 -0
  28. package/dist/esm/middlewares/content-guard.js +155 -0
  29. package/dist/esm/middlewares/content-guard.js.map +1 -0
  30. package/dist/esm/middlewares/index.d.ts +2 -0
  31. package/dist/esm/middlewares/index.js +7 -0
  32. package/dist/esm/middlewares/index.js.map +1 -0
  33. package/dist/esm/middlewares/tool-cache.d.ts +1 -0
  34. package/dist/esm/realtime/index.d.ts +30 -0
  35. package/dist/esm/realtime/index.js +8 -0
  36. package/dist/esm/realtime/index.js.map +1 -0
  37. package/dist/esm/realtime/types.d.ts +234 -0
  38. package/package.json +6 -6
  39. package/src/activities/chat/index.ts +322 -256
  40. package/src/activities/chat/middleware/compose.ts +392 -0
  41. package/src/activities/chat/middleware/index.ts +17 -0
  42. package/src/activities/chat/middleware/tool-cache-middleware.ts +189 -0
  43. package/src/activities/chat/middleware/types.ts +419 -0
  44. package/src/activities/chat/tools/tool-calls.ts +225 -87
  45. package/src/activities/generateImage/index.ts +1 -1
  46. package/src/activities/generateSpeech/index.ts +1 -1
  47. package/src/activities/generateTranscription/index.ts +1 -1
  48. package/src/activities/generateVideo/index.ts +1 -1
  49. package/src/activities/summarize/index.ts +1 -1
  50. package/src/index.ts +41 -2
  51. package/src/middlewares/content-guard.ts +285 -0
  52. package/src/middlewares/index.ts +13 -0
  53. package/src/middlewares/tool-cache.ts +6 -0
  54. package/src/realtime/index.ts +38 -0
  55. package/src/realtime/types.ts +294 -0
  56. package/dist/esm/event-client.d.ts +0 -394
  57. package/dist/esm/event-client.js +0 -13
  58. package/dist/esm/event-client.js.map +0 -1
  59. package/src/event-client.ts +0 -497
@@ -0,0 +1,307 @@
1
+ import { ModelMessage, StreamChunk, Tool, ToolCall } from '../../../types.js';
2
+ /**
3
+ * Phase of the chat middleware lifecycle.
4
+ * - 'init': Initial config transform before the chat engine starts
5
+ * - 'beforeModel': Before each adapter chatStream call (per agent iteration)
6
+ * - 'modelStream': During model streaming
7
+ * - 'beforeTools': Before tool execution phase
8
+ * - 'afterTools': After tool execution phase
9
+ */
10
+ export type ChatMiddlewarePhase = 'init' | 'beforeModel' | 'modelStream' | 'beforeTools' | 'afterTools';
11
+ /**
12
+ * Stable context object passed to all middleware hooks.
13
+ * Created once per chat() invocation and shared across all hooks.
14
+ */
15
+ export interface ChatMiddlewareContext {
16
+ /** Unique identifier for this chat request */
17
+ requestId: string;
18
+ /** Unique identifier for this stream */
19
+ streamId: string;
20
+ /** Conversation identifier, if provided by the caller */
21
+ conversationId?: string;
22
+ /** Current lifecycle phase */
23
+ phase: ChatMiddlewarePhase;
24
+ /** Current agent loop iteration (0-indexed) */
25
+ iteration: number;
26
+ /** Running count of chunks yielded so far */
27
+ chunkIndex: number;
28
+ /** Abort signal from the chat request */
29
+ signal?: AbortSignal;
30
+ /** Abort the chat run with a reason */
31
+ abort: (reason?: string) => void;
32
+ /** Opaque user-provided value from chat() options */
33
+ context: unknown;
34
+ /**
35
+ * Defer a non-blocking side-effect promise.
36
+ * Deferred promises do not block streaming and are awaited
37
+ * after the terminal hook (onFinish/onAbort/onError).
38
+ */
39
+ defer: (promise: Promise<unknown>) => void;
40
+ /** Provider name (e.g., 'openai', 'anthropic') */
41
+ provider: string;
42
+ /** Model identifier (e.g., 'gpt-4o') */
43
+ model: string;
44
+ /** Source of the chat invocation — always 'server' for server-side chat */
45
+ source: 'client' | 'server';
46
+ /** Whether the chat is streaming */
47
+ streaming: boolean;
48
+ /** System prompts configured for this chat */
49
+ systemPrompts: Array<string>;
50
+ /** Names of configured tools, if any */
51
+ toolNames?: Array<string>;
52
+ /** Flattened generation options (temperature, topP, maxTokens, metadata) */
53
+ options?: Record<string, unknown>;
54
+ /** Provider-specific model options */
55
+ modelOptions?: Record<string, unknown>;
56
+ /** Number of messages at the start of the request */
57
+ messageCount: number;
58
+ /** Whether tools are configured */
59
+ hasTools: boolean;
60
+ /** Current assistant message ID (changes per iteration) */
61
+ currentMessageId: string | null;
62
+ /** Accumulated text content for the current iteration */
63
+ accumulatedContent: string;
64
+ /** Current messages array (read-only view) */
65
+ messages: ReadonlyArray<ModelMessage>;
66
+ /** Generate a unique ID with the given prefix */
67
+ createId: (prefix: string) => string;
68
+ }
69
+ /**
70
+ * Chat configuration that middleware can observe or transform.
71
+ * This is a subset of the chat engine's effective configuration
72
+ * that middleware is allowed to modify.
73
+ */
74
+ export interface ChatMiddlewareConfig {
75
+ messages: Array<ModelMessage>;
76
+ systemPrompts: Array<string>;
77
+ tools: Array<Tool>;
78
+ temperature?: number;
79
+ topP?: number;
80
+ maxTokens?: number;
81
+ metadata?: Record<string, unknown>;
82
+ modelOptions?: Record<string, unknown>;
83
+ }
84
+ /**
85
+ * Context provided to tool call hooks (onBeforeToolCall / onAfterToolCall).
86
+ */
87
+ export interface ToolCallHookContext {
88
+ /** The tool call being executed */
89
+ toolCall: ToolCall;
90
+ /** The resolved tool definition, if found */
91
+ tool: Tool | undefined;
92
+ /** Parsed arguments for the tool call */
93
+ args: unknown;
94
+ /** Name of the tool */
95
+ toolName: string;
96
+ /** ID of the tool call */
97
+ toolCallId: string;
98
+ }
99
+ /**
100
+ * Decision returned from onBeforeToolCall.
101
+ * - undefined/void: continue with normal execution
102
+ * - { type: 'transformArgs', args }: replace args used for execution
103
+ * - { type: 'skip', result }: skip execution, use provided result
104
+ * - { type: 'abort', reason }: abort the entire chat run
105
+ */
106
+ export type BeforeToolCallDecision = void | undefined | null | {
107
+ type: 'transformArgs';
108
+ args: unknown;
109
+ } | {
110
+ type: 'skip';
111
+ result: unknown;
112
+ } | {
113
+ type: 'abort';
114
+ reason?: string;
115
+ };
116
+ /**
117
+ * Outcome information provided to onAfterToolCall.
118
+ */
119
+ export interface AfterToolCallInfo {
120
+ /** The tool call that was executed */
121
+ toolCall: ToolCall;
122
+ /** The resolved tool definition */
123
+ tool: Tool | undefined;
124
+ /** Name of the tool */
125
+ toolName: string;
126
+ /** ID of the tool call */
127
+ toolCallId: string;
128
+ /** Whether the execution succeeded */
129
+ ok: boolean;
130
+ /** Duration of tool execution in milliseconds */
131
+ duration: number;
132
+ /** The result (if ok) or error (if not ok) */
133
+ result?: unknown;
134
+ error?: unknown;
135
+ }
136
+ /**
137
+ * Information passed to onIteration at the start of each agent loop iteration.
138
+ */
139
+ export interface IterationInfo {
140
+ /** 0-based iteration index */
141
+ iteration: number;
142
+ /** The assistant message ID created for this iteration */
143
+ messageId: string;
144
+ }
145
+ /**
146
+ * Aggregate information passed to onToolPhaseComplete after all tool calls
147
+ * in an iteration have been processed.
148
+ */
149
+ export interface ToolPhaseCompleteInfo {
150
+ /** Tool calls that were assigned to the assistant message */
151
+ toolCalls: Array<ToolCall>;
152
+ /** Completed tool results */
153
+ results: Array<{
154
+ toolCallId: string;
155
+ toolName: string;
156
+ result: unknown;
157
+ duration?: number;
158
+ }>;
159
+ /** Tools that need user approval */
160
+ needsApproval: Array<{
161
+ toolCallId: string;
162
+ toolName: string;
163
+ input: unknown;
164
+ approvalId: string;
165
+ }>;
166
+ /** Tools that need client-side execution */
167
+ needsClientExecution: Array<{
168
+ toolCallId: string;
169
+ toolName: string;
170
+ input: unknown;
171
+ }>;
172
+ }
173
+ /**
174
+ * Token usage statistics passed to the onUsage hook.
175
+ * Extracted from the RUN_FINISHED chunk when usage data is present.
176
+ */
177
+ export interface UsageInfo {
178
+ promptTokens: number;
179
+ completionTokens: number;
180
+ totalTokens: number;
181
+ }
182
+ /**
183
+ * Information passed to onFinish.
184
+ */
185
+ export interface FinishInfo {
186
+ /** The finish reason from the last model response */
187
+ finishReason: string | null;
188
+ /** Total duration of the chat run in milliseconds */
189
+ duration: number;
190
+ /** Final accumulated text content */
191
+ content: string;
192
+ /** Final usage totals, if available */
193
+ usage?: {
194
+ promptTokens: number;
195
+ completionTokens: number;
196
+ totalTokens: number;
197
+ };
198
+ }
199
+ /**
200
+ * Information passed to onAbort.
201
+ */
202
+ export interface AbortInfo {
203
+ /** The reason for the abort, if provided */
204
+ reason?: string;
205
+ /** Duration until abort in milliseconds */
206
+ duration: number;
207
+ }
208
+ /**
209
+ * Information passed to onError.
210
+ */
211
+ export interface ErrorInfo {
212
+ /** The error that caused the failure */
213
+ error: unknown;
214
+ /** Duration until error in milliseconds */
215
+ duration: number;
216
+ }
217
+ /**
218
+ * Chat middleware interface.
219
+ *
220
+ * All hooks are optional. Middleware is composed in array order:
221
+ * - `onConfig`: config piped through middlewares in order (first transform influences later)
222
+ * - `onChunk`: each output chunk is fed into the next middleware in order
223
+ *
224
+ * @example Logging middleware
225
+ * ```ts
226
+ * const loggingMiddleware: ChatMiddleware = {
227
+ * name: 'logging',
228
+ * onStart(ctx) { console.log('Chat started', ctx.requestId) },
229
+ * onChunk(ctx, chunk) { console.log('Chunk:', chunk.type) },
230
+ * onFinish(ctx, info) { console.log('Done:', info.duration, 'ms') },
231
+ * }
232
+ * ```
233
+ *
234
+ * @example Redaction middleware
235
+ * ```ts
236
+ * const redactionMiddleware: ChatMiddleware = {
237
+ * name: 'redaction',
238
+ * onChunk(ctx, chunk) {
239
+ * if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
240
+ * return { ...chunk, delta: redact(chunk.delta) }
241
+ * }
242
+ * },
243
+ * }
244
+ * ```
245
+ */
246
+ export interface ChatMiddleware {
247
+ /** Optional name for debugging and identification */
248
+ name?: string;
249
+ /**
250
+ * Called to observe or transform the chat configuration.
251
+ * Called at init and at the beginning of each agent iteration.
252
+ *
253
+ * Return a partial config to merge with the current config, or void to pass through.
254
+ * Only the fields you return are overwritten — everything else is preserved.
255
+ */
256
+ onConfig?: (ctx: ChatMiddlewareContext, config: ChatMiddlewareConfig) => void | null | Partial<ChatMiddlewareConfig> | Promise<void | Partial<ChatMiddlewareConfig>>;
257
+ /**
258
+ * Called when the chat run starts (after initial onConfig).
259
+ */
260
+ onStart?: (ctx: ChatMiddlewareContext) => void | Promise<void>;
261
+ /**
262
+ * Called at the start of each agent loop iteration, after a new assistant message ID
263
+ * is created. Use this to observe iteration boundaries.
264
+ */
265
+ onIteration?: (ctx: ChatMiddlewareContext, info: IterationInfo) => void | Promise<void>;
266
+ /**
267
+ * Called for every chunk yielded by chat().
268
+ * Can observe, transform, expand, or drop chunks.
269
+ *
270
+ * @returns void (pass through), chunk (replace), chunk[] (expand), null (drop)
271
+ */
272
+ onChunk?: (ctx: ChatMiddlewareContext, chunk: StreamChunk) => void | StreamChunk | Array<StreamChunk> | null | Promise<void | StreamChunk | Array<StreamChunk> | null>;
273
+ /**
274
+ * Called before a tool is executed.
275
+ * Can observe, transform args, skip execution, or abort the run.
276
+ */
277
+ onBeforeToolCall?: (ctx: ChatMiddlewareContext, hookCtx: ToolCallHookContext) => BeforeToolCallDecision | Promise<BeforeToolCallDecision>;
278
+ /**
279
+ * Called after a tool execution completes (success or failure).
280
+ */
281
+ onAfterToolCall?: (ctx: ChatMiddlewareContext, info: AfterToolCallInfo) => void | Promise<void>;
282
+ /**
283
+ * Called after all tool calls in an iteration have been processed.
284
+ * Provides aggregate data about tool execution results, approvals, and client tools.
285
+ */
286
+ onToolPhaseComplete?: (ctx: ChatMiddlewareContext, info: ToolPhaseCompleteInfo) => void | Promise<void>;
287
+ /**
288
+ * Called when usage data is available from a RUN_FINISHED chunk.
289
+ * Called once per model iteration that reports usage.
290
+ */
291
+ onUsage?: (ctx: ChatMiddlewareContext, usage: UsageInfo) => void | Promise<void>;
292
+ /**
293
+ * Called when the chat run completes normally.
294
+ * Exactly one of onFinish/onAbort/onError will be called per run.
295
+ */
296
+ onFinish?: (ctx: ChatMiddlewareContext, info: FinishInfo) => void | Promise<void>;
297
+ /**
298
+ * Called when the chat run is aborted.
299
+ * Exactly one of onFinish/onAbort/onError will be called per run.
300
+ */
301
+ onAbort?: (ctx: ChatMiddlewareContext, info: AbortInfo) => void | Promise<void>;
302
+ /**
303
+ * Called when the chat run encounters an unhandled error.
304
+ * Exactly one of onFinish/onAbort/onError will be called per run.
305
+ */
306
+ onError?: (ctx: ChatMiddlewareContext, info: ErrorInfo) => void | Promise<void>;
307
+ }
@@ -1,4 +1,19 @@
1
1
  import { CustomEvent, ModelMessage, RunFinishedEvent, Tool, ToolCall, ToolCallArgsEvent, ToolCallEndEvent, ToolCallStartEvent } from '../../../types.js';
2
+ import { AfterToolCallInfo, BeforeToolCallDecision } from '../middleware/types.js';
3
+ /**
4
+ * Optional middleware hooks for tool execution.
5
+ * When provided, these callbacks are invoked before/after each tool execution.
6
+ */
7
+ export interface ToolExecutionMiddlewareHooks {
8
+ onBeforeToolCall?: (toolCall: ToolCall, tool: Tool | undefined, args: unknown) => Promise<BeforeToolCallDecision>;
9
+ onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>;
10
+ }
11
+ /**
12
+ * Error thrown when middleware decides to abort the chat run during tool execution.
13
+ */
14
+ export declare class MiddlewareAbortError extends Error {
15
+ constructor(reason: string);
16
+ }
2
17
  /**
3
18
  * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.
4
19
  *
@@ -111,5 +126,5 @@ interface ExecuteToolCallsResult {
111
126
  * @param clientResults - Map of client-side execution results (toolCallId -> result)
112
127
  * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)
113
128
  */
114
- export declare function executeToolCalls(toolCalls: Array<ToolCall>, tools: ReadonlyArray<Tool>, approvals?: Map<string, boolean>, clientResults?: Map<string, any>, createCustomEventChunk?: (eventName: string, value: Record<string, any>) => CustomEvent): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void>;
129
+ export declare function executeToolCalls(toolCalls: Array<ToolCall>, tools: ReadonlyArray<Tool>, approvals?: Map<string, boolean>, clientResults?: Map<string, any>, createCustomEventChunk?: (eventName: string, value: Record<string, any>) => CustomEvent, middlewareHooks?: ToolExecutionMiddlewareHooks): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void>;
115
130
  export {};
@@ -1,4 +1,17 @@
1
1
  import { isStandardSchema, parseWithStandardSchema } from "./schema-converter.js";
2
+ function safeJsonParse(value) {
3
+ try {
4
+ return JSON.parse(value);
5
+ } catch {
6
+ return value;
7
+ }
8
+ }
9
+ class MiddlewareAbortError extends Error {
10
+ constructor(reason) {
11
+ super(reason);
12
+ this.name = "MiddlewareAbortError";
13
+ }
14
+ }
2
15
  class ToolCallManager {
3
16
  constructor(tools) {
4
17
  this.toolCallsMap = /* @__PURE__ */ new Map();
@@ -152,7 +165,100 @@ async function* executeWithEventPolling(executionPromise, pendingEvents) {
152
165
  }
153
166
  return state.result;
154
167
  }
155
- async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ new Map(), clientResults = /* @__PURE__ */ new Map(), createCustomEventChunk) {
168
+ async function applyBeforeToolCallDecision(toolCall, tool, input, toolName, middlewareHooks, results) {
169
+ if (!middlewareHooks.onBeforeToolCall) {
170
+ return { proceed: true, input };
171
+ }
172
+ const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input);
173
+ if (!decision) {
174
+ return { proceed: true, input };
175
+ }
176
+ if (decision.type === "abort") {
177
+ throw new MiddlewareAbortError(decision.reason || "Aborted by middleware");
178
+ }
179
+ if (decision.type === "skip") {
180
+ const skipResult = decision.result;
181
+ results.push({
182
+ toolCallId: toolCall.id,
183
+ toolName,
184
+ result: typeof skipResult === "string" ? safeJsonParse(skipResult) : skipResult || null,
185
+ duration: 0
186
+ });
187
+ if (middlewareHooks.onAfterToolCall) {
188
+ await middlewareHooks.onAfterToolCall({
189
+ toolCall,
190
+ tool,
191
+ toolName,
192
+ toolCallId: toolCall.id,
193
+ ok: true,
194
+ duration: 0,
195
+ result: skipResult
196
+ });
197
+ }
198
+ return { proceed: false };
199
+ }
200
+ return { proceed: true, input: decision.args };
201
+ }
202
+ async function* executeServerTool(toolCall, tool, toolName, input, context, pendingEvents, results, middlewareHooks) {
203
+ const startTime = Date.now();
204
+ try {
205
+ const executionPromise = Promise.resolve(tool.execute(input, context));
206
+ let result = yield* executeWithEventPolling(executionPromise, pendingEvents);
207
+ const duration = Date.now() - startTime;
208
+ while (pendingEvents.length > 0) {
209
+ yield pendingEvents.shift();
210
+ }
211
+ if (tool.outputSchema && isStandardSchema(tool.outputSchema) && result !== void 0 && result !== null) {
212
+ result = parseWithStandardSchema(tool.outputSchema, result);
213
+ }
214
+ const finalResult = typeof result === "string" ? safeJsonParse(result) : result || null;
215
+ results.push({
216
+ toolCallId: toolCall.id,
217
+ toolName,
218
+ result: finalResult,
219
+ duration
220
+ });
221
+ if (middlewareHooks?.onAfterToolCall) {
222
+ await middlewareHooks.onAfterToolCall({
223
+ toolCall,
224
+ tool,
225
+ toolName,
226
+ toolCallId: toolCall.id,
227
+ ok: true,
228
+ duration,
229
+ result: finalResult
230
+ });
231
+ }
232
+ } catch (error) {
233
+ const duration = Date.now() - startTime;
234
+ while (pendingEvents.length > 0) {
235
+ yield pendingEvents.shift();
236
+ }
237
+ if (error instanceof MiddlewareAbortError) {
238
+ throw error;
239
+ }
240
+ const message = error instanceof Error ? error.message : "Unknown error";
241
+ results.push({
242
+ toolCallId: toolCall.id,
243
+ toolName,
244
+ result: { error: message },
245
+ state: "output-error",
246
+ duration
247
+ });
248
+ if (middlewareHooks?.onAfterToolCall) {
249
+ await middlewareHooks.onAfterToolCall({
250
+ toolCall,
251
+ tool,
252
+ toolName,
253
+ toolCallId: toolCall.id,
254
+ ok: false,
255
+ duration,
256
+ error
257
+ });
258
+ }
259
+ }
260
+ }
261
+ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ new Map(), clientResults = /* @__PURE__ */ new Map(), createCustomEventChunk, middlewareHooks) {
156
262
  const results = [];
157
263
  const needsApproval = [];
158
264
  const needsClientExecution = [];
@@ -220,11 +326,6 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
220
326
  }
221
327
  }
222
328
  };
223
- function* flushEvents() {
224
- while (pendingEvents.length > 0) {
225
- yield pendingEvents.shift();
226
- }
227
- }
228
329
  if (!tool.execute) {
229
330
  if (tool.needsApproval) {
230
331
  const approvalId = `approval_${toolCall.id}`;
@@ -282,38 +383,28 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
282
383
  if (approvals.has(approvalId)) {
283
384
  const approved = approvals.get(approvalId);
284
385
  if (approved) {
285
- const startTime2 = Date.now();
286
- try {
287
- const executionPromise = Promise.resolve(
288
- tool.execute(input, context)
289
- );
290
- let result = yield* executeWithEventPolling(
291
- executionPromise,
292
- pendingEvents
293
- );
294
- const duration = Date.now() - startTime2;
295
- yield* flushEvents();
296
- if (tool.outputSchema && isStandardSchema(tool.outputSchema) && result !== void 0 && result !== null) {
297
- result = parseWithStandardSchema(tool.outputSchema, result);
298
- }
299
- results.push({
300
- toolCallId: toolCall.id,
301
- toolName,
302
- result: typeof result === "string" ? JSON.parse(result) : result || null,
303
- duration
304
- });
305
- } catch (error) {
306
- const duration = Date.now() - startTime2;
307
- yield* flushEvents();
308
- const message = error instanceof Error ? error.message : "Unknown error";
309
- results.push({
310
- toolCallId: toolCall.id,
386
+ if (middlewareHooks) {
387
+ const decision = await applyBeforeToolCallDecision(
388
+ toolCall,
389
+ tool,
390
+ input,
311
391
  toolName,
312
- result: { error: message },
313
- state: "output-error",
314
- duration
315
- });
392
+ middlewareHooks,
393
+ results
394
+ );
395
+ if (!decision.proceed) continue;
396
+ input = decision.input;
316
397
  }
398
+ yield* executeServerTool(
399
+ toolCall,
400
+ tool,
401
+ toolName,
402
+ input,
403
+ context,
404
+ pendingEvents,
405
+ results,
406
+ middlewareHooks
407
+ );
317
408
  } else {
318
409
  results.push({
319
410
  toolCallId: toolCall.id,
@@ -332,40 +423,33 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
332
423
  }
333
424
  continue;
334
425
  }
335
- const startTime = Date.now();
336
- try {
337
- const executionPromise = Promise.resolve(tool.execute(input, context));
338
- let result = yield* executeWithEventPolling(
339
- executionPromise,
340
- pendingEvents
341
- );
342
- const duration = Date.now() - startTime;
343
- yield* flushEvents();
344
- if (tool.outputSchema && isStandardSchema(tool.outputSchema) && result !== void 0 && result !== null) {
345
- result = parseWithStandardSchema(tool.outputSchema, result);
346
- }
347
- results.push({
348
- toolCallId: toolCall.id,
349
- toolName,
350
- result: typeof result === "string" ? JSON.parse(result) : result || null,
351
- duration
352
- });
353
- } catch (error) {
354
- const duration = Date.now() - startTime;
355
- yield* flushEvents();
356
- const message = error instanceof Error ? error.message : "Unknown error";
357
- results.push({
358
- toolCallId: toolCall.id,
426
+ if (middlewareHooks) {
427
+ const decision = await applyBeforeToolCallDecision(
428
+ toolCall,
429
+ tool,
430
+ input,
359
431
  toolName,
360
- result: { error: message },
361
- state: "output-error",
362
- duration
363
- });
432
+ middlewareHooks,
433
+ results
434
+ );
435
+ if (!decision.proceed) continue;
436
+ input = decision.input;
364
437
  }
438
+ yield* executeServerTool(
439
+ toolCall,
440
+ tool,
441
+ toolName,
442
+ input,
443
+ context,
444
+ pendingEvents,
445
+ results,
446
+ middlewareHooks
447
+ );
365
448
  }
366
449
  return { results, needsApproval, needsClientExecution };
367
450
  }
368
451
  export {
452
+ MiddlewareAbortError,
369
453
  ToolCallManager,
370
454
  executeToolCalls
371
455
  };