@tiens.nguyen/gonext-local-worker 1.0.60 → 1.0.62

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.
@@ -32,18 +32,20 @@ _REAL_STDOUT = sys.stdout
32
32
 
33
33
  def _ssl_context():
34
34
  import ssl
35
- try:
36
- import certifi
37
- return ssl.create_default_context(cafile=certifi.where())
38
- except ImportError:
39
- return ssl.create_default_context()
35
+ # Disable cert verification — this agent runs locally against dev tunnels
36
+ # (gorok, ngrok) whose certs may not chain correctly in Python's SSL store.
37
+ ctx = ssl.create_default_context()
38
+ ctx.check_hostname = False
39
+ ctx.verify_mode = ssl.CERT_NONE
40
+ return ctx
40
41
 
41
42
 
42
43
  def _http_request_impl(method, url, headers=None, body=None, timeout=25):
43
- req = urllib.request.Request(url, method=method.upper())
44
+ # Merge caller headers on top of sensible defaults.
45
+ merged = {"User-Agent": "gonext-agent/1.0", "Accept": "*/*"}
44
46
  if headers:
45
- for k, v in headers.items():
46
- req.add_header(k, v)
47
+ merged.update(headers)
48
+ req = urllib.request.Request(url, method=method.upper(), headers=merged)
47
49
  data = body.encode() if isinstance(body, str) and body else (body or None)
48
50
  try:
49
51
  ctx = _ssl_context()
@@ -115,6 +117,55 @@ def _summarise_step(step_log):
115
117
  return label + (" | ".join(parts) if parts else "thinking…")
116
118
 
117
119
 
120
+ def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
121
+ """Ask the local model if this task needs an HTTP request.
122
+ Returns True → run full agent; False → plain chat reply."""
123
+ try:
124
+ from openai import OpenAI
125
+ client = OpenAI(base_url=base_url, api_key=api_key or "local",
126
+ max_retries=0, timeout=20)
127
+ resp = client.chat.completions.create(
128
+ model=model_id,
129
+ messages=[
130
+ {"role": "system", "content": (
131
+ "You are a task classifier. Reply with YES or NO only, no punctuation."
132
+ )},
133
+ {"role": "user", "content": (
134
+ f"Does this task require making an HTTP request to a URL or API?\n\n"
135
+ f"Task: {task_text}\n\nYES or NO:"
136
+ )},
137
+ ],
138
+ max_tokens=3,
139
+ temperature=0,
140
+ )
141
+ answer = (resp.choices[0].message.content or "").strip().upper()
142
+ _log(f"router → {answer!r}")
143
+ return answer.startswith("Y")
144
+ except Exception as e: # noqa: BLE001
145
+ _log(f"router error: {e} — defaulting to agent")
146
+ return True
147
+
148
+
149
+ def _plain_reply(task_text: str, base_url: str, api_key: str, model_id: str) -> str:
150
+ """Plain chat completion without any tools."""
151
+ try:
152
+ from openai import OpenAI
153
+ client = OpenAI(base_url=base_url, api_key=api_key or "local",
154
+ max_retries=0, timeout=60)
155
+ resp = client.chat.completions.create(
156
+ model=model_id,
157
+ messages=[
158
+ {"role": "system", "content": "You are a helpful assistant."},
159
+ {"role": "user", "content": task_text},
160
+ ],
161
+ temperature=0.7,
162
+ max_tokens=512,
163
+ )
164
+ return (resp.choices[0].message.content or "").strip()
165
+ except Exception as e: # noqa: BLE001
166
+ return f"[Error: {e}]"
167
+
168
+
118
169
  def run_agent_chat(cfg):
119
170
  try:
120
171
  from smolagents import CodeAgent, OpenAIServerModel, tool
@@ -130,29 +181,31 @@ def run_agent_chat(cfg):
130
181
 
131
182
  _log(f"start model={agent_model_id!r} base={agent_base_url!r} maxSteps={max_steps}")
132
183
 
133
- # Build task from the last user message; prepend prior assistant turns as context.
134
- # Strip <think>...</think> blocks from assistant messages — those are internal
135
- # reasoning steps and must not be fed back to the agent as conversation context.
136
- _THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
137
-
184
+ # Use only the latest user message as the agent task.
138
185
  task_text = ""
139
- context_lines = []
140
186
  for m in messages:
141
- role = m.get("role", "")
142
- content = m.get("content", "")
143
- if role == "user":
144
- task_text = content
145
- elif role == "assistant":
146
- clean = _THINK_RE.sub("", content).strip()
147
- if clean:
148
- context_lines.append(f"Assistant previously said: {clean[:500]}")
187
+ if m.get("role") == "user":
188
+ task_text = m.get("content", "")
149
189
 
150
190
  if not task_text:
151
191
  _emit({"type": "final", "text": "[No user message found in history]"})
152
192
  return
153
193
 
154
- if context_lines:
155
- task_text = "\n".join(context_lines) + "\n\nNow answer: " + task_text
194
+ _log(f"task={task_text[:120]!r}")
195
+
196
+ # Route: ask the model if this task needs HTTP tool use.
197
+ _emit({"type": "step", "text": "Routing…"})
198
+ needs_agent = _route(task_text, agent_base_url, agent_api_key, agent_model_id)
199
+
200
+ if not needs_agent:
201
+ _log("router: plain chat (no HTTP needed)")
202
+ _emit({"type": "step", "text": "No HTTP request needed — answering directly…"})
203
+ answer = _plain_reply(task_text, agent_base_url, agent_api_key, agent_model_id)
204
+ _log(f"plain reply: {len(answer)} chars")
205
+ _emit({"type": "final", "text": answer})
206
+ return
207
+
208
+ _log("router: agent (HTTP tool use needed)")
156
209
 
157
210
  # Prepend explicit tool instructions so small models use http_request correctly
158
211
  # and always terminate with final_answer() rather than looping forever.
@@ -165,7 +218,6 @@ def run_agent_chat(cfg):
165
218
  "final_answer(result)\n\n"
166
219
  )
167
220
  task_with_hint = tool_hint + "Task: " + task_text
168
- _log(f"task={task_text[:120]!r}")
169
221
 
170
222
  @tool
171
223
  def http_request(method: str, url: str, headers: str = "", body: str = "") -> str:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.60",
3
+ "version": "1.0.62",
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",