In the previous chapter, Permission Control, we learned how to stop the tool to ask the user for permission.
But what happens once the user says "Yes"? Or what if the tool takes 10 seconds to run? Does the screen just freeze?
This brings us to the Tool Execution Lifecycle.
This lifecycle manages the tool's behavior from the moment it starts until it finishes. It handles two main jobs:
call().Imagine you are ordering a pizza via an app. You don't just hit "Order" and wait in silence for 30 minutes. You see status updates:
A Tool needs to provide this same feedback. Even though the AI called the tool, the Human is watching the screen. The Lifecycle methods allow the tool to communicate its status to the human user.
In our TestingPermissionTool.tsx, we define several "render" methods. These methods control what the user sees in the chat interface.
Since TestingPermission is an internal testing tool, we don't want to clutter the screen, so we set them to return null (nothing). However, it is important to understand what they could do.
// What to show when the tool is waiting in line
renderToolUseQueuedMessage() {
return null;
},
// What to show while the tool is actually working
renderToolUseProgressMessage() {
return null;
},
Explanation:
'Running test...'), a spinner with that text would appear in the UI.Once the tool finishes, we need to tell the user the outcome.
// What to show if the user (or system) rejects the tool
renderToolUseRejectedMessage() {
return null;
},
// What to show when the tool finishes successfully
renderToolResultMessage() {
return null;
},
Explanation:
null) to keep the chat clean.call()We have prepared the inputs, checked permissions, and updated the UI. Now, we actually run the logic.
We briefly looked at call() in Tool Definition, but let's look at it as part of the lifecycle.
async call() {
// 1. Perform the Logic
return {
data: `${NAME} executed successfully` // 2. Return Data
};
},
The Lifecycle Rule:
The call() function only runs if:
If either of those fail, call() is never touched. This guarantees that your core logic is always safe and receives valid data.
mapToolResult...This is a step beginners often forget.
Your call() function returns a JavaScript object (e.g., { data: "Success" }).
However, the AI model (like Claude or GPT) doesn't speak "JavaScript Object". It speaks a specific API language.
We need a translator method to format our result into a block the AI can understand.
mapToolResultToToolResultBlockParam(result, toolUseID) {
return {
type: 'tool_result', // Required tag
content: String(result), // The actual output text
tool_use_id: toolUseID // Links result back to the specific question
};
}
Explanation:
toolUseID: The AI might ask 3 questions at once. This ID tells the AI: "This answer belongs to Question #2."content: We convert our result to a string so the AI can read it as text.How does the system orchestrate all these methods? It acts like a conductor.
Here is the flow of a tool execution from start to finish:
You might wonder: "Why not just put the print statements inside the call() function?"
By separating Logic (call) from Presentation (render...), we make the tool cleaner.
call function can be used by an automated script (headless) without needing a screen to print to.render functions can be changed by a designer without breaking the code logic.Congratulations! You have completed the Testing project tutorial series.
We have built a complete tool from scratch:
You now understand the anatomy of an AI tool. You can take this patternβSchema, Permission, Call, Lifecycleβand build tools for file editing, web browsing, or database management. The possibilities are endless!
Generated by Code IQ