feat: implement claude code backend

This commit is contained in:
h
2026-05-19 23:11:07 +02:00
parent 757065f21c
commit 99a30f256d
8 changed files with 797 additions and 7 deletions
+55 -3
View File
@@ -4,20 +4,65 @@
# ClaudeAgent, RaycastAgent, McpServer, ExposedMcp, Gateway already
# bound — so importing them is optional. We import explicitly here so
# IDEs and type-checkers see real symbols instead of free variables.
import tempfile
from datetime import date
from pathlib import Path
from beaver_gateway.agents.base import ExposedMcp
from beaver_gateway.agents.claude import ClaudeAgent
from beaver_gateway.agents.raycast import RaycastAgent, RemoteTool, UserPreferences
from beaver_gateway.core.registry import Gateway
from beaver_gateway.frontends.anthropic import AnthropicMessagesFrontend
from beaver_gateway.mcp.types import McpServer
def current_time() -> str:
"""Return the current local time as an ISO-8601 string.
Trivial demo tool for the Phase 2.1 internal MCP aggregator —
confirms a ``python_tool`` namespace is reachable on
``http://127.0.0.1:<INTERNAL_MCP_PORT>/mcp/time``.
"""
from datetime import datetime
return datetime.now().astimezone().isoformat()
gateway = Gateway(
agents=[
# Phase 2.2 — ClaudeCodeBackendAdapter routes this agent's
# ``/v1/messages`` calls through ``claude-code-api``. The
# ``time`` MCP gets exposed as ``mcp__time__current_time`` to
# the subscription claude session via
# ``BackendOptions.mcp_servers`` pointing at the internal
# aggregator on ``127.0.0.1:INTERNAL_MCP_PORT/mcp/time/``.
#
# Fresh empty tempdir (not a hardcoded ``/tmp``) for two
# reasons: claude-code-api derives the JSONL project-key from
# ``cwd``, but claude itself writes the JSONL using the cwd's
# realpath — on macOS ``/tmp`` and ``/var/folders/...`` are
# both ``/private/...`` symlinks, so unresolved cwds make
# ``JsonlWatcher`` time out waiting on the wrong path. The
# explicit ``.resolve()`` collapses the symlink before claude
# ever sees the dir, and ``mkdtemp`` guarantees the directory
# is empty so claude does not pick up leftover files.
ClaudeAgent(
name="stub",
model="claude-sonnet-4-6",
system_prompt="You are a stub agent used to validate the Phase 0 skeleton.",
cwd="/tmp",
# ``system_prompt`` is appended to claude's built-in agent
# prompt (via ``--append-system-prompt``) — so it adds the
# agent's identity on top of claude-code's baseline tool
# knowledge, rather than replacing it. Same shape as the
# RaycastAgent's ``system_prompt → additional_system_instructions``
# mapping. For full ``BackendOptions`` knobs (timeouts,
# extra_args, history mode, etc.) import ``ClaudeCodeOptions``
# and pass ``options=ClaudeCodeOptions(...)``.
system_prompt=(
"You are a stub agent used to validate the Phase 0 skeleton.\n"
"If asked the current time, call the `current_time`"
" MCP tool instead of guessing."
),
cwd=Path(tempfile.mkdtemp(prefix="beaver-stub-cwd-")).resolve(),
expose_mcps=(ExposedMcp(name="time"),),
),
# Phase 1.2 — a RaycastAgent the AnthropicMessagesFrontend will
# route via RaycastBackend. Phase 1.5 added the per-agent knobs
@@ -45,7 +90,14 @@ gateway = Gateway(
),
),
],
mcps=[],
mcps=[
# Phase 2.1 — bundle of plain Python callables exposed as one
# FastMCP namespace. The internal aggregator mounts it under
# ``/mcp/time`` on ``127.0.0.1:INTERNAL_MCP_PORT``; Phase 2.2's
# ClaudeCode adapter will forward that URL into
# ``BackendOptions.mcp_servers``.
McpServer.python_tool(name="time", tools=[current_time]),
],
frontends=[
# Phase 1.4 — expose the agents as `model=<name>` on an
# Anthropic-compatible Messages endpoint. Auth comes from