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:
William Valentin
2026-02-07 13:58:34 -08:00
parent f43d6edfe0
commit e12eb3a0be
2 changed files with 5 additions and 61 deletions
+4 -60
View File
@@ -43,77 +43,21 @@ export function registerTuiCommand(program: Command): void {
// Dynamic imports to keep CLI startup fast
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 { NativeAgent } = await import('../backends/index.js');
const { ToolRegistry, ToolExecutor, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager } = await import('../tools/index.js');
const { HookEngine } = await import('../hooks/index.js');
const { createModelRouter } = await import('../daemon/index.js');
const dataDir = resolve(homedir(), '.local/share/flynn');
mkdirSync(dataDir, { recursive: true });
const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
const sessionManager = new SessionManager(sessionStore);
const models = config.models;
// Provider-agnostic client factory for TUI
function createClient(cfg: typeof models.default) {
switch (cfg.provider) {
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,
});
// Reuse the daemon's model router factory — includes auto-fallback,
// local_providers, retry config, and per-tier fallback logic.
const modelRouter = createModelRouter(config);
const systemPrompt = loadSystemPrompt();
+1 -1
View File
@@ -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 defaultClient = createClientFromConfig(models.default);