183 lines
5.9 KiB
Python
183 lines
5.9 KiB
Python
"""Tests for the /meta gateway slash command (list + run)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from rich.console import Console
|
|
|
|
from opensquilla.cli.repl.session_state import ChatSessionState
|
|
from opensquilla.cli.repl.stream import TurnResult
|
|
|
|
|
|
class _FakeGatewayClient:
|
|
"""Records generic RPC ``call`` invocations and returns canned payloads."""
|
|
|
|
def __init__(self, responses: dict[str, Any] | None = None) -> None:
|
|
self.calls: list[tuple[str, dict[str, Any] | None]] = []
|
|
self.responses = responses or {}
|
|
|
|
async def call(self, method: str, params: dict | None = None) -> Any:
|
|
self.calls.append((method, params))
|
|
return self.responses.get(method, {})
|
|
|
|
|
|
def _make_console() -> tuple[Console, io.StringIO]:
|
|
buffer = io.StringIO()
|
|
console = Console(file=buffer, force_terminal=False, width=100, highlight=False, no_color=True)
|
|
return console, buffer
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_no_arg_lists_skills(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from opensquilla.cli.repl import slash_adapter
|
|
from opensquilla.cli.repl.slash_adapter import (
|
|
GatewaySlashContext,
|
|
handle_gateway_slash_command,
|
|
)
|
|
|
|
console, buffer = _make_console()
|
|
monkeypatch.setattr(slash_adapter, "console", console)
|
|
|
|
client = _FakeGatewayClient(
|
|
responses={
|
|
"meta.list": {
|
|
"skills": [
|
|
{"name": "meta-tiny", "description": "A tiny meta-skill."},
|
|
{"name": "meta-plan", "description": "Plan things."},
|
|
]
|
|
}
|
|
}
|
|
)
|
|
state = ChatSessionState(session_key="agent:main:test", model="local/test")
|
|
|
|
handled = await handle_gateway_slash_command(
|
|
"/meta",
|
|
GatewaySlashContext(state=state, client=client, elevated_state={"mode": None}),
|
|
)
|
|
|
|
assert handled is True
|
|
assert client.calls == [("meta.list", {})]
|
|
rendered = buffer.getvalue()
|
|
assert "meta-tiny" in rendered
|
|
assert "meta-plan" in rendered
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_no_arg_disabled_prints_notice(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from opensquilla.cli.repl import slash_adapter
|
|
from opensquilla.cli.repl.slash_adapter import (
|
|
GatewaySlashContext,
|
|
handle_gateway_slash_command,
|
|
)
|
|
|
|
console, buffer = _make_console()
|
|
monkeypatch.setattr(slash_adapter, "console", console)
|
|
|
|
client = _FakeGatewayClient(responses={"meta.list": {"disabled": True}})
|
|
state = ChatSessionState(session_key="agent:main:test", model="local/test")
|
|
|
|
handled = await handle_gateway_slash_command(
|
|
"/meta",
|
|
GatewaySlashContext(state=state, client=client, elevated_state={"mode": None}),
|
|
)
|
|
|
|
assert handled is True
|
|
assert client.calls == [("meta.list", {})]
|
|
assert "disabled" in buffer.getvalue().lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_run_ok_triggers_turn(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from opensquilla.cli.repl import slash_adapter
|
|
from opensquilla.cli.repl.slash_adapter import (
|
|
GatewaySlashContext,
|
|
handle_gateway_slash_command,
|
|
)
|
|
|
|
console, _buffer = _make_console()
|
|
monkeypatch.setattr(slash_adapter, "console", console)
|
|
|
|
captured: dict[str, Any] = {}
|
|
|
|
async def fake_stream_response_gateway(
|
|
gateway_client: object,
|
|
session_key: str,
|
|
message: str,
|
|
elevated_state: dict[str, str | None],
|
|
attachments: list[dict[str, Any]] | None = None,
|
|
*,
|
|
tui_output: object | None = None,
|
|
) -> TurnResult:
|
|
captured.update(
|
|
{
|
|
"client": gateway_client,
|
|
"session_key": session_key,
|
|
"message": message,
|
|
"elevated_state": elevated_state,
|
|
}
|
|
)
|
|
return TurnResult(text="launched")
|
|
|
|
monkeypatch.setattr(slash_adapter, "stream_response_gateway", fake_stream_response_gateway)
|
|
|
|
client = _FakeGatewayClient(responses={"meta.run": {"ok": True}})
|
|
state = ChatSessionState(session_key="agent:main:test", model="local/test")
|
|
|
|
handled = await handle_gateway_slash_command(
|
|
"/meta meta-tiny",
|
|
GatewaySlashContext(state=state, client=client, elevated_state={"mode": "on"}),
|
|
)
|
|
|
|
assert handled is True
|
|
assert client.calls == [
|
|
("meta.run", {"name": "meta-tiny", "sessionKey": "agent:main:test"}),
|
|
]
|
|
# A turn was triggered through the same path /image uses.
|
|
assert captured["session_key"] == "agent:main:test"
|
|
assert captured["message"] == "/meta meta-tiny"
|
|
assert captured["elevated_state"] == {"mode": "on"}
|
|
assert state.transcript.to_markdown()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_run_not_ok_prints_error_and_skips_turn(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from opensquilla.cli.repl import slash_adapter
|
|
from opensquilla.cli.repl.slash_adapter import (
|
|
GatewaySlashContext,
|
|
handle_gateway_slash_command,
|
|
)
|
|
|
|
console, buffer = _make_console()
|
|
monkeypatch.setattr(slash_adapter, "console", console)
|
|
|
|
stream_calls: list[str] = []
|
|
|
|
async def fake_stream_response_gateway(*args: Any, **kwargs: Any) -> TurnResult:
|
|
stream_calls.append("called")
|
|
return TurnResult(text="should-not-happen")
|
|
|
|
monkeypatch.setattr(slash_adapter, "stream_response_gateway", fake_stream_response_gateway)
|
|
|
|
client = _FakeGatewayClient(
|
|
responses={"meta.run": {"ok": False, "error": "unknown meta-skill: bad"}}
|
|
)
|
|
state = ChatSessionState(session_key="agent:main:test", model="local/test")
|
|
|
|
handled = await handle_gateway_slash_command(
|
|
"/meta bad",
|
|
GatewaySlashContext(state=state, client=client, elevated_state={"mode": None}),
|
|
)
|
|
|
|
assert handled is True
|
|
assert client.calls == [
|
|
("meta.run", {"name": "bad", "sessionKey": "agent:main:test"}),
|
|
]
|
|
# No turn fired.
|
|
assert stream_calls == []
|
|
assert "unknown meta-skill: bad" in buffer.getvalue()
|