|
|
@@ -4616,6 +4616,29 @@ CHATTO_SEND_DM_SCHEMA = {
|
|
|
},
|
|
|
}
|
|
|
|
|
|
+CHATTO_FETCH_LINK_PREVIEW_SCHEMA = {
|
|
|
+ "name": "chatto_fetch_link_preview",
|
|
|
+ "description": (
|
|
|
+ "Fetch an OpenGraph link preview for a URL. Returns title, description, "
|
|
|
+ "image URL and other metadata that Chatto's server would show when "
|
|
|
+ "rendering the link in a message. Passes through whatever metadata the "
|
|
|
+ "target site provides."
|
|
|
+ ),
|
|
|
+ "parameters": {
|
|
|
+ "type": "object",
|
|
|
+ "properties": {
|
|
|
+ "url": {
|
|
|
+ "type": "string",
|
|
|
+ "description": (
|
|
|
+ "The URL to fetch a preview for (e.g. 'https://example.com')."
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ "required": ["url"],
|
|
|
+ "additionalProperties": False,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
CHATTO_SEND_ROOM_SCHEMA = {
|
|
|
"name": "chatto_send_room",
|
|
|
@@ -5896,6 +5919,45 @@ async def _delete_custom_status(args: dict) -> str:
|
|
|
return json.dumps({"success": True, "message": "Custom status cleared"})
|
|
|
|
|
|
|
|
|
+async def _fetch_link_preview(args: dict) -> str:
|
|
|
+ """Fetch an OpenGraph link preview. See _handle_chatto_fetch_link_preview."""
|
|
|
+ url = (args.get("url") or "").strip()
|
|
|
+ if not url:
|
|
|
+ raise _refuse("url is required")
|
|
|
+
|
|
|
+ async with _tool_client(_chatto_config_for_tools()) as client:
|
|
|
+ preview, _ = await client.fetch_link_preview(url)
|
|
|
+ return json.dumps(
|
|
|
+ {
|
|
|
+ "success": True,
|
|
|
+ "url": url,
|
|
|
+ "title": preview.title if preview else None,
|
|
|
+ "description": preview.description if preview else None,
|
|
|
+ "image_url": preview.image_url if preview else None,
|
|
|
+ "site_name": preview.site_name if preview else None,
|
|
|
+ "embed_type": preview.embed_type if preview else None,
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+async def _handle_chatto_fetch_link_preview(args: dict) -> str:
|
|
|
+ """Tool handler for chatto_fetch_link_preview.
|
|
|
+
|
|
|
+ Returns a JSON string: Hermes' tool contract accepts no other type, and a
|
|
|
+ dict (or an unflagged coroutine) fails at dispatch with "unsupported result
|
|
|
+ type". Every early exit arrives here as a _ToolRefusal and leaves as that
|
|
|
+ refusal's own JSON, so no path escapes as an unhandled error.
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ return await _fetch_link_preview(args)
|
|
|
+ except _ToolRefusal as refusal:
|
|
|
+ return refusal.result_json
|
|
|
+ except ChattoAuthError as exc:
|
|
|
+ return json.dumps({"success": False, "error": f"Authentication error: {exc}"})
|
|
|
+ except ChattoError as exc:
|
|
|
+ return json.dumps({"success": False, "error": f"Chatto API error: {exc}"})
|
|
|
+
|
|
|
+
|
|
|
async def _handle_chatto_get_message(args: dict) -> str:
|
|
|
"""Tool handler for chatto_get_message.
|
|
|
|
|
|
@@ -6049,6 +6111,11 @@ _PLUGIN_TOOLS = (
|
|
|
CHATTO_DELETE_CUSTOM_STATUS_SCHEMA,
|
|
|
_handle_chatto_delete_custom_status,
|
|
|
),
|
|
|
+ (
|
|
|
+ "chatto_fetch_link_preview",
|
|
|
+ CHATTO_FETCH_LINK_PREVIEW_SCHEMA,
|
|
|
+ _handle_chatto_fetch_link_preview,
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
|