@tiens.nguyen/gonext-local-worker 1.0.125 → 1.0.126

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.
@@ -827,6 +827,33 @@ _PDF_READ_INTENT = re.compile(
827
827
  )
828
828
 
829
829
 
830
+ # A PDF request whose CONTENT must be gathered/researched first (e.g. "get me the world
831
+ # cup 2026 schedule … then create a pdf"). The deterministic fast-path can only render text
832
+ # the user already GAVE (or a prior assistant turn) — it cannot web_search, and the model
833
+ # would just hallucinate live facts. When these retrieval/currency signals appear AND no
834
+ # document body was provided, we skip the fast-path and let the multi-step agent gather the
835
+ # data (web_search / fetch_url) and THEN call create_pdf with it.
836
+ _PDF_RESEARCH_INTENT = re.compile(
837
+ r"\b(get me|get the|find|research|look ?up|search|fetch|gather|collect|compile|"
838
+ r"latest|current|today'?s|upcoming|recent|schedules?|fixtures?|standings?|"
839
+ r"news|weather|prices?|scores?|results?|rankings?|statistics|stats)\b",
840
+ re.IGNORECASE,
841
+ )
842
+
843
+
844
+ def _pdf_has_explicit_body(user_text: str) -> bool:
845
+ """True when the message itself supplies the document body — a quoted span, or text
846
+ after a 'text:/content:/following:/data:' lead-in. Distinguishes 'make a PDF of THIS
847
+ text …' (fast-path OK) from 'get me X then make a PDF' (needs the agent to fetch X)."""
848
+ t = user_text or ""
849
+ for pat in (r'"([\s\S]+)"', r"“([\s\S]+)”", r"'([\s\S]+)'"):
850
+ m = re.search(pat, t)
851
+ if m and len(m.group(1).strip()) >= 40:
852
+ return True
853
+ m = re.search(r"\b(texts?|following|below|content|data)\b\s*[:\-]\s*\S", t, re.IGNORECASE)
854
+ return bool(m)
855
+
856
+
830
857
  def _wants_pdf(text: str) -> bool:
831
858
  """True when the user is asking us to (re)generate a PDF — fresh request or a
832
859
  follow-up referencing a target PDF — but NOT when merely asking about one, and NOT
@@ -1079,8 +1106,8 @@ def run_agent_chat(cfg):
1079
1106
  # the most recent substantial assistant turn as the base document and treat the
1080
1107
  # current user message as an edit instruction the formatter should apply.
1081
1108
  edit_instruction = ""
1109
+ prior_doc = ""
1082
1110
  if len(doc_text.strip()) < 40:
1083
- prior_doc = ""
1084
1111
  for m in reversed(messages[:last_user_idx]):
1085
1112
  if m.get("role") != "assistant":
1086
1113
  continue
@@ -1095,52 +1122,62 @@ def run_agent_chat(cfg):
1095
1122
  if prior_doc:
1096
1123
  edit_instruction = latest_user_text.strip()
1097
1124
  doc_text = prior_doc
1098
- doc_title = _derive_pdf_title(doc_text)
1099
- _log(
1100
- f"PDF fast-path: title={doc_title!r} doc_chars={len(doc_text)}"
1101
- + (f" edit={edit_instruction[:60]!r}" if edit_instruction else "")
1102
- )
1103
1125
 
1104
- _emit({"type": "step", "text": "Formatting document…"})
1105
- markdown_text = _format_text_for_pdf(
1106
- doc_text, doc_title, coding_base_url, agent_api_key, coding_model_id,
1107
- instruction=edit_instruction,
1108
- )
1126
+ # Guard: if the PDF's content must be RESEARCHED (not provided in the message, not
1127
+ # from a prior turn), don't render the instruction text into a PDF — fall through to
1128
+ # the agent loop so it can web_search/fetch_url the facts and THEN create_pdf them.
1129
+ if (not prior_doc
1130
+ and not _pdf_has_explicit_body(latest_user_text)
1131
+ and _PDF_RESEARCH_INTENT.search(latest_user_text)):
1132
+ _log("PDF fast-path SKIPPED: content needs research (no provided body) "
1133
+ "→ agent loop (web_search → create_pdf)")
1134
+ else:
1135
+ doc_title = _derive_pdf_title(doc_text)
1136
+ _log(
1137
+ f"PDF fast-path: title={doc_title!r} doc_chars={len(doc_text)}"
1138
+ + (f" edit={edit_instruction[:60]!r}" if edit_instruction else "")
1139
+ )
1109
1140
 
1110
- _emit({"type": "step", "text": "Rendering PDF…"})
1111
- try:
1112
- pdf_bytes = _render_pdf_bytes(markdown_text, doc_title)
1113
- except RuntimeError as e:
1114
- msg = str(e)
1115
- _log(f"PDF fast-path render error: {msg[:200]}")
1116
- _emit({"type": "final", "text": msg})
1117
- return
1118
- except Exception as e: # noqa: BLE001
1119
- _log(f"PDF fast-path render crash: {type(e).__name__}: {e!r}\n{traceback.format_exc()}")
1120
- _emit({"type": "final", "text": (
1121
- f"Sorry — I couldn't render that into a PDF ({type(e).__name__}: {str(e)[:160]})."
1122
- )})
1123
- return
1141
+ _emit({"type": "step", "text": "Formatting document…"})
1142
+ markdown_text = _format_text_for_pdf(
1143
+ doc_text, doc_title, coding_base_url, agent_api_key, coding_model_id,
1144
+ instruction=edit_instruction,
1145
+ )
1124
1146
 
1125
- _emit({"type": "step", "text": "Uploading PDF…"})
1126
- try:
1127
- download_url = _pdf_upload_via_api(
1128
- pdf_api_base, pdf_worker_key, f"{doc_title}.pdf", pdf_bytes
1147
+ _emit({"type": "step", "text": "Rendering PDF…"})
1148
+ try:
1149
+ pdf_bytes = _render_pdf_bytes(markdown_text, doc_title)
1150
+ except RuntimeError as e:
1151
+ msg = str(e)
1152
+ _log(f"PDF fast-path render error: {msg[:200]}")
1153
+ _emit({"type": "final", "text": msg})
1154
+ return
1155
+ except Exception as e: # noqa: BLE001
1156
+ _log(f"PDF fast-path render crash: {type(e).__name__}: {e!r}\n{traceback.format_exc()}")
1157
+ _emit({"type": "final", "text": (
1158
+ f"Sorry — I couldn't render that into a PDF ({type(e).__name__}: {str(e)[:160]})."
1159
+ )})
1160
+ return
1161
+
1162
+ _emit({"type": "step", "text": "Uploading PDF…"})
1163
+ try:
1164
+ download_url = _pdf_upload_via_api(
1165
+ pdf_api_base, pdf_worker_key, f"{doc_title}.pdf", pdf_bytes
1166
+ )
1167
+ except Exception as e: # noqa: BLE001
1168
+ _log(f"PDF fast-path upload error: {type(e).__name__}: {e!r}\n{traceback.format_exc()}")
1169
+ _emit({"type": "final", "text": (
1170
+ f"PDF was created but could not be uploaded: {str(e)[:200]}"
1171
+ )})
1172
+ return
1173
+
1174
+ out = (
1175
+ f"✅ Your PDF \"{doc_title}\" is ready.\n"
1176
+ f"Download it here (link valid for a limited time):\n{download_url}"
1129
1177
  )
1130
- except Exception as e: # noqa: BLE001
1131
- _log(f"PDF fast-path upload error: {type(e).__name__}: {e!r}\n{traceback.format_exc()}")
1132
- _emit({"type": "final", "text": (
1133
- f"PDF was created but could not be uploaded: {str(e)[:200]}"
1134
- )})
1178
+ _log(f"PDF fast-path ok title={doc_title!r} bytes={len(pdf_bytes)}")
1179
+ _emit({"type": "final", "text": out})
1135
1180
  return
1136
-
1137
- out = (
1138
- f"✅ Your PDF \"{doc_title}\" is ready.\n"
1139
- f"Download it here (link valid for a limited time):\n{download_url}"
1140
- )
1141
- _log(f"PDF fast-path ok title={doc_title!r} bytes={len(pdf_bytes)}")
1142
- _emit({"type": "final", "text": out})
1143
- return
1144
1181
  # ---------------------------------------------------------------------------------
1145
1182
 
1146
1183
  # Prepend explicit tool instructions so small models pick the right tool, never
@@ -1217,6 +1254,9 @@ def run_agent_chat(cfg):
1217
1254
  "- a live/current NUMBER with no URL given (price, exchange rate, weather, score, "
1218
1255
  "stock/crypto) -> web_search(query). Do NOT guess an API URL for these.\n"
1219
1256
  "- 'create/make/generate/export a PDF' of some text/data -> create_pdf(text, title).\n"
1257
+ "- a PDF of info you must LOOK UP first (e.g. 'get the world cup schedule then make a "
1258
+ "pdf') -> FIRST web_search/fetch_url to gather the real facts, THEN in a later step "
1259
+ "create_pdf(text=<the gathered facts>, title=...). Never PDF the request wording itself.\n"
1220
1260
  + _pdf_read_choose_line + _email_choose_line +
1221
1261
  "- A specific known API/URL was given -> http_request().\n"
1222
1262
  "\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.125",
3
+ "version": "1.0.126",
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",