diff --git a/README.md b/README.md index 190c61a..3c5fab5 100644 --- a/README.md +++ b/README.md @@ -185,19 +185,46 @@ pnpm tui:fs | Command | Description | |---------|-------------| | `/help` | Show help | +| `/model` | Show all model tiers and which is active | +| `/model ` | Switch active tier (`local`, `default`, `fast`, `complex`, or aliases `ollama`, `sonnet`, `haiku`, `opus`) | +| `/model ` | Hot-swap a tier's provider and model at runtime | +| `/backend [provider]` | Show or switch local backend (`ollama`, `llamacpp`) | +| `/login [provider]` | Authenticate with GitHub (OAuth device flow) | | `/reset` | Clear history | | `/status` | Show session info | | `/compact` | Compact conversation context | | `/usage` | Show token usage and cost | | `/verbose` | Toggle verbose output mode | -| `/local` | Switch to local model | -| `/cloud` | Switch to cloud model | -| `/model` | Show model info and options | | `/pair` | Generate/list/revoke DM pairing codes | | `/fullscreen` | Switch to fullscreen mode | -| `/transfer telegram` | Transfer session to Telegram | +| `/transfer ` | Transfer session to another frontend | | `/quit` | Exit | +#### Runtime Model Switching + +Switch providers and models on the fly without editing config or restarting: + +```bash +# Show current tiers +/model + +# Switch active tier +/model fast +/model complex + +# Hot-swap a tier's provider/model +/model default anthropic/claude-sonnet-4 +/model default zhipuai/glm-4.7 +/model fast github/gpt-4o-mini +/model local ollama/glm-4.7-flash +``` + +The provider name must match a supported provider (`anthropic`, `openai`, `gemini`, `ollama`, `llamacpp`, `openrouter`, `bedrock`, `github`, `zhipuai`, `xai`). Tab completion is available for both tiers and provider names. + +For cloud Zhipu models, ensure `ZHIPUAI_API_KEY` is set or `api_key` is configured in the relevant tier. + +**Note:** The `/model` command works in the TUI only. WebChat sessions inherit the active tier from the daemon. + ## Running as Service ```bash diff --git a/src/config/index.ts b/src/config/index.ts index c913cec..5da0c79 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,2 +1,2 @@ export { loadConfig, deepMerge } from './loader.js'; -export { configSchema, type Config, type TelegramConfig, type ModelConfig, type CronJobConfig, type AgentsConfig, type CompactionConfig, type ToolProfile, type ToolOverrideConfig, type ToolsConfig, type SandboxConfig, type AgentConfigEntry, type RoutingConfig, type ServerConfig } from './schema.js'; +export { configSchema, MODEL_PROVIDERS, type ModelProvider, type Config, type TelegramConfig, type ModelConfig, type CronJobConfig, type AgentsConfig, type CompactionConfig, type ToolProfile, type ToolOverrideConfig, type ToolsConfig, type SandboxConfig, type AgentConfigEntry, type RoutingConfig, type ServerConfig } from './schema.js'; diff --git a/src/config/schema.ts b/src/config/schema.ts index d511002..30329db 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -38,8 +38,13 @@ const serverSchema = z.object({ lock: z.boolean().default(false), }); +/** All supported model provider identifiers. Used by the config schema and TUI autocompletion. */ +export const MODEL_PROVIDERS = ['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp', 'openrouter', 'bedrock', 'github', 'zhipuai', 'xai'] as const; + +export type ModelProvider = (typeof MODEL_PROVIDERS)[number]; + const modelConfigBaseSchema = z.object({ - provider: z.enum(['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp', 'openrouter', 'bedrock', 'github', 'zhipuai', 'xai']), + provider: z.enum(MODEL_PROVIDERS), model: z.string(), endpoint: z.string().optional(), api_key: z.string().optional(), diff --git a/src/frontends/tui/commands.test.ts b/src/frontends/tui/commands.test.ts index 9f7c1f8..036a52b 100644 --- a/src/frontends/tui/commands.test.ts +++ b/src/frontends/tui/commands.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { parseCommand, getHelpText } from './commands.js'; +import { parseCommand, getHelpText, getCommandCompletions, PROVIDER_NAMES } from './commands.js'; +import { MODEL_PROVIDERS } from '../../config/index.js'; describe('parseCommand', () => { it('parses /quit command', () => { @@ -130,3 +131,33 @@ describe('/pair command', () => { expect(parseCommand('/pair revoke telegram 12345')).toEqual({ type: 'pair', action: 'revoke', args: 'telegram 12345' }); }); }); + +describe('PROVIDER_NAMES', () => { + it('matches all providers from the config schema', () => { + expect(PROVIDER_NAMES).toEqual(MODEL_PROVIDERS); + }); + + it('includes zhipuai, openrouter, and xai', () => { + expect(PROVIDER_NAMES).toContain('zhipuai'); + expect(PROVIDER_NAMES).toContain('openrouter'); + expect(PROVIDER_NAMES).toContain('xai'); + }); +}); + +describe('getCommandCompletions', () => { + it('suggests all providers when typing /model default with partial provider', () => { + // Typing any character after the tier triggers provider suggestions + const completions = getCommandCompletions('/model default a'); + expect(completions).toContain('/model default anthropic'); + }); + + it('filters provider suggestions by partial input', () => { + const completions = getCommandCompletions('/model default zh'); + expect(completions).toEqual(['/model default zhipuai']); + }); + + it('completes xai provider', () => { + const completions = getCommandCompletions('/model fast x'); + expect(completions).toEqual(['/model fast xai']); + }); +}); diff --git a/src/frontends/tui/commands.ts b/src/frontends/tui/commands.ts index 207061a..322f7a8 100644 --- a/src/frontends/tui/commands.ts +++ b/src/frontends/tui/commands.ts @@ -139,6 +139,8 @@ Commands: `.trim(); } +import { MODEL_PROVIDERS } from '../../config/index.js'; + export type ModelAlias = 'local' | 'default' | 'fast' | 'complex' | 'opus' | 'sonnet' | 'haiku' | 'ollama'; // List of all slash commands for autocompletion @@ -186,8 +188,8 @@ export const COMMAND_TOOLTIPS: Record = { // Model aliases for /model command autocompletion export const MODEL_ALIASES = ['local', 'default', 'fast', 'complex', 'opus', 'sonnet', 'haiku', 'ollama']; -// Provider names for /model syntax -export const PROVIDER_NAMES = ['anthropic', 'openai', 'github-copilot', 'gemini', 'bedrock', 'ollama', 'llamacpp']; +// Provider names for /model syntax — derived from config schema +export const PROVIDER_NAMES: readonly string[] = MODEL_PROVIDERS; // Model alias descriptions export const MODEL_TOOLTIPS: Record = {