@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
@@ -0,0 +1,1012 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.FlinkApp = exports.autoRegisteredAgents = exports.autoRegisteredTools = exports.autoRegisteredJobs = exports.autoRegisteredRepos = exports.autoRegisteredHandlers = exports.expressFn = void 0;
54
+ var ajv_1 = __importDefault(require("ajv"));
55
+ var ajv_formats_1 = __importDefault(require("ajv-formats"));
56
+ var body_parser_1 = __importDefault(require("body-parser"));
57
+ var cors_1 = __importDefault(require("cors"));
58
+ var express_1 = __importDefault(require("express"));
59
+ var mongodb_1 = require("mongodb");
60
+ var morgan_1 = __importDefault(require("morgan"));
61
+ var ms_1 = __importDefault(require("ms"));
62
+ var toad_scheduler_1 = require("toad-scheduler");
63
+ var uuid_1 = require("uuid");
64
+ var FlinkErrors_1 = require("./FlinkErrors");
65
+ var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
66
+ var FlinkLog_1 = require("./FlinkLog");
67
+ var StreamWriterFactory_1 = require("./handlers/StreamWriterFactory");
68
+ var mock_data_generator_1 = __importDefault(require("./mock-data-generator"));
69
+ var utils_1 = require("./utils");
70
+ var ajv = new ajv_1.default();
71
+ (0, ajv_formats_1.default)(ajv);
72
+ var defaultCorsOptions = {
73
+ allowedHeaders: "",
74
+ credentials: true,
75
+ origin: [/.*/],
76
+ };
77
+ /**
78
+ * Re-export express factory function so sub packages
79
+ * do not need its own express
80
+ */
81
+ exports.expressFn = express_1.default;
82
+ /**
83
+ * This will be populated at compile time when the apps handlers
84
+ * are picked up by TypeScript compiler
85
+ */
86
+ exports.autoRegisteredHandlers = [];
87
+ /**
88
+ * This will be populated at compile time when the apps repos
89
+ * are picked up by TypeScript compiler
90
+ */
91
+ exports.autoRegisteredRepos = [];
92
+ /**
93
+ * This will be populated at compile time when the apps jobs
94
+ * are picked up by TypeScript compiler
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 = [];
107
+ var FlinkApp = /** @class */ (function () {
108
+ function FlinkApp(opts) {
109
+ var _a;
110
+ this.handlers = [];
111
+ this.started = false;
112
+ this.debug = false;
113
+ this.plugins = [];
114
+ this.routingConfigured = false;
115
+ this.disableHttpServer = false;
116
+ this.repos = {};
117
+ this.llmAdapters = new Map();
118
+ this.tools = {};
119
+ this.agents = {}; // FlinkAgent<C> instances
120
+ /**
121
+ * Internal cache used to track registered handlers and potentially any overlapping routes
122
+ */
123
+ this.handlerRouteCache = new Map();
124
+ this.name = opts.name;
125
+ this.port = opts.port || 3333;
126
+ this.dbOpts = opts.db;
127
+ this.debug = !!opts.debug;
128
+ this.onDbConnection = opts.onDbConnection;
129
+ this.plugins = opts.plugins || [];
130
+ this.corsOpts = __assign(__assign({}, defaultCorsOptions), opts.cors);
131
+ this.rawContentTypes = Array.isArray(opts.rawContentTypes)
132
+ ? opts.rawContentTypes
133
+ : typeof opts.rawContentTypes === "string"
134
+ ? [opts.rawContentTypes]
135
+ : undefined;
136
+ this.auth = opts.auth;
137
+ this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
138
+ this.schedulingOptions = opts.scheduling;
139
+ this.disableHttpServer = !!opts.disableHttpServer;
140
+ this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
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
+ }
147
+ }
148
+ Object.defineProperty(FlinkApp.prototype, "ctx", {
149
+ get: function () {
150
+ if (!this._ctx) {
151
+ throw new Error("Context is not yet initialized");
152
+ }
153
+ return this._ctx;
154
+ },
155
+ enumerable: false,
156
+ configurable: true
157
+ });
158
+ FlinkApp.prototype.start = function () {
159
+ return __awaiter(this, void 0, void 0, function () {
160
+ var startTime, offsetTime, _i, _a, type, _b, _c, plugin, db;
161
+ var _this = this;
162
+ var _d;
163
+ return __generator(this, function (_e) {
164
+ switch (_e.label) {
165
+ case 0:
166
+ startTime = Date.now();
167
+ offsetTime = 0;
168
+ return [4 /*yield*/, this.initDb()];
169
+ case 1:
170
+ _e.sent();
171
+ if (this.debug) {
172
+ offsetTime = Date.now();
173
+ FlinkLog_1.log.bgColorLog("cyan", "Init db took ".concat(offsetTime - startTime, " ms"));
174
+ }
175
+ // Build initial context (without agents - they'll be added later)
176
+ return [4 /*yield*/, this.buildContext()];
177
+ case 2:
178
+ // Build initial context (without agents - they'll be added later)
179
+ _e.sent();
180
+ if (this.debug) {
181
+ FlinkLog_1.log.bgColorLog("cyan", "Build context took ".concat(Date.now() - offsetTime, " ms"));
182
+ offsetTime = Date.now();
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
+ }
211
+ if (this.isSchedulingEnabled) {
212
+ this.scheduler = new toad_scheduler_1.ToadScheduler();
213
+ }
214
+ else {
215
+ FlinkLog_1.log.info("🚫 Scheduling is disabled");
216
+ }
217
+ if (!this.disableHttpServer) {
218
+ this.expressApp = (0, express_1.default)();
219
+ this.expressApp.use((0, cors_1.default)(this.corsOpts));
220
+ if (this.rawContentTypes) {
221
+ for (_i = 0, _a = this.rawContentTypes; _i < _a.length; _i++) {
222
+ type = _a[_i];
223
+ this.expressApp.use(express_1.default.raw({ type: type }));
224
+ }
225
+ }
226
+ this.expressApp.use(body_parser_1.default.json(this.jsonOptions));
227
+ if (this.accessLog.enabled) {
228
+ this.expressApp.use((0, morgan_1.default)(this.accessLog.format));
229
+ }
230
+ this.expressApp.use(function (req, res, next) {
231
+ req.reqId = (0, uuid_1.v4)();
232
+ next();
233
+ });
234
+ }
235
+ _b = 0, _c = this.plugins;
236
+ _e.label = 6;
237
+ case 6:
238
+ if (!(_b < _c.length)) return [3 /*break*/, 12];
239
+ plugin = _c[_b];
240
+ db = void 0;
241
+ if (!plugin.db) return [3 /*break*/, 8];
242
+ return [4 /*yield*/, this.initPluginDb(plugin)];
243
+ case 7:
244
+ db = _e.sent();
245
+ _e.label = 8;
246
+ case 8:
247
+ if (!plugin.init) return [3 /*break*/, 10];
248
+ return [4 /*yield*/, plugin.init(this, db)];
249
+ case 9:
250
+ _e.sent();
251
+ _e.label = 10;
252
+ case 10:
253
+ FlinkLog_1.log.info("Initialized plugin '".concat(plugin.id, "'"));
254
+ _e.label = 11;
255
+ case 11:
256
+ _b++;
257
+ return [3 /*break*/, 6];
258
+ case 12: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
259
+ case 13:
260
+ _e.sent();
261
+ if (this.debug) {
262
+ FlinkLog_1.log.bgColorLog("cyan", "Register handlers took ".concat(Date.now() - offsetTime, " ms"));
263
+ offsetTime = Date.now();
264
+ }
265
+ if (!this.isSchedulingEnabled) return [3 /*break*/, 15];
266
+ return [4 /*yield*/, this.registerAutoRegisterableJobs()];
267
+ case 14:
268
+ _e.sent();
269
+ if (this.debug) {
270
+ FlinkLog_1.log.bgColorLog("cyan", "Register jobs took ".concat(Date.now() - offsetTime, " ms"));
271
+ offsetTime = Date.now();
272
+ }
273
+ _e.label = 15;
274
+ case 15:
275
+ // Register 404 with slight delay to allow all manually added routes to be added
276
+ // TODO: Is there a better solution to force this handler to always run last?
277
+ setTimeout(function () {
278
+ if (!_this.disableHttpServer) {
279
+ _this.expressApp.use(function (req, res, next) {
280
+ res.status(404).json((0, FlinkErrors_1.notFound)());
281
+ });
282
+ }
283
+ _this.routingConfigured = true;
284
+ });
285
+ if (this.disableHttpServer) {
286
+ FlinkLog_1.log.info("🚧 HTTP server is disabled, but flink app is running");
287
+ this.started = true;
288
+ }
289
+ else {
290
+ this.expressServer = (_d = this.expressApp) === null || _d === void 0 ? void 0 : _d.listen(this.port, function () {
291
+ FlinkLog_1.log.fontColorLog("magenta", "\u26A1\uFE0F HTTP server '".concat(_this.name, "' is running and waiting for connections on ").concat(_this.port));
292
+ _this.started = true;
293
+ });
294
+ }
295
+ return [2 /*return*/, this];
296
+ }
297
+ });
298
+ });
299
+ };
300
+ FlinkApp.prototype.stop = function () {
301
+ return __awaiter(this, void 0, void 0, function () {
302
+ var _this = this;
303
+ return __generator(this, function (_a) {
304
+ switch (_a.label) {
305
+ case 0:
306
+ FlinkLog_1.log.info("🛑 Stopping Flink app...");
307
+ if (!this.scheduler) return [3 /*break*/, 2];
308
+ return [4 /*yield*/, this.scheduler.stop()];
309
+ case 1:
310
+ _a.sent();
311
+ _a.label = 2;
312
+ case 2:
313
+ if (this.expressServer) {
314
+ return [2 /*return*/, new Promise(function (resolve, reject) {
315
+ var int = setTimeout(function () {
316
+ reject("Failed to stop HTTP server in time");
317
+ }, 2000);
318
+ _this.expressServer.close(function () {
319
+ clearInterval(int);
320
+ FlinkLog_1.log.info("HTTP server stopped");
321
+ resolve();
322
+ });
323
+ })];
324
+ }
325
+ return [2 /*return*/];
326
+ }
327
+ });
328
+ });
329
+ };
330
+ /**
331
+ * Manually registers a handler.
332
+ *
333
+ * Typescript compiler will scan handler function and set schemas
334
+ * which are derived from handler function type arguments.
335
+ */
336
+ FlinkApp.prototype.addHandler = function (handler, routePropsOverride) {
337
+ var _a, _b, _c, _d, _e, _f;
338
+ if (this.routingConfigured) {
339
+ throw new Error("Cannot add handler after routes has been registered, make sure to invoke earlier");
340
+ }
341
+ var routeProps = __assign(__assign({}, (handler.Route || {})), routePropsOverride);
342
+ if (!routeProps.method) {
343
+ FlinkLog_1.log.error("Failed to register handler '".concat(handler.__file, "': Missing 'method' in route props, either set it or name handler file with HTTP method as prefix"));
344
+ return;
345
+ }
346
+ if (!routeProps.path) {
347
+ FlinkLog_1.log.error("Failed to register handler '".concat(handler.__file, "': Missing 'path' in route props"));
348
+ return;
349
+ }
350
+ var dup = this.handlers.find(function (h) { return h.routeProps.path === routeProps.path && h.routeProps.method === routeProps.method; });
351
+ var methodAndPath = "".concat(routeProps.method.toUpperCase(), " ").concat(routeProps.path);
352
+ if (dup) {
353
+ // TODO: Not sure if there is a case where you'd want to overwrite a route?
354
+ FlinkLog_1.log.warn("".concat(methodAndPath, " overlaps existing route"));
355
+ }
356
+ var handlerConfig = {
357
+ routeProps: __assign(__assign({}, routeProps), { method: routeProps.method, path: routeProps.path }),
358
+ schema: {
359
+ reqSchema: (_a = handler.__schemas) === null || _a === void 0 ? void 0 : _a.reqSchema,
360
+ resSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.resSchema,
361
+ },
362
+ queryMetadata: handler.__query || [],
363
+ paramsMetadata: handler.__params || [],
364
+ };
365
+ if (((_c = handler.__schemas) === null || _c === void 0 ? void 0 : _c.reqSchema) && !((_d = handlerConfig.schema) === null || _d === void 0 ? void 0 : _d.reqSchema)) {
366
+ FlinkLog_1.log.warn("Expected request schema ".concat(handler.__schemas.reqSchema, " for handler ").concat(methodAndPath, " but no such schema was found"));
367
+ }
368
+ if (((_e = handler.__schemas) === null || _e === void 0 ? void 0 : _e.resSchema) && !((_f = handlerConfig.schema) === null || _f === void 0 ? void 0 : _f.resSchema)) {
369
+ FlinkLog_1.log.warn("Expected response schema ".concat(handler.__schemas.resSchema, " for handler ").concat(methodAndPath, " but no such schema was found"));
370
+ }
371
+ this.registerHandler(handlerConfig, handler.default);
372
+ };
373
+ FlinkApp.prototype.registerHandler = function (handlerConfig, handler) {
374
+ var _this = this;
375
+ this.handlers.push(handlerConfig);
376
+ var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
377
+ var method = routeProps.method, streamFormat = routeProps.streamFormat;
378
+ if (!method) {
379
+ FlinkLog_1.log.error("Route ".concat(routeProps.path, " is missing http method"));
380
+ }
381
+ if (method) {
382
+ var methodAndRoute_1 = "".concat(method.toUpperCase(), " ").concat(routeProps.path);
383
+ if (this.disableHttpServer) {
384
+ return;
385
+ }
386
+ var validateReq_1;
387
+ var validateRes_1;
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) {
392
+ validateReq_1 = ajv.compile(schema.reqSchema);
393
+ }
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) {
396
+ validateRes_1 = ajv.compile(schema.resSchema);
397
+ }
398
+ this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
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) {
402
+ case 0:
403
+ if (!routeProps.permissions) return [3 /*break*/, 2];
404
+ return [4 /*yield*/, this.authenticate(req, routeProps.permissions)];
405
+ case 1:
406
+ if (!(_c.sent())) {
407
+ return [2 /*return*/, res.status(401).json((0, FlinkErrors_1.unauthorized)())];
408
+ }
409
+ _c.label = 2;
410
+ case 2:
411
+ if (validateReq_1) {
412
+ valid = validateReq_1(req.body);
413
+ if (!valid) {
414
+ formattedErrors = (0, utils_1.formatValidationErrors)(validateReq_1.errors, req.body);
415
+ FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad request\n").concat(formattedErrors));
416
+ return [2 /*return*/, res.status(400).json({
417
+ status: 400,
418
+ error: {
419
+ id: (0, uuid_1.v4)(),
420
+ title: "Bad request",
421
+ detail: formattedErrors,
422
+ },
423
+ })];
424
+ }
425
+ }
426
+ // Skip mock API for streaming handlers
427
+ if (routeProps.mockApi && schema.resSchema && !streamFormat) {
428
+ FlinkLog_1.log.warn("Mock response for ".concat(req.method.toUpperCase(), " ").concat(req.path));
429
+ data = (0, mock_data_generator_1.default)(schema.resSchema);
430
+ res.status(200).json({
431
+ status: 200,
432
+ data: data,
433
+ });
434
+ return [2 /*return*/];
435
+ }
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;
457
+ case 3:
458
+ _c.trys.push([3, 5, , 6]);
459
+ return [4 /*yield*/, handler({
460
+ req: req,
461
+ ctx: this.ctx,
462
+ origin: routeProps.origin,
463
+ stream: stream,
464
+ })];
465
+ case 4:
466
+ // 👇 This is where the actual handler gets invoked
467
+ handlerRes = _c.sent();
468
+ return [3 /*break*/, 6];
469
+ case 5:
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
+ }
481
+ errorResponse = void 0;
482
+ // duck typing to check if it is a FlinkError
483
+ if (typeof err_1.status === "number" && err_1.status >= 400 && err_1.status < 600 && err_1.error) {
484
+ errorResponse = {
485
+ status: err_1.status,
486
+ error: {
487
+ id: err_1.error.id || (0, uuid_1.v4)(),
488
+ title: err_1.error.title || "Unhandled error: ".concat(err_1.error.code || err_1.status),
489
+ detail: err_1.error.detail,
490
+ code: err_1.error.code,
491
+ },
492
+ };
493
+ }
494
+ else {
495
+ FlinkLog_1.log.warn("Handler '".concat(methodAndRoute_1, "' threw unhandled exception ").concat(err_1));
496
+ console.error(err_1);
497
+ errorResponse = (0, FlinkErrors_1.internalServerError)(err_1);
498
+ }
499
+ // Invoke onError callback if provided
500
+ if (this.onError) {
501
+ try {
502
+ result = this.onError(errorResponse, {
503
+ req: req,
504
+ method: method,
505
+ path: routeProps.path,
506
+ reqId: req.reqId,
507
+ });
508
+ // Handle async callbacks - don't wait for them
509
+ if (result instanceof Promise) {
510
+ result.catch(function (callbackErr) {
511
+ FlinkLog_1.log.error("onError callback rejected with: ".concat(callbackErr));
512
+ });
513
+ }
514
+ }
515
+ catch (callbackErr) {
516
+ FlinkLog_1.log.error("onError callback threw an exception: ".concat(callbackErr));
517
+ }
518
+ }
519
+ return [2 /*return*/, res.status(errorResponse.status || 500).json(errorResponse)];
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
+ }
529
+ if (validateRes_1 && !(0, utils_1.isError)(handlerRes)) {
530
+ valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
531
+ if (!valid) {
532
+ formattedErrors = (0, utils_1.formatValidationErrors)(validateRes_1.errors, handlerRes.data);
533
+ FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response\n").concat(formattedErrors));
534
+ return [2 /*return*/, res.status(500).json({
535
+ status: 500,
536
+ error: {
537
+ id: (0, uuid_1.v4)(),
538
+ title: "Bad response",
539
+ detail: formattedErrors,
540
+ },
541
+ })];
542
+ }
543
+ }
544
+ res.set(handlerRes.headers);
545
+ res.status(handlerRes.status || 200).json(handlerRes);
546
+ return [2 /*return*/];
547
+ }
548
+ });
549
+ }); });
550
+ if (this.handlerRouteCache.has(methodAndRoute_1)) {
551
+ FlinkLog_1.log.error("Cannot register handler ".concat(methodAndRoute_1, " - route already registered"));
552
+ return process.exit(1); // TODO: Do we need to exit?
553
+ }
554
+ else {
555
+ this.handlerRouteCache.set(methodAndRoute_1, JSON.stringify(routeProps));
556
+ FlinkLog_1.log.info("Registered ".concat(streamFormat ? 'streaming ' : '', "route ").concat(methodAndRoute_1).concat(streamFormat ? " (".concat(streamFormat, ")") : ''));
557
+ }
558
+ }
559
+ };
560
+ /**
561
+ * Register handlers found within the `/src/handlers`
562
+ * directory in Flink App.
563
+ *
564
+ * Will not register any handlers added programmatically.
565
+ */
566
+ FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
567
+ return __awaiter(this, void 0, void 0, function () {
568
+ var _i, _a, _b, handler, assumedHttpMethod, pathParams, _c, _d, param;
569
+ var _e, _f, _g;
570
+ return __generator(this, function (_h) {
571
+ for (_i = 0, _a = exports.autoRegisteredHandlers.sort(function (a, b) { var _a, _b; return (((_a = a.handler.Route) === null || _a === void 0 ? void 0 : _a.order) || 0) - (((_b = b.handler.Route) === null || _b === void 0 ? void 0 : _b.order) || 0); }); _i < _a.length; _i++) {
572
+ _b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod;
573
+ if (!handler.Route) {
574
+ FlinkLog_1.log.error("Missing Props in handler ".concat(handler.__file));
575
+ continue;
576
+ }
577
+ if (!handler.default) {
578
+ FlinkLog_1.log.error("Missing exported handler function in handler ".concat(handler.__file));
579
+ continue;
580
+ }
581
+ if (!!((_e = handler.__params) === null || _e === void 0 ? void 0 : _e.length)) {
582
+ pathParams = (0, utils_1.getPathParams)(handler.Route.path);
583
+ for (_c = 0, _d = handler.__params; _c < _d.length; _c++) {
584
+ param = _d[_c];
585
+ if (!pathParams.includes(param.name)) {
586
+ FlinkLog_1.log.error("Handler ".concat(handler.__file, " has param ").concat(param.name, " but it is not present in the path '").concat(handler.Route.path, "'"));
587
+ throw new Error("Invalid/missing handler path param");
588
+ }
589
+ }
590
+ if (pathParams.length !== handler.__params.length) {
591
+ FlinkLog_1.log.warn("Handler ".concat(handler.__file, " has ").concat(handler.__params.length, " typed params but the path '").concat(handler.Route.path, "' has ").concat(pathParams.length, " params"));
592
+ }
593
+ }
594
+ this.registerHandler({
595
+ routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
596
+ schema: {
597
+ reqSchema: (_f = handler.__schemas) === null || _f === void 0 ? void 0 : _f.reqSchema,
598
+ resSchema: (_g = handler.__schemas) === null || _g === void 0 ? void 0 : _g.resSchema,
599
+ },
600
+ queryMetadata: handler.__query || [],
601
+ paramsMetadata: handler.__params || [],
602
+ }, handler.default);
603
+ }
604
+ return [2 /*return*/];
605
+ });
606
+ });
607
+ };
608
+ FlinkApp.prototype.registerAutoRegisterableJobs = function () {
609
+ return __awaiter(this, void 0, void 0, function () {
610
+ var _loop_1, this_1, _i, autoRegisteredJobs_1, _a, jobProps, jobFn, __file;
611
+ var _this = this;
612
+ return __generator(this, function (_b) {
613
+ if (!this.scheduler) {
614
+ throw new Error("Scheduler not initialized"); // should never happen
615
+ }
616
+ _loop_1 = function (jobProps, jobFn, __file) {
617
+ if (jobProps.cron && jobProps.interval) {
618
+ FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both cron and interval are set in ").concat(__file));
619
+ return "continue";
620
+ }
621
+ if (jobProps.cron && jobProps.afterDelay) {
622
+ FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both cron and afterDelay are set in ").concat(__file));
623
+ return "continue";
624
+ }
625
+ if (jobProps.interval && jobProps.afterDelay) {
626
+ FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both interval and afterDelay are set in ").concat(__file));
627
+ return "continue";
628
+ }
629
+ if (this_1.scheduler.existsById(jobProps.id)) {
630
+ FlinkLog_1.log.error("Job with id ".concat(jobProps.id, " is already registered, found duplicate in ").concat(__file));
631
+ return "continue";
632
+ }
633
+ FlinkLog_1.log.debug("Registering job ".concat(jobProps.id, ": ").concat(JSON.stringify(jobProps), " from ").concat(__file));
634
+ var task = new toad_scheduler_1.AsyncTask(jobProps.id, function () { return __awaiter(_this, void 0, void 0, function () {
635
+ return __generator(this, function (_a) {
636
+ switch (_a.label) {
637
+ case 0: return [4 /*yield*/, jobFn({ ctx: this.ctx })];
638
+ case 1:
639
+ _a.sent();
640
+ FlinkLog_1.log.debug("Job ".concat(jobProps.id, " completed"));
641
+ if (jobProps.afterDelay) {
642
+ // afterDelay runs only once, so we remove the job
643
+ this.scheduler.removeById(jobProps.id);
644
+ }
645
+ return [2 /*return*/];
646
+ }
647
+ });
648
+ }); }, function (err) {
649
+ FlinkLog_1.log.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err));
650
+ console.error(err);
651
+ });
652
+ if (jobProps.cron) {
653
+ var job = new toad_scheduler_1.CronJob({ timezone: jobProps.timezone, cronExpression: jobProps.cron }, task, {
654
+ id: jobProps.id,
655
+ preventOverrun: jobProps.singleton,
656
+ });
657
+ this_1.scheduler.addCronJob(job);
658
+ }
659
+ else if (jobProps.interval) {
660
+ var job = new toad_scheduler_1.SimpleIntervalJob({
661
+ milliseconds: (0, ms_1.default)(jobProps.interval),
662
+ runImmediately: false, // TODO: Expose to props?
663
+ }, task, {
664
+ id: jobProps.id,
665
+ preventOverrun: jobProps.singleton,
666
+ });
667
+ this_1.scheduler.addSimpleIntervalJob(job);
668
+ }
669
+ else if (jobProps.afterDelay !== undefined) {
670
+ var job = new toad_scheduler_1.SimpleIntervalJob({
671
+ milliseconds: (0, ms_1.default)(jobProps.afterDelay),
672
+ runImmediately: false,
673
+ }, task, {
674
+ id: jobProps.id,
675
+ preventOverrun: jobProps.singleton,
676
+ });
677
+ this_1.scheduler.addSimpleIntervalJob(job);
678
+ }
679
+ else {
680
+ FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - no cron, interval or once set in ").concat(__file));
681
+ return "continue";
682
+ }
683
+ };
684
+ this_1 = this;
685
+ for (_i = 0, autoRegisteredJobs_1 = exports.autoRegisteredJobs; _i < autoRegisteredJobs_1.length; _i++) {
686
+ _a = autoRegisteredJobs_1[_i], jobProps = _a.Job, jobFn = _a.default, __file = _a.__file;
687
+ _loop_1(jobProps, jobFn, __file);
688
+ }
689
+ return [2 /*return*/];
690
+ });
691
+ });
692
+ };
693
+ FlinkApp.prototype.addRepo = function (instanceName, repoInstance) {
694
+ this.repos[instanceName] = repoInstance;
695
+ // TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
696
+ // repoInstance.ctx = this.ctx;
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
+ };
829
+ /**
830
+ * Constructs the app context. Will inject context in all components
831
+ * except for handlers which are handled in later stage.
832
+ */
833
+ FlinkApp.prototype.buildContext = function () {
834
+ return __awaiter(this, void 0, void 0, function () {
835
+ var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx, _b, _c, repo;
836
+ return __generator(this, function (_d) {
837
+ if (this.dbOpts) {
838
+ for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
839
+ _a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
840
+ repoInstance = new Repo(collectionName, this.db, this.dbClient);
841
+ this.repos[repoInstanceName] = repoInstance;
842
+ FlinkLog_1.log.info("Registered repo ".concat(repoInstanceName));
843
+ }
844
+ }
845
+ else if (exports.autoRegisteredRepos.length > 0) {
846
+ FlinkLog_1.log.warn("No db configured but found repo(s)");
847
+ }
848
+ pluginCtx = this.plugins.reduce(function (out, plugin) {
849
+ if (out[plugin.id]) {
850
+ throw new Error("Plugin ".concat(plugin.id, " is already registered"));
851
+ }
852
+ out[plugin.id] = plugin.ctx;
853
+ return out;
854
+ }, {});
855
+ this._ctx = {
856
+ repos: this.repos,
857
+ plugins: pluginCtx,
858
+ auth: this.auth,
859
+ agents: this.agents,
860
+ };
861
+ for (_b = 0, _c = Object.values(this.repos); _b < _c.length; _b++) {
862
+ repo = _c[_b];
863
+ repo.ctx = this.ctx;
864
+ }
865
+ return [2 /*return*/];
866
+ });
867
+ });
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
+ };
887
+ /**
888
+ * Connects to database.
889
+ */
890
+ FlinkApp.prototype.initDb = function () {
891
+ return __awaiter(this, void 0, void 0, function () {
892
+ var client, err_2;
893
+ return __generator(this, function (_a) {
894
+ switch (_a.label) {
895
+ case 0:
896
+ if (!this.dbOpts) return [3 /*break*/, 6];
897
+ _a.label = 1;
898
+ case 1:
899
+ _a.trys.push([1, 3, , 4]);
900
+ FlinkLog_1.log.debug("Connecting to db");
901
+ return [4 /*yield*/, mongodb_1.MongoClient.connect(this.dbOpts.uri, this.getMongoConnectionOptions())];
902
+ case 2:
903
+ client = _a.sent();
904
+ this.db = client.db();
905
+ this.dbClient = client;
906
+ return [3 /*break*/, 4];
907
+ case 3:
908
+ err_2 = _a.sent();
909
+ FlinkLog_1.log.error("Failed to connect to db: " + err_2);
910
+ process.exit(1);
911
+ return [3 /*break*/, 4];
912
+ case 4:
913
+ if (!this.onDbConnection) return [3 /*break*/, 6];
914
+ return [4 /*yield*/, this.onDbConnection(this.db)];
915
+ case 5:
916
+ _a.sent();
917
+ _a.label = 6;
918
+ case 6: return [2 /*return*/];
919
+ }
920
+ });
921
+ });
922
+ };
923
+ /**
924
+ * Connects plugin to database.
925
+ */
926
+ FlinkApp.prototype.initPluginDb = function (plugin) {
927
+ return __awaiter(this, void 0, void 0, function () {
928
+ var client, err_3;
929
+ return __generator(this, function (_a) {
930
+ switch (_a.label) {
931
+ case 0:
932
+ if (!plugin.db) {
933
+ return [2 /*return*/];
934
+ }
935
+ if (!plugin.db) return [3 /*break*/, 5];
936
+ if (!plugin.db.useHostDb) return [3 /*break*/, 1];
937
+ if (!this.db) {
938
+ FlinkLog_1.log.error("Plugin '".concat(this.name, " configured to use host app db, but no db exists in FlinkApp'"));
939
+ }
940
+ else {
941
+ return [2 /*return*/, this.db];
942
+ }
943
+ return [3 /*break*/, 5];
944
+ case 1:
945
+ if (!plugin.db.uri) return [3 /*break*/, 5];
946
+ _a.label = 2;
947
+ case 2:
948
+ _a.trys.push([2, 4, , 5]);
949
+ FlinkLog_1.log.debug("Connecting to '".concat(plugin.id, "' db"));
950
+ return [4 /*yield*/, mongodb_1.MongoClient.connect(plugin.db.uri, this.getMongoConnectionOptions())];
951
+ case 3:
952
+ client = _a.sent();
953
+ return [2 /*return*/, client.db()];
954
+ case 4:
955
+ err_3 = _a.sent();
956
+ FlinkLog_1.log.error("Failed to connect to db defined in plugin '".concat(plugin.id, "': ") + err_3);
957
+ return [3 /*break*/, 5];
958
+ case 5: return [2 /*return*/];
959
+ }
960
+ });
961
+ });
962
+ };
963
+ FlinkApp.prototype.authenticate = function (req, permissions) {
964
+ return __awaiter(this, void 0, void 0, function () {
965
+ return __generator(this, function (_a) {
966
+ switch (_a.label) {
967
+ case 0:
968
+ if (!this.auth) {
969
+ throw new Error("Attempting to authenticate request (".concat(req.method, " ").concat(req.path, ") but no authPlugin is set"));
970
+ }
971
+ return [4 /*yield*/, this.auth.authenticateRequest(req, permissions)];
972
+ case 1: return [2 /*return*/, _a.sent()];
973
+ }
974
+ });
975
+ });
976
+ };
977
+ FlinkApp.prototype.getRegisteredRoutes = function () {
978
+ return Array.from(this.handlerRouteCache.values());
979
+ };
980
+ Object.defineProperty(FlinkApp.prototype, "isSchedulingEnabled", {
981
+ get: function () {
982
+ var _a;
983
+ return ((_a = this.schedulingOptions) === null || _a === void 0 ? void 0 : _a.enabled) !== false;
984
+ },
985
+ enumerable: false,
986
+ configurable: true
987
+ });
988
+ FlinkApp.prototype.getMongoConnectionOptions = function () {
989
+ if (!this.dbOpts) {
990
+ throw new Error("No db configured");
991
+ }
992
+ var driverVersion = require("mongodb/package.json").version;
993
+ if (driverVersion.startsWith("3")) {
994
+ FlinkLog_1.log.debug("Using legacy mongodb connection options as mongo client is version ".concat(driverVersion));
995
+ return {
996
+ useNewUrlParser: true,
997
+ useUnifiedTopology: true,
998
+ };
999
+ }
1000
+ FlinkLog_1.log.debug("Using modern MongoDB client options (driver version ".concat(driverVersion, ")"));
1001
+ return {
1002
+ serverApi: {
1003
+ version: mongodb_1.ServerApiVersion.v1,
1004
+ strict: process.env.FLINK_MONGO_SERVER_API_DISABLE_STRICT !== "true",
1005
+ deprecationErrors: process.env.FLINK_MONGO_SERVER_API_DISABLE_DEPRECATION !== "true",
1006
+ },
1007
+ connectTimeoutMS: 10 * 1000,
1008
+ };
1009
+ };
1010
+ return FlinkApp;
1011
+ }());
1012
+ exports.FlinkApp = FlinkApp;