ucode-agent 1.60.0 → 1.60.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/package.json +1 -1
- package/src/core/provider.js +21 -4
- package/src/tools/files.js +22 -0
package/package.json
CHANGED
package/src/core/provider.js
CHANGED
|
@@ -118,6 +118,18 @@ const RATE_LIMIT_BACKOFF = [5, 10, 20];
|
|
|
118
118
|
*/
|
|
119
119
|
export const stallLimit = () => Number(process.env.UCODE_STALL_MS) || 60_000;
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* How long to wait for the first piece of a reply.
|
|
123
|
+
*
|
|
124
|
+
* Gemini sends a tool call whole, at the end, not piece by piece. A whole app
|
|
125
|
+
* in one create_app is 50-70 silent seconds before anything arrives, and the
|
|
126
|
+
* one-minute watchdog was cancelling those replies as frozen and starting them
|
|
127
|
+
* again — "provider stalled" on requests that were working fine. Silence before
|
|
128
|
+
* the reply starts gets this long; once it is arriving, stallLimit applies.
|
|
129
|
+
*/
|
|
130
|
+
export const firstReplyLimit = () =>
|
|
131
|
+
Number(process.env.UCODE_FIRST_REPLY_MS) || Number(process.env.UCODE_STALL_MS) || 180_000;
|
|
132
|
+
|
|
121
133
|
/** Freezes in a row before ucode stops asking and says to switch models. */
|
|
122
134
|
export const MAX_STALLS = 3;
|
|
123
135
|
let stalls = 0;
|
|
@@ -755,17 +767,21 @@ async function streamed(request, opts, id) {
|
|
|
755
767
|
opts.signal?.addEventListener('abort', stop, { once: true });
|
|
756
768
|
let stalled = false;
|
|
757
769
|
let timer;
|
|
770
|
+
// Nothing of the reply itself yet: text, reasoning or a tool call. Chunks
|
|
771
|
+
// that carry none of those (a role, usage) do not count as it having started.
|
|
772
|
+
let started = false;
|
|
773
|
+
const limit = () => (started ? stallLimit() : firstReplyLimit());
|
|
758
774
|
const frozen = (cause) => new Failure({
|
|
759
775
|
kind: 'timeout',
|
|
760
776
|
attempted: `asking ${modelName(id)} for a reply`,
|
|
761
|
-
failed: `${modelName(id)} went silent for ${Math.round(
|
|
777
|
+
failed: `${modelName(id)} went silent for ${Math.round(limit() / 1000)}s, so ucode stopped waiting.`,
|
|
762
778
|
fix: 'ucode asks again by itself. If it keeps freezing, /model to North Mini Code.',
|
|
763
779
|
detail: { stalled: true, handed: handed.size },
|
|
764
780
|
cause,
|
|
765
781
|
});
|
|
766
782
|
const alive = () => {
|
|
767
783
|
clearTimeout(timer);
|
|
768
|
-
timer = setTimeout(() => { stalled = true; quiet.abort(); },
|
|
784
|
+
timer = setTimeout(() => { stalled = true; quiet.abort(); }, limit());
|
|
769
785
|
};
|
|
770
786
|
|
|
771
787
|
let text = '';
|
|
@@ -788,14 +804,15 @@ async function streamed(request, opts, id) {
|
|
|
788
804
|
noteLimits(response?.headers);
|
|
789
805
|
|
|
790
806
|
for await (const chunk of stream) {
|
|
791
|
-
alive();
|
|
792
807
|
if (opts.signal?.aborted) break;
|
|
793
808
|
if (chunk.usage) usage = chunk.usage;
|
|
794
809
|
|
|
795
810
|
const choice = chunk.choices?.[0];
|
|
811
|
+
const delta = choice?.delta ?? {};
|
|
812
|
+
if (delta.content || delta.reasoning || delta.reasoning_content || delta.tool_calls?.length) started = true;
|
|
813
|
+
alive();
|
|
796
814
|
if (!choice) continue;
|
|
797
815
|
if (choice.finish_reason) finishReason = choice.finish_reason;
|
|
798
|
-
const delta = choice.delta ?? {};
|
|
799
816
|
|
|
800
817
|
// Reasoning arrives on a separate channel: `reasoning` on OpenRouter,
|
|
801
818
|
// `reasoning_content` on some upstreams.
|
package/src/tools/files.js
CHANGED
|
@@ -228,6 +228,27 @@ export function assetImport(abs, text) {
|
|
|
228
228
|
'instead — <link rel="stylesheet" href="…"> for a stylesheet, an <img> or a plain URL for an image.';
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* A page styled with Tailwind classes that never loads Tailwind.
|
|
233
|
+
*
|
|
234
|
+
* A live build wrote a 27 KB page of "flex px-4 bg-[var(--bg)]" and a
|
|
235
|
+
* stylesheet of colour tokens only: every class was ignored and the app came
|
|
236
|
+
* out as bare browser defaults, with nothing failing to tell anyone. When a
|
|
237
|
+
* page leans on those classes and has no Tailwind of its own, the browser
|
|
238
|
+
* build is added to its head, so the page looks the way it was written.
|
|
239
|
+
*/
|
|
240
|
+
const TAILWIND_CLASS = /^(?:-?(?:m|p)[trblxy]?-\S+|flex|grid|hidden|block|inline-flex|items-\S+|justify-\S+|gap-\S+|space-[xy]-\S+|(?:min-|max-)?[wh]-\S+|text-\S+|bg-\S+|rounded(?:-\S+)?|border(?:-\S+)?|shadow(?:-\S+)?|font-\S+|leading-\S+|tracking-\S+|(?:sm|md|lg|xl|hover|focus|dark):\S+)$/;
|
|
241
|
+
export function withTailwind(html) {
|
|
242
|
+
if (/tailwindcss|cdn\.tailwind/i.test(html) || !/<\/head>/i.test(html)) return html;
|
|
243
|
+
let count = 0;
|
|
244
|
+
for (const [, list] of html.matchAll(/class="([^"]*)"/g)) {
|
|
245
|
+
for (const token of list.split(/\s+/)) if (TAILWIND_CLASS.test(token)) count++;
|
|
246
|
+
if (count >= 12) break;
|
|
247
|
+
}
|
|
248
|
+
if (count < 12) return html;
|
|
249
|
+
return html.replace(/<\/head>/i, ' <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>\n</head>');
|
|
250
|
+
}
|
|
251
|
+
|
|
231
252
|
/** The note for that, appended to a write's result like a parse problem is. */
|
|
232
253
|
const pageNote = (target, text) => {
|
|
233
254
|
const problem = assetImport(target.abs, text);
|
|
@@ -355,6 +376,7 @@ export async function readFiles({ paths, limit = READ_LINES }) {
|
|
|
355
376
|
/** Write one file, returning the rows that show what changed. */
|
|
356
377
|
async function put(target, content, { diffMax = 16 } = {}) {
|
|
357
378
|
const attempted = `writing ${target.show}`;
|
|
379
|
+
if (/\.html?$/i.test(target.abs)) content = withTailwind(content);
|
|
358
380
|
|
|
359
381
|
// Read what is there before clobbering it, so an overwrite can be shown as
|
|
360
382
|
// an actual diff rather than as a claim that something changed.
|