From 2a898f3f08d12626d1ee687d30e5a4cab47931b4 Mon Sep 17 00:00:00 2001 From: haikdc Date: Sat, 18 Apr 2026 02:23:44 -0700 Subject: [PATCH] =?UTF-8?q?[Haik]:=20refactor:=20overhaul=20agents=20API?= =?UTF-8?q?=20contract=20and=20frontend=20backend=20layer=20=E2=80=94=20re?= =?UTF-8?q?name=20all=20backend=20agent=20routes=20from=20RESTful=20path-p?= =?UTF-8?q?aram=20style=20(e.g.=20/sessions/{id}/message)=20to=20flat=20ac?= =?UTF-8?q?tion-named=20endpoints=20(e.g.=20/send=5Fmessage),=20replace=20?= =?UTF-8?q?Pydantic=20BaseModel=20request=20classes=20(LaunchBody,=20Messa?= =?UTF-8?q?geBody,=20etc.)=20with=20inline=20FastAPI=20Body()=20parameters?= =?UTF-8?q?,=20delete=20the=20scattered=20frontend=20shared/routes/=20dire?= =?UTF-8?q?ctory=20(11=20files)=20and=20agentsThunks.ts,=20and=20replace?= =?UTF-8?q?=20them=20with=20a=20co-located=20shared/backend/apps/=20struct?= =?UTF-8?q?ure=20where=20each=20backend=20app=20has=20its=20routes,=20fetc?= =?UTF-8?q?h=20functions,=20and=20async=20thunks=20in=20a=20single=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/apps/agents/agents.py | 170 +++++----- backend/apps/dashboards/dashboards.py | 8 +- .../apps}/.lintignore-max-folder-items | 0 frontend/src/shared/backend/apps/agents.ts | 310 ++++++++++++++++++ .../base.ts => backend/base_routes.ts} | 0 frontend/src/shared/routes/agents.ts | 19 -- frontend/src/shared/routes/app_builder.ts | 15 - frontend/src/shared/routes/dashboards.ts | 11 - frontend/src/shared/routes/health.ts | 5 - frontend/src/shared/routes/modes.ts | 11 - frontend/src/shared/routes/settings.ts | 7 - frontend/src/shared/routes/skills.ts | 15 - frontend/src/shared/routes/subscriptions.ts | 10 - frontend/src/shared/routes/tools.ts | 16 - frontend/src/shared/state/agentsSlice.ts | 1 - frontend/src/shared/state/agentsThunks.ts | 240 -------------- 16 files changed, 407 insertions(+), 431 deletions(-) rename frontend/src/shared/{routes => backend/apps}/.lintignore-max-folder-items (100%) create mode 100644 frontend/src/shared/backend/apps/agents.ts rename frontend/src/shared/{routes/base.ts => backend/base_routes.ts} (100%) delete mode 100644 frontend/src/shared/routes/agents.ts delete mode 100644 frontend/src/shared/routes/app_builder.ts delete mode 100644 frontend/src/shared/routes/dashboards.ts delete mode 100644 frontend/src/shared/routes/health.ts delete mode 100644 frontend/src/shared/routes/modes.ts delete mode 100644 frontend/src/shared/routes/settings.ts delete mode 100644 frontend/src/shared/routes/skills.ts delete mode 100644 frontend/src/shared/routes/subscriptions.ts delete mode 100644 frontend/src/shared/routes/tools.ts delete mode 100644 frontend/src/shared/state/agentsThunks.ts diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py index a809ea9b..58190598 100644 --- a/backend/apps/agents/agents.py +++ b/backend/apps/agents/agents.py @@ -14,7 +14,7 @@ from datetime import datetime from typing import Optional, List, Dict from uuid import uuid4 -from fastapi import HTTPException, WebSocket, WebSocketDisconnect +from fastapi import HTTPException, WebSocket, WebSocketDisconnect, Body import json from pydantic import BaseModel @@ -113,32 +113,32 @@ async def websocket_dashboard(websocket: WebSocket): # Session CRUD # --------------------------------------------------------------------------- -@agents.router.get("/SESSIONS") -async def list_sessions(dashboard_id: str = "") -> dict: +@agents.router.get("/get_all_sessions") +async def get_all_sessions(dashboard_id: str = Body(default="")) -> dict: result: List[Agent] = list[Agent](SESSIONS.values()) if dashboard_id: result: List[Agent] = [a for a in result if getattr(a, "dashboard_id", None) == dashboard_id] return {"SESSIONS": [a.model_dump(mode="json") for a in result]} -@agents.router.get("/sessions/{session_id}") -async def get_session(session_id: str) -> dict: +@agents.router.get("/get_session") +async def get_session(session_id: str = Body()) -> dict: return get_agent(session_id).model_dump(mode="json") -class LaunchBody(BaseModel): - model: str = "claude-sonnet-4-6" - mode: str = "agent" - system_prompt: str = "" - max_turns: int = 200 -@agents.router.post("/launch") -async def launch(body: LaunchBody) -> dict: +@agents.router.post("/launch_agent") +async def launch_agent( + model: str = Body(), + mode: str = Body(), + system_prompt: str = Body(), + max_turns: int = Body(), +) -> dict: agent: Agent = Agent( - model=body.model, - mode=body.mode, + model=model, + mode=mode, status="stopped", - config=ClaudeAgentOptions(max_turns=body.max_turns), + config=ClaudeAgentOptions(max_turns=max_turns), ) agent.on_event = COMMS_MANAGER.make_session_emitter(agent.session_id) SESSIONS[agent.session_id] = agent @@ -152,8 +152,8 @@ async def launch(body: LaunchBody) -> dict: mcp_servers: Dict[str, McpServerConfig] = toolkit.collect_mcp_servers() resolved_mode_config: ResolvedModeConfig = await ResolvedModeConfig.create( - mode_id=body.mode, - session_prompt=body.system_prompt or None, + mode_id=mode, + session_prompt=system_prompt or None, toolkit=toolkit, ) @@ -167,9 +167,9 @@ async def launch(body: LaunchBody) -> dict: agent.config = ClaudeAgentOptions( env=env, - model=body.model, + model=model, system_prompt=resolved_mode_config.system_prompt, - max_turns=body.max_turns, + max_turns=max_turns, cwd=resolved_mode_config.cwd, mcp_servers=mcp_servers if mcp_servers else None, allowed_tools=resolved_mode_config.allowed_tools, @@ -189,14 +189,14 @@ async def launch(body: LaunchBody) -> dict: return {"session_id": agent.session_id, "session": agent.snapshot().model_dump(mode="json")} -class UpdateBody(BaseModel): - system_prompt: Optional[str] = None - -@agents.router.patch("/SESSIONS/{session_id}") -async def update_session(session_id: str, body: UpdateBody) -> dict: +@agents.router.patch("/update_system_prompt") +async def update_system_prompt( + session_id: str = Body(), + system_prompt: Optional[str] = Body(default=None), +) -> dict: agent: Agent = get_agent(session_id) - if body.system_prompt is not None: - agent.config.system_prompt = body.system_prompt + if system_prompt is not None: + agent.config.system_prompt = system_prompt await agent.emit(AgentStatusEvent( session_id=session_id, status=agent.status, session=agent.snapshot(), @@ -204,8 +204,8 @@ async def update_session(session_id: str, body: UpdateBody) -> dict: return {"ok": True} -@agents.router.delete("/sessions/{session_id}") -async def delete_session(session_id: str) -> dict: +@agents.router.delete("/delete_session") +async def delete_session(session_id: str = Body()) -> dict: agent: Optional[Agent] = SESSIONS.pop(session_id, None) if agent is not None: await agent.stop_agent() @@ -228,16 +228,28 @@ class MessageBody(BaseModel): attached_skills: Optional[List[dict]] = None hidden: bool = False -@agents.router.post("/SESSIONS/{session_id}/message") -async def send_message(session_id: str, body: MessageBody) -> dict: +@agents.router.post("/send_message") +async def send_message( + session_id: str = Body(), + prompt: str = Body(), + mode: Optional[str] = Body(default=None), + model: Optional[str] = Body(default=None), + images: Optional[List[str]] = Body(default=None), + image_media_types: Optional[List[str]] = Body(default=None), + context_paths: Optional[List[dict]] = Body(default=None), + forced_tools: Optional[List[str]] = Body(default=None), + attached_skills: Optional[List[dict]] = Body(default=None), + hidden: bool = Body(default=False), + +) -> dict: agent: Agent = get_agent(session_id) - mode_changed: bool = bool(body.mode and body.mode != agent.mode) - model_changed: bool = bool(body.model and body.model != agent.model) + mode_changed: bool = bool(mode and mode != agent.mode) + model_changed: bool = bool(model and model != agent.model) if mode_changed: - agent.mode = body.mode # type: ignore[assignment] + agent.mode = mode # type: ignore[assignment] if model_changed: - agent.model = body.model # type: ignore[assignment] + agent.model = model # type: ignore[assignment] if mode_changed or model_changed: resolved_mode_config: ResolvedModeConfig = await ResolvedModeConfig.create( @@ -253,39 +265,38 @@ async def send_message(session_id: str, body: MessageBody) -> dict: agent.config.cwd = resolved_mode_config.cwd msg: UserMessage = UserMessage( - content=body.prompt, + content=prompt, branch_id=agent.branch_id, - images=body.images or [], - image_media_types=body.image_media_types or [], - context_paths=body.context_paths or [], - attached_skills=body.attached_skills or [], - forced_tools=body.forced_tools or [], - hidden=body.hidden, + images=images or [], + image_media_types=image_media_types or [], + context_paths=context_paths or [], + attached_skills=attached_skills or [], + forced_tools=forced_tools or [], + hidden=hidden, ) await agent.send_message(msg) return {"ok": True} -@agents.router.post("/sessions/{session_id}/stop") -async def stop_agent(session_id: str) -> dict: +@agents.router.post("/stop_agent") +async def stop_agent(session_id: str = Body()) -> dict: agent: Agent = get_agent(session_id) await agent.stop_agent() return {"ok": True} -class ApprovalBody(BaseModel): - request_id: str - behavior: str - message: str = "" - updated_input: Optional[dict] = None - -@agents.router.post("/approval") -async def handle_approval(body: ApprovalBody) -> dict: +@agents.router.post("/handle_approval") +async def handle_approval( + request_id: str = Body(), + behavior: str = Body(), + message: str = Body(default=""), + updated_input: Optional[dict] = Body(default=None), +) -> dict: await COMMS_MANAGER.resolve_approval( - request_id=body.request_id, - behavior=body.behavior, - message=body.message, - updated_input=body.updated_input, + request_id=request_id, + behavior=behavior, + message=message, + updated_input=updated_input, ) return {"ok": True} @@ -294,31 +305,32 @@ async def handle_approval(body: ApprovalBody) -> dict: # Branching # --------------------------------------------------------------------------- -class EditMessageBody(BaseModel): - message_id: str - content: str -@agents.router.post("/sessions/{session_id}/edit_message") -async def edit_message(session_id: str, body: EditMessageBody) -> dict: +@agents.router.post("/edit_message") +async def edit_message( + session_id: str = Body(), + message_id: str = Body(), + content: str = Body(), +) -> dict: agent: Agent = get_agent(session_id) await agent.stop_agent() - fork: Agent = agent.branch(body.message_id) + fork: Agent = agent.branch(message_id) SESSIONS[fork.session_id] = fork - edited_msg: UserMessage = UserMessage(content=body.content, branch_id=fork.branch_id) + edited_msg: UserMessage = UserMessage(content=content, branch_id=fork.branch_id) await fork.send_message(edited_msg) return {"ok": True, "branch_id": fork.branch_id, "session_id": fork.session_id} -class SwitchBranchBody(BaseModel): - branch_id: str - -@agents.router.post("/SESSIONS/{session_id}/switch_branch") -async def switch_branch(session_id: str, body: SwitchBranchBody) -> dict: +@agents.router.post("/switch_branch") +async def switch_branch( + session_id: str = Body(), + branch_id: str = Body(), +) -> dict: agent: Agent = get_agent(session_id) - agent.branch_id = body.branch_id + agent.branch_id = branch_id await agent.emit(BranchSwitchedEvent( - session_id=session_id, active_branch_id=body.branch_id, + session_id=session_id, active_branch_id=branch_id, )) return {"ok": True} @@ -327,8 +339,8 @@ async def switch_branch(session_id: str, body: SwitchBranchBody) -> dict: # Persistence # --------------------------------------------------------------------------- -@agents.router.post("/sessions/{session_id}/close") -async def close_session(session_id: str) -> dict: +@agents.router.post("/close_session") +async def close_session(session_id: str = Body()) -> dict: agent: Optional[Agent] = SESSIONS.pop(session_id, None) if not agent: raise HTTPException(status_code=404, detail="Session not found") @@ -343,8 +355,8 @@ async def close_session(session_id: str) -> dict: return {"ok": True} -@agents.router.post("/SESSIONS/{session_id}/resume") -async def resume_session(session_id: str) -> dict: +@agents.router.post("/resume_session") +async def resume_session(session_id: str = Body()) -> dict: if session_id in SESSIONS: return {"session": SESSIONS[session_id].model_dump(mode="json")} agent: Optional[Agent] = AGENT_STORE.load_or_none(session_id) @@ -366,8 +378,8 @@ async def resume_session(session_id: str) -> dict: return {"session": agent.snapshot().model_dump(mode="json")} -@agents.router.post("/SESSIONS/{session_id}/duplicate") -async def duplicate_session(session_id: str, body: dict = {}) -> dict: +@agents.router.post("/duplicate_session") +async def duplicate_session(session_id: str = Body()) -> dict: source: Optional[Agent] = SESSIONS.get(session_id) if source is None: source = AGENT_STORE.load_or_none(session_id) @@ -395,8 +407,12 @@ async def duplicate_session(session_id: str, body: dict = {}) -> dict: return {"session": clone.snapshot().model_dump(mode="json")} -@agents.router.get("/history") -async def get_history(q: str = "", limit: int = 20, offset: int = 0, dashboard_id: str = "") -> dict: +@agents.router.get("/get_history") +async def get_history( + q: str = Body(default=""), + limit: int = Body(default=20), + offset: int = Body(default=0), +) -> dict: all_agents: List[Agent] = AGENT_STORE.load_all() all_agents.sort( key=lambda a: a.messages.messages[-1].timestamp if a.messages.messages else datetime.min, diff --git a/backend/apps/dashboards/dashboards.py b/backend/apps/dashboards/dashboards.py index 44d8a21b..0e2ffae2 100644 --- a/backend/apps/dashboards/dashboards.py +++ b/backend/apps/dashboards/dashboards.py @@ -9,7 +9,7 @@ from backend.config.Apps import SubApp from backend.core.db.PydanticStore import PydanticStore from backend.core.shared_structs.dashboard.Dashboard import Dashboard from backend.core.shared_structs.dashboard.DashboardLayout import DashboardLayout -from backend.apps.agents.agents import list_sessions, delete_session +from backend.apps.agents.agents import get_all_sessions, delete_session from backend.apps.settings.settings import load_settings from backend.apps.dashboards.generate_dashboard_name import generate_dashboard_name from backend.ports import NINE_ROUTER_PORT @@ -61,7 +61,7 @@ async def create_dashboard(body: DashboardCreate): return dashboard.model_dump(mode="json") -# TODO: Maybe parse the output of list_sessions into actual Agent objects? +# TODO: Maybe parse the output of get_all_sessions into actual Agent objects? @dashboards.router.post("/{dashboard_id}/generate-name") async def generate_name(dashboard_id: str): dashboard = DASHBOARD_STORE.load(dashboard_id) @@ -69,7 +69,7 @@ async def generate_name(dashboard_id: str): if not dashboard.auto_named and dashboard.name != "Untitled Dashboard": return {"name": dashboard.name, "auto_named": dashboard.auto_named} - sessions_resp = await list_sessions(dashboard_id=dashboard_id) + sessions_resp = await get_all_sessions(dashboard_id=dashboard_id) sessions = sessions_resp.get("SESSIONS", []) prompts = [] @@ -134,7 +134,7 @@ async def update_dashboard(dashboard_id: str, body: DashboardUpdate): async def delete_dashboard(dashboard_id: str): DASHBOARD_STORE.load(dashboard_id) # confirm it exists (raises 404 if not) - sessions_resp = await list_sessions(dashboard_id=dashboard_id) + sessions_resp = await get_all_sessions(dashboard_id=dashboard_id) for session in sessions_resp.get("SESSIONS", []): try: await delete_session(session["session_id"]) diff --git a/frontend/src/shared/routes/.lintignore-max-folder-items b/frontend/src/shared/backend/apps/.lintignore-max-folder-items similarity index 100% rename from frontend/src/shared/routes/.lintignore-max-folder-items rename to frontend/src/shared/backend/apps/.lintignore-max-folder-items diff --git a/frontend/src/shared/backend/apps/agents.ts b/frontend/src/shared/backend/apps/agents.ts new file mode 100644 index 00000000..7d4d04d7 --- /dev/null +++ b/frontend/src/shared/backend/apps/agents.ts @@ -0,0 +1,310 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { API_BASE } from '@/shared/backend/base_routes'; +import type { + AgentSession, AgentConfig, HistorySession, +} from '@/shared/state/agentsTypes'; + +const AGENTS_API: string = `${API_BASE}/agents`; + +// --------------------------------------------------------------------------- +// Session CRUD +// --------------------------------------------------------------------------- + + +const get_all_sessions_endpoint: string = `${AGENTS_API}/get_all_sessions`; +async function get_all_sessions_function(dashboardId?: string): Promise { + const params = new URLSearchParams(); + if (dashboardId) params.set('dashboard_id', dashboardId); + const res = await fetch(get_all_sessions_endpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ params }), + }); + const data = await res.json(); + return data.sessions as AgentSession[]; +} +export const GET_ALL_SESSIONS = createAsyncThunk( + get_all_sessions_endpoint, + get_all_sessions_function, +); + + + +const get_session_endpoint: string = `${AGENTS_API}/get_session`; +async function get_session_function(sessionId: string): Promise { + const res = await fetch(get_session_endpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const GET_SESSION = createAsyncThunk( + get_session_endpoint, + get_session_function, +); + + + +const launch_agent_endpoint: string = `${AGENTS_API}/launch_agent`; +async function launch_agent_function(config: AgentConfig): Promise { + const res = await fetch(launch_agent_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const LAUNCH_AGENT = createAsyncThunk( + launch_agent_endpoint, + launch_agent_function, +); + + + +const update_system_prompt_endpoint: string = `${AGENTS_API}/update_system_prompt`; +async function update_system_prompt_function(sessionId: string, systemPrompt: string): Promise { + const res = await fetch(update_system_prompt_endpoint, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId, system_prompt: systemPrompt }), + }); + const data = await res.json(); + return data.ok as string; +} +export const UPDATE_SYSTEM_PROMPT = createAsyncThunk( + update_system_prompt_endpoint, + update_system_prompt_function, +); + + + +const delete_session_endpoint: string = `${AGENTS_API}/delete_session`; +async function delete_session_function(sessionId: string): Promise { + const res = await fetch(delete_session_endpoint, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + const data = await res.json(); + return data.sessionId as string; +} +export const DELETE_SESSION = createAsyncThunk( + delete_session_endpoint, + delete_session_function +); + + + +// --------------------------------------------------------------------------- +// Agent lifecycle +// --------------------------------------------------------------------------- + + + +const send_message_endpoint: string = `${AGENTS_API}/send_message`; +async function send_message_function( + sessionId: string, + prompt: string, + mode: string, + model: string, + provider: string, + images: string[], + contextPaths: string[], + forcedTools: string[], + attachedSkills: string[], + hidden: boolean, + selectedBrowserIds: string[] +): Promise { + const res = await fetch(send_message_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + session_id: sessionId, + prompt: prompt, + mode: mode, + model: model, + provider: provider, + images: images, + context_paths: contextPaths, + forced_tools: forcedTools, + attached_skills: attachedSkills, + hidden: hidden, + selected_browser_ids: selectedBrowserIds, + }), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const SEND_MESSAGE = createAsyncThunk( + send_message_endpoint, + send_message_function, +); + + + +const stop_agent_endpoint: string = `${AGENTS_API}/stop_agent`; +async function stop_agent_function(sessionId: string): Promise { + const res = await fetch(stop_agent_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + const data = await res.json(); + return data.sessionId as string; +} +export const STOP_AGENT = createAsyncThunk( + stop_agent_endpoint, + stop_agent_function, +); + + + +const handle_approval_endpoint: string = `${AGENTS_API}/handle_approval`; +async function handle_approval_function( + requestId: string, + behavior: 'allow' | 'deny', + message?: string, + updatedInput?: Record +): Promise<{ requestId: string; behavior: 'allow' | 'deny' }> { + const res = await fetch(handle_approval_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + request_id: requestId, + behavior: behavior, + message: message, + updated_input: updatedInput, + }), + }); + if (!res.ok) throw new Error(`Approval request failed (${res.status})`); + return { requestId, behavior }; +} +export const HANDLE_APPROVAL = createAsyncThunk( + handle_approval_endpoint, + handle_approval_function +); + + + +// --------------------------------------------------------------------------- +// Branching +// --------------------------------------------------------------------------- + + + + +const edit_message_endpoint: string = `${AGENTS_API}/edit_message`; +async function edit_message_function(sessionId: string, messageId: string, content: string): Promise { + const res = await fetch(edit_message_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + session_id: sessionId, + message_id: messageId, + content: content, + }), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const EDIT_MESSAGE = createAsyncThunk( + edit_message_endpoint, + edit_message_function, +); + + +const switch_branch_endpoint: string = `${AGENTS_API}/switch_branch`; +async function switch_branch_function(sessionId: string, branchId: string): Promise { + const res = await fetch(switch_branch_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId, branch_id: branchId }), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const SWITCH_BRANCH = createAsyncThunk( + switch_branch_endpoint, + switch_branch_function, +); + + + +// --------------------------------------------------------------------------- +// Persistence +// --------------------------------------------------------------------------- + + + +const close_session_endpoint: string = `${AGENTS_API}/close_session`; +async function close_session_function(sessionId: string): Promise { + const res = await fetch(close_session_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + const data = await res.json(); + return data.sessionId as string; +} +export const CLOSE_SESSION = createAsyncThunk( + close_session_endpoint, + close_session_function +); + + + +const resume_session_endpoint: string = `${AGENTS_API}/resume_session`; +async function resume_session_function(sessionId: string): Promise { + const res = await fetch(resume_session_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const RESUME_SESSION = createAsyncThunk( + resume_session_endpoint, + resume_session_function +); + + + +const duplicate_session_endpoint: string = `${AGENTS_API}/duplicate_session`; +async function duplicate_session_function(sessionId: string, dashboardId: string, upToMessageId: string): Promise { + const res = await fetch(duplicate_session_endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + session_id: sessionId, + dashboard_id: dashboardId, + up_to_message_id: upToMessageId, + }), + }); + const data = await res.json(); + return data.session as AgentSession; +} +export const DUPLICATE_SESSION = createAsyncThunk( + duplicate_session_endpoint, + duplicate_session_function +); + + + +const get_history_endpoint: string = `${AGENTS_API}/get_history`; +async function get_history_function(q: string, limit: number, offset: number): Promise { + const res = await fetch(get_history_endpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ q, limit, offset }), + }); + const data = await res.json(); + return data.sessions as HistorySession[]; +} +export const GET_HISTORY = createAsyncThunk( + get_history_endpoint, + get_history_function, +); \ No newline at end of file diff --git a/frontend/src/shared/routes/base.ts b/frontend/src/shared/backend/base_routes.ts similarity index 100% rename from frontend/src/shared/routes/base.ts rename to frontend/src/shared/backend/base_routes.ts diff --git a/frontend/src/shared/routes/agents.ts b/frontend/src/shared/routes/agents.ts deleted file mode 100644 index 823c0ce3..00000000 --- a/frontend/src/shared/routes/agents.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { API_BASE, WS_BASE } from '@/shared/routes/base'; - -const AGENTS_API: string = `${API_BASE}/agents`; - -export const AGENTS_WS: string = `${WS_BASE}/api/agents/ws/dashboard`; - -export const AGENTS_SESSIONS_API: string = `${AGENTS_API}/SESSIONS`; -export const AGENTS_SESSION_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/sessions/${sessionId}`; -export const AGENTS_LAUNCH_API: string = `${AGENTS_API}/launch`; -export const AGENTS_UPDATE_SESSION_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/SESSIONS/${sessionId}`; -export const AGENTS_MESSAGE_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/SESSIONS/${sessionId}/message`; -export const AGENTS_STOP_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/sessions/${sessionId}/stop`; -export const AGENTS_APPROVAL_API: string = `${AGENTS_API}/approval`; -export const AGENTS_EDIT_MESSAGE_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/sessions/${sessionId}/edit_message`; -export const AGENTS_SWITCH_BRANCH_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/SESSIONS/${sessionId}/switch_branch`; -export const AGENTS_CLOSE_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/sessions/${sessionId}/close`; -export const AGENTS_RESUME_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/SESSIONS/${sessionId}/resume`; -export const AGENTS_DUPLICATE_API: (sessionId: string) => string = (sessionId: string) => `${AGENTS_API}/SESSIONS/${sessionId}/duplicate`; -export const AGENTS_HISTORY_API: string = `${AGENTS_API}/history`; diff --git a/frontend/src/shared/routes/app_builder.ts b/frontend/src/shared/routes/app_builder.ts deleted file mode 100644 index f2e676d7..00000000 --- a/frontend/src/shared/routes/app_builder.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const APP_BUILDER_API: string = `${API_BASE}/app_builder`; - -export const APP_BUILDER_SOURCE_FILE_API: (appId: string, filepath: string) => string = (appId: string, filepath: string) => `${APP_BUILDER_API}/app/${appId}/source_dir/${filepath}`; -export const APP_BUILDER_SERVE_API: (appId: string, filepath: string) => string = (appId: string, filepath: string) => `${APP_BUILDER_API}/${appId}/serve/${filepath}`; -export const APP_BUILDER_READ_APP_API: (appId: string) => string = (appId: string) => `${APP_BUILDER_API}/app/${appId}`; -export const APP_BUILDER_SEED_API: string = `${APP_BUILDER_API}/app/seed`; -export const APP_BUILDER_FILE_API: (appId: string, filepath: string) => string = (appId: string, filepath: string) => `${APP_BUILDER_API}/app/${appId}/file/${filepath}`; -export const APP_BUILDER_LIST_API: string = `${APP_BUILDER_API}/list`; -export const APP_BUILDER_GET_API: (appId: string) => string = (appId: string) => `${APP_BUILDER_API}/${appId}`; -export const APP_BUILDER_CREATE_API: string = `${APP_BUILDER_API}/create`; -export const APP_BUILDER_UPDATE_API: (appId: string) => string = (appId: string) => `${APP_BUILDER_API}/${appId}`; -export const APP_BUILDER_DELETE_API: (appId: string) => string = (appId: string) => `${APP_BUILDER_API}/${appId}`; -export const APP_BUILDER_EXECUTE_API: string = `${APP_BUILDER_API}/execute`; diff --git a/frontend/src/shared/routes/dashboards.ts b/frontend/src/shared/routes/dashboards.ts deleted file mode 100644 index 0af3eb59..00000000 --- a/frontend/src/shared/routes/dashboards.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const DASHBOARDS_API: string = `${API_BASE}/dashboards`; - -export const DASHBOARDS_LIST_API: string = `${DASHBOARDS_API}/list`; -export const DASHBOARDS_CREATE_API: string = `${DASHBOARDS_API}/create`; -export const DASHBOARDS_GET_API: (dashboardId: string) => string = (dashboardId: string) => `${DASHBOARDS_API}/${dashboardId}`; -export const DASHBOARDS_UPDATE_API: (dashboardId: string) => string = (dashboardId: string) => `${DASHBOARDS_API}/${dashboardId}`; -export const DASHBOARDS_DELETE_API: (dashboardId: string) => string = (dashboardId: string) => `${DASHBOARDS_API}/${dashboardId}`; -export const DASHBOARDS_DUPLICATE_API: (dashboardId: string) => string = (dashboardId: string) => `${DASHBOARDS_API}/${dashboardId}/duplicate`; -export const DASHBOARDS_GENERATE_NAME_API: (dashboardId: string) => string = (dashboardId: string) => `${DASHBOARDS_API}/${dashboardId}/generate-name`; diff --git a/frontend/src/shared/routes/health.ts b/frontend/src/shared/routes/health.ts deleted file mode 100644 index bb88aead..00000000 --- a/frontend/src/shared/routes/health.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const HEALTH_API: string = `${API_BASE}/health`; - -export const HEALTH_CHECK_API: string = `${HEALTH_API}/check`; diff --git a/frontend/src/shared/routes/modes.ts b/frontend/src/shared/routes/modes.ts deleted file mode 100644 index 56aa9cf1..00000000 --- a/frontend/src/shared/routes/modes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const MODES_API: string = `${API_BASE}/modes`; - -export const MODES_LIST_API: string = `${MODES_API}/list`; -export const MODES_GET_API: (modeId: string) => string = (modeId: string) => `${MODES_API}/${modeId}`; -export const MODES_CREATE_API: string = `${MODES_API}/create`; -export const MODES_UPDATE_API: (modeId: string) => string = (modeId: string) => `${MODES_API}/${modeId}`; -export const MODES_RESET_API: (modeId: string) => string = (modeId: string) => `${MODES_API}/${modeId}/reset`; -export const MODES_DELETE_API: (modeId: string) => string = (modeId: string) => `${MODES_API}/${modeId}`; -export const MODES_GET_BY_ID_API: string = `${MODES_API}/get_mode_by_id`; diff --git a/frontend/src/shared/routes/settings.ts b/frontend/src/shared/routes/settings.ts deleted file mode 100644 index 5adb86d0..00000000 --- a/frontend/src/shared/routes/settings.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const SETTINGS_API: string = `${API_BASE}/settings`; - -export const SETTINGS_GET_API: string = SETTINGS_API; -export const SETTINGS_UPDATE_API: string = SETTINGS_API; -export const SETTINGS_RESET_SYSTEM_PROMPT_API: string = `${SETTINGS_API}/reset-system-prompt`; diff --git a/frontend/src/shared/routes/skills.ts b/frontend/src/shared/routes/skills.ts deleted file mode 100644 index 1f662534..00000000 --- a/frontend/src/shared/routes/skills.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const SKILLS_API: string = `${API_BASE}/skills`; - -export const SKILLS_LIST_API: string = `${SKILLS_API}/list`; -export const SKILLS_WORKSPACE_API: (workspaceId: string) => string = (workspaceId: string) => `${SKILLS_API}/workspace/${workspaceId}`; -export const SKILLS_WORKSPACE_SEED_API: string = `${SKILLS_API}/workspace/seed`; -export const SKILLS_DETAIL_API: (skillId: string) => string = (skillId: string) => `${SKILLS_API}/detail/${skillId}`; -export const SKILLS_CREATE_API: string = `${SKILLS_API}/create`; -export const SKILLS_UPDATE_API: (skillId: string) => string = (skillId: string) => `${SKILLS_API}/${skillId}`; -export const SKILLS_DELETE_API: (skillId: string) => string = (skillId: string) => `${SKILLS_API}/${skillId}`; - -export const SKILLS_REGISTRY_STATS_API: string = `${SKILLS_API}/registry/stats`; -export const SKILLS_REGISTRY_SEARCH_API: string = `${SKILLS_API}/registry/search`; -export const SKILLS_REGISTRY_DETAIL_API: (skillName: string) => string = (skillName: string) => `${SKILLS_API}/registry/detail/${skillName}`; diff --git a/frontend/src/shared/routes/subscriptions.ts b/frontend/src/shared/routes/subscriptions.ts deleted file mode 100644 index 1de4aabe..00000000 --- a/frontend/src/shared/routes/subscriptions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const SUBSCRIPTIONS_API: string = `${API_BASE}/subscriptions`; - -export const SUBSCRIPTIONS_STATUS_API: string = `${SUBSCRIPTIONS_API}/status`; -export const SUBSCRIPTIONS_CONNECT_API: string = `${SUBSCRIPTIONS_API}/connect`; -export const SUBSCRIPTIONS_POLL_API: string = `${SUBSCRIPTIONS_API}/poll`; -export const SUBSCRIPTIONS_DISCONNECT_API: string = `${SUBSCRIPTIONS_API}/disconnect`; -export const SUBSCRIPTIONS_PENDING_API: (state: string) => string = (state: string) => `${SUBSCRIPTIONS_API}/pending/${state}`; -export const SUBSCRIPTIONS_CALLBACK_API: string = `${SUBSCRIPTIONS_API}/callback`; diff --git a/frontend/src/shared/routes/tools.ts b/frontend/src/shared/routes/tools.ts deleted file mode 100644 index 05905d18..00000000 --- a/frontend/src/shared/routes/tools.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { API_BASE } from '@/shared/routes/base'; - -const TOOLS_API: string = `${API_BASE}/tools`; - -export const TOOLS_BUILTIN_API: string = `${TOOLS_API}/builtin`; -export const TOOLS_BUILTIN_PERMISSIONS_API: string = `${TOOLS_API}/builtin/permissions`; -export const TOOLS_LIST_API: string = `${TOOLS_API}/list`; -export const TOOLS_CREATE_API: string = `${TOOLS_API}/create`; -export const TOOLS_GET_API: (toolId: string) => string = (toolId: string) => `${TOOLS_API}/${toolId}`; -export const TOOLS_UPDATE_API: (toolId: string) => string = (toolId: string) => `${TOOLS_API}/${toolId}`; -export const TOOLS_DELETE_API: (toolId: string) => string = (toolId: string) => `${TOOLS_API}/${toolId}`; -export const TOOLS_DISCOVER_API: (toolId: string) => string = (toolId: string) => `${TOOLS_API}/${toolId}/discover`; -export const TOOLS_LOAD_USER_TOOLKIT_API: string = `${TOOLS_API}/load_user_toolkit`; -export const TOOLS_OAUTH_CALLBACK_API: string = `${TOOLS_API}/oauth/callback`; -export const TOOLS_OAUTH_START_API: (toolId: string) => string = (toolId: string) => `${TOOLS_API}/${toolId}/oauth/start`; -export const TOOLS_OAUTH_DISCONNECT_API: (toolId: string) => string = (toolId: string) => `${TOOLS_API}/${toolId}/oauth/disconnect`; diff --git a/frontend/src/shared/state/agentsSlice.ts b/frontend/src/shared/state/agentsSlice.ts index b22479b9..52453c35 100644 --- a/frontend/src/shared/state/agentsSlice.ts +++ b/frontend/src/shared/state/agentsSlice.ts @@ -42,4 +42,3 @@ export const { export default agentsSlice.reducer; export * from './agentsTypes'; -export * from './agentsThunks'; diff --git a/frontend/src/shared/state/agentsThunks.ts b/frontend/src/shared/state/agentsThunks.ts deleted file mode 100644 index 47c0382f..00000000 --- a/frontend/src/shared/state/agentsThunks.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import { API_BASE } from '@/shared/config'; -import type { - AgentSession, AgentConfig, HistorySession, - SendMessagePayload, LaunchAndSendPayload, - GenerateGroupMetaPayload, SearchHistoryParams, -} from './agentsTypes'; - -const AGENTS_API = `${API_BASE}/agents`; - -export const fetchSessions = createAsyncThunk( - 'agents/fetchSessions', - async ({ dashboardId }: { dashboardId?: string } = {}) => { - const params = new URLSearchParams(); - if (dashboardId) params.set('dashboard_id', dashboardId); - const qs = params.toString(); - const res = await fetch(`${AGENTS_API}/sessions${qs ? `?${qs}` : ''}`); - const data = await res.json(); - return data.sessions as AgentSession[]; - }, -); - -export const launchAgent = createAsyncThunk('agents/launchAgent', async (config: AgentConfig) => { - const res = await fetch(`${AGENTS_API}/launch`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config), - }); - const data = await res.json(); - return data.session as AgentSession; -}); - -export const sendMessage = createAsyncThunk( - 'agents/sendMessage', - async ({ sessionId, prompt, mode, model, provider, images, contextPaths, forcedTools, attachedSkills, hidden, selectedBrowserIds }: SendMessagePayload) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}/message`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt, mode, model, provider, images, context_paths: contextPaths, forced_tools: forcedTools, attached_skills: attachedSkills, hidden, selected_browser_ids: selectedBrowserIds }), - }); - return { sessionId, prompt }; - } -); - -export const stopAgent = createAsyncThunk( - 'agents/stopAgent', - async ({ sessionId, removeWorktree = false }: { sessionId: string; removeWorktree?: boolean }) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}/stop`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ remove_worktree: removeWorktree }), - }); - return sessionId; - } -); - -export const editMessage = createAsyncThunk( - 'agents/editMessage', - async ({ sessionId, messageId, content }: { sessionId: string; messageId: string; content: string }) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}/edit_message`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message_id: messageId, content }), - }); - return { sessionId, messageId, content }; - } -); - -export const switchBranch = createAsyncThunk( - 'agents/switchBranch', - async ({ sessionId, branchId }: { sessionId: string; branchId: string }) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}/switch_branch`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ branch_id: branchId }), - }); - return { sessionId, branchId }; - } -); - -export const fetchSession = createAsyncThunk( - 'agents/fetchSession', - async (sessionId: string) => { - const res = await fetch(`${AGENTS_API}/sessions/${sessionId}`); - const session = await res.json(); - return session as AgentSession; - } -); - -export const launchAndSendFirstMessage = createAsyncThunk( - 'agents/launchAndSendFirstMessage', - async ({ draftId, config, prompt, mode, model, provider, images, contextPaths, forcedTools, attachedSkills, selectedBrowserIds }: LaunchAndSendPayload) => { - const launchRes = await fetch(`${AGENTS_API}/launch`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config), - }); - const launchData = await launchRes.json(); - const session = launchData.session as AgentSession; - - await fetch(`${AGENTS_API}/sessions/${session.id}/message`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt, mode, model, provider, images, context_paths: contextPaths, forced_tools: forcedTools, attached_skills: attachedSkills, selected_browser_ids: selectedBrowserIds }), - }); - - const refreshRes = await fetch(`${AGENTS_API}/sessions/${session.id}`); - const updatedSession = await refreshRes.json() as AgentSession; - - return { draftId, session: updatedSession }; - } -); - -export const generateTitle = createAsyncThunk( - 'agents/generateTitle', - async ({ sessionId, prompt }: { sessionId: string; prompt: string }) => { - const res = await fetch(`${AGENTS_API}/sessions/${sessionId}/generate-title`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt }), - }); - const data = await res.json(); - return { sessionId, title: data.title as string }; - } -); - -export const generateGroupMeta = createAsyncThunk( - 'agents/generateGroupMeta', - async ({ sessionId, groupId, toolCalls, resultsSummary, isRefinement }: GenerateGroupMetaPayload) => { - const res = await fetch(`${AGENTS_API}/sessions/${sessionId}/generate-group-meta`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - group_id: groupId, - tool_calls: toolCalls, - results_summary: resultsSummary, - is_refinement: isRefinement ?? false, - }), - }); - const data = await res.json(); - return { sessionId, groupId, name: data.name as string, svg: data.svg as string, isRefined: data.is_refined as boolean }; - } -); - -export const updateSystemPrompt = createAsyncThunk( - 'agents/updateSystemPrompt', - async ({ sessionId, systemPrompt }: { sessionId: string; systemPrompt: string }) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ system_prompt: systemPrompt }), - }); - return { sessionId, systemPrompt }; - } -); - -export const handleApproval = createAsyncThunk( - 'agents/handleApproval', - async ({ requestId, behavior, message, updatedInput }: { - requestId: string; behavior: 'allow' | 'deny'; message?: string; updatedInput?: Record; - }) => { - const res = await fetch(`${AGENTS_API}/approval`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ request_id: requestId, behavior, message, updated_input: updatedInput }), - }); - if (!res.ok) throw new Error(`Approval request failed (${res.status})`); - return { requestId, behavior }; - } -); - -export const closeSession = createAsyncThunk('agents/closeSession', async ({ sessionId }: { sessionId: string }) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}/close`, { method: 'POST' }); - return sessionId; -}); - -export const duplicateSession = createAsyncThunk( - 'agents/duplicateSession', - async ({ sessionId, dashboardId, upToMessageId }: { sessionId: string; dashboardId?: string; upToMessageId?: string }) => { - const res = await fetch(`${AGENTS_API}/sessions/${sessionId}/duplicate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dashboard_id: dashboardId, up_to_message_id: upToMessageId }), - }); - if (!res.ok) throw new Error('Failed to duplicate session'); - const data = await res.json(); - return data.session as AgentSession; - } -); - -export const deleteSession = createAsyncThunk('agents/deleteSession', async ({ sessionId }: { sessionId: string }) => { - await fetch(`${AGENTS_API}/sessions/${sessionId}`, { method: 'DELETE' }); - return sessionId; -}); - -export const fetchHistory = createAsyncThunk( - 'agents/fetchHistory', - async ({ dashboardId }: { dashboardId?: string } = {}) => { - const params = new URLSearchParams({ limit: '10000' }); - if (dashboardId) params.set('dashboard_id', dashboardId); - const res = await fetch(`${AGENTS_API}/history?${params}`); - const data = await res.json(); - return data.sessions as HistorySession[]; - }, -); - -export const searchHistory = createAsyncThunk( - 'agents/searchHistory', - async ({ q = '', limit = 20, offset = 0, dashboardId }: SearchHistoryParams) => { - const params = new URLSearchParams({ q, limit: String(limit), offset: String(offset) }); - if (dashboardId) params.set('dashboard_id', dashboardId); - const res = await fetch(`${AGENTS_API}/history?${params}`); - const data = await res.json(); - return { - sessions: data.sessions as HistorySession[], - total: data.total as number, - hasMore: data.has_more as boolean, - query: q, - offset, - }; - } -); - -export const resumeSession = createAsyncThunk( - 'agents/resumeSession', - async ({ sessionId }: { sessionId: string }) => { - const res = await fetch(`${AGENTS_API}/sessions/${sessionId}/resume`, { method: 'POST' }); - const data = await res.json(); - return data.session as AgentSession; - } -); - -export const fetchBrowserAgentChildren = createAsyncThunk( - 'agents/fetchBrowserAgentChildren', - async (parentSessionId: string) => { - const res = await fetch(`${AGENTS_API}/sessions/${parentSessionId}/browser-agents`); - const data = await res.json(); - return data.sessions as AgentSession[]; - } -);