|
|
@@ -3511,6 +3511,63 @@ def _capabilities() -> list[str]:
|
|
|
# Plugin tools: send DM to a user, or send to a room
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
+CHATTO_LIST_ROOMS_SCHEMA = {
|
|
|
+ "name": "chatto_list_rooms",
|
|
|
+ "description": (
|
|
|
+ "List the Chatto rooms (channels and DM rooms) the bot is a member of, "
|
|
|
+ "with their room IDs. Use this to resolve a channel name to its room ID — "
|
|
|
+ "e.g. to find the 'platform:chat_id' address for a cron job's `deliver` "
|
|
|
+ "target. Optional `name` filters case-insensitively by substring."
|
|
|
+ ),
|
|
|
+ "parameters": {
|
|
|
+ "type": "object",
|
|
|
+ "properties": {
|
|
|
+ "name": {
|
|
|
+ "type": "string",
|
|
|
+ "description": (
|
|
|
+ "Optional: case-insensitive substring filter on the room name "
|
|
|
+ "(e.g. 'liv' matches 'Liv' and 'Liv-Dev'). Empty lists all rooms."
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ "base_url": {
|
|
|
+ "type": "string",
|
|
|
+ "description": (
|
|
|
+ "Chatto server base URL (e.g. 'https://chat.oberrhein.de'). "
|
|
|
+ "If omitted, falls back to the configured server URL."
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ "token": {
|
|
|
+ "type": "string",
|
|
|
+ "description": (
|
|
|
+ "Chatto bot token (cht_BK_...). "
|
|
|
+ "If omitted, falls back to the configured token."
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ "required": [],
|
|
|
+ "additionalProperties": False,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+def _chatto_tool_client(args: dict, cfg):
|
|
|
+ """Shared client construction for tool handlers. Returns (client, error_json)."""
|
|
|
+ token = args.get("token") or ""
|
|
|
+ if not token and cfg is not None:
|
|
|
+ token = cfg.token.value or ""
|
|
|
+ base_url = args.get("base_url") or ""
|
|
|
+ if not base_url and cfg is not None:
|
|
|
+ base_url = cfg.base_url.value or "https://chat.chatto.run"
|
|
|
+ if not base_url:
|
|
|
+ base_url = "https://chat.chatto.run"
|
|
|
+ if not token:
|
|
|
+ return None, json.dumps({"success": False, "error": "Chatto token not configured"})
|
|
|
+ try:
|
|
|
+ return ChattoClient(base_url=base_url, token=token), None
|
|
|
+ except Exception as exc:
|
|
|
+ return None, json.dumps({"success": False, "error": f"Failed to create Chatto client: {exc}"})
|
|
|
+
|
|
|
+
|
|
|
CHATTO_SEND_DM_SCHEMA = {
|
|
|
"name": "chatto_send_dm",
|
|
|
"description": (
|
|
|
@@ -3924,6 +3981,61 @@ async def _handle_chatto_send_room(args: dict) -> dict:
|
|
|
pass
|
|
|
|
|
|
|
|
|
+async def _handle_chatto_list_rooms(args: dict) -> dict:
|
|
|
+ """Tool handler for chatto_list_rooms: list rooms the bot is a member of."""
|
|
|
+ name_filter = (args.get("name") or "").strip().casefold()
|
|
|
+
|
|
|
+ cfg = _chatto_config_for_tools()
|
|
|
+ client, err = _chatto_tool_client(args, cfg)
|
|
|
+ if err:
|
|
|
+ return err
|
|
|
+
|
|
|
+ try:
|
|
|
+ rooms_out = []
|
|
|
+ for rws in await client.list_rooms():
|
|
|
+ room = getattr(rws, "room", None)
|
|
|
+ if not room:
|
|
|
+ continue
|
|
|
+ # DM rooms have no name; label them by their ID so they stay
|
|
|
+ # distinguishable in the output.
|
|
|
+ name = room.name or ""
|
|
|
+ kind = str(getattr(room.kind, "value", getattr(room.kind, "name", room.kind)) or "")
|
|
|
+ label = name or f"(DM room {room.id})"
|
|
|
+ if name_filter:
|
|
|
+ # A filter implies named rooms: skip DMs (empty name) and
|
|
|
+ # non-matching names alike.
|
|
|
+ if not name or name_filter not in name.casefold():
|
|
|
+ continue
|
|
|
+ rooms_out.append({
|
|
|
+ "room_id": room.id,
|
|
|
+ "name": name,
|
|
|
+ "kind": kind,
|
|
|
+ "threading_mode": str(
|
|
|
+ getattr(getattr(room, "threading_mode", None), "value", "") or ""
|
|
|
+ ),
|
|
|
+ "archived": bool(getattr(room, "archived", False)),
|
|
|
+ "label": label,
|
|
|
+ })
|
|
|
+ return json.dumps({
|
|
|
+ "success": True,
|
|
|
+ "count": len(rooms_out),
|
|
|
+ "rooms": rooms_out,
|
|
|
+ "hint": (
|
|
|
+ "Use room_id in chatto_send_room, or as the chat_id part of a "
|
|
|
+ "cron deliver target like 'chatto-platform:<room_id>'."
|
|
|
+ ),
|
|
|
+ })
|
|
|
+ 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}"})
|
|
|
+ finally:
|
|
|
+ try:
|
|
|
+ await client.close()
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
def register(ctx) -> None:
|
|
|
"""Plugin entry point — called by the Hermes plugin system."""
|
|
|
logger.info("Registering Chatto platform plugin on Hermes Agent")
|
|
|
@@ -3945,6 +4057,14 @@ def register(ctx) -> None:
|
|
|
is_async=True,
|
|
|
description=CHATTO_SEND_ROOM_SCHEMA["description"],
|
|
|
)
|
|
|
+ ctx.register_tool(
|
|
|
+ name="chatto_list_rooms",
|
|
|
+ toolset="chatto-platform",
|
|
|
+ schema=CHATTO_LIST_ROOMS_SCHEMA,
|
|
|
+ handler=_handle_chatto_list_rooms,
|
|
|
+ is_async=True,
|
|
|
+ description=CHATTO_LIST_ROOMS_SCHEMA["description"],
|
|
|
+ )
|
|
|
|
|
|
for capability in _capabilities():
|
|
|
logger.info("Chatto capability: %s", capability)
|