@parallel-cli/parallel 0.4.1 → 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/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-flash', 'deepseek-v4-pro', 'deepseek-chat', 'deepseek-reasoner'],
19
- defaultModel: 'deepseek-v4-flash',
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', 'gpt-4.1', 'gpt-4.1-mini'],
27
- defaultModel: 'gpt-4o',
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-opus-4-8'],
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: 'OpenRouter',
39
- baseUrl: 'https://openrouter.ai/api/v1',
40
+ name: 'Google Gemini',
41
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
40
42
  apiKey: '',
41
- models: ['openai/gpt-4o', 'anthropic/claude-sonnet-4', 'deepseek/deepseek-chat', 'google/gemini-pro'],
42
- defaultModel: 'openai/gpt-4o',
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: 'Gemini',
46
- baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',
48
+ name: 'xAI Grok',
49
+ baseUrl: 'https://api.x.ai/v1',
47
50
  apiKey: '',
48
- models: ['gemini-3.5-flash', 'gemini-3.5-pro', 'gemini-2.5-pro'],
49
- defaultModel: 'gemini-3.5-flash',
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-latest', 'mistral-small-latest', 'codestral-latest'],
56
- defaultModel: 'mistral-large-latest',
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: 'Groq',
60
- baseUrl: 'https://api.groq.com/openai/v1',
64
+ name: 'Cohere',
65
+ baseUrl: 'https://api.cohere.com/v1',
61
66
  apiKey: '',
62
- models: ['llama-3.3-70b-versatile', 'openai/gpt-oss-120b'],
63
- defaultModel: 'llama-3.3-70b-versatile',
67
+ models: ['command-a', 'command-r-plus'],
68
+ defaultModel: 'command-a',
69
+ category: 'western',
64
70
  },
65
71
  {
66
- name: 'Together',
67
- baseUrl: 'https://api.together.ai/v1',
72
+ name: 'Perplexity',
73
+ baseUrl: 'https://api.perplexity.ai',
68
74
  apiKey: '',
69
- models: ['openai/gpt-oss-120b', 'openai/gpt-oss-20b'],
70
- defaultModel: 'openai/gpt-oss-120b',
75
+ models: ['sonar-pro', 'sonar-deep-research'],
76
+ defaultModel: 'sonar-pro',
77
+ category: 'western',
71
78
  },
79
+ // ── 🇨🇳 Chinese ──
72
80
  {
73
- name: 'xAI',
74
- baseUrl: 'https://api.x.ai/v1',
81
+ name: 'DeepSeek',
82
+ baseUrl: 'https://api.deepseek.com/v1',
75
83
  apiKey: '',
76
- models: ['grok-4', 'grok-3-beta', 'grok-3-mini'],
77
- defaultModel: 'grok-3-beta',
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: 'Perplexity',
81
- baseUrl: 'https://api.perplexity.ai',
89
+ name: 'MiniMax',
90
+ baseUrl: 'https://api.minimax.io/v1',
82
91
  apiKey: '',
83
- models: ['sonar-pro', 'sonar', 'sonar-reasoning'],
84
- defaultModel: 'sonar-pro',
92
+ models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed'],
93
+ defaultModel: 'MiniMax-M3',
94
+ category: 'chinese',
85
95
  },
86
96
  {
87
- name: 'Cohere',
88
- baseUrl: 'https://api.cohere.com/v2',
97
+ name: 'Z.ai / GLM',
98
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
89
99
  apiKey: '',
90
- models: ['command-a', 'command-r-plus', 'command-r'],
91
- defaultModel: 'command-a',
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: 'DeepInfra',
95
- baseUrl: 'https://api.deepinfra.com/v1/openai',
105
+ name: 'Alibaba / Qwen',
106
+ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
96
107
  apiKey: '',
97
- models: ['meta-llama/llama-4-maverick', 'deepseek-ai/deepseek-chat', 'microsoft/wizardlm-2-8x22b'],
98
- defaultModel: 'meta-llama/llama-4-maverick',
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: 'Fireworks',
102
- baseUrl: 'https://api.fireworks.ai/inference/v1',
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',
103
131
  apiKey: '',
104
- models: ['llama-4-maverick', 'llama-4-scout', 'mixtral-8x22b'],
105
- defaultModel: 'llama-4-maverick',
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',
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 ──
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-3.3-70b', 'llama-3.1-8b'],
112
- defaultModel: 'llama-3.3-70b',
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: ['deepseek-r1', 'deepseek-v3', 'llama-3.1-70b'],
119
- defaultModel: 'deepseek-v3',
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.xyz/v1',
228
+ baseUrl: 'https://api.hyperbolic.ai/v1',
124
229
  apiKey: '',
125
- models: ['deepseek-v3', 'llama-4-maverick', 'qwen3-235b'],
126
- defaultModel: 'deepseek-v3',
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: ['llama-4-maverick', 'llama-4-scout', 'deepseek-r1'],
133
- defaultModel: 'llama-4-maverick',
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: ['llama3', 'codellama', 'mistral'],
140
- defaultModel: 'llama3',
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,15 @@ 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 providerReady(p) {
284
+ return !providerNeedsApiKey(p) || p.apiKey.trim().length > 0;
285
+ }
158
286
  export function getProvider(cfg, name) {
159
287
  const n = (name ?? cfg.defaultProvider).toLowerCase();
160
288
  return cfg.providers.find((p) => p.name.toLowerCase() === n) ?? (name ? undefined : cfg.providers[0]);
@@ -185,6 +313,20 @@ function migrate(raw, cfg) {
185
313
  cfg.providers = [p];
186
314
  cfg.defaultProvider = p.name;
187
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
+ }
188
330
  export function loadConfig() {
189
331
  let cfg = { ...DEFAULTS, providers: [] };
190
332
  try {
@@ -201,17 +343,30 @@ export function loadConfig() {
201
343
  catch {
202
344
  // ignore corrupted config
203
345
  }
204
- // Env vars: ensure a DeepSeek provider exists / override the default provider.
205
- const envKey = process.env.PARALLEL_API_KEY || process.env.DEEPSEEK_API_KEY;
206
- if (envKey && cfg.providers.length === 0) {
207
- cfg.providers = [{ ...DEEPSEEK_PROVIDER, apiKey: envKey }];
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 }];
208
352
  cfg.defaultProvider = DEEPSEEK_PROVIDER.name;
209
353
  }
210
- else if (envKey) {
354
+ else if (parallelKey) {
211
355
  const p = getProvider(cfg);
212
356
  if (p)
213
- p.apiKey = envKey;
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
+ }
214
368
  }
369
+ normalizeConfig(cfg);
215
370
  const p = getProvider(cfg);
216
371
  if (p) {
217
372
  if (process.env.PARALLEL_BASE_URL)
@@ -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 m = spec.match(/^([^:]+):(.+)$/);
116
- if (m) {
117
- const provider = getProvider(this.config, m[1].trim());
118
- return provider ? { provider, model: m[2].trim() } : null;
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: spec.trim() };
124
- const any = this.config.providers.find((p) => p.models.includes(spec.trim()));
125
- return any ? { provider: any, model: spec.trim() } : null;
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
  }
@@ -731,7 +733,7 @@ export class Controller extends EventEmitter {
731
733
  if (this.session.providerName.toLowerCase() === name.toLowerCase()) {
732
734
  const fallback = this.config.providers[0];
733
735
  this.session.providerName = fallback?.name ?? '';
734
- this.session.model = fallback?.defaultModel ?? '';
736
+ this.session.model = fallback?.defaultModel || fallback?.models[0] || '';
735
737
  }
736
738
  saveConfig(this.config);
737
739
  this.llmCache.clear();