@tiens.nguyen/gonext-local-worker 1.0.53 → 1.0.55
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 +3 -1
- package/gonext_agent_chat.py +87 -35
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1245,7 +1245,9 @@ async function runAgentChatJob(job) {
|
|
|
1245
1245
|
let finalText = "";
|
|
1246
1246
|
|
|
1247
1247
|
await runProcessWithStreamingStdout(python, [scriptPath], input, timeoutMs, (event) => {
|
|
1248
|
-
if (event.type === "
|
|
1248
|
+
if (event.type === "log" && typeof event.text === "string") {
|
|
1249
|
+
console.log(`[gonext-agent] ${event.text}`);
|
|
1250
|
+
} else if (event.type === "step" && typeof event.text === "string") {
|
|
1249
1251
|
if (!inThink) {
|
|
1250
1252
|
inThink = true;
|
|
1251
1253
|
enqueueText("<think>");
|
package/gonext_agent_chat.py
CHANGED
|
@@ -12,17 +12,14 @@ Reads on stdin:
|
|
|
12
12
|
"maxSteps": int # default 10
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
Emits NDJSON lines on stdout
|
|
16
|
-
{"type": "
|
|
17
|
-
{"type": "
|
|
18
|
-
|
|
19
|
-
All smolagents/rich console output goes to stderr so stdout stays clean.
|
|
20
|
-
|
|
21
|
-
TLS: uses certifi CA bundle when available (macOS may lack system certs for
|
|
22
|
-
Python urllib), with fallback to the default bundle.
|
|
15
|
+
Emits NDJSON lines on stdout:
|
|
16
|
+
{"type": "log", "text": "..."} — worker logs to console, not shown in chat
|
|
17
|
+
{"type": "step", "text": "..."} — shown in <think> area
|
|
18
|
+
{"type": "final", "text": "..."} — assistant answer
|
|
23
19
|
"""
|
|
24
20
|
import contextlib
|
|
25
21
|
import json
|
|
22
|
+
import re
|
|
26
23
|
import sys
|
|
27
24
|
import urllib.request
|
|
28
25
|
import urllib.error
|
|
@@ -63,6 +60,59 @@ def _http_request_impl(method, url, headers=None, body=None, timeout=20):
|
|
|
63
60
|
return f"Error: {e}"
|
|
64
61
|
|
|
65
62
|
|
|
63
|
+
def _summarise_step(step_log):
|
|
64
|
+
"""Return a short human-readable description of an agent step."""
|
|
65
|
+
tool_calls = getattr(step_log, "tool_calls", None) or []
|
|
66
|
+
observations = getattr(step_log, "observations", None)
|
|
67
|
+
error = getattr(step_log, "error", None)
|
|
68
|
+
step_num = getattr(step_log, "step_number", None)
|
|
69
|
+
|
|
70
|
+
parts = []
|
|
71
|
+
for tc in tool_calls:
|
|
72
|
+
name = getattr(tc, "name", "")
|
|
73
|
+
args = getattr(tc, "arguments", None)
|
|
74
|
+
|
|
75
|
+
if name == "python_interpreter" and isinstance(args, dict):
|
|
76
|
+
code = args.get("code", "")
|
|
77
|
+
# Show the http_request call if present, else first meaningful line
|
|
78
|
+
m = re.search(r'http_request\s*\(\s*(?:method\s*=\s*)?[\'"]?(\w+)[\'"]?\s*,\s*(?:url\s*=\s*)?[\'"]([^\'"]+)', code)
|
|
79
|
+
if m:
|
|
80
|
+
parts.append(f"HTTP {m.group(1).upper()} {m.group(2)}")
|
|
81
|
+
else:
|
|
82
|
+
first = next(
|
|
83
|
+
(l.strip() for l in code.splitlines()
|
|
84
|
+
if l.strip() and not l.strip().startswith("#")),
|
|
85
|
+
code[:80],
|
|
86
|
+
)
|
|
87
|
+
parts.append(first[:120])
|
|
88
|
+
else:
|
|
89
|
+
if isinstance(args, dict):
|
|
90
|
+
method = args.get("method", "")
|
|
91
|
+
url = args.get("url", "")
|
|
92
|
+
if method and url:
|
|
93
|
+
parts.append(f"HTTP {method.upper()} {url}")
|
|
94
|
+
else:
|
|
95
|
+
parts.append(f"{name}()")
|
|
96
|
+
else:
|
|
97
|
+
parts.append(name or "tool call")
|
|
98
|
+
|
|
99
|
+
if observations:
|
|
100
|
+
obs = str(observations).strip()
|
|
101
|
+
# Keep only first line of response body for brevity
|
|
102
|
+
first_obs_line = obs.split("\n")[0][:200]
|
|
103
|
+
parts.append(f"→ {first_obs_line}")
|
|
104
|
+
|
|
105
|
+
if error:
|
|
106
|
+
err = str(error)
|
|
107
|
+
if "Import of" in err and "not allowed" in err:
|
|
108
|
+
parts.append("→ (import blocked — using http_request tool instead)")
|
|
109
|
+
else:
|
|
110
|
+
parts.append(f"→ Error: {err[:120]}")
|
|
111
|
+
|
|
112
|
+
label = f"Step {step_num}: " if step_num is not None else ""
|
|
113
|
+
return label + (" | ".join(parts) if parts else "thinking…")
|
|
114
|
+
|
|
115
|
+
|
|
66
116
|
def run_agent_chat(cfg):
|
|
67
117
|
try:
|
|
68
118
|
from smolagents import CodeAgent, OpenAIServerModel, tool
|
|
@@ -76,7 +126,9 @@ def run_agent_chat(cfg):
|
|
|
76
126
|
agent_model_id = cfg.get("agentModelId") or ""
|
|
77
127
|
max_steps = int(cfg.get("maxSteps") or 10)
|
|
78
128
|
|
|
79
|
-
|
|
129
|
+
_log(f"start model={agent_model_id!r} base={agent_base_url!r} maxSteps={max_steps}")
|
|
130
|
+
|
|
131
|
+
# Build task from the last user message; prepend prior assistant turns as context.
|
|
80
132
|
task_text = ""
|
|
81
133
|
context_lines = []
|
|
82
134
|
for m in messages:
|
|
@@ -94,9 +146,19 @@ def run_agent_chat(cfg):
|
|
|
94
146
|
if context_lines:
|
|
95
147
|
task_text = "\n".join(context_lines) + "\n\nNow answer: " + task_text
|
|
96
148
|
|
|
149
|
+
# Prepend explicit tool instructions so small models don't try to import
|
|
150
|
+
# requests/urllib themselves — the http_request function is already available.
|
|
151
|
+
tool_hint = (
|
|
152
|
+
"You have ONE built-in function: `http_request(method, url, headers='', body='')`. "
|
|
153
|
+
"Call it directly — do NOT write `import requests`, `import urllib`, or any import. "
|
|
154
|
+
"Example: result = http_request(method='GET', url='https://example.com')\n\n"
|
|
155
|
+
)
|
|
156
|
+
task_with_hint = tool_hint + "Task: " + task_text
|
|
157
|
+
_log(f"task={task_text[:120]!r}")
|
|
158
|
+
|
|
97
159
|
@tool
|
|
98
160
|
def http_request(method: str, url: str, headers: str = "", body: str = "") -> str:
|
|
99
|
-
"""Perform an HTTP request and return the status code and
|
|
161
|
+
"""Perform an HTTP request and return the status code and body preview.
|
|
100
162
|
|
|
101
163
|
Args:
|
|
102
164
|
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
@@ -110,30 +172,16 @@ def run_agent_chat(cfg):
|
|
|
110
172
|
parsed_headers = json.loads(headers)
|
|
111
173
|
except Exception: # noqa: BLE001
|
|
112
174
|
pass
|
|
113
|
-
|
|
175
|
+
result = _http_request_impl(method, url, parsed_headers, body or None)
|
|
176
|
+
_log(f"http_request {method.upper()} {url} → {result[:80]}")
|
|
177
|
+
return result
|
|
114
178
|
|
|
115
179
|
def step_callback(step_log):
|
|
116
180
|
try:
|
|
117
|
-
|
|
118
|
-
# tool_calls is a list of ToolCall(name, arguments, id)
|
|
119
|
-
tool_calls = getattr(step_log, "tool_calls", None) or []
|
|
120
|
-
for tc in tool_calls:
|
|
121
|
-
name = getattr(tc, "name", str(tc))
|
|
122
|
-
args = getattr(tc, "arguments", None)
|
|
123
|
-
arg_str = json.dumps(args) if args is not None else ""
|
|
124
|
-
parts.append(f"Tool: {name}({arg_str})")
|
|
125
|
-
observations = getattr(step_log, "observations", None)
|
|
126
|
-
if observations:
|
|
127
|
-
parts.append(f"→ {str(observations)[:400]}")
|
|
128
|
-
error = getattr(step_log, "error", None)
|
|
129
|
-
if error:
|
|
130
|
-
parts.append(f"Error: {error}")
|
|
131
|
-
if not parts:
|
|
132
|
-
parts.append(str(step_log)[:300])
|
|
133
|
-
text = " ".join(parts)
|
|
181
|
+
text = _summarise_step(step_log)
|
|
134
182
|
except Exception as e: # noqa: BLE001
|
|
135
183
|
text = f"Step: {e}"
|
|
136
|
-
|
|
184
|
+
_log(f"step: {text[:200]}")
|
|
137
185
|
_emit({"type": "step", "text": text})
|
|
138
186
|
|
|
139
187
|
try:
|
|
@@ -148,22 +196,26 @@ def run_agent_chat(cfg):
|
|
|
148
196
|
max_steps=max_steps,
|
|
149
197
|
step_callbacks=[step_callback],
|
|
150
198
|
executor_kwargs={"timeout_seconds": 60},
|
|
199
|
+
additional_authorized_imports=["json", "urllib", "urllib.request", "urllib.error"],
|
|
151
200
|
)
|
|
152
|
-
# Emit before agent.run() so the web no-progress timer resets while the
|
|
153
|
-
# model loads its weights and generates its first reasoning step.
|
|
154
201
|
_emit({"type": "step", "text": f"Sending task to {agent_model_id}…"})
|
|
155
202
|
with contextlib.redirect_stdout(sys.stderr):
|
|
156
|
-
result = agent.run(
|
|
203
|
+
result = agent.run(task_with_hint)
|
|
157
204
|
final_text = str(result).strip()
|
|
205
|
+
_log(f"done: {len(final_text)} chars")
|
|
158
206
|
_emit({"type": "final", "text": final_text})
|
|
159
207
|
except Exception as e: # noqa: BLE001
|
|
208
|
+
_log(f"agent error: {e}")
|
|
160
209
|
_emit({"type": "final", "text": f"[Agent error: {e}]"})
|
|
161
210
|
|
|
162
211
|
|
|
212
|
+
def _log(text: str):
|
|
213
|
+
"""Emit a log event — worker prints it to console, not forwarded to chat."""
|
|
214
|
+
_emit({"type": "log", "text": text})
|
|
215
|
+
|
|
216
|
+
|
|
163
217
|
def _emit(obj):
|
|
164
|
-
"""Write one NDJSON line to the real stdout and flush immediately.
|
|
165
|
-
Uses _REAL_STDOUT (captured at import time) so redirect_stdout blocks
|
|
166
|
-
inside agent.run() don't accidentally route events to stderr."""
|
|
218
|
+
"""Write one NDJSON line to the real stdout and flush immediately."""
|
|
167
219
|
_REAL_STDOUT.write(json.dumps(obj) + "\n")
|
|
168
220
|
_REAL_STDOUT.flush()
|
|
169
221
|
|
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.55",
|
|
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",
|