feat(chat): add stop and refresh watch tools

This commit is contained in:
CREDO23 2026-07-02 17:54:31 +02:00
parent cf546b6821
commit 32d542f7a3
2 changed files with 167 additions and 0 deletions

View file

@ -0,0 +1,82 @@
"""``refresh_watch`` — run the current chat's watch now (manual refresh).
Enqueues an immediate run of every watch bound to this chat, instead of waiting
for the next scheduled fire. Operates on the current thread only.
"""
from __future__ import annotations
import logging
from typing import Any
from fastapi import HTTPException
from langchain_core.tools import tool
from app.auth.context import AuthContext
from app.automations.services.automation import AutomationService
from app.automations.services.chat_watch import find_watches_for_thread, run_watch_now
from app.db import async_session_maker
logger = logging.getLogger(__name__)
def create_refresh_watch_tool(
*,
workspace_id: int | None,
thread_id: int | str | None,
auth_context: AuthContext | None,
):
"""Build the ``refresh_watch`` tool bound to the current chat."""
@tool
async def refresh_watch() -> dict[str, Any]:
"""Run THIS chat's watch now (a manual refresh).
Use when the user says "check now", "refresh", or "run it again"
without waiting for the schedule. No arguments it acts on the
current chat. The refreshed answer arrives as a new turn shortly after.
Returns:
``{"status": "refreshing", "refreshed_ids": [int], "count": int}``
when runs were enqueued, ``{"status": "not_watching", ...}`` when
there is no watch, or ``{"status": "error", "message": str}``.
"""
if thread_id is None or auth_context is None:
return {
"status": "error",
"message": "Watches can only be managed from inside a chat.",
}
try:
async with async_session_maker() as session:
service = AutomationService(session=session, auth=auth_context)
watches = await find_watches_for_thread(
service,
workspace_id=int(workspace_id) if workspace_id is not None else 0,
thread_id=int(thread_id),
)
if not watches:
return {
"status": "not_watching",
"message": "This chat has no active watch to refresh.",
}
refreshed_ids = []
for watch in watches:
await run_watch_now(service, automation_id=watch.id)
refreshed_ids.append(watch.id)
except HTTPException as exc:
return {"status": "error", "message": str(exc.detail)}
except Exception as exc:
logger.exception("refresh_watch failed")
return {"status": "error", "message": f"could not refresh watch: {exc}"}
return {
"status": "refreshing",
"refreshed_ids": refreshed_ids,
"count": len(refreshed_ids),
}
return refresh_watch
__all__ = ["create_refresh_watch_tool"]

View file

@ -0,0 +1,85 @@
"""``stop_watch`` — stop watching in the current chat.
Deletes every watch automation bound to this chat, so it reverts to a normal
chat. Operates on the current thread only; the model passes no arguments.
"""
from __future__ import annotations
import logging
from typing import Any
from fastapi import HTTPException
from langchain_core.tools import tool
from app.auth.context import AuthContext
from app.automations.services.automation import AutomationService
from app.automations.services.chat_watch import (
find_watches_for_thread,
stop_watch as stop_watch_service,
)
from app.db import async_session_maker
logger = logging.getLogger(__name__)
def create_stop_watch_tool(
*,
workspace_id: int | None,
thread_id: int | str | None,
auth_context: AuthContext | None,
):
"""Build the ``stop_watch`` tool bound to the current chat."""
@tool
async def stop_watch() -> dict[str, Any]:
"""Stop watching in THIS chat (cancel the recurring re-asks).
Use when the user says something like "stop watching", "cancel the
watch", or "you can stop checking now". The chat reverts to a normal
chat. No arguments it acts on the current chat.
Returns:
``{"status": "stopped", "stopped_ids": [int], "count": int}`` when
watches were removed, ``{"status": "not_watching", ...}`` when there
was nothing to stop, or ``{"status": "error", "message": str}``.
"""
if thread_id is None or auth_context is None:
return {
"status": "error",
"message": "Watches can only be managed from inside a chat.",
}
try:
async with async_session_maker() as session:
service = AutomationService(session=session, auth=auth_context)
watches = await find_watches_for_thread(
service,
workspace_id=int(workspace_id) if workspace_id is not None else 0,
thread_id=int(thread_id),
)
if not watches:
return {
"status": "not_watching",
"message": "This chat has no active watch.",
}
stopped_ids = []
for watch in watches:
await stop_watch_service(service, automation_id=watch.id)
stopped_ids.append(watch.id)
except HTTPException as exc:
return {"status": "error", "message": str(exc.detail)}
except Exception as exc:
logger.exception("stop_watch failed")
return {"status": "error", "message": f"could not stop watch: {exc}"}
return {
"status": "stopped",
"stopped_ids": stopped_ids,
"count": len(stopped_ids),
}
return stop_watch
__all__ = ["create_stop_watch_tool"]