(options: CreateAppOptions = {})
| 78 | |
| 79 | /** Build a Hono app mounting /api/* routes, plus SPA static fallback. */ |
| 80 | export async function createApp(options: CreateAppOptions = {}): Promise<Hono> { |
| 81 | const app = new Hono(); |
| 82 | |
| 83 | // /api/* handlers. |
| 84 | const api = new Hono(); |
| 85 | const authToken = options.authToken; |
| 86 | const home = options.homeDir ?? KIMI_CODE_HOME; |
| 87 | if (authToken !== undefined && authToken.length > 0) { |
| 88 | api.use('*', async (c, next) => { |
| 89 | const token = bearerToken(c.req.header('authorization')); |
| 90 | if (token !== null && tokenMatches(token, authToken)) { |
| 91 | await next(); |
| 92 | return; |
| 93 | } |
| 94 | c.header('www-authenticate', 'Bearer realm="kimi-vis"'); |
| 95 | return c.json({ error: 'unauthorized', code: 'UNAUTHORIZED' }, 401); |
| 96 | }); |
| 97 | } |
| 98 | api.route('/sessions', sessionsRoute(home)); |
| 99 | api.route('/sessions', sessionDetailRoute(home)); |
| 100 | api.route('/sessions', wireRoute(home)); |
| 101 | api.route('/sessions', subagentsRoute(home)); |
| 102 | api.route('/sessions', blobsRoute(home)); |
| 103 | api.route('/sessions', tasksRoute(home)); |
| 104 | api.route('/sessions', cronRoute(home)); |
| 105 | api.route('/sessions', logsRoute(home)); |
| 106 | api.route('/imports', importsRoute(home)); |
| 107 | // Mount contextRoute last because it currently uses a catch-all stub |
| 108 | // (Phase C scope) that would otherwise shadow more specific routes |
| 109 | // registered below it. |
| 110 | api.route('/sessions', contextRoute(home)); |
| 111 | |
| 112 | app.route('/api', api); |
| 113 | |
| 114 | // Static + SPA fallback. |
| 115 | if (options.webAsset !== undefined) { |
| 116 | // Serve the embedded single-file SPA from memory for any non-/api GET. |
| 117 | const asset = options.webAsset; |
| 118 | app.get('*', (c) => { |
| 119 | const pathname = new URL(c.req.url).pathname; |
| 120 | if (pathname.startsWith('/api')) { |
| 121 | // Should have been routed above; 404 here. |
| 122 | return c.json({ error: `api route not found: ${pathname}`, code: 'NOT_FOUND' }, 404); |
| 123 | } |
| 124 | return serveWebAsset(asset); |
| 125 | }); |
| 126 | } else { |
| 127 | // Filesystem static serving (production standalone only). |
| 128 | const publicDir = await resolvePublicDir(); |
| 129 | if (publicDir !== null) { |
| 130 | app.get('*', async (c) => { |
| 131 | const url = new URL(c.req.url); |
| 132 | let pathname = decodeURIComponent(url.pathname); |
| 133 | if (pathname.startsWith('/api')) { |
| 134 | // Should have been routed above; 404 here. |
| 135 | return c.json({ error: `api route not found: ${pathname}`, code: 'NOT_FOUND' }, 404); |
| 136 | } |
| 137 | if (pathname === '/' || pathname === '') pathname = '/index.html'; |
no test coverage detected