@parallel-cli/parallel 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/README.md +137 -121
- package/dist/commands.js +17 -4
- package/dist/config.js +255 -30
- package/dist/controller.js +32 -10
- package/dist/i18n.js +196 -8
- package/dist/index.js +5 -2
- package/dist/pricing.js +166 -31
- package/dist/ui/App.js +199 -45
- package/dist/ui/CommandInput.js +42 -17
- package/dist/ui/SettingsPanel.js +291 -61
- package/dist/ui/Wizard.js +43 -6
- package/dist/ui/views.js +15 -6
- package/package.json +10 -1
package/dist/config.js
CHANGED
|
@@ -13,61 +13,250 @@ export function configFile() {
|
|
|
13
13
|
}
|
|
14
14
|
export const DEEPSEEK_PROVIDER = {
|
|
15
15
|
name: 'DeepSeek',
|
|
16
|
-
baseUrl: 'https://api.deepseek.com',
|
|
16
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
17
17
|
apiKey: '',
|
|
18
|
-
models: ['deepseek-v4-
|
|
19
|
-
defaultModel: 'deepseek-v4-
|
|
18
|
+
models: ['deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-chat', 'deepseek-reasoner'],
|
|
19
|
+
defaultModel: 'deepseek-v4-pro',
|
|
20
20
|
};
|
|
21
21
|
export const PROVIDER_PRESETS = [
|
|
22
|
+
// ── 🇺🇸 Western ──
|
|
22
23
|
{
|
|
23
24
|
name: 'OpenAI',
|
|
24
25
|
baseUrl: 'https://api.openai.com/v1',
|
|
25
26
|
apiKey: '',
|
|
26
|
-
models: ['gpt-4o', 'gpt-4o-mini', 'o4-mini', '
|
|
27
|
-
defaultModel: 'gpt-
|
|
27
|
+
models: ['gpt-5.5', 'gpt-5.5-pro', 'gpt-5.4', 'gpt-5.3-codex', 'gpt-4o', 'gpt-4o-mini', 'o4-mini', 'o3', 'o3-mini', 'o1', 'o1-mini'],
|
|
28
|
+
defaultModel: 'gpt-5.5',
|
|
29
|
+
category: 'western',
|
|
28
30
|
},
|
|
29
|
-
DEEPSEEK_PROVIDER,
|
|
30
31
|
{
|
|
31
32
|
name: 'Anthropic',
|
|
32
|
-
baseUrl: 'https://api.anthropic.com/v1
|
|
33
|
+
baseUrl: 'https://api.anthropic.com/v1',
|
|
33
34
|
apiKey: '',
|
|
34
|
-
models: ['claude-sonnet-4-6', 'claude-
|
|
35
|
+
models: ['claude-opus-4-8', 'claude-opus-4-7', 'claude-sonnet-4-6', 'claude-haiku-4-5'],
|
|
35
36
|
defaultModel: 'claude-sonnet-4-6',
|
|
37
|
+
category: 'western',
|
|
36
38
|
},
|
|
37
39
|
{
|
|
38
|
-
name: '
|
|
39
|
-
baseUrl: 'https://
|
|
40
|
+
name: 'Google Gemini',
|
|
41
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
40
42
|
apiKey: '',
|
|
41
|
-
models: ['
|
|
42
|
-
defaultModel: '
|
|
43
|
+
models: ['gemini-3.1-pro', 'gemini-3.5-flash', 'gemini-3-flash', 'gemini-3.1-flash-lite'],
|
|
44
|
+
defaultModel: 'gemini-3.5-flash',
|
|
45
|
+
category: 'western',
|
|
43
46
|
},
|
|
44
47
|
{
|
|
45
|
-
name: '
|
|
46
|
-
baseUrl: 'https://
|
|
48
|
+
name: 'xAI Grok',
|
|
49
|
+
baseUrl: 'https://api.x.ai/v1',
|
|
47
50
|
apiKey: '',
|
|
48
|
-
models: ['
|
|
49
|
-
defaultModel: '
|
|
51
|
+
models: ['grok-4', 'grok-4-fast-reasoning', 'grok-3', 'grok-code-fast-1'],
|
|
52
|
+
defaultModel: 'grok-4',
|
|
53
|
+
category: 'western',
|
|
50
54
|
},
|
|
51
55
|
{
|
|
52
56
|
name: 'Mistral',
|
|
53
57
|
baseUrl: 'https://api.mistral.ai/v1',
|
|
54
58
|
apiKey: '',
|
|
55
|
-
models: ['mistral-large-
|
|
56
|
-
defaultModel: 'mistral-large-
|
|
59
|
+
models: ['mistral-large-2', 'magistral-medium', 'codestral-latest', 'mistral-small-latest'],
|
|
60
|
+
defaultModel: 'mistral-large-2',
|
|
61
|
+
category: 'western',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Cohere',
|
|
65
|
+
baseUrl: 'https://api.cohere.com/v1',
|
|
66
|
+
apiKey: '',
|
|
67
|
+
models: ['command-a', 'command-r-plus'],
|
|
68
|
+
defaultModel: 'command-a',
|
|
69
|
+
category: 'western',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Perplexity',
|
|
73
|
+
baseUrl: 'https://api.perplexity.ai',
|
|
74
|
+
apiKey: '',
|
|
75
|
+
models: ['sonar-pro', 'sonar-deep-research'],
|
|
76
|
+
defaultModel: 'sonar-pro',
|
|
77
|
+
category: 'western',
|
|
78
|
+
},
|
|
79
|
+
// ── 🇨🇳 Chinese ──
|
|
80
|
+
{
|
|
81
|
+
name: 'DeepSeek',
|
|
82
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
83
|
+
apiKey: '',
|
|
84
|
+
models: ['deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-chat', 'deepseek-reasoner'],
|
|
85
|
+
defaultModel: 'deepseek-v4-pro',
|
|
86
|
+
category: 'chinese',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'MiniMax',
|
|
90
|
+
baseUrl: 'https://api.minimax.io/v1',
|
|
91
|
+
apiKey: '',
|
|
92
|
+
models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed'],
|
|
93
|
+
defaultModel: 'MiniMax-M3',
|
|
94
|
+
category: 'chinese',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'Z.ai / GLM',
|
|
98
|
+
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
99
|
+
apiKey: '',
|
|
100
|
+
models: ['glm-5.2', 'glm-5.1', 'glm-4.7', 'glm-4.7-flash', 'glm-5v-turbo'],
|
|
101
|
+
defaultModel: 'glm-5.2',
|
|
102
|
+
category: 'chinese',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Alibaba / Qwen',
|
|
106
|
+
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
107
|
+
apiKey: '',
|
|
108
|
+
models: ['qwen3.7-max', 'qwen3.6-max-preview', 'qwen3.6-plus', 'qwen3.5-coder'],
|
|
109
|
+
defaultModel: 'qwen3.7-max',
|
|
110
|
+
category: 'chinese',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'Moonshot / Kimi',
|
|
114
|
+
baseUrl: 'https://api.moonshot.cn/v1',
|
|
115
|
+
apiKey: '',
|
|
116
|
+
models: ['kimi-k2.6', 'kimi-k2.7-code', 'kimi-k2.5', 'moonshot-v1-128k'],
|
|
117
|
+
defaultModel: 'kimi-k2.6',
|
|
118
|
+
category: 'chinese',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'Xiaomi / MiMo',
|
|
122
|
+
baseUrl: 'https://api.minimaxi.com/v1',
|
|
123
|
+
apiKey: '',
|
|
124
|
+
models: ['mimo-v2-pro', 'mimo-v2-omni'],
|
|
125
|
+
defaultModel: 'mimo-v2-pro',
|
|
126
|
+
category: 'chinese',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'StepFun',
|
|
130
|
+
baseUrl: 'https://api.stepfun.com/v1',
|
|
131
|
+
apiKey: '',
|
|
132
|
+
models: ['step-2-16k'],
|
|
133
|
+
defaultModel: 'step-2-16k',
|
|
134
|
+
category: 'chinese',
|
|
135
|
+
},
|
|
136
|
+
// ── 🌐 Gateways ──
|
|
137
|
+
{
|
|
138
|
+
name: 'OpenRouter',
|
|
139
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
140
|
+
apiKey: '',
|
|
141
|
+
models: ['openai/gpt-5.5', 'anthropic/claude-sonnet-4-6', 'google/gemini-3.5-flash', 'deepseek/deepseek-v4-pro', 'meta-llama/llama-4-maverick', 'mistralai/mistral-large-2'],
|
|
142
|
+
defaultModel: 'openai/gpt-5.5',
|
|
143
|
+
category: 'gateways',
|
|
57
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: 'SiliconFlow',
|
|
147
|
+
baseUrl: 'https://api.siliconflow.cn/v1',
|
|
148
|
+
apiKey: '',
|
|
149
|
+
models: ['deepseek-ai/DeepSeek-V4-Pro', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen3-Coder-480B', 'glm-4/GLM-5.2', 'moonshotai/Kimi-K2.6'],
|
|
150
|
+
defaultModel: 'deepseek-ai/DeepSeek-V4-Pro',
|
|
151
|
+
category: 'gateways',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'Atlas Cloud',
|
|
155
|
+
baseUrl: 'https://api.atlascloud.ai/v1',
|
|
156
|
+
apiKey: '',
|
|
157
|
+
models: ['deepseek-v4-pro', 'deepseek-r1', 'qwen3.7-max', 'glm-5.2', 'kimi-k2.6', 'llama-4-maverick'],
|
|
158
|
+
defaultModel: 'deepseek-v4-pro',
|
|
159
|
+
category: 'gateways',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'Requesty',
|
|
163
|
+
baseUrl: 'https://api.requesty.ai/api/v1',
|
|
164
|
+
apiKey: '',
|
|
165
|
+
models: ['gpt-5.5', 'claude-sonnet-4-6', 'gemini-3.5-flash', 'deepseek-v4-pro', 'llama-4-maverick', 'mistral-large-2'],
|
|
166
|
+
defaultModel: 'gpt-5.5',
|
|
167
|
+
category: 'gateways',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'Vercel AI Gateway',
|
|
171
|
+
baseUrl: 'https://api.vercel.ai/v1',
|
|
172
|
+
apiKey: '',
|
|
173
|
+
models: ['gpt-5.5', 'claude-sonnet-4-6', 'gemini-3.5-flash', 'deepseek-v4-pro', 'llama-4-maverick'],
|
|
174
|
+
defaultModel: 'gpt-5.5',
|
|
175
|
+
category: 'gateways',
|
|
176
|
+
},
|
|
177
|
+
// ── ⚡ Inference ──
|
|
58
178
|
{
|
|
59
179
|
name: 'Groq',
|
|
60
180
|
baseUrl: 'https://api.groq.com/openai/v1',
|
|
61
181
|
apiKey: '',
|
|
62
|
-
models: ['
|
|
63
|
-
defaultModel: '
|
|
182
|
+
models: ['qwen-2.5-coder-32b', 'deepseek-r1-distill-llama-70b', 'kimi-k2.6', 'llama-3.3-70b-versatile'],
|
|
183
|
+
defaultModel: 'qwen-2.5-coder-32b',
|
|
184
|
+
category: 'inference',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'Cerebras',
|
|
188
|
+
baseUrl: 'https://api.cerebras.ai/v1',
|
|
189
|
+
apiKey: '',
|
|
190
|
+
models: ['llama-4-maverick-17b-128e-instruct', 'qwen3-coder-480b', 'kimi-k2.6', 'llama-3.3-70b'],
|
|
191
|
+
defaultModel: 'llama-4-maverick-17b-128e-instruct',
|
|
192
|
+
category: 'inference',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'Together AI',
|
|
196
|
+
baseUrl: 'https://api.together.xyz/v1',
|
|
197
|
+
apiKey: '',
|
|
198
|
+
models: ['meta-llama/Llama-4-Maverick-17B-128E-Instruct', 'deepseek-ai/DeepSeek-V3', 'Qwen/Qwen3-Coder-480B', 'moonshotai/Kimi-K2.6'],
|
|
199
|
+
defaultModel: 'meta-llama/Llama-4-Maverick-17B-128E-Instruct',
|
|
200
|
+
category: 'inference',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Fireworks',
|
|
204
|
+
baseUrl: 'https://api.fireworks.ai/inference/v1',
|
|
205
|
+
apiKey: '',
|
|
206
|
+
models: ['accounts/fireworks/models/llama4-maverick-17b', 'accounts/fireworks/models/deepseek-v3', 'accounts/fireworks/models/qwen3-coder-480b', 'accounts/fireworks/models/kimi-k2.6'],
|
|
207
|
+
defaultModel: 'accounts/fireworks/models/llama4-maverick-17b',
|
|
208
|
+
category: 'inference',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'DeepInfra',
|
|
212
|
+
baseUrl: 'https://api.deepinfra.com/v1/openai',
|
|
213
|
+
apiKey: '',
|
|
214
|
+
models: ['meta-llama/Llama-4-Maverick-17B-128E', 'deepseek-ai/DeepSeek-V3', 'Qwen/Qwen3-Coder-480B', 'moonshotai/Kimi-K2.6'],
|
|
215
|
+
defaultModel: 'meta-llama/Llama-4-Maverick-17B-128E',
|
|
216
|
+
category: 'inference',
|
|
64
217
|
},
|
|
65
218
|
{
|
|
66
|
-
name: '
|
|
67
|
-
baseUrl: 'https://api.
|
|
219
|
+
name: 'Novita',
|
|
220
|
+
baseUrl: 'https://api.novita.ai/v3/openai',
|
|
68
221
|
apiKey: '',
|
|
69
|
-
models: ['
|
|
70
|
-
defaultModel: '
|
|
222
|
+
models: ['meta-llama/llama-4-maverick-17b-128e', 'deepseek/deepseek-v3', 'qwen/qwen3-coder-480b', 'moonshotai/kimi-k2.6'],
|
|
223
|
+
defaultModel: 'meta-llama/llama-4-maverick-17b-128e',
|
|
224
|
+
category: 'inference',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'Hyperbolic',
|
|
228
|
+
baseUrl: 'https://api.hyperbolic.ai/v1',
|
|
229
|
+
apiKey: '',
|
|
230
|
+
models: ['meta-llama/Llama-4-Maverick-17B-128E', 'deepseek-ai/DeepSeek-V3', 'Qwen/Qwen3-Coder-480B', 'moonshotai/Kimi-K2.6'],
|
|
231
|
+
defaultModel: 'meta-llama/Llama-4-Maverick-17B-128E',
|
|
232
|
+
category: 'inference',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'SambaNova',
|
|
236
|
+
baseUrl: 'https://api.sambanova.ai/v1',
|
|
237
|
+
apiKey: '',
|
|
238
|
+
models: ['Meta-Llama-4-Maverick-17B-128E-Instruct', 'DeepSeek-V3', 'Llama-3.3-70B-Instruct'],
|
|
239
|
+
defaultModel: 'Meta-Llama-4-Maverick-17B-128E-Instruct',
|
|
240
|
+
category: 'inference',
|
|
241
|
+
},
|
|
242
|
+
// ── 🏠 Local ──
|
|
243
|
+
{
|
|
244
|
+
name: 'Ollama',
|
|
245
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
246
|
+
apiKey: 'ollama-local',
|
|
247
|
+
models: ['qwen3-coder:480b', 'glm-4.7', 'deepseek-v3', 'kimi-k2', 'llama3.2', 'mistral', 'codellama', 'gemma3'],
|
|
248
|
+
defaultModel: 'qwen3-coder:480b',
|
|
249
|
+
category: 'local',
|
|
250
|
+
requiresApiKey: false,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'vLLM / SGLang',
|
|
254
|
+
baseUrl: 'http://localhost:8000/v1',
|
|
255
|
+
apiKey: '',
|
|
256
|
+
models: ['your-model-here'],
|
|
257
|
+
defaultModel: '',
|
|
258
|
+
category: 'local',
|
|
259
|
+
requiresApiKey: false,
|
|
71
260
|
},
|
|
72
261
|
];
|
|
73
262
|
export const DEFAULTS = {
|
|
@@ -85,6 +274,15 @@ function normalizeApprovalMode(mode) {
|
|
|
85
274
|
return 'auto-safe';
|
|
86
275
|
return 'ask';
|
|
87
276
|
}
|
|
277
|
+
export function isLocalProvider(p) {
|
|
278
|
+
return /localhost|127\.0\.0\.1|0\.0\.0\.0/.test(p.baseUrl);
|
|
279
|
+
}
|
|
280
|
+
export function providerNeedsApiKey(p) {
|
|
281
|
+
return p.requiresApiKey !== false && !isLocalProvider(p);
|
|
282
|
+
}
|
|
283
|
+
export function providerReady(p) {
|
|
284
|
+
return !providerNeedsApiKey(p) || p.apiKey.trim().length > 0;
|
|
285
|
+
}
|
|
88
286
|
export function getProvider(cfg, name) {
|
|
89
287
|
const n = (name ?? cfg.defaultProvider).toLowerCase();
|
|
90
288
|
return cfg.providers.find((p) => p.name.toLowerCase() === n) ?? (name ? undefined : cfg.providers[0]);
|
|
@@ -115,6 +313,20 @@ function migrate(raw, cfg) {
|
|
|
115
313
|
cfg.providers = [p];
|
|
116
314
|
cfg.defaultProvider = p.name;
|
|
117
315
|
}
|
|
316
|
+
function normalizeConfig(cfg) {
|
|
317
|
+
cfg.providers = cfg.providers.filter((p) => p && typeof p.name === 'string' && typeof p.baseUrl === 'string');
|
|
318
|
+
for (const p of cfg.providers) {
|
|
319
|
+
if (!Array.isArray(p.models))
|
|
320
|
+
p.models = [];
|
|
321
|
+
if (typeof p.defaultModel !== 'string')
|
|
322
|
+
p.defaultModel = p.models[0] ?? '';
|
|
323
|
+
if (typeof p.apiKey !== 'string')
|
|
324
|
+
p.apiKey = '';
|
|
325
|
+
}
|
|
326
|
+
const defaultExists = cfg.providers.some((p) => p.name.toLowerCase() === cfg.defaultProvider.toLowerCase());
|
|
327
|
+
if (!defaultExists)
|
|
328
|
+
cfg.defaultProvider = cfg.providers[0]?.name ?? '';
|
|
329
|
+
}
|
|
118
330
|
export function loadConfig() {
|
|
119
331
|
let cfg = { ...DEFAULTS, providers: [] };
|
|
120
332
|
try {
|
|
@@ -131,17 +343,30 @@ export function loadConfig() {
|
|
|
131
343
|
catch {
|
|
132
344
|
// ignore corrupted config
|
|
133
345
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
346
|
+
normalizeConfig(cfg);
|
|
347
|
+
// Env vars: PARALLEL_API_KEY targets the current default provider; DEEPSEEK_API_KEY only targets DeepSeek.
|
|
348
|
+
const parallelKey = process.env.PARALLEL_API_KEY;
|
|
349
|
+
const deepseekKey = process.env.DEEPSEEK_API_KEY;
|
|
350
|
+
if (parallelKey && cfg.providers.length === 0) {
|
|
351
|
+
cfg.providers = [{ ...DEEPSEEK_PROVIDER, apiKey: parallelKey }];
|
|
138
352
|
cfg.defaultProvider = DEEPSEEK_PROVIDER.name;
|
|
139
353
|
}
|
|
140
|
-
else if (
|
|
354
|
+
else if (parallelKey) {
|
|
141
355
|
const p = getProvider(cfg);
|
|
142
356
|
if (p)
|
|
143
|
-
p.apiKey =
|
|
357
|
+
p.apiKey = parallelKey;
|
|
358
|
+
}
|
|
359
|
+
if (deepseekKey) {
|
|
360
|
+
const p = getProvider(cfg, DEEPSEEK_PROVIDER.name);
|
|
361
|
+
if (p)
|
|
362
|
+
p.apiKey = deepseekKey;
|
|
363
|
+
else {
|
|
364
|
+
cfg.providers.push({ ...DEEPSEEK_PROVIDER, apiKey: deepseekKey });
|
|
365
|
+
if (!cfg.defaultProvider)
|
|
366
|
+
cfg.defaultProvider = DEEPSEEK_PROVIDER.name;
|
|
367
|
+
}
|
|
144
368
|
}
|
|
369
|
+
normalizeConfig(cfg);
|
|
145
370
|
const p = getProvider(cfg);
|
|
146
371
|
if (p) {
|
|
147
372
|
if (process.env.PARALLEL_BASE_URL)
|
package/dist/controller.js
CHANGED
|
@@ -112,17 +112,19 @@ export class Controller extends EventEmitter {
|
|
|
112
112
|
}
|
|
113
113
|
/** Resolve "model" or "provider:model" against the configured providers. */
|
|
114
114
|
resolveModel(spec) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
const trimmed = spec.trim();
|
|
116
|
+
const sep = trimmed.indexOf(':');
|
|
117
|
+
if (sep > 0) {
|
|
118
|
+
const provider = getProvider(this.config, trimmed.slice(0, sep).trim());
|
|
119
|
+
if (provider)
|
|
120
|
+
return { provider, model: trimmed.slice(sep + 1).trim() };
|
|
119
121
|
}
|
|
120
122
|
// bare model name: current session provider first, then any provider listing it
|
|
121
123
|
const cur = this.sessionProvider();
|
|
122
124
|
if (cur)
|
|
123
|
-
return { provider: cur, model:
|
|
124
|
-
const any = this.config.providers.find((p) => p.models.includes(
|
|
125
|
-
return any ? { provider: any, model:
|
|
125
|
+
return { provider: cur, model: trimmed };
|
|
126
|
+
const any = this.config.providers.find((p) => p.models.includes(trimmed));
|
|
127
|
+
return any ? { provider: any, model: trimmed } : null;
|
|
126
128
|
}
|
|
127
129
|
llmFor(provider, model) {
|
|
128
130
|
const key = JSON.stringify([provider.name, provider.baseUrl, provider.apiKey, model]);
|
|
@@ -670,7 +672,7 @@ export class Controller extends EventEmitter {
|
|
|
670
672
|
if (!p)
|
|
671
673
|
return false;
|
|
672
674
|
this.session.providerName = p.name;
|
|
673
|
-
this.session.model = p.defaultModel;
|
|
675
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
674
676
|
this.emit('update');
|
|
675
677
|
return true;
|
|
676
678
|
}
|
|
@@ -690,11 +692,11 @@ export class Controller extends EventEmitter {
|
|
|
690
692
|
if (this.session.providerName.toLowerCase() === p.name.toLowerCase()) {
|
|
691
693
|
this.session.providerName = p.name;
|
|
692
694
|
if (!p.models.includes(this.session.model))
|
|
693
|
-
this.session.model = p.defaultModel;
|
|
695
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
694
696
|
}
|
|
695
697
|
if (!this.session.providerName) {
|
|
696
698
|
this.session.providerName = p.name;
|
|
697
|
-
this.session.model = p.defaultModel;
|
|
699
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
698
700
|
}
|
|
699
701
|
this.emit('update');
|
|
700
702
|
}
|
|
@@ -718,6 +720,26 @@ export class Controller extends EventEmitter {
|
|
|
718
720
|
this.emit('update');
|
|
719
721
|
return true;
|
|
720
722
|
}
|
|
723
|
+
/** Remove a provider by name. Clears the default if it was the removed one. */
|
|
724
|
+
removeProvider(name) {
|
|
725
|
+
const idx = this.config.providers.findIndex(p => p.name.toLowerCase() === name.toLowerCase());
|
|
726
|
+
if (idx < 0)
|
|
727
|
+
return false;
|
|
728
|
+
this.config.providers.splice(idx, 1);
|
|
729
|
+
if (this.config.defaultProvider.toLowerCase() === name.toLowerCase()) {
|
|
730
|
+
this.config.defaultProvider = this.config.providers[0]?.name ?? '';
|
|
731
|
+
}
|
|
732
|
+
// If the session was using the removed provider, reset it
|
|
733
|
+
if (this.session.providerName.toLowerCase() === name.toLowerCase()) {
|
|
734
|
+
const fallback = this.config.providers[0];
|
|
735
|
+
this.session.providerName = fallback?.name ?? '';
|
|
736
|
+
this.session.model = fallback?.defaultModel || fallback?.models[0] || '';
|
|
737
|
+
}
|
|
738
|
+
saveConfig(this.config);
|
|
739
|
+
this.llmCache.clear();
|
|
740
|
+
this.emit('update');
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
721
743
|
setGlobalApprovalMode(mode) {
|
|
722
744
|
this.config.approvalMode = mode;
|
|
723
745
|
saveConfig(this.config);
|