From 032db9920b58607a60b2019e523f2134759d9b1d Mon Sep 17 00:00:00 2001 From: Yanan Wang Date: Fri, 4 Sep 2026 19:05:22 -0500 Subject: [PATCH] fix(routing): honor the Handled flag so a before-hook can refuse a function call --- .../Routing/RoutingService.InvokeFunction.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index d319d282d..c36af1038 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -48,12 +48,23 @@ public async Task InvokeFunction(string name, RoleDialogModel message, Inv try { - result = await funcExecutor.ExecuteAsync(clonedMessage); - - // After functions have been executed - foreach (var hook in hooks) + // A before-hook may REFUSE the call outright by setting Handled, which is what that + // flag has always been documented to mean on RoleDialogModel — it was simply never + // read here, so a hook that decided a tool must not run watched it run anyway. + // + // The refusal's own words are already on the cloned message and are copied back below + // as the tool's result, so the model reads why it was refused rather than a silent + // no-op. The executed-hooks are skipped with the execution: they exist to react to + // what a tool DID, and a call that never happened did nothing for them to record. + if (!clonedMessage.Handled) { - await hook.OnFunctionExecuted(clonedMessage, options); + result = await funcExecutor.ExecuteAsync(clonedMessage); + + // After functions have been executed + foreach (var hook in hooks) + { + await hook.OnFunctionExecuted(clonedMessage, options); + } } // Set result to original message