@tiens.nguyen/gonext-local-worker 1.0.301 → 1.0.303
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 +17 -0
- package/gonext-repl.mjs +29 -4
- package/gonext_agent_chat.py +27 -1
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1905,6 +1905,9 @@ async function runAgentChatJob(job) {
|
|
|
1905
1905
|
agentModelId: payload?.agentModelId ?? "",
|
|
1906
1906
|
codingBaseURL: payload?.codingBaseURL ?? "",
|
|
1907
1907
|
codingModelId: payload?.codingModelId ?? "",
|
|
1908
|
+
// Task #107: when true, python emits a {type:code_response} event per code-model
|
|
1909
|
+
// call so the worker persists the full raw text to Mongo.
|
|
1910
|
+
saveFullResponse: payload?.saveFullResponse === true,
|
|
1908
1911
|
// Dedicated SEARCH model (#105): when set, web_search synthesizes its results into a
|
|
1909
1912
|
// cited answer with this model instead of returning raw read pages. Empty = no synth.
|
|
1910
1913
|
searchBaseURL: payload?.searchBaseURL ?? "",
|
|
@@ -2170,6 +2173,20 @@ async function runAgentChatJob(job) {
|
|
|
2170
2173
|
}),
|
|
2171
2174
|
}).catch(() => {});
|
|
2172
2175
|
}
|
|
2176
|
+
} else if (event.type === "code_response" && typeof event.text === "string") {
|
|
2177
|
+
// Task #107: the user's Save-Full-Response toggle is on — persist this code-model
|
|
2178
|
+
// response's full raw text to Mongo. Fire-and-forget; never blocks the turn.
|
|
2179
|
+
workerFetch(`/api/worker/agent-response`, {
|
|
2180
|
+
method: "POST",
|
|
2181
|
+
body: JSON.stringify({
|
|
2182
|
+
jobId,
|
|
2183
|
+
step: Number.isFinite(event.step) ? event.step : 0,
|
|
2184
|
+
text: event.text,
|
|
2185
|
+
inputTokens: Number.isFinite(event.inputTokens) ? event.inputTokens : 0,
|
|
2186
|
+
outputTokens: Number.isFinite(event.outputTokens) ? event.outputTokens : 0,
|
|
2187
|
+
codingUrl: payload?.codingBaseURL ?? "",
|
|
2188
|
+
}),
|
|
2189
|
+
}).catch(() => {});
|
|
2173
2190
|
} else if (event.type === "final" && typeof event.text === "string") {
|
|
2174
2191
|
closeStreamFence();
|
|
2175
2192
|
if (inThink) {
|
package/gonext-repl.mjs
CHANGED
|
@@ -1383,11 +1383,15 @@ async function runAgentTurn(history) {
|
|
|
1383
1383
|
// > the "almost done" heartbeat > the bare fallback.
|
|
1384
1384
|
const max = Math.max(24, (process.stdout.columns || 80) - 14);
|
|
1385
1385
|
const fit = (s) => (s.length > max ? s.slice(0, max - 1) + "…" : s);
|
|
1386
|
+
// Task #104: global (user + coding-URL) OUTPUT-token total, ↓ = generated. Add THIS
|
|
1387
|
+
// turn's in-progress output (latestOutputTokens) to the persisted global so the live
|
|
1388
|
+
// count climbs in real time and matches the web; it's reset to 0 once the turn's total
|
|
1389
|
+
// is POSTed into globalOutputTokens (so it's never double-counted).
|
|
1390
|
+
const liveOutputTotal = globalOutputTokens + latestOutputTokens;
|
|
1386
1391
|
const tokLabel =
|
|
1387
1392
|
dim(` · ${fmtTokens(latestCodeTokens)} tok`) +
|
|
1388
1393
|
orange(` · ${fmtTokens(sessionMaxCodeTokens)} max`) +
|
|
1389
|
-
|
|
1390
|
-
(globalOutputTokens > 0 ? cyan(` · ↓ ${fmtTokens(globalOutputTokens)}`) : "");
|
|
1394
|
+
(liveOutputTotal > 0 ? cyan(` · ↓ ${fmtTokens(liveOutputTotal)}`) : "");
|
|
1391
1395
|
// Task #96: the thought line is "clickable" only when the fuller thought has MORE text than
|
|
1392
1396
|
// fits on line 1 (a live command / bare "Thinking" / a thought that already fits has nothing
|
|
1393
1397
|
// to reveal). Line 1 (what's happening now): a live command > the model's current Thought >
|
|
@@ -1765,6 +1769,9 @@ async function runAgentTurn(history) {
|
|
|
1765
1769
|
outputTokensPosted = true;
|
|
1766
1770
|
void addOutputTokens(resolve(process.cwd()), latestOutputTokens).then((t) => {
|
|
1767
1771
|
if (t && Number.isFinite(t.globalTotal)) globalOutputTokens = t.globalTotal;
|
|
1772
|
+
// This turn's total is now folded into globalOutputTokens — zero the live
|
|
1773
|
+
// per-turn tally so the ↓ (global + latest) doesn't double-count it.
|
|
1774
|
+
latestOutputTokens = 0;
|
|
1768
1775
|
});
|
|
1769
1776
|
}
|
|
1770
1777
|
// The job can flip to "completed" between polls with authoritative resultText that
|
|
@@ -1802,7 +1809,14 @@ async function runAgentTurn(history) {
|
|
|
1802
1809
|
}
|
|
1803
1810
|
}
|
|
1804
1811
|
clearStatus();
|
|
1805
|
-
|
|
1812
|
+
// Task #104: expose this turn's code-model INPUT + OUTPUT token totals so the caller
|
|
1813
|
+
// can print a persistent footer at the bottom of the screen.
|
|
1814
|
+
return {
|
|
1815
|
+
text: answerFrom(text),
|
|
1816
|
+
shown: answerShownLive,
|
|
1817
|
+
inputTokens: latestCodeTokens,
|
|
1818
|
+
outputTokens: latestOutputTokens,
|
|
1819
|
+
};
|
|
1806
1820
|
}
|
|
1807
1821
|
if (text.length > shownChars) {
|
|
1808
1822
|
if (seenRaw.length < 4096) {
|
|
@@ -2112,7 +2126,8 @@ async function main() {
|
|
|
2112
2126
|
}
|
|
2113
2127
|
history.push({ role: "user", content: taskLine });
|
|
2114
2128
|
try {
|
|
2115
|
-
const { text: answer, shown, cancelled } =
|
|
2129
|
+
const { text: answer, shown, cancelled, inputTokens, outputTokens } =
|
|
2130
|
+
await runAgentTurn(history);
|
|
2116
2131
|
if (answer) {
|
|
2117
2132
|
history.push({ role: "assistant", content: answer });
|
|
2118
2133
|
// Persist after every successful turn (not just on clean exit) — an ungraceful
|
|
@@ -2122,6 +2137,16 @@ async function main() {
|
|
|
2122
2137
|
// A plain-reply answer already streamed live in the loop above — printing it
|
|
2123
2138
|
// again here would just duplicate it; a blank line is enough for spacing.
|
|
2124
2139
|
console.log(shown ? "" : `\n\n${answer}\n`);
|
|
2140
|
+
// Task #104: persistent per-turn token footer at the bottom of the screen — the
|
|
2141
|
+
// last turn's code-model INPUT + OUTPUT tokens, kept visible above the next prompt.
|
|
2142
|
+
// Only for turns that actually used the code model (plain-reply greetings = 0/0).
|
|
2143
|
+
if ((inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0) {
|
|
2144
|
+
console.log(
|
|
2145
|
+
dim(" tokens · in ") +
|
|
2146
|
+
fmtTokens(inputTokens ?? 0) +
|
|
2147
|
+
cyan(` · ↓ out ${fmtTokens(outputTokens ?? 0)}`)
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2125
2150
|
} else {
|
|
2126
2151
|
history.pop(); // aborted/failed/cancelled turn — don't poison the next request
|
|
2127
2152
|
// A turn that ended WITHOUT an answer (Ctrl+C cancel, or an abort/failure) must not
|
package/gonext_agent_chat.py
CHANGED
|
@@ -3117,6 +3117,8 @@ def run_agent_chat(cfg):
|
|
|
3117
3117
|
# also fixes an older API layer that appended /v1 after the native /api path).
|
|
3118
3118
|
raw_coding_base = _normalize_openai_base(cfg.get("codingBaseURL") or "")
|
|
3119
3119
|
raw_coding_model = (cfg.get("codingModelId") or "").strip()
|
|
3120
|
+
# Task #107: when on, emit each code-model response so the worker persists it to Mongo.
|
|
3121
|
+
save_full_response = bool(cfg.get("saveFullResponse"))
|
|
3120
3122
|
if raw_coding_base:
|
|
3121
3123
|
same_server = raw_coding_base.rstrip("/") == (agent_base_url or "").rstrip("/")
|
|
3122
3124
|
if same_server and not raw_coding_model:
|
|
@@ -4878,7 +4880,7 @@ def run_agent_chat(cfg):
|
|
|
4878
4880
|
# and any step memory it accumulates. completion_kwargs["messages"] here is the
|
|
4879
4881
|
# literal messages array sent to /v1/chat/completions.
|
|
4880
4882
|
class _LoggingModel(OpenAIServerModel):
|
|
4881
|
-
def __init__(self, *a, stream_agent=False, **kw):
|
|
4883
|
+
def __init__(self, *a, stream_agent=False, save_full_response=False, **kw):
|
|
4882
4884
|
# Running total of PROMPT (input) tokens sent to the CODE model across the
|
|
4883
4885
|
# turn (task #83). Only this wrapper counts — the chat/brain helper calls
|
|
4884
4886
|
# (_chat_completion) don't route through here, so this is "code model only".
|
|
@@ -4904,6 +4906,10 @@ def run_agent_chat(cfg):
|
|
|
4904
4906
|
# idle timer never fires. smolagents still receives the COMPLETE text, so its
|
|
4905
4907
|
# <code>/final_answer parsing is unaffected.
|
|
4906
4908
|
self._stream_agent = bool(stream_agent)
|
|
4909
|
+
# Task #107: persist each code-model response's full text (opt-in) + a 1-based
|
|
4910
|
+
# per-turn call counter used as the stored `step`.
|
|
4911
|
+
self._save_full_response = bool(save_full_response)
|
|
4912
|
+
self._call_index = 0
|
|
4907
4913
|
super().__init__(*a, **kw)
|
|
4908
4914
|
|
|
4909
4915
|
def _generate_once(self, *args, **kwargs):
|
|
@@ -5246,6 +5252,24 @@ def run_agent_chat(cfg):
|
|
|
5246
5252
|
})
|
|
5247
5253
|
except Exception: # noqa: BLE001
|
|
5248
5254
|
pass
|
|
5255
|
+
# Task #107: persist the full RAW code-model response (opt-in) with this call's
|
|
5256
|
+
# token counts, emitted BEFORE any think-stripping/normalization so it matches
|
|
5257
|
+
# exactly what the token counts were computed from. Worker forwards to Mongo.
|
|
5258
|
+
if self._save_full_response:
|
|
5259
|
+
try:
|
|
5260
|
+
self._call_index += 1
|
|
5261
|
+
_raw = getattr(msg, "content", None)
|
|
5262
|
+
_tu2 = getattr(msg, "token_usage", None)
|
|
5263
|
+
_emit({
|
|
5264
|
+
"type": "code_response",
|
|
5265
|
+
"step": self._call_index,
|
|
5266
|
+
"text": _raw if isinstance(_raw, str)
|
|
5267
|
+
else ("" if _raw is None else str(_raw)),
|
|
5268
|
+
"inputTokens": int(getattr(_tu2, "input_tokens", 0) or 0) if _tu2 else 0,
|
|
5269
|
+
"outputTokens": int(getattr(_tu2, "output_tokens", 0) or 0) if _tu2 else 0,
|
|
5270
|
+
})
|
|
5271
|
+
except Exception: # noqa: BLE001
|
|
5272
|
+
pass
|
|
5249
5273
|
try:
|
|
5250
5274
|
content = getattr(msg, "content", None)
|
|
5251
5275
|
if content is None:
|
|
@@ -5515,6 +5539,8 @@ def run_agent_chat(cfg):
|
|
|
5515
5539
|
# Stream for Ollama so a long generation keeps the connection warm and can't
|
|
5516
5540
|
# trip a reverse-proxy idle read-timeout. smolagents still gets the full text.
|
|
5517
5541
|
stream_agent=coding_is_ollama,
|
|
5542
|
+
# Task #107: persist each code-model response to Mongo when the user opted in.
|
|
5543
|
+
save_full_response=save_full_response,
|
|
5518
5544
|
# Cap each HTTP attempt at 600s so a genuinely slow single call (a
|
|
5519
5545
|
# remote M6000/Vulkan box doing a cold ~4-5min prompt-eval) can finish
|
|
5520
5546
|
# on the first try, while a truly hung request still fails into OUR
|
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.303",
|
|
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",
|