@tiens.nguyen/gonext-local-worker 1.0.54 → 1.0.56

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.
@@ -1237,7 +1237,7 @@ async function runAgentChatJob(job) {
1237
1237
  agentApiKey: payload?.agentApiKey ?? "",
1238
1238
  agentModelId: payload?.agentModelId ?? "",
1239
1239
  tools: payload?.tools ?? ["http_request"],
1240
- maxSteps: payload?.maxSteps ?? 10,
1240
+ maxSteps: payload?.maxSteps ?? 5,
1241
1241
  });
1242
1242
  const timeoutMs = 300_000; // 5 min max for an agent run
1243
1243
 
@@ -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 === "step" && typeof event.text === "string") {
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>");
@@ -12,17 +12,14 @@ Reads on stdin:
12
12
  "maxSteps": int # default 10
13
13
  }
14
14
 
15
- Emits NDJSON lines on stdout per step/final:
16
- {"type": "step", "text": "<tool call summary>"}
17
- {"type": "final", "text": "<agent final answer>"}
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,61 @@ 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
+ # smolagents prefixes output with "Execution logs:" — skip that header line
102
+ # and show the first line of actual content.
103
+ lines = [l for l in obs.splitlines() if l.strip() and l.strip() != "Execution logs:"]
104
+ if lines:
105
+ parts.append(f"→ {lines[0][:200]}")
106
+
107
+ if error:
108
+ err = str(error)
109
+ if "Import of" in err and "not allowed" in err:
110
+ parts.append("→ (import blocked — using http_request tool instead)")
111
+ else:
112
+ parts.append(f"→ Error: {err[:120]}")
113
+
114
+ label = f"Step {step_num}: " if step_num is not None else ""
115
+ return label + (" | ".join(parts) if parts else "thinking…")
116
+
117
+
66
118
  def run_agent_chat(cfg):
67
119
  try:
68
120
  from smolagents import CodeAgent, OpenAIServerModel, tool
@@ -74,9 +126,11 @@ def run_agent_chat(cfg):
74
126
  agent_base_url = cfg.get("agentBaseURL") or ""
75
127
  agent_api_key = cfg.get("agentApiKey") or "local"
76
128
  agent_model_id = cfg.get("agentModelId") or ""
77
- max_steps = int(cfg.get("maxSteps") or 10)
129
+ max_steps = int(cfg.get("maxSteps") or 5)
130
+
131
+ _log(f"start model={agent_model_id!r} base={agent_base_url!r} maxSteps={max_steps}")
78
132
 
79
- # Build the task from the last user message; use prior turns as context.
133
+ # Build task from the last user message; prepend prior assistant turns as context.
80
134
  task_text = ""
81
135
  context_lines = []
82
136
  for m in messages:
@@ -94,9 +148,22 @@ def run_agent_chat(cfg):
94
148
  if context_lines:
95
149
  task_text = "\n".join(context_lines) + "\n\nNow answer: " + task_text
96
150
 
151
+ # Prepend explicit tool instructions so small models use http_request correctly
152
+ # and always terminate with final_answer() rather than looping forever.
153
+ tool_hint = (
154
+ "You have ONE built-in function: `http_request(method, url, headers='', body='')`. "
155
+ "Do NOT import requests, urllib, or any library — call http_request() directly.\n"
156
+ "When you have the answer, ALWAYS end with `final_answer(your_answer)` — this stops the agent.\n"
157
+ "Example:\n"
158
+ "result = http_request(method='GET', url='https://example.com')\n"
159
+ "final_answer(result)\n\n"
160
+ )
161
+ task_with_hint = tool_hint + "Task: " + task_text
162
+ _log(f"task={task_text[:120]!r}")
163
+
97
164
  @tool
98
165
  def http_request(method: str, url: str, headers: str = "", body: str = "") -> str:
99
- """Perform an HTTP request and return the status code and a body preview.
166
+ """Perform an HTTP request and return the status code and body preview.
100
167
 
101
168
  Args:
102
169
  method: HTTP method (GET, POST, PUT, DELETE, etc.)
@@ -110,30 +177,16 @@ def run_agent_chat(cfg):
110
177
  parsed_headers = json.loads(headers)
111
178
  except Exception: # noqa: BLE001
112
179
  pass
113
- return _http_request_impl(method, url, parsed_headers, body or None)
180
+ result = _http_request_impl(method, url, parsed_headers, body or None)
181
+ _log(f"http_request {method.upper()} {url} → {result[:80]}")
182
+ return result
114
183
 
115
184
  def step_callback(step_log):
116
185
  try:
117
- parts = []
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)
186
+ text = _summarise_step(step_log)
134
187
  except Exception as e: # noqa: BLE001
135
188
  text = f"Step: {e}"
136
-
189
+ _log(f"step: {text[:200]}")
137
190
  _emit({"type": "step", "text": text})
138
191
 
139
192
  try:
@@ -150,21 +203,24 @@ def run_agent_chat(cfg):
150
203
  executor_kwargs={"timeout_seconds": 60},
151
204
  additional_authorized_imports=["json", "urllib", "urllib.request", "urllib.error"],
152
205
  )
153
- # Emit before agent.run() so the web no-progress timer resets while the
154
- # model loads its weights and generates its first reasoning step.
155
206
  _emit({"type": "step", "text": f"Sending task to {agent_model_id}…"})
156
207
  with contextlib.redirect_stdout(sys.stderr):
157
- result = agent.run(task_text)
208
+ result = agent.run(task_with_hint)
158
209
  final_text = str(result).strip()
210
+ _log(f"done: {len(final_text)} chars")
159
211
  _emit({"type": "final", "text": final_text})
160
212
  except Exception as e: # noqa: BLE001
213
+ _log(f"agent error: {e}")
161
214
  _emit({"type": "final", "text": f"[Agent error: {e}]"})
162
215
 
163
216
 
217
+ def _log(text: str):
218
+ """Emit a log event — worker prints it to console, not forwarded to chat."""
219
+ _emit({"type": "log", "text": text})
220
+
221
+
164
222
  def _emit(obj):
165
- """Write one NDJSON line to the real stdout and flush immediately.
166
- Uses _REAL_STDOUT (captured at import time) so redirect_stdout blocks
167
- inside agent.run() don't accidentally route events to stderr."""
223
+ """Write one NDJSON line to the real stdout and flush immediately."""
168
224
  _REAL_STDOUT.write(json.dumps(obj) + "\n")
169
225
  _REAL_STDOUT.flush()
170
226
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.54",
3
+ "version": "1.0.56",
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",