coder-agent 2.3.1 → 2.3.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 CHANGED
@@ -2,7 +2,7 @@ import chalk from "chalk";
2
2
  import * as path from "path";
3
3
  import * as fs from "fs/promises";
4
4
  import { TOOL_DEFINITIONS, dispatchTool } from "./tools.js";
5
- import { Memory } from "./memory.js";
5
+ import { Memory, getAgentMemoryEntrypoint } from "./memory.js";
6
6
  // ─── Loading Spinner ──────────────────────────────────────────────────────────
7
7
  let spinnerTimer = null;
8
8
  let currentFrame = 0;
@@ -237,12 +237,14 @@ function extractTextToolCalls(content) {
237
237
  return calls;
238
238
  }
239
239
  // ─── Gemini API client with Auto-Rotation Fallback ────────────────────────────
240
- async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initialDelayMs = 1500, signal) {
240
+ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initialDelayMs = 1500, signal, silent = false) {
241
241
  const rotationList = [
242
+ "gemini-2.0-flash",
243
+ "gemini-2.0-pro-exp",
242
244
  "gemini-2.5-flash",
243
245
  "gemini-2.5-pro",
244
- "gemini-2.0-flash",
245
- "gemini-2.0-pro-exp"
246
+ "gemini-3.5-flash",
247
+ "gemini-3.1-flash-lite"
246
248
  ];
247
249
  let currentModel = params.model;
248
250
  let modelIndex = rotationList.indexOf(currentModel);
@@ -282,20 +284,24 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
282
284
  attempts++;
283
285
  const status = err?.status;
284
286
  const isRetryableError = status === 429 || status === 503 || (status >= 500 && status < 600) || !status;
285
- if (isRetryableError) {
286
- // Rotate model immediately if rate limit or service unavailable occurred
287
- if ((status === 429 || status === 503) && modelIndex + 1 < rotationList.length) {
287
+ const isModelError = status === 404 || status === 400;
288
+ if (isRetryableError || isModelError) {
289
+ // Rotate model immediately if rate limit or model error occurred
290
+ if (modelIndex + 1 < rotationList.length) {
288
291
  modelIndex++;
289
292
  const nextModel = rotationList[modelIndex];
290
- stopSpinner();
291
- console.log(chalk.hex('#ff9f0a')('⚠') + ' ' + chalk.gray(`Rate limited on ${currentModel}. Rotating to ${nextModel}`));
292
- startSpinner("thinking...");
293
+ if (!silent) {
294
+ stopSpinner();
295
+ const reason = isModelError ? "Model unavailable" : "Rate limited";
296
+ console.log(chalk.hex('#ff9f0a')('⚠') + ' ' + chalk.gray(`${reason} on ${currentModel}. Rotating to ${nextModel}`));
297
+ startSpinner("thinking...");
298
+ }
293
299
  currentModel = nextModel;
294
300
  attempts = 0; // reset retry counter for the fresh model
295
301
  continue;
296
302
  }
297
- // Otherwise do standard delay retry on same model
298
- if (attempts < maxRetries) {
303
+ // Otherwise do standard delay retry on same model ONLY if it is a transient/retryable error
304
+ if (isRetryableError && attempts < maxRetries) {
299
305
  const delay = initialDelayMs * Math.pow(2, attempts - 1);
300
306
  await new Promise((resolve, reject) => {
301
307
  const timer = setTimeout(resolve, delay);
@@ -348,7 +354,7 @@ export class Agent {
348
354
  abortErr.name = "AbortError";
349
355
  throw abortErr;
350
356
  }
351
- await this.memory.init(this.memoryScope, "coder");
357
+ await this.memory.init(this.memoryScope, "coder", true);
352
358
  if (signal?.aborted) {
353
359
  const abortErr = new Error("The user aborted a request.");
354
360
  abortErr.name = "AbortError";
@@ -527,9 +533,67 @@ export class Agent {
527
533
  console.log(chalk.hex('#ff453a')('✕ error'));
528
534
  console.log(chalk.dim(' Max tool iterations reached.'));
529
535
  }
536
+ // Auto-update persistent memory in the background (fire and forget)
537
+ this.autoUpdateMemory().catch(() => { });
530
538
  }
531
539
  finally {
532
540
  stopSpinner();
533
541
  }
534
542
  }
543
+ async autoUpdateMemory() {
544
+ try {
545
+ const memoryFile = getAgentMemoryEntrypoint("coder", this.memoryScope);
546
+ let existingMemory = "";
547
+ try {
548
+ existingMemory = await fs.readFile(memoryFile, "utf-8");
549
+ }
550
+ catch (err) {
551
+ if (err.code !== "ENOENT") {
552
+ return;
553
+ }
554
+ }
555
+ const allMessages = this.memory.getAll();
556
+ if (allMessages.length <= 1)
557
+ return;
558
+ const prompt = `You are a memory manager for the CLI coding agent.
559
+ Your task is to update the persistent agent memory file based on the recent conversation history.
560
+
561
+ Here is the current memory file content:
562
+ ---
563
+ ${existingMemory}
564
+ ---
565
+
566
+ Here is the recent conversation history:
567
+ ---
568
+ ${JSON.stringify(allMessages.slice(1).map(m => ({ role: m.role, content: m.content || JSON.stringify(m.tool_calls) })))}
569
+ ---
570
+
571
+ Instructions:
572
+ 1. Review the conversation history and extract any important new project setup details, style preferences, build/test commands, package quirks, or persistent instructions that should be remembered for future sessions.
573
+ 2. Integrate these new learnings into the existing memory structure. Keep existing useful learnings/preferences, but clean up duplicates or obsolete info.
574
+ 3. Keep the content concise, clean, and formatted in Markdown.
575
+ 4. If there are no new learnings, setup details, or instructions in the conversation, output EXACTLY the existing memory content. Do not add conversational text, just output the updated/existing markdown content.`;
576
+ const responseObj = await callGeminiAPIWithRotation(this.apiKey, {
577
+ model: this.model,
578
+ messages: [{ role: "user", content: prompt }],
579
+ temperature: 0.1,
580
+ }, 3, 1500, undefined, true);
581
+ let newMemory = responseObj.data.choices[0].message.content?.trim() || "";
582
+ if (newMemory.startsWith("```markdown")) {
583
+ newMemory = newMemory.slice(11).trim();
584
+ }
585
+ if (newMemory.startsWith("```")) {
586
+ newMemory = newMemory.slice(3).trim();
587
+ }
588
+ if (newMemory.endsWith("```")) {
589
+ newMemory = newMemory.slice(0, -3).trim();
590
+ }
591
+ if (newMemory && newMemory !== existingMemory.trim()) {
592
+ await fs.writeFile(memoryFile, newMemory, "utf-8");
593
+ }
594
+ }
595
+ catch {
596
+ // Fail silently to avoid interrupting the main interaction
597
+ }
598
+ }
535
599
  }
package/dist/index.js CHANGED
@@ -1,23 +1,56 @@
1
1
  #!/usr/bin/env node
2
2
  import * as readline from "readline";
3
3
  import chalk from "chalk";
4
+ import figlet from "figlet";
4
5
  import { Agent } from "./agent.js";
5
6
  import { getStoredApiKey, saveApiKey, getStoredModel, saveModel } from "./config.js";
6
7
  const VALID_MODELS = [
7
8
  "gemini-2.5-flash",
8
9
  "gemini-2.5-pro",
10
+ "gemini-3.5-flash",
11
+ "gemini-3.1-flash-lite",
9
12
  "gemini-2.0-flash",
10
13
  "gemini-2.0-pro-exp"
11
14
  ];
12
- // ─── Startup Screen ──────────────────────────────────────────────────────────
13
15
  function printBanner(modelName) {
14
- console.log(chalk.white.bold('coder-agent') + chalk.dim(' 1.1.0'));
15
- console.log("");
16
- console.log(chalk.dim('model ') + chalk.gray(modelName));
17
- console.log(chalk.dim('context ') + chalk.gray('1m+ tokens'));
18
- console.log(chalk.dim('tools ') + chalk.gray('read · write · run · search'));
19
- console.log("");
20
- console.log(chalk.dim('─'.repeat(48)));
16
+ console.clear();
17
+ console.log('');
18
+ // render logo
19
+ const logo = figlet.textSync('CODER', {
20
+ font: 'Slant',
21
+ horizontalLayout: 'default',
22
+ });
23
+ // split and color each line with a blue→white fade
24
+ const lines = logo.split('\n');
25
+ const colors = [
26
+ chalk.hex('#0a84ff'),
27
+ chalk.hex('#2a94ff'),
28
+ chalk.hex('#4aa4ff'),
29
+ chalk.hex('#8ac4ff'),
30
+ chalk.white,
31
+ ];
32
+ lines.forEach((line, i) => {
33
+ const color = colors[Math.min(i, colors.length - 1)];
34
+ console.log(' ' + color(line));
35
+ });
36
+ console.log('');
37
+ console.log(' ' + chalk.white.bold('coder-agent') +
38
+ chalk.dim(' v1.1.0 · by antigravity'));
39
+ console.log('');
40
+ console.log(chalk.dim(' ─────────────────────────────────────'));
41
+ console.log('');
42
+ console.log(' ' + chalk.dim('model ') + chalk.gray(modelName));
43
+ console.log(' ' + chalk.dim('context ') + chalk.gray('1m+ tokens'));
44
+ console.log(' ' + chalk.dim('tools ') +
45
+ chalk.hex('#0a84ff')('read') + chalk.dim(' · ') +
46
+ chalk.hex('#ff9f0a')('write') + chalk.dim(' · ') +
47
+ chalk.hex('#30d158')('run') + chalk.dim(' · ') +
48
+ chalk.gray('search'));
49
+ console.log('');
50
+ console.log(chalk.dim(' ─────────────────────────────────────'));
51
+ console.log('');
52
+ console.log(' ' + chalk.dim('type your task and press enter. ctrl+c to exit.'));
53
+ console.log('');
21
54
  }
22
55
  function printHelp() {
23
56
  console.log(chalk.white.bold("\n Coder CLI v1.1\n"));
@@ -35,10 +68,12 @@ function printHelp() {
35
68
  console.log(chalk.white(" --set-gemini-key <api_key> — Save your Gemini API Key globally (alias)"));
36
69
  console.log("");
37
70
  console.log(chalk.gray(" Popular Gemini Models:"));
71
+ console.log(chalk.white(" gemini-3.5-flash — Newest, highly capable & fast"));
38
72
  console.log(chalk.white(" gemini-2.5-flash — Default, highly capable & fast"));
39
73
  console.log(chalk.white(" gemini-2.5-pro — Reasoning model, excellent coding"));
40
- console.log(chalk.white(" gemini-2.0-flash Ultra-fast, lightweight"));
41
- console.log(chalk.white(" gemini-2.0-pro-exp Experimental reasoning model"));
74
+ console.log(chalk.white(" gemini-3.1-flash-lite Light-weight, high volume"));
75
+ console.log(chalk.white(" gemini-2.0-flash (Deprecated) Ultra-fast, lightweight"));
76
+ console.log(chalk.white(" gemini-2.0-pro-exp — (Deprecated) Experimental reasoning"));
42
77
  console.log("");
43
78
  }
44
79
  // ─── API Key Bootstrap ────────────────────────────────────────────────────────
package/dist/memory.js CHANGED
@@ -228,8 +228,8 @@ export class Memory {
228
228
  this.maxMessages = maxMessages;
229
229
  this.messages.push({ role: "system", content: SYSTEM_PROMPT });
230
230
  }
231
- async init(scope = "project", agentType = "coder") {
232
- if (this.initialized)
231
+ async init(scope = "project", agentType = "coder", forceRefresh = false) {
232
+ if (this.initialized && !forceRefresh)
233
233
  return;
234
234
  this.scope = scope;
235
235
  this.agentType = agentType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,9 +31,11 @@
31
31
  "start:build": "node dist/index.js"
32
32
  },
33
33
  "dependencies": {
34
- "chalk": "^5.3.0"
34
+ "chalk": "^5.3.0",
35
+ "figlet": "^1.11.0"
35
36
  },
36
37
  "devDependencies": {
38
+ "@types/figlet": "^1.7.0",
37
39
  "@types/node": "^20.0.0",
38
40
  "tsx": "^4.7.0",
39
41
  "typescript": "^5.4.0"