veryfront 0.1.419 → 0.1.421

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.
@@ -0,0 +1,501 @@
1
+ import {
2
+ buildChatStreamChunkMessageMetadata,
3
+ extractChatMessageMetadata,
4
+ } from "../chat/chat-ui-message-helpers.js";
5
+ import { getLastStreamStep } from "../chat/final-step-fallback.js";
6
+ import type { ChatUiMessage, ChatUiMessageChunk, MessageMetadata } from "../chat/types.js";
7
+ import {
8
+ buildDetachedFallbackChunks,
9
+ buildDetachedFallbackMessageState,
10
+ buildFinalizedMessageFallbackChunks,
11
+ buildFinalizedMessageState,
12
+ } from "./hosted-finalized-message.js";
13
+ import type {
14
+ HostedChatRuntimeStreamResult,
15
+ HostedChatRuntimeToUiMessageStreamOptions,
16
+ } from "./hosted-chat-runtime-contract.js";
17
+ import type { HostedLifecycleTerminalState } from "./hosted-lifecycle.js";
18
+ import {
19
+ type ConversationHostedTerminalRuntimeAdapter,
20
+ type ConversationHostedTerminalStateInput,
21
+ dispatchConversationHostedStreamErrorState,
22
+ dispatchConversationHostedTerminalState,
23
+ resolveConversationHostedTerminalState,
24
+ toConversationHostedTerminalState,
25
+ } from "./conversation-hosted-terminal.js";
26
+ import {
27
+ createHostedMirroredUiStream,
28
+ type MirroredToolChunkState,
29
+ } from "./mirrored-tool-chunk-state.js";
30
+ import {
31
+ finalizeHostedDetached,
32
+ finalizeHostedResponse,
33
+ type FinalizeHostedResponseOptions,
34
+ type HostedDetachedFinalizationState,
35
+ type HostedResponseFinalizationState,
36
+ } from "./hosted-stream-finalization.js";
37
+ import {
38
+ getEmptyHostedFinalizedMessageTerminalError,
39
+ getHostedStreamErrorText,
40
+ shouldFailEmptyHostedFinalizedMessage,
41
+ } from "./hosted-stream-terminal-error.js";
42
+ import type { ConversationRunChunkMirror } from "./conversation-run-chunk-mirror.js";
43
+ import type { BuildChatStreamChunkMessageMetadataInput } from "../chat/chat-ui-message-helpers.js";
44
+ import type { createChatStreamWatchdog } from "../chat/stream-watchdog.js";
45
+
46
+ const INCOMPLETE_TOOL_CALLS_PART_ERROR_TEXT = "Assistant ended before tool execution completed";
47
+
48
+ const FINALIZATION_TERMINAL_STATE_FALLBACK_MODEL_ID = "";
49
+
50
+ export interface HostedChatExecutionRuntime {
51
+ agentUIStream: AsyncIterable<ChatUiMessageChunk<MessageMetadata>>;
52
+ fail: (error: unknown) => Promise<void>;
53
+ waitForFinish: () => Promise<void>;
54
+ }
55
+
56
+ export interface HostedChatExecutionRuntimeLogger {
57
+ error: (message: string, metadata?: Record<string, unknown>) => void;
58
+ warn: (message: string, metadata?: Record<string, unknown>) => void;
59
+ }
60
+
61
+ export interface HostedChatExecutionRunContext {
62
+ withContext: <T>(fn: () => T) => T;
63
+ setMessageId?: (messageId: string) => void;
64
+ }
65
+
66
+ export interface HostedChatExecutionLifecycleAdapter
67
+ extends ConversationHostedTerminalRuntimeAdapter {
68
+ durableRootRun: {
69
+ runId: string;
70
+ messageId?: string | null;
71
+ } | null;
72
+ durableRunMirror: ConversationRunChunkMirror | null;
73
+ }
74
+
75
+ export type HostedChatExecutionRootStreamWatchdog = ReturnType<typeof createChatStreamWatchdog>;
76
+
77
+ export interface HostedChatExecutionRuntimeBootstrap {
78
+ cleanup: () => Promise<void>;
79
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
80
+ rootStreamWatchdog: HostedChatExecutionRootStreamWatchdog;
81
+ streamResult: HostedChatRuntimeStreamResult;
82
+ streamingMessageId: string | null;
83
+ capturedMessageId: string | null;
84
+ capturedConversationId?: string;
85
+ mirroredToolChunkState: MirroredToolChunkState;
86
+ }
87
+
88
+ export interface CreateHostedChatExecutionRuntimeInput {
89
+ agentId: string;
90
+ modelId: string;
91
+ originalMessages: ChatUiMessage[];
92
+ responseMessageId?: string;
93
+ runContext: HostedChatExecutionRunContext;
94
+ abortSignal: AbortSignal;
95
+ bootstrap: HostedChatExecutionRuntimeBootstrap;
96
+ logger?: HostedChatExecutionRuntimeLogger;
97
+ incompleteToolCallsPartErrorText?: string;
98
+ }
99
+
100
+ type SharedFinalizationHooks = Pick<
101
+ FinalizeHostedResponseOptions<ChatUiMessage, ChatUiMessageChunk<MessageMetadata>>,
102
+ | "resolveEmptyTerminalError"
103
+ | "appendFallbackChunk"
104
+ | "flushMirror"
105
+ | "dispatchTerminalState"
106
+ | "resolveTerminalState"
107
+ | "cleanup"
108
+ | "streamError"
109
+ >;
110
+
111
+ export function toHostedChatExecutionFinalState(
112
+ input: ConversationHostedTerminalStateInput,
113
+ ): HostedLifecycleTerminalState {
114
+ return toConversationHostedTerminalState({
115
+ state: input,
116
+ fallbackModelId: FINALIZATION_TERMINAL_STATE_FALLBACK_MODEL_ID,
117
+ });
118
+ }
119
+
120
+ export async function cleanupAfterHostedChatExecutionFinalization(input: {
121
+ cleanup: () => Promise<void>;
122
+ logger?: HostedChatExecutionRuntimeLogger;
123
+ }): Promise<void> {
124
+ await input.cleanup().catch((cleanupError: unknown) => {
125
+ input.logger?.error("Runtime cleanup failed during finalization", {
126
+ error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
127
+ });
128
+ });
129
+ }
130
+
131
+ export function createHostedChatStreamFinalizationHooks(input: {
132
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
133
+ cleanup: () => Promise<void>;
134
+ streamError: unknown;
135
+ logger?: HostedChatExecutionRuntimeLogger;
136
+ }): SharedFinalizationHooks {
137
+ return {
138
+ resolveEmptyTerminalError: (
139
+ { finalStep, streamError }: { finalStep: unknown; streamError?: unknown | null },
140
+ ) => getEmptyHostedFinalizedMessageTerminalError({ finalStep, streamError }),
141
+ appendFallbackChunk: (chunk: ChatUiMessageChunk<MessageMetadata>) =>
142
+ input.lifecycleAdapter.durableRunMirror?.handleChunk(chunk),
143
+ flushMirror: () => input.lifecycleAdapter.durableRunMirror?.flush(),
144
+ dispatchTerminalState: async (terminalState) => {
145
+ await dispatchConversationHostedTerminalState(input.lifecycleAdapter, terminalState);
146
+ },
147
+ resolveTerminalState: ({ isAborted, hasIncompleteToolParts }: {
148
+ isAborted: boolean;
149
+ hasIncompleteToolParts: boolean;
150
+ }) =>
151
+ toHostedChatExecutionFinalState(
152
+ resolveConversationHostedTerminalState({ isAborted, hasIncompleteToolParts }),
153
+ ),
154
+ cleanup: () =>
155
+ cleanupAfterHostedChatExecutionFinalization({
156
+ cleanup: input.cleanup,
157
+ logger: input.logger,
158
+ }),
159
+ streamError: input.streamError,
160
+ };
161
+ }
162
+
163
+ export function createHostedChatFinalizeResponseBuildState(input: {
164
+ responseMessage: ChatUiMessage;
165
+ isAborted: boolean;
166
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
167
+ mirroredToolChunkState: MirroredToolChunkState;
168
+ capturedMessageId: string | null;
169
+ incompleteToolCallsPartErrorText: string;
170
+ }): (
171
+ finalStep: unknown,
172
+ ) => Promise<HostedResponseFinalizationState<ChatUiMessage, ChatUiMessageChunk<MessageMetadata>>> {
173
+ return async (finalStep) => {
174
+ const { persistedMessage, sanitizedFinalizedMessage, hasIncompleteFinalizedToolParts } =
175
+ buildFinalizedMessageState({
176
+ responseMessage: input.responseMessage,
177
+ isAborted: input.isAborted,
178
+ finalStep,
179
+ incompleteToolCallsPartErrorText: input.incompleteToolCallsPartErrorText,
180
+ });
181
+
182
+ return {
183
+ persistedMessage,
184
+ finalizedMessage: sanitizedFinalizedMessage,
185
+ fallbackChunks:
186
+ sanitizedFinalizedMessage.parts.length > 0 && input.lifecycleAdapter.durableRunMirror
187
+ ? buildFinalizedMessageFallbackChunks({
188
+ persistedMessage,
189
+ sanitizedFinalizedMessage,
190
+ finalStep,
191
+ mirroredToolChunkState: input.mirroredToolChunkState,
192
+ capturedMessageId: input.capturedMessageId,
193
+ hasIncompleteFinalizedToolParts,
194
+ })
195
+ : [],
196
+ hasIncompleteToolParts: hasIncompleteFinalizedToolParts,
197
+ metadata: extractChatMessageMetadata(sanitizedFinalizedMessage.metadata),
198
+ };
199
+ };
200
+ }
201
+
202
+ export function createHostedChatFinalizeDetachedBuildState(input: {
203
+ capturedMessageId: string | null;
204
+ isAborted: boolean;
205
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
206
+ mirroredToolChunkState: MirroredToolChunkState;
207
+ mirroredDurableOutput: boolean;
208
+ incompleteToolCallsPartErrorText: string;
209
+ }): (
210
+ finalStep: unknown,
211
+ ) => Promise<HostedDetachedFinalizationState<ChatUiMessageChunk<MessageMetadata>>> {
212
+ return async (finalStep) => {
213
+ const { finalizedFallbackMessage, hasIncompleteFallbackToolParts } =
214
+ buildDetachedFallbackMessageState({
215
+ capturedMessageId: input.capturedMessageId,
216
+ finalStep,
217
+ isAborted: input.isAborted,
218
+ incompleteToolCallsPartErrorText: input.incompleteToolCallsPartErrorText,
219
+ });
220
+ const fallbackParts = finalizedFallbackMessage.parts;
221
+
222
+ return {
223
+ hasContent: fallbackParts.length > 0,
224
+ fallbackChunks: fallbackParts.length > 0 && input.lifecycleAdapter.durableRunMirror &&
225
+ input.capturedMessageId
226
+ ? buildDetachedFallbackChunks({
227
+ fallbackParts,
228
+ finalStep,
229
+ mirroredToolChunkState: input.mirroredToolChunkState,
230
+ mirroredDurableOutput: input.mirroredDurableOutput,
231
+ capturedMessageId: input.capturedMessageId,
232
+ hasIncompleteFallbackToolParts,
233
+ })
234
+ : [],
235
+ hasIncompleteToolParts: hasIncompleteFallbackToolParts,
236
+ };
237
+ };
238
+ }
239
+
240
+ async function finalizeExecutionFailure(input: {
241
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
242
+ error: unknown;
243
+ conversationId?: string;
244
+ runId?: string;
245
+ logMessage: string;
246
+ logger?: HostedChatExecutionRuntimeLogger;
247
+ }): Promise<void> {
248
+ await dispatchConversationHostedStreamErrorState(input.lifecycleAdapter, input.error).catch(
249
+ (finalizeError) => {
250
+ input.logger?.error(input.logMessage, {
251
+ conversationId: input.conversationId,
252
+ runId: input.runId,
253
+ error: finalizeError instanceof Error ? finalizeError.message : String(finalizeError),
254
+ });
255
+ },
256
+ );
257
+ }
258
+
259
+ function createStreamMessageMetadataBuilder(input: {
260
+ agentId: string;
261
+ modelId: string;
262
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
263
+ streamingMessageId: string | null;
264
+ }): HostedChatRuntimeToUiMessageStreamOptions["messageMetadata"] {
265
+ return ({ part }) =>
266
+ buildChatStreamChunkMessageMetadata(
267
+ {
268
+ agentId: input.agentId,
269
+ modelId: input.modelId,
270
+ ...(input.lifecycleAdapter.durableRootRun
271
+ ? { runId: input.lifecycleAdapter.durableRootRun.runId }
272
+ : {}),
273
+ ...(input.streamingMessageId ? { streamingMessageId: input.streamingMessageId } : {}),
274
+ part: {
275
+ type: part.type,
276
+ ...("totalUsage" in part ? { totalUsage: part.totalUsage } : {}),
277
+ },
278
+ } satisfies BuildChatStreamChunkMessageMetadataInput,
279
+ );
280
+ }
281
+
282
+ function logCleanupError(input: {
283
+ error: unknown;
284
+ logger?: HostedChatExecutionRuntimeLogger;
285
+ }): void {
286
+ input.logger?.error("Runtime cleanup failed", {
287
+ error: input.error instanceof Error ? input.error.message : String(input.error),
288
+ });
289
+ }
290
+
291
+ async function finalizeResponseFinish(input: {
292
+ responseMessage: ChatUiMessage;
293
+ isAborted: boolean;
294
+ streamResult: { steps: PromiseLike<readonly unknown[]> };
295
+ lastStreamError: unknown;
296
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
297
+ mirroredToolChunkState: MirroredToolChunkState;
298
+ capturedMessageId: string | null;
299
+ incompleteToolCallsPartErrorText: string;
300
+ cleanup: () => Promise<void>;
301
+ logger?: HostedChatExecutionRuntimeLogger;
302
+ }): Promise<void> {
303
+ const hooks = createHostedChatStreamFinalizationHooks({
304
+ lifecycleAdapter: input.lifecycleAdapter,
305
+ cleanup: input.cleanup,
306
+ streamError: input.lastStreamError,
307
+ logger: input.logger,
308
+ });
309
+ const buildState = createHostedChatFinalizeResponseBuildState({
310
+ responseMessage: input.responseMessage,
311
+ isAborted: input.isAborted,
312
+ lifecycleAdapter: input.lifecycleAdapter,
313
+ mirroredToolChunkState: input.mirroredToolChunkState,
314
+ capturedMessageId: input.capturedMessageId,
315
+ incompleteToolCallsPartErrorText: input.incompleteToolCallsPartErrorText,
316
+ });
317
+
318
+ await finalizeHostedResponse({
319
+ isAborted: input.isAborted,
320
+ getFinalStep: () => getLastStreamStep(input.streamResult),
321
+ buildState,
322
+ shouldFailEmptyMessage: ({ isAborted, message }) =>
323
+ shouldFailEmptyHostedFinalizedMessage({ isAborted, message }),
324
+ ...hooks,
325
+ });
326
+ }
327
+
328
+ async function finalizeDetachedStreamEnd(input: {
329
+ capturedMessageId: string | null;
330
+ streamResult: { steps: PromiseLike<readonly unknown[]> };
331
+ isAborted: boolean;
332
+ lastStreamError: unknown;
333
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
334
+ mirroredToolChunkState: MirroredToolChunkState;
335
+ mirroredDurableOutput: boolean;
336
+ incompleteToolCallsPartErrorText: string;
337
+ cleanup: () => Promise<void>;
338
+ logger?: HostedChatExecutionRuntimeLogger;
339
+ }): Promise<void> {
340
+ const hooks = createHostedChatStreamFinalizationHooks({
341
+ lifecycleAdapter: input.lifecycleAdapter,
342
+ cleanup: input.cleanup,
343
+ streamError: input.lastStreamError,
344
+ logger: input.logger,
345
+ });
346
+ const buildState = createHostedChatFinalizeDetachedBuildState({
347
+ capturedMessageId: input.capturedMessageId,
348
+ isAborted: input.isAborted,
349
+ lifecycleAdapter: input.lifecycleAdapter,
350
+ mirroredToolChunkState: input.mirroredToolChunkState,
351
+ mirroredDurableOutput: input.mirroredDurableOutput,
352
+ incompleteToolCallsPartErrorText: input.incompleteToolCallsPartErrorText,
353
+ });
354
+
355
+ await finalizeHostedDetached({
356
+ isAborted: input.isAborted,
357
+ mirroredDurableOutput: input.mirroredDurableOutput,
358
+ getFinalStep: () => getLastStreamStep(input.streamResult),
359
+ buildState,
360
+ ...hooks,
361
+ });
362
+ }
363
+
364
+ function resolveStreamingMessageId(input: {
365
+ conversationId?: string;
366
+ lifecycleAdapter: HostedChatExecutionLifecycleAdapter;
367
+ runContext: HostedChatExecutionRunContext;
368
+ }): string | null {
369
+ const streamingMessageId = input.lifecycleAdapter.durableRootRun?.messageId ?? null;
370
+ if (input.conversationId && !streamingMessageId) {
371
+ throw new Error("DURABLE_CHAT_ROOT_REQUIRES_CONVERSATION");
372
+ }
373
+
374
+ if (streamingMessageId) {
375
+ input.runContext.setMessageId?.(streamingMessageId);
376
+ }
377
+
378
+ return streamingMessageId;
379
+ }
380
+
381
+ export function createHostedChatExecutionRuntime(
382
+ input: CreateHostedChatExecutionRuntimeInput,
383
+ ): HostedChatExecutionRuntime {
384
+ let finishPromise: Promise<void> = Promise.resolve();
385
+ let lastStreamError: unknown = null;
386
+ let finishHandlerStarted = false;
387
+ let mirroredDurableOutput = false;
388
+ const incompleteToolCallsPartErrorText = input.incompleteToolCallsPartErrorText ??
389
+ INCOMPLETE_TOOL_CALLS_PART_ERROR_TEXT;
390
+ const streamingMessageId = resolveStreamingMessageId({
391
+ conversationId: input.bootstrap.capturedConversationId,
392
+ lifecycleAdapter: input.bootstrap.lifecycleAdapter,
393
+ runContext: input.runContext,
394
+ });
395
+
396
+ const finalizeDetachedStreamEndIfNeeded = async () => {
397
+ if (finishHandlerStarted) {
398
+ return;
399
+ }
400
+
401
+ finishHandlerStarted = true;
402
+ await finalizeDetachedStreamEnd({
403
+ capturedMessageId: input.bootstrap.capturedMessageId,
404
+ streamResult: input.bootstrap.streamResult,
405
+ isAborted: input.abortSignal.aborted,
406
+ lastStreamError,
407
+ lifecycleAdapter: input.bootstrap.lifecycleAdapter,
408
+ mirroredToolChunkState: input.bootstrap.mirroredToolChunkState,
409
+ mirroredDurableOutput,
410
+ incompleteToolCallsPartErrorText,
411
+ cleanup: input.bootstrap.cleanup,
412
+ logger: input.logger,
413
+ });
414
+ };
415
+
416
+ const fail = async (error: unknown) => {
417
+ await input.runContext.withContext(async () => {
418
+ input.bootstrap.rootStreamWatchdog.dispose();
419
+ await input.bootstrap.cleanup().catch((cleanupError: unknown) => {
420
+ logCleanupError({ error: cleanupError, logger: input.logger });
421
+ });
422
+ await finalizeExecutionFailure({
423
+ lifecycleAdapter: input.bootstrap.lifecycleAdapter,
424
+ error,
425
+ conversationId: input.bootstrap.capturedConversationId,
426
+ runId: input.bootstrap.lifecycleAdapter.durableRootRun?.runId,
427
+ logMessage: "Failed to mark durable chat root run as failed",
428
+ logger: input.logger,
429
+ });
430
+ });
431
+ };
432
+
433
+ const streamOptions: HostedChatRuntimeToUiMessageStreamOptions = {
434
+ sendReasoning: true,
435
+ originalMessages: input.originalMessages,
436
+ onError: (error) => {
437
+ lastStreamError = error;
438
+ return input.runContext.withContext(() => getHostedStreamErrorText(error));
439
+ },
440
+ onFinish: ({ responseMessage, isAborted }) => {
441
+ finishHandlerStarted = true;
442
+ finishPromise = input.runContext.withContext(() =>
443
+ finalizeResponseFinish({
444
+ responseMessage,
445
+ isAborted,
446
+ streamResult: input.bootstrap.streamResult,
447
+ lastStreamError,
448
+ lifecycleAdapter: input.bootstrap.lifecycleAdapter,
449
+ mirroredToolChunkState: input.bootstrap.mirroredToolChunkState,
450
+ capturedMessageId: input.bootstrap.capturedMessageId,
451
+ incompleteToolCallsPartErrorText,
452
+ cleanup: input.bootstrap.cleanup,
453
+ logger: input.logger,
454
+ }).catch((error) =>
455
+ finalizeExecutionFailure({
456
+ lifecycleAdapter: input.bootstrap.lifecycleAdapter,
457
+ error,
458
+ conversationId: input.bootstrap.capturedConversationId,
459
+ runId: input.bootstrap.lifecycleAdapter.durableRootRun?.runId,
460
+ logMessage: "Failed to finalize durable chat root run",
461
+ logger: input.logger,
462
+ })
463
+ )
464
+ );
465
+ },
466
+ messageMetadata: createStreamMessageMetadataBuilder({
467
+ agentId: input.agentId,
468
+ modelId: input.modelId,
469
+ lifecycleAdapter: input.bootstrap.lifecycleAdapter,
470
+ streamingMessageId,
471
+ }),
472
+ };
473
+
474
+ if (input.responseMessageId) {
475
+ const responseMessageId = input.responseMessageId;
476
+ streamOptions.generateMessageId = () => responseMessageId;
477
+ }
478
+ const agentUIStream = input.bootstrap.streamResult.toUIMessageStream(streamOptions);
479
+
480
+ return {
481
+ agentUIStream: createHostedMirroredUiStream({
482
+ sourceStream: agentUIStream,
483
+ rootStreamWatchdog: input.bootstrap.rootStreamWatchdog,
484
+ mirroredToolChunkState: input.bootstrap.mirroredToolChunkState,
485
+ appendChunk: (chunk) => input.bootstrap.lifecycleAdapter.durableRunMirror?.handleChunk(chunk),
486
+ setMirroredOutput: (value) => {
487
+ mirroredDurableOutput = value;
488
+ },
489
+ logger: input.logger,
490
+ }),
491
+ fail,
492
+ waitForFinish: async () => {
493
+ try {
494
+ await finalizeDetachedStreamEndIfNeeded();
495
+ await finishPromise;
496
+ } finally {
497
+ input.bootstrap.rootStreamWatchdog.dispose();
498
+ }
499
+ },
500
+ };
501
+ }
@@ -799,6 +799,21 @@ export {
799
799
  type DetachedFallbackMessageState,
800
800
  type FinalizedMessageState,
801
801
  } from "./hosted-finalized-message.js";
802
+ export {
803
+ cleanupAfterHostedChatExecutionFinalization,
804
+ createHostedChatExecutionRuntime,
805
+ type CreateHostedChatExecutionRuntimeInput,
806
+ createHostedChatFinalizeDetachedBuildState,
807
+ createHostedChatFinalizeResponseBuildState,
808
+ createHostedChatStreamFinalizationHooks,
809
+ type HostedChatExecutionLifecycleAdapter,
810
+ type HostedChatExecutionRootStreamWatchdog,
811
+ type HostedChatExecutionRunContext,
812
+ type HostedChatExecutionRuntime,
813
+ type HostedChatExecutionRuntimeBootstrap,
814
+ type HostedChatExecutionRuntimeLogger,
815
+ toHostedChatExecutionFinalState,
816
+ } from "./hosted-chat-execution-runtime.js";
802
817
  export {
803
818
  finalizeHostedDetached,
804
819
  type FinalizeHostedDetachedOptions,
@@ -21,6 +21,7 @@ import {
21
21
  type MessagePart,
22
22
  type ResolvedRuntimeState,
23
23
  type ToolCall,
24
+ type ToolExecutionResultRequest,
24
25
  type ToolResultPart,
25
26
  } from "../types.js";
26
27
  import { ensureModelReady, type ModelRuntime, resolveModel } from "../../provider/index.js";
@@ -530,6 +531,15 @@ export class AgentRuntime {
530
531
  };
531
532
  }
532
533
 
534
+ private async notifyToolResult(
535
+ request: Omit<ToolExecutionResultRequest, "agentId">,
536
+ ): Promise<void> {
537
+ await this.config.onToolResult?.({
538
+ agentId: this.id,
539
+ ...request,
540
+ });
541
+ }
542
+
533
543
  /**
534
544
  * Generate a response (non-streaming)
535
545
  */
@@ -932,18 +942,27 @@ export class AgentRuntime {
932
942
  const startTime = Date.now();
933
943
 
934
944
  const cacheCtx = tryGetCacheKeyContext();
945
+ const executionContext = {
946
+ toolCallId: tc.toolCallId,
947
+ ...toolContext,
948
+ projectId: cacheCtx?.projectId ?? toolContext?.projectId,
949
+ };
935
950
  const result = await executeConfiguredTool(
936
951
  tc.toolName,
937
952
  toolCall.args,
938
953
  this.config.tools,
939
- {
940
- toolCallId: tc.toolCallId,
941
- ...toolContext,
942
- projectId: cacheCtx?.projectId ?? toolContext?.projectId,
943
- },
954
+ executionContext,
944
955
  allowedRemoteToolNames,
945
956
  this.config.remoteTools,
946
957
  );
958
+ await this.notifyToolResult({
959
+ mode: "generate",
960
+ toolName: tc.toolName,
961
+ toolCallId: tc.toolCallId,
962
+ input: toolCall.args,
963
+ result,
964
+ context: executionContext,
965
+ });
947
966
 
948
967
  toolCall.status = "completed";
949
968
  toolCall.result = result;
@@ -1280,18 +1299,27 @@ export class AgentRuntime {
1280
1299
 
1281
1300
  callbacks?.onToolCall?.(toolCall);
1282
1301
 
1302
+ const executionContext = {
1303
+ toolCallId: tc.id,
1304
+ ...toolContext,
1305
+ };
1283
1306
  const result = await executeConfiguredTool(
1284
1307
  tc.name,
1285
1308
  toolCall.args,
1286
1309
  this.config.tools,
1287
- {
1288
- toolCallId: tc.id,
1289
- ...toolContext,
1290
- },
1310
+ executionContext,
1291
1311
  allowedRemoteToolNames,
1292
1312
  this.config.remoteTools,
1293
1313
  );
1294
1314
  throwIfAborted(abortSignal);
1315
+ await this.notifyToolResult({
1316
+ mode: "stream",
1317
+ toolName: tc.name,
1318
+ toolCallId: tc.id,
1319
+ input: toolCall.args,
1320
+ result,
1321
+ context: executionContext,
1322
+ });
1295
1323
 
1296
1324
  toolCall.status = "completed";
1297
1325
  toolCall.result = result;
@@ -3,7 +3,7 @@
3
3
  **************************/
4
4
 
5
5
  import type { ModelRuntime } from "../provider/types.js";
6
- import type { RemoteToolSource, Tool } from "../tool/index.js";
6
+ import type { RemoteToolSource, Tool, ToolExecutionContext } from "../tool/index.js";
7
7
  import { INVALID_ARGUMENT } from "../errors/error-registry.js";
8
8
  import type { Memory } from "./memory/memory-interface.js";
9
9
 
@@ -105,6 +105,12 @@ export interface AgentConfig {
105
105
  * host-owned context during a long-lived run.
106
106
  */
107
107
  resolveRuntimeState?: RuntimeStateResolver;
108
+ /**
109
+ * Optional hook invoked after the runtime executes a configured local,
110
+ * registry, integration, or remote tool and before the tool result is
111
+ * persisted or streamed back to callers.
112
+ */
113
+ onToolResult?: ToolExecutionResultHandler;
108
114
  /**
109
115
  * Enable skills for this agent.
110
116
  * - true: include all discovered skills from skills/ directory
@@ -158,6 +164,20 @@ export type RuntimeStateResolver = (
158
164
  request: RuntimeStateRequest,
159
165
  ) => ResolvedRuntimeState | undefined | Promise<ResolvedRuntimeState | undefined>;
160
166
 
167
+ export interface ToolExecutionResultRequest {
168
+ agentId: string;
169
+ mode: "generate" | "stream";
170
+ toolName: string;
171
+ toolCallId: string;
172
+ input: Record<string, unknown>;
173
+ result: unknown;
174
+ context?: ToolExecutionContext;
175
+ }
176
+
177
+ export type ToolExecutionResultHandler = (
178
+ request: ToolExecutionResultRequest,
179
+ ) => void | Promise<void>;
180
+
161
181
  // Import for use in AgentMiddleware
162
182
  import type { AgentContext, AgentResponse } from "./schemas/index.js";
163
183
 
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.419";
3
+ export const VERSION = "0.1.421";