Files
oh-my-opencode/src/tools/grep/tools.ts
T
ismeth 8676b1cdb9 fix(tools): resolve relative paths in glob/grep against project directory
When models pass relative paths (e.g. 'apps/ios/CleanSlate') to glob/grep
tools, they were passed directly to ripgrep which resolved them against
process.cwd(). In OpenCode Desktop, process.cwd() is '/' causing all
relative path lookups to fail with 'No such file or directory'.

Fix: use path.resolve(ctx.directory, args.path) to resolve relative paths
against the project directory instead of relying on process.cwd().
2026-04-15 16:43:54 +09:00

82 lines
3.3 KiB
TypeScript

import { resolve } from "node:path"
import type { PluginInput } from "@opencode-ai/plugin"
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
import { runRg, runRgCount } from "./cli"
import { resolveGrepCliWithAutoInstall } from "./constants"
import { formatGrepResult, formatCountResult } from "./result-formatter"
export function createGrepTools(ctx: PluginInput): Record<string, ToolDefinition> {
const grep: ToolDefinition = tool({
description:
"Fast content search tool with safety limits (60s timeout, 256KB output). " +
"Searches file contents using regular expressions. " +
"Supports full regex syntax (eg. \"log.*Error\", \"function\\s+\\w+\", etc.). " +
"Filter files by pattern with the include parameter (eg. \"*.js\", \"*.{ts,tsx}\"). " +
"Output modes: \"content\" shows matching lines, \"files_with_matches\" shows only file paths (default), \"count\" shows match counts per file.",
args: {
pattern: tool.schema.string().describe("The regex pattern to search for in file contents"),
include: tool.schema
.string()
.optional()
.describe("File pattern to include in the search (e.g. \"*.js\", \"*.{ts,tsx}\")"),
path: tool.schema
.string()
.optional()
.describe("The directory to search in. Defaults to the current working directory."),
output_mode: tool.schema
.enum(["content", "files_with_matches", "count"])
.optional()
.describe(
"Output mode: \"content\" shows matching lines, \"files_with_matches\" shows only file paths (default), \"count\" shows match counts per file."
),
head_limit: tool.schema
.number()
.optional()
.describe("Limit output to first N entries. 0 or omitted means no limit."),
},
execute: async (args, context) => {
try {
const globs = args.include ? [args.include] : undefined
<<<<<<< HEAD
const runtimeCtx = context as Record<string, unknown>
const dir = typeof runtimeCtx.directory === "string" ? runtimeCtx.directory : ctx.directory
const searchPath = args.path ? resolve(dir, args.path) : dir
||||||| parent of 804d517f (fix(tools): resolve relative paths in glob/grep against project directory)
const searchPath = args.path ?? ctx.directory
=======
const searchPath = args.path ? resolve(ctx.directory, args.path) : ctx.directory
>>>>>>> 804d517f (fix(tools): resolve relative paths in glob/grep against project directory)
const paths = [searchPath]
const outputMode = args.output_mode ?? "files_with_matches"
const headLimit = args.head_limit ?? 0
const cli = await resolveGrepCliWithAutoInstall()
if (outputMode === "count") {
const results = await runRgCount({
pattern: args.pattern,
paths,
globs,
}, cli)
const limited = headLimit > 0 ? results.slice(0, headLimit) : results
return formatCountResult(limited)
}
const result = await runRg({
pattern: args.pattern,
paths,
globs,
context: 0,
outputMode,
headLimit,
}, cli)
return formatGrepResult(result)
} catch (e) {
return `Error: ${e instanceof Error ? e.message : String(e)}`
}
},
})
return { grep }
}