@agentuity/cli 1.0.48 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/cmd/build/app-router-detector.d.ts +2 -5
  2. package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
  3. package/dist/cmd/build/app-router-detector.js +130 -154
  4. package/dist/cmd/build/app-router-detector.js.map +1 -1
  5. package/dist/cmd/build/ids.d.ts +11 -0
  6. package/dist/cmd/build/ids.d.ts.map +1 -0
  7. package/dist/cmd/build/ids.js +18 -0
  8. package/dist/cmd/build/ids.js.map +1 -0
  9. package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
  10. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/agent-discovery.js +166 -487
  12. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  13. package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
  14. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  15. package/dist/cmd/build/vite/bun-dev-server.js +67 -134
  16. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  17. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/docs-generator.js +0 -2
  19. package/dist/cmd/build/vite/docs-generator.js.map +1 -1
  20. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/index.js +0 -36
  22. package/dist/cmd/build/vite/index.js.map +1 -1
  23. package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
  24. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
  26. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  27. package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
  28. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  29. package/dist/cmd/build/vite/route-discovery.js +97 -177
  30. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  31. package/dist/cmd/build/vite/server-bundler.js +1 -1
  32. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  33. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/static-renderer.js +1 -9
  35. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  36. package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
  37. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/vite-asset-server-config.js +171 -21
  39. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  40. package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
  41. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  42. package/dist/cmd/build/vite/vite-asset-server.js +14 -13
  43. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  44. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/vite-builder.js +6 -36
  46. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  47. package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
  48. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
  49. package/dist/cmd/build/vite/ws-proxy.js +95 -0
  50. package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
  51. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  52. package/dist/cmd/build/vite-bundler.js +0 -3
  53. package/dist/cmd/build/vite-bundler.js.map +1 -1
  54. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  55. package/dist/cmd/cloud/deploy.js +0 -1
  56. package/dist/cmd/cloud/deploy.js.map +1 -1
  57. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  58. package/dist/cmd/dev/file-watcher.js +2 -8
  59. package/dist/cmd/dev/file-watcher.js.map +1 -1
  60. package/dist/cmd/dev/index.d.ts.map +1 -1
  61. package/dist/cmd/dev/index.js +369 -720
  62. package/dist/cmd/dev/index.js.map +1 -1
  63. package/package.json +6 -8
  64. package/src/cmd/ai/prompt/agent.md +0 -1
  65. package/src/cmd/ai/prompt/api.md +0 -7
  66. package/src/cmd/ai/prompt/web.md +51 -213
  67. package/src/cmd/build/app-router-detector.ts +152 -182
  68. package/src/cmd/build/ids.ts +19 -0
  69. package/src/cmd/build/vite/agent-discovery.ts +208 -679
  70. package/src/cmd/build/vite/bun-dev-server.ts +78 -154
  71. package/src/cmd/build/vite/docs-generator.ts +0 -2
  72. package/src/cmd/build/vite/index.ts +1 -42
  73. package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
  74. package/src/cmd/build/vite/route-discovery.ts +116 -274
  75. package/src/cmd/build/vite/server-bundler.ts +1 -1
  76. package/src/cmd/build/vite/static-renderer.ts +1 -11
  77. package/src/cmd/build/vite/vite-asset-server-config.ts +196 -23
  78. package/src/cmd/build/vite/vite-asset-server.ts +25 -15
  79. package/src/cmd/build/vite/vite-builder.ts +6 -53
  80. package/src/cmd/build/vite/ws-proxy.ts +126 -0
  81. package/src/cmd/build/vite-bundler.ts +0 -4
  82. package/src/cmd/cloud/deploy.ts +0 -1
  83. package/src/cmd/dev/file-watcher.ts +2 -9
  84. package/src/cmd/dev/index.ts +409 -832
  85. package/dist/cmd/build/ast.d.ts +0 -78
  86. package/dist/cmd/build/ast.d.ts.map +0 -1
  87. package/dist/cmd/build/ast.js +0 -2703
  88. package/dist/cmd/build/ast.js.map +0 -1
  89. package/dist/cmd/build/entry-generator.d.ts +0 -25
  90. package/dist/cmd/build/entry-generator.d.ts.map +0 -1
  91. package/dist/cmd/build/entry-generator.js +0 -695
  92. package/dist/cmd/build/entry-generator.js.map +0 -1
  93. package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
  94. package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
  95. package/dist/cmd/build/vite/api-mount-path.js +0 -83
  96. package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
  97. package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
  98. package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
  99. package/dist/cmd/build/vite/registry-generator.js +0 -1108
  100. package/dist/cmd/build/vite/registry-generator.js.map +0 -1
  101. package/dist/cmd/build/vite/tailwind-source-plugin.d.ts +0 -13
  102. package/dist/cmd/build/vite/tailwind-source-plugin.d.ts.map +0 -1
  103. package/dist/cmd/build/vite/tailwind-source-plugin.js +0 -44
  104. package/dist/cmd/build/vite/tailwind-source-plugin.js.map +0 -1
  105. package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
  106. package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
  107. package/dist/cmd/build/webanalytics-generator.js +0 -178
  108. package/dist/cmd/build/webanalytics-generator.js.map +0 -1
  109. package/dist/cmd/build/workbench.d.ts +0 -7
  110. package/dist/cmd/build/workbench.d.ts.map +0 -1
  111. package/dist/cmd/build/workbench.js +0 -55
  112. package/dist/cmd/build/workbench.js.map +0 -1
  113. package/dist/utils/route-migration.d.ts +0 -62
  114. package/dist/utils/route-migration.d.ts.map +0 -1
  115. package/dist/utils/route-migration.js +0 -630
  116. package/dist/utils/route-migration.js.map +0 -1
  117. package/src/cmd/build/ast.ts +0 -3529
  118. package/src/cmd/build/entry-generator.ts +0 -760
  119. package/src/cmd/build/vite/api-mount-path.ts +0 -87
  120. package/src/cmd/build/vite/registry-generator.ts +0 -1267
  121. package/src/cmd/build/vite/tailwind-source-plugin.ts +0 -54
  122. package/src/cmd/build/webanalytics-generator.ts +0 -197
  123. package/src/cmd/build/workbench.ts +0 -58
  124. package/src/utils/route-migration.ts +0 -757
@@ -1,16 +1,13 @@
1
1
  /**
2
- * Agent Discovery - READ-ONLY AST analysis
2
+ * Agent Discovery import-based
3
3
  *
4
- * Discovers agents by scanning src/agent/**\/*.ts files
5
- * Extracts metadata WITHOUT mutating source files
4
+ * Discovers agents by scanning src/agent/**\/*.ts files and importing them
5
+ * at build time. The agent instance already knows its own metadata, schemas,
6
+ * and evals — no AST parsing needed.
6
7
  */
7
8
 
8
- import * as acornLoose from 'acorn-loose';
9
- import { generate } from 'astring';
10
9
  import { dirname, join, relative } from 'node:path';
11
- import { existsSync } from 'node:fs';
12
10
  import type { Logger } from '../../../types';
13
- import { formatSchemaCode } from '../format-schema';
14
11
  import { StructuredError } from '@agentuity/core';
15
12
  import { toForwardSlash } from '../../../utils/normalize-path';
16
13
 
@@ -19,180 +16,6 @@ const DuplicateEvalNameError = StructuredError('DuplicateEvalNameError')<{
19
16
  filename: string;
20
17
  }>();
21
18
 
22
- interface ASTNode {
23
- type: string;
24
- start?: number;
25
- end?: number;
26
- }
27
-
28
- interface ASTNodeIdentifier extends ASTNode {
29
- name: string;
30
- }
31
-
32
- interface ASTPropertyNode {
33
- type: string;
34
- kind: string;
35
- key: ASTNodeIdentifier;
36
- value: ASTNode;
37
- shorthand?: boolean;
38
- method?: boolean;
39
- computed?: boolean;
40
- }
41
-
42
- interface ASTObjectExpression extends ASTNode {
43
- properties: ASTPropertyNode[];
44
- }
45
-
46
- interface ASTLiteral extends ASTNode {
47
- value: string | number | boolean | null;
48
- raw?: string;
49
- }
50
-
51
- interface ASTCallExpression extends ASTNode {
52
- arguments: unknown[];
53
- callee: ASTNode;
54
- }
55
-
56
- interface ASTMemberExpression extends ASTNode {
57
- object: ASTNode;
58
- property: ASTNode;
59
- computed?: boolean;
60
- }
61
-
62
- interface ASTVariableDeclarator extends ASTNode {
63
- id: ASTNode;
64
- init?: ASTNode;
65
- }
66
-
67
- interface ASTVariableDeclaration extends ASTNode {
68
- declarations: ASTVariableDeclarator[];
69
- }
70
-
71
- interface ASTExportNamedDeclaration extends ASTNode {
72
- declaration?: ASTNode;
73
- }
74
-
75
- interface ASTProgram {
76
- type: string;
77
- body: ASTNode[];
78
- }
79
-
80
- /**
81
- * Type for identifier resolver function
82
- */
83
- type IdentifierResolver = (name: string) => ASTNode | undefined;
84
-
85
- /**
86
- * Build a file-local identifier resolver that maps top-level variable names
87
- * to their initializer AST nodes. This allows resolving schema variable references.
88
- */
89
- function buildIdentifierResolver(program: ASTProgram): IdentifierResolver {
90
- const initMap = new Map<string, ASTNode>();
91
-
92
- for (const node of program.body) {
93
- // const x = ... or let x = ... or var x = ...
94
- if (node.type === 'VariableDeclaration') {
95
- const decl = node as unknown as ASTVariableDeclaration;
96
- for (const d of decl.declarations) {
97
- if (d.id.type === 'Identifier' && d.init) {
98
- const id = d.id as ASTNodeIdentifier;
99
- initMap.set(id.name, d.init);
100
- }
101
- }
102
- }
103
-
104
- // export const x = ...
105
- if (node.type === 'ExportNamedDeclaration') {
106
- const exp = node as unknown as ASTExportNamedDeclaration;
107
- if (exp.declaration && exp.declaration.type === 'VariableDeclaration') {
108
- const decl = exp.declaration as unknown as ASTVariableDeclaration;
109
- for (const d of decl.declarations) {
110
- if (d.id.type === 'Identifier' && d.init) {
111
- const id = d.id as ASTNodeIdentifier;
112
- initMap.set(id.name, d.init);
113
- }
114
- }
115
- }
116
- }
117
- }
118
-
119
- return (name: string) => initMap.get(name);
120
- }
121
-
122
- /**
123
- * Get the property name from an AST node (Identifier or Literal).
124
- */
125
- function getPropertyName(node: ASTNode): string | undefined {
126
- if (node.type === 'Identifier') {
127
- return (node as ASTNodeIdentifier).name;
128
- }
129
- if (node.type === 'Literal') {
130
- const lit = node as ASTLiteral;
131
- return typeof lit.value === 'string' ? lit.value : undefined;
132
- }
133
- return undefined;
134
- }
135
-
136
- /**
137
- * Resolve an expression by following identifier references and member access chains.
138
- * Applies a recursion limit to prevent infinite loops from cyclic references.
139
- *
140
- * Supported patterns:
141
- * - Identifiers: `AgentInput` -> resolves to variable definition
142
- * - Member access: `configs.agent1.schema` -> traverses object literals
143
- */
144
- function resolveExpression(
145
- node: ASTNode,
146
- resolveIdentifier: IdentifierResolver,
147
- depth = 0
148
- ): ASTNode {
149
- if (!node) return node;
150
- if (depth > 8) return node; // Prevent cycles / deep alias chains
151
-
152
- // Follow identifiers to their definitions
153
- if (node.type === 'Identifier') {
154
- const id = node as ASTNodeIdentifier;
155
- const resolved = resolveIdentifier(id.name);
156
- if (resolved) {
157
- return resolveExpression(resolved, resolveIdentifier, depth + 1);
158
- }
159
- }
160
-
161
- // Follow member expressions: configs.agent1.schema, baseSchemas.shared
162
- if (node.type === 'MemberExpression') {
163
- const memberExpr = node as unknown as ASTMemberExpression;
164
-
165
- // Skip computed properties like configs[agentName]
166
- if (memberExpr.computed) return node;
167
-
168
- const propName = getPropertyName(memberExpr.property);
169
- if (!propName) return node;
170
-
171
- // First resolve the object side (e.g., configs -> { agent1: {...} })
172
- const resolvedObj = resolveExpression(memberExpr.object, resolveIdentifier, depth + 1);
173
-
174
- // If we got an object literal, look up the property
175
- if (resolvedObj.type === 'ObjectExpression') {
176
- const obj = resolvedObj as ASTObjectExpression;
177
- for (const prop of obj.properties) {
178
- // Skip spread elements
179
- if (!prop || !('key' in prop) || !prop.key) continue;
180
-
181
- const keyName = getPropertyName(prop.key);
182
- if (keyName === propName && prop.value) {
183
- // Recurse into the property value
184
- return resolveExpression(prop.value as ASTNode, resolveIdentifier, depth + 1);
185
- }
186
- }
187
- }
188
-
189
- // Couldn't resolve - return original node
190
- return node;
191
- }
192
-
193
- return node;
194
- }
195
-
196
19
  export interface AgentMetadata {
197
20
  filename: string;
198
21
  name: string;
@@ -221,13 +44,13 @@ export interface EvalMetadata {
221
44
  */
222
45
  function hash(...val: string[]): string {
223
46
  const hasher = new Bun.CryptoHasher('sha256');
224
- val.forEach((v) => hasher.update(v));
47
+ for (const v of val) hasher.update(v);
225
48
  return hasher.digest().toHex();
226
49
  }
227
50
 
228
51
  function hashSHA1(...val: string[]): string {
229
52
  const hasher = new Bun.CryptoHasher('sha1');
230
- val.forEach((v) => hasher.update(v));
53
+ for (const v of val) hasher.update(v);
231
54
  return hasher.digest().toHex();
232
55
  }
233
56
 
@@ -259,448 +82,217 @@ function generateStableEvalId(projectId: string, agentId: string, name: string):
259
82
  }
260
83
 
261
84
  /**
262
- * Check if a property key matches a given name.
263
- * Handles both Identifier keys (schema) and Literal keys ('schema').
264
- */
265
- function isKeyNamed(prop: ASTPropertyNode, name: 'schema' | 'input' | 'output'): boolean {
266
- if (!prop || !prop.key) return false;
267
-
268
- if (prop.key.type === 'Identifier') {
269
- return (prop.key as ASTNodeIdentifier).name === name;
270
- }
271
- if (prop.key.type === 'Literal') {
272
- const lit = prop.key as unknown as ASTLiteral;
273
- return typeof lit.value === 'string' && lit.value === name;
274
- }
275
- return false;
276
- }
277
-
278
- /**
279
- * Extract schema code from createAgent call arguments.
280
- * Resolves variable references to their actual definitions when possible.
281
- *
282
- * Supported patterns:
283
- * - Inline schema: `schema: { input: s.object({...}), output: s.object({...}) }`
284
- * - Variable reference: `schema: { input: AgentInput, output: AgentOutput }`
285
- * - Schema object variable: `schema: schemaVar` where schemaVar is a top-level const
286
- * - Shorthand: `schema: { input, output }` where input/output are top-level consts
287
- *
288
- * Unsupported patterns (returns empty or partial result):
289
- * - Config alias: `createAgent('x', configVar)` - config must be inline object
290
- * - Schema from member access: `schema: configs.agent1.schema`
291
- * - Schema from function call: `schema: getSchema()`
292
- * - Destructured variables: `const { schema } = config`
293
- * - Cross-file imports (falls back to identifier name)
294
- */
295
- function extractSchemaCode(
296
- callargexp: ASTObjectExpression,
297
- resolveIdentifier: IdentifierResolver
298
- ): {
299
- inputSchemaCode?: string;
300
- outputSchemaCode?: string;
301
- } {
302
- let schemaObj: ASTObjectExpression | undefined;
303
-
304
- // Find the schema property
305
- for (const prop of callargexp.properties) {
306
- // Skip spread elements or any non-Property nodes
307
- if (!prop || !('key' in prop) || !prop.key) continue;
308
-
309
- if (isKeyNamed(prop, 'schema')) {
310
- // Resolve the schema value if it's an identifier (e.g., schema: schemaVar)
311
- let valueNode = prop.value as ASTNode;
312
- valueNode = resolveExpression(valueNode, resolveIdentifier);
313
-
314
- if (valueNode.type === 'ObjectExpression') {
315
- schemaObj = valueNode as ASTObjectExpression;
316
- break;
317
- }
318
- }
319
- }
320
-
321
- if (!schemaObj) {
322
- return {};
323
- }
324
-
325
- let inputSchemaCode: string | undefined;
326
- let outputSchemaCode: string | undefined;
327
-
328
- // Extract input and output schema code
329
- for (const prop of schemaObj.properties) {
330
- // Skip spread elements or any non-Property nodes
331
- if (!prop || !('key' in prop) || !prop.key) continue;
332
-
333
- if (isKeyNamed(prop, 'input') && prop.value) {
334
- // Resolve variable reference if the value is an identifier
335
- const resolvedValue = resolveExpression(prop.value as ASTNode, resolveIdentifier);
336
- inputSchemaCode = formatSchemaCode(generate(resolvedValue));
337
- } else if (isKeyNamed(prop, 'output') && prop.value) {
338
- // Resolve variable reference if the value is an identifier
339
- const resolvedValue = resolveExpression(prop.value as ASTNode, resolveIdentifier);
340
- outputSchemaCode = formatSchemaCode(generate(resolvedValue));
341
- }
342
- }
343
-
344
- return { inputSchemaCode, outputSchemaCode };
345
- }
346
-
347
- /**
348
- * Parse object expression to extract metadata
349
- */
350
- function parseObjectExpressionToMap(expr: ASTObjectExpression): Map<string, string> {
351
- const result = new Map<string, string>();
352
- for (const prop of expr.properties) {
353
- if (prop.value.type === 'Literal') {
354
- const value = prop.value as ASTLiteral;
355
- result.set(prop.key.name, String(value.value));
356
- }
357
- }
358
- return result;
359
- }
360
-
361
- /**
362
- * Extract metadata from createAgent call (READ-ONLY)
85
+ * Convert a StandardSchemaV1-compatible schema to a JSON Schema string.
86
+ * Dynamically imports toJSONSchema from @agentuity/schema (available in user's project).
363
87
  */
364
- function extractAgentMetadata(
365
- code: string,
366
- filename: string,
367
- projectId: string,
368
- deploymentId: string
369
- ): AgentMetadata | null {
370
- const ast = acornLoose.parse(code, {
371
- ecmaVersion: 'latest',
372
- sourceType: 'module',
373
- }) as ASTProgram;
374
-
375
- // Build identifier resolver for resolving schema variable references
376
- const resolveIdentifier = buildIdentifierResolver(ast);
377
-
378
- // Calculate file version (hash of contents)
379
- const version = hash(code);
380
-
381
- // Find createAgent calls
382
- for (const node of ast.body) {
383
- if (node.type === 'ExportDefaultDeclaration') {
384
- const declaration = (node as unknown as { declaration: ASTNode }).declaration;
385
-
386
- if (declaration.type === 'CallExpression') {
387
- const callExpr = declaration as ASTCallExpression;
388
-
389
- if (
390
- callExpr.callee.type === 'Identifier' &&
391
- (callExpr.callee as ASTNodeIdentifier).name === 'createAgent' &&
392
- callExpr.arguments.length >= 2
393
- ) {
394
- // First arg is agent name
395
- const nameArg = callExpr.arguments[0] as ASTLiteral;
396
- const name = String(nameArg.value);
397
-
398
- // Second arg is config object
399
- const callargexp = callExpr.arguments[1] as ASTObjectExpression;
400
-
401
- // Extract schemas (with variable resolution)
402
- const { inputSchemaCode, outputSchemaCode } = extractSchemaCode(
403
- callargexp,
404
- resolveIdentifier
405
- );
406
-
407
- // Extract description from either direct property or metadata object
408
- let description: string | undefined;
409
- for (const prop of callargexp.properties) {
410
- // Check for direct description property
411
- if (prop.key.name === 'description' && prop.value.type === 'Literal') {
412
- description = String((prop.value as ASTLiteral).value);
413
- break; // Direct description takes precedence
414
- }
415
- // Also check metadata.description for backwards compat
416
- if (prop.key.name === 'metadata' && prop.value.type === 'ObjectExpression') {
417
- const metadataMap = parseObjectExpressionToMap(
418
- prop.value as ASTObjectExpression
419
- );
420
- if (!description) {
421
- description = metadataMap.get('description');
422
- }
423
- break;
424
- }
425
- }
426
-
427
- // Generate IDs
428
- const id = getAgentId(projectId, deploymentId, filename, version);
429
- const agentId = generateStableAgentId(projectId, name);
430
-
431
- return {
432
- filename,
433
- name,
434
- id,
435
- agentId,
436
- version,
437
- description,
438
- inputSchemaCode,
439
- outputSchemaCode,
440
- };
441
- }
442
- }
443
- }
444
-
445
- // Also check variable declarations (e.g., const agent = createAgent(...))
446
- if (node.type === 'VariableDeclaration') {
447
- const declarations = (node as unknown as { declarations: ASTVariableDeclarator[] })
448
- .declarations;
449
- for (const decl of declarations) {
450
- if (decl.init && decl.init.type === 'CallExpression') {
451
- const callExpr = decl.init as ASTCallExpression;
452
-
453
- if (
454
- callExpr.callee.type === 'Identifier' &&
455
- (callExpr.callee as ASTNodeIdentifier).name === 'createAgent' &&
456
- callExpr.arguments.length >= 2
457
- ) {
458
- const nameArg = callExpr.arguments[0] as ASTLiteral;
459
- const name = String(nameArg.value);
460
-
461
- const callargexp = callExpr.arguments[1] as ASTObjectExpression;
462
- const { inputSchemaCode, outputSchemaCode } = extractSchemaCode(
463
- callargexp,
464
- resolveIdentifier
465
- );
466
-
467
- let description: string | undefined;
468
- for (const prop of callargexp.properties) {
469
- // Check for direct description property
470
- if (prop.key.name === 'description' && prop.value.type === 'Literal') {
471
- description = String((prop.value as ASTLiteral).value);
472
- break; // Direct description takes precedence
473
- }
474
- // Also check metadata.description for backwards compat
475
- if (prop.key.name === 'metadata' && prop.value.type === 'ObjectExpression') {
476
- const metadataMap = parseObjectExpressionToMap(
477
- prop.value as ASTObjectExpression
478
- );
479
- if (!description) {
480
- description = metadataMap.get('description');
481
- }
482
- break;
483
- }
484
- }
485
-
486
- const id = getAgentId(projectId, deploymentId, filename, version);
487
- const agentId = generateStableAgentId(projectId, name);
488
-
489
- return {
490
- filename,
491
- name,
492
- id,
493
- agentId,
494
- version,
495
- description,
496
- inputSchemaCode,
497
- outputSchemaCode,
498
- };
499
- }
500
- }
501
- }
502
- }
503
- }
504
-
505
- return null;
506
- }
507
-
508
- /**
509
- * Extract evals from a file (READ-ONLY)
510
- * Finds createEval calls regardless of whether they're exported or not
511
- */
512
- async function extractEvalMetadata(
513
- evalsPath: string,
514
- relativeEvalsPath: string,
515
- agentId: string,
516
- projectId: string,
517
- deploymentId: string,
88
+ async function schemaToJsonString(
89
+ schema: unknown,
90
+ rootDir: string,
518
91
  logger: Logger
519
- ): Promise<EvalMetadata[]> {
520
- const evalsFile = Bun.file(evalsPath);
521
- if (!(await evalsFile.exists())) {
522
- return [];
523
- }
92
+ ): Promise<string | undefined> {
93
+ if (!schema) return undefined;
524
94
 
525
95
  try {
526
- const evalsSource = await evalsFile.text();
527
- return extractEvalsFromSource(
528
- evalsSource,
529
- relativeEvalsPath,
530
- agentId,
531
- projectId,
532
- deploymentId,
533
- logger
534
- );
96
+ // Resolve @agentuity/schema from the user's project and import its public entry point.
97
+ // The CLI doesn't declare @agentuity/schema as its own dependency — it lives in the
98
+ // user's node_modules, so we resolve the path dynamically.
99
+ const schemaPackageDir = join(rootDir, 'node_modules', '@agentuity', 'schema');
100
+ if (!(await Bun.file(join(schemaPackageDir, 'package.json')).exists())) {
101
+ logger.debug('[agent-discovery] @agentuity/schema not found in user project');
102
+ return undefined;
103
+ }
104
+
105
+ const { toJSONSchema } = await import(schemaPackageDir);
106
+ const jsonSchema = toJSONSchema(schema);
107
+ return JSON.stringify(jsonSchema);
535
108
  } catch (error) {
536
- logger.warn(`Failed to parse evals from ${evalsPath}: ${error}`);
537
- return [];
109
+ logger.debug(
110
+ '[agent-discovery] Failed to convert schema to JSON Schema: %s',
111
+ error instanceof Error ? error.message : String(error)
112
+ );
113
+ return undefined;
538
114
  }
539
115
  }
540
116
 
541
117
  /**
542
- * Extract evals from source code (READ-ONLY)
543
- * Finds all createEval calls in the source, exported or not
118
+ * Import an agent file and extract metadata from the agent instance.
544
119
  */
545
- function extractEvalsFromSource(
546
- source: string,
547
- filename: string,
548
- agentId: string,
120
+ async function importAgentMetadata(
121
+ filePath: string,
122
+ relativeFilename: string,
123
+ rootDir: string,
549
124
  projectId: string,
550
125
  deploymentId: string,
551
126
  logger: Logger
552
- ): EvalMetadata[] {
553
- // Quick check - skip if no createEval in source
554
- if (!source.includes('createEval')) {
555
- return [];
556
- }
557
-
127
+ ): Promise<AgentMetadata | null> {
558
128
  try {
559
- const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
560
- const contents = transpiler.transformSync(source);
561
- const version = hash(contents);
129
+ const source = await Bun.file(filePath).text();
130
+ const version = hash(source);
562
131
 
563
- const ast = acornLoose.parse(contents, { ecmaVersion: 'latest', sourceType: 'module' });
564
- const evals: EvalMetadata[] = [];
132
+ // Import the agent file Bun handles TS natively
133
+ // No source-level gate: files may re-export agents created elsewhere
134
+ const mod = await import(filePath);
135
+ const agent = mod.default;
565
136
 
566
- // Recursively find all createEval calls in the AST
567
- function findCreateEvalCalls(node: unknown): void {
568
- if (!node || typeof node !== 'object') return;
137
+ if (!agent?.metadata?.name) {
138
+ logger.debug('[agent-discovery] No valid agent found in %s', relativeFilename);
139
+ return null;
140
+ }
569
141
 
570
- const n = node as Record<string, unknown>;
142
+ const name = agent.metadata.name;
143
+ const description = agent.metadata.description;
144
+ const id = getAgentId(projectId, deploymentId, relativeFilename, version);
145
+ const agentId = generateStableAgentId(projectId, name);
571
146
 
572
- // Check if this is a createEval call (either direct or method call)
573
- // Direct: createEval('name', {...})
574
- // Method: agent.createEval('name', {...})
575
- let isCreateEvalCall = false;
147
+ // Extract schemas as JSON Schema strings
148
+ const inputSchemaCode = await schemaToJsonString(agent.inputSchema, rootDir, logger);
149
+ const outputSchemaCode = await schemaToJsonString(agent.outputSchema, rootDir, logger);
576
150
 
577
- if (n.type === 'CallExpression' && n.callee && typeof n.callee === 'object') {
578
- const callee = n.callee as ASTNode & { property?: ASTNodeIdentifier };
151
+ // Extract evals from agent.evals array (self-registered by createEval())
152
+ const evals: EvalMetadata[] = [];
153
+ if (agent.evals && Array.isArray(agent.evals) && agent.evals.length > 0) {
154
+ for (const evalItem of agent.evals) {
155
+ const evalName = evalItem.metadata?.name ?? evalItem.name;
156
+ if (!evalName) continue;
157
+
158
+ const evalDescription = evalItem.metadata?.description ?? evalItem.description;
159
+ const evalVersion = version; // same file version
160
+ const evalId = getEvalId(
161
+ projectId,
162
+ deploymentId,
163
+ relativeFilename,
164
+ evalName,
165
+ evalVersion
166
+ );
167
+ const evalIdentifier = generateStableEvalId(projectId, agentId, evalName);
579
168
 
580
- // Direct function call: createEval(...)
581
- if (
582
- callee.type === 'Identifier' &&
583
- (callee as ASTNodeIdentifier).name === 'createEval'
584
- ) {
585
- isCreateEvalCall = true;
586
- }
169
+ logger.trace(
170
+ 'Found eval "%s" in %s (identifier: %s)',
171
+ evalName,
172
+ relativeFilename,
173
+ evalIdentifier
174
+ );
587
175
 
588
- // Method call: someAgent.createEval(...)
589
- if (
590
- callee.type === 'MemberExpression' &&
591
- callee.property &&
592
- callee.property.type === 'Identifier' &&
593
- callee.property.name === 'createEval'
594
- ) {
595
- isCreateEvalCall = true;
596
- }
176
+ evals.push({
177
+ id: evalId,
178
+ identifier: evalIdentifier,
179
+ name: evalName,
180
+ filename: relativeFilename,
181
+ version: evalVersion,
182
+ description: evalDescription,
183
+ agentIdentifier: agentId,
184
+ projectId,
185
+ });
597
186
  }
187
+ }
598
188
 
599
- if (isCreateEvalCall) {
600
- const callExpr = n as unknown as ASTCallExpression;
601
- let evalName: string | undefined;
602
- let description: string | undefined;
603
-
604
- if (callExpr.arguments.length >= 2) {
605
- // Format: agent.createEval('name', { config })
606
- const nameArg = callExpr.arguments[0] as ASTLiteral;
607
- evalName = String(nameArg.value);
608
-
609
- const callargexp = callExpr.arguments[1] as ASTObjectExpression;
610
- if (callargexp.properties) {
611
- for (const prop of callargexp.properties) {
612
- if (prop.key.name === 'metadata' && prop.value.type === 'ObjectExpression') {
613
- const metadataMap = parseObjectExpressionToMap(
614
- prop.value as ASTObjectExpression
615
- );
616
- description = metadataMap.get('description');
617
- break;
618
- }
619
- }
620
- }
621
- } else if (callExpr.arguments.length === 1) {
622
- // Format: agent.createEval(presetEval({ name: '...', ... }))
623
- // or: agent.createEval(presetEval()) - uses preset's default name
624
- // or: agent.createEval({ name: '...', ... })
625
- const arg = callExpr.arguments[0] as ASTNode;
626
-
627
- // Handle CallExpression: presetEval({ name: '...' }) or presetEval()
628
- if (arg.type === 'CallExpression') {
629
- const innerCall = arg as unknown as ASTCallExpression;
630
-
631
- // Try to get name from the call arguments first
632
- if (innerCall.arguments.length >= 1) {
633
- const configArg = innerCall.arguments[0] as ASTObjectExpression;
634
- if (configArg.type === 'ObjectExpression' && configArg.properties) {
635
- const configMap = parseObjectExpressionToMap(configArg);
636
- evalName = configMap.get('name');
637
- description = configMap.get('description');
638
- }
639
- }
189
+ // Also check for evals in separate eval.ts file in same directory
190
+ const agentDir = dirname(filePath);
191
+ const evalsPath = join(agentDir, 'eval.ts');
192
+ if (await Bun.file(evalsPath).exists()) {
193
+ const evalsSource = await Bun.file(evalsPath).text();
194
+ if (evalsSource.includes('createEval')) {
195
+ try {
196
+ await import(evalsPath);
197
+ // After importing, the evals self-register on the agent via agent.createEval()
198
+ // Re-check agent.evals for any newly registered evals
199
+ if (agent.evals && Array.isArray(agent.evals)) {
200
+ const relativeEvalsPath = toForwardSlash(relative(join(rootDir), evalsPath));
201
+ const evalVersion = hash(evalsSource);
202
+
203
+ for (const evalItem of agent.evals) {
204
+ const evalName = evalItem.metadata?.name ?? evalItem.name;
205
+ if (!evalName) continue;
206
+
207
+ // Skip if already collected from agent file
208
+ if (evals.some((e) => e.name === evalName)) continue;
209
+
210
+ const evalDescription = evalItem.metadata?.description ?? evalItem.description;
211
+ const evalId = getEvalId(
212
+ projectId,
213
+ deploymentId,
214
+ relativeEvalsPath,
215
+ evalName,
216
+ evalVersion
217
+ );
218
+ const evalIdentifier = generateStableEvalId(projectId, agentId, evalName);
640
219
 
641
- // Fallback: use the callee name as the eval name (e.g., politeness())
642
- if (!evalName && innerCall.callee) {
643
- const callee = innerCall.callee as ASTNode;
644
- if (callee.type === 'Identifier') {
645
- evalName = (callee as ASTNodeIdentifier).name;
646
- }
647
- }
648
- }
220
+ logger.trace(
221
+ 'Found eval "%s" in eval.ts for agent %s (identifier: %s)',
222
+ evalName,
223
+ name,
224
+ evalIdentifier
225
+ );
649
226
 
650
- // Handle ObjectExpression: { name: '...', handler: ... }
651
- if (arg.type === 'ObjectExpression') {
652
- const configArg = arg as ASTObjectExpression;
653
- if (configArg.properties) {
654
- const configMap = parseObjectExpressionToMap(configArg);
655
- evalName = configMap.get('name');
656
- description = configMap.get('description');
227
+ evals.push({
228
+ id: evalId,
229
+ identifier: evalIdentifier,
230
+ name: evalName,
231
+ filename: relativeEvalsPath,
232
+ version: evalVersion,
233
+ description: evalDescription,
234
+ agentIdentifier: agentId,
235
+ projectId,
236
+ });
657
237
  }
658
238
  }
659
- }
660
-
661
- if (evalName) {
662
- const id = getEvalId(projectId, deploymentId, filename, evalName, version);
663
- const identifier = generateStableEvalId(projectId, agentId, evalName);
664
-
665
- logger.trace(`Found eval '${evalName}' in ${filename} (identifier: ${identifier})`);
666
-
667
- evals.push({
668
- id,
669
- identifier,
670
- name: evalName,
671
- filename,
672
- version,
673
- description,
674
- agentIdentifier: agentId,
675
- projectId,
676
- });
239
+ } catch (error) {
240
+ logger.warn(
241
+ '[agent-discovery] Failed to import evals from %s: %s',
242
+ evalsPath,
243
+ error instanceof Error ? error.message : String(error)
244
+ );
677
245
  }
678
246
  }
247
+ }
679
248
 
680
- // Recursively search child nodes
681
- for (const key of Object.keys(n)) {
682
- const value = n[key];
683
- if (Array.isArray(value)) {
684
- for (const item of value) {
685
- findCreateEvalCalls(item);
686
- }
687
- } else if (value && typeof value === 'object') {
688
- findCreateEvalCalls(value);
249
+ // Check for duplicate eval names across sources (agent file + eval.ts).
250
+ // Same name + same agent = same stable identifier → duplicate key error
251
+ // on the backend database.
252
+ if (evals.length > 1) {
253
+ const seen = new Map<string, string>();
254
+ for (const evalItem of evals) {
255
+ const prev = seen.get(evalItem.name);
256
+ if (prev && prev !== evalItem.filename) {
257
+ throw new DuplicateEvalNameError({
258
+ message:
259
+ `Duplicate eval name '${evalItem.name}' for agent '${name}': ` +
260
+ `defined in both '${prev}' and '${evalItem.filename}'. ` +
261
+ 'Eval names must be unique per agent.',
262
+ agent: name,
263
+ filename: evalItem.filename,
264
+ });
689
265
  }
266
+ seen.set(evalItem.name, evalItem.filename);
690
267
  }
691
268
  }
692
269
 
693
- findCreateEvalCalls(ast);
694
-
695
- return evals;
270
+ return {
271
+ filename: relativeFilename,
272
+ name,
273
+ id,
274
+ agentId,
275
+ version,
276
+ description,
277
+ inputSchemaCode,
278
+ outputSchemaCode,
279
+ evals: evals.length > 0 ? evals : undefined,
280
+ };
696
281
  } catch (error) {
697
- logger.warn(`Failed to parse evals from ${filename}: ${error}`);
698
- return [];
282
+ logger.warn(
283
+ '[agent-discovery] Failed to import agent %s: %s',
284
+ filePath,
285
+ error instanceof Error ? error.message : String(error)
286
+ );
287
+ return null;
699
288
  }
700
289
  }
701
290
 
702
291
  /**
703
- * Discover all agents in src/agent directory (READ-ONLY)
292
+ * Discover all agents in src/agent directory.
293
+ *
294
+ * Imports each agent file at build time — the agent instance already knows
295
+ * its own metadata, schemas, and evals. No AST parsing needed.
704
296
  */
705
297
  export async function discoverAgents(
706
298
  srcDir: string,
@@ -710,114 +302,51 @@ export async function discoverAgents(
710
302
  ): Promise<AgentMetadata[]> {
711
303
  const agentsDir = join(srcDir, 'agent');
712
304
  const agents: AgentMetadata[] = [];
305
+ const rootDir = join(srcDir, '..');
713
306
 
714
- // Check if agent directory exists
715
- if (!existsSync(agentsDir)) {
307
+ // Scan all .ts files in agent directory
308
+ const glob = new Bun.Glob('**/*.ts');
309
+ let files: string[];
310
+ try {
311
+ files = await Array.fromAsync(glob.scan(agentsDir));
312
+ } catch {
716
313
  logger.trace('No agent directory found at %s', agentsDir);
717
314
  return agents;
718
315
  }
719
316
 
720
- const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
317
+ // Track seen agent names to deduplicate — e.g., index.ts re-exporting agent.ts
318
+ const seenAgentNames = new Set<string>();
721
319
 
722
- // Scan all .ts files in agent directory
723
- const glob = new Bun.Glob('**/*.ts');
724
- for await (const file of glob.scan(agentsDir)) {
320
+ for (const file of files) {
725
321
  const filePath = join(agentsDir, file);
726
322
 
727
- // Skip eval.ts files (processed separately)
323
+ // Skip eval.ts files (processed as part of agent discovery)
728
324
  if (file.endsWith('/eval.ts') || file === 'eval.ts') {
729
325
  continue;
730
326
  }
731
327
 
732
- try {
733
- const source = await Bun.file(filePath).text();
734
- const contents = transpiler.transformSync(source);
735
-
736
- // Use 'src/' prefix for consistency with bun bundler and registry imports
737
- const rootDir = join(srcDir, '..');
738
- const relativeFilename = toForwardSlash(relative(rootDir, filePath));
739
- const agentMetadata = extractAgentMetadata(
740
- contents,
741
- relativeFilename,
742
- projectId,
743
- deploymentId
744
- );
745
-
746
- if (agentMetadata) {
747
- logger.trace('Discovered agent: %s at %s', agentMetadata.name, relativeFilename);
748
-
749
- // Collect evals from multiple sources
750
- const allEvals: EvalMetadata[] = [];
751
-
752
- // 1. Extract evals from the agent file itself (agent.createEval() pattern)
753
- const evalsInAgentFile = extractEvalsFromSource(
754
- source,
755
- relativeFilename,
756
- agentMetadata.agentId,
757
- projectId,
758
- deploymentId,
759
- logger
760
- );
761
- if (evalsInAgentFile.length > 0) {
762
- logger.trace(
763
- 'Found %d eval(s) in agent file for %s',
764
- evalsInAgentFile.length,
765
- agentMetadata.name
766
- );
767
- allEvals.push(...evalsInAgentFile);
768
- }
328
+ const relativeFilename = toForwardSlash(relative(rootDir, filePath));
329
+ const agentMetadata = await importAgentMetadata(
330
+ filePath,
331
+ relativeFilename,
332
+ rootDir,
333
+ projectId,
334
+ deploymentId,
335
+ logger
336
+ );
769
337
 
770
- // 2. Check for evals in separate eval.ts file in same directory
771
- const agentDir = dirname(filePath);
772
- const evalsPath = join(agentDir, 'eval.ts');
773
- const relativeEvalsPath = toForwardSlash(relative(rootDir, evalsPath));
774
- const evalsInSeparateFile = await extractEvalMetadata(
775
- evalsPath,
776
- relativeEvalsPath,
777
- agentMetadata.agentId,
778
- projectId,
779
- deploymentId,
780
- logger
338
+ if (agentMetadata) {
339
+ if (seenAgentNames.has(agentMetadata.name)) {
340
+ logger.trace(
341
+ 'Skipping duplicate agent %s from %s (already discovered)',
342
+ agentMetadata.name,
343
+ relativeFilename
781
344
  );
782
- if (evalsInSeparateFile.length > 0) {
783
- logger.trace(
784
- 'Found %d eval(s) in eval.ts for agent %s',
785
- evalsInSeparateFile.length,
786
- agentMetadata.name
787
- );
788
- allEvals.push(...evalsInSeparateFile);
789
- }
790
-
791
- // Check for duplicate eval names across sources (agent file + eval.ts)
792
- // Same name + same agent = same stable identifier, which causes a
793
- // duplicate key error on the backend database.
794
- if (allEvals.length > 1) {
795
- const seen = new Map<string, string>();
796
- for (const evalItem of allEvals) {
797
- const prev = seen.get(evalItem.name);
798
- if (prev && prev !== evalItem.filename) {
799
- throw new DuplicateEvalNameError({
800
- message:
801
- `Duplicate eval name '${evalItem.name}' for agent '${agentMetadata.name}': ` +
802
- `defined in both '${prev}' and '${evalItem.filename}'. ` +
803
- 'Eval names must be unique per agent.',
804
- agent: agentMetadata.name,
805
- filename: evalItem.filename,
806
- });
807
- }
808
- seen.set(evalItem.name, evalItem.filename);
809
- }
810
- }
811
-
812
- if (allEvals.length > 0) {
813
- agentMetadata.evals = allEvals;
814
- logger.trace('Total %d eval(s) for agent %s', allEvals.length, agentMetadata.name);
815
- }
816
-
817
- agents.push(agentMetadata);
345
+ continue;
818
346
  }
819
- } catch (error) {
820
- logger.warn(`Failed to parse agent file ${filePath}: ${error}`);
347
+ seenAgentNames.add(agentMetadata.name);
348
+ logger.trace('Discovered agent: %s at %s', agentMetadata.name, relativeFilename);
349
+ agents.push(agentMetadata);
821
350
  }
822
351
  }
823
352