@flink-app/flink 1.0.0 → 2.0.0-alpha.49

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 +12 -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 +366 -8
  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 +420 -9
  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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @flink-app/flink
2
2
 
3
+ ## 2.0.0-alpha.49
4
+
5
+ ### Patch Changes
6
+
7
+ - Typescript include fix
8
+
9
+ ## 2.0.0-alpha.48
10
+
11
+ ### Minor Changes
12
+
13
+ - AI features
14
+
3
15
  ## 1.0.0
4
16
 
5
17
  ### Minor Changes
package/cli/build.ts CHANGED
@@ -40,7 +40,14 @@ module.exports = async function run(args: string[]) {
40
40
  process.exit(1);
41
41
  }
42
42
 
43
- await Promise.all([compiler.parseRepos(), compiler.parseHandlers(exclude.split(",")), compiler.parseJobs(), compiler.generateStartScript()]);
43
+ await Promise.all([
44
+ compiler.parseRepos(),
45
+ compiler.parseHandlers(exclude.split(",")),
46
+ compiler.parseTools(),
47
+ compiler.parseAgents(),
48
+ compiler.parseJobs(),
49
+ compiler.generateStartScript(),
50
+ ]);
44
51
 
45
52
  console.log(`Compilation done, took ${Date.now() - startTime}ms`);
46
53
 
package/cli/run.ts CHANGED
@@ -63,7 +63,14 @@ module.exports = async function run(args: string[]) {
63
63
  process.exit(1);
64
64
  }
65
65
 
66
- await Promise.all([compiler.parseRepos(), compiler.parseHandlers(), compiler.parseJobs(), compiler.generateStartScript(entry)]);
66
+ await Promise.all([
67
+ compiler.parseRepos(),
68
+ compiler.parseHandlers(),
69
+ compiler.parseTools(),
70
+ compiler.parseAgents(),
71
+ compiler.parseJobs(),
72
+ compiler.generateStartScript(entry)
73
+ ]);
67
74
 
68
75
  console.log(`Compilation done, took ${Date.now() - startTime}ms`);
69
76
 
package/dist/cli/build.js CHANGED
@@ -65,7 +65,14 @@ module.exports = function run(args) {
65
65
  if (!compiler.getPreEmitDiagnostics()) {
66
66
  process.exit(1);
67
67
  }
68
- return [4 /*yield*/, Promise.all([compiler.parseRepos(), compiler.parseHandlers(exclude.split(",")), compiler.parseJobs(), compiler.generateStartScript()])];
68
+ return [4 /*yield*/, Promise.all([
69
+ compiler.parseRepos(),
70
+ compiler.parseHandlers(exclude.split(",")),
71
+ compiler.parseTools(),
72
+ compiler.parseAgents(),
73
+ compiler.parseJobs(),
74
+ compiler.generateStartScript(),
75
+ ])];
69
76
  case 2:
70
77
  _a.sent();
71
78
  console.log("Compilation done, took ".concat(Date.now() - startTime, "ms"));
package/dist/cli/run.js CHANGED
@@ -83,7 +83,14 @@ module.exports = function run(args) {
83
83
  if (!compiler.getPreEmitDiagnostics()) {
84
84
  process.exit(1);
85
85
  }
86
- return [4 /*yield*/, Promise.all([compiler.parseRepos(), compiler.parseHandlers(), compiler.parseJobs(), compiler.generateStartScript(entry)])];
86
+ return [4 /*yield*/, Promise.all([
87
+ compiler.parseRepos(),
88
+ compiler.parseHandlers(),
89
+ compiler.parseTools(),
90
+ compiler.parseAgents(),
91
+ compiler.parseJobs(),
92
+ compiler.generateStartScript(entry)
93
+ ])];
87
94
  case 2:
88
95
  _a.sent();
89
96
  console.log("Compilation done, took ".concat(Date.now() - startTime, "ms"));
@@ -3,6 +3,9 @@ import express, { Express } from "express";
3
3
  import { JSONSchema7 } from "json-schema";
4
4
  import { Db, MongoClient } from "mongodb";
5
5
  import { ToadScheduler } from "toad-scheduler";
6
+ import { FlinkAgentFile } from "./ai/FlinkAgent";
7
+ import { FlinkToolFile } from "./ai/FlinkTool";
8
+ import { LLMAdapter } from "./ai/LLMAdapter";
6
9
  import { FlinkAuthPlugin } from "./auth/FlinkAuthPlugin";
7
10
  import { FlinkContext } from "./FlinkContext";
8
11
  import { FlinkError } from "./FlinkErrors";
@@ -39,6 +42,16 @@ export declare const autoRegisteredRepos: {
39
42
  * are picked up by TypeScript compiler
40
43
  */
41
44
  export declare const autoRegisteredJobs: FlinkJobFile[];
45
+ /**
46
+ * This will be populated at compile time when the apps tools
47
+ * are picked up by TypeScript compiler
48
+ */
49
+ export declare const autoRegisteredTools: FlinkToolFile[];
50
+ /**
51
+ * This will be populated at compile time when the apps agents
52
+ * are picked up by TypeScript compiler
53
+ */
54
+ export declare const autoRegisteredAgents: FlinkAgentFile[];
42
55
  export interface FlinkOptions {
43
56
  /**
44
57
  * Name of application, will only show in logs and in HTTP header.
@@ -118,6 +131,16 @@ export interface FlinkOptions {
118
131
  */
119
132
  enabled?: boolean;
120
133
  };
134
+ /**
135
+ * AI configuration for agents and tools
136
+ * Register LLM adapters with custom IDs (e.g., "anthropic", "openai", "anthropic-eu", etc.)
137
+ * This allows multiple adapters of the same type with different configurations
138
+ */
139
+ ai?: {
140
+ llms?: {
141
+ [id: string]: LLMAdapter;
142
+ };
143
+ };
121
144
  /**
122
145
  * If true, the HTTP server will be disabled.
123
146
  * Only useful when starting a Flink app for testing purposes.
@@ -226,6 +249,9 @@ export declare class FlinkApp<C extends FlinkContext> {
226
249
  private expressServer;
227
250
  private onError?;
228
251
  private repos;
252
+ private llmAdapters;
253
+ private tools;
254
+ private agents;
229
255
  /**
230
256
  * Internal cache used to track registered handlers and potentially any overlapping routes
231
257
  */
@@ -253,11 +279,18 @@ export declare class FlinkApp<C extends FlinkContext> {
253
279
  private registerAutoRegisterableHandlers;
254
280
  private registerAutoRegisterableJobs;
255
281
  addRepo(instanceName: string, repoInstance: FlinkRepo<C, any>): void;
282
+ private registerAutoRegisterableTools;
283
+ private registerAutoRegisterableAgents;
256
284
  /**
257
285
  * Constructs the app context. Will inject context in all components
258
286
  * except for handlers which are handled in later stage.
259
287
  */
260
288
  private buildContext;
289
+ /**
290
+ * Initialize agents after they've been registered and context is ready
291
+ * Must be called after registerAutoRegisterableAgents()
292
+ */
293
+ private initializeAgents;
261
294
  /**
262
295
  * Connects to database.
263
296
  */
@@ -50,7 +50,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
50
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
51
51
  };
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
- exports.FlinkApp = exports.autoRegisteredJobs = exports.autoRegisteredRepos = exports.autoRegisteredHandlers = exports.expressFn = void 0;
53
+ exports.FlinkApp = exports.autoRegisteredAgents = exports.autoRegisteredTools = exports.autoRegisteredJobs = exports.autoRegisteredRepos = exports.autoRegisteredHandlers = exports.expressFn = void 0;
54
54
  var ajv_1 = __importDefault(require("ajv"));
55
55
  var ajv_formats_1 = __importDefault(require("ajv-formats"));
56
56
  var body_parser_1 = __importDefault(require("body-parser"));
@@ -64,6 +64,7 @@ var uuid_1 = require("uuid");
64
64
  var FlinkErrors_1 = require("./FlinkErrors");
65
65
  var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
66
66
  var FlinkLog_1 = require("./FlinkLog");
67
+ var StreamWriterFactory_1 = require("./handlers/StreamWriterFactory");
67
68
  var mock_data_generator_1 = __importDefault(require("./mock-data-generator"));
68
69
  var utils_1 = require("./utils");
69
70
  var ajv = new ajv_1.default();
@@ -93,8 +94,19 @@ exports.autoRegisteredRepos = [];
93
94
  * are picked up by TypeScript compiler
94
95
  */
95
96
  exports.autoRegisteredJobs = [];
97
+ /**
98
+ * This will be populated at compile time when the apps tools
99
+ * are picked up by TypeScript compiler
100
+ */
101
+ exports.autoRegisteredTools = [];
102
+ /**
103
+ * This will be populated at compile time when the apps agents
104
+ * are picked up by TypeScript compiler
105
+ */
106
+ exports.autoRegisteredAgents = [];
96
107
  var FlinkApp = /** @class */ (function () {
97
108
  function FlinkApp(opts) {
109
+ var _a;
98
110
  this.handlers = [];
99
111
  this.started = false;
100
112
  this.debug = false;
@@ -102,6 +114,9 @@ var FlinkApp = /** @class */ (function () {
102
114
  this.routingConfigured = false;
103
115
  this.disableHttpServer = false;
104
116
  this.repos = {};
117
+ this.llmAdapters = new Map();
118
+ this.tools = {};
119
+ this.agents = {}; // FlinkAgent<C> instances
105
120
  /**
106
121
  * Internal cache used to track registered handlers and potentially any overlapping routes
107
122
  */
@@ -124,6 +139,11 @@ var FlinkApp = /** @class */ (function () {
124
139
  this.disableHttpServer = !!opts.disableHttpServer;
125
140
  this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
126
141
  this.onError = opts.onError;
142
+ // Register LLM adapters if configured
143
+ if ((_a = opts.ai) === null || _a === void 0 ? void 0 : _a.llms) {
144
+ // Convert plain object to Map for internal use
145
+ this.llmAdapters = new Map(Object.entries(opts.ai.llms));
146
+ }
127
147
  }
128
148
  Object.defineProperty(FlinkApp.prototype, "ctx", {
129
149
  get: function () {
@@ -152,13 +172,42 @@ var FlinkApp = /** @class */ (function () {
152
172
  offsetTime = Date.now();
153
173
  FlinkLog_1.log.bgColorLog("cyan", "Init db took ".concat(offsetTime - startTime, " ms"));
154
174
  }
175
+ // Build initial context (without agents - they'll be added later)
155
176
  return [4 /*yield*/, this.buildContext()];
156
177
  case 2:
178
+ // Build initial context (without agents - they'll be added later)
157
179
  _e.sent();
158
180
  if (this.debug) {
159
181
  FlinkLog_1.log.bgColorLog("cyan", "Build context took ".concat(Date.now() - offsetTime, " ms"));
160
182
  offsetTime = Date.now();
161
183
  }
184
+ // Register tools (needs context for ToolExecutor)
185
+ return [4 /*yield*/, this.registerAutoRegisterableTools()];
186
+ case 3:
187
+ // Register tools (needs context for ToolExecutor)
188
+ _e.sent();
189
+ if (this.debug) {
190
+ FlinkLog_1.log.bgColorLog("cyan", "Register tools took ".concat(Date.now() - offsetTime, " ms"));
191
+ offsetTime = Date.now();
192
+ }
193
+ // Register agents (creates agent instances)
194
+ return [4 /*yield*/, this.registerAutoRegisterableAgents()];
195
+ case 4:
196
+ // Register agents (creates agent instances)
197
+ _e.sent();
198
+ if (this.debug) {
199
+ FlinkLog_1.log.bgColorLog("cyan", "Register agents took ".concat(Date.now() - offsetTime, " ms"));
200
+ offsetTime = Date.now();
201
+ }
202
+ // Initialize agents now that context and tools are ready
203
+ return [4 /*yield*/, this.initializeAgents()];
204
+ case 5:
205
+ // Initialize agents now that context and tools are ready
206
+ _e.sent();
207
+ if (this.debug) {
208
+ FlinkLog_1.log.bgColorLog("cyan", "Initialize agents took ".concat(Date.now() - offsetTime, " ms"));
209
+ offsetTime = Date.now();
210
+ }
162
211
  if (this.isSchedulingEnabled) {
163
212
  this.scheduler = new toad_scheduler_1.ToadScheduler();
164
213
  }
@@ -184,45 +233,45 @@ var FlinkApp = /** @class */ (function () {
184
233
  });
185
234
  }
186
235
  _b = 0, _c = this.plugins;
187
- _e.label = 3;
188
- case 3:
189
- if (!(_b < _c.length)) return [3 /*break*/, 9];
236
+ _e.label = 6;
237
+ case 6:
238
+ if (!(_b < _c.length)) return [3 /*break*/, 12];
190
239
  plugin = _c[_b];
191
240
  db = void 0;
192
- if (!plugin.db) return [3 /*break*/, 5];
241
+ if (!plugin.db) return [3 /*break*/, 8];
193
242
  return [4 /*yield*/, this.initPluginDb(plugin)];
194
- case 4:
243
+ case 7:
195
244
  db = _e.sent();
196
- _e.label = 5;
197
- case 5:
198
- if (!plugin.init) return [3 /*break*/, 7];
245
+ _e.label = 8;
246
+ case 8:
247
+ if (!plugin.init) return [3 /*break*/, 10];
199
248
  return [4 /*yield*/, plugin.init(this, db)];
200
- case 6:
249
+ case 9:
201
250
  _e.sent();
202
- _e.label = 7;
203
- case 7:
251
+ _e.label = 10;
252
+ case 10:
204
253
  FlinkLog_1.log.info("Initialized plugin '".concat(plugin.id, "'"));
205
- _e.label = 8;
206
- case 8:
254
+ _e.label = 11;
255
+ case 11:
207
256
  _b++;
208
- return [3 /*break*/, 3];
209
- case 9: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
210
- case 10:
257
+ return [3 /*break*/, 6];
258
+ case 12: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
259
+ case 13:
211
260
  _e.sent();
212
261
  if (this.debug) {
213
262
  FlinkLog_1.log.bgColorLog("cyan", "Register handlers took ".concat(Date.now() - offsetTime, " ms"));
214
263
  offsetTime = Date.now();
215
264
  }
216
- if (!this.isSchedulingEnabled) return [3 /*break*/, 12];
265
+ if (!this.isSchedulingEnabled) return [3 /*break*/, 15];
217
266
  return [4 /*yield*/, this.registerAutoRegisterableJobs()];
218
- case 11:
267
+ case 14:
219
268
  _e.sent();
220
269
  if (this.debug) {
221
270
  FlinkLog_1.log.bgColorLog("cyan", "Register jobs took ".concat(Date.now() - offsetTime, " ms"));
222
271
  offsetTime = Date.now();
223
272
  }
224
- _e.label = 12;
225
- case 12:
273
+ _e.label = 15;
274
+ case 15:
226
275
  // Register 404 with slight delay to allow all manually added routes to be added
227
276
  // TODO: Is there a better solution to force this handler to always run last?
228
277
  setTimeout(function () {
@@ -325,7 +374,7 @@ var FlinkApp = /** @class */ (function () {
325
374
  var _this = this;
326
375
  this.handlers.push(handlerConfig);
327
376
  var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
328
- var method = routeProps.method;
377
+ var method = routeProps.method, streamFormat = routeProps.streamFormat;
329
378
  if (!method) {
330
379
  FlinkLog_1.log.error("Route ".concat(routeProps.path, " is missing http method"));
331
380
  }
@@ -342,12 +391,12 @@ var FlinkApp = /** @class */ (function () {
342
391
  if (schema.reqSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateResponse) {
343
392
  validateReq_1 = ajv.compile(schema.reqSchema);
344
393
  }
345
- // Compile response schema if validation mode requires it
346
- if (schema.resSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateRequest) {
394
+ // Skip response validation for streaming handlers (responses are stream chunks, not final JSON)
395
+ if (!streamFormat && schema.resSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateRequest) {
347
396
  validateRes_1 = ajv.compile(schema.resSchema);
348
397
  }
349
398
  this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
350
- var valid, formattedErrors, data, normalizedQuery, _i, _a, _b, key, value, handlerRes, err_1, errorResponse, result, valid, formattedErrors;
399
+ var valid, formattedErrors, data, normalizedQuery, _i, _a, _b, key, value, stream, handlerRes, err_1, errorResponse, result, valid, formattedErrors;
351
400
  return __generator(this, function (_c) {
352
401
  switch (_c.label) {
353
402
  case 0:
@@ -374,7 +423,8 @@ var FlinkApp = /** @class */ (function () {
374
423
  })];
375
424
  }
376
425
  }
377
- if (routeProps.mockApi && schema.resSchema) {
426
+ // Skip mock API for streaming handlers
427
+ if (routeProps.mockApi && schema.resSchema && !streamFormat) {
378
428
  FlinkLog_1.log.warn("Mock response for ".concat(req.method.toUpperCase(), " ").concat(req.path));
379
429
  data = (0, mock_data_generator_1.default)(schema.resSchema);
380
430
  res.status(200).json({
@@ -402,6 +452,7 @@ var FlinkApp = /** @class */ (function () {
402
452
  }
403
453
  req.query = normalizedQuery;
404
454
  }
455
+ stream = streamFormat ? StreamWriterFactory_1.StreamWriterFactory.create(res, streamFormat) : undefined;
405
456
  _c.label = 3;
406
457
  case 3:
407
458
  _c.trys.push([3, 5, , 6]);
@@ -409,6 +460,7 @@ var FlinkApp = /** @class */ (function () {
409
460
  req: req,
410
461
  ctx: this.ctx,
411
462
  origin: routeProps.origin,
463
+ stream: stream,
412
464
  })];
413
465
  case 4:
414
466
  // 👇 This is where the actual handler gets invoked
@@ -416,6 +468,16 @@ var FlinkApp = /** @class */ (function () {
416
468
  return [3 /*break*/, 6];
417
469
  case 5:
418
470
  err_1 = _c.sent();
471
+ // Handle errors for streaming handlers
472
+ if (streamFormat && stream) {
473
+ FlinkLog_1.log.error("Streaming handler error on ".concat(req.method.toUpperCase(), " ").concat(req.path, ": ").concat(err_1.message), {
474
+ error: err_1,
475
+ path: req.path,
476
+ method: req.method,
477
+ });
478
+ stream.error(err_1);
479
+ return [2 /*return*/];
480
+ }
419
481
  errorResponse = void 0;
420
482
  // duck typing to check if it is a FlinkError
421
483
  if (typeof err_1.status === "number" && err_1.status >= 400 && err_1.status < 600 && err_1.error) {
@@ -456,6 +518,14 @@ var FlinkApp = /** @class */ (function () {
456
518
  }
457
519
  return [2 /*return*/, res.status(errorResponse.status || 500).json(errorResponse)];
458
520
  case 6:
521
+ // Skip response handling for streaming handlers (stream controls response lifecycle)
522
+ if (streamFormat) {
523
+ return [2 /*return*/];
524
+ }
525
+ // Ensure handlerRes is defined for non-streaming handlers
526
+ if (!handlerRes) {
527
+ return [2 /*return*/, res.status(204).send()];
528
+ }
459
529
  if (validateRes_1 && !(0, utils_1.isError)(handlerRes)) {
460
530
  valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
461
531
  if (!valid) {
@@ -483,7 +553,7 @@ var FlinkApp = /** @class */ (function () {
483
553
  }
484
554
  else {
485
555
  this.handlerRouteCache.set(methodAndRoute_1, JSON.stringify(routeProps));
486
- FlinkLog_1.log.info("Registered route ".concat(methodAndRoute_1));
556
+ FlinkLog_1.log.info("Registered ".concat(streamFormat ? 'streaming ' : '', "route ").concat(methodAndRoute_1).concat(streamFormat ? " (".concat(streamFormat, ")") : ''));
487
557
  }
488
558
  }
489
559
  };
@@ -625,6 +695,137 @@ var FlinkApp = /** @class */ (function () {
625
695
  // TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
626
696
  // repoInstance.ctx = this.ctx;
627
697
  };
698
+ FlinkApp.prototype.registerAutoRegisterableTools = function () {
699
+ return __awaiter(this, void 0, void 0, function () {
700
+ var ToolExecutor, getRepoInstanceName, _i, autoRegisteredTools_1, toolFile, toolId, toolInstanceName, toolExecutor;
701
+ return __generator(this, function (_a) {
702
+ ToolExecutor = require("./ai/ToolExecutor").ToolExecutor;
703
+ getRepoInstanceName = require("./utils").getRepoInstanceName;
704
+ for (_i = 0, autoRegisteredTools_1 = exports.autoRegisteredTools; _i < autoRegisteredTools_1.length; _i++) {
705
+ toolFile = autoRegisteredTools_1[_i];
706
+ if (!toolFile.Tool) {
707
+ FlinkLog_1.log.error("Missing FlinkToolProps export in tool ".concat(toolFile.__file));
708
+ continue;
709
+ }
710
+ if (!toolFile.default) {
711
+ FlinkLog_1.log.error("Missing exported tool function in tool ".concat(toolFile.__file));
712
+ continue;
713
+ }
714
+ toolId = toolFile.Tool.id;
715
+ if (!toolId) {
716
+ FlinkLog_1.log.error("Tool ".concat(toolFile.__file, " missing 'id' property"));
717
+ continue;
718
+ }
719
+ toolInstanceName = getRepoInstanceName(toolId);
720
+ toolExecutor = new ToolExecutor(toolFile.Tool, toolFile.default, this.ctx);
721
+ this.tools[toolInstanceName] = toolExecutor;
722
+ FlinkLog_1.log.info("Registered tool ".concat(toolInstanceName, " (").concat(toolId, ")"));
723
+ }
724
+ return [2 /*return*/];
725
+ });
726
+ });
727
+ };
728
+ FlinkApp.prototype.registerAutoRegisterableAgents = function () {
729
+ return __awaiter(this, void 0, void 0, function () {
730
+ var _a, getRepoInstanceName, toKebabCase, SubAgentExecutor, _loop_2, this_2, _i, autoRegisteredAgents_1, agentFile, _b, autoRegisteredAgents_2, agentFile, AgentClass, agentInstance, _c, _d, agentRef, subAgentInstanceName;
731
+ return __generator(this, function (_e) {
732
+ _a = require("./utils"), getRepoInstanceName = _a.getRepoInstanceName, toKebabCase = _a.toKebabCase;
733
+ SubAgentExecutor = require("./ai/SubAgentExecutor").SubAgentExecutor;
734
+ _loop_2 = function (agentFile) {
735
+ // agentFile now exports a class, not a config object
736
+ var AgentClass = agentFile.default;
737
+ if (!AgentClass) {
738
+ FlinkLog_1.log.error("Missing default export in agent ".concat(agentFile.__file));
739
+ return "continue";
740
+ }
741
+ // Instantiate agent (similar to repo instantiation)
742
+ var agentInstance = new AgentClass();
743
+ // Derive instance name from class name (camelCase)
744
+ var agentInstanceName = getRepoInstanceName(AgentClass.name);
745
+ // Get agent ID (kebab-case) - either explicit or derived
746
+ var agentId = agentInstance.id || toKebabCase(AgentClass.name);
747
+ // Check for duplicate instance name
748
+ if (this_2.agents[agentInstanceName]) {
749
+ var existingAgent = this_2.agents[agentInstanceName];
750
+ throw new Error("Duplicate agent instance name: \"".concat(agentInstanceName, "\". ") +
751
+ "Agent class \"".concat(AgentClass.name, "\" conflicts with existing agent \"").concat(existingAgent.constructor.name, "\". ") +
752
+ "Instance names are derived by lowercasing the first letter of the class name. " +
753
+ "Rename one of the classes or use a unique explicit 'id' property.");
754
+ }
755
+ // Check for duplicate agent ID
756
+ var existingAgentWithSameId = Object.values(this_2.agents).find(function (agent) {
757
+ var existingId = agent.id || toKebabCase(agent.constructor.name);
758
+ return existingId === agentId;
759
+ });
760
+ if (existingAgentWithSameId) {
761
+ throw new Error("Duplicate agent ID: \"".concat(agentId, "\". ") +
762
+ "Agent class \"".concat(AgentClass.name, "\" conflicts with existing agent \"").concat(existingAgentWithSameId.constructor.name, "\". ") +
763
+ "Agent IDs are derived from class names using kebab-case (e.g., CarAgent \u2192 car-agent). " +
764
+ "Use an explicit 'id' property to resolve this conflict:\n" +
765
+ " id = \"my-unique-id\";");
766
+ }
767
+ // Validate tools exist
768
+ for (var _f = 0, _g = agentInstance.tools; _f < _g.length; _f++) {
769
+ var toolRef = _g[_f];
770
+ // Handle both string IDs and tool file references
771
+ var toolId = typeof toolRef === "string"
772
+ ? toolRef
773
+ : toolRef.Tool.id; // Extract ID from FlinkToolFile
774
+ var tool = this_2.tools[toolId];
775
+ if (!tool) {
776
+ FlinkLog_1.log.error("Agent ".concat(AgentClass.name, " references tool ").concat(toolId, " which is not registered"));
777
+ throw new Error("Invalid tool reference in agent ".concat(AgentClass.name));
778
+ }
779
+ }
780
+ // Validate and register sub-agents
781
+ if (agentInstance.agents && agentInstance.agents.length > 0) {
782
+ for (var _h = 0, _j = agentInstance.agents; _h < _j.length; _h++) {
783
+ var agentRef = _j[_h];
784
+ // Get instance name directly from class reference or string
785
+ var subAgentInstanceName = typeof agentRef === "string" ? agentRef : getRepoInstanceName(agentRef.name);
786
+ // Validate that sub-agent will exist (will be registered in this loop)
787
+ // For now, just log - actual validation happens at runtime
788
+ FlinkLog_1.log.debug("Agent ".concat(AgentClass.name, " references sub-agent ").concat(subAgentInstanceName));
789
+ // Create a SubAgentExecutor as a special tool
790
+ var subAgentToolName = "ask_".concat(subAgentInstanceName);
791
+ var subAgentExecutor = new SubAgentExecutor(subAgentInstanceName, this_2.ctx);
792
+ // Register as a tool so it appears in the agent's tool list
793
+ this_2.tools[subAgentToolName] = subAgentExecutor;
794
+ FlinkLog_1.log.debug("Created sub-agent tool ".concat(subAgentToolName, " for ").concat(subAgentInstanceName));
795
+ }
796
+ }
797
+ // Register agent (duplicate checks already performed above)
798
+ this_2.agents[agentInstanceName] = agentInstance;
799
+ FlinkLog_1.log.info("Registered agent ".concat(agentInstanceName, " (").concat(AgentClass.name, ") with ID: ").concat(agentId));
800
+ };
801
+ this_2 = this;
802
+ for (_i = 0, autoRegisteredAgents_1 = exports.autoRegisteredAgents; _i < autoRegisteredAgents_1.length; _i++) {
803
+ agentFile = autoRegisteredAgents_1[_i];
804
+ _loop_2(agentFile);
805
+ }
806
+ // Second pass: validate all sub-agent references
807
+ for (_b = 0, autoRegisteredAgents_2 = exports.autoRegisteredAgents; _b < autoRegisteredAgents_2.length; _b++) {
808
+ agentFile = autoRegisteredAgents_2[_b];
809
+ AgentClass = agentFile.default;
810
+ if (!AgentClass) {
811
+ continue;
812
+ }
813
+ agentInstance = new AgentClass();
814
+ if (agentInstance.agents && agentInstance.agents.length > 0) {
815
+ for (_c = 0, _d = agentInstance.agents; _c < _d.length; _c++) {
816
+ agentRef = _d[_c];
817
+ subAgentInstanceName = typeof agentRef === "string" ? agentRef : getRepoInstanceName(agentRef.name);
818
+ if (!this.agents[subAgentInstanceName]) {
819
+ FlinkLog_1.log.error("Agent ".concat(AgentClass.name, " references sub-agent ").concat(subAgentInstanceName, " which is not registered"));
820
+ throw new Error("Invalid sub-agent reference in agent ".concat(AgentClass.name, ": ").concat(subAgentInstanceName));
821
+ }
822
+ }
823
+ }
824
+ }
825
+ return [2 /*return*/];
826
+ });
827
+ });
828
+ };
628
829
  /**
629
830
  * Constructs the app context. Will inject context in all components
630
831
  * except for handlers which are handled in later stage.
@@ -655,6 +856,7 @@ var FlinkApp = /** @class */ (function () {
655
856
  repos: this.repos,
656
857
  plugins: pluginCtx,
657
858
  auth: this.auth,
859
+ agents: this.agents,
658
860
  };
659
861
  for (_b = 0, _c = Object.values(this.repos); _b < _c.length; _b++) {
660
862
  repo = _c[_b];
@@ -664,6 +866,24 @@ var FlinkApp = /** @class */ (function () {
664
866
  });
665
867
  });
666
868
  };
869
+ /**
870
+ * Initialize agents after they've been registered and context is ready
871
+ * Must be called after registerAutoRegisterableAgents()
872
+ */
873
+ FlinkApp.prototype.initializeAgents = function () {
874
+ return __awaiter(this, void 0, void 0, function () {
875
+ var _i, _a, agent;
876
+ return __generator(this, function (_b) {
877
+ // Inject context and initialize agents
878
+ for (_i = 0, _a = Object.values(this.agents); _i < _a.length; _i++) {
879
+ agent = _a[_i];
880
+ agent.ctx = this.ctx;
881
+ agent.__init(this.llmAdapters, this.tools);
882
+ }
883
+ return [2 /*return*/];
884
+ });
885
+ });
886
+ };
667
887
  /**
668
888
  * Connects to database.
669
889
  */
@@ -1,5 +1,6 @@
1
1
  import { FlinkAuthPlugin } from "./auth/FlinkAuthPlugin";
2
2
  import { FlinkRepo } from "./FlinkRepo";
3
+ import { FlinkAgent } from "./ai/FlinkAgent";
3
4
  export interface FlinkContext<P = any> {
4
5
  repos: {
5
6
  [x: string]: FlinkRepo<any, any>;
@@ -9,4 +10,24 @@ export interface FlinkContext<P = any> {
9
10
  * Type of authentication, if any.
10
11
  */
11
12
  auth?: FlinkAuthPlugin;
13
+ /**
14
+ * AI namespace containing agents
15
+ *
16
+ * Define agents directly in your context interface:
17
+ *
18
+ * @example
19
+ * interface AppCtx extends FlinkContext<PluginCtx> {
20
+ * auth: JwtAuthPlugin;
21
+ * repos: {
22
+ * carRepo: CarRepo;
23
+ * };
24
+ * agents: {
25
+ * carAgent: CarAgent;
26
+ * userAgent: UserAgent;
27
+ * };
28
+ * }
29
+ */
30
+ agents?: {
31
+ [x: string]: FlinkAgent<any>;
32
+ };
12
33
  }