coder-agent 2.8.1 → 2.8.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/agent.js +65 -0
- package/dist/index.js +112 -18
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -463,6 +463,50 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
463
463
|
processChunk(value);
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
|
+
// Flush remaining buffer in case the final chunk didn't end with a newline
|
|
467
|
+
if (buffer.trim()) {
|
|
468
|
+
const trimmed = buffer.trim();
|
|
469
|
+
if (trimmed.startsWith("data: ") && trimmed !== "data: [DONE]") {
|
|
470
|
+
try {
|
|
471
|
+
const parsed = JSON.parse(trimmed.slice(6));
|
|
472
|
+
const choice = parsed.choices?.[0];
|
|
473
|
+
if (choice) {
|
|
474
|
+
const content = choice.delta?.content;
|
|
475
|
+
if (content) {
|
|
476
|
+
if (!silent) {
|
|
477
|
+
pushToTypewriter(content);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
accumulatedContent += content;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const toolCalls = choice.delta?.tool_calls;
|
|
484
|
+
if (toolCalls) {
|
|
485
|
+
for (const tc of toolCalls) {
|
|
486
|
+
const idx = tc.index ?? 0;
|
|
487
|
+
if (!accumulatedToolCalls[idx]) {
|
|
488
|
+
accumulatedToolCalls[idx] = {
|
|
489
|
+
id: tc.id || "",
|
|
490
|
+
type: "function",
|
|
491
|
+
function: { name: "", arguments: "" }
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (tc.id)
|
|
495
|
+
accumulatedToolCalls[idx].id = tc.id;
|
|
496
|
+
if (tc.function?.name) {
|
|
497
|
+
accumulatedToolCalls[idx].function.name += tc.function.name;
|
|
498
|
+
}
|
|
499
|
+
if (tc.function?.arguments) {
|
|
500
|
+
accumulatedToolCalls[idx].function.arguments += tc.function.arguments;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch { }
|
|
507
|
+
}
|
|
508
|
+
buffer = "";
|
|
509
|
+
}
|
|
466
510
|
// Wait for the typewriter to finish writing before returning
|
|
467
511
|
if (typewriterActive && !silent) {
|
|
468
512
|
await new Promise((resolve) => {
|
|
@@ -563,6 +607,12 @@ export class Agent {
|
|
|
563
607
|
setModel(model) {
|
|
564
608
|
this.model = model;
|
|
565
609
|
}
|
|
610
|
+
getApiKey() {
|
|
611
|
+
return this.apiKey;
|
|
612
|
+
}
|
|
613
|
+
setApiKey(key) {
|
|
614
|
+
this.apiKey = key;
|
|
615
|
+
}
|
|
566
616
|
async chat(userMessage, signal) {
|
|
567
617
|
try {
|
|
568
618
|
if (signal?.aborted) {
|
|
@@ -621,6 +671,7 @@ export class Agent {
|
|
|
621
671
|
let iterations = 0;
|
|
622
672
|
const MAX_ITERATIONS = 12;
|
|
623
673
|
let waitCount = 0;
|
|
674
|
+
let emptyResponseRetries = 0;
|
|
624
675
|
const MAX_WAITS = 3;
|
|
625
676
|
const modifiedFiles = new Set();
|
|
626
677
|
let cleanContent = "";
|
|
@@ -704,8 +755,22 @@ export class Agent {
|
|
|
704
755
|
cleanContent = cleanContent.trim();
|
|
705
756
|
// ── No tool calls → final answer ─────────────────────────────────────
|
|
706
757
|
if (toolCalls.length === 0) {
|
|
758
|
+
if (cleanContent === "") {
|
|
759
|
+
if (emptyResponseRetries < 3) {
|
|
760
|
+
emptyResponseRetries++;
|
|
761
|
+
console.log(chalk.hex('#ff9f0a')(`\n⚠ Warning: Received empty response from API. Retrying (attempt ${emptyResponseRetries}/3)...`));
|
|
762
|
+
this.memory.getAll().pop(); // remove empty assistant message
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
console.log(chalk.hex('#ff453a')('\n✕ error: Received consecutive empty responses from Gemini API. Exiting.'));
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
707
770
|
break;
|
|
708
771
|
}
|
|
772
|
+
// Reset empty response retries on a successful non-empty turn
|
|
773
|
+
emptyResponseRetries = 0;
|
|
709
774
|
// ── Phase 2: Tool Execution ───────────────────────────────────────────
|
|
710
775
|
const statusLines = [];
|
|
711
776
|
for (const toolCall of toolCalls) {
|
package/dist/index.js
CHANGED
|
@@ -180,9 +180,15 @@ async function main() {
|
|
|
180
180
|
console.log(chalk.dim(`✓ Default model set to: ${tempModel}`));
|
|
181
181
|
process.exit(0);
|
|
182
182
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
// Load API Key
|
|
184
|
+
apiKey = apiKey || "";
|
|
185
|
+
// Single-Shot Mode key check
|
|
186
|
+
if (queryArgs.length > 0 && !apiKey) {
|
|
187
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
188
|
+
console.log(chalk.dim(" Gemini API Key is required but missing."));
|
|
189
|
+
console.log(chalk.dim(" Please set it using the GEMINI_API_KEY environment variable or run:"));
|
|
190
|
+
console.log(` ${chalk.cyan("coder-agent --set-key <key>")}`);
|
|
191
|
+
process.exit(1);
|
|
186
192
|
}
|
|
187
193
|
let currentAbortController = null;
|
|
188
194
|
let originalListeners = [];
|
|
@@ -279,7 +285,12 @@ async function main() {
|
|
|
279
285
|
// Save/update last used info immediately
|
|
280
286
|
await saveLastUsedInfo(CURRENT_VERSION, Date.now());
|
|
281
287
|
printBanner(modelToUse);
|
|
282
|
-
if (
|
|
288
|
+
if (!agent.getApiKey()) {
|
|
289
|
+
console.log(chalk.hex('#ff9f0a')(' 🔑 Gemini API Key is missing!'));
|
|
290
|
+
console.log(` Please type ${chalk.cyan('/key <your_api_key>')} to set and save it globally.`);
|
|
291
|
+
console.log(chalk.dim(" Get a free key at https://aistudio.google.com\n"));
|
|
292
|
+
}
|
|
293
|
+
else if (showWelcomeCommands) {
|
|
283
294
|
console.log(chalk.white.bold(" 💡 Get started with interactive slash commands:"));
|
|
284
295
|
console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} — View active model or switch to [name]`);
|
|
285
296
|
console.log(` ${chalk.hex('#0a84ff')('/clear')} — Wipe conversation memory`);
|
|
@@ -288,11 +299,15 @@ async function main() {
|
|
|
288
299
|
console.log(` ${chalk.hex('#0a84ff')('/exit')} — Exit Coder`);
|
|
289
300
|
console.log();
|
|
290
301
|
}
|
|
302
|
+
let isRlClosed = false;
|
|
291
303
|
rl = readline.createInterface({
|
|
292
304
|
input: process.stdin,
|
|
293
305
|
output: process.stdout,
|
|
294
306
|
terminal: true,
|
|
295
307
|
});
|
|
308
|
+
rl.on("close", () => {
|
|
309
|
+
isRlClosed = true;
|
|
310
|
+
});
|
|
296
311
|
process.stdin.on("keypress", (char, key) => {
|
|
297
312
|
if (isHijacked || !rl)
|
|
298
313
|
return;
|
|
@@ -301,6 +316,7 @@ async function main() {
|
|
|
301
316
|
console.log();
|
|
302
317
|
console.log(chalk.dim(" Available Commands:"));
|
|
303
318
|
console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} ${chalk.dim('— View active model or switch to [name]')}`);
|
|
319
|
+
console.log(` ${chalk.hex('#0a84ff')('/key')} ${chalk.gray('[api_key]')} ${chalk.dim('— Set/save your Gemini API Key globally')}`);
|
|
304
320
|
console.log(` ${chalk.hex('#0a84ff')('/clear')} ${chalk.dim('— Wipe conversation memory')}`);
|
|
305
321
|
console.log(` ${chalk.hex('#0a84ff')('/status')} ${chalk.dim('— Show active model and memory usage')}`);
|
|
306
322
|
console.log(` ${chalk.hex('#0a84ff')('/help')} ${chalk.dim('— Show help screen')}`);
|
|
@@ -314,24 +330,42 @@ async function main() {
|
|
|
314
330
|
let pasteTimeout = null;
|
|
315
331
|
let lineCountInBurst = 0;
|
|
316
332
|
async function executeAgentChat(trimmed) {
|
|
333
|
+
if (isRlClosed)
|
|
334
|
+
return;
|
|
317
335
|
// Pause standard input processing during agent thinking & updates
|
|
318
|
-
|
|
336
|
+
try {
|
|
337
|
+
rl.pause();
|
|
338
|
+
}
|
|
339
|
+
catch { }
|
|
319
340
|
// Built-in slash commands
|
|
320
341
|
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
321
|
-
|
|
342
|
+
try {
|
|
343
|
+
rl.close();
|
|
344
|
+
}
|
|
345
|
+
catch { }
|
|
322
346
|
process.exit(0);
|
|
323
347
|
}
|
|
324
348
|
if (trimmed === "/clear") {
|
|
325
349
|
agent.clearMemory();
|
|
326
350
|
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Memory cleared'));
|
|
327
|
-
|
|
328
|
-
|
|
351
|
+
if (!isRlClosed) {
|
|
352
|
+
try {
|
|
353
|
+
rl.resume();
|
|
354
|
+
rl.prompt();
|
|
355
|
+
}
|
|
356
|
+
catch { }
|
|
357
|
+
}
|
|
329
358
|
return;
|
|
330
359
|
}
|
|
331
360
|
if (trimmed === "/status") {
|
|
332
361
|
console.log(chalk.dim(`session · ${agent.memoryStatus()}`));
|
|
333
|
-
|
|
334
|
-
|
|
362
|
+
if (!isRlClosed) {
|
|
363
|
+
try {
|
|
364
|
+
rl.resume();
|
|
365
|
+
rl.prompt();
|
|
366
|
+
}
|
|
367
|
+
catch { }
|
|
368
|
+
}
|
|
335
369
|
return;
|
|
336
370
|
}
|
|
337
371
|
if (trimmed.startsWith("/model")) {
|
|
@@ -351,22 +385,77 @@ async function main() {
|
|
|
351
385
|
console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")}`));
|
|
352
386
|
}
|
|
353
387
|
}
|
|
354
|
-
|
|
355
|
-
|
|
388
|
+
if (!isRlClosed) {
|
|
389
|
+
try {
|
|
390
|
+
rl.resume();
|
|
391
|
+
rl.prompt();
|
|
392
|
+
}
|
|
393
|
+
catch { }
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (trimmed.startsWith("/key")) {
|
|
398
|
+
const parts = trimmed.split(/\s+/);
|
|
399
|
+
if (parts.length === 1) {
|
|
400
|
+
const keyToCheck = agent.getApiKey();
|
|
401
|
+
if (keyToCheck) {
|
|
402
|
+
const masked = keyToCheck.slice(0, 7) + "..." + keyToCheck.slice(-4);
|
|
403
|
+
console.log(chalk.dim(` Gemini API Key is set: `) + chalk.gray(masked));
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
console.log(chalk.hex('#ff453a')('✕ error') + chalk.dim(' — Gemini API Key is missing. Set it using: /key <your_api_key>'));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const newKey = parts[1].trim();
|
|
411
|
+
if (!newKey) {
|
|
412
|
+
console.log(chalk.hex('#ff453a')('✕ error') + chalk.dim(' — API Key cannot be empty.'));
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
await saveApiKey(newKey);
|
|
416
|
+
agent.setApiKey(newKey);
|
|
417
|
+
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Gemini API Key saved and set successfully.'));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (!isRlClosed) {
|
|
421
|
+
try {
|
|
422
|
+
rl.resume();
|
|
423
|
+
rl.prompt();
|
|
424
|
+
}
|
|
425
|
+
catch { }
|
|
426
|
+
}
|
|
356
427
|
return;
|
|
357
428
|
}
|
|
358
429
|
if (trimmed === "/help") {
|
|
359
430
|
console.log(chalk.white.bold("\n Interactive Commands:"));
|
|
360
431
|
console.log(chalk.gray(` /model [name] — View active model or switch to [name]
|
|
432
|
+
/key [api_key]— Set/save your Gemini API Key globally (hides the key from the LLM)
|
|
361
433
|
/clear — Wipe conversation memory
|
|
362
434
|
/status — Show active model and memory usage
|
|
363
435
|
/exit — Exit Coder`));
|
|
364
436
|
console.log(chalk.white.bold("\n API Keys & Configuration:"));
|
|
365
437
|
console.log(chalk.gray(` • Stored at: ~/.coder-config.json
|
|
366
|
-
• To change key:
|
|
438
|
+
• To change key: Type '/key <api_key>' or run 'coder-agent --set-key <key>'
|
|
367
439
|
• Env variable option: GEMINI_API_KEY`));
|
|
368
|
-
|
|
369
|
-
|
|
440
|
+
if (!isRlClosed) {
|
|
441
|
+
try {
|
|
442
|
+
rl.resume();
|
|
443
|
+
rl.prompt();
|
|
444
|
+
}
|
|
445
|
+
catch { }
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
// Block logic execution if API Key is missing
|
|
450
|
+
if (!agent.getApiKey()) {
|
|
451
|
+
console.log(chalk.hex('#ff453a')('✕ error') + chalk.dim(' — Gemini API Key is missing. Please set it using: /key <your_api_key>'));
|
|
452
|
+
if (!isRlClosed) {
|
|
453
|
+
try {
|
|
454
|
+
rl.resume();
|
|
455
|
+
rl.prompt();
|
|
456
|
+
}
|
|
457
|
+
catch { }
|
|
458
|
+
}
|
|
370
459
|
return;
|
|
371
460
|
}
|
|
372
461
|
currentAbortController = new AbortController();
|
|
@@ -395,9 +484,14 @@ async function main() {
|
|
|
395
484
|
stopHijack();
|
|
396
485
|
currentAbortController = null;
|
|
397
486
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
487
|
+
if (!isRlClosed) {
|
|
488
|
+
try {
|
|
489
|
+
rl.resume();
|
|
490
|
+
console.log(chalk.dim('────────────────────────────────────────────────'));
|
|
491
|
+
rl.prompt();
|
|
492
|
+
}
|
|
493
|
+
catch { }
|
|
494
|
+
}
|
|
401
495
|
}
|
|
402
496
|
console.log(chalk.dim('────────────────────────────────────────────────'));
|
|
403
497
|
rl.setPrompt(chalk.hex('#0a84ff')('›') + ' ');
|