orchestrator: recover from context overflow on fallback

This commit is contained in:
William Valentin
2026-02-13 21:19:02 -08:00
parent 944b2c916a
commit 151b48310e
4 changed files with 236 additions and 18 deletions
+88
View File
@@ -605,6 +605,94 @@ describe('AgentOrchestrator', () => {
});
});
describe('process()', () => {
it('rolls back tool-loop provider errors, hard-trims on context overflow, and retries once', async () => {
let callCount = 0;
const mockClient: ModelClient = {
chat: vi.fn().mockImplementation(async () => {
callCount++;
if (callCount === 1) {
return {
content: '',
stopReason: 'tool_use',
usage: { inputTokens: 10, outputTokens: 5 },
toolCalls: [{ id: 'call_1', name: 'test.echo', args: { text: 'hi' } }],
} as ChatResponse;
}
if (callCount === 2) {
// Simulate llama.cpp context overflow buried inside an aggregated router error.
throw new Error(
'llama-server error (400): {"error":{"type":"exceedcontextsizeerror","nprompttokens":9183,"nctx":4096}}',
);
}
return {
content: 'ok',
stopReason: 'end_turn',
usage: { inputTokens: 10, outputTokens: 5 },
} as ChatResponse;
}),
};
const router = new ModelRouter({
default: mockClient,
fallbackChain: [],
});
// Minimal Session stub that supports rollback via replaceHistory().
const history: any[] = [];
const session = {
id: 'test',
addMessage: vi.fn((m: any) => { history.push(m); }),
getHistory: vi.fn(() => [...history]),
clear: vi.fn(() => { history.length = 0; }),
replaceHistory: vi.fn((msgs: any[]) => {
history.length = 0;
history.push(...msgs);
}),
getConfig: vi.fn(() => undefined),
setConfig: vi.fn(),
deleteConfig: vi.fn(),
} as any;
const registry = new ToolRegistry();
registry.register({
name: 'test.echo',
description: 'echo',
inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
execute: async (args: any) => ({ success: true, output: String(args.text ?? '') }),
});
const hooks = new HookEngine({ confirm: [], log: [], silent: [] });
const executor = new ToolExecutor(registry, hooks);
const orchestrator = new AgentOrchestrator({
modelRouter: router,
systemPrompt: 'You are helpful.',
session,
toolRegistry: registry,
toolExecutor: executor,
primaryTier: 'default',
delegation: {
compaction: 'fast',
memory_extraction: 'default',
classification: 'complex',
tool_summarisation: 'default',
complex_reasoning: 'complex',
},
maxDelegationDepth: 3,
});
const res = await orchestrator.process('hello');
expect(res).toBe('ok');
// Ensure we didn't persist the low-level error string in history.
const textHistory = history
.map(m => (typeof m.content === 'string' ? m.content : ''))
.join('\n');
expect(textHistory).not.toContain('Error in tool loop');
});
});
describe('setModelTier()', () => {
it('sets model tier on primary agent', () => {
const orchestrator = new AgentOrchestrator({