@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.
Files changed (109) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/cli/build.ts +8 -1
  3. package/cli/run.ts +8 -1
  4. package/dist/cli/build.js +8 -1
  5. package/dist/cli/run.js +8 -1
  6. package/dist/src/FlinkApp.d.ts +33 -0
  7. package/dist/src/FlinkApp.js +247 -27
  8. package/dist/src/FlinkContext.d.ts +21 -0
  9. package/dist/src/FlinkHttpHandler.d.ts +90 -1
  10. package/dist/src/TypeScriptCompiler.d.ts +42 -0
  11. package/dist/src/TypeScriptCompiler.js +346 -4
  12. package/dist/src/TypeScriptUtils.js +4 -0
  13. package/dist/src/ai/AgentRunner.d.ts +39 -0
  14. package/dist/src/ai/AgentRunner.js +625 -0
  15. package/dist/src/ai/FlinkAgent.d.ts +446 -0
  16. package/dist/src/ai/FlinkAgent.js +633 -0
  17. package/dist/src/ai/FlinkTool.d.ts +37 -0
  18. package/dist/src/ai/FlinkTool.js +2 -0
  19. package/dist/src/ai/LLMAdapter.d.ts +119 -0
  20. package/dist/src/ai/LLMAdapter.js +2 -0
  21. package/dist/src/ai/SubAgentExecutor.d.ts +36 -0
  22. package/dist/src/ai/SubAgentExecutor.js +220 -0
  23. package/dist/src/ai/ToolExecutor.d.ts +35 -0
  24. package/dist/src/ai/ToolExecutor.js +237 -0
  25. package/dist/src/ai/index.d.ts +5 -0
  26. package/dist/src/ai/index.js +21 -0
  27. package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
  28. package/dist/src/handlers/StreamWriterFactory.js +83 -0
  29. package/dist/src/index.d.ts +4 -0
  30. package/dist/src/index.js +4 -0
  31. package/dist/src/utils.d.ts +30 -0
  32. package/dist/src/utils.js +52 -0
  33. package/package.json +14 -2
  34. package/readme.md +425 -0
  35. package/spec/AgentDuplicateDetection.spec.ts +112 -0
  36. package/spec/AgentRunner.spec.ts +527 -0
  37. package/spec/ConversationHooks.spec.ts +290 -0
  38. package/spec/FlinkAgent.spec.ts +310 -0
  39. package/spec/FlinkApp.onError.spec.ts +1 -2
  40. package/spec/StreamingIntegration.spec.ts +138 -0
  41. package/spec/SubAgentSupport.spec.ts +941 -0
  42. package/spec/ToolExecutor.spec.ts +360 -0
  43. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +57 -0
  44. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +59 -0
  45. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +53 -0
  46. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +53 -0
  47. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +53 -0
  48. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +55 -0
  49. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +55 -0
  50. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +58 -0
  51. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +58 -0
  52. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
  53. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
  54. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +58 -0
  55. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +76 -0
  56. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +58 -0
  57. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +59 -0
  58. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +59 -0
  59. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +55 -0
  60. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +56 -0
  61. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +55 -0
  62. package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +55 -0
  63. package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
  64. package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
  65. package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
  66. package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
  67. package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
  68. package/spec/mock-project/dist/src/FlinkApp.js +1012 -0
  69. package/spec/mock-project/dist/src/FlinkContext.js +2 -0
  70. package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
  71. package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
  72. package/spec/mock-project/dist/src/FlinkJob.js +2 -0
  73. package/spec/mock-project/dist/src/FlinkLog.js +26 -0
  74. package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
  75. package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
  76. package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
  77. package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
  78. package/spec/mock-project/dist/src/ai/AgentRunner.js +625 -0
  79. package/spec/mock-project/dist/src/ai/FlinkAgent.js +633 -0
  80. package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
  81. package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
  82. package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +220 -0
  83. package/spec/mock-project/dist/src/ai/ToolExecutor.js +237 -0
  84. package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
  85. package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
  86. package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
  87. package/spec/mock-project/dist/src/index.js +17 -69
  88. package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
  89. package/spec/mock-project/dist/src/utils.js +290 -0
  90. package/spec/mock-project/tsconfig.json +6 -1
  91. package/spec/testHelpers.ts +49 -0
  92. package/spec/utils.caseConversion.spec.ts +80 -0
  93. package/spec/utils.spec.ts +13 -13
  94. package/src/FlinkApp.ts +251 -7
  95. package/src/FlinkContext.ts +22 -0
  96. package/src/FlinkHttpHandler.ts +100 -2
  97. package/src/TypeScriptCompiler.ts +398 -7
  98. package/src/TypeScriptUtils.ts +5 -0
  99. package/src/ai/AgentRunner.ts +549 -0
  100. package/src/ai/FlinkAgent.ts +770 -0
  101. package/src/ai/FlinkTool.ts +40 -0
  102. package/src/ai/LLMAdapter.ts +96 -0
  103. package/src/ai/SubAgentExecutor.ts +199 -0
  104. package/src/ai/ToolExecutor.ts +193 -0
  105. package/src/ai/index.ts +5 -0
  106. package/src/handlers/StreamWriterFactory.ts +84 -0
  107. package/src/index.ts +4 -0
  108. package/src/utils.ts +52 -0
  109. package/tsconfig.json +6 -1
@@ -57,11 +57,48 @@ type Params = Record<string, string>;
57
57
  */
58
58
  type Query = Record<string, string | string[]>;
59
59
  /**
60
- * Flink request extends express Request but adds reqId and user object.
60
+ * Stream format for streaming handlers.
61
+ * - sse: Server-Sent Events (text/event-stream)
62
+ * - ndjson: Newline-Delimited JSON (application/x-ndjson)
63
+ */
64
+ export type StreamFormat = "sse" | "ndjson";
65
+ /**
66
+ * Stream writer interface for SSE/NDJSON streaming.
67
+ *
68
+ * Provides methods to write data chunks, handle errors, and manage the stream lifecycle.
69
+ * Only available in handlers where streamFormat is specified in RouteProps.
70
+ */
71
+ export interface StreamWriter<T = any> {
72
+ /**
73
+ * Write data to the stream.
74
+ * Data is automatically JSON-stringified and formatted according to streamFormat.
75
+ */
76
+ write(data: T): void;
77
+ /**
78
+ * Send error to client and close the stream.
79
+ */
80
+ error(error: Error | string): void;
81
+ /**
82
+ * Close the stream gracefully.
83
+ */
84
+ end(): void;
85
+ /**
86
+ * Check if stream is still open (client connected).
87
+ * Returns false if client has disconnected.
88
+ */
89
+ isOpen(): boolean;
90
+ }
91
+ /**
92
+ * Flink request extends express Request but adds reqId, user object, and userPermissions.
93
+ *
94
+ * userPermissions is populated by auth plugins during authentication and contains
95
+ * the resolved permissions array based on the plugin's configuration (roles, dynamic
96
+ * roles, custom permissions, etc.)
61
97
  */
62
98
  export type FlinkRequest<T = any, P = Params, Q = Query> = Request<P, any, T, Q> & {
63
99
  reqId: string;
64
100
  user?: any;
101
+ userPermissions?: string[];
65
102
  };
66
103
  /**
67
104
  * Route props to control routing.
@@ -93,6 +130,27 @@ export interface RouteProps {
93
130
  mockApi?: boolean;
94
131
  /**
95
132
  * Set permissions needed to access route if route requires authentication.
133
+ *
134
+ * When an array is provided, the user must have **ALL** permissions in the array (AND logic).
135
+ * To require any one of multiple permissions (OR logic), implement custom permission
136
+ * checking in the handler using the `user.permissions` array.
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * // Single permission
141
+ * permissions: "car:create"
142
+ *
143
+ * // Multiple permissions (user must have ALL)
144
+ * permissions: ["car:create", "car:premium"]
145
+ *
146
+ * // OR logic requires custom implementation
147
+ * const handler = async ({ ctx, user }) => {
148
+ * if (!user.permissions.includes("car:admin") && !user.permissions.includes("car:moderator")) {
149
+ * throw forbidden("Need admin or moderator permission");
150
+ * }
151
+ * // ... handler logic
152
+ * };
153
+ * ```
96
154
  */
97
155
  permissions?: string | string[];
98
156
  /**
@@ -136,15 +194,46 @@ export interface RouteProps {
136
194
  * @default ValidationMode.Validate
137
195
  */
138
196
  validation?: ValidationMode;
197
+ /**
198
+ * Stream format for streaming handlers (SSE or NDJSON).
199
+ *
200
+ * When specified, the handler becomes a streaming handler and receives a `stream`
201
+ * parameter for writing data chunks. Response validation is automatically skipped
202
+ * for streaming handlers (chunks are progressive, not a final JSON response).
203
+ *
204
+ * **Formats:**
205
+ * - sse: Server-Sent Events (text/event-stream) - ideal for browser EventSource API
206
+ * - ndjson: Newline-Delimited JSON (application/x-ndjson) - ideal for LLM text streaming
207
+ *
208
+ * **Example:**
209
+ * ```typescript
210
+ * export const Route: RouteProps = {
211
+ * path: "/ai/stream",
212
+ * streamFormat: "sse"
213
+ * };
214
+ *
215
+ * const handler: GetHandler<{}, void> = async ({ ctx, stream }) => {
216
+ * if (!stream) throw new Error("Stream not available");
217
+ * stream.write({ message: "Hello" });
218
+ * stream.end();
219
+ * };
220
+ * ```
221
+ */
222
+ streamFormat?: StreamFormat;
139
223
  }
140
224
  /**
141
225
  * Http handler function that handlers implements in order to
142
226
  * handle HTTP requests and return a JSON response.
227
+ *
228
+ * For streaming handlers (when streamFormat is specified in RouteProps),
229
+ * the stream parameter is available. Streaming handlers should still return
230
+ * a FlinkResponse (can be empty), but it will be ignored by the framework.
143
231
  */
144
232
  export type Handler<Ctx extends FlinkContext, ReqSchema = any, ResSchema = any, P extends Params = Params, Q extends Query = Query> = (props: {
145
233
  req: FlinkRequest<ReqSchema, P, Q>;
146
234
  ctx: Ctx;
147
235
  origin?: string;
236
+ stream?: StreamWriter;
148
237
  }) => Promise<FlinkResponse<ResSchema | FlinkError>>;
149
238
  /**
150
239
  * Http handler function specifically for GET requests as those does
@@ -18,6 +18,28 @@ declare class TypeScriptCompiler {
18
18
  */
19
19
  private tsSchemasSymbolsToImports;
20
20
  constructor(cwd: string);
21
+ /**
22
+ * Loads additional source paths from tsconfig.json's flink configuration.
23
+ * Allows projects to specify extra directories to include in compilation.
24
+ *
25
+ * Example tsconfig.json:
26
+ * {
27
+ * "flink": {
28
+ * "sourcePaths": ["src/custom-types/**\/*.ts", "src/utils/**\/*.ts"]
29
+ * }
30
+ * }
31
+ */
32
+ private getFlinkSourcePaths;
33
+ /**
34
+ * Recursively resolves and adds imported files to the project.
35
+ * This ensures that files imported by handlers, schemas, etc. are available for type resolution.
36
+ *
37
+ * Handles three types of imports:
38
+ * 1. Relative imports (./foo, ../bar) - always resolved
39
+ * 2. Workspace package imports (@mycompany/shared) - resolved if symlinked outside node_modules
40
+ * 3. External packages (lodash, express) - skipped to avoid loading entire dependency trees
41
+ */
42
+ private resolveImportedFiles;
21
43
  /**
22
44
  * Detects if the project is using ESM (ECMAScript Modules)
23
45
  * by checking type in package.json.
@@ -41,6 +63,15 @@ declare class TypeScriptCompiler {
41
63
  * exists. Warnings will be passed thru but logged.
42
64
  */
43
65
  getPreEmitDiagnostics(): boolean;
66
+ /**
67
+ * Finds a variable declaration by its TypeScript type name.
68
+ * This allows finding variables based on their type annotation rather than variable name.
69
+ *
70
+ * @param sf Source file to search in
71
+ * @param typeName Name of the type to search for (e.g., "FlinkToolProps", "FlinkAgentProps")
72
+ * @returns The first matching variable declaration, or undefined if not found
73
+ */
74
+ private findVariableDeclarationByType;
44
75
  /**
45
76
  * Scans project for handlers and add those to Flink
46
77
  * "singleton" property `autoRegisteredHandlers` so they can
@@ -55,6 +86,17 @@ declare class TypeScriptCompiler {
55
86
  */
56
87
  private parseHandlerDir;
57
88
  parseRepos(): Promise<SourceFile>;
89
+ /**
90
+ * Scans project for tools and adds those to Flink
91
+ * "singleton" property `autoRegisteredTools` so they can
92
+ * be registered during start.
93
+ */
94
+ parseTools(): Promise<SourceFile>;
95
+ /**
96
+ * Scans project for agents and validates tool references.
97
+ * Agents are declarative (no default function).
98
+ */
99
+ parseAgents(): Promise<SourceFile>;
58
100
  /**
59
101
  * Generates a start script that will import references to handlers, repos and the
60
102
  * actual Flink app to start.
@@ -130,11 +130,100 @@ var TypeScriptCompiler = /** @class */ (function () {
130
130
  this.project = new ts_morph_1.Project({
131
131
  tsConfigFilePath: tsConfigPath,
132
132
  compilerOptions: compilerOptions,
133
+ skipAddingFilesFromTsConfig: true, // Don't auto-load all files - we'll add specific directories we need
133
134
  });
135
+ // Load Flink-specific source paths from tsconfig.json if available
136
+ var additionalSourcePaths = this.getFlinkSourcePaths(tsConfigPath);
137
+ // Only add source files from directories we actually need to process
138
+ // This prevents loading all files from tsconfig include patterns and reduces memory usage
139
+ var defaultSourcePaths = [
140
+ (0, path_1.join)(cwd, "src/handlers/**/*.ts"),
141
+ (0, path_1.join)(cwd, "src/repos/**/*.ts"),
142
+ (0, path_1.join)(cwd, "src/tools/**/*.ts"),
143
+ (0, path_1.join)(cwd, "src/agents/**/*.ts"),
144
+ (0, path_1.join)(cwd, "src/jobs/**/*.ts"),
145
+ (0, path_1.join)(cwd, "src/index.ts"),
146
+ (0, path_1.join)(cwd, "src/schemas/**/*.ts"), // Include schemas for type resolution
147
+ ];
148
+ var allSourcePaths = __spreadArray(__spreadArray([], defaultSourcePaths, true), additionalSourcePaths, true);
149
+ this.project.addSourceFilesAtPaths(allSourcePaths);
150
+ // Resolve imports: add any files imported by our source files
151
+ // This ensures schemas that import types from other locations work correctly
152
+ this.resolveImportedFiles();
134
153
  console.log("Loaded", this.project.getSourceFiles().length, "source file(s) from", cwd);
135
154
  console.log("Module system:", this.isEsm ? "ESM" : "CommonJS");
136
155
  console.log("Using module:", compilerOptions.module === ts_morph_1.ts.ModuleKind.ESNext ? "ESNext" : "CommonJS");
137
156
  }
157
+ /**
158
+ * Loads additional source paths from tsconfig.json's flink configuration.
159
+ * Allows projects to specify extra directories to include in compilation.
160
+ *
161
+ * Example tsconfig.json:
162
+ * {
163
+ * "flink": {
164
+ * "sourcePaths": ["src/custom-types/**\/*.ts", "src/utils/**\/*.ts"]
165
+ * }
166
+ * }
167
+ */
168
+ TypeScriptCompiler.prototype.getFlinkSourcePaths = function (tsConfigPath) {
169
+ var _this = this;
170
+ try {
171
+ if (fs_1.default.existsSync(tsConfigPath)) {
172
+ var tsConfigContent = fs_1.default.readFileSync(tsConfigPath, "utf8");
173
+ var tsConfig = JSON.parse(tsConfigContent);
174
+ if (tsConfig.flink && Array.isArray(tsConfig.flink.sourcePaths)) {
175
+ console.log("Found Flink-specific source paths:", tsConfig.flink.sourcePaths);
176
+ return tsConfig.flink.sourcePaths.map(function (path) { return (0, path_1.join)(_this.cwd, path); });
177
+ }
178
+ }
179
+ }
180
+ catch (error) {
181
+ console.warn("Error reading Flink source paths from tsconfig.json:", error);
182
+ }
183
+ return [];
184
+ };
185
+ /**
186
+ * Recursively resolves and adds imported files to the project.
187
+ * This ensures that files imported by handlers, schemas, etc. are available for type resolution.
188
+ *
189
+ * Handles three types of imports:
190
+ * 1. Relative imports (./foo, ../bar) - always resolved
191
+ * 2. Workspace package imports (@mycompany/shared) - resolved if symlinked outside node_modules
192
+ * 3. External packages (lodash, express) - skipped to avoid loading entire dependency trees
193
+ */
194
+ TypeScriptCompiler.prototype.resolveImportedFiles = function () {
195
+ var processedFiles = new Set();
196
+ var filesToProcess = __spreadArray([], this.project.getSourceFiles(), true);
197
+ while (filesToProcess.length > 0) {
198
+ var sourceFile = filesToProcess.pop();
199
+ var filePath = sourceFile.getFilePath();
200
+ if (processedFiles.has(filePath)) {
201
+ continue;
202
+ }
203
+ processedFiles.add(filePath);
204
+ // Get all import declarations
205
+ var importDeclarations = sourceFile.getImportDeclarations();
206
+ for (var _i = 0, importDeclarations_1 = importDeclarations; _i < importDeclarations_1.length; _i++) {
207
+ var importDecl = importDeclarations_1[_i];
208
+ var moduleSpecifier = importDecl.getModuleSpecifierValue();
209
+ // Try to resolve the imported file
210
+ var moduleSourceFile = importDecl.getModuleSpecifierSourceFile();
211
+ if (moduleSourceFile) {
212
+ var importedPath = moduleSourceFile.getFilePath();
213
+ // For relative imports (./foo, ../bar), always include
214
+ var isRelativeImport = moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/");
215
+ // For package imports (@foo/bar, foo), check if resolved path is outside node_modules
216
+ // This handles workspace packages that are symlinked (pnpm, yarn workspaces)
217
+ var isWorkspacePackage = !isRelativeImport && !importedPath.includes("node_modules");
218
+ // Skip if it's in node_modules (external package) or already processed
219
+ if ((isRelativeImport || isWorkspacePackage) && !processedFiles.has(importedPath)) {
220
+ filesToProcess.push(moduleSourceFile);
221
+ }
222
+ }
223
+ }
224
+ }
225
+ console.log("Resolved imports, total files loaded:", processedFiles.size);
226
+ };
138
227
  /**
139
228
  * Detects if the project is using ESM (ECMAScript Modules)
140
229
  * by checking type in package.json.
@@ -244,6 +333,48 @@ var TypeScriptCompiler = /** @class */ (function () {
244
333
  }
245
334
  return true;
246
335
  };
336
+ /**
337
+ * Finds a variable declaration by its TypeScript type name.
338
+ * This allows finding variables based on their type annotation rather than variable name.
339
+ *
340
+ * @param sf Source file to search in
341
+ * @param typeName Name of the type to search for (e.g., "FlinkToolProps", "FlinkAgentProps")
342
+ * @returns The first matching variable declaration, or undefined if not found
343
+ */
344
+ TypeScriptCompiler.prototype.findVariableDeclarationByType = function (sf, typeName) {
345
+ var _a;
346
+ // Get all variable declarations from the source file
347
+ var variableDeclarations = sf.getVariableDeclarations();
348
+ for (var _i = 0, variableDeclarations_1 = variableDeclarations; _i < variableDeclarations_1.length; _i++) {
349
+ var varDecl = variableDeclarations_1[_i];
350
+ // Check if the variable is exported
351
+ var variableStatement = varDecl.getVariableStatement();
352
+ if (!(variableStatement === null || variableStatement === void 0 ? void 0 : variableStatement.isExported())) {
353
+ continue;
354
+ }
355
+ // Check if it's a const declaration
356
+ if (((_a = varDecl.getVariableStatement()) === null || _a === void 0 ? void 0 : _a.getDeclarationKind()) !== ts_morph_1.VariableDeclarationKind.Const) {
357
+ continue;
358
+ }
359
+ // Get the type node (explicit type annotation)
360
+ var typeNode = varDecl.getTypeNode();
361
+ if (!typeNode) {
362
+ continue;
363
+ }
364
+ // Check if it's a type reference
365
+ if (typeNode.getKind() === ts_morph_1.SyntaxKind.TypeReference) {
366
+ var typeRef = typeNode;
367
+ var typeRefName = typeRef.getTypeName();
368
+ // Handle both simple type references (FlinkToolProps) and qualified names (flink.FlinkToolProps)
369
+ var typeRefText = typeRefName.getText();
370
+ // Check if the type name matches (either exact match or ends with the type name for qualified references)
371
+ if (typeRefText === typeName || typeRefText.endsWith(".".concat(typeName))) {
372
+ return varDecl;
373
+ }
374
+ }
375
+ }
376
+ return undefined;
377
+ };
247
378
  /**
248
379
  * Scans project for handlers and add those to Flink
249
380
  * "singleton" property `autoRegisteredHandlers` so they can
@@ -408,6 +539,217 @@ var TypeScriptCompiler = /** @class */ (function () {
408
539
  });
409
540
  });
410
541
  };
542
+ /**
543
+ * Scans project for tools and adds those to Flink
544
+ * "singleton" property `autoRegisteredTools` so they can
545
+ * be registered during start.
546
+ */
547
+ TypeScriptCompiler.prototype.parseTools = function () {
548
+ return __awaiter(this, void 0, void 0, function () {
549
+ var generatedFile, toolsArr, imports, i, _i, _a, sf, namespaceImport, toolPropsVar, toolPropsExportName, existingFile;
550
+ return __generator(this, function (_b) {
551
+ switch (_b.label) {
552
+ case 0:
553
+ generatedFile = this.createSourceFile(["generatedTools.ts"], "// Generated ".concat(new Date(), "\nimport { autoRegisteredTools } from \"@flink-app/flink\";\nexport const tools = [];\nautoRegisteredTools.push(...tools);\n "));
554
+ toolsArr = generatedFile.getVariableDeclarationOrThrow("tools").getFirstDescendantByKindOrThrow(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
555
+ imports = [];
556
+ i = 0;
557
+ for (_i = 0, _a = this.project.getSourceFiles(); _i < _a.length; _i++) {
558
+ sf = _a[_i];
559
+ if (!sf.getFilePath().includes("src/tools/")) {
560
+ continue;
561
+ }
562
+ console.log("Detected tool ".concat(sf.getBaseName()));
563
+ namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
564
+ imports.push({
565
+ defaultImport: "* as " + namespaceImport,
566
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
567
+ });
568
+ toolPropsVar = this.findVariableDeclarationByType(sf, "FlinkToolProps");
569
+ toolPropsExportName = (toolPropsVar === null || toolPropsVar === void 0 ? void 0 : toolPropsVar.getName()) || "Tool";
570
+ existingFile = sf.getVariableStatements().filter(function (vs) {
571
+ var varNames = vs.getDeclarations().map(function (d) { return d.getName(); });
572
+ return varNames.includes("__file");
573
+ });
574
+ existingFile.forEach(function (v) { return v.remove(); });
575
+ // Append metadata to source file
576
+ sf.addVariableStatement({
577
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
578
+ isExported: true,
579
+ declarations: [
580
+ {
581
+ name: "__file",
582
+ initializer: "\"".concat(sf.getBaseName(), "\""),
583
+ },
584
+ ],
585
+ });
586
+ // Create an object that wraps the namespace and provides named access to the tool props
587
+ toolsArr.insertElement(i, "{...".concat(namespaceImport, ", Tool: ").concat(namespaceImport, ".").concat(toolPropsExportName, "}"));
588
+ i++;
589
+ }
590
+ generatedFile.addImportDeclarations(imports);
591
+ return [4 /*yield*/, generatedFile.save()];
592
+ case 1:
593
+ _b.sent();
594
+ return [2 /*return*/, generatedFile];
595
+ }
596
+ });
597
+ });
598
+ };
599
+ /**
600
+ * Scans project for agents and validates tool references.
601
+ * Agents are declarative (no default function).
602
+ */
603
+ TypeScriptCompiler.prototype.parseAgents = function () {
604
+ return __awaiter(this, void 0, void 0, function () {
605
+ var generatedFile, agentsArr, imports, registeredToolIds, _i, _a, sf, toolExport, props, idProp, idValue, i, _b, _c, sf, agentClass, toolsProperty, initializer, toolsArray, toolElements, _loop_1, _d, toolElements_1, toolElement, className, existingFile;
606
+ var _e;
607
+ return __generator(this, function (_f) {
608
+ switch (_f.label) {
609
+ case 0:
610
+ generatedFile = this.createSourceFile(["generatedAgents.ts"], "// Generated ".concat(new Date(), "\nimport { autoRegisteredAgents } from \"@flink-app/flink\";\nexport const agents = [];\nautoRegisteredAgents.push(...agents);\n "));
611
+ agentsArr = generatedFile.getVariableDeclarationOrThrow("agents").getFirstDescendantByKindOrThrow(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
612
+ imports = [];
613
+ registeredToolIds = new Set();
614
+ for (_i = 0, _a = this.project.getSourceFiles(); _i < _a.length; _i++) {
615
+ sf = _a[_i];
616
+ if (sf.getFilePath().includes("src/tools/")) {
617
+ toolExport = this.findVariableDeclarationByType(sf, "FlinkToolProps");
618
+ if (toolExport) {
619
+ props = toolExport.getFirstDescendantByKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
620
+ idProp = (props === null || props === void 0 ? void 0 : props.getProperty("id")) || (props === null || props === void 0 ? void 0 : props.getProperty("name"));
621
+ if (idProp) {
622
+ idValue = idProp.getLastChildByKind(ts_morph_1.SyntaxKind.StringLiteral);
623
+ if (idValue) {
624
+ registeredToolIds.add(idValue.getLiteralText());
625
+ }
626
+ }
627
+ }
628
+ }
629
+ }
630
+ i = 0;
631
+ for (_b = 0, _c = this.project.getSourceFiles(); _b < _c.length; _b++) {
632
+ sf = _c[_b];
633
+ if (!sf.getFilePath().includes("src/agents/")) {
634
+ continue;
635
+ }
636
+ agentClass = sf.getClasses().find(function (cls) {
637
+ var baseClass = cls.getBaseClass();
638
+ return (baseClass === null || baseClass === void 0 ? void 0 : baseClass.getName()) === "FlinkAgent";
639
+ });
640
+ if (!agentClass) {
641
+ // Skip files without FlinkAgent class
642
+ continue;
643
+ }
644
+ console.log("Detected agent ".concat(sf.getBaseName()));
645
+ toolsProperty = agentClass.getProperty("tools");
646
+ if (toolsProperty) {
647
+ initializer = toolsProperty.getInitializer();
648
+ if (initializer && initializer.getKind() === ts_morph_1.SyntaxKind.ArrayLiteralExpression) {
649
+ toolsArray = initializer;
650
+ toolElements = toolsArray.getElements();
651
+ _loop_1 = function (toolElement) {
652
+ var toolName = void 0;
653
+ // Handle string literals, method calls, and identifier references (tool imports)
654
+ if (toolElement.getKind() === ts_morph_1.SyntaxKind.StringLiteral) {
655
+ // Direct string: "tool-name"
656
+ toolName = toolElement.getText().replace(/['"]/g, "");
657
+ }
658
+ else if (toolElement.getKind() === ts_morph_1.SyntaxKind.CallExpression) {
659
+ // Method call: this.useTool("tool-name")
660
+ var args = toolElement.getArguments();
661
+ if (args.length > 0 && args[0].getKind() === ts_morph_1.SyntaxKind.StringLiteral) {
662
+ toolName = args[0].getText().replace(/['"]/g, "");
663
+ }
664
+ else {
665
+ console.warn("Agent ".concat(sf.getBaseName(), " has non-string tool reference, skipping validation"));
666
+ return "continue";
667
+ }
668
+ }
669
+ else if (toolElement.getKind() === ts_morph_1.SyntaxKind.Identifier) {
670
+ // Tool file reference (imported): SearchCarsByBrandTool
671
+ // Look up the import to find the actual tool file
672
+ var importName_1 = toolElement.getText();
673
+ var importDecl = sf.getImportDeclarations().find(function (imp) {
674
+ var namedImports = imp.getNamedImports();
675
+ return namedImports.some(function (ni) { return ni.getName() === importName_1; });
676
+ });
677
+ if (!importDecl) {
678
+ // Try namespace import (* as Foo)
679
+ var namespaceImport = sf.getImportDeclarations().find(function (imp) {
680
+ var _a;
681
+ return ((_a = imp.getNamespaceImport()) === null || _a === void 0 ? void 0 : _a.getText()) === importName_1;
682
+ });
683
+ if (namespaceImport) {
684
+ var moduleSpecifier = namespaceImport.getModuleSpecifierValue();
685
+ // Extract tool ID from the tool file path
686
+ // e.g., "../tools/SearchCarsByBrandTool" -> find in registeredToolIds
687
+ var toolFileName = (_e = moduleSpecifier.split("/").pop()) === null || _e === void 0 ? void 0 : _e.replace(/\.ts$/, "");
688
+ var matchingTool = Array.from(registeredToolIds).find(function (id) {
689
+ // Try to match by searching for the tool ID
690
+ // This is a heuristic - we'll validate it exists
691
+ return true; // Skip validation for imported tools for now
692
+ });
693
+ return "continue";
694
+ }
695
+ console.warn("Agent ".concat(sf.getBaseName(), " references tool \"").concat(importName_1, "\" but it's not imported, skipping validation"));
696
+ return "continue";
697
+ }
698
+ return "continue";
699
+ }
700
+ else {
701
+ console.warn("Agent ".concat(sf.getBaseName(), " has unexpected tool reference format: ").concat(toolElement.getText()));
702
+ return "continue";
703
+ }
704
+ if (!registeredToolIds.has(toolName)) {
705
+ console.error("Agent ".concat(sf.getBaseName(), " references tool \"").concat(toolName, "\" which does not exist"));
706
+ throw new Error("Invalid tool reference in agent ".concat(sf.getBaseName()));
707
+ }
708
+ };
709
+ for (_d = 0, toolElements_1 = toolElements; _d < toolElements_1.length; _d++) {
710
+ toolElement = toolElements_1[_d];
711
+ _loop_1(toolElement);
712
+ }
713
+ }
714
+ }
715
+ className = agentClass.getName();
716
+ if (!className) {
717
+ console.error("Agent class in ".concat(sf.getBaseName(), " has no name"));
718
+ continue;
719
+ }
720
+ imports.push({
721
+ defaultImport: className,
722
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
723
+ });
724
+ existingFile = sf.getVariableStatements().filter(function (vs) {
725
+ var varNames = vs.getDeclarations().map(function (d) { return d.getName(); });
726
+ return varNames.includes("__file");
727
+ });
728
+ existingFile.forEach(function (v) { return v.remove(); });
729
+ // Append metadata
730
+ sf.addVariableStatement({
731
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
732
+ isExported: true,
733
+ declarations: [
734
+ {
735
+ name: "__file",
736
+ initializer: "\"".concat(sf.getBaseName(), "\""),
737
+ },
738
+ ],
739
+ });
740
+ // Register the agent class
741
+ agentsArr.insertElement(i, "{ default: ".concat(className, ", __file: \"").concat(sf.getBaseName(), "\" }"));
742
+ i++;
743
+ }
744
+ generatedFile.addImportDeclarations(imports);
745
+ return [4 /*yield*/, generatedFile.save()];
746
+ case 1:
747
+ _f.sent();
748
+ return [2 /*return*/, generatedFile];
749
+ }
750
+ });
751
+ });
752
+ };
411
753
  /**
412
754
  * Generates a start script that will import references to handlers, repos and the
413
755
  * actual Flink app to start.
@@ -426,7 +768,7 @@ var TypeScriptCompiler = /** @class */ (function () {
426
768
  console.error("Cannot find entry script '".concat(appEntryScript, "'"));
427
769
  return [2 /*return*/, process.exit(1)];
428
770
  }
429
- sf = this.createSourceFile(["start.ts"], "// Generated ".concat(new Date(), "\nimport \"./generatedHandlers").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedRepos").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedJobs").concat(this.isEsm ? ".js" : "", "\";\nimport \"..").concat(appEntryScript.replace(/\.ts/g, "")).concat(this.isEsm ? ".js" : "", "\";\nexport default {}; // Export an empty object to make it a module\n"));
771
+ sf = this.createSourceFile(["start.ts"], "// Generated ".concat(new Date(), "\nimport \"./generatedHandlers").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedRepos").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedTools").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedAgents").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedJobs").concat(this.isEsm ? ".js" : "", "\";\nimport \"..").concat(appEntryScript.replace(/\.ts/g, "")).concat(this.isEsm ? ".js" : "", "\";\nexport default {}; // Export an empty object to make it a module\n"));
430
772
  return [4 /*yield*/, sf.save()];
431
773
  case 1:
432
774
  _a.sent();
@@ -528,7 +870,7 @@ var TypeScriptCompiler = /** @class */ (function () {
528
870
  return [2 /*return*/]; // 'any' indicates that no schema is used
529
871
  }
530
872
  schemaText = schema.getText();
531
- if (schemaText === 'void' || schemaText === 'undefined') {
873
+ if (schemaText === "void" || schemaText === "undefined") {
532
874
  return [2 /*return*/];
533
875
  }
534
876
  handlerFileName = handlerFile.getBaseNameWithoutExtension().replace(/\./g, "_");
@@ -564,7 +906,7 @@ var TypeScriptCompiler = /** @class */ (function () {
564
906
  if (interfaceNameMatches) {
565
907
  for (_d = 0, interfaceNameMatches_1 = interfaceNameMatches; _d < interfaceNameMatches_1.length; _d++) {
566
908
  match = interfaceNameMatches_1[_d];
567
- referencedInterfaceName = match.replace(/\s*\[$/, '').trim();
909
+ referencedInterfaceName = match.replace(/\s*\[$/, "").trim();
568
910
  referencedInterfaceDecl = handlerFile.getInterface(referencedInterfaceName) || handlerFile.getTypeAlias(referencedInterfaceName);
569
911
  if (referencedInterfaceDecl) {
570
912
  // Interface is in same file - copy it and all its dependencies recursively
@@ -663,7 +1005,7 @@ var TypeScriptCompiler = /** @class */ (function () {
663
1005
  if (interfaceNameMatches) {
664
1006
  for (_j = 0, interfaceNameMatches_2 = interfaceNameMatches; _j < interfaceNameMatches_2.length; _j++) {
665
1007
  match = interfaceNameMatches_2[_j];
666
- interfaceName = match.replace(/\s*\[$/, '').trim();
1008
+ interfaceName = match.replace(/\s*\[$/, "").trim();
667
1009
  interfaceDecl = handlerFile.getInterface(interfaceName) || handlerFile.getTypeAlias(interfaceName);
668
1010
  if (interfaceDecl) {
669
1011
  // Interface is in same file - copy it and all its dependencies recursively
@@ -191,6 +191,10 @@ function getTypeMetadata(type) {
191
191
  if (!type || ["void", "any"].includes(type.getText())) {
192
192
  return [];
193
193
  }
194
+ // Handle empty object literal {} (used in streaming handlers)
195
+ if (type.getText() === "{}") {
196
+ return [];
197
+ }
194
198
  var symbol = getSymbolOrAlias(type);
195
199
  if (!symbol) {
196
200
  throw new Error("Could not get type symbol for type: " + type.getText());
@@ -0,0 +1,39 @@
1
+ import { FlinkAgentProps, AgentExecuteInput, StreamChunk } from "./FlinkAgent";
2
+ import { ToolExecutor } from "./ToolExecutor";
3
+ import { LLMAdapter } from "./LLMAdapter";
4
+ export declare class AgentRunner {
5
+ private agentProps;
6
+ private tools;
7
+ private agentName?;
8
+ private llmAdapter;
9
+ private maxTokens;
10
+ private temperature;
11
+ private maxSteps;
12
+ private timeoutMs;
13
+ private maxSubAgentDepth;
14
+ constructor(agentProps: FlinkAgentProps, tools: Map<string, ToolExecutor<any>>, llmAdapters: Map<string, LLMAdapter>, agentName?: string | undefined);
15
+ /**
16
+ * Phase 1: Stream generator that yields complete event on finish
17
+ * Phase 2: Will yield text_delta and tool events during execution
18
+ */
19
+ streamGenerator(input: AgentExecuteInput): AsyncGenerator<StreamChunk>;
20
+ /**
21
+ * Convert Message[] to LLM message format
22
+ * Supports multi-turn conversations with history
23
+ */
24
+ private convertMessages;
25
+ private getToolSchemas;
26
+ /**
27
+ * Filter tools based on user permissions
28
+ * Only returns schemas for tools the user has permission to use
29
+ *
30
+ * @param user - User object
31
+ * @param userPermissions - Optional resolved permissions from auth plugin (preferred)
32
+ */
33
+ private filterToolsByPermissions;
34
+ /**
35
+ * Build delegation chain from metadata for error messages
36
+ * Extracts parent agent IDs to show the full call stack
37
+ */
38
+ private buildDelegationChain;
39
+ }