@tiens.nguyen/gonext-local-worker 1.0.294 → 1.0.295
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/gonext-local-worker.mjs +16 -1
- package/gonext-repl.mjs +108 -1
- package/gonext_agent_chat.py +21 -0
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1997,6 +1997,10 @@ async function runAgentChatJob(job) {
|
|
|
1997
1997
|
let latestCodeTokens = 0;
|
|
1998
1998
|
// Task #84: peak single-request prompt-token count seen this turn (not the sum).
|
|
1999
1999
|
let latestMaxCodeTokens = 0;
|
|
2000
|
+
// Task #104: running total of OUTPUT (completion) tokens the CODE model generated this
|
|
2001
|
+
// turn. Persisted to the job doc; the REPL folds this turn's total into the lifetime
|
|
2002
|
+
// per-workspace + per-(user, coding-URL) counters at completion.
|
|
2003
|
+
let latestCodeOutputTokens = 0;
|
|
2000
2004
|
// Raw model-stream tokens (event.type === "stream") are Python code + `Thought:`
|
|
2001
2005
|
// prose + <code>/<|"|> fragments. The web Thought panel renders reasoning as
|
|
2002
2006
|
// markdown, which turns `#`-prefixed code comments into <h1> headings and
|
|
@@ -2133,14 +2137,24 @@ async function runAgentChatJob(job) {
|
|
|
2133
2137
|
const nextMax = Number.isFinite(event.codeMax)
|
|
2134
2138
|
? Math.max(latestMaxCodeTokens, event.codeMax)
|
|
2135
2139
|
: latestMaxCodeTokens;
|
|
2136
|
-
|
|
2140
|
+
// Task #104: output tokens climb monotonically across the turn too.
|
|
2141
|
+
const nextOut = Number.isFinite(event.codeOutput)
|
|
2142
|
+
? Math.max(latestCodeOutputTokens, event.codeOutput)
|
|
2143
|
+
: latestCodeOutputTokens;
|
|
2144
|
+
if (
|
|
2145
|
+
nextTotal !== latestCodeTokens ||
|
|
2146
|
+
nextMax !== latestMaxCodeTokens ||
|
|
2147
|
+
nextOut !== latestCodeOutputTokens
|
|
2148
|
+
) {
|
|
2137
2149
|
latestCodeTokens = nextTotal;
|
|
2138
2150
|
latestMaxCodeTokens = nextMax;
|
|
2151
|
+
latestCodeOutputTokens = nextOut;
|
|
2139
2152
|
workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
2140
2153
|
method: "PATCH",
|
|
2141
2154
|
body: JSON.stringify({
|
|
2142
2155
|
agentCodeTokens: latestCodeTokens,
|
|
2143
2156
|
agentMaxCodeTokens: latestMaxCodeTokens,
|
|
2157
|
+
agentOutputTokens: latestCodeOutputTokens,
|
|
2144
2158
|
}),
|
|
2145
2159
|
}).catch(() => {});
|
|
2146
2160
|
}
|
|
@@ -2196,6 +2210,7 @@ async function runAgentChatJob(job) {
|
|
|
2196
2210
|
tokenCount: Math.max(1, Math.ceil((finalText || fullText).length / 4)),
|
|
2197
2211
|
...(latestCodeTokens > 0 ? { agentCodeTokens: latestCodeTokens } : {}),
|
|
2198
2212
|
...(latestMaxCodeTokens > 0 ? { agentMaxCodeTokens: latestMaxCodeTokens } : {}),
|
|
2213
|
+
...(latestCodeOutputTokens > 0 ? { agentOutputTokens: latestCodeOutputTokens } : {}),
|
|
2199
2214
|
totalTimeSeconds,
|
|
2200
2215
|
}),
|
|
2201
2216
|
});
|
package/gonext-repl.mjs
CHANGED
|
@@ -193,6 +193,8 @@ const COMMANDS = [
|
|
|
193
193
|
{ name: "/server", desc: "pick a deployment server (host/user) for this session" },
|
|
194
194
|
{ name: "/revert", usage: "/revert [runId]", desc: "undo the agent's file edits (latest run)" },
|
|
195
195
|
{ name: "/reset", aliases: ["/new"], desc: "forget this folder's saved conversation and start fresh" },
|
|
196
|
+
{ name: "/output-token", desc: "show the agent code-model OUTPUT-token total for this workspace" },
|
|
197
|
+
{ name: "/reset-output-token-global", desc: "reset the GLOBAL (you + coding server) output-token total to 0" },
|
|
196
198
|
{ name: "/help", desc: "list these commands" },
|
|
197
199
|
{ name: "/exit", aliases: ["/quit"], desc: "quit" },
|
|
198
200
|
];
|
|
@@ -548,6 +550,49 @@ let wsSync = { enabled: false, name: "", path: "" };
|
|
|
548
550
|
const workspaceKeyFor = (cwd) =>
|
|
549
551
|
createHash("sha256").update(cwd).digest("hex").slice(0, 32);
|
|
550
552
|
|
|
553
|
+
// Task #104: agent CODE-model OUTPUT-token counters. The API keys the global total on
|
|
554
|
+
// (userId + coding-model URL from settings) and the workspace total on workspaceKey; the
|
|
555
|
+
// REPL just supplies workspaceKey + this turn's delta. All best-effort (never block a turn).
|
|
556
|
+
async function fetchOutputTokenTotals(cwd) {
|
|
557
|
+
try {
|
|
558
|
+
const r = await fetch(
|
|
559
|
+
`${apiBase}/api/worker/output-tokens?workspaceKey=${workspaceKeyFor(cwd)}`,
|
|
560
|
+
{ headers: { "X-Worker-Key": workerKey } }
|
|
561
|
+
);
|
|
562
|
+
if (!r.ok) return null;
|
|
563
|
+
return await r.json();
|
|
564
|
+
} catch {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function addOutputTokens(cwd, delta) {
|
|
570
|
+
if (!(delta > 0)) return null;
|
|
571
|
+
try {
|
|
572
|
+
const r = await fetch(`${apiBase}/api/worker/output-tokens`, {
|
|
573
|
+
method: "POST",
|
|
574
|
+
headers: { "Content-Type": "application/json", "X-Worker-Key": workerKey },
|
|
575
|
+
body: JSON.stringify({ workspaceKey: workspaceKeyFor(cwd), delta }),
|
|
576
|
+
});
|
|
577
|
+
if (!r.ok) return null;
|
|
578
|
+
return await r.json();
|
|
579
|
+
} catch {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function resetGlobalOutputTokens() {
|
|
585
|
+
try {
|
|
586
|
+
const r = await fetch(`${apiBase}/api/worker/output-tokens/reset-global`, {
|
|
587
|
+
method: "POST",
|
|
588
|
+
headers: { "Content-Type": "application/json", "X-Worker-Key": workerKey },
|
|
589
|
+
});
|
|
590
|
+
return r.ok;
|
|
591
|
+
} catch {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
551
596
|
async function pushWorkspaceSync(cwd, history, { del = false } = {}) {
|
|
552
597
|
if (!wsSync.enabled) return;
|
|
553
598
|
const body = del
|
|
@@ -1023,6 +1068,10 @@ let selectedServer = null;
|
|
|
1023
1068
|
// as a max across turns and persisted per folder in the session file. Shown in orange
|
|
1024
1069
|
// next to the live token count.
|
|
1025
1070
|
let sessionMaxCodeTokens = 0;
|
|
1071
|
+
// Task #104: GLOBAL lifetime OUTPUT-token total for (this user + the agent coding-model
|
|
1072
|
+
// API URL), fetched from the API. Shown with a ↓ next to the input/max counts. Updated
|
|
1073
|
+
// after each turn (the API returns the new total) and refreshed at startup.
|
|
1074
|
+
let globalOutputTokens = 0;
|
|
1026
1075
|
|
|
1027
1076
|
// While a turn runs, mute readline's own echo/redraws so keystrokes can't corrupt the
|
|
1028
1077
|
// live status display and there's no way to type a new question — only Ctrl+C works.
|
|
@@ -1235,6 +1284,10 @@ async function runAgentTurn(history) {
|
|
|
1235
1284
|
// Running total of prompt tokens sent to the agent CODE model this turn (task #83),
|
|
1236
1285
|
// read from the job doc each poll. Rendered on the thinking line after the playful word.
|
|
1237
1286
|
let latestCodeTokens = 0;
|
|
1287
|
+
// Task #104: this turn's OUTPUT-token total, read from the job doc each poll. Folded into
|
|
1288
|
+
// the lifetime per-workspace + global counters once the turn completes.
|
|
1289
|
+
let latestOutputTokens = 0;
|
|
1290
|
+
let outputTokensPosted = false; // guard: increment the lifetime counters once per turn
|
|
1238
1291
|
// First few KB of the raw stream as consumed — a cheap fingerprint to verify the
|
|
1239
1292
|
// completed resultText really EXTENDS what we streamed (an old worker replaces it
|
|
1240
1293
|
// with the shorter bare answer, which must not be sliced by shownChars).
|
|
@@ -1321,7 +1374,9 @@ async function runAgentTurn(history) {
|
|
|
1321
1374
|
const fit = (s) => (s.length > max ? s.slice(0, max - 1) + "…" : s);
|
|
1322
1375
|
const tokLabel =
|
|
1323
1376
|
dim(` · ${fmtTokens(latestCodeTokens)} tok`) +
|
|
1324
|
-
orange(` · ${fmtTokens(sessionMaxCodeTokens)} max`)
|
|
1377
|
+
orange(` · ${fmtTokens(sessionMaxCodeTokens)} max`) +
|
|
1378
|
+
// Task #104: global (user + coding-URL) lifetime OUTPUT-token total, ↓ = generated.
|
|
1379
|
+
(globalOutputTokens > 0 ? cyan(` · ↓ ${fmtTokens(globalOutputTokens)}`) : "");
|
|
1325
1380
|
// Task #96: the thought line is "clickable" only when the fuller thought has MORE text than
|
|
1326
1381
|
// fits on line 1 (a live command / bare "Thinking" / a thought that already fits has nothing
|
|
1327
1382
|
// to reveal). Line 1 (what's happening now): a live command > the model's current Thought >
|
|
@@ -1686,8 +1741,21 @@ async function runAgentTurn(history) {
|
|
|
1686
1741
|
if (Number.isFinite(job.agentMaxCodeTokens) && job.agentMaxCodeTokens > sessionMaxCodeTokens) {
|
|
1687
1742
|
sessionMaxCodeTokens = job.agentMaxCodeTokens;
|
|
1688
1743
|
}
|
|
1744
|
+
// This turn's OUTPUT-token total (task #104), monotonic across polls.
|
|
1745
|
+
if (Number.isFinite(job.agentOutputTokens) && job.agentOutputTokens > latestOutputTokens) {
|
|
1746
|
+
latestOutputTokens = job.agentOutputTokens;
|
|
1747
|
+
}
|
|
1689
1748
|
const text = typeof job.resultText === "string" ? job.resultText : "";
|
|
1690
1749
|
if (job.jobStatus === "completed") {
|
|
1750
|
+
// Task #104: fold this turn's output-token total into the lifetime per-workspace +
|
|
1751
|
+
// global (user + coding-URL) counters, exactly once. Best-effort; the API returns
|
|
1752
|
+
// the new global total, which drives the ↓ header indicator.
|
|
1753
|
+
if (latestOutputTokens > 0 && !outputTokensPosted) {
|
|
1754
|
+
outputTokensPosted = true;
|
|
1755
|
+
void addOutputTokens(resolve(process.cwd()), latestOutputTokens).then((t) => {
|
|
1756
|
+
if (t && Number.isFinite(t.globalTotal)) globalOutputTokens = t.globalTotal;
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1691
1759
|
// The job can flip to "completed" between polls with authoritative resultText that
|
|
1692
1760
|
// the live stream hasn't caught up to yet (a debounced last chunk, or the final
|
|
1693
1761
|
// PATCH landing before its chunk flush was polled). resultText is the source of
|
|
@@ -1810,6 +1878,11 @@ async function main() {
|
|
|
1810
1878
|
selectedServer = await loadSessionServer(resolve(process.cwd()));
|
|
1811
1879
|
sessionMaxCodeTokens = await loadSessionMaxCodeTokens(resolve(process.cwd()));
|
|
1812
1880
|
sessionRagMode = await loadSessionRagMode(resolve(process.cwd())); // task #97: local/cloud/null
|
|
1881
|
+
// Task #104: seed the ↓ global OUTPUT-token total (user + coding-URL) from the API.
|
|
1882
|
+
{
|
|
1883
|
+
const t = await fetchOutputTokenTotals(resolve(process.cwd()));
|
|
1884
|
+
if (t && Number.isFinite(t.globalTotal)) globalOutputTokens = t.globalTotal;
|
|
1885
|
+
}
|
|
1813
1886
|
// Show which models will answer, straight from user settings (also validates auth).
|
|
1814
1887
|
try {
|
|
1815
1888
|
const probe = await fetchAgentPayload([{ role: "user", content: "ping" }]);
|
|
@@ -1957,6 +2030,40 @@ async function main() {
|
|
|
1957
2030
|
console.log(dim("Session cleared — starting fresh.\n"));
|
|
1958
2031
|
continue;
|
|
1959
2032
|
}
|
|
2033
|
+
if (line === "/output-token") {
|
|
2034
|
+
// Task #104: print the OUTPUT-token total for THIS workspace (and the global one).
|
|
2035
|
+
const t = await fetchOutputTokenTotals(cwd);
|
|
2036
|
+
if (t && Number.isFinite(t.globalTotal)) globalOutputTokens = t.globalTotal;
|
|
2037
|
+
const ws = t && Number.isFinite(t.workspaceTotal) ? t.workspaceTotal : 0;
|
|
2038
|
+
const gl = t && Number.isFinite(t.globalTotal) ? t.globalTotal : globalOutputTokens;
|
|
2039
|
+
console.log(
|
|
2040
|
+
cyan(`↓ output tokens`) +
|
|
2041
|
+
dim(` — this workspace: `) +
|
|
2042
|
+
`${fmtTokens(ws)}` +
|
|
2043
|
+
dim(` · global (you + coding server): `) +
|
|
2044
|
+
`${fmtTokens(gl)}\n`
|
|
2045
|
+
);
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
if (line === "/reset-output-token-global") {
|
|
2049
|
+
// Task #104: zero the GLOBAL (user + coding-URL) output-token counter after confirm.
|
|
2050
|
+
const ok = await askYesNo(
|
|
2051
|
+
`Reset the GLOBAL output-token total (currently ${fmtTokens(globalOutputTokens)}) to 0?`,
|
|
2052
|
+
false
|
|
2053
|
+
);
|
|
2054
|
+
if (!ok) {
|
|
2055
|
+
console.log(dim("Cancelled — global output-token total unchanged.\n"));
|
|
2056
|
+
continue;
|
|
2057
|
+
}
|
|
2058
|
+
const done = await resetGlobalOutputTokens();
|
|
2059
|
+
if (done) {
|
|
2060
|
+
globalOutputTokens = 0;
|
|
2061
|
+
console.log(green("✓ global output-token total reset to 0.\n"));
|
|
2062
|
+
} else {
|
|
2063
|
+
console.log(dim("Could not reset the global output-token total (API error).\n"));
|
|
2064
|
+
}
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
1960
2067
|
if (line.startsWith("/revert")) {
|
|
1961
2068
|
const runId = line.split(/\s+/)[1] ?? "";
|
|
1962
2069
|
await new Promise((res) => {
|
package/gonext_agent_chat.py
CHANGED
|
@@ -4886,6 +4886,10 @@ def run_agent_chat(cfg):
|
|
|
4886
4886
|
# Largest SINGLE-request prompt-token count seen this turn (task #84) — the
|
|
4887
4887
|
# peak context size, not the sum. The REPL keeps a per-workspace max of this.
|
|
4888
4888
|
self._code_tokens_max = 0
|
|
4889
|
+
# Running total of OUTPUT (completion) tokens the CODE model GENERATED across
|
|
4890
|
+
# the turn (task #104). Reported by the server usage; the REPL adds this turn's
|
|
4891
|
+
# total into the per-workspace + per-(user, coding-URL) global lifetime counters.
|
|
4892
|
+
self._code_output_tokens_total = 0
|
|
4889
4893
|
# Loop-breaker (#95 B1): how many CONSECUTIVE generations have produced a code
|
|
4890
4894
|
# block that STILL won't compile after our repair. A weak coder can get stuck
|
|
4891
4895
|
# re-emitting the same unparseable tool call (seen live: create_file with a
|
|
@@ -5225,6 +5229,23 @@ def run_agent_chat(cfg):
|
|
|
5225
5229
|
})
|
|
5226
5230
|
except Exception: # noqa: BLE001
|
|
5227
5231
|
pass
|
|
5232
|
+
# Task #104: accumulate OUTPUT (completion) tokens this step and report the
|
|
5233
|
+
# running total. Unlike input, there's no request-time estimate — output is
|
|
5234
|
+
# only known once the model finished, so we emit after the call returns.
|
|
5235
|
+
try:
|
|
5236
|
+
tu = getattr(msg, "token_usage", None)
|
|
5237
|
+
step_out = int(getattr(tu, "output_tokens", 0) or 0) if tu else 0
|
|
5238
|
+
if step_out > 0:
|
|
5239
|
+
self._code_output_tokens_total += step_out
|
|
5240
|
+
_emit({
|
|
5241
|
+
"type": "tokens",
|
|
5242
|
+
"codeInput": self._code_tokens_total,
|
|
5243
|
+
"codeMax": self._code_tokens_max,
|
|
5244
|
+
"codeOutput": self._code_output_tokens_total,
|
|
5245
|
+
"stepOutput": step_out,
|
|
5246
|
+
})
|
|
5247
|
+
except Exception: # noqa: BLE001
|
|
5248
|
+
pass
|
|
5228
5249
|
try:
|
|
5229
5250
|
content = getattr(msg, "content", None)
|
|
5230
5251
|
if content is None:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.295",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|