fix: provider-aware model routing with fallback visibility
- Extract createClientFromConfig() to dispatch on provider field instead of hardcoding all tiers as AnthropicClient - Add fallback/fallbackReason metadata to ChatResponse and ChatStreamEvent so callers know when a fallback model was used - Enhance doctor check to report full model stack and warn on missing API keys for cloud providers - Log fallback warnings in NativeAgent and display them in TUI - Support tier names and local_providers entries in fallback_chain - Add 8 tests for createClientFromConfig covering all provider types
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createClientFromConfig } from './index.js';
|
||||
import { AnthropicClient } from '../models/anthropic.js';
|
||||
import { OpenAIClient } from '../models/openai.js';
|
||||
import { OllamaClient } from '../models/local/ollama.js';
|
||||
import { LlamaCppClient } from '../models/local/llamacpp.js';
|
||||
|
||||
describe('createClientFromConfig', () => {
|
||||
it('creates AnthropicClient for anthropic provider', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'anthropic',
|
||||
model: 'claude-sonnet-4-5-20250514',
|
||||
api_key: 'sk-ant-test',
|
||||
});
|
||||
expect(client).toBeInstanceOf(AnthropicClient);
|
||||
});
|
||||
|
||||
it('creates OpenAIClient for openai provider', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'openai',
|
||||
model: 'gpt-4o',
|
||||
api_key: 'sk-test',
|
||||
});
|
||||
expect(client).toBeInstanceOf(OpenAIClient);
|
||||
});
|
||||
|
||||
it('creates OllamaClient for ollama provider', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'ollama',
|
||||
model: 'llama3.2:1b',
|
||||
endpoint: 'http://localhost:11434',
|
||||
});
|
||||
expect(client).toBeInstanceOf(OllamaClient);
|
||||
});
|
||||
|
||||
it('creates OllamaClient with num_gpu option', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'ollama',
|
||||
model: 'llama3.2:1b',
|
||||
num_gpu: 0,
|
||||
});
|
||||
expect(client).toBeInstanceOf(OllamaClient);
|
||||
});
|
||||
|
||||
it('creates LlamaCppClient for llamacpp provider', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'llamacpp',
|
||||
model: 'ministral-reasoning',
|
||||
endpoint: 'http://localhost:8080',
|
||||
});
|
||||
expect(client).toBeInstanceOf(LlamaCppClient);
|
||||
});
|
||||
|
||||
it('defaults llamacpp endpoint to localhost:8080', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'llamacpp',
|
||||
model: 'test-model',
|
||||
});
|
||||
expect(client).toBeInstanceOf(LlamaCppClient);
|
||||
});
|
||||
|
||||
it('creates OpenAI-compatible client for gemini provider (with warning)', () => {
|
||||
const client = createClientFromConfig({
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-pro',
|
||||
api_key: 'test-key',
|
||||
});
|
||||
// Gemini falls back to OpenAI-compatible client
|
||||
expect(client).toBeInstanceOf(OpenAIClient);
|
||||
});
|
||||
|
||||
it('throws for unknown provider', () => {
|
||||
expect(() => createClientFromConfig({
|
||||
provider: 'unknown' as 'anthropic',
|
||||
model: 'test',
|
||||
})).toThrow('Unknown model provider: unknown');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user