import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import cookie from '@fastify/cookie' import multipart from '@fastify/multipart' import fastifyStatic from '@fastify/static' import Fastify, { type FastifyServerOptions } from 'fastify' import type { ApiErrorBody } from '@agent-server/shared' import type { App } from '../app' import { config } from '../config' import { AppError } from '../lib/errors' import adminRoutes from './routes/admin' import v1Routes from './routes/v1' import { registerGateway } from '../mcp-gateway/server' export async function buildServer(app: App, opts: { logger: FastifyServerOptions['loggerInstance'] }) { const f = Fastify({ loggerInstance: opts.logger, trustProxy: config.trustProxy, bodyLimit: 2 * 1048576 }) await f.register(cookie) await f.register(multipart) f.setErrorHandler((err, req, reply) => { if (err instanceof AppError) { const body: ApiErrorBody = { error: { code: err.code, message: err.message, details: err.details } } return reply.code(err.status).send(body) } const status = (err as { statusCode?: number }).statusCode ?? 500 if (status >= 500) req.log.error({ err }, 'unhandled error') const body: ApiErrorBody = { error: { code: status >= 500 ? 'internal_error' : 'bad_request', message: status >= 500 ? '服务器内部错误' : (err as Error).message } } return reply.code(status).send(body) }) f.get('/healthz', async () => ({ ok: true })) f.get('/readyz', async () => { app.db.$client.prepare('select 1').get() return { ok: true, runs: app.runs.stats() } }) await f.register(v1Routes, { prefix: '/v1', app }) registerGateway(f, app.gatewayTokens) await f.register(adminRoutes, { prefix: '/admin/v1', app }) // 生产环境由服务托管后台静态资源,同源访问 // 源码运行时位于 src/http,打包后位于 dist,两种情况都向上寻找 apps/admin/dist const here = path.dirname(fileURLToPath(import.meta.url)) const adminDist = config.adminDist ?? [path.resolve(here, '../../../admin/dist'), path.resolve(here, '../../admin/dist')].find(p => fs.existsSync(path.join(p, 'index.html'))) if (adminDist && fs.existsSync(path.join(adminDist, 'index.html'))) { await f.register(fastifyStatic, { root: adminDist, prefix: '/admin/', wildcard: false }) f.get('/admin/*', (_req, reply) => reply.sendFile('index.html')) f.get('/', (_req, reply) => reply.redirect('/admin/')) } return f }