@tiens.nguyen/gonext-local-worker 1.0.74 → 1.0.76

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.
@@ -117,9 +117,31 @@ def _summarise_step(step_log):
117
117
  return label + (" | ".join(parts) if parts else "thinking…")
118
118
 
119
119
 
120
+ # Keywords that strongly indicate the user wants to make an HTTP/network request,
121
+ # regardless of what the final output is (time, text, data, etc.).
122
+ _AGENT_KEYWORDS = re.compile(
123
+ r"\b("
124
+ r"request|fetch|call|hit|ping|curl|wget|GET|POST|PUT|DELETE|PATCH"
125
+ r"|api|endpoint|url|http|https"
126
+ r"|external\s+source|external\s+api|external\s+service"
127
+ r"|web\s+service|rest\s+api|rest\s+call"
128
+ r"|download|scrape|crawl"
129
+ r")\b",
130
+ re.IGNORECASE,
131
+ )
132
+
133
+
120
134
  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."""
135
+ """Decide if the task needs the HTTP agent (True) or a plain chat reply (False).
136
+
137
+ Fast-path: if the user explicitly mentions network/request keywords → agent.
138
+ Otherwise: ask the model to classify.
139
+ """
140
+ # Fast-path: explicit HTTP/network intent overrides the model classifier.
141
+ if _AGENT_KEYWORDS.search(task_text):
142
+ _log(f"router → YES (keyword match)")
143
+ return True
144
+
123
145
  try:
124
146
  from openai import OpenAI
125
147
  client = OpenAI(base_url=base_url, api_key=api_key or "local",
@@ -128,10 +150,13 @@ def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
128
150
  model=model_id,
129
151
  messages=[
130
152
  {"role": "system", "content": (
131
- "You are a task classifier. Reply with YES or NO only, no punctuation."
153
+ "You are a task classifier. Reply YES or NO only, no punctuation.\n"
154
+ "Answer YES if the task requires fetching data from an external network source "
155
+ "(URL, API, website, web service, or any remote server).\n"
156
+ "Answer NO if it can be solved entirely with Python stdlib, math, or is just conversation."
132
157
  )},
133
158
  {"role": "user", "content": (
134
- f"Does this task require making an HTTP request to a URL or API?\n\n"
159
+ f"Does this task require fetching data from an external network source?\n\n"
135
160
  f"Task: {task_text}\n\nYES or NO:"
136
161
  )},
137
162
  ],
@@ -139,7 +164,7 @@ def _route(task_text: str, base_url: str, api_key: str, model_id: str) -> bool:
139
164
  temperature=0,
140
165
  )
141
166
  answer = (resp.choices[0].message.content or "").strip().upper()
142
- _log(f"router → {answer!r}")
167
+ _log(f"router → {answer!r} (model)")
143
168
  return answer.startswith("Y")
144
169
  except Exception as e: # noqa: BLE001
145
170
  _log(f"router error: {e} — defaulting to agent")
@@ -246,10 +271,17 @@ def run_agent_chat(cfg):
246
271
  "You have ONE built-in function: `http_request(method, url, headers='', body='')`. "
247
272
  "It returns a STRING with the HTTP status and body. "
248
273
  "Call it, then immediately call `final_answer(response)` inside the same code block.\n"
249
- "Do NOT put final_answer outside the code block.\n\n"
274
+ "IMPORTANT RULES:\n"
275
+ "- Python's `datetime` module is available — use it for date/time tasks, no HTTP needed.\n"
276
+ "- If http_request returns an error starting with 'Error:', the URL failed. "
277
+ "Do NOT retry the same URL. Try a DIFFERENT URL or API, or use Python stdlib.\n"
278
+ "- Do NOT put final_answer outside the code block.\n\n"
250
279
  )
251
280
  task_with_hint = tool_hint + "Task: " + task_text
252
281
 
282
+ # Track URLs that have already failed so we don't retry dead endpoints across steps.
283
+ _failed_urls: set = set()
284
+
253
285
  @tool
254
286
  def http_request(method: str, url: str, headers: str = "", body: str = "") -> str:
255
287
  """Perform an HTTP request and return the status code and body preview.
@@ -266,11 +298,25 @@ def run_agent_chat(cfg):
266
298
  parsed_headers = json.loads(headers)
267
299
  except Exception: # noqa: BLE001
268
300
  pass
269
- # Retry once on timeout — gorok tunnels can be flaky on the first attempt.
301
+ url_key = f"{method.upper()}:{url}"
302
+ if url_key in _failed_urls:
303
+ msg = f"Error: {url} already failed — try a different URL or use Python stdlib."
304
+ _emit({"type": "step", "text": f"HTTP {method.upper()} {url} → (skipped, already failed)"})
305
+ _log(f"http_request skipped (already failed): {url_key}")
306
+ return msg
270
307
  result = _http_request_impl(method, url, parsed_headers, body or None)
271
308
  if result.startswith("Error:"):
309
+ # Retry once for flaky connections (e.g. gorok tunnels).
272
310
  _log(f"http_request retry {method.upper()} {url}")
273
311
  result = _http_request_impl(method, url, parsed_headers, body or None)
312
+ if result.startswith("Error:"):
313
+ # Both attempts failed — mark URL as dead so model tries something else.
314
+ _failed_urls.add(url_key)
315
+ result = (
316
+ f"{result}\n"
317
+ "Note: This URL failed twice. Do NOT retry it. "
318
+ "Try a DIFFERENT URL or use Python's datetime/math/etc. module instead."
319
+ )
274
320
  status_line = result.split("\n")[0][:150] if result else "no response"
275
321
  _emit({"type": "step", "text": f"HTTP {method.upper()} {url} → {status_line}"})
276
322
  _log(f"http_request {method.upper()} {url} → {result[:80]}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.74",
3
+ "version": "1.0.76",
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",