coder-agent 2.9.12 → 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 +52 -17
- 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
|
@@ -4,7 +4,7 @@ import * as readline from "readline";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import figlet from "figlet";
|
|
6
6
|
import { Agent } from "./agent.js";
|
|
7
|
-
import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo } from "./config.js";
|
|
7
|
+
import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo, getProviderApiKey } from "./config.js";
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
const packageJson = require("../package.json");
|
|
10
10
|
const CURRENT_VERSION = packageJson.version;
|
|
@@ -18,6 +18,12 @@ const VALID_MODELS = [
|
|
|
18
18
|
"gemma-4-31b-it",
|
|
19
19
|
"gemma-4-26b-a4b-it"
|
|
20
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
|
+
}
|
|
21
27
|
function printBanner(modelName) {
|
|
22
28
|
console.clear();
|
|
23
29
|
console.log('');
|
|
@@ -83,6 +89,14 @@ function printHelp() {
|
|
|
83
89
|
console.log(chalk.white(" gemini-2.0-flash — (Deprecated) Ultra-fast, lightweight"));
|
|
84
90
|
console.log(chalk.white(" gemini-2.0-pro-exp — (Deprecated) Experimental reasoning"));
|
|
85
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("");
|
|
86
100
|
}
|
|
87
101
|
// ─── API Key Bootstrap ────────────────────────────────────────────────────────
|
|
88
102
|
async function promptApiKey() {
|
|
@@ -171,12 +185,12 @@ async function main() {
|
|
|
171
185
|
}
|
|
172
186
|
const storedModel = await getStoredModel();
|
|
173
187
|
let defaultModel = "gemini-2.5-flash";
|
|
174
|
-
if (storedModel &&
|
|
188
|
+
if (storedModel && isValidModel(storedModel)) {
|
|
175
189
|
defaultModel = storedModel;
|
|
176
190
|
}
|
|
177
|
-
if (tempModel && !
|
|
191
|
+
if (tempModel && !isValidModel(tempModel)) {
|
|
178
192
|
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
179
|
-
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/`));
|
|
180
194
|
process.exit(1);
|
|
181
195
|
}
|
|
182
196
|
const modelToUse = tempModel || defaultModel;
|
|
@@ -322,7 +336,7 @@ async function main() {
|
|
|
322
336
|
console.log();
|
|
323
337
|
console.log(chalk.dim(" Available Commands:"));
|
|
324
338
|
console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} ${chalk.dim('— View active model or switch to [name]')}`);
|
|
325
|
-
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')}`);
|
|
326
340
|
console.log(` ${chalk.hex('#0a84ff')('/clear')} ${chalk.dim('— Wipe conversation memory')}`);
|
|
327
341
|
console.log(` ${chalk.hex('#0a84ff')('/status')} ${chalk.dim('— Show active model and memory usage')}`);
|
|
328
342
|
console.log(` ${chalk.hex('#0a84ff')('/help')} ${chalk.dim('— Show help screen')}`);
|
|
@@ -378,17 +392,17 @@ async function main() {
|
|
|
378
392
|
const parts = trimmed.split(/\s+/);
|
|
379
393
|
if (parts.length === 1) {
|
|
380
394
|
console.log(chalk.dim(`model `) + chalk.gray(agent.getModel()));
|
|
381
|
-
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/..."));
|
|
382
396
|
}
|
|
383
397
|
else {
|
|
384
398
|
const newModel = parts[1];
|
|
385
|
-
if (
|
|
399
|
+
if (isValidModel(newModel)) {
|
|
386
400
|
agent.setModel(newModel);
|
|
387
401
|
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray(`Switched model to: ${newModel}`));
|
|
388
402
|
}
|
|
389
403
|
else {
|
|
390
404
|
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
391
|
-
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/`));
|
|
392
406
|
}
|
|
393
407
|
}
|
|
394
408
|
if (!isRlClosed) {
|
|
@@ -403,26 +417,47 @@ async function main() {
|
|
|
403
417
|
if (trimmed.startsWith("/key")) {
|
|
404
418
|
const parts = trimmed.split(/\s+/);
|
|
405
419
|
if (parts.length === 1) {
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
+
}
|
|
413
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"));
|
|
414
434
|
}
|
|
415
|
-
else {
|
|
435
|
+
else if (parts.length === 2) {
|
|
416
436
|
const newKey = parts[1].trim();
|
|
417
437
|
if (!newKey) {
|
|
418
438
|
console.log(chalk.hex('#ff453a')('✕ error') + chalk.dim(' — API Key cannot be empty.'));
|
|
419
439
|
}
|
|
420
440
|
else {
|
|
421
|
-
await saveApiKey(newKey);
|
|
441
|
+
await saveApiKey(newKey, "gemini");
|
|
422
442
|
agent.setApiKey(newKey);
|
|
423
443
|
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Gemini API Key saved and set successfully.'));
|
|
424
444
|
}
|
|
425
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
|
+
}
|
|
426
461
|
if (!isRlClosed) {
|
|
427
462
|
try {
|
|
428
463
|
rl.resume();
|