@agentuity/cli 1.0.48 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/cmd/build/app-router-detector.d.ts +2 -5
  2. package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
  3. package/dist/cmd/build/app-router-detector.js +130 -154
  4. package/dist/cmd/build/app-router-detector.js.map +1 -1
  5. package/dist/cmd/build/ids.d.ts +11 -0
  6. package/dist/cmd/build/ids.d.ts.map +1 -0
  7. package/dist/cmd/build/ids.js +18 -0
  8. package/dist/cmd/build/ids.js.map +1 -0
  9. package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
  10. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/agent-discovery.js +166 -487
  12. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  13. package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
  14. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  15. package/dist/cmd/build/vite/bun-dev-server.js +67 -134
  16. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  17. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/docs-generator.js +0 -2
  19. package/dist/cmd/build/vite/docs-generator.js.map +1 -1
  20. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/index.js +0 -36
  22. package/dist/cmd/build/vite/index.js.map +1 -1
  23. package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
  24. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
  26. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  27. package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
  28. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  29. package/dist/cmd/build/vite/route-discovery.js +97 -177
  30. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  31. package/dist/cmd/build/vite/server-bundler.js +1 -1
  32. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  33. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/static-renderer.js +1 -9
  35. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  36. package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
  37. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/vite-asset-server-config.js +171 -21
  39. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  40. package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
  41. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  42. package/dist/cmd/build/vite/vite-asset-server.js +14 -13
  43. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  44. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/vite-builder.js +6 -36
  46. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  47. package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
  48. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
  49. package/dist/cmd/build/vite/ws-proxy.js +95 -0
  50. package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
  51. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  52. package/dist/cmd/build/vite-bundler.js +0 -3
  53. package/dist/cmd/build/vite-bundler.js.map +1 -1
  54. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  55. package/dist/cmd/cloud/deploy.js +0 -1
  56. package/dist/cmd/cloud/deploy.js.map +1 -1
  57. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  58. package/dist/cmd/dev/file-watcher.js +2 -8
  59. package/dist/cmd/dev/file-watcher.js.map +1 -1
  60. package/dist/cmd/dev/index.d.ts.map +1 -1
  61. package/dist/cmd/dev/index.js +369 -720
  62. package/dist/cmd/dev/index.js.map +1 -1
  63. package/package.json +6 -8
  64. package/src/cmd/ai/prompt/agent.md +0 -1
  65. package/src/cmd/ai/prompt/api.md +0 -7
  66. package/src/cmd/ai/prompt/web.md +51 -213
  67. package/src/cmd/build/app-router-detector.ts +152 -182
  68. package/src/cmd/build/ids.ts +19 -0
  69. package/src/cmd/build/vite/agent-discovery.ts +208 -679
  70. package/src/cmd/build/vite/bun-dev-server.ts +78 -154
  71. package/src/cmd/build/vite/docs-generator.ts +0 -2
  72. package/src/cmd/build/vite/index.ts +1 -42
  73. package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
  74. package/src/cmd/build/vite/route-discovery.ts +116 -274
  75. package/src/cmd/build/vite/server-bundler.ts +1 -1
  76. package/src/cmd/build/vite/static-renderer.ts +1 -11
  77. package/src/cmd/build/vite/vite-asset-server-config.ts +196 -23
  78. package/src/cmd/build/vite/vite-asset-server.ts +25 -15
  79. package/src/cmd/build/vite/vite-builder.ts +6 -53
  80. package/src/cmd/build/vite/ws-proxy.ts +126 -0
  81. package/src/cmd/build/vite-bundler.ts +0 -4
  82. package/src/cmd/cloud/deploy.ts +0 -1
  83. package/src/cmd/dev/file-watcher.ts +2 -9
  84. package/src/cmd/dev/index.ts +409 -832
  85. package/dist/cmd/build/ast.d.ts +0 -78
  86. package/dist/cmd/build/ast.d.ts.map +0 -1
  87. package/dist/cmd/build/ast.js +0 -2703
  88. package/dist/cmd/build/ast.js.map +0 -1
  89. package/dist/cmd/build/entry-generator.d.ts +0 -25
  90. package/dist/cmd/build/entry-generator.d.ts.map +0 -1
  91. package/dist/cmd/build/entry-generator.js +0 -695
  92. package/dist/cmd/build/entry-generator.js.map +0 -1
  93. package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
  94. package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
  95. package/dist/cmd/build/vite/api-mount-path.js +0 -83
  96. package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
  97. package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
  98. package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
  99. package/dist/cmd/build/vite/registry-generator.js +0 -1108
  100. package/dist/cmd/build/vite/registry-generator.js.map +0 -1
  101. package/dist/cmd/build/vite/tailwind-source-plugin.d.ts +0 -13
  102. package/dist/cmd/build/vite/tailwind-source-plugin.d.ts.map +0 -1
  103. package/dist/cmd/build/vite/tailwind-source-plugin.js +0 -44
  104. package/dist/cmd/build/vite/tailwind-source-plugin.js.map +0 -1
  105. package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
  106. package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
  107. package/dist/cmd/build/webanalytics-generator.js +0 -178
  108. package/dist/cmd/build/webanalytics-generator.js.map +0 -1
  109. package/dist/cmd/build/workbench.d.ts +0 -7
  110. package/dist/cmd/build/workbench.d.ts.map +0 -1
  111. package/dist/cmd/build/workbench.js +0 -55
  112. package/dist/cmd/build/workbench.js.map +0 -1
  113. package/dist/utils/route-migration.d.ts +0 -62
  114. package/dist/utils/route-migration.d.ts.map +0 -1
  115. package/dist/utils/route-migration.js +0 -630
  116. package/dist/utils/route-migration.js.map +0 -1
  117. package/src/cmd/build/ast.ts +0 -3529
  118. package/src/cmd/build/entry-generator.ts +0 -760
  119. package/src/cmd/build/vite/api-mount-path.ts +0 -87
  120. package/src/cmd/build/vite/registry-generator.ts +0 -1267
  121. package/src/cmd/build/vite/tailwind-source-plugin.ts +0 -54
  122. package/src/cmd/build/webanalytics-generator.ts +0 -197
  123. package/src/cmd/build/workbench.ts +0 -58
  124. package/src/utils/route-migration.ts +0 -757
@@ -1,760 +0,0 @@
1
- /**
2
- * Vite-native entry file generator (v2 - clean architecture)
3
- * Single source for both dev and prod with minimal differences
4
- */
5
-
6
- import { join } from 'node:path';
7
- import type { Logger, WorkbenchConfig, AnalyticsConfig } from '../../types';
8
- import { discoverRoutes } from './vite/route-discovery';
9
- import { generateWebAnalyticsFile } from './webanalytics-generator';
10
- import { computeApiMountPath } from './vite/api-mount-path';
11
-
12
- interface GenerateEntryOptions {
13
- rootDir: string;
14
- projectId: string;
15
- deploymentId: string;
16
- logger: Logger;
17
- mode: 'dev' | 'prod';
18
- workbench?: WorkbenchConfig;
19
- analytics?: boolean | AnalyticsConfig;
20
- vitePort?: number; // Port of Vite asset server (dev mode only)
21
- noBundle?: boolean; // Skip bundling — apply runtime patches instead
22
- /** Pre-discovered routes to avoid redundant route discovery */
23
- preDiscoveredRoutes?: Awaited<ReturnType<typeof discoverRoutes>>['routeInfoList'];
24
- }
25
-
26
- /**
27
- * Generate entry file with clean Vite-native architecture
28
- */
29
- export async function generateEntryFile(options: GenerateEntryOptions): Promise<void> {
30
- const {
31
- rootDir,
32
- projectId,
33
- deploymentId,
34
- logger,
35
- mode,
36
- workbench,
37
- analytics,
38
- vitePort,
39
- noBundle,
40
- preDiscoveredRoutes,
41
- } = options;
42
-
43
- const srcDir = join(rootDir, 'src');
44
- const generatedDir = join(srcDir, 'generated');
45
- const entryPath = join(generatedDir, 'app.ts');
46
-
47
- logger.trace(`Generating unified entry file (supports both dev and prod modes)...`);
48
-
49
- // Check if analytics is enabled
50
- const analyticsEnabled = analytics !== false;
51
-
52
- // Generate web analytics files only if enabled
53
- if (analyticsEnabled) {
54
- await generateWebAnalyticsFile({ rootDir, logger, analytics });
55
- }
56
-
57
- // Use pre-discovered routes if available, otherwise discover them
58
- const routeInfoList =
59
- preDiscoveredRoutes ??
60
- (await discoverRoutes(srcDir, projectId, deploymentId, logger)).routeInfoList;
61
-
62
- // Check for web and workbench
63
- const hasWebFrontend =
64
- (await Bun.file(join(srcDir, 'web', 'index.html')).exists()) ||
65
- (await Bun.file(join(srcDir, 'web', 'frontend.tsx')).exists());
66
- // Workbench is configured at build time, but only enabled at runtime in dev mode
67
- const hasWorkbenchConfig = !!workbench;
68
-
69
- // Get unique route files that need to be imported (relative to src/)
70
- const routeFiles = new Set<string>();
71
- for (const route of routeInfoList) {
72
- if (route.filename) {
73
- routeFiles.add(route.filename);
74
- }
75
- }
76
-
77
- // Generate imports
78
- const runtimeImports = [
79
- ` createRouter,`,
80
- ` createBaseMiddleware,`,
81
- ` createCorsMiddleware,`,
82
- ` createOtelMiddleware,`,
83
- ` createAgentMiddleware,`,
84
- ` createCompressionMiddleware,`,
85
- ` getAppState,`,
86
- ` getAppConfig,`,
87
- ` getUserRouter,`,
88
- ` register,`,
89
- ` getSpanProcessors,`,
90
- ` createServices,`,
91
- ` runAgentSetups,`,
92
- ` getThreadProvider,`,
93
- ` getSessionProvider,`,
94
- ` setGlobalLogger,`,
95
- ` setGlobalTracer,`,
96
- ` setGlobalRouter,`,
97
- ` enableProcessExitProtection,`,
98
- ` hasWaitUntilPending,`,
99
- ` loadBuildMetadata,`,
100
- ` createWorkbenchRouter,`,
101
- ` bootstrapRuntimeEnv,`,
102
- ` patchBunS3ForStorageDev,`,
103
- ` runShutdown,`,
104
- ];
105
-
106
- if (noBundle) {
107
- runtimeImports.push(` applyDevPatches,`);
108
- }
109
-
110
- if (hasWebFrontend) {
111
- runtimeImports.push(` mimeTypes,`);
112
- }
113
-
114
- const imports = [
115
- `import { `,
116
- ...runtimeImports,
117
- `} from '@agentuity/runtime';`,
118
- `import type { Context } from 'hono';`,
119
- `import { websocket${hasWebFrontend ? ', serveStatic' : ''} } from 'hono/bun';`,
120
- hasWebFrontend ? `import { readFileSync, existsSync } from 'node:fs';` : '',
121
- ].filter(Boolean);
122
-
123
- imports.push(`import { type LogLevel } from '@agentuity/core';`);
124
- if (analyticsEnabled) {
125
- imports.push(`import { injectAnalytics, registerAnalyticsRoutes } from './webanalytics.js';`);
126
- imports.push(`import { analyticsConfig } from './analytics-config.js';`);
127
- }
128
-
129
- // Generate route mounting code for all discovered routes
130
- // Sort route files for deterministic output
131
- const sortedRouteFiles = [...routeFiles].sort();
132
- const routeImportsAndMounts: string[] = [];
133
- let routeIndex = 0;
134
-
135
- for (const routeFile of sortedRouteFiles) {
136
- // Normalize path separators for cross-platform compatibility (Windows uses backslashes)
137
- const normalizedRouteFile = routeFile.replace(/\\/g, '/');
138
- // Convert src/api/auth/route.ts -> auth/route
139
- const relativePath = normalizedRouteFile.replace(/^src\/api\//, '').replace(/\.tsx?$/, '');
140
-
141
- // Determine the mount path using the shared helper
142
- // This ensures consistency with route type generation in ast.ts
143
- const mountPath = computeApiMountPath(relativePath);
144
-
145
- const importName = `router_${routeIndex++}`;
146
- routeImportsAndMounts.push(
147
- `const { default: ${importName} } = await import('../api/${relativePath}.js');`
148
- );
149
- routeImportsAndMounts.push(`app.route('${mountPath}', ${importName});`);
150
- }
151
-
152
- const apiMount =
153
- routeImportsAndMounts.length > 0
154
- ? `
155
- // Apply middleware and mount API routes
156
- // If user passed router(s) via createApp({ router }), mount those instead of discovered files
157
- const __userMounts = getUserRouter();
158
- if (__userMounts) {
159
- for (const mount of __userMounts) {
160
- // Apply Agentuity middleware (CORS, OTel, agent context) to each user-provided prefix
161
- const prefix = mount.path.endsWith('/') ? mount.path + '*' : mount.path + '/*';
162
- app.use(prefix, createCorsMiddleware());
163
- app.use(prefix, createOtelMiddleware());
164
- app.use(prefix, createAgentMiddleware(''));
165
- app.route(mount.path, mount.router);
166
- }
167
- } else {
168
- // File-based routing: apply middleware to /api/* and mount discovered route files
169
- app.use('/api/*', createCorsMiddleware());
170
- app.use('/api/*', createOtelMiddleware());
171
- app.use('/api/*', createAgentMiddleware(''));
172
- ${routeImportsAndMounts.map((line) => `\t${line}`).join('\n')}
173
- }
174
- `
175
- : '';
176
-
177
- // Workbench API routes mounting
178
- // Always mounted - these routes are needed for the cloud workbench to communicate with deployed agents
179
- // Auth is handled by middleware inside the router (signature verification in production, no auth in development)
180
- // The hasWorkbenchConfig flag only controls whether the local workbench UI is served
181
- const workbenchApiMount = `
182
- // Mount workbench API routes (/_agentuity/workbench/*)
183
- // Always available for cloud workbench communication
184
- // Auth is handled inside the router (signature verification in production)
185
- const workbenchRouter = createWorkbenchRouter();
186
- app.route('/', workbenchRouter);
187
-
188
- // hasWorkbenchConfig controls whether the local workbench UI is served (dev mode only)
189
- const hasWorkbenchConfig = ${hasWorkbenchConfig};
190
- `;
191
-
192
- // Asset proxy routes - Always generated, but only active at runtime when:
193
- // - NODE_ENV !== 'production' (isDevelopment())
194
- // - and process.env.VITE_PORT is set
195
- const assetProxyRoutes = `
196
- // Asset proxy routes - Development mode only (proxies to Vite asset server)
197
- if (isDevelopment() && process.env.VITE_PORT) {
198
- const VITE_ASSET_PORT = parseInt(process.env.VITE_PORT, 10);
199
-
200
- const proxyToVite = async (c: Context, pathOverride?: string) => {
201
- const targetPath = pathOverride ?? c.req.path;
202
- const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}\${targetPath}\`;
203
- try {
204
- otel.logger.debug(\`[Proxy] \${c.req.method} \${c.req.path} -> Vite:\${VITE_ASSET_PORT}\${targetPath}\`);
205
- const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
206
- otel.logger.debug(\`[Proxy] \${c.req.path} -> \${res.status} (\${res.headers.get('content-type')})\`);
207
- return new Response(res.body, {
208
- status: res.status,
209
- headers: res.headers,
210
- });
211
- } catch (err) {
212
- if (err instanceof Error && err.name === 'TimeoutError') {
213
- otel.logger.error(\`Vite proxy timeout: \${c.req.path}\`);
214
- return c.text('Vite asset server timeout', 504);
215
- }
216
- otel.logger.error(\`Failed to proxy to Vite: \${c.req.path} - \${err instanceof Error ? err.message : String(err)}\`);
217
- return c.text('Vite asset server error', 500);
218
- }
219
- };
220
-
221
- // HMR WebSocket proxy - enables hot reload through tunnels (*.agentuity.live)
222
- // This proxies the Vite HMR WebSocket connection from the Bun server to Vite
223
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
- const viteHmrWebsocket = (globalThis as any).__AGENTUITY_VITE_HMR_WEBSOCKET__ = {
225
- // Map of client WebSocket -> Vite WebSocket
226
- connections: new Map<WebSocket, WebSocket>(),
227
-
228
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
229
- open(clientWs: any) {
230
- // Get the query string from ws.data (set during upgrade)
231
- const queryString = clientWs.data?.queryString || '';
232
- const viteWsUrl = \`ws://127.0.0.1:\${VITE_ASSET_PORT}/__vite_hmr\${queryString}\`;
233
- otel.logger.debug('[HMR Proxy] Client connected, opening connection to Vite at %s', viteWsUrl);
234
-
235
- // Connect to Vite with the 'vite-hmr' subprotocol (required by Vite)
236
- const viteWs = new WebSocket(viteWsUrl, ['vite-hmr']);
237
-
238
- viteWs.onopen = () => {
239
- otel.logger.debug('[HMR Proxy] Connected to Vite HMR server');
240
- };
241
-
242
- viteWs.onmessage = (event) => {
243
- // Forward messages from Vite to client
244
- if (clientWs.readyState === WebSocket.OPEN) {
245
- clientWs.send(event.data);
246
- }
247
- };
248
-
249
- viteWs.onerror = (error) => {
250
- otel.logger.error('[HMR Proxy] Vite WebSocket error: %s', error);
251
- };
252
-
253
- viteWs.onclose = () => {
254
- otel.logger.debug('[HMR Proxy] Vite WebSocket closed');
255
- viteHmrWebsocket.connections.delete(clientWs);
256
- if (clientWs.readyState === WebSocket.OPEN) {
257
- clientWs.close();
258
- }
259
- };
260
-
261
- viteHmrWebsocket.connections.set(clientWs, viteWs);
262
- },
263
-
264
- message(clientWs: WebSocket, message: string | Buffer) {
265
- // Forward messages from client to Vite
266
- const viteWs = viteHmrWebsocket.connections.get(clientWs);
267
- if (viteWs && viteWs.readyState === WebSocket.OPEN) {
268
- viteWs.send(message);
269
- }
270
- },
271
-
272
- close(clientWs: WebSocket) {
273
- otel.logger.debug('[HMR Proxy] Client WebSocket closed');
274
- const viteWs = viteHmrWebsocket.connections.get(clientWs);
275
- if (viteWs) {
276
- viteWs.close();
277
- viteHmrWebsocket.connections.delete(clientWs);
278
- }
279
- },
280
- };
281
-
282
- // Register HMR WebSocket route - must be before other routes
283
- app.get('/__vite_hmr', (c: Context) => {
284
- const upgradeHeader = c.req.header('upgrade');
285
- if (upgradeHeader?.toLowerCase() === 'websocket') {
286
- // Get the Bun server from context using Hono's pattern
287
- // When app.fetch(req, server) is called, Hono stores server as c.env
288
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
- const server = 'server' in (c.env as any) ? (c.env as any).server : c.env;
290
-
291
- if (server?.upgrade) {
292
- // Extract query string to forward to Vite (includes token parameter)
293
- const url = new URL(c.req.url);
294
- const queryString = url.search; // Includes the '?' prefix
295
-
296
- const success = server.upgrade(c.req.raw, {
297
- data: { type: 'vite-hmr', queryString },
298
- });
299
- if (success) {
300
- otel.logger.debug('[HMR Proxy] WebSocket upgrade successful');
301
- return new Response(null);
302
- }
303
- otel.logger.error('[HMR Proxy] WebSocket upgrade returned false');
304
- } else {
305
- otel.logger.error('[HMR Proxy] Server upgrade method not available. c.env type: %s, keys: %s',
306
- typeof c.env,
307
- Object.keys(c.env || {}).join(', '));
308
- }
309
- return c.text('WebSocket upgrade failed', 500);
310
- }
311
- // Non-WebSocket request to HMR endpoint - proxy to Vite
312
- return proxyToVite(c);
313
- });
314
-
315
- // Vite client scripts and HMR
316
- app.get('/@vite/*', (c: Context) => proxyToVite(c));
317
- app.get('/@react-refresh', (c: Context) => proxyToVite(c));
318
-
319
- // Source files for HMR
320
- app.get('/src/web/*', (c: Context) => proxyToVite(c));
321
- app.get('/src/*', (c: Context) => proxyToVite(c)); // Catch-all for other source files
322
-
323
- // Workbench source files (in .agentuity/workbench-src/)
324
- app.get('/.agentuity/workbench-src/*', (c: Context) => proxyToVite(c));
325
-
326
- // Node modules (Vite transforms these)
327
- app.get('/node_modules/*', (c: Context) => proxyToVite(c));
328
-
329
- // Scoped packages (e.g., @agentuity/*, @types/*)
330
- app.get('/@*', (c: Context) => proxyToVite(c));
331
-
332
- // File system access (for Vite's @fs protocol)
333
- app.get('/@fs/*', (c: Context) => proxyToVite(c));
334
-
335
- // Module resolution (for Vite's @id protocol)
336
- app.get('/@id/*', (c: Context) => proxyToVite(c));
337
-
338
- // Static assets - Vite serves src/web/public/* at root, but code uses /public/* paths
339
- // In production, the plugin transforms /public/foo.svg to CDN URLs
340
- // Rewrite /public/foo.svg -> /foo.svg before proxying to Vite
341
- app.get('/public/*', (c: Context) => {
342
- const rootPath = c.req.path.replace(/^\\/public/, '');
343
- return proxyToVite(c, rootPath);
344
- });
345
-
346
- // Any .js, .jsx, .ts, .tsx files (catch remaining modules)
347
- app.get('/*.js', (c: Context) => proxyToVite(c));
348
- app.get('/*.jsx', (c: Context) => proxyToVite(c));
349
- app.get('/*.ts', (c: Context) => proxyToVite(c));
350
- app.get('/*.tsx', (c: Context) => proxyToVite(c));
351
- app.get('/*.css', (c: Context) => proxyToVite(c));
352
- }
353
- `;
354
-
355
- // Runtime mode detection helper (defined at top level for reuse)
356
- // Dynamic property access prevents Bun.build from inlining NODE_ENV at build time
357
- const modeDetection = `
358
- // Runtime mode detection helper
359
- // Dynamic string concatenation prevents Bun.build from inlining NODE_ENV at build time
360
- // See: https://github.com/oven-sh/bun/issues/20183
361
- const getEnv = (key: string) => process.env[key];
362
- const isDevelopment = () => getEnv('NODE' + '_' + 'ENV') !== 'production';
363
- `;
364
-
365
- // Web routes (runtime mode detection)
366
- let webRoutes = '';
367
- if (hasWebFrontend) {
368
- webRoutes = `
369
- // Web routes - Runtime mode detection (dev proxies to Vite, prod serves static)
370
- // Note: Session/thread cookies are set by /_agentuity/webanalytics/session.js (loaded via script tag)
371
- // This keeps the HTML response static and cacheable
372
-
373
- if (isDevelopment()) {
374
- // Development mode: Proxy HTML from Vite to enable React Fast Refresh
375
- const VITE_ASSET_PORT = parseInt(process.env.VITE_PORT || '${vitePort || 5173}', 10);
376
-
377
- const devHtmlHandler = async (c: Context) => {
378
- const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}/src/web/index.html\`;
379
-
380
- try {
381
- otel.logger.debug('[Proxy] GET /src/web/index.html -> Vite:%d', VITE_ASSET_PORT);
382
- const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
383
-
384
- // Get HTML text and transform relative paths to absolute
385
- let html = await res.text();
386
- html = html
387
- .replace(/src="\\.\\//g, 'src="/src/web/')
388
- .replace(/href="\\.\\//g, 'href="/src/web/');
389
-
390
- ${
391
- analyticsEnabled
392
- ? ` // Inject analytics config and script (session/thread read from cookies by beacon)
393
- html = injectAnalytics(html, analyticsConfig);`
394
- : ''
395
- }
396
-
397
- return new Response(html, {
398
- status: res.status,
399
- headers: res.headers,
400
- });
401
- } catch (err) {
402
- otel.logger.error('Failed to proxy HTML to Vite: %s', err instanceof Error ? err.message : String(err));
403
- return c.text('Vite asset server error (HTML)', 500);
404
- }
405
- };
406
-
407
- app.get('/', devHtmlHandler);
408
-
409
- // 404 for unmatched API/system routes
410
- app.all('/_agentuity/*', (c: Context) => c.notFound());
411
- app.all('/api/*', (c: Context) => c.notFound());
412
- if (!(hasWorkbenchConfig && isDevelopment())) {
413
- app.all('/workbench/*', (c: Context) => c.notFound());
414
- }
415
-
416
- // SPA fallback - serve index.html for client-side routing
417
- app.get('*', async (c: Context) => {
418
- const path = c.req.path;
419
- // If path has a file extension, try proxying to Vite first (serves public files like robots.txt, llms.txt)
420
- // Fall back to 404 if Vite also returns 404
421
- if (/\\.[a-zA-Z0-9]+$/.test(path)) {
422
- try {
423
- const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}\${path}\`;
424
- const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
425
- if (res.status !== 404) {
426
- return new Response(res.body, { status: res.status, headers: res.headers });
427
- }
428
- } catch {
429
- // Vite unavailable, fall through to 404
430
- }
431
- return c.notFound();
432
- }
433
- return devHtmlHandler(c);
434
- });
435
- } else {
436
- // Production mode: Serve static files from bundled output
437
- const indexHtmlPath = import.meta.dir + '/client/index.html';
438
- const baseIndexHtml = existsSync(indexHtmlPath)
439
- ? readFileSync(indexHtmlPath, 'utf-8')
440
- : '';
441
-
442
- if (!baseIndexHtml) {
443
- otel.logger.warn('Production HTML not found at %s', indexHtmlPath);
444
- }
445
-
446
- const prodHtmlHandler = (c: Context) => {
447
- if (!baseIndexHtml) {
448
- return c.text('Production build incomplete', 500);
449
- }
450
- ${
451
- analyticsEnabled
452
- ? ` // Inject analytics config and script (session/thread loaded via session.js)
453
- const html = injectAnalytics(baseIndexHtml, analyticsConfig);
454
- return c.html(html);`
455
- : ` return c.html(baseIndexHtml);`
456
- }
457
- };
458
-
459
- app.get('/', prodHtmlHandler);
460
-
461
- // Serve static assets from /assets/* (Vite bundled output)
462
- app.use('/assets/*', serveStatic({ root: import.meta.dir + '/client', mimes: mimeTypes }));
463
-
464
- // Serve static public assets (favicon.ico, robots.txt, etc.)
465
- app.use('/*', serveStatic({ root: import.meta.dir + '/client', rewriteRequestPath: (path) => path, mimes: mimeTypes }));
466
-
467
- // 404 for unmatched API/system routes (IMPORTANT: comes before SPA fallback)
468
- app.all('/_agentuity/*', (c: Context) => c.notFound());
469
- app.all('/api/*', (c: Context) => c.notFound());
470
- if (!(hasWorkbenchConfig && isDevelopment())) {
471
- app.all('/workbench/*', (c: Context) => c.notFound());
472
- }
473
-
474
- // SPA fallback with asset protection
475
- app.get('*', (c: Context) => {
476
- const path = c.req.path;
477
- // If path has a file extension, it's likely an asset request - return 404
478
- if (/\\.[a-zA-Z0-9]+$/.test(path)) {
479
- return c.notFound();
480
- }
481
- return prodHtmlHandler(c);
482
- });
483
- }
484
- `;
485
- }
486
-
487
- // Workbench UI routes (development only)
488
- // The workbench UI is only served in development mode; the API routes are always available
489
- const workbenchRoute = workbench?.route ?? '/workbench';
490
- const workbenchRoutes = `
491
- // Workbench UI is only available in development mode (API routes are always available)
492
- if (hasWorkbenchConfig && isDevelopment()) {
493
- const workbenchSrcDir = import.meta.dir + '/workbench-src';
494
- const workbenchIndexPath = import.meta.dir + '/workbench-src/index.html';
495
- app.get('${workbenchRoute}', async (c: Context) => {
496
- const html = await Bun.file(workbenchIndexPath).text();
497
- // Rewrite script/css paths to use Vite's @fs protocol
498
- const withVite = html
499
- .replace('src="./main.tsx"', \`src="/@fs\${workbenchSrcDir}/main.tsx"\`)
500
- .replace('href="./styles.css"', \`href="/@fs\${workbenchSrcDir}/styles.css"\`);
501
- return c.html(withVite);
502
- });
503
- }
504
- `;
505
-
506
- // Server startup (same for dev and prod - Bun.serve with native WebSocket)
507
- const serverStartup = `
508
- // Start Bun server
509
- if (typeof Bun !== 'undefined') {
510
- // Enable process exit protection now that we're starting the server
511
- enableProcessExitProtection();
512
-
513
- const port = parseInt(process.env.PORT || '3500', 10);
514
-
515
- // Create custom WebSocket handler that supports both regular WebSockets and HMR proxy
516
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
517
- const hmrHandler = (globalThis as any).__AGENTUITY_VITE_HMR_WEBSOCKET__;
518
- const customWebsocket = {
519
- ...websocket,
520
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
521
- open(ws: any) {
522
- // Check if this is an HMR connection
523
- if (ws.data?.type === 'vite-hmr' && hmrHandler) {
524
- hmrHandler.open(ws);
525
- } else if (websocket.open) {
526
- websocket.open(ws);
527
- }
528
- },
529
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
530
- message(ws: any, message: string | Buffer) {
531
- // Check if this is an HMR connection
532
- if (ws.data?.type === 'vite-hmr' && hmrHandler) {
533
- hmrHandler.message(ws, message);
534
- } else if (websocket.message) {
535
- websocket.message(ws, message);
536
- }
537
- },
538
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
539
- close(ws: any, code?: number, reason?: string) {
540
- // Check if this is an HMR connection
541
- if (ws.data?.type === 'vite-hmr' && hmrHandler) {
542
- hmrHandler.close(ws);
543
- } else if (websocket.close) {
544
- websocket.close(ws, code, reason);
545
- }
546
- },
547
- };
548
-
549
- const server = Bun.serve({
550
- fetch: (req, server) => {
551
- // Get timeout from config on each request (0 = no timeout)
552
- server.timeout(req, getAppConfig()?.requestTimeout ?? 0);
553
- return app.fetch(req, server);
554
- },
555
- websocket: customWebsocket,
556
- port,
557
- hostname: '127.0.0.1',
558
- development: isDevelopment(),
559
- });
560
-
561
- // Make server available globally for health checks
562
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
563
- (globalThis as any).__AGENTUITY_SERVER__ = server;
564
-
565
- otel.logger.info(\`Server listening on http://127.0.0.1:\${port}\`);
566
- if (isDevelopment() && process.env.VITE_PORT) {
567
- otel.logger.debug(\`Proxying Vite assets from port \${process.env.VITE_PORT}\`);
568
- }
569
-
570
- // Register signal handlers for graceful shutdown (production only)
571
- // Dev mode has its own handlers in devmode.ts
572
- if (!isDevelopment()) {
573
- const handleShutdown = async (signal: string) => {
574
- otel.logger.info(\`Received \${signal}, initiating graceful shutdown...\`);
575
- try {
576
- await runShutdown();
577
- otel.logger.info('Shutdown complete');
578
- } catch (err) {
579
- otel.logger.error(\`Error during shutdown: \${err instanceof Error ? err.message : String(err)}\`);
580
- }
581
- process.exit(0);
582
- };
583
-
584
- process.once('SIGTERM', () => handleShutdown('SIGTERM'));
585
- process.once('SIGINT', () => handleShutdown('SIGINT'));
586
- }
587
- }
588
-
589
- // FOUND AN ERROR IN THIS FILE?
590
- // Please file an issue at https://github.com/agentuity/sdk/issues
591
- // or if you know the fix please submit a PR!
592
- `;
593
-
594
- const healthRoutes = `
595
- // Health check routes (production only)
596
- if (!isDevelopment()) {
597
- const healthHandler = (c: Context) => {
598
- return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
599
- };
600
- const idleHandler = (c: Context) => {
601
- // Check if server is idle (no pending requests/connections)
602
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
603
- const server = (globalThis as any).__AGENTUITY_SERVER__;
604
- if (!server) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
605
-
606
- // Check for pending background tasks
607
- if (hasWaitUntilPending()) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
608
-
609
- if (server.pendingRequests > 1) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
610
- if (server.pendingWebSockets > 0) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
611
-
612
- return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
613
- };
614
- app.get('/_agentuity/health', healthHandler);
615
- app.get('/_health', healthHandler);
616
- app.get('/_agentuity/idle', idleHandler);
617
- app.get('/_idle', idleHandler);
618
- }
619
-
620
- // Dev readiness check - verifies Vite asset server is ready to serve frontend
621
- if (isDevelopment()) {
622
- app.get('/_agentuity/ready', async (c: Context) => {
623
- const vitePort = process.env.VITE_PORT;
624
- if (!vitePort) {
625
- // No Vite port means we're not using Vite proxy
626
- return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
627
- }
628
-
629
- try {
630
- // Probe Vite to check if it can serve the main entry point
631
- // Use @vite/client as a lightweight check - it's always available
632
- const viteUrl = \`http://127.0.0.1:\${vitePort}/@vite/client\`;
633
- const res = await fetch(viteUrl, {
634
- signal: AbortSignal.timeout(5000),
635
- method: 'HEAD'
636
- });
637
-
638
- if (res.ok) {
639
- return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
640
- }
641
- return c.text('VITE_NOT_READY', 503, { 'Content-Type': 'text/plain; charset=utf-8' });
642
- } catch (err) {
643
- otel.logger.debug('Vite readiness check failed: %s', err instanceof Error ? err.message : String(err));
644
- return c.text('VITE_NOT_READY', 503, { 'Content-Type': 'text/plain; charset=utf-8' });
645
- }
646
- });
647
- }
648
- `;
649
-
650
- const devPatchesStep = noBundle
651
- ? `
652
- // Step 0.1: Apply runtime dev patches (--experimental-no-bundle mode)
653
- // Replaces build-time Bun.build patches for LLM gateway routing, AI SDK telemetry, and OTel spans
654
- if (isDevelopment()) {
655
- await applyDevPatches();
656
- }
657
- `
658
- : '';
659
- const code = `// @generated
660
- // Auto-generated by Agentuity
661
- // DO NOT EDIT - This file is regenerated on every build
662
- // Supports both development and production modes via runtime detection
663
- ${imports.join('\n')}
664
-
665
- ${modeDetection}
666
-
667
- // Step 0: Bootstrap runtime environment (load profile-specific .env files)
668
- // Only in development - production env vars are injected by platform
669
- // This must happen BEFORE any imports that depend on environment variables
670
- if (isDevelopment()) {
671
- // Pass project directory (two levels up from src/generated/) so .env files are loaded correctly
672
- await bootstrapRuntimeEnv({ projectDir: import.meta.dir + '/../..' });
673
- }
674
-
675
- ${devPatchesStep}
676
-
677
- // Step 0.25: load our runtime metadata and cache it
678
- loadBuildMetadata();
679
-
680
- // Step 0.5: Patch Bun's S3 client for Agentuity storage endpoints
681
- // Agentuity storage uses virtual-hosted-style URLs (*.storage.dev)
682
- // This patches s3.file() to automatically set virtualHostedStyle: true
683
- patchBunS3ForStorageDev();
684
-
685
- // Step 1: Initialize telemetry and services
686
- const serverUrl = \`http://127.0.0.1:\${process.env.PORT || '3500'}\`;
687
- const otel = register({ processors: getSpanProcessors(), logLevel: (process.env.AGENTUITY_LOG_LEVEL || 'info') as LogLevel });
688
-
689
- // Step 2: Create router and set as global
690
- const app = createRouter();
691
- setGlobalRouter(app);
692
-
693
- // Step 3: Apply middleware in correct order (BEFORE mounting routes)
694
- // Compression runs first (outermost) so it can compress the final response
695
- app.use('*', createCompressionMiddleware());
696
-
697
- app.use('*', createBaseMiddleware({
698
- logger: otel.logger,
699
- tracer: otel.tracer,
700
- meter: otel.meter,
701
- }));
702
-
703
- // Workbench routes always get OTel middleware for session tracking
704
- app.use('/_agentuity/workbench/*', createOtelMiddleware());
705
-
706
- // Note: /api/* middleware (CORS, OTel, agent context) is applied in Step 6
707
- // after app.ts import, so user-provided routers can specify custom prefixes.
708
-
709
- // Step 4: Import user's app.ts (runs createApp, gets state/config)
710
- await import('../../app.js');
711
-
712
- // Step 4.5: Import agent registry to ensure all agents are registered
713
- // This is needed for workbench metadata to return JSON schemas
714
- await import('./registry.js');
715
-
716
- // Step 5: Initialize providers
717
- const appState = getAppState();
718
- const appConfig = getAppConfig();
719
-
720
- createServices(otel.logger, appConfig, serverUrl);
721
-
722
- // Make logger and tracer globally available for user's app.ts
723
- setGlobalLogger(otel.logger);
724
- setGlobalTracer(otel.tracer);
725
-
726
- const threadProvider = getThreadProvider();
727
- const sessionProvider = getSessionProvider();
728
-
729
- await threadProvider.initialize(appState);
730
- await sessionProvider.initialize(appState);
731
-
732
- // Step 6: Mount routes (AFTER middleware is applied)
733
-
734
- ${healthRoutes}
735
-
736
- ${
737
- analyticsEnabled
738
- ? `// Register analytics routes
739
- registerAnalyticsRoutes(app);`
740
- : ''
741
- }
742
-
743
- ${assetProxyRoutes}
744
- ${apiMount}
745
- ${workbenchApiMount}
746
- ${workbenchRoutes}
747
- ${webRoutes}
748
-
749
- // Step 7: Run agent setup to signal completion
750
- await runAgentSetups(appState);
751
-
752
- ${serverStartup}
753
- `;
754
-
755
- // Collapse 2+ consecutive empty lines into 1 empty line (3+ \n becomes 2 \n)
756
- const cleanedCode = code.replace(/\n{3,}/g, '\n\n');
757
-
758
- await Bun.write(entryPath, cleanedCode);
759
- logger.trace(`Generated unified entry file at %s (mode: ${mode})`, entryPath);
760
- }