d5e6a8f6da
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
"""RSS and article tools."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fabledassistant.services.tools._registry import tool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@tool(
|
|
name="get_rss_items",
|
|
description="Get recent items from the user's RSS feeds (news, blogs, Reddit, podcasts). Returns titles, URLs, and summaries of recent posts.",
|
|
parameters={
|
|
"limit": {"type": "integer", "description": "Number of items to return (default 15, max 50)"},
|
|
"category": {"type": "string", "description": "Filter by feed category (e.g. 'news', 'tech'). Omit for all."},
|
|
},
|
|
read_only=True,
|
|
briefing=True,
|
|
requires="rss",
|
|
)
|
|
async def get_rss_items_tool(*, user_id, arguments, **_ctx):
|
|
from fabledassistant.services.rss import get_recent_items
|
|
|
|
limit = min(int(arguments.get("limit", 15)), 50)
|
|
items = await get_recent_items(user_id, limit=limit)
|
|
return {"data": {"items": items, "count": len(items)}}
|
|
|
|
|
|
@tool(
|
|
name="add_rss_feed",
|
|
description="Add an RSS/Atom feed. Use when user asks to subscribe to or track a feed, blog, subreddit, or podcast.",
|
|
parameters={
|
|
"url": {"type": "string", "description": "The RSS/Atom feed URL to add."},
|
|
"category": {"type": "string", "description": "Optional category label (e.g. 'news', 'tech', 'reddit'). Omit if unsure."},
|
|
},
|
|
required=["url"],
|
|
requires="rss",
|
|
)
|
|
async def add_rss_feed_tool(*, user_id, arguments, **_ctx):
|
|
import asyncio as _asyncio
|
|
|
|
from sqlalchemy import select as _select
|
|
|
|
from fabledassistant.models import async_session as _async_session
|
|
from fabledassistant.models.rss_feed import RssFeed
|
|
from fabledassistant.services.rss import fetch_and_cache_feed
|
|
|
|
url = str(arguments.get("url", "")).strip()
|
|
if not url:
|
|
return {"error": "url is required"}
|
|
category = arguments.get("category") or None
|
|
async with _async_session() as session:
|
|
existing = await session.execute(
|
|
_select(RssFeed).where(RssFeed.user_id == user_id, RssFeed.url == url)
|
|
)
|
|
if existing.scalars().first():
|
|
return {"error": "Feed already added", "url": url}
|
|
feed = RssFeed(user_id=user_id, url=url, title="", category=category)
|
|
session.add(feed)
|
|
await session.commit()
|
|
await session.refresh(feed)
|
|
feed_id = feed.id
|
|
_asyncio.create_task(fetch_and_cache_feed(feed_id, url))
|
|
return {"data": {"id": feed_id, "url": url, "message": "Feed added and fetching started."}}
|
|
|
|
|
|
@tool(
|
|
name="read_article",
|
|
description=(
|
|
"Fetch and read the full text of a web page or article from a URL. "
|
|
"Use when the user shares a URL and wants you to read it, "
|
|
"or to get the full content of a linked page. "
|
|
"Do NOT use lookup for URLs — use this tool instead."
|
|
),
|
|
parameters={
|
|
"url": {"type": "string", "description": "The URL to fetch and read"},
|
|
},
|
|
required=["url"],
|
|
read_only=True,
|
|
briefing=True,
|
|
)
|
|
async def read_article_tool(*, user_id, arguments, **_ctx):
|
|
from fabledassistant.services.rss import _fetch_full_article
|
|
|
|
url = arguments.get("url", "").strip()
|
|
if not url:
|
|
return {"success": False, "error": "No URL provided"}
|
|
content = await _fetch_full_article(url)
|
|
if not content:
|
|
return {"success": False, "error": f"Could not fetch article content from {url}"}
|
|
_TOOL_CONTENT_CAP = 40_000
|
|
truncated = len(content) > _TOOL_CONTENT_CAP
|
|
return {
|
|
"success": True,
|
|
"type": "article_content",
|
|
"url": url,
|
|
"content": content[:_TOOL_CONTENT_CAP],
|
|
"truncated": truncated,
|
|
}
|