@tiens.nguyen/gonext-local-worker 1.0.249 → 1.0.251
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 +18 -0
- package/gonext-repl.mjs +18 -1
- package/gonext_agent_chat.py +44 -0
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1980,6 +1980,10 @@ async function runAgentChatJob(job) {
|
|
|
1980
1980
|
let inThink = false;
|
|
1981
1981
|
let finalText = "";
|
|
1982
1982
|
let answerStreamed = false; // an answer_stream delta already delivered the visible text
|
|
1983
|
+
// Task #83: running total of prompt tokens sent to the agent CODE model. Updated on
|
|
1984
|
+
// each python "tokens" event, persisted to the job doc (Mongo) so the REPL can show
|
|
1985
|
+
// it live and it survives on the completed job.
|
|
1986
|
+
let latestCodeTokens = 0;
|
|
1983
1987
|
// Raw model-stream tokens (event.type === "stream") are Python code + `Thought:`
|
|
1984
1988
|
// prose + <code>/<|"|> fragments. The web Thought panel renders reasoning as
|
|
1985
1989
|
// markdown, which turns `#`-prefixed code comments into <h1> headings and
|
|
@@ -2106,6 +2110,19 @@ async function runAgentChatJob(job) {
|
|
|
2106
2110
|
}
|
|
2107
2111
|
enqueueText(event.text);
|
|
2108
2112
|
answerStreamed = true;
|
|
2113
|
+
} else if (event.type === "tokens" && Number.isFinite(event.codeInput)) {
|
|
2114
|
+
// Prompt tokens sent to the agent CODE model (task #83). Persist the running
|
|
2115
|
+
// total to the job doc so it's in Mongo and the REPL can render it live. Steps
|
|
2116
|
+
// are seconds apart, so a fire-and-forget PATCH per event is cheap; it only
|
|
2117
|
+
// touches agentCodeTokens (never resultText/jobStatus), so it can't race the
|
|
2118
|
+
// chunk/completed writes.
|
|
2119
|
+
if (event.codeInput > latestCodeTokens) {
|
|
2120
|
+
latestCodeTokens = event.codeInput;
|
|
2121
|
+
workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
2122
|
+
method: "PATCH",
|
|
2123
|
+
body: JSON.stringify({ agentCodeTokens: latestCodeTokens }),
|
|
2124
|
+
}).catch(() => {});
|
|
2125
|
+
}
|
|
2109
2126
|
} else if (event.type === "final" && typeof event.text === "string") {
|
|
2110
2127
|
closeStreamFence();
|
|
2111
2128
|
if (inThink) {
|
|
@@ -2156,6 +2173,7 @@ async function runAgentChatJob(job) {
|
|
|
2156
2173
|
// the whole story.
|
|
2157
2174
|
resultText: answerStreamed ? fullText : finalText || fullText,
|
|
2158
2175
|
tokenCount: Math.max(1, Math.ceil((finalText || fullText).length / 4)),
|
|
2176
|
+
...(latestCodeTokens > 0 ? { agentCodeTokens: latestCodeTokens } : {}),
|
|
2159
2177
|
totalTimeSeconds,
|
|
2160
2178
|
}),
|
|
2161
2179
|
});
|
package/gonext-repl.mjs
CHANGED
|
@@ -122,6 +122,12 @@ 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
|
+
// Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
|
|
126
|
+
const fmtTokens = (n) => {
|
|
127
|
+
if (n < 1000) return String(n);
|
|
128
|
+
if (n < 1_000_000) return `${(n / 1000).toFixed(n < 100_000 ? 1 : 0)}k`;
|
|
129
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
130
|
+
};
|
|
125
131
|
const BULLET = "●"; // a completed action
|
|
126
132
|
|
|
127
133
|
// ---------- flags ----------
|
|
@@ -1065,6 +1071,9 @@ async function runAgentTurn(history) {
|
|
|
1065
1071
|
// poll figure out how much of the FINAL answer is still unshown even when resultText
|
|
1066
1072
|
// was replaced (not extended) on completion. See the completed-branch recovery below.
|
|
1067
1073
|
let liveAnswer = "";
|
|
1074
|
+
// Running total of prompt tokens sent to the agent CODE model this turn (task #83),
|
|
1075
|
+
// read from the job doc each poll. Rendered on the thinking line after the playful word.
|
|
1076
|
+
let latestCodeTokens = 0;
|
|
1068
1077
|
// First few KB of the raw stream as consumed — a cheap fingerprint to verify the
|
|
1069
1078
|
// completed resultText really EXTENDS what we streamed (an old worker replaces it
|
|
1070
1079
|
// with the shorter bare answer, which must not be sliced by shownChars).
|
|
@@ -1139,9 +1148,13 @@ async function runAgentTurn(history) {
|
|
|
1139
1148
|
// Line 2 (indented): the SPINNER + the playful word together, both in the cycling
|
|
1140
1149
|
// color — the spinner belongs with the word (dot on the label line, spinner leading
|
|
1141
1150
|
// the flavor line).
|
|
1151
|
+
// Task #83: after the playful word, ALWAYS show the running agent-code-model token
|
|
1152
|
+
// count (dim), e.g. " ⠙ Caffeinating… · 12.3k tok" — starts at "· 0 tok" and climbs
|
|
1153
|
+
// as steps send more, so it's visible from the first thinking frame.
|
|
1154
|
+
const tokLabel = dim(` · ${fmtTokens(latestCodeTokens)} tok`);
|
|
1142
1155
|
process.stdout.write(
|
|
1143
1156
|
`${green(BULLET)} ${green(`${fit(primary)}… (${secs}s)`)}` +
|
|
1144
|
-
"\n" + ` ${color256(wc, `${glyph} ${thinkWord}…`)}`
|
|
1157
|
+
"\n" + ` ${color256(wc, `${glyph} ${thinkWord}…`)}${tokLabel}`
|
|
1145
1158
|
);
|
|
1146
1159
|
statusShown = true;
|
|
1147
1160
|
};
|
|
@@ -1407,6 +1420,10 @@ async function runAgentTurn(history) {
|
|
|
1407
1420
|
continue;
|
|
1408
1421
|
}
|
|
1409
1422
|
jobRunning = job.jobStatus === "running";
|
|
1423
|
+
// Live agent-code-model token total (task #83) — monotonic, shown on the thinking line.
|
|
1424
|
+
if (Number.isFinite(job.agentCodeTokens) && job.agentCodeTokens > latestCodeTokens) {
|
|
1425
|
+
latestCodeTokens = job.agentCodeTokens;
|
|
1426
|
+
}
|
|
1410
1427
|
const text = typeof job.resultText === "string" ? job.resultText : "";
|
|
1411
1428
|
if (job.jobStatus === "completed") {
|
|
1412
1429
|
// The job can flip to "completed" between polls with authoritative resultText that
|
package/gonext_agent_chat.py
CHANGED
|
@@ -3936,6 +3936,10 @@ def run_agent_chat(cfg):
|
|
|
3936
3936
|
# literal messages array sent to /v1/chat/completions.
|
|
3937
3937
|
class _LoggingModel(OpenAIServerModel):
|
|
3938
3938
|
def __init__(self, *a, stream_agent=False, **kw):
|
|
3939
|
+
# Running total of PROMPT (input) tokens sent to the CODE model across the
|
|
3940
|
+
# turn (task #83). Only this wrapper counts — the chat/brain helper calls
|
|
3941
|
+
# (_chat_completion) don't route through here, so this is "code model only".
|
|
3942
|
+
self._code_tokens_total = 0
|
|
3939
3943
|
# stream_agent: request the completion with stream=True and reassemble the
|
|
3940
3944
|
# deltas into one message. Enabled for Ollama, where a long single-shot
|
|
3941
3945
|
# (non-stream) generation sends nothing until the very end — a reverse proxy
|
|
@@ -4129,6 +4133,29 @@ def run_agent_chat(cfg):
|
|
|
4129
4133
|
token_usage=TokenUsage(input_tokens=in_tok, output_tokens=out_tok),
|
|
4130
4134
|
)
|
|
4131
4135
|
|
|
4136
|
+
def _estimate_prompt_tokens(self, args, kwargs):
|
|
4137
|
+
"""Fallback token estimate (~4 chars/token) from the outgoing messages, used
|
|
4138
|
+
only when the model server doesn't report prompt usage (task #83)."""
|
|
4139
|
+
msgs = kwargs.get("messages")
|
|
4140
|
+
if msgs is None and args:
|
|
4141
|
+
msgs = args[0]
|
|
4142
|
+
if not msgs:
|
|
4143
|
+
return 0
|
|
4144
|
+
chars = 0
|
|
4145
|
+
try:
|
|
4146
|
+
for m in msgs:
|
|
4147
|
+
c = m.get("content") if isinstance(m, dict) else getattr(m, "content", None)
|
|
4148
|
+
if isinstance(c, list): # multimodal content parts
|
|
4149
|
+
for part in c:
|
|
4150
|
+
t = part.get("text") if isinstance(part, dict) else None
|
|
4151
|
+
if isinstance(t, str):
|
|
4152
|
+
chars += len(t)
|
|
4153
|
+
elif isinstance(c, str):
|
|
4154
|
+
chars += len(c)
|
|
4155
|
+
except Exception: # noqa: BLE001
|
|
4156
|
+
return 0
|
|
4157
|
+
return max(1, chars // 4)
|
|
4158
|
+
|
|
4132
4159
|
def generate(self, *args, **kwargs):
|
|
4133
4160
|
# Two safeguards on each step's reply BEFORE smolagents parses it for the
|
|
4134
4161
|
# code block / final_answer:
|
|
@@ -4199,6 +4226,23 @@ def run_agent_chat(cfg):
|
|
|
4199
4226
|
raise last_err
|
|
4200
4227
|
finally:
|
|
4201
4228
|
hb_stop.set()
|
|
4229
|
+
# Task #83: tally PROMPT tokens sent to the code model this step, then emit
|
|
4230
|
+
# the running total so the worker can persist it (Mongo) and the REPL can
|
|
4231
|
+
# show it live. Best-effort — never let accounting break the run.
|
|
4232
|
+
try:
|
|
4233
|
+
tu = getattr(msg, "token_usage", None)
|
|
4234
|
+
step_in = int(getattr(tu, "input_tokens", 0) or 0) if tu else 0
|
|
4235
|
+
if step_in <= 0:
|
|
4236
|
+
step_in = self._estimate_prompt_tokens(args, kwargs)
|
|
4237
|
+
if step_in > 0:
|
|
4238
|
+
self._code_tokens_total += step_in
|
|
4239
|
+
_emit({
|
|
4240
|
+
"type": "tokens",
|
|
4241
|
+
"codeInput": self._code_tokens_total,
|
|
4242
|
+
"stepInput": step_in,
|
|
4243
|
+
})
|
|
4244
|
+
except Exception: # noqa: BLE001
|
|
4245
|
+
pass
|
|
4202
4246
|
try:
|
|
4203
4247
|
content = getattr(msg, "content", None)
|
|
4204
4248
|
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.251",
|
|
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",
|