added quick connect auth from jellyfin, still needs to have some more cleaning before push to prod
This commit is contained in:
+26
-1
@@ -47,6 +47,7 @@ class Skill:
|
||||
prompt_fragment: str = ""
|
||||
tools: List[Dict[str, Any]] = field(default_factory=list)
|
||||
execute: Optional[ToolExecutor] = None
|
||||
requires_auth: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -96,9 +97,15 @@ def get_all_tools(skill_names: list[str]) -> List[Dict[str, Any]]:
|
||||
|
||||
|
||||
async def execute_tool(
|
||||
skill_names: list[str], tool_name: str, args: dict
|
||||
skill_names: list[str], tool_name: str, args: dict,
|
||||
discord_user_id: int | None = None,
|
||||
) -> ToolResult | None:
|
||||
"""Find the skill that owns *tool_name* and run its executor.
|
||||
|
||||
If *discord_user_id* is provided, also checks whether the owning skill
|
||||
requires authentication for any services. If auth is missing, returns
|
||||
a friendly ToolResult.fail(...) telling the user how to log in.
|
||||
|
||||
Only logs failures to the console — successful calls are silent.
|
||||
"""
|
||||
import logging
|
||||
@@ -109,6 +116,24 @@ async def execute_tool(
|
||||
if s and s.execute:
|
||||
for t in s.tools:
|
||||
if t.get("function", {}).get("name") == tool_name:
|
||||
# --- Auth gate ---
|
||||
if s.requires_auth and discord_user_id is not None:
|
||||
from core import auth_store
|
||||
from auth import get_auth_service
|
||||
missing: list[str] = []
|
||||
for svc in s.requires_auth:
|
||||
if not auth_store.is_authenticated(discord_user_id, svc):
|
||||
missing.append(svc)
|
||||
if missing:
|
||||
svc_displays = ", ".join(
|
||||
(get_auth_service(m) and get_auth_service(m).display_name) or m
|
||||
for m in missing
|
||||
)
|
||||
return ToolResult.fail(
|
||||
f"You need to log in to {svc_displays} first. "
|
||||
+ " ".join(f"Send `/login {m}` in a DM to get started." for m in missing)
|
||||
)
|
||||
# --- End auth gate ---
|
||||
try:
|
||||
result = await s.execute(tool_name, args)
|
||||
if not result.success:
|
||||
|
||||
@@ -23,6 +23,16 @@ When responding:
|
||||
suggest submitting a ticket if there's a problem.
|
||||
- Always confirm successful actions and warn about failures.
|
||||
|
||||
## Jellyfin & Authentication
|
||||
|
||||
You are connected to the user's Jellyfin server. If a user asks you to
|
||||
"connect to Jellyfin", "link my Jellyfin", or asks about their watch history,
|
||||
simply call the `watch_history` tool. The system will automatically handle
|
||||
authentication — if the user isn't linked yet, they'll be guided through
|
||||
Quick Connect seamlessly. NEVER tell a user you "don't have access to
|
||||
Jellyfin" or "can't connect" — always try the tool first and let the system
|
||||
sort it out.
|
||||
|
||||
This is the base media assistant persona. Real API capabilities come from the
|
||||
attached skills (seerr, triage, etc.).""",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Watch History skill — fetch the user's Jellyfin watch history.
|
||||
|
||||
Currently a placeholder — returns a "coming soon" message.
|
||||
The auth gate (`requires_auth=["jellyfin"]`) is already active:
|
||||
users who haven't linked Jellyfin will be prompted to /login first.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from skills import Skill, register, ToolResult
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool definitions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "watch_history",
|
||||
"description": (
|
||||
"Get the user's recent Jellyfin watch history — movies and TV "
|
||||
"episodes they have watched, sorted by most recent. "
|
||||
"Call this when a user asks about their watching activity."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "How many items to return (default 10, max 20)",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Executor (placeholder)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _execute(tool_name: str, args: dict) -> ToolResult:
|
||||
if tool_name == "watch_history":
|
||||
return ToolResult.ok(
|
||||
"👷 **Watch History — Coming Soon!**\n\n"
|
||||
"This feature is currently being built. Soon you'll be able to "
|
||||
"see your recently watched movies and TV episodes right here.\n\n"
|
||||
"In the meantime, you can check your watch history directly in Jellyfin."
|
||||
)
|
||||
return ToolResult.fail(f"Unknown tool: {tool_name}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Skill registration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
watch_history_skill = Skill(
|
||||
name="watch_history",
|
||||
description="User's Jellyfin watch history (coming soon)",
|
||||
requires_auth=["jellyfin"],
|
||||
prompt_fragment="""## Watch History
|
||||
|
||||
You can fetch the user's Jellyfin watch history with the `watch_history` tool.
|
||||
Call it when users ask things like:
|
||||
- "what have I watched?"
|
||||
- "show my watch history"
|
||||
- "what did I watch recently?"
|
||||
- "what was the last movie I saw?"
|
||||
- "what TV shows have I been watching?"
|
||||
|
||||
The tool is currently a **placeholder** — it returns a "coming soon" message.
|
||||
Tell the user this feature is being worked on and will be available soon.""",
|
||||
tools=TOOLS,
|
||||
execute=_execute,
|
||||
)
|
||||
|
||||
register(watch_history_skill)
|
||||
Reference in New Issue
Block a user