fix(grep): enable ripgrep auto-download when not found in PATH

The auto-download mechanism for ripgrep existed but was never called.
When 'rg' wasn't in PATH, the grep tool silently fell back to GNU grep,
which wastes ~10% token budget due to noisy results.

Changes:
1. Wired up resolveGrepCliWithAutoInstall() in the CLI resolution path
2. When 'rg' is not found in PATH, auto-downloads ripgrep v14.1.1
3. Caches the downloaded binary in OpenCode data directory
4. Falls back to GNU grep only if auto-download fails (with warning)

Fixes #3003
This commit is contained in:
YeonGyu-Kim
2026-04-02 13:32:23 +09:00
parent 51d9685571
commit 4c4efc416a
5 changed files with 339 additions and 13 deletions
+15 -8
View File
@@ -1,6 +1,7 @@
import { spawn } from "bun"
import {
resolveGrepCli,
type ResolvedCli,
type GrepBackend,
DEFAULT_MAX_DEPTH,
DEFAULT_MAX_FILESIZE,
@@ -148,17 +149,17 @@ function parseCountOutput(output: string): CountResult[] {
return results
}
export async function runRg(options: GrepOptions): Promise<GrepResult> {
export async function runRg(options: GrepOptions, resolvedCli?: ResolvedCli): Promise<GrepResult> {
await rgSemaphore.acquire()
try {
return await runRgInternal(options)
return await runRgInternal(options, resolvedCli)
} finally {
rgSemaphore.release()
}
}
async function runRgInternal(options: GrepOptions): Promise<GrepResult> {
const cli = resolveGrepCli()
async function runRgInternal(options: GrepOptions, resolvedCli?: ResolvedCli): Promise<GrepResult> {
const cli = resolvedCli ?? resolveGrepCli()
const args = buildArgs(options, cli.backend)
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS)
@@ -224,17 +225,23 @@ async function runRgInternal(options: GrepOptions): Promise<GrepResult> {
}
}
export async function runRgCount(options: Omit<GrepOptions, "context">): Promise<CountResult[]> {
export async function runRgCount(
options: Omit<GrepOptions, "context">,
resolvedCli?: ResolvedCli
): Promise<CountResult[]> {
await rgSemaphore.acquire()
try {
return await runRgCountInternal(options)
return await runRgCountInternal(options, resolvedCli)
} finally {
rgSemaphore.release()
}
}
async function runRgCountInternal(options: Omit<GrepOptions, "context">): Promise<CountResult[]> {
const cli = resolveGrepCli()
async function runRgCountInternal(
options: Omit<GrepOptions, "context">,
resolvedCli?: ResolvedCli
): Promise<CountResult[]> {
const cli = resolvedCli ?? resolveGrepCli()
const args = buildArgs({ ...options, context: 0 }, cli.backend)
if (cli.backend === "rg") {