import type { IncomingMessage, ServerResponse } from 'node:http'; import type { ProjectType } from '../../../shared/project-config'; import type { CodingProjectConfigSnapshot } from '../../coding-projects/project-service'; import type { CodingProject } from '../../coding-projects/project-store'; import type { HostApiContext } from '../context'; import { parseJsonBody, sendJson, sendNoContent } from '../route-utils'; import { sendCodingRouteError } from './coding-route-errors'; function isProjectRoute(pathname: string): boolean { return pathname === '/api/coding/projects' || pathname.startsWith('/api/coding/projects/'); } function publicProject(project: CodingProject | null): Omit | null { if (!project) return null; const { path: _path, ...safe } = project; return safe; } function publicProjectSnapshot(snapshot: CodingProjectConfigSnapshot) { return { ...snapshot, project: publicProject(snapshot.project), }; } export async function handleCodingProjectRoutes( req: IncomingMessage, res: ServerResponse, url: URL, ctx: HostApiContext, ): Promise { if (!isProjectRoute(url.pathname)) return false; const projects = ctx.codingProducts?.projects; const conversations = ctx.codingProducts?.conversations; if (!projects || !conversations) { sendJson(res, 503, { success: false, code: 'CODING_CORE_UNAVAILABLE', error: '本地编程服务暂时不可用。', }); return true; } try { if (url.pathname === '/api/coding/projects' && req.method === 'GET') { const [items, activeProject] = await Promise.all([ projects.listProjects(), projects.getActiveProject(), ]); sendJson(res, 200, { projects: items.map((project) => publicProject(project)), activeProjectId: activeProject?.id ?? null, }); return true; } if (url.pathname === '/api/coding/projects/open' && req.method === 'POST') { const body = await parseJsonBody<{ projectPath?: string }>(req); sendJson(res, 200, { project: publicProject(await projects.openProject(body.projectPath ?? '')) }); return true; } if (url.pathname === '/api/coding/projects/create' && req.method === 'POST') { const body = await parseJsonBody<{ projectPath?: string; parentPath?: string; projectName?: string; projectType?: ProjectType; }>(req); sendJson(res, 201, { snapshot: publicProjectSnapshot(await projects.createProject(body)) }); return true; } if (url.pathname === '/api/coding/projects/remove' && req.method === 'POST') { const body = await parseJsonBody<{ projectId?: string }>(req); await projects.removeProject(body.projectId ?? ''); sendNoContent(res); return true; } if (url.pathname === '/api/coding/projects/active' && req.method === 'GET') { sendJson(res, 200, { project: publicProject(await projects.getActiveProject()) }); return true; } if (url.pathname === '/api/coding/projects/active' && req.method === 'POST') { const body = await parseJsonBody<{ projectId?: string }>(req); sendJson(res, 200, { project: publicProject(await projects.setActiveProject(body.projectId ?? '')) }); return true; } if (url.pathname === '/api/coding/projects/config' && req.method === 'GET') { sendJson(res, 200, { snapshot: publicProjectSnapshot( await projects.getConfig(url.searchParams.get('projectId')?.trim() || undefined), ), }); return true; } if (url.pathname === '/api/coding/projects/config' && req.method === 'PUT') { const body = await parseJsonBody<{ projectId?: string; config?: unknown }>(req); sendJson(res, 200, { snapshot: publicProjectSnapshot(await projects.saveConfig(body.projectId ?? '', body.config)), }); return true; } if (url.pathname === '/api/coding/projects/knowledge' && req.method === 'POST') { const body = await parseJsonBody<{ projectId?: string; fileName?: string; contentBase64?: string; }>(req); sendJson(res, 201, { knowledgeFiles: await projects.addKnowledgeFile({ projectId: body.projectId ?? '', fileName: body.fileName ?? '', contentBase64: body.contentBase64 ?? '', }), }); return true; } if (url.pathname === '/api/coding/projects/legacy-conversation-notice/acknowledge' && req.method === 'POST') { const body = await parseJsonBody<{ projectId?: string }>(req); sendJson(res, 200, { snapshot: publicProjectSnapshot( await projects.acknowledgeLegacyConversationNotice(body.projectId ?? ''), ), }); return true; } if (url.pathname === '/api/coding/projects/conversations' && req.method === 'GET') { sendJson(res, 200, { conversations: await conversations.listConversations( url.searchParams.get('projectId')?.trim() || undefined, ), }); return true; } if (url.pathname === '/api/coding/projects/conversations' && req.method === 'POST') { const body = await parseJsonBody<{ projectId?: string; agentId?: unknown; title?: unknown }>(req); sendJson(res, 201, { conversation: await conversations.createConversation(body) }); return true; } } catch (error) { sendCodingRouteError(res, error); return true; } return false; }