fix: TUI now uses shared model router with auto-fallback support
The TUI was building its own ModelRouter with a duplicated client factory that lacked auto same-model fallback, local_providers resolution, retry config, and per-tier fallback logic. When Anthropic failed, it skipped GitHub Models and fell straight to the local Ollama model. Replace the duplicated ~50-line createClient + router setup in tui.ts with a single call to the daemon's createModelRouter(), which already handles all of these correctly. This removes ~50 lines of duplicated code and ensures TUI and daemon have identical fallback behavior.
This commit is contained in:
+4
-60
@@ -43,77 +43,21 @@ export function registerTuiCommand(program: Command): void {
|
|||||||
|
|
||||||
// Dynamic imports to keep CLI startup fast
|
// Dynamic imports to keep CLI startup fast
|
||||||
const { SessionStore, SessionManager } = await import('../session/index.js');
|
const { SessionStore, SessionManager } = await import('../session/index.js');
|
||||||
const { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, GitHubModelsClient, GeminiClient, BedrockClient, ModelRouter } = await import('../models/index.js');
|
|
||||||
const { MinimalTui, startFullscreenTui } = await import('../frontends/tui/index.js');
|
const { MinimalTui, startFullscreenTui } = await import('../frontends/tui/index.js');
|
||||||
const { NativeAgent } = await import('../backends/index.js');
|
const { NativeAgent } = await import('../backends/index.js');
|
||||||
const { ToolRegistry, ToolExecutor, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager } = await import('../tools/index.js');
|
const { ToolRegistry, ToolExecutor, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager } = await import('../tools/index.js');
|
||||||
const { HookEngine } = await import('../hooks/index.js');
|
const { HookEngine } = await import('../hooks/index.js');
|
||||||
|
const { createModelRouter } = await import('../daemon/index.js');
|
||||||
|
|
||||||
const dataDir = resolve(homedir(), '.local/share/flynn');
|
const dataDir = resolve(homedir(), '.local/share/flynn');
|
||||||
mkdirSync(dataDir, { recursive: true });
|
mkdirSync(dataDir, { recursive: true });
|
||||||
|
|
||||||
const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
|
const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
|
||||||
const sessionManager = new SessionManager(sessionStore);
|
const sessionManager = new SessionManager(sessionStore);
|
||||||
const models = config.models;
|
|
||||||
|
|
||||||
// Provider-agnostic client factory for TUI
|
// Reuse the daemon's model router factory — includes auto-fallback,
|
||||||
function createClient(cfg: typeof models.default) {
|
// local_providers, retry config, and per-tier fallback logic.
|
||||||
switch (cfg.provider) {
|
const modelRouter = createModelRouter(config);
|
||||||
case 'anthropic':
|
|
||||||
return new AnthropicClient({ model: cfg.model, apiKey: cfg.api_key, authToken: cfg.auth_token });
|
|
||||||
case 'openai':
|
|
||||||
return new OpenAIClient({ model: cfg.model, apiKey: cfg.api_key });
|
|
||||||
case 'gemini':
|
|
||||||
return new GeminiClient({ model: cfg.model, apiKey: cfg.api_key });
|
|
||||||
case 'ollama':
|
|
||||||
return new OllamaClient({ model: cfg.model, host: cfg.endpoint, numGpu: cfg.num_gpu });
|
|
||||||
case 'llamacpp':
|
|
||||||
return new LlamaCppClient({ endpoint: cfg.endpoint ?? 'http://localhost:8080', model: cfg.model, authToken: cfg.auth_token });
|
|
||||||
case 'openrouter':
|
|
||||||
return new OpenAIClient({ model: cfg.model, apiKey: cfg.api_key ?? process.env.OPENROUTER_API_KEY, baseURL: cfg.endpoint ?? 'https://openrouter.ai/api/v1' });
|
|
||||||
case 'bedrock':
|
|
||||||
return new BedrockClient({ model: cfg.model, region: cfg.endpoint, accessKeyId: cfg.api_key, secretAccessKey: cfg.auth_token });
|
|
||||||
case 'github':
|
|
||||||
return new GitHubModelsClient({
|
|
||||||
model: cfg.model,
|
|
||||||
apiKey: cfg.api_key,
|
|
||||||
endpoint: cfg.endpoint,
|
|
||||||
onLoginRequired: async () => {
|
|
||||||
const { loginGitHub } = await import('../auth/index.js');
|
|
||||||
console.log('\nGitHub authentication required. Starting login flow...');
|
|
||||||
return loginGitHub((userCode, verificationUri) => {
|
|
||||||
console.log(`\nVisit: ${verificationUri}`);
|
|
||||||
console.log(`Enter code: ${userCode}\n`);
|
|
||||||
console.log('Waiting for authorization...');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown provider: ${cfg.provider}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultClient = createClient(models.default);
|
|
||||||
const fastClient = models.fast ? createClient(models.fast) : undefined;
|
|
||||||
const complexClient = models.complex ? createClient(models.complex) : undefined;
|
|
||||||
const localClient = models.local ? createClient(models.local) : undefined;
|
|
||||||
|
|
||||||
const fallbackChain = [];
|
|
||||||
for (const providerName of models.fallback_chain) {
|
|
||||||
if (providerName === 'openai') {
|
|
||||||
fallbackChain.push(new OpenAIClient({ model: 'gpt-4o' }));
|
|
||||||
} else if (providerName === 'local' && localClient) {
|
|
||||||
fallbackChain.push(localClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelRouter = new ModelRouter({
|
|
||||||
default: defaultClient,
|
|
||||||
fast: fastClient,
|
|
||||||
complex: complexClient,
|
|
||||||
local: localClient,
|
|
||||||
fallbackChain,
|
|
||||||
});
|
|
||||||
|
|
||||||
const systemPrompt = loadSystemPrompt();
|
const systemPrompt = loadSystemPrompt();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ export function createAutoFallbackClient(tierConfig: { provider: string; model:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createModelRouter(config: Config): ModelRouter {
|
export function createModelRouter(config: Config): ModelRouter {
|
||||||
const models = config.models;
|
const models = config.models;
|
||||||
|
|
||||||
const defaultClient = createClientFromConfig(models.default);
|
const defaultClient = createClientFromConfig(models.default);
|
||||||
|
|||||||
Reference in New Issue
Block a user