feat(backend): auto-stop/start daemon when switching backends
- Add local_providers with ollama and llamacpp configurations - /backend command now stops current daemon before starting new one - Start backends as detached processes to avoid blocking TUI - Wait 500ms for daemon to initialize before switching
This commit is contained in:
+11
-9
@@ -55,17 +55,19 @@ models:
|
|||||||
# fallback_chain. Useful for secondary API accounts or self-hosted
|
# fallback_chain. Useful for secondary API accounts or self-hosted
|
||||||
# endpoints that aren't tied to a specific tier.
|
# endpoints that aren't tied to a specific tier.
|
||||||
#
|
#
|
||||||
# local_providers:
|
# Use /backend <name> in the TUI to switch between these providers
|
||||||
# openrouter-backup:
|
local_providers:
|
||||||
# provider: openrouter
|
ollama:
|
||||||
# model: anthropic/claude-sonnet-4
|
provider: ollama
|
||||||
# ollama-big:
|
model: glm-4.7-flash
|
||||||
# provider: ollama
|
endpoint: http://localhost:11434
|
||||||
# model: llama3.1:70b
|
llamacpp:
|
||||||
# endpoint: http://gpu-server:11434
|
provider: llamacpp
|
||||||
|
model: gpt-oss-20b
|
||||||
|
endpoint: http://localhost:8080
|
||||||
#
|
#
|
||||||
# Then reference them in fallback_chain:
|
# Then reference them in fallback_chain:
|
||||||
# fallback_chain: [openrouter-backup, ollama-big, local]
|
# fallback_chain: [ollama, llamacpp, local]
|
||||||
|
|
||||||
hooks:
|
hooks:
|
||||||
confirm:
|
confirm:
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export class MinimalTui {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'backend':
|
case 'backend':
|
||||||
this.handleBackendCommand(command.provider);
|
await this.handleBackendCommand(command.provider);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'login':
|
case 'login':
|
||||||
@@ -277,7 +277,7 @@ export class MinimalTui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBackendCommand(provider?: string): void {
|
private async handleBackendCommand(provider?: string): Promise<void> {
|
||||||
const router = this.config.modelRouter;
|
const router = this.config.modelRouter;
|
||||||
if (!router) {
|
if (!router) {
|
||||||
console.log('Backend switching not available.\n');
|
console.log('Backend switching not available.\n');
|
||||||
@@ -300,6 +300,17 @@ export class MinimalTui {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop current daemon if running
|
||||||
|
const currentBackend = router.getLocalProviderName();
|
||||||
|
if (currentBackend && currentBackend !== provider) {
|
||||||
|
console.log(`${colors.gray}Stopping ${currentBackend}...${colors.reset}`);
|
||||||
|
await this.stopBackend(currentBackend);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new daemon
|
||||||
|
console.log(`${colors.gray}Starting ${provider}...${colors.reset}`);
|
||||||
|
await this.startBackend(provider, providerConfig);
|
||||||
|
|
||||||
const client = this.createLocalClient(providerConfig);
|
const client = this.createLocalClient(providerConfig);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
console.log(`Failed to create client for '${provider}'.\n`);
|
console.log(`Failed to create client for '${provider}'.\n`);
|
||||||
@@ -307,7 +318,53 @@ export class MinimalTui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.setLocalClient(client, provider);
|
router.setLocalClient(client, provider);
|
||||||
console.log(`Switched to backend: ${provider}\n`);
|
console.log(`${colors.gray}Switched to backend: ${provider}${colors.reset}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async stopBackend(provider: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { spawn } = await import('child_process');
|
||||||
|
let processName: string;
|
||||||
|
switch (provider) {
|
||||||
|
case 'ollama':
|
||||||
|
processName = 'ollama';
|
||||||
|
break;
|
||||||
|
case 'llamacpp':
|
||||||
|
processName = 'llama-server';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
spawn('pkill', [processName]).on('close', resolve);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors stopping backends
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startBackend(provider: string, config: ModelConfig): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { spawn } = await import('child_process');
|
||||||
|
const args: string[] = [];
|
||||||
|
|
||||||
|
switch (provider) {
|
||||||
|
case 'ollama':
|
||||||
|
spawn('ollama', ['serve'], { detached: true, stdio: 'ignore' }).unref();
|
||||||
|
break;
|
||||||
|
case 'llamacpp':
|
||||||
|
args.push('--model', config.model);
|
||||||
|
args.push('--port', new URL(config.endpoint ?? 'http://localhost:8080').port || '8080');
|
||||||
|
args.push('--host', '0.0.0.0');
|
||||||
|
spawn('llama-server', args, { detached: true, stdio: 'ignore' }).unref();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait briefly for the daemon to start
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`${colors.gray}Warning: Failed to start ${provider}: ${error instanceof Error ? error.message : String(error)}${colors.reset}\n`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleLoginCommand(provider?: string): Promise<void> {
|
private async handleLoginCommand(provider?: string): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user