mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Complete the native Grok (xAI) path so grok-build-0.1 runs as a real RoboCo agent, not just the provider seam. - roboco-agent-grok image (docker/agent-grok.Dockerfile): FROM agent-base + opencode (the OpenAI-protocol runtime). One image serves every role; role behaviour comes from the mounted manifest / mcp-config / system prompt, exactly as on the Claude path. - Entrypoint renders opencode.json at spawn from the GrokProvider env contract + the mounted Claude Code mcp-config.json (roboco.llm.providers.opencode_config): translates RoboCo's gateway servers (roboco-flow / roboco-do / ...) into opencode's mcp block, declares the xAI OpenAI-compatible provider + model, and wires permissions + instructions. Pure, unit-tested translation. - Orchestrator registers GrokProvider with the registry-qualified image (_qualify_agent_image) so it resolves in local and registry deploys. - Compose (both files + the registry compose) gain an agent-grok-image builder service. - Panel: a Grok (xAI) API key card on the AI Providers page, plus the grok ModelProvider value. KNOWN PARITY GAP (opencode runtime): the bash-guard PAT-scrub and the transcript-based usage/cost capture are Claude Code hooks and do not transfer to opencode. bash permission is operator-tunable (ROBOCO_GROK_BASH_PERMISSION) so a deployment can fail closed until a security/usage-parity opencode plugin lands. That plugin and live E2E validation are the remaining work to finalize with xAI.
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
"""Tests for the Grok opencode.json generator (RoboCo MCP -> opencode config)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from roboco.llm.providers.opencode_config import (
|
|
XaiTarget,
|
|
build_opencode_config,
|
|
translate_mcp_servers,
|
|
)
|
|
|
|
_TARGET = XaiTarget(
|
|
base_url="https://api.x.ai/v1", api_key="xai-key", model="grok-build-0.1"
|
|
)
|
|
|
|
_MCP = {
|
|
"mcpServers": {
|
|
"roboco-flow": {
|
|
"command": "uv",
|
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.flow_server"],
|
|
"env": {
|
|
"ROBOCO_AGENT_ID": "uuid-1",
|
|
"UV_PROJECT_ENVIRONMENT": "/app/.venv",
|
|
},
|
|
},
|
|
"roboco-do": {
|
|
"command": "uv",
|
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.do_server"],
|
|
"env": {"ROBOCO_AGENT_ID": "uuid-1"},
|
|
},
|
|
}
|
|
}
|
|
|
|
|
|
def test_translate_mcp_servers_shape() -> None:
|
|
out = translate_mcp_servers(_MCP)
|
|
flow = out["roboco-flow"]
|
|
assert flow["type"] == "local"
|
|
assert flow["enabled"] is True
|
|
# command + args collapse into a single command array (opencode shape).
|
|
assert flow["command"] == [
|
|
"uv",
|
|
"run",
|
|
"--no-sync",
|
|
"python",
|
|
"-m",
|
|
"roboco.mcp.flow_server",
|
|
]
|
|
# env -> environment (opencode key).
|
|
assert flow["environment"]["ROBOCO_AGENT_ID"] == "uuid-1"
|
|
assert "env" not in flow
|
|
assert set(out) == {"roboco-flow", "roboco-do"}
|
|
|
|
|
|
def test_translate_mcp_servers_empty() -> None:
|
|
assert translate_mcp_servers({}) == {}
|
|
assert translate_mcp_servers({"mcpServers": {}}) == {}
|
|
|
|
|
|
def test_translate_mcp_servers_omits_environment_when_no_env() -> None:
|
|
out = translate_mcp_servers(
|
|
{"mcpServers": {"x": {"command": "uv", "args": ["run"]}}}
|
|
)
|
|
assert "environment" not in out["x"]
|
|
assert out["x"]["command"] == ["uv", "run"]
|
|
|
|
|
|
def test_build_opencode_config_provider_and_model() -> None:
|
|
cfg = build_opencode_config(
|
|
_MCP,
|
|
_TARGET,
|
|
instruction_paths=["/app/system-prompt.md"],
|
|
)
|
|
provider = cfg["provider"]["xai"]
|
|
assert provider["npm"] == "@ai-sdk/openai-compatible"
|
|
assert provider["options"]["baseURL"] == "https://api.x.ai/v1"
|
|
assert provider["options"]["apiKey"] == "xai-key"
|
|
assert "grok-build-0.1" in provider["models"]
|
|
# Top-level model selector is "<provider>/<model>".
|
|
assert cfg["model"] == "xai/grok-build-0.1"
|
|
# Gateway servers carried through.
|
|
assert "roboco-flow" in cfg["mcp"]
|
|
assert cfg["instructions"] == ["/app/system-prompt.md"]
|
|
|
|
|
|
def test_build_opencode_config_bash_permission_is_tunable() -> None:
|
|
cfg = build_opencode_config(
|
|
{},
|
|
_TARGET,
|
|
instruction_paths=[],
|
|
bash_permission="deny",
|
|
)
|
|
assert cfg["permission"]["bash"] == "deny"
|
|
assert cfg["permission"]["edit"] == "allow"
|