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