fix(tools): resolve internal URLs in find tool before filesystem path lookup

This commit is contained in:
Miroslav Drbal
2026-05-12 14:55:58 +02:00
committed by can1357
parent 6d16e93b86
commit d43300250d
2 changed files with 38 additions and 1 deletions
+19 -1
View File
@@ -8,6 +8,7 @@ import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
import type { Static } from "@sinclair/typebox";
import { Type } from "@sinclair/typebox";
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
import { InternalUrlRouter } from "../internal-urls";
import type { Theme } from "../modes/theme/theme";
import findDescription from "../prompts/tools/find.md" with { type: "text" };
import { type TruncationResult, truncateHead } from "../session/streaming-output";
@@ -25,6 +26,7 @@ import { applyListLimit } from "./list-limit";
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
import {
formatPathRelativeToCwd,
hasGlobPathChars,
normalizePathLikeInput,
parseFindPattern,
partitionExistingPaths,
@@ -116,7 +118,23 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
return untilAborted(signal, async () => {
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
const normalizedPatterns = paths.map(input => normalizePathLikeInput(input).replace(/\\/g, "/"));
const rawPatterns = paths.map(input => normalizePathLikeInput(input).replace(/\\/g, "/"));
const internalRouter = InternalUrlRouter.instance();
const normalizedPatterns: string[] = [];
for (const rawPattern of rawPatterns) {
if (!internalRouter.canHandle(rawPattern)) {
normalizedPatterns.push(rawPattern);
continue;
}
if (hasGlobPathChars(rawPattern)) {
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPattern}`);
}
const resource = await internalRouter.resolve(rawPattern);
if (!resource.sourcePath) {
throw new ToolError(`Cannot find internal URL without a backing file: ${rawPattern}`);
}
normalizedPatterns.push(resource.sourcePath);
}
if (normalizedPatterns.some(pattern => pattern.length === 0)) {
throw new ToolError("`paths` must contain non-empty globs or paths");
}
@@ -5,6 +5,7 @@ import * as path from "node:path";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { InternalUrlRouter, LocalProtocolHandler } from "@oh-my-pi/pi-coding-agent/internal-urls";
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { FindTool } from "@oh-my-pi/pi-coding-agent/tools/find";
import { SearchTool } from "@oh-my-pi/pi-coding-agent/tools/search";
import { AgentRegistry } from "../../src/registry/agent-registry";
@@ -149,6 +150,24 @@ describe("SearchTool internal URL resolution", () => {
expect(text).not.toMatch(/^\*?\s*\d+[a-z]{2}\|/m);
});
it("resolves local:// URLs before file-name lookup", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(localRoot, { recursive: true });
await Bun.write(path.join(localRoot, "PLAN.md"), "# Plan\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession();
const tool = new FindTool(session);
const result = await tool.execute("test-call", {
paths: ["local://PLAN.md"],
});
const text = getResultText(result);
expect(text).toContain("PLAN.md");
});
it("keeps hashline anchors when searching mutable local:// sources", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(localRoot, { recursive: true });