codev / src /server /api /agents.ts
chenbhao's picture
feat: desktop
1f21206
Raw
History Blame Contribute Delete
8.08 kB
/**
* Agents REST API
*
* GET /api/agents β€” θŽ·ε– Agent εˆ—θ‘¨
* GET /api/agents/:name β€” θŽ·ε– Agent θ―¦ζƒ…
* POST /api/agents β€” εˆ›ε»Ί Agent
* PUT /api/agents/:name β€” ζ›΄ζ–° Agent
* DELETE /api/agents/:name β€” εˆ ι™€ Agent
*
* GET /api/tasks β€” θŽ·ε–εŽε°δ»»εŠ‘εˆ—θ‘¨
* GET /api/tasks/:id β€” θŽ·ε–δ»»εŠ‘θ―¦ζƒ…
*/
import { AgentService } from '../services/agentService.js'
import { taskService } from '../services/taskService.js'
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
import { resetTaskList } from '../../utils/tasks.js'
import {
resolveAgentModelDisplay,
resolveAgentOverrides,
type ResolvedAgent,
} from '../../tools/AgentTool/agentDisplay.js'
import {
clearAgentDefinitionsCache,
getAgentDefinitionsWithOverrides,
type AgentDefinition as SharedAgentDefinition,
} from '../../tools/AgentTool/loadAgentsDir.js'
import { getCwd } from '../../utils/cwd.js'
const agentService = new AgentService()
export async function handleAgentsApi(
req: Request,
url: URL,
segments: string[],
): Promise<Response> {
try {
const resource = segments[1] // 'agents' | 'tasks'
if (resource === 'tasks') {
return await handleTasksApi(req, segments)
}
return await handleAgents(req, url, segments)
} catch (error) {
return errorResponse(error)
}
}
// ─── Agent CRUD ─────────────────────────────────────────────────────────────
async function handleAgents(
req: Request,
url: URL,
segments: string[],
): Promise<Response> {
const method = req.method
const agentName = segments[2] ? decodeURIComponent(segments[2]) : undefined
// ── GET /api/agents ──────────────────────────────────────────────────
if (method === 'GET' && !agentName) {
const cwd = url.searchParams.get('cwd') || getCwd()
const { activeAgents, allAgents } = await getAgentDefinitionsWithOverrides(cwd)
const resolvedAgents = resolveAgentOverrides(allAgents, activeAgents)
return Response.json({
activeAgents: activeAgents.map(agent => serializeActiveAgent(agent, true)),
allAgents: resolvedAgents.map(serializeResolvedAgent),
})
}
// ── GET /api/agents/:name ────────────────────────────────────────────
if (method === 'GET' && agentName) {
const agent = await agentService.getAgent(agentName)
if (!agent) {
throw ApiError.notFound(`Agent not found: ${agentName}`)
}
return Response.json({ agent })
}
// ── POST /api/agents ─────────────────────────────────────────────────
if (method === 'POST' && !agentName) {
const body = await parseJsonBody(req)
if (!body.name || typeof body.name !== 'string') {
throw ApiError.badRequest('Missing or invalid "name" in request body')
}
await agentService.createAgent({
name: body.name as string,
description: body.description as string | undefined,
model: body.model as string | undefined,
tools: body.tools as string[] | undefined,
systemPrompt: body.systemPrompt as string | undefined,
color: body.color as string | undefined,
})
clearAgentDefinitionsCache()
return Response.json({ ok: true }, { status: 201 })
}
// ── PUT /api/agents/:name ────────────────────────────────────────────
if (method === 'PUT' && agentName) {
const body = await parseJsonBody(req)
await agentService.updateAgent(agentName, body as Record<string, unknown>)
clearAgentDefinitionsCache()
const updated = await agentService.getAgent(agentName)
return Response.json({ agent: updated })
}
// ── DELETE /api/agents/:name ─────────────────────────────────────────
if (method === 'DELETE' && agentName) {
await agentService.deleteAgent(agentName)
clearAgentDefinitionsCache()
return Response.json({ ok: true })
}
throw new ApiError(
405,
`Method ${method} not allowed on /api/agents${agentName ? `/${agentName}` : ''}`,
'METHOD_NOT_ALLOWED',
)
}
// ─── Tasks API ─────────────────────────────────────────────────────────────
//
// GET /api/tasks β†’ list all tasks (across all task lists)
// GET /api/tasks/lists β†’ list all task lists with summaries
// GET /api/tasks/lists/:taskListId β†’ get all tasks for a specific task list
// GET /api/tasks/lists/:taskListId/:id β†’ get a single task
// POST /api/tasks/lists/:taskListId/reset β†’ clear a completed task list
async function handleTasksApi(
req: Request,
segments: string[],
): Promise<Response> {
const sub = segments[2] // 'lists' or undefined
if (sub === 'lists') {
const taskListId = segments[3]
const taskId = segments[4]
if (req.method === 'POST' && taskListId && taskId === 'reset') {
await resetTaskList(taskListId)
return Response.json({ ok: true })
}
if (req.method !== 'GET') {
throw new ApiError(
405,
`Method ${req.method} not allowed on /api/tasks/lists`,
'METHOD_NOT_ALLOWED',
)
}
if (taskListId && taskId) {
// GET /api/tasks/lists/:taskListId/:taskId
const task = await taskService.getTask(taskListId, taskId)
if (!task) throw ApiError.notFound(`Task not found: ${taskListId}/${taskId}`)
return Response.json({ task })
}
if (taskListId) {
// GET /api/tasks/lists/:taskListId
const tasks = await taskService.getTasksForList(taskListId)
return Response.json({ tasks })
}
// GET /api/tasks/lists
const lists = await taskService.listTaskLists()
return Response.json({ lists })
}
if (req.method !== 'GET') {
throw new ApiError(
405,
`Method ${req.method} not allowed on /api/tasks`,
'METHOD_NOT_ALLOWED',
)
}
// GET /api/tasks β€” list all tasks
const tasks = await taskService.listTasks()
return Response.json({ tasks })
}
// ─── Helpers ────────────────────────────────────────────────────────────────
async function parseJsonBody(req: Request): Promise<Record<string, unknown>> {
try {
return (await req.json()) as Record<string, unknown>
} catch {
throw ApiError.badRequest('Invalid JSON body')
}
}
type ApiAgentDefinition = {
agentType: string
description?: string
model?: string
modelDisplay?: string
tools?: string[]
systemPrompt?: string
color?: string
source: SharedAgentDefinition['source']
baseDir?: string
isActive: boolean
}
type ApiResolvedAgentDefinition = ApiAgentDefinition & {
overriddenBy?: SharedAgentDefinition['source']
}
function serializeActiveAgent(
agent: SharedAgentDefinition,
isActive: boolean,
): ApiAgentDefinition {
return {
agentType: agent.agentType,
description: agent.whenToUse,
model: agent.model,
modelDisplay: resolveAgentModelDisplay(agent),
tools: agent.tools,
systemPrompt: agent.getSystemPrompt.length === 0 ? agent.getSystemPrompt() : undefined,
color: agent.color,
source: agent.source,
baseDir: agent.baseDir,
isActive,
}
}
function serializeResolvedAgent(agent: ResolvedAgent): ApiResolvedAgentDefinition {
return {
...serializeActiveAgent(agent, !agent.overriddenBy),
overriddenBy: agent.overriddenBy,
}
}