coder-agent 2.6.1 → 2.6.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.
Files changed (2) hide show
  1. package/dist/agent.js +147 -5
  2. package/package.json +1 -1
package/dist/agent.js CHANGED
@@ -276,6 +276,151 @@ 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
+ let typewriterQueue = [];
288
+ let typewriterActive = false;
289
+ let resolveTypewriterFinished = null;
290
+ const pushToTypewriter = (text) => {
291
+ typewriterQueue.push(...text.split(""));
292
+ if (!typewriterActive) {
293
+ typewriterActive = true;
294
+ runTypewriterLoop();
295
+ }
296
+ };
297
+ const runTypewriterLoop = async () => {
298
+ while (typewriterQueue.length > 0) {
299
+ const len = typewriterQueue.length;
300
+ let batchSize = 1;
301
+ let delay = 15; // default smooth delay
302
+ if (len > 80) {
303
+ batchSize = 8;
304
+ delay = 1;
305
+ }
306
+ else if (len > 40) {
307
+ batchSize = 4;
308
+ delay = 2;
309
+ }
310
+ else if (len > 20) {
311
+ batchSize = 2;
312
+ delay = 5;
313
+ }
314
+ else if (len > 10) {
315
+ batchSize = 1;
316
+ delay = 8;
317
+ }
318
+ const chars = typewriterQueue.splice(0, batchSize).join("");
319
+ process.stdout.write(chars);
320
+ await new Promise((resolve) => setTimeout(resolve, delay));
321
+ }
322
+ typewriterActive = false;
323
+ if (resolveTypewriterFinished) {
324
+ resolveTypewriterFinished();
325
+ resolveTypewriterFinished = null;
326
+ }
327
+ };
328
+ const processChunk = (chunk) => {
329
+ buffer += decoder.decode(chunk, { stream: true });
330
+ const lines = buffer.split("\n");
331
+ buffer = lines.pop() || "";
332
+ for (const line of lines) {
333
+ const trimmed = line.trim();
334
+ if (!trimmed)
335
+ continue;
336
+ if (trimmed === "data: [DONE]")
337
+ break;
338
+ if (trimmed.startsWith("data: ")) {
339
+ try {
340
+ const parsed = JSON.parse(trimmed.slice(6));
341
+ const choice = parsed.choices?.[0];
342
+ if (!choice)
343
+ continue;
344
+ const content = choice.delta?.content;
345
+ if (content) {
346
+ accumulatedContent += content;
347
+ if (!silent) {
348
+ pushToTypewriter(content);
349
+ }
350
+ }
351
+ const toolCalls = choice.delta?.tool_calls;
352
+ if (toolCalls) {
353
+ for (const tc of toolCalls) {
354
+ const idx = tc.index ?? 0;
355
+ if (!accumulatedToolCalls[idx]) {
356
+ accumulatedToolCalls[idx] = {
357
+ id: tc.id || "",
358
+ type: "function",
359
+ function: { name: "", arguments: "" }
360
+ };
361
+ }
362
+ if (tc.id)
363
+ accumulatedToolCalls[idx].id = tc.id;
364
+ if (tc.function?.name) {
365
+ accumulatedToolCalls[idx].function.name += tc.function.name;
366
+ }
367
+ if (tc.function?.arguments) {
368
+ accumulatedToolCalls[idx].function.arguments += tc.function.arguments;
369
+ }
370
+ }
371
+ }
372
+ }
373
+ catch (e) {
374
+ // Ignore partial JSON parse errors
375
+ }
376
+ }
377
+ }
378
+ };
379
+ const bodyStream = res.body;
380
+ if (bodyStream && typeof bodyStream[Symbol.asyncIterator] === "function") {
381
+ for await (const chunk of bodyStream) {
382
+ if (signal?.aborted) {
383
+ const abortErr = new Error("The user aborted a request.");
384
+ abortErr.name = "AbortError";
385
+ throw abortErr;
386
+ }
387
+ processChunk(chunk);
388
+ }
389
+ }
390
+ else if (bodyStream && typeof bodyStream.getReader === "function") {
391
+ const reader = bodyStream.getReader();
392
+ while (true) {
393
+ if (signal?.aborted) {
394
+ const abortErr = new Error("The user aborted a request.");
395
+ abortErr.name = "AbortError";
396
+ throw abortErr;
397
+ }
398
+ const { done, value } = await reader.read();
399
+ if (done)
400
+ break;
401
+ if (value)
402
+ processChunk(value);
403
+ }
404
+ }
405
+ // Wait for the typewriter to finish writing before returning
406
+ if (typewriterActive && !silent) {
407
+ await new Promise((resolve) => {
408
+ resolveTypewriterFinished = resolve;
409
+ });
410
+ }
411
+ const finalResponse = {
412
+ choices: [
413
+ {
414
+ message: {
415
+ role: "assistant",
416
+ content: accumulatedContent,
417
+ tool_calls: accumulatedToolCalls.length > 0 ? accumulatedToolCalls.filter(Boolean) : undefined
418
+ }
419
+ }
420
+ ]
421
+ };
422
+ return { data: finalResponse, modelUsed: currentModel };
423
+ }
279
424
  const data = await res.json();
280
425
  return { data, modelUsed: currentModel };
281
426
  }
@@ -451,6 +596,7 @@ export class Agent {
451
596
  tools: TOOL_DEFINITIONS,
452
597
  tool_choice: "auto",
453
598
  temperature: 0.2,
599
+ stream: true,
454
600
  }, 3, 1500, signal);
455
601
  stopSpinner();
456
602
  if (signal?.aborted) {
@@ -560,11 +706,7 @@ export class Agent {
560
706
  console.log(chalk.dim('─') + ' ' + chalk.dim(`${modifiedFiles.size} file(s) modified`));
561
707
  }
562
708
  // ── Phase 3: Final Agent Response ───────────────────────────────────────
563
- console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
564
- if (cleanContent) {
565
- console.log(formatResponseText(cleanContent));
566
- console.log(""); // one trailing blank line
567
- }
709
+ // Content has already been streamed in real-time.
568
710
  if (iterations >= MAX_ITERATIONS) {
569
711
  console.log(chalk.hex('#ff453a')('✕ error'));
570
712
  console.log(chalk.dim(' Max tool iterations reached.'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",