import { Box, Text } from 'ink'
import React from 'react'
import { z } from 'zod'
import { Cost } from '../../components/Cost'
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
import { Tool } from '../../Tool'
import { getCwd } from '../../utils/state'
import { glob } from '../../utils/file'
import { DESCRIPTION, TOOL_NAME_FOR_PROMPT } from './prompt'
import { isAbsolute, relative, resolve } from 'path'
import { hasReadPermission } from '../../utils/permissions/filesystem'
const inputSchema = z.strictObject({
pattern: z.string().describe('The glob pattern to match files against'),
path: z
.string()
.optional()
.describe(
'The directory to search in. Defaults to the current working directory.',
),
})
type Output = {
durationMs: number
numFiles: number
filenames: string[]
truncated: boolean
}
export const GlobTool = {
name: TOOL_NAME_FOR_PROMPT,
async description() {
return DESCRIPTION
},
userFacingName() {
return 'Search'
},
inputSchema,
async isEnabled() {
return true
},
isReadOnly() {
return true
},
isConcurrencySafe() {
return true // GlobTool is read-only, safe for concurrent execution
},
needsPermissions({ path }) {
return !hasReadPermission(path || getCwd())
},
async prompt() {
return DESCRIPTION
},
renderToolUseMessage({ pattern, path }, { verbose }) {
const absolutePath = path
? isAbsolute(path)
? path
: resolve(getCwd(), path)
: undefined
const relativePath = absolutePath
? relative(getCwd(), absolutePath)
: undefined
return `pattern: "${pattern}"${relativePath || verbose ? `, path: "${verbose ? absolutePath : relativePath}"` : ''}`
},
renderToolUseRejectedMessage() {
return
},
renderToolResultMessage(output) {
// Handle string content for backward compatibility
if (typeof output === 'string') {
output = JSON.parse(output) as Output
}
return (
⎿ Found
{output.numFiles}
{output.numFiles === 0 || output.numFiles > 1 ? 'files' : 'file'}
)
},
async *call({ pattern, path }, { abortController }) {
const start = Date.now()
const { files, truncated } = await glob(
pattern,
path ?? getCwd(),
{ limit: 100, offset: 0 },
abortController.signal,
)
const output: Output = {
filenames: files,
durationMs: Date.now() - start,
numFiles: files.length,
truncated,
}
yield {
type: 'result',
resultForAssistant: this.renderResultForAssistant(output),
data: output,
}
},
renderResultForAssistant(output) {
let result = output.filenames.join('\n')
if (output.filenames.length === 0) {
result = 'No files found'
}
// Only add truncation message if results were actually truncated
else if (output.truncated) {
result +=
'\n(Results are truncated. Consider using a more specific path or pattern.)'
}
return result
},
} satisfies Tool