import { NoSuchToolError, type LanguageModel } from 'ai'; import { describe, expect, it, vi } from 'vitest'; import { createKtxToolCallRepairHandler } from '../../src/llm/repair.js'; const repairModel = { modelId: 'claude-repair', provider: 'anthropic' } as LanguageModel; describe('createKtxToolCallRepairHandler', () => { it('returns null for NoSuchToolError', async () => { const handler = createKtxToolCallRepairHandler({ source: 'unit', getRepairModel: () => repairModel, generateText: vi.fn(), }); await expect( handler({ system: undefined, messages: [], toolCall: { type: 'tool-call', toolName: 'missing', toolCallId: 'tc_1', input: '{}' }, tools: {}, inputSchema: async () => ({}), error: new NoSuchToolError({ toolName: 'missing' }), }), ).resolves.toBeNull(); }); it('repairs string input by local JSON extraction without an LLM call', async () => { const generateText = vi.fn(); const handler = createKtxToolCallRepairHandler({ source: 'unit', getRepairModel: () => repairModel, generateText, }); await expect( handler({ system: undefined, messages: [], toolCall: { type: 'tool-call', toolName: 'write_source', toolCallId: 'tc_2', input: 'prefix {"path":"orders.yaml"} suffix', }, tools: { write_source: {} as never }, inputSchema: async () => ({ type: 'object' }), error: new Error('Invalid tool input') as never, }), ).resolves.toEqual({ type: 'tool-call', toolName: 'write_source', toolCallId: 'tc_2', input: '{"path":"orders.yaml"}', }); expect(generateText).not.toHaveBeenCalled(); }); it('falls back to the repair model when local extraction fails', async () => { const generateText = vi.fn().mockResolvedValue({ text: '{"path":"customers.yaml"}' }); const handler = createKtxToolCallRepairHandler({ source: 'unit', getRepairModel: () => repairModel, generateText, }); await expect( handler({ system: undefined, messages: [], toolCall: { type: 'tool-call', toolName: 'write_source', toolCallId: 'tc_3', input: 'not json', }, tools: { write_source: {} as never }, inputSchema: async () => ({ type: 'object', properties: { path: { type: 'string' } } }), error: new Error('Invalid tool input') as never, }), ).resolves.toEqual({ type: 'tool-call', toolName: 'write_source', toolCallId: 'tc_3', input: '{"path":"customers.yaml"}', }); expect(generateText).toHaveBeenCalledWith( expect.objectContaining({ model: repairModel, prompt: expect.stringContaining('The model tried to call the tool "write_source"'), }), ); }); });