@@ -106,7 +106,12 @@ import {
106106 type EnqueueEvalScoringArgs ,
107107 enqueueEvalScoring as enqueueEvalScoringHelper ,
108108} from "./eval" ;
109- import type { AgentHooks , OnToolEndHookResult , OnToolErrorHookResult } from "./hooks" ;
109+ import type {
110+ AgentHooks ,
111+ AgentToolGuard ,
112+ OnToolEndHookResult ,
113+ OnToolErrorHookResult ,
114+ } from "./hooks" ;
110115import { stripDanglingOpenAIReasoningFromModelMessages } from "./model-message-normalizer" ;
111116import { AgentTraceContext , addModelAttributesToSpan } from "./open-telemetry/trace-context" ;
112117import {
@@ -1044,6 +1049,7 @@ export class Agent {
10441049 private readonly workspaceToolkitOptions : AgentOptions [ "workspaceToolkits" ] ;
10451050 private readonly workspaceSkillsPromptOption : AgentOptions [ "workspaceSkillsPrompt" ] ;
10461051 private readonly configuredHooks ?: AgentHooks ;
1052+ private readonly toolGuard ?: AgentToolGuard ;
10471053 private readonly maxStepsConfigured : boolean ;
10481054 private defaultObservability ?: VoltAgentObservability ;
10491055 private readonly toolManager : ToolManager ;
@@ -1078,6 +1084,7 @@ export class Agent {
10781084 this . workspaceToolkitOptions = options . workspaceToolkits ;
10791085 this . workspaceSkillsPromptOption = options . workspaceSkillsPrompt ;
10801086 this . configuredHooks = options . hooks ;
1087+ this . toolGuard = options . toolGuard ;
10811088 this . maxStepsConfigured = options . maxSteps !== undefined ;
10821089 const globalWorkspace = AgentRegistry . getInstance ( ) . getGlobalWorkspace ( ) ;
10831090 const workspaceOption = options . workspace === undefined ? globalWorkspace : options . workspace ;
@@ -6421,6 +6428,46 @@ export class Agent {
64216428 return parseResult . data ;
64226429 }
64236430
6431+ private async assertToolGuardAllows (
6432+ tool : BaseTool | ProviderTool ,
6433+ args : any ,
6434+ oc : OperationContext ,
6435+ options ?: ToolExecuteOptions ,
6436+ ) : Promise < void > {
6437+ if ( ! this . toolGuard ) {
6438+ return ;
6439+ }
6440+
6441+ const result = await this . toolGuard ( {
6442+ agent : this ,
6443+ tool : tool as any ,
6444+ context : oc ,
6445+ args,
6446+ options,
6447+ } ) ;
6448+
6449+ const denied =
6450+ result === false ||
6451+ ( typeof result === "object" &&
6452+ result !== null &&
6453+ ( result . denied === true || result . allowed === false ) ) ;
6454+ if ( ! denied ) {
6455+ return ;
6456+ }
6457+
6458+ const reason =
6459+ typeof result === "object" && result !== null && typeof result . reason === "string"
6460+ ? result . reason
6461+ : "Tool execution denied by toolGuard." ;
6462+
6463+ throw new ToolDeniedError ( {
6464+ toolName : tool . name ,
6465+ message : reason ,
6466+ code : "TOOL_FORBIDDEN" ,
6467+ httpStatus : 403 ,
6468+ } ) ;
6469+ }
6470+
64246471 private createToolExecutionFactory (
64256472 oc : OperationContext ,
64266473 hooks : AgentHooks ,
@@ -6623,6 +6670,7 @@ export class Agent {
66236670 try {
66246671 await this . waitForSpeculativeInputGuardrail ( oc ) ;
66256672 await oc . traceContext . withSpan ( toolSpan , async ( ) => {
6673+ await this . assertToolGuardAllows ( tool , args , oc , executionOptions ) ;
66266674 await runToolStartHooks ( ) ;
66276675 } ) ;
66286676
@@ -6683,7 +6731,8 @@ export class Agent {
66836731 return oc . traceContext . withSpan ( toolSpan , async ( ) => {
66846732 try {
66856733 await this . waitForSpeculativeInputGuardrail ( oc ) ;
6686- // Call tool start hook - can throw ToolDeniedError
6734+ // Call tool guard and start hook - both can throw ToolDeniedError
6735+ await this . assertToolGuardAllows ( tool , args , oc , executionOptions ) ;
66876736 await runToolStartHooks ( ) ;
66886737
66896738 // Execute tool with merged options
@@ -7242,6 +7291,7 @@ export class Agent {
72427291 `Provider tool "${ tool . name } " received arguments that do not match callTool input.` ,
72437292 ) ;
72447293 }
7294+ await this . assertToolGuardAllows ( tool , callInput , oc , executionOptions ) ;
72457295 await hooks . onToolStart ?.( {
72467296 agent : this ,
72477297 tool : tool as any ,
0 commit comments