Welcome to the final chapter of the GlobTool tutorial!
In the previous chapter, Filesystem Security & Validation, we secured our tool. We added a "Bouncer" to ensure the AI doesn't access restricted files or crash on missing folders.
Now we have a tool that is Defined (Ch. 1), understands Data (Ch. 2), can Execute (Ch. 3), and is Safe (Ch. 4).
But there is one problem left: Presentation.
Right now, if the AI runs our tool, the interaction looks robotic and raw. In this chapter, we will build the UI Rendering layer. This is the "Frontend" of our toolβit determines what the human user actually sees in the chat window.
Imagine ordering food at a restaurant.
{ status: "cooking", ingredients: ["beef", "bun"] }.
The UI Rendering layer is that translation. It converts abstract data (like { pattern: "*.ts" }) into friendly status messages (like "Searching for TypeScript files...").
We want to achieve two things:
pattern: "*.ts").When the AI decides to use the tool, the tool might take a few seconds to run. We need to show the user a "loading state."
We use a function called renderToolUseMessage. It takes the Input Schema data and returns a string or a React component.
// From UI.tsx
export function renderToolUseMessage(input) {
const { pattern, path } = input
// If no path is specified, just show the pattern
if (!path) {
return `pattern: "${pattern}"`
}
// Otherwise show both
return `pattern: "${pattern}", path: "${path}"`
}
Explanation:
pattern: "*.ts").path exists. If the user didn't specify a folder, we don't want to show path: "undefined". We keep it clean.
Computers love error codes like ENOENT (Error NO ENTry). Humans hate them.
If our Validation logic (from Chapter 4) fails, or the system throws an error, we catch it in renderToolUseErrorMessage.
// From UI.tsx
import { Text } from '../../ink.js' // UI Component helper
export function renderToolUseErrorMessage(result) {
// Check if the error contains our specific flag
if (isFileNotFoundError(result)) {
// Return a styled Red text component
return (
<Text color="error">File not found</Text>
)
}
// Fallback for other errors
return <Text color="error">Error searching files</Text>
}
Explanation:
<Text color="error"> (similar to HTML/CSS styling) to make the message distinct and readable.
Finally, we need a short, human-readable label for the tool. While the internal ID might be glob_tool_v1, the user just wants to see "Search".
// From UI.tsx
export function userFacingName(): string {
return 'Search'
}
Explanation: This string appears on the tool badge in the UI history. It helps the user quickly scan their chat history to see what tools were used.
How does the system decide what to paint on the screen? Let's look at the lifecycle of a user request.
UI.tsx for a status string.call() function (Chapter 3) does the heavy lifting.
In GlobTool.ts, we bundle these UI functions into our definition, just like we did with Schemas and Execution logic.
// GlobTool.ts
import {
renderToolUseMessage,
renderToolUseErrorMessage,
userFacingName
} from './UI.js'
export const GlobTool = buildTool({
name: GLOB_TOOL_NAME,
// We attach the UI functions here:
userFacingName,
renderToolUseMessage,
renderToolUseErrorMessage,
// ... rest of the tool definition
})
You might wonder: "Where is the function that renders the list of files?"
For GlobTool, we actually borrow this logic from another tool (GrepTool) to keep our code DRY (Don't Repeat Yourself).
// From UI.tsx
import { GrepTool } from '../GrepTool/GrepTool.js'
// Reuse the existing list-view component
export const renderToolResultMessage = GrepTool.renderToolResultMessage
This ensures that whether you are searching for filenames (Glob) or text content (Grep), the results look consistent to the user.
Congratulations! You have built the GlobTool from scratch.
Let's review what we accomplished:
You now have a fully functional, safe, and beautiful AI tool capable of searching your computer's filesystem. You can use this pattern to build any tool you can imagineβfrom database queries to API integrations.
Happy Coding!
Generated by Code IQ