coder-agent 2.6.0 → 2.6.2

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
@@ -276,6 +276,104 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
276
276
  err.status = res.status;
277
277
  throw err;
278
278
  }
279
+ if (params.stream) {
280
+ const decoder = new TextDecoder();
281
+ let buffer = "";
282
+ let accumulatedContent = "";
283
+ let accumulatedToolCalls = [];
284
+ if (!silent) {
285
+ stopSpinner();
286
+ }
287
+ const processChunk = (chunk) => {
288
+ buffer += decoder.decode(chunk, { stream: true });
289
+ const lines = buffer.split("\n");
290
+ buffer = lines.pop() || "";
291
+ for (const line of lines) {
292
+ const trimmed = line.trim();
293
+ if (!trimmed)
294
+ continue;
295
+ if (trimmed === "data: [DONE]")
296
+ break;
297
+ if (trimmed.startsWith("data: ")) {
298
+ try {
299
+ const parsed = JSON.parse(trimmed.slice(6));
300
+ const choice = parsed.choices?.[0];
301
+ if (!choice)
302
+ continue;
303
+ const content = choice.delta?.content;
304
+ if (content) {
305
+ accumulatedContent += content;
306
+ if (!silent) {
307
+ process.stdout.write(content);
308
+ }
309
+ }
310
+ const toolCalls = choice.delta?.tool_calls;
311
+ if (toolCalls) {
312
+ for (const tc of toolCalls) {
313
+ const idx = tc.index ?? 0;
314
+ if (!accumulatedToolCalls[idx]) {
315
+ accumulatedToolCalls[idx] = {
316
+ id: tc.id || "",
317
+ type: "function",
318
+ function: { name: "", arguments: "" }
319
+ };
320
+ }
321
+ if (tc.id)
322
+ accumulatedToolCalls[idx].id = tc.id;
323
+ if (tc.function?.name) {
324
+ accumulatedToolCalls[idx].function.name += tc.function.name;
325
+ }
326
+ if (tc.function?.arguments) {
327
+ accumulatedToolCalls[idx].function.arguments += tc.function.arguments;
328
+ }
329
+ }
330
+ }
331
+ }
332
+ catch (e) {
333
+ // Ignore partial JSON parse errors
334
+ }
335
+ }
336
+ }
337
+ };
338
+ const bodyStream = res.body;
339
+ if (bodyStream && typeof bodyStream[Symbol.asyncIterator] === "function") {
340
+ for await (const chunk of bodyStream) {
341
+ if (signal?.aborted) {
342
+ const abortErr = new Error("The user aborted a request.");
343
+ abortErr.name = "AbortError";
344
+ throw abortErr;
345
+ }
346
+ processChunk(chunk);
347
+ }
348
+ }
349
+ else if (bodyStream && typeof bodyStream.getReader === "function") {
350
+ const reader = bodyStream.getReader();
351
+ while (true) {
352
+ if (signal?.aborted) {
353
+ const abortErr = new Error("The user aborted a request.");
354
+ abortErr.name = "AbortError";
355
+ throw abortErr;
356
+ }
357
+ const { done, value } = await reader.read();
358
+ if (done)
359
+ break;
360
+ if (value)
361
+ processChunk(value);
362
+ }
363
+ }
364
+ const finalResponse = {
365
+ choices: [
366
+ {
367
+ message: {
368
+ role: "assistant",
369
+ content: accumulatedContent,
370
+ tool_calls: accumulatedToolCalls.length > 0 ? accumulatedToolCalls.filter(Boolean) : undefined
371
+ }
372
+ }
373
+ ]
374
+ };
375
+ return { data: finalResponse, modelUsed: currentModel };
376
+ }
279
377
  const data = await res.json();
280
378
  return { data, modelUsed: currentModel };
281
379
  }
@@ -331,11 +429,13 @@ export class Agent {
331
429
  memory;
332
430
  model;
333
431
  memoryScope;
334
- constructor(apiKey, model = "gemini-2.5-flash", memoryScope = "project") {
432
+ confirmHandler;
433
+ constructor(apiKey, model = "gemini-2.5-flash", memoryScope = "project", confirmHandler) {
335
434
  this.apiKey = apiKey;
336
435
  this.memory = new Memory();
337
436
  this.model = model;
338
437
  this.memoryScope = memoryScope;
438
+ this.confirmHandler = confirmHandler;
339
439
  }
340
440
  clearMemory() {
341
441
  this.memory.clear();
@@ -449,6 +549,7 @@ export class Agent {
449
549
  tools: TOOL_DEFINITIONS,
450
550
  tool_choice: "auto",
451
551
  temperature: 0.2,
552
+ stream: true,
452
553
  }, 3, 1500, signal);
453
554
  stopSpinner();
454
555
  if (signal?.aborted) {
@@ -516,7 +617,7 @@ export class Agent {
516
617
  modifiedFiles.add(path.normalize(args.file_path));
517
618
  }
518
619
  }
519
- const result = await dispatchTool(name, args, signal);
620
+ const result = await dispatchTool(name, args, this.confirmHandler, signal);
520
621
  if (signal?.aborted) {
521
622
  const abortErr = new Error("The user aborted a request.");
522
623
  abortErr.name = "AbortError";
@@ -558,11 +659,7 @@ export class Agent {
558
659
  console.log(chalk.dim('─') + ' ' + chalk.dim(`${modifiedFiles.size} file(s) modified`));
559
660
  }
560
661
  // ── Phase 3: Final Agent Response ───────────────────────────────────────
561
- console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
562
- if (cleanContent) {
563
- console.log(formatResponseText(cleanContent));
564
- console.log(""); // one trailing blank line
565
- }
662
+ // Content has already been streamed in real-time.
566
663
  if (iterations >= MAX_ITERATIONS) {
567
664
  console.log(chalk.hex('#ff453a')('✕ error'));
568
665
  console.log(chalk.dim(' Max tool iterations reached.'));
package/dist/index.js CHANGED
@@ -114,6 +114,7 @@ async function promptApiKey() {
114
114
  }
115
115
  // ─── Main Execution ───────────────────────────────────────────────────────────
116
116
  async function main() {
117
+ let rl;
117
118
  const args = process.argv.slice(2);
118
119
  let tempModel;
119
120
  let tempMemoryScope;
@@ -182,7 +183,28 @@ async function main() {
182
183
  if (!apiKey) {
183
184
  apiKey = await promptApiKey();
184
185
  }
185
- const agent = new Agent(apiKey, modelToUse, tempMemoryScope);
186
+ const confirmHandler = async (question) => {
187
+ if (rl) {
188
+ return new Promise((resolve) => {
189
+ rl.question(question, (answer) => {
190
+ resolve(answer.trim().toLowerCase().startsWith("y"));
191
+ });
192
+ });
193
+ }
194
+ else {
195
+ const rlConfirm = readline.createInterface({
196
+ input: process.stdin,
197
+ output: process.stdout,
198
+ });
199
+ return new Promise((resolve) => {
200
+ rlConfirm.question(question, (answer) => {
201
+ rlConfirm.close();
202
+ resolve(answer.trim().toLowerCase().startsWith("y"));
203
+ });
204
+ });
205
+ }
206
+ };
207
+ const agent = new Agent(apiKey, modelToUse, tempMemoryScope, confirmHandler);
186
208
  // Single-Shot Mode
187
209
  if (queryArgs.length > 0) {
188
210
  const singleShotPrompt = queryArgs.join(" ").trim();
@@ -206,7 +228,7 @@ async function main() {
206
228
  }
207
229
  // Interactive REPL Mode
208
230
  printBanner(modelToUse);
209
- const rl = readline.createInterface({
231
+ rl = readline.createInterface({
210
232
  input: process.stdin,
211
233
  output: process.stdout,
212
234
  terminal: true,
package/dist/tools.js CHANGED
@@ -212,7 +212,7 @@ export async function list_directory({ dir_path = "." }) {
212
212
  return `ERROR: ${e.message}`;
213
213
  }
214
214
  }
215
- export async function run_shell({ command, cwd }, signal) {
215
+ export async function run_shell({ command, cwd }, confirmHandler, signal) {
216
216
  try {
217
217
  let targetCwd = process.cwd();
218
218
  if (cwd) {
@@ -234,7 +234,22 @@ export async function run_shell({ command, cwd }, signal) {
234
234
  if (cwd) {
235
235
  console.log(` in directory: ${chalk.gray(cwd)}`);
236
236
  }
237
- const allowed = await askConfirmation(` Allow execution? (y/N): `);
237
+ let allowed = false;
238
+ if (confirmHandler) {
239
+ allowed = await confirmHandler(` Allow execution? (y/N): `);
240
+ }
241
+ else {
242
+ const rlConfirm = readline.createInterface({
243
+ input: process.stdin,
244
+ output: process.stdout,
245
+ });
246
+ allowed = await new Promise((resolve) => {
247
+ rlConfirm.question(` Allow execution? (y/N): `, (answer) => {
248
+ rlConfirm.close();
249
+ resolve(answer.trim().toLowerCase().startsWith("y"));
250
+ });
251
+ });
252
+ }
238
253
  if (!allowed) {
239
254
  return "ERROR: Command execution denied by user.";
240
255
  }
@@ -392,12 +407,12 @@ export async function patch_file({ file_path, target_code, replacement_code }) {
392
407
  }
393
408
  }
394
409
  // ─── Dispatcher ──────────────────────────────────────────────────────────────
395
- export async function dispatchTool(name, args, signal) {
410
+ export async function dispatchTool(name, args, confirmHandler, signal) {
396
411
  switch (name) {
397
412
  case "read_file": return read_file(args);
398
413
  case "write_file": return write_file(args);
399
414
  case "list_directory": return list_directory(args);
400
- case "run_shell": return run_shell(args, signal);
415
+ case "run_shell": return run_shell(args, confirmHandler, signal);
401
416
  case "web_search": return web_search(args, signal);
402
417
  case "find_files": return find_files(args);
403
418
  case "read_file_lines": return read_file_lines(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",