@agentuity/cli 0.0.100 → 0.0.102

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