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