diff --git a/backend/app/api/activity.py b/backend/app/api/activity.py index caf4e399..c61c15b0 100644 --- a/backend/app/api/activity.py +++ b/backend/app/api/activity.py @@ -2,7 +2,8 @@ from __future__ import annotations from fastapi import APIRouter, Depends, Query from sqlalchemy import desc -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from app.api.deps import ActorContext, require_admin_or_agent from app.db.session import get_session @@ -13,14 +14,14 @@ router = APIRouter(prefix="/activity", tags=["activity"]) @router.get("", response_model=list[ActivityEventRead]) -def list_activity( +async def list_activity( limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> list[ActivityEvent]: statement = select(ActivityEvent) if actor.actor_type == "agent" and actor.agent: statement = statement.where(ActivityEvent.agent_id == actor.agent.id) statement = statement.order_by(desc(col(ActivityEvent.created_at))).offset(offset).limit(limit) - return list(session.exec(statement)) + return list(await session.exec(statement)) diff --git a/backend/app/api/agent.py b/backend/app/api/agent.py index de6e76fc..3c556c1f 100644 --- a/backend/app/api/agent.py +++ b/backend/app/api/agent.py @@ -1,10 +1,10 @@ from __future__ import annotations -import asyncio from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlmodel import Session, select +from sqlmodel import select +from sqlmodel.ext.asyncio.session import AsyncSession from app.api import agents as agents_api from app.api import approvals as approvals_api @@ -16,15 +16,20 @@ from app.core.agent_auth import AgentAuthContext, get_agent_auth_context from app.db.session import get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message +from app.models.activity_events import ActivityEvent from app.models.agents import Agent +from app.models.approvals import Approval +from app.models.board_memory import BoardMemory +from app.models.board_onboarding import BoardOnboardingSession from app.models.boards import Board from app.models.gateways import Gateway from app.models.tasks import Task from app.schemas.agents import AgentCreate, AgentHeartbeat, AgentHeartbeatCreate, AgentNudge, AgentRead -from app.schemas.approvals import ApprovalCreate, ApprovalRead +from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead -from app.schemas.board_onboarding import BoardOnboardingRead +from app.schemas.board_onboarding import BoardOnboardingAgentUpdate, BoardOnboardingRead from app.schemas.boards import BoardRead +from app.schemas.common import OkResponse from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate from app.services.activity_log import record_activity @@ -40,24 +45,24 @@ def _guard_board_access(agent_ctx: AgentAuthContext, board: Board) -> None: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) -def _gateway_config(session: Session, board: Board) -> GatewayClientConfig: +async def _gateway_config(session: AsyncSession, board: Board) -> GatewayClientConfig: if not board.gateway_id: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway is None or not gateway.url: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) return GatewayClientConfig(url=gateway.url, token=gateway.token) @router.get("/boards", response_model=list[BoardRead]) -def list_boards( - session: Session = Depends(get_session), +async def list_boards( + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), ) -> list[Board]: if agent_ctx.agent.board_id: - board = session.get(Board, agent_ctx.agent.board_id) + board = await session.get(Board, agent_ctx.agent.board_id) return [board] if board else [] - return list(session.exec(select(Board))) + return list(await session.exec(select(Board))) @router.get("/boards/{board_id}", response_model=BoardRead) @@ -70,10 +75,10 @@ def get_board( @router.get("/agents", response_model=list[AgentRead]) -def list_agents( +async def list_agents( board_id: UUID | None = Query(default=None), limit: int | None = Query(default=None, ge=1, le=200), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), ) -> list[AgentRead]: statement = select(Agent) @@ -85,8 +90,8 @@ def list_agents( statement = statement.where(Agent.board_id == board_id) if limit is not None: statement = statement.limit(limit) - agents = list(session.exec(statement)) - main_session_keys = agents_api._get_gateway_main_session_keys(session) + agents = list(await session.exec(statement)) + main_session_keys = await agents_api._get_gateway_main_session_keys(session) return [ agents_api._to_agent_read(agents_api._with_computed_status(agent), main_session_keys) for agent in agents @@ -94,17 +99,17 @@ def list_agents( @router.get("/boards/{board_id}/tasks", response_model=list[TaskRead]) -def list_tasks( +async def list_tasks( status_filter: str | None = Query(default=None, alias="status"), assigned_agent_id: UUID | None = None, unassigned: bool | None = None, limit: int | None = Query(default=None, ge=1, le=200), board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> list[TaskRead]: +) -> list[Task]: _guard_board_access(agent_ctx, board) - return tasks_api.list_tasks( + return await tasks_api.list_tasks( status_filter=status_filter, assigned_agent_id=assigned_agent_id, unassigned=unassigned, @@ -116,22 +121,21 @@ def list_tasks( @router.post("/boards/{board_id}/tasks", response_model=TaskRead) -def create_task( +async def create_task( payload: TaskCreate, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> TaskRead: +) -> Task: _guard_board_access(agent_ctx, board) if not agent_ctx.agent.is_board_lead: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - tasks_api.validate_task_status(payload.status) task = Task.model_validate(payload) task.board_id = board.id task.auto_created = True task.auto_reason = f"lead_agent:{agent_ctx.agent.id}" if task.assigned_agent_id: - agent = session.get(Agent, task.assigned_agent_id) + agent = await session.get(Agent, task.assigned_agent_id) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) if agent.is_board_lead: @@ -142,8 +146,8 @@ def create_task( if agent.board_id and agent.board_id != board.id: raise HTTPException(status_code=status.HTTP_409_CONFLICT) session.add(task) - session.commit() - session.refresh(task) + await session.commit() + await session.refresh(task) record_activity( session, event_type="task.created", @@ -151,11 +155,11 @@ def create_task( message=f"Task created by lead: {task.title}.", agent_id=agent_ctx.agent.id, ) - session.commit() + await session.commit() if task.assigned_agent_id: - assigned_agent = session.get(Agent, task.assigned_agent_id) + assigned_agent = await session.get(Agent, task.assigned_agent_id) if assigned_agent: - tasks_api._notify_agent_on_task_assign( + await tasks_api._notify_agent_on_task_assign( session=session, board=board, task=task, @@ -165,15 +169,15 @@ def create_task( @router.patch("/boards/{board_id}/tasks/{task_id}", response_model=TaskRead) -def update_task( +async def update_task( payload: TaskUpdate, - task=Depends(get_task_or_404), - session: Session = Depends(get_session), + task: Task = Depends(get_task_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> TaskRead: +) -> Task: if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - return tasks_api.update_task( + return await tasks_api.update_task( payload=payload, task=task, session=session, @@ -182,14 +186,14 @@ def update_task( @router.get("/boards/{board_id}/tasks/{task_id}/comments", response_model=list[TaskCommentRead]) -def list_task_comments( - task=Depends(get_task_or_404), - session: Session = Depends(get_session), +async def list_task_comments( + task: Task = Depends(get_task_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> list[TaskCommentRead]: +) -> list[ActivityEvent]: if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - return tasks_api.list_task_comments( + return await tasks_api.list_task_comments( task=task, session=session, actor=_actor(agent_ctx), @@ -197,15 +201,15 @@ def list_task_comments( @router.post("/boards/{board_id}/tasks/{task_id}/comments", response_model=TaskCommentRead) -def create_task_comment( +async def create_task_comment( payload: TaskCommentCreate, - task=Depends(get_task_or_404), - session: Session = Depends(get_session), + task: Task = Depends(get_task_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> TaskCommentRead: +) -> ActivityEvent: if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - return tasks_api.create_task_comment( + return await tasks_api.create_task_comment( payload=payload, task=task, session=session, @@ -214,15 +218,15 @@ def create_task_comment( @router.get("/boards/{board_id}/memory", response_model=list[BoardMemoryRead]) -def list_board_memory( +async def list_board_memory( limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), - board=Depends(get_board_or_404), - session: Session = Depends(get_session), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> list[BoardMemoryRead]: +) -> list[BoardMemory]: _guard_board_access(agent_ctx, board) - return board_memory_api.list_board_memory( + return await board_memory_api.list_board_memory( limit=limit, offset=offset, board=board, @@ -232,14 +236,14 @@ def list_board_memory( @router.post("/boards/{board_id}/memory", response_model=BoardMemoryRead) -def create_board_memory( +async def create_board_memory( payload: BoardMemoryCreate, - board=Depends(get_board_or_404), - session: Session = Depends(get_session), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> BoardMemoryRead: +) -> BoardMemory: _guard_board_access(agent_ctx, board) - return board_memory_api.create_board_memory( + return await board_memory_api.create_board_memory( payload=payload, board=board, session=session, @@ -248,14 +252,14 @@ def create_board_memory( @router.get("/boards/{board_id}/approvals", response_model=list[ApprovalRead]) -def list_approvals( - status_filter: str | None = Query(default=None, alias="status"), - board=Depends(get_board_or_404), - session: Session = Depends(get_session), +async def list_approvals( + status_filter: ApprovalStatus | None = Query(default=None, alias="status"), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> list[ApprovalRead]: +) -> list[Approval]: _guard_board_access(agent_ctx, board) - return approvals_api.list_approvals( + return await approvals_api.list_approvals( status_filter=status_filter, board=board, session=session, @@ -264,14 +268,14 @@ def list_approvals( @router.post("/boards/{board_id}/approvals", response_model=ApprovalRead) -def create_approval( +async def create_approval( payload: ApprovalCreate, - board=Depends(get_board_or_404), - session: Session = Depends(get_session), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> ApprovalRead: +) -> Approval: _guard_board_access(agent_ctx, board) - return approvals_api.create_approval( + return await approvals_api.create_approval( payload=payload, board=board, session=session, @@ -280,14 +284,14 @@ def create_approval( @router.post("/boards/{board_id}/onboarding", response_model=BoardOnboardingRead) -def update_onboarding( - payload: dict[str, object], +async def update_onboarding( + payload: BoardOnboardingAgentUpdate, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> BoardOnboardingRead: +) -> BoardOnboardingSession: _guard_board_access(agent_ctx, board) - return onboarding_api.agent_onboarding_update( + return await onboarding_api.agent_onboarding_update( payload=payload, board=board, session=session, @@ -298,7 +302,7 @@ def update_onboarding( @router.post("/agents", response_model=AgentRead) async def create_agent( payload: AgentCreate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), ) -> AgentRead: if not agent_ctx.agent.is_board_lead: @@ -313,18 +317,18 @@ async def create_agent( ) -@router.post("/boards/{board_id}/agents/{agent_id}/nudge") -def nudge_agent( +@router.post("/boards/{board_id}/agents/{agent_id}/nudge", response_model=OkResponse) +async def nudge_agent( payload: AgentNudge, agent_id: str, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), -) -> dict[str, bool]: +) -> OkResponse: _guard_board_access(agent_ctx, board) if not agent_ctx.agent.is_board_lead: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - target = session.get(Agent, agent_id) + target = await session.get(Agent, agent_id) if target is None or (target.board_id and target.board_id != board.id): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) if not target.openclaw_session_id: @@ -332,15 +336,9 @@ def nudge_agent( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Target agent has no session key", ) - message = payload.message.strip() - if not message: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="message is required", - ) - config = _gateway_config(session, board) - - async def _send() -> None: + message = payload.message + config = await _gateway_config(session, board) + try: await ensure_session(target.openclaw_session_id, config=config, label=target.name) await send_message( message, @@ -348,9 +346,6 @@ def nudge_agent( config=config, deliver=True, ) - - try: - asyncio.run(_send()) except OpenClawGatewayError as exc: record_activity( session, @@ -358,7 +353,7 @@ def nudge_agent( message=f"Nudge failed for {target.name}: {exc}", agent_id=agent_ctx.agent.id, ) - session.commit() + await session.commit() raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc record_activity( session, @@ -366,18 +361,18 @@ def nudge_agent( message=f"Nudge sent to {target.name}.", agent_id=agent_ctx.agent.id, ) - session.commit() - return {"ok": True} + await session.commit() + return OkResponse() @router.post("/heartbeat", response_model=AgentRead) async def agent_heartbeat( payload: AgentHeartbeatCreate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), ) -> AgentRead: # Heartbeats must apply to the authenticated agent; agent names are not unique. - return agents_api.heartbeat_agent( # type: ignore[attr-defined] + return await agents_api.heartbeat_agent( agent_id=str(agent_ctx.agent.id), payload=AgentHeartbeat(status=payload.status), session=session, diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py index d3cf8e6e..61e32879 100644 --- a/backend/app/api/agents.py +++ b/backend/app/api/agents.py @@ -3,19 +3,21 @@ from __future__ import annotations import asyncio import json import re +from collections.abc import AsyncIterator from datetime import datetime, timedelta, timezone from uuid import UUID, uuid4 from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from sqlalchemy import asc, or_, update -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from sse_starlette.sse import EventSourceResponse -from starlette.concurrency import run_in_threadpool from app.api.deps import ActorContext, require_admin_auth, require_admin_or_agent from app.core.agent_tokens import generate_agent_token, hash_agent_token from app.core.auth import AuthContext -from app.db.session import engine, get_session +from app.core.time import utcnow +from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.activity_events import ActivityEvent @@ -23,6 +25,7 @@ from app.models.agents import Agent from app.models.boards import Board from app.models.gateways import Gateway from app.models.tasks import Task +from app.schemas.common import OkResponse from app.schemas.agents import ( AgentCreate, AgentHeartbeat, @@ -60,27 +63,6 @@ def _parse_since(value: str | None) -> datetime | None: return parsed -def _normalize_identity_profile( - profile: dict[str, object] | None, -) -> dict[str, str] | None: - if not profile: - return None - normalized: dict[str, str] = {} - for key, raw in profile.items(): - if raw is None: - continue - if isinstance(raw, list): - parts = [str(item).strip() for item in raw if str(item).strip()] - if not parts: - continue - normalized[key] = ", ".join(parts) - continue - value = str(raw).strip() - if value: - normalized[key] = value - return normalized or None - - def _slugify(value: str) -> str: slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") return slug or uuid4().hex @@ -100,25 +82,25 @@ def _workspace_path(agent_name: str, workspace_root: str | None) -> str: return f"{root}/workspace-{_slugify(agent_name)}" -def _require_board(session: Session, board_id: UUID | str | None) -> Board: +async def _require_board(session: AsyncSession, board_id: UUID | str | None) -> Board: if not board_id: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="board_id is required", ) - board = session.get(Board, board_id) + board = await session.get(Board, board_id) if board is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board not found") return board -def _require_gateway(session: Session, board: Board) -> tuple[Gateway, GatewayClientConfig]: +async def _require_gateway(session: AsyncSession, board: Board) -> tuple[Gateway, GatewayClientConfig]: if not board.gateway_id: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Board gateway_id is required", ) - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -151,8 +133,8 @@ def _gateway_client_config(gateway: Gateway) -> GatewayClientConfig: return GatewayClientConfig(url=gateway.url, token=gateway.token) -def _get_gateway_main_session_keys(session: Session) -> set[str]: - keys = session.exec(select(Gateway.main_session_key)).all() +async def _get_gateway_main_session_keys(session: AsyncSession) -> set[str]: + keys = (await session.exec(select(Gateway.main_session_key))).all() return {key for key in keys if key} @@ -165,10 +147,12 @@ def _to_agent_read(agent: Agent, main_session_keys: set[str]) -> AgentRead: return model.model_copy(update={"is_gateway_main": _is_gateway_main(agent, main_session_keys)}) -def _find_gateway_for_main_session(session: Session, session_key: str | None) -> Gateway | None: +async def _find_gateway_for_main_session( + session: AsyncSession, session_key: str | None +) -> Gateway | None: if not session_key: return None - return session.exec(select(Gateway).where(Gateway.main_session_key == session_key)).first() + return (await session.exec(select(Gateway).where(Gateway.main_session_key == session_key))).first() async def _ensure_gateway_session( @@ -184,7 +168,7 @@ async def _ensure_gateway_session( def _with_computed_status(agent: Agent) -> Agent: - now = datetime.utcnow() + now = utcnow() if agent.status in {"deleting", "updating"}: return agent if agent.last_seen_at is None: @@ -198,24 +182,24 @@ def _serialize_agent(agent: Agent, main_session_keys: set[str]) -> dict[str, obj return _to_agent_read(_with_computed_status(agent), main_session_keys).model_dump(mode="json") -def _fetch_agent_events( +async def _fetch_agent_events( + session: AsyncSession, board_id: UUID | None, since: datetime, ) -> list[Agent]: - with Session(engine) as session: - statement = select(Agent) - if board_id: - statement = statement.where(col(Agent.board_id) == board_id) - statement = statement.where( - or_( - col(Agent.updated_at) >= since, - col(Agent.last_seen_at) >= since, - ) - ).order_by(asc(col(Agent.updated_at))) - return list(session.exec(statement)) + statement = select(Agent) + if board_id: + statement = statement.where(col(Agent.board_id) == board_id) + statement = statement.where( + or_( + col(Agent.updated_at) >= since, + col(Agent.last_seen_at) >= since, + ) + ).order_by(asc(col(Agent.updated_at))) + return list(await session.exec(statement)) -def _record_heartbeat(session: Session, agent: Agent) -> None: +def _record_heartbeat(session: AsyncSession, agent: Agent) -> None: record_activity( session, event_type="agent.heartbeat", @@ -224,7 +208,7 @@ def _record_heartbeat(session: Session, agent: Agent) -> None: ) -def _record_instruction_failure(session: Session, agent: Agent, error: str, action: str) -> None: +def _record_instruction_failure(session: AsyncSession, agent: Agent, error: str, action: str) -> None: action_label = action.replace("_", " ").capitalize() record_activity( session, @@ -248,12 +232,12 @@ async def _send_wakeup_message( @router.get("", response_model=list[AgentRead]) -def list_agents( - session: Session = Depends(get_session), +async def list_agents( + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), -) -> list[Agent]: - agents = list(session.exec(select(Agent))) - main_session_keys = _get_gateway_main_session_keys(session) +) -> list[AgentRead]: + agents = list(await session.exec(select(Agent))) + main_session_keys = await _get_gateway_main_session_keys(session) return [_to_agent_read(_with_computed_status(agent), main_session_keys) for agent in agents] @@ -264,24 +248,23 @@ async def stream_agents( since: str | None = Query(default=None), auth: AuthContext = Depends(require_admin_auth), ) -> EventSourceResponse: - since_dt = _parse_since(since) or datetime.utcnow() + since_dt = _parse_since(since) or utcnow() last_seen = since_dt - async def event_generator(): + async def event_generator() -> AsyncIterator[dict[str, str]]: nonlocal last_seen while True: if await request.is_disconnected(): break - agents = await run_in_threadpool(_fetch_agent_events, board_id, last_seen) - if agents: - with Session(engine) as session: - main_session_keys = _get_gateway_main_session_keys(session) - for agent in agents: - updated_at = agent.updated_at or agent.last_seen_at or datetime.utcnow() - if updated_at > last_seen: - last_seen = updated_at - payload = {"agent": _serialize_agent(agent, main_session_keys)} - yield {"event": "agent", "data": json.dumps(payload)} + async with async_session_maker() as session: + agents = await _fetch_agent_events(session, board_id, last_seen) + main_session_keys = await _get_gateway_main_session_keys(session) if agents else set() + for agent in agents: + updated_at = agent.updated_at or agent.last_seen_at or utcnow() + if updated_at > last_seen: + last_seen = updated_at + payload = {"agent": _serialize_agent(agent, main_session_keys)} + yield {"event": "agent", "data": json.dumps(payload)} await asyncio.sleep(2) return EventSourceResponse(event_generator(), ping=15) @@ -290,9 +273,9 @@ async def stream_agents( @router.post("", response_model=AgentRead) async def create_agent( payload: AgentCreate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), -) -> Agent: +) -> AgentRead: if actor.actor_type == "agent": if not actor.agent or not actor.agent.is_board_lead: raise HTTPException( @@ -311,39 +294,36 @@ async def create_agent( ) payload = AgentCreate(**{**payload.model_dump(), "board_id": actor.agent.board_id}) - board = _require_board(session, payload.board_id) - gateway, client_config = _require_gateway(session, board) + board = await _require_board(session, payload.board_id) + gateway, client_config = await _require_gateway(session, board) data = payload.model_dump() requested_name = (data.get("name") or "").strip() if requested_name: - existing = session.exec( - select(Agent) - .where(Agent.board_id == board.id) - .where(col(Agent.name).ilike(requested_name)) + existing = ( + await session.exec( + select(Agent) + .where(Agent.board_id == board.id) + .where(col(Agent.name).ilike(requested_name)) + ) ).first() if existing: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="An agent with this name already exists on this board.", ) - if data.get("identity_template") == "": - data["identity_template"] = None - if data.get("soul_template") == "": - data["soul_template"] = None - data["identity_profile"] = _normalize_identity_profile(data.get("identity_profile")) agent = Agent.model_validate(data) agent.status = "provisioning" raw_token = generate_agent_token() agent.agent_token_hash = hash_agent_token(raw_token) if agent.heartbeat_config is None: agent.heartbeat_config = DEFAULT_HEARTBEAT_CONFIG.copy() - agent.provision_requested_at = datetime.utcnow() + agent.provision_requested_at = utcnow() agent.provision_action = "provision" session_key, session_error = await _ensure_gateway_session(agent.name, client_config) agent.openclaw_session_id = session_key session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) if session_error: record_activity( session, @@ -358,7 +338,7 @@ async def create_agent( message=f"Session created for {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() try: await provision_agent( agent, @@ -372,9 +352,9 @@ async def create_agent( agent.provision_confirm_token_hash = None agent.provision_requested_at = None agent.provision_action = None - agent.updated_at = datetime.utcnow() + agent.updated_at = utcnow() session.add(agent) - session.commit() + await session.commit() record_activity( session, event_type="agent.provision", @@ -387,26 +367,27 @@ async def create_agent( message=f"Wakeup message sent to {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: _record_instruction_failure(session, agent, str(exc), "provision") - session.commit() + await session.commit() except Exception as exc: # pragma: no cover - unexpected provisioning errors _record_instruction_failure(session, agent, str(exc), "provision") - session.commit() - return agent + await session.commit() + main_session_keys = await _get_gateway_main_session_keys(session) + return _to_agent_read(_with_computed_status(agent), main_session_keys) @router.get("/{agent_id}", response_model=AgentRead) -def get_agent( +async def get_agent( agent_id: str, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), -) -> Agent: - agent = session.get(Agent, agent_id) +) -> AgentRead: + agent = await session.get(Agent, agent_id) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - main_session_keys = _get_gateway_main_session_keys(session) + main_session_keys = await _get_gateway_main_session_keys(session) return _to_agent_read(_with_computed_status(agent), main_session_keys) @@ -415,10 +396,10 @@ async def update_agent( agent_id: str, payload: AgentUpdate, force: bool = False, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), -) -> Agent: - agent = session.get(Agent, agent_id) +) -> AgentRead: + agent = await session.get(Agent, agent_id) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) updates = payload.model_dump(exclude_unset=True) @@ -428,21 +409,15 @@ async def update_agent( status_code=status.HTTP_403_FORBIDDEN, detail="status is controlled by agent heartbeat", ) - if updates.get("identity_template") == "": - updates["identity_template"] = None - if updates.get("soul_template") == "": - updates["soul_template"] = None - if "identity_profile" in updates: - updates["identity_profile"] = _normalize_identity_profile(updates.get("identity_profile")) if not updates and not force and make_main is None: - main_session_keys = _get_gateway_main_session_keys(session) + main_session_keys = await _get_gateway_main_session_keys(session) return _to_agent_read(_with_computed_status(agent), main_session_keys) - main_gateway = _find_gateway_for_main_session(session, agent.openclaw_session_id) + main_gateway = await _find_gateway_for_main_session(session, agent.openclaw_session_id) gateway_for_main: Gateway | None = None if make_main is True: board_source = updates.get("board_id") or agent.board_id - board_for_main = _require_board(session, board_source) - gateway_for_main, _ = _require_gateway(session, board_for_main) + board_for_main = await _require_board(session, board_source) + gateway_for_main, _ = await _require_gateway(session, board_for_main) updates["board_id"] = None agent.is_board_lead = False agent.openclaw_session_id = gateway_for_main.main_session_key @@ -450,18 +425,18 @@ async def update_agent( elif make_main is False: agent.openclaw_session_id = None if make_main is not True and "board_id" in updates: - _require_board(session, updates["board_id"]) + await _require_board(session, updates["board_id"]) for key, value in updates.items(): setattr(agent, key, value) if make_main is None and main_gateway is not None: agent.board_id = None agent.is_board_lead = False - agent.updated_at = datetime.utcnow() + agent.updated_at = utcnow() if agent.heartbeat_config is None: agent.heartbeat_config = DEFAULT_HEARTBEAT_CONFIG.copy() session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) is_main_agent = False board: Board | None = None gateway: Gateway | None = None @@ -490,8 +465,8 @@ async def update_agent( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="board_id is required for non-main agents", ) - board = _require_board(session, agent.board_id) - gateway, client_config = _require_gateway(session, board) + board = await _require_board(session, agent.board_id) + gateway, client_config = await _require_gateway(session, board) session_key = agent.openclaw_session_id or _build_session_key(agent.name) try: if client_config is None: @@ -503,19 +478,19 @@ async def update_agent( if not agent.openclaw_session_id: agent.openclaw_session_id = session_key session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) except OpenClawGatewayError as exc: _record_instruction_failure(session, agent, str(exc), "update") - session.commit() + await session.commit() raw_token = generate_agent_token() agent.agent_token_hash = hash_agent_token(raw_token) - agent.provision_requested_at = datetime.utcnow() + agent.provision_requested_at = utcnow() agent.provision_action = "update" agent.status = "updating" session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) try: if gateway is None: raise HTTPException( @@ -533,6 +508,11 @@ async def update_agent( reset_session=True, ) else: + if board is None: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="board is required for non-main agent provisioning", + ) await provision_agent( agent, board, @@ -548,9 +528,9 @@ async def update_agent( agent.provision_requested_at = None agent.provision_action = None agent.status = "online" - agent.updated_at = datetime.utcnow() + agent.updated_at = utcnow() session.add(agent) - session.commit() + await session.commit() record_activity( session, event_type="agent.update.direct", @@ -563,33 +543,33 @@ async def update_agent( message=f"Wakeup message sent to {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: _record_instruction_failure(session, agent, str(exc), "update") - session.commit() + await session.commit() raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Gateway update failed: {exc}", ) from exc except Exception as exc: # pragma: no cover - unexpected provisioning errors _record_instruction_failure(session, agent, str(exc), "update") - session.commit() + await session.commit() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Unexpected error updating agent provisioning.", ) from exc - main_session_keys = _get_gateway_main_session_keys(session) + main_session_keys = await _get_gateway_main_session_keys(session) return _to_agent_read(_with_computed_status(agent), main_session_keys) @router.post("/{agent_id}/heartbeat", response_model=AgentRead) -def heartbeat_agent( +async def heartbeat_agent( agent_id: str, payload: AgentHeartbeat, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> AgentRead: - agent = session.get(Agent, agent_id) + agent = await session.get(Agent, agent_id) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) if actor.actor_type == "agent" and actor.agent and actor.agent.id != agent.id: @@ -598,25 +578,25 @@ def heartbeat_agent( agent.status = payload.status elif agent.status == "provisioning": agent.status = "online" - agent.last_seen_at = datetime.utcnow() - agent.updated_at = datetime.utcnow() + agent.last_seen_at = utcnow() + agent.updated_at = utcnow() _record_heartbeat(session, agent) session.add(agent) - session.commit() - session.refresh(agent) - main_session_keys = _get_gateway_main_session_keys(session) + await session.commit() + await session.refresh(agent) + main_session_keys = await _get_gateway_main_session_keys(session) return _to_agent_read(_with_computed_status(agent), main_session_keys) @router.post("/heartbeat", response_model=AgentRead) async def heartbeat_or_create_agent( payload: AgentHeartbeatCreate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> AgentRead: # Agent tokens must heartbeat their authenticated agent record. Names are not unique. if actor.actor_type == "agent" and actor.agent: - return heartbeat_agent( + return await heartbeat_agent( agent_id=str(actor.agent.id), payload=AgentHeartbeat(status=payload.status), session=session, @@ -626,12 +606,12 @@ async def heartbeat_or_create_agent( statement = select(Agent).where(Agent.name == payload.name) if payload.board_id is not None: statement = statement.where(Agent.board_id == payload.board_id) - agent = session.exec(statement).first() + agent = (await session.exec(statement)).first() if agent is None: if actor.actor_type == "agent": raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - board = _require_board(session, payload.board_id) - gateway, client_config = _require_gateway(session, board) + board = await _require_board(session, payload.board_id) + gateway, client_config = await _require_gateway(session, board) agent = Agent( name=payload.name, status="provisioning", @@ -640,13 +620,13 @@ async def heartbeat_or_create_agent( ) raw_token = generate_agent_token() agent.agent_token_hash = hash_agent_token(raw_token) - agent.provision_requested_at = datetime.utcnow() + agent.provision_requested_at = utcnow() agent.provision_action = "provision" session_key, session_error = await _ensure_gateway_session(agent.name, client_config) agent.openclaw_session_id = session_key session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) if session_error: record_activity( session, @@ -661,16 +641,16 @@ async def heartbeat_or_create_agent( message=f"Session created for {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() try: await provision_agent(agent, board, gateway, raw_token, actor.user, action="provision") await _send_wakeup_message(agent, client_config, verb="provisioned") agent.provision_confirm_token_hash = None agent.provision_requested_at = None agent.provision_action = None - agent.updated_at = datetime.utcnow() + agent.updated_at = utcnow() session.add(agent) - session.commit() + await session.commit() record_activity( session, event_type="agent.provision", @@ -683,13 +663,13 @@ async def heartbeat_or_create_agent( message=f"Wakeup message sent to {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: _record_instruction_failure(session, agent, str(exc), "provision") - session.commit() + await session.commit() except Exception as exc: # pragma: no cover - unexpected provisioning errors _record_instruction_failure(session, agent, str(exc), "provision") - session.commit() + await session.commit() elif actor.actor_type == "agent" and actor.agent and actor.agent.id != agent.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) elif agent.agent_token_hash is None and actor.actor_type == "user": @@ -697,22 +677,22 @@ async def heartbeat_or_create_agent( agent.agent_token_hash = hash_agent_token(raw_token) if agent.heartbeat_config is None: agent.heartbeat_config = DEFAULT_HEARTBEAT_CONFIG.copy() - agent.provision_requested_at = datetime.utcnow() + agent.provision_requested_at = utcnow() agent.provision_action = "provision" session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) try: - board = _require_board(session, str(agent.board_id) if agent.board_id else None) - gateway, client_config = _require_gateway(session, board) + board = await _require_board(session, str(agent.board_id) if agent.board_id else None) + gateway, client_config = await _require_gateway(session, board) await provision_agent(agent, board, gateway, raw_token, actor.user, action="provision") await _send_wakeup_message(agent, client_config, verb="provisioned") agent.provision_confirm_token_hash = None agent.provision_requested_at = None agent.provision_action = None - agent.updated_at = datetime.utcnow() + agent.updated_at = utcnow() session.add(agent) - session.commit() + await session.commit() record_activity( session, event_type="agent.provision", @@ -725,16 +705,16 @@ async def heartbeat_or_create_agent( message=f"Wakeup message sent to {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: _record_instruction_failure(session, agent, str(exc), "provision") - session.commit() + await session.commit() except Exception as exc: # pragma: no cover - unexpected provisioning errors _record_instruction_failure(session, agent, str(exc), "provision") - session.commit() + await session.commit() elif not agent.openclaw_session_id: - board = _require_board(session, str(agent.board_id) if agent.board_id else None) - gateway, client_config = _require_gateway(session, board) + board = await _require_board(session, str(agent.board_id) if agent.board_id else None) + gateway, client_config = await _require_gateway(session, board) session_key, session_error = await _ensure_gateway_session(agent.name, client_config) agent.openclaw_session_id = session_key if session_error: @@ -751,47 +731,45 @@ async def heartbeat_or_create_agent( message=f"Session created for {agent.name}.", agent_id=agent.id, ) - session.commit() + await session.commit() if payload.status: agent.status = payload.status elif agent.status == "provisioning": agent.status = "online" - agent.last_seen_at = datetime.utcnow() - agent.updated_at = datetime.utcnow() + agent.last_seen_at = utcnow() + agent.updated_at = utcnow() _record_heartbeat(session, agent) session.add(agent) - session.commit() - session.refresh(agent) - main_session_keys = _get_gateway_main_session_keys(session) + await session.commit() + await session.refresh(agent) + main_session_keys = await _get_gateway_main_session_keys(session) return _to_agent_read(_with_computed_status(agent), main_session_keys) -@router.delete("/{agent_id}") -def delete_agent( +@router.delete("/{agent_id}", response_model=OkResponse) +async def delete_agent( agent_id: str, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), -) -> dict[str, bool]: - agent = session.get(Agent, agent_id) +) -> OkResponse: + agent = await session.get(Agent, agent_id) if agent is None: - return {"ok": True} + return OkResponse() - board = _require_board(session, str(agent.board_id) if agent.board_id else None) - gateway, client_config = _require_gateway(session, board) + board = await _require_board(session, str(agent.board_id) if agent.board_id else None) + gateway, client_config = await _require_gateway(session, board) try: - import asyncio - - workspace_path = asyncio.run(cleanup_agent(agent, gateway)) + workspace_path = await cleanup_agent(agent, gateway) except OpenClawGatewayError as exc: _record_instruction_failure(session, agent, str(exc), "delete") - session.commit() + await session.commit() raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Gateway cleanup failed: {exc}", ) from exc except Exception as exc: # pragma: no cover - unexpected cleanup errors _record_instruction_failure(session, agent, str(exc), "delete") - session.commit() + await session.commit() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Workspace cleanup failed: {exc}", @@ -804,7 +782,7 @@ def delete_agent( agent_id=None, ) now = datetime.now() - session.execute( + await session.execute( update(Task) .where(col(Task.assigned_agent_id) == agent.id) .where(col(Task.status) == "in_progress") @@ -815,7 +793,7 @@ def delete_agent( updated_at=now, ) ) - session.execute( + await session.execute( update(Task) .where(col(Task.assigned_agent_id) == agent.id) .where(col(Task.status) != "in_progress") @@ -824,11 +802,11 @@ def delete_agent( updated_at=now, ) ) - session.execute( + await session.execute( update(ActivityEvent).where(col(ActivityEvent.agent_id) == agent.id).values(agent_id=None) ) - session.delete(agent) - session.commit() + await session.delete(agent) + await session.commit() # Always ask the main agent to confirm workspace cleanup. try: @@ -843,20 +821,14 @@ def delete_agent( "1) Remove the workspace directory.\n" "2) Reply NO_REPLY.\n" ) - - async def _request_cleanup() -> None: - await ensure_session(main_session, config=client_config, label="Main Agent") - await send_message( - cleanup_message, - session_key=main_session, - config=client_config, - deliver=False, - ) - - import asyncio - - asyncio.run(_request_cleanup()) + await ensure_session(main_session, config=client_config, label="Main Agent") + await send_message( + cleanup_message, + session_key=main_session, + config=client_config, + deliver=False, + ) except Exception: # Cleanup request is best-effort; deletion already completed. pass - return {"ok": True} + return OkResponse() diff --git a/backend/app/api/approvals.py b/backend/app/api/approvals.py index 76abdacd..600cf9c5 100644 --- a/backend/app/api/approvals.py +++ b/backend/app/api/approvals.py @@ -2,24 +2,26 @@ from __future__ import annotations import asyncio import json +from collections.abc import AsyncIterator from datetime import datetime, timezone from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from sqlalchemy import asc, or_ -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from sse_starlette.sse import EventSourceResponse -from starlette.concurrency import run_in_threadpool from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent -from app.db.session import engine, get_session +from app.core.auth import AuthContext +from app.core.time import utcnow +from app.db.session import async_session_maker, get_session from app.models.approvals import Approval -from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalUpdate +from app.models.boards import Board +from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus, ApprovalUpdate router = APIRouter(prefix="/boards/{board_id}/approvals", tags=["approvals"]) -ALLOWED_STATUSES = {"pending", "approved", "rejected"} - def _parse_since(value: str | None) -> datetime | None: if not value: @@ -45,30 +47,30 @@ def _serialize_approval(approval: Approval) -> dict[str, object]: return ApprovalRead.model_validate(approval, from_attributes=True).model_dump(mode="json") -def _fetch_approval_events( +async def _fetch_approval_events( + session: AsyncSession, board_id: UUID, since: datetime, ) -> list[Approval]: - with Session(engine) as session: - statement = ( - select(Approval) - .where(col(Approval.board_id) == board_id) - .where( - or_( - col(Approval.created_at) >= since, - col(Approval.resolved_at) >= since, - ) + statement = ( + select(Approval) + .where(col(Approval.board_id) == board_id) + .where( + or_( + col(Approval.created_at) >= since, + col(Approval.resolved_at) >= since, ) - .order_by(asc(col(Approval.created_at))) ) - return list(session.exec(statement)) + .order_by(asc(col(Approval.created_at))) + ) + return list(await session.exec(statement)) @router.get("", response_model=list[ApprovalRead]) -def list_approvals( - status_filter: str | None = Query(default=None, alias="status"), - board=Depends(get_board_or_404), - session: Session = Depends(get_session), +async def list_approvals( + status_filter: ApprovalStatus | None = Query(default=None, alias="status"), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> list[Approval]: if actor.actor_type == "agent" and actor.agent: @@ -76,32 +78,31 @@ def list_approvals( raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) statement = select(Approval).where(col(Approval.board_id) == board.id) if status_filter: - if status_filter not in ALLOWED_STATUSES: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) statement = statement.where(col(Approval.status) == status_filter) statement = statement.order_by(col(Approval.created_at).desc()) - return list(session.exec(statement)) + return list(await session.exec(statement)) @router.get("/stream") async def stream_approvals( request: Request, - board=Depends(get_board_or_404), + board: Board = Depends(get_board_or_404), actor: ActorContext = Depends(require_admin_or_agent), since: str | None = Query(default=None), ) -> EventSourceResponse: if actor.actor_type == "agent" and actor.agent: if actor.agent.board_id and actor.agent.board_id != board.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - since_dt = _parse_since(since) or datetime.utcnow() + since_dt = _parse_since(since) or utcnow() last_seen = since_dt - async def event_generator(): + async def event_generator() -> AsyncIterator[dict[str, str]]: nonlocal last_seen while True: if await request.is_disconnected(): break - approvals = await run_in_threadpool(_fetch_approval_events, board.id, last_seen) + async with async_session_maker() as session: + approvals = await _fetch_approval_events(session, board.id, last_seen) for approval in approvals: updated_at = _approval_updated_at(approval) if updated_at > last_seen: @@ -114,10 +115,10 @@ async def stream_approvals( @router.post("", response_model=ApprovalRead) -def create_approval( +async def create_approval( payload: ApprovalCreate, - board=Depends(get_board_or_404), - session: Session = Depends(get_session), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> Approval: if actor.actor_type == "agent" and actor.agent: @@ -133,30 +134,28 @@ def create_approval( status=payload.status, ) session.add(approval) - session.commit() - session.refresh(approval) + await session.commit() + await session.refresh(approval) return approval @router.patch("/{approval_id}", response_model=ApprovalRead) -def update_approval( +async def update_approval( approval_id: str, payload: ApprovalUpdate, - board=Depends(get_board_or_404), - session: Session = Depends(get_session), - auth=Depends(require_admin_auth), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), + auth: AuthContext = Depends(require_admin_auth), ) -> Approval: - approval = session.get(Approval, approval_id) + approval = await session.get(Approval, approval_id) if approval is None or approval.board_id != board.id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) updates = payload.model_dump(exclude_unset=True) if "status" in updates: - if updates["status"] not in ALLOWED_STATUSES: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) approval.status = updates["status"] if approval.status != "pending": - approval.resolved_at = datetime.utcnow() + approval.resolved_at = utcnow() session.add(approval) - session.commit() - session.refresh(approval) + await session.commit() + await session.refresh(approval) return approval diff --git a/backend/app/api/board_memory.py b/backend/app/api/board_memory.py index 6fdb50d2..e8ad5d68 100644 --- a/backend/app/api/board_memory.py +++ b/backend/app/api/board_memory.py @@ -3,20 +3,24 @@ from __future__ import annotations import asyncio import json import re +from collections.abc import AsyncIterator from datetime import datetime, timezone +from uuid import UUID -from fastapi import APIRouter, Depends, HTTPException, Query, Request -from sqlmodel import Session, col, select +from fastapi import APIRouter, Depends, HTTPException, Query, Request, status +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from sse_starlette.sse import EventSourceResponse -from starlette.concurrency import run_in_threadpool from app.api.deps import ActorContext, get_board_or_404, require_admin_or_agent from app.core.config import settings -from app.db.session import engine, get_session +from app.core.time import utcnow +from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.agents import Agent from app.models.board_memory import BoardMemory +from app.models.boards import Board from app.models.gateways import Gateway from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead @@ -62,10 +66,10 @@ def _matches_mention(agent: Agent, mentions: set[str]) -> bool: return first in mentions -def _gateway_config(session: Session, board) -> GatewayClientConfig | None: - if not board.gateway_id: +async def _gateway_config(session: AsyncSession, board: Board) -> GatewayClientConfig | None: + if board.gateway_id is None: return None - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway is None or not gateway.url: return None return GatewayClientConfig(url=gateway.url, token=gateway.token) @@ -82,36 +86,36 @@ async def _send_agent_message( await send_message(message, session_key=session_key, config=config, deliver=False) -def _fetch_memory_events( - board_id, +async def _fetch_memory_events( + session: AsyncSession, + board_id: UUID, since: datetime, ) -> list[BoardMemory]: - with Session(engine) as session: - statement = ( - select(BoardMemory) - .where(col(BoardMemory.board_id) == board_id) - .where(col(BoardMemory.created_at) >= since) - .order_by(col(BoardMemory.created_at)) - ) - return list(session.exec(statement)) + statement = ( + select(BoardMemory) + .where(col(BoardMemory.board_id) == board_id) + .where(col(BoardMemory.created_at) >= since) + .order_by(col(BoardMemory.created_at)) + ) + return list(await session.exec(statement)) -def _notify_chat_targets( +async def _notify_chat_targets( *, - session: Session, - board, + session: AsyncSession, + board: Board, memory: BoardMemory, actor: ActorContext, ) -> None: if not memory.content: return - config = _gateway_config(session, board) + config = await _gateway_config(session, board) if config is None: return mentions = _extract_mentions(memory.content) statement = select(Agent).where(col(Agent.board_id) == board.id) targets: dict[str, Agent] = {} - for agent in session.exec(statement): + for agent in await session.exec(statement): if agent.is_board_lead: targets[str(agent.id)] = agent continue @@ -145,24 +149,22 @@ def _notify_chat_targets( 'Body: {"content":"...","tags":["chat"]}' ) try: - asyncio.run( - _send_agent_message( - session_key=agent.openclaw_session_id, - config=config, - agent_name=agent.name, - message=message, - ) + await _send_agent_message( + session_key=agent.openclaw_session_id, + config=config, + agent_name=agent.name, + message=message, ) except OpenClawGatewayError: continue @router.get("", response_model=list[BoardMemoryRead]) -def list_board_memory( +async def list_board_memory( limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), - board=Depends(get_board_or_404), - session: Session = Depends(get_session), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> list[BoardMemory]: if actor.actor_type == "agent" and actor.agent: @@ -175,28 +177,29 @@ def list_board_memory( .offset(offset) .limit(limit) ) - return list(session.exec(statement)) + return list(await session.exec(statement)) @router.get("/stream") async def stream_board_memory( request: Request, - board=Depends(get_board_or_404), + board: Board = Depends(get_board_or_404), actor: ActorContext = Depends(require_admin_or_agent), since: str | None = Query(default=None), ) -> EventSourceResponse: if actor.actor_type == "agent" and actor.agent: if actor.agent.board_id and actor.agent.board_id != board.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - since_dt = _parse_since(since) or datetime.utcnow() + since_dt = _parse_since(since) or utcnow() last_seen = since_dt - async def event_generator(): + async def event_generator() -> AsyncIterator[dict[str, str]]: nonlocal last_seen while True: if await request.is_disconnected(): break - memories = await run_in_threadpool(_fetch_memory_events, board.id, last_seen) + async with async_session_maker() as session: + memories = await _fetch_memory_events(session, board.id, last_seen) for memory in memories: if memory.created_at > last_seen: last_seen = memory.created_at @@ -208,10 +211,10 @@ async def stream_board_memory( @router.post("", response_model=BoardMemoryRead) -def create_board_memory( +async def create_board_memory( payload: BoardMemoryCreate, - board=Depends(get_board_or_404), - session: Session = Depends(get_session), + board: Board = Depends(get_board_or_404), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> BoardMemory: if actor.actor_type == "agent" and actor.agent: @@ -231,8 +234,8 @@ def create_board_memory( source=source, ) session.add(memory) - session.commit() - session.refresh(memory) + await session.commit() + await session.refresh(memory) if is_chat: - _notify_chat_targets(session=session, board=board, memory=memory, actor=actor) + await _notify_chat_targets(session=session, board=board, memory=memory, actor=actor) return memory diff --git a/backend/app/api/board_onboarding.py b/backend/app/api/board_onboarding.py index 023702c8..f43cec3d 100644 --- a/backend/app/api/board_onboarding.py +++ b/backend/app/api/board_onboarding.py @@ -1,18 +1,19 @@ from __future__ import annotations -import json import logging import re from datetime import datetime from uuid import uuid4 from fastapi import APIRouter, Depends, HTTPException, status -from sqlmodel import Session, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent from app.core.agent_tokens import generate_agent_token, hash_agent_token from app.core.auth import AuthContext from app.core.config import settings +from app.core.time import utcnow from app.db.session import get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message @@ -22,6 +23,8 @@ from app.models.boards import Board from app.models.gateways import Gateway from app.schemas.board_onboarding import ( BoardOnboardingAnswer, + BoardOnboardingAgentComplete, + BoardOnboardingAgentUpdate, BoardOnboardingConfirm, BoardOnboardingRead, BoardOnboardingStart, @@ -33,10 +36,12 @@ router = APIRouter(prefix="/boards/{board_id}/onboarding", tags=["board-onboardi logger = logging.getLogger(__name__) -def _gateway_config(session: Session, board: Board) -> tuple[Gateway, GatewayClientConfig]: +async def _gateway_config( + session: AsyncSession, board: Board +) -> tuple[Gateway, GatewayClientConfig]: if not board.gateway_id: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway is None or not gateway.url or not gateway.main_session_key: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) return gateway, GatewayClientConfig(url=gateway.url, token=gateway.token) @@ -56,21 +61,25 @@ def _lead_session_key(board: Board) -> str: async def _ensure_lead_agent( - session: Session, + session: AsyncSession, board: Board, gateway: Gateway, config: GatewayClientConfig, auth: AuthContext, ) -> Agent: - existing = session.exec( - select(Agent).where(Agent.board_id == board.id).where(Agent.is_board_lead.is_(True)) + existing = ( + await session.exec( + select(Agent) + .where(Agent.board_id == board.id) + .where(col(Agent.is_board_lead).is_(True)) + ) ).first() if existing: if existing.name != _lead_agent_name(board): existing.name = _lead_agent_name(board) session.add(existing) - session.commit() - session.refresh(existing) + await session.commit() + await session.refresh(existing) return existing agent = Agent( @@ -87,12 +96,12 @@ async def _ensure_lead_agent( ) raw_token = generate_agent_token() agent.agent_token_hash = hash_agent_token(raw_token) - agent.provision_requested_at = datetime.utcnow() + agent.provision_requested_at = utcnow() agent.provision_action = "provision" agent.openclaw_session_id = _lead_session_key(board) session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) try: await provision_agent(agent, board, gateway, raw_token, auth.user, action="provision") @@ -114,15 +123,17 @@ async def _ensure_lead_agent( @router.get("", response_model=BoardOnboardingRead) -def get_onboarding( +async def get_onboarding( board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> BoardOnboardingSession: - onboarding = session.exec( - select(BoardOnboardingSession) - .where(BoardOnboardingSession.board_id == board.id) - .order_by(BoardOnboardingSession.created_at.desc()) + onboarding = ( + await session.exec( + select(BoardOnboardingSession) + .where(BoardOnboardingSession.board_id == board.id) + .order_by(col(BoardOnboardingSession.created_at).desc()) + ) ).first() if onboarding is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) @@ -133,18 +144,20 @@ def get_onboarding( async def start_onboarding( payload: BoardOnboardingStart, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> BoardOnboardingSession: - onboarding = session.exec( - select(BoardOnboardingSession) - .where(BoardOnboardingSession.board_id == board.id) - .where(BoardOnboardingSession.status == "active") + onboarding = ( + await session.exec( + select(BoardOnboardingSession) + .where(BoardOnboardingSession.board_id == board.id) + .where(BoardOnboardingSession.status == "active") + ) ).first() if onboarding: return onboarding - gateway, config = _gateway_config(session, board) + gateway, config = await _gateway_config(session, board) session_key = gateway.main_session_key base_url = settings.base_url or "http://localhost:8000" prompt = ( @@ -185,11 +198,11 @@ async def start_onboarding( board_id=board.id, session_key=session_key, status="active", - messages=[{"role": "user", "content": prompt, "timestamp": datetime.utcnow().isoformat()}], + messages=[{"role": "user", "content": prompt, "timestamp": utcnow().isoformat()}], ) session.add(onboarding) - session.commit() - session.refresh(onboarding) + await session.commit() + await session.refresh(onboarding) return onboarding @@ -197,25 +210,27 @@ async def start_onboarding( async def answer_onboarding( payload: BoardOnboardingAnswer, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> BoardOnboardingSession: - onboarding = session.exec( - select(BoardOnboardingSession) - .where(BoardOnboardingSession.board_id == board.id) - .order_by(BoardOnboardingSession.created_at.desc()) + onboarding = ( + await session.exec( + select(BoardOnboardingSession) + .where(BoardOnboardingSession.board_id == board.id) + .order_by(col(BoardOnboardingSession.created_at).desc()) + ) ).first() if onboarding is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - _, config = _gateway_config(session, board) + _, config = await _gateway_config(session, board) answer_text = payload.answer if payload.other_text: answer_text = f"{payload.answer}: {payload.other_text}" messages = list(onboarding.messages or []) messages.append( - {"role": "user", "content": answer_text, "timestamp": datetime.utcnow().isoformat()} + {"role": "user", "content": answer_text, "timestamp": utcnow().isoformat()} ) try: @@ -227,18 +242,18 @@ async def answer_onboarding( raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc onboarding.messages = messages - onboarding.updated_at = datetime.utcnow() + onboarding.updated_at = utcnow() session.add(onboarding) - session.commit() - session.refresh(onboarding) + await session.commit() + await session.refresh(onboarding) return onboarding @router.post("/agent", response_model=BoardOnboardingRead) -def agent_onboarding_update( - payload: dict[str, object], +async def agent_onboarding_update( + payload: BoardOnboardingAgentUpdate, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> BoardOnboardingSession: if actor.actor_type != "agent" or actor.agent is None: @@ -248,15 +263,17 @@ def agent_onboarding_update( raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) if board.gateway_id: - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway and gateway.main_session_key and agent.openclaw_session_id: if agent.openclaw_session_id != gateway.main_session_key: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - onboarding = session.exec( - select(BoardOnboardingSession) - .where(BoardOnboardingSession.board_id == board.id) - .order_by(BoardOnboardingSession.created_at.desc()) + onboarding = ( + await session.exec( + select(BoardOnboardingSession) + .where(BoardOnboardingSession.board_id == board.id) + .order_by(col(BoardOnboardingSession.created_at).desc()) + ) ).first() if onboarding is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) @@ -264,31 +281,27 @@ def agent_onboarding_update( raise HTTPException(status_code=status.HTTP_409_CONFLICT) messages = list(onboarding.messages or []) - now = datetime.utcnow().isoformat() - payload_text = json.dumps(payload) + now = utcnow().isoformat() + payload_text = payload.model_dump_json(exclude_none=True) + payload_data = payload.model_dump(mode="json", exclude_none=True) logger.info( "onboarding.agent.update board_id=%s agent_id=%s payload=%s", board.id, agent.id, payload_text, ) - payload_status = payload.get("status") - if payload_status == "complete": - onboarding.draft_goal = payload + if isinstance(payload, BoardOnboardingAgentComplete): + onboarding.draft_goal = payload_data onboarding.status = "completed" messages.append({"role": "assistant", "content": payload_text, "timestamp": now}) else: - question = payload.get("question") - options = payload.get("options") - if not isinstance(question, str) or not isinstance(options, list): - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) messages.append({"role": "assistant", "content": payload_text, "timestamp": now}) onboarding.messages = messages - onboarding.updated_at = datetime.utcnow() + onboarding.updated_at = utcnow() session.add(onboarding) - session.commit() - session.refresh(onboarding) + await session.commit() + await session.refresh(onboarding) logger.info( "onboarding.agent.update stored board_id=%s messages_count=%s status=%s", board.id, @@ -302,13 +315,15 @@ def agent_onboarding_update( async def confirm_onboarding( payload: BoardOnboardingConfirm, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> Board: - onboarding = session.exec( - select(BoardOnboardingSession) - .where(BoardOnboardingSession.board_id == board.id) - .order_by(BoardOnboardingSession.created_at.desc()) + onboarding = ( + await session.exec( + select(BoardOnboardingSession) + .where(BoardOnboardingSession.board_id == board.id) + .order_by(col(BoardOnboardingSession.created_at).desc()) + ) ).first() if onboarding is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) @@ -321,12 +336,12 @@ async def confirm_onboarding( board.goal_source = "lead_agent_onboarding" onboarding.status = "confirmed" - onboarding.updated_at = datetime.utcnow() + onboarding.updated_at = utcnow() - gateway, config = _gateway_config(session, board) + gateway, config = await _gateway_config(session, board) session.add(board) session.add(onboarding) - session.commit() - session.refresh(board) + await session.commit() + await session.refresh(board) await _ensure_lead_agent(session, board, gateway, config, auth) return board diff --git a/backend/app/api/boards.py b/backend/app/api/boards.py index 75a6663d..1ca67ad9 100644 --- a/backend/app/api/boards.py +++ b/backend/app/api/boards.py @@ -1,15 +1,17 @@ from __future__ import annotations -import asyncio import re from uuid import uuid4 from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import delete -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent from app.core.auth import AuthContext +from app.core.time import utcnow +from app.db import crud from app.db.session import get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import ( @@ -27,6 +29,7 @@ from app.models.boards import Board from app.models.gateways import Gateway from app.models.task_fingerprints import TaskFingerprint from app.models.tasks import Task +from app.schemas.common import OkResponse from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate router = APIRouter(prefix="/boards", tags=["boards"]) @@ -43,12 +46,56 @@ def _build_session_key(agent_name: str) -> str: return f"{AGENT_SESSION_PREFIX}:{_slugify(agent_name)}:main" -def _board_gateway( - session: Session, board: Board +async def _require_gateway(session: AsyncSession, gateway_id: object) -> Gateway: + gateway = await crud.get_by_id(session, Gateway, gateway_id) + if gateway is None: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="gateway_id is invalid", + ) + return gateway + + +async def _require_gateway_for_create( + payload: BoardCreate, + session: AsyncSession = Depends(get_session), +) -> Gateway: + return await _require_gateway(session, payload.gateway_id) + + +async def _apply_board_update( + *, + payload: BoardUpdate, + session: AsyncSession, + board: Board, +) -> Board: + updates = payload.model_dump(exclude_unset=True) + if "gateway_id" in updates: + await _require_gateway(session, updates["gateway_id"]) + for key, value in updates.items(): + setattr(board, key, value) + if updates.get("board_type") == "goal": + # Validate only when explicitly switching to goal boards. + if not board.objective or not board.success_metrics: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Goal boards require objective and success_metrics", + ) + if not board.gateway_id: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="gateway_id is required", + ) + board.updated_at = utcnow() + return await crud.save(session, board) + + +async def _board_gateway( + session: AsyncSession, board: Board ) -> tuple[Gateway | None, GatewayClientConfig | None]: if not board.gateway_id: return None, None - config = session.get(Gateway, board.gateway_id) + config = await session.get(Gateway, board.gateway_id) if config is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -103,36 +150,21 @@ async def _cleanup_agent_on_gateway( @router.get("", response_model=list[BoardRead]) -def list_boards( - session: Session = Depends(get_session), +async def list_boards( + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> list[Board]: - return list(session.exec(select(Board))) + return list(await session.exec(select(Board))) @router.post("", response_model=BoardRead) -def create_board( +async def create_board( payload: BoardCreate, - session: Session = Depends(get_session), + _gateway: Gateway = Depends(_require_gateway_for_create), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> Board: - data = payload.model_dump() - if not data.get("gateway_id"): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="gateway_id is required", - ) - config = session.get(Gateway, data["gateway_id"]) - if config is None: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="gateway_id is invalid", - ) - board = Board.model_validate(data) - session.add(board) - session.commit() - session.refresh(board) - return board + return await crud.create(session, Board, **payload.model_dump()) @router.get("/{board_id}", response_model=BoardRead) @@ -144,60 +176,29 @@ def get_board( @router.patch("/{board_id}", response_model=BoardRead) -def update_board( +async def update_board( payload: BoardUpdate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), board: Board = Depends(get_board_or_404), auth: AuthContext = Depends(require_admin_auth), ) -> Board: - updates = payload.model_dump(exclude_unset=True) - if "gateway_id" in updates: - if not updates.get("gateway_id"): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="gateway_id is required", - ) - config = session.get(Gateway, updates["gateway_id"]) - if config is None: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="gateway_id is invalid", - ) - for key, value in updates.items(): - setattr(board, key, value) - if updates.get("board_type") == "goal": - objective = updates.get("objective") or board.objective - metrics = updates.get("success_metrics") or board.success_metrics - if not objective or not metrics: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Goal boards require objective and success_metrics", - ) - if not board.gateway_id: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="gateway_id is required", - ) - session.add(board) - session.commit() - session.refresh(board) - return board + return await _apply_board_update(payload=payload, session=session, board=board) -@router.delete("/{board_id}") -def delete_board( - session: Session = Depends(get_session), +@router.delete("/{board_id}", response_model=OkResponse) +async def delete_board( + session: AsyncSession = Depends(get_session), board: Board = Depends(get_board_or_404), auth: AuthContext = Depends(require_admin_auth), -) -> dict[str, bool]: - agents = list(session.exec(select(Agent).where(Agent.board_id == board.id))) - task_ids = list(session.exec(select(Task.id).where(Task.board_id == board.id))) +) -> OkResponse: + agents = list(await session.exec(select(Agent).where(Agent.board_id == board.id))) + task_ids = list(await session.exec(select(Task.id).where(Task.board_id == board.id))) - config, client_config = _board_gateway(session, board) + config, client_config = await _board_gateway(session, board) if config and client_config: try: for agent in agents: - asyncio.run(_cleanup_agent_on_gateway(agent, config, client_config)) + await _cleanup_agent_on_gateway(agent, config, client_config) except OpenClawGatewayError as exc: raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, @@ -205,18 +206,18 @@ def delete_board( ) from exc if task_ids: - session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids))) - session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.board_id) == board.id)) + await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids))) + await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.board_id) == board.id)) if agents: agent_ids = [agent.id for agent in agents] - session.execute(delete(ActivityEvent).where(col(ActivityEvent.agent_id).in_(agent_ids))) - session.execute(delete(Agent).where(col(Agent.id).in_(agent_ids))) - session.execute(delete(Approval).where(col(Approval.board_id) == board.id)) - session.execute(delete(BoardMemory).where(col(BoardMemory.board_id) == board.id)) - session.execute( + await session.execute(delete(ActivityEvent).where(col(ActivityEvent.agent_id).in_(agent_ids))) + await session.execute(delete(Agent).where(col(Agent.id).in_(agent_ids))) + await session.execute(delete(Approval).where(col(Approval.board_id) == board.id)) + await session.execute(delete(BoardMemory).where(col(BoardMemory.board_id) == board.id)) + await session.execute( delete(BoardOnboardingSession).where(col(BoardOnboardingSession.board_id) == board.id) ) - session.execute(delete(Task).where(col(Task.board_id) == board.id)) - session.delete(board) - session.commit() - return {"ok": True} + await session.execute(delete(Task).where(col(Task.board_id) == board.id)) + await session.delete(board) + await session.commit() + return OkResponse() diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py index c1a68d56..5106f140 100644 --- a/backend/app/api/deps.py +++ b/backend/app/api/deps.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from typing import Literal from fastapi import Depends, HTTPException, status -from sqlmodel import Session +from sqlmodel.ext.asyncio.session import AsyncSession from app.core.agent_auth import AgentAuthContext, get_agent_auth_context_optional from app.core.auth import AuthContext, get_auth_context, get_auth_context_optional @@ -40,22 +40,22 @@ def require_admin_or_agent( raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) -def get_board_or_404( +async def get_board_or_404( board_id: str, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), ) -> Board: - board = session.get(Board, board_id) + board = await session.get(Board, board_id) if board is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) return board -def get_task_or_404( +async def get_task_or_404( task_id: str, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), ) -> Task: - task = session.get(Task, task_id) + task = await session.get(Task, task_id) if task is None or task.board_id != board.id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) return task diff --git a/backend/app/api/gateway.py b/backend/app/api/gateway.py index 352ac962..cda089db 100644 --- a/backend/app/api/gateway.py +++ b/backend/app/api/gateway.py @@ -1,7 +1,7 @@ from __future__ import annotations -from fastapi import APIRouter, Body, Depends, HTTPException, Query, status -from sqlmodel import Session +from fastapi import APIRouter, Depends, HTTPException, Query, status +from sqlmodel.ext.asyncio.session import AsyncSession from app.core.auth import AuthContext, get_auth_context from app.db.session import get_session @@ -20,12 +20,22 @@ from app.integrations.openclaw_gateway_protocol import ( ) from app.models.boards import Board from app.models.gateways import Gateway +from app.schemas.common import OkResponse +from app.schemas.gateway_api import ( + GatewayCommandsResponse, + GatewayResolveQuery, + GatewaySessionHistoryResponse, + GatewaySessionMessageRequest, + GatewaySessionResponse, + GatewaySessionsResponse, + GatewaysStatusResponse, +) router = APIRouter(prefix="/gateways", tags=["gateways"]) -def _resolve_gateway( - session: Session, +async def _resolve_gateway( + session: AsyncSession, board_id: str | None, gateway_url: str | None, gateway_token: str | None, @@ -42,7 +52,7 @@ def _resolve_gateway( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="board_id or gateway_url is required", ) - board = session.get(Board, board_id) + board = await session.get(Board, board_id) if board is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board not found") if not board.gateway_id: @@ -50,7 +60,7 @@ def _resolve_gateway( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Board gateway_id is required", ) - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -68,10 +78,10 @@ def _resolve_gateway( ) -def _require_gateway( - session: Session, board_id: str | None +async def _require_gateway( + session: AsyncSession, board_id: str | None ) -> tuple[Board, GatewayClientConfig, str | None]: - board, config, main_session = _resolve_gateway(session, board_id, None, None, None) + board, config, main_session = await _resolve_gateway(session, board_id, None, None, None) if board is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -80,21 +90,18 @@ def _require_gateway( return board, config, main_session -@router.get("/status") +@router.get("/status", response_model=GatewaysStatusResponse) async def gateways_status( - board_id: str | None = Query(default=None), - gateway_url: str | None = Query(default=None), - gateway_token: str | None = Query(default=None), - gateway_main_session_key: str | None = Query(default=None), - session: Session = Depends(get_session), + params: GatewayResolveQuery = Depends(), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), -) -> dict[str, object]: - board, config, main_session = _resolve_gateway( +) -> GatewaysStatusResponse: + board, config, main_session = await _resolve_gateway( session, - board_id, - gateway_url, - gateway_token, - gateway_main_session_key, + params.board_id, + params.gateway_url, + params.gateway_token, + params.gateway_main_session_key, ) try: sessions = await openclaw_call("sessions.list", config=config) @@ -111,30 +118,26 @@ async def gateways_status( main_session_entry = ensured.get("entry") or ensured except OpenClawGatewayError as exc: main_session_error = str(exc) - return { - "connected": True, - "gateway_url": config.url, - "sessions_count": len(sessions_list), - "sessions": sessions_list, - "main_session_key": main_session, - "main_session": main_session_entry, - "main_session_error": main_session_error, - } + return GatewaysStatusResponse( + connected=True, + gateway_url=config.url, + sessions_count=len(sessions_list), + sessions=sessions_list, + main_session_key=main_session, + main_session=main_session_entry, + main_session_error=main_session_error, + ) except OpenClawGatewayError as exc: - return { - "connected": False, - "gateway_url": config.url, - "error": str(exc), - } + return GatewaysStatusResponse(connected=False, gateway_url=config.url, error=str(exc)) -@router.get("/sessions") +@router.get("/sessions", response_model=GatewaySessionsResponse) async def list_gateway_sessions( board_id: str | None = Query(default=None), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), -) -> dict[str, object]: - board, config, main_session = _resolve_gateway( +) -> GatewaySessionsResponse: + board, config, main_session = await _resolve_gateway( session, board_id, None, @@ -159,21 +162,21 @@ async def list_gateway_sessions( except OpenClawGatewayError: main_session_entry = None - return { - "sessions": sessions_list, - "main_session_key": main_session, - "main_session": main_session_entry, - } + return GatewaySessionsResponse( + sessions=sessions_list, + main_session_key=main_session, + main_session=main_session_entry, + ) -@router.get("/sessions/{session_id}") +@router.get("/sessions/{session_id}", response_model=GatewaySessionResponse) async def get_gateway_session( session_id: str, board_id: str | None = Query(default=None), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), -) -> dict[str, object]: - board, config, main_session = _resolve_gateway( +) -> GatewaySessionResponse: + board, config, main_session = await _resolve_gateway( session, board_id, None, @@ -208,55 +211,50 @@ async def get_gateway_session( session_entry = None if session_entry is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found") - return {"session": session_entry} + return GatewaySessionResponse(session=session_entry) -@router.get("/sessions/{session_id}/history") +@router.get("/sessions/{session_id}/history", response_model=GatewaySessionHistoryResponse) async def get_session_history( session_id: str, board_id: str | None = Query(default=None), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), -) -> dict[str, object]: - _, config, _ = _require_gateway(session, board_id) +) -> GatewaySessionHistoryResponse: + _, config, _ = await _require_gateway(session, board_id) try: history = await get_chat_history(session_id, config=config) except OpenClawGatewayError as exc: raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc if isinstance(history, dict) and isinstance(history.get("messages"), list): - return {"history": history["messages"]} - return {"history": list(history or [])} + return GatewaySessionHistoryResponse(history=history["messages"]) + return GatewaySessionHistoryResponse(history=list(history or [])) -@router.post("/sessions/{session_id}/message") +@router.post("/sessions/{session_id}/message", response_model=OkResponse) async def send_gateway_session_message( session_id: str, - payload: dict = Body(...), + payload: GatewaySessionMessageRequest, board_id: str | None = Query(default=None), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), -) -> dict[str, bool]: - content = payload.get("content") - if not content: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="content is required" - ) - board, config, main_session = _require_gateway(session, board_id) +) -> OkResponse: + board, config, main_session = await _require_gateway(session, board_id) try: if main_session and session_id == main_session: await ensure_session(main_session, config=config, label="Main Agent") - await send_message(content, session_key=session_id, config=config) + await send_message(payload.content, session_key=session_id, config=config) except OpenClawGatewayError as exc: raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc - return {"ok": True} + return OkResponse() -@router.get("/commands") +@router.get("/commands", response_model=GatewayCommandsResponse) async def gateway_commands( auth: AuthContext = Depends(get_auth_context), -) -> dict[str, object]: - return { - "protocol_version": PROTOCOL_VERSION, - "methods": GATEWAY_METHODS, - "events": GATEWAY_EVENTS, - } +) -> GatewayCommandsResponse: + return GatewayCommandsResponse( + protocol_version=PROTOCOL_VERSION, + methods=GATEWAY_METHODS, + events=GATEWAY_EVENTS, + ) diff --git a/backend/app/api/gateways.py b/backend/app/api/gateways.py index 0ac8d8e1..2918b65c 100644 --- a/backend/app/api/gateways.py +++ b/backend/app/api/gateways.py @@ -4,15 +4,18 @@ from datetime import datetime from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status -from sqlmodel import Session, select +from sqlmodel import select +from sqlmodel.ext.asyncio.session import AsyncSession from app.core.agent_tokens import generate_agent_token, hash_agent_token from app.core.auth import AuthContext, get_auth_context +from app.core.time import utcnow from app.db.session import get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.agents import Agent from app.models.gateways import Gateway +from app.schemas.common import OkResponse from app.schemas.gateways import GatewayCreate, GatewayRead, GatewayUpdate from app.services.agent_provisioning import DEFAULT_HEARTBEAT_CONFIG, provision_main_agent @@ -235,21 +238,25 @@ def _main_agent_name(gateway: Gateway) -> str: return f"{gateway.name} Main" -def _find_main_agent( - session: Session, +async def _find_main_agent( + session: AsyncSession, gateway: Gateway, previous_name: str | None = None, previous_session_key: str | None = None, ) -> Agent | None: if gateway.main_session_key: - agent = session.exec( - select(Agent).where(Agent.openclaw_session_id == gateway.main_session_key) + agent = ( + await session.exec( + select(Agent).where(Agent.openclaw_session_id == gateway.main_session_key) + ) ).first() if agent: return agent if previous_session_key: - agent = session.exec( - select(Agent).where(Agent.openclaw_session_id == previous_session_key) + agent = ( + await session.exec( + select(Agent).where(Agent.openclaw_session_id == previous_session_key) + ) ).first() if agent: return agent @@ -257,14 +264,14 @@ def _find_main_agent( if previous_name: names.add(f"{previous_name} Main") for name in names: - agent = session.exec(select(Agent).where(Agent.name == name)).first() + agent = (await session.exec(select(Agent).where(Agent.name == name))).first() if agent: return agent return None async def _ensure_main_agent( - session: Session, + session: AsyncSession, gateway: Gateway, auth: AuthContext, *, @@ -274,7 +281,7 @@ async def _ensure_main_agent( ) -> Agent | None: if not gateway.url or not gateway.main_session_key: return None - agent = _find_main_agent(session, gateway, previous_name, previous_session_key) + agent = await _find_main_agent(session, gateway, previous_name, previous_session_key) if agent is None: agent = Agent( name=_main_agent_name(gateway), @@ -294,14 +301,14 @@ async def _ensure_main_agent( agent.openclaw_session_id = gateway.main_session_key raw_token = generate_agent_token() agent.agent_token_hash = hash_agent_token(raw_token) - agent.provision_requested_at = datetime.utcnow() + agent.provision_requested_at = utcnow() agent.provision_action = action - agent.updated_at = datetime.utcnow() + agent.updated_at = utcnow() if agent.heartbeat_config is None: agent.heartbeat_config = DEFAULT_HEARTBEAT_CONFIG.copy() session.add(agent) - session.commit() - session.refresh(agent) + await session.commit() + await session.refresh(agent) try: await provision_main_agent(agent, gateway, raw_token, auth.user, action=action) await ensure_session( @@ -356,26 +363,24 @@ async def _send_skyll_disable_message(gateway: Gateway) -> None: @router.get("", response_model=list[GatewayRead]) -def list_gateways( - session: Session = Depends(get_session), +async def list_gateways( + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), ) -> list[Gateway]: - return list(session.exec(select(Gateway))) + return list(await session.exec(select(Gateway))) @router.post("", response_model=GatewayRead) async def create_gateway( payload: GatewayCreate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), ) -> Gateway: data = payload.model_dump() - if data.get("token") == "": - data["token"] = None gateway = Gateway.model_validate(data) session.add(gateway) - session.commit() - session.refresh(gateway) + await session.commit() + await session.refresh(gateway) await _ensure_main_agent(session, gateway, auth, action="provision") if gateway.skyll_enabled: try: @@ -386,12 +391,12 @@ async def create_gateway( @router.get("/{gateway_id}", response_model=GatewayRead) -def get_gateway( +async def get_gateway( gateway_id: UUID, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), ) -> Gateway: - gateway = session.get(Gateway, gateway_id) + gateway = await session.get(Gateway, gateway_id) if gateway is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Gateway not found") return gateway @@ -401,23 +406,21 @@ def get_gateway( async def update_gateway( gateway_id: UUID, payload: GatewayUpdate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), ) -> Gateway: - gateway = session.get(Gateway, gateway_id) + gateway = await session.get(Gateway, gateway_id) if gateway is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Gateway not found") previous_name = gateway.name previous_session_key = gateway.main_session_key previous_skyll_enabled = gateway.skyll_enabled updates = payload.model_dump(exclude_unset=True) - if updates.get("token") == "": - updates["token"] = None for key, value in updates.items(): setattr(gateway, key, value) session.add(gateway) - session.commit() - session.refresh(gateway) + await session.commit() + await session.refresh(gateway) await _ensure_main_agent( session, gateway, @@ -439,15 +442,15 @@ async def update_gateway( return gateway -@router.delete("/{gateway_id}") -def delete_gateway( +@router.delete("/{gateway_id}", response_model=OkResponse) +async def delete_gateway( gateway_id: UUID, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), -) -> dict[str, bool]: - gateway = session.get(Gateway, gateway_id) +) -> OkResponse: + gateway = await session.get(Gateway, gateway_id) if gateway is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Gateway not found") - session.delete(gateway) - session.commit() - return {"ok": True} + await session.delete(gateway) + await session.commit() + return OkResponse() diff --git a/backend/app/api/metrics.py b/backend/app/api/metrics.py index 591c7059..916ed0d6 100644 --- a/backend/app/api/metrics.py +++ b/backend/app/api/metrics.py @@ -6,10 +6,12 @@ from typing import Literal from fastapi import APIRouter, Depends, Query from sqlalchemy import DateTime, case, cast, func -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from app.api.deps import require_admin_auth from app.core.auth import AuthContext +from app.core.time import utcnow from app.db.session import get_session from app.models.activity_events import ActivityEvent from app.models.agents import Agent @@ -40,7 +42,7 @@ class RangeSpec: def _resolve_range(range_key: Literal["24h", "7d"]) -> RangeSpec: - now = datetime.utcnow() + now = utcnow() if range_key == "7d": return RangeSpec( key="7d", @@ -111,7 +113,7 @@ def _wip_series_from_mapping( ) -def _query_throughput(session: Session, range_spec: RangeSpec) -> DashboardRangeSeries: +async def _query_throughput(session: AsyncSession, range_spec: RangeSpec) -> DashboardRangeSeries: bucket_col = func.date_trunc(range_spec.bucket, Task.updated_at).label("bucket") statement = ( select(bucket_col, func.count()) @@ -121,12 +123,12 @@ def _query_throughput(session: Session, range_spec: RangeSpec) -> DashboardRange .group_by(bucket_col) .order_by(bucket_col) ) - results = session.exec(statement).all() + results = (await session.exec(statement)).all() mapping = {row[0]: float(row[1]) for row in results} return _series_from_mapping(range_spec, mapping) -def _query_cycle_time(session: Session, range_spec: RangeSpec) -> DashboardRangeSeries: +async def _query_cycle_time(session: AsyncSession, range_spec: RangeSpec) -> DashboardRangeSeries: bucket_col = func.date_trunc(range_spec.bucket, Task.updated_at).label("bucket") in_progress = cast(Task.in_progress_at, DateTime) duration_hours = func.extract("epoch", Task.updated_at - in_progress) / 3600.0 @@ -139,12 +141,12 @@ def _query_cycle_time(session: Session, range_spec: RangeSpec) -> DashboardRange .group_by(bucket_col) .order_by(bucket_col) ) - results = session.exec(statement).all() + results = (await session.exec(statement)).all() mapping = {row[0]: float(row[1] or 0) for row in results} return _series_from_mapping(range_spec, mapping) -def _query_error_rate(session: Session, range_spec: RangeSpec) -> DashboardRangeSeries: +async def _query_error_rate(session: AsyncSession, range_spec: RangeSpec) -> DashboardRangeSeries: bucket_col = func.date_trunc(range_spec.bucket, ActivityEvent.created_at).label("bucket") error_case = case( ( @@ -160,7 +162,7 @@ def _query_error_rate(session: Session, range_spec: RangeSpec) -> DashboardRange .group_by(bucket_col) .order_by(bucket_col) ) - results = session.exec(statement).all() + results = (await session.exec(statement)).all() mapping: dict[datetime, float] = {} for bucket, errors, total in results: total_count = float(total or 0) @@ -170,7 +172,7 @@ def _query_error_rate(session: Session, range_spec: RangeSpec) -> DashboardRange return _series_from_mapping(range_spec, mapping) -def _query_wip(session: Session, range_spec: RangeSpec) -> DashboardWipRangeSeries: +async def _query_wip(session: AsyncSession, range_spec: RangeSpec) -> DashboardWipRangeSeries: bucket_col = func.date_trunc(range_spec.bucket, Task.updated_at).label("bucket") inbox_case = case((col(Task.status) == "inbox", 1), else_=0) progress_case = case((col(Task.status) == "in_progress", 1), else_=0) @@ -187,7 +189,7 @@ def _query_wip(session: Session, range_spec: RangeSpec) -> DashboardWipRangeSeri .group_by(bucket_col) .order_by(bucket_col) ) - results = session.exec(statement).all() + results = (await session.exec(statement)).all() mapping: dict[datetime, dict[str, int]] = {} for bucket, inbox, in_progress, review in results: mapping[bucket] = { @@ -198,8 +200,8 @@ def _query_wip(session: Session, range_spec: RangeSpec) -> DashboardWipRangeSeri return _wip_series_from_mapping(range_spec, mapping) -def _median_cycle_time_7d(session: Session) -> float | None: - now = datetime.utcnow() +async def _median_cycle_time_7d(session: AsyncSession) -> float | None: + now = utcnow() start = now - timedelta(days=7) in_progress = cast(Task.in_progress_at, DateTime) duration_hours = func.extract("epoch", Task.updated_at - in_progress) / 3600.0 @@ -210,7 +212,7 @@ def _median_cycle_time_7d(session: Session) -> float | None: .where(col(Task.updated_at) >= start) .where(col(Task.updated_at) <= now) ) - value = session.exec(statement).one_or_none() + value = (await session.exec(statement)).one_or_none() if value is None: return None if isinstance(value, tuple): @@ -220,7 +222,7 @@ def _median_cycle_time_7d(session: Session) -> float | None: return float(value) -def _error_rate_kpi(session: Session, range_spec: RangeSpec) -> float: +async def _error_rate_kpi(session: AsyncSession, range_spec: RangeSpec) -> float: error_case = case( ( col(ActivityEvent.event_type).like(ERROR_EVENT_PATTERN), @@ -233,7 +235,7 @@ def _error_rate_kpi(session: Session, range_spec: RangeSpec) -> float: .where(col(ActivityEvent.created_at) >= range_spec.start) .where(col(ActivityEvent.created_at) <= range_spec.end) ) - result = session.exec(statement).one_or_none() + result = (await session.exec(statement)).one_or_none() if result is None: return 0.0 errors, total = result @@ -242,58 +244,66 @@ def _error_rate_kpi(session: Session, range_spec: RangeSpec) -> float: return (error_count / total_count) * 100 if total_count > 0 else 0.0 -def _active_agents(session: Session) -> int: - threshold = datetime.utcnow() - OFFLINE_AFTER +async def _active_agents(session: AsyncSession) -> int: + threshold = utcnow() - OFFLINE_AFTER statement = select(func.count()).where( col(Agent.last_seen_at).is_not(None), col(Agent.last_seen_at) >= threshold, ) - result = session.exec(statement).one() + result = (await session.exec(statement)).one() return int(result) -def _tasks_in_progress(session: Session) -> int: +async def _tasks_in_progress(session: AsyncSession) -> int: statement = select(func.count()).where(col(Task.status) == "in_progress") - result = session.exec(statement).one() + result = (await session.exec(statement)).one() return int(result) @router.get("/dashboard", response_model=DashboardMetrics) -def dashboard_metrics( +async def dashboard_metrics( range: Literal["24h", "7d"] = Query(default="24h"), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> DashboardMetrics: primary = _resolve_range(range) comparison = _comparison_range(range) + throughput_primary = await _query_throughput(session, primary) + throughput_comparison = await _query_throughput(session, comparison) throughput = DashboardSeriesSet( - primary=_query_throughput(session, primary), - comparison=_query_throughput(session, comparison), + primary=throughput_primary, + comparison=throughput_comparison, ) + cycle_time_primary = await _query_cycle_time(session, primary) + cycle_time_comparison = await _query_cycle_time(session, comparison) cycle_time = DashboardSeriesSet( - primary=_query_cycle_time(session, primary), - comparison=_query_cycle_time(session, comparison), + primary=cycle_time_primary, + comparison=cycle_time_comparison, ) + error_rate_primary = await _query_error_rate(session, primary) + error_rate_comparison = await _query_error_rate(session, comparison) error_rate = DashboardSeriesSet( - primary=_query_error_rate(session, primary), - comparison=_query_error_rate(session, comparison), + primary=error_rate_primary, + comparison=error_rate_comparison, ) + wip_primary = await _query_wip(session, primary) + wip_comparison = await _query_wip(session, comparison) wip = DashboardWipSeriesSet( - primary=_query_wip(session, primary), - comparison=_query_wip(session, comparison), + primary=wip_primary, + comparison=wip_comparison, ) kpis = DashboardKpis( - active_agents=_active_agents(session), - tasks_in_progress=_tasks_in_progress(session), - error_rate_pct=_error_rate_kpi(session, primary), - median_cycle_time_hours_7d=_median_cycle_time_7d(session), + active_agents=await _active_agents(session), + tasks_in_progress=await _tasks_in_progress(session), + error_rate_pct=await _error_rate_kpi(session, primary), + median_cycle_time_hours_7d=await _median_cycle_time_7d(session), ) return DashboardMetrics( range=primary.key, - generated_at=datetime.utcnow(), + generated_at=utcnow(), kpis=kpis, throughput=throughput, cycle_time=cycle_time, diff --git a/backend/app/api/tasks.py b/backend/app/api/tasks.py index 94e349e2..9a09a3b4 100644 --- a/backend/app/api/tasks.py +++ b/backend/app/api/tasks.py @@ -4,14 +4,17 @@ import asyncio import json import re from collections import deque +from collections.abc import AsyncIterator from datetime import datetime, timezone +from typing import cast from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from sqlalchemy import asc, delete, desc -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.sql.expression import Select +from sqlmodel.ext.asyncio.session import AsyncSession from sse_starlette.sse import EventSourceResponse -from starlette.concurrency import run_in_threadpool from app.api.deps import ( ActorContext, @@ -21,7 +24,8 @@ from app.api.deps import ( require_admin_or_agent, ) from app.core.auth import AuthContext -from app.db.session import engine, get_session +from app.core.time import utcnow +from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.activity_events import ActivityEvent @@ -30,6 +34,7 @@ from app.models.boards import Board from app.models.gateways import Gateway from app.models.task_fingerprints import TaskFingerprint from app.models.tasks import Task +from app.schemas.common import OkResponse from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate from app.services.activity_log import record_activity @@ -46,14 +51,6 @@ SSE_SEEN_MAX = 2000 MENTION_PATTERN = re.compile(r"@([A-Za-z][\w-]{0,31})") -def validate_task_status(status_value: str) -> None: - if status_value not in ALLOWED_STATUSES: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Unsupported task status.", - ) - - def _comment_validation_error() -> HTTPException: return HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -61,8 +58,8 @@ def _comment_validation_error() -> HTTPException: ) -def has_valid_recent_comment( - session: Session, +async def has_valid_recent_comment( + session: AsyncSession, task: Task, agent_id: UUID | None, since: datetime | None, @@ -77,7 +74,7 @@ def has_valid_recent_comment( .where(col(ActivityEvent.created_at) >= since) .order_by(desc(col(ActivityEvent.created_at))) ) - event = session.exec(statement).first() + event = (await session.exec(statement)).first() if event is None or event.message is None: return False return bool(event.message.strip()) @@ -116,8 +113,8 @@ def _matches_mention(agent: Agent, mentions: set[str]) -> bool: return first in mentions -def _lead_was_mentioned( - session: Session, +async def _lead_was_mentioned( + session: AsyncSession, task: Task, lead: Agent, ) -> bool: @@ -127,7 +124,7 @@ def _lead_was_mentioned( .where(col(ActivityEvent.event_type) == "task.comment") .order_by(desc(col(ActivityEvent.created_at))) ) - for message in session.exec(statement): + for message in await session.exec(statement): if not message: continue mentions = _extract_mentions(message) @@ -142,23 +139,24 @@ def _lead_created_task(task: Task, lead: Agent) -> bool: return task.auto_reason == f"lead_agent:{lead.id}" -def _fetch_task_events( +async def _fetch_task_events( + session: AsyncSession, board_id: UUID, since: datetime, ) -> list[tuple[ActivityEvent, Task | None]]: - with Session(engine) as session: - task_ids = list(session.exec(select(Task.id).where(col(Task.board_id) == board_id))) - if not task_ids: - return [] - statement = ( - select(ActivityEvent, Task) - .outerjoin(Task, ActivityEvent.task_id == Task.id) - .where(col(ActivityEvent.task_id).in_(task_ids)) - .where(col(ActivityEvent.event_type).in_(TASK_EVENT_TYPES)) - .where(col(ActivityEvent.created_at) >= since) - .order_by(asc(col(ActivityEvent.created_at))) - ) - return list(session.exec(statement)) + task_ids = list(await session.exec(select(Task.id).where(col(Task.board_id) == board_id))) + if not task_ids: + return [] + statement = cast( + Select[tuple[ActivityEvent, Task | None]], + select(ActivityEvent, Task) + .outerjoin(Task, col(ActivityEvent.task_id) == col(Task.id)) + .where(col(ActivityEvent.task_id).in_(task_ids)) + .where(col(ActivityEvent.event_type).in_(TASK_EVENT_TYPES)) + .where(col(ActivityEvent.created_at) >= since) + .order_by(asc(col(ActivityEvent.created_at))), + ) + return list(await session.exec(statement)) def _serialize_task(task: Task | None) -> dict[str, object] | None: @@ -171,10 +169,10 @@ def _serialize_comment(event: ActivityEvent) -> dict[str, object]: return TaskCommentRead.model_validate(event).model_dump(mode="json") -def _gateway_config(session: Session, board: Board) -> GatewayClientConfig | None: +async def _gateway_config(session: AsyncSession, board: Board) -> GatewayClientConfig | None: if not board.gateway_id: return None - gateway = session.get(Gateway, board.gateway_id) + gateway = await session.get(Gateway, board.gateway_id) if gateway is None or not gateway.url: return None return GatewayClientConfig(url=gateway.url, token=gateway.token) @@ -201,16 +199,16 @@ async def _send_agent_task_message( await send_message(message, session_key=session_key, config=config, deliver=False) -def _notify_agent_on_task_assign( +async def _notify_agent_on_task_assign( *, - session: Session, + session: AsyncSession, board: Board, task: Task, agent: Agent, ) -> None: if not agent.openclaw_session_id: return - config = _gateway_config(session, board) + config = await _gateway_config(session, board) if config is None: return description = (task.description or "").strip() @@ -230,13 +228,11 @@ def _notify_agent_on_task_assign( + "\n\nTake action: open the task and begin work. Post updates as task comments." ) try: - asyncio.run( - _send_agent_task_message( - session_key=agent.openclaw_session_id, - config=config, - agent_name=agent.name, - message=message, - ) + await _send_agent_task_message( + session_key=agent.openclaw_session_id, + config=config, + agent_name=agent.name, + message=message, ) record_activity( session, @@ -245,7 +241,7 @@ def _notify_agent_on_task_assign( agent_id=agent.id, task_id=task.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: record_activity( session, @@ -254,21 +250,25 @@ def _notify_agent_on_task_assign( agent_id=agent.id, task_id=task.id, ) - session.commit() + await session.commit() -def _notify_lead_on_task_create( +async def _notify_lead_on_task_create( *, - session: Session, + session: AsyncSession, board: Board, task: Task, ) -> None: - lead = session.exec( - select(Agent).where(Agent.board_id == board.id).where(Agent.is_board_lead.is_(True)) + lead = ( + await session.exec( + select(Agent) + .where(Agent.board_id == board.id) + .where(col(Agent.is_board_lead).is_(True)) + ) ).first() if lead is None or not lead.openclaw_session_id: return - config = _gateway_config(session, board) + config = await _gateway_config(session, board) if config is None: return description = (task.description or "").strip() @@ -288,12 +288,10 @@ def _notify_lead_on_task_create( + "\n\nTake action: triage, assign, or plan next steps." ) try: - asyncio.run( - _send_lead_task_message( - session_key=lead.openclaw_session_id, - config=config, - message=message, - ) + await _send_lead_task_message( + session_key=lead.openclaw_session_id, + config=config, + message=message, ) record_activity( session, @@ -302,7 +300,7 @@ def _notify_lead_on_task_create( agent_id=lead.id, task_id=task.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: record_activity( session, @@ -311,21 +309,25 @@ def _notify_lead_on_task_create( agent_id=lead.id, task_id=task.id, ) - session.commit() + await session.commit() -def _notify_lead_on_task_unassigned( +async def _notify_lead_on_task_unassigned( *, - session: Session, + session: AsyncSession, board: Board, task: Task, ) -> None: - lead = session.exec( - select(Agent).where(Agent.board_id == board.id).where(Agent.is_board_lead.is_(True)) + lead = ( + await session.exec( + select(Agent) + .where(Agent.board_id == board.id) + .where(col(Agent.is_board_lead).is_(True)) + ) ).first() if lead is None or not lead.openclaw_session_id: return - config = _gateway_config(session, board) + config = await _gateway_config(session, board) if config is None: return description = (task.description or "").strip() @@ -345,12 +347,10 @@ def _notify_lead_on_task_unassigned( + "\n\nTake action: assign a new owner or adjust the plan." ) try: - asyncio.run( - _send_lead_task_message( - session_key=lead.openclaw_session_id, - config=config, - message=message, - ) + await _send_lead_task_message( + session_key=lead.openclaw_session_id, + config=config, + message=message, ) record_activity( session, @@ -359,7 +359,7 @@ def _notify_lead_on_task_unassigned( agent_id=lead.id, task_id=task.id, ) - session.commit() + await session.commit() except OpenClawGatewayError as exc: record_activity( session, @@ -368,7 +368,7 @@ def _notify_lead_on_task_unassigned( agent_id=lead.id, task_id=task.id, ) - session.commit() + await session.commit() @router.get("/stream") @@ -378,16 +378,17 @@ async def stream_tasks( actor: ActorContext = Depends(require_admin_or_agent), since: str | None = Query(default=None), ) -> EventSourceResponse: - since_dt = _parse_since(since) or datetime.utcnow() + since_dt = _parse_since(since) or utcnow() seen_ids: set[UUID] = set() seen_queue: deque[UUID] = deque() - async def event_generator(): + async def event_generator() -> AsyncIterator[dict[str, str]]: last_seen = since_dt while True: if await request.is_disconnected(): break - rows = await run_in_threadpool(_fetch_task_events, board.id, last_seen) + async with async_session_maker() as session: + rows = await _fetch_task_events(session, board.id, last_seen) for event, task in rows: if event.id in seen_ids: continue @@ -410,13 +411,13 @@ async def stream_tasks( @router.get("", response_model=list[TaskRead]) -def list_tasks( +async def list_tasks( status_filter: str | None = Query(default=None, alias="status"), assigned_agent_id: UUID | None = None, unassigned: bool | None = None, limit: int | None = Query(default=None, ge=1, le=200), board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> list[Task]: statement = select(Task).where(Task.board_id == board.id) @@ -435,24 +436,23 @@ def list_tasks( statement = statement.where(col(Task.assigned_agent_id).is_(None)) if limit is not None: statement = statement.limit(limit) - return list(session.exec(statement)) + return list(await session.exec(statement)) @router.post("", response_model=TaskRead) -def create_task( +async def create_task( payload: TaskCreate, board: Board = Depends(get_board_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(require_admin_auth), ) -> Task: - validate_task_status(payload.status) task = Task.model_validate(payload) task.board_id = board.id if task.created_by_user_id is None and auth.user is not None: task.created_by_user_id = auth.user.id session.add(task) - session.commit() - session.refresh(task) + await session.commit() + await session.refresh(task) record_activity( session, @@ -460,12 +460,12 @@ def create_task( task_id=task.id, message=f"Task created: {task.title}.", ) - session.commit() - _notify_lead_on_task_create(session=session, board=board, task=task) + await session.commit() + await _notify_lead_on_task_create(session=session, board=board, task=task) if task.assigned_agent_id: - assigned_agent = session.get(Agent, task.assigned_agent_id) + assigned_agent = await session.get(Agent, task.assigned_agent_id) if assigned_agent: - _notify_agent_on_task_assign( + await _notify_agent_on_task_assign( session=session, board=board, task=task, @@ -475,18 +475,16 @@ def create_task( @router.patch("/{task_id}", response_model=TaskRead) -def update_task( +async def update_task( payload: TaskUpdate, task: Task = Depends(get_task_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> Task: previous_status = task.status previous_assigned = task.assigned_agent_id updates = payload.model_dump(exclude_unset=True) comment = updates.pop("comment", None) - if comment is not None and not comment.strip(): - comment = None if actor.actor_type == "agent" and actor.agent and actor.agent.is_board_lead: allowed_fields = {"assigned_agent_id", "status"} @@ -498,7 +496,7 @@ def update_task( if "assigned_agent_id" in updates: assigned_id = updates["assigned_agent_id"] if assigned_id: - agent = session.get(Agent, assigned_id) + agent = await session.get(Agent, assigned_id) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) if agent.is_board_lead: @@ -512,7 +510,6 @@ def update_task( else: task.assigned_agent_id = None if "status" in updates: - validate_task_status(updates["status"]) if task.status != "review": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -526,8 +523,8 @@ def update_task( if updates["status"] == "inbox": task.assigned_agent_id = None task.in_progress_at = None - task.status = updates["status"] - task.updated_at = datetime.utcnow() + task.status = updates["status"] + task.updated_at = utcnow() session.add(task) if task.status != previous_status: event_type = "task.status_changed" @@ -542,17 +539,17 @@ def update_task( message=message, agent_id=actor.agent.id, ) - session.commit() - session.refresh(task) + await session.commit() + await session.refresh(task) if task.assigned_agent_id and task.assigned_agent_id != previous_assigned: if actor.actor_type == "agent" and actor.agent and task.assigned_agent_id == actor.agent.id: return task - assigned_agent = session.get(Agent, task.assigned_agent_id) + assigned_agent = await session.get(Agent, task.assigned_agent_id) if assigned_agent: - board = session.get(Board, task.board_id) if task.board_id else None + board = await session.get(Board, task.board_id) if task.board_id else None if board: - _notify_agent_on_task_assign( + await _notify_agent_on_task_assign( session=session, board=board, task=task, @@ -567,37 +564,35 @@ def update_task( if not set(updates).issubset(allowed_fields): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) if "status" in updates: - validate_task_status(updates["status"]) if updates["status"] == "inbox": task.assigned_agent_id = None task.in_progress_at = None else: task.assigned_agent_id = actor.agent.id if actor.agent else None if updates["status"] == "in_progress": - task.in_progress_at = datetime.utcnow() + task.in_progress_at = utcnow() elif "status" in updates: - validate_task_status(updates["status"]) if updates["status"] == "inbox": task.assigned_agent_id = None task.in_progress_at = None elif updates["status"] == "in_progress": - task.in_progress_at = datetime.utcnow() + task.in_progress_at = utcnow() if "assigned_agent_id" in updates and updates["assigned_agent_id"]: - agent = session.get(Agent, updates["assigned_agent_id"]) + agent = await session.get(Agent, updates["assigned_agent_id"]) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) if agent.board_id and task.board_id and agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_409_CONFLICT) for key, value in updates.items(): setattr(task, key, value) - task.updated_at = datetime.utcnow() + task.updated_at = utcnow() if "status" in updates and updates["status"] == "review": if comment is not None and comment.strip(): if not comment.strip(): raise _comment_validation_error() else: - if not has_valid_recent_comment( + if not await has_valid_recent_comment( session, task, task.assigned_agent_id, @@ -606,8 +601,8 @@ def update_task( raise _comment_validation_error() session.add(task) - session.commit() - session.refresh(task) + await session.commit() + await session.refresh(task) if comment is not None and comment.strip(): event = ActivityEvent( @@ -617,7 +612,7 @@ def update_task( agent_id=actor.agent.id if actor.actor_type == "agent" and actor.agent else None, ) session.add(event) - session.commit() + await session.commit() if "status" in updates and task.status != previous_status: event_type = "task.status_changed" @@ -632,12 +627,12 @@ def update_task( message=message, agent_id=actor.agent.id if actor.actor_type == "agent" and actor.agent else None, ) - session.commit() + await session.commit() if task.status == "inbox" and task.assigned_agent_id is None: if previous_status != "inbox" or previous_assigned is not None: - board = session.get(Board, task.board_id) if task.board_id else None + board = await session.get(Board, task.board_id) if task.board_id else None if board: - _notify_lead_on_task_unassigned( + await _notify_lead_on_task_unassigned( session=session, board=board, task=task, @@ -645,11 +640,11 @@ def update_task( if task.assigned_agent_id and task.assigned_agent_id != previous_assigned: if actor.actor_type == "agent" and actor.agent and task.assigned_agent_id == actor.agent.id: return task - assigned_agent = session.get(Agent, task.assigned_agent_id) + assigned_agent = await session.get(Agent, task.assigned_agent_id) if assigned_agent: - board = session.get(Board, task.board_id) if task.board_id else None + board = await session.get(Board, task.board_id) if task.board_id else None if board: - _notify_agent_on_task_assign( + await _notify_agent_on_task_assign( session=session, board=board, task=task, @@ -658,23 +653,23 @@ def update_task( return task -@router.delete("/{task_id}") -def delete_task( - session: Session = Depends(get_session), +@router.delete("/{task_id}", response_model=OkResponse) +async def delete_task( + session: AsyncSession = Depends(get_session), task: Task = Depends(get_task_or_404), auth: AuthContext = Depends(require_admin_auth), -) -> dict[str, bool]: - session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id)) - session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id)) - session.delete(task) - session.commit() - return {"ok": True} +) -> OkResponse: + await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id)) + await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id)) + await session.delete(task) + await session.commit() + return OkResponse() @router.get("/{task_id}/comments", response_model=list[TaskCommentRead]) -def list_task_comments( +async def list_task_comments( task: Task = Depends(get_task_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> list[ActivityEvent]: if actor.actor_type == "agent" and actor.agent: @@ -686,19 +681,19 @@ def list_task_comments( .where(col(ActivityEvent.event_type) == "task.comment") .order_by(asc(col(ActivityEvent.created_at))) ) - return list(session.exec(statement)) + return list(await session.exec(statement)) @router.post("/{task_id}/comments", response_model=TaskCommentRead) -def create_task_comment( +async def create_task_comment( payload: TaskCommentCreate, task: Task = Depends(get_task_or_404), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), actor: ActorContext = Depends(require_admin_or_agent), ) -> ActivityEvent: if actor.actor_type == "agent" and actor.agent: if actor.agent.is_board_lead and task.status != "review": - if not _lead_was_mentioned(session, task, actor.agent) and not _lead_created_task( + if not await _lead_was_mentioned(session, task, actor.agent) and not _lead_created_task( task, actor.agent ): raise HTTPException( @@ -709,8 +704,6 @@ def create_task_comment( ) if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - if not payload.message.strip(): - raise _comment_validation_error() event = ActivityEvent( event_type="task.comment", message=payload.message, @@ -718,24 +711,24 @@ def create_task_comment( agent_id=actor.agent.id if actor.actor_type == "agent" and actor.agent else None, ) session.add(event) - session.commit() - session.refresh(event) + await session.commit() + await session.refresh(event) mention_names = _extract_mentions(payload.message) targets: dict[UUID, Agent] = {} if mention_names and task.board_id: statement = select(Agent).where(col(Agent.board_id) == task.board_id) - for agent in session.exec(statement): + for agent in await session.exec(statement): if _matches_mention(agent, mention_names): targets[agent.id] = agent if not mention_names and task.assigned_agent_id: - assigned_agent = session.get(Agent, task.assigned_agent_id) + assigned_agent = await session.get(Agent, task.assigned_agent_id) if assigned_agent: targets[assigned_agent.id] = assigned_agent if actor.actor_type == "agent" and actor.agent: targets.pop(actor.agent.id, None) if targets: - board = session.get(Board, task.board_id) if task.board_id else None - config = _gateway_config(session, board) if board else None + board = await session.get(Board, task.board_id) if task.board_id else None + config = await _gateway_config(session, board) if board else None if board and config: snippet = payload.message.strip() if len(snippet) > 500: @@ -762,13 +755,11 @@ def create_task_comment( "If you are mentioned but not assigned, reply in the task thread but do not change task status." ) try: - asyncio.run( - _send_agent_task_message( - session_key=agent.openclaw_session_id, - config=config, - agent_name=agent.name, - message=message, - ) + await _send_agent_task_message( + session_key=agent.openclaw_session_id, + config=config, + agent_name=agent.name, + message=message, ) except OpenClawGatewayError: pass diff --git a/backend/app/api/users.py b/backend/app/api/users.py index 99fce48c..7d353ec8 100644 --- a/backend/app/api/users.py +++ b/backend/app/api/users.py @@ -1,7 +1,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, status -from sqlmodel import Session +from sqlmodel.ext.asyncio.session import AsyncSession from app.core.auth import AuthContext, get_auth_context from app.db.session import get_session @@ -21,7 +21,7 @@ async def get_me(auth: AuthContext = Depends(get_auth_context)) -> UserRead: @router.patch("/me", response_model=UserRead) async def update_me( payload: UserUpdate, - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), auth: AuthContext = Depends(get_auth_context), ) -> UserRead: if auth.actor_type != "user" or auth.user is None: @@ -31,6 +31,6 @@ async def update_me( for key, value in updates.items(): setattr(user, key, value) session.add(user) - session.commit() - session.refresh(user) + await session.commit() + await session.refresh(user) return UserRead.model_validate(user) diff --git a/backend/app/core/agent_auth.py b/backend/app/core/agent_auth.py index 4a676c26..b49bf181 100644 --- a/backend/app/core/agent_auth.py +++ b/backend/app/core/agent_auth.py @@ -5,7 +5,8 @@ from dataclasses import dataclass from typing import Literal from fastapi import Depends, Header, HTTPException, Request, status -from sqlmodel import Session, col, select +from sqlmodel import col, select +from sqlmodel.ext.asyncio.session import AsyncSession from app.core.agent_tokens import verify_agent_token from app.db.session import get_session @@ -20,8 +21,10 @@ class AgentAuthContext: agent: Agent -def _find_agent_for_token(session: Session, token: str) -> Agent | None: - agents = list(session.exec(select(Agent).where(col(Agent.agent_token_hash).is_not(None)))) +async def _find_agent_for_token(session: AsyncSession, token: str) -> Agent | None: + agents = list( + await session.exec(select(Agent).where(col(Agent.agent_token_hash).is_not(None))) + ) for agent in agents: if agent.agent_token_hash and verify_agent_token(token, agent.agent_token_hash): return agent @@ -48,11 +51,11 @@ def _resolve_agent_token( return None -def get_agent_auth_context( +async def get_agent_auth_context( request: Request, agent_token: str | None = Header(default=None, alias="X-Agent-Token"), authorization: str | None = Header(default=None, alias="Authorization"), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), ) -> AgentAuthContext: resolved = _resolve_agent_token(agent_token, authorization, accept_authorization=True) if not resolved: @@ -63,7 +66,7 @@ def get_agent_auth_context( bool(authorization), ) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) - agent = _find_agent_for_token(session, resolved) + agent = await _find_agent_for_token(session, resolved) if agent is None: logger.warning( "agent auth invalid token path=%s token_prefix=%s", @@ -74,11 +77,11 @@ def get_agent_auth_context( return AgentAuthContext(actor_type="agent", agent=agent) -def get_agent_auth_context_optional( +async def get_agent_auth_context_optional( request: Request, agent_token: str | None = Header(default=None, alias="X-Agent-Token"), authorization: str | None = Header(default=None, alias="Authorization"), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), ) -> AgentAuthContext | None: resolved = _resolve_agent_token( agent_token, @@ -94,7 +97,7 @@ def get_agent_auth_context_optional( bool(authorization), ) return None - agent = _find_agent_for_token(session, resolved) + agent = await _find_agent_for_token(session, resolved) if agent is None: logger.warning( "agent auth optional invalid token path=%s token_prefix=%s", diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index 14fae05a..d30085f4 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -9,9 +9,10 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi_clerk_auth import ClerkConfig, ClerkHTTPBearer from fastapi_clerk_auth import HTTPAuthorizationCredentials as ClerkCredentials from pydantic import BaseModel, ValidationError -from sqlmodel import Session, select +from sqlmodel.ext.asyncio.session import AsyncSession from app.core.config import settings +from app.db import crud from app.db.session import get_session from app.models.users import User @@ -44,7 +45,9 @@ def _resolve_clerk_auth( request: Request, fallback: ClerkCredentials | None ) -> ClerkCredentials | None: auth_data = getattr(request.state, "clerk_auth", None) - return auth_data or fallback + if isinstance(auth_data, ClerkCredentials): + return auth_data + return fallback def _parse_subject(auth_data: ClerkCredentials | None) -> str | None: @@ -57,7 +60,7 @@ def _parse_subject(auth_data: ClerkCredentials | None) -> str | None: async def get_auth_context( request: Request, credentials: HTTPAuthorizationCredentials | None = Depends(security), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), ) -> AuthContext: if credentials is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) @@ -79,17 +82,21 @@ async def get_auth_context( if not clerk_user_id: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) - user = session.exec(select(User).where(User.clerk_user_id == clerk_user_id)).first() - if user is None: - claims = auth_data.decoded if auth_data and auth_data.decoded else {} - user = User( - clerk_user_id=clerk_user_id, - email=claims.get("email"), - name=claims.get("name"), - ) - session.add(user) - session.commit() - session.refresh(user) + claims: dict[str, object] = {} + if auth_data and auth_data.decoded: + claims = auth_data.decoded + email_obj = claims.get("email") + name_obj = claims.get("name") + defaults: dict[str, object | None] = { + "email": email_obj if isinstance(email_obj, str) else None, + "name": name_obj if isinstance(name_obj, str) else None, + } + user, _created = await crud.get_or_create( + session, + User, + clerk_user_id=clerk_user_id, + defaults=defaults, + ) return AuthContext( actor_type="user", @@ -100,7 +107,7 @@ async def get_auth_context( async def get_auth_context_optional( request: Request, credentials: HTTPAuthorizationCredentials | None = Depends(security), - session: Session = Depends(get_session), + session: AsyncSession = Depends(get_session), ) -> AuthContext | None: if request.headers.get("X-Agent-Token"): return None @@ -124,17 +131,21 @@ async def get_auth_context_optional( if not clerk_user_id: return None - user = session.exec(select(User).where(User.clerk_user_id == clerk_user_id)).first() - if user is None: - claims = auth_data.decoded if auth_data and auth_data.decoded else {} - user = User( - clerk_user_id=clerk_user_id, - email=claims.get("email"), - name=claims.get("name"), - ) - session.add(user) - session.commit() - session.refresh(user) + claims: dict[str, object] = {} + if auth_data and auth_data.decoded: + claims = auth_data.decoded + email_obj = claims.get("email") + name_obj = claims.get("name") + defaults: dict[str, object | None] = { + "email": email_obj if isinstance(email_obj, str) else None, + "name": name_obj if isinstance(name_obj, str) else None, + } + user, _created = await crud.get_or_create( + session, + User, + clerk_user_id=clerk_user_id, + defaults=defaults, + ) return AuthContext( actor_type="user", diff --git a/backend/app/core/logging.py b/backend/app/core/logging.py index 3b3e34ce..c9eb42fc 100644 --- a/backend/app/core/logging.py +++ b/backend/app/core/logging.py @@ -20,7 +20,7 @@ def _trace(self: logging.Logger, message: str, *args: Any, **kwargs: Any) -> Non self._log(TRACE_LEVEL, message, args, **kwargs) -logging.Logger.trace = _trace # type: ignore[attr-defined] +setattr(logging.Logger, "trace", _trace) _STANDARD_LOG_RECORD_ATTRS = { "args", diff --git a/backend/app/core/time.py b/backend/app/core/time.py new file mode 100644 index 00000000..969fa853 --- /dev/null +++ b/backend/app/core/time.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from datetime import UTC, datetime + + +def utcnow() -> datetime: + """Return a naive UTC datetime without using deprecated datetime.utcnow().""" + + # Keep naive UTC values for compatibility with existing DB schema/queries. + return datetime.now(UTC).replace(tzinfo=None) + diff --git a/backend/app/db/crud.py b/backend/app/db/crud.py new file mode 100644 index 00000000..176886b5 --- /dev/null +++ b/backend/app/db/crud.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from sqlalchemy.exc import IntegrityError +from sqlmodel import SQLModel, select +from sqlmodel.ext.asyncio.session import AsyncSession + +ModelT = TypeVar("ModelT", bound=SQLModel) + + +class DoesNotExist(LookupError): + pass + + +class MultipleObjectsReturned(LookupError): + pass + + +async def get_by_id(session: AsyncSession, model: type[ModelT], obj_id: Any) -> ModelT | None: + return await session.get(model, obj_id) + + +async def get(session: AsyncSession, model: type[ModelT], **lookup: Any) -> ModelT: + stmt = select(model) + for key, value in lookup.items(): + stmt = stmt.where(getattr(model, key) == value) + stmt = stmt.limit(2) + items = (await session.exec(stmt)).all() + if not items: + raise DoesNotExist(f"{model.__name__} matching query does not exist.") + if len(items) > 1: + raise MultipleObjectsReturned( + f"Multiple {model.__name__} objects returned for lookup {lookup!r}." + ) + return items[0] + + +async def get_one_by(session: AsyncSession, model: type[ModelT], **lookup: Any) -> ModelT | None: + stmt = select(model) + for key, value in lookup.items(): + stmt = stmt.where(getattr(model, key) == value) + return (await session.exec(stmt)).first() + + +async def create( + session: AsyncSession, + model: type[ModelT], + *, + commit: bool = True, + refresh: bool = True, + **data: Any, +) -> ModelT: + obj = model.model_validate(data) + session.add(obj) + await session.flush() + if commit: + await session.commit() + if refresh: + await session.refresh(obj) + return obj + + +async def save( + session: AsyncSession, + obj: ModelT, + *, + commit: bool = True, + refresh: bool = True, +) -> ModelT: + session.add(obj) + await session.flush() + if commit: + await session.commit() + if refresh: + await session.refresh(obj) + return obj + + +async def delete(session: AsyncSession, obj: ModelT, *, commit: bool = True) -> None: + await session.delete(obj) + if commit: + await session.commit() + + +async def get_or_create( + session: AsyncSession, + model: type[ModelT], + *, + defaults: Mapping[str, Any] | None = None, + commit: bool = True, + refresh: bool = True, + **lookup: Any, +) -> tuple[ModelT, bool]: + stmt = select(model) + for key, value in lookup.items(): + stmt = stmt.where(getattr(model, key) == value) + + existing = (await session.exec(stmt)).first() + if existing is not None: + return existing, False + + payload: dict[str, Any] = dict(lookup) + if defaults: + for key, value in defaults.items(): + payload.setdefault(key, value) + + obj = model.model_validate(payload) + session.add(obj) + try: + await session.flush() + if commit: + await session.commit() + except IntegrityError: + # If another concurrent request inserted the same unique row, surface that row. + await session.rollback() + existing = (await session.exec(stmt)).first() + if existing is not None: + return existing, False + raise + + if refresh: + await session.refresh(obj) + return obj, True diff --git a/backend/app/db/session.py b/backend/app/db/session.py index 17030b77..3842171c 100644 --- a/backend/app/db/session.py +++ b/backend/app/db/session.py @@ -1,17 +1,38 @@ from __future__ import annotations import logging -from collections.abc import Generator +from collections.abc import AsyncGenerator from pathlib import Path -from sqlmodel import Session, SQLModel, create_engine +import anyio +from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine +from sqlmodel import SQLModel +from sqlmodel.ext.asyncio.session import AsyncSession from alembic import command from alembic.config import Config from app import models # noqa: F401 from app.core.config import settings -engine = create_engine(settings.database_url, pool_pre_ping=True) + +def _normalize_database_url(database_url: str) -> str: + if "://" not in database_url: + return database_url + scheme, rest = database_url.split("://", 1) + if scheme == "postgresql": + return f"postgresql+psycopg://{rest}" + return database_url + + +async_engine: AsyncEngine = create_async_engine( + _normalize_database_url(settings.database_url), + pool_pre_ping=True, +) +async_session_maker = async_sessionmaker( + async_engine, + class_=AsyncSession, + expire_on_commit=False, +) logger = logging.getLogger(__name__) @@ -28,18 +49,19 @@ def run_migrations() -> None: logger.info("Database migrations complete.") -def init_db() -> None: +async def init_db() -> None: if settings.db_auto_migrate: versions_dir = Path(__file__).resolve().parents[2] / "alembic" / "versions" if any(versions_dir.glob("*.py")): logger.info("Running Alembic migrations on startup") - run_migrations() + await anyio.to_thread.run_sync(run_migrations) return logger.warning("No Alembic revisions found; falling back to create_all") - SQLModel.metadata.create_all(engine) + async with async_engine.begin() as conn: + await conn.run_sync(SQLModel.metadata.create_all) -def get_session() -> Generator[Session, None, None]: - with Session(engine) as session: +async def get_session() -> AsyncGenerator[AsyncSession, None]: + async with async_session_maker() as session: yield session diff --git a/backend/app/main.py b/backend/app/main.py index 025c19dc..6e205a94 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections.abc import AsyncIterator from contextlib import asynccontextmanager from fastapi import APIRouter, FastAPI @@ -26,8 +27,8 @@ configure_logging() @asynccontextmanager -async def lifespan(_: FastAPI): - init_db() +async def lifespan(_: FastAPI) -> AsyncIterator[None]: + await init_db() yield diff --git a/backend/app/models/activity_events.py b/backend/app/models/activity_events.py index 223fab1f..15f7c69e 100644 --- a/backend/app/models/activity_events.py +++ b/backend/app/models/activity_events.py @@ -5,6 +5,8 @@ from uuid import UUID, uuid4 from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class ActivityEvent(SQLModel, table=True): __tablename__ = "activity_events" @@ -14,4 +16,4 @@ class ActivityEvent(SQLModel, table=True): message: str | None = None agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True) task_id: UUID | None = Field(default=None, foreign_key="tasks.id", index=True) - created_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/agents.py b/backend/app/models/agents.py index d7d56427..cce8f829 100644 --- a/backend/app/models/agents.py +++ b/backend/app/models/agents.py @@ -7,6 +7,8 @@ from uuid import UUID, uuid4 from sqlalchemy import JSON, Column, Text from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class Agent(SQLModel, table=True): __tablename__ = "agents" @@ -28,5 +30,5 @@ class Agent(SQLModel, table=True): delete_confirm_token_hash: str | None = Field(default=None, index=True) last_seen_at: datetime | None = Field(default=None) is_board_lead: bool = Field(default=False, index=True) - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) + updated_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/approvals.py b/backend/app/models/approvals.py index 6fdf6622..17fec2b5 100644 --- a/backend/app/models/approvals.py +++ b/backend/app/models/approvals.py @@ -6,6 +6,8 @@ from uuid import UUID, uuid4 from sqlalchemy import JSON, Column from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class Approval(SQLModel, table=True): __tablename__ = "approvals" @@ -18,5 +20,5 @@ class Approval(SQLModel, table=True): confidence: int rubric_scores: dict[str, int] | None = Field(default=None, sa_column=Column(JSON)) status: str = Field(default="pending", index=True) - created_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) resolved_at: datetime | None = None diff --git a/backend/app/models/board_memory.py b/backend/app/models/board_memory.py index abf63866..0df77269 100644 --- a/backend/app/models/board_memory.py +++ b/backend/app/models/board_memory.py @@ -6,6 +6,8 @@ from uuid import UUID, uuid4 from sqlalchemy import JSON, Column from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class BoardMemory(SQLModel, table=True): __tablename__ = "board_memory" @@ -15,4 +17,4 @@ class BoardMemory(SQLModel, table=True): content: str tags: list[str] | None = Field(default=None, sa_column=Column(JSON)) source: str | None = None - created_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/board_onboarding.py b/backend/app/models/board_onboarding.py index 4c634287..b23d5991 100644 --- a/backend/app/models/board_onboarding.py +++ b/backend/app/models/board_onboarding.py @@ -6,6 +6,8 @@ from uuid import UUID, uuid4 from sqlalchemy import JSON, Column from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class BoardOnboardingSession(SQLModel, table=True): __tablename__ = "board_onboarding_sessions" @@ -16,5 +18,5 @@ class BoardOnboardingSession(SQLModel, table=True): status: str = Field(default="active", index=True) messages: list[dict[str, object]] | None = Field(default=None, sa_column=Column(JSON)) draft_goal: dict[str, object] | None = Field(default=None, sa_column=Column(JSON)) - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) + updated_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/boards.py b/backend/app/models/boards.py index a7f8b788..49707b32 100644 --- a/backend/app/models/boards.py +++ b/backend/app/models/boards.py @@ -6,6 +6,7 @@ from uuid import UUID, uuid4 from sqlalchemy import JSON, Column from sqlmodel import Field +from app.core.time import utcnow from app.models.tenancy import TenantScoped @@ -22,5 +23,5 @@ class Board(TenantScoped, table=True): target_date: datetime | None = None goal_confirmed: bool = Field(default=False) goal_source: str | None = None - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) + updated_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/gateways.py b/backend/app/models/gateways.py index cc85d5da..fedc43bc 100644 --- a/backend/app/models/gateways.py +++ b/backend/app/models/gateways.py @@ -5,6 +5,8 @@ from uuid import UUID, uuid4 from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class Gateway(SQLModel, table=True): __tablename__ = "gateways" @@ -16,5 +18,5 @@ class Gateway(SQLModel, table=True): main_session_key: str workspace_root: str skyll_enabled: bool = Field(default=False) - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) + updated_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/task_fingerprints.py b/backend/app/models/task_fingerprints.py index 3a1cc890..e720b36f 100644 --- a/backend/app/models/task_fingerprints.py +++ b/backend/app/models/task_fingerprints.py @@ -5,6 +5,8 @@ from uuid import UUID, uuid4 from sqlmodel import Field, SQLModel +from app.core.time import utcnow + class TaskFingerprint(SQLModel, table=True): __tablename__ = "task_fingerprints" @@ -13,4 +15,4 @@ class TaskFingerprint(SQLModel, table=True): board_id: UUID = Field(foreign_key="boards.id", index=True) fingerprint_hash: str = Field(index=True) task_id: UUID = Field(foreign_key="tasks.id") - created_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/models/tasks.py b/backend/app/models/tasks.py index a599e303..a1c2b2ec 100644 --- a/backend/app/models/tasks.py +++ b/backend/app/models/tasks.py @@ -6,6 +6,7 @@ from uuid import UUID, uuid4 from sqlmodel import Field from app.models.tenancy import TenantScoped +from app.core.time import utcnow class Task(TenantScoped, table=True): @@ -26,5 +27,5 @@ class Task(TenantScoped, table=True): auto_created: bool = Field(default=False) auto_reason: str | None = None - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utcnow) + updated_at: datetime = Field(default_factory=utcnow) diff --git a/backend/app/schemas/agents.py b/backend/app/schemas/agents.py index 43dd8feb..4fa2ca34 100644 --- a/backend/app/schemas/agents.py +++ b/backend/app/schemas/agents.py @@ -1,21 +1,64 @@ from __future__ import annotations +from collections.abc import Mapping from datetime import datetime from typing import Any from uuid import UUID +from pydantic import field_validator from sqlmodel import SQLModel +from app.schemas.common import NonEmptyStr + + +def _normalize_identity_profile( + profile: object, +) -> dict[str, str] | None: + if not isinstance(profile, Mapping): + return None + normalized: dict[str, str] = {} + for raw_key, raw in profile.items(): + if raw is None: + continue + key = str(raw_key).strip() + if not key: + continue + if isinstance(raw, list): + parts = [str(item).strip() for item in raw if str(item).strip()] + if not parts: + continue + normalized[key] = ", ".join(parts) + continue + value = str(raw).strip() + if value: + normalized[key] = value + return normalized or None + class AgentBase(SQLModel): board_id: UUID | None = None - name: str + name: NonEmptyStr status: str = "provisioning" heartbeat_config: dict[str, Any] | None = None identity_profile: dict[str, Any] | None = None identity_template: str | None = None soul_template: str | None = None + @field_validator("identity_template", "soul_template", mode="before") + @classmethod + def normalize_templates(cls, value: Any) -> Any: + if value is None: + return None + if isinstance(value, str): + value = value.strip() + return value or None + return value + + @field_validator("identity_profile", mode="before") + @classmethod + def normalize_identity_profile(cls, value: Any) -> Any: + return _normalize_identity_profile(value) + class AgentCreate(AgentBase): pass @@ -24,13 +67,28 @@ class AgentCreate(AgentBase): class AgentUpdate(SQLModel): board_id: UUID | None = None is_gateway_main: bool | None = None - name: str | None = None + name: NonEmptyStr | None = None status: str | None = None heartbeat_config: dict[str, Any] | None = None identity_profile: dict[str, Any] | None = None identity_template: str | None = None soul_template: str | None = None + @field_validator("identity_template", "soul_template", mode="before") + @classmethod + def normalize_templates(cls, value: Any) -> Any: + if value is None: + return None + if isinstance(value, str): + value = value.strip() + return value or None + return value + + @field_validator("identity_profile", mode="before") + @classmethod + def normalize_identity_profile(cls, value: Any) -> Any: + return _normalize_identity_profile(value) + class AgentRead(AgentBase): id: UUID @@ -47,9 +105,9 @@ class AgentHeartbeat(SQLModel): class AgentHeartbeatCreate(AgentHeartbeat): - name: str + name: NonEmptyStr board_id: UUID | None = None class AgentNudge(SQLModel): - message: str + message: NonEmptyStr diff --git a/backend/app/schemas/approvals.py b/backend/app/schemas/approvals.py index 2277a581..b32b2538 100644 --- a/backend/app/schemas/approvals.py +++ b/backend/app/schemas/approvals.py @@ -1,17 +1,22 @@ from __future__ import annotations from datetime import datetime +from typing import Literal, Self from uuid import UUID +from pydantic import model_validator from sqlmodel import SQLModel +ApprovalStatus = Literal["pending", "approved", "rejected"] + + class ApprovalBase(SQLModel): action_type: str payload: dict[str, object] | None = None confidence: int rubric_scores: dict[str, int] | None = None - status: str = "pending" + status: ApprovalStatus = "pending" class ApprovalCreate(ApprovalBase): @@ -19,7 +24,13 @@ class ApprovalCreate(ApprovalBase): class ApprovalUpdate(SQLModel): - status: str | None = None + status: ApprovalStatus | None = None + + @model_validator(mode="after") + def validate_status(self) -> Self: + if "status" in self.model_fields_set and self.status is None: + raise ValueError("status is required") + return self class ApprovalRead(ApprovalBase): diff --git a/backend/app/schemas/board_memory.py b/backend/app/schemas/board_memory.py index 97eec0a7..a02820e7 100644 --- a/backend/app/schemas/board_memory.py +++ b/backend/app/schemas/board_memory.py @@ -5,9 +5,11 @@ from uuid import UUID from sqlmodel import SQLModel +from app.schemas.common import NonEmptyStr + class BoardMemoryCreate(SQLModel): - content: str + content: NonEmptyStr tags: list[str] | None = None source: str | None = None diff --git a/backend/app/schemas/board_onboarding.py b/backend/app/schemas/board_onboarding.py index f8fade52..3ff42e0d 100644 --- a/backend/app/schemas/board_onboarding.py +++ b/backend/app/schemas/board_onboarding.py @@ -1,18 +1,21 @@ from __future__ import annotations from datetime import datetime +from typing import Literal, Self from uuid import UUID -from pydantic import model_validator +from pydantic import Field, model_validator from sqlmodel import SQLModel +from app.schemas.common import NonEmptyStr + class BoardOnboardingStart(SQLModel): pass class BoardOnboardingAnswer(SQLModel): - answer: str + answer: NonEmptyStr other_text: str | None = None @@ -23,13 +26,30 @@ class BoardOnboardingConfirm(SQLModel): target_date: datetime | None = None @model_validator(mode="after") - def validate_goal_fields(self): + def validate_goal_fields(self) -> Self: if self.board_type == "goal": if not self.objective or not self.success_metrics: raise ValueError("Confirmed goal boards require objective and success_metrics") return self +class BoardOnboardingQuestionOption(SQLModel): + id: NonEmptyStr + label: NonEmptyStr + + +class BoardOnboardingAgentQuestion(SQLModel): + question: NonEmptyStr + options: list[BoardOnboardingQuestionOption] = Field(min_length=1) + + +class BoardOnboardingAgentComplete(BoardOnboardingConfirm): + status: Literal["complete"] + + +BoardOnboardingAgentUpdate = BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion + + class BoardOnboardingRead(SQLModel): id: UUID board_id: UUID diff --git a/backend/app/schemas/boards.py b/backend/app/schemas/boards.py index 59223cb2..20bad915 100644 --- a/backend/app/schemas/boards.py +++ b/backend/app/schemas/boards.py @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import datetime +from typing import Self from uuid import UUID from pydantic import model_validator @@ -20,8 +21,10 @@ class BoardBase(SQLModel): class BoardCreate(BoardBase): + gateway_id: UUID + @model_validator(mode="after") - def validate_goal_fields(self): + def validate_goal_fields(self) -> Self: if self.board_type == "goal" and self.goal_confirmed: if not self.objective or not self.success_metrics: raise ValueError("Confirmed goal boards require objective and success_metrics") @@ -39,6 +42,13 @@ class BoardUpdate(SQLModel): goal_confirmed: bool | None = None goal_source: str | None = None + @model_validator(mode="after") + def validate_gateway_id(self) -> Self: + # Treat explicit null like "unset" is invalid for patch updates. + if "gateway_id" in self.model_fields_set and self.gateway_id is None: + raise ValueError("gateway_id is required") + return self + class BoardRead(BoardBase): id: UUID diff --git a/backend/app/schemas/common.py b/backend/app/schemas/common.py new file mode 100644 index 00000000..7f1383c5 --- /dev/null +++ b/backend/app/schemas/common.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from typing import Annotated + +from pydantic import StringConstraints +from sqlmodel import SQLModel + +# Reusable string type for request payloads where blank/whitespace-only values are invalid. +NonEmptyStr = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] + + +class OkResponse(SQLModel): + ok: bool = True diff --git a/backend/app/schemas/gateway_api.py b/backend/app/schemas/gateway_api.py new file mode 100644 index 00000000..f533b499 --- /dev/null +++ b/backend/app/schemas/gateway_api.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from sqlmodel import SQLModel + +from app.schemas.common import NonEmptyStr + + +class GatewaySessionMessageRequest(SQLModel): + content: NonEmptyStr + + +class GatewayResolveQuery(SQLModel): + board_id: str | None = None + gateway_url: str | None = None + gateway_token: str | None = None + gateway_main_session_key: str | None = None + + +class GatewaysStatusResponse(SQLModel): + connected: bool + gateway_url: str + sessions_count: int | None = None + sessions: list[object] | None = None + main_session_key: str | None = None + main_session: object | None = None + main_session_error: str | None = None + error: str | None = None + + +class GatewaySessionsResponse(SQLModel): + sessions: list[object] + main_session_key: str | None = None + main_session: object | None = None + + +class GatewaySessionResponse(SQLModel): + session: object + + +class GatewaySessionHistoryResponse(SQLModel): + history: list[object] + + +class GatewayCommandsResponse(SQLModel): + protocol_version: int + methods: list[str] + events: list[str] diff --git a/backend/app/schemas/gateways.py b/backend/app/schemas/gateways.py index 06596bc3..bc2e85b8 100644 --- a/backend/app/schemas/gateways.py +++ b/backend/app/schemas/gateways.py @@ -1,8 +1,10 @@ from __future__ import annotations from datetime import datetime +from typing import Any from uuid import UUID +from pydantic import field_validator from sqlmodel import SQLModel @@ -17,6 +19,16 @@ class GatewayBase(SQLModel): class GatewayCreate(GatewayBase): token: str | None = None + @field_validator("token", mode="before") + @classmethod + def normalize_token(cls, value: Any) -> Any: + if value is None: + return None + if isinstance(value, str): + value = value.strip() + return value or None + return value + class GatewayUpdate(SQLModel): name: str | None = None @@ -26,6 +38,16 @@ class GatewayUpdate(SQLModel): workspace_root: str | None = None skyll_enabled: bool | None = None + @field_validator("token", mode="before") + @classmethod + def normalize_token(cls, value: Any) -> Any: + if value is None: + return None + if isinstance(value, str): + value = value.strip() + return value or None + return value + class GatewayRead(GatewayBase): id: UUID diff --git a/backend/app/schemas/tasks.py b/backend/app/schemas/tasks.py index b1b26fe1..ae0cf30c 100644 --- a/backend/app/schemas/tasks.py +++ b/backend/app/schemas/tasks.py @@ -1,15 +1,22 @@ from __future__ import annotations from datetime import datetime +from typing import Any, Literal, Self from uuid import UUID +from pydantic import field_validator, model_validator from sqlmodel import SQLModel +from app.schemas.common import NonEmptyStr + + +TaskStatus = Literal["inbox", "in_progress", "review", "done"] + class TaskBase(SQLModel): title: str description: str | None = None - status: str = "inbox" + status: TaskStatus = "inbox" priority: str = "medium" due_at: datetime | None = None assigned_agent_id: UUID | None = None @@ -22,11 +29,26 @@ class TaskCreate(TaskBase): class TaskUpdate(SQLModel): title: str | None = None description: str | None = None - status: str | None = None + status: TaskStatus | None = None priority: str | None = None due_at: datetime | None = None assigned_agent_id: UUID | None = None - comment: str | None = None + comment: NonEmptyStr | None = None + + @field_validator("comment", mode="before") + @classmethod + def normalize_comment(cls, value: Any) -> Any: + if value is None: + return None + if isinstance(value, str) and not value.strip(): + return None + return value + + @model_validator(mode="after") + def validate_status(self) -> Self: + if "status" in self.model_fields_set and self.status is None: + raise ValueError("status is required") + return self class TaskRead(TaskBase): @@ -39,7 +61,7 @@ class TaskRead(TaskBase): class TaskCommentCreate(SQLModel): - message: str + message: NonEmptyStr class TaskCommentRead(SQLModel): diff --git a/backend/app/services/activity_log.py b/backend/app/services/activity_log.py index 49cf7843..487a6289 100644 --- a/backend/app/services/activity_log.py +++ b/backend/app/services/activity_log.py @@ -2,13 +2,13 @@ from __future__ import annotations from uuid import UUID -from sqlmodel import Session +from sqlmodel.ext.asyncio.session import AsyncSession from app.models.activity_events import ActivityEvent def record_activity( - session: Session, + session: AsyncSession, *, event_type: str, message: str, diff --git a/backend/app/services/agent_provisioning.py b/backend/app/services/agent_provisioning.py index 256f1f68..da4e50c3 100644 --- a/backend/app/services/agent_provisioning.py +++ b/backend/app/services/agent_provisioning.py @@ -3,7 +3,7 @@ from __future__ import annotations import json import re from pathlib import Path -from typing import Any +from typing import Any, cast from uuid import uuid4 from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape @@ -240,7 +240,13 @@ async def _supported_gateway_files(config: GatewayClientConfig) -> set[str]: ) if isinstance(files_payload, dict): files = files_payload.get("files") or [] - supported = {item.get("name") for item in files if isinstance(item, dict)} + supported: set[str] = set() + for item in files: + if not isinstance(item, dict): + continue + name = item.get("name") + if isinstance(name, str) and name: + supported.add(name) return supported or set(DEFAULT_GATEWAY_FILES) except OpenClawGatewayError: pass @@ -260,11 +266,14 @@ async def _gateway_agent_files_index( payload = await openclaw_call("agents.files.list", {"agentId": agent_id}, config=config) if isinstance(payload, dict): files = payload.get("files") or [] - return { - item.get("name"): item - for item in files - if isinstance(item, dict) and item.get("name") - } + index: dict[str, dict[str, Any]] = {} + for item in files: + if not isinstance(item, dict): + continue + name = item.get("name") + if isinstance(name, str) and name: + index[name] = cast(dict[str, Any], item) + return index except OpenClawGatewayError: pass return {} @@ -294,7 +303,7 @@ def _render_agent_files( continue if name == "HEARTBEAT.md": heartbeat_template = ( - template_overrides.get(name) + template_overrides[name] if template_overrides and name in template_overrides else _heartbeat_template_name(agent) ) @@ -307,7 +316,7 @@ def _render_agent_files( rendered[name] = env.from_string(override).render(**context).strip() continue template_name = ( - template_overrides.get(name) + template_overrides[name] if template_overrides and name in template_overrides else name ) @@ -329,13 +338,15 @@ async def _gateway_default_agent_id( if not isinstance(payload, dict): return None default_id = payload.get("defaultId") or payload.get("default_id") - if default_id: + if isinstance(default_id, str) and default_id: return default_id agents = payload.get("agents") or [] if isinstance(agents, list) and agents: first = agents[0] if isinstance(first, dict): - return first.get("id") + agent_id = first.get("id") + if isinstance(agent_id, str) and agent_id: + return agent_id return None @@ -539,7 +550,7 @@ async def cleanup_agent( gateway: Gateway, ) -> str | None: if not gateway.url: - return + return None if not gateway.workspace_root: raise ValueError("gateway_workspace_root is required") client_config = GatewayClientConfig(url=gateway.url, token=gateway.token) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 6e8ff779..35a5bf90 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -41,12 +41,11 @@ dev = [ [tool.mypy] python_version = "3.12" -ignore_missing_imports = true -warn_unused_ignores = true -warn_redundant_casts = true -warn_unused_configs = true -check_untyped_defs = true +strict = true plugins = ["pydantic.mypy"] +files = ["app", "scripts"] +mypy_path = ["typings"] +show_error_codes = true [tool.pytest.ini_options] asyncio_default_fixture_loop_scope = "function" diff --git a/backend/scripts/seed_demo.py b/backend/scripts/seed_demo.py index af81d08d..18184025 100644 --- a/backend/scripts/seed_demo.py +++ b/backend/scripts/seed_demo.py @@ -1,25 +1,41 @@ from __future__ import annotations +import asyncio from uuid import uuid4 -from sqlmodel import Session - -from app.db.session import engine -from app.models.orgs import Org, Workspace -from app.models.users import Membership, User +from app.db.session import async_session_maker, init_db +from app.models.agents import Agent +from app.models.boards import Board +from app.models.gateways import Gateway +from app.models.users import User -def run() -> None: - with Session(engine) as session: - org = Org(name="Demo Org", slug="demo-org") - session.add(org) - session.commit() - session.refresh(org) +async def run() -> None: + await init_db() + async with async_session_maker() as session: + gateway = Gateway( + name="Demo Gateway", + url="http://localhost:8080", + token=None, + main_session_key="demo:main", + workspace_root="/tmp/openclaw-demo", + skyll_enabled=False, + ) + session.add(gateway) + await session.commit() + await session.refresh(gateway) - workspace = Workspace(org_id=org.id, name="Demo Workspace", slug="demo-workspace") - session.add(workspace) - session.commit() - session.refresh(workspace) + board = Board( + name="Demo Board", + slug="demo-board", + gateway_id=gateway.id, + board_type="goal", + objective="Demo objective", + success_metrics={"demo": True}, + ) + session.add(board) + await session.commit() + await session.refresh(board) user = User( clerk_user_id=f"demo-{uuid4()}", @@ -28,19 +44,18 @@ def run() -> None: is_super_admin=True, ) session.add(user) - session.commit() - session.refresh(user) + await session.commit() + await session.refresh(user) - membership = Membership( - org_id=org.id, - workspace_id=workspace.id, - user_id=user.id, - role="admin", + lead = Agent( + board_id=board.id, + name="Lead Agent", + status="online", + is_board_lead=True, ) - session.add(membership) - - session.commit() + session.add(lead) + await session.commit() if __name__ == "__main__": - run() + asyncio.run(run()) diff --git a/backend/tests/test_board_schema.py b/backend/tests/test_board_schema.py index 6a2a08ee..cf800eee 100644 --- a/backend/tests/test_board_schema.py +++ b/backend/tests/test_board_schema.py @@ -1,4 +1,5 @@ import pytest +from uuid import uuid4 from app.schemas.board_onboarding import BoardOnboardingConfirm from app.schemas.boards import BoardCreate @@ -9,6 +10,7 @@ def test_goal_board_requires_objective_and_metrics_when_confirmed(): BoardCreate( name="Goal Board", slug="goal", + gateway_id=uuid4(), board_type="goal", goal_confirmed=True, ) @@ -16,6 +18,7 @@ def test_goal_board_requires_objective_and_metrics_when_confirmed(): BoardCreate( name="Goal Board", slug="goal", + gateway_id=uuid4(), board_type="goal", goal_confirmed=True, objective="Launch onboarding", @@ -24,11 +27,11 @@ def test_goal_board_requires_objective_and_metrics_when_confirmed(): def test_goal_board_allows_missing_objective_before_confirmation(): - BoardCreate(name="Draft", slug="draft", board_type="goal") + BoardCreate(name="Draft", slug="draft", gateway_id=uuid4(), board_type="goal") def test_general_board_allows_missing_objective(): - BoardCreate(name="General", slug="general", board_type="general") + BoardCreate(name="General", slug="general", gateway_id=uuid4(), board_type="general") def test_onboarding_confirm_requires_goal_fields(): diff --git a/backend/typings/fastapi_clerk_auth/__init__.pyi b/backend/typings/fastapi_clerk_auth/__init__.pyi new file mode 100644 index 00000000..2d81b5cb --- /dev/null +++ b/backend/typings/fastapi_clerk_auth/__init__.pyi @@ -0,0 +1,37 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from starlette.requests import Request + + +@dataclass +class ClerkConfig: + jwks_url: str + verify_iat: bool = ... + leeway: float = ... + + +class HTTPAuthorizationCredentials: + scheme: str + credentials: str + decoded: dict[str, object] | None + + def __init__( + self, + scheme: str, + credentials: str, + decoded: dict[str, object] | None = ..., + ) -> None: ... + + +class ClerkHTTPBearer: + def __init__( + self, + config: ClerkConfig, + auto_error: bool = ..., + add_state: bool = ..., + ) -> None: ... + + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: ... + diff --git a/frontend/src/api/generated/agent/agent.ts b/frontend/src/api/generated/agent/agent.ts new file mode 100644 index 00000000..38b41761 --- /dev/null +++ b/frontend/src/api/generated/agent/agent.ts @@ -0,0 +1,2960 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + MutationFunction, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; + +import type { + AgentCreate, + AgentHeartbeatCreate, + AgentNudge, + AgentRead, + ApprovalCreate, + ApprovalRead, + BoardMemoryCreate, + BoardMemoryRead, + BoardOnboardingAgentComplete, + BoardOnboardingAgentQuestion, + BoardOnboardingRead, + BoardRead, + HTTPValidationError, + ListAgentsApiV1AgentAgentsGetParams, + ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + OkResponse, + TaskCommentCreate, + TaskCommentRead, + TaskCreate, + TaskRead, + TaskUpdate, +} from ".././model"; + +import { customFetch } from "../../mutator"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary List Boards + */ +export type listBoardsApiV1AgentBoardsGetResponse200 = { + data: BoardRead[]; + status: 200; +}; + +export type listBoardsApiV1AgentBoardsGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listBoardsApiV1AgentBoardsGetResponseSuccess = + listBoardsApiV1AgentBoardsGetResponse200 & { + headers: Headers; + }; +export type listBoardsApiV1AgentBoardsGetResponseError = + listBoardsApiV1AgentBoardsGetResponse422 & { + headers: Headers; + }; + +export type listBoardsApiV1AgentBoardsGetResponse = + | listBoardsApiV1AgentBoardsGetResponseSuccess + | listBoardsApiV1AgentBoardsGetResponseError; + +export const getListBoardsApiV1AgentBoardsGetUrl = () => { + return `/api/v1/agent/boards`; +}; + +export const listBoardsApiV1AgentBoardsGet = async ( + options?: RequestInit, +): Promise => { + return customFetch( + getListBoardsApiV1AgentBoardsGetUrl(), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListBoardsApiV1AgentBoardsGetQueryKey = () => { + return [`/api/v1/agent/boards`] as const; +}; + +export const getListBoardsApiV1AgentBoardsGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>(options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getListBoardsApiV1AgentBoardsGetQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listBoardsApiV1AgentBoardsGet({ signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListBoardsApiV1AgentBoardsGetQueryResult = NonNullable< + Awaited> +>; +export type ListBoardsApiV1AgentBoardsGetQueryError = HTTPValidationError; + +export function useListBoardsApiV1AgentBoardsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListBoardsApiV1AgentBoardsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListBoardsApiV1AgentBoardsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Boards + */ + +export function useListBoardsApiV1AgentBoardsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getListBoardsApiV1AgentBoardsGetQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Get Board + */ +export type getBoardApiV1AgentBoardsBoardIdGetResponse200 = { + data: BoardRead; + status: 200; +}; + +export type getBoardApiV1AgentBoardsBoardIdGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type getBoardApiV1AgentBoardsBoardIdGetResponseSuccess = + getBoardApiV1AgentBoardsBoardIdGetResponse200 & { + headers: Headers; + }; +export type getBoardApiV1AgentBoardsBoardIdGetResponseError = + getBoardApiV1AgentBoardsBoardIdGetResponse422 & { + headers: Headers; + }; + +export type getBoardApiV1AgentBoardsBoardIdGetResponse = + | getBoardApiV1AgentBoardsBoardIdGetResponseSuccess + | getBoardApiV1AgentBoardsBoardIdGetResponseError; + +export const getGetBoardApiV1AgentBoardsBoardIdGetUrl = (boardId: string) => { + return `/api/v1/agent/boards/${boardId}`; +}; + +export const getBoardApiV1AgentBoardsBoardIdGet = async ( + boardId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getGetBoardApiV1AgentBoardsBoardIdGetUrl(boardId), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetBoardApiV1AgentBoardsBoardIdGetQueryKey = ( + boardId: string, +) => { + return [`/api/v1/agent/boards/${boardId}`] as const; +}; + +export const getGetBoardApiV1AgentBoardsBoardIdGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetBoardApiV1AgentBoardsBoardIdGetQueryKey(boardId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getBoardApiV1AgentBoardsBoardIdGet(boardId, { signal, ...requestOptions }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetBoardApiV1AgentBoardsBoardIdGetQueryResult = NonNullable< + Awaited> +>; +export type GetBoardApiV1AgentBoardsBoardIdGetQueryError = HTTPValidationError; + +export function useGetBoardApiV1AgentBoardsBoardIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetBoardApiV1AgentBoardsBoardIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetBoardApiV1AgentBoardsBoardIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Board + */ + +export function useGetBoardApiV1AgentBoardsBoardIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetBoardApiV1AgentBoardsBoardIdGetQueryOptions( + boardId, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary List Agents + */ +export type listAgentsApiV1AgentAgentsGetResponse200 = { + data: AgentRead[]; + status: 200; +}; + +export type listAgentsApiV1AgentAgentsGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listAgentsApiV1AgentAgentsGetResponseSuccess = + listAgentsApiV1AgentAgentsGetResponse200 & { + headers: Headers; + }; +export type listAgentsApiV1AgentAgentsGetResponseError = + listAgentsApiV1AgentAgentsGetResponse422 & { + headers: Headers; + }; + +export type listAgentsApiV1AgentAgentsGetResponse = + | listAgentsApiV1AgentAgentsGetResponseSuccess + | listAgentsApiV1AgentAgentsGetResponseError; + +export const getListAgentsApiV1AgentAgentsGetUrl = ( + params?: ListAgentsApiV1AgentAgentsGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/agent/agents?${stringifiedParams}` + : `/api/v1/agent/agents`; +}; + +export const listAgentsApiV1AgentAgentsGet = async ( + params?: ListAgentsApiV1AgentAgentsGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListAgentsApiV1AgentAgentsGetUrl(params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListAgentsApiV1AgentAgentsGetQueryKey = ( + params?: ListAgentsApiV1AgentAgentsGetParams, +) => { + return [`/api/v1/agent/agents`, ...(params ? [params] : [])] as const; +}; + +export const getListAgentsApiV1AgentAgentsGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: ListAgentsApiV1AgentAgentsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getListAgentsApiV1AgentAgentsGetQueryKey(params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listAgentsApiV1AgentAgentsGet(params, { signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListAgentsApiV1AgentAgentsGetQueryResult = NonNullable< + Awaited> +>; +export type ListAgentsApiV1AgentAgentsGetQueryError = HTTPValidationError; + +export function useListAgentsApiV1AgentAgentsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params: undefined | ListAgentsApiV1AgentAgentsGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListAgentsApiV1AgentAgentsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: ListAgentsApiV1AgentAgentsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListAgentsApiV1AgentAgentsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: ListAgentsApiV1AgentAgentsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Agents + */ + +export function useListAgentsApiV1AgentAgentsGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: ListAgentsApiV1AgentAgentsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getListAgentsApiV1AgentAgentsGetQueryOptions( + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Agent + */ +export type createAgentApiV1AgentAgentsPostResponse200 = { + data: AgentRead; + status: 200; +}; + +export type createAgentApiV1AgentAgentsPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createAgentApiV1AgentAgentsPostResponseSuccess = + createAgentApiV1AgentAgentsPostResponse200 & { + headers: Headers; + }; +export type createAgentApiV1AgentAgentsPostResponseError = + createAgentApiV1AgentAgentsPostResponse422 & { + headers: Headers; + }; + +export type createAgentApiV1AgentAgentsPostResponse = + | createAgentApiV1AgentAgentsPostResponseSuccess + | createAgentApiV1AgentAgentsPostResponseError; + +export const getCreateAgentApiV1AgentAgentsPostUrl = () => { + return `/api/v1/agent/agents`; +}; + +export const createAgentApiV1AgentAgentsPost = async ( + agentCreate: AgentCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateAgentApiV1AgentAgentsPostUrl(), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(agentCreate), + }, + ); +}; + +export const getCreateAgentApiV1AgentAgentsPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AgentCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { data: AgentCreate }, + TContext +> => { + const mutationKey = ["createAgentApiV1AgentAgentsPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { data: AgentCreate } + > = (props) => { + const { data } = props ?? {}; + + return createAgentApiV1AgentAgentsPost(data, requestOptions); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type CreateAgentApiV1AgentAgentsPostMutationResult = NonNullable< + Awaited> +>; +export type CreateAgentApiV1AgentAgentsPostMutationBody = AgentCreate; +export type CreateAgentApiV1AgentAgentsPostMutationError = HTTPValidationError; + +/** + * @summary Create Agent + */ +export const useCreateAgentApiV1AgentAgentsPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AgentCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { data: AgentCreate }, + TContext +> => { + return useMutation( + getCreateAgentApiV1AgentAgentsPostMutationOptions(options), + queryClient, + ); +}; +/** + * @summary List Tasks + */ +export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse200 = { + data: TaskRead[]; + status: 200; +}; + +export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listTasksApiV1AgentBoardsBoardIdTasksGetResponseSuccess = + listTasksApiV1AgentBoardsBoardIdTasksGetResponse200 & { + headers: Headers; + }; +export type listTasksApiV1AgentBoardsBoardIdTasksGetResponseError = + listTasksApiV1AgentBoardsBoardIdTasksGetResponse422 & { + headers: Headers; + }; + +export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse = + | listTasksApiV1AgentBoardsBoardIdTasksGetResponseSuccess + | listTasksApiV1AgentBoardsBoardIdTasksGetResponseError; + +export const getListTasksApiV1AgentBoardsBoardIdTasksGetUrl = ( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/agent/boards/${boardId}/tasks?${stringifiedParams}` + : `/api/v1/agent/boards/${boardId}/tasks`; +}; + +export const listTasksApiV1AgentBoardsBoardIdTasksGet = async ( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListTasksApiV1AgentBoardsBoardIdTasksGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListTasksApiV1AgentBoardsBoardIdTasksGetQueryKey = ( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, +) => { + return [ + `/api/v1/agent/boards/${boardId}/tasks`, + ...(params ? [params] : []), + ] as const; +}; + +export const getListTasksApiV1AgentBoardsBoardIdTasksGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListTasksApiV1AgentBoardsBoardIdTasksGetQueryKey(boardId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listTasksApiV1AgentBoardsBoardIdTasksGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListTasksApiV1AgentBoardsBoardIdTasksGetQueryResult = NonNullable< + Awaited> +>; +export type ListTasksApiV1AgentBoardsBoardIdTasksGetQueryError = + HTTPValidationError; + +export function useListTasksApiV1AgentBoardsBoardIdTasksGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListTasksApiV1AgentBoardsBoardIdTasksGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListTasksApiV1AgentBoardsBoardIdTasksGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Tasks + */ + +export function useListTasksApiV1AgentBoardsBoardIdTasksGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListTasksApiV1AgentBoardsBoardIdTasksGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getListTasksApiV1AgentBoardsBoardIdTasksGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Task + */ +export type createTaskApiV1AgentBoardsBoardIdTasksPostResponse200 = { + data: TaskRead; + status: 200; +}; + +export type createTaskApiV1AgentBoardsBoardIdTasksPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createTaskApiV1AgentBoardsBoardIdTasksPostResponseSuccess = + createTaskApiV1AgentBoardsBoardIdTasksPostResponse200 & { + headers: Headers; + }; +export type createTaskApiV1AgentBoardsBoardIdTasksPostResponseError = + createTaskApiV1AgentBoardsBoardIdTasksPostResponse422 & { + headers: Headers; + }; + +export type createTaskApiV1AgentBoardsBoardIdTasksPostResponse = + | createTaskApiV1AgentBoardsBoardIdTasksPostResponseSuccess + | createTaskApiV1AgentBoardsBoardIdTasksPostResponseError; + +export const getCreateTaskApiV1AgentBoardsBoardIdTasksPostUrl = ( + boardId: string, +) => { + return `/api/v1/agent/boards/${boardId}/tasks`; +}; + +export const createTaskApiV1AgentBoardsBoardIdTasksPost = async ( + boardId: string, + taskCreate: TaskCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateTaskApiV1AgentBoardsBoardIdTasksPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(taskCreate), + }, + ); +}; + +export const getCreateTaskApiV1AgentBoardsBoardIdTasksPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: TaskCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: TaskCreate }, + TContext +> => { + const mutationKey = ["createTaskApiV1AgentBoardsBoardIdTasksPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { boardId: string; data: TaskCreate } + > = (props) => { + const { boardId, data } = props ?? {}; + + return createTaskApiV1AgentBoardsBoardIdTasksPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type CreateTaskApiV1AgentBoardsBoardIdTasksPostMutationResult = + NonNullable< + Awaited> + >; +export type CreateTaskApiV1AgentBoardsBoardIdTasksPostMutationBody = TaskCreate; +export type CreateTaskApiV1AgentBoardsBoardIdTasksPostMutationError = + HTTPValidationError; + +/** + * @summary Create Task + */ +export const useCreateTaskApiV1AgentBoardsBoardIdTasksPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: TaskCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { boardId: string; data: TaskCreate }, + TContext +> => { + return useMutation( + getCreateTaskApiV1AgentBoardsBoardIdTasksPostMutationOptions(options), + queryClient, + ); +}; +/** + * @summary Update Task + */ +export type updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponse200 = { + data: TaskRead; + status: 200; +}; + +export type updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponseSuccess = + updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponse200 & { + headers: Headers; + }; +export type updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponseError = + updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponse422 & { + headers: Headers; + }; + +export type updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponse = + | updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponseSuccess + | updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchResponseError; + +export const getUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchUrl = ( + boardId: string, + taskId: string, +) => { + return `/api/v1/agent/boards/${boardId}/tasks/${taskId}`; +}; + +export const updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatch = async ( + boardId: string, + taskId: string, + taskUpdate: TaskUpdate, + options?: RequestInit, +): Promise => { + return customFetch( + getUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchUrl(boardId, taskId), + { + ...options, + method: "PATCH", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(taskUpdate), + }, + ); +}; + +export const getUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; taskId: string; data: TaskUpdate }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; taskId: string; data: TaskUpdate }, + TContext + > => { + const mutationKey = ["updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatch"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType + >, + { boardId: string; taskId: string; data: TaskUpdate } + > = (props) => { + const { boardId, taskId, data } = props ?? {}; + + return updateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatch( + boardId, + taskId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type UpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type UpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchMutationBody = + TaskUpdate; +export type UpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchMutationError = + HTTPValidationError; + +/** + * @summary Update Task + */ +export const useUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatch = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; taskId: string; data: TaskUpdate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { boardId: string; taskId: string; data: TaskUpdate }, + TContext +> => { + return useMutation( + getUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatchMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary List Task Comments + */ +export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse200 = + { + data: TaskCommentRead[]; + status: 200; + }; + +export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseSuccess = + listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse200 & { + headers: Headers; + }; +export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseError = + listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse422 & { + headers: Headers; + }; + +export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse = + + | listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseSuccess + | listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseError; + +export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl = + (boardId: string, taskId: string) => { + return `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`; + }; + +export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet = + async ( + boardId: string, + taskId: string, + options?: RequestInit, + ): Promise => { + return customFetch( + getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl( + boardId, + taskId, + ), + { + ...options, + method: "GET", + }, + ); + }; + +export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey = + (boardId: string, taskId: string) => { + return [ + `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`, + ] as const; + }; + +export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryOptions = + < + TData = Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError = HTTPValidationError, + >( + boardId: string, + taskId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey( + boardId, + taskId, + ); + + const queryFn: QueryFunction< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + > + > = ({ signal }) => + listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet( + boardId, + taskId, + { signal, ...requestOptions }, + ); + + return { + queryKey, + queryFn, + enabled: !!(boardId && taskId), + ...queryOptions, + } as UseQueryOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + TData + > & { queryKey: DataTag }; + }; + +export type ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryResult = + NonNullable< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + > + >; +export type ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryError = + HTTPValidationError; + +export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet< + TData = Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError = HTTPValidationError, +>( + boardId: string, + taskId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet< + TData = Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError = HTTPValidationError, +>( + boardId: string, + taskId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet< + TData = Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError = HTTPValidationError, +>( + boardId: string, + taskId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Task Comments + */ + +export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet< + TData = Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError = HTTPValidationError, +>( + boardId: string, + taskId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryOptions( + boardId, + taskId, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Task Comment + */ +export type createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponse200 = + { + data: TaskCommentRead; + status: 200; + }; + +export type createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponseSuccess = + createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponse200 & { + headers: Headers; + }; +export type createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponseError = + createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponse422 & { + headers: Headers; + }; + +export type createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponse = + + | createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponseSuccess + | createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostResponseError; + +export const getCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostUrl = + (boardId: string, taskId: string) => { + return `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`; + }; + +export const createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost = + async ( + boardId: string, + taskId: string, + taskCommentCreate: TaskCommentCreate, + options?: RequestInit, + ): Promise => { + return customFetch( + getCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostUrl( + boardId, + taskId, + ), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(taskCommentCreate), + }, + ); + }; + +export const getCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost + > + >, + TError, + { boardId: string; taskId: string; data: TaskCommentCreate }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost + > + >, + TError, + { boardId: string; taskId: string; data: TaskCommentCreate }, + TContext + > => { + const mutationKey = [ + "createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost + > + >, + { boardId: string; taskId: string; data: TaskCommentCreate } + > = (props) => { + const { boardId, taskId, data } = props ?? {}; + + return createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost( + boardId, + taskId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type CreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost + > + > + >; +export type CreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostMutationBody = + TaskCommentCreate; +export type CreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostMutationError = + HTTPValidationError; + +/** + * @summary Create Task Comment + */ +export const useCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost = + ( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost + > + >, + TError, + { boardId: string; taskId: string; data: TaskCommentCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, + ): UseMutationResult< + Awaited< + ReturnType< + typeof createTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost + > + >, + TError, + { boardId: string; taskId: string; data: TaskCommentCreate }, + TContext + > => { + return useMutation( + getCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPostMutationOptions( + options, + ), + queryClient, + ); + }; +/** + * @summary List Board Memory + */ +export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse200 = { + data: BoardMemoryRead[]; + status: 200; +}; + +export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponseSuccess = + listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse200 & { + headers: Headers; + }; +export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponseError = + listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse422 & { + headers: Headers; + }; + +export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse = + | listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponseSuccess + | listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponseError; + +export const getListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetUrl = ( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/agent/boards/${boardId}/memory?${stringifiedParams}` + : `/api/v1/agent/boards/${boardId}/memory`; +}; + +export const listBoardMemoryApiV1AgentBoardsBoardIdMemoryGet = async ( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetQueryKey = ( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, +) => { + return [ + `/api/v1/agent/boards/${boardId}/memory`, + ...(params ? [params] : []), + ] as const; +}; + +export const getListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetQueryKey(boardId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listBoardMemoryApiV1AgentBoardsBoardIdMemoryGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetQueryResult = + NonNullable< + Awaited> + >; +export type ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetQueryError = + HTTPValidationError; + +export function useListBoardMemoryApiV1AgentBoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListBoardMemoryApiV1AgentBoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListBoardMemoryApiV1AgentBoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Board Memory + */ + +export function useListBoardMemoryApiV1AgentBoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Board Memory + */ +export type createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponse200 = { + data: BoardMemoryRead; + status: 200; +}; + +export type createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponseSuccess = + createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponse200 & { + headers: Headers; + }; +export type createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponseError = + createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponse422 & { + headers: Headers; + }; + +export type createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponse = + | createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponseSuccess + | createBoardMemoryApiV1AgentBoardsBoardIdMemoryPostResponseError; + +export const getCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostUrl = ( + boardId: string, +) => { + return `/api/v1/agent/boards/${boardId}/memory`; +}; + +export const createBoardMemoryApiV1AgentBoardsBoardIdMemoryPost = async ( + boardId: string, + boardMemoryCreate: BoardMemoryCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(boardMemoryCreate), + }, + ); +}; + +export const getCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext + > => { + const mutationKey = ["createBoardMemoryApiV1AgentBoardsBoardIdMemoryPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType + >, + { boardId: string; data: BoardMemoryCreate } + > = (props) => { + const { boardId, data } = props ?? {}; + + return createBoardMemoryApiV1AgentBoardsBoardIdMemoryPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type CreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type CreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostMutationBody = + BoardMemoryCreate; +export type CreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostMutationError = + HTTPValidationError; + +/** + * @summary Create Board Memory + */ +export const useCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext +> => { + return useMutation( + getCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary List Approvals + */ +export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse200 = { + data: ApprovalRead[]; + status: 200; +}; + +export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponseSuccess = + listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse200 & { + headers: Headers; + }; +export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponseError = + listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse422 & { + headers: Headers; + }; + +export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse = + | listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponseSuccess + | listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponseError; + +export const getListApprovalsApiV1AgentBoardsBoardIdApprovalsGetUrl = ( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/agent/boards/${boardId}/approvals?${stringifiedParams}` + : `/api/v1/agent/boards/${boardId}/approvals`; +}; + +export const listApprovalsApiV1AgentBoardsBoardIdApprovalsGet = async ( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListApprovalsApiV1AgentBoardsBoardIdApprovalsGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListApprovalsApiV1AgentBoardsBoardIdApprovalsGetQueryKey = ( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, +) => { + return [ + `/api/v1/agent/boards/${boardId}/approvals`, + ...(params ? [params] : []), + ] as const; +}; + +export const getListApprovalsApiV1AgentBoardsBoardIdApprovalsGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListApprovalsApiV1AgentBoardsBoardIdApprovalsGetQueryKey( + boardId, + params, + ); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listApprovalsApiV1AgentBoardsBoardIdApprovalsGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetQueryResult = + NonNullable< + Awaited> + >; +export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetQueryError = + HTTPValidationError; + +export function useListApprovalsApiV1AgentBoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListApprovalsApiV1AgentBoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListApprovalsApiV1AgentBoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Approvals + */ + +export function useListApprovalsApiV1AgentBoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListApprovalsApiV1AgentBoardsBoardIdApprovalsGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Approval + */ +export type createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponse200 = { + data: ApprovalRead; + status: 200; +}; + +export type createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponseSuccess = + createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponse200 & { + headers: Headers; + }; +export type createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponseError = + createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponse422 & { + headers: Headers; + }; + +export type createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponse = + | createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponseSuccess + | createApprovalApiV1AgentBoardsBoardIdApprovalsPostResponseError; + +export const getCreateApprovalApiV1AgentBoardsBoardIdApprovalsPostUrl = ( + boardId: string, +) => { + return `/api/v1/agent/boards/${boardId}/approvals`; +}; + +export const createApprovalApiV1AgentBoardsBoardIdApprovalsPost = async ( + boardId: string, + approvalCreate: ApprovalCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateApprovalApiV1AgentBoardsBoardIdApprovalsPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(approvalCreate), + }, + ); +}; + +export const getCreateApprovalApiV1AgentBoardsBoardIdApprovalsPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: ApprovalCreate }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: ApprovalCreate }, + TContext + > => { + const mutationKey = ["createApprovalApiV1AgentBoardsBoardIdApprovalsPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType + >, + { boardId: string; data: ApprovalCreate } + > = (props) => { + const { boardId, data } = props ?? {}; + + return createApprovalApiV1AgentBoardsBoardIdApprovalsPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type CreateApprovalApiV1AgentBoardsBoardIdApprovalsPostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type CreateApprovalApiV1AgentBoardsBoardIdApprovalsPostMutationBody = + ApprovalCreate; +export type CreateApprovalApiV1AgentBoardsBoardIdApprovalsPostMutationError = + HTTPValidationError; + +/** + * @summary Create Approval + */ +export const useCreateApprovalApiV1AgentBoardsBoardIdApprovalsPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: ApprovalCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: ApprovalCreate }, + TContext +> => { + return useMutation( + getCreateApprovalApiV1AgentBoardsBoardIdApprovalsPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Update Onboarding + */ +export type updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponse200 = { + data: BoardOnboardingRead; + status: 200; +}; + +export type updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponseSuccess = + updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponse200 & { + headers: Headers; + }; +export type updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponseError = + updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponse422 & { + headers: Headers; + }; + +export type updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponse = + | updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponseSuccess + | updateOnboardingApiV1AgentBoardsBoardIdOnboardingPostResponseError; + +export const getUpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostUrl = ( + boardId: string, +) => { + return `/api/v1/agent/boards/${boardId}/onboarding`; +}; + +export const updateOnboardingApiV1AgentBoardsBoardIdOnboardingPost = async ( + boardId: string, + boardOnboardingAgentCompleteBoardOnboardingAgentQuestion: + | BoardOnboardingAgentComplete + | BoardOnboardingAgentQuestion, + options?: RequestInit, +): Promise => { + return customFetch( + getUpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify( + boardOnboardingAgentCompleteBoardOnboardingAgentQuestion, + ), + }, + ); +}; + +export const getUpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext + > => { + const mutationKey = [ + "updateOnboardingApiV1AgentBoardsBoardIdOnboardingPost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType + >, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + } + > = (props) => { + const { boardId, data } = props ?? {}; + + return updateOnboardingApiV1AgentBoardsBoardIdOnboardingPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type UpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type UpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostMutationBody = + | BoardOnboardingAgentComplete + | BoardOnboardingAgentQuestion; +export type UpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostMutationError = + HTTPValidationError; + +/** + * @summary Update Onboarding + */ +export const useUpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext +> => { + return useMutation( + getUpdateOnboardingApiV1AgentBoardsBoardIdOnboardingPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Nudge Agent + */ +export type nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponse200 = + { + data: OkResponse; + status: 200; + }; + +export type nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponseSuccess = + nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponse200 & { + headers: Headers; + }; +export type nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponseError = + nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponse422 & { + headers: Headers; + }; + +export type nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponse = + | nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponseSuccess + | nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostResponseError; + +export const getNudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostUrl = ( + boardId: string, + agentId: string, +) => { + return `/api/v1/agent/boards/${boardId}/agents/${agentId}/nudge`; +}; + +export const nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost = async ( + boardId: string, + agentId: string, + agentNudge: AgentNudge, + options?: RequestInit, +): Promise => { + return customFetch( + getNudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostUrl( + boardId, + agentId, + ), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(agentNudge), + }, + ); +}; + +export const getNudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost + > + >, + TError, + { boardId: string; agentId: string; data: AgentNudge }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; agentId: string; data: AgentNudge }, + TContext + > => { + const mutationKey = [ + "nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost + > + >, + { boardId: string; agentId: string; data: AgentNudge } + > = (props) => { + const { boardId, agentId, data } = props ?? {}; + + return nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost( + boardId, + agentId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type NudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type NudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostMutationBody = + AgentNudge; +export type NudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostMutationError = + HTTPValidationError; + +/** + * @summary Nudge Agent + */ +export const useNudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof nudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePost + > + >, + TError, + { boardId: string; agentId: string; data: AgentNudge }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; agentId: string; data: AgentNudge }, + TContext +> => { + return useMutation( + getNudgeAgentApiV1AgentBoardsBoardIdAgentsAgentIdNudgePostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Agent Heartbeat + */ +export type agentHeartbeatApiV1AgentHeartbeatPostResponse200 = { + data: AgentRead; + status: 200; +}; + +export type agentHeartbeatApiV1AgentHeartbeatPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type agentHeartbeatApiV1AgentHeartbeatPostResponseSuccess = + agentHeartbeatApiV1AgentHeartbeatPostResponse200 & { + headers: Headers; + }; +export type agentHeartbeatApiV1AgentHeartbeatPostResponseError = + agentHeartbeatApiV1AgentHeartbeatPostResponse422 & { + headers: Headers; + }; + +export type agentHeartbeatApiV1AgentHeartbeatPostResponse = + | agentHeartbeatApiV1AgentHeartbeatPostResponseSuccess + | agentHeartbeatApiV1AgentHeartbeatPostResponseError; + +export const getAgentHeartbeatApiV1AgentHeartbeatPostUrl = () => { + return `/api/v1/agent/heartbeat`; +}; + +export const agentHeartbeatApiV1AgentHeartbeatPost = async ( + agentHeartbeatCreate: AgentHeartbeatCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getAgentHeartbeatApiV1AgentHeartbeatPostUrl(), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(agentHeartbeatCreate), + }, + ); +}; + +export const getAgentHeartbeatApiV1AgentHeartbeatPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AgentHeartbeatCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { data: AgentHeartbeatCreate }, + TContext +> => { + const mutationKey = ["agentHeartbeatApiV1AgentHeartbeatPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { data: AgentHeartbeatCreate } + > = (props) => { + const { data } = props ?? {}; + + return agentHeartbeatApiV1AgentHeartbeatPost(data, requestOptions); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type AgentHeartbeatApiV1AgentHeartbeatPostMutationResult = NonNullable< + Awaited> +>; +export type AgentHeartbeatApiV1AgentHeartbeatPostMutationBody = + AgentHeartbeatCreate; +export type AgentHeartbeatApiV1AgentHeartbeatPostMutationError = + HTTPValidationError; + +/** + * @summary Agent Heartbeat + */ +export const useAgentHeartbeatApiV1AgentHeartbeatPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AgentHeartbeatCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { data: AgentHeartbeatCreate }, + TContext +> => { + return useMutation( + getAgentHeartbeatApiV1AgentHeartbeatPostMutationOptions(options), + queryClient, + ); +}; diff --git a/frontend/src/api/generated/agents/agents.ts b/frontend/src/api/generated/agents/agents.ts index b8c8602b..0f45ec8c 100644 --- a/frontend/src/api/generated/agents/agents.ts +++ b/frontend/src/api/generated/agents/agents.ts @@ -22,16 +22,14 @@ import type { import type { AgentCreate, - AgentDeleteConfirm, AgentHeartbeat, AgentHeartbeatCreate, - AgentProvisionConfirm, AgentRead, AgentUpdate, - ConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200, - ConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost200, - DeleteAgentApiV1AgentsAgentIdDelete200, HTTPValidationError, + OkResponse, + StreamAgentsApiV1AgentsStreamGetParams, + UpdateAgentApiV1AgentsAgentIdPatchParams, } from ".././model"; import { customFetch } from "../../mutator"; @@ -326,6 +324,217 @@ export const useCreateAgentApiV1AgentsPost = < queryClient, ); }; +/** + * @summary Stream Agents + */ +export type streamAgentsApiV1AgentsStreamGetResponse200 = { + data: unknown; + status: 200; +}; + +export type streamAgentsApiV1AgentsStreamGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type streamAgentsApiV1AgentsStreamGetResponseSuccess = + streamAgentsApiV1AgentsStreamGetResponse200 & { + headers: Headers; + }; +export type streamAgentsApiV1AgentsStreamGetResponseError = + streamAgentsApiV1AgentsStreamGetResponse422 & { + headers: Headers; + }; + +export type streamAgentsApiV1AgentsStreamGetResponse = + | streamAgentsApiV1AgentsStreamGetResponseSuccess + | streamAgentsApiV1AgentsStreamGetResponseError; + +export const getStreamAgentsApiV1AgentsStreamGetUrl = ( + params?: StreamAgentsApiV1AgentsStreamGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/agents/stream?${stringifiedParams}` + : `/api/v1/agents/stream`; +}; + +export const streamAgentsApiV1AgentsStreamGet = async ( + params?: StreamAgentsApiV1AgentsStreamGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getStreamAgentsApiV1AgentsStreamGetUrl(params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getStreamAgentsApiV1AgentsStreamGetQueryKey = ( + params?: StreamAgentsApiV1AgentsStreamGetParams, +) => { + return [`/api/v1/agents/stream`, ...(params ? [params] : [])] as const; +}; + +export const getStreamAgentsApiV1AgentsStreamGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: StreamAgentsApiV1AgentsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getStreamAgentsApiV1AgentsStreamGetQueryKey(params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + streamAgentsApiV1AgentsStreamGet(params, { signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type StreamAgentsApiV1AgentsStreamGetQueryResult = NonNullable< + Awaited> +>; +export type StreamAgentsApiV1AgentsStreamGetQueryError = HTTPValidationError; + +export function useStreamAgentsApiV1AgentsStreamGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params: undefined | StreamAgentsApiV1AgentsStreamGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useStreamAgentsApiV1AgentsStreamGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: StreamAgentsApiV1AgentsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useStreamAgentsApiV1AgentsStreamGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: StreamAgentsApiV1AgentsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Stream Agents + */ + +export function useStreamAgentsApiV1AgentsStreamGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: StreamAgentsApiV1AgentsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getStreamAgentsApiV1AgentsStreamGetQueryOptions( + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + /** * @summary Get Agent */ @@ -551,17 +760,33 @@ export type updateAgentApiV1AgentsAgentIdPatchResponse = | updateAgentApiV1AgentsAgentIdPatchResponseSuccess | updateAgentApiV1AgentsAgentIdPatchResponseError; -export const getUpdateAgentApiV1AgentsAgentIdPatchUrl = (agentId: string) => { - return `/api/v1/agents/${agentId}`; +export const getUpdateAgentApiV1AgentsAgentIdPatchUrl = ( + agentId: string, + params?: UpdateAgentApiV1AgentsAgentIdPatchParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/agents/${agentId}?${stringifiedParams}` + : `/api/v1/agents/${agentId}`; }; export const updateAgentApiV1AgentsAgentIdPatch = async ( agentId: string, agentUpdate: AgentUpdate, + params?: UpdateAgentApiV1AgentsAgentIdPatchParams, options?: RequestInit, ): Promise => { return customFetch( - getUpdateAgentApiV1AgentsAgentIdPatchUrl(agentId), + getUpdateAgentApiV1AgentsAgentIdPatchUrl(agentId, params), { ...options, method: "PATCH", @@ -578,14 +803,22 @@ export const getUpdateAgentApiV1AgentsAgentIdPatchMutationOptions = < mutation?: UseMutationOptions< Awaited>, TError, - { agentId: string; data: AgentUpdate }, + { + agentId: string; + data: AgentUpdate; + params?: UpdateAgentApiV1AgentsAgentIdPatchParams; + }, TContext >; request?: SecondParameter; }): UseMutationOptions< Awaited>, TError, - { agentId: string; data: AgentUpdate }, + { + agentId: string; + data: AgentUpdate; + params?: UpdateAgentApiV1AgentsAgentIdPatchParams; + }, TContext > => { const mutationKey = ["updateAgentApiV1AgentsAgentIdPatch"]; @@ -599,11 +832,20 @@ export const getUpdateAgentApiV1AgentsAgentIdPatchMutationOptions = < const mutationFn: MutationFunction< Awaited>, - { agentId: string; data: AgentUpdate } + { + agentId: string; + data: AgentUpdate; + params?: UpdateAgentApiV1AgentsAgentIdPatchParams; + } > = (props) => { - const { agentId, data } = props ?? {}; + const { agentId, data, params } = props ?? {}; - return updateAgentApiV1AgentsAgentIdPatch(agentId, data, requestOptions); + return updateAgentApiV1AgentsAgentIdPatch( + agentId, + data, + params, + requestOptions, + ); }; return { mutationFn, ...mutationOptions }; @@ -627,7 +869,11 @@ export const useUpdateAgentApiV1AgentsAgentIdPatch = < mutation?: UseMutationOptions< Awaited>, TError, - { agentId: string; data: AgentUpdate }, + { + agentId: string; + data: AgentUpdate; + params?: UpdateAgentApiV1AgentsAgentIdPatchParams; + }, TContext >; request?: SecondParameter; @@ -636,7 +882,11 @@ export const useUpdateAgentApiV1AgentsAgentIdPatch = < ): UseMutationResult< Awaited>, TError, - { agentId: string; data: AgentUpdate }, + { + agentId: string; + data: AgentUpdate; + params?: UpdateAgentApiV1AgentsAgentIdPatchParams; + }, TContext > => { return useMutation( @@ -648,7 +898,7 @@ export const useUpdateAgentApiV1AgentsAgentIdPatch = < * @summary Delete Agent */ export type deleteAgentApiV1AgentsAgentIdDeleteResponse200 = { - data: DeleteAgentApiV1AgentsAgentIdDelete200; + data: OkResponse; status: 200; }; @@ -1014,302 +1264,3 @@ export const useHeartbeatOrCreateAgentApiV1AgentsHeartbeatPost = < queryClient, ); }; -/** - * @summary Confirm Provision Agent - */ -export type confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponse200 = - { - data: ConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost200; - status: 200; - }; - -export type confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponse422 = - { - data: HTTPValidationError; - status: 422; - }; - -export type confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponseSuccess = - confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponse200 & { - headers: Headers; - }; -export type confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponseError = - confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponse422 & { - headers: Headers; - }; - -export type confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponse = - - | confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponseSuccess - | confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostResponseError; - -export const getConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostUrl = - (agentId: string) => { - return `/api/v1/agents/${agentId}/provision/confirm`; - }; - -export const confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost = - async ( - agentId: string, - agentProvisionConfirm: AgentProvisionConfirm, - options?: RequestInit, - ): Promise => { - return customFetch( - getConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostUrl( - agentId, - ), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(agentProvisionConfirm), - }, - ); - }; - -export const getConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost - > - >, - TError, - { agentId: string; data: AgentProvisionConfirm }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType< - typeof confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost - > - >, - TError, - { agentId: string; data: AgentProvisionConfirm }, - TContext - > => { - const mutationKey = [ - "confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost", - ]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType< - typeof confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost - > - >, - { agentId: string; data: AgentProvisionConfirm } - > = (props) => { - const { agentId, data } = props ?? {}; - - return confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost( - agentId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type ConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostMutationResult = - NonNullable< - Awaited< - ReturnType< - typeof confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost - > - > - >; -export type ConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostMutationBody = - AgentProvisionConfirm; -export type ConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostMutationError = - HTTPValidationError; - -/** - * @summary Confirm Provision Agent - */ -export const useConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost - > - >, - TError, - { agentId: string; data: AgentProvisionConfirm }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType< - typeof confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost - > - >, - TError, - { agentId: string; data: AgentProvisionConfirm }, - TContext -> => { - return useMutation( - getConfirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPostMutationOptions( - options, - ), - queryClient, - ); -}; -/** - * @summary Confirm Delete Agent - */ -export type confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponse200 = { - data: ConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200; - status: 200; -}; - -export type confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponseSuccess = - confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponse200 & { - headers: Headers; - }; -export type confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponseError = - confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponse422 & { - headers: Headers; - }; - -export type confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponse = - | confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponseSuccess - | confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostResponseError; - -export const getConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostUrl = ( - agentId: string, -) => { - return `/api/v1/agents/${agentId}/delete/confirm`; -}; - -export const confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost = async ( - agentId: string, - agentDeleteConfirm: AgentDeleteConfirm, - options?: RequestInit, -): Promise => { - return customFetch( - getConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostUrl(agentId), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(agentDeleteConfirm), - }, - ); -}; - -export const getConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { agentId: string; data: AgentDeleteConfirm }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { agentId: string; data: AgentDeleteConfirm }, - TContext - > => { - const mutationKey = [ - "confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost", - ]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType - >, - { agentId: string; data: AgentDeleteConfirm } - > = (props) => { - const { agentId, data } = props ?? {}; - - return confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost( - agentId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type ConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostMutationResult = - NonNullable< - Awaited< - ReturnType - > - >; -export type ConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostMutationBody = - AgentDeleteConfirm; -export type ConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostMutationError = - HTTPValidationError; - -/** - * @summary Confirm Delete Agent - */ -export const useConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { agentId: string; data: AgentDeleteConfirm }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType - >, - TError, - { agentId: string; data: AgentDeleteConfirm }, - TContext -> => { - return useMutation( - getConfirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPostMutationOptions( - options, - ), - queryClient, - ); -}; diff --git a/frontend/src/api/generated/approvals/approvals.ts b/frontend/src/api/generated/approvals/approvals.ts new file mode 100644 index 00000000..e84364fe --- /dev/null +++ b/frontend/src/api/generated/approvals/approvals.ts @@ -0,0 +1,855 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + MutationFunction, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; + +import type { + ApprovalCreate, + ApprovalRead, + ApprovalUpdate, + HTTPValidationError, + ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, +} from ".././model"; + +import { customFetch } from "../../mutator"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary List Approvals + */ +export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse200 = { + data: ApprovalRead[]; + status: 200; +}; + +export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponseSuccess = + listApprovalsApiV1BoardsBoardIdApprovalsGetResponse200 & { + headers: Headers; + }; +export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponseError = + listApprovalsApiV1BoardsBoardIdApprovalsGetResponse422 & { + headers: Headers; + }; + +export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse = + | listApprovalsApiV1BoardsBoardIdApprovalsGetResponseSuccess + | listApprovalsApiV1BoardsBoardIdApprovalsGetResponseError; + +export const getListApprovalsApiV1BoardsBoardIdApprovalsGetUrl = ( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/boards/${boardId}/approvals?${stringifiedParams}` + : `/api/v1/boards/${boardId}/approvals`; +}; + +export const listApprovalsApiV1BoardsBoardIdApprovalsGet = async ( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListApprovalsApiV1BoardsBoardIdApprovalsGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListApprovalsApiV1BoardsBoardIdApprovalsGetQueryKey = ( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, +) => { + return [ + `/api/v1/boards/${boardId}/approvals`, + ...(params ? [params] : []), + ] as const; +}; + +export const getListApprovalsApiV1BoardsBoardIdApprovalsGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListApprovalsApiV1BoardsBoardIdApprovalsGetQueryKey(boardId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listApprovalsApiV1BoardsBoardIdApprovalsGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListApprovalsApiV1BoardsBoardIdApprovalsGetQueryResult = + NonNullable< + Awaited> + >; +export type ListApprovalsApiV1BoardsBoardIdApprovalsGetQueryError = + HTTPValidationError; + +export function useListApprovalsApiV1BoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListApprovalsApiV1BoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListApprovalsApiV1BoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Approvals + */ + +export function useListApprovalsApiV1BoardsBoardIdApprovalsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListApprovalsApiV1BoardsBoardIdApprovalsGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Approval + */ +export type createApprovalApiV1BoardsBoardIdApprovalsPostResponse200 = { + data: ApprovalRead; + status: 200; +}; + +export type createApprovalApiV1BoardsBoardIdApprovalsPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createApprovalApiV1BoardsBoardIdApprovalsPostResponseSuccess = + createApprovalApiV1BoardsBoardIdApprovalsPostResponse200 & { + headers: Headers; + }; +export type createApprovalApiV1BoardsBoardIdApprovalsPostResponseError = + createApprovalApiV1BoardsBoardIdApprovalsPostResponse422 & { + headers: Headers; + }; + +export type createApprovalApiV1BoardsBoardIdApprovalsPostResponse = + | createApprovalApiV1BoardsBoardIdApprovalsPostResponseSuccess + | createApprovalApiV1BoardsBoardIdApprovalsPostResponseError; + +export const getCreateApprovalApiV1BoardsBoardIdApprovalsPostUrl = ( + boardId: string, +) => { + return `/api/v1/boards/${boardId}/approvals`; +}; + +export const createApprovalApiV1BoardsBoardIdApprovalsPost = async ( + boardId: string, + approvalCreate: ApprovalCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateApprovalApiV1BoardsBoardIdApprovalsPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(approvalCreate), + }, + ); +}; + +export const getCreateApprovalApiV1BoardsBoardIdApprovalsPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: ApprovalCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: ApprovalCreate }, + TContext +> => { + const mutationKey = ["createApprovalApiV1BoardsBoardIdApprovalsPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { boardId: string; data: ApprovalCreate } + > = (props) => { + const { boardId, data } = props ?? {}; + + return createApprovalApiV1BoardsBoardIdApprovalsPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type CreateApprovalApiV1BoardsBoardIdApprovalsPostMutationResult = + NonNullable< + Awaited> + >; +export type CreateApprovalApiV1BoardsBoardIdApprovalsPostMutationBody = + ApprovalCreate; +export type CreateApprovalApiV1BoardsBoardIdApprovalsPostMutationError = + HTTPValidationError; + +/** + * @summary Create Approval + */ +export const useCreateApprovalApiV1BoardsBoardIdApprovalsPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: ApprovalCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { boardId: string; data: ApprovalCreate }, + TContext +> => { + return useMutation( + getCreateApprovalApiV1BoardsBoardIdApprovalsPostMutationOptions(options), + queryClient, + ); +}; +/** + * @summary Stream Approvals + */ +export type streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponse200 = { + data: unknown; + status: 200; +}; + +export type streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponseSuccess = + streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponse200 & { + headers: Headers; + }; +export type streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponseError = + streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponse422 & { + headers: Headers; + }; + +export type streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponse = + | streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponseSuccess + | streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetResponseError; + +export const getStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetUrl = ( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/boards/${boardId}/approvals/stream?${stringifiedParams}` + : `/api/v1/boards/${boardId}/approvals/stream`; +}; + +export const streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet = async ( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetQueryKey = ( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, +) => { + return [ + `/api/v1/boards/${boardId}/approvals/stream`, + ...(params ? [params] : []), + ] as const; +}; + +export const getStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetQueryOptions = + < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, + >( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetQueryKey( + boardId, + params, + ); + + const queryFn: QueryFunction< + Awaited< + ReturnType + > + > = ({ signal }) => + streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > & { queryKey: DataTag }; + }; + +export type StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetQueryResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetQueryError = + HTTPValidationError; + +export function useStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType< + typeof streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet + > + >, + TError, + Awaited< + ReturnType< + typeof streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType< + typeof streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet + > + >, + TError, + Awaited< + ReturnType< + typeof streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Stream Approvals + */ + +export function useStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getStreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Update Approval + */ +export type updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponse200 = + { + data: ApprovalRead; + status: 200; + }; + +export type updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponseSuccess = + updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponse200 & { + headers: Headers; + }; +export type updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponseError = + updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponse422 & { + headers: Headers; + }; + +export type updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponse = + | updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponseSuccess + | updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchResponseError; + +export const getUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchUrl = ( + boardId: string, + approvalId: string, +) => { + return `/api/v1/boards/${boardId}/approvals/${approvalId}`; +}; + +export const updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch = async ( + boardId: string, + approvalId: string, + approvalUpdate: ApprovalUpdate, + options?: RequestInit, +): Promise => { + return customFetch( + getUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchUrl( + boardId, + approvalId, + ), + { + ...options, + method: "PATCH", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(approvalUpdate), + }, + ); +}; + +export const getUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch + > + >, + TError, + { boardId: string; approvalId: string; data: ApprovalUpdate }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch + > + >, + TError, + { boardId: string; approvalId: string; data: ApprovalUpdate }, + TContext + > => { + const mutationKey = [ + "updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch + > + >, + { boardId: string; approvalId: string; data: ApprovalUpdate } + > = (props) => { + const { boardId, approvalId, data } = props ?? {}; + + return updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch( + boardId, + approvalId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type UpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch + > + > + >; +export type UpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchMutationBody = + ApprovalUpdate; +export type UpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchMutationError = + HTTPValidationError; + +/** + * @summary Update Approval + */ +export const useUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch + > + >, + TError, + { boardId: string; approvalId: string; data: ApprovalUpdate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; approvalId: string; data: ApprovalUpdate }, + TContext +> => { + return useMutation( + getUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatchMutationOptions( + options, + ), + queryClient, + ); +}; diff --git a/frontend/src/api/generated/board-memory/board-memory.ts b/frontend/src/api/generated/board-memory/board-memory.ts new file mode 100644 index 00000000..f2bd2589 --- /dev/null +++ b/frontend/src/api/generated/board-memory/board-memory.ts @@ -0,0 +1,689 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + MutationFunction, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; + +import type { + BoardMemoryCreate, + BoardMemoryRead, + HTTPValidationError, + ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, +} from ".././model"; + +import { customFetch } from "../../mutator"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary List Board Memory + */ +export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse200 = { + data: BoardMemoryRead[]; + status: 200; +}; + +export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponseSuccess = + listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse200 & { + headers: Headers; + }; +export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponseError = + listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse422 & { + headers: Headers; + }; + +export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse = + | listBoardMemoryApiV1BoardsBoardIdMemoryGetResponseSuccess + | listBoardMemoryApiV1BoardsBoardIdMemoryGetResponseError; + +export const getListBoardMemoryApiV1BoardsBoardIdMemoryGetUrl = ( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/boards/${boardId}/memory?${stringifiedParams}` + : `/api/v1/boards/${boardId}/memory`; +}; + +export const listBoardMemoryApiV1BoardsBoardIdMemoryGet = async ( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListBoardMemoryApiV1BoardsBoardIdMemoryGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListBoardMemoryApiV1BoardsBoardIdMemoryGetQueryKey = ( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, +) => { + return [ + `/api/v1/boards/${boardId}/memory`, + ...(params ? [params] : []), + ] as const; +}; + +export const getListBoardMemoryApiV1BoardsBoardIdMemoryGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListBoardMemoryApiV1BoardsBoardIdMemoryGetQueryKey(boardId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listBoardMemoryApiV1BoardsBoardIdMemoryGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetQueryResult = NonNullable< + Awaited> +>; +export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetQueryError = + HTTPValidationError; + +export function useListBoardMemoryApiV1BoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListBoardMemoryApiV1BoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListBoardMemoryApiV1BoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Board Memory + */ + +export function useListBoardMemoryApiV1BoardsBoardIdMemoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListBoardMemoryApiV1BoardsBoardIdMemoryGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Board Memory + */ +export type createBoardMemoryApiV1BoardsBoardIdMemoryPostResponse200 = { + data: BoardMemoryRead; + status: 200; +}; + +export type createBoardMemoryApiV1BoardsBoardIdMemoryPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createBoardMemoryApiV1BoardsBoardIdMemoryPostResponseSuccess = + createBoardMemoryApiV1BoardsBoardIdMemoryPostResponse200 & { + headers: Headers; + }; +export type createBoardMemoryApiV1BoardsBoardIdMemoryPostResponseError = + createBoardMemoryApiV1BoardsBoardIdMemoryPostResponse422 & { + headers: Headers; + }; + +export type createBoardMemoryApiV1BoardsBoardIdMemoryPostResponse = + | createBoardMemoryApiV1BoardsBoardIdMemoryPostResponseSuccess + | createBoardMemoryApiV1BoardsBoardIdMemoryPostResponseError; + +export const getCreateBoardMemoryApiV1BoardsBoardIdMemoryPostUrl = ( + boardId: string, +) => { + return `/api/v1/boards/${boardId}/memory`; +}; + +export const createBoardMemoryApiV1BoardsBoardIdMemoryPost = async ( + boardId: string, + boardMemoryCreate: BoardMemoryCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateBoardMemoryApiV1BoardsBoardIdMemoryPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(boardMemoryCreate), + }, + ); +}; + +export const getCreateBoardMemoryApiV1BoardsBoardIdMemoryPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext +> => { + const mutationKey = ["createBoardMemoryApiV1BoardsBoardIdMemoryPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { boardId: string; data: BoardMemoryCreate } + > = (props) => { + const { boardId, data } = props ?? {}; + + return createBoardMemoryApiV1BoardsBoardIdMemoryPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type CreateBoardMemoryApiV1BoardsBoardIdMemoryPostMutationResult = + NonNullable< + Awaited> + >; +export type CreateBoardMemoryApiV1BoardsBoardIdMemoryPostMutationBody = + BoardMemoryCreate; +export type CreateBoardMemoryApiV1BoardsBoardIdMemoryPostMutationError = + HTTPValidationError; + +/** + * @summary Create Board Memory + */ +export const useCreateBoardMemoryApiV1BoardsBoardIdMemoryPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { boardId: string; data: BoardMemoryCreate }, + TContext +> => { + return useMutation( + getCreateBoardMemoryApiV1BoardsBoardIdMemoryPostMutationOptions(options), + queryClient, + ); +}; +/** + * @summary Stream Board Memory + */ +export type streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponse200 = { + data: unknown; + status: 200; +}; + +export type streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponseSuccess = + streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponse200 & { + headers: Headers; + }; +export type streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponseError = + streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponse422 & { + headers: Headers; + }; + +export type streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponse = + | streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponseSuccess + | streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetResponseError; + +export const getStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetUrl = ( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/boards/${boardId}/memory/stream?${stringifiedParams}` + : `/api/v1/boards/${boardId}/memory/stream`; +}; + +export const streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet = async ( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetQueryKey = ( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, +) => { + return [ + `/api/v1/boards/${boardId}/memory/stream`, + ...(params ? [params] : []), + ] as const; +}; + +export const getStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetQueryOptions = + < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, + >( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetQueryKey( + boardId, + params, + ); + + const queryFn: QueryFunction< + Awaited< + ReturnType + > + > = ({ signal }) => + streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > & { queryKey: DataTag }; + }; + +export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetQueryResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetQueryError = + HTTPValidationError; + +export function useStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType< + typeof streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet + > + >, + TError, + Awaited< + ReturnType< + typeof streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType< + typeof streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet + > + >, + TError, + Awaited< + ReturnType< + typeof streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Stream Board Memory + */ + +export function useStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getStreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/frontend/src/api/generated/board-onboarding/board-onboarding.ts b/frontend/src/api/generated/board-onboarding/board-onboarding.ts new file mode 100644 index 00000000..f063822b --- /dev/null +++ b/frontend/src/api/generated/board-onboarding/board-onboarding.ts @@ -0,0 +1,892 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + MutationFunction, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; + +import type { + BoardOnboardingAgentComplete, + BoardOnboardingAgentQuestion, + BoardOnboardingAnswer, + BoardOnboardingConfirm, + BoardOnboardingRead, + BoardOnboardingStart, + BoardRead, + HTTPValidationError, +} from ".././model"; + +import { customFetch } from "../../mutator"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary Get Onboarding + */ +export type getOnboardingApiV1BoardsBoardIdOnboardingGetResponse200 = { + data: BoardOnboardingRead; + status: 200; +}; + +export type getOnboardingApiV1BoardsBoardIdOnboardingGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type getOnboardingApiV1BoardsBoardIdOnboardingGetResponseSuccess = + getOnboardingApiV1BoardsBoardIdOnboardingGetResponse200 & { + headers: Headers; + }; +export type getOnboardingApiV1BoardsBoardIdOnboardingGetResponseError = + getOnboardingApiV1BoardsBoardIdOnboardingGetResponse422 & { + headers: Headers; + }; + +export type getOnboardingApiV1BoardsBoardIdOnboardingGetResponse = + | getOnboardingApiV1BoardsBoardIdOnboardingGetResponseSuccess + | getOnboardingApiV1BoardsBoardIdOnboardingGetResponseError; + +export const getGetOnboardingApiV1BoardsBoardIdOnboardingGetUrl = ( + boardId: string, +) => { + return `/api/v1/boards/${boardId}/onboarding`; +}; + +export const getOnboardingApiV1BoardsBoardIdOnboardingGet = async ( + boardId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getGetOnboardingApiV1BoardsBoardIdOnboardingGetUrl(boardId), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetOnboardingApiV1BoardsBoardIdOnboardingGetQueryKey = ( + boardId: string, +) => { + return [`/api/v1/boards/${boardId}/onboarding`] as const; +}; + +export const getGetOnboardingApiV1BoardsBoardIdOnboardingGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetOnboardingApiV1BoardsBoardIdOnboardingGetQueryKey(boardId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getOnboardingApiV1BoardsBoardIdOnboardingGet(boardId, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetOnboardingApiV1BoardsBoardIdOnboardingGetQueryResult = + NonNullable< + Awaited> + >; +export type GetOnboardingApiV1BoardsBoardIdOnboardingGetQueryError = + HTTPValidationError; + +export function useGetOnboardingApiV1BoardsBoardIdOnboardingGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetOnboardingApiV1BoardsBoardIdOnboardingGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetOnboardingApiV1BoardsBoardIdOnboardingGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Onboarding + */ + +export function useGetOnboardingApiV1BoardsBoardIdOnboardingGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getGetOnboardingApiV1BoardsBoardIdOnboardingGetQueryOptions( + boardId, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Start Onboarding + */ +export type startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponse200 = { + data: BoardOnboardingRead; + status: 200; +}; + +export type startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponseSuccess = + startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponse200 & { + headers: Headers; + }; +export type startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponseError = + startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponse422 & { + headers: Headers; + }; + +export type startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponse = + | startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponseSuccess + | startOnboardingApiV1BoardsBoardIdOnboardingStartPostResponseError; + +export const getStartOnboardingApiV1BoardsBoardIdOnboardingStartPostUrl = ( + boardId: string, +) => { + return `/api/v1/boards/${boardId}/onboarding/start`; +}; + +export const startOnboardingApiV1BoardsBoardIdOnboardingStartPost = async ( + boardId: string, + boardOnboardingStart: BoardOnboardingStart, + options?: RequestInit, +): Promise => { + return customFetch( + getStartOnboardingApiV1BoardsBoardIdOnboardingStartPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(boardOnboardingStart), + }, + ); +}; + +export const getStartOnboardingApiV1BoardsBoardIdOnboardingStartPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingStart }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingStart }, + TContext + > => { + const mutationKey = [ + "startOnboardingApiV1BoardsBoardIdOnboardingStartPost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType + >, + { boardId: string; data: BoardOnboardingStart } + > = (props) => { + const { boardId, data } = props ?? {}; + + return startOnboardingApiV1BoardsBoardIdOnboardingStartPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type StartOnboardingApiV1BoardsBoardIdOnboardingStartPostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type StartOnboardingApiV1BoardsBoardIdOnboardingStartPostMutationBody = + BoardOnboardingStart; +export type StartOnboardingApiV1BoardsBoardIdOnboardingStartPostMutationError = + HTTPValidationError; + +/** + * @summary Start Onboarding + */ +export const useStartOnboardingApiV1BoardsBoardIdOnboardingStartPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingStart }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingStart }, + TContext +> => { + return useMutation( + getStartOnboardingApiV1BoardsBoardIdOnboardingStartPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Answer Onboarding + */ +export type answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponse200 = + { + data: BoardOnboardingRead; + status: 200; + }; + +export type answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponseSuccess = + answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponse200 & { + headers: Headers; + }; +export type answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponseError = + answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponse422 & { + headers: Headers; + }; + +export type answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponse = + | answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponseSuccess + | answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostResponseError; + +export const getAnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostUrl = ( + boardId: string, +) => { + return `/api/v1/boards/${boardId}/onboarding/answer`; +}; + +export const answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost = async ( + boardId: string, + boardOnboardingAnswer: BoardOnboardingAnswer, + options?: RequestInit, +): Promise => { + return customFetch( + getAnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(boardOnboardingAnswer), + }, + ); +}; + +export const getAnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost + > + >, + TError, + { boardId: string; data: BoardOnboardingAnswer }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingAnswer }, + TContext + > => { + const mutationKey = [ + "answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost + > + >, + { boardId: string; data: BoardOnboardingAnswer } + > = (props) => { + const { boardId, data } = props ?? {}; + + return answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type AnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type AnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostMutationBody = + BoardOnboardingAnswer; +export type AnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostMutationError = + HTTPValidationError; + +/** + * @summary Answer Onboarding + */ +export const useAnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost + > + >, + TError, + { boardId: string; data: BoardOnboardingAnswer }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingAnswer }, + TContext +> => { + return useMutation( + getAnswerOnboardingApiV1BoardsBoardIdOnboardingAnswerPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Agent Onboarding Update + */ +export type agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponse200 = + { + data: BoardOnboardingRead; + status: 200; + }; + +export type agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponseSuccess = + agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponse200 & { + headers: Headers; + }; +export type agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponseError = + agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponse422 & { + headers: Headers; + }; + +export type agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponse = + | agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponseSuccess + | agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostResponseError; + +export const getAgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostUrl = + (boardId: string) => { + return `/api/v1/boards/${boardId}/onboarding/agent`; + }; + +export const agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost = + async ( + boardId: string, + boardOnboardingAgentCompleteBoardOnboardingAgentQuestion: + | BoardOnboardingAgentComplete + | BoardOnboardingAgentQuestion, + options?: RequestInit, + ): Promise => { + return customFetch( + getAgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify( + boardOnboardingAgentCompleteBoardOnboardingAgentQuestion, + ), + }, + ); + }; + +export const getAgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost + > + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost + > + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext + > => { + const mutationKey = [ + "agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost + > + >, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + } + > = (props) => { + const { boardId, data } = props ?? {}; + + return agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type AgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost + > + > + >; +export type AgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostMutationBody = + BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; +export type AgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostMutationError = + HTTPValidationError; + +/** + * @summary Agent Onboarding Update + */ +export const useAgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost + > + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType< + typeof agentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPost + > + >, + TError, + { + boardId: string; + data: BoardOnboardingAgentComplete | BoardOnboardingAgentQuestion; + }, + TContext +> => { + return useMutation( + getAgentOnboardingUpdateApiV1BoardsBoardIdOnboardingAgentPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Confirm Onboarding + */ +export type confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponse200 = + { + data: BoardRead; + status: 200; + }; + +export type confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponseSuccess = + confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponse200 & { + headers: Headers; + }; +export type confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponseError = + confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponse422 & { + headers: Headers; + }; + +export type confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponse = + | confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponseSuccess + | confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostResponseError; + +export const getConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostUrl = ( + boardId: string, +) => { + return `/api/v1/boards/${boardId}/onboarding/confirm`; +}; + +export const confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost = async ( + boardId: string, + boardOnboardingConfirm: BoardOnboardingConfirm, + options?: RequestInit, +): Promise => { + return customFetch( + getConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostUrl(boardId), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(boardOnboardingConfirm), + }, + ); +}; + +export const getConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost + > + >, + TError, + { boardId: string; data: BoardOnboardingConfirm }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost + > + >, + TError, + { boardId: string; data: BoardOnboardingConfirm }, + TContext + > => { + const mutationKey = [ + "confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost + > + >, + { boardId: string; data: BoardOnboardingConfirm } + > = (props) => { + const { boardId, data } = props ?? {}; + + return confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost( + boardId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type ConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost + > + > + >; +export type ConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostMutationBody = + BoardOnboardingConfirm; +export type ConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostMutationError = + HTTPValidationError; + +/** + * @summary Confirm Onboarding + */ +export const useConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost + > + >, + TError, + { boardId: string; data: BoardOnboardingConfirm }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { boardId: string; data: BoardOnboardingConfirm }, + TContext +> => { + return useMutation( + getConfirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPostMutationOptions( + options, + ), + queryClient, + ); +}; diff --git a/frontend/src/api/generated/boards/boards.ts b/frontend/src/api/generated/boards/boards.ts index fe993b75..4f1def92 100644 --- a/frontend/src/api/generated/boards/boards.ts +++ b/frontend/src/api/generated/boards/boards.ts @@ -24,8 +24,8 @@ import type { BoardCreate, BoardRead, BoardUpdate, - DeleteBoardApiV1BoardsBoardIdDelete200, HTTPValidationError, + OkResponse, } from ".././model"; import { customFetch } from "../../mutator"; @@ -653,7 +653,7 @@ export const useUpdateBoardApiV1BoardsBoardIdPatch = < * @summary Delete Board */ export type deleteBoardApiV1BoardsBoardIdDeleteResponse200 = { - data: DeleteBoardApiV1BoardsBoardIdDelete200; + data: OkResponse; status: 200; }; diff --git a/frontend/src/api/generated/gateways/gateways.ts b/frontend/src/api/generated/gateways/gateways.ts new file mode 100644 index 00000000..70064c31 --- /dev/null +++ b/frontend/src/api/generated/gateways/gateways.ts @@ -0,0 +1,2191 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + MutationFunction, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; + +import type { + GatewayCommandsResponse, + GatewayCreate, + GatewayRead, + GatewaySessionHistoryResponse, + GatewaySessionMessageRequest, + GatewaySessionResponse, + GatewaySessionsResponse, + GatewayUpdate, + GatewaysStatusApiV1GatewaysStatusGetParams, + GatewaysStatusResponse, + GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + HTTPValidationError, + ListGatewaySessionsApiV1GatewaysSessionsGetParams, + OkResponse, + SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams, +} from ".././model"; + +import { customFetch } from "../../mutator"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary Gateways Status + */ +export type gatewaysStatusApiV1GatewaysStatusGetResponse200 = { + data: GatewaysStatusResponse; + status: 200; +}; + +export type gatewaysStatusApiV1GatewaysStatusGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type gatewaysStatusApiV1GatewaysStatusGetResponseSuccess = + gatewaysStatusApiV1GatewaysStatusGetResponse200 & { + headers: Headers; + }; +export type gatewaysStatusApiV1GatewaysStatusGetResponseError = + gatewaysStatusApiV1GatewaysStatusGetResponse422 & { + headers: Headers; + }; + +export type gatewaysStatusApiV1GatewaysStatusGetResponse = + | gatewaysStatusApiV1GatewaysStatusGetResponseSuccess + | gatewaysStatusApiV1GatewaysStatusGetResponseError; + +export const getGatewaysStatusApiV1GatewaysStatusGetUrl = ( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/gateways/status?${stringifiedParams}` + : `/api/v1/gateways/status`; +}; + +export const gatewaysStatusApiV1GatewaysStatusGet = async ( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getGatewaysStatusApiV1GatewaysStatusGetUrl(params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGatewaysStatusApiV1GatewaysStatusGetQueryKey = ( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, +) => { + return [`/api/v1/gateways/status`, ...(params ? [params] : [])] as const; +}; + +export const getGatewaysStatusApiV1GatewaysStatusGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGatewaysStatusApiV1GatewaysStatusGetQueryKey(params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + gatewaysStatusApiV1GatewaysStatusGet(params, { signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GatewaysStatusApiV1GatewaysStatusGetQueryResult = NonNullable< + Awaited> +>; +export type GatewaysStatusApiV1GatewaysStatusGetQueryError = + HTTPValidationError; + +export function useGatewaysStatusApiV1GatewaysStatusGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params: undefined | GatewaysStatusApiV1GatewaysStatusGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGatewaysStatusApiV1GatewaysStatusGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGatewaysStatusApiV1GatewaysStatusGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Gateways Status + */ + +export function useGatewaysStatusApiV1GatewaysStatusGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: GatewaysStatusApiV1GatewaysStatusGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGatewaysStatusApiV1GatewaysStatusGetQueryOptions( + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary List Gateway Sessions + */ +export type listGatewaySessionsApiV1GatewaysSessionsGetResponse200 = { + data: GatewaySessionsResponse; + status: 200; +}; + +export type listGatewaySessionsApiV1GatewaysSessionsGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type listGatewaySessionsApiV1GatewaysSessionsGetResponseSuccess = + listGatewaySessionsApiV1GatewaysSessionsGetResponse200 & { + headers: Headers; + }; +export type listGatewaySessionsApiV1GatewaysSessionsGetResponseError = + listGatewaySessionsApiV1GatewaysSessionsGetResponse422 & { + headers: Headers; + }; + +export type listGatewaySessionsApiV1GatewaysSessionsGetResponse = + | listGatewaySessionsApiV1GatewaysSessionsGetResponseSuccess + | listGatewaySessionsApiV1GatewaysSessionsGetResponseError; + +export const getListGatewaySessionsApiV1GatewaysSessionsGetUrl = ( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/gateways/sessions?${stringifiedParams}` + : `/api/v1/gateways/sessions`; +}; + +export const listGatewaySessionsApiV1GatewaysSessionsGet = async ( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getListGatewaySessionsApiV1GatewaysSessionsGetUrl(params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListGatewaySessionsApiV1GatewaysSessionsGetQueryKey = ( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, +) => { + return [`/api/v1/gateways/sessions`, ...(params ? [params] : [])] as const; +}; + +export const getListGatewaySessionsApiV1GatewaysSessionsGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getListGatewaySessionsApiV1GatewaysSessionsGetQueryKey(params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listGatewaySessionsApiV1GatewaysSessionsGet(params, { + signal, + ...requestOptions, + }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListGatewaySessionsApiV1GatewaysSessionsGetQueryResult = + NonNullable< + Awaited> + >; +export type ListGatewaySessionsApiV1GatewaysSessionsGetQueryError = + HTTPValidationError; + +export function useListGatewaySessionsApiV1GatewaysSessionsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + params: undefined | ListGatewaySessionsApiV1GatewaysSessionsGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListGatewaySessionsApiV1GatewaysSessionsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListGatewaySessionsApiV1GatewaysSessionsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Gateway Sessions + */ + +export function useListGatewaySessionsApiV1GatewaysSessionsGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + params?: ListGatewaySessionsApiV1GatewaysSessionsGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListGatewaySessionsApiV1GatewaysSessionsGetQueryOptions(params, options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Get Gateway Session + */ +export type getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponse200 = { + data: GatewaySessionResponse; + status: 200; +}; + +export type getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponseSuccess = + getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponse200 & { + headers: Headers; + }; +export type getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponseError = + getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponse422 & { + headers: Headers; + }; + +export type getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponse = + | getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponseSuccess + | getGatewaySessionApiV1GatewaysSessionsSessionIdGetResponseError; + +export const getGetGatewaySessionApiV1GatewaysSessionsSessionIdGetUrl = ( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/gateways/sessions/${sessionId}?${stringifiedParams}` + : `/api/v1/gateways/sessions/${sessionId}`; +}; + +export const getGatewaySessionApiV1GatewaysSessionsSessionIdGet = async ( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getGetGatewaySessionApiV1GatewaysSessionsSessionIdGetUrl(sessionId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetGatewaySessionApiV1GatewaysSessionsSessionIdGetQueryKey = ( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, +) => { + return [ + `/api/v1/gateways/sessions/${sessionId}`, + ...(params ? [params] : []), + ] as const; +}; + +export const getGetGatewaySessionApiV1GatewaysSessionsSessionIdGetQueryOptions = + < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, + >( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof getGatewaySessionApiV1GatewaysSessionsSessionIdGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetGatewaySessionApiV1GatewaysSessionsSessionIdGetQueryKey( + sessionId, + params, + ); + + const queryFn: QueryFunction< + Awaited< + ReturnType + > + > = ({ signal }) => + getGatewaySessionApiV1GatewaysSessionsSessionIdGet(sessionId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!sessionId, + ...queryOptions, + } as UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > & { queryKey: DataTag }; + }; + +export type GetGatewaySessionApiV1GatewaysSessionsSessionIdGetQueryResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type GetGatewaySessionApiV1GatewaysSessionsSessionIdGetQueryError = + HTTPValidationError; + +export function useGetGatewaySessionApiV1GatewaysSessionsSessionIdGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params: undefined | GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType< + typeof getGatewaySessionApiV1GatewaysSessionsSessionIdGet + > + >, + TError, + Awaited< + ReturnType< + typeof getGatewaySessionApiV1GatewaysSessionsSessionIdGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetGatewaySessionApiV1GatewaysSessionsSessionIdGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType< + typeof getGatewaySessionApiV1GatewaysSessionsSessionIdGet + > + >, + TError, + Awaited< + ReturnType< + typeof getGatewaySessionApiV1GatewaysSessionsSessionIdGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetGatewaySessionApiV1GatewaysSessionsSessionIdGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Gateway Session + */ + +export function useGetGatewaySessionApiV1GatewaysSessionsSessionIdGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params?: GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getGetGatewaySessionApiV1GatewaysSessionsSessionIdGetQueryOptions( + sessionId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Get Session History + */ +export type getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponse200 = + { + data: GatewaySessionHistoryResponse; + status: 200; + }; + +export type getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponseSuccess = + getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponse200 & { + headers: Headers; + }; +export type getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponseError = + getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponse422 & { + headers: Headers; + }; + +export type getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponse = + | getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponseSuccess + | getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetResponseError; + +export const getGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetUrl = ( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/gateways/sessions/${sessionId}/history?${stringifiedParams}` + : `/api/v1/gateways/sessions/${sessionId}/history`; +}; + +export const getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet = async ( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetUrl( + sessionId, + params, + ), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetQueryKey = + ( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + ) => { + return [ + `/api/v1/gateways/sessions/${sessionId}/history`, + ...(params ? [params] : []), + ] as const; + }; + +export const getGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetQueryOptions = + < + TData = Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError = HTTPValidationError, + >( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetQueryKey( + sessionId, + params, + ); + + const queryFn: QueryFunction< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + > + > = ({ signal }) => + getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet( + sessionId, + params, + { signal, ...requestOptions }, + ); + + return { + queryKey, + queryFn, + enabled: !!sessionId, + ...queryOptions, + } as UseQueryOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + TData + > & { queryKey: DataTag }; + }; + +export type GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetQueryResult = + NonNullable< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + > + >; +export type GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetQueryError = + HTTPValidationError; + +export function useGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params: + | undefined + | GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Session History + */ + +export function useGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + sessionId: string, + params?: GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited< + ReturnType< + typeof getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGet + > + >, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getGetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetQueryOptions( + sessionId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Send Gateway Session Message + */ +export type sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponse200 = + { + data: OkResponse; + status: 200; + }; + +export type sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponseSuccess = + sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponse200 & { + headers: Headers; + }; +export type sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponseError = + sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponse422 & { + headers: Headers; + }; + +export type sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponse = + + | sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponseSuccess + | sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostResponseError; + +export const getSendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostUrl = + ( + sessionId: string, + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams, + ) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append( + key, + value === null ? "null" : value.toString(), + ); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/gateways/sessions/${sessionId}/message?${stringifiedParams}` + : `/api/v1/gateways/sessions/${sessionId}/message`; + }; + +export const sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost = + async ( + sessionId: string, + gatewaySessionMessageRequest: GatewaySessionMessageRequest, + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams, + options?: RequestInit, + ): Promise => { + return customFetch( + getSendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostUrl( + sessionId, + params, + ), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(gatewaySessionMessageRequest), + }, + ); + }; + +export const getSendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost + > + >, + TError, + { + sessionId: string; + data: GatewaySessionMessageRequest; + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams; + }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost + > + >, + TError, + { + sessionId: string; + data: GatewaySessionMessageRequest; + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams; + }, + TContext + > => { + const mutationKey = [ + "sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost + > + >, + { + sessionId: string; + data: GatewaySessionMessageRequest; + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams; + } + > = (props) => { + const { sessionId, data, params } = props ?? {}; + + return sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost( + sessionId, + data, + params, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost + > + > + >; +export type SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostMutationBody = + GatewaySessionMessageRequest; +export type SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostMutationError = + HTTPValidationError; + +/** + * @summary Send Gateway Session Message + */ +export const useSendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost = + ( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost + > + >, + TError, + { + sessionId: string; + data: GatewaySessionMessageRequest; + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams; + }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, + ): UseMutationResult< + Awaited< + ReturnType< + typeof sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePost + > + >, + TError, + { + sessionId: string; + data: GatewaySessionMessageRequest; + params?: SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams; + }, + TContext + > => { + return useMutation( + getSendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostMutationOptions( + options, + ), + queryClient, + ); + }; +/** + * @summary Gateway Commands + */ +export type gatewayCommandsApiV1GatewaysCommandsGetResponse200 = { + data: GatewayCommandsResponse; + status: 200; +}; + +export type gatewayCommandsApiV1GatewaysCommandsGetResponseSuccess = + gatewayCommandsApiV1GatewaysCommandsGetResponse200 & { + headers: Headers; + }; +export type gatewayCommandsApiV1GatewaysCommandsGetResponse = + gatewayCommandsApiV1GatewaysCommandsGetResponseSuccess; + +export const getGatewayCommandsApiV1GatewaysCommandsGetUrl = () => { + return `/api/v1/gateways/commands`; +}; + +export const gatewayCommandsApiV1GatewaysCommandsGet = async ( + options?: RequestInit, +): Promise => { + return customFetch( + getGatewayCommandsApiV1GatewaysCommandsGetUrl(), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGatewayCommandsApiV1GatewaysCommandsGetQueryKey = () => { + return [`/api/v1/gateways/commands`] as const; +}; + +export const getGatewayCommandsApiV1GatewaysCommandsGetQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGatewayCommandsApiV1GatewaysCommandsGetQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + gatewayCommandsApiV1GatewaysCommandsGet({ signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GatewayCommandsApiV1GatewaysCommandsGetQueryResult = NonNullable< + Awaited> +>; +export type GatewayCommandsApiV1GatewaysCommandsGetQueryError = unknown; + +export function useGatewayCommandsApiV1GatewaysCommandsGet< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGatewayCommandsApiV1GatewaysCommandsGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGatewayCommandsApiV1GatewaysCommandsGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Gateway Commands + */ + +export function useGatewayCommandsApiV1GatewaysCommandsGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getGatewayCommandsApiV1GatewaysCommandsGetQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary List Gateways + */ +export type listGatewaysApiV1GatewaysGetResponse200 = { + data: GatewayRead[]; + status: 200; +}; + +export type listGatewaysApiV1GatewaysGetResponseSuccess = + listGatewaysApiV1GatewaysGetResponse200 & { + headers: Headers; + }; +export type listGatewaysApiV1GatewaysGetResponse = + listGatewaysApiV1GatewaysGetResponseSuccess; + +export const getListGatewaysApiV1GatewaysGetUrl = () => { + return `/api/v1/gateways`; +}; + +export const listGatewaysApiV1GatewaysGet = async ( + options?: RequestInit, +): Promise => { + return customFetch( + getListGatewaysApiV1GatewaysGetUrl(), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListGatewaysApiV1GatewaysGetQueryKey = () => { + return [`/api/v1/gateways`] as const; +}; + +export const getListGatewaysApiV1GatewaysGetQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getListGatewaysApiV1GatewaysGetQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listGatewaysApiV1GatewaysGet({ signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListGatewaysApiV1GatewaysGetQueryResult = NonNullable< + Awaited> +>; +export type ListGatewaysApiV1GatewaysGetQueryError = unknown; + +export function useListGatewaysApiV1GatewaysGet< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListGatewaysApiV1GatewaysGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListGatewaysApiV1GatewaysGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Gateways + */ + +export function useListGatewaysApiV1GatewaysGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getListGatewaysApiV1GatewaysGetQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Gateway + */ +export type createGatewayApiV1GatewaysPostResponse200 = { + data: GatewayRead; + status: 200; +}; + +export type createGatewayApiV1GatewaysPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createGatewayApiV1GatewaysPostResponseSuccess = + createGatewayApiV1GatewaysPostResponse200 & { + headers: Headers; + }; +export type createGatewayApiV1GatewaysPostResponseError = + createGatewayApiV1GatewaysPostResponse422 & { + headers: Headers; + }; + +export type createGatewayApiV1GatewaysPostResponse = + | createGatewayApiV1GatewaysPostResponseSuccess + | createGatewayApiV1GatewaysPostResponseError; + +export const getCreateGatewayApiV1GatewaysPostUrl = () => { + return `/api/v1/gateways`; +}; + +export const createGatewayApiV1GatewaysPost = async ( + gatewayCreate: GatewayCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateGatewayApiV1GatewaysPostUrl(), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(gatewayCreate), + }, + ); +}; + +export const getCreateGatewayApiV1GatewaysPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: GatewayCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { data: GatewayCreate }, + TContext +> => { + const mutationKey = ["createGatewayApiV1GatewaysPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { data: GatewayCreate } + > = (props) => { + const { data } = props ?? {}; + + return createGatewayApiV1GatewaysPost(data, requestOptions); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type CreateGatewayApiV1GatewaysPostMutationResult = NonNullable< + Awaited> +>; +export type CreateGatewayApiV1GatewaysPostMutationBody = GatewayCreate; +export type CreateGatewayApiV1GatewaysPostMutationError = HTTPValidationError; + +/** + * @summary Create Gateway + */ +export const useCreateGatewayApiV1GatewaysPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: GatewayCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { data: GatewayCreate }, + TContext +> => { + return useMutation( + getCreateGatewayApiV1GatewaysPostMutationOptions(options), + queryClient, + ); +}; +/** + * @summary Get Gateway + */ +export type getGatewayApiV1GatewaysGatewayIdGetResponse200 = { + data: GatewayRead; + status: 200; +}; + +export type getGatewayApiV1GatewaysGatewayIdGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type getGatewayApiV1GatewaysGatewayIdGetResponseSuccess = + getGatewayApiV1GatewaysGatewayIdGetResponse200 & { + headers: Headers; + }; +export type getGatewayApiV1GatewaysGatewayIdGetResponseError = + getGatewayApiV1GatewaysGatewayIdGetResponse422 & { + headers: Headers; + }; + +export type getGatewayApiV1GatewaysGatewayIdGetResponse = + | getGatewayApiV1GatewaysGatewayIdGetResponseSuccess + | getGatewayApiV1GatewaysGatewayIdGetResponseError; + +export const getGetGatewayApiV1GatewaysGatewayIdGetUrl = ( + gatewayId: string, +) => { + return `/api/v1/gateways/${gatewayId}`; +}; + +export const getGatewayApiV1GatewaysGatewayIdGet = async ( + gatewayId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getGetGatewayApiV1GatewaysGatewayIdGetUrl(gatewayId), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetGatewayApiV1GatewaysGatewayIdGetQueryKey = ( + gatewayId: string, +) => { + return [`/api/v1/gateways/${gatewayId}`] as const; +}; + +export const getGetGatewayApiV1GatewaysGatewayIdGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + gatewayId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetGatewayApiV1GatewaysGatewayIdGetQueryKey(gatewayId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getGatewayApiV1GatewaysGatewayIdGet(gatewayId, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!gatewayId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetGatewayApiV1GatewaysGatewayIdGetQueryResult = NonNullable< + Awaited> +>; +export type GetGatewayApiV1GatewaysGatewayIdGetQueryError = HTTPValidationError; + +export function useGetGatewayApiV1GatewaysGatewayIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + gatewayId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetGatewayApiV1GatewaysGatewayIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + gatewayId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetGatewayApiV1GatewaysGatewayIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + gatewayId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Gateway + */ + +export function useGetGatewayApiV1GatewaysGatewayIdGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + gatewayId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetGatewayApiV1GatewaysGatewayIdGetQueryOptions( + gatewayId, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Update Gateway + */ +export type updateGatewayApiV1GatewaysGatewayIdPatchResponse200 = { + data: GatewayRead; + status: 200; +}; + +export type updateGatewayApiV1GatewaysGatewayIdPatchResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type updateGatewayApiV1GatewaysGatewayIdPatchResponseSuccess = + updateGatewayApiV1GatewaysGatewayIdPatchResponse200 & { + headers: Headers; + }; +export type updateGatewayApiV1GatewaysGatewayIdPatchResponseError = + updateGatewayApiV1GatewaysGatewayIdPatchResponse422 & { + headers: Headers; + }; + +export type updateGatewayApiV1GatewaysGatewayIdPatchResponse = + | updateGatewayApiV1GatewaysGatewayIdPatchResponseSuccess + | updateGatewayApiV1GatewaysGatewayIdPatchResponseError; + +export const getUpdateGatewayApiV1GatewaysGatewayIdPatchUrl = ( + gatewayId: string, +) => { + return `/api/v1/gateways/${gatewayId}`; +}; + +export const updateGatewayApiV1GatewaysGatewayIdPatch = async ( + gatewayId: string, + gatewayUpdate: GatewayUpdate, + options?: RequestInit, +): Promise => { + return customFetch( + getUpdateGatewayApiV1GatewaysGatewayIdPatchUrl(gatewayId), + { + ...options, + method: "PATCH", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(gatewayUpdate), + }, + ); +}; + +export const getUpdateGatewayApiV1GatewaysGatewayIdPatchMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { gatewayId: string; data: GatewayUpdate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { gatewayId: string; data: GatewayUpdate }, + TContext +> => { + const mutationKey = ["updateGatewayApiV1GatewaysGatewayIdPatch"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { gatewayId: string; data: GatewayUpdate } + > = (props) => { + const { gatewayId, data } = props ?? {}; + + return updateGatewayApiV1GatewaysGatewayIdPatch( + gatewayId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type UpdateGatewayApiV1GatewaysGatewayIdPatchMutationResult = + NonNullable< + Awaited> + >; +export type UpdateGatewayApiV1GatewaysGatewayIdPatchMutationBody = + GatewayUpdate; +export type UpdateGatewayApiV1GatewaysGatewayIdPatchMutationError = + HTTPValidationError; + +/** + * @summary Update Gateway + */ +export const useUpdateGatewayApiV1GatewaysGatewayIdPatch = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { gatewayId: string; data: GatewayUpdate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { gatewayId: string; data: GatewayUpdate }, + TContext +> => { + return useMutation( + getUpdateGatewayApiV1GatewaysGatewayIdPatchMutationOptions(options), + queryClient, + ); +}; +/** + * @summary Delete Gateway + */ +export type deleteGatewayApiV1GatewaysGatewayIdDeleteResponse200 = { + data: OkResponse; + status: 200; +}; + +export type deleteGatewayApiV1GatewaysGatewayIdDeleteResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type deleteGatewayApiV1GatewaysGatewayIdDeleteResponseSuccess = + deleteGatewayApiV1GatewaysGatewayIdDeleteResponse200 & { + headers: Headers; + }; +export type deleteGatewayApiV1GatewaysGatewayIdDeleteResponseError = + deleteGatewayApiV1GatewaysGatewayIdDeleteResponse422 & { + headers: Headers; + }; + +export type deleteGatewayApiV1GatewaysGatewayIdDeleteResponse = + | deleteGatewayApiV1GatewaysGatewayIdDeleteResponseSuccess + | deleteGatewayApiV1GatewaysGatewayIdDeleteResponseError; + +export const getDeleteGatewayApiV1GatewaysGatewayIdDeleteUrl = ( + gatewayId: string, +) => { + return `/api/v1/gateways/${gatewayId}`; +}; + +export const deleteGatewayApiV1GatewaysGatewayIdDelete = async ( + gatewayId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getDeleteGatewayApiV1GatewaysGatewayIdDeleteUrl(gatewayId), + { + ...options, + method: "DELETE", + }, + ); +}; + +export const getDeleteGatewayApiV1GatewaysGatewayIdDeleteMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { gatewayId: string }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { gatewayId: string }, + TContext +> => { + const mutationKey = ["deleteGatewayApiV1GatewaysGatewayIdDelete"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { gatewayId: string } + > = (props) => { + const { gatewayId } = props ?? {}; + + return deleteGatewayApiV1GatewaysGatewayIdDelete(gatewayId, requestOptions); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type DeleteGatewayApiV1GatewaysGatewayIdDeleteMutationResult = + NonNullable< + Awaited> + >; + +export type DeleteGatewayApiV1GatewaysGatewayIdDeleteMutationError = + HTTPValidationError; + +/** + * @summary Delete Gateway + */ +export const useDeleteGatewayApiV1GatewaysGatewayIdDelete = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { gatewayId: string }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { gatewayId: string }, + TContext +> => { + return useMutation( + getDeleteGatewayApiV1GatewaysGatewayIdDeleteMutationOptions(options), + queryClient, + ); +}; diff --git a/frontend/src/api/generated/metrics/metrics.ts b/frontend/src/api/generated/metrics/metrics.ts new file mode 100644 index 00000000..3c9c6177 --- /dev/null +++ b/frontend/src/api/generated/metrics/metrics.ts @@ -0,0 +1,243 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import { useQuery } from "@tanstack/react-query"; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; + +import type { + DashboardMetrics, + DashboardMetricsApiV1MetricsDashboardGetParams, + HTTPValidationError, +} from ".././model"; + +import { customFetch } from "../../mutator"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary Dashboard Metrics + */ +export type dashboardMetricsApiV1MetricsDashboardGetResponse200 = { + data: DashboardMetrics; + status: 200; +}; + +export type dashboardMetricsApiV1MetricsDashboardGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type dashboardMetricsApiV1MetricsDashboardGetResponseSuccess = + dashboardMetricsApiV1MetricsDashboardGetResponse200 & { + headers: Headers; + }; +export type dashboardMetricsApiV1MetricsDashboardGetResponseError = + dashboardMetricsApiV1MetricsDashboardGetResponse422 & { + headers: Headers; + }; + +export type dashboardMetricsApiV1MetricsDashboardGetResponse = + | dashboardMetricsApiV1MetricsDashboardGetResponseSuccess + | dashboardMetricsApiV1MetricsDashboardGetResponseError; + +export const getDashboardMetricsApiV1MetricsDashboardGetUrl = ( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/metrics/dashboard?${stringifiedParams}` + : `/api/v1/metrics/dashboard`; +}; + +export const dashboardMetricsApiV1MetricsDashboardGet = async ( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getDashboardMetricsApiV1MetricsDashboardGetUrl(params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getDashboardMetricsApiV1MetricsDashboardGetQueryKey = ( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, +) => { + return [`/api/v1/metrics/dashboard`, ...(params ? [params] : [])] as const; +}; + +export const getDashboardMetricsApiV1MetricsDashboardGetQueryOptions = < + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getDashboardMetricsApiV1MetricsDashboardGetQueryKey(params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + dashboardMetricsApiV1MetricsDashboardGet(params, { + signal, + ...requestOptions, + }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type DashboardMetricsApiV1MetricsDashboardGetQueryResult = NonNullable< + Awaited> +>; +export type DashboardMetricsApiV1MetricsDashboardGetQueryError = + HTTPValidationError; + +export function useDashboardMetricsApiV1MetricsDashboardGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params: undefined | DashboardMetricsApiV1MetricsDashboardGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useDashboardMetricsApiV1MetricsDashboardGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useDashboardMetricsApiV1MetricsDashboardGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Dashboard Metrics + */ + +export function useDashboardMetricsApiV1MetricsDashboardGet< + TData = Awaited>, + TError = HTTPValidationError, +>( + params?: DashboardMetricsApiV1MetricsDashboardGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getDashboardMetricsApiV1MetricsDashboardGetQueryOptions( + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/frontend/src/api/generated/model/agentCreate.ts b/frontend/src/api/generated/model/agentCreate.ts index 82df9dc5..5a9d0876 100644 --- a/frontend/src/api/generated/model/agentCreate.ts +++ b/frontend/src/api/generated/model/agentCreate.ts @@ -5,10 +5,15 @@ * OpenAPI spec version: 0.1.0 */ import type { AgentCreateHeartbeatConfig } from "./agentCreateHeartbeatConfig"; +import type { AgentCreateIdentityProfile } from "./agentCreateIdentityProfile"; export interface AgentCreate { board_id?: string | null; + /** @minLength 1 */ name: string; status?: string; heartbeat_config?: AgentCreateHeartbeatConfig; + identity_profile?: AgentCreateIdentityProfile; + identity_template?: string | null; + soul_template?: string | null; } diff --git a/frontend/src/api/generated/model/agentCreateIdentityProfile.ts b/frontend/src/api/generated/model/agentCreateIdentityProfile.ts new file mode 100644 index 00000000..10c8ab64 --- /dev/null +++ b/frontend/src/api/generated/model/agentCreateIdentityProfile.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type AgentCreateIdentityProfile = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/agentHeartbeatCreate.ts b/frontend/src/api/generated/model/agentHeartbeatCreate.ts index a847fa38..29283425 100644 --- a/frontend/src/api/generated/model/agentHeartbeatCreate.ts +++ b/frontend/src/api/generated/model/agentHeartbeatCreate.ts @@ -7,6 +7,7 @@ export interface AgentHeartbeatCreate { status?: string | null; + /** @minLength 1 */ name: string; board_id?: string | null; } diff --git a/frontend/src/api/generated/model/agentNudge.ts b/frontend/src/api/generated/model/agentNudge.ts new file mode 100644 index 00000000..1b57a129 --- /dev/null +++ b/frontend/src/api/generated/model/agentNudge.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface AgentNudge { + /** @minLength 1 */ + message: string; +} diff --git a/frontend/src/api/generated/model/agentRead.ts b/frontend/src/api/generated/model/agentRead.ts index df553bac..9cf0df4e 100644 --- a/frontend/src/api/generated/model/agentRead.ts +++ b/frontend/src/api/generated/model/agentRead.ts @@ -5,13 +5,20 @@ * OpenAPI spec version: 0.1.0 */ import type { AgentReadHeartbeatConfig } from "./agentReadHeartbeatConfig"; +import type { AgentReadIdentityProfile } from "./agentReadIdentityProfile"; export interface AgentRead { board_id?: string | null; + /** @minLength 1 */ name: string; status?: string; heartbeat_config?: AgentReadHeartbeatConfig; + identity_profile?: AgentReadIdentityProfile; + identity_template?: string | null; + soul_template?: string | null; id: string; + is_board_lead?: boolean; + is_gateway_main?: boolean; openclaw_session_id?: string | null; last_seen_at: string | null; created_at: string; diff --git a/frontend/src/api/generated/model/agentReadIdentityProfile.ts b/frontend/src/api/generated/model/agentReadIdentityProfile.ts new file mode 100644 index 00000000..defa63b0 --- /dev/null +++ b/frontend/src/api/generated/model/agentReadIdentityProfile.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type AgentReadIdentityProfile = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/agentUpdate.ts b/frontend/src/api/generated/model/agentUpdate.ts index dcddf1c3..73f74045 100644 --- a/frontend/src/api/generated/model/agentUpdate.ts +++ b/frontend/src/api/generated/model/agentUpdate.ts @@ -5,10 +5,15 @@ * OpenAPI spec version: 0.1.0 */ import type { AgentUpdateHeartbeatConfig } from "./agentUpdateHeartbeatConfig"; +import type { AgentUpdateIdentityProfile } from "./agentUpdateIdentityProfile"; export interface AgentUpdate { board_id?: string | null; + is_gateway_main?: boolean | null; name?: string | null; status?: string | null; heartbeat_config?: AgentUpdateHeartbeatConfig; + identity_profile?: AgentUpdateIdentityProfile; + identity_template?: string | null; + soul_template?: string | null; } diff --git a/frontend/src/api/generated/model/agentUpdateIdentityProfile.ts b/frontend/src/api/generated/model/agentUpdateIdentityProfile.ts new file mode 100644 index 00000000..effb7ed9 --- /dev/null +++ b/frontend/src/api/generated/model/agentUpdateIdentityProfile.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type AgentUpdateIdentityProfile = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/approvalCreate.ts b/frontend/src/api/generated/model/approvalCreate.ts new file mode 100644 index 00000000..099b2a67 --- /dev/null +++ b/frontend/src/api/generated/model/approvalCreate.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { ApprovalCreatePayload } from "./approvalCreatePayload"; +import type { ApprovalCreateRubricScores } from "./approvalCreateRubricScores"; +import type { ApprovalCreateStatus } from "./approvalCreateStatus"; + +export interface ApprovalCreate { + action_type: string; + payload?: ApprovalCreatePayload; + confidence: number; + rubric_scores?: ApprovalCreateRubricScores; + status?: ApprovalCreateStatus; + agent_id?: string | null; +} diff --git a/frontend/src/api/generated/model/approvalCreatePayload.ts b/frontend/src/api/generated/model/approvalCreatePayload.ts new file mode 100644 index 00000000..e6cbaaf0 --- /dev/null +++ b/frontend/src/api/generated/model/approvalCreatePayload.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ApprovalCreatePayload = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/approvalCreateRubricScores.ts b/frontend/src/api/generated/model/approvalCreateRubricScores.ts new file mode 100644 index 00000000..bb36bada --- /dev/null +++ b/frontend/src/api/generated/model/approvalCreateRubricScores.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ApprovalCreateRubricScores = { [key: string]: number } | null; diff --git a/frontend/src/api/generated/model/approvalCreateStatus.ts b/frontend/src/api/generated/model/approvalCreateStatus.ts new file mode 100644 index 00000000..b3d053d2 --- /dev/null +++ b/frontend/src/api/generated/model/approvalCreateStatus.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ApprovalCreateStatus = + (typeof ApprovalCreateStatus)[keyof typeof ApprovalCreateStatus]; + +export const ApprovalCreateStatus = { + pending: "pending", + approved: "approved", + rejected: "rejected", +} as const; diff --git a/frontend/src/api/generated/model/approvalRead.ts b/frontend/src/api/generated/model/approvalRead.ts new file mode 100644 index 00000000..d00ffd95 --- /dev/null +++ b/frontend/src/api/generated/model/approvalRead.ts @@ -0,0 +1,22 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { ApprovalReadPayload } from "./approvalReadPayload"; +import type { ApprovalReadRubricScores } from "./approvalReadRubricScores"; +import type { ApprovalReadStatus } from "./approvalReadStatus"; + +export interface ApprovalRead { + action_type: string; + payload?: ApprovalReadPayload; + confidence: number; + rubric_scores?: ApprovalReadRubricScores; + status?: ApprovalReadStatus; + id: string; + board_id: string; + agent_id?: string | null; + created_at: string; + resolved_at?: string | null; +} diff --git a/frontend/src/api/generated/model/approvalReadPayload.ts b/frontend/src/api/generated/model/approvalReadPayload.ts new file mode 100644 index 00000000..685a9537 --- /dev/null +++ b/frontend/src/api/generated/model/approvalReadPayload.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ApprovalReadPayload = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/approvalReadRubricScores.ts b/frontend/src/api/generated/model/approvalReadRubricScores.ts new file mode 100644 index 00000000..02a67489 --- /dev/null +++ b/frontend/src/api/generated/model/approvalReadRubricScores.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ApprovalReadRubricScores = { [key: string]: number } | null; diff --git a/frontend/src/api/generated/model/approvalReadStatus.ts b/frontend/src/api/generated/model/approvalReadStatus.ts new file mode 100644 index 00000000..7210e355 --- /dev/null +++ b/frontend/src/api/generated/model/approvalReadStatus.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ApprovalReadStatus = + (typeof ApprovalReadStatus)[keyof typeof ApprovalReadStatus]; + +export const ApprovalReadStatus = { + pending: "pending", + approved: "approved", + rejected: "rejected", +} as const; diff --git a/frontend/src/api/generated/model/approvalUpdate.ts b/frontend/src/api/generated/model/approvalUpdate.ts new file mode 100644 index 00000000..d7c35209 --- /dev/null +++ b/frontend/src/api/generated/model/approvalUpdate.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface ApprovalUpdate { + status?: "pending" | "approved" | "rejected" | null; +} diff --git a/frontend/src/api/generated/model/boardCreate.ts b/frontend/src/api/generated/model/boardCreate.ts index c96d038b..51a1425a 100644 --- a/frontend/src/api/generated/model/boardCreate.ts +++ b/frontend/src/api/generated/model/boardCreate.ts @@ -4,9 +4,16 @@ * Mission Control API * OpenAPI spec version: 0.1.0 */ +import type { BoardCreateSuccessMetrics } from "./boardCreateSuccessMetrics"; export interface BoardCreate { name: string; slug: string; - gateway_id?: string | null; + gateway_id: string; + board_type?: string; + objective?: string | null; + success_metrics?: BoardCreateSuccessMetrics; + target_date?: string | null; + goal_confirmed?: boolean; + goal_source?: string | null; } diff --git a/frontend/src/api/generated/model/boardCreateSuccessMetrics.ts b/frontend/src/api/generated/model/boardCreateSuccessMetrics.ts new file mode 100644 index 00000000..e52fb7ed --- /dev/null +++ b/frontend/src/api/generated/model/boardCreateSuccessMetrics.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardCreateSuccessMetrics = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/boardMemoryCreate.ts b/frontend/src/api/generated/model/boardMemoryCreate.ts new file mode 100644 index 00000000..1aff6e46 --- /dev/null +++ b/frontend/src/api/generated/model/boardMemoryCreate.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface BoardMemoryCreate { + /** @minLength 1 */ + content: string; + tags?: string[] | null; + source?: string | null; +} diff --git a/frontend/src/api/generated/model/boardMemoryRead.ts b/frontend/src/api/generated/model/boardMemoryRead.ts new file mode 100644 index 00000000..4e3c7db0 --- /dev/null +++ b/frontend/src/api/generated/model/boardMemoryRead.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface BoardMemoryRead { + /** @minLength 1 */ + content: string; + tags?: string[] | null; + source?: string | null; + id: string; + board_id: string; + created_at: string; +} diff --git a/frontend/src/api/generated/model/boardOnboardingAgentComplete.ts b/frontend/src/api/generated/model/boardOnboardingAgentComplete.ts new file mode 100644 index 00000000..faa846da --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingAgentComplete.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { BoardOnboardingAgentCompleteSuccessMetrics } from "./boardOnboardingAgentCompleteSuccessMetrics"; + +export interface BoardOnboardingAgentComplete { + board_type: string; + objective?: string | null; + success_metrics?: BoardOnboardingAgentCompleteSuccessMetrics; + target_date?: string | null; + status: "complete"; +} diff --git a/frontend/src/api/generated/model/boardOnboardingAgentCompleteSuccessMetrics.ts b/frontend/src/api/generated/model/boardOnboardingAgentCompleteSuccessMetrics.ts new file mode 100644 index 00000000..d6d3445d --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingAgentCompleteSuccessMetrics.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardOnboardingAgentCompleteSuccessMetrics = { + [key: string]: unknown; +} | null; diff --git a/frontend/src/api/generated/model/boardOnboardingAgentQuestion.ts b/frontend/src/api/generated/model/boardOnboardingAgentQuestion.ts new file mode 100644 index 00000000..6904ef01 --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingAgentQuestion.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { BoardOnboardingQuestionOption } from "./boardOnboardingQuestionOption"; + +export interface BoardOnboardingAgentQuestion { + /** @minLength 1 */ + question: string; + /** @minItems 1 */ + options: BoardOnboardingQuestionOption[]; +} diff --git a/frontend/src/api/generated/model/boardOnboardingAnswer.ts b/frontend/src/api/generated/model/boardOnboardingAnswer.ts new file mode 100644 index 00000000..9a5e4ea4 --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingAnswer.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface BoardOnboardingAnswer { + /** @minLength 1 */ + answer: string; + other_text?: string | null; +} diff --git a/frontend/src/api/generated/model/boardOnboardingConfirm.ts b/frontend/src/api/generated/model/boardOnboardingConfirm.ts new file mode 100644 index 00000000..af198322 --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingConfirm.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { BoardOnboardingConfirmSuccessMetrics } from "./boardOnboardingConfirmSuccessMetrics"; + +export interface BoardOnboardingConfirm { + board_type: string; + objective?: string | null; + success_metrics?: BoardOnboardingConfirmSuccessMetrics; + target_date?: string | null; +} diff --git a/frontend/src/api/generated/model/boardOnboardingConfirmSuccessMetrics.ts b/frontend/src/api/generated/model/boardOnboardingConfirmSuccessMetrics.ts new file mode 100644 index 00000000..e5fd7e79 --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingConfirmSuccessMetrics.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardOnboardingConfirmSuccessMetrics = { + [key: string]: unknown; +} | null; diff --git a/frontend/src/api/generated/model/boardOnboardingQuestionOption.ts b/frontend/src/api/generated/model/boardOnboardingQuestionOption.ts new file mode 100644 index 00000000..aab7ec6b --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingQuestionOption.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface BoardOnboardingQuestionOption { + /** @minLength 1 */ + id: string; + /** @minLength 1 */ + label: string; +} diff --git a/frontend/src/api/generated/model/boardOnboardingRead.ts b/frontend/src/api/generated/model/boardOnboardingRead.ts new file mode 100644 index 00000000..63be1621 --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingRead.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { BoardOnboardingReadDraftGoal } from "./boardOnboardingReadDraftGoal"; +import type { BoardOnboardingReadMessages } from "./boardOnboardingReadMessages"; + +export interface BoardOnboardingRead { + id: string; + board_id: string; + session_key: string; + status: string; + messages?: BoardOnboardingReadMessages; + draft_goal?: BoardOnboardingReadDraftGoal; + created_at: string; + updated_at: string; +} diff --git a/frontend/src/api/generated/model/boardOnboardingReadDraftGoal.ts b/frontend/src/api/generated/model/boardOnboardingReadDraftGoal.ts new file mode 100644 index 00000000..bd652498 --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingReadDraftGoal.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardOnboardingReadDraftGoal = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/boardOnboardingReadMessages.ts b/frontend/src/api/generated/model/boardOnboardingReadMessages.ts new file mode 100644 index 00000000..26a1264c --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingReadMessages.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardOnboardingReadMessages = { [key: string]: unknown }[] | null; diff --git a/frontend/src/api/generated/model/boardOnboardingStart.ts b/frontend/src/api/generated/model/boardOnboardingStart.ts new file mode 100644 index 00000000..714382be --- /dev/null +++ b/frontend/src/api/generated/model/boardOnboardingStart.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface BoardOnboardingStart { + [key: string]: unknown; +} diff --git a/frontend/src/api/generated/model/boardRead.ts b/frontend/src/api/generated/model/boardRead.ts index 8ed02db7..9ed20aa0 100644 --- a/frontend/src/api/generated/model/boardRead.ts +++ b/frontend/src/api/generated/model/boardRead.ts @@ -4,11 +4,18 @@ * Mission Control API * OpenAPI spec version: 0.1.0 */ +import type { BoardReadSuccessMetrics } from "./boardReadSuccessMetrics"; export interface BoardRead { name: string; slug: string; gateway_id?: string | null; + board_type?: string; + objective?: string | null; + success_metrics?: BoardReadSuccessMetrics; + target_date?: string | null; + goal_confirmed?: boolean; + goal_source?: string | null; id: string; created_at: string; updated_at: string; diff --git a/frontend/src/api/generated/model/boardReadSuccessMetrics.ts b/frontend/src/api/generated/model/boardReadSuccessMetrics.ts new file mode 100644 index 00000000..6623dce6 --- /dev/null +++ b/frontend/src/api/generated/model/boardReadSuccessMetrics.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardReadSuccessMetrics = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/boardUpdate.ts b/frontend/src/api/generated/model/boardUpdate.ts index 6eb95054..60fe9844 100644 --- a/frontend/src/api/generated/model/boardUpdate.ts +++ b/frontend/src/api/generated/model/boardUpdate.ts @@ -4,9 +4,16 @@ * Mission Control API * OpenAPI spec version: 0.1.0 */ +import type { BoardUpdateSuccessMetrics } from "./boardUpdateSuccessMetrics"; export interface BoardUpdate { name?: string | null; slug?: string | null; gateway_id?: string | null; + board_type?: string | null; + objective?: string | null; + success_metrics?: BoardUpdateSuccessMetrics; + target_date?: string | null; + goal_confirmed?: boolean | null; + goal_source?: string | null; } diff --git a/frontend/src/api/generated/model/boardUpdateSuccessMetrics.ts b/frontend/src/api/generated/model/boardUpdateSuccessMetrics.ts new file mode 100644 index 00000000..87b8d04e --- /dev/null +++ b/frontend/src/api/generated/model/boardUpdateSuccessMetrics.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type BoardUpdateSuccessMetrics = { [key: string]: unknown } | null; diff --git a/frontend/src/api/generated/model/dashboardKpis.ts b/frontend/src/api/generated/model/dashboardKpis.ts new file mode 100644 index 00000000..72fdd827 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardKpis.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface DashboardKpis { + active_agents: number; + tasks_in_progress: number; + error_rate_pct: number; + median_cycle_time_hours_7d: number | null; +} diff --git a/frontend/src/api/generated/model/dashboardMetrics.ts b/frontend/src/api/generated/model/dashboardMetrics.ts new file mode 100644 index 00000000..9fe0efee --- /dev/null +++ b/frontend/src/api/generated/model/dashboardMetrics.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { DashboardKpis } from "./dashboardKpis"; +import type { DashboardMetricsRange } from "./dashboardMetricsRange"; +import type { DashboardSeriesSet } from "./dashboardSeriesSet"; +import type { DashboardWipSeriesSet } from "./dashboardWipSeriesSet"; + +export interface DashboardMetrics { + range: DashboardMetricsRange; + generated_at: string; + kpis: DashboardKpis; + throughput: DashboardSeriesSet; + cycle_time: DashboardSeriesSet; + error_rate: DashboardSeriesSet; + wip: DashboardWipSeriesSet; +} diff --git a/frontend/src/api/generated/model/dashboardMetricsApiV1MetricsDashboardGetParams.ts b/frontend/src/api/generated/model/dashboardMetricsApiV1MetricsDashboardGetParams.ts new file mode 100644 index 00000000..381b4ec7 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardMetricsApiV1MetricsDashboardGetParams.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { DashboardMetricsApiV1MetricsDashboardGetRange } from "./dashboardMetricsApiV1MetricsDashboardGetRange"; + +export type DashboardMetricsApiV1MetricsDashboardGetParams = { + range?: DashboardMetricsApiV1MetricsDashboardGetRange; +}; diff --git a/frontend/src/api/generated/model/dashboardMetricsApiV1MetricsDashboardGetRange.ts b/frontend/src/api/generated/model/dashboardMetricsApiV1MetricsDashboardGetRange.ts new file mode 100644 index 00000000..ec029151 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardMetricsApiV1MetricsDashboardGetRange.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type DashboardMetricsApiV1MetricsDashboardGetRange = + (typeof DashboardMetricsApiV1MetricsDashboardGetRange)[keyof typeof DashboardMetricsApiV1MetricsDashboardGetRange]; + +export const DashboardMetricsApiV1MetricsDashboardGetRange = { + "24h": "24h", + "7d": "7d", +} as const; diff --git a/frontend/src/api/generated/model/dashboardMetricsRange.ts b/frontend/src/api/generated/model/dashboardMetricsRange.ts new file mode 100644 index 00000000..b1882b2c --- /dev/null +++ b/frontend/src/api/generated/model/dashboardMetricsRange.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type DashboardMetricsRange = + (typeof DashboardMetricsRange)[keyof typeof DashboardMetricsRange]; + +export const DashboardMetricsRange = { + "24h": "24h", + "7d": "7d", +} as const; diff --git a/frontend/src/api/generated/model/dashboardRangeSeries.ts b/frontend/src/api/generated/model/dashboardRangeSeries.ts new file mode 100644 index 00000000..b9dfc206 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardRangeSeries.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { DashboardRangeSeriesBucket } from "./dashboardRangeSeriesBucket"; +import type { DashboardRangeSeriesRange } from "./dashboardRangeSeriesRange"; +import type { DashboardSeriesPoint } from "./dashboardSeriesPoint"; + +export interface DashboardRangeSeries { + range: DashboardRangeSeriesRange; + bucket: DashboardRangeSeriesBucket; + points: DashboardSeriesPoint[]; +} diff --git a/frontend/src/api/generated/model/dashboardRangeSeriesBucket.ts b/frontend/src/api/generated/model/dashboardRangeSeriesBucket.ts new file mode 100644 index 00000000..1f8db99a --- /dev/null +++ b/frontend/src/api/generated/model/dashboardRangeSeriesBucket.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type DashboardRangeSeriesBucket = + (typeof DashboardRangeSeriesBucket)[keyof typeof DashboardRangeSeriesBucket]; + +export const DashboardRangeSeriesBucket = { + hour: "hour", + day: "day", +} as const; diff --git a/frontend/src/api/generated/model/dashboardRangeSeriesRange.ts b/frontend/src/api/generated/model/dashboardRangeSeriesRange.ts new file mode 100644 index 00000000..1d4eb083 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardRangeSeriesRange.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type DashboardRangeSeriesRange = + (typeof DashboardRangeSeriesRange)[keyof typeof DashboardRangeSeriesRange]; + +export const DashboardRangeSeriesRange = { + "24h": "24h", + "7d": "7d", +} as const; diff --git a/frontend/src/api/generated/model/dashboardSeriesPoint.ts b/frontend/src/api/generated/model/dashboardSeriesPoint.ts new file mode 100644 index 00000000..8d1d585a --- /dev/null +++ b/frontend/src/api/generated/model/dashboardSeriesPoint.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface DashboardSeriesPoint { + period: string; + value: number; +} diff --git a/frontend/src/api/generated/model/dashboardSeriesSet.ts b/frontend/src/api/generated/model/dashboardSeriesSet.ts new file mode 100644 index 00000000..529b8d38 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardSeriesSet.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { DashboardRangeSeries } from "./dashboardRangeSeries"; + +export interface DashboardSeriesSet { + primary: DashboardRangeSeries; + comparison: DashboardRangeSeries; +} diff --git a/frontend/src/api/generated/model/dashboardWipPoint.ts b/frontend/src/api/generated/model/dashboardWipPoint.ts new file mode 100644 index 00000000..2f47f974 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardWipPoint.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface DashboardWipPoint { + period: string; + inbox: number; + in_progress: number; + review: number; +} diff --git a/frontend/src/api/generated/model/dashboardWipRangeSeries.ts b/frontend/src/api/generated/model/dashboardWipRangeSeries.ts new file mode 100644 index 00000000..9c34d786 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardWipRangeSeries.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { DashboardWipPoint } from "./dashboardWipPoint"; +import type { DashboardWipRangeSeriesBucket } from "./dashboardWipRangeSeriesBucket"; +import type { DashboardWipRangeSeriesRange } from "./dashboardWipRangeSeriesRange"; + +export interface DashboardWipRangeSeries { + range: DashboardWipRangeSeriesRange; + bucket: DashboardWipRangeSeriesBucket; + points: DashboardWipPoint[]; +} diff --git a/frontend/src/api/generated/model/dashboardWipRangeSeriesBucket.ts b/frontend/src/api/generated/model/dashboardWipRangeSeriesBucket.ts new file mode 100644 index 00000000..e45ca78f --- /dev/null +++ b/frontend/src/api/generated/model/dashboardWipRangeSeriesBucket.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type DashboardWipRangeSeriesBucket = + (typeof DashboardWipRangeSeriesBucket)[keyof typeof DashboardWipRangeSeriesBucket]; + +export const DashboardWipRangeSeriesBucket = { + hour: "hour", + day: "day", +} as const; diff --git a/frontend/src/api/generated/model/dashboardWipRangeSeriesRange.ts b/frontend/src/api/generated/model/dashboardWipRangeSeriesRange.ts new file mode 100644 index 00000000..a8746ed0 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardWipRangeSeriesRange.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type DashboardWipRangeSeriesRange = + (typeof DashboardWipRangeSeriesRange)[keyof typeof DashboardWipRangeSeriesRange]; + +export const DashboardWipRangeSeriesRange = { + "24h": "24h", + "7d": "7d", +} as const; diff --git a/frontend/src/api/generated/model/dashboardWipSeriesSet.ts b/frontend/src/api/generated/model/dashboardWipSeriesSet.ts new file mode 100644 index 00000000..55ad1ab1 --- /dev/null +++ b/frontend/src/api/generated/model/dashboardWipSeriesSet.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ +import type { DashboardWipRangeSeries } from "./dashboardWipRangeSeries"; + +export interface DashboardWipSeriesSet { + primary: DashboardWipRangeSeries; + comparison: DashboardWipRangeSeries; +} diff --git a/frontend/src/api/generated/model/gatewayCommandsResponse.ts b/frontend/src/api/generated/model/gatewayCommandsResponse.ts new file mode 100644 index 00000000..cf902c2d --- /dev/null +++ b/frontend/src/api/generated/model/gatewayCommandsResponse.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewayCommandsResponse { + protocol_version: number; + methods: string[]; + events: string[]; +} diff --git a/frontend/src/api/generated/model/gatewayCreate.ts b/frontend/src/api/generated/model/gatewayCreate.ts new file mode 100644 index 00000000..b145f7c8 --- /dev/null +++ b/frontend/src/api/generated/model/gatewayCreate.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewayCreate { + name: string; + url: string; + main_session_key: string; + workspace_root: string; + skyll_enabled?: boolean; + token?: string | null; +} diff --git a/frontend/src/api/generated/model/gatewayRead.ts b/frontend/src/api/generated/model/gatewayRead.ts new file mode 100644 index 00000000..ba0e1568 --- /dev/null +++ b/frontend/src/api/generated/model/gatewayRead.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewayRead { + name: string; + url: string; + main_session_key: string; + workspace_root: string; + skyll_enabled?: boolean; + id: string; + token?: string | null; + created_at: string; + updated_at: string; +} diff --git a/frontend/src/api/generated/model/gatewaySessionHistoryResponse.ts b/frontend/src/api/generated/model/gatewaySessionHistoryResponse.ts new file mode 100644 index 00000000..ea6226f5 --- /dev/null +++ b/frontend/src/api/generated/model/gatewaySessionHistoryResponse.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewaySessionHistoryResponse { + history: unknown[]; +} diff --git a/frontend/src/api/generated/model/gatewaySessionMessageRequest.ts b/frontend/src/api/generated/model/gatewaySessionMessageRequest.ts new file mode 100644 index 00000000..4d1c135e --- /dev/null +++ b/frontend/src/api/generated/model/gatewaySessionMessageRequest.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewaySessionMessageRequest { + /** @minLength 1 */ + content: string; +} diff --git a/frontend/src/api/generated/model/gatewaySessionResponse.ts b/frontend/src/api/generated/model/gatewaySessionResponse.ts new file mode 100644 index 00000000..16c347cd --- /dev/null +++ b/frontend/src/api/generated/model/gatewaySessionResponse.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewaySessionResponse { + session: unknown; +} diff --git a/frontend/src/api/generated/model/gatewaySessionsResponse.ts b/frontend/src/api/generated/model/gatewaySessionsResponse.ts new file mode 100644 index 00000000..126b131b --- /dev/null +++ b/frontend/src/api/generated/model/gatewaySessionsResponse.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewaySessionsResponse { + sessions: unknown[]; + main_session_key?: string | null; + main_session?: unknown | null; +} diff --git a/frontend/src/api/generated/model/gatewayUpdate.ts b/frontend/src/api/generated/model/gatewayUpdate.ts new file mode 100644 index 00000000..a949944c --- /dev/null +++ b/frontend/src/api/generated/model/gatewayUpdate.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewayUpdate { + name?: string | null; + url?: string | null; + token?: string | null; + main_session_key?: string | null; + workspace_root?: string | null; + skyll_enabled?: boolean | null; +} diff --git a/frontend/src/api/generated/model/gatewaysStatusApiV1GatewaysStatusGetParams.ts b/frontend/src/api/generated/model/gatewaysStatusApiV1GatewaysStatusGetParams.ts new file mode 100644 index 00000000..75b8fed5 --- /dev/null +++ b/frontend/src/api/generated/model/gatewaysStatusApiV1GatewaysStatusGetParams.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type GatewaysStatusApiV1GatewaysStatusGetParams = { + board_id?: string | null; + gateway_url?: string | null; + gateway_token?: string | null; + gateway_main_session_key?: string | null; +}; diff --git a/frontend/src/api/generated/model/gatewaysStatusResponse.ts b/frontend/src/api/generated/model/gatewaysStatusResponse.ts new file mode 100644 index 00000000..033d8daa --- /dev/null +++ b/frontend/src/api/generated/model/gatewaysStatusResponse.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface GatewaysStatusResponse { + connected: boolean; + gateway_url: string; + sessions_count?: number | null; + sessions?: unknown[] | null; + main_session_key?: string | null; + main_session?: unknown | null; + main_session_error?: string | null; + error?: string | null; +} diff --git a/frontend/src/api/generated/model/getGatewaySessionApiV1GatewaysSessionsSessionIdGetParams.ts b/frontend/src/api/generated/model/getGatewaySessionApiV1GatewaysSessionsSessionIdGetParams.ts new file mode 100644 index 00000000..8e6bd2ec --- /dev/null +++ b/frontend/src/api/generated/model/getGatewaySessionApiV1GatewaysSessionsSessionIdGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams = { + board_id?: string | null; +}; diff --git a/frontend/src/api/generated/model/getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams.ts b/frontend/src/api/generated/model/getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams.ts new file mode 100644 index 00000000..2bb1eca5 --- /dev/null +++ b/frontend/src/api/generated/model/getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams = { + board_id?: string | null; +}; diff --git a/frontend/src/api/generated/model/index.ts b/frontend/src/api/generated/model/index.ts index dd7cf50d..ccdf4117 100644 --- a/frontend/src/api/generated/model/index.ts +++ b/frontend/src/api/generated/model/index.ts @@ -8,45 +8,117 @@ export * from "./activityEventRead"; export * from "./agentCreate"; export * from "./agentCreateHeartbeatConfig"; +export * from "./agentCreateIdentityProfile"; export * from "./agentDeleteConfirm"; export * from "./agentHeartbeat"; export * from "./agentHeartbeatCreate"; +export * from "./agentNudge"; export * from "./agentProvisionConfirm"; export * from "./agentRead"; export * from "./agentReadHeartbeatConfig"; +export * from "./agentReadIdentityProfile"; export * from "./agentUpdate"; export * from "./agentUpdateHeartbeatConfig"; +export * from "./agentUpdateIdentityProfile"; +export * from "./approvalCreate"; +export * from "./approvalCreatePayload"; +export * from "./approvalCreateRubricScores"; +export * from "./approvalCreateStatus"; +export * from "./approvalRead"; +export * from "./approvalReadPayload"; +export * from "./approvalReadRubricScores"; +export * from "./approvalReadStatus"; +export * from "./approvalUpdate"; export * from "./boardCreate"; +export * from "./boardCreateSuccessMetrics"; +export * from "./boardMemoryCreate"; +export * from "./boardMemoryRead"; +export * from "./boardOnboardingAgentComplete"; +export * from "./boardOnboardingAgentCompleteSuccessMetrics"; +export * from "./boardOnboardingAgentQuestion"; +export * from "./boardOnboardingAnswer"; +export * from "./boardOnboardingConfirm"; +export * from "./boardOnboardingConfirmSuccessMetrics"; +export * from "./boardOnboardingQuestionOption"; +export * from "./boardOnboardingRead"; +export * from "./boardOnboardingReadDraftGoal"; +export * from "./boardOnboardingReadMessages"; +export * from "./boardOnboardingStart"; export * from "./boardRead"; +export * from "./boardReadSuccessMetrics"; export * from "./boardUpdate"; +export * from "./boardUpdateSuccessMetrics"; export * from "./confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200"; export * from "./confirmProvisionAgentApiV1AgentsAgentIdProvisionConfirmPost200"; +export * from "./dashboardKpis"; +export * from "./dashboardMetrics"; +export * from "./dashboardMetricsApiV1MetricsDashboardGetParams"; +export * from "./dashboardMetricsApiV1MetricsDashboardGetRange"; +export * from "./dashboardMetricsRange"; +export * from "./dashboardRangeSeries"; +export * from "./dashboardRangeSeriesBucket"; +export * from "./dashboardRangeSeriesRange"; +export * from "./dashboardSeriesPoint"; +export * from "./dashboardSeriesSet"; +export * from "./dashboardWipPoint"; +export * from "./dashboardWipRangeSeries"; +export * from "./dashboardWipRangeSeriesBucket"; +export * from "./dashboardWipRangeSeriesRange"; +export * from "./dashboardWipSeriesSet"; export * from "./deleteAgentApiV1AgentsAgentIdDelete200"; export * from "./deleteBoardApiV1BoardsBoardIdDelete200"; export * from "./deleteTaskApiV1BoardsBoardIdTasksTaskIdDelete200"; export * from "./gatewayCommandsApiV1GatewayCommandsGet200"; +export * from "./gatewayCommandsResponse"; +export * from "./gatewayCreate"; +export * from "./gatewayRead"; +export * from "./gatewaySessionHistoryResponse"; +export * from "./gatewaySessionMessageRequest"; +export * from "./gatewaySessionResponse"; +export * from "./gatewaySessionsResponse"; +export * from "./gatewaysStatusApiV1GatewaysStatusGetParams"; +export * from "./gatewaysStatusResponse"; export * from "./gatewayStatusApiV1GatewayStatusGet200"; export * from "./gatewayStatusApiV1GatewayStatusGetParams"; +export * from "./gatewayUpdate"; export * from "./getGatewaySessionApiV1GatewaySessionsSessionIdGet200"; export * from "./getGatewaySessionApiV1GatewaySessionsSessionIdGetParams"; +export * from "./getGatewaySessionApiV1GatewaysSessionsSessionIdGetParams"; export * from "./getSessionHistoryApiV1GatewaySessionsSessionIdHistoryGet200"; export * from "./getSessionHistoryApiV1GatewaySessionsSessionIdHistoryGetParams"; +export * from "./getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams"; export * from "./healthHealthGet200"; export * from "./healthzHealthzGet200"; export * from "./hTTPValidationError"; export * from "./listActivityApiV1ActivityGetParams"; +export * from "./listAgentsApiV1AgentAgentsGetParams"; +export * from "./listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams"; +export * from "./listApprovalsApiV1BoardsBoardIdApprovalsGetParams"; +export * from "./listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams"; +export * from "./listBoardMemoryApiV1BoardsBoardIdMemoryGetParams"; +export * from "./listGatewaySessionsApiV1GatewaysSessionsGetParams"; export * from "./listSessionsApiV1GatewaySessionsGet200"; export * from "./listSessionsApiV1GatewaySessionsGetParams"; +export * from "./listTasksApiV1AgentBoardsBoardIdTasksGetParams"; export * from "./listTasksApiV1BoardsBoardIdTasksGetParams"; +export * from "./okResponse"; export * from "./readyzReadyzGet200"; +export * from "./sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams"; export * from "./sendSessionMessageApiV1GatewaySessionsSessionIdMessagePost200"; export * from "./sendSessionMessageApiV1GatewaySessionsSessionIdMessagePostBody"; export * from "./sendSessionMessageApiV1GatewaySessionsSessionIdMessagePostParams"; +export * from "./streamAgentsApiV1AgentsStreamGetParams"; +export * from "./streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams"; +export * from "./streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams"; +export * from "./streamTasksApiV1BoardsBoardIdTasksStreamGetParams"; export * from "./taskCommentCreate"; export * from "./taskCommentRead"; export * from "./taskCreate"; +export * from "./taskCreateStatus"; export * from "./taskRead"; +export * from "./taskReadStatus"; export * from "./taskUpdate"; +export * from "./updateAgentApiV1AgentsAgentIdPatchParams"; export * from "./userRead"; export * from "./userUpdate"; export * from "./validationError"; diff --git a/frontend/src/api/generated/model/listAgentsApiV1AgentAgentsGetParams.ts b/frontend/src/api/generated/model/listAgentsApiV1AgentAgentsGetParams.ts new file mode 100644 index 00000000..e22792fb --- /dev/null +++ b/frontend/src/api/generated/model/listAgentsApiV1AgentAgentsGetParams.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListAgentsApiV1AgentAgentsGetParams = { + board_id?: string | null; + limit?: number | null; +}; diff --git a/frontend/src/api/generated/model/listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams.ts b/frontend/src/api/generated/model/listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams.ts new file mode 100644 index 00000000..13281422 --- /dev/null +++ b/frontend/src/api/generated/model/listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams = { + status?: "pending" | "approved" | "rejected" | null; +}; diff --git a/frontend/src/api/generated/model/listApprovalsApiV1BoardsBoardIdApprovalsGetParams.ts b/frontend/src/api/generated/model/listApprovalsApiV1BoardsBoardIdApprovalsGetParams.ts new file mode 100644 index 00000000..ffc822d9 --- /dev/null +++ b/frontend/src/api/generated/model/listApprovalsApiV1BoardsBoardIdApprovalsGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListApprovalsApiV1BoardsBoardIdApprovalsGetParams = { + status?: "pending" | "approved" | "rejected" | null; +}; diff --git a/frontend/src/api/generated/model/listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams.ts b/frontend/src/api/generated/model/listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams.ts new file mode 100644 index 00000000..1e142170 --- /dev/null +++ b/frontend/src/api/generated/model/listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams = { + /** + * @minimum 1 + * @maximum 200 + */ + limit?: number; + /** + * @minimum 0 + */ + offset?: number; +}; diff --git a/frontend/src/api/generated/model/listBoardMemoryApiV1BoardsBoardIdMemoryGetParams.ts b/frontend/src/api/generated/model/listBoardMemoryApiV1BoardsBoardIdMemoryGetParams.ts new file mode 100644 index 00000000..2c679c2a --- /dev/null +++ b/frontend/src/api/generated/model/listBoardMemoryApiV1BoardsBoardIdMemoryGetParams.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams = { + /** + * @minimum 1 + * @maximum 200 + */ + limit?: number; + /** + * @minimum 0 + */ + offset?: number; +}; diff --git a/frontend/src/api/generated/model/listGatewaySessionsApiV1GatewaysSessionsGetParams.ts b/frontend/src/api/generated/model/listGatewaySessionsApiV1GatewaysSessionsGetParams.ts new file mode 100644 index 00000000..4e44c0b4 --- /dev/null +++ b/frontend/src/api/generated/model/listGatewaySessionsApiV1GatewaysSessionsGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListGatewaySessionsApiV1GatewaysSessionsGetParams = { + board_id?: string | null; +}; diff --git a/frontend/src/api/generated/model/listTasksApiV1AgentBoardsBoardIdTasksGetParams.ts b/frontend/src/api/generated/model/listTasksApiV1AgentBoardsBoardIdTasksGetParams.ts new file mode 100644 index 00000000..7f9f34fb --- /dev/null +++ b/frontend/src/api/generated/model/listTasksApiV1AgentBoardsBoardIdTasksGetParams.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type ListTasksApiV1AgentBoardsBoardIdTasksGetParams = { + status?: string | null; + assigned_agent_id?: string | null; + unassigned?: boolean | null; + limit?: number | null; +}; diff --git a/frontend/src/api/generated/model/okResponse.ts b/frontend/src/api/generated/model/okResponse.ts new file mode 100644 index 00000000..9481d814 --- /dev/null +++ b/frontend/src/api/generated/model/okResponse.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export interface OkResponse { + ok?: boolean; +} diff --git a/frontend/src/api/generated/model/sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams.ts b/frontend/src/api/generated/model/sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams.ts new file mode 100644 index 00000000..b307f124 --- /dev/null +++ b/frontend/src/api/generated/model/sendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams = + { + board_id?: string | null; + }; diff --git a/frontend/src/api/generated/model/streamAgentsApiV1AgentsStreamGetParams.ts b/frontend/src/api/generated/model/streamAgentsApiV1AgentsStreamGetParams.ts new file mode 100644 index 00000000..7a4b4297 --- /dev/null +++ b/frontend/src/api/generated/model/streamAgentsApiV1AgentsStreamGetParams.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type StreamAgentsApiV1AgentsStreamGetParams = { + board_id?: string | null; + since?: string | null; +}; diff --git a/frontend/src/api/generated/model/streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams.ts b/frontend/src/api/generated/model/streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams.ts new file mode 100644 index 00000000..1a1a24c1 --- /dev/null +++ b/frontend/src/api/generated/model/streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams = { + since?: string | null; +}; diff --git a/frontend/src/api/generated/model/streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams.ts b/frontend/src/api/generated/model/streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams.ts new file mode 100644 index 00000000..4ef48164 --- /dev/null +++ b/frontend/src/api/generated/model/streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams = { + since?: string | null; +}; diff --git a/frontend/src/api/generated/model/streamTasksApiV1BoardsBoardIdTasksStreamGetParams.ts b/frontend/src/api/generated/model/streamTasksApiV1BoardsBoardIdTasksStreamGetParams.ts new file mode 100644 index 00000000..3139a4a1 --- /dev/null +++ b/frontend/src/api/generated/model/streamTasksApiV1BoardsBoardIdTasksStreamGetParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type StreamTasksApiV1BoardsBoardIdTasksStreamGetParams = { + since?: string | null; +}; diff --git a/frontend/src/api/generated/model/taskCommentCreate.ts b/frontend/src/api/generated/model/taskCommentCreate.ts index 187ca019..8da36449 100644 --- a/frontend/src/api/generated/model/taskCommentCreate.ts +++ b/frontend/src/api/generated/model/taskCommentCreate.ts @@ -6,5 +6,6 @@ */ export interface TaskCommentCreate { + /** @minLength 1 */ message: string; } diff --git a/frontend/src/api/generated/model/taskCreate.ts b/frontend/src/api/generated/model/taskCreate.ts index b52d6172..d9547ee6 100644 --- a/frontend/src/api/generated/model/taskCreate.ts +++ b/frontend/src/api/generated/model/taskCreate.ts @@ -4,11 +4,12 @@ * Mission Control API * OpenAPI spec version: 0.1.0 */ +import type { TaskCreateStatus } from "./taskCreateStatus"; export interface TaskCreate { title: string; description?: string | null; - status?: string; + status?: TaskCreateStatus; priority?: string; due_at?: string | null; assigned_agent_id?: string | null; diff --git a/frontend/src/api/generated/model/taskCreateStatus.ts b/frontend/src/api/generated/model/taskCreateStatus.ts new file mode 100644 index 00000000..f184079c --- /dev/null +++ b/frontend/src/api/generated/model/taskCreateStatus.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type TaskCreateStatus = + (typeof TaskCreateStatus)[keyof typeof TaskCreateStatus]; + +export const TaskCreateStatus = { + inbox: "inbox", + in_progress: "in_progress", + review: "review", + done: "done", +} as const; diff --git a/frontend/src/api/generated/model/taskRead.ts b/frontend/src/api/generated/model/taskRead.ts index 6cfc6ecd..c4854855 100644 --- a/frontend/src/api/generated/model/taskRead.ts +++ b/frontend/src/api/generated/model/taskRead.ts @@ -4,11 +4,12 @@ * Mission Control API * OpenAPI spec version: 0.1.0 */ +import type { TaskReadStatus } from "./taskReadStatus"; export interface TaskRead { title: string; description?: string | null; - status?: string; + status?: TaskReadStatus; priority?: string; due_at?: string | null; assigned_agent_id?: string | null; diff --git a/frontend/src/api/generated/model/taskReadStatus.ts b/frontend/src/api/generated/model/taskReadStatus.ts new file mode 100644 index 00000000..66e5c36d --- /dev/null +++ b/frontend/src/api/generated/model/taskReadStatus.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type TaskReadStatus = + (typeof TaskReadStatus)[keyof typeof TaskReadStatus]; + +export const TaskReadStatus = { + inbox: "inbox", + in_progress: "in_progress", + review: "review", + done: "done", +} as const; diff --git a/frontend/src/api/generated/model/taskUpdate.ts b/frontend/src/api/generated/model/taskUpdate.ts index 45715dfc..ce865ebb 100644 --- a/frontend/src/api/generated/model/taskUpdate.ts +++ b/frontend/src/api/generated/model/taskUpdate.ts @@ -8,7 +8,7 @@ export interface TaskUpdate { title?: string | null; description?: string | null; - status?: string | null; + status?: "inbox" | "in_progress" | "review" | "done" | null; priority?: string | null; due_at?: string | null; assigned_agent_id?: string | null; diff --git a/frontend/src/api/generated/model/updateAgentApiV1AgentsAgentIdPatchParams.ts b/frontend/src/api/generated/model/updateAgentApiV1AgentsAgentIdPatchParams.ts new file mode 100644 index 00000000..7a78a25d --- /dev/null +++ b/frontend/src/api/generated/model/updateAgentApiV1AgentsAgentIdPatchParams.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * Mission Control API + * OpenAPI spec version: 0.1.0 + */ + +export type UpdateAgentApiV1AgentsAgentIdPatchParams = { + force?: boolean; +}; diff --git a/frontend/src/api/generated/tasks/tasks.ts b/frontend/src/api/generated/tasks/tasks.ts index 5de0e1ee..75dbcc39 100644 --- a/frontend/src/api/generated/tasks/tasks.ts +++ b/frontend/src/api/generated/tasks/tasks.ts @@ -21,9 +21,10 @@ import type { } from "@tanstack/react-query"; import type { - DeleteTaskApiV1BoardsBoardIdTasksTaskIdDelete200, HTTPValidationError, ListTasksApiV1BoardsBoardIdTasksGetParams, + OkResponse, + StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, TaskCommentCreate, TaskCommentRead, TaskCreate, @@ -35,6 +36,258 @@ import { customFetch } from "../../mutator"; type SecondParameter unknown> = Parameters[1]; +/** + * @summary Stream Tasks + */ +export type streamTasksApiV1BoardsBoardIdTasksStreamGetResponse200 = { + data: unknown; + status: 200; +}; + +export type streamTasksApiV1BoardsBoardIdTasksStreamGetResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type streamTasksApiV1BoardsBoardIdTasksStreamGetResponseSuccess = + streamTasksApiV1BoardsBoardIdTasksStreamGetResponse200 & { + headers: Headers; + }; +export type streamTasksApiV1BoardsBoardIdTasksStreamGetResponseError = + streamTasksApiV1BoardsBoardIdTasksStreamGetResponse422 & { + headers: Headers; + }; + +export type streamTasksApiV1BoardsBoardIdTasksStreamGetResponse = + | streamTasksApiV1BoardsBoardIdTasksStreamGetResponseSuccess + | streamTasksApiV1BoardsBoardIdTasksStreamGetResponseError; + +export const getStreamTasksApiV1BoardsBoardIdTasksStreamGetUrl = ( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/api/v1/boards/${boardId}/tasks/stream?${stringifiedParams}` + : `/api/v1/boards/${boardId}/tasks/stream`; +}; + +export const streamTasksApiV1BoardsBoardIdTasksStreamGet = async ( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, + options?: RequestInit, +): Promise => { + return customFetch( + getStreamTasksApiV1BoardsBoardIdTasksStreamGetUrl(boardId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getStreamTasksApiV1BoardsBoardIdTasksStreamGetQueryKey = ( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, +) => { + return [ + `/api/v1/boards/${boardId}/tasks/stream`, + ...(params ? [params] : []), + ] as const; +}; + +export const getStreamTasksApiV1BoardsBoardIdTasksStreamGetQueryOptions = < + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getStreamTasksApiV1BoardsBoardIdTasksStreamGetQueryKey(boardId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + streamTasksApiV1BoardsBoardIdTasksStreamGet(boardId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!boardId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type StreamTasksApiV1BoardsBoardIdTasksStreamGetQueryResult = + NonNullable< + Awaited> + >; +export type StreamTasksApiV1BoardsBoardIdTasksStreamGetQueryError = + HTTPValidationError; + +export function useStreamTasksApiV1BoardsBoardIdTasksStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params: undefined | StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useStreamTasksApiV1BoardsBoardIdTasksStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited< + ReturnType + >, + TError, + Awaited< + ReturnType + > + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useStreamTasksApiV1BoardsBoardIdTasksStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Stream Tasks + */ + +export function useStreamTasksApiV1BoardsBoardIdTasksStreamGet< + TData = Awaited< + ReturnType + >, + TError = HTTPValidationError, +>( + boardId: string, + params?: StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getStreamTasksApiV1BoardsBoardIdTasksStreamGetQueryOptions( + boardId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + /** * @summary List Tasks */ @@ -521,7 +774,7 @@ export const useUpdateTaskApiV1BoardsBoardIdTasksTaskIdPatch = < * @summary Delete Task */ export type deleteTaskApiV1BoardsBoardIdTasksTaskIdDeleteResponse200 = { - data: DeleteTaskApiV1BoardsBoardIdTasksTaskIdDelete200; + data: OkResponse; status: 200; }; diff --git a/frontend/src/api/mutator.ts b/frontend/src/api/mutator.ts index 581c51ee..4c1a12ff 100644 --- a/frontend/src/api/mutator.ts +++ b/frontend/src/api/mutator.ts @@ -1,3 +1,38 @@ +type ClerkSession = { + getToken: () => Promise; +}; + +type ClerkGlobal = { + session?: ClerkSession | null; +}; + +export class ApiError extends Error { + status: number; + data: TData | null; + + constructor(status: number, message: string, data: TData | null) { + super(message); + this.name = "ApiError"; + this.status = status; + this.data = data; + } +} + +const resolveClerkToken = async (): Promise => { + if (typeof window === "undefined") { + return null; + } + const clerk = (window as unknown as { Clerk?: ClerkGlobal }).Clerk; + if (!clerk?.session) { + return null; + } + try { + return await clerk.session.getToken(); + } catch { + return null; + } +}; + export const customFetch = async ( url: string, options: RequestInit @@ -7,21 +42,73 @@ export const customFetch = async ( throw new Error("NEXT_PUBLIC_API_URL is not set."); } const baseUrl = rawBaseUrl.replace(/\/+$/, ""); + + const headers = new Headers(options.headers); + const hasBody = options.body !== undefined && options.body !== null; + if (hasBody && !headers.has("Content-Type")) { + headers.set("Content-Type", "application/json"); + } + if (!headers.has("Authorization")) { + const token = await resolveClerkToken(); + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + } + const response = await fetch(`${baseUrl}${url}`, { ...options, - headers: { - "Content-Type": "application/json", - ...(options.headers ?? {}), - }, + headers, }); if (!response.ok) { - throw new Error("Request failed"); + const contentType = response.headers.get("content-type") ?? ""; + let errorData: unknown = null; + const isJson = + contentType.includes("application/json") || contentType.includes("+json"); + if (isJson) { + errorData = (await response.json().catch(() => null)) as unknown; + } else { + errorData = await response.text().catch(() => ""); + } + + let message = + typeof errorData === "string" && errorData ? errorData : "Request failed"; + if (errorData && typeof errorData === "object") { + const detail = (errorData as { detail?: unknown }).detail; + if (typeof detail === "string" && detail) { + message = detail; + } else if (Array.isArray(detail) && detail.length) { + const first = detail[0] as { msg?: unknown }; + if (first && typeof first === "object" && typeof first.msg === "string") { + message = first.msg; + } + } + } + throw new ApiError(response.status, message, errorData); } if (response.status === 204) { - return undefined as T; + return { + data: undefined, + status: response.status, + headers: response.headers, + } as T; } - return (await response.json()) as T; + const contentType = response.headers.get("content-type") ?? ""; + const isJson = + contentType.includes("application/json") || contentType.includes("+json"); + if (isJson) { + const data = (await response.json()) as unknown; + return { data, status: response.status, headers: response.headers } as T; + } + if (contentType.includes("text/event-stream")) { + return { + data: response, + status: response.status, + headers: response.headers, + } as T; + } + const text = await response.text().catch(() => ""); + return { data: text, status: response.status, headers: response.headers } as T; }; diff --git a/frontend/src/app/agents/[agentId]/edit/page.tsx b/frontend/src/app/agents/[agentId]/edit/page.tsx index 7917d4f0..83f7be96 100644 --- a/frontend/src/app/agents/[agentId]/edit/page.tsx +++ b/frontend/src/app/agents/[agentId]/edit/page.tsx @@ -1,10 +1,21 @@ "use client"; -import { useEffect, useState } from "react"; +import { useMemo, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { ApiError } from "@/api/mutator"; +import { + type getAgentApiV1AgentsAgentIdGetResponse, + useGetAgentApiV1AgentsAgentIdGet, + useUpdateAgentApiV1AgentsAgentIdPatch, +} from "@/api/generated/agents/agents"; +import { + type listBoardsApiV1BoardsGetResponse, + useListBoardsApiV1BoardsGet, +} from "@/api/generated/boards/boards"; +import type { AgentRead, AgentUpdate, BoardRead } from "@/api/generated/model"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardShell } from "@/components/templates/DashboardShell"; import { Button } from "@/components/ui/button"; @@ -20,34 +31,11 @@ import { SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; -import { getApiBaseUrl } from "@/lib/api-base"; import { DEFAULT_IDENTITY_PROFILE, DEFAULT_SOUL_TEMPLATE, } from "@/lib/agent-templates"; -const apiBase = getApiBaseUrl(); - -type Agent = { - id: string; - name: string; - board_id?: string | null; - is_gateway_main?: boolean; - heartbeat_config?: { - every?: string; - target?: string; - } | null; - identity_profile?: IdentityProfile | null; - identity_template?: string | null; - soul_template?: string | null; -}; - -type Board = { - id: string; - name: string; - slug: string; -}; - type IdentityProfile = { role: string; communication_style: string; @@ -72,7 +60,7 @@ const HEARTBEAT_TARGET_OPTIONS: SearchableSelectOption[] = [ { value: "last", label: "Last channel" }, ]; -const getBoardOptions = (boards: Board[]): SearchableSelectOption[] => +const getBoardOptions = (boards: BoardRead[]): SearchableSelectOption[] => boards.map((board) => ({ value: board.id, label: board.name, @@ -100,157 +88,169 @@ const withIdentityDefaults = ( }); export default function EditAgentPage() { - const { getToken, isSignedIn } = useAuth(); + const { isSignedIn } = useAuth(); const router = useRouter(); const params = useParams(); const agentIdParam = params?.agentId; const agentId = Array.isArray(agentIdParam) ? agentIdParam[0] : agentIdParam; - const [agent, setAgent] = useState(null); - const [name, setName] = useState(""); - const [boards, setBoards] = useState([]); - const [boardId, setBoardId] = useState(""); - const [boardTouched, setBoardTouched] = useState(false); - const [isGatewayMain, setIsGatewayMain] = useState(false); - const [heartbeatEvery, setHeartbeatEvery] = useState("10m"); - const [heartbeatTarget, setHeartbeatTarget] = useState("none"); - const [identityProfile, setIdentityProfile] = useState({ - ...DEFAULT_IDENTITY_PROFILE, - }); - const [soulTemplate, setSoulTemplate] = useState(DEFAULT_SOUL_TEMPLATE); - const [isLoading, setIsLoading] = useState(false); + const [name, setName] = useState(undefined); + const [boardId, setBoardId] = useState(undefined); + const [isGatewayMain, setIsGatewayMain] = useState( + undefined, + ); + const [heartbeatEvery, setHeartbeatEvery] = useState( + undefined, + ); + const [heartbeatTarget, setHeartbeatTarget] = useState( + undefined, + ); + const [identityProfile, setIdentityProfile] = useState< + IdentityProfile | undefined + >(undefined); + const [soulTemplate, setSoulTemplate] = useState( + undefined, + ); const [error, setError] = useState(null); - const loadBoards = async () => { - if (!isSignedIn) return; - try { - const token = await getToken(); - const response = await fetch(`${apiBase}/api/v1/boards`, { - headers: { Authorization: token ? `Bearer ${token}` : "" }, + const boardsQuery = useListBoardsApiV1BoardsGet< + listBoardsApiV1BoardsGetResponse, + ApiError + >({ + query: { + enabled: Boolean(isSignedIn), + refetchOnMount: "always", + retry: false, + }, + }); + + const agentQuery = useGetAgentApiV1AgentsAgentIdGet< + getAgentApiV1AgentsAgentIdGetResponse, + ApiError + >(agentId ?? "", { + query: { + enabled: Boolean(isSignedIn && agentId), + refetchOnMount: "always", + retry: false, + }, + }); + + const updateMutation = useUpdateAgentApiV1AgentsAgentIdPatch({ + mutation: { + onSuccess: () => { + if (agentId) { + router.push(`/agents/${agentId}`); + } + }, + onError: (err) => { + setError(err.message || "Something went wrong."); + }, + }, + }); + + const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : []; + const loadedAgent: AgentRead | null = + agentQuery.data?.status === 200 ? agentQuery.data.data : null; + + const loadedHeartbeat = useMemo(() => { + const heartbeat = loadedAgent?.heartbeat_config; + if (heartbeat && typeof heartbeat === "object") { + const record = heartbeat as Record; + const every = record.every; + const target = record.target; + return { + every: typeof every === "string" && every.trim() ? every : "10m", + target: typeof target === "string" && target.trim() ? target : "none", + }; + } + return { every: "10m", target: "none" }; + }, [loadedAgent?.heartbeat_config]); + + const loadedIdentityProfile = useMemo(() => { + const identity = loadedAgent?.identity_profile; + if (identity && typeof identity === "object") { + const record = identity as Record; + return withIdentityDefaults({ + role: typeof record.role === "string" ? record.role : undefined, + communication_style: + typeof record.communication_style === "string" + ? record.communication_style + : undefined, + emoji: typeof record.emoji === "string" ? record.emoji : undefined, }); - if (!response.ok) { - throw new Error("Unable to load boards."); - } - const data = (await response.json()) as Board[]; - setBoards(data); - } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong."); } - }; + return withIdentityDefaults(null); + }, [loadedAgent?.identity_profile]); - const loadAgent = async () => { - if (!isSignedIn || !agentId) return; - setIsLoading(true); - setError(null); - try { - const token = await getToken(); - const response = await fetch(`${apiBase}/api/v1/agents/${agentId}`, { - headers: { Authorization: token ? `Bearer ${token}` : "" }, - }); - if (!response.ok) { - throw new Error("Unable to load agent."); - } - const data = (await response.json()) as Agent; - setAgent(data); - setName(data.name); - setIsGatewayMain(Boolean(data.is_gateway_main)); - if (!data.is_gateway_main && data.board_id) { - setBoardId(data.board_id); - } else { - setBoardId(""); - } - setBoardTouched(false); - if (data.heartbeat_config?.every) { - setHeartbeatEvery(data.heartbeat_config.every); - } - if (data.heartbeat_config?.target) { - setHeartbeatTarget(data.heartbeat_config.target); - } - setIdentityProfile(withIdentityDefaults(data.identity_profile)); - setSoulTemplate(data.soul_template?.trim() || DEFAULT_SOUL_TEMPLATE); - } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong."); - } finally { - setIsLoading(false); - } - }; + const loadedSoulTemplate = useMemo(() => { + return loadedAgent?.soul_template?.trim() || DEFAULT_SOUL_TEMPLATE; + }, [loadedAgent?.soul_template]); - useEffect(() => { - loadBoards(); - loadAgent(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSignedIn, agentId]); + const isLoading = + boardsQuery.isLoading || agentQuery.isLoading || updateMutation.isPending; + const errorMessage = + error ?? agentQuery.error?.message ?? boardsQuery.error?.message ?? null; - useEffect(() => { - if (boardTouched || boardId || isGatewayMain) return; - if (agent?.board_id) { - setBoardId(agent.board_id); - return; - } - if (boards.length > 0) { - setBoardId(boards[0].id); - } - }, [agent, boards, boardId, isGatewayMain, boardTouched]); + const resolvedName = name ?? loadedAgent?.name ?? ""; + const resolvedIsGatewayMain = + isGatewayMain ?? Boolean(loadedAgent?.is_gateway_main); + const resolvedHeartbeatEvery = heartbeatEvery ?? loadedHeartbeat.every; + const resolvedHeartbeatTarget = heartbeatTarget ?? loadedHeartbeat.target; + const resolvedIdentityProfile = identityProfile ?? loadedIdentityProfile; + const resolvedSoulTemplate = soulTemplate ?? loadedSoulTemplate; - const handleSubmit = async (event: React.FormEvent) => { + const resolvedBoardId = useMemo(() => { + if (resolvedIsGatewayMain) return boardId ?? ""; + return boardId ?? loadedAgent?.board_id ?? boards[0]?.id ?? ""; + }, [boardId, boards, loadedAgent?.board_id, resolvedIsGatewayMain]); + + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - if (!isSignedIn || !agentId) return; - const trimmed = name.trim(); + if (!isSignedIn || !agentId || !loadedAgent) return; + const trimmed = resolvedName.trim(); if (!trimmed) { setError("Agent name is required."); return; } - if (!isGatewayMain && !boardId) { + if (!resolvedIsGatewayMain && !resolvedBoardId) { setError("Select a board or mark this agent as the gateway main."); return; } - if (isGatewayMain && !boardId && !agent?.is_gateway_main && !agent?.board_id) { + if ( + resolvedIsGatewayMain && + !resolvedBoardId && + !loadedAgent.is_gateway_main && + !loadedAgent.board_id + ) { setError( "Select a board once so we can resolve the gateway main session key." ); return; } - setIsLoading(true); setError(null); - try { - const token = await getToken(); - const payload: Record = { - name: trimmed, - heartbeat_config: { - every: heartbeatEvery.trim() || "10m", - target: heartbeatTarget, - }, - identity_profile: normalizeIdentityProfile(identityProfile), - soul_template: soulTemplate.trim() || null, - }; - if (!isGatewayMain) { - payload.board_id = boardId || null; - } else if (boardId) { - payload.board_id = boardId; - } - if (agent?.is_gateway_main !== isGatewayMain) { - payload.is_gateway_main = isGatewayMain; - } - const response = await fetch( - `${apiBase}/api/v1/agents/${agentId}?force=true`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: token ? `Bearer ${token}` : "", - }, - body: JSON.stringify(payload), - } - ); - if (!response.ok) { - throw new Error("Unable to update agent."); - } - router.push(`/agents/${agentId}`); - } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong."); - } finally { - setIsLoading(false); + + const payload: AgentUpdate = { + name: trimmed, + heartbeat_config: { + every: resolvedHeartbeatEvery.trim() || "10m", + target: resolvedHeartbeatTarget, + } as unknown as Record, + identity_profile: normalizeIdentityProfile(resolvedIdentityProfile) as unknown as Record< + string, + unknown + > | null, + soul_template: resolvedSoulTemplate.trim() || null, + }; + if (!resolvedIsGatewayMain) { + payload.board_id = resolvedBoardId || null; + } else if (resolvedBoardId) { + payload.board_id = resolvedBoardId; } + if (Boolean(loadedAgent.is_gateway_main) !== resolvedIsGatewayMain) { + payload.is_gateway_main = resolvedIsGatewayMain; + } + + updateMutation.mutate({ agentId, params: { force: true }, data: payload }); }; return ( @@ -275,7 +275,7 @@ export default function EditAgentPage() {

- {agent?.name ?? "Edit agent"} + {resolvedName.trim() ? resolvedName : loadedAgent?.name ?? "Edit agent"}

Status is controlled by agent heartbeat. @@ -299,7 +299,7 @@ export default function EditAgentPage() { Agent name * setName(event.target.value)} placeholder="e.g. Deploy bot" disabled={isLoading} @@ -310,12 +310,12 @@ export default function EditAgentPage() { Role - setIdentityProfile((current) => ({ - ...current, + setIdentityProfile({ + ...resolvedIdentityProfile, role: event.target.value, - })) + }) } placeholder="e.g. Founder, Social Media Manager" disabled={isLoading} @@ -327,7 +327,7 @@ export default function EditAgentPage() {

- {boardId ? ( + {resolvedBoardId ? (
{ - setBoardTouched(true); - setBoardId(value); - }} + value={resolvedBoardId} + onValueChange={(value) => setBoardId(value)} options={getBoardOptions(boards)} - placeholder={isGatewayMain ? "No board (main agent)" : "Select board"} + placeholder={resolvedIsGatewayMain ? "No board (main agent)" : "Select board"} searchPlaceholder="Search boards..." emptyMessage="No matching boards." triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200" @@ -365,7 +361,7 @@ export default function EditAgentPage() { itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900" disabled={boards.length === 0} /> - {isGatewayMain ? ( + {resolvedIsGatewayMain ? (

Main agents are not attached to a board. If a board is selected, it is only used to resolve the gateway main @@ -382,12 +378,12 @@ export default function EditAgentPage() { Emoji setIsGatewayMain(event.target.checked)} disabled={isLoading} /> @@ -437,12 +433,12 @@ export default function EditAgentPage() { Communication style - setIdentityProfile((current) => ({ - ...current, + setIdentityProfile({ + ...resolvedIdentityProfile, communication_style: event.target.value, - })) + }) } disabled={isLoading} /> @@ -452,7 +448,7 @@ export default function EditAgentPage() { Soul template