"""Fast taskId + goPath history lookup for single-agent deep-link workspaces. Messages and execution state remain canonical LangGraph threads. This API reads only the dedicated ``task_deeplink_threads`` cards and never joins or scans the generic ``threads_meta`` catalogue. """ from __future__ import annotations from fastapi import APIRouter, HTTPException, Query, Request from pydantic import BaseModel from app.gateway.deps import get_task_deeplink_thread_store from app.gateway.routers.threads import ThreadResponse from deerflow.persistence.task_deeplink_threads.base import is_valid_task_deeplink_go_path from deerflow.utils.time import coerce_iso router = APIRouter( prefix="/api/task-deeplink-threads", tags=["task-deeplink-threads"], ) class TaskDeeplinkThreadListResponse(BaseModel): threads: list[ThreadResponse] @router.get("", response_model=TaskDeeplinkThreadListResponse) async def list_task_deeplink_threads( request: Request, task_id: str = Query(min_length=1, max_length=128), go_path: str = Query(min_length=1, max_length=32), limit: int = Query(default=100, ge=1, le=500), # b5-1 ?type= partition. Provided (even empty) → only that sub-type; # omitted → every sub-type of the task/goPath partition (legacy callers). sub_type: str | None = Query(default=None, max_length=64), ) -> TaskDeeplinkThreadListResponse: """Return one shared deep-link history partition, newest first.""" normalized_go_path = go_path.strip().lower() if not is_valid_task_deeplink_go_path(normalized_go_path): raise HTTPException(status_code=422, detail="Invalid task deep-link goPath") store = get_task_deeplink_thread_store(request) rows = await store.list_threads( task_id.strip(), normalized_go_path, limit=limit, sub_type=sub_type.strip() if sub_type is not None else None, ) return TaskDeeplinkThreadListResponse( threads=[ ThreadResponse( thread_id=row["thread_id"], status=row.get("status", "idle"), created_at=coerce_iso(row.get("created_at", "")), updated_at=coerce_iso(row.get("updated_at", "")), metadata=row.get("metadata", {}), values=row.get("values", {}), interrupts=row.get("interrupts", {}), ) for row in rows ] )