fix(tools): throttle ripgrep CPU usage with thread limits and concurrency control

- Add --threads=4 flag to all rg invocations (grep and glob)
- Add global semaphore limiting concurrent rg processes to 2
- Reduce grep timeout from 300s to 60s (matches tool description)
- Reduce max output from 10MB to 256KB (prevents excessive memory usage)
- Add output_mode parameter (content/files_with_matches/count)
- Add head_limit parameter for incremental result fetching

Closes #2008

Ref: #674, #1722
This commit is contained in:
JiHongKim98
2026-02-21 03:01:23 +09:00
parent 7fa22aebdf
commit dafdca217b
8 changed files with 117 additions and 11 deletions
+28 -4
View File
@@ -1,16 +1,16 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
import { runRg } from "./cli"
import { formatGrepResult } from "./result-formatter"
import { runRg, runRgCount } from "./cli"
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, 10MB output). " +
"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}\"). " +
"Returns file paths with matches sorted by modification time.",
"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
@@ -21,18 +21,42 @@ export function createGrepTools(ctx: PluginInput): Record<string, ToolDefinition
.string()
.optional()
.describe("The directory to search in. Defaults to the current working directory."),
output_mode: tool.schema
.string()
.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) => {
try {
const globs = args.include ? [args.include] : undefined
const searchPath = args.path ?? ctx.directory
const paths = [searchPath]
const outputMode = (args.output_mode as "content" | "files_with_matches" | "count") ?? "files_with_matches"
const headLimit = args.head_limit ?? 0
if (outputMode === "count") {
const results = await runRgCount({
pattern: args.pattern,
paths,
globs,
})
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,
})
return formatGrepResult(result)