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 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
- if (!content.includes(target_code)) {
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 = content.split(target_code).length - 1;
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 newContent = content.replace(target_code, replacement_code);
434
- await fs.writeFile(targetPath, newContent, "utf-8");
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",