@melaya/runner 1.1.28 → 1.1.29

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.
@@ -889,7 +889,32 @@ def _register_stream_hooks(agent) -> None:
889
889
  _log(f"stream hooks registered (delta={ok_a} tool={ok_b})")
890
890
 
891
891
 
892
- def _run_turn(agent, turn_id: str, message: str, browser_turn: bool = False) -> None:
892
+ def _build_user_msg(message: str, images=None):
893
+ """Build the user Msg. With images (base64 blocks from the turn frame) and a
894
+ vision-capable model, this is a multimodal Msg ([text?] + image blocks in the
895
+ Anthropic-style `source.base64` shape agentscope formats per provider). Any
896
+ problem building the blocks falls back to a plain text Msg so a turn NEVER
897
+ breaks on a text-only model / older agentscope."""
898
+ from agentscope.message import Msg
899
+ if not images:
900
+ return Msg("user", message, "user")
901
+ try:
902
+ blocks = []
903
+ if message:
904
+ blocks.append({"type": "text", "text": message})
905
+ for im in images:
906
+ data = str((im or {}).get("data") or "")
907
+ mt = str((im or {}).get("media_type") or "")
908
+ if data and mt:
909
+ blocks.append({"type": "image", "source": {"type": "base64", "media_type": mt, "data": data}})
910
+ if len(blocks) > (1 if message else 0):
911
+ return Msg("user", blocks, "user")
912
+ except Exception:
913
+ pass
914
+ return Msg("user", message, "user")
915
+
916
+
917
+ def _run_turn(agent, turn_id: str, message: str, browser_turn: bool = False, images=None) -> None:
893
918
  import asyncio
894
919
  from agentscope.message import Msg
895
920
 
@@ -916,7 +941,7 @@ def _run_turn(agent, turn_id: str, message: str, browser_turn: bool = False) ->
916
941
  # unwinds the agent loop wherever it is awaiting — including mid phone-command
917
942
  # HTTP wait — so the run stops promptly instead of finishing the step first.
918
943
  async def _go():
919
- task = asyncio.ensure_future(agent(Msg("user", message, "user")))
944
+ task = asyncio.ensure_future(agent(_build_user_msg(message, images)))
920
945
  while not task.done():
921
946
  if _stream.get("cancel"):
922
947
  task.cancel()
@@ -1108,6 +1133,10 @@ def main() -> int:
1108
1133
  continue
1109
1134
  turn_id = str(req.get("turnId") or "")
1110
1135
  message = str(req.get("message") or "")
1136
+ # Vision: base64 image blocks for THIS turn (validated server-side). Used
1137
+ # to build a multimodal Msg when the model supports it; text-only otherwise.
1138
+ _images = req.get("images")
1139
+ images = _images if isinstance(_images, list) and _images else None
1111
1140
  # Per-turn autonomy mode: the TS side carries an updated `hitl_mode`
1112
1141
  # on runner:assistant_turn so a mid-session flip takes effect next
1113
1142
  # turn. Absent ⇒ keep the current (spawn / previous-turn) mode. The
@@ -1123,7 +1152,7 @@ def main() -> int:
1123
1152
  # instructions into the system prompt before running the turn (parity with
1124
1153
  # the cloud path; handles set / edit / clear mid-conversation).
1125
1154
  _apply_static_context(agent, base_sys_prompt, req.get("static_context"))
1126
- if not message:
1155
+ if not message and not images:
1127
1156
  _emit(turn_id, "done")
1128
1157
  continue
1129
1158
  # P2-6: nudge the live memory budget down if a prior turn's OOM downgraded
@@ -1135,7 +1164,7 @@ def main() -> int:
1135
1164
  # ever survives in the warm host environment.
1136
1165
  browser_turn = _apply_browser_turn_grant(req)
1137
1166
  try:
1138
- _run_turn(agent, turn_id, message, browser_turn=browser_turn)
1167
+ _run_turn(agent, turn_id, message, browser_turn=browser_turn, images=images)
1139
1168
  finally:
1140
1169
  _purge_browser_turn_grant()
1141
1170
  _emit_usage(agent, turn_id)
package/dist/detect.js CHANGED
@@ -8,7 +8,9 @@ import { readFile } from "fs/promises";
8
8
  import { homedir } from "os";
9
9
  import { join } from "path";
10
10
  const execFileAsync = promisify(execFile);
11
- const COPILOT_MODELS_FALLBACK = ["gpt-4o", "gpt-4.1", "o3-mini", "claude-3.5-sonnet", "gemini-2.0-flash"];
11
+ // Fallback ONLY when the live /models fetch fails. Must be ENABLED-by-default
12
+ // Copilot models (policy!=disabled) — Claude/Gemini default to disabled and 400.
13
+ const COPILOT_MODELS_FALLBACK = ["gpt-4.1", "gpt-4o", "gpt-5-mini"];
12
14
  /** Fallbacks used ONLY when live discovery fails (offline runner, transient
13
15
  * error, missing cache). The REAL lists are fetched dynamically below so a new
14
16
  * claude/codex release is available on Melaya automatically — no code change,
@@ -208,11 +210,18 @@ async function detectGithubCopilot() {
208
210
  });
209
211
  if (r.ok) {
210
212
  const d = (await r.json());
211
- // Keep ONLY real chat models the agent runtime can drive (tool calling
212
- // is required), and drop Copilot-internal routing/search/compaction ids
213
- // and embedding models that the /models list also advertises.
213
+ // Keep ONLY real chat models the agent runtime can DRIVE + CALL:
214
+ // - tool calling required (agent loop),
215
+ // - `policy.state !== "disabled"` Copilot's /models over-reports:
216
+ // Gemini/Claude/kimi/gpt-5.4-5.6 default to policy=disabled and a
217
+ // chat/completions call to one 400s `model_not_supported` until the
218
+ // user enables it at github.com/settings/copilot/features. `enabled`
219
+ // and absent-policy (base gpt-4o/4.1) are callable. Enabling a model
220
+ // there flips it to `enabled` and it auto-appears on the next detect.
221
+ // - drop Copilot-internal routing/search/compaction/embedding ids.
214
222
  const ids = (d.data ?? [])
215
223
  .filter((m) => m?.capabilities?.type === "chat" && m?.capabilities?.supports?.tool_calls === true)
224
+ .filter((m) => (m?.policy?.state ?? "enabled") !== "disabled")
216
225
  .map((m) => m?.id ?? "")
217
226
  .filter((s) => s && !/^copilot-search|^exec-agent|^trajectory-compaction|-free-auto$|-flash-(picker|secondary|tertiary)$|embedding/i.test(s));
218
227
  if (ids.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.1.28",
3
+ "version": "1.1.29",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,