@agentuity/cli 0.0.77 → 0.0.79

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.
@@ -1,5 +1,5 @@
1
1
  import * as acornLoose from 'acorn-loose';
2
- import { dirname, relative } from 'node:path';
2
+ import { dirname, relative, join, basename } from 'node:path';
3
3
  import { parse as parseCronExpression } from '@datasert/cronjs-parser';
4
4
  import { generate } from 'astring';
5
5
  import type { BuildMetadata } from '../../types';
@@ -7,7 +7,7 @@ import { createLogger } from '@agentuity/server';
7
7
  import * as ts from 'typescript';
8
8
  import { StructuredError, type WorkbenchConfig } from '@agentuity/core';
9
9
  import type { LogLevel } from '../../types';
10
- import { join } from 'node:path';
10
+
11
11
  import { existsSync, mkdirSync } from 'node:fs';
12
12
  import JSON5 from 'json5';
13
13
  import { formatSchemaCode } from './format-schema';
@@ -48,7 +48,7 @@ interface ASTObjectExpression extends ASTNode {
48
48
  }
49
49
 
50
50
  interface ASTLiteral extends ASTNode {
51
- value: string;
51
+ value: string | number | boolean | null;
52
52
  raw?: string;
53
53
  }
54
54
 
@@ -75,7 +75,7 @@ function parseObjectExpressionToMap(expr: ASTObjectExpression): Map<string, stri
75
75
  switch (prop.value.type) {
76
76
  case 'Literal': {
77
77
  const value = prop.value as unknown as ASTLiteral;
78
- result.set(prop.key.name, value.value);
78
+ result.set(prop.key.name, String(value.value));
79
79
  break;
80
80
  }
81
81
  default: {
@@ -152,7 +152,7 @@ function getEvalId(
152
152
  name: string,
153
153
  version: string
154
154
  ): string {
155
- return `eval_${hashSHA1(projectId, deploymentId, filename, name, version)}`;
155
+ return `evalid_${hashSHA1(projectId, deploymentId, filename, name, version)}`;
156
156
  }
157
157
 
158
158
  function generateRouteId(
@@ -172,7 +172,7 @@ function generateStableAgentId(projectId: string, name: string): string {
172
172
  }
173
173
 
174
174
  function generateStableEvalId(projectId: string, agentId: string, name: string): string {
175
- return `evalid_${hashSHA1(projectId, agentId, name)}`.substring(0, 64);
175
+ return `eval_${hashSHA1(projectId, agentId, name)}`.substring(0, 64);
176
176
  }
177
177
 
178
178
  /**
@@ -255,7 +255,8 @@ function augmentAgentMetadataNode(
255
255
  }
256
256
  const name = metadata.get('name')!;
257
257
  const descriptionNode = propvalue.properties.find((x) => x.key.name === 'description')?.value;
258
- const description = descriptionNode ? (descriptionNode as ASTLiteral).value : '';
258
+ const descriptionValue = descriptionNode ? (descriptionNode as ASTLiteral).value : '';
259
+ const description = typeof descriptionValue === 'string' ? descriptionValue : '';
259
260
  const agentId = generateStableAgentId(projectId, name);
260
261
  metadata.set('version', version);
261
262
  metadata.set('filename', rel);
@@ -327,14 +328,118 @@ function createAgentMetadataNode(
327
328
 
328
329
  const DuplicateNameError = StructuredError('DuplicateNameError')<{ filename: string }>();
329
330
 
330
- export function parseEvalMetadata(
331
+ function injectEvalMetadata(
332
+ configObj: ASTObjectExpression,
333
+ evalId: string,
334
+ stableEvalId: string,
335
+ version: string,
336
+ filename: string,
337
+ agentId?: string
338
+ ): void {
339
+ // Create metadata object with eval IDs and version
340
+ const properties = [
341
+ createObjectPropertyNode('id', evalId),
342
+ createObjectPropertyNode('evalId', stableEvalId),
343
+ createObjectPropertyNode('version', version),
344
+ createObjectPropertyNode('filename', filename),
345
+ ];
346
+
347
+ // Add agentId if available
348
+ if (agentId) {
349
+ properties.push(createObjectPropertyNode('agentId', agentId));
350
+ }
351
+
352
+ const metadataObj: ASTPropertyNode = {
353
+ type: 'Property',
354
+ kind: 'init',
355
+ key: {
356
+ type: 'Identifier',
357
+ name: 'metadata',
358
+ },
359
+ value: {
360
+ type: 'ObjectExpression',
361
+ properties,
362
+ } as ASTObjectExpression,
363
+ };
364
+
365
+ // Add metadata to the config object
366
+ configObj.properties.push(metadataObj);
367
+ }
368
+
369
+ function findAgentVariableAndImport(ast: ASTProgram): { varName: string; importPath: string } | undefined {
370
+ // First, find what variable is being used in agent.createEval() calls
371
+ let agentVarName: string | undefined;
372
+
373
+ for (const node of ast.body) {
374
+ if (node.type === 'ExportNamedDeclaration') {
375
+ const exportDecl = node as {
376
+ declaration?: { type: string; declarations?: Array<ASTVariableDeclarator> };
377
+ };
378
+ if (exportDecl.declaration?.type === 'VariableDeclaration') {
379
+ const variableDeclaration = exportDecl.declaration as {
380
+ declarations: Array<ASTVariableDeclarator>;
381
+ };
382
+
383
+ for (const vardecl of variableDeclaration.declarations) {
384
+ if (vardecl.type === 'VariableDeclarator' && vardecl.init?.type === 'CallExpression') {
385
+ const call = vardecl.init as ASTCallExpression;
386
+ if (call.callee.type === 'MemberExpression') {
387
+ const memberExpr = call.callee as ASTMemberExpression;
388
+ const object = memberExpr.object as ASTNodeIdentifier;
389
+ const property = memberExpr.property as ASTNodeIdentifier;
390
+ if (
391
+ object.type === 'Identifier' &&
392
+ property.type === 'Identifier' &&
393
+ property.name === 'createEval'
394
+ ) {
395
+ agentVarName = object.name;
396
+ break;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ if (agentVarName) break;
402
+ }
403
+ }
404
+ }
405
+
406
+ if (!agentVarName) return undefined;
407
+
408
+ // Now find the import for this variable
409
+ for (const node of ast.body) {
410
+ if (node.type === 'ImportDeclaration') {
411
+ const importDecl = node as unknown as {
412
+ source: ASTLiteral;
413
+ specifiers: Array<{
414
+ type: string;
415
+ local: ASTNodeIdentifier;
416
+ }>;
417
+ };
418
+
419
+ // Find default import specifier that matches our variable
420
+ for (const spec of importDecl.specifiers) {
421
+ if (spec.type === 'ImportDefaultSpecifier' && spec.local.name === agentVarName) {
422
+ const importPath = importDecl.source.value;
423
+ if (typeof importPath === 'string') {
424
+ return { varName: agentVarName, importPath };
425
+ }
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ return undefined;
432
+ }
433
+
434
+ export async function parseEvalMetadata(
331
435
  rootDir: string,
332
436
  filename: string,
333
437
  contents: string,
334
438
  projectId: string,
335
439
  deploymentId: string,
336
- agentId?: string
337
- ): [
440
+ agentId?: string,
441
+ agentMetadata?: Map<string, Map<string, string>>
442
+ ): Promise<[
338
443
  string,
339
444
  Array<{
340
445
  filename: string;
@@ -344,7 +449,7 @@ export function parseEvalMetadata(
344
449
  evalId: string;
345
450
  description?: string;
346
451
  }>,
347
- ] {
452
+ ]> {
348
453
  const logLevel = (process.env.AGENTUITY_LOG_LEVEL || 'info') as
349
454
  | 'trace'
350
455
  | 'debug'
@@ -369,6 +474,41 @@ export function parseEvalMetadata(
369
474
  description?: string;
370
475
  }> = [];
371
476
 
477
+ // Try to find the corresponding agent to get the agentId
478
+ let resolvedAgentId = agentId;
479
+ if (!resolvedAgentId && agentMetadata) {
480
+ const agentInfo = findAgentVariableAndImport(ast);
481
+ if (agentInfo) {
482
+ logger.trace(`[EVAL METADATA] Found agent variable '${agentInfo.varName}' imported from '${agentInfo.importPath}'`);
483
+
484
+ // Resolve the import path to actual file path
485
+ let resolvedPath = agentInfo.importPath;
486
+ if (resolvedPath.startsWith('./') || resolvedPath.startsWith('../')) {
487
+ // Convert relative path to match the format in agentMetadata
488
+ const baseDir = dirname(filename);
489
+ resolvedPath = join(baseDir, resolvedPath);
490
+ // Normalize and ensure .ts extension
491
+ if (!resolvedPath.endsWith('.ts')) {
492
+ resolvedPath += '.ts';
493
+ }
494
+ }
495
+
496
+ // Find the agent metadata from the passed agentMetadata map
497
+ for (const [agentFile, metadata] of agentMetadata) {
498
+ // Check if this agent file matches the resolved import path
499
+ if (agentFile.includes(basename(resolvedPath)) && metadata.has('agentId')) {
500
+ resolvedAgentId = metadata.get('agentId');
501
+ logger.trace(`[EVAL METADATA] Resolved agentId from agent metadata: ${resolvedAgentId} (file: ${agentFile})`);
502
+ break;
503
+ }
504
+ }
505
+
506
+ if (!resolvedAgentId) {
507
+ logger.warn(`[EVAL METADATA] Could not find agent metadata for import path: ${resolvedPath}`);
508
+ }
509
+ }
510
+ }
511
+
372
512
  // Find all exported agent.createEval() calls
373
513
  for (const body of ast.body) {
374
514
  let variableDeclaration: { declarations: Array<ASTVariableDeclarator> } | undefined;
@@ -414,7 +554,7 @@ export function parseEvalMetadata(
414
554
  firstArg.type === 'Literal' &&
415
555
  typeof (firstArg as ASTLiteral).value === 'string'
416
556
  ) {
417
- evalName = (firstArg as ASTLiteral).value;
557
+ evalName = (firstArg as ASTLiteral).value as string;
418
558
  } else {
419
559
  throw new MetadataError({
420
560
  filename,
@@ -435,7 +575,9 @@ export function parseEvalMetadata(
435
575
  prop.key.name === 'description'
436
576
  ) {
437
577
  if (prop.value.type === 'Literal') {
438
- evalDescription = (prop.value as ASTLiteral).value;
578
+ const literalValue = (prop.value as ASTLiteral).value;
579
+ evalDescription =
580
+ typeof literalValue === 'string' ? literalValue : undefined;
439
581
  }
440
582
  }
441
583
  }
@@ -448,16 +590,18 @@ export function parseEvalMetadata(
448
590
  );
449
591
  const evalId = getEvalId(projectId, deploymentId, rel, finalName, version);
450
592
 
451
- // Generate stable evalId
452
- const effectiveAgentId = agentId || '';
593
+ // Generate stable evalId using resolved agentId
594
+ const effectiveAgentId = resolvedAgentId || '';
453
595
  const stableEvalId = generateStableEvalId(
454
596
  projectId,
455
597
  effectiveAgentId,
456
598
  finalName
457
599
  );
458
600
 
459
- // Note: We no longer inject metadata into the AST since there's no metadata object
460
- // The runtime will generate IDs from the name parameter
601
+ // Inject eval metadata into the AST (same pattern as agents)
602
+ if (configObj) {
603
+ injectEvalMetadata(configObj, evalId, stableEvalId, version, rel, resolvedAgentId);
604
+ }
461
605
 
462
606
  evals.push({
463
607
  filename: rel,
@@ -737,13 +881,14 @@ export async function parseAgentMetadata(
737
881
  const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
738
882
  const evalsContents = transpiler.transformSync(evalsSource);
739
883
  const agentId = result[1].get('agentId') || '';
740
- const [, evals] = parseEvalMetadata(
884
+ const [, evals] = await parseEvalMetadata(
741
885
  rootDir,
742
886
  evalsPath,
743
887
  evalsContents,
744
888
  projectId,
745
889
  deploymentId,
746
- agentId
890
+ agentId,
891
+ new Map() // Empty map since we already have agentId
747
892
  );
748
893
  if (evals.length > 0) {
749
894
  logger.trace(`Adding ${evals.length} eval(s) to agent metadata for ${name}`);
@@ -777,6 +922,7 @@ interface ValidatorInfo {
777
922
  agentVariable?: string;
778
923
  inputSchemaVariable?: string;
779
924
  outputSchemaVariable?: string;
925
+ stream?: boolean;
780
926
  }
781
927
 
782
928
  function hasValidatorCall(args: unknown[]): ValidatorInfo {
@@ -826,14 +972,16 @@ function hasValidatorCall(args: unknown[]): ValidatorInfo {
826
972
  }
827
973
 
828
974
  /**
829
- * Extract schema variable names from validator() call arguments
830
- * Example: validator({ input: myInputSchema, output: myOutputSchema })
975
+ * Extract schema variable names and stream flag from validator() call arguments
976
+ * Example: validator({ input: myInputSchema, output: myOutputSchema, stream: true })
831
977
  */
832
978
  function extractValidatorSchemas(callExpr: ASTCallExpression): {
833
979
  inputSchemaVariable?: string;
834
980
  outputSchemaVariable?: string;
981
+ stream?: boolean;
835
982
  } {
836
- const result: { inputSchemaVariable?: string; outputSchemaVariable?: string } = {};
983
+ const result: { inputSchemaVariable?: string; outputSchemaVariable?: string; stream?: boolean } =
984
+ {};
837
985
 
838
986
  // Check if validator has arguments
839
987
  if (!callExpr.arguments || callExpr.arguments.length === 0) {
@@ -848,7 +996,17 @@ function extractValidatorSchemas(callExpr: ASTCallExpression): {
848
996
 
849
997
  const objExpr = firstArg as ASTObjectExpression;
850
998
  for (const prop of objExpr.properties) {
851
- const keyName = prop.key.name;
999
+ // Extract key name defensively - could be Identifier or Literal
1000
+ let keyName: string | undefined;
1001
+ const propKey = prop.key as { type: string; name?: string; value?: unknown };
1002
+ if (propKey.type === 'Identifier') {
1003
+ keyName = propKey.name;
1004
+ } else if (propKey.type === 'Literal') {
1005
+ keyName = String(propKey.value);
1006
+ }
1007
+
1008
+ if (!keyName) continue;
1009
+
852
1010
  if ((keyName === 'input' || keyName === 'output') && prop.value.type === 'Identifier') {
853
1011
  const valueName = (prop.value as ASTNodeIdentifier).name;
854
1012
  if (keyName === 'input') {
@@ -857,6 +1015,46 @@ function extractValidatorSchemas(callExpr: ASTCallExpression): {
857
1015
  result.outputSchemaVariable = valueName;
858
1016
  }
859
1017
  }
1018
+ // Extract stream flag - can be Literal, Identifier, or UnaryExpression (!0 or !1)
1019
+ if (keyName === 'stream') {
1020
+ if (prop.value.type === 'Literal') {
1021
+ const literal = prop.value as ASTLiteral;
1022
+ if (typeof literal.value === 'boolean') {
1023
+ result.stream = literal.value;
1024
+ }
1025
+ } else if (prop.value.type === 'Identifier') {
1026
+ const identifier = prop.value as ASTNodeIdentifier;
1027
+ // Handle stream: true or stream: false as identifiers
1028
+ if (identifier.name === 'true') {
1029
+ result.stream = true;
1030
+ } else if (identifier.name === 'false') {
1031
+ result.stream = false;
1032
+ }
1033
+ } else if (prop.value.type === 'UnaryExpression') {
1034
+ // Handle !0 (true) or !1 (false) - acorn-loose transpiles booleans this way
1035
+ const unary = prop.value as { type: string; operator?: string; argument?: ASTNode };
1036
+ if (unary.argument?.type === 'Literal') {
1037
+ const literal = unary.argument as ASTLiteral;
1038
+ // Numeric literal: !0 = true, !1 = false
1039
+ if (typeof literal.value === 'number') {
1040
+ if (unary.operator === '!') {
1041
+ result.stream = literal.value === 0;
1042
+ }
1043
+ } else if (typeof literal.value === 'boolean') {
1044
+ result.stream = unary.operator === '!' ? !literal.value : literal.value;
1045
+ }
1046
+ }
1047
+ // Handle true/false as identifiers
1048
+ if (unary.argument?.type === 'Identifier') {
1049
+ const identifier = unary.argument as ASTNodeIdentifier;
1050
+ if (identifier.name === 'true') {
1051
+ result.stream = unary.operator === '!' ? false : true;
1052
+ } else if (identifier.name === 'false') {
1053
+ result.stream = unary.operator === '!' ? true : false;
1054
+ }
1055
+ }
1056
+ }
1057
+ }
860
1058
  }
861
1059
 
862
1060
  return result;
@@ -883,7 +1081,7 @@ function extractZValidatorSchema(callExpr: ASTCallExpression): {
883
1081
  if (targetArg.type === 'Literal') {
884
1082
  const targetValue = (targetArg as ASTLiteral).value;
885
1083
  // Only extract schemas for JSON body validation
886
- if (targetValue !== 'json') {
1084
+ if (typeof targetValue === 'string' && targetValue !== 'json') {
887
1085
  return result;
888
1086
  }
889
1087
  } else {
@@ -1051,7 +1249,7 @@ export async function parseRoute(
1051
1249
  case 'patch':
1052
1250
  case 'delete': {
1053
1251
  if (action && (action as ASTLiteral).type === 'Literal') {
1054
- suffix = (action as ASTLiteral).value;
1252
+ suffix = String((action as ASTLiteral).value);
1055
1253
  } else {
1056
1254
  throw new InvalidRouterConfigError({
1057
1255
  filename,
@@ -1068,7 +1266,7 @@ export async function parseRoute(
1068
1266
  method = 'post';
1069
1267
  const theaction = action as ASTLiteral;
1070
1268
  if (theaction.type === 'Literal') {
1071
- suffix = theaction.value;
1269
+ suffix = String(theaction.value);
1072
1270
  break;
1073
1271
  }
1074
1272
  break;
@@ -1088,7 +1286,7 @@ export async function parseRoute(
1088
1286
  const number = theaction.properties.find((p) => p.key.name === 'number');
1089
1287
  if (number && number.value.type === 'Literal') {
1090
1288
  const phoneNumber = number.value as ASTLiteral;
1091
- suffix = hash(phoneNumber.value);
1289
+ suffix = hash(String(phoneNumber.value));
1092
1290
  break;
1093
1291
  }
1094
1292
  }
@@ -1099,7 +1297,7 @@ export async function parseRoute(
1099
1297
  method = 'post';
1100
1298
  const theaction = action as ASTLiteral;
1101
1299
  if (theaction.type === 'Literal') {
1102
- const email = theaction.value;
1300
+ const email = String(theaction.value);
1103
1301
  suffix = hash(email);
1104
1302
  break;
1105
1303
  }
@@ -1110,7 +1308,7 @@ export async function parseRoute(
1110
1308
  method = 'post';
1111
1309
  const theaction = action as ASTLiteral;
1112
1310
  if (theaction.type === 'Literal') {
1113
- const expression = theaction.value;
1311
+ const expression = String(theaction.value);
1114
1312
  try {
1115
1313
  parseCronExpression(expression, { hasSeconds: false });
1116
1314
  } catch (ex) {
@@ -1169,6 +1367,9 @@ export async function parseRoute(
1169
1367
  if (validatorInfo.outputSchemaVariable) {
1170
1368
  routeConfig.outputSchemaVariable = validatorInfo.outputSchemaVariable;
1171
1369
  }
1370
+ if (validatorInfo.stream !== undefined) {
1371
+ routeConfig.stream = validatorInfo.stream;
1372
+ }
1172
1373
  }
1173
1374
 
1174
1375
  routes.push({
@@ -305,10 +305,12 @@ export async function bundle({
305
305
  format: 'esm',
306
306
  banner: `// Generated file. DO NOT EDIT`,
307
307
  // Disable minify for server bundle (keep code readable for debugging)
308
- // Enable splitting to reduce bundle size by extracting common code
309
- minify: false,
308
+ minify: !dev,
310
309
  drop: isProd ? ['debugger'] : undefined,
311
- splitting: true,
310
+ // Disable splitting - causes module initialization issues with externalized packages
311
+ // The chunk helper functions (__commonJS, __esm, etc.) don't properly handle
312
+ // CommonJS packages in node_modules that require() other modules
313
+ splitting: false,
312
314
  conditions: [isProd ? 'production' : 'development', 'bun'],
313
315
  external,
314
316
  naming: {
@@ -322,9 +324,6 @@ export async function bundle({
322
324
  if (!buildResult.success) {
323
325
  handleBuildFailure(buildResult);
324
326
  }
325
- // Fix duplicate exports caused by Bun splitting bug
326
- // See: https://github.com/oven-sh/bun/issues/5344
327
- await fixDuplicateExportsInDirectory(outDir, false);
328
327
  })();
329
328
 
330
329
  const buildmetadata = getBuildMetadata();
@@ -486,19 +485,19 @@ export async function bundle({
486
485
  await Bun.write(workbenchIndexFile, generateWorkbenchIndexHtml());
487
486
 
488
487
  // Bundle workbench using generated files
489
- // NOTE: Don't set 'root' to tempWorkbenchDir because it breaks module resolution
490
- // Bun needs to resolve @agentuity/* packages from the project's node_modules
488
+ // Use same strategy as web bundle - fully bundle all dependencies to avoid cross-bundle chunk conflicts
491
489
  const workbenchBuildConfig: Bun.BuildConfig = {
492
490
  entrypoints: [workbenchIndexFile],
493
491
  outdir: join(outDir, 'workbench'),
494
492
  sourcemap: dev ? 'inline' : 'linked',
495
- plugins: [AgentuityBundler], // i dont think we need this plugin here
496
493
  target: 'browser',
497
494
  format: 'esm',
498
495
  banner: `// Generated file. DO NOT EDIT`,
499
- minify: !dev, // Disable minification in dev to avoid module resolution issues
500
- splitting: !dev, // Disable code splitting in dev to avoid relative import resolution issues
496
+ minify: true,
497
+ drop: isProd ? ['debugger'] : undefined,
498
+ splitting: true,
501
499
  packages: 'bundle',
500
+ conditions: ['browser', 'import', 'default'],
502
501
  naming: {
503
502
  entry: '[dir]/[name].[ext]',
504
503
  chunk: 'workbench/chunk/[name]-[hash].[ext]',
@@ -236,38 +236,55 @@ const AgentuityBundler: BunPlugin = {
236
236
  let newsource = await Bun.file(args.path).text();
237
237
  if (args.path.startsWith(srcDir)) {
238
238
  const contents = transpiler.transformSync(newsource);
239
- const result = await parseAgentMetadata(
240
- rootDir,
241
- args.path,
242
- contents,
243
- projectId,
244
- deploymentId
245
- );
246
239
 
247
- // Skip files that don't have a createAgent export
248
- if (result === undefined) {
249
- return {
250
- contents: newsource,
251
- loader: 'ts',
252
- };
253
- }
240
+ // Check if this is an eval file (eval.ts)
241
+ if (args.path.endsWith('/eval.ts')) {
242
+ // parseEvalMetadata will find the agent from the import statement
243
+ const [ns] = await parseEvalMetadata(
244
+ rootDir,
245
+ args.path,
246
+ contents,
247
+ projectId,
248
+ deploymentId,
249
+ undefined, // No agentId - will be resolved from import
250
+ agentMetadata
251
+ );
252
+ newsource = ns;
253
+ } else {
254
+ // Handle regular agent files
255
+ const result = await parseAgentMetadata(
256
+ rootDir,
257
+ args.path,
258
+ contents,
259
+ projectId,
260
+ deploymentId
261
+ );
262
+
263
+ // Skip files that don't have a createAgent export
264
+ if (result === undefined) {
265
+ return {
266
+ contents: newsource,
267
+ loader: 'ts',
268
+ };
269
+ }
254
270
 
255
- const [ns, md] = result;
256
- newsource = ns;
257
-
258
- // Only process files that actually export an agent
259
- if (md.has('name')) {
260
- const newAgentName = md.get('name');
261
- for (const [, kv] of agentMetadata) {
262
- const found = kv.get('name');
263
- if (newAgentName === found) {
264
- throw new AgentNameDuplicateError({
265
- message: `The agent in ${kv.get('filename')} and the agent in ${md.get('filename')} have the same name (${found}). Agent Names must be unique within a project.`,
266
- });
271
+ const [ns, md] = result;
272
+ newsource = ns;
273
+
274
+ // Only process files that actually export an agent
275
+ if (md.has('name')) {
276
+ const newAgentName = md.get('name');
277
+ for (const [, kv] of agentMetadata) {
278
+ const found = kv.get('name');
279
+ if (newAgentName === found) {
280
+ throw new AgentNameDuplicateError({
281
+ message: `The agent in ${kv.get('filename')} and the agent in ${md.get('filename')} have the same name (${found}). Agent Names must be unique within a project.`,
282
+ });
283
+ }
267
284
  }
268
- }
269
285
 
270
- agentMetadata.set(md.get('name')!, md);
286
+ agentMetadata.set(md.get('name')!, md);
287
+ }
271
288
  }
272
289
  }
273
290
  return {
@@ -276,19 +293,6 @@ const AgentuityBundler: BunPlugin = {
276
293
  };
277
294
  });
278
295
 
279
- build.onLoad({ filter: /\/eval\.ts$/, namespace: 'file' }, async (args) => {
280
- let newsource = await Bun.file(args.path).text();
281
- if (args.path.startsWith(srcDir)) {
282
- const contents = transpiler.transformSync(newsource);
283
- const [ns] = parseEvalMetadata(rootDir, args.path, contents, projectId, deploymentId);
284
- newsource = ns;
285
- }
286
- return {
287
- contents: newsource,
288
- loader: 'ts',
289
- };
290
- });
291
-
292
296
  const patches = generatePatches();
293
297
  for (const [, patch] of patches) {
294
298
  let modulePath = join('node_modules', patch.module, '.*');
@@ -25,6 +25,8 @@ export interface RouteInfo {
25
25
  inputSchemaVariable?: string;
26
26
  /** Output schema variable name if using validator({ output }) */
27
27
  outputSchemaVariable?: string;
28
+ /** Whether this route is a streaming route (from validator({ stream: true })) */
29
+ stream?: boolean;
28
30
  }
29
31
 
30
32
  /**
@@ -38,7 +40,8 @@ export interface RouteInfo {
38
40
  */
39
41
  export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void {
40
42
  // Filter routes by type (include ALL routes, not just those with validators)
41
- const apiRoutes = routes.filter((r) => r.routeType === 'api');
43
+ // Note: 'stream' routes are HTTP routes that return ReadableStream, so include them with API routes
44
+ const apiRoutes = routes.filter((r) => r.routeType === 'api' || r.routeType === 'stream');
42
45
  const websocketRoutes = routes.filter((r) => r.routeType === 'websocket');
43
46
  const sseRoutes = routes.filter((r) => r.routeType === 'sse');
44
47
 
@@ -142,6 +145,7 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
142
145
  return ` '${routeKey}': {
143
146
  inputSchema: typeof ${importName} extends { inputSchema?: infer I } ? I : never;
144
147
  outputSchema: typeof ${importName} extends { outputSchema?: infer O } ? O : never;
148
+ stream: typeof ${importName} extends { stream?: infer S } ? S : false;
145
149
  };`;
146
150
  }
147
151
 
@@ -153,9 +157,11 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
153
157
  const outputType = route.outputSchemaVariable
154
158
  ? `typeof ${route.outputSchemaVariable}`
155
159
  : 'never';
160
+ const streamValue = route.stream === true ? 'true' : 'false';
156
161
  return ` '${routeKey}': {
157
162
  inputSchema: ${inputType};
158
163
  outputSchema: ${outputType};
164
+ stream: ${streamValue};
159
165
  };`;
160
166
  }
161
167
 
@@ -27,6 +27,7 @@ import {
27
27
  type DeploymentInstructions,
28
28
  type DeploymentComplete,
29
29
  type DeploymentStatusResult,
30
+ getAppBaseURL,
30
31
  } from '@agentuity/server';
31
32
  import {
32
33
  findEnvFile,
@@ -51,6 +52,7 @@ const DeployResponseSchema = z.object({
51
52
  deployment: z.string().describe('Deployment-specific URL'),
52
53
  latest: z.string().describe('Latest/active deployment URL'),
53
54
  custom: z.array(z.string()).optional().describe('Custom domain URLs'),
55
+ dashboard: z.string().describe('The dashboard URL for the deployment'),
54
56
  })
55
57
  .optional()
56
58
  .describe('Deployment URLs'),
@@ -551,9 +553,12 @@ export const deploySubcommand = createSubcommand({
551
553
  tui.success('Your project was deployed!');
552
554
  }
553
555
 
556
+ const appUrl = getAppBaseURL(config?.name, config?.overrides);
557
+
558
+ const dashboard = `${appUrl}/r/${deployment.id}`;
559
+
554
560
  // Show deployment URLs
555
561
  if (complete?.publicUrls) {
556
- tui.arrow(tui.bold(tui.padRight('Deployment ID:', 17)) + tui.link(deployment.id));
557
562
  if (complete.publicUrls.custom?.length) {
558
563
  for (const url of complete.publicUrls.custom) {
559
564
  tui.arrow(tui.bold(tui.padRight('Deployment URL:', 17)) + tui.link(url));
@@ -566,6 +571,7 @@ export const deploySubcommand = createSubcommand({
566
571
  tui.arrow(
567
572
  tui.bold(tui.padRight('Project URL:', 17)) + tui.link(complete.publicUrls.latest)
568
573
  );
574
+ tui.arrow(tui.bold(tui.padRight('Dashboard URL:', 17)) + tui.link(dashboard));
569
575
  }
570
576
  }
571
577
 
@@ -579,6 +585,7 @@ export const deploySubcommand = createSubcommand({
579
585
  deployment: complete.publicUrls.deployment,
580
586
  latest: complete.publicUrls.latest,
581
587
  custom: complete.publicUrls.custom,
588
+ dashboard,
582
589
  }
583
590
  : undefined,
584
591
  };