@ducci/jarvis 1.0.97 → 1.0.98
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/package.json +1 -1
- package/src/scripts/onboarding.js +81 -1
- package/src/server/agent.js +5 -1
- package/src/server/config.js +6 -1
- package/src/server/provider.js +6 -0
package/package.json
CHANGED
|
@@ -121,6 +121,7 @@ async function run() {
|
|
|
121
121
|
{ name: 'OpenRouter (access many models via one key)', value: 'openrouter' },
|
|
122
122
|
{ name: 'Anthropic Direct (use your Anthropic API key)', value: 'anthropic' },
|
|
123
123
|
{ name: 'Z.AI Direct (GLM models, use your Z.AI API key)', value: 'z-ai' },
|
|
124
|
+
{ name: 'OpenAI-compatible (any provider with an OpenAI-compatible API)', value: 'openai-compatible' },
|
|
124
125
|
],
|
|
125
126
|
default: settings.provider || 'openrouter',
|
|
126
127
|
}
|
|
@@ -187,6 +188,46 @@ async function run() {
|
|
|
187
188
|
saveEnvVar('ZAI_API_KEY', apiKey);
|
|
188
189
|
console.log(chalk.green('Z.AI API key saved.'));
|
|
189
190
|
}
|
|
191
|
+
} else if (provider === 'openai-compatible') {
|
|
192
|
+
const { baseURL } = await inquirer.prompt([
|
|
193
|
+
{
|
|
194
|
+
type: 'input',
|
|
195
|
+
name: 'baseURL',
|
|
196
|
+
message: 'Enter the base URL of the OpenAI-compatible API (e.g., https://api.example.com/v1):',
|
|
197
|
+
default: settings.customBaseURL || '',
|
|
198
|
+
validate: (input) => input.trim().length > 0 || 'Base URL cannot be empty.',
|
|
199
|
+
}
|
|
200
|
+
]);
|
|
201
|
+
settings.customBaseURL = baseURL.trim();
|
|
202
|
+
|
|
203
|
+
const existingKey = loadEnvVar('CUSTOM_API_KEY');
|
|
204
|
+
apiKey = existingKey;
|
|
205
|
+
|
|
206
|
+
if (existingKey) {
|
|
207
|
+
const { keepKey } = await inquirer.prompt([
|
|
208
|
+
{
|
|
209
|
+
type: 'confirm',
|
|
210
|
+
name: 'keepKey',
|
|
211
|
+
message: 'A CUSTOM_API_KEY is already configured. Do you want to keep it?',
|
|
212
|
+
default: true,
|
|
213
|
+
}
|
|
214
|
+
]);
|
|
215
|
+
if (!keepKey) apiKey = null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!apiKey) {
|
|
219
|
+
const { newKey } = await inquirer.prompt([
|
|
220
|
+
{
|
|
221
|
+
type: 'password',
|
|
222
|
+
name: 'newKey',
|
|
223
|
+
message: 'Enter your API key:',
|
|
224
|
+
validate: (input) => input.length >= 1 || 'API key cannot be empty.',
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
227
|
+
apiKey = newKey;
|
|
228
|
+
saveEnvVar('CUSTOM_API_KEY', apiKey);
|
|
229
|
+
console.log(chalk.green('API key saved.'));
|
|
230
|
+
}
|
|
190
231
|
} else {
|
|
191
232
|
const existingKey = loadEnvVar('OPENROUTER_API_KEY');
|
|
192
233
|
apiKey = existingKey;
|
|
@@ -238,7 +279,17 @@ async function run() {
|
|
|
238
279
|
}
|
|
239
280
|
|
|
240
281
|
if (!selectedModel) {
|
|
241
|
-
if (provider === '
|
|
282
|
+
if (provider === 'openai-compatible') {
|
|
283
|
+
const { manualModel } = await inquirer.prompt([
|
|
284
|
+
{
|
|
285
|
+
type: 'input',
|
|
286
|
+
name: 'manualModel',
|
|
287
|
+
message: 'Enter model ID (e.g., gpt-4o, mistral-large):',
|
|
288
|
+
validate: (input) => input.trim().length > 0 || 'Model ID cannot be empty.',
|
|
289
|
+
}
|
|
290
|
+
]);
|
|
291
|
+
selectedModel = manualModel.trim();
|
|
292
|
+
} else if (provider === 'z-ai') {
|
|
242
293
|
const models = await fetchZaiModels(apiKey);
|
|
243
294
|
let choices;
|
|
244
295
|
if (models.length > 0) {
|
|
@@ -374,8 +425,37 @@ async function run() {
|
|
|
374
425
|
if (!settings.fallbackModel || previousProvider !== provider) {
|
|
375
426
|
if (provider === 'anthropic') settings.fallbackModel = 'claude-haiku-4-5-20251001';
|
|
376
427
|
else if (provider === 'z-ai') settings.fallbackModel = 'glm-4-flash';
|
|
428
|
+
else if (provider === 'openai-compatible') settings.fallbackModel = null;
|
|
377
429
|
else settings.fallbackModel = 'openrouter/free';
|
|
378
430
|
}
|
|
431
|
+
|
|
432
|
+
if (provider === 'openai-compatible') {
|
|
433
|
+
const currentFallback = settings.fallbackModel;
|
|
434
|
+
if (currentFallback) {
|
|
435
|
+
const { keepFallback } = await inquirer.prompt([
|
|
436
|
+
{
|
|
437
|
+
type: 'list',
|
|
438
|
+
name: 'keepFallback',
|
|
439
|
+
message: `Current fallback model is ${chalk.yellow(currentFallback)}. Keep it or change it?`,
|
|
440
|
+
choices: [
|
|
441
|
+
{ name: 'Keep current fallback model', value: true },
|
|
442
|
+
{ name: 'Change fallback model', value: false },
|
|
443
|
+
],
|
|
444
|
+
}
|
|
445
|
+
]);
|
|
446
|
+
if (!keepFallback) settings.fallbackModel = null;
|
|
447
|
+
}
|
|
448
|
+
if (!settings.fallbackModel) {
|
|
449
|
+
const { fallbackModel } = await inquirer.prompt([
|
|
450
|
+
{
|
|
451
|
+
type: 'input',
|
|
452
|
+
name: 'fallbackModel',
|
|
453
|
+
message: 'Enter fallback model ID (leave empty to skip):',
|
|
454
|
+
}
|
|
455
|
+
]);
|
|
456
|
+
settings.fallbackModel = fallbackModel.trim() || null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
379
459
|
if (settings.maxIterations === undefined) {
|
|
380
460
|
settings.maxIterations = 20;
|
|
381
461
|
}
|
package/src/server/agent.js
CHANGED
|
@@ -150,6 +150,9 @@ async function callModelWithFallback(client, config, messages, tools) {
|
|
|
150
150
|
} catch (err) {
|
|
151
151
|
primaryErr = err;
|
|
152
152
|
}
|
|
153
|
+
if (!config.fallbackModel) {
|
|
154
|
+
throw primaryErr;
|
|
155
|
+
}
|
|
153
156
|
try {
|
|
154
157
|
return await callModel(client, config.fallbackModel, messages, tools);
|
|
155
158
|
} catch (fallbackErr) {
|
|
@@ -536,8 +539,9 @@ export async function runAgentLoop(client, config, session, prepareMessages, usa
|
|
|
536
539
|
try {
|
|
537
540
|
parsed = JSON.parse(sanitizeJson(content));
|
|
538
541
|
} catch {
|
|
539
|
-
// Step 1: retry with fallback model
|
|
542
|
+
// Step 1: retry with fallback model (if configured)
|
|
540
543
|
try {
|
|
544
|
+
if (!config.fallbackModel) throw new Error('no fallback model configured');
|
|
541
545
|
const fallbackResult = await callModel(client, config.fallbackModel, preparedMessages, toolDefs);
|
|
542
546
|
accumulateUsage(usageAccum, fallbackResult);
|
|
543
547
|
const fallbackContent = fallbackResult.choices[0]?.message?.content || '';
|
package/src/server/config.js
CHANGED
|
@@ -50,6 +50,10 @@ export function loadConfig() {
|
|
|
50
50
|
} else if (provider === 'z-ai') {
|
|
51
51
|
apiKey = process.env.ZAI_API_KEY;
|
|
52
52
|
if (!apiKey) throw new Error('ZAI_API_KEY not found. Add it to ~/.jarvis/.env first.');
|
|
53
|
+
} else if (provider === 'openai-compatible') {
|
|
54
|
+
apiKey = process.env.CUSTOM_API_KEY;
|
|
55
|
+
if (!apiKey) throw new Error('CUSTOM_API_KEY not found. Run `jarvis setup` first.');
|
|
56
|
+
if (!settings.customBaseURL) throw new Error('customBaseURL not set in settings.json. Run `jarvis setup` first.');
|
|
53
57
|
} else {
|
|
54
58
|
apiKey = process.env.OPENROUTER_API_KEY;
|
|
55
59
|
if (!apiKey) throw new Error('OPENROUTER_API_KEY not found. Run `jarvis setup` first.');
|
|
@@ -71,8 +75,9 @@ export function loadConfig() {
|
|
|
71
75
|
return {
|
|
72
76
|
provider,
|
|
73
77
|
apiKey,
|
|
78
|
+
baseURL: settings.customBaseURL || null,
|
|
74
79
|
selectedModel: settings.selectedModel,
|
|
75
|
-
fallbackModel: settings.fallbackModel || (provider === 'anthropic' ? 'claude-haiku-4-5-20251001' : 'openrouter/free'),
|
|
80
|
+
fallbackModel: settings.fallbackModel || (provider === 'anthropic' ? 'claude-haiku-4-5-20251001' : provider === 'openai-compatible' ? null : 'openrouter/free'),
|
|
76
81
|
maxIterations: settings.maxIterations || 20,
|
|
77
82
|
maxHandoffs: settings.maxHandoffs || 3,
|
|
78
83
|
messageWindow: settings.messageWindow || 300,
|
package/src/server/provider.js
CHANGED
|
@@ -155,6 +155,12 @@ export function createClient(config) {
|
|
|
155
155
|
apiKey: config.apiKey,
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
|
+
if (config.provider === 'openai-compatible') {
|
|
159
|
+
return new OpenAI({
|
|
160
|
+
baseURL: config.baseURL,
|
|
161
|
+
apiKey: config.apiKey,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
158
164
|
// Default: OpenRouter (OpenAI-compatible)
|
|
159
165
|
return new OpenAI({
|
|
160
166
|
baseURL: 'https://openrouter.ai/api/v1',
|