feat: add runtime provider/model switching via /model <tier> <provider/model>
- ModelRouter: add setClient(), labels map, getLabel(), getAllLabels() - TUI commands: parse /model <tier> <provider/model> syntax with autocompletion - TUI minimal: handle provider switching via createClientFromConfig factory - Daemon: wire initial labels into router config - Fix /model alias mappings (opus=complex, sonnet=default, haiku=fast) - Add design doc and update state.json with feature status
This commit is contained in:
@@ -7,6 +7,7 @@ import { parseCommand, getHelpText, resolveModelAlias, getCommandCompletions, ge
|
||||
import { renderMarkdown } from './markdown.js';
|
||||
import type { ModelConfig } from '../../config/schema.js';
|
||||
import { OllamaClient, LlamaCppClient } from '../../models/index.js';
|
||||
import { createClientFromConfig } from '../../daemon/index.js';
|
||||
import { loginGitHub } from '../../auth/index.js';
|
||||
|
||||
export { parseCommand, type Command };
|
||||
@@ -180,7 +181,7 @@ export class MinimalTui {
|
||||
break;
|
||||
|
||||
case 'model':
|
||||
this.handleModelCommand(command.name);
|
||||
this.handleModelCommand(command.name, command.providerModel);
|
||||
break;
|
||||
|
||||
case 'backend':
|
||||
@@ -201,21 +202,51 @@ export class MinimalTui {
|
||||
}
|
||||
}
|
||||
|
||||
private handleModelCommand(name?: string): void {
|
||||
private handleModelCommand(name?: string, providerModel?: string): void {
|
||||
const router = this.config.modelRouter;
|
||||
if (!router) {
|
||||
console.log(`${colors.gray}Model switching not available.${colors.reset}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
const current = router.getTier();
|
||||
const available = router.getAvailableTiers();
|
||||
console.log(`${colors.gray}Current model:${colors.reset} ${current}`);
|
||||
console.log(`${colors.gray}Available:${colors.reset} ${available.join(', ')}\n`);
|
||||
// /model <tier> <provider/model> — change a tier's provider and model
|
||||
if (name && providerModel) {
|
||||
const tier = resolveModelAlias(name);
|
||||
const slashIdx = providerModel.indexOf('/');
|
||||
if (slashIdx === -1) {
|
||||
console.log(`${colors.gray}Invalid format. Use provider/model (e.g. anthropic/claude-sonnet-4)${colors.reset}\n`);
|
||||
return;
|
||||
}
|
||||
const provider = providerModel.slice(0, slashIdx);
|
||||
const model = providerModel.slice(slashIdx + 1);
|
||||
|
||||
try {
|
||||
const client = createClientFromConfig({ provider: provider as 'anthropic', model });
|
||||
router.setClient(tier, client, providerModel);
|
||||
console.log(`${colors.gray}Set ${tier} to:${colors.reset} ${providerModel}\n`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(`${colors.gray}Failed to create client:${colors.reset} ${message}\n`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// /model — show all tiers with labels
|
||||
if (!name) {
|
||||
const current = router.getTier();
|
||||
const available = router.getAvailableTiers();
|
||||
const labels = router.getAllLabels();
|
||||
console.log(`${colors.gray}Active tier:${colors.reset} ${current}`);
|
||||
for (const tier of available) {
|
||||
const label = labels[tier] ?? 'unknown';
|
||||
const marker = tier === current ? ' ←' : '';
|
||||
console.log(` ${tier}: ${label}${marker}`);
|
||||
}
|
||||
console.log();
|
||||
return;
|
||||
}
|
||||
|
||||
// /model <tier> — switch active tier
|
||||
const tier = resolveModelAlias(name);
|
||||
if (router.setTier(tier)) {
|
||||
// Also update the agent tier so chatWithRouter uses the correct client
|
||||
|
||||
Reference in New Issue
Block a user