@bubblelab/bubble-core 0.1.8 → 0.1.10

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 (224) hide show
  1. package/dist/bubble-bundle.d.ts +333 -1438
  2. package/dist/bubble-factory.d.ts.map +1 -1
  3. package/dist/bubble-factory.js +64 -28
  4. package/dist/bubble-factory.js.map +1 -1
  5. package/dist/bubble-flow/bubble-flow-class.d.ts +17 -1
  6. package/dist/bubble-flow/bubble-flow-class.d.ts.map +1 -1
  7. package/dist/bubble-flow/bubble-flow-class.js +16 -0
  8. package/dist/bubble-flow/bubble-flow-class.js.map +1 -1
  9. package/dist/bubble-flow/sample/data-analyst-flow.d.ts +1 -1
  10. package/dist/bubble-flow/sample/data-analyst-flow.d.ts.map +1 -1
  11. package/dist/bubble-flow/sample/error-ts.d.ts +1 -1
  12. package/dist/bubble-flow/sample/error-ts.d.ts.map +1 -1
  13. package/dist/bubble-flow/sample/sanitytest.d.ts +1 -1
  14. package/dist/bubble-flow/sample/sanitytest.d.ts.map +1 -1
  15. package/dist/bubble-flow/sample/simple-webhook-2.d.ts +1 -1
  16. package/dist/bubble-flow/sample/simple-webhook-2.d.ts.map +1 -1
  17. package/dist/bubble-flow/sample/simple-webhook.d.ts +1 -1
  18. package/dist/bubble-flow/sample/simple-webhook.d.ts.map +1 -1
  19. package/dist/bubble-flow/sample/simplified-data-analysis.flow.d.ts +1 -1
  20. package/dist/bubble-flow/sample/simplified-data-analysis.flow.d.ts.map +1 -1
  21. package/dist/bubble-flow/sample/slack-v0.1.d.ts +1 -1
  22. package/dist/bubble-flow/sample/slack-v0.1.d.ts.map +1 -1
  23. package/dist/bubble-flow/sample/slackagenttest.d.ts +1 -1
  24. package/dist/bubble-flow/sample/slackagenttest.d.ts.map +1 -1
  25. package/dist/bubbles/service-bubble/ai-agent.d.ts +115 -97
  26. package/dist/bubbles/service-bubble/ai-agent.d.ts.map +1 -1
  27. package/dist/bubbles/service-bubble/ai-agent.js +276 -96
  28. package/dist/bubbles/service-bubble/ai-agent.js.map +1 -1
  29. package/dist/bubbles/service-bubble/apify/actors/instagram-hashtag-scraper.d.ts +805 -0
  30. package/dist/bubbles/service-bubble/apify/actors/instagram-hashtag-scraper.d.ts.map +1 -0
  31. package/dist/bubbles/service-bubble/apify/actors/instagram-hashtag-scraper.js +131 -0
  32. package/dist/bubbles/service-bubble/apify/actors/instagram-hashtag-scraper.js.map +1 -0
  33. package/dist/bubbles/service-bubble/apify/actors/instagram-scraper.d.ts +485 -0
  34. package/dist/bubbles/service-bubble/apify/actors/instagram-scraper.d.ts.map +1 -0
  35. package/dist/bubbles/service-bubble/apify/actors/instagram-scraper.js +176 -0
  36. package/dist/bubbles/service-bubble/apify/actors/instagram-scraper.js.map +1 -0
  37. package/dist/bubbles/service-bubble/apify/actors/linkedin-posts-search.d.ts +302 -0
  38. package/dist/bubbles/service-bubble/apify/actors/linkedin-posts-search.d.ts.map +1 -0
  39. package/dist/bubbles/service-bubble/apify/actors/linkedin-posts-search.js +138 -0
  40. package/dist/bubbles/service-bubble/apify/actors/linkedin-posts-search.js.map +1 -0
  41. package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-posts.d.ts +642 -0
  42. package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-posts.d.ts.map +1 -0
  43. package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-posts.js +123 -0
  44. package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-posts.js.map +1 -0
  45. package/dist/bubbles/service-bubble/apify/actors/youtube-scraper.d.ts +184 -0
  46. package/dist/bubbles/service-bubble/apify/actors/youtube-scraper.d.ts.map +1 -0
  47. package/dist/bubbles/service-bubble/apify/actors/youtube-scraper.js +145 -0
  48. package/dist/bubbles/service-bubble/apify/actors/youtube-scraper.js.map +1 -0
  49. package/dist/bubbles/service-bubble/apify/actors/youtube-transcript-scraper.d.ts +52 -0
  50. package/dist/bubbles/service-bubble/apify/actors/youtube-transcript-scraper.d.ts.map +1 -0
  51. package/dist/bubbles/service-bubble/apify/actors/youtube-transcript-scraper.js +29 -0
  52. package/dist/bubbles/service-bubble/apify/actors/youtube-transcript-scraper.js.map +1 -0
  53. package/dist/bubbles/service-bubble/apify/apify-scraper.schema.d.ts +1999 -0
  54. package/dist/bubbles/service-bubble/apify/apify-scraper.schema.d.ts.map +1 -0
  55. package/dist/bubbles/service-bubble/apify/apify-scraper.schema.js +54 -0
  56. package/dist/bubbles/service-bubble/apify/apify-scraper.schema.js.map +1 -0
  57. package/dist/bubbles/service-bubble/apify/apify.d.ts +143 -0
  58. package/dist/bubbles/service-bubble/apify/apify.d.ts.map +1 -0
  59. package/dist/bubbles/service-bubble/apify/apify.js +276 -0
  60. package/dist/bubbles/service-bubble/apify/apify.js.map +1 -0
  61. package/dist/bubbles/service-bubble/apify/index.d.ts +6 -0
  62. package/dist/bubbles/service-bubble/apify/index.d.ts.map +1 -0
  63. package/dist/bubbles/service-bubble/apify/index.js +6 -0
  64. package/dist/bubbles/service-bubble/apify/index.js.map +1 -0
  65. package/dist/bubbles/service-bubble/apify/types.d.ts +7 -0
  66. package/dist/bubbles/service-bubble/apify/types.d.ts.map +1 -0
  67. package/dist/bubbles/service-bubble/apify/types.js +5 -0
  68. package/dist/bubbles/service-bubble/apify/types.js.map +1 -0
  69. package/dist/bubbles/service-bubble/gmail.d.ts +626 -132
  70. package/dist/bubbles/service-bubble/gmail.d.ts.map +1 -1
  71. package/dist/bubbles/service-bubble/gmail.js +435 -7
  72. package/dist/bubbles/service-bubble/gmail.js.map +1 -1
  73. package/dist/bubbles/service-bubble/google-calendar.d.ts +36 -36
  74. package/dist/bubbles/service-bubble/google-drive.d.ts +233 -4
  75. package/dist/bubbles/service-bubble/google-drive.d.ts.map +1 -1
  76. package/dist/bubbles/service-bubble/google-drive.js +65 -75
  77. package/dist/bubbles/service-bubble/google-drive.js.map +1 -1
  78. package/dist/bubbles/service-bubble/google-sheets.d.ts +52 -52
  79. package/dist/bubbles/service-bubble/hello-world.js +2 -2
  80. package/dist/bubbles/service-bubble/hello-world.js.map +1 -1
  81. package/dist/bubbles/service-bubble/http.d.ts +6 -6
  82. package/dist/bubbles/service-bubble/postgresql.d.ts +4 -4
  83. package/dist/bubbles/service-bubble/resend.d.ts +5 -5
  84. package/dist/bubbles/service-bubble/resend.d.ts.map +1 -1
  85. package/dist/bubbles/service-bubble/resend.js +16 -5
  86. package/dist/bubbles/service-bubble/resend.js.map +1 -1
  87. package/dist/bubbles/service-bubble/slack.d.ts +18 -18
  88. package/dist/bubbles/service-bubble/storage.d.ts +4 -4
  89. package/dist/bubbles/service-bubble/storage.d.ts.map +1 -1
  90. package/dist/bubbles/service-bubble/storage.js +16 -5
  91. package/dist/bubbles/service-bubble/storage.js.map +1 -1
  92. package/dist/bubbles/service-bubble/x-twitter.d.ts +814 -0
  93. package/dist/bubbles/service-bubble/x-twitter.d.ts.map +1 -0
  94. package/dist/bubbles/service-bubble/x-twitter.js +445 -0
  95. package/dist/bubbles/service-bubble/x-twitter.js.map +1 -0
  96. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts +20 -20
  97. package/dist/bubbles/tool-bubble/chart-js-tool.d.ts +16 -16
  98. package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts +14 -1
  99. package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts.map +1 -1
  100. package/dist/bubbles/tool-bubble/get-bubble-details-tool.js +101 -47
  101. package/dist/bubbles/tool-bubble/get-bubble-details-tool.js.map +1 -1
  102. package/dist/bubbles/tool-bubble/instagram-tool.d.ts +435 -0
  103. package/dist/bubbles/tool-bubble/instagram-tool.d.ts.map +1 -0
  104. package/dist/bubbles/tool-bubble/instagram-tool.js +474 -0
  105. package/dist/bubbles/tool-bubble/instagram-tool.js.map +1 -0
  106. package/dist/bubbles/tool-bubble/linkedin-tool.d.ts +2136 -0
  107. package/dist/bubbles/tool-bubble/linkedin-tool.d.ts.map +1 -0
  108. package/dist/bubbles/tool-bubble/linkedin-tool.js +608 -0
  109. package/dist/bubbles/tool-bubble/linkedin-tool.js.map +1 -0
  110. package/dist/bubbles/tool-bubble/reddit-scrape-tool.d.ts +69 -64
  111. package/dist/bubbles/tool-bubble/reddit-scrape-tool.d.ts.map +1 -1
  112. package/dist/bubbles/tool-bubble/reddit-scrape-tool.js +97 -22
  113. package/dist/bubbles/tool-bubble/reddit-scrape-tool.js.map +1 -1
  114. package/dist/bubbles/tool-bubble/research-agent-tool.d.ts +6 -6
  115. package/dist/bubbles/tool-bubble/research-agent-tool.js +5 -5
  116. package/dist/bubbles/tool-bubble/research-agent-tool.js.map +1 -1
  117. package/dist/bubbles/tool-bubble/tool-template.d.ts +8 -8
  118. package/dist/bubbles/tool-bubble/web-crawl-tool.d.ts +14 -14
  119. package/dist/bubbles/tool-bubble/web-extract-tool.d.ts +4 -4
  120. package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts +28 -28
  121. package/dist/bubbles/tool-bubble/web-scrape-tool.js +1 -1
  122. package/dist/bubbles/tool-bubble/web-scrape-tool.js.map +1 -1
  123. package/dist/bubbles/tool-bubble/web-search-tool.d.ts +5 -4
  124. package/dist/bubbles/tool-bubble/web-search-tool.d.ts.map +1 -1
  125. package/dist/bubbles/tool-bubble/web-search-tool.js +6 -2
  126. package/dist/bubbles/tool-bubble/web-search-tool.js.map +1 -1
  127. package/dist/bubbles/tool-bubble/youtube-tool.d.ts +394 -0
  128. package/dist/bubbles/tool-bubble/youtube-tool.d.ts.map +1 -0
  129. package/dist/bubbles/tool-bubble/youtube-tool.js +352 -0
  130. package/dist/bubbles/tool-bubble/youtube-tool.js.map +1 -0
  131. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts +47 -36
  132. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts.map +1 -1
  133. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js +96 -65
  134. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js.map +1 -1
  135. package/dist/bubbles/workflow-bubble/generate-document.workflow.d.ts +38 -38
  136. package/dist/bubbles/workflow-bubble/generate-document.workflow.js +1 -1
  137. package/dist/bubbles/workflow-bubble/generate-document.workflow.js.map +1 -1
  138. package/dist/bubbles/workflow-bubble/parse-document.workflow.d.ts +42 -42
  139. package/dist/bubbles/workflow-bubble/parse-document.workflow.js +1 -1
  140. package/dist/bubbles/workflow-bubble/parse-document.workflow.js.map +1 -1
  141. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts +22 -22
  142. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts.map +1 -1
  143. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.js +1 -4
  144. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.js.map +1 -1
  145. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.d.ts +60 -60
  146. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.d.ts.map +1 -1
  147. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.js +2 -2
  148. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.js.map +1 -1
  149. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.d.ts +20 -20
  150. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.js +1 -1
  151. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.js.map +1 -1
  152. package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts +66 -66
  153. package/dist/bubbles/workflow-bubble/slack-formatter-agent.js +1 -1
  154. package/dist/bubbles/workflow-bubble/slack-formatter-agent.js.map +1 -1
  155. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts +18 -18
  156. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts.map +1 -1
  157. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.js +1 -2
  158. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.js.map +1 -1
  159. package/dist/bubbles.json +489 -0
  160. package/dist/index.d.ts +16 -3
  161. package/dist/index.d.ts.map +1 -1
  162. package/dist/index.js +8 -1
  163. package/dist/index.js.map +1 -1
  164. package/dist/logging/BubbleLogger.d.ts +11 -0
  165. package/dist/logging/BubbleLogger.d.ts.map +1 -1
  166. package/dist/logging/BubbleLogger.js +87 -33
  167. package/dist/logging/BubbleLogger.js.map +1 -1
  168. package/dist/logging/StreamingBubbleLogger.d.ts.map +1 -1
  169. package/dist/logging/StreamingBubbleLogger.js +23 -16
  170. package/dist/logging/StreamingBubbleLogger.js.map +1 -1
  171. package/dist/test-gm.d.ts +10 -0
  172. package/dist/test-gm.d.ts.map +1 -0
  173. package/dist/test-gm.js +95 -0
  174. package/dist/test-gm.js.map +1 -0
  175. package/dist/types/available-tools.d.ts +1 -1
  176. package/dist/types/available-tools.d.ts.map +1 -1
  177. package/dist/types/available-tools.js +2 -0
  178. package/dist/types/available-tools.js.map +1 -1
  179. package/dist/types/base-bubble-class.d.ts +6 -1
  180. package/dist/types/base-bubble-class.d.ts.map +1 -1
  181. package/dist/types/base-bubble-class.js +47 -24
  182. package/dist/types/base-bubble-class.js.map +1 -1
  183. package/dist/types/bubble.d.ts +3 -13
  184. package/dist/types/bubble.d.ts.map +1 -1
  185. package/dist/types/bubble.js +1 -1
  186. package/dist/types/bubble.js.map +1 -1
  187. package/dist/types/service-bubble-class.d.ts +5 -5
  188. package/dist/types/service-bubble-class.d.ts.map +1 -1
  189. package/dist/types/service-bubble-class.js +6 -7
  190. package/dist/types/service-bubble-class.js.map +1 -1
  191. package/dist/types/tool-bubble-class.d.ts +1 -1
  192. package/dist/types/tool-bubble-class.d.ts.map +1 -1
  193. package/dist/types/tool-bubble-class.js +9 -3
  194. package/dist/types/tool-bubble-class.js.map +1 -1
  195. package/dist/types/workflow-bubble-class.d.ts +1 -1
  196. package/dist/types/workflow-bubble-class.d.ts.map +1 -1
  197. package/dist/utils/agent-formatter.d.ts +17 -0
  198. package/dist/utils/agent-formatter.d.ts.map +1 -0
  199. package/dist/utils/agent-formatter.js +139 -0
  200. package/dist/utils/agent-formatter.js.map +1 -0
  201. package/dist/utils/bubbleflow-validation.d.ts.map +1 -1
  202. package/dist/utils/bubbleflow-validation.js +89 -32
  203. package/dist/utils/bubbleflow-validation.js.map +1 -1
  204. package/dist/utils/error-sanitizer.d.ts +12 -0
  205. package/dist/utils/error-sanitizer.d.ts.map +1 -0
  206. package/dist/utils/error-sanitizer.js +77 -0
  207. package/dist/utils/error-sanitizer.js.map +1 -0
  208. package/dist/utils/json-parsing.d.ts +1 -0
  209. package/dist/utils/json-parsing.d.ts.map +1 -1
  210. package/dist/utils/json-parsing.js +205 -32
  211. package/dist/utils/json-parsing.js.map +1 -1
  212. package/package.json +6 -5
  213. package/dist/bubble-trigger/index.d.ts +0 -2
  214. package/dist/bubble-trigger/index.d.ts.map +0 -1
  215. package/dist/bubble-trigger/index.js +0 -2
  216. package/dist/bubble-trigger/index.js.map +0 -1
  217. package/dist/bubble-trigger/types.d.ts +0 -87
  218. package/dist/bubble-trigger/types.d.ts.map +0 -1
  219. package/dist/bubble-trigger/types.js +0 -14
  220. package/dist/bubble-trigger/types.js.map +0 -1
  221. package/dist/types/ai-models.d.ts +0 -4
  222. package/dist/types/ai-models.d.ts.map +0 -1
  223. package/dist/types/ai-models.js +0 -16
  224. package/dist/types/ai-models.js.map +0 -1
@@ -2,16 +2,16 @@ import { z } from 'zod';
2
2
  import { ServiceBubble } from '../../types/service-bubble-class.js';
3
3
  import { CredentialType, BUBBLE_CREDENTIAL_OPTIONS, } from '@bubblelab/shared-schemas';
4
4
  import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';
5
- import { ToolNode } from '@langchain/langgraph/prebuilt';
6
5
  import { ChatOpenAI } from '@langchain/openai';
7
6
  import { ChatAnthropic } from '@langchain/anthropic';
8
7
  import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
9
- import { HumanMessage, AIMessage, ToolMessage } from '@langchain/core/messages';
8
+ import { HumanMessage, AIMessage, ToolMessage, AIMessageChunk, } from '@langchain/core/messages';
10
9
  import { DynamicStructuredTool } from '@langchain/core/tools';
11
- import { AvailableModels } from '../../types/ai-models.js';
12
- import { AvailableTools } from '../../types/available-tools.js';
10
+ import { AvailableModels } from '@bubblelab/shared-schemas';
11
+ import { AvailableTools, } from '../../types/available-tools.js';
13
12
  import { BubbleFactory } from '../../bubble-factory.js';
14
- import { parseJsonWithFallbacks } from '../../utils/json-parsing.js';
13
+ import { extractAndStreamThinkingTokens, formatFinalResponse, } from '../../utils/agent-formatter.js';
14
+ import { isAIMessage, isAIMessageChunk } from '@langchain/core/messages';
15
15
  // Define model configuration
16
16
  const ModelConfigSchema = z.object({
17
17
  model: AvailableModels.default('google/gemini-2.5-flash').describe('AI model to use (format: provider/model-name).'),
@@ -19,20 +19,20 @@ const ModelConfigSchema = z.object({
19
19
  .number()
20
20
  .min(0)
21
21
  .max(2)
22
- .default(0.7)
22
+ .default(1)
23
23
  .describe('Temperature for response randomness (0 = deterministic, 2 = very random)'),
24
24
  maxTokens: z
25
25
  .number()
26
26
  .positive()
27
27
  .optional()
28
- .default(40000)
29
- .describe('Maximum number of tokens to generate in response'),
28
+ .default(12800)
29
+ .describe('Maximum number of tokens to generate in response, keep at default of 40000 unless the response is expected to be certain length'),
30
30
  jsonMode: z
31
31
  .boolean()
32
32
  .default(false)
33
33
  .describe('When true, strips markdown formatting and returns clean JSON response'),
34
34
  });
35
- // Define tool configuration
35
+ // Define tool configuration for pre-registered tools
36
36
  const ToolConfigSchema = z.object({
37
37
  name: AvailableTools.describe('Name of the tool type or tool bubble to enable for the AI agent'),
38
38
  credentials: z
@@ -45,6 +45,25 @@ const ToolConfigSchema = z.object({
45
45
  .optional()
46
46
  .describe('Configuration for the tool or tool bubble'),
47
47
  });
48
+ // Define custom tool schema for runtime-defined tools
49
+ const CustomToolSchema = z.object({
50
+ name: z
51
+ .string()
52
+ .min(1)
53
+ .describe('Unique name for your custom tool (e.g., "calculate-tax")'),
54
+ description: z
55
+ .string()
56
+ .min(1)
57
+ .describe('Description of what the tool does - helps the AI know when to use it'),
58
+ schema: z
59
+ .record(z.string(), z.unknown())
60
+ .describe('Zod schema object defining the tool parameters. Example: { amount: z.number().describe("Amount to calculate tax on"), rate: z.number().describe("Tax rate") }'),
61
+ func: z
62
+ .function()
63
+ .args(z.record(z.string(), z.unknown()))
64
+ .returns(z.promise(z.unknown()))
65
+ .describe('Async function that executes the tool logic. Receives params matching the schema and returns a result.'),
66
+ });
48
67
  // Define image input schemas - supports both base64 data and URLs
49
68
  const Base64ImageSchema = z.object({
50
69
  type: z.literal('base64').default('base64'),
@@ -107,12 +126,18 @@ const AIAgentParamsSchema = z.object({
107
126
  },
108
127
  },
109
128
  ])
110
- .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 []'),
129
+ .describe('Array of pre-registered tools the AI agent can use. Can be tool types (web-search-tool, web-scrape-tool, web-crawl-tool, web-extract-tool, instagram-tool). If using image models, set the tools to []'),
130
+ customTools: z
131
+ .array(CustomToolSchema)
132
+ .default([])
133
+ .optional()
134
+ .describe('Array of custom runtime-defined tools with their own schemas and functions. Use this to add domain-specific tools without pre-registration. Example: [{ name: "calculate-tax", description: "Calculates sales tax", schema: { amount: z.number() }, func: async (input) => {...} }]'),
111
135
  maxIterations: z
112
136
  .number()
113
137
  .positive()
138
+ .min(2)
114
139
  .default(10)
115
- .describe('Maximum number of iterations for the agent workflow'),
140
+ .describe('Maximum number of iterations for the agent workflow, 2 iterations per turn of conversation'),
116
141
  credentials: z
117
142
  .record(z.nativeEnum(CredentialType), z.string())
118
143
  .optional()
@@ -121,6 +146,8 @@ const AIAgentParamsSchema = z.object({
121
146
  .boolean()
122
147
  .default(false)
123
148
  .describe('Enable real-time streaming of tokens, tool calls, and iteration progress'),
149
+ // Note: beforeToolCall and afterToolCall are function hooks added via TypeScript interface
150
+ // They cannot be part of the Zod schema but are available in the params
124
151
  });
125
152
  const AIAgentResultSchema = z.object({
126
153
  response: z
@@ -161,11 +188,18 @@ export class AIAgentBubble extends ServiceBubble {
161
188
  `;
162
189
  static alias = 'agent';
163
190
  factory;
191
+ beforeToolCallHook;
192
+ afterToolCallHook;
193
+ streamingCallback;
194
+ shouldStopAfterTools = false;
164
195
  constructor(params = {
165
196
  message: 'Hello, how are you?',
166
197
  systemPrompt: 'You are a helpful AI assistant',
167
198
  }, context) {
168
199
  super(params, context);
200
+ this.beforeToolCallHook = params.beforeToolCall;
201
+ this.afterToolCallHook = params.afterToolCall;
202
+ this.streamingCallback = params.streamingCallback;
169
203
  this.factory = new BubbleFactory();
170
204
  }
171
205
  async testCredential() {
@@ -180,12 +214,12 @@ export class AIAgentBubble extends ServiceBubble {
180
214
  async performAction(context) {
181
215
  // Context is available but not currently used in this implementation
182
216
  void context;
183
- const { message, images, systemPrompt, model, tools, maxIterations } = this.params;
217
+ const { message, images, systemPrompt, model, tools, customTools, maxIterations, } = this.params;
184
218
  try {
185
219
  // Initialize the language model
186
220
  const llm = this.initializeModel(model);
187
- // Initialize tools
188
- const agentTools = await this.initializeTools(tools);
221
+ // Initialize tools (both pre-registered and custom)
222
+ const agentTools = await this.initializeTools(tools, customTools);
189
223
  // Create the agent graph
190
224
  const graph = await this.createAgentGraph(llm, agentTools, systemPrompt);
191
225
  // Execute the agent
@@ -211,7 +245,7 @@ export class AIAgentBubble extends ServiceBubble {
211
245
  async actionWithStreaming(streamingCallback, context) {
212
246
  // Context is available but not currently used in this implementation
213
247
  void context;
214
- const { message, images, systemPrompt, model, tools, maxIterations } = this.params;
248
+ const { message, images, systemPrompt, model, tools, customTools, maxIterations, } = this.params;
215
249
  const startTime = Date.now();
216
250
  // Send start event
217
251
  await streamingCallback({
@@ -233,8 +267,8 @@ export class AIAgentBubble extends ServiceBubble {
233
267
  });
234
268
  // Initialize the language model
235
269
  const llm = this.initializeModel(model);
236
- // Initialize tools
237
- const agentTools = await this.initializeTools(tools);
270
+ // Initialize tools (both pre-registered and custom)
271
+ const agentTools = await this.initializeTools(tools, customTools);
238
272
  // Create the agent graph
239
273
  const graph = await this.createAgentGraph(llm, agentTools, systemPrompt);
240
274
  // Execute the agent with streaming
@@ -292,70 +326,16 @@ export class AIAgentBubble extends ServiceBubble {
292
326
  throw new Error(`Unsupported model provider: ${provider}`);
293
327
  }
294
328
  }
295
- /**
296
- * Format final response with special handling for Gemini image models and JSON mode
297
- */
298
- async formatFinalResponse(response, modelConfig, jsonMode) {
299
- let finalResponse = typeof response === 'string' ? response : JSON.stringify(response);
300
- // Special handling for Gemini image models that return images in inlineData format
301
- if (modelConfig.model.includes('gemini') &&
302
- modelConfig.model.includes('image')) {
303
- finalResponse = this.formatGeminiImageResponse(finalResponse);
304
- }
305
- else if (jsonMode && typeof finalResponse === 'string') {
306
- // Handle JSON mode: use the improved utility function
307
- const result = parseJsonWithFallbacks(finalResponse);
308
- if (!result.success) {
309
- return {
310
- response: result.response,
311
- error: `${this.params.name || 'AI Agent'} failed to generate valid JSON. Post-processing attempted but JSON is still malformed. Original response: ${finalResponse}`,
312
- };
313
- }
314
- return { response: result.response };
315
- }
316
- return { response: finalResponse };
317
- }
318
- /**
319
- * Convert Gemini's inlineData format to LangChain-compatible data URI format
320
- */
321
- formatGeminiImageResponse(response) {
322
- if (typeof response !== 'string') {
323
- return String(response);
324
- }
325
- try {
326
- console.log('[AIAgent] Formatting Gemini image response...');
327
- // Look for Gemini's inlineData format in the response
328
- const inlineDataRegex = /\{\s*"inlineData"\s*:\s*\{\s*"mimeType"\s*:\s*"([^"]+)"\s*,\s*"data"\s*:\s*"([^"]+)"\s*\}\s*\}/;
329
- const match = response.match(inlineDataRegex);
330
- if (match) {
331
- const [, mimeType, data] = match;
332
- const dataUri = `data:${mimeType};base64,${data}`;
333
- console.log(`[AIAgent] Extracted first data URI from Gemini inlineData: ${mimeType}`);
334
- return dataUri;
335
- }
336
- // Also check for the more complex format with text
337
- const complexInlineDataRegex = /\{\s*"inlineData"\s*:\s*\{\s*"mimeType"\s*:\s*"([^"]+)"\s*,\s*"data"\s*:\s*"([^"]+)"/;
338
- const complexMatch = response.match(complexInlineDataRegex);
339
- if (complexMatch) {
340
- const [, mimeType, data] = complexMatch;
341
- const dataUri = `data:${mimeType};base64,${data}`;
342
- console.log(`[AIAgent] Extracted first data URI from complex Gemini inlineData: ${mimeType}`);
343
- return dataUri;
344
- }
345
- // If no inlineData found, return original response
346
- return response;
347
- }
348
- catch (error) {
349
- console.warn('[AIAgent] Error formatting Gemini image response:', error);
350
- return response;
351
- }
352
- }
353
329
  initializeModel(modelConfig) {
354
330
  const { model, temperature, maxTokens } = modelConfig;
355
- const [provider, modelName] = model.split('/');
331
+ const slashIndex = model.indexOf('/');
332
+ const provider = model.substring(0, slashIndex);
333
+ const modelName = model.substring(slashIndex + 1);
356
334
  // Use chooseCredential to get the appropriate credential
357
335
  // This will throw immediately if credentials are missing
358
336
  const apiKey = this.chooseCredential();
337
+ // Enable streaming if streamingCallback is provided
338
+ const enableStreaming = !!this.streamingCallback;
359
339
  switch (provider) {
360
340
  case 'openai':
361
341
  return new ChatOpenAI({
@@ -363,6 +343,7 @@ export class AIAgentBubble extends ServiceBubble {
363
343
  temperature,
364
344
  maxTokens,
365
345
  apiKey,
346
+ streaming: enableStreaming,
366
347
  });
367
348
  case 'google':
368
349
  return new ChatGoogleGenerativeAI({
@@ -370,39 +351,70 @@ export class AIAgentBubble extends ServiceBubble {
370
351
  temperature,
371
352
  maxOutputTokens: maxTokens,
372
353
  apiKey,
354
+ streaming: enableStreaming,
373
355
  });
374
356
  case 'anthropic':
375
357
  return new ChatAnthropic({
376
358
  model: modelName,
377
359
  temperature,
378
360
  anthropicApiKey: apiKey,
379
- maxTokens: 1000,
380
- streaming: false,
361
+ maxTokens,
362
+ streaming: enableStreaming,
381
363
  apiKey,
382
364
  });
383
365
  case 'openrouter':
366
+ console.log('openrouter', modelName);
384
367
  return new ChatOpenAI({
385
368
  model: modelName,
369
+ __includeRawResponse: true,
386
370
  temperature,
387
371
  maxTokens,
388
372
  apiKey,
373
+ streaming: enableStreaming,
389
374
  configuration: {
390
375
  baseURL: 'https://openrouter.ai/api/v1',
391
376
  },
377
+ modelKwargs: {
378
+ reasoning: {
379
+ effort: 'medium',
380
+ exclude: false,
381
+ },
382
+ },
392
383
  });
393
384
  default:
394
385
  throw new Error(`Unsupported model provider: ${provider}`);
395
386
  }
396
387
  }
397
- async initializeTools(toolConfigs) {
388
+ async initializeTools(toolConfigs, customToolConfigs = []) {
398
389
  const tools = [];
399
390
  await this.factory.registerDefaults();
391
+ // First, initialize custom tools
392
+ for (const customTool of customToolConfigs) {
393
+ try {
394
+ console.log(`🛠️ [AIAgent] Initializing custom tool: ${customTool.name}`);
395
+ const dynamicTool = new DynamicStructuredTool({
396
+ name: customTool.name,
397
+ description: customTool.description,
398
+ schema: z.object(customTool.schema),
399
+ func: customTool.func,
400
+ });
401
+ tools.push(dynamicTool);
402
+ }
403
+ catch (error) {
404
+ console.error(`Error initializing custom tool '${customTool.name}':`, error);
405
+ // Continue with other tools even if one fails
406
+ continue;
407
+ }
408
+ }
409
+ // Then, initialize pre-registered tools from factory
400
410
  for (const toolConfig of toolConfigs) {
401
411
  try {
402
- // Get the tool bubble class from the factory
403
412
  const ToolBubbleClass = this.factory.get(toolConfig.name);
404
413
  if (!ToolBubbleClass) {
405
- console.warn(`Tool bubble '${toolConfig.name}' not found in factory`);
414
+ if (this.context && this.context.logger) {
415
+ this.context.logger.warn(`Tool bubble '${toolConfig.name}' not found in factory. This tool will not be used.`);
416
+ }
417
+ console.warn(`Tool bubble '${toolConfig.name}' not found in factory. This tool will not be used.`);
406
418
  continue;
407
419
  }
408
420
  // Check if it's a tool bubble (has toAgentTool method)
@@ -447,6 +459,107 @@ export class AIAgentBubble extends ServiceBubble {
447
459
  }
448
460
  return tools;
449
461
  }
462
+ /**
463
+ * Custom tool execution node that supports hooks
464
+ */
465
+ async executeToolsWithHooks(state, tools) {
466
+ const { messages } = state;
467
+ const lastMessage = messages[messages.length - 1];
468
+ const toolCalls = lastMessage.tool_calls || [];
469
+ const toolMessages = [];
470
+ let currentMessages = [...messages];
471
+ // Reset stop flag at the start of tool execution
472
+ this.shouldStopAfterTools = false;
473
+ // Execute each tool call
474
+ for (const toolCall of toolCalls) {
475
+ const tool = tools.find((t) => t.name === toolCall.name);
476
+ if (!tool) {
477
+ console.warn(`Tool ${toolCall.name} not found`);
478
+ toolMessages.push(new ToolMessage({
479
+ content: `Error: Tool ${toolCall.name} not found`,
480
+ tool_call_id: toolCall.id,
481
+ }));
482
+ continue;
483
+ }
484
+ try {
485
+ // Call beforeToolCall hook if provided
486
+ const hookResult_before = await this.beforeToolCallHook?.({
487
+ toolName: toolCall.name,
488
+ toolInput: toolCall.args,
489
+ messages: currentMessages,
490
+ });
491
+ const startTime = Date.now();
492
+ this.streamingCallback?.({
493
+ type: 'tool_start',
494
+ data: {
495
+ tool: toolCall.name,
496
+ input: toolCall.args,
497
+ callId: toolCall.id,
498
+ },
499
+ });
500
+ // If hook returns modified messages/toolInput, apply them
501
+ if (hookResult_before) {
502
+ if (hookResult_before.messages) {
503
+ currentMessages = hookResult_before.messages;
504
+ }
505
+ toolCall.args = hookResult_before.toolInput;
506
+ }
507
+ // Execute the tool
508
+ const toolOutput = await tool.invoke(toolCall.args);
509
+ // Create tool message
510
+ const toolMessage = new ToolMessage({
511
+ content: typeof toolOutput === 'string'
512
+ ? toolOutput
513
+ : JSON.stringify(toolOutput),
514
+ tool_call_id: toolCall.id,
515
+ });
516
+ toolMessages.push(toolMessage);
517
+ currentMessages = [...currentMessages, toolMessage];
518
+ // Call afterToolCall hook if provided
519
+ const hookResult_after = await this.afterToolCallHook?.({
520
+ toolName: toolCall.name,
521
+ toolInput: toolCall.args,
522
+ toolOutput,
523
+ messages: currentMessages,
524
+ });
525
+ // If hook returns modified messages, update current messages
526
+ if (hookResult_after) {
527
+ if (hookResult_after.messages) {
528
+ currentMessages = hookResult_after.messages;
529
+ }
530
+ // Check if hook wants to stop execution
531
+ if (hookResult_after.shouldStop === true) {
532
+ this.shouldStopAfterTools = true;
533
+ }
534
+ }
535
+ this.streamingCallback?.({
536
+ type: 'tool_complete',
537
+ data: {
538
+ callId: toolCall.id,
539
+ input: toolCall.args,
540
+ tool: toolCall.name,
541
+ output: toolOutput,
542
+ duration: Date.now() - startTime,
543
+ },
544
+ });
545
+ }
546
+ catch (error) {
547
+ console.error(`Error executing tool ${toolCall.name}:`, error);
548
+ const errorMessage = new ToolMessage({
549
+ content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
550
+ tool_call_id: toolCall.id,
551
+ });
552
+ toolMessages.push(errorMessage);
553
+ currentMessages = [...currentMessages, errorMessage];
554
+ }
555
+ }
556
+ // Return the updated messages
557
+ // If hooks modified messages, use those; otherwise use the original messages + tool messages
558
+ if (currentMessages.length !== messages.length + toolMessages.length) {
559
+ return { messages: currentMessages };
560
+ }
561
+ return { messages: toolMessages };
562
+ }
450
563
  async createAgentGraph(llm, tools, systemPrompt) {
451
564
  // Define the agent node
452
565
  const agentNode = async ({ messages }) => {
@@ -455,8 +568,52 @@ export class AIAgentBubble extends ServiceBubble {
455
568
  const allMessages = [systemMessage, ...messages];
456
569
  // If we have tools, bind them to the LLM
457
570
  const modelWithTools = tools.length > 0 ? llm.bindTools(tools) : llm;
458
- const response = await modelWithTools.invoke(allMessages);
459
- return { messages: [response] };
571
+ // Use streaming if streamingCallback is provided
572
+ if (this.streamingCallback) {
573
+ const messageId = `msg-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
574
+ // Use invoke with callbacks for streaming
575
+ const response = await modelWithTools.invoke(allMessages, {
576
+ callbacks: [
577
+ {
578
+ handleLLMStart: async () => {
579
+ await this.streamingCallback?.({
580
+ type: 'llm_start',
581
+ data: {
582
+ model: this.params.model.model,
583
+ temperature: this.params.model.temperature,
584
+ },
585
+ });
586
+ },
587
+ handleLLMEnd: async (output) => {
588
+ // Extract thinking tokens from different model providers
589
+ const thinking = extractAndStreamThinkingTokens(output);
590
+ if (thinking) {
591
+ await this.streamingCallback?.({
592
+ type: 'think',
593
+ data: {
594
+ content: thinking,
595
+ messageId,
596
+ },
597
+ });
598
+ }
599
+ await this.streamingCallback?.({
600
+ type: 'llm_complete',
601
+ data: {
602
+ messageId,
603
+ totalTokens: output.llmOutput?.usage_metadata?.total_tokens,
604
+ },
605
+ });
606
+ },
607
+ },
608
+ ],
609
+ });
610
+ return { messages: [response] };
611
+ }
612
+ else {
613
+ // Non-streaming fallback
614
+ const response = await modelWithTools.invoke(allMessages);
615
+ return { messages: [response] };
616
+ }
460
617
  };
461
618
  // Define conditional edge function
462
619
  const shouldContinue = ({ messages }) => {
@@ -467,16 +624,27 @@ export class AIAgentBubble extends ServiceBubble {
467
624
  }
468
625
  return '__end__';
469
626
  };
627
+ // Define conditional edge after tools to check if we should stop
628
+ const shouldContinueAfterTools = () => {
629
+ // Check if the afterToolCall hook requested stopping
630
+ if (this.shouldStopAfterTools) {
631
+ return '__end__';
632
+ }
633
+ // Otherwise continue back to agent
634
+ return 'agent';
635
+ };
470
636
  // Build the graph
471
637
  const graph = new StateGraph(MessagesAnnotation).addNode('agent', agentNode);
472
638
  if (tools.length > 0) {
473
- // Use the official ToolNode for tool execution
474
- const toolNode = new ToolNode(tools);
639
+ // Use custom tool node with hooks support
640
+ const toolNode = async (state) => {
641
+ return await this.executeToolsWithHooks(state, tools);
642
+ };
475
643
  graph
476
644
  .addNode('tools', toolNode)
477
645
  .addEdge('__start__', 'agent')
478
646
  .addConditionalEdges('agent', shouldContinue)
479
- .addEdge('tools', 'agent');
647
+ .addConditionalEdges('tools', shouldContinueAfterTools);
480
648
  }
481
649
  else {
482
650
  graph.addEdge('__start__', 'agent').addEdge('agent', '__end__');
@@ -545,15 +713,16 @@ export class AIAgentBubble extends ServiceBubble {
545
713
  console.log('[AIAgent] Graph execution completed');
546
714
  console.log('[AIAgent] Total messages:', result.messages.length);
547
715
  iterations = result.messages.length;
548
- // Extract tool calls from messages
716
+ // Extract tool calls from messages and track individual LLM calls
549
717
  // Store tool calls temporarily to match with their responses
550
718
  const toolCallMap = new Map();
551
719
  for (let i = 0; i < result.messages.length; i++) {
552
720
  const msg = result.messages[i];
553
- if (msg instanceof AIMessage && msg.tool_calls) {
721
+ if (msg instanceof AIMessage ||
722
+ (msg instanceof AIMessageChunk && msg.tool_calls)) {
554
723
  const typedToolCalls = msg.tool_calls;
555
724
  // Log and track tool calls
556
- for (const toolCall of typedToolCalls) {
725
+ for (const toolCall of typedToolCalls || []) {
557
726
  toolCallMap.set(toolCall.id, {
558
727
  name: toolCall.name,
559
728
  args: toolCall.args,
@@ -588,7 +757,7 @@ export class AIAgentBubble extends ServiceBubble {
588
757
  }
589
758
  // Get the final AI message response
590
759
  console.log('[AIAgent] Filtering AI messages...');
591
- const aiMessages = result.messages.filter((msg) => msg instanceof AIMessage);
760
+ const aiMessages = result.messages.filter((msg) => isAIMessage(msg) || isAIMessageChunk(msg));
592
761
  console.log('[AIAgent] Found', aiMessages.length, 'AI messages');
593
762
  const finalMessage = aiMessages[aiMessages.length - 1];
594
763
  // Check for MAX_TOKENS finish reason
@@ -601,10 +770,17 @@ export class AIAgentBubble extends ServiceBubble {
601
770
  let totalOutputTokens = 0;
602
771
  let totalTokensSum = 0;
603
772
  for (const msg of result.messages) {
604
- if (msg instanceof AIMessage && msg.usage_metadata) {
605
- totalInputTokens += msg.usage_metadata.input_tokens || 0;
606
- totalOutputTokens += msg.usage_metadata.output_tokens || 0;
607
- totalTokensSum += msg.usage_metadata.total_tokens || 0;
773
+ if (msg instanceof AIMessage ||
774
+ (msg instanceof AIMessageChunk && msg.usage_metadata)) {
775
+ totalInputTokens +=
776
+ msg.usage_metadata?.input_tokens ||
777
+ 0;
778
+ totalOutputTokens +=
779
+ msg.usage_metadata?.output_tokens ||
780
+ 0;
781
+ totalTokensSum +=
782
+ msg.usage_metadata?.total_tokens ||
783
+ 0;
608
784
  }
609
785
  }
610
786
  if (totalTokensSum > 0 && this.context && this.context.logger) {
@@ -615,12 +791,13 @@ export class AIAgentBubble extends ServiceBubble {
615
791
  modelName: this.params.model.model,
616
792
  }, `LLM completion: ${totalInputTokens} input + ${totalOutputTokens} output = ${totalTokensSum} total tokens`, {
617
793
  bubbleName: 'ai-agent',
794
+ variableId: this.context?.variableId,
618
795
  operationType: 'bubble_execution',
619
796
  });
620
797
  }
621
798
  const response = finalMessage?.content || 'No response generated';
622
799
  // Use shared formatting method
623
- const formattedResult = await this.formatFinalResponse(response, this.params.model, jsonMode);
800
+ const formattedResult = await formatFinalResponse(response, this.params.model.model, jsonMode);
624
801
  // If there's an error from formatting (e.g., invalid JSON), return early
625
802
  if (formattedResult.error) {
626
803
  return {
@@ -778,6 +955,7 @@ export class AIAgentBubble extends ServiceBubble {
778
955
  };
779
956
  this.context.logger.logTokenUsage(tokenUsage, `LLM completion: ${tokenUsage.inputTokens} input + ${tokenUsage.outputTokens} output = ${tokenUsage.totalTokens} total tokens`, {
780
957
  bubbleName: 'ai-agent',
958
+ variableId: this.context?.variableId,
781
959
  operationType: 'bubble_execution',
782
960
  });
783
961
  }
@@ -824,6 +1002,8 @@ export class AIAgentBubble extends ServiceBubble {
824
1002
  type: 'tool_complete',
825
1003
  data: {
826
1004
  callId,
1005
+ input: callData.args,
1006
+ tool: callData.name,
827
1007
  output: event.data.output,
828
1008
  duration,
829
1009
  },
@@ -856,7 +1036,7 @@ export class AIAgentBubble extends ServiceBubble {
856
1036
  // Process final result
857
1037
  const accumulatedResponse = accumulatedContent || 'No response generated';
858
1038
  // Use shared formatting method
859
- const formattedResult = await this.formatFinalResponse(accumulatedResponse, this.params.model, jsonMode);
1039
+ const formattedResult = await formatFinalResponse(accumulatedResponse, this.params.model.model, jsonMode);
860
1040
  // If there's an error from formatting (e.g., invalid JSON), return early with consistent behavior
861
1041
  if (formattedResult.error) {
862
1042
  return {