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

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 (277) hide show
  1. package/CHANGELOG.md +991 -0
  2. package/SCHEMA_EXTRACTION_ANALYSIS.md +494 -0
  3. package/SIMPLE_AST_FEASIBILITY.md +570 -0
  4. package/bin/flink.ts +13 -2
  5. package/cli/build.ts +24 -44
  6. package/cli/clean.ts +13 -25
  7. package/cli/cli-utils.ts +190 -17
  8. package/cli/dev.ts +252 -0
  9. package/cli/loadEnvFiles.ts +116 -0
  10. package/cli/run.ts +45 -62
  11. package/dist/bin/flink.js +61 -2
  12. package/dist/cli/build.js +20 -25
  13. package/dist/cli/clean.js +12 -10
  14. package/dist/cli/cli-utils.d.ts +34 -3
  15. package/dist/cli/cli-utils.js +193 -12
  16. package/dist/cli/dev.d.ts +2 -0
  17. package/dist/cli/dev.js +279 -0
  18. package/dist/cli/loadEnvFiles.d.ts +30 -0
  19. package/dist/cli/loadEnvFiles.js +113 -0
  20. package/dist/cli/run.js +47 -46
  21. package/dist/src/DependencyTracker.d.ts +44 -0
  22. package/dist/src/DependencyTracker.js +239 -0
  23. package/dist/src/FlinkApp.d.ts +163 -10
  24. package/dist/src/FlinkApp.js +823 -184
  25. package/dist/src/FlinkContext.d.ts +41 -0
  26. package/dist/src/FlinkErrors.d.ts +19 -6
  27. package/dist/src/FlinkErrors.js +36 -42
  28. package/dist/src/FlinkHttpHandler.d.ts +157 -18
  29. package/dist/src/FlinkJob.d.ts +10 -0
  30. package/dist/src/FlinkLog.d.ts +82 -18
  31. package/dist/src/FlinkLog.js +165 -13
  32. package/dist/src/FlinkLogFactory.d.ts +288 -0
  33. package/dist/src/FlinkLogFactory.js +619 -0
  34. package/dist/src/FlinkRepo.d.ts +10 -2
  35. package/dist/src/FlinkRepo.js +11 -1
  36. package/dist/src/FlinkRequestContext.d.ts +63 -0
  37. package/dist/src/FlinkRequestContext.js +74 -0
  38. package/dist/src/FlinkResponse.d.ts +6 -0
  39. package/dist/src/FlinkService.d.ts +38 -0
  40. package/dist/src/FlinkService.js +46 -0
  41. package/dist/src/LeaderElection.d.ts +45 -0
  42. package/dist/src/LeaderElection.js +269 -0
  43. package/dist/src/SchemaCache.d.ts +84 -0
  44. package/dist/src/SchemaCache.js +289 -0
  45. package/dist/src/TypeScriptCompiler.d.ts +161 -51
  46. package/dist/src/TypeScriptCompiler.js +1253 -617
  47. package/dist/src/TypeScriptUtils.js +4 -0
  48. package/dist/src/ai/AgentRunner.d.ts +39 -0
  49. package/dist/src/ai/AgentRunner.js +760 -0
  50. package/dist/src/ai/ConversationAgent.d.ts +279 -0
  51. package/dist/src/ai/ConversationAgent.js +404 -0
  52. package/dist/src/ai/ConversationFlinkAgent.d.ts +278 -0
  53. package/dist/src/ai/ConversationFlinkAgent.js +404 -0
  54. package/dist/src/ai/FlinkAgent.d.ts +690 -0
  55. package/dist/src/ai/FlinkAgent.js +729 -0
  56. package/dist/src/ai/FlinkTool.d.ts +135 -0
  57. package/dist/src/ai/FlinkTool.js +2 -0
  58. package/dist/src/ai/InMemoryConversationAgent.d.ts +121 -0
  59. package/dist/src/ai/InMemoryConversationAgent.js +209 -0
  60. package/dist/src/ai/LLMAdapter.d.ts +148 -0
  61. package/dist/src/ai/LLMAdapter.js +2 -0
  62. package/dist/src/ai/PersistentFlinkAgent.d.ts +278 -0
  63. package/dist/src/ai/PersistentFlinkAgent.js +403 -0
  64. package/dist/src/ai/SubAgentExecutor.d.ts +38 -0
  65. package/dist/src/ai/SubAgentExecutor.js +223 -0
  66. package/dist/src/ai/ToolExecutor.d.ts +64 -0
  67. package/dist/src/ai/ToolExecutor.js +497 -0
  68. package/dist/src/ai/agentInstructions.d.ts +68 -0
  69. package/dist/src/ai/agentInstructions.js +286 -0
  70. package/dist/src/ai/index.d.ts +8 -0
  71. package/dist/src/ai/index.js +26 -0
  72. package/dist/src/ai/instructionFileLoader.d.ts +44 -0
  73. package/dist/src/ai/instructionFileLoader.js +179 -0
  74. package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
  75. package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
  76. package/dist/src/handlers/StreamWriterFactory.js +83 -0
  77. package/dist/src/index.d.ts +14 -0
  78. package/dist/src/index.js +17 -0
  79. package/dist/src/loadPluginSchemas.d.ts +45 -0
  80. package/dist/src/loadPluginSchemas.js +143 -0
  81. package/dist/src/schema-extraction/ComplexTypeDetection.d.ts +40 -0
  82. package/dist/src/schema-extraction/ComplexTypeDetection.js +75 -0
  83. package/dist/src/schema-extraction/TypeScriptSourceParser.d.ts +321 -0
  84. package/dist/src/schema-extraction/TypeScriptSourceParser.js +925 -0
  85. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.d.ts +1 -0
  86. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.js +233 -0
  87. package/dist/src/schema-extraction/TypeScriptTokenizer.d.ts +57 -0
  88. package/dist/src/schema-extraction/TypeScriptTokenizer.js +177 -0
  89. package/dist/src/schema-extraction/index.d.ts +2 -0
  90. package/dist/src/schema-extraction/index.js +20 -0
  91. package/dist/src/schema-extraction/types.d.ts +31 -0
  92. package/dist/src/schema-extraction/types.js +2 -0
  93. package/dist/src/utils/loadFlinkConfig.d.ts +53 -0
  94. package/dist/src/utils/loadFlinkConfig.js +77 -0
  95. package/dist/src/utils.d.ts +30 -0
  96. package/dist/src/utils.js +52 -0
  97. package/dist/src/workers/SchemaGeneratorWorker.d.ts +1 -0
  98. package/dist/src/workers/SchemaGeneratorWorker.js +49 -0
  99. package/dist/src/workers/WorkerPool.d.ts +60 -0
  100. package/dist/src/workers/WorkerPool.js +306 -0
  101. package/examples/logging-hierarchical-example.ts +125 -0
  102. package/package.json +27 -4
  103. package/readme.md +499 -0
  104. package/spec/AgentDescendantDetection.spec.ts +335 -0
  105. package/spec/AgentDuplicateDetection.spec.ts +112 -0
  106. package/spec/AgentObserver.spec.ts +266 -0
  107. package/spec/AgentRunner.spec.ts +1062 -0
  108. package/spec/AsyncLocalStorageContext.spec.ts +223 -0
  109. package/spec/ConversationHooks.spec.ts +257 -0
  110. package/spec/FlinkAgent.spec.ts +681 -0
  111. package/spec/FlinkApp.htmlResponse.spec.ts +260 -0
  112. package/spec/FlinkApp.onError.invocation.spec.ts +151 -0
  113. package/spec/FlinkApp.onError.spec.ts +1 -2
  114. package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
  115. package/spec/FlinkApp.undefinedResponse.spec.ts +123 -0
  116. package/spec/FlinkJob.spec.ts +171 -0
  117. package/spec/FlinkLogFactory.spec.ts +337 -0
  118. package/spec/FlinkRepo.spec.ts +1 -1
  119. package/spec/LeaderElection.spec.ts +174 -0
  120. package/spec/StreamingIntegration.spec.ts +139 -0
  121. package/spec/ToolExecutor.spec.ts +465 -0
  122. package/spec/TypeScriptCompiler.spec.ts +1 -1
  123. package/spec/TypeScriptSourceParser.spec.ts +1215 -0
  124. package/spec/TypeScriptTokenizer.spec.ts +366 -0
  125. package/spec/ai/ContextCompaction.spec.ts +405 -0
  126. package/spec/ai/ConversationAgent.spec.ts +520 -0
  127. package/spec/ai/InMemoryConversationAgent.spec.ts +144 -0
  128. package/spec/ai/agentInstructions.spec.ts +358 -0
  129. package/spec/fixtures/agent-instructions/TestAgent.ts +24 -0
  130. package/spec/fixtures/agent-instructions/simple.md +3 -0
  131. package/spec/fixtures/agent-instructions/template.md +18 -0
  132. package/spec/fixtures/agent-instructions/yaml-format.yaml +9 -0
  133. package/spec/mock-project/dist/.tsbuildinfo +1 -0
  134. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +56 -0
  135. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +58 -0
  136. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +52 -0
  137. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +52 -0
  138. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +52 -0
  139. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +54 -0
  140. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +54 -0
  141. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +57 -0
  142. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +57 -0
  143. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
  144. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
  145. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +57 -0
  146. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +75 -0
  147. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +57 -0
  148. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +58 -0
  149. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +58 -0
  150. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +54 -0
  151. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +55 -0
  152. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +54 -0
  153. package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +54 -0
  154. package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
  155. package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
  156. package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
  157. package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
  158. package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
  159. package/spec/mock-project/dist/src/FlinkApp.js +1000 -0
  160. package/spec/mock-project/dist/src/FlinkContext.js +2 -0
  161. package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
  162. package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
  163. package/spec/mock-project/dist/src/FlinkJob.js +2 -0
  164. package/spec/mock-project/dist/src/FlinkLog.js +119 -0
  165. package/spec/mock-project/dist/src/FlinkLogFactory.js +617 -0
  166. package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
  167. package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
  168. package/spec/mock-project/dist/src/FlinkRequestContext.js +74 -0
  169. package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
  170. package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
  171. package/spec/mock-project/dist/src/ai/AgentRunner.js +632 -0
  172. package/spec/mock-project/dist/src/ai/ConversationAgent.js +402 -0
  173. package/spec/mock-project/dist/src/ai/ConversationFlinkAgent.js +422 -0
  174. package/spec/mock-project/dist/src/ai/FlinkAgent.js +699 -0
  175. package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
  176. package/spec/mock-project/dist/src/ai/InMemoryConversationAgent.js +209 -0
  177. package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
  178. package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +223 -0
  179. package/spec/mock-project/dist/src/ai/ToolExecutor.js +412 -0
  180. package/spec/mock-project/dist/src/ai/agentInstructions.js +246 -0
  181. package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
  182. package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
  183. package/spec/mock-project/dist/src/handlers/GetCar.js +26 -52
  184. package/spec/mock-project/dist/src/handlers/GetCar.js.map +1 -0
  185. package/spec/mock-project/dist/src/handlers/GetCar2.js +32 -54
  186. package/spec/mock-project/dist/src/handlers/GetCar2.js.map +1 -0
  187. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +26 -48
  188. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js.map +1 -0
  189. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +28 -48
  190. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js.map +1 -0
  191. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +29 -48
  192. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js.map +1 -0
  193. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +26 -50
  194. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js.map +1 -0
  195. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +28 -50
  196. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js.map +1 -0
  197. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +27 -53
  198. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js.map +1 -0
  199. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +29 -53
  200. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js.map +1 -0
  201. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +16 -49
  202. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js.map +1 -0
  203. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +25 -50
  204. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js.map +1 -0
  205. package/spec/mock-project/dist/src/handlers/PatchCar.js +27 -53
  206. package/spec/mock-project/dist/src/handlers/PatchCar.js.map +1 -0
  207. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js +44 -70
  208. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js.map +1 -0
  209. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js +27 -53
  210. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js.map +1 -0
  211. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js +28 -54
  212. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js.map +1 -0
  213. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js +28 -54
  214. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js.map +1 -0
  215. package/spec/mock-project/dist/src/handlers/PostCar.js +24 -50
  216. package/spec/mock-project/dist/src/handlers/PostCar.js.map +1 -0
  217. package/spec/mock-project/dist/src/handlers/PostLogin.js +25 -51
  218. package/spec/mock-project/dist/src/handlers/PostLogin.js.map +1 -0
  219. package/spec/mock-project/dist/src/handlers/PostLogout.js +24 -50
  220. package/spec/mock-project/dist/src/handlers/PostLogout.js.map +1 -0
  221. package/spec/mock-project/dist/src/handlers/PutCar.js +24 -50
  222. package/spec/mock-project/dist/src/handlers/PutCar.js.map +1 -0
  223. package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
  224. package/spec/mock-project/dist/src/index.js +52 -76
  225. package/spec/mock-project/dist/src/index.js.map +1 -0
  226. package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
  227. package/spec/mock-project/dist/src/repos/CarRepo.js +12 -24
  228. package/spec/mock-project/dist/src/repos/CarRepo.js.map +1 -0
  229. package/spec/mock-project/dist/src/schemas/Car.js +3 -1
  230. package/spec/mock-project/dist/src/schemas/Car.js.map +1 -0
  231. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +3 -1
  232. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js.map +1 -0
  233. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +3 -1
  234. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js.map +1 -0
  235. package/spec/mock-project/dist/src/utils.js +290 -0
  236. package/spec/mock-project/tsconfig.json +6 -1
  237. package/spec/schema-generation-nested-objects.spec.ts +97 -0
  238. package/spec/testHelpers.ts +49 -0
  239. package/spec/utils.caseConversion.spec.ts +78 -0
  240. package/spec/utils.spec.ts +13 -13
  241. package/src/DependencyTracker.ts +166 -0
  242. package/src/FlinkApp.ts +895 -154
  243. package/src/FlinkContext.ts +43 -0
  244. package/src/FlinkErrors.ts +32 -12
  245. package/src/FlinkHttpHandler.ts +182 -20
  246. package/src/FlinkJob.ts +11 -0
  247. package/src/FlinkLog.ts +119 -12
  248. package/src/FlinkLogFactory.ts +699 -0
  249. package/src/FlinkRepo.ts +10 -3
  250. package/src/FlinkRequestContext.ts +95 -0
  251. package/src/FlinkResponse.ts +6 -0
  252. package/src/FlinkService.ts +49 -0
  253. package/src/LeaderElection.ts +203 -0
  254. package/src/SchemaCache.ts +232 -0
  255. package/src/TypeScriptCompiler.ts +1347 -610
  256. package/src/TypeScriptUtils.ts +5 -0
  257. package/src/ai/AgentRunner.ts +646 -0
  258. package/src/ai/ConversationAgent.ts +413 -0
  259. package/src/ai/FlinkAgent.ts +1069 -0
  260. package/src/ai/FlinkTool.ts +165 -0
  261. package/src/ai/InMemoryConversationAgent.ts +149 -0
  262. package/src/ai/LLMAdapter.ts +126 -0
  263. package/src/ai/ToolExecutor.ts +485 -0
  264. package/src/ai/agentInstructions.ts +245 -0
  265. package/src/ai/index.ts +8 -0
  266. package/src/ai/instructionFileLoader.ts +156 -0
  267. package/src/auth/FlinkAuthPlugin.ts +2 -1
  268. package/src/handlers/StreamWriterFactory.ts +84 -0
  269. package/src/index.ts +14 -0
  270. package/src/loadPluginSchemas.ts +141 -0
  271. package/src/schema-extraction/TypeScriptSourceParser.ts +1058 -0
  272. package/src/schema-extraction/TypeScriptTokenizer.ts +205 -0
  273. package/src/schema-extraction/index.ts +2 -0
  274. package/src/schema-extraction/types.ts +34 -0
  275. package/src/utils/loadFlinkConfig.ts +89 -0
  276. package/src/utils.ts +52 -0
  277. 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.autoRegisteredServices = 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"));
@@ -63,9 +63,16 @@ var toad_scheduler_1 = require("toad-scheduler");
63
63
  var uuid_1 = require("uuid");
64
64
  var FlinkErrors_1 = require("./FlinkErrors");
65
65
  var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
66
+ var LeaderElection_1 = require("./LeaderElection");
66
67
  var FlinkLog_1 = require("./FlinkLog");
68
+ var FlinkLogFactory_1 = require("./FlinkLogFactory");
69
+ var FlinkRequestContext_1 = require("./FlinkRequestContext");
70
+ var StreamWriterFactory_1 = require("./handlers/StreamWriterFactory");
67
71
  var mock_data_generator_1 = __importDefault(require("./mock-data-generator"));
68
72
  var utils_1 = require("./utils");
73
+ var initLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.init");
74
+ var perfLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.perf");
75
+ var schedulerLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.scheduler");
69
76
  var ajv = new ajv_1.default();
70
77
  (0, ajv_formats_1.default)(ajv);
71
78
  var defaultCorsOptions = {
@@ -93,8 +100,24 @@ exports.autoRegisteredRepos = [];
93
100
  * are picked up by TypeScript compiler
94
101
  */
95
102
  exports.autoRegisteredJobs = [];
103
+ /**
104
+ * This will be populated at compile time when the apps tools
105
+ * are picked up by TypeScript compiler
106
+ */
107
+ exports.autoRegisteredTools = [];
108
+ /**
109
+ * This will be populated at compile time when the apps agents
110
+ * are picked up by TypeScript compiler
111
+ */
112
+ exports.autoRegisteredAgents = [];
113
+ /**
114
+ * This will be populated at compile time when the apps services
115
+ * are picked up by TypeScript compiler
116
+ */
117
+ exports.autoRegisteredServices = [];
96
118
  var FlinkApp = /** @class */ (function () {
97
119
  function FlinkApp(opts) {
120
+ var _a, _b;
98
121
  this.handlers = [];
99
122
  this.started = false;
100
123
  this.debug = false;
@@ -102,10 +125,18 @@ var FlinkApp = /** @class */ (function () {
102
125
  this.routingConfigured = false;
103
126
  this.disableHttpServer = false;
104
127
  this.repos = {};
128
+ this.services = {};
129
+ this.llmAdapters = new Map();
130
+ this.tools = {};
131
+ this.agents = {}; // FlinkAgent<C> instances
105
132
  /**
106
133
  * Internal cache used to track registered handlers and potentially any overlapping routes
107
134
  */
108
135
  this.handlerRouteCache = new Map();
136
+ // Load config file and initialize logging
137
+ var loadFlinkConfig = require("./utils/loadFlinkConfig").loadFlinkConfig;
138
+ var flinkConfig = loadFlinkConfig();
139
+ FlinkLogFactory_1.FlinkLogFactory.configure(flinkConfig === null || flinkConfig === void 0 ? void 0 : flinkConfig.logging);
109
140
  this.name = opts.name;
110
141
  this.port = opts.port || 3333;
111
142
  this.dbOpts = opts.db;
@@ -124,6 +155,13 @@ var FlinkApp = /** @class */ (function () {
124
155
  this.disableHttpServer = !!opts.disableHttpServer;
125
156
  this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
126
157
  this.onError = opts.onError;
158
+ // Register LLM adapters if configured
159
+ if ((_a = opts.ai) === null || _a === void 0 ? void 0 : _a.llms) {
160
+ // Convert plain object to Map for internal use
161
+ this.llmAdapters = new Map(Object.entries(opts.ai.llms));
162
+ }
163
+ // Register global agent observer if configured
164
+ this.agentObserver = (_b = opts.ai) === null || _b === void 0 ? void 0 : _b.observer;
127
165
  }
128
166
  Object.defineProperty(FlinkApp.prototype, "ctx", {
129
167
  get: function () {
@@ -137,33 +175,43 @@ var FlinkApp = /** @class */ (function () {
137
175
  });
138
176
  FlinkApp.prototype.start = function () {
139
177
  return __awaiter(this, void 0, void 0, function () {
140
- var startTime, offsetTime, _i, _a, type, _b, _c, plugin, db;
178
+ var startTime, dbStartTime, contextStartTime, toolsStartTime, agentsStartTime, agentInitStartTime, _i, _a, type, pluginsStartTime, _b, _c, plugin, db, handlersStartTime, jobsStartTime, totalStartTime;
141
179
  var _this = this;
142
180
  var _d;
143
181
  return __generator(this, function (_e) {
144
182
  switch (_e.label) {
145
183
  case 0:
146
184
  startTime = Date.now();
147
- offsetTime = 0;
185
+ dbStartTime = Date.now();
148
186
  return [4 /*yield*/, this.initDb()];
149
187
  case 1:
150
188
  _e.sent();
151
- if (this.debug) {
152
- offsetTime = Date.now();
153
- FlinkLog_1.log.bgColorLog("cyan", "Init db took ".concat(offsetTime - startTime, " ms"));
154
- }
189
+ perfLog.debug("Init db took ".concat(Date.now() - dbStartTime, "ms"));
190
+ contextStartTime = Date.now();
155
191
  return [4 /*yield*/, this.buildContext()];
156
192
  case 2:
157
193
  _e.sent();
158
- if (this.debug) {
159
- FlinkLog_1.log.bgColorLog("cyan", "Build context took ".concat(Date.now() - offsetTime, " ms"));
160
- offsetTime = Date.now();
161
- }
162
- if (this.isSchedulingEnabled) {
194
+ perfLog.debug("Build context took ".concat(Date.now() - contextStartTime, "ms"));
195
+ toolsStartTime = Date.now();
196
+ return [4 /*yield*/, this.registerAutoRegisterableTools()];
197
+ case 3:
198
+ _e.sent();
199
+ perfLog.debug("Register tools took ".concat(Date.now() - toolsStartTime, "ms"));
200
+ agentsStartTime = Date.now();
201
+ return [4 /*yield*/, this.registerAutoRegisterableAgents()];
202
+ case 4:
203
+ _e.sent();
204
+ perfLog.debug("Register agents took ".concat(Date.now() - agentsStartTime, "ms"));
205
+ agentInitStartTime = Date.now();
206
+ return [4 /*yield*/, this.initializeAgents()];
207
+ case 5:
208
+ _e.sent();
209
+ perfLog.debug("Initialize agents took ".concat(Date.now() - agentInitStartTime, "ms"));
210
+ if (this.isSchedulingEnabled && !this.leaderElectionConfig) {
163
211
  this.scheduler = new toad_scheduler_1.ToadScheduler();
164
212
  }
165
- else {
166
- FlinkLog_1.log.info("🚫 Scheduling is disabled");
213
+ else if (!this.isSchedulingEnabled) {
214
+ schedulerLog.info("Scheduling is disabled");
167
215
  }
168
216
  if (!this.disableHttpServer) {
169
217
  this.expressApp = (0, express_1.default)();
@@ -183,46 +231,53 @@ var FlinkApp = /** @class */ (function () {
183
231
  next();
184
232
  });
185
233
  }
234
+ pluginsStartTime = Date.now();
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:
195
- db = _e.sent();
196
- _e.label = 5;
197
- case 5:
198
- if (!plugin.init) return [3 /*break*/, 7];
199
- return [4 /*yield*/, plugin.init(this, db)];
200
- case 6:
201
- _e.sent();
202
- _e.label = 7;
203
243
  case 7:
204
- FlinkLog_1.log.info("Initialized plugin '".concat(plugin.id, "'"));
244
+ db = _e.sent();
205
245
  _e.label = 8;
206
246
  case 8:
207
- _b++;
208
- return [3 /*break*/, 3];
209
- case 9: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
210
- case 10:
247
+ if (!plugin.init) return [3 /*break*/, 10];
248
+ return [4 /*yield*/, plugin.init(this, db)];
249
+ case 9:
211
250
  _e.sent();
212
- if (this.debug) {
213
- FlinkLog_1.log.bgColorLog("cyan", "Register handlers took ".concat(Date.now() - offsetTime, " ms"));
214
- offsetTime = Date.now();
251
+ _e.label = 10;
252
+ case 10:
253
+ initLog.info("Initialized plugin '".concat(plugin.id, "'"));
254
+ _e.label = 11;
255
+ case 11:
256
+ _b++;
257
+ return [3 /*break*/, 6];
258
+ case 12:
259
+ if (this.plugins.length > 0) {
260
+ perfLog.debug("Initialize plugins took ".concat(Date.now() - pluginsStartTime, "ms (").concat(this.plugins.length, " plugins)"));
215
261
  }
216
- if (!this.isSchedulingEnabled) return [3 /*break*/, 12];
262
+ handlersStartTime = Date.now();
263
+ return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
264
+ case 13:
265
+ _e.sent();
266
+ perfLog.debug("Register handlers took ".concat(Date.now() - handlersStartTime, "ms"));
267
+ if (!this.isSchedulingEnabled) return [3 /*break*/, 17];
268
+ if (!this.leaderElectionConfig) return [3 /*break*/, 15];
269
+ return [4 /*yield*/, this.startLeaderElection()];
270
+ case 14:
271
+ _e.sent();
272
+ return [3 /*break*/, 17];
273
+ case 15:
274
+ jobsStartTime = Date.now();
217
275
  return [4 /*yield*/, this.registerAutoRegisterableJobs()];
218
- case 11:
276
+ case 16:
219
277
  _e.sent();
220
- if (this.debug) {
221
- FlinkLog_1.log.bgColorLog("cyan", "Register jobs took ".concat(Date.now() - offsetTime, " ms"));
222
- offsetTime = Date.now();
223
- }
224
- _e.label = 12;
225
- case 12:
278
+ perfLog.debug("Register jobs took ".concat(Date.now() - jobsStartTime, "ms"));
279
+ _e.label = 17;
280
+ case 17:
226
281
  // Register 404 with slight delay to allow all manually added routes to be added
227
282
  // TODO: Is there a better solution to force this handler to always run last?
228
283
  setTimeout(function () {
@@ -234,7 +289,7 @@ var FlinkApp = /** @class */ (function () {
234
289
  _this.routingConfigured = true;
235
290
  });
236
291
  if (this.disableHttpServer) {
237
- FlinkLog_1.log.info("🚧 HTTP server is disabled, but flink app is running");
292
+ initLog.info("🚧 HTTP server is disabled, but flink app is running");
238
293
  this.started = true;
239
294
  }
240
295
  else {
@@ -243,6 +298,8 @@ var FlinkApp = /** @class */ (function () {
243
298
  _this.started = true;
244
299
  });
245
300
  }
301
+ totalStartTime = Date.now() - startTime;
302
+ perfLog.info("\u2713 FlinkApp started in ".concat(totalStartTime, "ms"));
246
303
  return [2 /*return*/, this];
247
304
  }
248
305
  });
@@ -255,12 +312,24 @@ var FlinkApp = /** @class */ (function () {
255
312
  switch (_a.label) {
256
313
  case 0:
257
314
  FlinkLog_1.log.info("🛑 Stopping Flink app...");
258
- if (!this.scheduler) return [3 /*break*/, 2];
259
- return [4 /*yield*/, this.scheduler.stop()];
315
+ if (!this.leaderElection) return [3 /*break*/, 2];
316
+ return [4 /*yield*/, this.leaderElection.stop()];
260
317
  case 1:
261
318
  _a.sent();
262
319
  _a.label = 2;
263
320
  case 2:
321
+ if (!this.scheduler) return [3 /*break*/, 4];
322
+ return [4 /*yield*/, this.scheduler.stop()];
323
+ case 3:
324
+ _a.sent();
325
+ _a.label = 4;
326
+ case 4:
327
+ if (!this.allInstanceScheduler) return [3 /*break*/, 6];
328
+ return [4 /*yield*/, this.allInstanceScheduler.stop()];
329
+ case 5:
330
+ _a.sent();
331
+ _a.label = 6;
332
+ case 6:
264
333
  if (this.expressServer) {
265
334
  return [2 /*return*/, new Promise(function (resolve, reject) {
266
335
  var int = setTimeout(function () {
@@ -285,7 +354,6 @@ var FlinkApp = /** @class */ (function () {
285
354
  * which are derived from handler function type arguments.
286
355
  */
287
356
  FlinkApp.prototype.addHandler = function (handler, routePropsOverride) {
288
- var _a, _b, _c, _d, _e, _f;
289
357
  if (this.routingConfigured) {
290
358
  throw new Error("Cannot add handler after routes has been registered, make sure to invoke earlier");
291
359
  }
@@ -304,28 +372,37 @@ var FlinkApp = /** @class */ (function () {
304
372
  // TODO: Not sure if there is a case where you'd want to overwrite a route?
305
373
  FlinkLog_1.log.warn("".concat(methodAndPath, " overlaps existing route"));
306
374
  }
375
+ // Use direct schemas from routeProps if provided, otherwise fall back to manifest lookup
376
+ var reqSchema = routeProps.reqSchema;
377
+ var resSchema = routeProps.resSchema;
378
+ var queryMetadata = [];
379
+ var paramsMetadata = [];
380
+ if (!reqSchema || !resSchema) {
381
+ var schemaManifest = this.loadSchemaManifest();
382
+ var metadata = handler.__file ? schemaManifest.handlers[handler.__file] : undefined;
383
+ if (!reqSchema)
384
+ reqSchema = this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.reqSchemaName);
385
+ if (!resSchema)
386
+ resSchema = this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.resSchemaName);
387
+ queryMetadata = (metadata === null || metadata === void 0 ? void 0 : metadata.queryMetadata) || [];
388
+ paramsMetadata = (metadata === null || metadata === void 0 ? void 0 : metadata.paramsMetadata) || [];
389
+ }
307
390
  var handlerConfig = {
308
391
  routeProps: __assign(__assign({}, routeProps), { method: routeProps.method, path: routeProps.path }),
309
392
  schema: {
310
- reqSchema: (_a = handler.__schemas) === null || _a === void 0 ? void 0 : _a.reqSchema,
311
- resSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.resSchema,
393
+ reqSchema: reqSchema,
394
+ resSchema: resSchema,
312
395
  },
313
- queryMetadata: handler.__query || [],
314
- paramsMetadata: handler.__params || [],
396
+ queryMetadata: queryMetadata,
397
+ paramsMetadata: paramsMetadata,
315
398
  };
316
- if (((_c = handler.__schemas) === null || _c === void 0 ? void 0 : _c.reqSchema) && !((_d = handlerConfig.schema) === null || _d === void 0 ? void 0 : _d.reqSchema)) {
317
- FlinkLog_1.log.warn("Expected request schema ".concat(handler.__schemas.reqSchema, " for handler ").concat(methodAndPath, " but no such schema was found"));
318
- }
319
- if (((_e = handler.__schemas) === null || _e === void 0 ? void 0 : _e.resSchema) && !((_f = handlerConfig.schema) === null || _f === void 0 ? void 0 : _f.resSchema)) {
320
- FlinkLog_1.log.warn("Expected response schema ".concat(handler.__schemas.resSchema, " for handler ").concat(methodAndPath, " but no such schema was found"));
321
- }
322
399
  this.registerHandler(handlerConfig, handler.default);
323
400
  };
324
401
  FlinkApp.prototype.registerHandler = function (handlerConfig, handler) {
325
402
  var _this = this;
326
403
  this.handlers.push(handlerConfig);
327
404
  var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
328
- var method = routeProps.method;
405
+ var method = routeProps.method, streamFormat = routeProps.streamFormat;
329
406
  if (!method) {
330
407
  FlinkLog_1.log.error("Route ".concat(routeProps.path, " is missing http method"));
331
408
  }
@@ -336,45 +413,77 @@ var FlinkApp = /** @class */ (function () {
336
413
  }
337
414
  var validateReq_1;
338
415
  var validateRes_1;
416
+ // Select AJV instance (use schemaAjv for v2.0 manifests, fallback to global ajv)
417
+ var ajvInstance = this.schemaAjv || ajv;
339
418
  // Determine validation mode (default to Validate if not specified)
340
419
  var validationMode = routeProps.validation || FlinkHttpHandler_1.ValidationMode.Validate;
341
420
  // Compile request schema if validation mode requires it
342
421
  if (schema.reqSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateResponse) {
343
- validateReq_1 = ajv.compile(schema.reqSchema);
422
+ // For v2.0 manifests with $id, use getSchema() if available
423
+ if (schema.reqSchema.$id && this.schemaAjv) {
424
+ validateReq_1 = this.schemaAjv.getSchema(schema.reqSchema.$id);
425
+ if (!validateReq_1) {
426
+ FlinkLog_1.log.warn("Schema ".concat(schema.reqSchema.$id, " not found in AJV registry, compiling inline"));
427
+ validateReq_1 = ajvInstance.compile(schema.reqSchema);
428
+ }
429
+ }
430
+ else {
431
+ validateReq_1 = ajvInstance.compile(schema.reqSchema);
432
+ }
344
433
  }
345
- // Compile response schema if validation mode requires it
346
- if (schema.resSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateRequest) {
347
- validateRes_1 = ajv.compile(schema.resSchema);
434
+ // Skip response validation for streaming handlers (responses are stream chunks, not final JSON)
435
+ // Skip response validation for non-JSON response types (html, csv, etc.)
436
+ if (!streamFormat &&
437
+ !routeProps.responseType &&
438
+ schema.resSchema &&
439
+ validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation &&
440
+ validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateRequest) {
441
+ // For v2.0 manifests with $id, use getSchema() if available
442
+ if (schema.resSchema.$id && this.schemaAjv) {
443
+ validateRes_1 = this.schemaAjv.getSchema(schema.resSchema.$id);
444
+ if (!validateRes_1) {
445
+ FlinkLog_1.log.warn("Schema ".concat(schema.resSchema.$id, " not found in AJV registry, compiling inline"));
446
+ validateRes_1 = ajvInstance.compile(schema.resSchema);
447
+ }
448
+ }
449
+ else {
450
+ validateRes_1 = ajvInstance.compile(schema.resSchema);
451
+ }
348
452
  }
349
453
  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;
351
- return __generator(this, function (_c) {
352
- switch (_c.label) {
454
+ var valid, formattedErrors, errorResponse, data, normalizedQuery, _i, _a, _b, key, value, stream, handlerRes, flinkReq_1, err_1, errorResponse, detail, errorResponse, valid, formattedErrors, errorResponse;
455
+ var _this = this;
456
+ var _c;
457
+ return __generator(this, function (_d) {
458
+ switch (_d.label) {
353
459
  case 0:
354
460
  if (!routeProps.permissions) return [3 /*break*/, 2];
355
461
  return [4 /*yield*/, this.authenticate(req, routeProps.permissions)];
356
462
  case 1:
357
- if (!(_c.sent())) {
463
+ if (!(_d.sent())) {
358
464
  return [2 /*return*/, res.status(401).json((0, FlinkErrors_1.unauthorized)())];
359
465
  }
360
- _c.label = 2;
466
+ _d.label = 2;
361
467
  case 2:
362
468
  if (validateReq_1) {
363
469
  valid = validateReq_1(req.body);
364
470
  if (!valid) {
365
471
  formattedErrors = (0, utils_1.formatValidationErrors)(validateReq_1.errors, req.body);
366
472
  FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad request\n").concat(formattedErrors));
367
- return [2 /*return*/, res.status(400).json({
368
- status: 400,
369
- error: {
370
- id: (0, uuid_1.v4)(),
371
- title: "Bad request",
372
- detail: formattedErrors,
373
- },
374
- })];
473
+ errorResponse = {
474
+ status: 400,
475
+ error: {
476
+ id: (0, uuid_1.v4)(),
477
+ title: "Bad request",
478
+ detail: formattedErrors,
479
+ },
480
+ };
481
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
482
+ return [2 /*return*/, res.status(400).json(errorResponse)];
375
483
  }
376
484
  }
377
- if (routeProps.mockApi && schema.resSchema) {
485
+ // Skip mock API for streaming handlers
486
+ if (routeProps.mockApi && schema.resSchema && !streamFormat) {
378
487
  FlinkLog_1.log.warn("Mock response for ".concat(req.method.toUpperCase(), " ").concat(req.path));
379
488
  data = (0, mock_data_generator_1.default)(schema.resSchema);
380
489
  res.status(200).json({
@@ -402,20 +511,47 @@ var FlinkApp = /** @class */ (function () {
402
511
  }
403
512
  req.query = normalizedQuery;
404
513
  }
405
- _c.label = 3;
514
+ stream = streamFormat ? StreamWriterFactory_1.StreamWriterFactory.create(res, streamFormat) : undefined;
515
+ _d.label = 3;
406
516
  case 3:
407
- _c.trys.push([3, 5, , 6]);
408
- return [4 /*yield*/, handler({
409
- req: req,
410
- ctx: this.ctx,
411
- origin: routeProps.origin,
412
- })];
517
+ _d.trys.push([3, 5, , 6]);
518
+ flinkReq_1 = req;
519
+ return [4 /*yield*/, FlinkRequestContext_1.requestContext.run({
520
+ reqId: flinkReq_1.reqId,
521
+ user: flinkReq_1.user,
522
+ userPermissions: flinkReq_1.userPermissions,
523
+ method: method,
524
+ path: routeProps.path,
525
+ timestamp: Date.now(),
526
+ isStreaming: !!streamFormat,
527
+ }, function () { return __awaiter(_this, void 0, void 0, function () {
528
+ return __generator(this, function (_a) {
529
+ switch (_a.label) {
530
+ case 0: return [4 /*yield*/, handler({
531
+ req: flinkReq_1,
532
+ ctx: this.ctx,
533
+ origin: routeProps.origin,
534
+ stream: stream,
535
+ })];
536
+ case 1: return [2 /*return*/, _a.sent()];
537
+ }
538
+ });
539
+ }); })];
413
540
  case 4:
414
- // 👇 This is where the actual handler gets invoked
415
- handlerRes = _c.sent();
541
+ handlerRes = _d.sent();
416
542
  return [3 /*break*/, 6];
417
543
  case 5:
418
- err_1 = _c.sent();
544
+ err_1 = _d.sent();
545
+ // Handle errors for streaming handlers
546
+ if (streamFormat && stream) {
547
+ FlinkLog_1.log.error("Streaming handler error on ".concat(req.method.toUpperCase(), " ").concat(req.path, ": ").concat(err_1.message), {
548
+ error: err_1,
549
+ path: req.path,
550
+ method: req.method,
551
+ });
552
+ stream.error(err_1);
553
+ return [2 /*return*/];
554
+ }
419
555
  errorResponse = void 0;
420
556
  // duck typing to check if it is a FlinkError
421
557
  if (typeof err_1.status === "number" && err_1.status >= 400 && err_1.status < 600 && err_1.error) {
@@ -434,44 +570,64 @@ var FlinkApp = /** @class */ (function () {
434
570
  console.error(err_1);
435
571
  errorResponse = (0, FlinkErrors_1.internalServerError)(err_1);
436
572
  }
437
- // Invoke onError callback if provided
438
- if (this.onError) {
439
- try {
440
- result = this.onError(errorResponse, {
441
- req: req,
442
- method: method,
443
- path: routeProps.path,
444
- reqId: req.reqId,
445
- });
446
- // Handle async callbacks - don't wait for them
447
- if (result instanceof Promise) {
448
- result.catch(function (callbackErr) {
449
- FlinkLog_1.log.error("onError callback rejected with: ".concat(callbackErr));
450
- });
451
- }
452
- }
453
- catch (callbackErr) {
454
- FlinkLog_1.log.error("onError callback threw an exception: ".concat(callbackErr));
455
- }
456
- }
573
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
457
574
  return [2 /*return*/, res.status(errorResponse.status || 500).json(errorResponse)];
458
575
  case 6:
576
+ // Skip response handling for streaming handlers (stream controls response lifecycle)
577
+ if (streamFormat) {
578
+ return [2 /*return*/];
579
+ }
580
+ // Ensure handlerRes is defined for non-streaming handlers
581
+ if (!handlerRes) {
582
+ return [2 /*return*/, res.status(204).send()];
583
+ }
459
584
  if (validateRes_1 && !(0, utils_1.isError)(handlerRes)) {
460
- valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
461
- if (!valid) {
462
- formattedErrors = (0, utils_1.formatValidationErrors)(validateRes_1.errors, handlerRes.data);
463
- FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response\n").concat(formattedErrors));
464
- return [2 /*return*/, res.status(500).json({
585
+ if (handlerRes.data === undefined) {
586
+ if (handlerRes.status !== 204) {
587
+ detail = "Response schema is defined but handler returned no data";
588
+ FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response - ").concat(detail));
589
+ errorResponse = {
590
+ status: 500,
591
+ error: { id: (0, uuid_1.v4)(), title: "Bad response", detail: detail },
592
+ };
593
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
594
+ return [2 /*return*/, res.status(500).json(errorResponse)];
595
+ }
596
+ }
597
+ else {
598
+ valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
599
+ if (!valid) {
600
+ formattedErrors = (0, utils_1.formatValidationErrors)(validateRes_1.errors, handlerRes.data);
601
+ FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response\n").concat(formattedErrors));
602
+ errorResponse = {
465
603
  status: 500,
466
604
  error: {
467
605
  id: (0, uuid_1.v4)(),
468
606
  title: "Bad response",
469
607
  detail: formattedErrors,
470
608
  },
471
- })];
609
+ };
610
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
611
+ return [2 /*return*/, res.status(500).json(errorResponse)];
612
+ }
472
613
  }
473
614
  }
474
615
  res.set(handlerRes.headers);
616
+ if (routeProps.responseType) {
617
+ return [2 /*return*/, res
618
+ .status(handlerRes.status || 200)
619
+ .type(routeProps.responseType)
620
+ .send(handlerRes.data)];
621
+ }
622
+ if (((_c = handlerRes.error) === null || _c === void 0 ? void 0 : _c.meta) !== undefined) {
623
+ try {
624
+ JSON.stringify(handlerRes.error.meta);
625
+ }
626
+ catch (e) {
627
+ FlinkLog_1.log.warn("[".concat(handlerRes.reqId, "] error.meta stripped from error ").concat(handlerRes.error.id, ": not JSON-serializable (").concat(e.message, ")"));
628
+ delete handlerRes.error.meta;
629
+ }
630
+ }
475
631
  res.status(handlerRes.status || 200).json(handlerRes);
476
632
  return [2 /*return*/];
477
633
  }
@@ -483,10 +639,175 @@ var FlinkApp = /** @class */ (function () {
483
639
  }
484
640
  else {
485
641
  this.handlerRouteCache.set(methodAndRoute_1, JSON.stringify(routeProps));
486
- FlinkLog_1.log.info("Registered route ".concat(methodAndRoute_1));
642
+ initLog.info("Registered ".concat(streamFormat ? "streaming " : "", "route ").concat(methodAndRoute_1).concat(streamFormat ? " (".concat(streamFormat, ")") : ""));
487
643
  }
488
644
  }
489
645
  };
646
+ /**
647
+ * Load schema manifest from .flink directory.
648
+ * Returns empty structure if manifest doesn't exist (dev mode without build).
649
+ *
650
+ * The manifest contains:
651
+ * - definitions: ALL JSON Schema type definitions (supports $ref resolution)
652
+ * - handlers: Handler metadata with schema names (references to definitions)
653
+ * - tools: Tool metadata with schema names (references to definitions)
654
+ */
655
+ FlinkApp.prototype.loadSchemaManifest = function () {
656
+ // Return cached manifest if already loaded
657
+ if (this.schemaManifest) {
658
+ return this.schemaManifest;
659
+ }
660
+ var fs = require("fs");
661
+ var path = require("path");
662
+ var manifestPath = path.join(process.cwd(), "dist/.flink/schema-manifest.json");
663
+ if (!fs.existsSync(manifestPath)) {
664
+ FlinkLog_1.log.warn("Schema manifest not found at dist/.flink/schema-manifest.json - handlers/tools may not have validation schemas");
665
+ var emptyManifest = { definitions: {}, handlers: {}, tools: {} };
666
+ this.schemaManifest = emptyManifest;
667
+ return emptyManifest;
668
+ }
669
+ try {
670
+ var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
671
+ // Check version for backward compatibility
672
+ if (manifest.version === "2.0") {
673
+ // New format with schema universe and AJV global registry
674
+ this.schemaManifest = manifest;
675
+ // Initialize AJV and register all schemas
676
+ this.schemaAjv = new ajv_1.default({
677
+ strict: false, // Allow additional properties by default
678
+ allErrors: true, // Return all validation errors, not just first
679
+ });
680
+ (0, ajv_formats_1.default)(this.schemaAjv);
681
+ // Register all schemas in the universe
682
+ var schemas = manifest.schemas || {};
683
+ for (var _i = 0, _a = Object.values(schemas); _i < _a.length; _i++) {
684
+ var schema = _a[_i];
685
+ if (schema && typeof schema === "object" && schema.$id) {
686
+ // Skip schemas with unresolved generic type parameters (T, U, V, etc.)
687
+ if (this.hasUnresolvedTypeParams(schema, Object.keys(schemas))) {
688
+ FlinkLog_1.log.debug("Skipping registration of generic schema: ".concat(schema.$id));
689
+ continue;
690
+ }
691
+ this.schemaAjv.addSchema(schema);
692
+ }
693
+ }
694
+ FlinkLog_1.log.debug("Loaded schema manifest v2.0: ".concat(Object.keys(schemas).length, " schemas, ").concat(Object.keys(manifest.handlers).length, " handlers, ").concat(Object.keys(manifest.tools).length, " tools"));
695
+ return manifest;
696
+ }
697
+ else {
698
+ // Old format (v1.0) - still supported for migration
699
+ FlinkLog_1.log.debug("Loaded schema manifest v1.0: ".concat(Object.keys(manifest.definitions || {}).length, " definitions, ").concat(Object.keys(manifest.handlers).length, " handlers, ").concat(Object.keys(manifest.tools).length, " tools"));
700
+ this.schemaManifest = manifest;
701
+ return manifest;
702
+ }
703
+ }
704
+ catch (error) {
705
+ FlinkLog_1.log.error("Failed to parse schema manifest:", error);
706
+ var errorManifest = { definitions: {}, handlers: {}, tools: {} };
707
+ this.schemaManifest = errorManifest;
708
+ return errorManifest;
709
+ }
710
+ };
711
+ /**
712
+ * Get the AJV instance for validation (v2.0 manifests).
713
+ * Returns undefined for v1.0 manifests.
714
+ */
715
+ FlinkApp.prototype.getSchemaAjv = function () {
716
+ return this.schemaAjv;
717
+ };
718
+ /**
719
+ * Register plugin schemas into the app's AJV instance.
720
+ * Schema $id values are prefixed with `pluginId::` to avoid collisions
721
+ * with the app's own schemas or other plugins.
722
+ *
723
+ * @param pluginId Unique identifier for the plugin (used as namespace prefix)
724
+ * @param schemas Record of schema name to JSON Schema object (already prefixed)
725
+ */
726
+ FlinkApp.prototype.registerSchemas = function (pluginId, schemas) {
727
+ // Ensure schema manifest and AJV are initialized
728
+ this.loadSchemaManifest();
729
+ if (!this.schemaAjv) {
730
+ // If no v2.0 manifest exists, create a fresh AJV instance
731
+ this.schemaAjv = new ajv_1.default({
732
+ strict: false,
733
+ allErrors: true,
734
+ });
735
+ (0, ajv_formats_1.default)(this.schemaAjv);
736
+ }
737
+ for (var _i = 0, _a = Object.values(schemas); _i < _a.length; _i++) {
738
+ var schema = _a[_i];
739
+ if (schema && typeof schema === "object" && schema.$id) {
740
+ // Skip if already registered
741
+ if (this.schemaAjv.getSchema(schema.$id)) {
742
+ continue;
743
+ }
744
+ try {
745
+ this.schemaAjv.addSchema(schema);
746
+ }
747
+ catch (err) {
748
+ FlinkLog_1.log.warn("Failed to register plugin schema ".concat(schema.$id, ": ").concat(err));
749
+ }
750
+ }
751
+ }
752
+ FlinkLog_1.log.debug("Registered ".concat(Object.keys(schemas).length, " schemas from plugin '").concat(pluginId, "'"));
753
+ };
754
+ /**
755
+ * Check if a schema has unresolved generic type parameter references.
756
+ * Generic type parameters like T, U, V are single uppercase letters.
757
+ *
758
+ * @param schema JSON schema object
759
+ * @param registeredSchemaIds List of schema IDs in the manifest
760
+ * @returns true if schema references generic type params that don't exist
761
+ */
762
+ FlinkApp.prototype.hasUnresolvedTypeParams = function (schema, registeredSchemaIds) {
763
+ var schemaIdSet = new Set(registeredSchemaIds);
764
+ var checkRefs = function (obj) {
765
+ if (!obj || typeof obj !== "object") {
766
+ return false;
767
+ }
768
+ if (Array.isArray(obj)) {
769
+ return obj.some(function (item) { return checkRefs(item); });
770
+ }
771
+ for (var _i = 0, _a = Object.entries(obj); _i < _a.length; _i++) {
772
+ var _b = _a[_i], key = _b[0], value = _b[1];
773
+ if (key === "$ref" && typeof value === "string") {
774
+ // Check if ref is a single uppercase letter (generic type param)
775
+ // and not in the registered schema IDs
776
+ if (/^[A-Z]$/.test(value) && !schemaIdSet.has(value)) {
777
+ return true;
778
+ }
779
+ }
780
+ else if (typeof value === "object") {
781
+ if (checkRefs(value)) {
782
+ return true;
783
+ }
784
+ }
785
+ }
786
+ return false;
787
+ };
788
+ return checkRefs(schema);
789
+ };
790
+ /**
791
+ * Resolve schema by name from manifest.
792
+ * Works with both v1.0 (definitions) and v2.0 (schema universe) formats.
793
+ *
794
+ * @param schemaName Schema name or $id
795
+ * @returns JSON schema object or undefined
796
+ */
797
+ FlinkApp.prototype.resolveSchema = function (schemaName) {
798
+ if (!schemaName)
799
+ return undefined;
800
+ var manifest = this.loadSchemaManifest();
801
+ // v2.0 manifest - return schema from universe
802
+ if (manifest.version === "2.0" && manifest.schemas) {
803
+ return manifest.schemas[schemaName];
804
+ }
805
+ // v1.0 manifest - return from definitions
806
+ if (manifest.definitions) {
807
+ return manifest.definitions[schemaName];
808
+ }
809
+ return undefined;
810
+ };
490
811
  /**
491
812
  * Register handlers found within the `/src/handlers`
492
813
  * directory in Flink App.
@@ -495,47 +816,77 @@ var FlinkApp = /** @class */ (function () {
495
816
  */
496
817
  FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
497
818
  return __awaiter(this, void 0, void 0, function () {
498
- var _i, _a, _b, handler, assumedHttpMethod, pathParams, _c, _d, param;
499
- var _e, _f, _g;
500
- return __generator(this, function (_h) {
501
- 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++) {
502
- _b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod;
819
+ var schemaManifest, schemaCount, _i, _a, _b, handler, assumedHttpMethod, __file, metadata, reqSchema, resSchema, pathParams, _c, _d, param;
820
+ var _e;
821
+ return __generator(this, function (_f) {
822
+ schemaManifest = this.loadSchemaManifest();
823
+ schemaCount = schemaManifest.version === "2.0" ? Object.keys(schemaManifest.schemas || {}).length : Object.keys(schemaManifest.definitions || {}).length;
824
+ FlinkLog_1.log.debug("Registering ".concat(schemaCount, " schemas with AJV (manifest version: ").concat(schemaManifest.version || "1.0", ")"));
825
+ for (_i = 0, _a = exports.autoRegisteredHandlers.sort(function (a, b) {
826
+ var _a, _b, _c, _d, _e, _f;
827
+ var orderDiff = (((_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);
828
+ if (orderDiff !== 0)
829
+ return orderDiff;
830
+ // Static segments must be registered before parameterized ones to avoid
831
+ // Express matching e.g. GET /jobs/by-tags with the /jobs/:id route.
832
+ var aHasParam = ((_d = (_c = a.handler.Route) === null || _c === void 0 ? void 0 : _c.path) === null || _d === void 0 ? void 0 : _d.includes("/:")) ? 1 : 0;
833
+ var bHasParam = ((_f = (_e = b.handler.Route) === null || _e === void 0 ? void 0 : _e.path) === null || _f === void 0 ? void 0 : _f.includes("/:")) ? 1 : 0;
834
+ return aHasParam - bHasParam;
835
+ }); _i < _a.length; _i++) {
836
+ _b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod, __file = _b.__file;
503
837
  if (!handler.Route) {
504
- FlinkLog_1.log.error("Missing Props in handler ".concat(handler.__file));
838
+ FlinkLog_1.log.error("Missing Props in handler ".concat(__file));
505
839
  continue;
506
840
  }
507
841
  if (!handler.default) {
508
- FlinkLog_1.log.error("Missing exported handler function in handler ".concat(handler.__file));
842
+ FlinkLog_1.log.error("Missing exported handler function in handler ".concat(__file));
509
843
  continue;
510
844
  }
511
- if (!!((_e = handler.__params) === null || _e === void 0 ? void 0 : _e.length)) {
845
+ metadata = schemaManifest.handlers[__file || ""];
846
+ reqSchema = handler.Route.reqSchema || this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.reqSchemaName);
847
+ resSchema = handler.Route.resSchema || this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.resSchemaName);
848
+ // Validation warnings
849
+ if (!metadata &&
850
+ (handler.Route.validation === FlinkHttpHandler_1.ValidationMode.Validate ||
851
+ handler.Route.validation === FlinkHttpHandler_1.ValidationMode.ValidateRequest ||
852
+ handler.Route.validation === FlinkHttpHandler_1.ValidationMode.ValidateResponse)) {
853
+ FlinkLog_1.log.warn("Handler ".concat(__file, " expects validation but no metadata found in manifest"));
854
+ }
855
+ // Warn if schema name doesn't resolve
856
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.reqSchemaName) && !reqSchema) {
857
+ FlinkLog_1.log.warn("Handler ".concat(__file, " references reqSchema \"").concat(metadata.reqSchemaName, "\" but not found in schema universe"));
858
+ }
859
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.resSchemaName) && !resSchema) {
860
+ FlinkLog_1.log.warn("Handler ".concat(__file, " references resSchema \"").concat(metadata.resSchemaName, "\" but not found in schema universe"));
861
+ }
862
+ if (!!((_e = metadata === null || metadata === void 0 ? void 0 : metadata.paramsMetadata) === null || _e === void 0 ? void 0 : _e.length)) {
512
863
  pathParams = (0, utils_1.getPathParams)(handler.Route.path);
513
- for (_c = 0, _d = handler.__params; _c < _d.length; _c++) {
864
+ for (_c = 0, _d = metadata.paramsMetadata; _c < _d.length; _c++) {
514
865
  param = _d[_c];
515
866
  if (!pathParams.includes(param.name)) {
516
- 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, "'"));
867
+ FlinkLog_1.log.error("Handler ".concat(__file, " has param ").concat(param.name, " but it is not present in the path '").concat(handler.Route.path, "'"));
517
868
  throw new Error("Invalid/missing handler path param");
518
869
  }
519
870
  }
520
- if (pathParams.length !== handler.__params.length) {
521
- 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"));
871
+ if (pathParams.length !== metadata.paramsMetadata.length) {
872
+ FlinkLog_1.log.warn("Handler ".concat(__file, " has ").concat(metadata.paramsMetadata.length, " typed params but the path '").concat(handler.Route.path, "' has ").concat(pathParams.length, " params"));
522
873
  }
523
874
  }
524
875
  this.registerHandler({
525
- routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
876
+ routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod || (metadata === null || metadata === void 0 ? void 0 : metadata.assumedMethod), origin: this.name }),
526
877
  schema: {
527
- reqSchema: (_f = handler.__schemas) === null || _f === void 0 ? void 0 : _f.reqSchema,
528
- resSchema: (_g = handler.__schemas) === null || _g === void 0 ? void 0 : _g.resSchema,
878
+ reqSchema: reqSchema,
879
+ resSchema: resSchema,
529
880
  },
530
- queryMetadata: handler.__query || [],
531
- paramsMetadata: handler.__params || [],
881
+ queryMetadata: (metadata === null || metadata === void 0 ? void 0 : metadata.queryMetadata) || [],
882
+ paramsMetadata: (metadata === null || metadata === void 0 ? void 0 : metadata.paramsMetadata) || [],
532
883
  }, handler.default);
533
884
  }
534
885
  return [2 /*return*/];
535
886
  });
536
887
  });
537
888
  };
538
- FlinkApp.prototype.registerAutoRegisterableJobs = function () {
889
+ FlinkApp.prototype.registerAutoRegisterableJobs = function (filter) {
539
890
  return __awaiter(this, void 0, void 0, function () {
540
891
  var _loop_1, this_1, _i, autoRegisteredJobs_1, _a, jobProps, jobFn, __file;
541
892
  var _this = this;
@@ -544,30 +895,33 @@ var FlinkApp = /** @class */ (function () {
544
895
  throw new Error("Scheduler not initialized"); // should never happen
545
896
  }
546
897
  _loop_1 = function (jobProps, jobFn, __file) {
898
+ if (filter && !filter(jobProps)) {
899
+ return "continue";
900
+ }
547
901
  if (jobProps.cron && jobProps.interval) {
548
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both cron and interval are set in ").concat(__file));
902
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - both cron and interval are set in ").concat(__file));
549
903
  return "continue";
550
904
  }
551
905
  if (jobProps.cron && jobProps.afterDelay) {
552
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both cron and afterDelay are set in ").concat(__file));
906
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - both cron and afterDelay are set in ").concat(__file));
553
907
  return "continue";
554
908
  }
555
909
  if (jobProps.interval && jobProps.afterDelay) {
556
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both interval and afterDelay are set in ").concat(__file));
910
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - both interval and afterDelay are set in ").concat(__file));
557
911
  return "continue";
558
912
  }
559
913
  if (this_1.scheduler.existsById(jobProps.id)) {
560
- FlinkLog_1.log.error("Job with id ".concat(jobProps.id, " is already registered, found duplicate in ").concat(__file));
914
+ schedulerLog.error("Job with id ".concat(jobProps.id, " is already registered, found duplicate in ").concat(__file));
561
915
  return "continue";
562
916
  }
563
- FlinkLog_1.log.debug("Registering job ".concat(jobProps.id, ": ").concat(JSON.stringify(jobProps), " from ").concat(__file));
917
+ schedulerLog.debug("Registering job ".concat(jobProps.id, ": ").concat(JSON.stringify(jobProps), " from ").concat(__file));
564
918
  var task = new toad_scheduler_1.AsyncTask(jobProps.id, function () { return __awaiter(_this, void 0, void 0, function () {
565
919
  return __generator(this, function (_a) {
566
920
  switch (_a.label) {
567
921
  case 0: return [4 /*yield*/, jobFn({ ctx: this.ctx })];
568
922
  case 1:
569
923
  _a.sent();
570
- FlinkLog_1.log.debug("Job ".concat(jobProps.id, " completed"));
924
+ schedulerLog.debug("Job ".concat(jobProps.id, " completed"));
571
925
  if (jobProps.afterDelay) {
572
926
  // afterDelay runs only once, so we remove the job
573
927
  this.scheduler.removeById(jobProps.id);
@@ -576,7 +930,7 @@ var FlinkApp = /** @class */ (function () {
576
930
  }
577
931
  });
578
932
  }); }, function (err) {
579
- FlinkLog_1.log.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err));
933
+ schedulerLog.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err));
580
934
  console.error(err);
581
935
  });
582
936
  if (jobProps.cron) {
@@ -597,17 +951,41 @@ var FlinkApp = /** @class */ (function () {
597
951
  this_1.scheduler.addSimpleIntervalJob(job);
598
952
  }
599
953
  else if (jobProps.afterDelay !== undefined) {
600
- var job = new toad_scheduler_1.SimpleIntervalJob({
601
- milliseconds: (0, ms_1.default)(jobProps.afterDelay),
602
- runImmediately: false,
603
- }, task, {
604
- id: jobProps.id,
605
- preventOverrun: jobProps.singleton,
606
- });
607
- this_1.scheduler.addSimpleIntervalJob(job);
954
+ var delayMs = (0, ms_1.default)(jobProps.afterDelay);
955
+ if (delayMs === 0) {
956
+ setImmediate(function () { return __awaiter(_this, void 0, void 0, function () {
957
+ var err_2;
958
+ return __generator(this, function (_a) {
959
+ switch (_a.label) {
960
+ case 0:
961
+ _a.trys.push([0, 2, , 3]);
962
+ return [4 /*yield*/, jobFn({ ctx: this.ctx })];
963
+ case 1:
964
+ _a.sent();
965
+ return [3 /*break*/, 3];
966
+ case 2:
967
+ err_2 = _a.sent();
968
+ schedulerLog.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err_2));
969
+ console.error(err_2);
970
+ return [3 /*break*/, 3];
971
+ case 3: return [2 /*return*/];
972
+ }
973
+ });
974
+ }); });
975
+ }
976
+ else {
977
+ var job = new toad_scheduler_1.SimpleIntervalJob({
978
+ milliseconds: delayMs,
979
+ runImmediately: false,
980
+ }, task, {
981
+ id: jobProps.id,
982
+ preventOverrun: jobProps.singleton,
983
+ });
984
+ this_1.scheduler.addSimpleIntervalJob(job);
985
+ }
608
986
  }
609
987
  else {
610
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - no cron, interval or once set in ").concat(__file));
988
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - no cron, interval or once set in ").concat(__file));
611
989
  return "continue";
612
990
  }
613
991
  };
@@ -625,40 +1003,201 @@ var FlinkApp = /** @class */ (function () {
625
1003
  // TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
626
1004
  // repoInstance.ctx = this.ctx;
627
1005
  };
1006
+ FlinkApp.prototype.registerAutoRegisterableTools = function () {
1007
+ return __awaiter(this, void 0, void 0, function () {
1008
+ var ToolExecutor, getRepoInstanceName, schemaManifest, _i, autoRegisteredTools_1, toolFile, toolId, toolInstanceName, metadata, schemas, allSchemas, toolExecutor;
1009
+ return __generator(this, function (_a) {
1010
+ ToolExecutor = require("./ai/ToolExecutor").ToolExecutor;
1011
+ getRepoInstanceName = require("./utils").getRepoInstanceName;
1012
+ schemaManifest = this.loadSchemaManifest();
1013
+ for (_i = 0, autoRegisteredTools_1 = exports.autoRegisteredTools; _i < autoRegisteredTools_1.length; _i++) {
1014
+ toolFile = autoRegisteredTools_1[_i];
1015
+ if (!toolFile.Tool) {
1016
+ FlinkLog_1.log.error("Missing FlinkToolProps export in tool ".concat(toolFile.__file));
1017
+ continue;
1018
+ }
1019
+ if (!toolFile.default) {
1020
+ FlinkLog_1.log.error("Missing exported tool function in tool ".concat(toolFile.__file));
1021
+ continue;
1022
+ }
1023
+ toolId = toolFile.Tool.id;
1024
+ if (!toolId) {
1025
+ FlinkLog_1.log.error("Tool ".concat(toolFile.__file, " missing 'id' property"));
1026
+ continue;
1027
+ }
1028
+ toolInstanceName = getRepoInstanceName(toolId);
1029
+ metadata = schemaManifest.tools[toolFile.__file || ""];
1030
+ schemas = metadata
1031
+ ? {
1032
+ inputSchema: this.resolveSchema(metadata.inputSchemaName),
1033
+ outputSchema: this.resolveSchema(metadata.outputSchemaName),
1034
+ inputTypeHint: metadata.inputTypeHint,
1035
+ outputTypeHint: metadata.outputTypeHint,
1036
+ }
1037
+ : undefined;
1038
+ // Warn if schema name doesn't resolve
1039
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.inputSchemaName) && !(schemas === null || schemas === void 0 ? void 0 : schemas.inputSchema)) {
1040
+ FlinkLog_1.log.warn("Tool ".concat(toolFile.__file, " references inputSchema \"").concat(metadata.inputSchemaName, "\" but not found in schema universe"));
1041
+ }
1042
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.outputSchemaName) && !(schemas === null || schemas === void 0 ? void 0 : schemas.outputSchema)) {
1043
+ FlinkLog_1.log.warn("Tool ".concat(toolFile.__file, " references outputSchema \"").concat(metadata.outputSchemaName, "\" but not found in schema universe"));
1044
+ }
1045
+ allSchemas = schemaManifest.version === "2.0" ? schemaManifest.schemas : schemaManifest.definitions;
1046
+ toolExecutor = new ToolExecutor(toolFile.Tool, toolFile.default, this.ctx, schemas, // Auto-generated schemas from manifest (resolved from definitions)
1047
+ allSchemas);
1048
+ this.tools[toolInstanceName] = toolExecutor;
1049
+ initLog.info("Registered tool ".concat(toolInstanceName, " (").concat(toolId, ")"));
1050
+ }
1051
+ return [2 /*return*/];
1052
+ });
1053
+ });
1054
+ };
1055
+ FlinkApp.prototype.registerAutoRegisterableAgents = function () {
1056
+ return __awaiter(this, void 0, void 0, function () {
1057
+ var _a, getRepoInstanceName, toKebabCase, _loop_2, this_2, _i, autoRegisteredAgents_1, agentFile;
1058
+ return __generator(this, function (_b) {
1059
+ _a = require("./utils"), getRepoInstanceName = _a.getRepoInstanceName, toKebabCase = _a.toKebabCase;
1060
+ _loop_2 = function (agentFile) {
1061
+ // agentFile now exports a class, not a config object
1062
+ var AgentClass = agentFile.default;
1063
+ if (!AgentClass) {
1064
+ FlinkLog_1.log.error("Missing default export in agent ".concat(agentFile.__file));
1065
+ return "continue";
1066
+ }
1067
+ // Instantiate agent (similar to repo instantiation)
1068
+ var agentInstance = new AgentClass();
1069
+ // Derive instance name from class name (camelCase)
1070
+ var agentInstanceName = getRepoInstanceName(AgentClass.name);
1071
+ // Get agent ID (kebab-case) - either explicit or derived
1072
+ var agentId = agentInstance.id;
1073
+ // Check for duplicate instance name
1074
+ if (this_2.agents[agentInstanceName]) {
1075
+ var existingAgent = this_2.agents[agentInstanceName];
1076
+ throw new Error("Duplicate agent instance name: \"".concat(agentInstanceName, "\". ") +
1077
+ "Agent class \"".concat(AgentClass.name, "\" conflicts with existing agent \"").concat(existingAgent.constructor.name, "\". ") +
1078
+ "Instance names are derived by lowercasing the first letter of the class name. " +
1079
+ "Rename one of the classes or use a unique explicit 'id' property.");
1080
+ }
1081
+ // Check for duplicate agent ID
1082
+ var existingAgentWithSameId = Object.values(this_2.agents).find(function (agent) { return agent.id === agentId; });
1083
+ if (existingAgentWithSameId) {
1084
+ throw new Error("Duplicate agent ID: \"".concat(agentId, "\". ") +
1085
+ "Agent class \"".concat(AgentClass.name, "\" conflicts with existing agent \"").concat(existingAgentWithSameId.constructor.name, "\". ") +
1086
+ "Change the 'id' property on one of them to resolve the conflict.");
1087
+ }
1088
+ // Validate tools exist
1089
+ if (agentInstance.tools) {
1090
+ for (var _c = 0, _d = agentInstance.tools; _c < _d.length; _c++) {
1091
+ var toolRef = _d[_c];
1092
+ // Handle string IDs, tool file references, and tool props
1093
+ var toolId = void 0;
1094
+ if (typeof toolRef === "string") {
1095
+ toolId = toolRef;
1096
+ }
1097
+ else if ("Tool" in toolRef) {
1098
+ // FlinkToolFile - extract ID from Tool property
1099
+ toolId = toolRef.Tool.id;
1100
+ }
1101
+ else {
1102
+ // FlinkToolProps - extract ID directly
1103
+ toolId = toolRef.id;
1104
+ }
1105
+ var tool = this_2.tools[toolId];
1106
+ if (!tool) {
1107
+ FlinkLog_1.log.error("Agent ".concat(AgentClass.name, " references tool ").concat(toolId, " which is not registered"));
1108
+ throw new Error("Invalid tool reference in agent ".concat(AgentClass.name));
1109
+ }
1110
+ }
1111
+ }
1112
+ // Register agent (duplicate checks already performed above)
1113
+ this_2.agents[agentInstanceName] = agentInstance;
1114
+ initLog.info("Registered agent ".concat(agentInstanceName, " (").concat(AgentClass.name, ") with ID: ").concat(agentId));
1115
+ };
1116
+ this_2 = this;
1117
+ for (_i = 0, autoRegisteredAgents_1 = exports.autoRegisteredAgents; _i < autoRegisteredAgents_1.length; _i++) {
1118
+ agentFile = autoRegisteredAgents_1[_i];
1119
+ _loop_2(agentFile);
1120
+ }
1121
+ return [2 /*return*/];
1122
+ });
1123
+ });
1124
+ };
628
1125
  /**
629
1126
  * Constructs the app context. Will inject context in all components
630
1127
  * except for handlers which are handled in later stage.
631
1128
  */
632
1129
  FlinkApp.prototype.buildContext = function () {
633
1130
  return __awaiter(this, void 0, void 0, function () {
634
- var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx, _b, _c, repo;
635
- return __generator(this, function (_d) {
636
- if (this.dbOpts) {
637
- for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
638
- _a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
639
- repoInstance = new Repo(collectionName, this.db, this.dbClient);
640
- this.repos[repoInstanceName] = repoInstance;
641
- FlinkLog_1.log.info("Registered repo ".concat(repoInstanceName));
642
- }
643
- }
644
- else if (exports.autoRegisteredRepos.length > 0) {
645
- FlinkLog_1.log.warn("No db configured but found repo(s)");
1131
+ var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx, _b, autoRegisteredServices_1, _c, serviceInstanceName, Service, serviceInstance, _d, _e, repo, _f, _g, service, servicesWithInit;
1132
+ return __generator(this, function (_h) {
1133
+ switch (_h.label) {
1134
+ case 0:
1135
+ if (this.dbOpts) {
1136
+ for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
1137
+ _a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
1138
+ repoInstance = new Repo(collectionName, this.db, this.dbClient);
1139
+ this.repos[repoInstanceName] = repoInstance;
1140
+ initLog.info("Registered repo ".concat(repoInstanceName));
1141
+ }
1142
+ }
1143
+ else if (exports.autoRegisteredRepos.length > 0) {
1144
+ FlinkLog_1.log.warn("No db configured but found repo(s)");
1145
+ }
1146
+ pluginCtx = this.plugins.reduce(function (out, plugin) {
1147
+ if (out[plugin.id]) {
1148
+ throw new Error("Plugin ".concat(plugin.id, " is already registered"));
1149
+ }
1150
+ out[plugin.id] = plugin.ctx;
1151
+ return out;
1152
+ }, {});
1153
+ // Instantiate services (ctx not yet available - constructors must not access it)
1154
+ for (_b = 0, autoRegisteredServices_1 = exports.autoRegisteredServices; _b < autoRegisteredServices_1.length; _b++) {
1155
+ _c = autoRegisteredServices_1[_b], serviceInstanceName = _c.serviceInstanceName, Service = _c.Service;
1156
+ serviceInstance = new Service();
1157
+ this.services[serviceInstanceName] = serviceInstance;
1158
+ initLog.info("Registered service ".concat(serviceInstanceName));
1159
+ }
1160
+ this._ctx = {
1161
+ repos: this.repos,
1162
+ plugins: pluginCtx,
1163
+ auth: this.auth,
1164
+ agents: this.agents,
1165
+ services: this.services,
1166
+ };
1167
+ // Inject context into repos
1168
+ for (_d = 0, _e = Object.values(this.repos); _d < _e.length; _d++) {
1169
+ repo = _e[_d];
1170
+ repo.ctx = this.ctx;
1171
+ }
1172
+ // Inject context into services, then call onInit() in parallel
1173
+ for (_f = 0, _g = Object.values(this.services); _f < _g.length; _f++) {
1174
+ service = _g[_f];
1175
+ service.ctx = this.ctx;
1176
+ }
1177
+ servicesWithInit = Object.values(this.services).filter(function (s) { return typeof s.onInit === "function"; });
1178
+ if (!(servicesWithInit.length > 0)) return [3 /*break*/, 2];
1179
+ return [4 /*yield*/, Promise.all(servicesWithInit.map(function (s) { return s.onInit(); }))];
1180
+ case 1:
1181
+ _h.sent();
1182
+ _h.label = 2;
1183
+ case 2: return [2 /*return*/];
646
1184
  }
647
- pluginCtx = this.plugins.reduce(function (out, plugin) {
648
- if (out[plugin.id]) {
649
- throw new Error("Plugin ".concat(plugin.id, " is already registered"));
650
- }
651
- out[plugin.id] = plugin.ctx;
652
- return out;
653
- }, {});
654
- this._ctx = {
655
- repos: this.repos,
656
- plugins: pluginCtx,
657
- auth: this.auth,
658
- };
659
- for (_b = 0, _c = Object.values(this.repos); _b < _c.length; _b++) {
660
- repo = _c[_b];
661
- repo.ctx = this.ctx;
1185
+ });
1186
+ });
1187
+ };
1188
+ /**
1189
+ * Initialize agents after they've been registered and context is ready
1190
+ * Must be called after registerAutoRegisterableAgents()
1191
+ */
1192
+ FlinkApp.prototype.initializeAgents = function () {
1193
+ return __awaiter(this, void 0, void 0, function () {
1194
+ var _i, _a, agent;
1195
+ return __generator(this, function (_b) {
1196
+ // Inject context and initialize agents
1197
+ for (_i = 0, _a = Object.values(this.agents); _i < _a.length; _i++) {
1198
+ agent = _a[_i];
1199
+ agent.ctx = this.ctx;
1200
+ agent.__init(this.llmAdapters, this.tools, this.agentObserver);
662
1201
  }
663
1202
  return [2 /*return*/];
664
1203
  });
@@ -669,7 +1208,7 @@ var FlinkApp = /** @class */ (function () {
669
1208
  */
670
1209
  FlinkApp.prototype.initDb = function () {
671
1210
  return __awaiter(this, void 0, void 0, function () {
672
- var client, err_2;
1211
+ var client, err_3;
673
1212
  return __generator(this, function (_a) {
674
1213
  switch (_a.label) {
675
1214
  case 0:
@@ -685,8 +1224,8 @@ var FlinkApp = /** @class */ (function () {
685
1224
  this.dbClient = client;
686
1225
  return [3 /*break*/, 4];
687
1226
  case 3:
688
- err_2 = _a.sent();
689
- FlinkLog_1.log.error("Failed to connect to db: " + err_2);
1227
+ err_3 = _a.sent();
1228
+ FlinkLog_1.log.error("Failed to connect to db: " + err_3);
690
1229
  process.exit(1);
691
1230
  return [3 /*break*/, 4];
692
1231
  case 4:
@@ -705,7 +1244,7 @@ var FlinkApp = /** @class */ (function () {
705
1244
  */
706
1245
  FlinkApp.prototype.initPluginDb = function (plugin) {
707
1246
  return __awaiter(this, void 0, void 0, function () {
708
- var client, err_3;
1247
+ var client, err_4;
709
1248
  return __generator(this, function (_a) {
710
1249
  switch (_a.label) {
711
1250
  case 0:
@@ -726,14 +1265,14 @@ var FlinkApp = /** @class */ (function () {
726
1265
  _a.label = 2;
727
1266
  case 2:
728
1267
  _a.trys.push([2, 4, , 5]);
729
- FlinkLog_1.log.debug("Connecting to '".concat(plugin.id, "' db"));
1268
+ initLog.debug("Connecting to '".concat(plugin.id, "' db"));
730
1269
  return [4 /*yield*/, mongodb_1.MongoClient.connect(plugin.db.uri, this.getMongoConnectionOptions())];
731
1270
  case 3:
732
1271
  client = _a.sent();
733
1272
  return [2 /*return*/, client.db()];
734
1273
  case 4:
735
- err_3 = _a.sent();
736
- FlinkLog_1.log.error("Failed to connect to db defined in plugin '".concat(plugin.id, "': ") + err_3);
1274
+ err_4 = _a.sent();
1275
+ FlinkLog_1.log.error("Failed to connect to db defined in plugin '".concat(plugin.id, "': ") + err_4);
737
1276
  return [3 /*break*/, 5];
738
1277
  case 5: return [2 /*return*/];
739
1278
  }
@@ -748,12 +1287,40 @@ var FlinkApp = /** @class */ (function () {
748
1287
  if (!this.auth) {
749
1288
  throw new Error("Attempting to authenticate request (".concat(req.method, " ").concat(req.path, ") but no authPlugin is set"));
750
1289
  }
751
- return [4 /*yield*/, this.auth.authenticateRequest(req, permissions)];
1290
+ return [4 /*yield*/, this.auth.authenticateRequest(req, permissions, this._ctx)];
752
1291
  case 1: return [2 /*return*/, _a.sent()];
753
1292
  }
754
1293
  });
755
1294
  });
756
1295
  };
1296
+ /**
1297
+ * Invokes the optional onError callback in a fire-and-forget manner.
1298
+ * Any error thrown or rejected by the callback is caught and logged so
1299
+ * it never affects the error response sent to the client.
1300
+ */
1301
+ FlinkApp.prototype.invokeOnError = function (errorResponse, req, method, path) {
1302
+ if (!this.onError) {
1303
+ return;
1304
+ }
1305
+ try {
1306
+ var result = this.onError(errorResponse, {
1307
+ req: req,
1308
+ method: method,
1309
+ path: path,
1310
+ reqId: req.reqId,
1311
+ ctx: this.ctx,
1312
+ });
1313
+ // Handle async callbacks - don't wait for them
1314
+ if (result instanceof Promise) {
1315
+ result.catch(function (callbackErr) {
1316
+ FlinkLog_1.log.error("onError callback rejected with: ".concat(callbackErr));
1317
+ });
1318
+ }
1319
+ }
1320
+ catch (callbackErr) {
1321
+ FlinkLog_1.log.error("onError callback threw an exception: ".concat(callbackErr));
1322
+ }
1323
+ };
757
1324
  FlinkApp.prototype.getRegisteredRoutes = function () {
758
1325
  return Array.from(this.handlerRouteCache.values());
759
1326
  };
@@ -765,19 +1332,91 @@ var FlinkApp = /** @class */ (function () {
765
1332
  enumerable: false,
766
1333
  configurable: true
767
1334
  });
1335
+ Object.defineProperty(FlinkApp.prototype, "leaderElectionConfig", {
1336
+ get: function () {
1337
+ var _a;
1338
+ var opt = (_a = this.schedulingOptions) === null || _a === void 0 ? void 0 : _a.leaderElection;
1339
+ if (!opt)
1340
+ return undefined;
1341
+ return opt === true ? {} : opt;
1342
+ },
1343
+ enumerable: false,
1344
+ configurable: true
1345
+ });
1346
+ FlinkApp.prototype.startLeaderElection = function () {
1347
+ return __awaiter(this, void 0, void 0, function () {
1348
+ var hasAllInstanceJobs, opts;
1349
+ var _this = this;
1350
+ return __generator(this, function (_a) {
1351
+ switch (_a.label) {
1352
+ case 0:
1353
+ if (!!this.db) return [3 /*break*/, 2];
1354
+ schedulerLog.warn("Leader election is enabled but no database is configured. " +
1355
+ "Leader election requires a MongoDB connection to coordinate between instances. " +
1356
+ "Either add a database connection via the `db` option, or remove `scheduling.leaderElection` from your config. " +
1357
+ "Jobs will run on ALL instances without leader election.");
1358
+ // Fall back to running jobs on all instances
1359
+ this.scheduler = new toad_scheduler_1.ToadScheduler();
1360
+ return [4 /*yield*/, this.registerAutoRegisterableJobs()];
1361
+ case 1:
1362
+ _a.sent();
1363
+ return [2 /*return*/];
1364
+ case 2:
1365
+ hasAllInstanceJobs = exports.autoRegisteredJobs.some(function (j) { return j.Job.runOnAllInstances; });
1366
+ if (!hasAllInstanceJobs) return [3 /*break*/, 4];
1367
+ this.allInstanceScheduler = new toad_scheduler_1.ToadScheduler();
1368
+ this.scheduler = this.allInstanceScheduler;
1369
+ return [4 /*yield*/, this.registerAutoRegisterableJobs(function (job) { return !!job.runOnAllInstances; })];
1370
+ case 3:
1371
+ _a.sent();
1372
+ this.scheduler = undefined;
1373
+ _a.label = 4;
1374
+ case 4:
1375
+ opts = this.leaderElectionConfig;
1376
+ this.leaderElection = new LeaderElection_1.LeaderElection(this.db, opts);
1377
+ return [4 /*yield*/, this.leaderElection.start(
1378
+ // onBecameLeader
1379
+ function () { return __awaiter(_this, void 0, void 0, function () {
1380
+ return __generator(this, function (_a) {
1381
+ switch (_a.label) {
1382
+ case 0:
1383
+ schedulerLog.info("This instance is now the leader - starting scheduled jobs");
1384
+ this.scheduler = new toad_scheduler_1.ToadScheduler();
1385
+ return [4 /*yield*/, this.registerAutoRegisterableJobs(function (job) { return !job.runOnAllInstances; })];
1386
+ case 1:
1387
+ _a.sent();
1388
+ return [2 /*return*/];
1389
+ }
1390
+ });
1391
+ }); },
1392
+ // onLostLeadership
1393
+ function () {
1394
+ schedulerLog.info("This instance lost leadership - stopping scheduled jobs");
1395
+ if (_this.scheduler) {
1396
+ _this.scheduler.stop();
1397
+ _this.scheduler = undefined;
1398
+ }
1399
+ })];
1400
+ case 5:
1401
+ _a.sent();
1402
+ return [2 /*return*/];
1403
+ }
1404
+ });
1405
+ });
1406
+ };
768
1407
  FlinkApp.prototype.getMongoConnectionOptions = function () {
769
1408
  if (!this.dbOpts) {
770
1409
  throw new Error("No db configured");
771
1410
  }
772
1411
  var driverVersion = require("mongodb/package.json").version;
773
1412
  if (driverVersion.startsWith("3")) {
774
- FlinkLog_1.log.debug("Using legacy mongodb connection options as mongo client is version ".concat(driverVersion));
1413
+ initLog.debug("Using legacy mongodb connection options as mongo client is version ".concat(driverVersion));
775
1414
  return {
776
1415
  useNewUrlParser: true,
777
1416
  useUnifiedTopology: true,
778
1417
  };
779
1418
  }
780
- FlinkLog_1.log.debug("Using modern MongoDB client options (driver version ".concat(driverVersion, ")"));
1419
+ initLog.debug("Using modern MongoDB client options (driver version ".concat(driverVersion, ")"));
781
1420
  return {
782
1421
  serverApi: {
783
1422
  version: mongodb_1.ServerApiVersion.v1,