u-foo 3.0.27 → 3.0.29
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/tui/darwin-arm64/ufoo-tui +0 -0
- package/package.json +1 -1
- package/src/code/contextWindow.js +2 -0
- package/src/code/nativeRunner.js +29 -2
- package/src/code/usageStore.js +19 -2
- package/src/ui/format/index.js +3 -1
- package/src/ui/rustUcodeHost.js +37 -8
- package/dist/tui/darwin-x64/ufoo-tui +0 -0
- package/dist/tui/linux-arm64/ufoo-tui +0 -0
- package/dist/tui/linux-x64/ufoo-tui +0 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -24,6 +24,8 @@ function contextTokensFromUsage(usage = null) {
|
|
|
24
24
|
const input = toTokenCount(usage.input);
|
|
25
25
|
const cacheRead = toTokenCount(usage.cacheRead);
|
|
26
26
|
const cacheCreation = toTokenCount(usage.cacheCreation);
|
|
27
|
+
if (usage.inputIncludesCache === true) return input;
|
|
28
|
+
if (usage.inputIncludesCache === false) return input + cacheRead + cacheCreation;
|
|
27
29
|
if (cacheCreation > 0) return input + cacheRead + cacheCreation;
|
|
28
30
|
// Anthropic exclusive split: input can be smaller than cache_read alone.
|
|
29
31
|
if (cacheRead > 0 && input < cacheRead) return input + cacheRead + cacheCreation;
|
package/src/code/nativeRunner.js
CHANGED
|
@@ -96,6 +96,7 @@ const DEFAULT_KIMI_MODEL = "k3";
|
|
|
96
96
|
// for non-trivial tasks while still catching runaway loops. Override via env.
|
|
97
97
|
const DEFAULT_MAX_NATIVE_TOOL_CALLS = 100;
|
|
98
98
|
const DEFAULT_MAX_NATIVE_TOOL_ERRORS = 20;
|
|
99
|
+
const MAX_EMPTY_TERMINAL_RETRIES = 2;
|
|
99
100
|
const DEFAULT_NATIVE_TIMEOUT_MS = 43200000; // 12 hours
|
|
100
101
|
/** Max text-only auto-continues while a plan is waiting on a task (per user submit). */
|
|
101
102
|
const DEFAULT_MAX_PLAN_AUTO_CONTINUES = 24;
|
|
@@ -213,7 +214,8 @@ function readOpenAiUsage(raw) {
|
|
|
213
214
|
input: toUsageInt(raw.prompt_tokens),
|
|
214
215
|
output: toUsageInt(raw.completion_tokens),
|
|
215
216
|
cacheRead: toUsageInt(details.cached_tokens),
|
|
216
|
-
cacheCreation:
|
|
217
|
+
cacheCreation: toUsageInt(details.created_cache_tokens),
|
|
218
|
+
inputIncludesCache: true,
|
|
217
219
|
};
|
|
218
220
|
}
|
|
219
221
|
|
|
@@ -226,6 +228,7 @@ function readAnthropicUsage(raw, { includeOutput = false } = {}) {
|
|
|
226
228
|
output: includeOutput ? toUsageInt(raw.output_tokens) : 0,
|
|
227
229
|
cacheRead: toUsageInt(raw.cache_read_input_tokens),
|
|
228
230
|
cacheCreation: toUsageInt(raw.cache_creation_input_tokens),
|
|
231
|
+
inputIncludesCache: false,
|
|
229
232
|
};
|
|
230
233
|
}
|
|
231
234
|
|
|
@@ -1431,7 +1434,13 @@ async function runAnthropicTurn({
|
|
|
1431
1434
|
let responseText = "";
|
|
1432
1435
|
let nextSyntheticBlockIndex = 0;
|
|
1433
1436
|
let lastBlockIndex = -1;
|
|
1434
|
-
const turnUsage = {
|
|
1437
|
+
const turnUsage = {
|
|
1438
|
+
input: 0,
|
|
1439
|
+
output: 0,
|
|
1440
|
+
cacheRead: 0,
|
|
1441
|
+
cacheCreation: 0,
|
|
1442
|
+
inputIncludesCache: false,
|
|
1443
|
+
};
|
|
1435
1444
|
|
|
1436
1445
|
return runSseRequest({
|
|
1437
1446
|
url,
|
|
@@ -1806,6 +1815,7 @@ async function runNativeLoop({
|
|
|
1806
1815
|
let streamed = false;
|
|
1807
1816
|
let toolCallsExecuted = 0;
|
|
1808
1817
|
let toolErrors = 0;
|
|
1818
|
+
let emptyTerminalRetries = 0;
|
|
1809
1819
|
let executionState = initialExecutionState && typeof initialExecutionState === "object"
|
|
1810
1820
|
? initialExecutionState
|
|
1811
1821
|
: emptyExecutionState();
|
|
@@ -1979,6 +1989,22 @@ async function runNativeLoop({
|
|
|
1979
1989
|
|
|
1980
1990
|
if (toolCalls.length === 0) {
|
|
1981
1991
|
const text = String(turnResult.text || "").trim();
|
|
1992
|
+
if (!text && toolCallsExecuted > 0) {
|
|
1993
|
+
if (emptyTerminalRetries >= MAX_EMPTY_TERMINAL_RETRIES) {
|
|
1994
|
+
throw new Error("model returned an empty response after tool execution");
|
|
1995
|
+
}
|
|
1996
|
+
emptyTerminalRetries += 1;
|
|
1997
|
+
providerMessages.push({
|
|
1998
|
+
role: "user",
|
|
1999
|
+
content: [
|
|
2000
|
+
"Continue the current task after the tool results.",
|
|
2001
|
+
"Call any remaining tools, or provide a concrete final answer to the user.",
|
|
2002
|
+
"Do not end the turn with an empty response.",
|
|
2003
|
+
].join(" "),
|
|
2004
|
+
});
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
emptyTerminalRetries = 0;
|
|
1982
2008
|
const sideEffects = parseStructuredSideEffects(text);
|
|
1983
2009
|
const planCommand = sideEffects ? normalizePlanGraphCommand(sideEffects) : null;
|
|
1984
2010
|
if (planCommand) {
|
|
@@ -2032,6 +2058,7 @@ async function runNativeLoop({
|
|
|
2032
2058
|
|
|
2033
2059
|
// A tool-using turn resets the empty auto-continue streak (progress possible).
|
|
2034
2060
|
consecutiveEmptyAutoContinues = 0;
|
|
2061
|
+
emptyTerminalRetries = 0;
|
|
2035
2062
|
|
|
2036
2063
|
const pendingCalls = transport.prepareToolCalls({
|
|
2037
2064
|
messages: providerMessages,
|
package/src/code/usageStore.js
CHANGED
|
@@ -59,9 +59,22 @@ function createUsageSummary() {
|
|
|
59
59
|
output: 0,
|
|
60
60
|
cacheRead: 0,
|
|
61
61
|
cacheCreation: 0,
|
|
62
|
+
cacheInput: 0,
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
function promptTokenTotal(row = {}) {
|
|
67
|
+
const input = toUsageCount(row.input);
|
|
68
|
+
const provider = String(row.provider || "").trim().toLowerCase();
|
|
69
|
+
// OpenAI-compatible usage.prompt_tokens already includes cached tokens.
|
|
70
|
+
// Anthropic reports uncached, cache-read, and cache-creation tokens as
|
|
71
|
+
// disjoint counters, so reconstruct the total prompt-token denominator.
|
|
72
|
+
if (provider === "anthropic") {
|
|
73
|
+
return input + toUsageCount(row.cacheRead) + toUsageCount(row.cacheCreation);
|
|
74
|
+
}
|
|
75
|
+
return input;
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
function summarizeSessionUsage({ workspaceRoot = process.cwd(), sessionId = "" } = {}) {
|
|
66
79
|
const summary = createUsageSummary();
|
|
67
80
|
const targetSessionId = String(sessionId || "").trim();
|
|
@@ -88,6 +101,7 @@ function summarizeSessionUsage({ workspaceRoot = process.cwd(), sessionId = "" }
|
|
|
88
101
|
summary.output += toUsageCount(row.output);
|
|
89
102
|
summary.cacheRead += toUsageCount(row.cacheRead);
|
|
90
103
|
summary.cacheCreation += toUsageCount(row.cacheCreation);
|
|
104
|
+
summary.cacheInput += promptTokenTotal(row);
|
|
91
105
|
}
|
|
92
106
|
return summary;
|
|
93
107
|
}
|
|
@@ -98,11 +112,14 @@ function formatSessionUsageStatus(summary = {}) {
|
|
|
98
112
|
const output = Number(source.output) || 0;
|
|
99
113
|
const cacheRead = Number(source.cacheRead) || 0;
|
|
100
114
|
const cacheCreation = Number(source.cacheCreation) || 0;
|
|
101
|
-
const
|
|
115
|
+
const reportedCacheInput = Number(source.cacheInput);
|
|
116
|
+
const denominator = Number.isFinite(reportedCacheInput) && reportedCacheInput > 0
|
|
117
|
+
? reportedCacheInput
|
|
118
|
+
: input;
|
|
102
119
|
const hitRate = denominator > 0 ? (cacheRead / denominator) * 100 : 0;
|
|
103
120
|
return [
|
|
104
121
|
`Session tokens: input=${input} output=${output} cache_read=${cacheRead} cache_creation=${cacheCreation}`,
|
|
105
|
-
`Cache hit rate: ${hitRate.toFixed(1)}% (cache_read/
|
|
122
|
+
`Cache hit rate: ${hitRate.toFixed(1)}% (cache_read/prompt_input)`,
|
|
106
123
|
].join("\n");
|
|
107
124
|
}
|
|
108
125
|
|
package/src/ui/format/index.js
CHANGED
|
@@ -835,7 +835,9 @@ function buildMergedToolExpandedLines(entries = []) {
|
|
|
835
835
|
: [];
|
|
836
836
|
const maxLength = 120;
|
|
837
837
|
return list.map((item) => {
|
|
838
|
-
const base = `${formatToolDisplayName(item.tool)}${item.detail ? ` ${item.detail}` : ""}
|
|
838
|
+
const base = `${formatToolDisplayName(item.tool)}${item.detail ? ` ${item.detail}` : ""}`
|
|
839
|
+
.replace(/\s+/g, " ")
|
|
840
|
+
.trim();
|
|
839
841
|
let line;
|
|
840
842
|
if (!item.isError) {
|
|
841
843
|
line = base;
|
package/src/ui/rustUcodeHost.js
CHANGED
|
@@ -203,6 +203,31 @@ function createThinkingLogPublisher(publish, thinkingStatus, streamId) {
|
|
|
203
203
|
return { id, onThinkingDelta, stop };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
function createLeadingWhitespaceNormalizer() {
|
|
207
|
+
let hasVisibleText = false;
|
|
208
|
+
return (delta) => {
|
|
209
|
+
const text = String(delta || "");
|
|
210
|
+
if (hasVisibleText || !text) return text;
|
|
211
|
+
const visible = text.replace(/^\s+/u, "");
|
|
212
|
+
if (!visible) return "";
|
|
213
|
+
hasVisibleText = true;
|
|
214
|
+
return visible;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function appendNaturalLanguageResult(nlResult, formatNlResult, appendLog) {
|
|
219
|
+
if (typeof formatNlResult !== "function" || typeof appendLog !== "function" || !nlResult) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
// Successful streamed text is already present in the mutable stream entry.
|
|
223
|
+
// Appending summary + formatted summary here duplicated the same answer.
|
|
224
|
+
if (nlResult.ok !== false && nlResult.streamed) return false;
|
|
225
|
+
const formatted = String(formatNlResult(nlResult) || "").trim();
|
|
226
|
+
if (!formatted) return false;
|
|
227
|
+
appendLog(formatted, nlResult.ok === false ? "error" : "assistant");
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
206
231
|
async function runUcodeRust(props = {}) {
|
|
207
232
|
const plan = resolveTuiLaunchPlan({
|
|
208
233
|
mode: props.tuiMode || process.env.UFOO_TUI || "rust",
|
|
@@ -562,6 +587,7 @@ async function runUcodeRust(props = {}) {
|
|
|
562
587
|
thinking.reset();
|
|
563
588
|
publish("stream.start", { id: streamId });
|
|
564
589
|
const thinkingLog = createThinkingLogPublisher(publish, thinking, streamId);
|
|
590
|
+
const normalizeStreamDelta = createLeadingWhitespaceNormalizer();
|
|
565
591
|
let responseStarted = false;
|
|
566
592
|
const beginResponse = () => {
|
|
567
593
|
if (responseStarted) return;
|
|
@@ -573,8 +599,10 @@ async function runUcodeRust(props = {}) {
|
|
|
573
599
|
const result = await submit(answerText, props.state, {
|
|
574
600
|
signal: abort.signal,
|
|
575
601
|
onDelta: (delta) => {
|
|
602
|
+
const visibleDelta = normalizeStreamDelta(delta);
|
|
603
|
+
if (!visibleDelta) return;
|
|
576
604
|
beginResponse();
|
|
577
|
-
publish("stream.delta", { id: streamId, text:
|
|
605
|
+
publish("stream.delta", { id: streamId, text: visibleDelta });
|
|
578
606
|
},
|
|
579
607
|
onThinkingDelta: (delta) => {
|
|
580
608
|
if (!responseStarted) thinkingLog.onThinkingDelta(delta);
|
|
@@ -621,6 +649,7 @@ async function runUcodeRust(props = {}) {
|
|
|
621
649
|
thinking.reset();
|
|
622
650
|
publish("stream.start", { id: streamId });
|
|
623
651
|
const thinkingLog = createThinkingLogPublisher(publish, thinking, streamId);
|
|
652
|
+
const normalizeStreamDelta = createLeadingWhitespaceNormalizer();
|
|
624
653
|
let responseStarted = false;
|
|
625
654
|
const beginResponse = () => {
|
|
626
655
|
if (responseStarted) return;
|
|
@@ -632,8 +661,10 @@ async function runUcodeRust(props = {}) {
|
|
|
632
661
|
const nlResult = await props.runNaturalLanguageTask(text, props.state, {
|
|
633
662
|
signal: abort.signal,
|
|
634
663
|
onDelta: (delta) => {
|
|
664
|
+
const visibleDelta = normalizeStreamDelta(delta);
|
|
665
|
+
if (!visibleDelta) return;
|
|
635
666
|
beginResponse();
|
|
636
|
-
publish("stream.delta", { id: streamId, text:
|
|
667
|
+
publish("stream.delta", { id: streamId, text: visibleDelta });
|
|
637
668
|
},
|
|
638
669
|
onThinkingDelta: (delta) => {
|
|
639
670
|
if (!responseStarted) thinkingLog.onThinkingDelta(delta);
|
|
@@ -656,11 +687,7 @@ async function runUcodeRust(props = {}) {
|
|
|
656
687
|
tools.flush();
|
|
657
688
|
thinkingLog.stop();
|
|
658
689
|
publish("stream.done", { id: streamId });
|
|
659
|
-
|
|
660
|
-
if (typeof props.formatNlResult === "function" && nlResult) {
|
|
661
|
-
const formatted = props.formatNlResult(nlResult);
|
|
662
|
-
if (formatted) appendLog(formatted, "assistant");
|
|
663
|
-
}
|
|
690
|
+
appendNaturalLanguageResult(nlResult, props.formatNlResult, appendLog);
|
|
664
691
|
persistIfNeeded();
|
|
665
692
|
publishPlan();
|
|
666
693
|
publishUsageFromResult(nlResult);
|
|
@@ -782,7 +809,7 @@ async function runUcodeRust(props = {}) {
|
|
|
782
809
|
if (name === "task.cancel") {
|
|
783
810
|
controller.cancelTask();
|
|
784
811
|
publish("status.set", { text: "cancelling…", busy: true });
|
|
785
|
-
appendLog("
|
|
812
|
+
appendLog("• Cancellation requested. Stopping the current task...", "system");
|
|
786
813
|
return { ok: true };
|
|
787
814
|
}
|
|
788
815
|
if (name === "completion.request") {
|
|
@@ -1070,6 +1097,8 @@ module.exports = {
|
|
|
1070
1097
|
normalizeToolLogEntry,
|
|
1071
1098
|
splitUcodeBannerRow,
|
|
1072
1099
|
createThinkingLogPublisher,
|
|
1100
|
+
createLeadingWhitespaceNormalizer,
|
|
1101
|
+
appendNaturalLanguageResult,
|
|
1073
1102
|
buildUcodeAgentsSnapshot,
|
|
1074
1103
|
buildUcodeCompletionItems,
|
|
1075
1104
|
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|