Welcome to the Testing project tutorial!
Imagine you are hiring a very smart assistant (the AI). This assistant is great at conversation, but they don't have access to your computer's terminal, your files, or your testing suite. To fix this, you need to give them specific "gadgets" or "tools" to perform tasks.
In this project, a Tool Definition is the blueprint for one of these gadgets. It tells the AI:
In this chapter, we will learn how to wrap code into a tool that the AI can understand using the buildTool function.
Let's say we want to build a tool called TestingPermission. Its job is simple but critical:
We will look at how TestingPermissionTool.tsx implements this.
buildTool
To create a tool, we don't just write a function. We need to package metadata, configuration, and logic together. We use the buildTool helper for this.
Think of buildTool as a laminating machine. You give it a piece of paper with instructions, and it turns it into a sturdy, standardized card the system can file and use.
Here is how we start defining the tool:
import { buildTool } from '../../Tool.js';
const NAME = 'TestingPermission';
export const TestingPermissionTool = buildTool({
name: NAME,
userFacingName() {
return 'TestingPermission';
},
// ... more properties go here
});
Explanation:
buildTool.NAME.buildTool containing the name. This is the unique ID the AI uses to find this tool.If you hand someone a gadget without a label, they won't know when to use it. We need to provide a Description and a Prompt.
async description() {
return 'Test tool that always asks for permission';
},
async prompt() {
return 'Test tool that always asks for permission before executing.';
},
Explanation:
description: A short summary used by the system UI or logs.prompt: Detailed instructions specifically for the AI model. This tells the AI when and why it should pick this tool.The AI needs to know exactly what data to send to the tool. Does it need a filename? A number? In our specific case, this tool is simple and takes no inputs.
We use a library called zod to define this shape.
import { z } from 'zod/v4';
import { lazySchema } from '../../utils/lazySchema.js';
// We expect an empty object because there are no inputs
const inputSchema = lazySchema(() => z.strictObject({}));
// Inside buildTool:
get inputSchema() {
return inputSchema();
},
Explanation:
z.strictObject({}) means "I expect an object with absolutely no properties."For a deep dive on validating data, see Input Schema Validation.
Tools have behavior settings (flags). For our testing tool, we want to ensure it is safe to run.
isEnabled() {
return "production" === 'test';
},
isReadOnly() {
return true;
},
isConcurrencySafe() {
return true;
},
Explanation:
isEnabled: checks if we are in the 'test' environment. If not, the tool essentially disappears.isReadOnly: Tells the system this tool reads data but doesn't modify your files (a safety feature).isConcurrencySafe: Tells the system multiple versions of this tool can run at the same time.call
This is the engine of the tool. When the AI decides to use the tool, the call function is executed.
async call() {
// The actual work happens here
return {
data: `${NAME} executed successfully`
};
},
Explanation:
data.
Our specific use case requires asking the user for permission. We define a checkPermissions method within the tool definition.
async checkPermissions() {
return {
behavior: 'ask' as const,
message: `Run test?`
};
},
Explanation:
call() runs, the system checks this method.behavior: 'ask' forces a popup dialog for the user.We will cover the details of how this halts execution in Permission Control.
What actually happens when you define this tool?
The buildTool function validates that you haven't forgotten anything (like the description or schema). It binds your specific logic into a generic Tool object that the rest of the system knows how to handle.
Here is a simplified flow of how the definition comes to life:
The TestingPermissionTool constant we exported isn't just a raw object anymore. It is a typed instance of Tool.
import type { ToolDef } from '../../Tool.js';
// The object we passed to buildTool satisfies the ToolDef interface
} satisfies ToolDef<InputSchema, string>);
Explanation:
satisfies ToolDef: This is a TypeScript feature. It checks that our object matches the requirements of a tool definition without changing the inferred types.name or call, your code editor will show a red error line immediately.In this chapter, we learned that a Tool Definition is a structured way to give the AI new capabilities.
We used buildTool to combine:
name, description, prompt.inputSchema.isReadOnly, isEnabled.call, checkPermissions.Now that we have the blueprint, the next step is to ensure the AI uses the tool correctly by sending valid data.
Next Chapter: Input Schema Validation
Generated by Code IQ