cc-haha/src/server/api/search.ts
程序员阿江(Relakkes) 0c6d2754b4 feat: add Desktop UI backend server with full REST API and WebSocket support
Add complete server-side implementation for the Claude Code Desktop App UI:

- REST API: sessions, conversations, settings, models, scheduled tasks,
  search, agents, and status endpoints (9 modules, 30+ endpoints)
- WebSocket: real-time chat streaming with state transitions, ping/pong,
  permission request forwarding, and stop generation support
- Services: sessionService (JSONL read/write, CLI-compatible),
  settingsService (atomic writes), cronService, searchService (ripgrep),
  agentService (YAML management)
- Middleware: CORS (localhost-only), auth, unified error handling
- Tests: 180 tests (unit + E2E + business flow), all passing
- Docs: PRD, UI design spec, server architecture design

Non-invasive: all new code under src/server/, no changes to existing CLI code.
CLI/UI data interop: reads/writes the same JSONL/JSON files as the CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:23:44 +08:00

68 lines
2.3 KiB
TypeScript

/**
* Search REST API
*
* POST /api/search — 全局工作区搜索
* POST /api/search/sessions — 搜索会话历史
*/
import { SearchService } from '../services/searchService.js'
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
const searchService = new SearchService()
export async function handleSearchApi(
req: Request,
_url: URL,
segments: string[],
): Promise<Response> {
try {
const method = req.method
const sub = segments[2] // 'sessions' | undefined
if (method !== 'POST') {
throw new ApiError(
405,
`Method ${method} not allowed. Use POST.`,
'METHOD_NOT_ALLOWED',
)
}
const body = await parseJsonBody(req)
const query = body.query as string
if (!query || typeof query !== 'string') {
throw ApiError.badRequest('Missing or invalid "query" in request body')
}
// ── POST /api/search/sessions ──────────────────────────────────────────
if (sub === 'sessions') {
const results = await searchService.searchSessions(query)
return Response.json({ results })
}
// ── POST /api/search ───────────────────────────────────────────────────
if (!sub) {
const results = await searchService.searchWorkspace(query, {
cwd: body.cwd as string | undefined,
maxResults: body.maxResults as number | undefined,
glob: body.glob as string | undefined,
caseSensitive: body.caseSensitive as boolean | undefined,
})
return Response.json({ results, total: results.length })
}
throw ApiError.notFound(`Unknown search endpoint: ${sub}`)
} catch (error) {
return errorResponse(error)
}
}
// ─── 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')
}
}