CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
Auto-version and Release / release (push) Successful in 38s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m2s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 2m48s
_runtime_for_model hardcoded OllamaChatTransport, so the post-approval follow-up model call hit localhost with the cloud-pinned name → 400 'invalid model name'. The direct turn path was fixed in 0.66.17; the resume path now delegates to the same _transport_for authority (one selection rule, two call sites). Red-first regression tests added.
97 lines
4.3 KiB
Python
97 lines
4.3 KiB
Python
"""Transport selection (T15 PL1 substrate): provider-prefix keyed, not routing_mode.
|
|
|
|
Observed live: an operator-requested model keeps routing_mode='local' (line
|
|
~887), so keying selection on routing_mode=='cloud' still sent pinned
|
|
'Verboo Code/glm-5.3-flash' to OllamaChatTransport → 400. Selection must key
|
|
on the model's provider prefix."""
|
|
import importlib.util
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
|
|
from rune import rune_operator # noqa: E402
|
|
from rune.openai_compat_transport import OpenAICompatTransport # noqa: E402
|
|
from rune.local_tool_runtime import OllamaChatTransport # noqa: E402
|
|
|
|
|
|
class _FakeRuntime:
|
|
def __init__(self, transport, executor, model, tools):
|
|
self.transport = transport
|
|
self.model = model
|
|
|
|
def run(self, messages, options=None):
|
|
from rune.tool_protocol import OperatorResponse
|
|
return OperatorResponse(success=True, user_text="done", model=self.model)
|
|
|
|
|
|
def _send(op, description, model):
|
|
captured = {}
|
|
class _RT(_FakeRuntime):
|
|
def __init__(self, transport, executor, model, tools):
|
|
super().__init__(transport, executor, model, tools)
|
|
captured["transport"] = transport
|
|
captured["model"] = model
|
|
with mock.patch.object(rune_operator, "LocalToolRuntime", _RT):
|
|
op.send_task(description, model=model)
|
|
return captured
|
|
|
|
|
|
def test_pinned_cloud_selects_openai_compat_transport():
|
|
"""Selection is the unit under test — config resolution (cloud_transport_for
|
|
reads the user's mimocode config, absent in CI) is mocked."""
|
|
from unittest import mock as _mock
|
|
import tempfile
|
|
op = rune_operator.RuneOperator(storage_path=str(Path(tempfile.mkdtemp()) / "op.json"))
|
|
import rune.openai_compat_transport as _oct
|
|
with _mock.patch.object(_oct, "cloud_transport_for",
|
|
side_effect=lambda model, timeout=120: OpenAICompatTransport(
|
|
"https://mock/v1", "test-key", model.split("/", 1)[0])):
|
|
captured = _send(op, "Fix the bug in /mnt/workdrive/pl1_heldout/greeting.py: returns 'Helo' not 'Hello'. Verify by running python3 /mnt/workdrive/pl1_heldout/check_greeting.py", "Verboo Code/glm-5.3-flash")
|
|
assert isinstance(captured["transport"], OpenAICompatTransport)
|
|
assert captured["model"] == "Verboo Code/glm-5.3-flash"
|
|
|
|
|
|
def test_local_model_keeps_ollama_transport():
|
|
import tempfile
|
|
op = rune_operator.RuneOperator(storage_path=str(Path(tempfile.mkdtemp()) / "op.json"))
|
|
captured = _send(op, "inspect the log for errors", "qwen2.5:3b")
|
|
assert isinstance(captured["transport"], OllamaChatTransport)
|
|
|
|
|
|
def test_bare_model_name_keeps_ollama_transport():
|
|
import tempfile
|
|
op = rune_operator.RuneOperator(storage_path=str(Path(tempfile.mkdtemp()) / "op.json"))
|
|
captured = _send(op, "inspect the log for errors", "qwen2.5:3b")
|
|
assert isinstance(captured["transport"], OllamaChatTransport)
|
|
|
|
|
|
class TestResumeTransportSelection:
|
|
"""Approval-resume path must use the SAME provider-prefix selection as
|
|
the direct turn. Observed live (PL1 Task A, 2026-09-12): after an
|
|
approval gate, the resumed turn's follow-up model call went to
|
|
OllamaChatTransport with the cloud-pinned name → localhost 400
|
|
'invalid model name' — the direct path had been fixed in 0.66.17 but
|
|
_runtime_for_model (resume) still hardcoded Ollama."""
|
|
|
|
def test_resume_path_uses_cloud_transport_for_pinned_model(self):
|
|
import rune.openai_compat_transport as _oct
|
|
op = rune_operator.RuneOperator(
|
|
storage_path=str(Path(tempfile.mkdtemp()) / "op.json"))
|
|
with mock.patch.object(
|
|
_oct, "cloud_transport_for",
|
|
side_effect=lambda model, timeout=120: OpenAICompatTransport(
|
|
"https://mock/v1", "test-key", model.split("/", 1)[0]),
|
|
):
|
|
rt = op._runtime_for_model("Verboo Code/glm-5.3-flash", "fix the bug")
|
|
assert isinstance(rt.transport, OpenAICompatTransport), type(rt.transport)
|
|
|
|
def test_resume_path_keeps_ollama_for_local_model(self):
|
|
op = rune_operator.RuneOperator(
|
|
storage_path=str(Path(tempfile.mkdtemp()) / "op.json"))
|
|
rt = op._runtime_for_model("qwen2.5:3b", "inspect the log")
|
|
assert isinstance(rt.transport, OllamaChatTransport), type(rt.transport)
|