@flink-app/flink 0.14.3 → 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 (280) hide show
  1. package/CHANGELOG.md +1051 -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 +847 -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 +219 -26
  29. package/dist/src/FlinkHttpHandler.js +37 -1
  30. package/dist/src/FlinkJob.d.ts +10 -0
  31. package/dist/src/FlinkLog.d.ts +82 -18
  32. package/dist/src/FlinkLog.js +165 -13
  33. package/dist/src/FlinkLogFactory.d.ts +288 -0
  34. package/dist/src/FlinkLogFactory.js +619 -0
  35. package/dist/src/FlinkRepo.d.ts +10 -2
  36. package/dist/src/FlinkRepo.js +11 -1
  37. package/dist/src/FlinkRequestContext.d.ts +63 -0
  38. package/dist/src/FlinkRequestContext.js +74 -0
  39. package/dist/src/FlinkResponse.d.ts +6 -0
  40. package/dist/src/FlinkService.d.ts +38 -0
  41. package/dist/src/FlinkService.js +46 -0
  42. package/dist/src/LeaderElection.d.ts +45 -0
  43. package/dist/src/LeaderElection.js +269 -0
  44. package/dist/src/SchemaCache.d.ts +84 -0
  45. package/dist/src/SchemaCache.js +289 -0
  46. package/dist/src/TypeScriptCompiler.d.ts +161 -51
  47. package/dist/src/TypeScriptCompiler.js +1253 -617
  48. package/dist/src/TypeScriptUtils.js +4 -0
  49. package/dist/src/ai/AgentRunner.d.ts +39 -0
  50. package/dist/src/ai/AgentRunner.js +760 -0
  51. package/dist/src/ai/ConversationAgent.d.ts +279 -0
  52. package/dist/src/ai/ConversationAgent.js +404 -0
  53. package/dist/src/ai/ConversationFlinkAgent.d.ts +278 -0
  54. package/dist/src/ai/ConversationFlinkAgent.js +404 -0
  55. package/dist/src/ai/FlinkAgent.d.ts +690 -0
  56. package/dist/src/ai/FlinkAgent.js +729 -0
  57. package/dist/src/ai/FlinkTool.d.ts +135 -0
  58. package/dist/src/ai/FlinkTool.js +2 -0
  59. package/dist/src/ai/InMemoryConversationAgent.d.ts +121 -0
  60. package/dist/src/ai/InMemoryConversationAgent.js +209 -0
  61. package/dist/src/ai/LLMAdapter.d.ts +148 -0
  62. package/dist/src/ai/LLMAdapter.js +2 -0
  63. package/dist/src/ai/PersistentFlinkAgent.d.ts +278 -0
  64. package/dist/src/ai/PersistentFlinkAgent.js +403 -0
  65. package/dist/src/ai/SubAgentExecutor.d.ts +38 -0
  66. package/dist/src/ai/SubAgentExecutor.js +223 -0
  67. package/dist/src/ai/ToolExecutor.d.ts +64 -0
  68. package/dist/src/ai/ToolExecutor.js +497 -0
  69. package/dist/src/ai/agentInstructions.d.ts +68 -0
  70. package/dist/src/ai/agentInstructions.js +286 -0
  71. package/dist/src/ai/index.d.ts +8 -0
  72. package/dist/src/ai/index.js +26 -0
  73. package/dist/src/ai/instructionFileLoader.d.ts +44 -0
  74. package/dist/src/ai/instructionFileLoader.js +179 -0
  75. package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
  76. package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
  77. package/dist/src/handlers/StreamWriterFactory.js +83 -0
  78. package/dist/src/index.d.ts +14 -0
  79. package/dist/src/index.js +17 -0
  80. package/dist/src/loadPluginSchemas.d.ts +45 -0
  81. package/dist/src/loadPluginSchemas.js +143 -0
  82. package/dist/src/schema-extraction/ComplexTypeDetection.d.ts +40 -0
  83. package/dist/src/schema-extraction/ComplexTypeDetection.js +75 -0
  84. package/dist/src/schema-extraction/TypeScriptSourceParser.d.ts +321 -0
  85. package/dist/src/schema-extraction/TypeScriptSourceParser.js +925 -0
  86. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.d.ts +1 -0
  87. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.js +233 -0
  88. package/dist/src/schema-extraction/TypeScriptTokenizer.d.ts +57 -0
  89. package/dist/src/schema-extraction/TypeScriptTokenizer.js +177 -0
  90. package/dist/src/schema-extraction/index.d.ts +2 -0
  91. package/dist/src/schema-extraction/index.js +20 -0
  92. package/dist/src/schema-extraction/types.d.ts +31 -0
  93. package/dist/src/schema-extraction/types.js +2 -0
  94. package/dist/src/utils/loadFlinkConfig.d.ts +53 -0
  95. package/dist/src/utils/loadFlinkConfig.js +77 -0
  96. package/dist/src/utils.d.ts +30 -0
  97. package/dist/src/utils.js +52 -0
  98. package/dist/src/workers/SchemaGeneratorWorker.d.ts +1 -0
  99. package/dist/src/workers/SchemaGeneratorWorker.js +49 -0
  100. package/dist/src/workers/WorkerPool.d.ts +60 -0
  101. package/dist/src/workers/WorkerPool.js +306 -0
  102. package/examples/logging-hierarchical-example.ts +125 -0
  103. package/package.json +29 -4
  104. package/readme.md +499 -0
  105. package/spec/AgentDescendantDetection.spec.ts +335 -0
  106. package/spec/AgentDuplicateDetection.spec.ts +112 -0
  107. package/spec/AgentObserver.spec.ts +266 -0
  108. package/spec/AgentRunner.spec.ts +1062 -0
  109. package/spec/AsyncLocalStorageContext.spec.ts +223 -0
  110. package/spec/ConversationHooks.spec.ts +257 -0
  111. package/spec/FlinkAgent.spec.ts +681 -0
  112. package/spec/FlinkApp.htmlResponse.spec.ts +260 -0
  113. package/spec/FlinkApp.onError.invocation.spec.ts +151 -0
  114. package/spec/FlinkApp.onError.spec.ts +1 -2
  115. package/spec/FlinkApp.query.spec.ts +107 -0
  116. package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
  117. package/spec/FlinkApp.undefinedResponse.spec.ts +123 -0
  118. package/spec/FlinkApp.validationMode.spec.ts +155 -0
  119. package/spec/FlinkJob.spec.ts +171 -0
  120. package/spec/FlinkLogFactory.spec.ts +337 -0
  121. package/spec/FlinkRepo.spec.ts +1 -1
  122. package/spec/LeaderElection.spec.ts +174 -0
  123. package/spec/StreamingIntegration.spec.ts +139 -0
  124. package/spec/ToolExecutor.spec.ts +465 -0
  125. package/spec/TypeScriptCompiler.spec.ts +1 -1
  126. package/spec/TypeScriptSourceParser.spec.ts +1215 -0
  127. package/spec/TypeScriptTokenizer.spec.ts +366 -0
  128. package/spec/ai/ContextCompaction.spec.ts +405 -0
  129. package/spec/ai/ConversationAgent.spec.ts +520 -0
  130. package/spec/ai/InMemoryConversationAgent.spec.ts +144 -0
  131. package/spec/ai/agentInstructions.spec.ts +358 -0
  132. package/spec/fixtures/agent-instructions/TestAgent.ts +24 -0
  133. package/spec/fixtures/agent-instructions/simple.md +3 -0
  134. package/spec/fixtures/agent-instructions/template.md +18 -0
  135. package/spec/fixtures/agent-instructions/yaml-format.yaml +9 -0
  136. package/spec/mock-project/dist/.tsbuildinfo +1 -0
  137. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +56 -0
  138. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +58 -0
  139. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +52 -0
  140. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +52 -0
  141. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +52 -0
  142. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +54 -0
  143. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +54 -0
  144. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +57 -0
  145. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +57 -0
  146. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
  147. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
  148. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +57 -0
  149. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +75 -0
  150. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +57 -0
  151. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +58 -0
  152. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +58 -0
  153. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +54 -0
  154. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +55 -0
  155. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +54 -0
  156. package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +54 -0
  157. package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
  158. package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
  159. package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
  160. package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
  161. package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
  162. package/spec/mock-project/dist/src/FlinkApp.js +1000 -0
  163. package/spec/mock-project/dist/src/FlinkContext.js +2 -0
  164. package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
  165. package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
  166. package/spec/mock-project/dist/src/FlinkJob.js +2 -0
  167. package/spec/mock-project/dist/src/FlinkLog.js +119 -0
  168. package/spec/mock-project/dist/src/FlinkLogFactory.js +617 -0
  169. package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
  170. package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
  171. package/spec/mock-project/dist/src/FlinkRequestContext.js +74 -0
  172. package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
  173. package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
  174. package/spec/mock-project/dist/src/ai/AgentRunner.js +632 -0
  175. package/spec/mock-project/dist/src/ai/ConversationAgent.js +402 -0
  176. package/spec/mock-project/dist/src/ai/ConversationFlinkAgent.js +422 -0
  177. package/spec/mock-project/dist/src/ai/FlinkAgent.js +699 -0
  178. package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
  179. package/spec/mock-project/dist/src/ai/InMemoryConversationAgent.js +209 -0
  180. package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
  181. package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +223 -0
  182. package/spec/mock-project/dist/src/ai/ToolExecutor.js +412 -0
  183. package/spec/mock-project/dist/src/ai/agentInstructions.js +246 -0
  184. package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
  185. package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
  186. package/spec/mock-project/dist/src/handlers/GetCar.js +26 -52
  187. package/spec/mock-project/dist/src/handlers/GetCar.js.map +1 -0
  188. package/spec/mock-project/dist/src/handlers/GetCar2.js +32 -54
  189. package/spec/mock-project/dist/src/handlers/GetCar2.js.map +1 -0
  190. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +26 -48
  191. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js.map +1 -0
  192. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +28 -48
  193. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js.map +1 -0
  194. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +29 -48
  195. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js.map +1 -0
  196. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +26 -50
  197. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js.map +1 -0
  198. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +28 -50
  199. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js.map +1 -0
  200. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +27 -53
  201. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js.map +1 -0
  202. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +29 -53
  203. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js.map +1 -0
  204. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +16 -49
  205. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js.map +1 -0
  206. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +25 -50
  207. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js.map +1 -0
  208. package/spec/mock-project/dist/src/handlers/PatchCar.js +27 -53
  209. package/spec/mock-project/dist/src/handlers/PatchCar.js.map +1 -0
  210. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js +44 -70
  211. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js.map +1 -0
  212. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js +27 -53
  213. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js.map +1 -0
  214. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js +28 -54
  215. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js.map +1 -0
  216. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js +28 -54
  217. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js.map +1 -0
  218. package/spec/mock-project/dist/src/handlers/PostCar.js +24 -50
  219. package/spec/mock-project/dist/src/handlers/PostCar.js.map +1 -0
  220. package/spec/mock-project/dist/src/handlers/PostLogin.js +25 -51
  221. package/spec/mock-project/dist/src/handlers/PostLogin.js.map +1 -0
  222. package/spec/mock-project/dist/src/handlers/PostLogout.js +24 -50
  223. package/spec/mock-project/dist/src/handlers/PostLogout.js.map +1 -0
  224. package/spec/mock-project/dist/src/handlers/PutCar.js +24 -50
  225. package/spec/mock-project/dist/src/handlers/PutCar.js.map +1 -0
  226. package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
  227. package/spec/mock-project/dist/src/index.js +52 -76
  228. package/spec/mock-project/dist/src/index.js.map +1 -0
  229. package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
  230. package/spec/mock-project/dist/src/repos/CarRepo.js +12 -24
  231. package/spec/mock-project/dist/src/repos/CarRepo.js.map +1 -0
  232. package/spec/mock-project/dist/src/schemas/Car.js +3 -1
  233. package/spec/mock-project/dist/src/schemas/Car.js.map +1 -0
  234. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +3 -1
  235. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js.map +1 -0
  236. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +3 -1
  237. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js.map +1 -0
  238. package/spec/mock-project/dist/src/utils.js +290 -0
  239. package/spec/mock-project/tsconfig.json +6 -1
  240. package/spec/schema-generation-nested-objects.spec.ts +97 -0
  241. package/spec/testHelpers.ts +49 -0
  242. package/spec/utils.caseConversion.spec.ts +78 -0
  243. package/spec/utils.spec.ts +13 -13
  244. package/src/DependencyTracker.ts +166 -0
  245. package/src/FlinkApp.ts +919 -155
  246. package/src/FlinkContext.ts +43 -0
  247. package/src/FlinkErrors.ts +32 -12
  248. package/src/FlinkHttpHandler.ts +246 -28
  249. package/src/FlinkJob.ts +11 -0
  250. package/src/FlinkLog.ts +119 -12
  251. package/src/FlinkLogFactory.ts +699 -0
  252. package/src/FlinkRepo.ts +10 -3
  253. package/src/FlinkRequestContext.ts +95 -0
  254. package/src/FlinkResponse.ts +6 -0
  255. package/src/FlinkService.ts +49 -0
  256. package/src/LeaderElection.ts +203 -0
  257. package/src/SchemaCache.ts +232 -0
  258. package/src/TypeScriptCompiler.ts +1347 -610
  259. package/src/TypeScriptUtils.ts +5 -0
  260. package/src/ai/AgentRunner.ts +646 -0
  261. package/src/ai/ConversationAgent.ts +413 -0
  262. package/src/ai/FlinkAgent.ts +1069 -0
  263. package/src/ai/FlinkTool.ts +165 -0
  264. package/src/ai/InMemoryConversationAgent.ts +149 -0
  265. package/src/ai/LLMAdapter.ts +126 -0
  266. package/src/ai/ToolExecutor.ts +485 -0
  267. package/src/ai/agentInstructions.ts +245 -0
  268. package/src/ai/index.ts +8 -0
  269. package/src/ai/instructionFileLoader.ts +156 -0
  270. package/src/auth/FlinkAuthPlugin.ts +2 -1
  271. package/src/handlers/StreamWriterFactory.ts +84 -0
  272. package/src/index.ts +14 -0
  273. package/src/loadPluginSchemas.ts +141 -0
  274. package/src/schema-extraction/TypeScriptSourceParser.ts +1058 -0
  275. package/src/schema-extraction/TypeScriptTokenizer.ts +205 -0
  276. package/src/schema-extraction/index.ts +2 -0
  277. package/src/schema-extraction/types.ts +34 -0
  278. package/src/utils/loadFlinkConfig.ts +89 -0
  279. package/src/utils.ts +52 -0
  280. package/tsconfig.json +6 -1
@@ -0,0 +1,485 @@
1
+ import Ajv from "ajv";
2
+ import addFormats from "ajv-formats";
3
+ import { FlinkContext } from "../FlinkContext";
4
+ import { forbidden } from "../FlinkErrors";
5
+ import { FlinkLogFactory } from "../FlinkLogFactory";
6
+ import { getRequestContext, getRequestUser } from "../FlinkRequestContext";
7
+ import { FlinkTool, FlinkToolProps, ToolResult } from "./FlinkTool";
8
+ import { FlinkToolSchema } from "./LLMAdapter";
9
+
10
+ const toolLog = FlinkLogFactory.createLogger("flink.ai.tool");
11
+
12
+ export class ToolExecutor<Ctx extends FlinkContext> {
13
+ private ajv: Ajv;
14
+ private compiledInputValidator?: ReturnType<Ajv["compile"]>;
15
+ private compiledOutputValidator?: ReturnType<Ajv["compile"]>;
16
+
17
+ constructor(
18
+ private toolProps: FlinkToolProps,
19
+ private toolFn: FlinkTool<Ctx, any, any>,
20
+ private ctx: Ctx,
21
+ private autoSchemas?: {
22
+ inputSchema?: any;
23
+ outputSchema?: any;
24
+ inputTypeHint?: 'void' | 'any' | 'named';
25
+ outputTypeHint?: 'void' | 'any' | 'named';
26
+ },
27
+ private allSchemas?: Record<string, any>
28
+ ) {
29
+ this.ajv = new Ajv({ allErrors: true });
30
+ addFormats(this.ajv);
31
+
32
+ // Pre-populate AJV with all schemas so $ref references resolve across schema boundaries
33
+ if (allSchemas) {
34
+ for (const schema of Object.values(allSchemas)) {
35
+ if (schema && schema.$id) {
36
+ try {
37
+ this.ajv.addSchema(schema);
38
+ } catch {
39
+ // Ignore duplicate schema errors (schema may already be added)
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ // Pre-compile validators once at construction time (not per invocation)
46
+ if (toolProps.inputJsonSchema) {
47
+ this.compiledInputValidator = this.ajv.compile(toolProps.inputJsonSchema);
48
+ } else if (autoSchemas?.inputSchema) {
49
+ this.compiledInputValidator = this.ajv.compile(autoSchemas.inputSchema);
50
+ }
51
+
52
+ if (toolProps.outputJsonSchema) {
53
+ this.compiledOutputValidator = this.ajv.compile(toolProps.outputJsonSchema);
54
+ } else if (autoSchemas?.outputSchema) {
55
+ this.compiledOutputValidator = this.ajv.compile(autoSchemas.outputSchema);
56
+ }
57
+ // Log when using auto-schemas
58
+ if (autoSchemas?.inputSchema && !toolProps.inputSchema && !toolProps.inputJsonSchema) {
59
+ toolLog.debug(`Tool ${toolProps.id}: Using auto-generated schemas from type parameters`);
60
+ }
61
+
62
+ // Log when no schema is provided (valid for void/any input types)
63
+ const hasInputSchema = toolProps.inputSchema || toolProps.inputJsonSchema || autoSchemas?.inputSchema;
64
+ if (!hasInputSchema) {
65
+ toolLog.debug(`Tool ${toolProps.id}: No input schema provided (input type is void or any)`);
66
+ }
67
+
68
+ // Warn if both manual and auto provided (manual takes precedence)
69
+ if ((toolProps.inputSchema || toolProps.inputJsonSchema) && autoSchemas?.inputSchema) {
70
+ toolLog.debug(`Tool ${toolProps.id}: Manual schemas take precedence over auto-generated schemas`);
71
+ }
72
+
73
+ // Log warning if both manual Zod and JSON schemas are provided
74
+ if (toolProps.inputSchema && toolProps.inputJsonSchema) {
75
+ toolLog.warn(`Tool ${toolProps.id}: Both 'inputSchema' (Zod) and 'inputJsonSchema' are provided. ` + `Using 'inputSchema' for validation.`);
76
+ }
77
+
78
+ if (toolProps.outputSchema && toolProps.outputJsonSchema) {
79
+ toolLog.warn(`Tool ${toolProps.id}: Both 'outputSchema' (Zod) and 'outputJsonSchema' are provided. ` + `Using 'outputSchema' for validation.`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Execute the tool with input
85
+ * @param input - Tool input data
86
+ * @param overrides - Optional overrides for user/permissions/conversationContext (for testing)
87
+ */
88
+ async execute(input: any, overrides?: { user?: any; permissions?: string[]; conversationContext?: any }): Promise<ToolResult<any>> {
89
+ // Get user, permissions, and conversationContext from AsyncLocalStorage or overrides
90
+ const user = overrides?.user ?? getRequestUser();
91
+ const userPermissions = overrides?.permissions ?? getRequestContext()?.userPermissions;
92
+ const conversationContext = overrides?.conversationContext;
93
+
94
+ // 1. Permission check
95
+ if (this.toolProps.permissions) {
96
+ const hasPermission = await this.checkPermissionsInternal(input, user, userPermissions);
97
+ if (!hasPermission) {
98
+ toolLog.debug("Tool invocator is missing required permission(s)", this.toolProps.permissions, "user has", userPermissions);
99
+ throw forbidden(`Permission denied for tool ${this.toolProps.id}`, "PERMISSION_DENIED");
100
+ }
101
+ }
102
+
103
+ // 2. Input validation (priority: Zod > JSON Schema > Auto-generated)
104
+ let validatedInput: any;
105
+ try {
106
+ if (this.toolProps.inputSchema) {
107
+ // Priority 1: Use Zod validation
108
+ validatedInput = this.toolProps.inputSchema.parse(input);
109
+ } else if (this.compiledInputValidator) {
110
+ // Priority 2 & 3: Use pre-compiled JSON Schema validator (manual or auto-generated)
111
+ const valid = this.compiledInputValidator(input);
112
+ if (!valid) {
113
+ const errorDetails = this.formatAjvErrors(this.compiledInputValidator.errors || [], input);
114
+ toolLog.warn(`Tool ${this.toolProps.id} input validation failed:`, errorDetails);
115
+ return {
116
+ success: false,
117
+ error: `Invalid input for tool '${this.toolProps.id}': ${errorDetails}`,
118
+ code: "VALIDATION_ERROR",
119
+ };
120
+ }
121
+ validatedInput = input;
122
+ } else {
123
+ // No schema available - skip validation
124
+ validatedInput = input;
125
+ }
126
+ } catch (err: any) {
127
+ toolLog.warn(`Tool ${this.toolProps.id} input validation failed:`, err.message);
128
+
129
+ // Format Zod validation errors for better LLM understanding
130
+ const errorDetails = this.formatZodError(err, input);
131
+
132
+ return {
133
+ success: false,
134
+ error: `Invalid input for tool '${this.toolProps.id}': ${errorDetails}`,
135
+ code: "VALIDATION_ERROR",
136
+ };
137
+ }
138
+
139
+ // 3. Execute tool
140
+ toolLog.debug(`Executing tool ${this.toolProps.id}`);
141
+ let result: ToolResult<any>;
142
+ try {
143
+ toolLog.trace(this.toolFn.name + " input:", validatedInput);
144
+
145
+ result = await this.toolFn({
146
+ input: validatedInput,
147
+ ctx: this.ctx,
148
+ user,
149
+ permissions: userPermissions,
150
+ conversationCtx: conversationContext,
151
+ });
152
+ } catch (err: any) {
153
+ toolLog.error(`Tool ${this.toolProps.id} threw error:`, err.message);
154
+ return {
155
+ success: false,
156
+ error: `Tool execution failed: ${err.message}`,
157
+ code: "EXECUTION_ERROR",
158
+ };
159
+ }
160
+
161
+ // 4. Handle error results
162
+ if (!result.success) {
163
+ toolLog.warn(`Tool ${this.toolProps.id} returned error:`, result.error);
164
+ return result; // Return error result as-is
165
+ }
166
+
167
+ // 5. Output validation (priority: Zod > JSON Schema > Auto-generated)
168
+ if (this.toolProps.outputSchema) {
169
+ // Priority 1: Use Zod validation
170
+ try {
171
+ const validatedData = this.toolProps.outputSchema.parse(result.data);
172
+ return { success: true, data: validatedData };
173
+ } catch (err: any) {
174
+ toolLog.error(`Tool ${this.toolProps.id} output validation failed:`, err.message);
175
+ return {
176
+ success: false,
177
+ error: `Invalid output from tool ${this.toolProps.id}: ${err.message}`,
178
+ code: "OUTPUT_VALIDATION_ERROR",
179
+ };
180
+ }
181
+ } else if (this.compiledOutputValidator) {
182
+ // Priority 2 & 3: Use pre-compiled JSON Schema validator (manual or auto-generated)
183
+ try {
184
+ const valid = this.compiledOutputValidator(result.data);
185
+ if (!valid) {
186
+ const errorDetails = this.formatAjvErrors(this.compiledOutputValidator.errors || []);
187
+ toolLog.error(`Tool ${this.toolProps.id} output validation failed:`, errorDetails);
188
+ return {
189
+ success: false,
190
+ error: `Invalid output from tool ${this.toolProps.id}: ${errorDetails}`,
191
+ code: "OUTPUT_VALIDATION_ERROR",
192
+ };
193
+ }
194
+ return { success: true, data: result.data };
195
+ } catch (err: any) {
196
+ toolLog.error(`Tool ${this.toolProps.id} output validation failed:`, err.message);
197
+ return {
198
+ success: false,
199
+ error: `Invalid output from tool ${this.toolProps.id}: ${err.message}`,
200
+ code: "OUTPUT_VALIDATION_ERROR",
201
+ };
202
+ }
203
+ }
204
+
205
+ // No output validation - return result as-is
206
+ return result;
207
+ }
208
+
209
+ getToolSchema(): FlinkToolSchema {
210
+ // Priority order: inputJsonSchema > inputSchema (Zod 4.x) > autoSchemas
211
+
212
+ // Priority 1: Manual JSON schema
213
+ if (this.toolProps.inputJsonSchema) {
214
+ return {
215
+ name: this.toolProps.id,
216
+ description: this.toolProps.description,
217
+ inputSchema: this.toolProps.inputJsonSchema,
218
+ };
219
+ }
220
+
221
+ // Priority 2: Zod schema (convert to JSON Schema using Zod 4.x)
222
+ if (this.toolProps.inputSchema) {
223
+ const z = require("zod") as any;
224
+
225
+ if (!z.toJSONSchema) {
226
+ throw new Error(
227
+ `Tool ${this.toolProps.id}: Zod 4.x is required for automatic schema generation. ` +
228
+ `Either upgrade to Zod 4.x or provide 'inputJsonSchema' in your tool definition, ` +
229
+ `or use TypeScript type parameters for auto-generation.`
230
+ );
231
+ }
232
+
233
+ return {
234
+ name: this.toolProps.id,
235
+ description: this.toolProps.description,
236
+ inputSchema: z.toJSONSchema(this.toolProps.inputSchema),
237
+ };
238
+ }
239
+
240
+ // Priority 3: Auto-generated schema from type parameters
241
+ if (this.autoSchemas?.inputSchema) {
242
+ return {
243
+ name: this.toolProps.id,
244
+ description: this.toolProps.description,
245
+ inputSchema: this.resolveSchemaRefs(this.autoSchemas.inputSchema),
246
+ };
247
+ }
248
+
249
+ // No schema provided - return schema based on type hint
250
+ const typeHint = this.autoSchemas?.inputTypeHint;
251
+
252
+ if (typeHint === 'void') {
253
+ // void: Tool takes no input - reject any properties
254
+ return {
255
+ name: this.toolProps.id,
256
+ description: this.toolProps.description,
257
+ inputSchema: {
258
+ type: "object",
259
+ properties: {},
260
+ additionalProperties: false,
261
+ },
262
+ };
263
+ } else if (typeHint === 'any') {
264
+ // any: Tool accepts any input - allow any properties
265
+ return {
266
+ name: this.toolProps.id,
267
+ description: this.toolProps.description,
268
+ inputSchema: {
269
+ type: "object",
270
+ additionalProperties: true,
271
+ },
272
+ };
273
+ } else {
274
+ // No type hint - default to void behavior (no input expected)
275
+ return {
276
+ name: this.toolProps.id,
277
+ description: this.toolProps.description,
278
+ inputSchema: {
279
+ type: "object",
280
+ properties: {},
281
+ additionalProperties: false,
282
+ },
283
+ };
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Resolve cross-schema $ref values into $defs so the schema is self-contained.
289
+ *
290
+ * Flink's schema manifest stores schemas as separate documents with IDs like
291
+ * "Canvas.ElementInput". These are valid for AJV (which uses a schema registry),
292
+ * but LLM providers like OpenAI require a single self-contained schema where all
293
+ * $ref values point to #/$defs/... entries at the top level.
294
+ */
295
+ private resolveSchemaRefs(schema: any): any {
296
+ if (!this.allSchemas) return schema;
297
+
298
+ const defs: Record<string, any> = {};
299
+
300
+ const collectRefs = (node: any, visited: Set<string>): void => {
301
+ if (!node || typeof node !== "object") return;
302
+
303
+ if (node.$ref && typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
304
+ const refId = node.$ref;
305
+ if (!visited.has(refId) && this.allSchemas![refId]) {
306
+ visited.add(refId);
307
+ // Clone and strip top-level JSON Schema meta fields not valid inside $defs
308
+ const def = JSON.parse(JSON.stringify(this.allSchemas![refId]));
309
+ delete def.$id;
310
+ delete def.$schema;
311
+ defs[refId] = def;
312
+ // Recurse into the referenced schema to collect its deps
313
+ collectRefs(def, visited);
314
+ }
315
+ }
316
+
317
+ for (const value of Object.values(node)) {
318
+ collectRefs(value, visited);
319
+ }
320
+ };
321
+
322
+ const visited = new Set<string>();
323
+ collectRefs(schema, visited);
324
+
325
+ if (Object.keys(defs).length === 0) return schema;
326
+
327
+ // Deep clone and rewrite all non-standard $ref values to #/$defs/<id>
328
+ const resolved = JSON.parse(JSON.stringify(schema));
329
+ delete resolved.$id;
330
+ delete resolved.$schema;
331
+
332
+ const rewriteRefs = (node: any): void => {
333
+ if (!node || typeof node !== "object") return;
334
+
335
+ if (node.$ref && typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
336
+ node.$ref = `#/$defs/${node.$ref}`;
337
+ }
338
+
339
+ for (const value of Object.values(node)) {
340
+ rewriteRefs(value);
341
+ }
342
+ };
343
+
344
+ rewriteRefs(resolved);
345
+ // Also rewrite refs inside the collected defs
346
+ for (const def of Object.values(defs)) {
347
+ rewriteRefs(def);
348
+ }
349
+
350
+ resolved.$defs = defs;
351
+ return resolved;
352
+ }
353
+
354
+ /**
355
+ * Get tool result for AI consumption
356
+ * Formats ToolResult into string for AI context
357
+ */
358
+ formatResultForAI(result: ToolResult<any>): string {
359
+ if (result.success) {
360
+ return JSON.stringify(result.data);
361
+ } else {
362
+ return `Error: ${result.error}${result.code ? ` (${result.code})` : ""}`;
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Check if user has permission to use this tool
368
+ * Used by AgentRunner to filter tools before showing to LLM
369
+ *
370
+ * @param user - User object
371
+ * @param input - Tool input (for function-based permissions)
372
+ * @param userPermissions - Optional resolved permissions from auth plugin (preferred)
373
+ * @param conversationContext - Optional conversation context
374
+ */
375
+ async checkPermissions(user?: any, input?: any, userPermissions?: string[], conversationContext?: any): Promise<boolean> {
376
+ const perms = this.toolProps.permissions;
377
+ if (!perms) return true;
378
+ if (typeof perms === "function") return await perms(input ?? {}, user);
379
+
380
+ // Get effective permissions (prefer explicit userPermissions)
381
+ const effectivePerms = userPermissions || user?.permissions || [];
382
+
383
+ // If no user and no explicit permissions, deny access
384
+ if (!user && !userPermissions) return false;
385
+
386
+ const requiredPerms = Array.isArray(perms) ? perms : [perms];
387
+ return requiredPerms.every((p) => effectivePerms.includes(p));
388
+ }
389
+
390
+ private async checkPermissionsInternal(input: any, user?: any, userPermissions?: string[], conversationContext?: any): Promise<boolean> {
391
+ return this.checkPermissions(user, input, userPermissions, conversationContext);
392
+ }
393
+
394
+ /**
395
+ * Format Zod validation errors into LLM-friendly error messages
396
+ * Provides specific guidance on what's missing or incorrect
397
+ */
398
+ private formatZodError(err: any, receivedInput?: any): string {
399
+ // Check if it's a Zod error with issues array
400
+ if (err.issues && Array.isArray(err.issues)) {
401
+ const issues = err.issues.map((issue: any) => {
402
+ const path = issue.path.join(".");
403
+ const field = path || "input";
404
+
405
+ if (issue.code === "invalid_type") {
406
+ if (issue.received === "undefined") {
407
+ return `Missing required field '${field}' (expected ${issue.expected})`;
408
+ }
409
+ return `Field '${field}' has wrong type: expected ${issue.expected}, got ${issue.received}`;
410
+ }
411
+
412
+ if (issue.code === "too_small") {
413
+ if (issue.type === "string") {
414
+ return `Field '${field}' is too short (minimum ${issue.minimum} characters)`;
415
+ }
416
+ return `Field '${field}' is too small (minimum ${issue.minimum})`;
417
+ }
418
+
419
+ if (issue.code === "too_big") {
420
+ if (issue.type === "string") {
421
+ return `Field '${field}' is too long (maximum ${issue.maximum} characters)`;
422
+ }
423
+ return `Field '${field}' is too large (maximum ${issue.maximum})`;
424
+ }
425
+
426
+ // Generic fallback
427
+ return `${field}: ${issue.message}`;
428
+ });
429
+
430
+ if (receivedInput) {
431
+ const inputInfo =
432
+ Object.keys(receivedInput || {}).length === 0 ? "You provided an empty object {}." : `You provided: ${JSON.stringify(receivedInput)}`;
433
+ return `${issues.join("; ")}. ${inputInfo}`;
434
+ }
435
+
436
+ return issues.join("; ");
437
+ }
438
+
439
+ // Fallback for non-Zod errors
440
+ return err.message || "Unknown validation error";
441
+ }
442
+
443
+ /**
444
+ * Format AJV validation errors into LLM-friendly error messages
445
+ */
446
+ private formatAjvErrors(errors: any[], receivedInput?: any): string {
447
+ const issues = errors.map((error) => {
448
+ const field = error.instancePath ? error.instancePath.substring(1).replace(/\//g, ".") : "input";
449
+
450
+ if (error.keyword === "required") {
451
+ return `Missing required field '${error.params.missingProperty}'`;
452
+ }
453
+
454
+ if (error.keyword === "type") {
455
+ return `Field '${field}' has wrong type: expected ${error.params.type}`;
456
+ }
457
+
458
+ if (error.keyword === "minLength") {
459
+ return `Field '${field}' is too short (minimum ${error.params.limit} characters)`;
460
+ }
461
+
462
+ if (error.keyword === "maxLength") {
463
+ return `Field '${field}' is too long (maximum ${error.params.limit} characters)`;
464
+ }
465
+
466
+ if (error.keyword === "minimum") {
467
+ return `Field '${field}' is too small (minimum ${error.params.limit})`;
468
+ }
469
+
470
+ if (error.keyword === "maximum") {
471
+ return `Field '${field}' is too large (maximum ${error.params.limit})`;
472
+ }
473
+
474
+ return `${field}: ${error.message}`;
475
+ });
476
+
477
+ if (receivedInput) {
478
+ const inputInfo =
479
+ Object.keys(receivedInput || {}).length === 0 ? "You provided an empty object {}." : `You provided: ${JSON.stringify(receivedInput)}`;
480
+ return `${issues.join("; ")}. ${inputInfo}`;
481
+ }
482
+
483
+ return issues.join("; ");
484
+ }
485
+ }