@flink-app/flink 1.0.0 → 2.0.0-alpha.48
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/CHANGELOG.md +6 -0
- package/cli/build.ts +8 -1
- package/cli/run.ts +8 -1
- package/dist/cli/build.js +8 -1
- package/dist/cli/run.js +8 -1
- package/dist/src/FlinkApp.d.ts +33 -0
- package/dist/src/FlinkApp.js +247 -27
- package/dist/src/FlinkContext.d.ts +21 -0
- package/dist/src/FlinkHttpHandler.d.ts +90 -1
- package/dist/src/TypeScriptCompiler.d.ts +42 -0
- package/dist/src/TypeScriptCompiler.js +346 -4
- package/dist/src/TypeScriptUtils.js +4 -0
- package/dist/src/ai/AgentRunner.d.ts +39 -0
- package/dist/src/ai/AgentRunner.js +625 -0
- package/dist/src/ai/FlinkAgent.d.ts +446 -0
- package/dist/src/ai/FlinkAgent.js +633 -0
- package/dist/src/ai/FlinkTool.d.ts +37 -0
- package/dist/src/ai/FlinkTool.js +2 -0
- package/dist/src/ai/LLMAdapter.d.ts +119 -0
- package/dist/src/ai/LLMAdapter.js +2 -0
- package/dist/src/ai/SubAgentExecutor.d.ts +36 -0
- package/dist/src/ai/SubAgentExecutor.js +220 -0
- package/dist/src/ai/ToolExecutor.d.ts +35 -0
- package/dist/src/ai/ToolExecutor.js +237 -0
- package/dist/src/ai/index.d.ts +5 -0
- package/dist/src/ai/index.js +21 -0
- package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
- package/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/utils.d.ts +30 -0
- package/dist/src/utils.js +52 -0
- package/package.json +14 -2
- package/readme.md +425 -0
- package/spec/AgentDuplicateDetection.spec.ts +112 -0
- package/spec/AgentRunner.spec.ts +527 -0
- package/spec/ConversationHooks.spec.ts +290 -0
- package/spec/FlinkAgent.spec.ts +310 -0
- package/spec/FlinkApp.onError.spec.ts +1 -2
- package/spec/StreamingIntegration.spec.ts +138 -0
- package/spec/SubAgentSupport.spec.ts +941 -0
- package/spec/ToolExecutor.spec.ts +360 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +59 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +76 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +59 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +59 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +56 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
- package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
- package/spec/mock-project/dist/src/FlinkApp.js +1012 -0
- package/spec/mock-project/dist/src/FlinkContext.js +2 -0
- package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
- package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
- package/spec/mock-project/dist/src/FlinkJob.js +2 -0
- package/spec/mock-project/dist/src/FlinkLog.js +26 -0
- package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
- package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
- package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
- package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
- package/spec/mock-project/dist/src/ai/AgentRunner.js +625 -0
- package/spec/mock-project/dist/src/ai/FlinkAgent.js +633 -0
- package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
- package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
- package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +220 -0
- package/spec/mock-project/dist/src/ai/ToolExecutor.js +237 -0
- package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
- package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
- package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/spec/mock-project/dist/src/index.js +17 -69
- package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
- package/spec/mock-project/dist/src/utils.js +290 -0
- package/spec/mock-project/tsconfig.json +6 -1
- package/spec/testHelpers.ts +49 -0
- package/spec/utils.caseConversion.spec.ts +80 -0
- package/spec/utils.spec.ts +13 -13
- package/src/FlinkApp.ts +251 -7
- package/src/FlinkContext.ts +22 -0
- package/src/FlinkHttpHandler.ts +100 -2
- package/src/TypeScriptCompiler.ts +398 -7
- package/src/TypeScriptUtils.ts +5 -0
- package/src/ai/AgentRunner.ts +549 -0
- package/src/ai/FlinkAgent.ts +770 -0
- package/src/ai/FlinkTool.ts +40 -0
- package/src/ai/LLMAdapter.ts +96 -0
- package/src/ai/SubAgentExecutor.ts +199 -0
- package/src/ai/ToolExecutor.ts +193 -0
- package/src/ai/index.ts +5 -0
- package/src/handlers/StreamWriterFactory.ts +84 -0
- package/src/index.ts +4 -0
- package/src/utils.ts +52 -0
- package/tsconfig.json +6 -1
|
@@ -69,13 +69,118 @@ class TypeScriptCompiler {
|
|
|
69
69
|
this.project = new Project({
|
|
70
70
|
tsConfigFilePath: tsConfigPath,
|
|
71
71
|
compilerOptions,
|
|
72
|
+
skipAddingFilesFromTsConfig: true, // Don't auto-load all files - we'll add specific directories we need
|
|
72
73
|
});
|
|
73
74
|
|
|
75
|
+
// Load Flink-specific source paths from tsconfig.json if available
|
|
76
|
+
const additionalSourcePaths = this.getFlinkSourcePaths(tsConfigPath);
|
|
77
|
+
|
|
78
|
+
// Only add source files from directories we actually need to process
|
|
79
|
+
// This prevents loading all files from tsconfig include patterns and reduces memory usage
|
|
80
|
+
const defaultSourcePaths = [
|
|
81
|
+
join(cwd, "src/handlers/**/*.ts"),
|
|
82
|
+
join(cwd, "src/repos/**/*.ts"),
|
|
83
|
+
join(cwd, "src/tools/**/*.ts"),
|
|
84
|
+
join(cwd, "src/agents/**/*.ts"),
|
|
85
|
+
join(cwd, "src/jobs/**/*.ts"),
|
|
86
|
+
join(cwd, "src/index.ts"),
|
|
87
|
+
join(cwd, "src/schemas/**/*.ts"), // Include schemas for type resolution
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const allSourcePaths = [...defaultSourcePaths, ...additionalSourcePaths];
|
|
91
|
+
this.project.addSourceFilesAtPaths(allSourcePaths);
|
|
92
|
+
|
|
93
|
+
// Resolve imports: add any files imported by our source files
|
|
94
|
+
// This ensures schemas that import types from other locations work correctly
|
|
95
|
+
this.resolveImportedFiles();
|
|
96
|
+
|
|
74
97
|
console.log("Loaded", this.project.getSourceFiles().length, "source file(s) from", cwd);
|
|
75
98
|
console.log("Module system:", this.isEsm ? "ESM" : "CommonJS");
|
|
76
99
|
console.log("Using module:", compilerOptions.module === ts.ModuleKind.ESNext ? "ESNext" : "CommonJS");
|
|
77
100
|
}
|
|
78
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Loads additional source paths from tsconfig.json's flink configuration.
|
|
104
|
+
* Allows projects to specify extra directories to include in compilation.
|
|
105
|
+
*
|
|
106
|
+
* Example tsconfig.json:
|
|
107
|
+
* {
|
|
108
|
+
* "flink": {
|
|
109
|
+
* "sourcePaths": ["src/custom-types/**\/*.ts", "src/utils/**\/*.ts"]
|
|
110
|
+
* }
|
|
111
|
+
* }
|
|
112
|
+
*/
|
|
113
|
+
private getFlinkSourcePaths(tsConfigPath: string): string[] {
|
|
114
|
+
try {
|
|
115
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
116
|
+
const tsConfigContent = fs.readFileSync(tsConfigPath, "utf8");
|
|
117
|
+
const tsConfig = JSON.parse(tsConfigContent);
|
|
118
|
+
|
|
119
|
+
if (tsConfig.flink && Array.isArray(tsConfig.flink.sourcePaths)) {
|
|
120
|
+
console.log("Found Flink-specific source paths:", tsConfig.flink.sourcePaths);
|
|
121
|
+
return tsConfig.flink.sourcePaths.map((path: string) => join(this.cwd, path));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.warn("Error reading Flink source paths from tsconfig.json:", error);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Recursively resolves and adds imported files to the project.
|
|
133
|
+
* This ensures that files imported by handlers, schemas, etc. are available for type resolution.
|
|
134
|
+
*
|
|
135
|
+
* Handles three types of imports:
|
|
136
|
+
* 1. Relative imports (./foo, ../bar) - always resolved
|
|
137
|
+
* 2. Workspace package imports (@mycompany/shared) - resolved if symlinked outside node_modules
|
|
138
|
+
* 3. External packages (lodash, express) - skipped to avoid loading entire dependency trees
|
|
139
|
+
*/
|
|
140
|
+
private resolveImportedFiles() {
|
|
141
|
+
const processedFiles = new Set<string>();
|
|
142
|
+
const filesToProcess = [...this.project.getSourceFiles()];
|
|
143
|
+
|
|
144
|
+
while (filesToProcess.length > 0) {
|
|
145
|
+
const sourceFile = filesToProcess.pop()!;
|
|
146
|
+
const filePath = sourceFile.getFilePath();
|
|
147
|
+
|
|
148
|
+
if (processedFiles.has(filePath)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
processedFiles.add(filePath);
|
|
153
|
+
|
|
154
|
+
// Get all import declarations
|
|
155
|
+
const importDeclarations = sourceFile.getImportDeclarations();
|
|
156
|
+
|
|
157
|
+
for (const importDecl of importDeclarations) {
|
|
158
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
159
|
+
|
|
160
|
+
// Try to resolve the imported file
|
|
161
|
+
const moduleSourceFile = importDecl.getModuleSpecifierSourceFile();
|
|
162
|
+
|
|
163
|
+
if (moduleSourceFile) {
|
|
164
|
+
const importedPath = moduleSourceFile.getFilePath();
|
|
165
|
+
|
|
166
|
+
// For relative imports (./foo, ../bar), always include
|
|
167
|
+
const isRelativeImport = moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/");
|
|
168
|
+
|
|
169
|
+
// For package imports (@foo/bar, foo), check if resolved path is outside node_modules
|
|
170
|
+
// This handles workspace packages that are symlinked (pnpm, yarn workspaces)
|
|
171
|
+
const isWorkspacePackage = !isRelativeImport && !importedPath.includes("node_modules");
|
|
172
|
+
|
|
173
|
+
// Skip if it's in node_modules (external package) or already processed
|
|
174
|
+
if ((isRelativeImport || isWorkspacePackage) && !processedFiles.has(importedPath)) {
|
|
175
|
+
filesToProcess.push(moduleSourceFile);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log("Resolved imports, total files loaded:", processedFiles.size);
|
|
182
|
+
}
|
|
183
|
+
|
|
79
184
|
/**
|
|
80
185
|
* Detects if the project is using ESM (ECMAScript Modules)
|
|
81
186
|
* by checking type in package.json.
|
|
@@ -171,6 +276,54 @@ class TypeScriptCompiler {
|
|
|
171
276
|
return true;
|
|
172
277
|
}
|
|
173
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Finds a variable declaration by its TypeScript type name.
|
|
281
|
+
* This allows finding variables based on their type annotation rather than variable name.
|
|
282
|
+
*
|
|
283
|
+
* @param sf Source file to search in
|
|
284
|
+
* @param typeName Name of the type to search for (e.g., "FlinkToolProps", "FlinkAgentProps")
|
|
285
|
+
* @returns The first matching variable declaration, or undefined if not found
|
|
286
|
+
*/
|
|
287
|
+
private findVariableDeclarationByType(sf: SourceFile, typeName: string) {
|
|
288
|
+
// Get all variable declarations from the source file
|
|
289
|
+
const variableDeclarations = sf.getVariableDeclarations();
|
|
290
|
+
|
|
291
|
+
for (const varDecl of variableDeclarations) {
|
|
292
|
+
// Check if the variable is exported
|
|
293
|
+
const variableStatement = varDecl.getVariableStatement();
|
|
294
|
+
if (!variableStatement?.isExported()) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check if it's a const declaration
|
|
299
|
+
if (varDecl.getVariableStatement()?.getDeclarationKind() !== VariableDeclarationKind.Const) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Get the type node (explicit type annotation)
|
|
304
|
+
const typeNode = varDecl.getTypeNode();
|
|
305
|
+
if (!typeNode) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check if it's a type reference
|
|
310
|
+
if (typeNode.getKind() === SyntaxKind.TypeReference) {
|
|
311
|
+
const typeRef = typeNode as TypeReferenceNode;
|
|
312
|
+
const typeRefName = typeRef.getTypeName();
|
|
313
|
+
|
|
314
|
+
// Handle both simple type references (FlinkToolProps) and qualified names (flink.FlinkToolProps)
|
|
315
|
+
const typeRefText = typeRefName.getText();
|
|
316
|
+
|
|
317
|
+
// Check if the type name matches (either exact match or ends with the type name for qualified references)
|
|
318
|
+
if (typeRefText === typeName || typeRefText.endsWith(`.${typeName}`)) {
|
|
319
|
+
return varDecl;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
|
|
174
327
|
/**
|
|
175
328
|
* Scans project for handlers and add those to Flink
|
|
176
329
|
* "singleton" property `autoRegisteredHandlers` so they can
|
|
@@ -338,6 +491,243 @@ autoRegisteredHandlers.push(...handlers);
|
|
|
338
491
|
return generatedFile;
|
|
339
492
|
}
|
|
340
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Scans project for tools and adds those to Flink
|
|
496
|
+
* "singleton" property `autoRegisteredTools` so they can
|
|
497
|
+
* be registered during start.
|
|
498
|
+
*/
|
|
499
|
+
async parseTools() {
|
|
500
|
+
const generatedFile = this.createSourceFile(
|
|
501
|
+
["generatedTools.ts"],
|
|
502
|
+
`// Generated ${new Date()}
|
|
503
|
+
import { autoRegisteredTools } from "@flink-app/flink";
|
|
504
|
+
export const tools = [];
|
|
505
|
+
autoRegisteredTools.push(...tools);
|
|
506
|
+
`
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const toolsArr = generatedFile.getVariableDeclarationOrThrow("tools").getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
510
|
+
|
|
511
|
+
const imports: OptionalKind<ImportDeclarationStructure>[] = [];
|
|
512
|
+
let i = 0;
|
|
513
|
+
|
|
514
|
+
for (const sf of this.project.getSourceFiles()) {
|
|
515
|
+
if (!sf.getFilePath().includes("src/tools/")) {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
console.log(`Detected tool ${sf.getBaseName()}`);
|
|
520
|
+
|
|
521
|
+
const namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
|
|
522
|
+
|
|
523
|
+
imports.push({
|
|
524
|
+
defaultImport: "* as " + namespaceImport,
|
|
525
|
+
moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Find the tool props variable (can have any name, but must be of type FlinkToolProps)
|
|
529
|
+
const toolPropsVar = this.findVariableDeclarationByType(sf, "FlinkToolProps");
|
|
530
|
+
const toolPropsExportName = toolPropsVar?.getName() || "Tool";
|
|
531
|
+
|
|
532
|
+
// Remove existing __file variable if it exists
|
|
533
|
+
const existingFile = sf.getVariableStatements().filter((vs) => {
|
|
534
|
+
const varNames = vs.getDeclarations().map((d) => d.getName());
|
|
535
|
+
return varNames.includes("__file");
|
|
536
|
+
});
|
|
537
|
+
existingFile.forEach((v) => v.remove());
|
|
538
|
+
|
|
539
|
+
// Append metadata to source file
|
|
540
|
+
sf.addVariableStatement({
|
|
541
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
542
|
+
isExported: true,
|
|
543
|
+
declarations: [
|
|
544
|
+
{
|
|
545
|
+
name: "__file",
|
|
546
|
+
initializer: `"${sf.getBaseName()}"`,
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Create an object that wraps the namespace and provides named access to the tool props
|
|
552
|
+
toolsArr.insertElement(i, `{...${namespaceImport}, Tool: ${namespaceImport}.${toolPropsExportName}}`);
|
|
553
|
+
i++;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
generatedFile.addImportDeclarations(imports);
|
|
557
|
+
await generatedFile.save();
|
|
558
|
+
|
|
559
|
+
return generatedFile;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Scans project for agents and validates tool references.
|
|
564
|
+
* Agents are declarative (no default function).
|
|
565
|
+
*/
|
|
566
|
+
async parseAgents() {
|
|
567
|
+
const generatedFile = this.createSourceFile(
|
|
568
|
+
["generatedAgents.ts"],
|
|
569
|
+
`// Generated ${new Date()}
|
|
570
|
+
import { autoRegisteredAgents } from "@flink-app/flink";
|
|
571
|
+
export const agents = [];
|
|
572
|
+
autoRegisteredAgents.push(...agents);
|
|
573
|
+
`
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const agentsArr = generatedFile.getVariableDeclarationOrThrow("agents").getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
577
|
+
|
|
578
|
+
const imports: OptionalKind<ImportDeclarationStructure>[] = [];
|
|
579
|
+
|
|
580
|
+
// Collect all registered tool ids for validation
|
|
581
|
+
const registeredToolIds = new Set<string>();
|
|
582
|
+
for (const sf of this.project.getSourceFiles()) {
|
|
583
|
+
if (sf.getFilePath().includes("src/tools/")) {
|
|
584
|
+
const toolExport = this.findVariableDeclarationByType(sf, "FlinkToolProps");
|
|
585
|
+
if (toolExport) {
|
|
586
|
+
const props = toolExport.getFirstDescendantByKind(SyntaxKind.ObjectLiteralExpression);
|
|
587
|
+
// Try 'id' first, fallback to 'name' for backwards compatibility
|
|
588
|
+
const idProp = props?.getProperty("id") || props?.getProperty("name");
|
|
589
|
+
if (idProp) {
|
|
590
|
+
const idValue = idProp.getLastChildByKind(SyntaxKind.StringLiteral);
|
|
591
|
+
if (idValue) {
|
|
592
|
+
registeredToolIds.add(idValue.getLiteralText());
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
let i = 0;
|
|
600
|
+
|
|
601
|
+
for (const sf of this.project.getSourceFiles()) {
|
|
602
|
+
if (!sf.getFilePath().includes("src/agents/")) {
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Check if file has class extending FlinkAgent (new pattern)
|
|
607
|
+
const agentClass = sf.getClasses().find((cls) => {
|
|
608
|
+
const baseClass = cls.getBaseClass();
|
|
609
|
+
return baseClass?.getName() === "FlinkAgent";
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
if (!agentClass) {
|
|
613
|
+
// Skip files without FlinkAgent class
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
console.log(`Detected agent ${sf.getBaseName()}`);
|
|
618
|
+
|
|
619
|
+
// Validate tool references exist (read from class properties)
|
|
620
|
+
const toolsProperty = agentClass.getProperty("tools");
|
|
621
|
+
if (toolsProperty) {
|
|
622
|
+
const initializer = toolsProperty.getInitializer();
|
|
623
|
+
if (initializer && initializer.getKind() === SyntaxKind.ArrayLiteralExpression) {
|
|
624
|
+
const toolsArray = initializer as any;
|
|
625
|
+
const toolElements = toolsArray.getElements();
|
|
626
|
+
for (const toolElement of toolElements) {
|
|
627
|
+
let toolName: string;
|
|
628
|
+
|
|
629
|
+
// Handle string literals, method calls, and identifier references (tool imports)
|
|
630
|
+
if (toolElement.getKind() === SyntaxKind.StringLiteral) {
|
|
631
|
+
// Direct string: "tool-name"
|
|
632
|
+
toolName = toolElement.getText().replace(/['"]/g, "");
|
|
633
|
+
} else if (toolElement.getKind() === SyntaxKind.CallExpression) {
|
|
634
|
+
// Method call: this.useTool("tool-name")
|
|
635
|
+
const args = toolElement.getArguments();
|
|
636
|
+
if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {
|
|
637
|
+
toolName = args[0].getText().replace(/['"]/g, "");
|
|
638
|
+
} else {
|
|
639
|
+
console.warn(`Agent ${sf.getBaseName()} has non-string tool reference, skipping validation`);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
} else if (toolElement.getKind() === SyntaxKind.Identifier) {
|
|
643
|
+
// Tool file reference (imported): SearchCarsByBrandTool
|
|
644
|
+
// Look up the import to find the actual tool file
|
|
645
|
+
const importName = toolElement.getText();
|
|
646
|
+
const importDecl = sf.getImportDeclarations().find((imp) => {
|
|
647
|
+
const namedImports = imp.getNamedImports();
|
|
648
|
+
return namedImports.some((ni) => ni.getName() === importName);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
if (!importDecl) {
|
|
652
|
+
// Try namespace import (* as Foo)
|
|
653
|
+
const namespaceImport = sf.getImportDeclarations().find((imp) => {
|
|
654
|
+
return imp.getNamespaceImport()?.getText() === importName;
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
if (namespaceImport) {
|
|
658
|
+
const moduleSpecifier = namespaceImport.getModuleSpecifierValue();
|
|
659
|
+
// Extract tool ID from the tool file path
|
|
660
|
+
// e.g., "../tools/SearchCarsByBrandTool" -> find in registeredToolIds
|
|
661
|
+
const toolFileName = moduleSpecifier.split("/").pop()?.replace(/\.ts$/, "");
|
|
662
|
+
const matchingTool = Array.from(registeredToolIds).find((id) => {
|
|
663
|
+
// Try to match by searching for the tool ID
|
|
664
|
+
// This is a heuristic - we'll validate it exists
|
|
665
|
+
return true; // Skip validation for imported tools for now
|
|
666
|
+
});
|
|
667
|
+
// Skip validation - tool imports are validated at runtime
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
console.warn(`Agent ${sf.getBaseName()} references tool "${importName}" but it's not imported, skipping validation`);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Skip validation for tool file references - they're validated at runtime
|
|
676
|
+
continue;
|
|
677
|
+
} else {
|
|
678
|
+
console.warn(`Agent ${sf.getBaseName()} has unexpected tool reference format: ${toolElement.getText()}`);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!registeredToolIds.has(toolName)) {
|
|
683
|
+
console.error(`Agent ${sf.getBaseName()} references tool "${toolName}" which does not exist`);
|
|
684
|
+
throw new Error(`Invalid tool reference in agent ${sf.getBaseName()}`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const className = agentClass.getName();
|
|
691
|
+
if (!className) {
|
|
692
|
+
console.error(`Agent class in ${sf.getBaseName()} has no name`);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
imports.push({
|
|
697
|
+
defaultImport: className,
|
|
698
|
+
moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Remove existing __file variable
|
|
702
|
+
const existingFile = sf.getVariableStatements().filter((vs) => {
|
|
703
|
+
const varNames = vs.getDeclarations().map((d) => d.getName());
|
|
704
|
+
return varNames.includes("__file");
|
|
705
|
+
});
|
|
706
|
+
existingFile.forEach((v) => v.remove());
|
|
707
|
+
|
|
708
|
+
// Append metadata
|
|
709
|
+
sf.addVariableStatement({
|
|
710
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
711
|
+
isExported: true,
|
|
712
|
+
declarations: [
|
|
713
|
+
{
|
|
714
|
+
name: "__file",
|
|
715
|
+
initializer: `"${sf.getBaseName()}"`,
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// Register the agent class
|
|
721
|
+
agentsArr.insertElement(i, `{ default: ${className}, __file: "${sf.getBaseName()}" }`);
|
|
722
|
+
i++;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
generatedFile.addImportDeclarations(imports);
|
|
726
|
+
await generatedFile.save();
|
|
727
|
+
|
|
728
|
+
return generatedFile;
|
|
729
|
+
}
|
|
730
|
+
|
|
341
731
|
/**
|
|
342
732
|
* Generates a start script that will import references to handlers, repos and the
|
|
343
733
|
* actual Flink app to start.
|
|
@@ -356,6 +746,8 @@ autoRegisteredHandlers.push(...handlers);
|
|
|
356
746
|
`// Generated ${new Date()}
|
|
357
747
|
import "./generatedHandlers${this.isEsm ? ".js" : ""}";
|
|
358
748
|
import "./generatedRepos${this.isEsm ? ".js" : ""}";
|
|
749
|
+
import "./generatedTools${this.isEsm ? ".js" : ""}";
|
|
750
|
+
import "./generatedAgents${this.isEsm ? ".js" : ""}";
|
|
359
751
|
import "./generatedJobs${this.isEsm ? ".js" : ""}";
|
|
360
752
|
import "..${appEntryScript.replace(/\.ts/g, "")}${this.isEsm ? ".js" : ""}";
|
|
361
753
|
export default {}; // Export an empty object to make it a module
|
|
@@ -417,9 +809,7 @@ export default {}; // Export an empty object to make it a module
|
|
|
417
809
|
if (!interfaceName) return;
|
|
418
810
|
|
|
419
811
|
// Check if already copied
|
|
420
|
-
const existingInterface = this.parsedTsSchemas.find(
|
|
421
|
-
s => s.includes(`interface ${interfaceName} `) || s.includes(`type ${interfaceName} =`)
|
|
422
|
-
);
|
|
812
|
+
const existingInterface = this.parsedTsSchemas.find((s) => s.includes(`interface ${interfaceName} `) || s.includes(`type ${interfaceName} =`));
|
|
423
813
|
if (existingInterface) return;
|
|
424
814
|
|
|
425
815
|
// Copy the interface
|
|
@@ -458,7 +848,7 @@ export default {}; // Export an empty object to make it a module
|
|
|
458
848
|
|
|
459
849
|
// Check for void and undefined types - these indicate no schema validation needed
|
|
460
850
|
const schemaText = schema.getText();
|
|
461
|
-
if (schemaText ===
|
|
851
|
+
if (schemaText === "void" || schemaText === "undefined") {
|
|
462
852
|
return;
|
|
463
853
|
}
|
|
464
854
|
|
|
@@ -505,9 +895,10 @@ export default {}; // Export an empty object to make it a module
|
|
|
505
895
|
const interfaceNameMatches = propText.match(/\b([A-Z][a-zA-Z0-9]*)\s*\[/g);
|
|
506
896
|
if (interfaceNameMatches) {
|
|
507
897
|
for (const match of interfaceNameMatches) {
|
|
508
|
-
const referencedInterfaceName = match.replace(/\s*\[$/,
|
|
898
|
+
const referencedInterfaceName = match.replace(/\s*\[$/, "").trim();
|
|
509
899
|
// Try to find this interface in the handler file
|
|
510
|
-
const referencedInterfaceDecl =
|
|
900
|
+
const referencedInterfaceDecl =
|
|
901
|
+
handlerFile.getInterface(referencedInterfaceName) || handlerFile.getTypeAlias(referencedInterfaceName);
|
|
511
902
|
if (referencedInterfaceDecl) {
|
|
512
903
|
// Interface is in same file - copy it and all its dependencies recursively
|
|
513
904
|
this.copyInterfaceWithDependencies(referencedInterfaceDecl, handlerFile);
|
|
@@ -621,7 +1012,7 @@ export default {}; // Export an empty object to make it a module
|
|
|
621
1012
|
const interfaceNameMatches = currentPropTypeText.match(/\b([A-Z][a-zA-Z0-9]*)\s*\[/g);
|
|
622
1013
|
if (interfaceNameMatches) {
|
|
623
1014
|
for (const match of interfaceNameMatches) {
|
|
624
|
-
const interfaceName = match.replace(/\s*\[$/,
|
|
1015
|
+
const interfaceName = match.replace(/\s*\[$/, "").trim();
|
|
625
1016
|
// Try to find this interface in the handler file
|
|
626
1017
|
const interfaceDecl = handlerFile.getInterface(interfaceName) || handlerFile.getTypeAlias(interfaceName);
|
|
627
1018
|
if (interfaceDecl) {
|
package/src/TypeScriptUtils.ts
CHANGED
|
@@ -214,6 +214,11 @@ export function getTypeMetadata(type: Type<ts.Type>) {
|
|
|
214
214
|
return [];
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
// Handle empty object literal {} (used in streaming handlers)
|
|
218
|
+
if (type.getText() === "{}") {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
|
|
217
222
|
const symbol = getSymbolOrAlias(type);
|
|
218
223
|
|
|
219
224
|
if (!symbol) {
|