feat(models): add streaming and tier switching to ModelRouter

This commit is contained in:
William Valentin
2026-02-05 10:48:41 -08:00
parent 896a0da10e
commit 9a48c39b07
2 changed files with 122 additions and 3 deletions
+64 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ModelRouter } from './router.js';
import type { ModelClient, ChatResponse } from './types.js';
import type { ModelClient, ChatResponse, ChatStreamEvent } from './types.js';
describe('ModelRouter', () => {
const createMockClient = (name: string, shouldFail = false): ModelClient => ({
@@ -78,3 +78,66 @@ describe('ModelRouter', () => {
expect(defaultClient.chat).not.toHaveBeenCalled();
});
});
describe('ModelRouter streaming', () => {
it('streams from primary client', async () => {
const mockStream = async function* (): AsyncIterable<ChatStreamEvent> {
yield { type: 'content', content: 'Hello' };
yield { type: 'done', usage: { inputTokens: 5, outputTokens: 3 } };
};
const mockClient = {
chat: vi.fn(),
chatStream: vi.fn().mockReturnValue(mockStream()),
};
const router = new ModelRouter({
default: mockClient,
fallbackChain: [],
});
const chunks: string[] = [];
for await (const event of router.chatStream({ messages: [] })) {
if (event.type === 'content' && event.content) {
chunks.push(event.content);
}
}
expect(chunks).toEqual(['Hello']);
});
it('falls back when primary stream fails', async () => {
const failingStream = async function* (): AsyncIterable<ChatStreamEvent> {
yield { type: 'error', error: new Error('Primary failed') };
};
const fallbackStream = async function* (): AsyncIterable<ChatStreamEvent> {
yield { type: 'content', content: 'Fallback' };
yield { type: 'done', usage: { inputTokens: 5, outputTokens: 3 } };
};
const primaryClient = {
chat: vi.fn(),
chatStream: vi.fn().mockReturnValue(failingStream()),
};
const fallbackClient = {
chat: vi.fn(),
chatStream: vi.fn().mockReturnValue(fallbackStream()),
};
const router = new ModelRouter({
default: primaryClient,
fallbackChain: [fallbackClient],
});
const chunks: string[] = [];
for await (const event of router.chatStream({ messages: [] })) {
if (event.type === 'content' && event.content) {
chunks.push(event.content);
}
}
expect(chunks).toEqual(['Fallback']);
});
});