[Haik]: refactor: overhaul agents API contract and frontend backend layer — rename all backend agent routes from RESTful path-param style (e.g. /sessions/{id}/message) to flat action-named endpoints (e.g. /send_message), replace Pydantic BaseModel request classes (LaunchBody, MessageBody, etc.) with inline FastAPI Body() parameters, delete the scattered frontend shared/routes/ directory (11 files) and agentsThunks.ts, and replace them with a co-located shared/backend/apps/ structure where each backend app has its routes, fetch functions, and async thunks in a single file

This commit is contained in:
haikdc
2026-04-18 02:23:44 -07:00
parent f691ca1ff1
commit 2a898f3f08
16 changed files with 407 additions and 431 deletions
+93 -77
View File
@@ -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,
+4 -4
View File
@@ -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"])
+310
View File
@@ -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<AgentSession[]> {
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<AgentSession> {
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<AgentSession> {
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<string> {
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<string> {
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<AgentSession> {
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<string> {
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<string, unknown>
): 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<AgentSession> {
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<AgentSession> {
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<string> {
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<AgentSession> {
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<AgentSession> {
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<HistorySession[]> {
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,
);
-19
View File
@@ -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`;
-15
View File
@@ -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`;
-11
View File
@@ -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`;
-5
View File
@@ -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`;
-11
View File
@@ -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`;
-7
View File
@@ -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`;
-15
View File
@@ -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}`;
@@ -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`;
-16
View File
@@ -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`;
-1
View File
@@ -42,4 +42,3 @@ export const {
export default agentsSlice.reducer;
export * from './agentsTypes';
export * from './agentsThunks';
-240
View File
@@ -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<string, any>;
}) => {
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[];
}
);