Welcome to the final chapter of the GrepTool tutorial!
In the previous chapter, Output Mode Processing, we learned how to clean up raw text into structured data. We fixed paths and sorted files.
However, we have one final, critical problem to solve.
Imagine asking the AI to search for the word if in a massive codebase.
There could be 10,000 matches.
If we send 10,000 lines of code to the AI at once:
We need a way to control the flow. We need Pagination.
Think of your tool as a News Editor. A journalist brings in a 50-page story. The Editor doesn't print the whole thing.
applyHeadLimit Helper
We implement this logic in a helper function called applyHeadLimit. It takes a massive array of results and slices it into a manageable chunk.
We use two variables defined in our Input Schema:
head_limit: How many items to show (Default: 250).offset: How many items to skip (Default: 0).// Inside applyHeadLimit helper function
const effectiveLimit = limit ?? 250;
// Slice the array: Skip 'offset', then take 'limit' amount
const sliced = items.slice(offset, offset + effectiveLimit);
We need to know if we actually cut anything off. If the result was short (e.g., 5 matches), we don't need to say "Page 1 of 100".
// Did we have more items than we are showing?
const wasTruncated = items.length - offset > effectiveLimit;
// If yes, we report the limit. If no, undefined.
const appliedLimit = wasTruncated ? effectiveLimit : undefined;
We return the small slice and the metadata indicating if truncation happened.
return {
items: sliced,
appliedLimit: appliedLimit,
};
How does this look when the tool actually runs? Let's visualize a scenario where the user searches for something common.
Scenario:
call()
Now we use this helper inside our main logic. We do this after ripgrep returns the raw results, but before we do heavy processing (like formatting strings).
In call(), specifically for Content Mode:
// results = The massive array from ripgrep
const { items: limitedResults, appliedLimit } = applyHeadLimit(
results,
head_limit, // from input
offset, // from input
);
Why do this early? If we have 10,000 results and only need 250, we don't want to waste CPU time converting paths or formatting strings for the 9,750 items we are going to throw away.
When we construct the final JSON output, we include the pagination info. This tells the AI: "Hey, I only gave you 250 lines, but there is more."
return {
data: {
mode: 'content',
content: finalLines.join('\n'),
// Only include these if limits were actually applied
...(appliedLimit !== undefined && { appliedLimit }),
...(offset > 0 && { appliedOffset: offset }),
}
}
Finally, we need to show this on the UI so the human knows what's happening.
We use a helper formatLimitInfo to create a friendly string.
function formatLimitInfo(appliedLimit, appliedOffset) {
const parts = [];
if (appliedLimit) parts.push(`limit: ${appliedLimit}`);
if (appliedOffset) parts.push(`offset: ${appliedOffset}`);
return parts.join(', ');
}
Then, in our mapToolResultToToolResultBlockParam (which formats the final text for the AI/User), we append this info.
// Inside mapToolResultToToolResultBlockParam
if (limitInfo) {
return `${resultContent}\n\n[Showing results with pagination = ${limitInfo}]`;
}
The Result: The user sees:
... (lines of code) ...
[Showing results with pagination = limit: 250, offset: 0]
If the AI sees this, it knows: "Aha! I didn't see everything. If I need more, I should call the tool again with offset: 250."
Congratulations! You have built the entire architecture for GrepTool.
Let's review what we built:
You now have a production-ready tool that is safe, user-friendly, and optimized for AI interaction. Happy coding!
Generated by Code IQ