Files
beaver-agent/config.py
T
2026-05-21 23:54:13 +02:00

152 lines
4.6 KiB
Python

import os
from datetime import date
from pathlib import Path
from beaver_gateway.agents.base import ExposedMcp
from beaver_gateway.agents.claude import ClaudeAgent, ClaudeCodeOptions
from beaver_gateway.agents.raycast import RaycastAgent, RemoteTool, UserPreferences
from beaver_gateway.core.registry import Gateway
from beaver_gateway.core.turn_record import TurnRecord, slugify
from beaver_gateway.frontends.admin import AdminFrontend
from beaver_gateway.frontends.anthropic import AnthropicMessagesFrontend
from beaver_gateway.frontends.markdown import MarkdownFrontend
from beaver_gateway.frontends.mcp_server import McpServerFrontend
from beaver_gateway.mcp.types import HttpMcp, McpServer
VAULT = Path("/vault")
CHATS_DIR = VAULT / "💬 чаты"
def chat_log_path(record: TurnRecord, vault: Path) -> Path:
today = date.today()
topic = slugify(record.first_user_text, maxlen=60)
rel = CHATS_DIR.relative_to(vault) / f"{today:%Y-%m}" / f"{today:%Y-%m-%d} - {topic}.md"
return vault / rel
def _calendar_mcps() -> list[HttpMcp]:
raw = os.environ.get("CALENDAR_MCPS", "").strip()
servers: list[HttpMcp] = []
for entry in raw.split(","):
entry = entry.strip()
if not entry:
continue
name, sep, url = entry.partition("=")
if not sep or not url.strip():
raise ValueError(f"CALENDAR_MCPS entry must be `name=url`, got: {entry!r}")
servers.append(McpServer.http(name=f"calendar-{name.strip()}", url=url.strip()))
return servers
calendar_mcps = _calendar_mcps()
calendar_exposed = tuple(ExposedMcp(name=m.name) for m in calendar_mcps)
mcps = [
McpServer.stdio(
name="obsidian-fs",
command=[
"bunx",
"-y",
"@modelcontextprotocol/server-filesystem",
"/vault",
],
lenient=True,
),
McpServer.stdio(
name="firefly",
command=[
"bunx",
"-y",
"@firefly-iii-mcp/local",
"--pat",
os.environ["FIREFLY_PAT"],
"--baseUrl",
os.environ["FIREFLY_BASE_URL"],
"--preset",
"default",
],
lenient=True,
),
*calendar_mcps,
]
CBO_PROMPT = (Path(__file__).parent / "prompt.md").read_text(encoding="utf-8")
UserPrefsRu = lambda: UserPreferences( # noqa: E731
locale="ru-RU",
timezone="Europe/Warsaw",
current_date=date.today().isoformat(), # noqa: DTZ011
)
def claude(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
return ClaudeAgent(
name=name,
model=model,
system_prompt=CBO_PROMPT,
cwd=VAULT,
options=ClaudeCodeOptions(effort=effort, extra_args=("--remote-control",)),
expose_mcps=(ExposedMcp(name="firefly"), *calendar_exposed),
)
def raycast(name: str, model: str, reasoning_effort: str | None = None) -> RaycastAgent:
return RaycastAgent(
name=name,
model=model,
system_prompt=CBO_PROMPT,
reasoning_effort=reasoning_effort,
available_native_tools=(RemoteTool.WEB_SEARCH, RemoteTool.READ_PAGE),
user_preferences=UserPrefsRu,
expose_mcps=(
ExposedMcp(name="obsidian-fs"),
ExposedMcp(name="firefly"),
*calendar_exposed,
),
)
agents = [
claude("beaver-opus-high", "claude-opus-4-7", effort="high"),
raycast("beaver-gemini-pro-high", "google-gemini-3.1-pro", reasoning_effort="high"),
claude("beaver-opus-medium", "claude-opus-4-7", effort="medium"),
claude("beaver-opus-xhigh", "claude-opus-4-7", effort="xhigh"),
raycast("beaver-gemini-pro-low", "google-gemini-3.1-pro", reasoning_effort="low"),
raycast("beaver-gemini-flash-high", "google-gemini-3.5-flash", reasoning_effort="high"),
raycast("beaver-gemini-flash-low", "google-gemini-3.5-flash", reasoning_effort="low"),
]
PUBLIC_BASE_URL = os.environ.get("PUBLIC_BASE_URL", "").rstrip("/")
def _public(suffix: str) -> str | None:
return f"{PUBLIC_BASE_URL}{suffix}" if PUBLIC_BASE_URL else None
frontends = [
AnthropicMessagesFrontend(
host="0.0.0.0", port=62990, public_base_url=_public("/anthropic")
),
McpServerFrontend(
host="0.0.0.0", port=62991, public_base_url=_public("/mcp")
),
AdminFrontend(
host="0.0.0.0", port=62992, public_base_url=_public("/admin")
),
MarkdownFrontend(
host="0.0.0.0",
port=62993,
vault_path=CHATS_DIR,
default_agent="research",
log_all_chats=True,
log_path=chat_log_path,
public_base_url=_public("/md"),
),
]
gateway = Gateway(agents=agents, mcps=mcps, frontends=frontends)