coder-agent 2.2.2 → 2.3.0

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 +38 -10
  2. package/package.json +1 -1
package/dist/agent.js CHANGED
@@ -236,8 +236,19 @@ function extractTextToolCalls(content) {
236
236
  }
237
237
  return calls;
238
238
  }
239
- // ─── Gemini API client ────────────────────────────────────────────────────────
240
- async function callGeminiAPI(apiKey, params, maxRetries = 3, initialDelayMs = 1500) {
239
+ // ─── Gemini API client with Auto-Rotation Fallback ────────────────────────────
240
+ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initialDelayMs = 1500) {
241
+ const rotationList = [
242
+ "gemini-2.5-flash",
243
+ "gemini-2.5-pro",
244
+ "gemini-2.0-flash",
245
+ "gemini-2.0-pro-exp"
246
+ ];
247
+ let currentModel = params.model;
248
+ let modelIndex = rotationList.indexOf(currentModel);
249
+ if (modelIndex === -1) {
250
+ modelIndex = 0; // Default if not in list
251
+ }
241
252
  let attempts = 0;
242
253
  while (attempts < maxRetries) {
243
254
  try {
@@ -247,7 +258,7 @@ async function callGeminiAPI(apiKey, params, maxRetries = 3, initialDelayMs = 15
247
258
  "Content-Type": "application/json",
248
259
  "Authorization": `Bearer ${apiKey}`
249
260
  },
250
- body: JSON.stringify(params)
261
+ body: JSON.stringify({ ...params, model: currentModel })
251
262
  });
252
263
  if (!res.ok) {
253
264
  const errText = await res.text();
@@ -255,20 +266,36 @@ async function callGeminiAPI(apiKey, params, maxRetries = 3, initialDelayMs = 15
255
266
  err.status = res.status;
256
267
  throw err;
257
268
  }
258
- return await res.json();
269
+ const data = await res.json();
270
+ return { data, modelUsed: currentModel };
259
271
  }
260
272
  catch (err) {
261
273
  attempts++;
262
274
  const status = err?.status;
263
275
  const isRetryableError = status === 429 || status === 503 || (status >= 500 && status < 600) || !status;
264
- if (isRetryableError && attempts < maxRetries) {
265
- const delay = initialDelayMs * Math.pow(2, attempts - 1);
266
- await new Promise(resolve => setTimeout(resolve, delay));
267
- continue;
276
+ if (isRetryableError) {
277
+ // Rotate model immediately if rate limit or service unavailable occurred
278
+ if ((status === 429 || status === 503) && modelIndex + 1 < rotationList.length) {
279
+ modelIndex++;
280
+ const nextModel = rotationList[modelIndex];
281
+ stopSpinner();
282
+ console.log(chalk.hex('#ff9f0a')('⚠') + ' ' + chalk.gray(`Rate limited on ${currentModel}. Rotating to ${nextModel}`));
283
+ startSpinner("thinking...");
284
+ currentModel = nextModel;
285
+ attempts = 0; // reset retry counter for the fresh model
286
+ continue;
287
+ }
288
+ // Otherwise do standard delay retry on same model
289
+ if (attempts < maxRetries) {
290
+ const delay = initialDelayMs * Math.pow(2, attempts - 1);
291
+ await new Promise(resolve => setTimeout(resolve, delay));
292
+ continue;
293
+ }
268
294
  }
269
295
  throw err;
270
296
  }
271
297
  }
298
+ throw new Error("Request failed after all fallback rotations.");
272
299
  }
273
300
  // ─── Agent Class ─────────────────────────────────────────────────────────────
274
301
  export class Agent {
@@ -316,7 +343,6 @@ export class Agent {
316
343
  contexts.push(`File contents of ${filePath} (around line ${diag.startLineNumber}):\n` + numberedLines.join("\n"));
317
344
  }
318
345
  catch (err) {
319
- // If the absolute path doesn't exist directly (e.g. running in virtual workspaces), try to resolve it relative to current cwd
320
346
  try {
321
347
  const relativePath = path.relative("/", filePath).replace(/^[a-zA-Z]:/, "").replace(/^\\+|^[//]+/, "");
322
348
  const localContent = await fs.readFile(relativePath, "utf-8");
@@ -342,7 +368,7 @@ export class Agent {
342
368
  while (iterations < MAX_ITERATIONS) {
343
369
  iterations++;
344
370
  updateSpinner("thinking...");
345
- const response = await callGeminiAPI(this.apiKey, {
371
+ const responseObj = await callGeminiAPIWithRotation(this.apiKey, {
346
372
  model: this.model,
347
373
  messages: this.memory.getAll(),
348
374
  tools: TOOL_DEFINITIONS,
@@ -350,6 +376,8 @@ export class Agent {
350
376
  temperature: 0.2,
351
377
  });
352
378
  stopSpinner();
379
+ this.model = responseObj.modelUsed; // Persist successful model rotation
380
+ const response = responseObj.data;
353
381
  const choice = response.choices[0];
354
382
  const msg = choice.message;
355
383
  // Extract text-based tool calls if native tool_calls is empty
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",