@bubblelab/bubble-core 0.1.0

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 (275) hide show
  1. package/LICENSE.txt +202 -0
  2. package/dist/bubble-bundle.d.ts +2021 -0
  3. package/dist/bubble-factory.d.ts +161 -0
  4. package/dist/bubble-factory.d.ts.map +1 -0
  5. package/dist/bubble-factory.js +426 -0
  6. package/dist/bubble-factory.js.map +1 -0
  7. package/dist/bubble-flow/bubble-flow-class.d.ts +19 -0
  8. package/dist/bubble-flow/bubble-flow-class.d.ts.map +1 -0
  9. package/dist/bubble-flow/bubble-flow-class.js +23 -0
  10. package/dist/bubble-flow/bubble-flow-class.js.map +1 -0
  11. package/dist/bubble-flow/sample/data-analyst-flow.d.ts +15 -0
  12. package/dist/bubble-flow/sample/data-analyst-flow.d.ts.map +1 -0
  13. package/dist/bubble-flow/sample/data-analyst-flow.js +63 -0
  14. package/dist/bubble-flow/sample/data-analyst-flow.js.map +1 -0
  15. package/dist/bubble-flow/sample/error-ts.d.ts +23 -0
  16. package/dist/bubble-flow/sample/error-ts.d.ts.map +1 -0
  17. package/dist/bubble-flow/sample/error-ts.js +31 -0
  18. package/dist/bubble-flow/sample/error-ts.js.map +1 -0
  19. package/dist/bubble-flow/sample/sanitytest.d.ts +10 -0
  20. package/dist/bubble-flow/sample/sanitytest.d.ts.map +1 -0
  21. package/dist/bubble-flow/sample/sanitytest.js +13 -0
  22. package/dist/bubble-flow/sample/sanitytest.js.map +1 -0
  23. package/dist/bubble-flow/sample/simple-webhook-2.d.ts +19 -0
  24. package/dist/bubble-flow/sample/simple-webhook-2.d.ts.map +1 -0
  25. package/dist/bubble-flow/sample/simple-webhook-2.js +23 -0
  26. package/dist/bubble-flow/sample/simple-webhook-2.js.map +1 -0
  27. package/dist/bubble-flow/sample/simple-webhook.d.ts +10 -0
  28. package/dist/bubble-flow/sample/simple-webhook.d.ts.map +1 -0
  29. package/dist/bubble-flow/sample/simple-webhook.js +18 -0
  30. package/dist/bubble-flow/sample/simple-webhook.js.map +1 -0
  31. package/dist/bubble-flow/sample/simplified-data-analysis.flow.d.ts +29 -0
  32. package/dist/bubble-flow/sample/simplified-data-analysis.flow.d.ts.map +1 -0
  33. package/dist/bubble-flow/sample/simplified-data-analysis.flow.js +150 -0
  34. package/dist/bubble-flow/sample/simplified-data-analysis.flow.js.map +1 -0
  35. package/dist/bubble-flow/sample/slack-v0.1.d.ts +10 -0
  36. package/dist/bubble-flow/sample/slack-v0.1.d.ts.map +1 -0
  37. package/dist/bubble-flow/sample/slack-v0.1.js +59 -0
  38. package/dist/bubble-flow/sample/slack-v0.1.js.map +1 -0
  39. package/dist/bubble-flow/sample/slackagenttest.d.ts +10 -0
  40. package/dist/bubble-flow/sample/slackagenttest.d.ts.map +1 -0
  41. package/dist/bubble-flow/sample/slackagenttest.js +59 -0
  42. package/dist/bubble-flow/sample/slackagenttest.js.map +1 -0
  43. package/dist/bubble-trigger/index.d.ts +2 -0
  44. package/dist/bubble-trigger/index.d.ts.map +1 -0
  45. package/dist/bubble-trigger/index.js +2 -0
  46. package/dist/bubble-trigger/index.js.map +1 -0
  47. package/dist/bubble-trigger/types.d.ts +87 -0
  48. package/dist/bubble-trigger/types.d.ts.map +1 -0
  49. package/dist/bubble-trigger/types.js +14 -0
  50. package/dist/bubble-trigger/types.js.map +1 -0
  51. package/dist/bubbles/service-bubble/ai-agent.d.ts +428 -0
  52. package/dist/bubbles/service-bubble/ai-agent.d.ts.map +1 -0
  53. package/dist/bubbles/service-bubble/ai-agent.js +881 -0
  54. package/dist/bubbles/service-bubble/ai-agent.js.map +1 -0
  55. package/dist/bubbles/service-bubble/gmail.d.ts +3073 -0
  56. package/dist/bubbles/service-bubble/gmail.d.ts.map +1 -0
  57. package/dist/bubbles/service-bubble/gmail.js +908 -0
  58. package/dist/bubbles/service-bubble/gmail.js.map +1 -0
  59. package/dist/bubbles/service-bubble/google-calendar.d.ts +3377 -0
  60. package/dist/bubbles/service-bubble/google-calendar.d.ts.map +1 -0
  61. package/dist/bubbles/service-bubble/google-calendar.js +527 -0
  62. package/dist/bubbles/service-bubble/google-calendar.js.map +1 -0
  63. package/dist/bubbles/service-bubble/google-drive.d.ts +1152 -0
  64. package/dist/bubbles/service-bubble/google-drive.d.ts.map +1 -0
  65. package/dist/bubbles/service-bubble/google-drive.js +943 -0
  66. package/dist/bubbles/service-bubble/google-drive.js.map +1 -0
  67. package/dist/bubbles/service-bubble/google-sheets.d.ts +1811 -0
  68. package/dist/bubbles/service-bubble/google-sheets.d.ts.map +1 -0
  69. package/dist/bubbles/service-bubble/google-sheets.js +904 -0
  70. package/dist/bubbles/service-bubble/google-sheets.js.map +1 -0
  71. package/dist/bubbles/service-bubble/hello-world.d.ts +74 -0
  72. package/dist/bubbles/service-bubble/hello-world.d.ts.map +1 -0
  73. package/dist/bubbles/service-bubble/hello-world.js +67 -0
  74. package/dist/bubbles/service-bubble/hello-world.js.map +1 -0
  75. package/dist/bubbles/service-bubble/http.d.ts +134 -0
  76. package/dist/bubbles/service-bubble/http.d.ts.map +1 -0
  77. package/dist/bubbles/service-bubble/http.js +184 -0
  78. package/dist/bubbles/service-bubble/http.js.map +1 -0
  79. package/dist/bubbles/service-bubble/postgresql.d.ts +180 -0
  80. package/dist/bubbles/service-bubble/postgresql.d.ts.map +1 -0
  81. package/dist/bubbles/service-bubble/postgresql.js +448 -0
  82. package/dist/bubbles/service-bubble/postgresql.js.map +1 -0
  83. package/dist/bubbles/service-bubble/resend.d.ts +301 -0
  84. package/dist/bubbles/service-bubble/resend.d.ts.map +1 -0
  85. package/dist/bubbles/service-bubble/resend.js +253 -0
  86. package/dist/bubbles/service-bubble/resend.js.map +1 -0
  87. package/dist/bubbles/service-bubble/slack.d.ts +5869 -0
  88. package/dist/bubbles/service-bubble/slack.d.ts.map +1 -0
  89. package/dist/bubbles/service-bubble/slack.js +1536 -0
  90. package/dist/bubbles/service-bubble/slack.js.map +1 -0
  91. package/dist/bubbles/service-bubble/storage.d.ts +571 -0
  92. package/dist/bubbles/service-bubble/storage.d.ts.map +1 -0
  93. package/dist/bubbles/service-bubble/storage.js +504 -0
  94. package/dist/bubbles/service-bubble/storage.js.map +1 -0
  95. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts +308 -0
  96. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts.map +1 -0
  97. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.js +285 -0
  98. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.js.map +1 -0
  99. package/dist/bubbles/tool-bubble/chart-js-tool.d.ts +416 -0
  100. package/dist/bubbles/tool-bubble/chart-js-tool.d.ts.map +1 -0
  101. package/dist/bubbles/tool-bubble/chart-js-tool.js +570 -0
  102. package/dist/bubbles/tool-bubble/chart-js-tool.js.map +1 -0
  103. package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts +99 -0
  104. package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts.map +1 -0
  105. package/dist/bubbles/tool-bubble/get-bubble-details-tool.js +645 -0
  106. package/dist/bubbles/tool-bubble/get-bubble-details-tool.js.map +1 -0
  107. package/dist/bubbles/tool-bubble/list-bubbles-tool.d.ts +112 -0
  108. package/dist/bubbles/tool-bubble/list-bubbles-tool.d.ts.map +1 -0
  109. package/dist/bubbles/tool-bubble/list-bubbles-tool.js +82 -0
  110. package/dist/bubbles/tool-bubble/list-bubbles-tool.js.map +1 -0
  111. package/dist/bubbles/tool-bubble/reddit-scrape-tool.d.ts +413 -0
  112. package/dist/bubbles/tool-bubble/reddit-scrape-tool.d.ts.map +1 -0
  113. package/dist/bubbles/tool-bubble/reddit-scrape-tool.js +327 -0
  114. package/dist/bubbles/tool-bubble/reddit-scrape-tool.js.map +1 -0
  115. package/dist/bubbles/tool-bubble/research-agent-tool.d.ts +122 -0
  116. package/dist/bubbles/tool-bubble/research-agent-tool.d.ts.map +1 -0
  117. package/dist/bubbles/tool-bubble/research-agent-tool.js +343 -0
  118. package/dist/bubbles/tool-bubble/research-agent-tool.js.map +1 -0
  119. package/dist/bubbles/tool-bubble/sql-query-tool.d.ts +131 -0
  120. package/dist/bubbles/tool-bubble/sql-query-tool.d.ts.map +1 -0
  121. package/dist/bubbles/tool-bubble/sql-query-tool.js +147 -0
  122. package/dist/bubbles/tool-bubble/sql-query-tool.js.map +1 -0
  123. package/dist/bubbles/tool-bubble/tool-template.d.ts +257 -0
  124. package/dist/bubbles/tool-bubble/tool-template.d.ts.map +1 -0
  125. package/dist/bubbles/tool-bubble/tool-template.js +238 -0
  126. package/dist/bubbles/tool-bubble/tool-template.js.map +1 -0
  127. package/dist/bubbles/tool-bubble/virtual-file-editor-example.d.ts +8 -0
  128. package/dist/bubbles/tool-bubble/virtual-file-editor-example.d.ts.map +1 -0
  129. package/dist/bubbles/tool-bubble/virtual-file-editor-example.js +65 -0
  130. package/dist/bubbles/tool-bubble/virtual-file-editor-example.js.map +1 -0
  131. package/dist/bubbles/tool-bubble/virtual-file-editor.tool.d.ts +125 -0
  132. package/dist/bubbles/tool-bubble/virtual-file-editor.tool.d.ts.map +1 -0
  133. package/dist/bubbles/tool-bubble/virtual-file-editor.tool.js +169 -0
  134. package/dist/bubbles/tool-bubble/virtual-file-editor.tool.js.map +1 -0
  135. package/dist/bubbles/tool-bubble/web-crawl-tool.d.ts +218 -0
  136. package/dist/bubbles/tool-bubble/web-crawl-tool.d.ts.map +1 -0
  137. package/dist/bubbles/tool-bubble/web-crawl-tool.js +255 -0
  138. package/dist/bubbles/tool-bubble/web-crawl-tool.js.map +1 -0
  139. package/dist/bubbles/tool-bubble/web-extract-tool.d.ts +134 -0
  140. package/dist/bubbles/tool-bubble/web-extract-tool.d.ts.map +1 -0
  141. package/dist/bubbles/tool-bubble/web-extract-tool.js +175 -0
  142. package/dist/bubbles/tool-bubble/web-extract-tool.js.map +1 -0
  143. package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts +228 -0
  144. package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts.map +1 -0
  145. package/dist/bubbles/tool-bubble/web-scrape-tool.js +214 -0
  146. package/dist/bubbles/tool-bubble/web-scrape-tool.js.map +1 -0
  147. package/dist/bubbles/tool-bubble/web-search-tool.d.ts +134 -0
  148. package/dist/bubbles/tool-bubble/web-search-tool.d.ts.map +1 -0
  149. package/dist/bubbles/tool-bubble/web-search-tool.js +155 -0
  150. package/dist/bubbles/tool-bubble/web-search-tool.js.map +1 -0
  151. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts +114 -0
  152. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts.map +1 -0
  153. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js +777 -0
  154. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js.map +1 -0
  155. package/dist/bubbles/workflow-bubble/bubblscript-generateor.workflow.d.ts +97 -0
  156. package/dist/bubbles/workflow-bubble/bubblscript-generateor.workflow.d.ts.map +1 -0
  157. package/dist/bubbles/workflow-bubble/bubblscript-generateor.workflow.js +327 -0
  158. package/dist/bubbles/workflow-bubble/bubblscript-generateor.workflow.js.map +1 -0
  159. package/dist/bubbles/workflow-bubble/database-analyzer.workflow.d.ts +303 -0
  160. package/dist/bubbles/workflow-bubble/database-analyzer.workflow.d.ts.map +1 -0
  161. package/dist/bubbles/workflow-bubble/database-analyzer.workflow.js +297 -0
  162. package/dist/bubbles/workflow-bubble/database-analyzer.workflow.js.map +1 -0
  163. package/dist/bubbles/workflow-bubble/file-editor-agent.workflow.d.ts +157 -0
  164. package/dist/bubbles/workflow-bubble/file-editor-agent.workflow.d.ts.map +1 -0
  165. package/dist/bubbles/workflow-bubble/file-editor-agent.workflow.js +310 -0
  166. package/dist/bubbles/workflow-bubble/file-editor-agent.workflow.js.map +1 -0
  167. package/dist/bubbles/workflow-bubble/generate-document.workflow.d.ts +543 -0
  168. package/dist/bubbles/workflow-bubble/generate-document.workflow.d.ts.map +1 -0
  169. package/dist/bubbles/workflow-bubble/generate-document.workflow.js +628 -0
  170. package/dist/bubbles/workflow-bubble/generate-document.workflow.js.map +1 -0
  171. package/dist/bubbles/workflow-bubble/parse-document.workflow.d.ts +679 -0
  172. package/dist/bubbles/workflow-bubble/parse-document.workflow.d.ts.map +1 -0
  173. package/dist/bubbles/workflow-bubble/parse-document.workflow.js +604 -0
  174. package/dist/bubbles/workflow-bubble/parse-document.workflow.js.map +1 -0
  175. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts +1011 -0
  176. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts.map +1 -0
  177. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.js +841 -0
  178. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.js.map +1 -0
  179. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.d.ts +883 -0
  180. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.d.ts.map +1 -0
  181. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.js +781 -0
  182. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.js.map +1 -0
  183. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.d.ts +300 -0
  184. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.d.ts.map +1 -0
  185. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.js +508 -0
  186. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.js.map +1 -0
  187. package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts +731 -0
  188. package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts.map +1 -0
  189. package/dist/bubbles/workflow-bubble/slack-formatter-agent.js +690 -0
  190. package/dist/bubbles/workflow-bubble/slack-formatter-agent.js.map +1 -0
  191. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts +401 -0
  192. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts.map +1 -0
  193. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.js +382 -0
  194. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.js.map +1 -0
  195. package/dist/bubbles/workflow-bubble/workflow-template.d.ts +144 -0
  196. package/dist/bubbles/workflow-bubble/workflow-template.d.ts.map +1 -0
  197. package/dist/bubbles/workflow-bubble/workflow-template.js +124 -0
  198. package/dist/bubbles/workflow-bubble/workflow-template.js.map +1 -0
  199. package/dist/index.d.ts +46 -0
  200. package/dist/index.d.ts.map +1 -0
  201. package/dist/index.js +53 -0
  202. package/dist/index.js.map +1 -0
  203. package/dist/logging/BubbleLogger.d.ts +146 -0
  204. package/dist/logging/BubbleLogger.d.ts.map +1 -0
  205. package/dist/logging/BubbleLogger.js +472 -0
  206. package/dist/logging/BubbleLogger.js.map +1 -0
  207. package/dist/logging/StreamingBubbleLogger.d.ts +85 -0
  208. package/dist/logging/StreamingBubbleLogger.d.ts.map +1 -0
  209. package/dist/logging/StreamingBubbleLogger.js +340 -0
  210. package/dist/logging/StreamingBubbleLogger.js.map +1 -0
  211. package/dist/types/ai-models.d.ts +4 -0
  212. package/dist/types/ai-models.d.ts.map +1 -0
  213. package/dist/types/ai-models.js +14 -0
  214. package/dist/types/ai-models.js.map +1 -0
  215. package/dist/types/available-tools.d.ts +4 -0
  216. package/dist/types/available-tools.d.ts.map +1 -0
  217. package/dist/types/available-tools.js +19 -0
  218. package/dist/types/available-tools.js.map +1 -0
  219. package/dist/types/base-bubble-class.d.ts +47 -0
  220. package/dist/types/base-bubble-class.d.ts.map +1 -0
  221. package/dist/types/base-bubble-class.js +212 -0
  222. package/dist/types/base-bubble-class.js.map +1 -0
  223. package/dist/types/bubble-errors.d.ts +44 -0
  224. package/dist/types/bubble-errors.d.ts.map +1 -0
  225. package/dist/types/bubble-errors.js +51 -0
  226. package/dist/types/bubble-errors.js.map +1 -0
  227. package/dist/types/bubble.d.ts +73 -0
  228. package/dist/types/bubble.d.ts.map +1 -0
  229. package/dist/types/bubble.js +2 -0
  230. package/dist/types/bubble.js.map +1 -0
  231. package/dist/types/credentials.d.ts +6 -0
  232. package/dist/types/credentials.d.ts.map +1 -0
  233. package/dist/types/credentials.js +6 -0
  234. package/dist/types/credentials.js.map +1 -0
  235. package/dist/types/service-bubble-class.d.ts +31 -0
  236. package/dist/types/service-bubble-class.d.ts.map +1 -0
  237. package/dist/types/service-bubble-class.js +36 -0
  238. package/dist/types/service-bubble-class.js.map +1 -0
  239. package/dist/types/streaming-events.d.ts +18 -0
  240. package/dist/types/streaming-events.d.ts.map +1 -0
  241. package/dist/types/streaming-events.js +5 -0
  242. package/dist/types/streaming-events.js.map +1 -0
  243. package/dist/types/tool-bubble-class.d.ts +19 -0
  244. package/dist/types/tool-bubble-class.d.ts.map +1 -0
  245. package/dist/types/tool-bubble-class.js +48 -0
  246. package/dist/types/tool-bubble-class.js.map +1 -0
  247. package/dist/types/workflow-bubble-class.d.ts +25 -0
  248. package/dist/types/workflow-bubble-class.d.ts.map +1 -0
  249. package/dist/types/workflow-bubble-class.js +30 -0
  250. package/dist/types/workflow-bubble-class.js.map +1 -0
  251. package/dist/utils/bubbleflow-parser.d.ts +32 -0
  252. package/dist/utils/bubbleflow-parser.d.ts.map +1 -0
  253. package/dist/utils/bubbleflow-parser.js +332 -0
  254. package/dist/utils/bubbleflow-parser.js.map +1 -0
  255. package/dist/utils/bubbleflow-validation.d.ts +9 -0
  256. package/dist/utils/bubbleflow-validation.d.ts.map +1 -0
  257. package/dist/utils/bubbleflow-validation.js +116 -0
  258. package/dist/utils/bubbleflow-validation.js.map +1 -0
  259. package/dist/utils/json-parsing.d.ts +20 -0
  260. package/dist/utils/json-parsing.d.ts.map +1 -0
  261. package/dist/utils/json-parsing.js +394 -0
  262. package/dist/utils/json-parsing.js.map +1 -0
  263. package/dist/utils/mock-data-generator.d.ts +43 -0
  264. package/dist/utils/mock-data-generator.d.ts.map +1 -0
  265. package/dist/utils/mock-data-generator.js +312 -0
  266. package/dist/utils/mock-data-generator.js.map +1 -0
  267. package/dist/utils/param-helper.d.ts +2 -0
  268. package/dist/utils/param-helper.d.ts.map +1 -0
  269. package/dist/utils/param-helper.js +5 -0
  270. package/dist/utils/param-helper.js.map +1 -0
  271. package/dist/utils/source-bubble-parser.d.ts +31 -0
  272. package/dist/utils/source-bubble-parser.d.ts.map +1 -0
  273. package/dist/utils/source-bubble-parser.js +231 -0
  274. package/dist/utils/source-bubble-parser.js.map +1 -0
  275. package/package.json +63 -0
@@ -0,0 +1,881 @@
1
+ import { z } from 'zod';
2
+ import { ServiceBubble } from '../../types/service-bubble-class.js';
3
+ import { CredentialType, BUBBLE_CREDENTIAL_OPTIONS, } from '@bubblelab/shared-schemas';
4
+ import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';
5
+ import { ToolNode } from '@langchain/langgraph/prebuilt';
6
+ import { ChatOpenAI } from '@langchain/openai';
7
+ import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
8
+ import { HumanMessage, AIMessage, ToolMessage } from '@langchain/core/messages';
9
+ import { DynamicStructuredTool } from '@langchain/core/tools';
10
+ import { AvailableModels } from '../../types/ai-models.js';
11
+ import { AvailableTools } from '../../types/available-tools.js';
12
+ import { BubbleFactory } from '../../bubble-factory.js';
13
+ import { parseJsonWithFallbacks } from '../../utils/json-parsing.js';
14
+ // Define model configuration
15
+ const ModelConfigSchema = z.object({
16
+ model: AvailableModels.default('google/gemini-2.5-flash').describe('AI model to use (format: provider/model-name).'),
17
+ temperature: z
18
+ .number()
19
+ .min(0)
20
+ .max(2)
21
+ .default(0.7)
22
+ .describe('Temperature for response randomness (0 = deterministic, 2 = very random)'),
23
+ maxTokens: z
24
+ .number()
25
+ .positive()
26
+ .optional()
27
+ .default(40000)
28
+ .describe('Maximum number of tokens to generate in response'),
29
+ jsonMode: z
30
+ .boolean()
31
+ .default(false)
32
+ .describe('When true, strips markdown formatting and returns clean JSON response'),
33
+ });
34
+ // Define tool configuration
35
+ const ToolConfigSchema = z.object({
36
+ name: AvailableTools.describe('Name of the tool type or tool bubble to enable for the AI agent'),
37
+ credentials: z
38
+ .record(z.nativeEnum(CredentialType), z.string())
39
+ .default({})
40
+ .optional()
41
+ .describe('Credential types to use for the tool bubble (injected at runtime)'),
42
+ config: z
43
+ .record(z.string(), z.unknown())
44
+ .optional()
45
+ .describe('Configuration for the tool or tool bubble'),
46
+ });
47
+ // Define image input schemas - supports both base64 data and URLs
48
+ const Base64ImageSchema = z.object({
49
+ type: z.literal('base64').default('base64'),
50
+ data: z
51
+ .string()
52
+ .describe('Base64 encoded image data (without data:image/... prefix)'),
53
+ mimeType: z
54
+ .string()
55
+ .default('image/png')
56
+ .describe('MIME type of the image (e.g., image/png, image/jpeg)'),
57
+ description: z
58
+ .string()
59
+ .optional()
60
+ .describe('Optional description or context for the image'),
61
+ });
62
+ const UrlImageSchema = z.object({
63
+ type: z.literal('url'),
64
+ url: z.string().url().describe('URL to the image (http/https)'),
65
+ description: z
66
+ .string()
67
+ .optional()
68
+ .describe('Optional description or context for the image'),
69
+ });
70
+ const ImageInputSchema = z.discriminatedUnion('type', [
71
+ Base64ImageSchema,
72
+ UrlImageSchema,
73
+ ]);
74
+ // Define the parameters schema for the AI Agent bubble
75
+ const AIAgentParamsSchema = z.object({
76
+ message: z
77
+ .string()
78
+ .min(1, 'Message is required')
79
+ .describe('The message or question to send to the AI agent'),
80
+ images: z
81
+ .array(ImageInputSchema)
82
+ .default([])
83
+ .describe('Array of base64 encoded images to include with the message (for multimodal AI models). Example: [{type: "base64", data: "base64...", mimeType: "image/png", description: "A beautiful image of a cat"}] or [{type: "url", url: "https://example.com/image.png", description: "A beautiful image of a cat"}]'),
84
+ systemPrompt: z
85
+ .string()
86
+ .default('You are a helpful AI assistant')
87
+ .describe('System prompt that defines the AI agents behavior and personality'),
88
+ name: z
89
+ .string()
90
+ .default('AI Agent')
91
+ .optional()
92
+ .describe('A friendly name for the AI agent'),
93
+ model: ModelConfigSchema.default({
94
+ model: 'google/gemini-2.5-flash',
95
+ temperature: 0.7,
96
+ maxTokens: 50000,
97
+ jsonMode: false,
98
+ }).describe('AI model configuration including provider, temperature, and tokens. For model unless otherwise specified, use google/gemini-2.5-flash as default. Use google/gemini-2.5-flash-image-preview to edit and generate images.'),
99
+ tools: z
100
+ .array(ToolConfigSchema)
101
+ .default([
102
+ {
103
+ name: 'web-search-tool',
104
+ config: {
105
+ maxResults: 5,
106
+ },
107
+ },
108
+ ])
109
+ .describe('Array of tools the AI agent can use. Can be tool types (web-search-tool, web-scrape-tool, web-crawl-tool, web-extract-tool). If using image models, set the tools to []'),
110
+ maxIterations: z
111
+ .number()
112
+ .positive()
113
+ .default(10)
114
+ .describe('Maximum number of iterations for the agent workflow'),
115
+ credentials: z
116
+ .record(z.nativeEnum(CredentialType), z.string())
117
+ .optional()
118
+ .describe('Object mapping credential types to values (injected at runtime)'),
119
+ streaming: z
120
+ .boolean()
121
+ .default(false)
122
+ .describe('Enable real-time streaming of tokens, tool calls, and iteration progress'),
123
+ });
124
+ const AIAgentResultSchema = z.object({
125
+ response: z
126
+ .string()
127
+ .describe('The AI agents final response to the user message. For text responses, returns plain text or JSON string. For image generation models (like gemini-2.5-flash-image-preview), returns base64-encoded image data with data URI format (data:image/png;base64,...)'),
128
+ toolCalls: z
129
+ .array(z.object({
130
+ tool: z.string().describe('Name of the tool that was called'),
131
+ input: z.unknown().describe('Input parameters passed to the tool'),
132
+ output: z.unknown().describe('Output returned by the tool'),
133
+ }))
134
+ .describe('Array of tool calls made during the conversation'),
135
+ iterations: z
136
+ .number()
137
+ .describe('Number of back-and-forth iterations in the agent workflow'),
138
+ error: z
139
+ .string()
140
+ .describe('Error message of the run, undefined if successful'),
141
+ success: z
142
+ .boolean()
143
+ .describe('Whether the agent execution completed successfully'),
144
+ });
145
+ export class AIAgentBubble extends ServiceBubble {
146
+ static type = 'service';
147
+ static service = 'ai-agent';
148
+ static authType = 'apikey';
149
+ static bubbleName = 'ai-agent';
150
+ static schema = AIAgentParamsSchema;
151
+ static resultSchema = AIAgentResultSchema;
152
+ static shortDescription = 'AI agent with LangGraph for tool-enabled conversations, multimodal support, and JSON mode';
153
+ static longDescription = `
154
+ An AI agent powered by LangGraph that can use any tool bubble to answer questions.
155
+ Use cases:
156
+ - Add tools to enhance the AI agent's capabilities (web-search-tool, web-scrape-tool)
157
+ - Multi-step reasoning with tool assistance
158
+ - Tool-augmented conversations with any registered tool
159
+ - JSON mode for structured output (strips markdown formatting)
160
+ `;
161
+ static alias = 'agent';
162
+ factory;
163
+ constructor(params = {
164
+ message: 'Hello, how are you?',
165
+ systemPrompt: 'You are a helpful AI assistant',
166
+ }, context) {
167
+ super(params, context);
168
+ this.factory = new BubbleFactory();
169
+ }
170
+ async testCredential() {
171
+ // Make a test API call to the model provider
172
+ const llm = this.initializeModel(this.params.model);
173
+ const response = await llm.invoke(['Hello, how are you?']);
174
+ if (response.content) {
175
+ return true;
176
+ }
177
+ return false;
178
+ }
179
+ async performAction(context) {
180
+ // Context is available but not currently used in this implementation
181
+ void context;
182
+ const { message, images, systemPrompt, model, tools, maxIterations } = this.params;
183
+ try {
184
+ // Initialize the language model
185
+ const llm = this.initializeModel(model);
186
+ // Initialize tools
187
+ const agentTools = await this.initializeTools(tools);
188
+ // Create the agent graph
189
+ const graph = await this.createAgentGraph(llm, agentTools, systemPrompt);
190
+ // Execute the agent
191
+ const result = await this.executeAgent(graph, message, images, maxIterations, model.jsonMode);
192
+ return result;
193
+ }
194
+ catch (error) {
195
+ // Return error information but mark as recoverable
196
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
197
+ console.warn('[AIAgent] Execution error (continuing):', errorMessage);
198
+ return {
199
+ response: `Error: ${errorMessage}`,
200
+ success: false, // Still false but execution can continue
201
+ toolCalls: [],
202
+ error: errorMessage,
203
+ iterations: 0,
204
+ };
205
+ }
206
+ }
207
+ /**
208
+ * Execute the AI agent with streaming support for real-time feedback
209
+ */
210
+ async actionWithStreaming(streamingCallback, context) {
211
+ // Context is available but not currently used in this implementation
212
+ void context;
213
+ const { message, images, systemPrompt, model, tools, maxIterations } = this.params;
214
+ const startTime = Date.now();
215
+ // Send start event
216
+ await streamingCallback({
217
+ type: 'start',
218
+ data: {
219
+ message: `Analyzing with ${this.params.name || 'AI Agent'}`,
220
+ maxIterations,
221
+ timestamp: new Date().toISOString(),
222
+ },
223
+ });
224
+ try {
225
+ // Send LLM start event
226
+ await streamingCallback({
227
+ type: 'llm_start',
228
+ data: {
229
+ model: model.model,
230
+ temperature: model.temperature,
231
+ },
232
+ });
233
+ // Initialize the language model
234
+ const llm = this.initializeModel(model);
235
+ // Initialize tools
236
+ const agentTools = await this.initializeTools(tools);
237
+ // Create the agent graph
238
+ const graph = await this.createAgentGraph(llm, agentTools, systemPrompt);
239
+ // Execute the agent with streaming
240
+ const result = await this.executeAgentWithStreaming(graph, message, images, maxIterations, model.jsonMode, streamingCallback);
241
+ const totalDuration = Date.now() - startTime;
242
+ // Send completion event
243
+ await streamingCallback({
244
+ type: 'complete',
245
+ data: {
246
+ result,
247
+ totalDuration,
248
+ },
249
+ });
250
+ return result;
251
+ }
252
+ catch (error) {
253
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
254
+ // Send error event as recoverable
255
+ await streamingCallback({
256
+ type: 'error',
257
+ data: {
258
+ error: errorMessage,
259
+ recoverable: true, // Mark as recoverable to continue execution
260
+ },
261
+ });
262
+ console.warn('[AIAgent] Streaming execution error (continuing):', errorMessage);
263
+ return {
264
+ response: `Error: ${errorMessage}`,
265
+ success: false, // Still false but execution can continue
266
+ toolCalls: [],
267
+ error: errorMessage,
268
+ iterations: 0,
269
+ };
270
+ }
271
+ }
272
+ chooseCredential() {
273
+ const { model } = this.params;
274
+ const credentials = this.params.credentials;
275
+ const [provider] = model.model.split('/');
276
+ // If no credentials were injected, throw error immediately (like PostgreSQL)
277
+ if (!credentials || typeof credentials !== 'object') {
278
+ throw new Error(`No ${provider.toUpperCase()} credentials provided`);
279
+ }
280
+ // Choose credential based on the model provider
281
+ switch (provider) {
282
+ case 'openai':
283
+ return credentials[CredentialType.OPENAI_CRED];
284
+ case 'google':
285
+ return credentials[CredentialType.GOOGLE_GEMINI_CRED];
286
+ case 'openrouter':
287
+ return credentials[CredentialType.OPENROUTER_CRED];
288
+ default:
289
+ throw new Error(`Unsupported model provider: ${provider}`);
290
+ }
291
+ }
292
+ /**
293
+ * Format final response with special handling for Gemini image models and JSON mode
294
+ */
295
+ async formatFinalResponse(response, modelConfig, jsonMode) {
296
+ let finalResponse = typeof response === 'string' ? response : JSON.stringify(response);
297
+ // Special handling for Gemini image models that return images in inlineData format
298
+ if (modelConfig.model.includes('gemini') &&
299
+ modelConfig.model.includes('image')) {
300
+ finalResponse = this.formatGeminiImageResponse(finalResponse);
301
+ }
302
+ else if (jsonMode && typeof finalResponse === 'string') {
303
+ // Handle JSON mode: use the improved utility function
304
+ const result = parseJsonWithFallbacks(finalResponse);
305
+ if (!result.success) {
306
+ return {
307
+ response: result.response,
308
+ error: `${this.params.name || 'AI Agent'} failed to generate valid JSON. Post-processing attempted but JSON is still malformed. Original response: ${finalResponse}`,
309
+ };
310
+ }
311
+ return { response: result.response };
312
+ }
313
+ return { response: finalResponse };
314
+ }
315
+ /**
316
+ * Convert Gemini's inlineData format to LangChain-compatible data URI format
317
+ */
318
+ formatGeminiImageResponse(response) {
319
+ if (typeof response !== 'string') {
320
+ return String(response);
321
+ }
322
+ try {
323
+ console.log('[AIAgent] Formatting Gemini image response...');
324
+ // Look for Gemini's inlineData format in the response
325
+ const inlineDataRegex = /\{\s*"inlineData"\s*:\s*\{\s*"mimeType"\s*:\s*"([^"]+)"\s*,\s*"data"\s*:\s*"([^"]+)"\s*\}\s*\}/;
326
+ const match = response.match(inlineDataRegex);
327
+ if (match) {
328
+ const [, mimeType, data] = match;
329
+ const dataUri = `data:${mimeType};base64,${data}`;
330
+ console.log(`[AIAgent] Extracted first data URI from Gemini inlineData: ${mimeType}`);
331
+ return dataUri;
332
+ }
333
+ // Also check for the more complex format with text
334
+ const complexInlineDataRegex = /\{\s*"inlineData"\s*:\s*\{\s*"mimeType"\s*:\s*"([^"]+)"\s*,\s*"data"\s*:\s*"([^"]+)"/;
335
+ const complexMatch = response.match(complexInlineDataRegex);
336
+ if (complexMatch) {
337
+ const [, mimeType, data] = complexMatch;
338
+ const dataUri = `data:${mimeType};base64,${data}`;
339
+ console.log(`[AIAgent] Extracted first data URI from complex Gemini inlineData: ${mimeType}`);
340
+ return dataUri;
341
+ }
342
+ // If no inlineData found, return original response
343
+ return response;
344
+ }
345
+ catch (error) {
346
+ console.warn('[AIAgent] Error formatting Gemini image response:', error);
347
+ return response;
348
+ }
349
+ }
350
+ initializeModel(modelConfig) {
351
+ const { model, temperature, maxTokens } = modelConfig;
352
+ const [provider, modelName] = model.split('/');
353
+ // Use chooseCredential to get the appropriate credential
354
+ // This will throw immediately if credentials are missing
355
+ const apiKey = this.chooseCredential();
356
+ switch (provider) {
357
+ case 'openai':
358
+ return new ChatOpenAI({
359
+ model: modelName,
360
+ temperature,
361
+ maxTokens,
362
+ apiKey,
363
+ });
364
+ case 'google':
365
+ return new ChatGoogleGenerativeAI({
366
+ model: modelName,
367
+ temperature,
368
+ maxOutputTokens: maxTokens,
369
+ apiKey,
370
+ });
371
+ case 'openrouter':
372
+ return new ChatOpenAI({
373
+ model: modelName,
374
+ temperature,
375
+ maxTokens,
376
+ apiKey,
377
+ configuration: {
378
+ baseURL: 'https://openrouter.ai/api/v1',
379
+ },
380
+ });
381
+ default:
382
+ throw new Error(`Unsupported model provider: ${provider}`);
383
+ }
384
+ }
385
+ async initializeTools(toolConfigs) {
386
+ const tools = [];
387
+ await this.factory.registerDefaults();
388
+ for (const toolConfig of toolConfigs) {
389
+ try {
390
+ // Get the tool bubble class from the factory
391
+ const ToolBubbleClass = this.factory.get(toolConfig.name);
392
+ if (!ToolBubbleClass) {
393
+ console.warn(`Tool bubble '${toolConfig.name}' not found in factory`);
394
+ continue;
395
+ }
396
+ // Check if it's a tool bubble (has toAgentTool method)
397
+ if (!('type' in ToolBubbleClass) || ToolBubbleClass.type !== 'tool') {
398
+ console.warn(`Bubble '${toolConfig.name}' is not a tool bubble`);
399
+ continue;
400
+ }
401
+ // Convert to LangGraph tool and add to tools array
402
+ if (!ToolBubbleClass.toolAgent) {
403
+ console.warn(`Tool bubble '${toolConfig.name}' does not have a toolAgent method`);
404
+ continue;
405
+ }
406
+ // Get tool's credential requirements and pass relevant credentials from AI agent
407
+ const toolCredentialOptions = BUBBLE_CREDENTIAL_OPTIONS[toolConfig.name] || [];
408
+ const toolCredentials = {};
409
+ // Pass AI agent's credentials to tools that need them
410
+ for (const credType of toolCredentialOptions) {
411
+ if (this.params.credentials && this.params.credentials[credType]) {
412
+ toolCredentials[credType] = this.params.credentials[credType];
413
+ }
414
+ }
415
+ // Merge with any explicitly provided tool credentials (explicit ones take precedence)
416
+ const finalToolCredentials = {
417
+ ...toolCredentials,
418
+ ...(toolConfig.credentials || {}),
419
+ };
420
+ console.log(`🔍 [AIAgent] Passing credentials to ${toolConfig.name}:`, Object.keys(finalToolCredentials));
421
+ const langGraphTool = ToolBubbleClass.toolAgent(finalToolCredentials, toolConfig.config || {}, this.context);
422
+ const dynamicTool = new DynamicStructuredTool({
423
+ name: langGraphTool.name,
424
+ description: langGraphTool.description,
425
+ schema: langGraphTool.schema,
426
+ func: langGraphTool.func,
427
+ });
428
+ tools.push(dynamicTool);
429
+ }
430
+ catch (error) {
431
+ console.error(`Error initializing tool '${toolConfig.name}':`, error);
432
+ // Continue with other tools even if one fails
433
+ continue;
434
+ }
435
+ }
436
+ return tools;
437
+ }
438
+ async createAgentGraph(llm, tools, systemPrompt) {
439
+ // Define the agent node
440
+ const agentNode = async ({ messages }) => {
441
+ // Enhance system prompt for JSON mode
442
+ const systemMessage = new HumanMessage(systemPrompt);
443
+ const allMessages = [systemMessage, ...messages];
444
+ // If we have tools, bind them to the LLM
445
+ const modelWithTools = tools.length > 0 ? llm.bindTools(tools) : llm;
446
+ const response = await modelWithTools.invoke(allMessages);
447
+ return { messages: [response] };
448
+ };
449
+ // Define conditional edge function
450
+ const shouldContinue = ({ messages }) => {
451
+ const lastMessage = messages[messages.length - 1];
452
+ // Check if the last message has tool calls
453
+ if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {
454
+ return 'tools';
455
+ }
456
+ return '__end__';
457
+ };
458
+ // Build the graph
459
+ const graph = new StateGraph(MessagesAnnotation).addNode('agent', agentNode);
460
+ if (tools.length > 0) {
461
+ // Use the official ToolNode for tool execution
462
+ const toolNode = new ToolNode(tools);
463
+ graph
464
+ .addNode('tools', toolNode)
465
+ .addEdge('__start__', 'agent')
466
+ .addConditionalEdges('agent', shouldContinue)
467
+ .addEdge('tools', 'agent');
468
+ }
469
+ else {
470
+ graph.addEdge('__start__', 'agent').addEdge('agent', '__end__');
471
+ }
472
+ return graph.compile();
473
+ }
474
+ async executeAgent(graph, message, images, maxIterations, jsonMode) {
475
+ const toolCalls = [];
476
+ let iterations = 0;
477
+ console.log('[AIAgent] Starting execution with message:', message.substring(0, 100) + '...');
478
+ console.log('[AIAgent] Max iterations:', maxIterations);
479
+ try {
480
+ console.log('[AIAgent] Invoking graph...');
481
+ // Create human message with text and optional images
482
+ let humanMessage;
483
+ if (images && images.length > 0) {
484
+ console.log('[AIAgent] Creating multimodal message with', images.length, 'images');
485
+ // Create multimodal content array
486
+ const content = [{ type: 'text', text: message }];
487
+ // Add images to content
488
+ for (const image of images) {
489
+ let imageUrl;
490
+ if (image.type === 'base64') {
491
+ // Base64 encoded image
492
+ imageUrl = `data:${image.mimeType};base64,${image.data}`;
493
+ }
494
+ else {
495
+ // URL image - fetch and convert to base64 for Google Gemini compatibility
496
+ try {
497
+ console.log('[AIAgent] Fetching image from URL:', image.url);
498
+ const response = await fetch(image.url);
499
+ if (!response.ok) {
500
+ throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
501
+ }
502
+ const arrayBuffer = await response.arrayBuffer();
503
+ const base64Data = Buffer.from(arrayBuffer).toString('base64');
504
+ // Detect MIME type from response or default to PNG
505
+ const contentType = response.headers.get('content-type') || 'image/png';
506
+ imageUrl = `data:${contentType};base64,${base64Data}`;
507
+ console.log('[AIAgent] Successfully converted URL image to base64');
508
+ }
509
+ catch (error) {
510
+ console.error('[AIAgent] Error fetching image from URL:', error);
511
+ throw new Error(`Failed to load image from URL ${image.url}: ${error instanceof Error ? error.message : 'Unknown error'}`);
512
+ }
513
+ }
514
+ content.push({
515
+ type: 'image_url',
516
+ image_url: { url: imageUrl },
517
+ });
518
+ // Add image description if provided
519
+ if (image.description) {
520
+ content.push({
521
+ type: 'text',
522
+ text: `Image description: ${image.description}`,
523
+ });
524
+ }
525
+ }
526
+ humanMessage = new HumanMessage({ content });
527
+ }
528
+ else {
529
+ // Text-only message
530
+ humanMessage = new HumanMessage(message);
531
+ }
532
+ const result = await graph.invoke({ messages: [humanMessage] }, { recursionLimit: maxIterations });
533
+ console.log('[AIAgent] Graph execution completed');
534
+ console.log('[AIAgent] Total messages:', result.messages.length);
535
+ iterations = result.messages.length;
536
+ // Extract tool calls from messages
537
+ // Store tool calls temporarily to match with their responses
538
+ const toolCallMap = new Map();
539
+ for (let i = 0; i < result.messages.length; i++) {
540
+ const msg = result.messages[i];
541
+ if (msg instanceof AIMessage && msg.tool_calls) {
542
+ const typedToolCalls = msg.tool_calls;
543
+ // Log and track tool calls
544
+ for (const toolCall of typedToolCalls) {
545
+ toolCallMap.set(toolCall.id, {
546
+ name: toolCall.name,
547
+ args: toolCall.args,
548
+ });
549
+ console.log('[AIAgent] Tool call:', toolCall.name, 'with args:', toolCall.args);
550
+ }
551
+ }
552
+ else if (msg instanceof ToolMessage) {
553
+ // Match tool response to its call
554
+ const toolCall = toolCallMap.get(msg.tool_call_id);
555
+ if (toolCall) {
556
+ // Parse content if it's a JSON string
557
+ let output = msg.content;
558
+ if (typeof output === 'string') {
559
+ try {
560
+ output = JSON.parse(output);
561
+ }
562
+ catch {
563
+ // Keep as string if not valid JSON
564
+ }
565
+ }
566
+ console.log('[AIAgent] Tool output preview:', typeof output === 'string'
567
+ ? output.substring(0, 100) + '...'
568
+ : JSON.stringify(output).substring(0, 100) + '...');
569
+ toolCalls.push({
570
+ tool: toolCall.name,
571
+ input: toolCall.args,
572
+ output,
573
+ });
574
+ }
575
+ }
576
+ }
577
+ // Get the final AI message response
578
+ console.log('[AIAgent] Filtering AI messages...');
579
+ const aiMessages = result.messages.filter((msg) => msg instanceof AIMessage);
580
+ console.log('[AIAgent] Found', aiMessages.length, 'AI messages');
581
+ const finalMessage = aiMessages[aiMessages.length - 1];
582
+ // Check for MAX_TOKENS finish reason
583
+ if (finalMessage?.additional_kwargs?.finishReason === 'MAX_TOKENS') {
584
+ throw new Error('Response was truncated due to max tokens limit. Please increase maxTokens in model configuration.');
585
+ }
586
+ // Track token usage from ALL AI messages (not just the final one)
587
+ // This is critical for multi-iteration workflows where the agent calls tools multiple times
588
+ let totalInputTokens = 0;
589
+ let totalOutputTokens = 0;
590
+ let totalTokensSum = 0;
591
+ for (const msg of result.messages) {
592
+ if (msg instanceof AIMessage && msg.usage_metadata) {
593
+ totalInputTokens += msg.usage_metadata.input_tokens || 0;
594
+ totalOutputTokens += msg.usage_metadata.output_tokens || 0;
595
+ totalTokensSum += msg.usage_metadata.total_tokens || 0;
596
+ }
597
+ }
598
+ if (totalTokensSum > 0 && this.context?.logger) {
599
+ this.context.logger.logTokenUsage({
600
+ inputTokens: totalInputTokens,
601
+ outputTokens: totalOutputTokens,
602
+ totalTokens: totalTokensSum,
603
+ modelName: this.params.model.model,
604
+ }, `LLM completion: ${totalInputTokens} input + ${totalOutputTokens} output = ${totalTokensSum} total tokens`, {
605
+ bubbleName: 'ai-agent',
606
+ operationType: 'bubble_execution',
607
+ });
608
+ }
609
+ const response = finalMessage?.content || 'No response generated';
610
+ // Use shared formatting method
611
+ const formattedResult = await this.formatFinalResponse(response, this.params.model, jsonMode);
612
+ // If there's an error from formatting (e.g., invalid JSON), return early
613
+ if (formattedResult.error) {
614
+ return {
615
+ response: formattedResult.response,
616
+ toolCalls: toolCalls.length > 0 ? toolCalls : [],
617
+ iterations,
618
+ error: formattedResult.error,
619
+ success: false,
620
+ };
621
+ }
622
+ const finalResponse = formattedResult.response;
623
+ console.log('[AIAgent] Final response length:', typeof finalResponse === 'string'
624
+ ? finalResponse.length
625
+ : JSON.stringify(finalResponse).length);
626
+ console.log('[AIAgent] Tool calls made:', toolCalls.length);
627
+ console.log('[AIAgent] Execution completed with', iterations, 'iterations');
628
+ return {
629
+ response: typeof finalResponse === 'string'
630
+ ? finalResponse
631
+ : JSON.stringify(finalResponse),
632
+ toolCalls: toolCalls.length > 0 ? toolCalls : [],
633
+ iterations,
634
+ error: '',
635
+ success: true,
636
+ };
637
+ }
638
+ catch (error) {
639
+ console.warn('[AIAgent] Execution error (continuing):', error);
640
+ console.log('[AIAgent] Tool calls before error:', toolCalls.length);
641
+ console.log('[AIAgent] Iterations before error:', iterations);
642
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
643
+ // Return partial results to allow execution to continue
644
+ // Include any tool calls that were completed before the error
645
+ return {
646
+ response: `Execution error: ${errorMessage}`,
647
+ success: false, // Still false but don't completely halt execution
648
+ iterations,
649
+ toolCalls: toolCalls.length > 0 ? toolCalls : [], // Preserve completed tool calls
650
+ error: errorMessage,
651
+ };
652
+ }
653
+ }
654
+ /**
655
+ * Execute agent with streaming support using LangGraph streamEvents
656
+ */
657
+ async executeAgentWithStreaming(graph, message, images, maxIterations, jsonMode, streamingCallback) {
658
+ const toolCalls = [];
659
+ let iterations = 0;
660
+ let currentMessageId = '';
661
+ console.log('[AIAgent] Starting streaming execution with message:', message.substring(0, 100) + '...');
662
+ try {
663
+ // Create human message with text and optional images
664
+ let humanMessage;
665
+ if (images && images.length > 0) {
666
+ console.log('[AIAgent] Creating multimodal message with', images.length, 'images');
667
+ // Create multimodal content array
668
+ const content = [{ type: 'text', text: message }];
669
+ // Add images to content
670
+ for (const image of images) {
671
+ let imageUrl;
672
+ if (image.type === 'base64') {
673
+ // Base64 encoded image
674
+ imageUrl = `data:${image.mimeType};base64,${image.data}`;
675
+ }
676
+ else {
677
+ // URL image - fetch and convert to base64 for Google Gemini compatibility
678
+ try {
679
+ console.log('[AIAgent] Fetching image from URL:', image.url);
680
+ const response = await fetch(image.url);
681
+ if (!response.ok) {
682
+ throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
683
+ }
684
+ const arrayBuffer = await response.arrayBuffer();
685
+ const base64Data = Buffer.from(arrayBuffer).toString('base64');
686
+ // Detect MIME type from response or default to PNG
687
+ const contentType = response.headers.get('content-type') || 'image/png';
688
+ imageUrl = `data:${contentType};base64,${base64Data}`;
689
+ console.log('[AIAgent] Successfully converted URL image to base64');
690
+ }
691
+ catch (error) {
692
+ console.error('[AIAgent] Error fetching image from URL:', error);
693
+ throw new Error(`Failed to load image from URL ${image.url}: ${error instanceof Error ? error.message : 'Unknown error'}`);
694
+ }
695
+ }
696
+ content.push({
697
+ type: 'image_url',
698
+ image_url: { url: imageUrl },
699
+ });
700
+ // Add image description if provided
701
+ if (image.description) {
702
+ content.push({
703
+ type: 'text',
704
+ text: `Image description: ${image.description}`,
705
+ });
706
+ }
707
+ }
708
+ humanMessage = new HumanMessage({ content });
709
+ }
710
+ else {
711
+ // Text-only message
712
+ humanMessage = new HumanMessage(message);
713
+ }
714
+ // Stream events from the graph
715
+ const eventStream = graph.streamEvents({ messages: [humanMessage] }, {
716
+ version: 'v2',
717
+ recursionLimit: maxIterations,
718
+ });
719
+ let currentIteration = 0;
720
+ const toolCallMap = new Map();
721
+ let accumulatedContent = '';
722
+ // Track processed events to prevent duplicates
723
+ const processedIterationEvents = new Set();
724
+ for await (const event of eventStream) {
725
+ if (!event || typeof event !== 'object')
726
+ continue;
727
+ // Handle different types of streaming events
728
+ switch (event.event) {
729
+ case 'on_chat_model_start':
730
+ currentIteration++;
731
+ currentMessageId = `msg-${Date.now()}-${currentIteration}`;
732
+ if (streamingCallback) {
733
+ await streamingCallback({
734
+ type: 'iteration_start',
735
+ data: { iteration: currentIteration },
736
+ });
737
+ }
738
+ break;
739
+ case 'on_chat_model_stream':
740
+ // Stream individual tokens
741
+ if (event.data?.chunk?.content && streamingCallback) {
742
+ const content = event.data.chunk.content;
743
+ accumulatedContent += content;
744
+ await streamingCallback({
745
+ type: 'token',
746
+ data: {
747
+ content,
748
+ messageId: currentMessageId,
749
+ },
750
+ });
751
+ }
752
+ break;
753
+ case 'on_chat_model_end':
754
+ if (streamingCallback) {
755
+ const usageMetadata = event.data?.output?.usage_metadata;
756
+ const totalTokens = usageMetadata?.total_tokens;
757
+ // Track token usage if available
758
+ if (usageMetadata && this.context?.logger) {
759
+ const tokenUsage = {
760
+ inputTokens: usageMetadata.input_tokens || 0,
761
+ outputTokens: usageMetadata.output_tokens || 0,
762
+ totalTokens: totalTokens || 0,
763
+ modelName: this.params.model.model,
764
+ };
765
+ this.context.logger.logTokenUsage(tokenUsage, `LLM completion: ${tokenUsage.inputTokens} input + ${tokenUsage.outputTokens} output = ${tokenUsage.totalTokens} total tokens`, {
766
+ bubbleName: 'ai-agent',
767
+ operationType: 'bubble_execution',
768
+ });
769
+ }
770
+ await streamingCallback({
771
+ type: 'llm_complete',
772
+ data: {
773
+ messageId: currentMessageId,
774
+ totalTokens,
775
+ },
776
+ });
777
+ }
778
+ break;
779
+ case 'on_tool_start':
780
+ if (event.name && event.data?.input && streamingCallback) {
781
+ const callId = `tool-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
782
+ toolCallMap.set(callId, {
783
+ name: event.name,
784
+ args: event.data.input,
785
+ startTime: Date.now(),
786
+ });
787
+ await streamingCallback({
788
+ type: 'tool_start',
789
+ data: {
790
+ tool: event.name,
791
+ input: event.data.input,
792
+ callId,
793
+ },
794
+ });
795
+ }
796
+ break;
797
+ case 'on_tool_end':
798
+ if (event.name && event.data?.output && streamingCallback) {
799
+ // Find matching tool call
800
+ const matchingCall = Array.from(toolCallMap.entries()).find(([, callData]) => callData.name === event.name);
801
+ if (matchingCall) {
802
+ const [callId, callData] = matchingCall;
803
+ const duration = Date.now() - callData.startTime;
804
+ toolCalls.push({
805
+ tool: callData.name,
806
+ input: callData.args,
807
+ output: event.data.output,
808
+ });
809
+ await streamingCallback({
810
+ type: 'tool_complete',
811
+ data: {
812
+ callId,
813
+ output: event.data.output,
814
+ duration,
815
+ },
816
+ });
817
+ toolCallMap.delete(callId);
818
+ }
819
+ }
820
+ break;
821
+ case 'on_chain_end':
822
+ // This indicates the completion of the entire graph
823
+ if (event.data?.output) {
824
+ iterations = currentIteration;
825
+ // Prevent duplicate iteration_complete events
826
+ const iterationKey = `iteration_${currentIteration}`;
827
+ if (streamingCallback &&
828
+ !processedIterationEvents.has(iterationKey)) {
829
+ processedIterationEvents.add(iterationKey);
830
+ await streamingCallback({
831
+ type: 'iteration_complete',
832
+ data: {
833
+ iteration: currentIteration,
834
+ hasToolCalls: toolCalls.length > 0,
835
+ },
836
+ });
837
+ }
838
+ }
839
+ break;
840
+ }
841
+ }
842
+ // Process final result
843
+ const accumulatedResponse = accumulatedContent || 'No response generated';
844
+ // Use shared formatting method
845
+ const formattedResult = await this.formatFinalResponse(accumulatedResponse, this.params.model, jsonMode);
846
+ // If there's an error from formatting (e.g., invalid JSON), return early with consistent behavior
847
+ if (formattedResult.error) {
848
+ return {
849
+ response: formattedResult.response,
850
+ toolCalls: toolCalls.length > 0 ? toolCalls : [],
851
+ iterations,
852
+ error: formattedResult.error,
853
+ success: false,
854
+ };
855
+ }
856
+ const finalResponse = formattedResult.response;
857
+ console.log('[AIAgent] Streaming execution completed with', iterations, 'iterations and', toolCalls.length, 'tool calls');
858
+ return {
859
+ response: typeof finalResponse === 'string'
860
+ ? finalResponse
861
+ : JSON.stringify(finalResponse),
862
+ toolCalls: toolCalls.length > 0 ? toolCalls : [],
863
+ iterations,
864
+ error: '',
865
+ success: true,
866
+ };
867
+ }
868
+ catch (error) {
869
+ console.warn('[AIAgent] Streaming execution error (continuing):', error);
870
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
871
+ return {
872
+ response: `Execution error: ${errorMessage}`,
873
+ success: false, // Still false but don't completely halt execution
874
+ iterations,
875
+ toolCalls: toolCalls.length > 0 ? toolCalls : [], // Preserve completed tool calls
876
+ error: errorMessage,
877
+ };
878
+ }
879
+ }
880
+ }
881
+ //# sourceMappingURL=ai-agent.js.map