From f722cf2bf0720ca170a655d78ad0e14e022d0ff5 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Thu, 5 Feb 2026 13:36:36 -0800 Subject: [PATCH] feat: add /backend command handler to MinimalTui --- src/frontends/tui/minimal.test.ts | 46 ++++++++++++++++++++- src/frontends/tui/minimal.ts | 68 +++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/frontends/tui/minimal.test.ts b/src/frontends/tui/minimal.test.ts index 33d8980..ecdfe89 100644 --- a/src/frontends/tui/minimal.test.ts +++ b/src/frontends/tui/minimal.test.ts @@ -1,5 +1,7 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { formatPrompt, parseCommand } from './minimal.js'; +import type { ModelConfig } from '../../config/schema.js'; +import { MinimalTui } from './minimal.js'; describe('formatPrompt', () => { it('formats default prompt', () => { @@ -34,3 +36,45 @@ describe('parseCommand (re-exported)', () => { expect(result).toBeNull(); }); }); + +describe('MinimalTui backend command', () => { + it('switches local backend when provider is configured', async () => { + const mockSession = { + id: 'test', + getHistory: () => [], + addMessage: vi.fn(), + clear: vi.fn(), + }; + + const mockRouter = { + getTier: () => 'default' as const, + getAvailableTiers: () => ['default', 'local'], + setTier: vi.fn(() => true), + getLocalProviderName: () => 'ollama', + setLocalClient: vi.fn(), + chat: vi.fn(), + getClient: vi.fn(), + }; + + const localProviders: Record = { + llamacpp: { + provider: 'llamacpp', + model: '', + endpoint: 'http://localhost:8080', + }, + }; + + const tui = new MinimalTui({ + session: mockSession as any, + modelClient: mockRouter as any, + modelRouter: mockRouter as any, + systemPrompt: 'test', + localProviders, + }); + + // Access private method for testing + await (tui as any).handleBackendCommand('llamacpp'); + + expect(mockRouter.setLocalClient).toHaveBeenCalled(); + }); +}); diff --git a/src/frontends/tui/minimal.ts b/src/frontends/tui/minimal.ts index 48576cf..be3626f 100644 --- a/src/frontends/tui/minimal.ts +++ b/src/frontends/tui/minimal.ts @@ -4,6 +4,8 @@ import type { ModelClient, TokenUsage } from '../../models/types.js'; import type { ModelRouter, ModelTier } from '../../models/router.js'; import { parseCommand, getHelpText, resolveModelAlias, type Command } from './commands.js'; import { renderMarkdown } from './markdown.js'; +import type { ModelConfig } from '../../config/schema.js'; +import { OllamaClient, LlamaCppClient } from '../../models/index.js'; export { parseCommand, type Command }; @@ -21,6 +23,8 @@ export interface MinimalTuiConfig { systemPrompt: string; onFullscreen?: () => void; onTransfer?: (target: string) => void; + localProviders?: Record; + currentLocalProvider?: string; } export class MinimalTui { @@ -96,6 +100,10 @@ export class MinimalTui { this.handleModelCommand(command.name); break; + case 'backend': + this.handleBackendCommand(command.provider); + break; + case 'transfer': this.config.onTransfer?.(command.target); break; @@ -129,6 +137,66 @@ export class MinimalTui { } } + private handleBackendCommand(provider?: string): void { + const router = this.config.modelRouter; + if (!router) { + console.log('Backend switching not available.\n'); + return; + } + + if (!provider) { + const current = router.getLocalProviderName() ?? this.config.currentLocalProvider ?? 'unknown'; + const available = this.getAvailableBackends(); + console.log(`Current local backend: ${current}`); + console.log(`Available: ${available.join(', ')}\n`); + return; + } + + const providerConfig = this.config.localProviders?.[provider]; + if (!providerConfig) { + const available = this.getAvailableBackends(); + console.log(`Backend '${provider}' not configured.`); + console.log(`Available: ${available.join(', ')}\n`); + return; + } + + const client = this.createLocalClient(providerConfig); + if (!client) { + console.log(`Failed to create client for '${provider}'.\n`); + return; + } + + router.setLocalClient(client, provider); + console.log(`Switched to backend: ${provider}\n`); + } + + private getAvailableBackends(): string[] { + const backends: string[] = []; + if (this.config.currentLocalProvider) { + backends.push(this.config.currentLocalProvider); + } + if (this.config.localProviders) { + backends.push(...Object.keys(this.config.localProviders)); + } + return [...new Set(backends)]; + } + + private createLocalClient(config: ModelConfig): ModelClient | null { + if (config.provider === 'ollama') { + return new OllamaClient({ + model: config.model, + host: config.endpoint, + }); + } + if (config.provider === 'llamacpp') { + return new LlamaCppClient({ + endpoint: config.endpoint ?? 'http://localhost:8080', + authToken: config.auth_token, + }); + } + return null; + } + private printStatus(): void { console.log(`Session: ${this.config.session.id}`); console.log(`Messages: ${this.config.session.getHistory().length}`);