





















@@ -41,6 +41,14 @@ type ToolExecuteArgs = ToolDefinition["execute"] extends (...args: infer P) => u
4141type ToolExecuteArgsAny = ToolExecuteArgs | ToolExecuteArgsLegacy | ToolExecuteArgsCurrent;
4242const TOOL_ERROR_PARAM_PREVIEW_MAX_CHARS = 600;
434344+export type ClientToolCallRecorder =
45+| ((toolName: string, params: Record<string, unknown>) => void)
46+| {
47+reserve?: (toolCallId: string, toolName: string) => void;
48+complete: (toolCallId: string, toolName: string, params: Record<string, unknown>) => void;
49+discard?: (toolCallId: string, toolName: string) => void;
50+};
51+4452function isAbortSignal(value: unknown): value is AbortSignal {
4553return typeof value === "object" && value !== null && "aborted" in value;
4654}
@@ -318,7 +326,7 @@ function coerceParamsRecord(value: unknown): Record<string, unknown> {
318326// These tools are intercepted to return a "pending" result instead of executing
319327export function toClientToolDefinitions(
320328tools: ClientToolDefinition[],
321-onClientToolCall?: (toolName: string, params: Record<string, unknown>) => void,
329+onClientToolCall?: ClientToolCallRecorder,
322330hookContext?: HookContext,
323331): ToolDefinition[] {
324332return tools.map((tool) => {
@@ -330,27 +338,44 @@ export function toClientToolDefinitions(
330338parameters: func.parameters as ToolDefinition["parameters"],
331339execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
332340const { toolCallId, params } = splitToolExecuteArgs(args);
341+if (onClientToolCall && typeof onClientToolCall !== "function") {
342+onClientToolCall.reserve?.(toolCallId, func.name);
343+}
333344const initialParamsRecord = coerceParamsRecord(params);
334-const outcome = await runBeforeToolCallHook({
335-toolName: func.name,
336-params: initialParamsRecord,
337- toolCallId,
338-ctx: hookContext,
339-});
340-if (outcome.blocked) {
341-if (outcome.kind === "veto") {
342-return buildBlockedToolResult({
343-reason: outcome.reason,
344-deniedReason: outcome.deniedReason,
345-});
345+try {
346+const outcome = await runBeforeToolCallHook({
347+toolName: func.name,
348+params: initialParamsRecord,
349+ toolCallId,
350+ctx: hookContext,
351+});
352+if (outcome.blocked) {
353+if (onClientToolCall && typeof onClientToolCall !== "function") {
354+onClientToolCall.discard?.(toolCallId, func.name);
355+}
356+if (outcome.kind === "veto") {
357+return buildBlockedToolResult({
358+reason: outcome.reason,
359+deniedReason: outcome.deniedReason,
360+});
361+}
362+throw new Error(outcome.reason);
346363}
347-throw new Error(outcome.reason);
348-}
349-const adjustedParams = outcome.params;
350-const paramsRecord = coerceParamsRecord(adjustedParams);
351-// Notify handler that a client tool was called
352-if (onClientToolCall) {
353-onClientToolCall(func.name, paramsRecord);
364+const adjustedParams = outcome.params;
365+const paramsRecord = coerceParamsRecord(adjustedParams);
366+// Notify handler that a client tool was called.
367+if (onClientToolCall) {
368+if (typeof onClientToolCall === "function") {
369+onClientToolCall(func.name, paramsRecord);
370+} else {
371+onClientToolCall.complete(toolCallId, func.name, paramsRecord);
372+}
373+}
374+} catch (err) {
375+if (onClientToolCall && typeof onClientToolCall !== "function") {
376+onClientToolCall.discard?.(toolCallId, func.name);
377+}
378+throw err;
354379}
355380// Return a pending result - the client will execute this tool
356381return jsonResult({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。