@arcteninc/core 0.0.21 → 0.0.22

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 (62) hide show
  1. package/dist/components/ArctenAgent.d.ts +25 -0
  2. package/dist/components/ArctenAgent.d.ts.map +1 -1
  3. package/dist/components/ai-elements/prompt-input.d.ts +187 -0
  4. package/dist/components/ai-elements/prompt-input.d.ts.map +1 -0
  5. package/dist/components/ai-elements/reasoning.d.ts +17 -0
  6. package/dist/components/ai-elements/reasoning.d.ts.map +1 -0
  7. package/dist/components/ai-elements/response.d.ts +8 -0
  8. package/dist/components/ai-elements/response.d.ts.map +1 -0
  9. package/dist/components/ai-elements/shimmer.d.ts +10 -0
  10. package/dist/components/ai-elements/shimmer.d.ts.map +1 -0
  11. package/dist/components/tool-call-approval.d.ts +9 -0
  12. package/dist/components/tool-call-approval.d.ts.map +1 -0
  13. package/dist/components/tool-call-result.d.ts +8 -0
  14. package/dist/components/tool-call-result.d.ts.map +1 -0
  15. package/dist/components/ui/autotextarea.d.ts +19 -0
  16. package/dist/components/ui/autotextarea.d.ts.map +1 -0
  17. package/dist/components/ui/badge.d.ts +10 -0
  18. package/dist/components/ui/badge.d.ts.map +1 -0
  19. package/dist/components/ui/button.d.ts +14 -0
  20. package/dist/components/ui/button.d.ts.map +1 -0
  21. package/dist/components/ui/collapsible.d.ts +6 -0
  22. package/dist/components/ui/collapsible.d.ts.map +1 -0
  23. package/dist/components/ui/command.d.ts +19 -0
  24. package/dist/components/ui/command.d.ts.map +1 -0
  25. package/dist/components/ui/dialog.d.ts +16 -0
  26. package/dist/components/ui/dialog.d.ts.map +1 -0
  27. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  28. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  29. package/dist/components/ui/hover-card.d.ts +7 -0
  30. package/dist/components/ui/hover-card.d.ts.map +1 -0
  31. package/dist/components/ui/input-group.d.ts +17 -0
  32. package/dist/components/ui/input-group.d.ts.map +1 -0
  33. package/dist/components/ui/input.d.ts +4 -0
  34. package/dist/components/ui/input.d.ts.map +1 -0
  35. package/dist/components/ui/kbd.d.ts +4 -0
  36. package/dist/components/ui/kbd.d.ts.map +1 -0
  37. package/dist/components/ui/select.d.ts +16 -0
  38. package/dist/components/ui/select.d.ts.map +1 -0
  39. package/dist/components/ui/textarea.d.ts +4 -0
  40. package/dist/components/ui/textarea.d.ts.map +1 -0
  41. package/dist/components/ui/tooltip.d.ts +8 -0
  42. package/dist/components/ui/tooltip.d.ts.map +1 -0
  43. package/dist/index.cjs +20 -7
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.mjs +2361 -2537
  47. package/dist/lib/useAgent.d.ts +3 -0
  48. package/dist/lib/useAgent.d.ts.map +1 -0
  49. package/dist/lib/utils.d.ts +3 -0
  50. package/dist/lib/utils.d.ts.map +1 -0
  51. package/dist/server.d.ts +9 -0
  52. package/dist/server.d.ts.map +1 -0
  53. package/dist/types/tool-description.d.ts +18 -0
  54. package/dist/types/tool-description.d.ts.map +1 -0
  55. package/dist/types/use-agent.d.ts +73 -0
  56. package/dist/types/use-agent.d.ts.map +1 -0
  57. package/dist/utils/extract-tool-metadata.d.ts +26 -0
  58. package/dist/utils/extract-tool-metadata.d.ts.map +1 -0
  59. package/dist/verifyToken.d.ts +19 -0
  60. package/dist/verifyToken.d.ts.map +1 -0
  61. package/package.json +7 -4
  62. package/scripts/cli-extract-types-auto.ts +598 -541
@@ -1,542 +1,599 @@
1
1
  #!/usr/bin/env bun
2
- /**
3
- * Automatically discover and extract types for tools used in ArctenAgent/useAgent
4
- * Scans the project, finds all tool usages, and generates metadata only for used tools
5
- */
6
-
7
- import * as ts from 'typescript';
8
- import * as fs from 'fs';
9
- import * as path from 'path';
10
- import { glob } from 'glob';
11
-
12
- interface TypeMetadata {
13
- type: string;
14
- description?: string;
15
- required: boolean;
16
- default?: string;
17
- values?: string[];
18
- arrayItemType?: TypeMetadata;
19
- properties?: Record<string, TypeMetadata>;
20
- }
21
-
22
- interface FunctionMetadata {
23
- name: string;
24
- description: string;
25
- parameters: Record<string, TypeMetadata>;
26
- returnType?: string;
27
- isAsync?: boolean;
28
- }
29
-
30
- interface GeneratedMetadata {
31
- generated: string;
32
- discoveredFrom: string[];
33
- functions: Record<string, FunctionMetadata>;
34
- }
35
-
36
- interface ToolUsage {
37
- toolNames: Set<string>;
38
- file: string;
39
- component: 'ArctenAgent' | 'useAgent';
40
- }
41
-
42
- /**
43
- * Find all files that use ArctenAgent or useAgent
44
- */
45
- async function findToolUsageFiles(projectRoot: string): Promise<string[]> {
46
- const pattern = path.join(projectRoot, '**/*.{ts,tsx}').replace(/\\/g, '/');
47
- const files = await glob(pattern, {
48
- ignore: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'],
49
- });
50
-
51
- console.log(`šŸ“‚ Found ${files.length} TypeScript files to scan`);
52
- return files;
53
- }
54
-
55
- /**
56
- * Extract tool names from ArctenAgent or useAgent usage
57
- */
58
- function extractToolNamesFromFile(sourceFile: ts.SourceFile): ToolUsage[] {
59
- const usages: ToolUsage[] = [];
60
-
61
- function visit(node: ts.Node) {
62
- // Check for <ArctenAgent tools={[...]} safeTools={[...]} />
63
- if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
64
- const tagName = ts.isJsxElement(node)
65
- ? node.openingElement.tagName.getText(sourceFile)
66
- : node.tagName.getText(sourceFile);
67
-
68
- if (tagName === 'ArctenAgent') {
69
- const toolNames = new Set<string>();
70
- const attributes = ts.isJsxElement(node)
71
- ? node.openingElement.attributes.properties
72
- : node.attributes.properties;
73
-
74
- for (const attr of attributes) {
75
- if (ts.isJsxAttribute(attr)) {
76
- const attrName = attr.name.getText(sourceFile);
77
- if (attrName === 'tools' || attrName === 'safeTools') {
78
- if (attr.initializer && ts.isJsxExpression(attr.initializer)) {
79
- const expr = attr.initializer.expression;
80
- if (expr && ts.isArrayLiteralExpression(expr)) {
81
- for (const element of expr.elements) {
82
- const name = element.getText(sourceFile).trim();
83
- // Remove any object property access (e.g., tools.getOrders -> getOrders)
84
- const simpleName = name.split('.').pop() || name;
85
- toolNames.add(simpleName);
86
- }
87
- }
88
- }
89
- }
90
- }
91
- }
92
-
93
- if (toolNames.size > 0) {
94
- usages.push({
95
- toolNames,
96
- file: sourceFile.fileName,
97
- component: 'ArctenAgent',
98
- });
99
- }
100
- }
101
- }
102
-
103
- // Check for useAgent({ tools: [...], safeTools: [...] })
104
- if (ts.isCallExpression(node)) {
105
- const expr = node.expression;
106
- if (ts.isIdentifier(expr) && expr.getText(sourceFile) === 'useAgent') {
107
- const toolNames = new Set<string>();
108
-
109
- if (node.arguments.length > 0) {
110
- const arg = node.arguments[0];
111
- if (ts.isObjectLiteralExpression(arg)) {
112
- for (const prop of arg.properties) {
113
- if (ts.isPropertyAssignment(prop)) {
114
- const propName = prop.name.getText(sourceFile);
115
- if (propName === 'tools' || propName === 'safeTools') {
116
- if (ts.isArrayLiteralExpression(prop.initializer)) {
117
- for (const element of prop.initializer.elements) {
118
- const name = element.getText(sourceFile).trim();
119
- const simpleName = name.split('.').pop() || name;
120
- toolNames.add(simpleName);
121
- }
122
- }
123
- }
124
- }
125
- }
126
- }
127
- }
128
-
129
- if (toolNames.size > 0) {
130
- usages.push({
131
- toolNames,
132
- file: sourceFile.fileName,
133
- component: 'useAgent',
134
- });
135
- }
136
- }
137
- }
138
-
139
- ts.forEachChild(node, visit);
140
- }
141
-
142
- visit(sourceFile);
143
- return usages;
144
- }
145
-
146
- /**
147
- * Find where a tool function is defined
148
- */
149
- function findFunctionDefinition(
150
- functionName: string,
151
- sourceFile: ts.SourceFile,
152
- program: ts.Program
153
- ): { sourceFile: ts.SourceFile; node: ts.FunctionDeclaration } | null {
154
- // First, check if it's defined in the current file
155
- let foundNode: ts.FunctionDeclaration | null = null;
156
-
157
- function visitForDefinition(node: ts.Node) {
158
- if (ts.isFunctionDeclaration(node) && node.name) {
159
- if (node.name.getText(sourceFile) === functionName) {
160
- foundNode = node;
161
- }
162
- }
163
- if (!foundNode) {
164
- ts.forEachChild(node, visitForDefinition);
165
- }
166
- }
167
-
168
- visitForDefinition(sourceFile);
169
-
170
- if (foundNode) {
171
- return { sourceFile, node: foundNode };
172
- }
173
-
174
- // If not found, check imports
175
- const imports: { name: string; from: string }[] = [];
176
-
177
- function visitForImports(node: ts.Node) {
178
- if (ts.isImportDeclaration(node)) {
179
- const moduleSpecifier = node.moduleClause;
180
- if (moduleSpecifier && ts.isImportClause(moduleSpecifier)) {
181
- const namedBindings = moduleSpecifier.namedBindings;
182
- if (namedBindings && ts.isNamedImports(namedBindings)) {
183
- for (const element of namedBindings.elements) {
184
- const importedName = element.name.getText(sourceFile);
185
- if (importedName === functionName) {
186
- const modulePath = (node.moduleSpecifier as ts.StringLiteral).text;
187
- imports.push({ name: importedName, from: modulePath });
188
- }
189
- }
190
- }
191
- }
192
- }
193
- ts.forEachChild(node, visitForImports);
194
- }
195
-
196
- visitForImports(sourceFile);
197
-
198
- // Follow imports to find definition
199
- for (const imp of imports) {
200
- const resolvedPath = resolveImportPath(imp.from, sourceFile.fileName);
201
- if (resolvedPath) {
202
- const importedSourceFile = program.getSourceFile(resolvedPath);
203
- if (importedSourceFile) {
204
- const result = findFunctionDefinition(functionName, importedSourceFile, program);
205
- if (result) {
206
- return result;
207
- }
208
- }
209
- }
210
- }
211
-
212
- return null;
213
- }
214
-
215
- /**
216
- * Resolve import path to actual file path
217
- */
218
- function resolveImportPath(importPath: string, fromFile: string): string | null {
219
- if (importPath.startsWith('.')) {
220
- // Relative import
221
- const dir = path.dirname(fromFile);
222
- const resolved = path.resolve(dir, importPath);
223
-
224
- // Try common extensions
225
- const extensions = ['.ts', '.tsx', '.js', '.jsx'];
226
- for (const ext of extensions) {
227
- const withExt = resolved + ext;
228
- if (fs.existsSync(withExt)) {
229
- return withExt;
230
- }
231
- }
232
-
233
- // Try index files
234
- for (const ext of extensions) {
235
- const indexPath = path.join(resolved, `index${ext}`);
236
- if (fs.existsSync(indexPath)) {
237
- return indexPath;
238
- }
239
- }
240
-
241
- return resolved;
242
- }
243
-
244
- // Node modules - we'll skip for now
245
- return null;
246
- }
247
-
248
- /**
249
- * Serialize a TypeScript type (same as previous implementation)
250
- */
251
- function serializeType(
252
- type: ts.Type,
253
- checker: ts.TypeChecker,
254
- visited = new Set<number>(),
255
- depth = 0
256
- ): TypeMetadata {
257
- const typeString = checker.typeToString(type);
258
-
259
- if (depth > 10) {
260
- return { type: 'any', required: true };
261
- }
262
-
263
- const typeId = (type as any).id;
264
- if (typeId !== undefined && visited.has(typeId)) {
265
- return { type: 'any', required: true };
266
- }
267
-
268
- if (type.flags & ts.TypeFlags.String) {
269
- return { type: 'string', required: true };
270
- }
271
- if (type.flags & ts.TypeFlags.Number) {
272
- return { type: 'number', required: true };
273
- }
274
- if (type.flags & ts.TypeFlags.Boolean) {
275
- return { type: 'boolean', required: true };
276
- }
277
- if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
278
- return { type: 'undefined', required: false };
279
- }
280
-
281
- if (type.isUnion()) {
282
- const types = type.types;
283
- const stringLiterals = types
284
- .filter(t => t.flags & ts.TypeFlags.StringLiteral)
285
- .map(t => (t as ts.StringLiteralType).value);
286
-
287
- if (stringLiterals.length === types.length) {
288
- return { type: 'enum', values: stringLiterals, required: true };
289
- }
290
-
291
- const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
292
- const nonUndefinedTypes = types.filter(t => !(t.flags & ts.TypeFlags.Undefined));
293
-
294
- if (hasUndefined && nonUndefinedTypes.length === 1) {
295
- const metadata = serializeType(nonUndefinedTypes[0], checker, visited, depth + 1);
296
- metadata.required = false;
297
- return metadata;
298
- }
299
-
300
- return {
301
- type: types.map(t => checker.typeToString(t)).join(' | '),
302
- required: true,
303
- };
304
- }
305
-
306
- if (checker.isArrayType(type)) {
307
- const typeArgs = (type as ts.TypeReference).typeArguments;
308
- if (typeArgs && typeArgs.length > 0) {
309
- return {
310
- type: 'array',
311
- arrayItemType: serializeType(typeArgs[0], checker, visited, depth + 1),
312
- required: true,
313
- };
314
- }
315
- return { type: 'array', required: true };
316
- }
317
-
318
- if (type.flags & ts.TypeFlags.Object) {
319
- if (typeId !== undefined) {
320
- visited.add(typeId);
321
- }
322
-
323
- const properties: Record<string, TypeMetadata> = {};
324
- const props = checker.getPropertiesOfType(type);
325
-
326
- if (props.length > 0) {
327
- for (const prop of props) {
328
- const propDeclaration = prop.valueDeclaration || prop.declarations?.[0];
329
-
330
- if (propDeclaration) {
331
- const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
332
- const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
333
-
334
- const propMetadata = serializeType(propType, checker, visited, depth + 1);
335
- propMetadata.required = !isOptional;
336
-
337
- properties[prop.name] = propMetadata;
338
- }
339
- }
340
-
341
- return { type: 'object', properties, required: true };
342
- }
343
- }
344
-
345
- return {
346
- type: typeString.length < 100 ? typeString : 'any',
347
- required: true,
348
- };
349
- }
350
-
351
- /**
352
- * Extract metadata from a function
353
- */
354
- function extractFunctionMetadata(
355
- node: ts.FunctionDeclaration,
356
- checker: ts.TypeChecker,
357
- sourceFile: ts.SourceFile
358
- ): FunctionMetadata | null {
359
- if (!node.name) {
360
- return null;
361
- }
362
-
363
- const functionName = node.name.getText(sourceFile);
364
-
365
- const jsDocTags = ts.getJSDocTags(node);
366
- const jsDocComments = ts.getJSDocCommentsAndTags(node);
367
-
368
- let description = `Execute ${functionName}`;
369
- for (const comment of jsDocComments) {
370
- if (ts.isJSDoc(comment) && comment.comment) {
371
- if (typeof comment.comment === 'string') {
372
- description = comment.comment.trim();
373
- }
374
- break;
375
- }
376
- }
377
-
378
- const paramDescriptions = new Map<string, string>();
379
- for (const tag of jsDocTags) {
380
- if (tag.tagName.text === 'param' && ts.isJSDocParameterTag(tag)) {
381
- const paramName = tag.name?.getText(sourceFile);
382
- const paramDesc = typeof tag.comment === 'string' ? tag.comment.trim() : '';
383
- if (paramName && paramDesc) {
384
- paramDescriptions.set(paramName, paramDesc);
385
- }
386
- }
387
- }
388
-
389
- const parameters: Record<string, TypeMetadata> = {};
390
- const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
391
-
392
- for (const param of node.parameters) {
393
- const paramName = param.name.getText(sourceFile);
394
- const hasDefault = param.initializer !== undefined;
395
-
396
- let typeMetadata: TypeMetadata;
397
- if (param.type) {
398
- const type = checker.getTypeFromTypeNode(param.type);
399
- typeMetadata = serializeType(type, checker);
400
- } else {
401
- typeMetadata = { type: 'any', required: true };
402
- }
403
-
404
- const isOptional = param.questionToken !== undefined || hasDefault;
405
- if (isOptional) {
406
- typeMetadata.required = false;
407
- }
408
-
409
- if (hasDefault) {
410
- typeMetadata.default = param.initializer!.getText(sourceFile);
411
- }
412
-
413
- if (paramDescriptions.has(paramName)) {
414
- typeMetadata.description = paramDescriptions.get(paramName);
415
- }
416
-
417
- parameters[paramName] = typeMetadata;
418
- }
419
-
420
- let returnType = 'any';
421
- if (node.type) {
422
- returnType = node.type.getText(sourceFile);
423
- }
424
-
425
- return {
426
- name: functionName,
427
- description,
428
- parameters,
429
- returnType,
430
- isAsync,
431
- };
432
- }
433
-
434
- /**
435
- * Main function
436
- */
437
- async function autoDiscoverAndExtract(projectRoot: string, outputPath: string) {
438
- console.log(`\nšŸ” Auto-discovering tools in: ${projectRoot}\n`);
439
-
440
- // Find all TypeScript files
441
- const files = await findToolUsageFiles(projectRoot);
442
-
443
- // Read tsconfig
444
- const configPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, 'tsconfig.json');
445
- let compilerOptions: ts.CompilerOptions = {
446
- target: ts.ScriptTarget.ESNext,
447
- module: ts.ModuleKind.ESNext,
448
- jsx: ts.JsxEmit.React,
449
- };
450
-
451
- if (configPath) {
452
- const config = ts.readConfigFile(configPath, ts.sys.readFile);
453
- const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
454
- compilerOptions = parsed.options;
455
- }
456
-
457
- // Create program with all files
458
- const program = ts.createProgram(files, compilerOptions);
459
- const checker = program.getTypeChecker();
460
-
461
- // Scan for tool usages
462
- const allToolUsages: ToolUsage[] = [];
463
- const allToolNames = new Set<string>();
464
-
465
- for (const file of files) {
466
- const sourceFile = program.getSourceFile(file);
467
- if (sourceFile) {
468
- const usages = extractToolNamesFromFile(sourceFile);
469
- if (usages.length > 0) {
470
- allToolUsages.push(...usages);
471
- usages.forEach(u => u.toolNames.forEach(name => allToolNames.add(name)));
472
- }
473
- }
474
- }
475
-
476
- console.log(`āœ“ Found ${allToolUsages.length} usage site(s) with ${allToolNames.size} unique tool(s)`);
477
- allToolUsages.forEach(usage => {
478
- console.log(` - ${usage.component} in ${path.relative(projectRoot, usage.file)}`);
479
- console.log(` Tools: ${Array.from(usage.toolNames).join(', ')}`);
480
- });
481
-
482
- // Extract metadata for discovered tools
483
- console.log(`\nšŸ“ Extracting types for discovered tools...\n`);
484
-
485
- const functionsMap: Record<string, FunctionMetadata> = {};
486
- const discoveredFrom: string[] = [];
487
-
488
- for (const toolName of allToolNames) {
489
- let found = false;
490
-
491
- // Search in all files for the definition
492
- for (const file of files) {
493
- const sourceFile = program.getSourceFile(file);
494
- if (sourceFile) {
495
- const result = findFunctionDefinition(toolName, sourceFile, program);
496
- if (result) {
497
- const metadata = extractFunctionMetadata(result.node, checker, result.sourceFile);
498
- if (metadata) {
499
- functionsMap[toolName] = metadata;
500
- discoveredFrom.push(path.relative(projectRoot, result.sourceFile.fileName));
501
- console.log(` āœ“ ${toolName} (from ${path.relative(projectRoot, result.sourceFile.fileName)})`);
502
- found = true;
503
- break;
504
- }
505
- }
506
- }
507
- }
508
-
509
- if (!found) {
510
- console.log(` ⚠ ${toolName} (definition not found)`);
511
- }
512
- }
513
-
514
- // Generate output
515
- const output: GeneratedMetadata = {
516
- generated: new Date().toISOString(),
517
- discoveredFrom: Array.from(new Set(discoveredFrom)),
518
- functions: functionsMap,
519
- };
520
-
521
- const outputDir = path.dirname(outputPath);
522
- if (!fs.existsSync(outputDir)) {
523
- fs.mkdirSync(outputDir, { recursive: true });
524
- }
525
-
526
- fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
527
- console.log(`\nāœ… Generated metadata for ${Object.keys(functionsMap).length} tool(s)`);
528
- console.log(`šŸ“ Output: ${outputPath}\n`);
529
- }
530
-
531
- // CLI
532
- if (import.meta.main) {
533
- const args = process.argv.slice(2);
534
- const projectRoot = args[0] ? path.resolve(args[0]) : process.cwd();
535
- const outputPath = args[1]
536
- ? path.resolve(args[1])
537
- : path.join(projectRoot, '.arcten', 'tool-types.json');
538
-
539
- autoDiscoverAndExtract(projectRoot, outputPath);
540
- }
541
-
542
- export { autoDiscoverAndExtract };
2
+ /**
3
+ * Automatically discover and extract types for tools used in ArctenAgent/useAgent
4
+ * Scans the project, finds all tool usages, and generates metadata only for used tools
5
+ */
6
+
7
+ import * as ts from 'typescript';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { glob } from 'glob';
11
+
12
+ // JSON Schema compatible format
13
+ interface JsonSchemaProperty {
14
+ type: string;
15
+ description?: string;
16
+ default?: string;
17
+ enum?: string[]; // For enum types
18
+ items?: JsonSchemaProperty; // For array types
19
+ properties?: Record<string, JsonSchemaProperty>; // For object types
20
+ required?: string[]; // For nested objects
21
+ }
22
+
23
+ interface FunctionMetadata {
24
+ name: string;
25
+ description: string;
26
+ parameters: {
27
+ type: 'object';
28
+ properties: Record<string, JsonSchemaProperty>;
29
+ required?: string[]; // Array of required parameter names
30
+ };
31
+ returnType?: string;
32
+ isAsync?: boolean;
33
+ }
34
+
35
+ interface GeneratedMetadata {
36
+ generated: string;
37
+ discoveredFrom: string[];
38
+ functions: Record<string, FunctionMetadata>;
39
+ }
40
+
41
+ interface ToolUsage {
42
+ toolNames: Set<string>;
43
+ file: string;
44
+ component: 'ArctenAgent' | 'useAgent';
45
+ }
46
+
47
+ /**
48
+ * Find all files that use ArctenAgent or useAgent
49
+ */
50
+ async function findToolUsageFiles(projectRoot: string): Promise<string[]> {
51
+ const pattern = path.join(projectRoot, '**/*.{ts,tsx}').replace(/\\/g, '/');
52
+ const files = await glob(pattern, {
53
+ ignore: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'],
54
+ });
55
+
56
+ console.log(`šŸ“‚ Found ${files.length} TypeScript files to scan`);
57
+ return files;
58
+ }
59
+
60
+ /**
61
+ * Extract tool names from ArctenAgent or useAgent usage
62
+ */
63
+ function extractToolNamesFromFile(sourceFile: ts.SourceFile): ToolUsage[] {
64
+ const usages: ToolUsage[] = [];
65
+
66
+ function visit(node: ts.Node) {
67
+ // Check for <ArctenAgent tools={[...]} safeTools={[...]} />
68
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
69
+ const tagName = ts.isJsxElement(node)
70
+ ? node.openingElement.tagName.getText(sourceFile)
71
+ : node.tagName.getText(sourceFile);
72
+
73
+ if (tagName === 'ArctenAgent') {
74
+ const toolNames = new Set<string>();
75
+ const attributes = ts.isJsxElement(node)
76
+ ? node.openingElement.attributes.properties
77
+ : node.attributes.properties;
78
+
79
+ for (const attr of attributes) {
80
+ if (ts.isJsxAttribute(attr)) {
81
+ const attrName = attr.name.getText(sourceFile);
82
+ if (attrName === 'tools' || attrName === 'safeTools') {
83
+ if (attr.initializer && ts.isJsxExpression(attr.initializer)) {
84
+ const expr = attr.initializer.expression;
85
+ if (expr && ts.isArrayLiteralExpression(expr)) {
86
+ for (const element of expr.elements) {
87
+ const name = element.getText(sourceFile).trim();
88
+ // Remove any object property access (e.g., tools.getOrders -> getOrders)
89
+ const simpleName = name.split('.').pop() || name;
90
+ toolNames.add(simpleName);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ if (toolNames.size > 0) {
99
+ usages.push({
100
+ toolNames,
101
+ file: sourceFile.fileName,
102
+ component: 'ArctenAgent',
103
+ });
104
+ }
105
+ }
106
+ }
107
+
108
+ // Check for useAgent({ tools: [...], safeTools: [...] })
109
+ if (ts.isCallExpression(node)) {
110
+ const expr = node.expression;
111
+ if (ts.isIdentifier(expr) && expr.getText(sourceFile) === 'useAgent') {
112
+ const toolNames = new Set<string>();
113
+
114
+ if (node.arguments.length > 0) {
115
+ const arg = node.arguments[0];
116
+ if (ts.isObjectLiteralExpression(arg)) {
117
+ for (const prop of arg.properties) {
118
+ if (ts.isPropertyAssignment(prop)) {
119
+ const propName = prop.name.getText(sourceFile);
120
+ if (propName === 'tools' || propName === 'safeTools') {
121
+ if (ts.isArrayLiteralExpression(prop.initializer)) {
122
+ for (const element of prop.initializer.elements) {
123
+ const name = element.getText(sourceFile).trim();
124
+ const simpleName = name.split('.').pop() || name;
125
+ toolNames.add(simpleName);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ if (toolNames.size > 0) {
135
+ usages.push({
136
+ toolNames,
137
+ file: sourceFile.fileName,
138
+ component: 'useAgent',
139
+ });
140
+ }
141
+ }
142
+ }
143
+
144
+ ts.forEachChild(node, visit);
145
+ }
146
+
147
+ visit(sourceFile);
148
+ return usages;
149
+ }
150
+
151
+ /**
152
+ * Find where a tool function is defined
153
+ */
154
+ function findFunctionDefinition(
155
+ functionName: string,
156
+ sourceFile: ts.SourceFile,
157
+ program: ts.Program
158
+ ): { sourceFile: ts.SourceFile; node: ts.FunctionDeclaration } | null {
159
+ // First, check if it's defined in the current file
160
+ let foundNode: ts.FunctionDeclaration | null = null;
161
+
162
+ function visitForDefinition(node: ts.Node) {
163
+ if (ts.isFunctionDeclaration(node) && node.name) {
164
+ if (node.name.getText(sourceFile) === functionName) {
165
+ foundNode = node;
166
+ }
167
+ }
168
+ if (!foundNode) {
169
+ ts.forEachChild(node, visitForDefinition);
170
+ }
171
+ }
172
+
173
+ visitForDefinition(sourceFile);
174
+
175
+ if (foundNode) {
176
+ return { sourceFile, node: foundNode };
177
+ }
178
+
179
+ // If not found, check imports
180
+ const imports: { name: string; from: string }[] = [];
181
+
182
+ function visitForImports(node: ts.Node) {
183
+ if (ts.isImportDeclaration(node)) {
184
+ const moduleSpecifier = node.moduleClause;
185
+ if (moduleSpecifier && ts.isImportClause(moduleSpecifier)) {
186
+ const namedBindings = moduleSpecifier.namedBindings;
187
+ if (namedBindings && ts.isNamedImports(namedBindings)) {
188
+ for (const element of namedBindings.elements) {
189
+ const importedName = element.name.getText(sourceFile);
190
+ if (importedName === functionName) {
191
+ const modulePath = (node.moduleSpecifier as ts.StringLiteral).text;
192
+ imports.push({ name: importedName, from: modulePath });
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ ts.forEachChild(node, visitForImports);
199
+ }
200
+
201
+ visitForImports(sourceFile);
202
+
203
+ // Follow imports to find definition
204
+ for (const imp of imports) {
205
+ const resolvedPath = resolveImportPath(imp.from, sourceFile.fileName);
206
+ if (resolvedPath) {
207
+ const importedSourceFile = program.getSourceFile(resolvedPath);
208
+ if (importedSourceFile) {
209
+ const result = findFunctionDefinition(functionName, importedSourceFile, program);
210
+ if (result) {
211
+ return result;
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ /**
221
+ * Resolve import path to actual file path
222
+ */
223
+ function resolveImportPath(importPath: string, fromFile: string): string | null {
224
+ if (importPath.startsWith('.')) {
225
+ // Relative import
226
+ const dir = path.dirname(fromFile);
227
+ const resolved = path.resolve(dir, importPath);
228
+
229
+ // Try common extensions
230
+ const extensions = ['.ts', '.tsx', '.js', '.jsx'];
231
+ for (const ext of extensions) {
232
+ const withExt = resolved + ext;
233
+ if (fs.existsSync(withExt)) {
234
+ return withExt;
235
+ }
236
+ }
237
+
238
+ // Try index files
239
+ for (const ext of extensions) {
240
+ const indexPath = path.join(resolved, `index${ext}`);
241
+ if (fs.existsSync(indexPath)) {
242
+ return indexPath;
243
+ }
244
+ }
245
+
246
+ return resolved;
247
+ }
248
+
249
+ // Node modules - we'll skip for now
250
+ return null;
251
+ }
252
+
253
+ /**
254
+ * Serialize a TypeScript type to JSON Schema format
255
+ * Returns: { isOptional, schema }
256
+ */
257
+ function serializeType(
258
+ type: ts.Type,
259
+ checker: ts.TypeChecker,
260
+ visited = new Set<number>(),
261
+ depth = 0
262
+ ): { isOptional: boolean; schema: JsonSchemaProperty } {
263
+ const typeString = checker.typeToString(type);
264
+
265
+ if (depth > 10) {
266
+ return { isOptional: false, schema: { type: 'any' } };
267
+ }
268
+
269
+ const typeId = (type as any).id;
270
+ if (typeId !== undefined && visited.has(typeId)) {
271
+ return { isOptional: false, schema: { type: 'any' } };
272
+ }
273
+
274
+ // Handle primitives
275
+ if (type.flags & ts.TypeFlags.String) {
276
+ return { isOptional: false, schema: { type: 'string' } };
277
+ }
278
+ if (type.flags & ts.TypeFlags.Number) {
279
+ return { isOptional: false, schema: { type: 'number' } };
280
+ }
281
+ if (type.flags & ts.TypeFlags.Boolean) {
282
+ return { isOptional: false, schema: { type: 'boolean' } };
283
+ }
284
+ if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
285
+ return { isOptional: true, schema: { type: 'null' } };
286
+ }
287
+
288
+ // Handle union types
289
+ if (type.isUnion()) {
290
+ const types = type.types;
291
+
292
+ // Check if it's a string literal union (enum)
293
+ const stringLiterals = types
294
+ .filter(t => t.flags & ts.TypeFlags.StringLiteral)
295
+ .map(t => (t as ts.StringLiteralType).value);
296
+
297
+ if (stringLiterals.length === types.length) {
298
+ return {
299
+ isOptional: false,
300
+ schema: { type: 'string', enum: stringLiterals }
301
+ };
302
+ }
303
+
304
+ // Check for optional (T | undefined)
305
+ const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
306
+ const nonUndefinedTypes = types.filter(t => !(t.flags & ts.TypeFlags.Undefined));
307
+
308
+ if (hasUndefined && nonUndefinedTypes.length === 1) {
309
+ const result = serializeType(nonUndefinedTypes[0], checker, visited, depth + 1);
310
+ return { isOptional: true, schema: result.schema };
311
+ }
312
+
313
+ // Generic union - just use string representation
314
+ return {
315
+ isOptional: false,
316
+ schema: { type: types.map(t => checker.typeToString(t)).join(' | ') }
317
+ };
318
+ }
319
+
320
+ // Handle arrays
321
+ if (checker.isArrayType(type)) {
322
+ const typeArgs = (type as ts.TypeReference).typeArguments;
323
+ if (typeArgs && typeArgs.length > 0) {
324
+ const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
325
+ return {
326
+ isOptional: false,
327
+ schema: { type: 'array', items: itemResult.schema }
328
+ };
329
+ }
330
+ return { isOptional: false, schema: { type: 'array' } };
331
+ }
332
+
333
+ // Handle objects
334
+ if (type.flags & ts.TypeFlags.Object) {
335
+ if (typeId !== undefined) {
336
+ visited.add(typeId);
337
+ }
338
+
339
+ const properties: Record<string, JsonSchemaProperty> = {};
340
+ const required: string[] = [];
341
+ const props = checker.getPropertiesOfType(type);
342
+
343
+ if (props.length > 0) {
344
+ for (const prop of props) {
345
+ const propDeclaration = prop.valueDeclaration || prop.declarations?.[0];
346
+
347
+ if (propDeclaration) {
348
+ const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
349
+ const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
350
+
351
+ const propResult = serializeType(propType, checker, visited, depth + 1);
352
+ properties[prop.name] = propResult.schema;
353
+
354
+ // Track required properties
355
+ if (!isOptional && !propResult.isOptional) {
356
+ required.push(prop.name);
357
+ }
358
+ }
359
+ }
360
+
361
+ const schema: JsonSchemaProperty = {
362
+ type: 'object',
363
+ properties
364
+ };
365
+
366
+ if (required.length > 0) {
367
+ schema.required = required;
368
+ }
369
+
370
+ return { isOptional: false, schema };
371
+ }
372
+ }
373
+
374
+ // Fallback
375
+ return {
376
+ isOptional: false,
377
+ schema: { type: typeString.length < 100 ? typeString : 'any' }
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Extract metadata from a function
383
+ */
384
+ function extractFunctionMetadata(
385
+ node: ts.FunctionDeclaration,
386
+ checker: ts.TypeChecker,
387
+ sourceFile: ts.SourceFile
388
+ ): FunctionMetadata | null {
389
+ if (!node.name) {
390
+ return null;
391
+ }
392
+
393
+ const functionName = node.name.getText(sourceFile);
394
+
395
+ const jsDocTags = ts.getJSDocTags(node);
396
+ const jsDocComments = ts.getJSDocCommentsAndTags(node);
397
+
398
+ let description = `Execute ${functionName}`;
399
+ for (const comment of jsDocComments) {
400
+ if (ts.isJSDoc(comment) && comment.comment) {
401
+ if (typeof comment.comment === 'string') {
402
+ description = comment.comment.trim();
403
+ }
404
+ break;
405
+ }
406
+ }
407
+
408
+ const paramDescriptions = new Map<string, string>();
409
+ for (const tag of jsDocTags) {
410
+ if (tag.tagName.text === 'param' && ts.isJSDocParameterTag(tag)) {
411
+ const paramName = tag.name?.getText(sourceFile);
412
+ const paramDesc = typeof tag.comment === 'string' ? tag.comment.trim() : '';
413
+ if (paramName && paramDesc) {
414
+ paramDescriptions.set(paramName, paramDesc);
415
+ }
416
+ }
417
+ }
418
+
419
+ const properties: Record<string, JsonSchemaProperty> = {};
420
+ const required: string[] = [];
421
+ const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
422
+
423
+ for (const param of node.parameters) {
424
+ const paramName = param.name.getText(sourceFile);
425
+ const hasDefault = param.initializer !== undefined;
426
+
427
+ let propSchema: JsonSchemaProperty;
428
+ let isOptional = false;
429
+
430
+ if (param.type) {
431
+ const type = checker.getTypeFromTypeNode(param.type);
432
+ const result = serializeType(type, checker);
433
+ propSchema = result.schema;
434
+ isOptional = result.isOptional;
435
+ } else {
436
+ propSchema = { type: 'any' };
437
+ }
438
+
439
+ // Check if parameter has default value or question mark
440
+ const hasQuestionMark = param.questionToken !== undefined;
441
+ if (hasDefault || hasQuestionMark) {
442
+ isOptional = true;
443
+ }
444
+
445
+ // Add default value if present
446
+ if (hasDefault) {
447
+ propSchema.default = param.initializer!.getText(sourceFile);
448
+ }
449
+
450
+ // Add description from JSDoc
451
+ if (paramDescriptions.has(paramName)) {
452
+ propSchema.description = paramDescriptions.get(paramName);
453
+ }
454
+
455
+ properties[paramName] = propSchema;
456
+
457
+ // Track required params
458
+ if (!isOptional) {
459
+ required.push(paramName);
460
+ }
461
+ }
462
+
463
+ let returnType = 'any';
464
+ if (node.type) {
465
+ returnType = node.type.getText(sourceFile);
466
+ }
467
+
468
+ return {
469
+ name: functionName,
470
+ description,
471
+ parameters: {
472
+ type: 'object',
473
+ properties,
474
+ required: required.length > 0 ? required : undefined
475
+ },
476
+ returnType,
477
+ isAsync,
478
+ };
479
+ }
480
+
481
+ /**
482
+ * Main function
483
+ */
484
+ async function autoDiscoverAndExtract(projectRoot: string, outputPath: string) {
485
+ console.log(`\nšŸ” Auto-discovering tools in: ${projectRoot}\n`);
486
+
487
+ // Find all TypeScript files
488
+ const files = await findToolUsageFiles(projectRoot);
489
+
490
+ // Read tsconfig
491
+ const configPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, 'tsconfig.json');
492
+ let compilerOptions: ts.CompilerOptions = {
493
+ target: ts.ScriptTarget.ESNext,
494
+ module: ts.ModuleKind.ESNext,
495
+ jsx: ts.JsxEmit.React,
496
+ };
497
+
498
+ if (configPath) {
499
+ const config = ts.readConfigFile(configPath, ts.sys.readFile);
500
+ const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
501
+ compilerOptions = parsed.options;
502
+ }
503
+
504
+ // Create program with all files
505
+ const program = ts.createProgram(files, compilerOptions);
506
+ const checker = program.getTypeChecker();
507
+
508
+ // Scan for tool usages
509
+ const allToolUsages: ToolUsage[] = [];
510
+ const allToolNames = new Set<string>();
511
+
512
+ for (const file of files) {
513
+ const sourceFile = program.getSourceFile(file);
514
+ if (sourceFile) {
515
+ const usages = extractToolNamesFromFile(sourceFile);
516
+ if (usages.length > 0) {
517
+ allToolUsages.push(...usages);
518
+ usages.forEach(u => u.toolNames.forEach(name => allToolNames.add(name)));
519
+ }
520
+ }
521
+ }
522
+
523
+ console.log(`āœ“ Found ${allToolUsages.length} usage site(s) with ${allToolNames.size} unique tool(s)`);
524
+ allToolUsages.forEach(usage => {
525
+ console.log(` - ${usage.component} in ${path.relative(projectRoot, usage.file)}`);
526
+ console.log(` Tools: ${Array.from(usage.toolNames).join(', ')}`);
527
+ });
528
+
529
+ // Extract metadata for discovered tools
530
+ console.log(`\nšŸ“ Extracting types for discovered tools...\n`);
531
+
532
+ const functionsMap: Record<string, FunctionMetadata> = {};
533
+ const discoveredFrom: string[] = [];
534
+
535
+ for (const toolName of allToolNames) {
536
+ let found = false;
537
+
538
+ // Search in all files for the definition
539
+ for (const file of files) {
540
+ const sourceFile = program.getSourceFile(file);
541
+ if (sourceFile) {
542
+ const result = findFunctionDefinition(toolName, sourceFile, program);
543
+ if (result) {
544
+ const metadata = extractFunctionMetadata(result.node, checker, result.sourceFile);
545
+ if (metadata) {
546
+ functionsMap[toolName] = metadata;
547
+ discoveredFrom.push(path.relative(projectRoot, result.sourceFile.fileName));
548
+ console.log(` āœ“ ${toolName} (from ${path.relative(projectRoot, result.sourceFile.fileName)})`);
549
+ found = true;
550
+ break;
551
+ }
552
+ }
553
+ }
554
+ }
555
+
556
+ if (!found) {
557
+ console.log(` ⚠ ${toolName} (definition not found)`);
558
+ }
559
+ }
560
+
561
+ // Generate output
562
+ const output: GeneratedMetadata = {
563
+ generated: new Date().toISOString(),
564
+ discoveredFrom: Array.from(new Set(discoveredFrom)),
565
+ functions: functionsMap,
566
+ };
567
+
568
+ const outputDir = path.dirname(outputPath);
569
+ if (!fs.existsSync(outputDir)) {
570
+ fs.mkdirSync(outputDir, { recursive: true });
571
+ }
572
+
573
+ // Generate TypeScript module instead of JSON for easier bundler integration
574
+ const tsContent = `// Auto-generated by arcten-extract-types
575
+ // Do not edit this file manually
576
+
577
+ export const toolMetadata = ${JSON.stringify(output, null, 2)} as const;
578
+ `;
579
+
580
+ fs.writeFileSync(outputPath, tsContent);
581
+ console.log(`\nāœ… Generated metadata for ${Object.keys(functionsMap).length} tool(s)`);
582
+ console.log(`šŸ“ Output: ${outputPath}`);
583
+ console.log(`\nšŸ’” Usage in your component:`);
584
+ console.log(` import { toolMetadata } from './.arcten/tool-metadata';`);
585
+ console.log(` useAgent({ tools: [...], toolMetadata: toolMetadata.functions })\n`);
586
+ }
587
+
588
+ // CLI
589
+ if (import.meta.main) {
590
+ const args = process.argv.slice(2);
591
+ const projectRoot = args[0] ? path.resolve(args[0]) : process.cwd();
592
+ const outputPath = args[1]
593
+ ? path.resolve(args[1])
594
+ : path.join(projectRoot, '.arcten', 'tool-metadata.ts');
595
+
596
+ autoDiscoverAndExtract(projectRoot, outputPath);
597
+ }
598
+
599
+ export { autoDiscoverAndExtract };