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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user