| 118 | export { ServerLockedError }; |
| 119 | |
| 120 | export async function startServer(opts: ServerStartOptions): Promise<RunningServer> { |
| 121 | const pinoLogger: ServerLogger = |
| 122 | opts.logger ?? createServerLogger({ level: opts.logLevel ?? 'info' }); |
| 123 | |
| 124 | const lockHandle = acquireLock({ |
| 125 | port: opts.port, |
| 126 | host: opts.host, |
| 127 | lockPath: opts.lockPath, |
| 128 | // Record the host build identity so `kimi server status` can detect a |
| 129 | // build-mismatched server. |
| 130 | hostVersion: opts.coreProcessOptions?.identity?.version, |
| 131 | entry: process.argv[1], |
| 132 | }); |
| 133 | |
| 134 | const app = Fastify({ |
| 135 | loggerInstance: pinoLogger, |
| 136 | disableRequestLogging: false, |
| 137 | genReqId: (req) => resolveRequestId(req.headers), |
| 138 | }); |
| 139 | |
| 140 | app.setValidatorCompiler(() => () => true); |
| 141 | app.setSerializerCompiler(() => (data) => JSON.stringify(data)); |
| 142 | installErrorHandler(app); |
| 143 | |
| 144 | // Host / Origin checks (ROADMAP M4.3). Registered before any route so they |
| 145 | // run ahead of every handler and ahead of the (future, M5.1) auth hook. |
| 146 | // Host is evaluated before Origin; both are uniform across bindings (PLAN |
| 147 | // D3) — even on loopback — so behavior does not depend on how the server is |
| 148 | // reached. The default-allow set keeps `app.inject` (`Host: localhost:80`) |
| 149 | // and real `fetch` to `127.0.0.1:<port>` working. |
| 150 | const allowedHosts = [...parseAllowedHosts(process.env), ...(opts.allowedHosts ?? [])]; |
| 151 | const hostCheck = createHostCheck({ |
| 152 | boundHost: opts.host, |
| 153 | extra: allowedHosts, |
| 154 | disable: isHostCheckDisabled(process.env), |
| 155 | }); |
| 156 | const originHook = createOriginHook({ allowedOrigins: parseCorsOrigins(process.env) }); |
| 157 | app.addHook('onRequest', hostCheck.onRequest); |
| 158 | app.addHook('onRequest', originHook); |
| 159 | |
| 160 | const serverVersion = opts.coreProcessOptions?.identity?.version ?? getServerVersion(); |
| 161 | |
| 162 | async function registerOpenApi(): Promise<void> { |
| 163 | const { default: swagger } = await import('@fastify/swagger'); |
| 164 | await app.register(swagger, { |
| 165 | openapi: { |
| 166 | info: { |
| 167 | title: 'Kimi Code Server API', |
| 168 | description: |
| 169 | 'REST API for the Kimi Code local server. All JSON responses are wrapped in a uniform envelope `{ code, msg, data, request_id }`.', |
| 170 | version: serverVersion, |
| 171 | }, |
| 172 | tags: [ |
| 173 | { name: 'meta', description: 'Server metadata' }, |
| 174 | { name: 'auth', description: 'Auth readiness & login state' }, |
| 175 | { name: 'models', description: 'Configured model aliases' }, |
| 176 | { name: 'providers', description: 'Configured providers' }, |
| 177 | { name: 'sessions', description: 'Session lifecycle' }, |