ucode-agent 1.61.1 → 1.62.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.
package/README.md CHANGED
@@ -96,6 +96,9 @@ Served by Google (aistudio.google.com), free with a key.
96
96
 
97
97
  `/model` shows them and switches. `ucode -m gemini-3.5-flash` starts on one.
98
98
 
99
+ When Google is overloaded and Flash-Lite stops answering, ucode carries on with
100
+ 3.5 Flash by itself and goes back to Flash-Lite a few minutes later.
101
+
99
102
  ## What it does
100
103
 
101
104
  **Twenty-one tools.** `create_app`, `read_file`, `read_files`, `write_file`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.61.1",
3
+ "version": "1.62.0",
4
4
  "description": "ucode - a terminal coding agent that reads, edits and runs your code, on Google Gemini models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
package/src/core/loop.js CHANGED
@@ -27,7 +27,7 @@ import { spawn } from 'node:child_process';
27
27
 
28
28
  import {
29
29
  ask, model, setModel, modelName, modelList, contextLimit, rateLimits,
30
- estimateConversation, MODELS, DEFAULT_MODEL, PROVIDER, fallbackFor,
30
+ estimateConversation, MODELS, DEFAULT_MODEL, PROVIDER, fallbackFor, backupFor,
31
31
  } from './provider.js';
32
32
  import {
33
33
  tools, runTool, describe, setRoot, setConfirm, setRequest,
@@ -2095,6 +2095,8 @@ export class Agent {
2095
2095
  if (++this.failovers > MAX_FAILOVERS) return false;
2096
2096
  const from = model();
2097
2097
  let next = fallbackFor(from, this.tried);
2098
+ const backup = err.kind !== 'rate_limit' && backupFor(from);
2099
+ if (!next && backup && !this.tried?.has(backup)) next = backup;
2098
2100
 
2099
2101
  const why = err.kind === 'rate_limit' ? 'busy' : err.kind === 'timeout' ? 'too slow to answer' : 'not answering';
2100
2102
 
@@ -104,6 +104,15 @@ export function fallbackFor(id, tried = new Set()) {
104
104
  return null;
105
105
  }
106
106
 
107
+ /**
108
+ * Where a Flash-Lite model goes when Google is overloaded (503) or it freezes.
109
+ * Unlike fallbackFor this is on by default: both Lite models go down together
110
+ * at peak hours while Flash keeps answering, and a build that waits on them
111
+ * can wait for hours. UCODE_BACKUP=0 turns it off.
112
+ */
113
+ export const backupFor = (id) =>
114
+ /flash-lite/.test(id) && process.env.UCODE_BACKUP !== '0' ? 'gemini-3.5-flash' : null;
115
+
107
116
  /** Seconds to wait on successive rate limits that come with no retry-after. */
108
117
  const RATE_LIMIT_BACKOFF = [5, 10, 20];
109
118
 
@@ -711,7 +720,7 @@ export async function ask(messages, tools = [], opts = {}) {
711
720
  kind: 'stalled',
712
721
  attempted: `asking ${modelName(id)} for a reply`,
713
722
  failed: `${modelName(id)} froze ${MAX_STALLS} times in a row — it took the request and then sent nothing.`,
714
- fix: 'Its free endpoint is struggling right now. Run /model and pick another one; North Mini Code answers soonest.',
723
+ fix: 'Google is struggling with it right now. Run /model and pick another one.',
715
724
  cause: problem,
716
725
  });
717
726
  }
@@ -741,6 +750,7 @@ export async function ask(messages, tools = [], opts = {}) {
741
750
  if (!worthRetrying || attempt === attempts || opts.signal?.aborted) break;
742
751
  if (printed > 0) break; // half an answer is on screen; do not print it twice
743
752
  if (problem.detail?.handed) break; // its first tool calls are already running
753
+ if (problem.kind === 'timeout' && backupFor(id)) break; // one freeze is enough: the loop moves to the backup
744
754
 
745
755
  // A stalled provider needs longer to come back than a dropped socket
746
756
  // does, and the wait is narrated so a slow turn never looks like a hang.