@tiens.nguyen/gonext-local-worker 1.0.310 → 1.0.311

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.
@@ -1803,8 +1803,12 @@ async function runAgentChatJob(job) {
1803
1803
  let cancelled = false;
1804
1804
  // Remember this job's Ollama coding model and keep it resident via a background
1805
1805
  // interval. Only warms now if the model is new/changed — repeat questions rely on
1806
- // the interval, so we don't re-warm (or re-log) on every question.
1807
- trackCodingModelForWarmup(payload?.codingBaseURL ?? "", payload?.codingModelId ?? "");
1806
+ // the interval, so we don't re-warm (or re-log) on every question. The warm-up uses
1807
+ // Ollama's native /api/generate, so SKIP it for an OpenAI-compatible coder (task #108):
1808
+ // that endpoint doesn't exist there and we shouldn't hit a paid API with keep-warm pings.
1809
+ if (payload?.codingKind !== "openai") {
1810
+ trackCodingModelForWarmup(payload?.codingBaseURL ?? "", payload?.codingModelId ?? "");
1811
+ }
1808
1812
  const runRes = await workerFetch(`/api/worker/jobs/${jobId}`, {
1809
1813
  method: "PATCH",
1810
1814
  body: JSON.stringify({ jobStatus: "running" }),
@@ -1898,6 +1902,25 @@ async function runAgentChatJob(job) {
1898
1902
  jobActiveWorkspace = webWork;
1899
1903
  }
1900
1904
 
1905
+ // Task #108: coding backend kind ("ollama" | "openai" | "" = Auto/sniff). For an
1906
+ // OpenAI-compatible coder, fetch the API key from a dedicated worker-authed endpoint
1907
+ // (NOT the job payload/Mongo doc — secret hygiene) and inject it into python stdin only.
1908
+ const codingKind = payload?.codingKind === "ollama" || payload?.codingKind === "openai"
1909
+ ? payload.codingKind
1910
+ : "";
1911
+ let codingApiKey = "";
1912
+ if (codingKind === "openai") {
1913
+ try {
1914
+ const kr = await workerFetch("/api/worker/coding-key");
1915
+ if (kr.ok) {
1916
+ const kj = await kr.json().catch(() => ({}));
1917
+ if (typeof kj?.apiKey === "string") codingApiKey = kj.apiKey;
1918
+ }
1919
+ } catch {
1920
+ /* best-effort — python falls back to a keyless call, which the endpoint may 401 */
1921
+ }
1922
+ }
1923
+
1901
1924
  const input = JSON.stringify({
1902
1925
  messages: payload?.messages ?? [],
1903
1926
  agentBaseURL: payload?.agentBaseURL ?? "",
@@ -1905,6 +1928,8 @@ async function runAgentChatJob(job) {
1905
1928
  agentModelId: payload?.agentModelId ?? "",
1906
1929
  codingBaseURL: payload?.codingBaseURL ?? "",
1907
1930
  codingModelId: payload?.codingModelId ?? "",
1931
+ codingKind,
1932
+ codingApiKey,
1908
1933
  // Task #107: when true, python emits a {type:code_response} event per code-model
1909
1934
  // call so the worker persists the full raw text to Mongo.
1910
1935
  saveFullResponse: payload?.saveFullResponse === true,
@@ -3123,6 +3123,14 @@ def run_agent_chat(cfg):
3123
3123
  # also fixes an older API layer that appended /v1 after the native /api path).
3124
3124
  raw_coding_base = _normalize_openai_base(cfg.get("codingBaseURL") or "")
3125
3125
  raw_coding_model = (cfg.get("codingModelId") or "").strip()
3126
+ # Task #108: explicit coding backend kind + API key for an OpenAI-compatible endpoint.
3127
+ # coding_kind: "ollama" | "openai" | "" (Auto → sniff by URL). coding_api_key: the
3128
+ # Bearer key for an OpenAI-compatible coder (empty otherwise). The key is used for the
3129
+ # code-model calls (and coding-model auto-detect), NOT the chat model's agent_api_key.
3130
+ coding_kind = (cfg.get("codingKind") or "").strip().lower()
3131
+ if coding_kind not in ("ollama", "openai"):
3132
+ coding_kind = ""
3133
+ coding_api_key = (cfg.get("codingApiKey") or "").strip()
3126
3134
  # Task #107: when on, emit each code-model response so the worker persists it to Mongo.
3127
3135
  save_full_response = bool(cfg.get("saveFullResponse"))
3128
3136
  if raw_coding_base:
@@ -3142,7 +3150,8 @@ def run_agent_chat(cfg):
3142
3150
  # A DIFFERENT dedicated coding server. If no model name was given, ask the
3143
3151
  # server which model it serves (mlx_lm.server otherwise tries to download a
3144
3152
  # mismatched name from HF and 404s).
3145
- detected = raw_coding_model or _detect_model_id(raw_coding_base, agent_api_key)
3153
+ detected = raw_coding_model or _detect_model_id(
3154
+ raw_coding_base, coding_api_key or agent_api_key)
3146
3155
  if detected:
3147
3156
  coding_base_url = raw_coding_base
3148
3157
  coding_model_id = detected
@@ -3156,6 +3165,15 @@ def run_agent_chat(cfg):
3156
3165
  else:
3157
3166
  coding_base_url = agent_base_url
3158
3167
  coding_model_id = agent_model_id
3168
+ # Task #108: an OpenAI-compatible coder REQUIRES an explicit model name (its
3169
+ # /v1/chat/completions needs a `model` id, and we won't silently reuse the MLX chat
3170
+ # model's name against a cloud endpoint). Fail with a clear, actionable message.
3171
+ if coding_kind == "openai" and not (raw_coding_model or "").strip():
3172
+ raise _AgentConfigError(
3173
+ "Your agent coding model is set to OpenAI-compatible but has no model name. "
3174
+ "Enter the exact model id your endpoint expects (e.g. gpt-4o-mini) in "
3175
+ "Settings → Agent → Agent coding model name."
3176
+ )
3159
3177
  # Multi-step ReAct loop (thinking agent): the agent may take several
3160
3178
  # Thought → tool → Observation steps and then call final_answer() itself. This
3161
3179
  # replaced the old strict single-shot (max_steps=1) once a stronger coding model
@@ -5534,17 +5552,34 @@ def run_agent_chat(cfg):
5534
5552
  # the live test — minutes per step on a slow GPU — so disable it explicitly.
5535
5553
  # Sniffed per-server because MLX might reject the unknown param.
5536
5554
  extra_model_kwargs = {}
5537
- coding_is_ollama = _is_ollama_server(coding_base_url)
5555
+ # Task #108: the explicit backend kind (from web Settings) OVERRIDES the URL-sniff.
5556
+ # Auto ("") → sniff by URL (today's behavior; correctly False for MLX). Only Ollama
5557
+ # gets reasoning_effort='none' (MLX/OpenAI reject or don't need it).
5558
+ if coding_kind == "ollama":
5559
+ coding_is_ollama = True
5560
+ elif coding_kind == "openai":
5561
+ coding_is_ollama = False
5562
+ else:
5563
+ coding_is_ollama = _is_ollama_server(coding_base_url)
5538
5564
  if coding_is_ollama:
5539
5565
  _log("coding server is Ollama → reasoning_effort='none' (disable thinking) + streaming")
5540
5566
  extra_model_kwargs["reasoning_effort"] = "none"
5567
+ # Stream for Ollama AND for OpenAI-compatible remotes (task #108/#104): a remote
5568
+ # endpoint behind a proxy hits the same idle read-timeout streaming avoids, and
5569
+ # stream_options.include_usage keeps output-token counting working. Only a LOCAL
5570
+ # MLX coder (Auto, non-ollama) stays non-streaming.
5571
+ _stream_coder = coding_is_ollama or coding_kind == "openai"
5572
+ _log(f"coding backend: kind={coding_kind or 'auto'} ollama={coding_is_ollama} "
5573
+ f"stream={_stream_coder} key={'yes' if coding_api_key else 'no'}")
5541
5574
  model = _LoggingModel(
5542
5575
  model_id=coding_model_id,
5543
5576
  api_base=coding_base_url,
5544
- api_key=agent_api_key,
5545
- # Stream for Ollama so a long generation keeps the connection warm and can't
5546
- # trip a reverse-proxy idle read-timeout. smolagents still gets the full text.
5547
- stream_agent=coding_is_ollama,
5577
+ # Task #108: use the OpenAI-compatible coding key when set, else the chat key.
5578
+ api_key=coding_api_key or agent_api_key,
5579
+ # Stream for Ollama / OpenAI-compatible so a long generation keeps the connection
5580
+ # warm and can't trip a reverse-proxy idle read-timeout (smolagents still gets the
5581
+ # full text). Only a local MLX coder stays non-streaming.
5582
+ stream_agent=_stream_coder,
5548
5583
  # Task #107: persist each code-model response to Mongo when the user opted in.
5549
5584
  save_full_response=save_full_response,
5550
5585
  # Cap each HTTP attempt at 600s so a genuinely slow single call (a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.310",
3
+ "version": "1.0.311",
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",