GlobTool.ts
tools/GlobTool/GlobTool.ts
199
Lines
6064
Bytes
2
Exports
15
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is part of the tool layer, which means it describes actions the system can perform for the user or model.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with tool-system, repo-context. It contains 199 lines, 15 detected imports, and 2 detected exports.
Important relationships
Detected exports
OutputGlobTool
Keywords
pathutilspatternfilesfilenamesoutputinputinputschemadirectoryoutputschema
Detected imports
zod/v4../../Tool.js../../Tool.js../../utils/cwd.js../../utils/errors.js../../utils/file.js../../utils/fsOperations.js../../utils/glob.js../../utils/lazySchema.js../../utils/path.js../../utils/permissions/filesystem.js../../utils/permissions/PermissionResult.js../../utils/permissions/shellRuleMatching.js./prompt.js./UI.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { z } from 'zod/v4'
import type { ValidationResult } from '../../Tool.js'
import { buildTool, type ToolDef } from '../../Tool.js'
import { getCwd } from '../../utils/cwd.js'
import { isENOENT } from '../../utils/errors.js'
import {
FILE_NOT_FOUND_CWD_NOTE,
suggestPathUnderCwd,
} from '../../utils/file.js'
import { getFsImplementation } from '../../utils/fsOperations.js'
import { glob } from '../../utils/glob.js'
import { lazySchema } from '../../utils/lazySchema.js'
import { expandPath, toRelativePath } from '../../utils/path.js'
import { checkReadPermissionForTool } from '../../utils/permissions/filesystem.js'
import type { PermissionDecision } from '../../utils/permissions/PermissionResult.js'
import { matchWildcardPattern } from '../../utils/permissions/shellRuleMatching.js'
import { DESCRIPTION, GLOB_TOOL_NAME } from './prompt.js'
import {
getToolUseSummary,
renderToolResultMessage,
renderToolUseErrorMessage,
renderToolUseMessage,
userFacingName,
} from './UI.js'
const inputSchema = lazySchema(() =>
z.strictObject({
pattern: z.string().describe('The glob pattern to match files against'),
path: z
.string()
.optional()
.describe(
'The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.',
),
}),
)
type InputSchema = ReturnType<typeof inputSchema>
const outputSchema = lazySchema(() =>
z.object({
durationMs: z
.number()
.describe('Time taken to execute the search in milliseconds'),
numFiles: z.number().describe('Total number of files found'),
filenames: z
.array(z.string())
.describe('Array of file paths that match the pattern'),
truncated: z
.boolean()
.describe('Whether results were truncated (limited to 100 files)'),
}),
)
type OutputSchema = ReturnType<typeof outputSchema>
export type Output = z.infer<OutputSchema>
export const GlobTool = buildTool({
name: GLOB_TOOL_NAME,
searchHint: 'find files by name pattern or wildcard',
maxResultSizeChars: 100_000,
async description() {
return DESCRIPTION
},
userFacingName,
getToolUseSummary,
getActivityDescription(input) {
const summary = getToolUseSummary(input)
return summary ? `Finding ${summary}` : 'Finding files'
},
get inputSchema(): InputSchema {
return inputSchema()
},
get outputSchema(): OutputSchema {
return outputSchema()
},
isConcurrencySafe() {
return true
},
isReadOnly() {
return true
},
toAutoClassifierInput(input) {
return input.pattern
},
isSearchOrReadCommand() {
return { isSearch: true, isRead: false }
},
getPath({ path }): string {
return path ? expandPath(path) : getCwd()
},
async preparePermissionMatcher({ pattern }) {
return rulePattern => matchWildcardPattern(rulePattern, pattern)
},
async validateInput({ path }): Promise<ValidationResult> {
// If path is provided, validate that it exists and is a directory
if (path) {
const fs = getFsImplementation()
const absolutePath = expandPath(path)
// SECURITY: Skip filesystem operations for UNC paths to prevent NTLM credential leaks.
if (absolutePath.startsWith('\\\\') || absolutePath.startsWith('//')) {
return { result: true }
}
let stats
try {
stats = await fs.stat(absolutePath)
} catch (e: unknown) {
if (isENOENT(e)) {
const cwdSuggestion = await suggestPathUnderCwd(absolutePath)
let message = `Directory does not exist: ${path}. ${FILE_NOT_FOUND_CWD_NOTE} ${getCwd()}.`
if (cwdSuggestion) {
message += ` Did you mean ${cwdSuggestion}?`
}
return {
result: false,
message,
errorCode: 1,
}
}
throw e
}
if (!stats.isDirectory()) {
return {
result: false,
message: `Path is not a directory: ${path}`,
errorCode: 2,
}
}
}
return { result: true }
},
async checkPermissions(input, context): Promise<PermissionDecision> {
const appState = context.getAppState()
return checkReadPermissionForTool(
GlobTool,
input,
appState.toolPermissionContext,
)
},
async prompt() {
return DESCRIPTION
},
renderToolUseMessage,
renderToolUseErrorMessage,
renderToolResultMessage,
// Reuses Grep's render (UI.tsx:65) — shows filenames.join. durationMs/
// numFiles are "Found 3 files in 12ms" chrome (under-count, fine).
extractSearchText({ filenames }) {
return filenames.join('\n')
},
async call(input, { abortController, getAppState, globLimits }) {
const start = Date.now()
const appState = getAppState()
const limit = globLimits?.maxResults ?? 100
const { files, truncated } = await glob(
input.pattern,
GlobTool.getPath(input),
{ limit, offset: 0 },
abortController.signal,
appState.toolPermissionContext,
)
// Relativize paths under cwd to save tokens (same as GrepTool)
const filenames = files.map(toRelativePath)
const output: Output = {
filenames,
durationMs: Date.now() - start,
numFiles: filenames.length,
truncated,
}
return {
data: output,
}
},
mapToolResultToToolResultBlockParam(output, toolUseID) {
if (output.filenames.length === 0) {
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: 'No files found',
}
}
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: [
...output.filenames,
...(output.truncated
? [
'(Results are truncated. Consider using a more specific path or pattern.)',
]
: []),
].join('\n'),
}
},
} satisfies ToolDef<InputSchema, Output>)