mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(grok): Grok-native interactive runtime (opencode serve) — container side
Builds the Grok analogue of the Claude intake/secretary live-session runtime, satisfying the same IntakeSession seam so the existing IntakeDriver loop, message source, relay, and StreamChunk panel contract are reused unchanged: - OpencodeServeSession: a held-open `opencode serve` session (context persists across turns) where each human turn is one synchronous POST /session/:id/ message; normalize_opencode_message maps the reply parts to text/thinking/ tool_use/draft/turn_end chunks (draft via a propose_draft tool part or the fenced roboco-draft fallback). Doc-verified against opencode's server API. - grok_intake_main / grok_secretary_main: container entrypoints mirroring the Claude mains but yielding an OpencodeServeSession; they render opencode.json (xAI provider + MCP + system prompt) first, then run the receiver + driver. - roboco-agent-grok-prompter / -secretary images (FROM roboco-agent-grok) + their builder services in all three compose files. UNVERIFIED-LIVE: the opencode serve flow + exact Part schema + draft path need a live run against grok-build-0.1 (the part mapping is defensive). The orchestrator wiring that routes a GROK intake/secretary route to these images is the next step (a design decision is open — see the handoff notes).
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""normalize_opencode_message maps opencode message parts to panel chunks.
|
||||
|
||||
The OpencodeServeSession transport (subprocess + HTTP) needs a live opencode,
|
||||
like the Claude SdkIntakeSession; the deterministic part→chunk mapping and the
|
||||
session-id extraction are covered here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from roboco.agent_sdk.opencode_session import (
|
||||
_extract_session_id,
|
||||
normalize_opencode_message,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from roboco.agent_sdk.intake_driver import StreamChunk
|
||||
|
||||
|
||||
def _kinds(chunks: list[StreamChunk]) -> list[str]:
|
||||
return [c.kind for c in chunks]
|
||||
|
||||
|
||||
def test_text_part_emits_text_then_turn_end() -> None:
|
||||
chunks = normalize_opencode_message([{"type": "text", "text": "Hi there"}])
|
||||
assert _kinds(chunks) == ["text", "turn_end"]
|
||||
assert chunks[0].text == "Hi there"
|
||||
|
||||
|
||||
def test_reasoning_part_maps_to_thinking() -> None:
|
||||
chunks = normalize_opencode_message([{"type": "reasoning", "text": "hmm"}])
|
||||
assert chunks[0].kind == "thinking"
|
||||
assert chunks[0].text == "hmm"
|
||||
|
||||
|
||||
def test_tool_part_maps_to_tool_use() -> None:
|
||||
chunks = normalize_opencode_message(
|
||||
[{"type": "tool", "tool": "read", "input": {"path": "x"}}]
|
||||
)
|
||||
tool = next(c for c in chunks if c.kind == "tool_use")
|
||||
assert tool.tool == "read"
|
||||
assert tool.data == {"input": {"path": "x"}}
|
||||
|
||||
|
||||
def test_fenced_draft_in_text_becomes_draft_chunk() -> None:
|
||||
fenced = '```roboco-draft\n{"title": "Add login"}\n```'
|
||||
chunks = normalize_opencode_message([{"type": "text", "text": fenced}])
|
||||
draft = next(c for c in chunks if c.kind == "draft")
|
||||
assert draft.data["title"] == "Add login"
|
||||
|
||||
|
||||
def test_unknown_part_skipped_but_turn_still_ends() -> None:
|
||||
chunks = normalize_opencode_message([{"type": "mystery", "x": 1}])
|
||||
assert _kinds(chunks) == ["turn_end"]
|
||||
|
||||
|
||||
def test_empty_message_yields_only_turn_end() -> None:
|
||||
assert _kinds(normalize_opencode_message([])) == ["turn_end"]
|
||||
|
||||
|
||||
def test_extract_session_id_is_tolerant() -> None:
|
||||
assert _extract_session_id({"id": "s1"}) == "s1"
|
||||
assert _extract_session_id({"sessionID": "s2"}) == "s2"
|
||||
assert _extract_session_id({"info": {"id": "s3"}}) == "s3"
|
||||
assert _extract_session_id({}) is None
|
||||
assert _extract_session_id("nope") is None
|
||||
Reference in New Issue
Block a user