@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.
- package/dist/cmd/build/app-router-detector.d.ts +2 -5
- package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
- package/dist/cmd/build/app-router-detector.js +130 -154
- package/dist/cmd/build/app-router-detector.js.map +1 -1
- package/dist/cmd/build/ids.d.ts +11 -0
- package/dist/cmd/build/ids.d.ts.map +1 -0
- package/dist/cmd/build/ids.js +18 -0
- package/dist/cmd/build/ids.js.map +1 -0
- package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +166 -487
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +67 -134
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/docs-generator.js +0 -2
- package/dist/cmd/build/vite/docs-generator.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +0 -36
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
- package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
- package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +97 -177
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/server-bundler.js +1 -1
- package/dist/cmd/build/vite/server-bundler.js.map +1 -1
- package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
- package/dist/cmd/build/vite/static-renderer.js +1 -9
- package/dist/cmd/build/vite/static-renderer.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +171 -21
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +14 -13
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +6 -36
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
- package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
- package/dist/cmd/build/vite/ws-proxy.js +95 -0
- package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
- package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite-bundler.js +0 -3
- 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 +0 -1
- 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 +2 -8
- 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 +369 -720
- package/dist/cmd/dev/index.js.map +1 -1
- package/package.json +6 -8
- package/src/cmd/ai/prompt/agent.md +0 -1
- package/src/cmd/ai/prompt/api.md +0 -7
- package/src/cmd/ai/prompt/web.md +51 -213
- package/src/cmd/build/app-router-detector.ts +152 -182
- package/src/cmd/build/ids.ts +19 -0
- package/src/cmd/build/vite/agent-discovery.ts +208 -679
- package/src/cmd/build/vite/bun-dev-server.ts +78 -154
- package/src/cmd/build/vite/docs-generator.ts +0 -2
- package/src/cmd/build/vite/index.ts +1 -42
- package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
- package/src/cmd/build/vite/route-discovery.ts +116 -274
- package/src/cmd/build/vite/server-bundler.ts +1 -1
- package/src/cmd/build/vite/static-renderer.ts +1 -11
- package/src/cmd/build/vite/vite-asset-server-config.ts +196 -23
- package/src/cmd/build/vite/vite-asset-server.ts +25 -15
- package/src/cmd/build/vite/vite-builder.ts +6 -53
- package/src/cmd/build/vite/ws-proxy.ts +126 -0
- package/src/cmd/build/vite-bundler.ts +0 -4
- package/src/cmd/cloud/deploy.ts +0 -1
- package/src/cmd/dev/file-watcher.ts +2 -9
- package/src/cmd/dev/index.ts +409 -832
- package/dist/cmd/build/ast.d.ts +0 -78
- package/dist/cmd/build/ast.d.ts.map +0 -1
- package/dist/cmd/build/ast.js +0 -2703
- package/dist/cmd/build/ast.js.map +0 -1
- package/dist/cmd/build/entry-generator.d.ts +0 -25
- package/dist/cmd/build/entry-generator.d.ts.map +0 -1
- package/dist/cmd/build/entry-generator.js +0 -695
- package/dist/cmd/build/entry-generator.js.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
- package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.js +0 -83
- package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
- package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
- package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
- package/dist/cmd/build/vite/registry-generator.js +0 -1108
- package/dist/cmd/build/vite/registry-generator.js.map +0 -1
- package/dist/cmd/build/vite/tailwind-source-plugin.d.ts +0 -13
- package/dist/cmd/build/vite/tailwind-source-plugin.d.ts.map +0 -1
- package/dist/cmd/build/vite/tailwind-source-plugin.js +0 -44
- package/dist/cmd/build/vite/tailwind-source-plugin.js.map +0 -1
- package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
- package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
- package/dist/cmd/build/webanalytics-generator.js +0 -178
- package/dist/cmd/build/webanalytics-generator.js.map +0 -1
- package/dist/cmd/build/workbench.d.ts +0 -7
- package/dist/cmd/build/workbench.d.ts.map +0 -1
- package/dist/cmd/build/workbench.js +0 -55
- package/dist/cmd/build/workbench.js.map +0 -1
- package/dist/utils/route-migration.d.ts +0 -62
- package/dist/utils/route-migration.d.ts.map +0 -1
- package/dist/utils/route-migration.js +0 -630
- package/dist/utils/route-migration.js.map +0 -1
- package/src/cmd/build/ast.ts +0 -3529
- package/src/cmd/build/entry-generator.ts +0 -760
- package/src/cmd/build/vite/api-mount-path.ts +0 -87
- package/src/cmd/build/vite/registry-generator.ts +0 -1267
- package/src/cmd/build/vite/tailwind-source-plugin.ts +0 -54
- package/src/cmd/build/webanalytics-generator.ts +0 -197
- package/src/cmd/build/workbench.ts +0 -58
- package/src/utils/route-migration.ts +0 -757
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent Discovery
|
|
2
|
+
* Agent Discovery — import-based
|
|
3
3
|
*
|
|
4
|
-
* Discovers agents by scanning src/agent/**\/*.ts files
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
263
|
-
*
|
|
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
|
|
365
|
-
|
|
366
|
-
|
|
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<
|
|
520
|
-
|
|
521
|
-
if (!(await evalsFile.exists())) {
|
|
522
|
-
return [];
|
|
523
|
-
}
|
|
92
|
+
): Promise<string | undefined> {
|
|
93
|
+
if (!schema) return undefined;
|
|
524
94
|
|
|
525
95
|
try {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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.
|
|
537
|
-
|
|
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
|
-
*
|
|
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
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
):
|
|
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
|
|
560
|
-
const
|
|
561
|
-
const version = hash(contents);
|
|
129
|
+
const source = await Bun.file(filePath).text();
|
|
130
|
+
const version = hash(source);
|
|
562
131
|
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
137
|
+
if (!agent?.metadata?.name) {
|
|
138
|
+
logger.debug('[agent-discovery] No valid agent found in %s', relativeFilename);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
569
141
|
|
|
570
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
578
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
169
|
+
logger.trace(
|
|
170
|
+
'Found eval "%s" in %s (identifier: %s)',
|
|
171
|
+
evalName,
|
|
172
|
+
relativeFilename,
|
|
173
|
+
evalIdentifier
|
|
174
|
+
);
|
|
587
175
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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(
|
|
698
|
-
|
|
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
|
|
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
|
-
//
|
|
715
|
-
|
|
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
|
-
|
|
317
|
+
// Track seen agent names to deduplicate — e.g., index.ts re-exporting agent.ts
|
|
318
|
+
const seenAgentNames = new Set<string>();
|
|
721
319
|
|
|
722
|
-
|
|
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
|
|
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
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
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
|
-
|
|
820
|
-
logger.
|
|
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
|
|