@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,223 @@
1
+ import { FlinkContext } from '../src/FlinkContext';
2
+ import { ToolExecutor } from '../src/ai/ToolExecutor';
3
+ import { FlinkTool, FlinkToolProps } from '../src/ai/FlinkTool';
4
+ import { requestContext, getRequestUser, getReqId, getRequestPermissions, hasPermission, hasAllPermissions, hasAnyPermission } from '../src/FlinkRequestContext';
5
+ import { z } from 'zod';
6
+
7
+ describe("AsyncLocalStorage Request Context", () => {
8
+ let mockCtx: FlinkContext;
9
+
10
+ beforeEach(() => {
11
+ mockCtx = {
12
+ repos: {},
13
+ plugins: {},
14
+ };
15
+ });
16
+
17
+ describe("Basic context availability", () => {
18
+ it("should provide request context within requestContext.run()", async () => {
19
+ const testContext = {
20
+ reqId: "test-123",
21
+ user: { id: "user-1", username: "testuser" },
22
+ userPermissions: ["car:read"],
23
+ timestamp: Date.now(),
24
+ };
25
+
26
+ await requestContext.run(testContext, async () => {
27
+ expect(getReqId()).toBe("test-123");
28
+ expect(getRequestUser()).toEqual(testContext.user);
29
+ expect(getRequestPermissions()).toEqual(["car:read"]);
30
+ });
31
+ });
32
+
33
+ it("should return undefined outside of request context", () => {
34
+ expect(getReqId()).toBeUndefined();
35
+ expect(getRequestUser()).toBeUndefined();
36
+ expect(getRequestPermissions()).toEqual([]);
37
+ });
38
+ });
39
+
40
+ describe("Tool execution with context", () => {
41
+ it("should access context inside tool execution", async () => {
42
+ const toolProps: FlinkToolProps = {
43
+ id: "test-tool",
44
+ description: "Test tool",
45
+ inputSchema: z.object({ value: z.string() }),
46
+ };
47
+
48
+ let capturedReqId: string | undefined;
49
+ let capturedUser: any;
50
+
51
+ const toolFn: FlinkTool<any, any, any> = async ({ input }) => {
52
+ capturedReqId = getReqId();
53
+ capturedUser = getRequestUser();
54
+ return { success: true, data: { result: input.value } };
55
+ };
56
+
57
+ const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
58
+
59
+ const testContext = {
60
+ reqId: "tool-test-123",
61
+ user: { id: "user-2", username: "tooltester" },
62
+ timestamp: Date.now(),
63
+ };
64
+
65
+ await requestContext.run(testContext, async () => {
66
+ const result = await executor.execute({ value: "hello" });
67
+ expect(result.success).toBe(true);
68
+ });
69
+
70
+ expect(capturedReqId).toBe("tool-test-123");
71
+ expect(capturedUser).toEqual(testContext.user);
72
+ });
73
+
74
+ it("should inject user and permissions into tool handler parameters", async () => {
75
+ const toolProps: FlinkToolProps = {
76
+ id: "test-tool-params",
77
+ description: "Test tool with user/permissions parameters",
78
+ inputSchema: z.object({ value: z.string() }),
79
+ };
80
+
81
+ let capturedUser: any;
82
+ let capturedPermissions: string[] | undefined;
83
+
84
+ const toolFn: FlinkTool<any, any, any> = async ({ input, user, permissions }) => {
85
+ capturedUser = user;
86
+ capturedPermissions = permissions;
87
+ return { success: true, data: { result: input.value } };
88
+ };
89
+
90
+ const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
91
+
92
+ const testContext = {
93
+ reqId: "params-test-123",
94
+ user: { id: "user-3", username: "paramstester" },
95
+ userPermissions: ["car:read", "car:write"],
96
+ timestamp: Date.now(),
97
+ };
98
+
99
+ await requestContext.run(testContext, async () => {
100
+ const result = await executor.execute({ value: "test" });
101
+ expect(result.success).toBe(true);
102
+ });
103
+
104
+ expect(capturedUser).toEqual(testContext.user);
105
+ expect(capturedPermissions).toEqual(["car:read", "car:write"]);
106
+ });
107
+
108
+ it("should support override parameters for testing", async () => {
109
+ const toolProps: FlinkToolProps = {
110
+ id: "test-tool-override",
111
+ description: "Test tool with overrides",
112
+ inputSchema: z.object({ value: z.string() }),
113
+ };
114
+
115
+ let capturedUser: any;
116
+ let capturedPermissions: string[] | undefined;
117
+
118
+ const toolFn: FlinkTool<any, any, any> = async ({ input, user, permissions }) => {
119
+ capturedUser = user;
120
+ capturedPermissions = permissions;
121
+ return { success: true, data: { result: input.value } };
122
+ };
123
+
124
+ const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
125
+
126
+ // Execute with explicit overrides (no AsyncLocalStorage context)
127
+ const result = await executor.execute(
128
+ { value: "test" },
129
+ {
130
+ user: { id: "override-user", username: "override" },
131
+ permissions: ["override:permission"],
132
+ }
133
+ );
134
+
135
+ expect(result.success).toBe(true);
136
+ expect(capturedUser).toEqual({ id: "override-user", username: "override" });
137
+ expect(capturedPermissions).toEqual(["override:permission"]);
138
+ });
139
+ });
140
+
141
+ describe("Nested async operations", () => {
142
+ it("should maintain context across async boundaries", async () => {
143
+ const testContext = {
144
+ reqId: "nested-123",
145
+ user: { id: "user-3" },
146
+ timestamp: Date.now(),
147
+ };
148
+
149
+ await requestContext.run(testContext, async () => {
150
+ // Simulate async operation
151
+ await new Promise(resolve => setTimeout(resolve, 10));
152
+
153
+ expect(getReqId()).toBe("nested-123");
154
+
155
+ // Nested async operation
156
+ await (async () => {
157
+ await new Promise(resolve => setTimeout(resolve, 10));
158
+ expect(getReqId()).toBe("nested-123");
159
+ })();
160
+ });
161
+ });
162
+ });
163
+
164
+ describe("Permission helpers", () => {
165
+ it("should check permissions correctly", async () => {
166
+ const testContext = {
167
+ reqId: "perm-test",
168
+ userPermissions: ["car:read", "car:write"],
169
+ timestamp: Date.now(),
170
+ };
171
+
172
+ await requestContext.run(testContext, async () => {
173
+ expect(hasPermission("car:read")).toBe(true);
174
+ expect(hasPermission("car:delete")).toBe(false);
175
+ expect(hasAllPermissions(["car:read", "car:write"])).toBe(true);
176
+ expect(hasAllPermissions(["car:read", "car:delete"])).toBe(false);
177
+ expect(hasAnyPermission(["car:read", "car:delete"])).toBe(true);
178
+ expect(hasAnyPermission(["car:delete", "car:admin"])).toBe(false);
179
+ });
180
+ });
181
+
182
+ it("should return false for permissions when no context exists", () => {
183
+ expect(hasPermission("car:read")).toBe(false);
184
+ expect(hasAllPermissions(["car:read"])).toBe(false);
185
+ expect(hasAnyPermission(["car:read"])).toBe(false);
186
+ });
187
+ });
188
+
189
+ describe("Multiple concurrent contexts", () => {
190
+ it("should isolate contexts between concurrent requests", async () => {
191
+ const context1 = {
192
+ reqId: "req-1",
193
+ user: { id: "user-1" },
194
+ timestamp: Date.now(),
195
+ };
196
+
197
+ const context2 = {
198
+ reqId: "req-2",
199
+ user: { id: "user-2" },
200
+ timestamp: Date.now(),
201
+ };
202
+
203
+ // Run two contexts concurrently
204
+ const [result1, result2] = await Promise.all([
205
+ requestContext.run(context1, async () => {
206
+ await new Promise(resolve => setTimeout(resolve, 5));
207
+ return { reqId: getReqId(), userId: getRequestUser()?.id };
208
+ }),
209
+ requestContext.run(context2, async () => {
210
+ await new Promise(resolve => setTimeout(resolve, 5));
211
+ return { reqId: getReqId(), userId: getRequestUser()?.id };
212
+ }),
213
+ ]);
214
+
215
+ // Each context should maintain its own values
216
+ expect(result1.reqId).toBe("req-1");
217
+ expect(result1.userId).toBe("user-1");
218
+ expect(result2.reqId).toBe("req-2");
219
+ expect(result2.userId).toBe("user-2");
220
+ });
221
+ });
222
+
223
+ });
@@ -0,0 +1,257 @@
1
+ import { z } from "zod";
2
+ import { AgentExecuteContext, AgentExecuteInput, FlinkAgent, Message } from "../src/ai/FlinkAgent";
3
+ import { FlinkToolProps } from "../src/ai/FlinkTool";
4
+ import { LLMAdapter } from "../src/ai/LLMAdapter";
5
+ import { ToolExecutor } from "../src/ai/ToolExecutor";
6
+ import { FlinkContext } from "../src/FlinkContext";
7
+ import { createStreamingMock } from "./testHelpers";
8
+
9
+ describe("Conversation Hooks", () => {
10
+ let mockCtx: FlinkContext;
11
+ let mockLLMAdapter: LLMAdapter;
12
+
13
+ beforeEach(() => {
14
+ mockCtx = {
15
+ repos: {},
16
+ plugins: {},
17
+ agents: {},
18
+ };
19
+
20
+ mockLLMAdapter = createStreamingMock([
21
+ {
22
+ textContent: "Test response",
23
+ toolCalls: [],
24
+ usage: { inputTokens: 10, outputTokens: 20 },
25
+ stopReason: "end_turn" as const,
26
+ },
27
+ ]);
28
+ });
29
+
30
+ describe("beforeRun hook", () => {
31
+ it("should be called before execution with context", async () => {
32
+ const beforeRunSpy = jasmine.createSpy("beforeRun");
33
+
34
+ class TestAgent extends FlinkAgent<FlinkContext> {
35
+ id = "test-agent";
36
+ description = "Test agent";
37
+ instructions() { return "Test instructions"; }
38
+ tools: string[] = [];
39
+
40
+ protected beforeRun = beforeRunSpy;
41
+
42
+ async query(message: string, conversationId?: string) {
43
+ const response = this.execute({ message, conversationId });
44
+ return await response.result;
45
+ }
46
+
47
+ setContext(ctx: FlinkContext) {
48
+ (this as any).ctx = ctx;
49
+ }
50
+ }
51
+
52
+ const agent = new TestAgent();
53
+ agent.setContext(mockCtx);
54
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
55
+
56
+ await agent.query("Hello", "conv-123");
57
+
58
+ expect(beforeRunSpy).toHaveBeenCalled();
59
+ const [input, context] = beforeRunSpy.calls.mostRecent().args;
60
+ expect(input.message).toBe("Hello");
61
+ expect(input.conversationId).toBe("conv-123");
62
+ expect(context.conversationId).toBe("conv-123");
63
+ });
64
+
65
+ it("should allow loading conversation history", async () => {
66
+ const mockHistory: Message[] = [
67
+ { role: "user", content: "Previous question" },
68
+ { role: "assistant", content: "Previous answer" },
69
+ ];
70
+
71
+ class TestAgent extends FlinkAgent<FlinkContext> {
72
+ id = "test-agent";
73
+ description = "Test agent";
74
+ instructions() { return "Test instructions"; }
75
+ tools: string[] = [];
76
+
77
+ protected async beforeRun(input: AgentExecuteInput, context: AgentExecuteContext) {
78
+ if (input.conversationId === "conv-123") {
79
+ input.history = mockHistory;
80
+ }
81
+ }
82
+
83
+ async query(message: string, conversationId?: string) {
84
+ const response = this.execute({ message, conversationId });
85
+ return await response.result;
86
+ }
87
+
88
+ setContext(ctx: FlinkContext) {
89
+ (this as any).ctx = ctx;
90
+ }
91
+ }
92
+
93
+ const agent = new TestAgent();
94
+ agent.setContext(mockCtx);
95
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
96
+
97
+ await agent.query("New question", "conv-123");
98
+
99
+ // Verify LLM was called with history via stream
100
+ const llmCall = (mockLLMAdapter.stream as jasmine.Spy).calls.mostRecent();
101
+ const messages = llmCall.args[0].messages;
102
+
103
+ expect(messages.length).toBeGreaterThan(1);
104
+ expect(messages[0].content).toBe("Previous question");
105
+ expect(messages[1].content).toBe("Previous answer");
106
+ });
107
+ });
108
+
109
+ describe("onStep hook", () => {
110
+ it("should be called after each step with context", async () => {
111
+ const onStepSpy = jasmine.createSpy("onStep");
112
+
113
+ // Create tool that triggers multiple steps
114
+ const toolProps: FlinkToolProps = {
115
+ id: "test_tool",
116
+ description: "Test tool",
117
+ inputSchema: z.object({}),
118
+ };
119
+ const toolFn = jasmine.createSpy("toolFn").and.returnValue(Promise.resolve({ success: true, data: { result: "ok" } }));
120
+ const toolExecutor = new ToolExecutor(toolProps, toolFn as any, mockCtx);
121
+ const toolsMap = new Map([["test_tool", toolExecutor]]);
122
+
123
+ // Mock LLM to call tool, then finish
124
+ const toolMockAdapter = createStreamingMock([
125
+ {
126
+ textContent: undefined,
127
+ toolCalls: [{ id: "1", name: "test_tool", input: {} }],
128
+ usage: { inputTokens: 10, outputTokens: 20 },
129
+ stopReason: "tool_use" as const,
130
+ },
131
+ {
132
+ textContent: "Done",
133
+ toolCalls: [],
134
+ usage: { inputTokens: 10, outputTokens: 20 },
135
+ stopReason: "end_turn" as const,
136
+ },
137
+ ]);
138
+
139
+ class TestAgent extends FlinkAgent<FlinkContext> {
140
+ id = "test-agent";
141
+ description = "Test agent";
142
+ instructions() { return "Test instructions"; }
143
+ tools: string[] = ["test_tool"];
144
+
145
+ protected onStep = onStepSpy;
146
+
147
+ async query(message: string) {
148
+ const response = this.execute({ message });
149
+ return await response.result;
150
+ }
151
+
152
+ setContext(ctx: FlinkContext) {
153
+ (this as any).ctx = ctx;
154
+ }
155
+ }
156
+
157
+ const agent = new TestAgent();
158
+ agent.setContext(mockCtx);
159
+ agent.__init(new Map([["default", toolMockAdapter]]), Object.fromEntries(toolsMap));
160
+
161
+ await agent.query("Test");
162
+
163
+ // Should be called twice (after each step)
164
+ expect(onStepSpy).toHaveBeenCalledTimes(2);
165
+
166
+ const firstCall = onStepSpy.calls.first().args[0];
167
+ expect(firstCall.step).toBe(1);
168
+ expect(firstCall.messages).toBeDefined();
169
+ expect(Array.isArray(firstCall.messages)).toBe(true);
170
+ });
171
+ });
172
+
173
+ describe("afterRun hook", () => {
174
+ it("should be called after execution with full context", async () => {
175
+ const afterRunSpy = jasmine.createSpy("afterRun");
176
+
177
+ class TestAgent extends FlinkAgent<FlinkContext> {
178
+ id = "test-agent";
179
+ description = "Test agent";
180
+ instructions() { return "Test instructions"; }
181
+ tools: string[] = [];
182
+
183
+ protected afterRun = afterRunSpy;
184
+
185
+ async query(message: string, conversationId?: string) {
186
+ const response = this.execute({ message, conversationId });
187
+ return await response.result;
188
+ }
189
+
190
+ setContext(ctx: FlinkContext) {
191
+ (this as any).ctx = ctx;
192
+ }
193
+ }
194
+
195
+ const agent = new TestAgent();
196
+ agent.setContext(mockCtx);
197
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
198
+
199
+ await agent.query("Hello", "conv-456");
200
+
201
+ expect(afterRunSpy).toHaveBeenCalled();
202
+ const [result, context] = afterRunSpy.calls.mostRecent().args;
203
+ expect(result.message).toBe("Test response");
204
+ expect(context.conversationId).toBe("conv-456");
205
+ expect(context.messages).toBeDefined();
206
+ expect(context.result).toBe(result);
207
+ });
208
+
209
+ });
210
+
211
+ describe("metadata propagation", () => {
212
+ it("should propagate metadata through execution context", async () => {
213
+ const beforeRunSpy = jasmine.createSpy("beforeRun");
214
+ const afterRunSpy = jasmine.createSpy("afterRun");
215
+
216
+ class TestAgent extends FlinkAgent<FlinkContext> {
217
+ id = "test-agent";
218
+ description = "Test agent";
219
+ instructions() { return "Test instructions"; }
220
+ tools: string[] = [];
221
+
222
+ protected beforeRun = beforeRunSpy;
223
+ protected afterRun = afterRunSpy;
224
+
225
+ async query(message: string) {
226
+ const response = this.execute({
227
+ message,
228
+ metadata: { customKey: "customValue", traceId: "trace-123" },
229
+ });
230
+ return await response.result;
231
+ }
232
+
233
+ setContext(ctx: FlinkContext) {
234
+ (this as any).ctx = ctx;
235
+ }
236
+ }
237
+
238
+ const agent = new TestAgent();
239
+ agent.setContext(mockCtx);
240
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
241
+
242
+ await agent.query("Test");
243
+
244
+ const beforeRunContext = beforeRunSpy.calls.mostRecent().args[1];
245
+ expect(beforeRunContext.metadata).toEqual({
246
+ customKey: "customValue",
247
+ traceId: "trace-123",
248
+ });
249
+
250
+ const afterRunContext = afterRunSpy.calls.mostRecent().args[1];
251
+ expect(afterRunContext.metadata).toEqual({
252
+ customKey: "customValue",
253
+ traceId: "trace-123",
254
+ });
255
+ });
256
+ });
257
+ });