@flink-app/flink 0.14.3 → 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 (112) hide show
  1. package/CHANGELOG.md +66 -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 +279 -35
  8. package/dist/src/FlinkContext.d.ts +21 -0
  9. package/dist/src/FlinkHttpHandler.d.ts +152 -9
  10. package/dist/src/FlinkHttpHandler.js +37 -1
  11. package/dist/src/TypeScriptCompiler.d.ts +42 -0
  12. package/dist/src/TypeScriptCompiler.js +346 -4
  13. package/dist/src/TypeScriptUtils.js +4 -0
  14. package/dist/src/ai/AgentRunner.d.ts +39 -0
  15. package/dist/src/ai/AgentRunner.js +625 -0
  16. package/dist/src/ai/FlinkAgent.d.ts +446 -0
  17. package/dist/src/ai/FlinkAgent.js +633 -0
  18. package/dist/src/ai/FlinkTool.d.ts +37 -0
  19. package/dist/src/ai/FlinkTool.js +2 -0
  20. package/dist/src/ai/LLMAdapter.d.ts +119 -0
  21. package/dist/src/ai/LLMAdapter.js +2 -0
  22. package/dist/src/ai/SubAgentExecutor.d.ts +36 -0
  23. package/dist/src/ai/SubAgentExecutor.js +220 -0
  24. package/dist/src/ai/ToolExecutor.d.ts +35 -0
  25. package/dist/src/ai/ToolExecutor.js +237 -0
  26. package/dist/src/ai/index.d.ts +5 -0
  27. package/dist/src/ai/index.js +21 -0
  28. package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
  29. package/dist/src/handlers/StreamWriterFactory.js +83 -0
  30. package/dist/src/index.d.ts +4 -0
  31. package/dist/src/index.js +4 -0
  32. package/dist/src/utils.d.ts +30 -0
  33. package/dist/src/utils.js +52 -0
  34. package/package.json +16 -2
  35. package/readme.md +425 -0
  36. package/spec/AgentDuplicateDetection.spec.ts +112 -0
  37. package/spec/AgentRunner.spec.ts +527 -0
  38. package/spec/ConversationHooks.spec.ts +290 -0
  39. package/spec/FlinkAgent.spec.ts +310 -0
  40. package/spec/FlinkApp.onError.spec.ts +1 -2
  41. package/spec/FlinkApp.query.spec.ts +107 -0
  42. package/spec/FlinkApp.validationMode.spec.ts +155 -0
  43. package/spec/StreamingIntegration.spec.ts +138 -0
  44. package/spec/SubAgentSupport.spec.ts +941 -0
  45. package/spec/ToolExecutor.spec.ts +360 -0
  46. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +57 -0
  47. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +59 -0
  48. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +53 -0
  49. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +53 -0
  50. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +53 -0
  51. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +55 -0
  52. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +55 -0
  53. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +58 -0
  54. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +58 -0
  55. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
  56. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
  57. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +58 -0
  58. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +76 -0
  59. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +58 -0
  60. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +59 -0
  61. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +59 -0
  62. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +55 -0
  63. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +56 -0
  64. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +55 -0
  65. package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +55 -0
  66. package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
  67. package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
  68. package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
  69. package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
  70. package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
  71. package/spec/mock-project/dist/src/FlinkApp.js +1012 -0
  72. package/spec/mock-project/dist/src/FlinkContext.js +2 -0
  73. package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
  74. package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
  75. package/spec/mock-project/dist/src/FlinkJob.js +2 -0
  76. package/spec/mock-project/dist/src/FlinkLog.js +26 -0
  77. package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
  78. package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
  79. package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
  80. package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
  81. package/spec/mock-project/dist/src/ai/AgentRunner.js +625 -0
  82. package/spec/mock-project/dist/src/ai/FlinkAgent.js +633 -0
  83. package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
  84. package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
  85. package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +220 -0
  86. package/spec/mock-project/dist/src/ai/ToolExecutor.js +237 -0
  87. package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
  88. package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
  89. package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
  90. package/spec/mock-project/dist/src/index.js +17 -69
  91. package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
  92. package/spec/mock-project/dist/src/utils.js +290 -0
  93. package/spec/mock-project/tsconfig.json +6 -1
  94. package/spec/testHelpers.ts +49 -0
  95. package/spec/utils.caseConversion.spec.ts +80 -0
  96. package/spec/utils.spec.ts +13 -13
  97. package/src/FlinkApp.ts +275 -8
  98. package/src/FlinkContext.ts +22 -0
  99. package/src/FlinkHttpHandler.ts +164 -10
  100. package/src/TypeScriptCompiler.ts +398 -7
  101. package/src/TypeScriptUtils.ts +5 -0
  102. package/src/ai/AgentRunner.ts +549 -0
  103. package/src/ai/FlinkAgent.ts +770 -0
  104. package/src/ai/FlinkTool.ts +40 -0
  105. package/src/ai/LLMAdapter.ts +96 -0
  106. package/src/ai/SubAgentExecutor.ts +199 -0
  107. package/src/ai/ToolExecutor.ts +193 -0
  108. package/src/ai/index.ts +5 -0
  109. package/src/handlers/StreamWriterFactory.ts +84 -0
  110. package/src/index.ts +4 -0
  111. package/src/utils.ts +52 -0
  112. package/tsconfig.json +6 -1
@@ -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"));
@@ -62,7 +62,9 @@ var ms_1 = __importDefault(require("ms"));
62
62
  var toad_scheduler_1 = require("toad-scheduler");
63
63
  var uuid_1 = require("uuid");
64
64
  var FlinkErrors_1 = require("./FlinkErrors");
65
+ var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
65
66
  var FlinkLog_1 = require("./FlinkLog");
67
+ var StreamWriterFactory_1 = require("./handlers/StreamWriterFactory");
66
68
  var mock_data_generator_1 = __importDefault(require("./mock-data-generator"));
67
69
  var utils_1 = require("./utils");
68
70
  var ajv = new ajv_1.default();
@@ -92,8 +94,19 @@ exports.autoRegisteredRepos = [];
92
94
  * are picked up by TypeScript compiler
93
95
  */
94
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 = [];
95
107
  var FlinkApp = /** @class */ (function () {
96
108
  function FlinkApp(opts) {
109
+ var _a;
97
110
  this.handlers = [];
98
111
  this.started = false;
99
112
  this.debug = false;
@@ -101,6 +114,9 @@ var FlinkApp = /** @class */ (function () {
101
114
  this.routingConfigured = false;
102
115
  this.disableHttpServer = false;
103
116
  this.repos = {};
117
+ this.llmAdapters = new Map();
118
+ this.tools = {};
119
+ this.agents = {}; // FlinkAgent<C> instances
104
120
  /**
105
121
  * Internal cache used to track registered handlers and potentially any overlapping routes
106
122
  */
@@ -123,6 +139,11 @@ var FlinkApp = /** @class */ (function () {
123
139
  this.disableHttpServer = !!opts.disableHttpServer;
124
140
  this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
125
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
+ }
126
147
  }
127
148
  Object.defineProperty(FlinkApp.prototype, "ctx", {
128
149
  get: function () {
@@ -151,13 +172,42 @@ var FlinkApp = /** @class */ (function () {
151
172
  offsetTime = Date.now();
152
173
  FlinkLog_1.log.bgColorLog("cyan", "Init db took ".concat(offsetTime - startTime, " ms"));
153
174
  }
175
+ // Build initial context (without agents - they'll be added later)
154
176
  return [4 /*yield*/, this.buildContext()];
155
177
  case 2:
178
+ // Build initial context (without agents - they'll be added later)
156
179
  _e.sent();
157
180
  if (this.debug) {
158
181
  FlinkLog_1.log.bgColorLog("cyan", "Build context took ".concat(Date.now() - offsetTime, " ms"));
159
182
  offsetTime = Date.now();
160
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
+ }
161
211
  if (this.isSchedulingEnabled) {
162
212
  this.scheduler = new toad_scheduler_1.ToadScheduler();
163
213
  }
@@ -183,45 +233,45 @@ var FlinkApp = /** @class */ (function () {
183
233
  });
184
234
  }
185
235
  _b = 0, _c = this.plugins;
186
- _e.label = 3;
187
- case 3:
188
- if (!(_b < _c.length)) return [3 /*break*/, 9];
236
+ _e.label = 6;
237
+ case 6:
238
+ if (!(_b < _c.length)) return [3 /*break*/, 12];
189
239
  plugin = _c[_b];
190
240
  db = void 0;
191
- if (!plugin.db) return [3 /*break*/, 5];
241
+ if (!plugin.db) return [3 /*break*/, 8];
192
242
  return [4 /*yield*/, this.initPluginDb(plugin)];
193
- case 4:
243
+ case 7:
194
244
  db = _e.sent();
195
- _e.label = 5;
196
- case 5:
197
- if (!plugin.init) return [3 /*break*/, 7];
245
+ _e.label = 8;
246
+ case 8:
247
+ if (!plugin.init) return [3 /*break*/, 10];
198
248
  return [4 /*yield*/, plugin.init(this, db)];
199
- case 6:
249
+ case 9:
200
250
  _e.sent();
201
- _e.label = 7;
202
- case 7:
251
+ _e.label = 10;
252
+ case 10:
203
253
  FlinkLog_1.log.info("Initialized plugin '".concat(plugin.id, "'"));
204
- _e.label = 8;
205
- case 8:
254
+ _e.label = 11;
255
+ case 11:
206
256
  _b++;
207
- return [3 /*break*/, 3];
208
- case 9: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
209
- case 10:
257
+ return [3 /*break*/, 6];
258
+ case 12: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
259
+ case 13:
210
260
  _e.sent();
211
261
  if (this.debug) {
212
262
  FlinkLog_1.log.bgColorLog("cyan", "Register handlers took ".concat(Date.now() - offsetTime, " ms"));
213
263
  offsetTime = Date.now();
214
264
  }
215
- if (!this.isSchedulingEnabled) return [3 /*break*/, 12];
265
+ if (!this.isSchedulingEnabled) return [3 /*break*/, 15];
216
266
  return [4 /*yield*/, this.registerAutoRegisterableJobs()];
217
- case 11:
267
+ case 14:
218
268
  _e.sent();
219
269
  if (this.debug) {
220
270
  FlinkLog_1.log.bgColorLog("cyan", "Register jobs took ".concat(Date.now() - offsetTime, " ms"));
221
271
  offsetTime = Date.now();
222
272
  }
223
- _e.label = 12;
224
- case 12:
273
+ _e.label = 15;
274
+ case 15:
225
275
  // Register 404 with slight delay to allow all manually added routes to be added
226
276
  // TODO: Is there a better solution to force this handler to always run last?
227
277
  setTimeout(function () {
@@ -324,7 +374,7 @@ var FlinkApp = /** @class */ (function () {
324
374
  var _this = this;
325
375
  this.handlers.push(handlerConfig);
326
376
  var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
327
- var method = routeProps.method;
377
+ var method = routeProps.method, streamFormat = routeProps.streamFormat;
328
378
  if (!method) {
329
379
  FlinkLog_1.log.error("Route ".concat(routeProps.path, " is missing http method"));
330
380
  }
@@ -335,24 +385,28 @@ var FlinkApp = /** @class */ (function () {
335
385
  }
336
386
  var validateReq_1;
337
387
  var validateRes_1;
338
- if (schema.reqSchema) {
388
+ // Determine validation mode (default to Validate if not specified)
389
+ var validationMode = routeProps.validation || FlinkHttpHandler_1.ValidationMode.Validate;
390
+ // Compile request schema if validation mode requires it
391
+ if (schema.reqSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateResponse) {
339
392
  validateReq_1 = ajv.compile(schema.reqSchema);
340
393
  }
341
- if (schema.resSchema) {
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) {
342
396
  validateRes_1 = ajv.compile(schema.resSchema);
343
397
  }
344
398
  this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
345
- var valid, formattedErrors, data, handlerRes, err_1, errorResponse, result, valid, formattedErrors;
346
- return __generator(this, function (_a) {
347
- switch (_a.label) {
399
+ var valid, formattedErrors, data, normalizedQuery, _i, _a, _b, key, value, stream, handlerRes, err_1, errorResponse, result, valid, formattedErrors;
400
+ return __generator(this, function (_c) {
401
+ switch (_c.label) {
348
402
  case 0:
349
403
  if (!routeProps.permissions) return [3 /*break*/, 2];
350
404
  return [4 /*yield*/, this.authenticate(req, routeProps.permissions)];
351
405
  case 1:
352
- if (!(_a.sent())) {
406
+ if (!(_c.sent())) {
353
407
  return [2 /*return*/, res.status(401).json((0, FlinkErrors_1.unauthorized)())];
354
408
  }
355
- _a.label = 2;
409
+ _c.label = 2;
356
410
  case 2:
357
411
  if (validateReq_1) {
358
412
  valid = validateReq_1(req.body);
@@ -369,7 +423,8 @@ var FlinkApp = /** @class */ (function () {
369
423
  })];
370
424
  }
371
425
  }
372
- if (routeProps.mockApi && schema.resSchema) {
426
+ // Skip mock API for streaming handlers
427
+ if (routeProps.mockApi && schema.resSchema && !streamFormat) {
373
428
  FlinkLog_1.log.warn("Mock response for ".concat(req.method.toUpperCase(), " ").concat(req.path));
374
429
  data = (0, mock_data_generator_1.default)(schema.resSchema);
375
430
  res.status(200).json({
@@ -378,20 +433,51 @@ var FlinkApp = /** @class */ (function () {
378
433
  });
379
434
  return [2 /*return*/];
380
435
  }
381
- _a.label = 3;
436
+ // Normalize query parameters to predictable string or string[] types
437
+ // Express query parser can produce numbers, booleans, objects, etc.
438
+ // We normalize everything to strings or string arrays for consistency
439
+ if (req.query && typeof req.query === "object") {
440
+ normalizedQuery = {};
441
+ for (_i = 0, _a = Object.entries(req.query); _i < _a.length; _i++) {
442
+ _b = _a[_i], key = _b[0], value = _b[1];
443
+ if (Array.isArray(value)) {
444
+ // Handle array values (e.g., ?tag=a&tag=b)
445
+ normalizedQuery[key] = value.map(function (v) { return String(v); });
446
+ }
447
+ else if (value !== undefined && value !== null) {
448
+ // Convert single values to strings
449
+ normalizedQuery[key] = String(value);
450
+ }
451
+ // Skip undefined/null values - they won't appear in the normalized query
452
+ }
453
+ req.query = normalizedQuery;
454
+ }
455
+ stream = streamFormat ? StreamWriterFactory_1.StreamWriterFactory.create(res, streamFormat) : undefined;
456
+ _c.label = 3;
382
457
  case 3:
383
- _a.trys.push([3, 5, , 6]);
458
+ _c.trys.push([3, 5, , 6]);
384
459
  return [4 /*yield*/, handler({
385
460
  req: req,
386
461
  ctx: this.ctx,
387
462
  origin: routeProps.origin,
463
+ stream: stream,
388
464
  })];
389
465
  case 4:
390
466
  // 👇 This is where the actual handler gets invoked
391
- handlerRes = _a.sent();
467
+ handlerRes = _c.sent();
392
468
  return [3 /*break*/, 6];
393
469
  case 5:
394
- err_1 = _a.sent();
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
+ }
395
481
  errorResponse = void 0;
396
482
  // duck typing to check if it is a FlinkError
397
483
  if (typeof err_1.status === "number" && err_1.status >= 400 && err_1.status < 600 && err_1.error) {
@@ -432,6 +518,14 @@ var FlinkApp = /** @class */ (function () {
432
518
  }
433
519
  return [2 /*return*/, res.status(errorResponse.status || 500).json(errorResponse)];
434
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
+ }
435
529
  if (validateRes_1 && !(0, utils_1.isError)(handlerRes)) {
436
530
  valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
437
531
  if (!valid) {
@@ -459,7 +553,7 @@ var FlinkApp = /** @class */ (function () {
459
553
  }
460
554
  else {
461
555
  this.handlerRouteCache.set(methodAndRoute_1, JSON.stringify(routeProps));
462
- 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, ")") : ''));
463
557
  }
464
558
  }
465
559
  };
@@ -601,6 +695,137 @@ var FlinkApp = /** @class */ (function () {
601
695
  // TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
602
696
  // repoInstance.ctx = this.ctx;
603
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
+ };
604
829
  /**
605
830
  * Constructs the app context. Will inject context in all components
606
831
  * except for handlers which are handled in later stage.
@@ -631,6 +856,7 @@ var FlinkApp = /** @class */ (function () {
631
856
  repos: this.repos,
632
857
  plugins: pluginCtx,
633
858
  auth: this.auth,
859
+ agents: this.agents,
634
860
  };
635
861
  for (_b = 0, _c = Object.values(this.repos); _b < _c.length; _b++) {
636
862
  repo = _c[_b];
@@ -640,6 +866,24 @@ var FlinkApp = /** @class */ (function () {
640
866
  });
641
867
  });
642
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
+ };
643
887
  /**
644
888
  * Connects to database.
645
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
  }