"""Tool / command catalog API endpoints. Exposes the agent's user-invocable slash-command catalog so the Golang backend can cache it and the frontend can render its "/" command menu WITHOUT calling the AI agent for every list (Golang GETs + caches `list_tools`). Scope confirmed: the catalog is the UNIFIED set of everything the user can invoke via `/` — spanning what the team internally splits into skills + analytics tools + data-access tools. Naming : verb-first, kebab-case, `/` prefix. Each command maps 1:1 to a real internal tool/intent `name` (the dispatch key); the granular data-access tools (check_data, check_knowledge, retrieve_data, retrieve_knowledge) are listed separately. NOTE: the merged `check` intent still exists for natural-language routing — it is NOT a slash command; slash invocation bypasses the router to the tool directly. Deferred analytics tools (comparison/contribution/profile/segment) are NOT exposed (not wired to the Planner). Stateless and deterministic — safe for the Golang backend to cache. """ from typing import Literal from fastapi import APIRouter from pydantic import BaseModel from src.middlewares.logging import get_logger, log_execution logger = get_logger("tools_api") router = APIRouter(prefix="/api/v1", tags=["Tools"]) CommandType = Literal["skill", "analytics", "data_access"] class CommandResponse(BaseModel): command: str # FE-facing slash command, e.g. "/analyze-descriptive" name: str # internal handler/tool name, e.g. "analyze_descriptive" type: CommandType description: str class ListToolsResponse(BaseModel): count: int tools: list[CommandResponse] # Single source of truth for the FE slash-command catalog. Order = display order. # Keep `command` in Harry's convention (verb-first, kebab-case, `/`); `name` is the # internal route/tool name used by the orchestrator. _COMMAND_CATALOG: list[CommandResponse] = [ CommandResponse( command="/help", name="help", type="skill", description="Show what the assistant can do and guide your next step.", ), CommandResponse( command="/problem-statement", name="problem_statement", type="skill", description="Define and validate your analysis goal (objective + metric) " "before exploring data.", ), CommandResponse( command="/analyze-descriptive", name="analyze_descriptive", type="analytics", description="Summary statistics for selected columns (count, mean, min, max, …).", ), CommandResponse( command="/analyze-aggregate", name="analyze_aggregate", type="analytics", description="Group and aggregate values (sum, count, average) by dimension.", ), CommandResponse( command="/analyze-correlation", name="analyze_correlation", type="analytics", description="Correlation strength between numeric columns.", ), CommandResponse( command="/analyze-trend", name="analyze_trend", type="analytics", description="Trend of a value over time at a chosen frequency.", ), CommandResponse( command="/check-data", name="check_data", type="data_access", description="Inventory of the available structured data sources.", ), CommandResponse( command="/check-knowledge", name="check_knowledge", type="data_access", description="Inventory of the available knowledge / uploaded documents.", ), CommandResponse( command="/retrieve-data", name="retrieve_data", type="data_access", description="Pull rows from a structured source for analysis.", ), CommandResponse( command="/retrieve-knowledge", name="retrieve_knowledge", type="data_access", description="Retrieve relevant passages from your uploaded documents.", ), ] @router.get("/tools", response_model=ListToolsResponse) @log_execution(logger) async def list_tools() -> ListToolsResponse: """List the user-invocable slash-command catalog (skills + tools). Static per deployment — safe for the Golang backend to cache. """ return ListToolsResponse(count=len(_COMMAND_CATALOG), tools=_COMMAND_CATALOG)