fbed7d1787
- Expanded path parsing to split top-level comma, semicolon, and whitespace entries. - Updated find/search and scope resolution to apply delimiter expansion before path-spec validation. - Updated read tool fallback to try split path parts before raising missing-path errors. - Coerced bare string arguments into singleton arrays for array-typed schemas.
11 KiB
11 KiB
find
Find filesystem paths by glob; use
searchwhen you need content matches instead of path matches.
Source
- Entry:
packages/coding-agent/src/tools/find.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/find.md - Key collaborators:
packages/coding-agent/src/tools/path-utils.ts— normalize inputs; split base path vs glob.packages/coding-agent/src/tools/list-limit.ts— apply result-count caps.packages/coding-agent/src/session/streaming-output.ts— truncate text output at byte cap.packages/coding-agent/src/tools/tool-result.ts— buildcontentanddetails.meta.packages/coding-agent/src/tools/output-meta.ts— encode limit / truncation metadata.packages/coding-agent/src/tools/tool-errors.ts— map user-facing tool errors.packages/coding-agent/src/tools/index.ts— register the built-in local implementation.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
paths |
string[] |
Yes | One or more globs, files, directories, or internal URLs with backing files. Empty strings are rejected. Single entries accidentally joined with comma, semicolon, or whitespace are expanded only after existence validation; existing paths containing delimiters stay intact. Multiple entries may be merged into one brace-union search when their base paths can be resolved together. |
hidden |
boolean |
No | Whether hidden files are included. Defaults to true (hidden ?? true). |
gitignore |
boolean |
No | Whether .gitignore is respected during local native globbing. Defaults to true; set false to include gitignored files. |
limit |
number |
No | Max returned paths. Defaults to 200; finite positive inputs are floored then clamped to 1..200. |
timeout |
number |
No | Timeout in seconds. Defaults to 5; clamped to 0.5..60. On timeout, returns partial matches collected so far with a timeout notice and truncated: true. |
Outputs
The tool returns a single text block plus structured details.
- Success text: matching paths grouped by directory. Each non-root group starts with
# <dir>/and then lists basenames; root-level matches are listed without a header. Directory matches carry a trailing/. Exact file inputs return that file path as one line. - Empty result text:
No files found matching pattern, optionally followed by a timeout or missing-path notice. - Multi-path partial miss: appends
Skipped missing paths: ...after the result block, or after the empty-result line. detailsmay include:scopePath: display form of the searched root or merged roots.fileCount: number of paths returned after result limiting.files: returned paths as an array.truncated: whether result count or byte truncation occurred.resultLimitReached: reached result limit.missingPaths: skipped missing inputs in multi-path calls.truncation/meta.limits: structured truncation and limit metadata for renderers.
- Streaming: when the runtime supplies
onUpdate, the local implementation emits incremental newline-delimited text snapshots during globbing, throttled to 200 ms. Final output is grouped; streaming snapshots are not.
Flow
FindTool.execute()expands delimiter-flattened localpathsentries withexpandDelimitedPathEntries(..., parseFindPattern)unless custom operations are injected. The splitter validates candidate parts by statting their parsed base paths, keeps existing delimiter-containing paths intact, accepts comma/semicolon splits when at least one part resolves, and accepts whitespace splits only when every part resolves.- The tool normalizes each resulting entry with
normalizePathLikeInput()and/\\/g -> "/"(packages/coding-agent/src/tools/find.ts). Empty normalized entries fail with`paths` must contain non-empty globs or paths. - For multi-path local calls,
partitionExistingPaths(..., parseFindPattern)(packages/coding-agent/src/tools/path-utils.ts) stats each base path. Missing entries are skipped; if all are missing, the tool throwsPath not found: .... Single missing paths still hard-fail. - The tool tries
resolveExplicitFindPatterns()to merge multiple inputs into one search rooted at a common base path. If that does not apply, it parses one input withparseFindPattern(). parseFindPattern()determines(basePath, globPattern, hasGlob):- no glob chars (
*,?,[,{) => search that path with implicit**/*. - glob in the first segment => search from
.and, unless the pattern already starts with**/, prefix it with**/. - glob later in the path => split at the first glob-bearing segment.
- no glob chars (
resolveToCwd()converts the base path to an absolute path under the session cwd. A resolved/is rejected withSearching from root directory '/' is not allowed.limitdefaults toDEFAULT_LIMIT(200), must be positive and finite, is floored, then clamped toMAX_LIMIT(200).hiddenandgitignoreboth default totrue.timeoutis converted to milliseconds and clamped to500..60_000before building anAbortSignal.timeout(...).- Execution then branches:
- Custom operations branch: if
FindToolOptions.operations.globexists, the tool checks existence withoperations.exists(), short-circuits exact-file inputs viaoperations.stat()when available, then callsoperations.glob(globPattern, searchPath, { ignore: ["**/node_modules/**", "**/.git/**"], limit }). - Built-in local branch: the tool stats
searchPath. Exact-file inputs return immediately. Directory inputs callnatives.glob()withhidden,maxResults: effectiveLimit,sortByMtime: true,gitignore: useGitignore, and the combined abort signal.
- Custom operations branch: if
- In the local branch, optional
onMatchcallbacks convert each match to a cwd-relative display path and emit throttled progress updates. - After native glob returns, JS sorts
result.matchesbymtimedescending ((b.mtime ?? 0) - (a.mtime ?? 0)) before formatting paths. buildResult()appliesapplyListLimit()to cap the array again ateffectiveLimit, formats paths withformatFindGroupedOutput(), appends notices, then runstruncateHead()withmaxLines: Number.MAX_SAFE_INTEGER. In practice this leaves the 50 KB byte cap in place while disabling the default 3000-line cap.toolResult()packages text plusdetails, and records result-limit / truncation metadata for renderers.
Modes / Variants
- Exact file path: if the parsed input has no glob and the resolved path stats as a file, output is that one path.
- Directory path: if the parsed input has no glob and stats as a directory, the tool searches it with implicit
**/*. - Single glob path: one input parsed by
parseFindPattern(). - Merged multi-path search: multiple inputs resolved by
resolveExplicitFindPatterns()into one brace-union glob rooted at a common base path. - Partial multi-path search with missing inputs: local multi-path calls skip missing base paths and surface them as
missingPaths/Skipped missing paths: .... - Internal URL input: supported when the internal router resolves the URL to a backing file. Internal URL globs are rejected.
- Custom delegated search: uses injected
FindOperationsinstead of local fs + native glob.
Side Effects
- Filesystem
- Stats the resolved base path, and in local multi-path mode stats every candidate base path up front.
- Does not write files.
- Subprocesses / native bindings
- Built-in local mode calls the native
@oh-my-pi/pi-nativesglob implementation.
- Built-in local mode calls the native
- Session state (transcript, memory, jobs, checkpoints, registries)
- Emits structured progress updates when
onUpdateis provided. - Adds truncation / limit metadata to the tool result.
- Emits structured progress updates when
- Background work / cancellation
- Local globbing is cancellable through the caller abort signal plus the configured internal timeout.
Limits & Caps
- Default result limit:
200(DEFAULT_LIMITinpackages/coding-agent/src/tools/find.ts). - Maximum result limit:
200(MAX_LIMIT); larger inputs are clamped. - Local glob timeout: default
5000ms, clamped to500..60_000ms. - Output byte cap:
50 * 1024bytes (DEFAULT_MAX_BYTESinpackages/coding-agent/src/session/streaming-output.ts). - Default generic line cap in
truncateHead()is3000, butfindoverridesmaxLinestoNumber.MAX_SAFE_INTEGER, so byte size — not line count — is the practical output truncation cap. - Streaming update throttle:
200ms betweenonUpdateemissions. - Sort order: most recent
mtimefirst in the built-in local branch and promised in the prompt. The tool re-sorts in JS even though native glob receivessortByMtime: trueso native code can still stop early atmaxResults.
Errors
- User-facing
ToolErrors fromFindTool.execute()include:`paths` must contain non-empty globs or pathsPath not found: ...Searching from root directory '/' is not allowedLimit must be a positive numberPath is not a directory: ...- timeout result text is
find timed out after <seconds>s; returning <N> partial matches — increase timeout or narrow patternand is returned as a successful, truncated partial result rather than an error.
- If the caller aborts, the local branch converts
AbortErrorintoToolAbortError. - Non-
ENOENTstat failures and other unexpected errors are rethrown. - Empty matches are not errors; they return the no-files text result.
Notes
- Reach for
findfor filename / path discovery. Reach forsearchwhen the selection criterion is file contents or regex matches;searchtakes apatternand returns anchored content matches, whilefindonly returns matching paths (packages/coding-agent/src/prompts/tools/find.md,packages/coding-agent/src/prompts/tools/search.md). - Bare top-level globs are made recursive.
*.tsis parsed as base.plus glob**/*.ts;src/*.tsstays rooted atsrcwith a non-recursive*.tssegment;src/**/*.tspreserves explicit recursion. .gitignoredefaults to enabled in the built-in local branch. Usegitignore: falseto disable it for native traversal.hiddendefaults totrue; hidden-file exclusion is opt-out, not opt-in.- Multi-path missing-input tolerance only applies in the built-in local branch. The custom-operations branch hard-fails the first missing
searchPathit checks. - The custom
FindOperations.glob()hook receivesignoreandlimit, but not thehiddenflag or an explicit.gitignoretoggle. A remote delegate must account for that itself if it wants parity with the local branch. - Built-in local globbing does not force
fileType: File; it can return files and directories from native glob. Directory outputs also occur through exact-path passthrough or custom delegates that return them.