@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,681 @@
1
+ import { z } from "zod";
2
+ import { FlinkAgent } 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("FlinkAgent", () => {
10
+ let mockCtx: FlinkContext;
11
+ let mockLLMAdapter: LLMAdapter;
12
+
13
+ beforeEach(() => {
14
+ mockCtx = {
15
+ repos: {},
16
+ plugins: {},
17
+ };
18
+
19
+ // Mock LLM Adapter
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("withUser binding", () => {
31
+ class TestAgent extends FlinkAgent<FlinkContext> {
32
+ id = "test-agent";
33
+ description = "Test agent";
34
+ instructions() { return "Test instructions"; }
35
+ tools: string[] = [];
36
+
37
+ async query(message: string) {
38
+ const response = this.execute({ message });
39
+ return await response.result;
40
+ }
41
+
42
+ setContext(ctx: FlinkContext) {
43
+ (this as any).ctx = ctx;
44
+ }
45
+ }
46
+
47
+ it("should bind user to agent instance", () => {
48
+ const agent = new TestAgent();
49
+ agent.setContext(mockCtx);
50
+
51
+ const user = { id: "123", permissions: ["admin"] };
52
+ const boundAgent = agent.withUser(user);
53
+
54
+ // Should return a new instance
55
+ expect(boundAgent).not.toBe(agent);
56
+ // Should have bound user
57
+ expect((boundAgent as any)._boundUser).toBe(user);
58
+ });
59
+
60
+ it("should use bound user for agent permission checks", async () => {
61
+ class PermissionedAgent extends TestAgent {
62
+ permissions = "admin";
63
+ }
64
+
65
+ const agent = new PermissionedAgent();
66
+ agent.setContext(mockCtx);
67
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
68
+
69
+ const userWithoutPermission = { permissions: [] };
70
+ const userWithPermission = { permissions: ["admin"] };
71
+
72
+ // Should fail without permission
73
+ const boundAgentFail = agent.withUser(userWithoutPermission);
74
+ await expectAsync(boundAgentFail.query("test")).toBeRejected();
75
+
76
+ // Should succeed with permission
77
+ const boundAgentSuccess = agent.withUser(userWithPermission);
78
+ const result = await boundAgentSuccess.query("test");
79
+ expect(result.message).toBeDefined();
80
+ });
81
+
82
+ it("should pass bound user to tools", async () => {
83
+ const toolSpy = jasmine.createSpy("toolFn").and.returnValue(Promise.resolve({ success: true, data: { result: "ok" } }));
84
+
85
+ const toolProps: FlinkToolProps = {
86
+ id: "test_tool",
87
+ description: "Test tool",
88
+ inputSchema: z.object({}),
89
+ };
90
+
91
+ const toolExecutor = new ToolExecutor(toolProps, toolSpy as any, mockCtx);
92
+ const toolsMap = new Map([["test_tool", toolExecutor]]);
93
+
94
+ // Mock LLM to call tool
95
+ const toolMockAdapter = createStreamingMock([
96
+ {
97
+ textContent: undefined,
98
+ toolCalls: [{ id: "1", name: "test_tool", input: {} }],
99
+ usage: { inputTokens: 10, outputTokens: 20 },
100
+ stopReason: "tool_use" as const,
101
+ },
102
+ {
103
+ textContent: "Done",
104
+ toolCalls: [],
105
+ usage: { inputTokens: 10, outputTokens: 20 },
106
+ stopReason: "end_turn" as const,
107
+ },
108
+ ]);
109
+
110
+ class ToolAgent extends TestAgent {
111
+ tools: string[] = ["test_tool"];
112
+ }
113
+
114
+ const agent = new ToolAgent();
115
+ agent.setContext(mockCtx);
116
+ agent.__init(new Map([["default", toolMockAdapter]]), Object.fromEntries(toolsMap));
117
+
118
+ const user = { id: "123", permissions: ["admin"] };
119
+ const boundAgent = agent.withUser(user);
120
+
121
+ await boundAgent.query("test");
122
+
123
+ // Tool should have been called with the bound user
124
+ expect(toolSpy).toHaveBeenCalled();
125
+ const toolCall = (toolSpy as jasmine.Spy).calls.mostRecent();
126
+ expect(toolCall.args[0].user).toBe(user);
127
+ });
128
+ });
129
+
130
+ describe("Tool permission filtering", () => {
131
+ class TestAgent extends FlinkAgent<FlinkContext> {
132
+ id = "test-agent";
133
+ description = "Test agent";
134
+ instructions() { return "Test instructions"; }
135
+ tools: string[] = ["public_tool", "admin_tool"];
136
+
137
+ async query(message: string) {
138
+ const response = this.execute({ message });
139
+ return await response.result;
140
+ }
141
+
142
+ setContext(ctx: FlinkContext) {
143
+ (this as any).ctx = ctx;
144
+ }
145
+ }
146
+
147
+ it("should filter out tools user doesn't have permission for", async () => {
148
+ // Create public tool (no permissions)
149
+ const publicToolProps: FlinkToolProps = {
150
+ id: "public_tool",
151
+ description: "Public tool",
152
+ inputSchema: z.object({}),
153
+ };
154
+ const publicToolFn = async () => ({ success: true as const, data: {} });
155
+ const publicTool = new ToolExecutor(publicToolProps, publicToolFn, mockCtx);
156
+
157
+ // Create admin tool (requires admin permission)
158
+ const adminToolProps: FlinkToolProps = {
159
+ id: "admin_tool",
160
+ description: "Admin tool",
161
+ inputSchema: z.object({}),
162
+ permissions: "admin",
163
+ };
164
+ const adminToolFn = async () => ({ success: true as const, data: {} });
165
+ const adminTool = new ToolExecutor(adminToolProps, adminToolFn, mockCtx);
166
+
167
+ const toolsMap = new Map([
168
+ ["public_tool", publicTool],
169
+ ["admin_tool", adminTool],
170
+ ]);
171
+
172
+ const agent = new TestAgent();
173
+ agent.setContext(mockCtx);
174
+ agent.__init(new Map([["default", mockLLMAdapter]]), Object.fromEntries(toolsMap));
175
+
176
+ // User without admin permission
177
+ const regularUser = { permissions: [] };
178
+ const boundAgent = agent.withUser(regularUser);
179
+
180
+ await boundAgent.query("test");
181
+
182
+ // LLM should have been called with only the public tool via stream
183
+ const llmCall = (mockLLMAdapter.stream as jasmine.Spy).calls.mostRecent();
184
+ const toolsProvided = llmCall.args[0].tools;
185
+
186
+ expect(toolsProvided.length).toBe(1);
187
+ expect(toolsProvided[0].name).toBe("public_tool");
188
+ });
189
+
190
+ it("should show all tools to admin users", async () => {
191
+ // Create public and admin tools (same as above)
192
+ const publicToolProps: FlinkToolProps = {
193
+ id: "public_tool",
194
+ description: "Public tool",
195
+ inputSchema: z.object({}),
196
+ };
197
+ const publicToolFn = async () => ({ success: true as const, data: {} });
198
+ const publicTool = new ToolExecutor(publicToolProps, publicToolFn, mockCtx);
199
+
200
+ const adminToolProps: FlinkToolProps = {
201
+ id: "admin_tool",
202
+ description: "Admin tool",
203
+ inputSchema: z.object({}),
204
+ permissions: "admin",
205
+ };
206
+ const adminToolFn = async () => ({ success: true as const, data: {} });
207
+ const adminTool = new ToolExecutor(adminToolProps, adminToolFn, mockCtx);
208
+
209
+ const toolsMap = new Map([
210
+ ["public_tool", publicTool],
211
+ ["admin_tool", adminTool],
212
+ ]);
213
+
214
+ const agent = new TestAgent();
215
+ agent.setContext(mockCtx);
216
+ agent.__init(new Map([["default", mockLLMAdapter]]), Object.fromEntries(toolsMap));
217
+
218
+ // User with admin permission
219
+ const adminUser = { permissions: ["admin"] };
220
+ const boundAgent = agent.withUser(adminUser);
221
+
222
+ await boundAgent.query("test");
223
+
224
+ // LLM should have been called with both tools via stream
225
+ const llmCall = (mockLLMAdapter.stream as jasmine.Spy).calls.mostRecent();
226
+ const toolsProvided = llmCall.args[0].tools;
227
+
228
+ expect(toolsProvided.length).toBe(2);
229
+ expect(toolsProvided.map((t: any) => t.name).sort()).toEqual(["admin_tool", "public_tool"]);
230
+ });
231
+ });
232
+
233
+ describe("Tool references (FlinkToolFile imports)", () => {
234
+ it("should support tool file references in addition to string IDs", () => {
235
+ // Create mock tool file (same structure as real tool files)
236
+ const mockToolFile = {
237
+ Tool: {
238
+ id: "mock-tool",
239
+ description: "Mock tool for testing",
240
+ inputSchema: z.object({ input: z.string() }),
241
+ },
242
+ default: async () => ({ success: true as const, data: "ok" }),
243
+ };
244
+
245
+ // Create tool executor from the mock file
246
+ const toolExecutor = new ToolExecutor(mockToolFile.Tool, mockToolFile.default as any, mockCtx);
247
+
248
+ // Set up tools in context (not as Map, but as plain object for _tools)
249
+ // Agent using tool reference instead of string ID
250
+ class TestAgentWithToolRef extends FlinkAgent<FlinkContext> {
251
+ id = "test-agent";
252
+ description = "Test agent";
253
+ instructions() { return "Test instructions"; }
254
+ tools = [mockToolFile]; // Direct tool file reference!
255
+
256
+ async query(message: string) {
257
+ const response = this.execute({ message });
258
+ return await response.result;
259
+ }
260
+ }
261
+
262
+ const agent = new TestAgentWithToolRef();
263
+ (agent as any).ctx = mockCtx;
264
+ agent.__init(new Map([["default", mockLLMAdapter]]), { "mock-tool": toolExecutor });
265
+
266
+ // Verify resolveTools() correctly extracts tool ID from FlinkToolFile
267
+ const resolvedTools = (agent as any).resolveTools();
268
+ expect(resolvedTools.has("mock-tool")).toBe(true);
269
+ expect(resolvedTools.get("mock-tool")).toBe(toolExecutor);
270
+ });
271
+
272
+ it("should support mixed string IDs and tool file references", () => {
273
+ // Create two tools: one referenced by string, one by file reference
274
+ const mockToolFile = {
275
+ Tool: {
276
+ id: "tool-by-reference",
277
+ description: "Tool referenced by import",
278
+ inputSchema: z.object({}),
279
+ },
280
+ default: async () => ({ success: true as const, data: "ok" }),
281
+ };
282
+
283
+ const toolByRefExecutor = new ToolExecutor(mockToolFile.Tool, mockToolFile.default as any, mockCtx);
284
+ const toolByStringExecutor = new ToolExecutor(
285
+ { id: "tool-by-string", description: "Tool by string", inputSchema: z.object({}) },
286
+ async () => ({ success: true as const, data: "ok" }),
287
+ mockCtx
288
+ );
289
+
290
+ class MixedToolsAgent extends FlinkAgent<FlinkContext> {
291
+ id = "test-agent";
292
+ description = "Test agent";
293
+ instructions() { return "Test instructions"; }
294
+ tools = [
295
+ mockToolFile, // File reference
296
+ "tool-by-string", // String ID
297
+ ];
298
+ }
299
+
300
+ const agent = new MixedToolsAgent();
301
+ (agent as any).ctx = mockCtx;
302
+ agent.__init(new Map([["default", mockLLMAdapter]]), {
303
+ "tool-by-reference": toolByRefExecutor,
304
+ "tool-by-string": toolByStringExecutor,
305
+ });
306
+
307
+ // Both tools should be resolved correctly
308
+ const resolvedTools = (agent as any).resolveTools();
309
+ expect(resolvedTools.size).toBe(2);
310
+ expect(resolvedTools.has("tool-by-reference")).toBe(true);
311
+ expect(resolvedTools.has("tool-by-string")).toBe(true);
312
+ });
313
+ });
314
+
315
+ describe("Dynamic Instructions via Callback", () => {
316
+ it("should support static string instructions (backwards compatibility)", async () => {
317
+ class StaticAgent extends FlinkAgent<FlinkContext> {
318
+ id = "test-agent";
319
+ description = "Static agent";
320
+ instructions() { return "Static instructions"; }
321
+ tools: string[] = [];
322
+
323
+ async query(message: string) {
324
+ const response = this.execute({ message });
325
+ return await response.result;
326
+ }
327
+ }
328
+
329
+ const agent = new StaticAgent();
330
+ (agent as any).ctx = mockCtx;
331
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
332
+
333
+ const result = await agent.query("Hello");
334
+
335
+ expect(result.message).toBe("Test response");
336
+ expect(mockLLMAdapter.stream).toHaveBeenCalled();
337
+ const callArgs = (mockLLMAdapter.stream as jasmine.Spy).calls.first().args[0];
338
+ expect(callArgs.instructions).toBe("Static instructions");
339
+ });
340
+
341
+ it("should support synchronous callback", async () => {
342
+ class DynamicAgent extends FlinkAgent<FlinkContext> {
343
+ id = "test-agent";
344
+ description = "Dynamic agent";
345
+ instructions(ctx: FlinkContext, agentContext: any) {
346
+ return `You are a support agent for user ${agentContext.user?.name || "unknown"}`;
347
+ }
348
+ tools: string[] = [];
349
+
350
+ async query(message: string) {
351
+ const response = this.execute({ message });
352
+ return await response.result;
353
+ }
354
+ }
355
+
356
+ const agent = new DynamicAgent();
357
+ (agent as any).ctx = mockCtx;
358
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
359
+
360
+ const boundAgent = agent.withUser({ id: "123", name: "John Doe" });
361
+ const result = await boundAgent.query("Hello");
362
+
363
+ expect(result.message).toBe("Test response");
364
+ expect(mockLLMAdapter.stream).toHaveBeenCalled();
365
+ const callArgs = (mockLLMAdapter.stream as jasmine.Spy).calls.first().args[0];
366
+ expect(callArgs.instructions).toBe("You are a support agent for user John Doe");
367
+ });
368
+
369
+ it("should support async callback with database lookup", async () => {
370
+ const mockUser = { id: "123", name: "Jane Smith", tier: "premium" };
371
+ const mockRepoCtx = {
372
+ repos: {
373
+ userRepo: {
374
+ getById: jasmine.createSpy("getById").and.returnValue(Promise.resolve(mockUser)),
375
+ },
376
+ },
377
+ plugins: {},
378
+ };
379
+
380
+ class DatabaseAgent extends FlinkAgent<any> {
381
+ id = "test-agent";
382
+ description = "Database agent";
383
+ async instructions(ctx: any, agentContext: any) {
384
+ if (!agentContext.user) {
385
+ return "You are a support agent.";
386
+ }
387
+ const profile = await ctx.repos.userRepo.getById(agentContext.user.id);
388
+ return `You are a support agent. Customer: ${profile.name}, Tier: ${profile.tier}`;
389
+ }
390
+ tools: string[] = [];
391
+
392
+ async query(message: string) {
393
+ const response = this.execute({ message });
394
+ return await response.result;
395
+ }
396
+ }
397
+
398
+ const agent = new DatabaseAgent();
399
+ (agent as any).ctx = mockRepoCtx;
400
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
401
+
402
+ const boundAgent = agent.withUser({ id: "123" });
403
+ const result = await boundAgent.query("Hello");
404
+
405
+ expect(mockRepoCtx.repos.userRepo.getById).toHaveBeenCalledWith("123");
406
+ expect(result.message).toBe("Test response");
407
+ expect(mockLLMAdapter.stream).toHaveBeenCalled();
408
+ const callArgs = (mockLLMAdapter.stream as jasmine.Spy).calls.first().args[0];
409
+ expect(callArgs.instructions).toBe("You are a support agent. Customer: Jane Smith, Tier: premium");
410
+ });
411
+
412
+ it("should work with runner caching", async () => {
413
+ let callCount = 0;
414
+
415
+ class CachedAgent extends FlinkAgent<FlinkContext> {
416
+ id = "test-agent";
417
+ description = "Cached agent";
418
+ instructions(ctx: FlinkContext, agentContext: any) {
419
+ callCount++;
420
+ return `Instructions for user ${agentContext.user?.name || "unknown"}`;
421
+ }
422
+ tools: string[] = [];
423
+
424
+ async query(message: string) {
425
+ const response = this.execute({ message });
426
+ return await response.result;
427
+ }
428
+ }
429
+
430
+ const agent = new CachedAgent();
431
+ (agent as any).ctx = mockCtx;
432
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
433
+
434
+ // First execution with user1
435
+ const user1 = { id: "1", name: "Alice" };
436
+ const boundAgent1 = agent.withUser(user1);
437
+ await boundAgent1.query("Hello");
438
+
439
+ expect(callCount).toBe(1);
440
+ expect(mockLLMAdapter.stream).toHaveBeenCalled();
441
+ let callArgs = (mockLLMAdapter.stream as jasmine.Spy).calls.first().args[0];
442
+ expect(callArgs.instructions).toBe("Instructions for user Alice");
443
+
444
+ // Reset mock
445
+ (mockLLMAdapter.stream as jasmine.Spy).calls.reset();
446
+
447
+ // Second execution with different user (new bound agent = new runner)
448
+ const user2 = { id: "2", name: "Bob" };
449
+ const boundAgent2 = agent.withUser(user2);
450
+ await boundAgent2.query("Hello");
451
+
452
+ expect(callCount).toBe(2); // Should increment because new runner instance
453
+ expect(mockLLMAdapter.stream).toHaveBeenCalled();
454
+ callArgs = (mockLLMAdapter.stream as jasmine.Spy).calls.first().args[0];
455
+ expect(callArgs.instructions).toBe("Instructions for user Bob");
456
+ });
457
+
458
+ it("should handle callback with conversationId", async () => {
459
+ let capturedContext: any;
460
+
461
+ class ConversationAgent extends FlinkAgent<FlinkContext> {
462
+ id = "test-agent";
463
+ description = "Conversation agent";
464
+ instructions(ctx: FlinkContext, agentContext: any) {
465
+ capturedContext = agentContext;
466
+ return `Conversation: ${agentContext.conversationId || "new"}`;
467
+ }
468
+ tools: string[] = [];
469
+
470
+ async query(message: string, conversationId?: string) {
471
+ const response = this.execute({ message, conversationId });
472
+ return await response.result;
473
+ }
474
+ }
475
+
476
+ const agent = new ConversationAgent();
477
+ (agent as any).ctx = mockCtx;
478
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
479
+
480
+ await agent.query("Hello", "conv-456");
481
+
482
+ expect(capturedContext.conversationId).toBe("conv-456");
483
+ expect(mockLLMAdapter.stream).toHaveBeenCalled();
484
+ const callArgs = (mockLLMAdapter.stream as jasmine.Spy).calls.first().args[0];
485
+ expect(callArgs.instructions).toBe("Conversation: conv-456");
486
+ });
487
+
488
+ it("should throw error if callback throws", async () => {
489
+ class ErrorAgent extends FlinkAgent<FlinkContext> {
490
+ id = "test-agent";
491
+ description = "Error agent";
492
+ instructions(): string {
493
+ throw new Error("Database unavailable");
494
+ }
495
+ tools: string[] = [];
496
+
497
+ async query(message: string) {
498
+ const response = this.execute({ message });
499
+ return await response.result;
500
+ }
501
+ }
502
+
503
+ const agent = new ErrorAgent();
504
+ (agent as any).ctx = mockCtx;
505
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
506
+
507
+ await expectAsync(agent.query("Hello")).toBeRejectedWithError(/Failed to resolve instructions/);
508
+ });
509
+ });
510
+
511
+ describe("withConversationContext binding", () => {
512
+ interface TestConversationContext {
513
+ sessionId: string;
514
+ featureFlags: Record<string, boolean>;
515
+ requestId?: string;
516
+ }
517
+
518
+ class TestAgent extends FlinkAgent<FlinkContext, TestConversationContext> {
519
+ id = "test-agent";
520
+ description = "Test agent";
521
+ instructions() { return "Test instructions"; }
522
+ tools: string[] = [];
523
+
524
+ async query(message: string) {
525
+ const response = this.execute({ message });
526
+ return await response.result;
527
+ }
528
+
529
+ setContext(ctx: FlinkContext) {
530
+ (this as any).ctx = ctx;
531
+ }
532
+ }
533
+
534
+ it("should bind conversation context to agent instance", () => {
535
+ const agent = new TestAgent();
536
+ agent.setContext(mockCtx);
537
+
538
+ const conversationCtx: TestConversationContext = {
539
+ sessionId: "session-123",
540
+ featureFlags: { enableNewUI: true },
541
+ requestId: "req-456",
542
+ };
543
+ const boundAgent = agent.withConversationContext(conversationCtx);
544
+
545
+ // Should return a new instance
546
+ expect(boundAgent).not.toBe(agent);
547
+ // Should have bound conversation context
548
+ expect((boundAgent as any)._boundConversationContext).toBe(conversationCtx);
549
+ });
550
+
551
+ it("should pass bound conversation context to tools", async () => {
552
+ const toolSpy = jasmine.createSpy("toolFn").and.returnValue(Promise.resolve({ success: true, data: { result: "ok" } }));
553
+
554
+ const toolProps: FlinkToolProps = {
555
+ id: "test_tool",
556
+ description: "Test tool",
557
+ inputSchema: z.object({}),
558
+ };
559
+
560
+ const toolExecutor = new ToolExecutor(toolProps, toolSpy as any, mockCtx);
561
+ const toolsMap = new Map([["test_tool", toolExecutor]]);
562
+
563
+ // Mock LLM to call tool
564
+ const toolMockAdapter = createStreamingMock([
565
+ {
566
+ textContent: undefined,
567
+ toolCalls: [{ id: "1", name: "test_tool", input: {} }],
568
+ usage: { inputTokens: 10, outputTokens: 20 },
569
+ stopReason: "tool_use" as const,
570
+ },
571
+ {
572
+ textContent: "Done",
573
+ toolCalls: [],
574
+ usage: { inputTokens: 10, outputTokens: 20 },
575
+ stopReason: "end_turn" as const,
576
+ },
577
+ ]);
578
+
579
+ class ToolAgent extends TestAgent {
580
+ tools = ["test_tool"];
581
+ }
582
+
583
+ const agent = new ToolAgent();
584
+ agent.setContext(mockCtx);
585
+ agent.__init(new Map([["default", toolMockAdapter]]), Object.fromEntries(toolsMap));
586
+
587
+ const conversationCtx: TestConversationContext = {
588
+ sessionId: "session-123",
589
+ featureFlags: { enableNewUI: true },
590
+ };
591
+
592
+ const boundAgent = agent.withConversationContext(conversationCtx);
593
+ await boundAgent.query("test");
594
+
595
+ // Verify tool was called with conversation context
596
+ expect(toolSpy).toHaveBeenCalled();
597
+ const toolCallArgs = toolSpy.calls.first().args[0];
598
+ expect(toolCallArgs.conversationCtx).toBe(conversationCtx);
599
+ expect(toolCallArgs.conversationCtx.sessionId).toBe("session-123");
600
+ expect(toolCallArgs.conversationCtx.featureFlags.enableNewUI).toBe(true);
601
+ });
602
+
603
+ it("should support fluent chaining with other binding methods", () => {
604
+ const agent = new TestAgent();
605
+ agent.setContext(mockCtx);
606
+ agent.__init(new Map([["default", mockLLMAdapter]]), {});
607
+
608
+ const user = { id: "123", permissions: ["admin"] };
609
+ const conversationCtx: TestConversationContext = {
610
+ sessionId: "session-123",
611
+ featureFlags: { enableNewUI: true },
612
+ };
613
+
614
+ // Chain withUser and withConversationContext
615
+ const boundAgent = agent.withUser(user).withConversationContext(conversationCtx);
616
+
617
+ expect((boundAgent as any)._boundUser).toBe(user);
618
+ expect((boundAgent as any)._boundConversationContext).toBe(conversationCtx);
619
+ });
620
+
621
+ it("should allow explicit conversationContext in execute to override bound", async () => {
622
+ const toolSpy = jasmine.createSpy("toolFn").and.returnValue(Promise.resolve({ success: true, data: { result: "ok" } }));
623
+
624
+ const toolProps: FlinkToolProps = {
625
+ id: "test_tool",
626
+ description: "Test tool",
627
+ inputSchema: z.object({}),
628
+ };
629
+
630
+ const toolExecutor = new ToolExecutor(toolProps, toolSpy as any, mockCtx);
631
+ const toolsMap = new Map([["test_tool", toolExecutor]]);
632
+
633
+ const toolMockAdapter = createStreamingMock([
634
+ {
635
+ textContent: undefined,
636
+ toolCalls: [{ id: "1", name: "test_tool", input: {} }],
637
+ usage: { inputTokens: 10, outputTokens: 20 },
638
+ stopReason: "tool_use" as const,
639
+ },
640
+ {
641
+ textContent: "Done",
642
+ toolCalls: [],
643
+ usage: { inputTokens: 10, outputTokens: 20 },
644
+ stopReason: "end_turn" as const,
645
+ },
646
+ ]);
647
+
648
+ class ToolAgent extends TestAgent {
649
+ tools = ["test_tool"];
650
+
651
+ async queryWithContext(message: string, ctx: TestConversationContext) {
652
+ const response = this.execute({ message, conversationContext: ctx });
653
+ return await response.result;
654
+ }
655
+ }
656
+
657
+ const agent = new ToolAgent();
658
+ agent.setContext(mockCtx);
659
+ agent.__init(new Map([["default", toolMockAdapter]]), Object.fromEntries(toolsMap));
660
+
661
+ const boundCtx: TestConversationContext = {
662
+ sessionId: "bound-session",
663
+ featureFlags: {},
664
+ };
665
+
666
+ const explicitCtx: TestConversationContext = {
667
+ sessionId: "explicit-session",
668
+ featureFlags: { newFeature: true },
669
+ };
670
+
671
+ const boundAgent = agent.withConversationContext(boundCtx);
672
+ await boundAgent.queryWithContext("test", explicitCtx);
673
+
674
+ // Verify tool was called with explicit context (override)
675
+ expect(toolSpy).toHaveBeenCalled();
676
+ const toolCallArgs = toolSpy.calls.first().args[0];
677
+ expect(toolCallArgs.conversationCtx.sessionId).toBe("explicit-session");
678
+ expect(toolCallArgs.conversationCtx.featureFlags.newFeature).toBe(true);
679
+ });
680
+ });
681
+ });