import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartagent from '../ts/index.js'; import { filesystemTool, shellTool, httpTool, jsonTool, truncateOutput } from '../ts_tools/index.js'; import { compactMessages } from '../ts_compaction/index.js'; // ============================================================ // Core exports // ============================================================ tap.test('should export runAgent function', async () => { expect(smartagent.runAgent).toBeTypeOf('function'); }); tap.test('should export ToolRegistry class', async () => { expect(smartagent.ToolRegistry).toBeTypeOf('function'); }); tap.test('should export ContextOverflowError class', async () => { expect(smartagent.ContextOverflowError).toBeTypeOf('function'); }); tap.test('should export truncateOutput function', async () => { expect(smartagent.truncateOutput).toBeTypeOf('function'); }); tap.test('should re-export tool helper', async () => { expect(smartagent.tool).toBeTypeOf('function'); }); tap.test('should re-export z (zod)', async () => { expect(smartagent.z).toBeTruthy(); }); tap.test('should re-export stepCountIs', async () => { expect(smartagent.stepCountIs).toBeTypeOf('function'); }); // ============================================================ // ToolRegistry // ============================================================ tap.test('ToolRegistry should register and return tools', async () => { const registry = new smartagent.ToolRegistry(); const echoTool = smartagent.tool({ description: 'Echo tool', inputSchema: smartagent.z.object({ text: smartagent.z.string() }), execute: async ({ text }: { text: string }) => text, }); registry.register('echo', echoTool); const tools = registry.getTools(); expect(Object.keys(tools)).toContain('echo'); }); // ============================================================ // Truncation // ============================================================ tap.test('truncateOutput should not truncate short strings', async () => { const result = truncateOutput('hello world'); expect(result.truncated).toBeFalse(); expect(result.content).toEqual('hello world'); }); tap.test('truncateOutput should truncate strings over maxLines', async () => { const lines = Array.from({ length: 3000 }, (_, i) => `line ${i}`).join('\n'); const result = truncateOutput(lines, { maxLines: 100 }); expect(result.truncated).toBeTrue(); expect(result.notice).toBeTruthy(); expect(result.content).toInclude('[Output truncated'); }); tap.test('truncateOutput should truncate strings over maxBytes', async () => { const big = 'x'.repeat(100_000); const result = truncateOutput(big, { maxBytes: 1000 }); expect(result.truncated).toBeTrue(); }); // ============================================================ // Tool factories // ============================================================ tap.test('filesystemTool returns expected tool names', async () => { const tools = filesystemTool(); const names = Object.keys(tools); expect(names).toContain('read_file'); expect(names).toContain('write_file'); expect(names).toContain('list_directory'); expect(names).toContain('delete_file'); }); tap.test('shellTool returns expected tool names', async () => { const tools = shellTool(); const names = Object.keys(tools); expect(names).toContain('run_command'); }); tap.test('httpTool returns expected tool names', async () => { const tools = httpTool(); const names = Object.keys(tools); expect(names).toContain('http_get'); expect(names).toContain('http_post'); }); tap.test('jsonTool returns expected tool names', async () => { const tools = jsonTool(); const names = Object.keys(tools); expect(names).toContain('json_validate'); expect(names).toContain('json_transform'); }); tap.test('json_validate tool should validate valid JSON', async () => { const tools = jsonTool(); const result = await (tools.json_validate as any).execute({ jsonString: '{"name":"test","value":42}', }); expect(result).toInclude('Valid JSON'); }); tap.test('json_validate tool should detect invalid JSON', async () => { const tools = jsonTool(); const result = await (tools.json_validate as any).execute({ jsonString: '{invalid json', }); expect(result).toInclude('Invalid JSON'); }); tap.test('json_validate tool should check required fields', async () => { const tools = jsonTool(); const result = await (tools.json_validate as any).execute({ jsonString: '{"name":"test"}', requiredFields: ['name', 'missing_field'], }); expect(result).toInclude('missing_field'); }); tap.test('json_transform tool should pretty-print JSON', async () => { const tools = jsonTool(); const result = await (tools.json_transform as any).execute({ jsonString: '{"a":1,"b":2}', }); expect(result).toInclude(' "a": 1'); }); // ============================================================ // Compaction export // ============================================================ tap.test('compactMessages should be a function', async () => { expect(compactMessages).toBeTypeOf('function'); }); // ============================================================ // Filesystem tool read/write round-trip // ============================================================ tap.test('filesystem tool should write and read a file', async () => { const tmpDir = '/tmp/smartagent-test-' + Date.now(); const tools = filesystemTool({ rootDir: tmpDir }); await (tools.write_file as any).execute({ path: tmpDir + '/hello.txt', content: 'Hello, world!', }); const content = await (tools.read_file as any).execute({ path: tmpDir + '/hello.txt', }); expect(content).toInclude('Hello, world!'); // Cleanup await (tools.delete_file as any).execute({ path: tmpDir + '/hello.txt', }); }); tap.test('filesystem tool should enforce rootDir restriction', async () => { const tools = filesystemTool({ rootDir: '/tmp/restricted' }); let threw = false; try { await (tools.read_file as any).execute({ path: '/etc/passwd' }); } catch (e) { threw = true; expect((e as Error).message).toInclude('Access denied'); } expect(threw).toBeTrue(); }); export default tap.start();