agent-orchestrator/v2/src/lib/utils/tool-files.test.ts
Hibryda 1efcb13869 feat(provider-adapter): implement multi-provider abstraction layer (Phase 1)
Add provider types, registry, capabilities, and message adapter registry.
Rename sdk-messages→claude-messages, agent-runner→claude-runner,
ClaudeSession→AgentSession. Update Rust AgentQueryOptions with provider
and provider_config fields. Capability-driven AgentPane rendering.
2026-03-11 02:08:45 +01:00

148 lines
6.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { extractFilePaths, extractWritePaths, extractWorktreePath } from './tool-files';
import type { ToolCallContent } from '../adapters/claude-messages';
function makeTc(name: string, input: unknown): ToolCallContent {
return { toolUseId: `tu-${Math.random()}`, name, input };
}
describe('extractFilePaths', () => {
it('extracts Read file_path', () => {
const result = extractFilePaths(makeTc('Read', { file_path: '/src/main.ts' }));
expect(result).toEqual([{ path: '/src/main.ts', op: 'read' }]);
});
it('extracts Write file_path as write op', () => {
const result = extractFilePaths(makeTc('Write', { file_path: '/src/out.ts' }));
expect(result).toEqual([{ path: '/src/out.ts', op: 'write' }]);
});
it('extracts Edit file_path as write op', () => {
const result = extractFilePaths(makeTc('Edit', { file_path: '/src/edit.ts' }));
expect(result).toEqual([{ path: '/src/edit.ts', op: 'write' }]);
});
it('extracts Glob pattern', () => {
const result = extractFilePaths(makeTc('Glob', { pattern: '**/*.ts' }));
expect(result).toEqual([{ path: '**/*.ts', op: 'glob' }]);
});
it('extracts Grep path', () => {
const result = extractFilePaths(makeTc('Grep', { path: '/src', pattern: 'TODO' }));
expect(result).toEqual([{ path: '/src', op: 'grep' }]);
});
it('extracts Bash read paths from common commands', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'cat /etc/hosts' }));
expect(result).toEqual([{ path: '/etc/hosts', op: 'bash' }]);
});
it('handles lowercase tool names', () => {
const result = extractFilePaths(makeTc('read', { file_path: '/foo' }));
expect(result).toEqual([{ path: '/foo', op: 'read' }]);
});
it('returns empty for unknown tool', () => {
const result = extractFilePaths(makeTc('Agent', { prompt: 'do stuff' }));
expect(result).toEqual([]);
});
it('returns empty when input has no file_path', () => {
const result = extractFilePaths(makeTc('Read', {}));
expect(result).toEqual([]);
});
// Bash write detection
it('detects echo > redirect as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'echo "hello" > /tmp/out.txt' }));
expect(result).toEqual([{ path: '/tmp/out.txt', op: 'write' }]);
});
it('detects >> append redirect as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'echo "data" >> /tmp/log.txt' }));
expect(result).toEqual([{ path: '/tmp/log.txt', op: 'write' }]);
});
it('detects sed -i as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: "sed -i 's/foo/bar/g' /src/config.ts" }));
expect(result).toEqual([{ path: '/src/config.ts', op: 'write' }]);
});
it('detects tee as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'echo "content" | tee /tmp/output.log' }));
expect(result).toEqual([{ path: '/tmp/output.log', op: 'write' }]);
});
it('detects tee -a as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'echo "append" | tee -a /tmp/output.log' }));
expect(result).toEqual([{ path: '/tmp/output.log', op: 'write' }]);
});
it('detects cp destination as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'cp /src/a.ts /src/b.ts' }));
expect(result).toEqual([{ path: '/src/b.ts', op: 'write' }]);
});
it('detects mv destination as write', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'mv /old/file.ts /new/file.ts' }));
expect(result).toEqual([{ path: '/new/file.ts', op: 'write' }]);
});
it('ignores /dev/null redirects', () => {
const result = extractFilePaths(makeTc('Bash', { command: 'echo "test" > /dev/null' }));
expect(result).toEqual([]);
});
it('prefers write detection over read for ambiguous commands', () => {
// "cat file > out" should detect the write target, not the read source
const result = extractFilePaths(makeTc('Bash', { command: 'cat /src/input.ts > /tmp/output.ts' }));
expect(result).toEqual([{ path: '/tmp/output.ts', op: 'write' }]);
});
});
describe('extractWritePaths', () => {
it('returns only write-op paths for Write/Edit tools', () => {
expect(extractWritePaths(makeTc('Write', { file_path: '/a.ts' }))).toEqual(['/a.ts']);
expect(extractWritePaths(makeTc('Edit', { file_path: '/b.ts' }))).toEqual(['/b.ts']);
});
it('returns empty for read-only tools', () => {
expect(extractWritePaths(makeTc('Read', { file_path: '/c.ts' }))).toEqual([]);
expect(extractWritePaths(makeTc('Glob', { pattern: '*.ts' }))).toEqual([]);
expect(extractWritePaths(makeTc('Grep', { path: '/src' }))).toEqual([]);
});
it('returns empty for bash read commands', () => {
expect(extractWritePaths(makeTc('Bash', { command: 'cat /foo' }))).toEqual([]);
});
it('detects bash write commands', () => {
expect(extractWritePaths(makeTc('Bash', { command: 'echo "x" > /tmp/out.ts' }))).toEqual(['/tmp/out.ts']);
expect(extractWritePaths(makeTc('Bash', { command: "sed -i 's/a/b/' /src/file.ts" }))).toEqual(['/src/file.ts']);
expect(extractWritePaths(makeTc('Bash', { command: 'cp /a.ts /b.ts' }))).toEqual(['/b.ts']);
});
});
describe('extractWorktreePath', () => {
it('detects Agent tool with isolation: worktree', () => {
const result = extractWorktreePath(makeTc('Agent', { prompt: 'do stuff', isolation: 'worktree' }));
expect(result).toMatch(/^worktree:/);
});
it('detects Task tool with isolation: worktree', () => {
const result = extractWorktreePath(makeTc('Task', { prompt: 'do stuff', isolation: 'worktree' }));
expect(result).toMatch(/^worktree:/);
});
it('returns null for Agent without isolation', () => {
expect(extractWorktreePath(makeTc('Agent', { prompt: 'do stuff' }))).toBeNull();
});
it('detects EnterWorktree with path', () => {
expect(extractWorktreePath(makeTc('EnterWorktree', { path: '/tmp/wt-1' }))).toBe('/tmp/wt-1');
});
it('returns null for unrelated tool', () => {
expect(extractWorktreePath(makeTc('Read', { file_path: '/foo' }))).toBeNull();
});
});