@tiens.nguyen/gonext-local-worker 1.0.251 → 1.0.253
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 +19 -8
- package/gonext-repl.mjs +27 -4
- package/gonext_agent_chat.py +26 -9
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1984,6 +1984,8 @@ async function runAgentChatJob(job) {
|
|
|
1984
1984
|
// each python "tokens" event, persisted to the job doc (Mongo) so the REPL can show
|
|
1985
1985
|
// it live and it survives on the completed job.
|
|
1986
1986
|
let latestCodeTokens = 0;
|
|
1987
|
+
// Task #84: peak single-request prompt-token count seen this turn (not the sum).
|
|
1988
|
+
let latestMaxCodeTokens = 0;
|
|
1987
1989
|
// Raw model-stream tokens (event.type === "stream") are Python code + `Thought:`
|
|
1988
1990
|
// prose + <code>/<|"|> fragments. The web Thought panel renders reasoning as
|
|
1989
1991
|
// markdown, which turns `#`-prefixed code comments into <h1> headings and
|
|
@@ -2111,16 +2113,24 @@ async function runAgentChatJob(job) {
|
|
|
2111
2113
|
enqueueText(event.text);
|
|
2112
2114
|
answerStreamed = true;
|
|
2113
2115
|
} else if (event.type === "tokens" && Number.isFinite(event.codeInput)) {
|
|
2114
|
-
// Prompt tokens sent to the agent CODE model (
|
|
2115
|
-
//
|
|
2116
|
-
// are seconds apart, so a fire-and-forget PATCH per event is cheap;
|
|
2117
|
-
// touches agentCodeTokens (never resultText/jobStatus),
|
|
2118
|
-
// chunk/completed writes.
|
|
2119
|
-
|
|
2120
|
-
|
|
2116
|
+
// Prompt tokens sent to the agent CODE model (tasks #83 accumulated + #84 peak).
|
|
2117
|
+
// Persist both to the job doc so they're in Mongo and the REPL can render them
|
|
2118
|
+
// live. Steps are seconds apart, so a fire-and-forget PATCH per event is cheap;
|
|
2119
|
+
// it only touches agentCodeTokens/agentMaxCodeTokens (never resultText/jobStatus),
|
|
2120
|
+
// so it can't race the chunk/completed writes.
|
|
2121
|
+
const nextTotal = Math.max(latestCodeTokens, event.codeInput);
|
|
2122
|
+
const nextMax = Number.isFinite(event.codeMax)
|
|
2123
|
+
? Math.max(latestMaxCodeTokens, event.codeMax)
|
|
2124
|
+
: latestMaxCodeTokens;
|
|
2125
|
+
if (nextTotal !== latestCodeTokens || nextMax !== latestMaxCodeTokens) {
|
|
2126
|
+
latestCodeTokens = nextTotal;
|
|
2127
|
+
latestMaxCodeTokens = nextMax;
|
|
2121
2128
|
workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
2122
2129
|
method: "PATCH",
|
|
2123
|
-
body: JSON.stringify({
|
|
2130
|
+
body: JSON.stringify({
|
|
2131
|
+
agentCodeTokens: latestCodeTokens,
|
|
2132
|
+
agentMaxCodeTokens: latestMaxCodeTokens,
|
|
2133
|
+
}),
|
|
2124
2134
|
}).catch(() => {});
|
|
2125
2135
|
}
|
|
2126
2136
|
} else if (event.type === "final" && typeof event.text === "string") {
|
|
@@ -2174,6 +2184,7 @@ async function runAgentChatJob(job) {
|
|
|
2174
2184
|
resultText: answerStreamed ? fullText : finalText || fullText,
|
|
2175
2185
|
tokenCount: Math.max(1, Math.ceil((finalText || fullText).length / 4)),
|
|
2176
2186
|
...(latestCodeTokens > 0 ? { agentCodeTokens: latestCodeTokens } : {}),
|
|
2187
|
+
...(latestMaxCodeTokens > 0 ? { agentMaxCodeTokens: latestMaxCodeTokens } : {}),
|
|
2177
2188
|
totalTimeSeconds,
|
|
2178
2189
|
}),
|
|
2179
2190
|
});
|
package/gonext-repl.mjs
CHANGED
|
@@ -122,6 +122,7 @@ const SPINNERS = [
|
|
|
122
122
|
// green, cyan, blue, indigo, pink, amber).
|
|
123
123
|
const WAIT_COLORS_256 = [120, 87, 111, 147, 218, 221];
|
|
124
124
|
const color256 = (n, s) => `\x1b[38;5;${n}m${s}\x1b[0m`;
|
|
125
|
+
const orange = (s) => color256(208, s); // peak-token label (task #84)
|
|
125
126
|
// Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
|
|
126
127
|
const fmtTokens = (n) => {
|
|
127
128
|
if (n < 1000) return String(n);
|
|
@@ -564,6 +565,16 @@ async function loadSessionServer(cwd) {
|
|
|
564
565
|
}
|
|
565
566
|
}
|
|
566
567
|
|
|
568
|
+
// Peak single-request agent-code-model token count remembered per folder (task #84).
|
|
569
|
+
async function loadSessionMaxCodeTokens(cwd) {
|
|
570
|
+
try {
|
|
571
|
+
const raw = JSON.parse(await readFile(sessionFilePath(cwd), "utf8"));
|
|
572
|
+
return Number.isFinite(raw?.maxCodeTokens) ? raw.maxCodeTokens : 0;
|
|
573
|
+
} catch {
|
|
574
|
+
return 0;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
567
578
|
async function saveSession(cwd, history) {
|
|
568
579
|
try {
|
|
569
580
|
await mkdir(SESSIONS_DIR, { recursive: true });
|
|
@@ -576,6 +587,7 @@ async function saveSession(cwd, history) {
|
|
|
576
587
|
updatedAt: new Date().toISOString(),
|
|
577
588
|
testAuto: sessionTestAuto,
|
|
578
589
|
deployServer: selectedServer, // remember the /server pick per folder (task #69)
|
|
590
|
+
maxCodeTokens: sessionMaxCodeTokens, // peak per-request tokens (task #84)
|
|
579
591
|
history: trimmed,
|
|
580
592
|
},
|
|
581
593
|
null,
|
|
@@ -919,6 +931,10 @@ let alwaysExtendOnMaxStep = false;
|
|
|
919
931
|
// Deploy target chosen via /server (task #69): {name, host, user} or null. Session-scoped;
|
|
920
932
|
// sent to /agent-ask as deployServer so the agent deploys to this host with KEY auth.
|
|
921
933
|
let selectedServer = null;
|
|
934
|
+
// Task #84: peak single-request agent-code-model token count for THIS workspace, kept
|
|
935
|
+
// as a max across turns and persisted per folder in the session file. Shown in orange
|
|
936
|
+
// next to the live token count.
|
|
937
|
+
let sessionMaxCodeTokens = 0;
|
|
922
938
|
|
|
923
939
|
// While a turn runs, mute readline's own echo/redraws so keystrokes can't corrupt the
|
|
924
940
|
// live status display and there's no way to type a new question — only Ctrl+C works.
|
|
@@ -1148,10 +1164,12 @@ async function runAgentTurn(history) {
|
|
|
1148
1164
|
// Line 2 (indented): the SPINNER + the playful word together, both in the cycling
|
|
1149
1165
|
// color — the spinner belongs with the word (dot on the label line, spinner leading
|
|
1150
1166
|
// the flavor line).
|
|
1151
|
-
// Task #83: after the playful word,
|
|
1152
|
-
// count (dim)
|
|
1153
|
-
//
|
|
1154
|
-
const tokLabel =
|
|
1167
|
+
// Task #83/#84: after the playful word, show the running agent-code-model token
|
|
1168
|
+
// count (dim) plus the per-workspace PEAK single-request count (orange), e.g.
|
|
1169
|
+
// " ⠙ Caffeinating… · 12.3k tok · 8.1k max". Both start at 0 and climb.
|
|
1170
|
+
const tokLabel =
|
|
1171
|
+
dim(` · ${fmtTokens(latestCodeTokens)} tok`) +
|
|
1172
|
+
orange(` · ${fmtTokens(sessionMaxCodeTokens)} max`);
|
|
1155
1173
|
process.stdout.write(
|
|
1156
1174
|
`${green(BULLET)} ${green(`${fit(primary)}… (${secs}s)`)}` +
|
|
1157
1175
|
"\n" + ` ${color256(wc, `${glyph} ${thinkWord}…`)}${tokLabel}`
|
|
@@ -1424,6 +1442,10 @@ async function runAgentTurn(history) {
|
|
|
1424
1442
|
if (Number.isFinite(job.agentCodeTokens) && job.agentCodeTokens > latestCodeTokens) {
|
|
1425
1443
|
latestCodeTokens = job.agentCodeTokens;
|
|
1426
1444
|
}
|
|
1445
|
+
// Peak single-request tokens (task #84): keep the per-workspace max across turns.
|
|
1446
|
+
if (Number.isFinite(job.agentMaxCodeTokens) && job.agentMaxCodeTokens > sessionMaxCodeTokens) {
|
|
1447
|
+
sessionMaxCodeTokens = job.agentMaxCodeTokens;
|
|
1448
|
+
}
|
|
1427
1449
|
const text = typeof job.resultText === "string" ? job.resultText : "";
|
|
1428
1450
|
if (job.jobStatus === "completed") {
|
|
1429
1451
|
// The job can flip to "completed" between polls with authoritative resultText that
|
|
@@ -1541,6 +1563,7 @@ async function main() {
|
|
|
1541
1563
|
// reflect them (both survive a fresh terminal in the same project).
|
|
1542
1564
|
sessionTestAuto = await loadSessionTestAuto(resolve(process.cwd()));
|
|
1543
1565
|
selectedServer = await loadSessionServer(resolve(process.cwd()));
|
|
1566
|
+
sessionMaxCodeTokens = await loadSessionMaxCodeTokens(resolve(process.cwd()));
|
|
1544
1567
|
// Show which models will answer, straight from user settings (also validates auth).
|
|
1545
1568
|
try {
|
|
1546
1569
|
const probe = await fetchAgentPayload([{ role: "user", content: "ping" }]);
|
package/gonext_agent_chat.py
CHANGED
|
@@ -3940,6 +3940,9 @@ def run_agent_chat(cfg):
|
|
|
3940
3940
|
# turn (task #83). Only this wrapper counts — the chat/brain helper calls
|
|
3941
3941
|
# (_chat_completion) don't route through here, so this is "code model only".
|
|
3942
3942
|
self._code_tokens_total = 0
|
|
3943
|
+
# Largest SINGLE-request prompt-token count seen this turn (task #84) — the
|
|
3944
|
+
# peak context size, not the sum. The REPL keeps a per-workspace max of this.
|
|
3945
|
+
self._code_tokens_max = 0
|
|
3943
3946
|
# stream_agent: request the completion with stream=True and reassemble the
|
|
3944
3947
|
# deltas into one message. Enabled for Ollama, where a long single-shot
|
|
3945
3948
|
# (non-stream) generation sends nothing until the very end — a reverse proxy
|
|
@@ -4182,6 +4185,19 @@ def run_agent_chat(cfg):
|
|
|
4182
4185
|
# _mark_first), read by the heartbeat below to switch its wording.
|
|
4183
4186
|
self._first_token_seen = False
|
|
4184
4187
|
|
|
4188
|
+
# Task #83: emit the PROMPT-token estimate NOW, at request time — so the
|
|
4189
|
+
# code-model token count climbs the instant a step is SENT, not only after a
|
|
4190
|
+
# slow step returns usage (a cold gemma4 can wait minutes for the first token,
|
|
4191
|
+
# during which the count would otherwise sit at 0). We reconcile to the exact
|
|
4192
|
+
# server-reported prompt_tokens after the call when it's higher.
|
|
4193
|
+
_step_est = self._estimate_prompt_tokens(args, kwargs)
|
|
4194
|
+
if _step_est > 0:
|
|
4195
|
+
self._code_tokens_total += _step_est
|
|
4196
|
+
if _step_est > self._code_tokens_max:
|
|
4197
|
+
self._code_tokens_max = _step_est
|
|
4198
|
+
_emit({"type": "tokens", "codeInput": self._code_tokens_total,
|
|
4199
|
+
"codeMax": self._code_tokens_max, "stepInput": _step_est})
|
|
4200
|
+
|
|
4185
4201
|
def _heartbeat():
|
|
4186
4202
|
waited = 0
|
|
4187
4203
|
while not hb_stop.wait(45):
|
|
@@ -4226,20 +4242,21 @@ def run_agent_chat(cfg):
|
|
|
4226
4242
|
raise last_err
|
|
4227
4243
|
finally:
|
|
4228
4244
|
hb_stop.set()
|
|
4229
|
-
# Task #83:
|
|
4230
|
-
#
|
|
4231
|
-
#
|
|
4245
|
+
# Task #83: reconcile the pre-call estimate with the server's EXACT
|
|
4246
|
+
# prompt_tokens when it's higher — keeps the running total monotonic (the
|
|
4247
|
+
# estimate already showed the count climbing at request time). Best-effort.
|
|
4232
4248
|
try:
|
|
4233
4249
|
tu = getattr(msg, "token_usage", None)
|
|
4234
|
-
|
|
4235
|
-
if
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4250
|
+
exact = int(getattr(tu, "input_tokens", 0) or 0) if tu else 0
|
|
4251
|
+
if exact > _step_est:
|
|
4252
|
+
self._code_tokens_total += (exact - _step_est)
|
|
4253
|
+
if exact > self._code_tokens_max:
|
|
4254
|
+
self._code_tokens_max = exact
|
|
4239
4255
|
_emit({
|
|
4240
4256
|
"type": "tokens",
|
|
4241
4257
|
"codeInput": self._code_tokens_total,
|
|
4242
|
-
"
|
|
4258
|
+
"codeMax": self._code_tokens_max,
|
|
4259
|
+
"stepInput": exact,
|
|
4243
4260
|
})
|
|
4244
4261
|
except Exception: # noqa: BLE001
|
|
4245
4262
|
pass
|
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.253",
|
|
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",
|