coder-agent 2.9.11 → 2.9.13
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/agent.js +140 -17
- package/dist/config.js +48 -9
- package/dist/index.js +56 -18
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from "path";
|
|
|
3
3
|
import * as fs from "fs/promises";
|
|
4
4
|
import { TOOL_DEFINITIONS, dispatchTool } from "./tools.js";
|
|
5
5
|
import { Memory, getAgentMemoryEntrypoint } from "./memory.js";
|
|
6
|
+
import { getProviderApiKey } from "./config.js";
|
|
6
7
|
// ─── Loading Spinner ──────────────────────────────────────────────────────────
|
|
7
8
|
let spinnerTimer = null;
|
|
8
9
|
let currentFrame = 0;
|
|
@@ -405,21 +406,110 @@ function extractTextToolCalls(content) {
|
|
|
405
406
|
return calls;
|
|
406
407
|
}
|
|
407
408
|
// ─── Gemini API client with Auto-Rotation Fallback ────────────────────────────
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
const lastRequestTimestamps = {};
|
|
410
|
+
function getProviderAndModel(modelName) {
|
|
411
|
+
const lowercase = modelName.toLowerCase();
|
|
412
|
+
if (lowercase.startsWith("groq/")) {
|
|
413
|
+
return { provider: "groq", model: modelName.slice(5) };
|
|
414
|
+
}
|
|
415
|
+
if (lowercase.startsWith("mistral/")) {
|
|
416
|
+
return { provider: "mistral", model: modelName.slice(8) };
|
|
417
|
+
}
|
|
418
|
+
if (lowercase.startsWith("openrouter/")) {
|
|
419
|
+
return { provider: "openrouter", model: modelName.slice(11) };
|
|
420
|
+
}
|
|
421
|
+
if (lowercase.startsWith("nvidia/")) {
|
|
422
|
+
return { provider: "nvidia", model: modelName.slice(7) };
|
|
423
|
+
}
|
|
424
|
+
if (lowercase.startsWith("github/")) {
|
|
425
|
+
return { provider: "github", model: modelName.slice(7) };
|
|
426
|
+
}
|
|
427
|
+
return { provider: "gemini", model: modelName };
|
|
428
|
+
}
|
|
429
|
+
function getProviderEndpoint(provider) {
|
|
430
|
+
switch (provider) {
|
|
431
|
+
case "gemini":
|
|
432
|
+
return "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions";
|
|
433
|
+
case "groq":
|
|
434
|
+
return "https://api.groq.com/openai/v1/chat/completions";
|
|
435
|
+
case "mistral":
|
|
436
|
+
return "https://api.mistral.ai/v1/chat/completions";
|
|
437
|
+
case "openrouter":
|
|
438
|
+
return "https://openrouter.ai/api/v1/chat/completions";
|
|
439
|
+
case "nvidia":
|
|
440
|
+
return "https://integrate.api.nvidia.com/v1/chat/completions";
|
|
441
|
+
case "github":
|
|
442
|
+
return "https://models.inference.ai.azure.com/chat/completions";
|
|
443
|
+
}
|
|
444
|
+
return "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions";
|
|
445
|
+
}
|
|
446
|
+
function getRequiredDelayMs(provider, model) {
|
|
447
|
+
const lowercaseModel = model.toLowerCase();
|
|
448
|
+
switch (provider) {
|
|
449
|
+
case "gemini":
|
|
450
|
+
if (lowercaseModel.includes("pro")) {
|
|
451
|
+
return 30000; // 30s for Pro (2 RPM)
|
|
452
|
+
}
|
|
453
|
+
return 4000; // 4s for Flash (15 RPM)
|
|
454
|
+
case "groq":
|
|
455
|
+
return 2000; // 2s (30 RPM)
|
|
456
|
+
case "mistral":
|
|
457
|
+
return 1500; // 1.5s (1 request/second + buffer)
|
|
458
|
+
case "openrouter":
|
|
459
|
+
return 5000; // 5s for free/shared queue
|
|
460
|
+
case "nvidia":
|
|
461
|
+
return 2000; // 2s (40 RPM)
|
|
462
|
+
case "github":
|
|
463
|
+
if (lowercaseModel.includes("sonnet") || lowercaseModel.includes("gpt-4o")) {
|
|
464
|
+
return 30000; // 30s for flagship (50 RPD)
|
|
465
|
+
}
|
|
466
|
+
return 6000; // 6s for open-weights (150 RPD)
|
|
467
|
+
}
|
|
468
|
+
return 2000; // default fallback
|
|
469
|
+
}
|
|
470
|
+
const DEFAULT_ROTATION_MAP = {
|
|
471
|
+
gemini: [
|
|
412
472
|
"gemini-2.5-flash",
|
|
413
473
|
"gemini-2.5-pro",
|
|
414
474
|
"gemini-3.5-flash",
|
|
415
475
|
"gemini-3.1-flash-lite",
|
|
416
476
|
"gemma-4-31b-it",
|
|
417
|
-
"gemma-4-26b-a4b-it"
|
|
418
|
-
|
|
477
|
+
"gemma-4-26b-a4b-it",
|
|
478
|
+
"gemini-2.0-flash",
|
|
479
|
+
"gemini-2.0-pro-exp"
|
|
480
|
+
],
|
|
481
|
+
groq: [
|
|
482
|
+
"groq/llama-3.3-70b-specdec",
|
|
483
|
+
"groq/gemma-2-9b-it"
|
|
484
|
+
],
|
|
485
|
+
mistral: [
|
|
486
|
+
"mistral/codestral",
|
|
487
|
+
"mistral/mistral-large-latest"
|
|
488
|
+
],
|
|
489
|
+
openrouter: [
|
|
490
|
+
"openrouter/qwen/qwen-2.5-coder-32b-instruct:free",
|
|
491
|
+
"openrouter/deepseek/deepseek-r1:free",
|
|
492
|
+
"openrouter/meta-llama/llama-3.3-70b-instruct:free"
|
|
493
|
+
],
|
|
494
|
+
nvidia: [
|
|
495
|
+
"nvidia/qwen-2.5-coder-32b-instruct",
|
|
496
|
+
"nvidia/deepseek-r1",
|
|
497
|
+
"nvidia/meta/llama-3.3-70b-instruct"
|
|
498
|
+
],
|
|
499
|
+
github: [
|
|
500
|
+
"github/claude-3.5-sonnet",
|
|
501
|
+
"github/gpt-4o",
|
|
502
|
+
"github/meta-llama-3.3-70b-instruct"
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
async function callAPIWithRotationAndThrottling(defaultApiKey, params, maxRetries = 3, initialDelayMs = 1500, signal, silent = false) {
|
|
506
|
+
const { provider: initialProvider } = getProviderAndModel(params.model);
|
|
507
|
+
let rotationList = DEFAULT_ROTATION_MAP[initialProvider] || DEFAULT_ROTATION_MAP.gemini;
|
|
419
508
|
let currentModel = params.model;
|
|
420
509
|
let modelIndex = rotationList.indexOf(currentModel);
|
|
421
510
|
if (modelIndex === -1) {
|
|
422
|
-
|
|
511
|
+
rotationList = [currentModel, ...rotationList];
|
|
512
|
+
modelIndex = 0;
|
|
423
513
|
}
|
|
424
514
|
let attempts = 0;
|
|
425
515
|
while (attempts < maxRetries) {
|
|
@@ -428,19 +518,53 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
428
518
|
abortErr.name = "AbortError";
|
|
429
519
|
throw abortErr;
|
|
430
520
|
}
|
|
521
|
+
const { provider, model: actualModelName } = getProviderAndModel(currentModel);
|
|
522
|
+
const providerKey = await getProviderApiKey(provider) || defaultApiKey;
|
|
523
|
+
if (!providerKey) {
|
|
524
|
+
throw new Error(`API key for provider '${provider}' is missing. Please set it using: /key ${provider} <api_key>`);
|
|
525
|
+
}
|
|
526
|
+
// Apply rate-limiting throttle cooldown
|
|
527
|
+
const requiredDelay = getRequiredDelayMs(provider, actualModelName);
|
|
528
|
+
const lastRequestTime = lastRequestTimestamps[provider] || 0;
|
|
529
|
+
const elapsed = Date.now() - lastRequestTime;
|
|
530
|
+
if (elapsed < requiredDelay) {
|
|
531
|
+
const waitMs = requiredDelay - elapsed;
|
|
532
|
+
if (!silent) {
|
|
533
|
+
updateSpinner("thinking...");
|
|
534
|
+
}
|
|
535
|
+
await new Promise((resolve, reject) => {
|
|
536
|
+
const timer = setTimeout(resolve, waitMs);
|
|
537
|
+
if (signal) {
|
|
538
|
+
const onAbort = () => {
|
|
539
|
+
clearTimeout(timer);
|
|
540
|
+
const abortErr = new Error("The user aborted a request.");
|
|
541
|
+
abortErr.name = "AbortError";
|
|
542
|
+
reject(abortErr);
|
|
543
|
+
};
|
|
544
|
+
signal.addEventListener("abort", onAbort);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
lastRequestTimestamps[provider] = Date.now();
|
|
431
549
|
try {
|
|
432
|
-
const
|
|
550
|
+
const endpoint = getProviderEndpoint(provider);
|
|
551
|
+
const headers = {
|
|
552
|
+
"Content-Type": "application/json",
|
|
553
|
+
"Authorization": `Bearer ${providerKey}`
|
|
554
|
+
};
|
|
555
|
+
if (provider === "openrouter") {
|
|
556
|
+
headers["HTTP-Referer"] = "https://github.com/google/antigravity";
|
|
557
|
+
headers["X-Title"] = "Coder Agent";
|
|
558
|
+
}
|
|
559
|
+
const res = await fetch(endpoint, {
|
|
433
560
|
method: "POST",
|
|
434
|
-
headers
|
|
435
|
-
|
|
436
|
-
"Authorization": `Bearer ${apiKey}`
|
|
437
|
-
},
|
|
438
|
-
body: JSON.stringify({ ...params, model: currentModel }),
|
|
561
|
+
headers,
|
|
562
|
+
body: JSON.stringify({ ...params, model: actualModelName }),
|
|
439
563
|
signal
|
|
440
564
|
});
|
|
441
565
|
if (!res.ok) {
|
|
442
566
|
const errText = await res.text();
|
|
443
|
-
const err = new Error(
|
|
567
|
+
const err = new Error(`${provider.toUpperCase()} API error (${res.status}): ${errText}`);
|
|
444
568
|
err.status = res.status;
|
|
445
569
|
throw err;
|
|
446
570
|
}
|
|
@@ -609,7 +733,6 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
609
733
|
const isRetryableError = status === 429 || status === 503 || (status >= 500 && status < 600) || !status;
|
|
610
734
|
const isModelError = status === 404 || status === 400;
|
|
611
735
|
if (isRetryableError || isModelError) {
|
|
612
|
-
// Rotate model immediately if rate limit or model error occurred
|
|
613
736
|
if (modelIndex + 1 < rotationList.length) {
|
|
614
737
|
modelIndex++;
|
|
615
738
|
const nextModel = rotationList[modelIndex];
|
|
@@ -843,7 +966,7 @@ export class Agent {
|
|
|
843
966
|
}
|
|
844
967
|
iterations++;
|
|
845
968
|
updateSpinner("thinking...");
|
|
846
|
-
const responseObj = await
|
|
969
|
+
const responseObj = await callAPIWithRotationAndThrottling(this.apiKey, {
|
|
847
970
|
model: this.model,
|
|
848
971
|
messages: this.memory.getAll(),
|
|
849
972
|
tools: TOOL_DEFINITIONS,
|
|
@@ -1062,7 +1185,7 @@ Instructions:
|
|
|
1062
1185
|
3. Integrate these new learnings and file catalog updates into the existing memory structure. Keep existing useful learnings/preferences, but clean up duplicates or obsolete info.
|
|
1063
1186
|
4. Keep the content concise, clean, and formatted in Markdown.
|
|
1064
1187
|
5. If there are no new learnings, setup details, file updates, or instructions in the conversation, output EXACTLY the existing memory content. Do not add conversational text, just output the updated/existing markdown content.`;
|
|
1065
|
-
const responseObj = await
|
|
1188
|
+
const responseObj = await callAPIWithRotationAndThrottling(this.apiKey, {
|
|
1066
1189
|
model: this.model,
|
|
1067
1190
|
messages: [{ role: "user", content: prompt }],
|
|
1068
1191
|
temperature: 0.1,
|
package/dist/config.js
CHANGED
|
@@ -53,15 +53,57 @@ async function writeConfig(config) {
|
|
|
53
53
|
console.error(`\n ⚠️ Failed to save config to ${CONFIG_FILE}: ${err.message}`);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
export async function getStoredApiKey() {
|
|
56
|
+
export async function getStoredApiKey(provider) {
|
|
57
57
|
const config = await readConfig();
|
|
58
|
-
|
|
58
|
+
const p = provider?.toLowerCase();
|
|
59
|
+
if (!p || p === "gemini")
|
|
60
|
+
return config.geminiApiKey;
|
|
61
|
+
if (p === "groq")
|
|
62
|
+
return config.groqApiKey;
|
|
63
|
+
if (p === "mistral")
|
|
64
|
+
return config.mistralApiKey;
|
|
65
|
+
if (p === "openrouter")
|
|
66
|
+
return config.openrouterApiKey;
|
|
67
|
+
if (p === "nvidia")
|
|
68
|
+
return config.nvidiaApiKey;
|
|
69
|
+
if (p === "github")
|
|
70
|
+
return config.githubApiKey;
|
|
71
|
+
return undefined;
|
|
59
72
|
}
|
|
60
|
-
export async function saveApiKey(apiKey) {
|
|
73
|
+
export async function saveApiKey(apiKey, provider) {
|
|
61
74
|
const config = await readConfig();
|
|
62
|
-
|
|
75
|
+
const trimmed = apiKey.trim();
|
|
76
|
+
const p = provider?.toLowerCase();
|
|
77
|
+
if (!p || p === "gemini")
|
|
78
|
+
config.geminiApiKey = trimmed;
|
|
79
|
+
else if (p === "groq")
|
|
80
|
+
config.groqApiKey = trimmed;
|
|
81
|
+
else if (p === "mistral")
|
|
82
|
+
config.mistralApiKey = trimmed;
|
|
83
|
+
else if (p === "openrouter")
|
|
84
|
+
config.openrouterApiKey = trimmed;
|
|
85
|
+
else if (p === "nvidia")
|
|
86
|
+
config.nvidiaApiKey = trimmed;
|
|
87
|
+
else if (p === "github")
|
|
88
|
+
config.githubApiKey = trimmed;
|
|
63
89
|
await writeConfig(config);
|
|
64
90
|
}
|
|
91
|
+
export async function getProviderApiKey(provider) {
|
|
92
|
+
const p = provider.toLowerCase();
|
|
93
|
+
if (p === "gemini")
|
|
94
|
+
return process.env.GEMINI_API_KEY || await getStoredApiKey("gemini");
|
|
95
|
+
if (p === "groq")
|
|
96
|
+
return process.env.GROQ_API_KEY || await getStoredApiKey("groq");
|
|
97
|
+
if (p === "mistral")
|
|
98
|
+
return process.env.MISTRAL_API_KEY || await getStoredApiKey("mistral");
|
|
99
|
+
if (p === "openrouter")
|
|
100
|
+
return process.env.OPENROUTER_API_KEY || await getStoredApiKey("openrouter");
|
|
101
|
+
if (p === "nvidia")
|
|
102
|
+
return process.env.NVIDIA_API_KEY || await getStoredApiKey("nvidia");
|
|
103
|
+
if (p === "github")
|
|
104
|
+
return process.env.GITHUB_API_KEY || process.env.GITHUB_TOKEN || await getStoredApiKey("github");
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
65
107
|
export async function getStoredModel() {
|
|
66
108
|
const config = await readConfig();
|
|
67
109
|
return config.defaultModel;
|
|
@@ -72,13 +114,10 @@ export async function saveModel(model) {
|
|
|
72
114
|
await writeConfig(config);
|
|
73
115
|
}
|
|
74
116
|
export async function getStoredGeminiApiKey() {
|
|
75
|
-
|
|
76
|
-
return config.geminiApiKey;
|
|
117
|
+
return getStoredApiKey("gemini");
|
|
77
118
|
}
|
|
78
119
|
export async function saveGeminiApiKey(geminiApiKey) {
|
|
79
|
-
|
|
80
|
-
config.geminiApiKey = geminiApiKey.trim();
|
|
81
|
-
await writeConfig(config);
|
|
120
|
+
await saveApiKey(geminiApiKey, "gemini");
|
|
82
121
|
}
|
|
83
122
|
export async function getLastUsedInfo() {
|
|
84
123
|
const config = await readConfig();
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "module";
|
|
2
3
|
import * as readline from "readline";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
import figlet from "figlet";
|
|
5
6
|
import { Agent } from "./agent.js";
|
|
6
|
-
import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo } from "./config.js";
|
|
7
|
-
const
|
|
7
|
+
import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo, getProviderApiKey } from "./config.js";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const packageJson = require("../package.json");
|
|
10
|
+
const CURRENT_VERSION = packageJson.version;
|
|
8
11
|
const VALID_MODELS = [
|
|
9
12
|
"gemini-2.5-flash",
|
|
10
13
|
"gemini-2.5-pro",
|
|
@@ -15,6 +18,12 @@ const VALID_MODELS = [
|
|
|
15
18
|
"gemma-4-31b-it",
|
|
16
19
|
"gemma-4-26b-a4b-it"
|
|
17
20
|
];
|
|
21
|
+
function isValidModel(modelName) {
|
|
22
|
+
if (VALID_MODELS.includes(modelName))
|
|
23
|
+
return true;
|
|
24
|
+
const prefixes = ["groq/", "mistral/", "openrouter/", "nvidia/", "github/"];
|
|
25
|
+
return prefixes.some(prefix => modelName.toLowerCase().startsWith(prefix));
|
|
26
|
+
}
|
|
18
27
|
function printBanner(modelName) {
|
|
19
28
|
console.clear();
|
|
20
29
|
console.log('');
|
|
@@ -80,6 +89,14 @@ function printHelp() {
|
|
|
80
89
|
console.log(chalk.white(" gemini-2.0-flash — (Deprecated) Ultra-fast, lightweight"));
|
|
81
90
|
console.log(chalk.white(" gemini-2.0-pro-exp — (Deprecated) Experimental reasoning"));
|
|
82
91
|
console.log("");
|
|
92
|
+
console.log(chalk.gray(" Popular Multi-Provider Models (prefixed with provider/):"));
|
|
93
|
+
console.log(chalk.white(" groq/llama-3.3-70b-specdec — Groq Llama 3.3 70B (extremely fast)"));
|
|
94
|
+
console.log(chalk.white(" mistral/codestral — Mistral Codestral (highly rated coding model)"));
|
|
95
|
+
console.log(chalk.white(" openrouter/qwen/qwen-2.5-coder-32b-instruct:free — OpenRouter Qwen 2.5 Coder Free"));
|
|
96
|
+
console.log(chalk.white(" openrouter/deepseek/deepseek-r1:free — OpenRouter DeepSeek R1 Free"));
|
|
97
|
+
console.log(chalk.white(" nvidia/qwen-2.5-coder-32b-instruct — NVIDIA NIM Qwen 2.5 Coder 32B"));
|
|
98
|
+
console.log(chalk.white(" github/claude-3.5-sonnet — GitHub Claude 3.5 Sonnet (requires GitHub token)"));
|
|
99
|
+
console.log("");
|
|
83
100
|
}
|
|
84
101
|
// ─── API Key Bootstrap ────────────────────────────────────────────────────────
|
|
85
102
|
async function promptApiKey() {
|
|
@@ -168,12 +185,12 @@ async function main() {
|
|
|
168
185
|
}
|
|
169
186
|
const storedModel = await getStoredModel();
|
|
170
187
|
let defaultModel = "gemini-2.5-flash";
|
|
171
|
-
if (storedModel &&
|
|
188
|
+
if (storedModel && isValidModel(storedModel)) {
|
|
172
189
|
defaultModel = storedModel;
|
|
173
190
|
}
|
|
174
|
-
if (tempModel && !
|
|
191
|
+
if (tempModel && !isValidModel(tempModel)) {
|
|
175
192
|
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
176
|
-
console.log(chalk.dim(` Invalid model: ${tempModel}. Choose one of: ${VALID_MODELS.join(", ")}
|
|
193
|
+
console.log(chalk.dim(` Invalid model: ${tempModel}. Choose one of: ${VALID_MODELS.join(", ")} or prefix with groq/, mistral/, openrouter/, nvidia/, github/`));
|
|
177
194
|
process.exit(1);
|
|
178
195
|
}
|
|
179
196
|
const modelToUse = tempModel || defaultModel;
|
|
@@ -319,7 +336,7 @@ async function main() {
|
|
|
319
336
|
console.log();
|
|
320
337
|
console.log(chalk.dim(" Available Commands:"));
|
|
321
338
|
console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} ${chalk.dim('— View active model or switch to [name]')}`);
|
|
322
|
-
console.log(` ${chalk.hex('#0a84ff')('/key')} ${chalk.gray('[api_key]')} ${chalk.dim('— Set/save
|
|
339
|
+
console.log(` ${chalk.hex('#0a84ff')('/key')} ${chalk.gray('[provider] [api_key]')} ${chalk.dim('— Set/save API keys globally')}`);
|
|
323
340
|
console.log(` ${chalk.hex('#0a84ff')('/clear')} ${chalk.dim('— Wipe conversation memory')}`);
|
|
324
341
|
console.log(` ${chalk.hex('#0a84ff')('/status')} ${chalk.dim('— Show active model and memory usage')}`);
|
|
325
342
|
console.log(` ${chalk.hex('#0a84ff')('/help')} ${chalk.dim('— Show help screen')}`);
|
|
@@ -375,17 +392,17 @@ async function main() {
|
|
|
375
392
|
const parts = trimmed.split(/\s+/);
|
|
376
393
|
if (parts.length === 1) {
|
|
377
394
|
console.log(chalk.dim(`model `) + chalk.gray(agent.getModel()));
|
|
378
|
-
console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" · ")));
|
|
395
|
+
console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" · ") + " · groq/... · mistral/... · openrouter/... · nvidia/... · github/..."));
|
|
379
396
|
}
|
|
380
397
|
else {
|
|
381
398
|
const newModel = parts[1];
|
|
382
|
-
if (
|
|
399
|
+
if (isValidModel(newModel)) {
|
|
383
400
|
agent.setModel(newModel);
|
|
384
401
|
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray(`Switched model to: ${newModel}`));
|
|
385
402
|
}
|
|
386
403
|
else {
|
|
387
404
|
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
388
|
-
console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")}
|
|
405
|
+
console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")} or prefixed with groq/, mistral/, openrouter/, nvidia/, github/`));
|
|
389
406
|
}
|
|
390
407
|
}
|
|
391
408
|
if (!isRlClosed) {
|
|
@@ -400,26 +417,47 @@ async function main() {
|
|
|
400
417
|
if (trimmed.startsWith("/key")) {
|
|
401
418
|
const parts = trimmed.split(/\s+/);
|
|
402
419
|
if (parts.length === 1) {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
420
|
+
const providers = ["gemini", "groq", "mistral", "openrouter", "nvidia", "github"];
|
|
421
|
+
console.log(chalk.white.bold("\n Stored API Keys:"));
|
|
422
|
+
for (const p of providers) {
|
|
423
|
+
const keyToCheck = await getProviderApiKey(p);
|
|
424
|
+
if (keyToCheck) {
|
|
425
|
+
const masked = keyToCheck.slice(0, 7) + "..." + keyToCheck.slice(-4);
|
|
426
|
+
console.log(` ${chalk.dim(p.toUpperCase().padEnd(10))} : ${chalk.gray(masked)}`);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
console.log(` ${chalk.dim(p.toUpperCase().padEnd(10))} : ${chalk.hex('#ff453a')('missing')}`);
|
|
430
|
+
}
|
|
410
431
|
}
|
|
432
|
+
console.log(chalk.dim("\n To set a key, use: /key <provider> <api_key> (e.g. /key groq gsk_...)"));
|
|
433
|
+
console.log(chalk.dim(" Or set it in environment (e.g. GEMINI_API_KEY, GROQ_API_KEY, etc.)\n"));
|
|
411
434
|
}
|
|
412
|
-
else {
|
|
435
|
+
else if (parts.length === 2) {
|
|
413
436
|
const newKey = parts[1].trim();
|
|
414
437
|
if (!newKey) {
|
|
415
438
|
console.log(chalk.hex('#ff453a')('✕ error') + chalk.dim(' — API Key cannot be empty.'));
|
|
416
439
|
}
|
|
417
440
|
else {
|
|
418
|
-
await saveApiKey(newKey);
|
|
441
|
+
await saveApiKey(newKey, "gemini");
|
|
419
442
|
agent.setApiKey(newKey);
|
|
420
443
|
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Gemini API Key saved and set successfully.'));
|
|
421
444
|
}
|
|
422
445
|
}
|
|
446
|
+
else {
|
|
447
|
+
const provider = parts[1].toLowerCase();
|
|
448
|
+
const newKey = parts[2].trim();
|
|
449
|
+
const validProviders = ["gemini", "groq", "mistral", "openrouter", "nvidia", "github"];
|
|
450
|
+
if (!validProviders.includes(provider)) {
|
|
451
|
+
console.log(chalk.hex('#ff453a')('✕ error') + chalk.dim(` — Invalid provider. Choose one of: ${validProviders.join(", ")}`));
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
await saveApiKey(newKey, provider);
|
|
455
|
+
if (provider === "gemini") {
|
|
456
|
+
agent.setApiKey(newKey);
|
|
457
|
+
}
|
|
458
|
+
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray(`${provider.toUpperCase()} API Key saved and set successfully.`));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
423
461
|
if (!isRlClosed) {
|
|
424
462
|
try {
|
|
425
463
|
rl.resume();
|