feat: add setup
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user