Welcome to the Tool Execution Pipeline! This is the engine room of our application. If the AI is the "brain," this pipeline is the "nervous system" that connects thoughts to actual actions.
Imagine you are at a high-end hotel. You (the AI) pick up the phone and ask the Concierge (the Pipeline) to "Book a table for two at 8 PM."
The Concierge doesn't just run out the door immediately. They follow a strict process:
Without this pipeline, the AI might try to "call a restaurant" that doesn't exist, or perform actions the user hasn't authorized (like formatting your hard drive!). The Tool Execution Pipeline ensures every action is safe, valid, and successful.
Throughout this chapter, we will follow a simple example:
The AI wants to list the files in your current directory using the bash tool.
The pipeline is handled primarily in toolExecution.ts. It breaks down a tool call into these distinct phases:
command is a string).Before looking at the code, let's visualize the flow.
Let's look at how toolExecution.ts handles this in code. We will look at simplified versions of the real logic to make it easy to follow.
runToolUse
This is where the request starts. The pipeline receives a toolUse object from the AI.
// Inside runToolUse function
export async function* runToolUse(toolUse, context, ...) {
const toolName = toolUse.name;
// 1. Discovery: Find the tool in our toolbox
let tool = findToolByName(context.options.tools, toolName);
if (!tool) {
// If the tool doesn't exist, tell the AI immediately
yield createErrorResult(`No such tool available: ${toolName}`);
return;
}
// ... continue to execution
}
Explanation: The code first checks if the requested tool (like bash) actually exists. If not, it returns an error immediately, saving time.
The AI might hallucinate and send a number instead of text. We use Zod schemas to catch this.
// Inside checkPermissionsAndCallTool function
// 2. Validate: Check inputs against the tool's strict schema
const parsedInput = tool.inputSchema.safeParse(input);
if (!parsedInput.success) {
// If validation fails, format the Zod error for the AI
const errorMsg = formatZodValidationError(tool.name, parsedInput.error);
return [{
message: createErrorResult(`InputValidationError: ${errorMsg}`)
}];
}
Explanation: Every tool defines what inputs it accepts. If the bash tool expects a command string, but the AI sends a file_path, this step rejects it before any code runs.
We cannot blindly trust the tool to run. We must determine if the user allows it.
// 3. Permission: Ask the system/user if this is okay
const resolved = await resolveHookPermissionDecision(
tool,
parsedInput.data,
context
);
if (resolved.decision.behavior !== 'allow') {
// User or Policy said NO
return createRejectionMessage("Permission denied by user");
}
Explanation: This step might trigger a popup for the user or check a whitelist. We will dive deeper into this in Permission Resolution.
If we pass validation and permission, we finally do the work!
// 4. Execution: Run the actual tool logic
const startTime = Date.now();
try {
// logic: call the specific tool's function
const result = await tool.call(
parsedInput.data,
context
);
// logic: Success! Move to formatting.
} catch (error) {
// logic: Handle execution crashes gracefully
}
Explanation: tool.call() is where the ls -la command actually runs on the system. We wrap it in a try/catch block so that if the tool crashes, the whole application doesn't crashβinstead, we report the error to the AI.
The AI needs the result in a specific format (usually an XML-like tag called tool_result).
// 5. Result: Package the output
const toolResultBlock = await processToolResultBlock(
tool,
result.data,
toolUseID
);
// Add the result to the message history
resultingMessages.push({
message: createUserMessage({
content: [toolResultBlock],
is_error: false
})
});
Explanation: The raw data (e.g., a list of files) is wrapped in a standardized block. This ensures the AI always knows which tool answer corresponds to which question.
You might have noticed the code handles a lot of "metadata" like logging, analytics, and special checks. The pipeline allows specific logic to run before and after the tool executes.
For example:
These are covered in detail in the next chapter: Lifecycle Hooks.
The Tool Execution Pipeline is the safe bridge between the AI's intent and your computer's reality. It ensures:
Now that we understand the main flow, let's look at how we can inject custom logic into this pipeline.
Generated by Code IQ