@parallel-cli/parallel 0.4.1 → 0.4.4
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 +149 -0
- package/README.md +183 -195
- package/dist/agents/agent.js +2 -2
- package/dist/agents/tools.js +47 -5
- package/dist/commands.js +117 -18
- package/dist/config.js +248 -63
- package/dist/controller.js +48 -17
- package/dist/i18n.js +192 -44
- package/dist/index.js +8 -5
- package/dist/pricing.js +162 -54
- package/dist/ui/App.js +208 -102
- package/dist/ui/CommandInput.js +42 -17
- package/dist/ui/SettingsPanel.js +224 -34
- package/dist/ui/Wizard.js +33 -3
- package/dist/ui/views.js +53 -21
- package/package.json +10 -1
package/dist/config.js
CHANGED
|
@@ -13,131 +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',
|
|
57
62
|
},
|
|
58
63
|
{
|
|
59
|
-
name: '
|
|
60
|
-
baseUrl: 'https://api.
|
|
64
|
+
name: 'Cohere',
|
|
65
|
+
baseUrl: 'https://api.cohere.com/v1',
|
|
61
66
|
apiKey: '',
|
|
62
|
-
models: ['
|
|
63
|
-
defaultModel: '
|
|
67
|
+
models: ['command-a', 'command-r-plus'],
|
|
68
|
+
defaultModel: 'command-a',
|
|
69
|
+
category: 'western',
|
|
64
70
|
},
|
|
65
71
|
{
|
|
66
|
-
name: '
|
|
67
|
-
baseUrl: 'https://api.
|
|
72
|
+
name: 'Perplexity',
|
|
73
|
+
baseUrl: 'https://api.perplexity.ai',
|
|
68
74
|
apiKey: '',
|
|
69
|
-
models: ['
|
|
70
|
-
defaultModel: '
|
|
75
|
+
models: ['sonar-pro', 'sonar-deep-research'],
|
|
76
|
+
defaultModel: 'sonar-pro',
|
|
77
|
+
category: 'western',
|
|
71
78
|
},
|
|
79
|
+
// ── 🇨🇳 Chinese ──
|
|
72
80
|
{
|
|
73
|
-
name: '
|
|
74
|
-
baseUrl: 'https://api.
|
|
81
|
+
name: 'DeepSeek',
|
|
82
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
75
83
|
apiKey: '',
|
|
76
|
-
models: ['
|
|
77
|
-
defaultModel: '
|
|
84
|
+
models: ['deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-chat', 'deepseek-reasoner'],
|
|
85
|
+
defaultModel: 'deepseek-v4-pro',
|
|
86
|
+
category: 'chinese',
|
|
78
87
|
},
|
|
79
88
|
{
|
|
80
|
-
name: '
|
|
81
|
-
baseUrl: 'https://api.
|
|
89
|
+
name: 'MiniMax',
|
|
90
|
+
baseUrl: 'https://api.minimax.io/v1',
|
|
82
91
|
apiKey: '',
|
|
83
|
-
models: ['
|
|
84
|
-
defaultModel: '
|
|
92
|
+
models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed'],
|
|
93
|
+
defaultModel: 'MiniMax-M3',
|
|
94
|
+
category: 'chinese',
|
|
85
95
|
},
|
|
86
96
|
{
|
|
87
|
-
name: '
|
|
88
|
-
baseUrl: 'https://
|
|
97
|
+
name: 'Z.ai / GLM',
|
|
98
|
+
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
89
99
|
apiKey: '',
|
|
90
|
-
models: ['
|
|
91
|
-
defaultModel: '
|
|
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',
|
|
92
103
|
},
|
|
93
104
|
{
|
|
94
|
-
name: '
|
|
95
|
-
baseUrl: 'https://
|
|
105
|
+
name: 'Alibaba / Qwen',
|
|
106
|
+
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
96
107
|
apiKey: '',
|
|
97
|
-
models: ['
|
|
98
|
-
defaultModel: '
|
|
108
|
+
models: ['qwen3.7-max', 'qwen3.6-max-preview', 'qwen3.6-plus', 'qwen3.5-coder'],
|
|
109
|
+
defaultModel: 'qwen3.7-max',
|
|
110
|
+
category: 'chinese',
|
|
99
111
|
},
|
|
100
112
|
{
|
|
101
|
-
name: '
|
|
102
|
-
baseUrl: 'https://api.
|
|
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',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'SiliconFlow',
|
|
147
|
+
baseUrl: 'https://api.siliconflow.cn/v1',
|
|
103
148
|
apiKey: '',
|
|
104
|
-
models: ['
|
|
105
|
-
defaultModel: '
|
|
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 ──
|
|
178
|
+
{
|
|
179
|
+
name: 'Groq',
|
|
180
|
+
baseUrl: 'https://api.groq.com/openai/v1',
|
|
181
|
+
apiKey: '',
|
|
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',
|
|
106
185
|
},
|
|
107
186
|
{
|
|
108
187
|
name: 'Cerebras',
|
|
109
188
|
baseUrl: 'https://api.cerebras.ai/v1',
|
|
110
189
|
apiKey: '',
|
|
111
|
-
models: ['llama-
|
|
112
|
-
defaultModel: 'llama-
|
|
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',
|
|
113
217
|
},
|
|
114
218
|
{
|
|
115
219
|
name: 'Novita',
|
|
116
220
|
baseUrl: 'https://api.novita.ai/v3/openai',
|
|
117
221
|
apiKey: '',
|
|
118
|
-
models: ['
|
|
119
|
-
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',
|
|
120
225
|
},
|
|
121
226
|
{
|
|
122
227
|
name: 'Hyperbolic',
|
|
123
|
-
baseUrl: 'https://api.hyperbolic.
|
|
228
|
+
baseUrl: 'https://api.hyperbolic.ai/v1',
|
|
124
229
|
apiKey: '',
|
|
125
|
-
models: ['deepseek-
|
|
126
|
-
defaultModel: '
|
|
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',
|
|
127
233
|
},
|
|
128
234
|
{
|
|
129
235
|
name: 'SambaNova',
|
|
130
236
|
baseUrl: 'https://api.sambanova.ai/v1',
|
|
131
237
|
apiKey: '',
|
|
132
|
-
models: ['
|
|
133
|
-
defaultModel: '
|
|
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',
|
|
134
241
|
},
|
|
242
|
+
// ── 🏠 Local ──
|
|
135
243
|
{
|
|
136
244
|
name: 'Ollama',
|
|
137
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',
|
|
138
255
|
apiKey: '',
|
|
139
|
-
models: ['
|
|
140
|
-
defaultModel: '
|
|
256
|
+
models: ['your-model-here'],
|
|
257
|
+
defaultModel: '',
|
|
258
|
+
category: 'local',
|
|
259
|
+
requiresApiKey: false,
|
|
141
260
|
},
|
|
142
261
|
];
|
|
143
262
|
export const DEFAULTS = {
|
|
@@ -155,6 +274,45 @@ function normalizeApprovalMode(mode) {
|
|
|
155
274
|
return 'auto-safe';
|
|
156
275
|
return 'ask';
|
|
157
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 isPlaceholderModel(model) {
|
|
284
|
+
return !model.trim() || /^your-model-here$/i.test(model.trim());
|
|
285
|
+
}
|
|
286
|
+
export function providerHasUsableModel(p) {
|
|
287
|
+
return !isPlaceholderModel(p.defaultModel || p.models[0] || '');
|
|
288
|
+
}
|
|
289
|
+
export function providerReady(p) {
|
|
290
|
+
return (!providerNeedsApiKey(p) || p.apiKey.trim().length > 0) && providerHasUsableModel(p);
|
|
291
|
+
}
|
|
292
|
+
export async function detectProviderModels(provider, timeoutMs = 2000) {
|
|
293
|
+
let timeout;
|
|
294
|
+
try {
|
|
295
|
+
const controller = new AbortController();
|
|
296
|
+
timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
297
|
+
const resp = await fetch(provider.baseUrl.replace(/\/+$/, '') + '/models', { signal: controller.signal });
|
|
298
|
+
if (!resp.ok)
|
|
299
|
+
return null;
|
|
300
|
+
const data = (await resp.json());
|
|
301
|
+
const models = [
|
|
302
|
+
...(data.data?.map((m) => m.id || m.name).filter(Boolean) ?? []),
|
|
303
|
+
...(data.models?.map((m) => m.name).filter(Boolean) ?? []),
|
|
304
|
+
];
|
|
305
|
+
const unique = [...new Set(models.filter((m) => !isPlaceholderModel(m)))];
|
|
306
|
+
return unique.length > 0 ? { models: unique, defaultModel: unique[0] } : null;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
if (timeout)
|
|
313
|
+
clearTimeout(timeout);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
158
316
|
export function getProvider(cfg, name) {
|
|
159
317
|
const n = (name ?? cfg.defaultProvider).toLowerCase();
|
|
160
318
|
return cfg.providers.find((p) => p.name.toLowerCase() === n) ?? (name ? undefined : cfg.providers[0]);
|
|
@@ -185,6 +343,20 @@ function migrate(raw, cfg) {
|
|
|
185
343
|
cfg.providers = [p];
|
|
186
344
|
cfg.defaultProvider = p.name;
|
|
187
345
|
}
|
|
346
|
+
function normalizeConfig(cfg) {
|
|
347
|
+
cfg.providers = cfg.providers.filter((p) => p && typeof p.name === 'string' && typeof p.baseUrl === 'string');
|
|
348
|
+
for (const p of cfg.providers) {
|
|
349
|
+
if (!Array.isArray(p.models))
|
|
350
|
+
p.models = [];
|
|
351
|
+
if (typeof p.defaultModel !== 'string')
|
|
352
|
+
p.defaultModel = p.models[0] ?? '';
|
|
353
|
+
if (typeof p.apiKey !== 'string')
|
|
354
|
+
p.apiKey = '';
|
|
355
|
+
}
|
|
356
|
+
const defaultExists = cfg.providers.some((p) => p.name.toLowerCase() === cfg.defaultProvider.toLowerCase());
|
|
357
|
+
if (!defaultExists)
|
|
358
|
+
cfg.defaultProvider = cfg.providers[0]?.name ?? '';
|
|
359
|
+
}
|
|
188
360
|
export function loadConfig() {
|
|
189
361
|
let cfg = { ...DEFAULTS, providers: [] };
|
|
190
362
|
try {
|
|
@@ -201,17 +373,30 @@ export function loadConfig() {
|
|
|
201
373
|
catch {
|
|
202
374
|
// ignore corrupted config
|
|
203
375
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
376
|
+
normalizeConfig(cfg);
|
|
377
|
+
// Env vars: PARALLEL_API_KEY targets the current default provider; DEEPSEEK_API_KEY only targets DeepSeek.
|
|
378
|
+
const parallelKey = process.env.PARALLEL_API_KEY;
|
|
379
|
+
const deepseekKey = process.env.DEEPSEEK_API_KEY;
|
|
380
|
+
if (parallelKey && cfg.providers.length === 0) {
|
|
381
|
+
cfg.providers = [{ ...DEEPSEEK_PROVIDER, apiKey: parallelKey }];
|
|
208
382
|
cfg.defaultProvider = DEEPSEEK_PROVIDER.name;
|
|
209
383
|
}
|
|
210
|
-
else if (
|
|
384
|
+
else if (parallelKey) {
|
|
211
385
|
const p = getProvider(cfg);
|
|
212
386
|
if (p)
|
|
213
|
-
p.apiKey =
|
|
387
|
+
p.apiKey = parallelKey;
|
|
388
|
+
}
|
|
389
|
+
if (deepseekKey) {
|
|
390
|
+
const p = getProvider(cfg, DEEPSEEK_PROVIDER.name);
|
|
391
|
+
if (p)
|
|
392
|
+
p.apiKey = deepseekKey;
|
|
393
|
+
else {
|
|
394
|
+
cfg.providers.push({ ...DEEPSEEK_PROVIDER, apiKey: deepseekKey });
|
|
395
|
+
if (!cfg.defaultProvider)
|
|
396
|
+
cfg.defaultProvider = DEEPSEEK_PROVIDER.name;
|
|
397
|
+
}
|
|
214
398
|
}
|
|
399
|
+
normalizeConfig(cfg);
|
|
215
400
|
const p = getProvider(cfg);
|
|
216
401
|
if (p) {
|
|
217
402
|
if (process.env.PARALLEL_BASE_URL)
|
package/dist/controller.js
CHANGED
|
@@ -72,6 +72,7 @@ export class Controller extends EventEmitter {
|
|
|
72
72
|
conversationFiles = new Map();
|
|
73
73
|
/** The session restored at startup (source of /restore conversations). */
|
|
74
74
|
loadedSession = null;
|
|
75
|
+
sessionOnlyProvider = null;
|
|
75
76
|
constructor(config, projectRoot) {
|
|
76
77
|
super();
|
|
77
78
|
this.config = config;
|
|
@@ -108,21 +109,30 @@ export class Controller extends EventEmitter {
|
|
|
108
109
|
// ---------- providers / models ----------
|
|
109
110
|
/** Provider used by the current session (falls back to the global default). */
|
|
110
111
|
sessionProvider() {
|
|
112
|
+
if (this.sessionOnlyProvider &&
|
|
113
|
+
this.session.providerName.toLowerCase() === this.sessionOnlyProvider.name.toLowerCase()) {
|
|
114
|
+
return this.sessionOnlyProvider;
|
|
115
|
+
}
|
|
111
116
|
return getProvider(this.config, this.session.providerName || undefined);
|
|
112
117
|
}
|
|
113
118
|
/** Resolve "model" or "provider:model" against the configured providers. */
|
|
114
119
|
resolveModel(spec) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
const trimmed = spec.trim();
|
|
121
|
+
const sep = trimmed.indexOf(':');
|
|
122
|
+
if (sep > 0) {
|
|
123
|
+
const left = trimmed.slice(0, sep).trim();
|
|
124
|
+
const provider = this.sessionOnlyProvider && this.sessionOnlyProvider.name.toLowerCase() === left.toLowerCase()
|
|
125
|
+
? this.sessionOnlyProvider
|
|
126
|
+
: getProvider(this.config, left);
|
|
127
|
+
if (provider)
|
|
128
|
+
return { provider, model: trimmed.slice(sep + 1).trim() };
|
|
119
129
|
}
|
|
120
130
|
// bare model name: current session provider first, then any provider listing it
|
|
121
131
|
const cur = this.sessionProvider();
|
|
122
132
|
if (cur)
|
|
123
|
-
return { provider: cur, model:
|
|
124
|
-
const any = this.config.providers.find((p) => p.models.includes(
|
|
125
|
-
return any ? { provider: any, model:
|
|
133
|
+
return { provider: cur, model: trimmed };
|
|
134
|
+
const any = this.config.providers.find((p) => p.models.includes(trimmed));
|
|
135
|
+
return any ? { provider: any, model: trimmed } : null;
|
|
126
136
|
}
|
|
127
137
|
llmFor(provider, model) {
|
|
128
138
|
const key = JSON.stringify([provider.name, provider.baseUrl, provider.apiKey, model]);
|
|
@@ -229,7 +239,7 @@ export class Controller extends EventEmitter {
|
|
|
229
239
|
return;
|
|
230
240
|
const [q] = this.questions.splice(idx, 1);
|
|
231
241
|
this.board.log('', 'system', auto ? t('m.qAuto', { name: q.agentName, answer }) : t('m.qAnswered', { name: q.agentName, answer }));
|
|
232
|
-
q.resolve(answer);
|
|
242
|
+
q.resolve({ answer, auto });
|
|
233
243
|
this.emit('update');
|
|
234
244
|
}
|
|
235
245
|
// ---------- agents ----------
|
|
@@ -380,7 +390,7 @@ export class Controller extends EventEmitter {
|
|
|
380
390
|
respawnAgent(name) {
|
|
381
391
|
const sa = this.loadedSession?.agents.find((a) => a.name.toLowerCase() === name.toLowerCase());
|
|
382
392
|
if (!sa)
|
|
383
|
-
return 'no-
|
|
393
|
+
return 'no-agent';
|
|
384
394
|
if (!sa.conversation || !fs.existsSync(sa.conversation))
|
|
385
395
|
return 'no-conversation';
|
|
386
396
|
let history;
|
|
@@ -425,7 +435,7 @@ export class Controller extends EventEmitter {
|
|
|
425
435
|
for (const req of this.approvals.splice(0))
|
|
426
436
|
req.resolve(false);
|
|
427
437
|
for (const q of this.questions.splice(0))
|
|
428
|
-
q.resolve(q.options[q.recommended] ?? '');
|
|
438
|
+
q.resolve({ answer: q.options[q.recommended] ?? '', auto: true });
|
|
429
439
|
}
|
|
430
440
|
sendToAgent(name, content) {
|
|
431
441
|
const a = this.findAgent(name);
|
|
@@ -436,6 +446,15 @@ export class Controller extends EventEmitter {
|
|
|
436
446
|
}
|
|
437
447
|
broadcast(content) {
|
|
438
448
|
this.board.addNote('user', 'all', content);
|
|
449
|
+
let n = 0;
|
|
450
|
+
for (const [id, agent] of this.agents.entries()) {
|
|
451
|
+
const info = this.board.agents.get(id);
|
|
452
|
+
if (!info || ['done', 'error', 'stopped'].includes(info.state))
|
|
453
|
+
continue;
|
|
454
|
+
agent.instruct(content);
|
|
455
|
+
n++;
|
|
456
|
+
}
|
|
457
|
+
return n;
|
|
439
458
|
}
|
|
440
459
|
hasRunningAgents() {
|
|
441
460
|
return [...this.board.agents.values()].some((a) => ['working', 'thinking', 'listening', 'waiting', 'paused', 'idle'].includes(a.state));
|
|
@@ -462,8 +481,10 @@ export class Controller extends EventEmitter {
|
|
|
462
481
|
continue;
|
|
463
482
|
const conflict = this.board.changes.some((c2) => c2.id > c.id && c2.path === c.path && c2.agentId !== info.id);
|
|
464
483
|
try {
|
|
465
|
-
const
|
|
466
|
-
|
|
484
|
+
const root = path.resolve(this.projectRoot);
|
|
485
|
+
const abs = path.resolve(root, c.path);
|
|
486
|
+
const rel = path.relative(root, abs);
|
|
487
|
+
if (rel.startsWith('..') || path.isAbsolute(rel))
|
|
467
488
|
return 'none';
|
|
468
489
|
fs.writeFileSync(abs, c.before);
|
|
469
490
|
}
|
|
@@ -628,7 +649,7 @@ export class Controller extends EventEmitter {
|
|
|
628
649
|
return { file, data: JSON.parse(fs.readFileSync(file, 'utf8')) };
|
|
629
650
|
})
|
|
630
651
|
.sort((a, b) => (a.data.savedAt < b.data.savedAt ? 1 : -1))
|
|
631
|
-
.slice(0,
|
|
652
|
+
.slice(0, 20);
|
|
632
653
|
}
|
|
633
654
|
catch {
|
|
634
655
|
return [];
|
|
@@ -669,11 +690,19 @@ export class Controller extends EventEmitter {
|
|
|
669
690
|
const p = getProvider(this.config, name);
|
|
670
691
|
if (!p)
|
|
671
692
|
return false;
|
|
693
|
+
this.sessionOnlyProvider = null;
|
|
672
694
|
this.session.providerName = p.name;
|
|
673
|
-
this.session.model = p.defaultModel;
|
|
695
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
674
696
|
this.emit('update');
|
|
675
697
|
return true;
|
|
676
698
|
}
|
|
699
|
+
setSessionProviderConfig(p) {
|
|
700
|
+
this.sessionOnlyProvider = p;
|
|
701
|
+
this.session.providerName = p.name;
|
|
702
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
703
|
+
this.llmCache.clear();
|
|
704
|
+
this.emit('update');
|
|
705
|
+
}
|
|
677
706
|
setSessionApprovalMode(mode) {
|
|
678
707
|
this.session.approvalMode = mode;
|
|
679
708
|
this.emit('update');
|
|
@@ -685,16 +714,18 @@ export class Controller extends EventEmitter {
|
|
|
685
714
|
// ---------- GLOBAL settings (/settings) — persisted ----------
|
|
686
715
|
saveProvider(p) {
|
|
687
716
|
upsertProvider(this.config, p);
|
|
717
|
+
if (this.sessionOnlyProvider?.name.toLowerCase() === p.name.toLowerCase())
|
|
718
|
+
this.sessionOnlyProvider = null;
|
|
688
719
|
this.llmCache.clear();
|
|
689
720
|
// if the session points at this provider, refresh its view
|
|
690
721
|
if (this.session.providerName.toLowerCase() === p.name.toLowerCase()) {
|
|
691
722
|
this.session.providerName = p.name;
|
|
692
723
|
if (!p.models.includes(this.session.model))
|
|
693
|
-
this.session.model = p.defaultModel;
|
|
724
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
694
725
|
}
|
|
695
726
|
if (!this.session.providerName) {
|
|
696
727
|
this.session.providerName = p.name;
|
|
697
|
-
this.session.model = p.defaultModel;
|
|
728
|
+
this.session.model = p.defaultModel || p.models[0] || '';
|
|
698
729
|
}
|
|
699
730
|
this.emit('update');
|
|
700
731
|
}
|
|
@@ -731,7 +762,7 @@ export class Controller extends EventEmitter {
|
|
|
731
762
|
if (this.session.providerName.toLowerCase() === name.toLowerCase()) {
|
|
732
763
|
const fallback = this.config.providers[0];
|
|
733
764
|
this.session.providerName = fallback?.name ?? '';
|
|
734
|
-
this.session.model = fallback?.defaultModel
|
|
765
|
+
this.session.model = fallback?.defaultModel || fallback?.models[0] || '';
|
|
735
766
|
}
|
|
736
767
|
saveConfig(this.config);
|
|
737
768
|
this.llmCache.clear();
|