Files
goodbuddy/src/main/agent/ripgrep-provider.test.ts
T
mesalogo ff746bdc1a fix: preserve native ripgrep capabilities for direct models
Replace the restricted workspace_rg parameter mapping with native args and optional cwd. Preserve rg output and exit codes, and reuse process cancellation and output_read pagination so large search results remain accessible. Ask remains read-only within the workspace; Execute uses current-account permissions.

Validation: focused regression 169 passed, 1 skipped; typecheck and lint passed. Real DeepSeek Ask and Execute pagination scenarios passed with six counted requests in the successful run; an earlier failed fixture run has no retained request count. Full-suite validation is not green: the recorded run had unrelated failures and the latest run timed out without a summary. The remote Agent does not use this local execution path.

Release note: 直连模型可使用 ripgrep 原生参数搜索文件并保留原生结果,大输出可通过 output_read 完整续读。Ask 保持工作区只读,Execute 使用当前账号权限。
2026-09-25 23:58:21 +08:00

48 lines
2.5 KiB
TypeScript

import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { rgPath } from '@vscode/ripgrep'
import { expect, it } from 'vitest'
import { ModelToolProvider, type ModelToolResult } from './model-tool-provider'
it('pages native rg output through the production provider in Ask and releases it', async () => {
const root = await mkdtemp(join(tmpdir(), 'goodbuddy-rg-provider-'))
const provider = new ModelToolProvider(root, [], undefined, undefined, false, { ripgrepExecutablePath: rgPath })
const context = { conversationId: 'rg-pages', runtimeTarget: 'model' as const, workMode: 'ask' as const }
const signal = new AbortController().signal
const parse = (result: ModelToolResult) => {
expect(result.contextBytes).toBeLessThanOrEqual(256 * 1024)
const part = result.parts[0]!
if (part.type !== 'text') throw new Error('Expected text')
return JSON.parse(part.text)
}
try {
const text = 'target 你好\\"'.repeat(30_000) + 'tail\n'
await writeFile(join(root, 'large.txt'), text)
expect(await provider.listTools(context, signal)).toEqual(expect.arrayContaining([
expect.objectContaining({ name: 'workspace_rg', inputSchema: expect.objectContaining({ required: ['args'] }) }),
expect.objectContaining({ name: 'output_read' })
]))
const result = parse(await provider.callTool('workspace_rg', { args: ['--no-line-number', 'target', 'large.txt'] }, signal, context))
let output = result.stdout
const reference = result.stdoutReference
let cursor = reference.nextCursor
while (cursor < reference.totalBytes) {
const page = parse(await provider.callTool('output_read', { handle: reference.handle, cursor }, signal, context))
output += page.content
expect(page.nextCursor).toBeGreaterThan(cursor)
cursor = page.nextCursor
}
expect(output).toBe(text)
const error = parse(await provider.callTool('workspace_rg', { args: ['[', '.'] }, signal, context))
expect(error).toMatchObject({ exitCode: 2, stderr: expect.stringContaining('regex parse error') })
await expect(provider.callTool('workspace_rg', { args: ['--pre=cmd', 'target'] }, signal, context))
.rejects.toMatchObject({ name: 'RecoverableModelToolError' })
await provider.releaseConversation(context.conversationId)
await expect(provider.callTool('output_read', { handle: reference.handle }, signal, context)).rejects.toThrow('不存在')
} finally {
await provider.dispose()
await rm(root, { recursive: true, force: true })
}
})