fix(agent): add model request timeouts and empty-response fallback

This commit is contained in:
William Valentin
2026-02-17 23:05:21 -08:00
parent 73c58fcbde
commit 9345a864f4
3 changed files with 158 additions and 12 deletions
+98
View File
@@ -110,6 +110,43 @@ describe('NativeAgent', () => {
const history = agent.getHistory();
expect(history[history.length - 1]).toEqual({ role: 'assistant', content: 'Operation cancelled by user.' });
});
it('returns fallback text when model response is empty', async () => {
const mockClient: ModelClient = {
chat: vi.fn().mockResolvedValue({
content: '',
stopReason: 'end_turn',
usage: { inputTokens: 10, outputTokens: 5 },
}),
};
const agent = new NativeAgent({
modelClient: mockClient,
systemPrompt: 'You are helpful.',
});
const response = await agent.process('Hi');
expect(response).toBe('I could not generate a response for that. Please try again.');
const history = agent.getHistory();
expect(history[history.length - 1]).toEqual({
role: 'assistant',
content: 'I could not generate a response for that. Please try again.',
});
});
it('times out single-turn model calls', async () => {
const mockClient: ModelClient = {
chat: vi.fn().mockImplementation(() => new Promise<ChatResponse>(() => {})),
};
const agent = new NativeAgent({
modelClient: mockClient,
systemPrompt: 'You are helpful.',
modelTimeoutMs: 10,
});
await expect(agent.process('Hi')).rejects.toThrow('Model request timed out after 10ms');
});
});
// Simple test tool
@@ -611,4 +648,65 @@ describe('NativeAgent tool loop', () => {
expect(response).toBe('Got both results');
expect(mockClient.chat).toHaveBeenCalledTimes(2);
});
it('returns fallback text when tool loop final response is empty', async () => {
const mockClient: ModelClient = {
chat: vi
.fn()
.mockResolvedValueOnce({
content: '',
stopReason: 'tool_use',
usage: { inputTokens: 10, outputTokens: 5 },
toolCalls: [{ id: 'call_1', name: 'test.echo', args: { text: 'hello' } }],
})
.mockResolvedValueOnce({
content: '',
stopReason: 'end_turn',
usage: { inputTokens: 12, outputTokens: 4 },
}),
};
const registry = new ToolRegistry();
registry.register(echoTool);
const hooks = new HookEngine({ confirm: [], log: [], silent: [] });
const executor = new ToolExecutor(registry, hooks);
const agent = new NativeAgent({
modelClient: mockClient,
systemPrompt: 'You are helpful.',
toolRegistry: registry,
toolExecutor: executor,
});
const response = await agent.process('echo hello');
expect(response).toBe('I could not generate a response for that. Please try again.');
const history = agent.getHistory();
expect(history[history.length - 1]).toEqual({
role: 'assistant',
content: 'I could not generate a response for that. Please try again.',
});
});
it('times out tool-loop model calls and returns an error message', async () => {
const mockClient: ModelClient = {
chat: vi.fn().mockImplementation(() => new Promise<ChatResponse>(() => {})),
};
const registry = new ToolRegistry();
registry.register(echoTool);
const hooks = new HookEngine({ confirm: [], log: [], silent: [] });
const executor = new ToolExecutor(registry, hooks);
const agent = new NativeAgent({
modelClient: mockClient,
systemPrompt: 'You are helpful.',
toolRegistry: registry,
toolExecutor: executor,
modelTimeoutMs: 10,
});
const response = await agent.process('echo hello');
expect(response).toContain('Error in tool loop');
expect(response).toContain('Model request timed out after 10ms');
});
});