mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 12:17:45 +02:00
128 lines
5.3 KiB
Python
128 lines
5.3 KiB
Python
"""LLM-powered metadata, persistence helpers, and delete operation.
|
|
|
|
Extracted from agent_manager_ops to keep every file under 250 lines.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
|
|
from backend.apps.agents.models import AgentSession, ToolGroupMeta
|
|
from backend.apps.agents.manager.ws_manager import ws_manager
|
|
from backend.apps.agents.manager.session_store import (
|
|
save_session, delete_session_file, build_search_text,
|
|
)
|
|
from backend.apps.common.llm_helpers import quick_llm_call, quick_llm_json
|
|
from backend.apps.agents.manager.session_store import load_all_session_data
|
|
from backend.apps.agents.execution.agent_mock import fire_session_completed
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def generate_title_op(sessions: dict, session_id: str, first_prompt: str) -> str:
|
|
session = sessions.get(session_id)
|
|
if not session:
|
|
raise ValueError(f"Session {session_id} not found")
|
|
title = first_prompt[:40].strip()
|
|
try:
|
|
title = await quick_llm_call(
|
|
"Generate a concise 3-6 word title for a chat that starts with this message. Return only the title, nothing else.",
|
|
first_prompt, max_tokens=30,
|
|
)
|
|
title = title.strip('"\'') or first_prompt[:40].strip()
|
|
except Exception as e:
|
|
logger.warning(f"Title generation failed, using fallback: {e}")
|
|
session.name = title
|
|
await ws_manager.emit_name_updated(session_id, title)
|
|
return title
|
|
|
|
|
|
async def generate_group_meta_op(
|
|
sessions: dict, session_id: str, group_id: str,
|
|
tool_calls: list[dict], results_summary: list[str] | None = None,
|
|
is_refinement: bool = False,
|
|
) -> dict:
|
|
session = sessions.get(session_id)
|
|
if not session:
|
|
raise ValueError(f"Session {session_id} not found")
|
|
fallback_name = tool_calls[0].get("tool", "Tool calls") if tool_calls else "Tool calls"
|
|
fallback_name = fallback_name.split("__")[-1].replace("_", " ").title() if "__" in fallback_name else fallback_name
|
|
name, svg = fallback_name, ""
|
|
try:
|
|
tool_desc = "\n".join(f"- {tc.get('tool', '?')}: {tc.get('input_summary', '')}" for tc in tool_calls)
|
|
user_content = f"Tool actions:\n{tool_desc}"
|
|
if results_summary:
|
|
user_content += "\n\nResults:\n" + "\n".join(f"- {r}" for r in results_summary)
|
|
system = (
|
|
"Generate a concise 2-5 word name and a minimal SVG icon for a group of tool actions.\n\n"
|
|
"Return ONLY valid JSON: {\"name\": \"...\", \"svg\": \"...\"}\n\n"
|
|
"Name rules:\n- 2-5 words, title case, describes the action\n\n"
|
|
"SVG rules:\n- 24x24 viewBox\n- Use currentColor for all stroke/fill\n"
|
|
"- Simple geometric shapes only\n- No text, no images, no gradients\n"
|
|
"- Max 400 characters for the svg string"
|
|
)
|
|
parsed = await quick_llm_json(system, user_content)
|
|
if parsed.get("name"):
|
|
name = parsed["name"].strip().strip("\"'")
|
|
if parsed.get("svg"):
|
|
svg = parsed["svg"].strip()
|
|
except Exception as e:
|
|
logger.warning(f"Group meta generation failed, using fallback: {e}")
|
|
|
|
meta = ToolGroupMeta(id=group_id, name=name, svg=svg, is_refined=is_refinement)
|
|
session.tool_group_meta[group_id] = meta
|
|
await ws_manager.emit_group_meta_updated(session_id, group_id, name, svg, is_refinement)
|
|
return {"name": name, "svg": svg, "is_refined": is_refinement}
|
|
|
|
|
|
async def persist_all_sessions_op(sessions: dict, tasks: dict) -> None:
|
|
for session_id, session in list(sessions.items()):
|
|
if session.status in ("running", "waiting_approval"):
|
|
session.status = "stopped"
|
|
for req in list(session.pending_approvals):
|
|
ws_manager.resolve_approval(req.id, {"behavior": "deny", "message": "Server shutting down"})
|
|
session.pending_approvals = []
|
|
fire_session_completed(session, sessions)
|
|
doc_data = session.model_dump(mode="json")
|
|
doc_data["search_text"] = build_search_text(session)
|
|
save_session(session_id, doc_data)
|
|
logger.info(f"Persisted session {session_id} on shutdown")
|
|
sessions.clear()
|
|
tasks.clear()
|
|
|
|
|
|
async def restore_all_sessions_op(sessions: dict) -> None:
|
|
for sid, data in load_all_session_data():
|
|
try:
|
|
session = AgentSession(**data)
|
|
except Exception as e:
|
|
logger.warning(f"Skipping corrupt session file {sid}: {e}")
|
|
continue
|
|
if session.closed_at is not None:
|
|
continue
|
|
if session.status in ("running", "waiting_approval"):
|
|
session.status = "stopped"
|
|
session.pending_approvals = []
|
|
sessions[session.id] = session
|
|
delete_session_file(sid)
|
|
logger.info(f"Restored session {session.id}")
|
|
|
|
|
|
async def delete_session_op(manager, session_id: str) -> None:
|
|
children = [s for s in manager.sessions.values() if s.parent_session_id == session_id and s.mode == "browser-agent"]
|
|
for child in children:
|
|
await manager.stop_agent(child.id)
|
|
task = manager.tasks.get(session_id)
|
|
if task and not task.done():
|
|
task.cancel()
|
|
try:
|
|
await task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
manager.sessions.pop(session_id, None)
|
|
manager.tasks.pop(session_id, None)
|
|
delete_session_file(session_id)
|
|
logger.info(f"Session {session_id} permanently deleted")
|