feat!: Huge update - Link another CLI (such as gemini directly from with Claude Code / Codex). https://github.com/BeehiveInnovations/zen-mcp-server/issues/208
Zen now allows you to define `roles` for an external CLI and delegate work to another CLI via the new `clink` tool (short for `CLI + Link`). Gemini, for instance, offers 1000 free requests a day - this means you can save on tokens and your weekly limits within Claude Code by delegating work to another entirely capable CLI agent! Define your own system prompts as `roles` and make another CLI do anything you'd like. Like the current tool you're connected to, the other CLI has complete access to your files and the current context. This also works incredibly well with Zen's `conversation continuity`.
This commit is contained in:
42
tests/test_clink_integration.py
Normal file
42
tests/test_clink_integration.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from tools.clink import CLinkTool
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_clink_gemini_single_digit_sum():
|
||||
if shutil.which("gemini") is None:
|
||||
pytest.skip("gemini CLI is not installed or on PATH")
|
||||
|
||||
if not (os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")):
|
||||
pytest.skip("Gemini API key is not configured")
|
||||
|
||||
tool = CLinkTool()
|
||||
prompt = "Respond with a single digit equal to the sum of 2 + 2. Output only that digit."
|
||||
|
||||
results = await tool.execute(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"cli_name": "gemini",
|
||||
"role": "default",
|
||||
"files": [],
|
||||
"images": [],
|
||||
}
|
||||
)
|
||||
|
||||
assert results, "clink tool returned no outputs"
|
||||
payload = json.loads(results[0].text)
|
||||
status = payload["status"]
|
||||
assert status in {"success", "continuation_available"}
|
||||
|
||||
content = payload.get("content", "").strip()
|
||||
assert content == "4"
|
||||
|
||||
if status == "continuation_available":
|
||||
offer = payload.get("continuation_offer") or {}
|
||||
assert offer.get("continuation_id"), "Expected continuation metadata when status indicates availability"
|
||||
94
tests/test_clink_tool.py
Normal file
94
tests/test_clink_tool.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from clink import get_registry
|
||||
from clink.agents import AgentOutput
|
||||
from clink.parsers.base import ParsedCLIResponse
|
||||
from tools.clink import CLinkTool
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clink_tool_execute(monkeypatch):
|
||||
tool = CLinkTool()
|
||||
|
||||
async def fake_run(**kwargs):
|
||||
return AgentOutput(
|
||||
parsed=ParsedCLIResponse(content="Hello from Gemini", metadata={"model_used": "gemini-2.5-pro"}),
|
||||
sanitized_command=["gemini", "-o", "json"],
|
||||
returncode=0,
|
||||
stdout='{"response": "Hello from Gemini"}',
|
||||
stderr="",
|
||||
duration_seconds=0.42,
|
||||
parser_name="gemini_json",
|
||||
output_file_content=None,
|
||||
)
|
||||
|
||||
class DummyAgent:
|
||||
async def run(self, **kwargs):
|
||||
return await fake_run(**kwargs)
|
||||
|
||||
def fake_create_agent(client):
|
||||
return DummyAgent()
|
||||
|
||||
monkeypatch.setattr("tools.clink.create_agent", fake_create_agent)
|
||||
|
||||
arguments = {
|
||||
"prompt": "Summarize the project",
|
||||
"cli_name": "gemini",
|
||||
"role": "default",
|
||||
"files": [],
|
||||
"images": [],
|
||||
}
|
||||
|
||||
results = await tool.execute(arguments)
|
||||
assert len(results) == 1
|
||||
|
||||
payload = json.loads(results[0].text)
|
||||
assert payload["status"] in {"success", "continuation_available"}
|
||||
assert "Hello from Gemini" in payload["content"]
|
||||
metadata = payload.get("metadata", {})
|
||||
assert metadata.get("cli_name") == "gemini"
|
||||
assert metadata.get("command") == ["gemini", "-o", "json"]
|
||||
|
||||
|
||||
def test_registry_lists_roles():
|
||||
registry = get_registry()
|
||||
clients = registry.list_clients()
|
||||
assert "gemini" in clients
|
||||
roles = registry.list_roles("gemini")
|
||||
assert "default" in roles
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clink_tool_defaults_to_first_cli(monkeypatch):
|
||||
tool = CLinkTool()
|
||||
|
||||
async def fake_run(**kwargs):
|
||||
return AgentOutput(
|
||||
parsed=ParsedCLIResponse(content="Default CLI response", metadata={}),
|
||||
sanitized_command=["gemini"],
|
||||
returncode=0,
|
||||
stdout='{"response": "Default CLI response"}',
|
||||
stderr="",
|
||||
duration_seconds=0.1,
|
||||
parser_name="gemini_json",
|
||||
output_file_content=None,
|
||||
)
|
||||
|
||||
class DummyAgent:
|
||||
async def run(self, **kwargs):
|
||||
return await fake_run(**kwargs)
|
||||
|
||||
monkeypatch.setattr("tools.clink.create_agent", lambda client: DummyAgent())
|
||||
|
||||
arguments = {
|
||||
"prompt": "Hello",
|
||||
"files": [],
|
||||
"images": [],
|
||||
}
|
||||
|
||||
result = await tool.execute(arguments)
|
||||
payload = json.loads(result[0].text)
|
||||
metadata = payload.get("metadata", {})
|
||||
assert metadata.get("cli_name") == tool._default_cli_name
|
||||
Reference in New Issue
Block a user