docs(tools): updated search tool docs to reflect string or array paths support

- Updated type signature to show `paths` accepts `string | string[]` instead of only arrays.
- Clarified that single string paths are wrapped into a one-element list before resolution.
- Improved prompt instructions to explicitly show both string and array usage patterns.
This commit is contained in:
can1357
2026-05-26 14:44:30 +02:00
parent b0258e575a
commit 0bf1684b97
4 changed files with 21 additions and 13 deletions
+3 -2
View File
@@ -21,7 +21,7 @@
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pattern` | `string` | Yes | Regex pattern. `search.ts` trims it and rejects empty input. The native matcher enables multiline only when the pattern text contains a literal newline or the two-character sequence `\\n`. The model prompt explicitly documents literal-brace escaping such as ``interface\\{\\}``, although the native layer also auto-escapes braces that cannot be valid repetition quantifiers. |
| `paths` | `string[]` | Yes | One or more file paths, directory paths, glob-like paths, or internal URLs. Empty strings are rejected after trimming/quote stripping. Internal URLs must resolve to a backing file and cannot contain glob characters. |
| `paths` | `string \| string[]` | Yes | One file path, directory path, glob-like path, internal URL, or an array of those. Empty strings are rejected after trimming/quote stripping. Internal URLs must resolve to a backing file and cannot contain glob characters. |
| `i` | `boolean` | No | Case-insensitive search. Defaults to `false`. Passed to native `ignoreCase`. |
| `gitignore` | `boolean` | No | Respect `.gitignore` during directory scans. Defaults to `true`. Passed to native `gitignore`. |
| `skip` | `number` | No | Global match offset. Defaults to `0`. `search.ts` floors finite numbers and rejects negative or non-finite values. |
@@ -49,7 +49,8 @@ The tool returns a single text block in `content[0].text` plus structured `detai
- trims `pattern`, rejects empty patterns;
- normalizes `skip` to a non-negative integer;
- reads `search.contextBefore` and `search.contextAfter` from session settings (`1` and `3` by default);
- enables multiline only when `pattern` contains `\n` or an actual newline.
- enables multiline only when `pattern` contains `\n` or an actual newline;
- wraps a single string `paths` value into a one-element list before path resolution.
2. Each `paths` entry is normalized with `normalizePathLikeInput()`.
3. Internal URLs are resolved through `session.internalRouter`:
- glob metacharacters (`*`, `?`, `[`, `{`) are rejected for internal URLs;
@@ -2,8 +2,8 @@ Searches files using powerful regex matching.
<instruction>
- Supports Rust regex syntax (RE2-style — no lookaround or backreferences). Use line anchors or post-filters instead of (?!…)/(?<!…)
- `paths` is required and accepts an array of files, directories, globs, or internal URLs
- `paths` is an array; do not embed commas or spaces inside a single entry. Pass `["src", "tests"]` not `["src,tests"]`.
- `paths` is required and accepts either one string or an array of files, directories, globs, or internal URLs
- For multiple targets, pass an array with one target per element. Do not comma-join targets inside one string: pass `["src", "tests"]`, not `"src,tests"` or `["src,tests"]`.
- Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
</instruction>
+2
View File
@@ -139,6 +139,8 @@ export interface ToolSession {
requireYieldTool?: boolean;
/** Task recursion depth (0 = top-level, 1 = first child, etc.) */
taskDepth?: number;
/** Get shared eval executor session ID. Subagents inherit this to share JS/Python state. */
getEvalSessionId?: () => string | null;
/** Get session file */
getSessionFile: () => string | null;
/** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */
+14 -9
View File
@@ -9,11 +9,12 @@ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
import * as z from "zod/v4";
import { getFileReadCache } from "../edit/file-read-cache";
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
import { computeFileHash, formatHashlineHeader } from "../hashline/hash";
import { formatHashlineHeader, getHashlineSyntax } from "../hashline/hash";
import type { Theme } from "../modes/theme/theme";
import searchDescription from "../prompts/tools/search.md" with { type: "text" };
import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead } from "../session/streaming-output";
import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
import { resolveEditMode } from "../utils/edit-mode";
import { resolveFileDisplayMode } from "../utils/file-display-mode";
import type { ToolSession } from ".";
import {
@@ -39,13 +40,13 @@ import {
import { ToolError } from "./tool-errors";
import { toolResult } from "./tool-result";
const searchPathEntrySchema = z.string().describe("file, directory, glob, or internal URL to search");
const searchSchema = z
.object({
pattern: z.string().describe("regex pattern"),
paths: z
.array(z.string().describe("file, directory, glob, or internal URL to search"))
.min(1)
.describe("files, directories, globs, or internal URLs to search"),
.union([searchPathEntrySchema, z.array(searchPathEntrySchema).min(1)])
.describe("file, directory, glob, internal URL, or array of those to search"),
i: z.boolean().optional().describe("case-insensitive search"),
gitignore: z.boolean().optional().describe("respect gitignore"),
skip: z
@@ -237,7 +238,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
_onUpdate?: AgentToolUpdateCallback<SearchToolDetails>,
_toolContext?: AgentToolContext,
): Promise<AgentToolResult<SearchToolDetails>> {
const { pattern, paths, i, gitignore, skip } = params;
const { pattern, paths: rawPaths, i, gitignore, skip } = params;
return untilAborted(signal, async () => {
const normalizedPattern = pattern.trim();
@@ -249,6 +250,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
if (normalizedSkip < 0 || !Number.isFinite(normalizedSkip)) {
throw new ToolError("Skip must be a non-negative number");
}
const paths = typeof rawPaths === "string" ? [rawPaths] : rawPaths;
for (const entry of paths) {
if (containsTopLevelComma(entry)) {
throw new ToolError('paths is an array — pass ["a", "b"] not ["a,b"]');
@@ -486,6 +488,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
}
const displayLines: string[] = [];
const hashContexts = new Map<string, { absolutePath: string; fileHash: string }>();
const hashlineSyntax = getHashlineSyntax(resolveEditMode(this.session));
if (baseDisplayMode.hashLines) {
for (const relativePath of fileList) {
if (archiveDisplaySet.has(relativePath)) continue;
@@ -493,7 +496,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
if (immutableSourcePaths.has(absoluteFilePath)) continue;
try {
const fullText = await Bun.file(absoluteFilePath).text();
const fileHash = computeFileHash(fullText);
const fileHash = hashlineSyntax.computeFileHash(fullText);
hashContexts.set(relativePath, { absolutePath: absoluteFilePath, fileHash });
} catch {
// Best-effort: if the file disappeared between grep and render, fall back to plain line output.
@@ -525,7 +528,9 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
modelOut.push("...");
displayOut.push(`${gutterPad}│...`);
}
modelOut.push(formatMatchLine(lineNumber, line, isMatch, { useHashLines }));
modelOut.push(
formatMatchLine(lineNumber, line, isMatch, { useHashLines, syntax: hashlineSyntax }),
);
displayOut.push(formatCodeFrameLine(isMatch ? "*" : " ", lineNumber, line, lineNumberWidth));
if (recordable) cacheEntries.push([lineNumber, line] as const);
lastEmittedLine = lineNumber;
@@ -560,7 +565,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
return {
modelLines: rendered.model,
displayLines: rendered.display,
headerSuffix: hashContext ? `#${hashContext.fileHash}` : "",
headerSuffix: hashContext && hashlineSyntax.id === "file" ? `#${hashContext.fileHash}` : "",
skip: rendered.model.length === 0,
};
});
@@ -576,7 +581,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
}
const hashContext = hashContexts.get(relativePath);
if (hashContext) {
outputLines.push(formatHashlineHeader(relativePath, hashContext.fileHash));
outputLines.push(formatHashlineHeader(relativePath, hashContext.fileHash, hashlineSyntax));
}
outputLines.push(...rendered.model);
displayLines.push(...rendered.display);