update api for subagent protocol and delete hermes agent
This commit is contained in:
parent
ff1799cd98
commit
952b2e7d17
1150 changed files with 704 additions and 458893 deletions
8
api/routes/dependencies.py
Normal file
8
api/routes/dependencies.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from fastapi import Request
|
||||
|
||||
from api.services.protocols import TaskServiceProtocol
|
||||
|
||||
|
||||
def get_task_service(request: Request) -> TaskServiceProtocol:
|
||||
return request.app.state.task_service
|
||||
|
||||
130
api/routes/runs.py
Normal file
130
api/routes/runs.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import asyncio
|
||||
import json
|
||||
from typing import AsyncIterator
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from api.contracts.task_schemas import (
|
||||
RunCreateRequest,
|
||||
RunListResponse,
|
||||
RunResponse,
|
||||
RunStreamEvent,
|
||||
RunSummaryResponse,
|
||||
RunWaitResponse,
|
||||
)
|
||||
from api.mappers.task_record_mapper import TaskRecordMapper
|
||||
from api.routes.dependencies import get_task_service
|
||||
from api.services.protocols import TaskServiceProtocol
|
||||
|
||||
router = APIRouter(tags=["runs"])
|
||||
|
||||
|
||||
@router.get("/threads/{thread_id}/runs", response_model=RunListResponse)
|
||||
async def list_thread_runs(
|
||||
thread_id: str,
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> RunListResponse:
|
||||
runs = await service.list_thread_runs(thread_id)
|
||||
return TaskRecordMapper.to_thread_run_list(thread_id, runs)
|
||||
|
||||
|
||||
@router.post("/runs", response_model=RunSummaryResponse, status_code=202)
|
||||
async def create_run(
|
||||
payload: RunCreateRequest,
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> RunSummaryResponse:
|
||||
rec = await service.create_run(
|
||||
thread_id=payload.thread_id.strip(),
|
||||
user_input=payload.input.strip(),
|
||||
timeout=payload.timeout,
|
||||
metadata=payload.metadata,
|
||||
)
|
||||
return TaskRecordMapper.to_run_summary(rec)
|
||||
|
||||
|
||||
@router.get("/runs/{run_id}", response_model=RunResponse)
|
||||
async def get_run(
|
||||
run_id: str,
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> RunResponse:
|
||||
rec = await service.get_run(run_id)
|
||||
if rec is None:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
return TaskRecordMapper.to_run_response(rec)
|
||||
|
||||
|
||||
@router.post("/runs/{run_id}/cancel", response_model=RunSummaryResponse)
|
||||
async def cancel_run(
|
||||
run_id: str,
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> RunSummaryResponse:
|
||||
rec = await service.cancel_run(run_id)
|
||||
if rec is None:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
return TaskRecordMapper.to_run_summary(rec)
|
||||
|
||||
|
||||
@router.delete("/runs/{run_id}", status_code=204)
|
||||
async def delete_run(
|
||||
run_id: str,
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> Response:
|
||||
exists, deleted = await service.delete_run(run_id)
|
||||
if not exists:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=409, detail="Run is still active. Cancel it first.")
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@router.get("/runs/{run_id}/wait", response_model=RunWaitResponse)
|
||||
async def wait_run(
|
||||
run_id: str,
|
||||
timeout: float | None = Query(default=None, ge=0),
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> JSONResponse | RunWaitResponse:
|
||||
rec = await service.wait_run(run_id, timeout=timeout)
|
||||
if rec is None:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
|
||||
if TaskRecordMapper.is_active_status(rec.status):
|
||||
pending = TaskRecordMapper.to_run_wait(rec)
|
||||
return JSONResponse(status_code=202, content=pending.model_dump(mode="json"))
|
||||
|
||||
return TaskRecordMapper.to_run_wait(rec)
|
||||
|
||||
|
||||
@router.get("/runs/{run_id}/stream")
|
||||
async def stream_run(
|
||||
run_id: str,
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> StreamingResponse:
|
||||
queue = await service.subscribe_run_stream(run_id)
|
||||
if queue is None:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
stream_queue = queue
|
||||
|
||||
async def event_stream() -> AsyncIterator[str]:
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
item = await asyncio.wait_for(stream_queue.get(), timeout=15)
|
||||
except asyncio.TimeoutError:
|
||||
rec = await service.get_run(run_id)
|
||||
if rec is None:
|
||||
break
|
||||
if not TaskRecordMapper.is_active_status(rec.status):
|
||||
break
|
||||
yield ": keep-alive\n\n"
|
||||
continue
|
||||
|
||||
payload = RunStreamEvent.model_validate(item).model_dump(mode="json")
|
||||
yield f"data: {json.dumps(payload, ensure_ascii=False)}\\n\\n"
|
||||
|
||||
if payload["event"] in ("completed", "failed", "cancelled"):
|
||||
break
|
||||
finally:
|
||||
await service.unsubscribe_run_stream(run_id, stream_queue)
|
||||
|
||||
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from api.contracts.task_schemas import (
|
||||
|
|
@ -7,123 +7,59 @@ from api.contracts.task_schemas import (
|
|||
BrowserTaskRequest,
|
||||
BrowserTaskResultResponse,
|
||||
BrowserTaskStatusResponse,
|
||||
TaskHistoryEvent,
|
||||
)
|
||||
from api.domain.task_status import TaskStatus
|
||||
from api.repositories.task_store import TaskRecord
|
||||
from api.services.task_service import TaskService
|
||||
from api.mappers.task_record_mapper import TaskRecordMapper
|
||||
from api.routes.dependencies import get_task_service
|
||||
from api.services.protocols import TaskServiceProtocol
|
||||
|
||||
router = APIRouter(prefix="/api/browser", tags=["browser-tasks"])
|
||||
|
||||
|
||||
def get_task_service(request: Request) -> TaskService:
|
||||
return request.app.state.task_service
|
||||
|
||||
|
||||
@router.post("/tasks", response_model=BrowserTaskAcceptedResponse, status_code=202)
|
||||
async def create_task(
|
||||
payload: BrowserTaskRequest,
|
||||
service: TaskService = Depends(get_task_service),
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> BrowserTaskAcceptedResponse:
|
||||
rec = await service.submit_task(task=payload.task.strip(), timeout=payload.timeout, metadata=payload.metadata)
|
||||
return BrowserTaskAcceptedResponse(task_id=rec.task_id, status=rec.status)
|
||||
return TaskRecordMapper.to_task_accepted(rec)
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}", response_model=BrowserTaskStatusResponse)
|
||||
async def get_task_status(task_id: str, service: TaskService = Depends(get_task_service)) -> BrowserTaskStatusResponse:
|
||||
async def get_task_status(task_id: str, service: TaskServiceProtocol = Depends(get_task_service)) -> BrowserTaskStatusResponse:
|
||||
rec = await service.get_task(task_id)
|
||||
if rec is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
return _to_status_response(rec)
|
||||
return TaskRecordMapper.to_task_status(rec)
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}/result", response_model=BrowserTaskResultResponse)
|
||||
async def get_task_result(
|
||||
task_id: str,
|
||||
service: TaskService = Depends(get_task_service),
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> JSONResponse | BrowserTaskResultResponse:
|
||||
rec = await service.get_task(task_id)
|
||||
if rec is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
if rec.status in (TaskStatus.queued, TaskStatus.running):
|
||||
return JSONResponse(
|
||||
status_code=202,
|
||||
content={
|
||||
"task_id": rec.task_id,
|
||||
"status": rec.status.value,
|
||||
"success": False,
|
||||
"execution_time": rec.execution_time,
|
||||
"result": None,
|
||||
"error": None,
|
||||
"raw_response": None,
|
||||
},
|
||||
)
|
||||
if TaskRecordMapper.is_active_status(rec.status):
|
||||
pending = TaskRecordMapper.to_pending_task_result(rec)
|
||||
return JSONResponse(status_code=202, content=pending.model_dump(mode="json"))
|
||||
|
||||
return BrowserTaskResultResponse(
|
||||
task_id=rec.task_id,
|
||||
status=rec.status,
|
||||
success=(rec.status == TaskStatus.succeeded),
|
||||
execution_time=rec.execution_time,
|
||||
result=rec.result,
|
||||
error=rec.error,
|
||||
raw_response=rec.raw_response,
|
||||
)
|
||||
return TaskRecordMapper.to_task_result(rec)
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}/history", response_model=BrowserTaskHistoryResponse)
|
||||
async def get_task_history(
|
||||
task_id: str,
|
||||
service: TaskService = Depends(get_task_service),
|
||||
service: TaskServiceProtocol = Depends(get_task_service),
|
||||
) -> JSONResponse | BrowserTaskHistoryResponse:
|
||||
rec = await service.get_task(task_id)
|
||||
if rec is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
if rec.status in (TaskStatus.queued, TaskStatus.running):
|
||||
return JSONResponse(
|
||||
status_code=202,
|
||||
content={
|
||||
"task_id": rec.task_id,
|
||||
"status": rec.status.value,
|
||||
"history": rec.history,
|
||||
},
|
||||
)
|
||||
if TaskRecordMapper.is_active_status(rec.status):
|
||||
pending = TaskRecordMapper.to_pending_task_history(rec)
|
||||
return JSONResponse(status_code=202, content=pending.model_dump(mode="json"))
|
||||
|
||||
return BrowserTaskHistoryResponse(
|
||||
task_id=rec.task_id,
|
||||
status=rec.status,
|
||||
history=_to_history_events(rec),
|
||||
)
|
||||
|
||||
|
||||
def _to_status_response(rec: TaskRecord) -> BrowserTaskStatusResponse:
|
||||
return BrowserTaskStatusResponse(
|
||||
task_id=rec.task_id,
|
||||
status=rec.status,
|
||||
create_at=rec.create_at,
|
||||
started_at=rec.started_at,
|
||||
finished_at=rec.finished_at,
|
||||
error=rec.error,
|
||||
)
|
||||
|
||||
|
||||
def _to_history_events(rec: TaskRecord) -> list[TaskHistoryEvent]:
|
||||
events: list[TaskHistoryEvent] = []
|
||||
for index, item in enumerate(rec.history, start=1):
|
||||
kind = str(item.get("kind") or item.get("type") or "system")
|
||||
content = item.get("content")
|
||||
if content is not None:
|
||||
content = str(content)
|
||||
data = item.get("data")
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
|
||||
step = item.get("step")
|
||||
if not isinstance(step, int):
|
||||
step = index
|
||||
|
||||
events.append(TaskHistoryEvent(step=step, kind=kind, content=content, data=data))
|
||||
|
||||
return events
|
||||
return TaskRecordMapper.to_task_history(rec)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue