coder-agent 2.8.2 → 2.8.4
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 +67 -0
- package/dist/tools.js +11 -4
- 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) => {
|
|
@@ -627,6 +671,7 @@ export class Agent {
|
|
|
627
671
|
let iterations = 0;
|
|
628
672
|
const MAX_ITERATIONS = 12;
|
|
629
673
|
let waitCount = 0;
|
|
674
|
+
let emptyResponseRetries = 0;
|
|
630
675
|
const MAX_WAITS = 3;
|
|
631
676
|
const modifiedFiles = new Set();
|
|
632
677
|
let cleanContent = "";
|
|
@@ -710,8 +755,22 @@ export class Agent {
|
|
|
710
755
|
cleanContent = cleanContent.trim();
|
|
711
756
|
// ── No tool calls → final answer ─────────────────────────────────────
|
|
712
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
|
+
}
|
|
713
770
|
break;
|
|
714
771
|
}
|
|
772
|
+
// Reset empty response retries on a successful non-empty turn
|
|
773
|
+
emptyResponseRetries = 0;
|
|
715
774
|
// ── Phase 2: Tool Execution ───────────────────────────────────────────
|
|
716
775
|
const statusLines = [];
|
|
717
776
|
for (const toolCall of toolCalls) {
|
|
@@ -724,6 +783,14 @@ export class Agent {
|
|
|
724
783
|
let args = {};
|
|
725
784
|
try {
|
|
726
785
|
args = JSON.parse(toolCall.function.arguments);
|
|
786
|
+
if (args && typeof args === "object") {
|
|
787
|
+
if (args.filepath && !args.file_path)
|
|
788
|
+
args.file_path = args.filepath;
|
|
789
|
+
if (args.path && !args.file_path)
|
|
790
|
+
args.file_path = args.path;
|
|
791
|
+
if (args.dirpath && !args.dir_path)
|
|
792
|
+
args.dir_path = args.dirpath;
|
|
793
|
+
}
|
|
727
794
|
}
|
|
728
795
|
catch { }
|
|
729
796
|
// Render tool execution card
|
package/dist/tools.js
CHANGED
|
@@ -423,15 +423,22 @@ export async function patch_file({ file_path, target_code, replacement_code }) {
|
|
|
423
423
|
}
|
|
424
424
|
const targetPath = normalizeFilePath(file_path);
|
|
425
425
|
const content = await fs.readFile(targetPath, "utf-8");
|
|
426
|
-
|
|
426
|
+
// Normalize all line endings to LF (\n) for comparison and patching
|
|
427
|
+
const normalizedContent = content.replace(/\r\n/g, "\n");
|
|
428
|
+
const normalizedTarget = target_code.replace(/\r\n/g, "\n");
|
|
429
|
+
const normalizedReplacement = replacement_code.replace(/\r\n/g, "\n");
|
|
430
|
+
if (!normalizedContent.includes(normalizedTarget)) {
|
|
427
431
|
return `ERROR: Target code not found in file ${file_path}. Please verify target content.`;
|
|
428
432
|
}
|
|
429
|
-
const occurrences =
|
|
433
|
+
const occurrences = normalizedContent.split(normalizedTarget).length - 1;
|
|
430
434
|
if (occurrences > 1) {
|
|
431
435
|
return `ERROR: Target code matches ${occurrences} times in file ${file_path}. Please provide more unique context to target the exact edit.`;
|
|
432
436
|
}
|
|
433
|
-
const
|
|
434
|
-
|
|
437
|
+
const newNormalizedContent = normalizedContent.replace(normalizedTarget, normalizedReplacement);
|
|
438
|
+
// Restore original CRLF line endings if the original file had them
|
|
439
|
+
const hasCrlf = content.includes("\r\n");
|
|
440
|
+
const finalContent = hasCrlf ? newNormalizedContent.replace(/\n/g, "\r\n") : newNormalizedContent;
|
|
441
|
+
await fs.writeFile(targetPath, finalContent, "utf-8");
|
|
435
442
|
return `✓ Patched file ${file_path} successfully.`;
|
|
436
443
|
}
|
|
437
444
|
catch (e) {
|