@agentuity/cli 0.0.99 → 0.0.101

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 (107) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/api.d.ts +1 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/auth.js +5 -0
  8. package/dist/auth.js.map +1 -1
  9. package/dist/cmd/build/ast.d.ts +2 -1
  10. package/dist/cmd/build/ast.d.ts.map +1 -1
  11. package/dist/cmd/build/ast.js +135 -47
  12. package/dist/cmd/build/ast.js.map +1 -1
  13. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  14. package/dist/cmd/build/entry-generator.js +220 -188
  15. package/dist/cmd/build/entry-generator.js.map +1 -1
  16. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  17. package/dist/cmd/build/vite/agent-discovery.js +103 -45
  18. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  19. package/dist/cmd/build/vite/bun-dev-server.js +1 -1
  20. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  21. package/dist/cmd/build/vite/docs-generator.d.ts +13 -0
  22. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -0
  23. package/dist/cmd/build/vite/docs-generator.js +81 -0
  24. package/dist/cmd/build/vite/docs-generator.js.map +1 -0
  25. package/dist/cmd/build/vite/index.d.ts +3 -4
  26. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  27. package/dist/cmd/build/vite/index.js +9 -8
  28. package/dist/cmd/build/vite/index.js.map +1 -1
  29. package/dist/cmd/build/vite/lifecycle-generator.d.ts +1 -1
  30. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  31. package/dist/cmd/build/vite/lifecycle-generator.js +19 -5
  32. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  33. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/metadata-generator.js +145 -0
  35. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  36. package/dist/cmd/build/vite/registry-generator.d.ts +3 -3
  37. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/registry-generator.js +627 -103
  39. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  40. package/dist/cmd/build/vite/route-discovery.d.ts +4 -0
  41. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  42. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  43. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  44. package/dist/cmd/build/vite/server-bundler.js +48 -1
  45. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  46. package/dist/cmd/build/vite/vite-builder.d.ts +1 -1
  47. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  48. package/dist/cmd/build/vite/vite-builder.js +30 -21
  49. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  50. package/dist/cmd/build/vite-bundler.js +6 -6
  51. package/dist/cmd/build/vite-bundler.js.map +1 -1
  52. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  53. package/dist/cmd/cloud/deploy.js +11 -5
  54. package/dist/cmd/cloud/deploy.js.map +1 -1
  55. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  56. package/dist/cmd/dev/file-watcher.js +33 -1
  57. package/dist/cmd/dev/file-watcher.js.map +1 -1
  58. package/dist/cmd/dev/index.d.ts.map +1 -1
  59. package/dist/cmd/dev/index.js +102 -21
  60. package/dist/cmd/dev/index.js.map +1 -1
  61. package/dist/cmd/dev/sync.d.ts.map +1 -1
  62. package/dist/cmd/dev/sync.js +19 -3
  63. package/dist/cmd/dev/sync.js.map +1 -1
  64. package/dist/cmd/project/create.d.ts.map +1 -1
  65. package/dist/cmd/project/create.js +8 -2
  66. package/dist/cmd/project/create.js.map +1 -1
  67. package/dist/config.d.ts.map +1 -1
  68. package/dist/config.js +8 -0
  69. package/dist/config.js.map +1 -1
  70. package/dist/index.d.ts +0 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +0 -1
  73. package/dist/index.js.map +1 -1
  74. package/package.json +5 -8
  75. package/src/api.ts +1 -1
  76. package/src/auth.ts +6 -0
  77. package/src/cmd/build/ast.ts +161 -48
  78. package/src/cmd/build/entry-generator.ts +225 -190
  79. package/src/cmd/build/vite/agent-discovery.ts +151 -58
  80. package/src/cmd/build/vite/bun-dev-server.ts +1 -1
  81. package/src/cmd/build/vite/docs-generator.ts +87 -0
  82. package/src/cmd/build/vite/index.ts +9 -8
  83. package/src/cmd/build/vite/lifecycle-generator.ts +19 -5
  84. package/src/cmd/build/vite/metadata-generator.ts +178 -0
  85. package/src/cmd/build/vite/registry-generator.ts +727 -108
  86. package/src/cmd/build/vite/route-discovery.ts +4 -0
  87. package/src/cmd/build/vite/server-bundler.ts +56 -1
  88. package/src/cmd/build/vite/vite-builder.ts +46 -33
  89. package/src/cmd/build/vite-bundler.ts +6 -6
  90. package/src/cmd/cloud/deploy.ts +15 -5
  91. package/src/cmd/dev/file-watcher.ts +37 -1
  92. package/src/cmd/dev/index.ts +141 -30
  93. package/src/cmd/dev/sync.ts +41 -6
  94. package/src/cmd/project/create.ts +13 -3
  95. package/src/config.ts +9 -0
  96. package/src/index.ts +0 -5
  97. package/src/runtime-bootstrap.md +1 -1
  98. package/dist/cmd/build/vite/patch-plugin.d.ts +0 -21
  99. package/dist/cmd/build/vite/patch-plugin.d.ts.map +0 -1
  100. package/dist/cmd/build/vite/patch-plugin.js +0 -70
  101. package/dist/cmd/build/vite/patch-plugin.js.map +0 -1
  102. package/dist/runtime-bootstrap.d.ts +0 -56
  103. package/dist/runtime-bootstrap.d.ts.map +0 -1
  104. package/dist/runtime-bootstrap.js +0 -95
  105. package/dist/runtime-bootstrap.js.map +0 -1
  106. package/src/cmd/build/vite/patch-plugin.ts +0 -88
  107. package/src/runtime-bootstrap.ts +0 -131
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Registry Generator
3
3
  *
4
- * Generates .agentuity/registry.generated.ts from discovered agents
4
+ * Generates src/generated/registry.ts from discovered agents
5
5
  */
6
6
 
7
7
  import { join } from 'node:path';
8
- import { writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
8
+ import { writeFileSync, mkdirSync, existsSync, unlinkSync, readFileSync } from 'node:fs';
9
9
  import { StructuredError } from '@agentuity/core';
10
10
  import { toCamelCase, toPascalCase } from '../../../utils/string';
11
11
  import type { AgentMetadata } from './agent-discovery';
@@ -14,12 +14,11 @@ import type { RouteInfo } from './route-discovery';
14
14
  const AgentIdentifierCollisionError = StructuredError('AgentIdentifierCollisionError');
15
15
 
16
16
  /**
17
- * Generate .agentuity/registry.generated.ts with agent registry and types
17
+ * Generate src/generated/registry.ts with agent registry and types
18
18
  */
19
19
  export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]): void {
20
- const projectRoot = join(srcDir, '..');
21
- const agentuityDir = join(projectRoot, '.agentuity');
22
- const registryPath = join(agentuityDir, 'registry.generated.ts');
20
+ const generatedDir = join(srcDir, 'generated');
21
+ const registryPath = join(generatedDir, 'registry.ts');
23
22
 
24
23
  // Detect naming collisions in generated identifiers
25
24
  const generatedNames = new Set<string>();
@@ -50,60 +49,131 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
50
49
  // Handle both './agent/...' and 'src/agent/...' formats
51
50
  let relativePath = filename;
52
51
  if (relativePath.startsWith('./agent/')) {
53
- relativePath = relativePath.replace(/^\.\/agent\//, '../src/agent/');
52
+ // ./agent/foo.ts -> ../agent/foo.js (use .js extension for TypeScript)
53
+ relativePath = relativePath
54
+ .replace(/^\.\/agent\//, '../agent/')
55
+ .replace(/\.tsx?$/, '.js');
54
56
  } else if (relativePath.startsWith('src/agent/')) {
55
- relativePath = '../' + relativePath;
57
+ // src/agent/foo.ts -> ../agent/foo.js (use .js extension for TypeScript)
58
+ relativePath = relativePath
59
+ .replace(/^src\/agent\//, '../agent/')
60
+ .replace(/\.tsx?$/, '.js');
56
61
  }
57
62
  return `import ${camelName} from '${relativePath}';`;
58
63
  })
59
64
  .join('\n');
60
65
 
61
- // Generate flat registry structure
62
- const registry = agents
63
- .map(({ name }) => {
66
+ // Generate schema type exports for all agents
67
+ const schemaTypeExports = agents
68
+ .map(({ name, description }) => {
64
69
  const camelName = toCamelCase(name);
65
- return ` ${camelName}: ${camelName},`;
70
+ const pascalName = toPascalCase(name);
71
+ const descComment = description ? `\n * ${description}` : '';
72
+
73
+ const parts = [
74
+ '',
75
+ `/**`,
76
+ ` * Input type for ${name} agent${descComment}`,
77
+ ` */`,
78
+ `export type ${pascalName}Input = InferInput<typeof ${camelName}['inputSchema']>;`,
79
+ '',
80
+ `/**`,
81
+ ` * Output type for ${name} agent${descComment}`,
82
+ ` */`,
83
+ `export type ${pascalName}Output = InferOutput<typeof ${camelName}['outputSchema']>;`,
84
+ '',
85
+ `/**`,
86
+ ` * Input schema type for ${name} agent${descComment}`,
87
+ ` */`,
88
+ `export type ${pascalName}InputSchema = typeof ${camelName}['inputSchema'];`,
89
+ '',
90
+ `/**`,
91
+ ` * Output schema type for ${name} agent${descComment}`,
92
+ ` */`,
93
+ `export type ${pascalName}OutputSchema = typeof ${camelName}['outputSchema'];`,
94
+ '',
95
+ `/**`,
96
+ ` * Agent type for ${name}${descComment}`,
97
+ ` */`,
98
+ `export type ${pascalName}Agent = AgentRunner<`,
99
+ `\t${pascalName}InputSchema,`,
100
+ `\t${pascalName}OutputSchema,`,
101
+ `\ttypeof ${camelName}['stream'] extends true ? true : false`,
102
+ `>;`,
103
+ ];
104
+ return parts.join('\n');
66
105
  })
67
106
  .join('\n');
68
107
 
69
- // Generate type exports for all agents
70
- const typeExports = agents
71
- .map(({ name }) => {
108
+ // Generate flat registry structure with JSDoc
109
+ const registry = agents
110
+ .map(({ name, description }) => {
72
111
  const camelName = toCamelCase(name);
73
112
  const pascalName = toPascalCase(name);
74
- return `export type ${pascalName}Runner = AgentRunner<typeof ${camelName}['inputSchema'], typeof ${camelName}['outputSchema'], typeof ${camelName}['stream'] extends true ? true : false>;`;
113
+ const descComment = description ? `\n\t * ${description}` : '';
114
+
115
+ return `\t/**
116
+ \t * ${name}${descComment}
117
+ \t * @type {${pascalName}Agent}
118
+ \t */
119
+ \t${camelName},`;
75
120
  })
76
121
  .join('\n');
77
122
 
78
123
  // Generate flat agent type definitions for AgentRegistry interface augmentation
124
+ // Uses the exported Agent types defined above
79
125
  const runtimeAgentTypes = agents
80
126
  .map(({ name }) => {
81
127
  const camelName = toCamelCase(name);
82
- return ` ${camelName}: AgentRunner<typeof ${camelName}['inputSchema'], typeof ${camelName}['outputSchema'], typeof ${camelName}['stream'] extends true ? true : false>;`;
128
+ const pascalName = toPascalCase(name);
129
+ return ` ${camelName}: ${pascalName}Agent;`;
83
130
  })
84
131
  .join('\n');
85
132
 
86
- const generatedContent = `/// <reference types="hono" />
87
- // Auto-generated by Agentuity - do not edit manually
133
+ const generatedContent = `// @generated
134
+ // Auto-generated by Agentuity - DO NOT EDIT
88
135
  ${imports}
89
- import type { AgentRunner, Logger } from '@agentuity/runtime';
90
- import type { KeyValueStorage, StreamStorage, VectorStorage } from '@agentuity/core';
136
+ import type { AgentRunner } from '@agentuity/runtime';
137
+ import type { InferInput, InferOutput } from '@agentuity/core';
138
+
139
+ // ============================================================================
140
+ // Schema Type Exports
141
+ // ============================================================================
142
+ ${schemaTypeExports}
143
+
144
+ // ============================================================================
145
+ // Agent Definitions
146
+ // ============================================================================
91
147
 
92
148
  /**
149
+ * Agent Definitions
150
+ *
93
151
  * Registry of all agents in this application.
94
152
  * Provides strongly-typed access to agent metadata and runner functions.
95
- * Auto-generated from your agent files during build.
153
+ *
154
+ * @remarks
155
+ * This object is auto-generated from your agent files during build.
156
+ * Each agent has corresponding Input, Output, and Runner types exported above.
157
+ *
158
+ * @example
159
+ * \`\`\`typescript
160
+ * import { AgentDefinitions, SessionBasicInput } from './generated/registry';
161
+ *
162
+ * // Access agent definition
163
+ * const agent = AgentDefinitions.sessionBasic;
164
+ *
165
+ * // Use typed schema types
166
+ * const input: SessionBasicInput = { ... };
167
+ * const result = await agent.run(input);
168
+ * \`\`\`
96
169
  */
97
- export const agentRegistry = {
170
+ export const AgentDefinitions = {
98
171
  ${registry}
99
172
  } as const;
100
173
 
101
- // Local type aliases for Hono augmentation
102
- type LocalAgentName = keyof typeof agentRegistry;
103
- type LocalAgentRegistry = typeof agentRegistry;
104
-
105
- // Typed runners for each agent
106
- ${typeExports}
174
+ // ============================================================================
175
+ // Module Augmentation
176
+ // ============================================================================
107
177
 
108
178
  // Augment @agentuity/runtime types with strongly-typed agents from this project
109
179
  declare module "@agentuity/runtime" {
@@ -113,38 +183,289 @@ ${runtimeAgentTypes}
113
183
  }
114
184
  }
115
185
 
116
- // NOTE: Hono Context properties are accessed via c.var (e.g., c.var.logger, c.var.kv)
117
- // The Variables interface in @agentuity/runtime defines all available context properties
186
+ // FOUND AN ERROR IN THIS FILE?
187
+ // Please file an issue at https://github.com/agentuity/sdk/issues
188
+ // or if you know the fix please submit a PR!
118
189
  `;
119
190
 
120
191
  const agentsDir = join(srcDir, 'agent');
121
192
  const legacyTypesPath = join(agentsDir, 'types.generated.d.ts');
122
193
 
123
- // Ensure .agentuity directory exists
124
- if (!existsSync(agentuityDir)) {
125
- mkdirSync(agentuityDir, { recursive: true });
194
+ // Ensure src/generated directory exists
195
+ if (!existsSync(generatedDir)) {
196
+ mkdirSync(generatedDir, { recursive: true });
126
197
  }
127
198
 
128
- writeFileSync(registryPath, generatedContent, 'utf-8');
199
+ // Collapse 2+ consecutive empty lines into 1 empty line (3+ \n becomes 2 \n)
200
+ const cleanedContent = generatedContent.replace(/\n{3,}/g, '\n\n');
201
+
202
+ writeFileSync(registryPath, cleanedContent, 'utf-8');
129
203
 
130
- // Remove legacy types.generated.d.ts if it exists (now consolidated into registry.generated.ts)
204
+ // Remove legacy types.generated.d.ts if it exists (legacy cleanup)
131
205
  if (existsSync(legacyTypesPath)) {
132
206
  unlinkSync(legacyTypesPath);
133
207
  }
134
208
  }
135
209
 
210
+ /**
211
+ * Helper function to generate RPC-style nested registry type.
212
+ * Converts routes like "POST /api/hello" to nested structure: post.api.hello
213
+ */
214
+ function generateRPCRegistryType(
215
+ apiRoutes: RouteInfo[],
216
+ websocketRoutes: RouteInfo[],
217
+ sseRoutes: RouteInfo[],
218
+ _agentImports: Map<string, string>,
219
+ _schemaImportAliases: Map<string, Map<string, string>>,
220
+ agentMetadataMap: Map<string, AgentMetadata>
221
+ ): string {
222
+ // Build nested structure from routes
223
+ interface NestedNode {
224
+ [key: string]: NestedNode | { input: string; output: string; type: string; route: RouteInfo };
225
+ }
226
+
227
+ const tree: NestedNode = {};
228
+
229
+ // Helper to add route to tree
230
+ const addRoute = (route: RouteInfo, routeType: 'api' | 'websocket' | 'sse' | 'stream') => {
231
+ const method = route.method.toLowerCase();
232
+
233
+ // Strip /api prefix from path
234
+ let cleanPath = route.path;
235
+ if (cleanPath.startsWith('/api/')) {
236
+ cleanPath = cleanPath.substring(4); // Remove '/api'
237
+ } else if (cleanPath === '/api') {
238
+ cleanPath = '/';
239
+ }
240
+
241
+ const pathParts = cleanPath.split('/').filter(Boolean);
242
+
243
+ // Navigate/create tree structure: path segments first, then method
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
+ let current: any = tree;
246
+
247
+ // Add path segments (all parts) - convert to camelCase for safe property access
248
+ for (let i = 0; i < pathParts.length; i++) {
249
+ const part = toCamelCase(pathParts[i]);
250
+ if (!current[part]) {
251
+ current[part] = {};
252
+ }
253
+ current = current[part];
254
+ }
255
+
256
+ // Determine terminal method name based on route type
257
+ // For stream types (websocket, sse, stream), use the type name as the method
258
+ // For regular API routes, use the HTTP method
259
+ const terminalMethod =
260
+ routeType === 'websocket'
261
+ ? 'websocket'
262
+ : routeType === 'sse'
263
+ ? 'eventstream'
264
+ : routeType === 'stream'
265
+ ? 'stream'
266
+ : method;
267
+
268
+ // Add method as final level with schema types
269
+ const routeKey = `${route.method.toUpperCase()} ${route.path}`;
270
+ const safeName = routeKey
271
+ .replace(/[^a-zA-Z0-9]/g, '_')
272
+ .replace(/^_+|_+$/g, '')
273
+ .replace(/_+/g, '_');
274
+ const pascalName = toPascalCase(safeName);
275
+
276
+ // Only reference type names if route has schemas, otherwise use 'never'
277
+ const hasSchemas =
278
+ route.hasValidator ||
279
+ route.inputSchemaVariable ||
280
+ route.outputSchemaVariable ||
281
+ route.agentVariable;
282
+
283
+ current[terminalMethod] = {
284
+ input: hasSchemas ? `${pascalName}Input` : 'never',
285
+ output: hasSchemas ? `${pascalName}Output` : 'never',
286
+ type: `'${routeType}'`,
287
+ route,
288
+ };
289
+ };
290
+
291
+ // Add all routes with their types
292
+ apiRoutes.forEach((route) => {
293
+ const routeType = route.routeType === 'stream' ? 'stream' : 'api';
294
+ addRoute(route, routeType);
295
+ });
296
+ websocketRoutes.forEach((route) => addRoute(route, 'websocket'));
297
+ sseRoutes.forEach((route) => addRoute(route, 'sse'));
298
+
299
+ // Convert tree to TypeScript type string
300
+ function treeToTypeString(node: NestedNode, indent: string = '\t\t'): string {
301
+ const lines: string[] = [];
302
+
303
+ for (const [key, value] of Object.entries(node)) {
304
+ if (
305
+ value &&
306
+ typeof value === 'object' &&
307
+ 'input' in value &&
308
+ 'output' in value &&
309
+ 'type' in value &&
310
+ 'route' in value
311
+ ) {
312
+ // Leaf node with schema and type - add JSDoc
313
+ const route = value.route;
314
+ const jsdoc: string[] = [];
315
+
316
+ // Access route info from value
317
+ const routeInfo = route as RouteInfo;
318
+
319
+ // Look up agent metadata
320
+ let agentMeta: AgentMetadata | undefined;
321
+ if (routeInfo.agentVariable) {
322
+ agentMeta = agentMetadataMap.get(routeInfo.agentVariable);
323
+ }
324
+
325
+ // Build JSDoc comment
326
+ jsdoc.push(`${indent}/**`);
327
+ jsdoc.push(`${indent} * Route: ${routeInfo.method.toUpperCase()} ${routeInfo.path}`);
328
+ if (agentMeta?.name) {
329
+ jsdoc.push(`${indent} * @agent ${agentMeta.name}`);
330
+ }
331
+ if (agentMeta?.description) {
332
+ jsdoc.push(`${indent} * @description ${agentMeta.description}`);
333
+ }
334
+ jsdoc.push(`${indent} */`);
335
+ lines.push(...jsdoc);
336
+
337
+ lines.push(
338
+ `${indent}${key}: { input: ${value.input}; output: ${value.output}; type: ${value.type} };`
339
+ );
340
+ } else {
341
+ // Nested node
342
+ lines.push(`${indent}${key}: {`);
343
+ lines.push(treeToTypeString(value as NestedNode, indent + '\t'));
344
+ lines.push(`${indent}};`);
345
+ }
346
+ }
347
+
348
+ return lines.join('\n');
349
+ }
350
+
351
+ if (Object.keys(tree).length === 0) {
352
+ return '\t\t// No routes discovered';
353
+ }
354
+
355
+ return treeToTypeString(tree);
356
+ }
357
+
358
+ /**
359
+ * Generate runtime metadata object for RPC routes.
360
+ * This allows the client to know route types at runtime.
361
+ */
362
+ function generateRPCRuntimeMetadata(
363
+ apiRoutes: RouteInfo[],
364
+ websocketRoutes: RouteInfo[],
365
+ sseRoutes: RouteInfo[]
366
+ ): string {
367
+ interface MetadataNode {
368
+ [key: string]: MetadataNode | { type: string };
369
+ }
370
+
371
+ const tree: MetadataNode = {};
372
+
373
+ const addRoute = (route: RouteInfo, routeType: string) => {
374
+ let cleanPath = route.path;
375
+ if (cleanPath.startsWith('/api/')) {
376
+ cleanPath = cleanPath.substring(4);
377
+ } else if (cleanPath === '/api') {
378
+ cleanPath = '/';
379
+ }
380
+
381
+ const pathParts = cleanPath.split('/').filter(Boolean);
382
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
383
+ let current: any = tree;
384
+
385
+ // Convert path segments to camelCase for safe property access
386
+ for (const part of pathParts) {
387
+ const camelPart = toCamelCase(part);
388
+ if (!current[camelPart]) current[camelPart] = {};
389
+ current = current[camelPart];
390
+ }
391
+
392
+ // Use terminal method name based on route type
393
+ const terminalMethod =
394
+ routeType === 'websocket'
395
+ ? 'websocket'
396
+ : routeType === 'sse'
397
+ ? 'eventstream'
398
+ : routeType === 'stream'
399
+ ? 'stream'
400
+ : route.method.toLowerCase();
401
+
402
+ current[terminalMethod] = { type: routeType };
403
+ };
404
+
405
+ apiRoutes.forEach((r) => addRoute(r, r.routeType === 'stream' ? 'stream' : 'api'));
406
+ websocketRoutes.forEach((r) => addRoute(r, 'websocket'));
407
+ sseRoutes.forEach((r) => addRoute(r, 'sse'));
408
+
409
+ return JSON.stringify(tree, null, '\t\t');
410
+ }
411
+
136
412
  /**
137
413
  * Generate RouteRegistry type definitions from discovered routes.
138
414
  *
139
415
  * Creates a module augmentation for @agentuity/react that provides
140
416
  * strongly-typed route keys with input/output schema information.
141
417
  */
142
- export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void {
418
+ export function generateRouteRegistry(
419
+ srcDir: string,
420
+ routes: RouteInfo[],
421
+ agents: AgentMetadata[] = []
422
+ ): void {
423
+ // Check if project uses @agentuity/react
424
+ const projectRoot = join(srcDir, '..');
425
+ const packageJsonPath = join(projectRoot, 'package.json');
426
+ let hasReactDependency = false;
427
+
428
+ try {
429
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
430
+ hasReactDependency = !!(
431
+ packageJson.dependencies?.['@agentuity/react'] ||
432
+ packageJson.devDependencies?.['@agentuity/react']
433
+ );
434
+ } catch {
435
+ // If we can't read package.json, assume no React dependency
436
+ }
437
+
143
438
  // Filter routes by type
144
439
  const apiRoutes = routes.filter((r) => r.routeType === 'api' || r.routeType === 'stream');
145
440
  const websocketRoutes = routes.filter((r) => r.routeType === 'websocket');
146
441
  const sseRoutes = routes.filter((r) => r.routeType === 'sse');
147
442
 
443
+ const allRoutes = [...apiRoutes, ...websocketRoutes, ...sseRoutes];
444
+
445
+ // Create maps for agent metadata lookup
446
+ const agentMetadataMap = new Map<string, AgentMetadata>();
447
+ const agentNameMap = new Map<string, AgentMetadata>();
448
+
449
+ // Map by agent name for easy lookup
450
+ agents.forEach((agent) => {
451
+ agentNameMap.set(agent.name, agent);
452
+ });
453
+
454
+ // Map agent import variables to metadata by extracting agent name from import path
455
+ allRoutes.forEach((route) => {
456
+ if (route.agentVariable && route.agentImportPath) {
457
+ // Extract agent name from import path (e.g., "@agent/hello" -> "hello")
458
+ const match = route.agentImportPath.match(/@agent[s]?\/([^/]+)/);
459
+ if (match) {
460
+ const agentName = match[1];
461
+ const metadata = agentNameMap.get(agentName);
462
+ if (metadata) {
463
+ agentMetadataMap.set(route.agentVariable, metadata);
464
+ }
465
+ }
466
+ }
467
+ });
468
+
148
469
  if (apiRoutes.length === 0 && websocketRoutes.length === 0 && sseRoutes.length === 0) {
149
470
  return;
150
471
  }
@@ -154,25 +475,73 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
154
475
  const agentImports = new Map<string, string>();
155
476
  const routeFileImports = new Map<string, Set<string>>();
156
477
 
157
- const allRoutes = [...apiRoutes, ...websocketRoutes, ...sseRoutes];
158
-
159
- // Collect agent imports
478
+ // Collect agent and schema imports from routes with validators or exported schemas
160
479
  allRoutes.forEach((route) => {
161
- if (!route.hasValidator) return;
162
-
163
- if (route.agentVariable && route.agentImportPath && !agentImports.has(route.agentVariable)) {
480
+ const hasSchemaVars = !!route.inputSchemaVariable || !!route.outputSchemaVariable;
481
+ if (!route.hasValidator && !hasSchemaVars) return;
482
+
483
+ // Collect agent imports (when using agent.validator())
484
+ if (
485
+ route.hasValidator &&
486
+ route.agentVariable &&
487
+ route.agentImportPath &&
488
+ !agentImports.has(route.agentVariable)
489
+ ) {
164
490
  let resolvedPath = route.agentImportPath;
165
491
 
166
- if (resolvedPath.startsWith('@agent/')) {
167
- resolvedPath = `../src/agent/${resolvedPath.substring('@agent/'.length)}`;
492
+ if (resolvedPath.startsWith('@agents/') || resolvedPath.startsWith('@agent/')) {
493
+ // Handle both @agents/ and @agent/ aliases -> ../agent/
494
+ const suffix = resolvedPath.startsWith('@agents/')
495
+ ? resolvedPath.substring('@agents/'.length)
496
+ : resolvedPath.substring('@agent/'.length);
497
+
498
+ // Convert @agent/hello -> ../agent/hello/index.js
499
+ // Convert @agent/hello/agent -> ../agent/hello/agent.js
500
+ if (!suffix.includes('/')) {
501
+ // Bare module (e.g., @agent/hello) - add /index.js
502
+ resolvedPath = `../agent/${suffix}/index.js`;
503
+ } else {
504
+ // File path (e.g., @agent/hello/agent) - add .js
505
+ const finalPath = suffix.endsWith('.js')
506
+ ? suffix
507
+ : suffix.replace(/\.tsx?$/, '') + '.js';
508
+ resolvedPath = `../agent/${finalPath}`;
509
+ }
168
510
  } else if (resolvedPath.startsWith('@api/')) {
169
- resolvedPath = `../src/web/${resolvedPath.substring('@api/'.length)}`;
511
+ // src/generated/ -> src/api/ is ../api/
512
+ const suffix = resolvedPath.substring('@api/'.length);
513
+ const finalPath = suffix.endsWith('.js')
514
+ ? suffix
515
+ : suffix.replace(/\.tsx?$/, '') + '.js';
516
+ resolvedPath = `../api/${finalPath}`;
170
517
  } else if (resolvedPath.startsWith('./') || resolvedPath.startsWith('../')) {
518
+ // Resolve relative import from route file's directory
171
519
  const routeDir = route.filename.substring(0, route.filename.lastIndexOf('/'));
172
- resolvedPath = `../${routeDir}/${resolvedPath}`;
520
+ // Join and normalize the path
521
+ const joined = `${routeDir}/${resolvedPath}`;
522
+ // Normalize by resolving .. and . segments
523
+ const normalized = joined
524
+ .split('/')
525
+ .reduce((acc: string[], segment) => {
526
+ if (segment === '..') {
527
+ acc.pop();
528
+ } else if (segment !== '.' && segment !== '') {
529
+ acc.push(segment);
530
+ }
531
+ return acc;
532
+ }, [])
533
+ .join('/');
534
+ // Remove 'src/' prefix if present (routes are in src/, generated is in src/generated/)
535
+ const withoutSrc = normalized.startsWith('src/') ? normalized.substring(4) : normalized;
536
+ // Make it relative from src/generated/
537
+ resolvedPath = `../${withoutSrc}`;
538
+ // Add .js extension if not already present
539
+ if (!resolvedPath.endsWith('.js')) {
540
+ resolvedPath = resolvedPath.replace(/\.tsx?$/, '') + '.js';
541
+ }
173
542
  }
174
543
 
175
- const uniqueImportName = `agent_${route.agentVariable}`;
544
+ const uniqueImportName = route.agentVariable;
176
545
  imports.push(`import type ${uniqueImportName} from '${resolvedPath}';`);
177
546
  agentImports.set(route.agentVariable, uniqueImportName);
178
547
  }
@@ -180,7 +549,12 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
180
549
  // Collect schema variable imports
181
550
  if (route.inputSchemaVariable || route.outputSchemaVariable) {
182
551
  const filename = route.filename.replace(/\\/g, '/');
183
- const importPath = `../${filename.replace(/\.ts$/, '')}`;
552
+ // Remove 'src/' prefix if present (routes.filename might be './api/...' or 'src/api/...')
553
+ const withoutSrc = filename.startsWith('src/') ? filename.substring(4) : filename;
554
+ const withoutLeadingDot = withoutSrc.startsWith('./')
555
+ ? withoutSrc.substring(2)
556
+ : withoutSrc;
557
+ const importPath = `../${withoutLeadingDot.replace(/\.ts$/, '')}`;
184
558
 
185
559
  if (!routeFileImports.has(importPath)) {
186
560
  routeFileImports.set(importPath, new Set());
@@ -195,94 +569,339 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
195
569
  }
196
570
  });
197
571
 
198
- // Generate schema imports
572
+ // Generate schema imports with unique aliases to avoid conflicts
573
+ const schemaImportAliases = new Map<string, Map<string, string>>(); // importPath -> (schemaName -> alias)
574
+ let aliasCounter = 0;
575
+
199
576
  routeFileImports.forEach((schemas, importPath) => {
200
- const schemaList = Array.from(schemas).join(', ');
201
- imports.push(`import type { ${schemaList} } from '${importPath}';`);
577
+ const aliases = new Map<string, string>();
578
+ const importParts: string[] = [];
579
+
580
+ for (const schemaName of Array.from(schemas)) {
581
+ // Create a unique alias for this schema to avoid collisions
582
+ const alias = `${schemaName}_${aliasCounter++}`;
583
+ aliases.set(schemaName, alias);
584
+ importParts.push(`${schemaName} as ${alias}`);
585
+ }
586
+
587
+ schemaImportAliases.set(importPath, aliases);
588
+ imports.push(`import type { ${importParts.join(', ')} } from '${importPath}';`);
202
589
  });
203
590
 
204
- const importsStr = imports.join('\n');
591
+ const importsStr = imports.length > 0 ? imports.join('\n') + '\n' : '';
205
592
 
206
- // Helper to generate route entry
207
- const generateRouteEntry = (route: RouteInfo): string => {
208
- const routeKey = route.path;
593
+ // Add InferInput/InferOutput imports if we have any routes with schemas
594
+ const hasSchemas = allRoutes.some(
595
+ (r) => r.hasValidator || r.inputSchemaVariable || r.outputSchemaVariable
596
+ );
597
+ const typeImports = hasSchemas
598
+ ? `import type { InferInput, InferOutput } from '@agentuity/core';\n`
599
+ : '';
209
600
 
210
- if (!route.hasValidator) {
211
- const streamValue = route.stream === true ? 'true' : 'false';
212
- return ` '${routeKey}': {
213
- inputSchema: never;
214
- outputSchema: never;
215
- stream: ${streamValue};
216
- };`;
217
- }
601
+ // Generate individual route schema types
602
+ const routeSchemaTypes = allRoutes
603
+ .filter((r) => r.hasValidator || r.inputSchemaVariable || r.outputSchemaVariable)
604
+ .map((route) => {
605
+ const routeKey = route.method ? `${route.method.toUpperCase()} ${route.path}` : route.path;
606
+ const safeName = routeKey
607
+ .replace(/[^a-zA-Z0-9]/g, '_')
608
+ .replace(/^_+|_+$/g, '')
609
+ .replace(/_+/g, '_');
610
+ const pascalName = toPascalCase(safeName);
611
+
612
+ let inputType = 'never';
613
+ let outputType = 'never';
614
+ let inputSchemaType = 'never';
615
+ let outputSchemaType = 'never';
616
+ let agentMeta: AgentMetadata | undefined;
617
+
618
+ // Look up agent metadata if available
619
+ if (route.agentVariable) {
620
+ agentMeta = agentMetadataMap.get(route.agentVariable);
621
+ }
218
622
 
219
- if (route.agentVariable) {
220
- const importName = agentImports.get(route.agentVariable)!;
221
- return ` '${routeKey}': {
222
- inputSchema: typeof ${importName} extends { inputSchema?: infer I } ? I : never;
223
- outputSchema: typeof ${importName} extends { outputSchema?: infer O } ? O : never;
224
- stream: typeof ${importName} extends { stream?: infer S } ? S : false;
225
- };`;
226
- }
623
+ if (route.agentVariable) {
624
+ const importName = agentImports.get(route.agentVariable)!;
625
+ inputType = `InferInput<typeof ${importName}['inputSchema']>`;
626
+ outputType = `InferOutput<typeof ${importName}['outputSchema']>`;
627
+ inputSchemaType = `typeof ${importName} extends { inputSchema?: infer I } ? I : never`;
628
+ outputSchemaType = `typeof ${importName} extends { outputSchema?: infer O } ? O : never`;
629
+ } else if (route.inputSchemaVariable || route.outputSchemaVariable) {
630
+ // Get the aliased schema names for this route's file
631
+ const filename = route.filename.replace(/\\/g, '/');
632
+ const withoutSrc = filename.startsWith('src/') ? filename.substring(4) : filename;
633
+ const withoutLeadingDot = withoutSrc.startsWith('./')
634
+ ? withoutSrc.substring(2)
635
+ : withoutSrc;
636
+ const importPath = `../${withoutLeadingDot.replace(/\.ts$/, '')}`;
637
+ const aliases = schemaImportAliases.get(importPath);
638
+
639
+ const inputAlias = route.inputSchemaVariable && aliases?.get(route.inputSchemaVariable);
640
+ const outputAlias =
641
+ route.outputSchemaVariable && aliases?.get(route.outputSchemaVariable);
642
+
643
+ inputType = inputAlias ? `InferInput<typeof ${inputAlias}>` : 'never';
644
+ outputType = outputAlias ? `InferOutput<typeof ${outputAlias}>` : 'never';
645
+ inputSchemaType = inputAlias ? `typeof ${inputAlias}` : 'never';
646
+ outputSchemaType = outputAlias ? `typeof ${outputAlias}` : 'never';
647
+ }
227
648
 
228
- if (route.inputSchemaVariable || route.outputSchemaVariable) {
229
- const inputType = route.inputSchemaVariable
230
- ? `typeof ${route.inputSchemaVariable}`
231
- : 'never';
232
- const outputType = route.outputSchemaVariable
233
- ? `typeof ${route.outputSchemaVariable}`
234
- : 'never';
649
+ if (inputType === 'never' && outputType === 'never') {
650
+ return ''; // Skip routes without schemas
651
+ }
652
+
653
+ // Build JSDoc with agent description and schema details
654
+ const inputJSDoc = ['/**', ` * Input type for route: ${routeKey}`];
655
+ if (agentMeta?.description) {
656
+ inputJSDoc.push(` * @description ${agentMeta.description}`);
657
+ }
658
+ if (agentMeta?.inputSchemaCode) {
659
+ inputJSDoc.push(` * @schema ${agentMeta.inputSchemaCode}`);
660
+ }
661
+ inputJSDoc.push(' */');
662
+
663
+ const outputJSDoc = ['/**', ` * Output type for route: ${routeKey}`];
664
+ if (agentMeta?.description) {
665
+ outputJSDoc.push(` * @description ${agentMeta.description}`);
666
+ }
667
+ if (agentMeta?.outputSchemaCode) {
668
+ outputJSDoc.push(` * @schema ${agentMeta.outputSchemaCode}`);
669
+ }
670
+ outputJSDoc.push(' */');
671
+
672
+ const parts = [
673
+ '',
674
+ ...inputJSDoc,
675
+ `export type ${pascalName}Input = ${inputType};`,
676
+ '',
677
+ ...outputJSDoc,
678
+ `export type ${pascalName}Output = ${outputType};`,
679
+ '',
680
+ `/**`,
681
+ ` * Input schema type for route: ${routeKey}`,
682
+ ` */`,
683
+ `export type ${pascalName}InputSchema = ${inputSchemaType};`,
684
+ '',
685
+ `/**`,
686
+ ` * Output schema type for route: ${routeKey}`,
687
+ ` */`,
688
+ `export type ${pascalName}OutputSchema = ${outputSchemaType};`,
689
+ ];
690
+ return parts.join('\n');
691
+ })
692
+ .filter(Boolean)
693
+ .join('\n');
694
+
695
+ // Helper to generate route entry - uses exported schema types
696
+ const generateRouteEntry = (route: RouteInfo, pathIncludesMethod = false): string => {
697
+ const routeKey = route.path;
698
+ // For WebSocket/SSE routes, we need to include the method in the type name
699
+ // to match the generated types (which use "POST /api/websocket/echo" as the routeKey)
700
+ // For API routes, the method is already in the path from the caller
701
+ const typeRouteKey = pathIncludesMethod
702
+ ? route.path
703
+ : `${route.method?.toUpperCase()} ${route.path}`;
704
+ const safeName = typeRouteKey
705
+ .replace(/[^a-zA-Z0-9]/g, '_')
706
+ .replace(/^_+|_+$/g, '')
707
+ .replace(/_+/g, '_');
708
+ const pascalName = toPascalCase(safeName);
709
+
710
+ if (!route.hasValidator && !route.inputSchemaVariable && !route.outputSchemaVariable) {
235
711
  const streamValue = route.stream === true ? 'true' : 'false';
236
- return ` '${routeKey}': {
237
- inputSchema: ${inputType};
238
- outputSchema: ${outputType};
239
- stream: ${streamValue};
240
- };`;
712
+ return `\t'${routeKey}': {
713
+ \t\tinputSchema: never;
714
+ \t\toutputSchema: never;
715
+ \t\tstream: ${streamValue};
716
+ \t};`;
241
717
  }
242
718
 
243
- return ` '${routeKey}': {
244
- inputSchema: any;
245
- outputSchema: any;
246
- };`;
719
+ // Use the exported schema types we generated above
720
+ const importName = route.agentVariable ? agentImports.get(route.agentVariable)! : null;
721
+ const streamValue = importName
722
+ ? `typeof ${importName} extends { stream?: infer S } ? S : false`
723
+ : route.stream === true
724
+ ? 'true'
725
+ : 'false';
726
+
727
+ return `\t'${routeKey}': {
728
+ \t\tinputSchema: ${pascalName}InputSchema;
729
+ \t\toutputSchema: ${pascalName}OutputSchema;
730
+ \t\tstream: ${streamValue};
731
+ \t};`;
247
732
  };
248
733
 
249
734
  // Generate route entries with METHOD prefix for API routes
250
735
  const apiRouteEntries = apiRoutes
251
736
  .map((route) => {
252
737
  const routeKey = `${route.method.toUpperCase()} ${route.path}`;
253
- return generateRouteEntry({ ...route, path: routeKey });
738
+ return generateRouteEntry({ ...route, path: routeKey }, true);
254
739
  })
255
740
  .join('\n');
256
741
 
257
- const websocketRouteEntries = websocketRoutes.map(generateRouteEntry).join('\n');
258
- const sseRouteEntries = sseRoutes.map(generateRouteEntry).join('\n');
742
+ const websocketRouteEntries = websocketRoutes
743
+ .map((r) => generateRouteEntry(r, false))
744
+ .join('\n');
745
+ const sseRouteEntries = sseRoutes.map((r) => generateRouteEntry(r, false)).join('\n');
746
+
747
+ // Generate RPC-style nested registry type
748
+ const rpcRegistryType = generateRPCRegistryType(
749
+ apiRoutes,
750
+ websocketRoutes,
751
+ sseRoutes,
752
+ agentImports,
753
+ schemaImportAliases,
754
+ agentMetadataMap
755
+ );
756
+ const rpcRuntimeMetadata = generateRPCRuntimeMetadata(apiRoutes, websocketRoutes, sseRoutes);
757
+
758
+ const generatedContent = `// @generated
759
+ // Auto-generated by Agentuity - DO NOT EDIT
760
+ ${importsStr}${typeImports}${
761
+ !hasReactDependency
762
+ ? `
763
+ import { createClient } from '@agentuity/frontend';`
764
+ : ''
765
+ }
766
+ // ============================================================================
767
+ // Route Schema Type Exports
768
+ // ============================================================================
769
+ ${routeSchemaTypes}
259
770
 
260
- const generatedContent = `// Auto-generated by Agentuity - do not edit manually
261
- ${importsStr}
771
+ // ============================================================================
772
+ // Route Definitions
773
+ // ============================================================================
262
774
 
263
- // Augment @agentuity/react types with project-specific routes
775
+ /**
776
+ * Route Definitions
777
+ *
778
+ * Type-safe route registry for all API routes, WebSocket connections, and SSE endpoints.
779
+ * Used by @agentuity/react for client-side type-safe routing.
780
+ *
781
+ * @remarks
782
+ * This module augmentation is auto-generated from your route files during build.
783
+ * Individual route Input/Output types are exported above for direct usage.
784
+ */
785
+ ${
786
+ !hasReactDependency
787
+ ? `
788
+ /**
789
+ * RPC Route Registry
790
+ *
791
+ * Nested structure for RPC-style client access (e.g., client.hello.post())
792
+ * Used by createClient() from @agentuity/frontend for type-safe RPC calls.
793
+ */
794
+ export interface RPCRouteRegistry {
795
+ ${rpcRegistryType}
796
+ }
797
+ `
798
+ : ''
799
+ }
264
800
  declare module '@agentuity/react' {
265
- export interface RouteRegistry {
801
+ \t/**
802
+ \t * API Route Registry
803
+ \t *
804
+ \t * Maps route keys (METHOD /path) to their input/output schemas
805
+ \t */
806
+ \texport interface RouteRegistry {
266
807
  ${apiRouteEntries}
267
- }
268
-
269
- export interface WebSocketRouteRegistry {
808
+ \t}
809
+ \t
810
+ \t/**
811
+ \t * WebSocket Route Registry
812
+ \t *
813
+ \t * Maps WebSocket route paths to their schemas
814
+ \t */
815
+ \texport interface WebSocketRouteRegistry {
270
816
  ${websocketRouteEntries}
271
- }
272
-
273
- export interface SSERouteRegistry {
817
+ \t}
818
+ \t
819
+ \t/**
820
+ \t * Server-Sent Events Route Registry
821
+ \t *
822
+ \t * Maps SSE route paths to their schemas
823
+ \t */
824
+ \texport interface SSERouteRegistry {
274
825
  ${sseRouteEntries}
275
- }
826
+ \t}
827
+
828
+ \t/**
829
+ \t * RPC Route Registry
830
+ \t *
831
+ \t * Nested structure for RPC-style client access (e.g., client.hello.post())
832
+ \t * Used by createClient() from @agentuity/core for type-safe RPC calls.
833
+ \t */
834
+ \texport interface RPCRouteRegistry {
835
+ ${rpcRegistryType}
836
+ \t}
276
837
  }
838
+
839
+ /**
840
+ * Runtime metadata for RPC routes.
841
+ * Contains route type information for client routing decisions.
842
+ * @internal
843
+ */
844
+ const _rpcRouteMetadata = ${rpcRuntimeMetadata} as const;
845
+
846
+ // Store metadata globally for createAPIClient() to access
847
+ if (typeof globalThis !== 'undefined') {
848
+ (globalThis as Record<string, unknown>).__rpcRouteMetadata = _rpcRouteMetadata;
849
+ }
850
+ ${
851
+ !hasReactDependency
852
+ ? `
853
+ /**
854
+ * Create a type-safe API client with optional configuration.
855
+ *
856
+ * This function is only generated when @agentuity/react is not installed.
857
+ * If using React, import createAPIClient from '@agentuity/react' instead.
858
+ *
859
+ * @example
860
+ * \`\`\`typescript
861
+ * import { createAPIClient } from './generated/routes';
862
+ *
863
+ * // Basic usage
864
+ * const api = createAPIClient();
865
+ * const result = await api.hello.post({ name: 'World' });
866
+ *
867
+ * // With custom headers
868
+ * const api = createAPIClient({ headers: { 'X-Custom-Header': 'value' } });
869
+ * await api.hello.post({ name: 'World' });
870
+ * \`\`\`
871
+ */
872
+ export function createAPIClient(options?: Parameters<typeof createClient>[0]): import('@agentuity/frontend').Client<RPCRouteRegistry> {
873
+ return createClient(options || {}, _rpcRouteMetadata) as import('@agentuity/frontend').Client<RPCRouteRegistry>;
874
+ }
875
+ `
876
+ : `
877
+ /**
878
+ * Type-safe API client is available from @agentuity/react
879
+ *
880
+ * @example
881
+ * \`\`\`typescript
882
+ * import { createAPIClient } from '@agentuity/react';
883
+ *
884
+ * const api = createAPIClient();
885
+ * const result = await api.hello.post({ name: 'World' });
886
+ * \`\`\`
887
+ */
888
+ `
889
+ }
890
+
891
+ // FOUND AN ERROR IN THIS FILE?
892
+ // Please file an issue at https://github.com/agentuity/sdk/issues
893
+ // or if you know the fix please submit a PR!
277
894
  `;
278
895
 
279
- const projectRoot = join(srcDir, '..');
280
- const agentuityDir = join(projectRoot, '.agentuity');
281
- const registryPath = join(agentuityDir, 'routes.generated.ts');
896
+ const generatedDir = join(srcDir, 'generated');
897
+ const registryPath = join(generatedDir, 'routes.ts');
282
898
 
283
- if (!existsSync(agentuityDir)) {
284
- mkdirSync(agentuityDir, { recursive: true });
899
+ if (!existsSync(generatedDir)) {
900
+ mkdirSync(generatedDir, { recursive: true });
285
901
  }
286
902
 
287
- writeFileSync(registryPath, generatedContent, 'utf-8');
903
+ // Collapse 2+ consecutive empty lines into 1 empty line (3+ \n becomes 2 \n)
904
+ const cleanedContent = generatedContent.replace(/\n{3,}/g, '\n\n');
905
+
906
+ writeFileSync(registryPath, cleanedContent, 'utf-8');
288
907
  }