206 lines
6.5 KiB
Python
206 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from opensquilla.cli.repl.session_state import ChatSessionState
|
|
from opensquilla.cli.repl.stream import TurnResult
|
|
from opensquilla.cli.tui.contracts import TuiOutputHandle
|
|
|
|
|
|
class _FakeGatewayClient:
|
|
def __init__(self) -> None:
|
|
self.reset_calls: list[str] = []
|
|
|
|
async def create_session(
|
|
self,
|
|
agent_id: str = "main",
|
|
model: str | None = None,
|
|
display_name: str | None = None,
|
|
) -> str:
|
|
raise AssertionError("create_session is not used by these tests")
|
|
|
|
async def list_sessions(self, limit: int = 50) -> dict[str, Any]:
|
|
raise AssertionError("list_sessions is not used by these tests")
|
|
|
|
async def resolve_session(self, key: str) -> dict[str, Any]:
|
|
raise AssertionError("resolve_session is not used by these tests")
|
|
|
|
async def delete_sessions(self, keys: list[str]) -> dict[str, Any]:
|
|
raise AssertionError("delete_sessions is not used by these tests")
|
|
|
|
async def reset_session(self, session_key: str) -> dict[str, object]:
|
|
self.reset_calls.append(session_key)
|
|
return {"reset": True, "key": session_key}
|
|
|
|
async def compact_session(self, key: str) -> dict[str, Any]:
|
|
raise AssertionError("compact_session is not used by these tests")
|
|
|
|
async def list_models(
|
|
self,
|
|
provider: str | None = None,
|
|
capabilities: list[str] | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
raise AssertionError("list_models is not used by these tests")
|
|
|
|
async def patch_session(self, key: str, **fields: Any) -> dict[str, Any]:
|
|
raise AssertionError("patch_session is not used by these tests")
|
|
|
|
async def usage_status(self) -> dict[str, Any]:
|
|
raise AssertionError("usage_status is not used by these tests")
|
|
|
|
async def upload_file(self, path: Path, mime: str, name: str) -> str:
|
|
raise AssertionError("upload_file is not used by these tests")
|
|
|
|
def send_message(
|
|
self,
|
|
session_key: str,
|
|
message: str,
|
|
attachments: list[dict] | None = None,
|
|
elevated: str | None = None,
|
|
) -> AsyncIterator[dict[str, Any]]:
|
|
async def _unused_events() -> AsyncIterator[dict[str, Any]]:
|
|
raise AssertionError("send_message is not used by these tests")
|
|
yield {}
|
|
|
|
return _unused_events()
|
|
|
|
async def resolve_approval(
|
|
self,
|
|
approval_id: str,
|
|
approved: bool,
|
|
*,
|
|
choice: str | None = None,
|
|
) -> Any:
|
|
del choice
|
|
raise AssertionError("resolve_approval is not used by these tests")
|
|
|
|
async def abort_session(self, key: str) -> dict[str, Any]:
|
|
raise AssertionError("abort_session is not used by these tests")
|
|
|
|
|
|
class _RecordingOutputHandle:
|
|
@property
|
|
def approval_surface(self) -> object:
|
|
return "approval-surface"
|
|
|
|
async def write_through(self, payload: str) -> None:
|
|
return None
|
|
|
|
def stream_output(self):
|
|
raise AssertionError("stream_output is only consumed by the renderer")
|
|
|
|
|
|
def test_gateway_slash_adapter_exposes_typed_context() -> None:
|
|
from opensquilla.cli.repl.slash_adapter import GatewaySlashContext
|
|
|
|
tui_output = _RecordingOutputHandle()
|
|
context = GatewaySlashContext(
|
|
state=ChatSessionState(session_key="agent:main:test", model="openai/test"),
|
|
client=_FakeGatewayClient(),
|
|
elevated_state={"mode": None},
|
|
tui_output=tui_output,
|
|
)
|
|
|
|
assert isinstance(context.tui_output, TuiOutputHandle)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_gateway_slash_adapter_handles_clear_without_chat_command_state() -> None:
|
|
from opensquilla.cli.repl.slash_adapter import (
|
|
GatewaySlashContext,
|
|
handle_gateway_slash_command,
|
|
)
|
|
|
|
state = ChatSessionState(session_key="agent:main:test", model="openai/test")
|
|
state.transcript.add("user", "hello")
|
|
client = _FakeGatewayClient()
|
|
|
|
handled = await handle_gateway_slash_command(
|
|
"/clear",
|
|
GatewaySlashContext(
|
|
state=state,
|
|
client=client,
|
|
elevated_state={"mode": None},
|
|
),
|
|
)
|
|
|
|
assert handled is True
|
|
assert client.reset_calls == ["agent:main:test"]
|
|
assert state.transcript.turns == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_gateway_slash_adapter_threads_tui_output_to_streaming_slash_command(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from opensquilla.cli.repl import slash_adapter
|
|
from opensquilla.cli.repl.slash_adapter import (
|
|
GatewaySlashContext,
|
|
handle_gateway_slash_command,
|
|
)
|
|
|
|
state = ChatSessionState(session_key="agent:main:test", model="openai/test")
|
|
client = SimpleNamespace(is_local_gateway=True)
|
|
tui_output = _RecordingOutputHandle()
|
|
captured: dict[str, Any] = {}
|
|
|
|
def fake_path_prompt_and_attachments(command: str) -> tuple[str, list[dict[str, Any]]]:
|
|
captured["path_command"] = command
|
|
return "inspect /repo", []
|
|
|
|
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: TuiOutputHandle | None = None,
|
|
) -> TurnResult:
|
|
captured.update(
|
|
{
|
|
"client": gateway_client,
|
|
"session_key": session_key,
|
|
"message": message,
|
|
"elevated_state": elevated_state,
|
|
"attachments": attachments,
|
|
"tui_output": tui_output,
|
|
}
|
|
)
|
|
return TurnResult(text="done")
|
|
|
|
monkeypatch.setattr(
|
|
slash_adapter,
|
|
"path_prompt_and_attachments",
|
|
fake_path_prompt_and_attachments,
|
|
)
|
|
monkeypatch.setattr(
|
|
slash_adapter,
|
|
"stream_response_gateway",
|
|
fake_stream_response_gateway,
|
|
)
|
|
|
|
handled = await handle_gateway_slash_command(
|
|
"/path /repo inspect",
|
|
GatewaySlashContext(
|
|
state=state,
|
|
client=client,
|
|
elevated_state={"mode": "on"},
|
|
tui_output=tui_output,
|
|
),
|
|
)
|
|
|
|
assert handled is True
|
|
assert captured["path_command"] == "/path /repo inspect"
|
|
assert captured["session_key"] == "agent:main:test"
|
|
assert captured["message"] == "inspect /repo"
|
|
assert captured["elevated_state"] == {"mode": "on"}
|
|
assert captured["attachments"] == []
|
|
assert captured["tui_output"] is tui_output
|
|
assert state.transcript.to_markdown()
|