The Pirate — Phase 1.a: conversational read-only media agent
Ships a chat-based agent at /pirate that LLM-routes user questions to media-stack tools and returns natural-language answers grounded in real data. Foundation built on top of the existing API-tokens + dual-auth infrastructure so other apps (Open WebUI, HA voice, Synap) can consume the same Pirate API. New subsystem (not the standard trigger/result pattern): - pirate_conversations + pirate_messages tables - service_configs table (admin-wide creds shared by media agents) - /api/pirate/chat + /api/pirate/conversations/* (dual-auth: user session OR Bearer token scoped to user's pirate instance) - /api/internal/pirate/* endpoints used by runtime subprocess - /api/admin/services + Services tab in admin.html for cred management - Auto-seeded service_configs on startup from Media Stack Reference defaults (never overwrite admin edits) - Auto-seeded pirate catalog entry + per-user pirate instance on startup Pirate package (agents/pirate/): - prompts.py: system prompt, enforces read-only in Phase 1 - runtime.py: Anthropic-native tool-use loop (max 8 iterations, persists every turn) - tools/_common.py: service_configs fetch + qBit session auth - tools/sonarr.py: queue, upcoming, series_search, library_stats - tools/radarr.py: queue, movie_search, library_stats - tools/qbittorrent.py: torrents, transfer_stats, categories - tools/storage.py: disk_space (via Sonarr diskspace API) - Default model: claude-sonnet-4-5 (Haiku fumbles multi-step chains) Dashboard: - static/pirate.html — full chat UI with conversation sidebar, suggestion chips, inline tool-call visualization, 24h idle reset + New Chat button - Pirate button added to main dashboard header Wiki reorg: Agents / Developer Guides / Plans parent docs, per-agent reference docs, The Pirate doc. API Clients + Calling Agents docs moved under Developer Guides. Working folder: PIRATE_PHASE_1A.md + NEXT_SESSION_PROMPT.md for fast bootstrap. Smoke tested end-to-end: real tool calls against qBittorrent (13 active torrents correctly reported) and Sonarr disk-space; multi-turn conversation state preserved across follow-up questions. On deck: Phase 1.b (Lidarr/Whisparr/Overseerr/Plex tools), then 1.d (OWUI pipeline), then 1.c (HA voice).
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""qBittorrent tools — torrent queue and transfer stats (read-only)."""
|
||||
|
||||
from ._common import qbit_login_if_needed, qbit_request
|
||||
|
||||
|
||||
def _compact_torrent(t):
|
||||
return {
|
||||
"name": t.get("name"),
|
||||
"state": t.get("state"), # downloading, uploading, stalledDL, pausedDL, completed, ...
|
||||
"progress_pct": round((t.get("progress", 0) or 0) * 100, 1),
|
||||
"size_gb": round((t.get("size", 0) or 0) / 1e9, 2),
|
||||
"dl_speed_mbps": round((t.get("dlspeed", 0) or 0) / 1e6, 2),
|
||||
"up_speed_mbps": round((t.get("upspeed", 0) or 0) / 1e6, 2),
|
||||
"eta_seconds": t.get("eta"),
|
||||
"category": t.get("category"),
|
||||
"ratio": round(t.get("ratio", 0) or 0, 2),
|
||||
"num_seeds": t.get("num_seeds"),
|
||||
"num_peers": t.get("num_leechs") or t.get("num_peers"),
|
||||
"added_on_epoch": t.get("added_on"),
|
||||
}
|
||||
|
||||
|
||||
def qbit_torrents(filter="all", category=None, limit=30):
|
||||
"""List torrents. Filter is one of: all, downloading, seeding, completed, paused, stalled, errored."""
|
||||
cookie = qbit_login_if_needed()
|
||||
params = {"filter": filter}
|
||||
if category:
|
||||
params["category"] = category
|
||||
data, _ = qbit_request("/api/v2/torrents/info", params=params, cookies=cookie)
|
||||
if isinstance(data, str):
|
||||
return {"error": data}
|
||||
items = [_compact_torrent(t) for t in data[:limit]]
|
||||
return {"filter": filter, "category": category, "count": len(data), "torrents": items}
|
||||
|
||||
|
||||
def qbit_transfer_stats():
|
||||
"""Global transfer stats: overall download / upload speed, session totals, DHT nodes."""
|
||||
cookie = qbit_login_if_needed()
|
||||
data, _ = qbit_request("/api/v2/transfer/info", cookies=cookie)
|
||||
if isinstance(data, str):
|
||||
return {"error": data}
|
||||
return {
|
||||
"dl_speed_mbps": round((data.get("dl_info_speed", 0) or 0) / 1e6, 2),
|
||||
"up_speed_mbps": round((data.get("up_info_speed", 0) or 0) / 1e6, 2),
|
||||
"dl_session_gb": round((data.get("dl_info_data", 0) or 0) / 1e9, 2),
|
||||
"up_session_gb": round((data.get("up_info_data", 0) or 0) / 1e9, 2),
|
||||
"dht_nodes": data.get("dht_nodes"),
|
||||
"connection_status": data.get("connection_status"),
|
||||
}
|
||||
|
||||
|
||||
def qbit_categories():
|
||||
"""Configured torrent categories (e.g., sonarr, radarr, lidarr, whisparr)."""
|
||||
cookie = qbit_login_if_needed()
|
||||
data, _ = qbit_request("/api/v2/torrents/categories", cookies=cookie)
|
||||
return data if isinstance(data, dict) else {"error": str(data)}
|
||||
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"name": "qbit_torrents",
|
||||
"description": "List torrents in qBittorrent with their progress, state, speed, and ETA. Filter options: all, downloading, seeding, completed, paused, stalled, errored. Category options: sonarr, radarr, lidarr, whisparr (which *arr requested it).",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filter": {"type": "string", "default": "all", "enum": ["all", "downloading", "seeding", "completed", "paused", "stalled", "errored"]},
|
||||
"category": {"type": "string", "description": "Optional category filter"},
|
||||
"limit": {"type": "integer", "default": 30},
|
||||
},
|
||||
},
|
||||
"read_only": True,
|
||||
"fn": qbit_torrents,
|
||||
},
|
||||
{
|
||||
"name": "qbit_transfer_stats",
|
||||
"description": "Current global download / upload speed and session totals from qBittorrent. Use when user asks 'how fast is the download', 'what's my current download speed', 'total downloaded this session'.",
|
||||
"input_schema": {"type": "object", "properties": {}},
|
||||
"read_only": True,
|
||||
"fn": qbit_transfer_stats,
|
||||
},
|
||||
{
|
||||
"name": "qbit_categories",
|
||||
"description": "List the torrent categories configured in qBittorrent. Shows which *arr is pulling what.",
|
||||
"input_schema": {"type": "object", "properties": {}},
|
||||
"read_only": True,
|
||||
"fn": qbit_categories,
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user