@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
@@ -214,6 +214,11 @@ export function getTypeMetadata(type: Type<ts.Type>) {
214
214
  return [];
215
215
  }
216
216
 
217
+ // Handle empty object literal {} (used in streaming handlers)
218
+ if (type.getText() === "{}") {
219
+ return [];
220
+ }
221
+
217
222
  const symbol = getSymbolOrAlias(type);
218
223
 
219
224
  if (!symbol) {
@@ -0,0 +1,646 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import {
3
+ FlinkAgentProps,
4
+ AgentExecuteResult,
5
+ AgentExecuteInput,
6
+ StreamChunk,
7
+ Message,
8
+ AgentExecuteContext,
9
+ AgentStepContext,
10
+ AgentFinishContext,
11
+ AgentObserver,
12
+ } from "./FlinkAgent";
13
+ import { ToolExecutor } from "./ToolExecutor";
14
+ import { LLMAdapter, LLMMessage, LLMContentBlock, FlinkToolSchema } from "./LLMAdapter";
15
+ import { FlinkLogFactory } from "../FlinkLogFactory";
16
+ import { log } from "../FlinkLog";
17
+
18
+ const observerLog = FlinkLogFactory.createLogger("flink.ai.observer");
19
+
20
+ export class AgentRunner {
21
+ private llmAdapter: LLMAdapter;
22
+ private maxTokens: number;
23
+ private temperature: number;
24
+ private maxSteps: number;
25
+ private timeoutMs: number;
26
+
27
+ constructor(
28
+ private agentProps: FlinkAgentProps<any>,
29
+ private tools: Map<string, ToolExecutor<any>>,
30
+ llmAdapters: Map<string, LLMAdapter>,
31
+ private agentName?: string, // Optional agent name for logging
32
+ private ctx?: any, // FlinkContext for instruction callbacks (any for flexibility)
33
+ private observer?: AgentObserver
34
+ ) {
35
+ // Get appropriate LLM adapter based on adapterId
36
+ const adapterId = agentProps.model?.adapterId || "default";
37
+
38
+ const adapter = llmAdapters.get(adapterId);
39
+ if (!adapter) {
40
+ throw new Error(`LLM adapter "${adapterId}" not configured - register it in FlinkOptions.ai.llms`);
41
+ }
42
+
43
+ this.llmAdapter = adapter;
44
+ this.maxTokens = agentProps.model?.maxTokens || 4096;
45
+ this.temperature = agentProps.model?.temperature || 0.7;
46
+ this.maxSteps = agentProps.limits?.maxSteps || 10;
47
+ this.timeoutMs = agentProps.limits?.timeoutMs || 60000;
48
+ }
49
+
50
+ async *streamGenerator(input: AgentExecuteInput): AsyncGenerator<StreamChunk> {
51
+ const maxSteps = input.options?.maxSteps || this.maxSteps;
52
+ const toolCalls: AgentExecuteResult["toolCalls"] = [];
53
+ const runId = uuidv4();
54
+ const runStartedAt = Date.now();
55
+ const agentId = this.agentName || "unknown";
56
+ const modelInfo = {
57
+ adapterId: this.agentProps.model?.adapterId,
58
+ maxTokens: this.maxTokens,
59
+ temperature: this.temperature,
60
+ };
61
+ const declaredToolNames = Array.from(this.tools.keys());
62
+
63
+ // Build execution context
64
+ const execContext: AgentExecuteContext = {
65
+ agentId,
66
+ conversationId: input.conversationId,
67
+ user: input.user,
68
+ metadata: input.metadata,
69
+ conversationContext: input.conversationContext,
70
+ };
71
+
72
+ // Resolve instructions (callback or string)
73
+ let resolvedInstructions: string;
74
+ try {
75
+ resolvedInstructions =
76
+ typeof this.agentProps.instructions === "function" ? await this.agentProps.instructions(this.ctx, execContext) : this.agentProps.instructions;
77
+ } catch (err: any) {
78
+ throw new Error(`Failed to resolve instructions for agent ${this.agentName}: ${err.message}`);
79
+ }
80
+
81
+ // Initialize messages from history + new input
82
+ let messages: LLMMessage[];
83
+
84
+ if (input.history && input.history.length > 0) {
85
+ // Start with history
86
+ messages = this.convertMessages(input.history);
87
+ } else {
88
+ messages = [];
89
+ }
90
+
91
+ // Add new user message
92
+ if (typeof input.message === "string") {
93
+ messages.push({ role: "user", content: input.message });
94
+ } else if (Array.isArray(input.message) && input.message.length > 0 && "type" in input.message[0]) {
95
+ // LLMContentBlock[] — multimodal content (e.g. text + images)
96
+ messages.push({ role: "user", content: input.message as LLMContentBlock[] });
97
+ } else {
98
+ messages.push(...this.convertMessages(input.message as Message[]));
99
+ }
100
+
101
+ // Dispatch observer onRun (pre-loop, before compaction / tool filtering)
102
+ this.safeDispatch("onRun", () =>
103
+ this.observer?.onRun?.({
104
+ runId,
105
+ agentId,
106
+ instructions: resolvedInstructions,
107
+ input,
108
+ messages: [...messages],
109
+ tools: declaredToolNames,
110
+ model: modelInfo,
111
+ context: execContext,
112
+ })
113
+ );
114
+
115
+ let step = 0;
116
+ let finalMessage = "";
117
+ let stoppedEarly = false;
118
+ let totalInputTokens = 0;
119
+ let totalOutputTokens = 0;
120
+ let totalCachedInputTokens = 0;
121
+ let totalCacheCreationInputTokens = 0;
122
+ let finalProviderMetadata: Record<string, any> = {};
123
+
124
+ const buildResult = (): AgentExecuteResult => ({
125
+ runId,
126
+ message: finalMessage,
127
+ toolCalls,
128
+ stepsUsed: step,
129
+ stoppedEarly,
130
+ usage: {
131
+ inputTokens: totalInputTokens,
132
+ outputTokens: totalOutputTokens,
133
+ ...(totalCachedInputTokens > 0 && { cachedInputTokens: totalCachedInputTokens }),
134
+ ...(totalCacheCreationInputTokens > 0 && { cacheCreationInputTokens: totalCacheCreationInputTokens }),
135
+ },
136
+ providerMetadata: Object.keys(finalProviderMetadata).length > 0 ? finalProviderMetadata : undefined,
137
+ });
138
+
139
+ try {
140
+ while (step < maxSteps) {
141
+ step++;
142
+
143
+ // Filter tools based on user permissions (only show allowed tools to LLM)
144
+ const availableTools = await this.filterToolsByPermissions(input.user, input.userPermissions, input.conversationContext);
145
+
146
+ // Check if context compaction is needed
147
+ if (this.agentProps.shouldCompact) {
148
+ try {
149
+ const needsCompaction = await this.agentProps.shouldCompact(messages, step);
150
+
151
+ if (needsCompaction) {
152
+ const beforeCount = messages.length;
153
+ const originalMessages = messages; // Save original in case of error
154
+
155
+ // Perform compaction using provided callback or default strategy
156
+ let compactedMessages: LLMMessage[];
157
+ if (this.agentProps.compactHistory) {
158
+ compactedMessages = await this.agentProps.compactHistory(messages, step);
159
+ } else {
160
+ // Default strategy: sliding window keeping last 10 messages
161
+ compactedMessages = messages.slice(-10);
162
+ }
163
+
164
+ // Validation: ensure compacted array is not empty
165
+ if (!compactedMessages || compactedMessages.length === 0) {
166
+ throw new Error("compactHistory must return at least one message");
167
+ }
168
+
169
+ // Apply compaction
170
+ messages = compactedMessages;
171
+
172
+ // Log compaction for debugging
173
+ log.debug(`[Agent:${this.agentName}] Step ${step}: Compacted ${beforeCount} messages → ${messages.length}`);
174
+
175
+ log.debug(`[Agent:${this.agentName}] Compacted messages:`, {
176
+ messageCount: messages.length,
177
+ messages: messages.map((m) => ({
178
+ role: m.role,
179
+ contentPreview:
180
+ typeof m.content === "string"
181
+ ? m.content.substring(0, 100) + (m.content.length > 100 ? "..." : "")
182
+ : `${(m.content as any[]).length} blocks`,
183
+ })),
184
+ });
185
+ }
186
+ } catch (error: any) {
187
+ // Log error but don't fail execution - compaction is optional optimization
188
+ log.error(`[Agent:${this.agentName}] Context compaction failed:`, error.message);
189
+ }
190
+ }
191
+
192
+ log.debug(`[Agent:${this.agentName}] Step ${step}/${maxSteps} - Calling LLM with:`, {
193
+ instructionsType: typeof this.agentProps.instructions === "function" ? "dynamic-callback" : "static",
194
+ messageCount: messages.length,
195
+ messages: messages.map((m) => ({
196
+ role: m.role,
197
+ contentPreview:
198
+ typeof m.content === "string"
199
+ ? m.content.substring(0, 100) + (m.content.length > 100 ? "..." : "")
200
+ : `${(m.content as any[]).length} blocks`,
201
+ })),
202
+ toolCount: availableTools.length,
203
+ tools: availableTools.map((t) => t.name),
204
+ maxTokens: this.maxTokens,
205
+ temperature: this.temperature,
206
+ });
207
+
208
+ // Dispatch observer onLlmCall — messages reflect post-compaction state;
209
+ // tools reflect per-step permission filtering
210
+ this.safeDispatch("onLlmCall", () =>
211
+ this.observer?.onLlmCall?.({
212
+ runId,
213
+ agentId,
214
+ step,
215
+ maxSteps,
216
+ instructions: resolvedInstructions,
217
+ messages: [...messages],
218
+ tools: availableTools.map((t) => t.name),
219
+ model: modelInfo,
220
+ context: execContext,
221
+ })
222
+ );
223
+
224
+ // Track tool call count before this step so we can slice the per-step
225
+ // tool calls for the onStep observer event
226
+ const toolCallsBeforeStep = toolCalls.length;
227
+
228
+ // Call AI model via adapter using streaming
229
+ const llmStream = this.llmAdapter.stream({
230
+ instructions: resolvedInstructions,
231
+ messages,
232
+ tools: availableTools,
233
+ maxTokens: this.maxTokens,
234
+ temperature: this.temperature,
235
+ providerMetadata: input.providerMetadata,
236
+ });
237
+
238
+ // Accumulate response from stream
239
+ let textContent = "";
240
+ const toolCallsFromStream: Array<{ id: string; name: string; input: any }> = [];
241
+ let usage: import("./LLMAdapter").LLMUsage = { inputTokens: 0, outputTokens: 0 };
242
+ let stopReason: "end_turn" | "tool_use" | "max_tokens" = "end_turn";
243
+ let providerMetadata: Record<string, any> = {};
244
+
245
+ // Process streaming chunks
246
+ for await (const chunk of llmStream) {
247
+ switch (chunk.type) {
248
+ case "text":
249
+ textContent += chunk.delta;
250
+ // Yield text_delta event in real-time
251
+ yield { type: "text_delta", delta: chunk.delta };
252
+ break;
253
+
254
+ case "tool_call":
255
+ toolCallsFromStream.push(chunk.toolCall);
256
+ break;
257
+
258
+ case "usage":
259
+ usage = chunk.usage;
260
+ totalInputTokens += chunk.usage.inputTokens;
261
+ totalOutputTokens += chunk.usage.outputTokens;
262
+ totalCachedInputTokens += chunk.usage.cachedInputTokens || 0;
263
+ totalCacheCreationInputTokens += chunk.usage.cacheCreationInputTokens || 0;
264
+ break;
265
+
266
+ case "metadata":
267
+ // Merge provider metadata (e.g., responseId for continuation)
268
+ providerMetadata = { ...providerMetadata, ...chunk.metadata };
269
+ finalProviderMetadata = { ...finalProviderMetadata, ...chunk.metadata };
270
+ break;
271
+
272
+ case "done":
273
+ stopReason = chunk.stopReason as "end_turn" | "tool_use" | "max_tokens";
274
+ break;
275
+ }
276
+ }
277
+
278
+ // Create llmResponse structure for compatibility with existing code
279
+ const llmResponse = {
280
+ textContent: textContent || undefined,
281
+ toolCalls: toolCallsFromStream,
282
+ usage,
283
+ stopReason,
284
+ };
285
+
286
+ log.debug(`[Agent:${this.agentName}] Step ${step} - LLM Response:`, {
287
+ textLength: llmResponse.textContent?.length || 0,
288
+ textPreview: llmResponse.textContent?.substring(0, 200) + (llmResponse.textContent && llmResponse.textContent.length > 200 ? "..." : ""),
289
+ toolCallsCount: llmResponse.toolCalls.length,
290
+ toolCalls: llmResponse.toolCalls.map((tc) => ({
291
+ name: tc.name,
292
+ inputKeys: Object.keys(tc.input),
293
+ input: tc.input,
294
+ })),
295
+ stopReason: llmResponse.stopReason,
296
+ usage: llmResponse.usage,
297
+ });
298
+
299
+ // Extract text response
300
+ if (llmResponse.textContent) {
301
+ finalMessage = llmResponse.textContent;
302
+ }
303
+
304
+ // Build assistant message
305
+ const assistantContent: LLMContentBlock[] = [];
306
+
307
+ if (llmResponse.textContent) {
308
+ assistantContent.push({
309
+ type: "text",
310
+ text: llmResponse.textContent,
311
+ });
312
+ }
313
+
314
+ for (const toolCall of llmResponse.toolCalls) {
315
+ assistantContent.push({
316
+ type: "tool_use",
317
+ id: toolCall.id,
318
+ name: toolCall.name,
319
+ input: toolCall.input,
320
+ });
321
+ }
322
+
323
+ messages.push({
324
+ role: "assistant",
325
+ content: assistantContent,
326
+ });
327
+
328
+ // Check for tool calls - if none, we're done after calling onStep
329
+ if (llmResponse.toolCalls.length === 0) {
330
+ // Call onStep hook before breaking
331
+ if (this.agentProps.onStep) {
332
+ const stepContext: AgentStepContext = {
333
+ ...execContext,
334
+ step,
335
+ maxSteps,
336
+ messages: [...messages],
337
+ };
338
+ await this.agentProps.onStep(stepContext);
339
+ }
340
+ // Dispatch observer onStep (no tool calls executed this step)
341
+ this.safeDispatch("onStep", () =>
342
+ this.observer?.onStep?.({
343
+ runId,
344
+ agentId,
345
+ step,
346
+ maxSteps,
347
+ messages: [...messages],
348
+ assistantText: llmResponse.textContent,
349
+ toolCalls: toolCalls.slice(toolCallsBeforeStep),
350
+ usage,
351
+ context: execContext,
352
+ })
353
+ );
354
+ break; // No more tool calls - done
355
+ }
356
+
357
+ // Execute all tool calls
358
+ const toolResults: LLMContentBlock[] = [];
359
+
360
+ for (const toolCall of llmResponse.toolCalls) {
361
+ const toolExecutor = this.tools.get(toolCall.name);
362
+ let toolOutput: any;
363
+ let toolError: string | undefined;
364
+
365
+ log.debug(`[Agent:${this.agentName}] Executing tool '${toolCall.name}':`, {
366
+ input: toolCall.input,
367
+ inputSize: JSON.stringify(toolCall.input).length,
368
+ });
369
+
370
+ try {
371
+ if (!toolExecutor) {
372
+ throw new Error(`Tool ${toolCall.name} not found`);
373
+ }
374
+
375
+ // Yield tool_call_start event
376
+ yield {
377
+ type: "tool_call_start",
378
+ toolCall: {
379
+ id: toolCall.id,
380
+ name: toolCall.name,
381
+ input: toolCall.input,
382
+ },
383
+ };
384
+
385
+ // Execute tool
386
+ const toolResult = await toolExecutor.execute(toolCall.input, {
387
+ user: input.user,
388
+ permissions: input.userPermissions,
389
+ conversationContext: input.conversationContext,
390
+ });
391
+
392
+ // Format result for AI using new ToolResult format
393
+ const formattedResult = toolExecutor.formatResultForAI(toolResult);
394
+
395
+ toolResults.push({
396
+ type: "tool_result",
397
+ tool_use_id: toolCall.id,
398
+ content: formattedResult,
399
+ is_error: !toolResult.success,
400
+ });
401
+
402
+ // Yield tool_call_result event
403
+ yield {
404
+ type: "tool_call_result",
405
+ toolCall: {
406
+ id: toolCall.id,
407
+ name: toolCall.name,
408
+ input: toolCall.input,
409
+ },
410
+ output: toolResult.success ? toolResult.data : null,
411
+ error: toolResult.success ? undefined : toolResult.error,
412
+ };
413
+
414
+ toolCalls.push({
415
+ name: toolCall.name,
416
+ input: toolCall.input,
417
+ output: toolResult.success ? toolResult.data : null,
418
+ error: toolResult.success ? undefined : toolResult.error,
419
+ });
420
+
421
+ log.debug(`[Agent:${this.agentName}] Tool '${toolCall.name}' ${toolResult.success ? "succeeded" : "failed"}:`, {
422
+ success: toolResult.success,
423
+ outputSize: toolResult.success ? JSON.stringify(toolResult.data).length : 0,
424
+ outputPreview: toolResult.success
425
+ ? JSON.stringify(toolResult.data).substring(0, 200) + (JSON.stringify(toolResult.data).length > 200 ? "..." : "")
426
+ : toolResult.error,
427
+ code: (toolResult as any).code,
428
+ });
429
+
430
+ if (!toolResult.success) {
431
+ log.warn(`Tool ${toolCall.name} returned error:`, toolResult.error);
432
+ }
433
+ } catch (err: any) {
434
+ // Unexpected errors (not from tool itself)
435
+ toolError = err.message;
436
+
437
+ // Yield tool_call_result with error
438
+ yield {
439
+ type: "tool_call_result",
440
+ toolCall: {
441
+ id: toolCall.id,
442
+ name: toolCall.name,
443
+ input: toolCall.input,
444
+ },
445
+ output: null,
446
+ error: toolError,
447
+ };
448
+
449
+ toolResults.push({
450
+ type: "tool_result",
451
+ tool_use_id: toolCall.id,
452
+ content: `Error: ${err.message}`,
453
+ is_error: true,
454
+ });
455
+
456
+ toolCalls.push({
457
+ name: toolCall.name,
458
+ input: toolCall.input,
459
+ output: null,
460
+ error: toolError,
461
+ });
462
+ log.error(`Tool ${toolCall.name} execution failed:`, err.message);
463
+ }
464
+ }
465
+
466
+ // Add tool results to conversation
467
+ messages.push({
468
+ role: "user",
469
+ content: toolResults,
470
+ });
471
+
472
+ // Call onStep hook after each step
473
+ if (this.agentProps.onStep) {
474
+ const stepContext: AgentStepContext = {
475
+ ...execContext,
476
+ step,
477
+ maxSteps,
478
+ messages: [...messages], // Clone to prevent mutation
479
+ };
480
+ await this.agentProps.onStep(stepContext);
481
+ }
482
+
483
+ // Dispatch observer onStep with per-step tool calls slice
484
+ this.safeDispatch("onStep", () =>
485
+ this.observer?.onStep?.({
486
+ runId,
487
+ agentId,
488
+ step,
489
+ maxSteps,
490
+ messages: [...messages],
491
+ assistantText: llmResponse.textContent,
492
+ toolCalls: toolCalls.slice(toolCallsBeforeStep),
493
+ usage,
494
+ context: execContext,
495
+ })
496
+ );
497
+ }
498
+
499
+ if (step >= maxSteps && toolCalls.length > 0) {
500
+ stoppedEarly = true;
501
+ log.warn(`Agent ${this.agentName || "unknown"} stopped early after ${maxSteps} steps`);
502
+ }
503
+
504
+ const result = buildResult();
505
+
506
+ // Call afterRun hook with full context
507
+ if (this.agentProps.afterRun) {
508
+ const finishContext: AgentFinishContext = {
509
+ ...execContext,
510
+ messages: [...messages],
511
+ result,
512
+ };
513
+ await this.agentProps.afterRun(result, finishContext);
514
+ }
515
+
516
+ // Dispatch observer onFinish (success path)
517
+ this.safeDispatch("onFinish", () =>
518
+ this.observer?.onFinish?.({
519
+ runId,
520
+ agentId,
521
+ result,
522
+ messages: [...messages],
523
+ durationMs: Date.now() - runStartedAt,
524
+ context: execContext,
525
+ })
526
+ );
527
+
528
+ // Phase 1: Yield only complete event
529
+ // Phase 2: Will yield text_delta and tool events during loop
530
+ yield { type: "complete", result };
531
+
532
+ return result;
533
+ } catch (err: any) {
534
+ // Dispatch observer onFinish with error before rethrowing
535
+ this.safeDispatch("onFinish", () =>
536
+ this.observer?.onFinish?.({
537
+ runId,
538
+ agentId,
539
+ result: buildResult(),
540
+ messages: [...messages],
541
+ durationMs: Date.now() - runStartedAt,
542
+ error: err?.message || String(err),
543
+ context: execContext,
544
+ })
545
+ );
546
+ throw err;
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Fire-and-forget observer dispatch: catches synchronous throws and
552
+ * rejected promises so observer failures never break agent execution.
553
+ */
554
+ private safeDispatch(eventName: string, invoke: () => void | Promise<void> | undefined): void {
555
+ try {
556
+ const maybePromise = invoke();
557
+ if (maybePromise && typeof (maybePromise as Promise<void>).then === "function") {
558
+ (maybePromise as Promise<void>).catch((err: any) => {
559
+ observerLog.warn(`AgentObserver.${eventName} threw (async):`, err?.message || err);
560
+ });
561
+ }
562
+ } catch (err: any) {
563
+ observerLog.warn(`AgentObserver.${eventName} threw (sync):`, err?.message || err);
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Convert Message[] to LLM message format
569
+ * Supports multi-turn conversations with history
570
+ */
571
+ private convertMessages(messages: Message[]): LLMMessage[] {
572
+ return messages.map((m) => {
573
+ if (m.role === "user") {
574
+ return { role: "user" as const, content: m.content };
575
+ } else if (m.role === "assistant") {
576
+ // If assistant message has tool calls, convert to content blocks
577
+ if (m.toolCalls && m.toolCalls.length > 0) {
578
+ const contentBlocks: LLMContentBlock[] = [];
579
+
580
+ // Add text content if present
581
+ if (m.content) {
582
+ contentBlocks.push({ type: "text", text: m.content });
583
+ }
584
+
585
+ // Add tool use blocks
586
+ for (const toolCall of m.toolCalls) {
587
+ contentBlocks.push({
588
+ type: "tool_use",
589
+ id: toolCall.id,
590
+ name: toolCall.name,
591
+ input: toolCall.input,
592
+ });
593
+ }
594
+
595
+ return { role: "assistant" as const, content: contentBlocks };
596
+ }
597
+
598
+ // Text-only assistant message
599
+ return { role: "assistant" as const, content: m.content };
600
+ } else {
601
+ // For tool messages, convert to user message with tool_result content block
602
+ return {
603
+ role: "user" as const,
604
+ content: [
605
+ {
606
+ type: "tool_result",
607
+ tool_use_id: m.toolCallId,
608
+ content: m.result,
609
+ },
610
+ ] as LLMContentBlock[],
611
+ };
612
+ }
613
+ });
614
+ }
615
+
616
+ private getToolSchemas(): FlinkToolSchema[] {
617
+ return Array.from(this.tools.values()).map((t) => t.getToolSchema());
618
+ }
619
+
620
+ /**
621
+ * Filter tools based on user permissions
622
+ * Only returns schemas for tools the user has permission to use
623
+ *
624
+ * @param user - User object
625
+ * @param userPermissions - Optional resolved permissions from auth plugin (preferred)
626
+ * @param conversationContext - Optional conversation context
627
+ */
628
+ private async filterToolsByPermissions(user?: any, userPermissions?: string[], conversationContext?: any): Promise<FlinkToolSchema[]> {
629
+ const allowedTools: FlinkToolSchema[] = [];
630
+ const toolExecutors = Array.from(this.tools.values());
631
+
632
+ for (const tool of toolExecutors) {
633
+ const hasPermission = await tool.checkPermissions(user, undefined, userPermissions, conversationContext);
634
+ if (hasPermission) {
635
+ allowedTools.push(tool.getToolSchema());
636
+ }
637
+ }
638
+
639
+ return allowedTools;
640
+ }
641
+
642
+ /**
643
+ * Build delegation chain from metadata for error messages
644
+ * Extracts parent agent IDs to show the full call stack
645
+ */
646
+ }