@superatomai/sdk-node 0.0.11 → 0.0.13

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.
package/dist/index.mjs CHANGED
@@ -445,6 +445,13 @@ var ComponentListResponseMessageSchema = z3.object({
445
445
  type: z3.literal("COMPONENT_LIST_RES"),
446
446
  payload: ComponentListResponsePayloadSchema
447
447
  });
448
+ var ToolSchema = z3.object({
449
+ id: z3.string(),
450
+ name: z3.string(),
451
+ description: z3.string(),
452
+ params: z3.record(z3.string()),
453
+ fn: z3.function().args(z3.any()).returns(z3.any())
454
+ });
448
455
  var UsersRequestPayloadSchema = z3.object({
449
456
  operation: z3.enum(["create", "update", "delete", "getAll", "getOne"]),
450
457
  data: z3.object({
@@ -515,6 +522,26 @@ var ReportsRequestMessageSchema = z3.object({
515
522
  type: z3.literal("REPORTS"),
516
523
  payload: ReportsRequestPayloadSchema
517
524
  });
525
+ var BookmarkDataSchema = z3.object({
526
+ id: z3.number().optional(),
527
+ uiblock: z3.any(),
528
+ // JSON object
529
+ created_at: z3.string().optional(),
530
+ updated_at: z3.string().optional()
531
+ });
532
+ var BookmarksRequestPayloadSchema = z3.object({
533
+ operation: z3.enum(["create", "update", "delete", "getAll", "getOne"]),
534
+ data: z3.object({
535
+ id: z3.number().optional(),
536
+ uiblock: z3.any().optional()
537
+ }).optional()
538
+ });
539
+ var BookmarksRequestMessageSchema = z3.object({
540
+ id: z3.string(),
541
+ from: MessageParticipantSchema,
542
+ type: z3.literal("BOOKMARKS"),
543
+ payload: BookmarksRequestPayloadSchema
544
+ });
518
545
 
519
546
  // src/utils/logger.ts
520
547
  import fs from "fs";
@@ -2570,6 +2597,71 @@ Format your response as a JSON object with this structure:
2570
2597
  }
2571
2598
 
2572
2599
  Return ONLY valid JSON.`
2600
+ },
2601
+ "execute-tools": {
2602
+ system: `You are an expert AI assistant that executes external tools to fetch data from external services.
2603
+
2604
+ You have access to external tools that can retrieve information like emails, calendar events, and other external data. When the user requests this information, you should call the appropriate tools.
2605
+
2606
+ ## Available External Tools
2607
+ {{AVAILABLE_TOOLS}}
2608
+
2609
+ ## Your Task
2610
+
2611
+ Analyze the user's request and:
2612
+
2613
+ 1. **Determine if external tools are needed**
2614
+ - Examples that NEED external tools:
2615
+ - "Show me my emails" \u2192 needs email tool
2616
+ - "Get my last 5 Gmail messages" \u2192 needs Gmail tool
2617
+ - "Check my Outlook inbox" \u2192 needs Outlook tool
2618
+
2619
+ - Examples that DON'T need external tools:
2620
+ - "What is the total sales?" \u2192 database query (handled elsewhere)
2621
+ - "Show me revenue trends" \u2192 internal data analysis
2622
+ - "Hello" \u2192 general conversation
2623
+
2624
+ 2. **Call the appropriate tools**
2625
+ - Use the tool calling mechanism to execute external tools
2626
+ - Extract parameters from the user's request
2627
+ - Use sensible defaults when parameters aren't specified:
2628
+ - For email limit: default to 10
2629
+ - For email address: use "me" for the authenticated user if not specified
2630
+
2631
+ 3. **Handle errors and retry**
2632
+ - If a tool call fails, analyze the error message
2633
+ - Retry with corrected parameters if possible
2634
+ - You have up to 3 attempts per tool
2635
+
2636
+ ## Important Guidelines
2637
+
2638
+ - **Only call external tools when necessary** - Don't call tools for database queries or general conversation
2639
+ - **Choose the right tool** - For email requests, select Gmail vs Outlook based on:
2640
+ - Explicit mention (e.g., "Gmail", "Outlook")
2641
+ - Email domain (e.g., @gmail.com \u2192 Gmail, @company.com \u2192 Outlook)
2642
+ - **Extract parameters carefully** - Use the user's exact values when provided
2643
+ - **If no tools are needed** - Simply respond that no external tools are required for this request
2644
+
2645
+ ## Examples
2646
+
2647
+ **Example 1 - Gmail Request:**
2648
+ User: "Show me my last 5 Gmail messages"
2649
+ Action: Call get-gmail-mails tool with parameters: { email: "me", limit: 5 }
2650
+
2651
+ **Example 2 - Outlook Request:**
2652
+ User: "Get emails from john.doe@company.com"
2653
+ Action: Call get-outlook-mails tool with parameters: { email: "john.doe@company.com", limit: 10 }
2654
+
2655
+ **Example 3 - No Tools Needed:**
2656
+ User: "What is the total sales?"
2657
+ Response: This is a database query, not an external tool request. No external tools are needed.
2658
+
2659
+ **Example 4 - Error Retry:**
2660
+ Tool call fails with: "Invalid email parameter"
2661
+ Action: Analyze error, correct the parameter, and retry the tool call
2662
+ `,
2663
+ user: `{{USER_PROMPT}}
2664
+ `
2573
2665
  }
2574
2666
  };
2575
2667
 
@@ -3616,13 +3708,14 @@ var BaseLLM = class {
3616
3708
  * Takes a text response with component suggestions (c1:type format) and matches with available components
3617
3709
  * Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
3618
3710
  * All components are placed in a default MultiComponentContainer layout
3619
- * @param textResponse - The text response containing component suggestions
3711
+ * @param analysisContent - The text response containing component suggestions
3620
3712
  * @param components - List of available components
3621
3713
  * @param apiKey - Optional API key
3622
3714
  * @param logCollector - Optional log collector
3715
+ * @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
3623
3716
  * @returns Object containing matched components, layout title/description, and follow-up actions
3624
3717
  */
3625
- async matchComponentsFromTextResponse(textResponse, components, apiKey, logCollector) {
3718
+ async matchComponentsFromAnalysis(analysisContent, components, apiKey, logCollector, componentStreamCallback) {
3626
3719
  try {
3627
3720
  logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
3628
3721
  let availableComponentsText = "No components available";
@@ -3639,14 +3732,94 @@ var BaseLLM = class {
3639
3732
  }).join("\n\n");
3640
3733
  }
3641
3734
  const schemaDoc = schema.generateSchemaDocumentation();
3735
+ logger.file("\n=============================\nText analysis response:", analysisContent);
3642
3736
  const prompts = await promptLoader.loadPrompts("match-text-components", {
3643
- TEXT_RESPONSE: textResponse,
3737
+ ANALYSIS_CONTENT: analysisContent,
3644
3738
  AVAILABLE_COMPONENTS: availableComponentsText,
3645
3739
  SCHEMA_DOC: schemaDoc
3646
3740
  });
3647
3741
  logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
3648
3742
  logger.file("\n=============================\nmatch text components system prompt:", prompts.system);
3649
3743
  logCollector?.info("Matching components from text response...");
3744
+ let fullResponseText = "";
3745
+ let answerComponentExtracted = false;
3746
+ const answerCallback = componentStreamCallback;
3747
+ const partialCallback = answerCallback ? (chunk) => {
3748
+ fullResponseText += chunk;
3749
+ if (!answerComponentExtracted && answerCallback) {
3750
+ const hasAnswerComponentMatch = fullResponseText.match(/"hasAnswerComponent"\s*:\s*(true|false)/);
3751
+ if (!hasAnswerComponentMatch || hasAnswerComponentMatch[1] !== "true") {
3752
+ return;
3753
+ }
3754
+ const answerComponentStartMatch = fullResponseText.match(/"answerComponent"\s*:\s*\{/);
3755
+ if (!answerComponentStartMatch) {
3756
+ return;
3757
+ }
3758
+ const startPos = answerComponentStartMatch.index + answerComponentStartMatch[0].length - 1;
3759
+ let braceDepth = 0;
3760
+ let inString = false;
3761
+ let escapeNext = false;
3762
+ let endPos = -1;
3763
+ for (let i = startPos; i < fullResponseText.length; i++) {
3764
+ const char = fullResponseText[i];
3765
+ if (escapeNext) {
3766
+ escapeNext = false;
3767
+ continue;
3768
+ }
3769
+ if (char === "\\") {
3770
+ escapeNext = true;
3771
+ continue;
3772
+ }
3773
+ if (char === '"') {
3774
+ inString = !inString;
3775
+ continue;
3776
+ }
3777
+ if (!inString) {
3778
+ if (char === "{") {
3779
+ braceDepth++;
3780
+ } else if (char === "}") {
3781
+ braceDepth--;
3782
+ if (braceDepth === 0) {
3783
+ endPos = i + 1;
3784
+ break;
3785
+ }
3786
+ }
3787
+ }
3788
+ }
3789
+ if (endPos > startPos) {
3790
+ const answerComponentString = fullResponseText.substring(startPos, endPos);
3791
+ try {
3792
+ const answerComponentData = JSON.parse(answerComponentString);
3793
+ if (answerComponentData && answerComponentData.componentId) {
3794
+ const originalComponent = components.find((c) => c.id === answerComponentData.componentId);
3795
+ if (originalComponent) {
3796
+ const answerComponent = {
3797
+ ...originalComponent,
3798
+ props: {
3799
+ ...originalComponent.props,
3800
+ ...answerComponentData.props
3801
+ }
3802
+ };
3803
+ const streamTime = (/* @__PURE__ */ new Date()).toISOString();
3804
+ logger.info(`[${this.getProviderName()}] \u2713 [${streamTime}] Answer component detected in stream: ${answerComponent.name} (${answerComponent.type}) - STREAMING TO FRONTEND NOW`);
3805
+ logCollector?.info(`\u2713 Answer component: ${answerComponent.name} (${answerComponent.type}) - streaming to frontend at ${streamTime}`);
3806
+ if (answerComponentData.props?.query) {
3807
+ logCollector?.logQuery(
3808
+ "Answer component query",
3809
+ answerComponentData.props.query,
3810
+ { componentName: answerComponent.name, componentType: answerComponent.type, reasoning: answerComponentData.reasoning }
3811
+ );
3812
+ }
3813
+ answerCallback(answerComponent);
3814
+ answerComponentExtracted = true;
3815
+ }
3816
+ }
3817
+ } catch (e) {
3818
+ logger.debug(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
3819
+ }
3820
+ }
3821
+ }
3822
+ } : void 0;
3650
3823
  const result = await LLM.stream(
3651
3824
  {
3652
3825
  sys: prompts.system,
@@ -3656,23 +3829,54 @@ var BaseLLM = class {
3656
3829
  model: this.model,
3657
3830
  maxTokens: 3e3,
3658
3831
  temperature: 0.2,
3659
- apiKey: this.getApiKey(apiKey)
3832
+ apiKey: this.getApiKey(apiKey),
3833
+ partial: partialCallback
3660
3834
  },
3661
3835
  true
3662
3836
  // Parse as JSON
3663
3837
  );
3664
3838
  logger.debug(`[${this.getProviderName()}] Component matching response parsed successfully`);
3839
+ const componentSuggestionPattern = /c\d+:(\w+)\s*:\s*(.+)/g;
3840
+ const suggestedComponents = [];
3841
+ let match;
3842
+ while ((match = componentSuggestionPattern.exec(analysisContent)) !== null) {
3843
+ suggestedComponents.push({
3844
+ type: match[1],
3845
+ reasoning: match[2].trim()
3846
+ });
3847
+ }
3665
3848
  const matchedComponents = result.matchedComponents || [];
3666
3849
  const layoutTitle = result.layoutTitle || "Dashboard";
3667
3850
  const layoutDescription = result.layoutDescription || "Multi-component dashboard";
3851
+ logger.info(`[${this.getProviderName()}] \u{1F4CA} Component Suggestions from Text Analysis: ${suggestedComponents.length}`);
3852
+ suggestedComponents.forEach((comp, idx) => {
3853
+ logger.info(`[${this.getProviderName()}] c${idx + 1}: ${comp.type} - ${comp.reasoning}`);
3854
+ });
3855
+ logger.info(`[${this.getProviderName()}] \u{1F4E6} Matched Components from LLM: ${matchedComponents.length}`);
3856
+ matchedComponents.forEach((comp, idx) => {
3857
+ logger.info(`[${this.getProviderName()}] ${idx + 1}. ${comp.componentType} (${comp.componentName}) - ${comp.originalSuggestion || "N/A"}`);
3858
+ });
3859
+ if (suggestedComponents.length !== matchedComponents.length) {
3860
+ logger.warn(`[${this.getProviderName()}] \u26A0\uFE0F MISMATCH: Text suggested ${suggestedComponents.length} components, but LLM matched ${matchedComponents.length}`);
3861
+ }
3862
+ logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
3668
3863
  const rawActions = result.actions || [];
3669
3864
  const actions = convertQuestionsToActions(rawActions);
3670
3865
  logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
3671
3866
  logger.info(`[${this.getProviderName()}] Layout title: "${layoutTitle}"`);
3672
3867
  logger.info(`[${this.getProviderName()}] Layout description: "${layoutDescription}"`);
3673
3868
  logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
3869
+ if (suggestedComponents.length > 0) {
3870
+ logCollector?.info(`\u{1F4DD} Text Analysis suggested ${suggestedComponents.length} dashboard components:`);
3871
+ suggestedComponents.forEach((comp, idx) => {
3872
+ logCollector?.info(` c${idx + 1}: ${comp.type} - ${comp.reasoning}`);
3873
+ });
3874
+ }
3674
3875
  if (matchedComponents.length > 0) {
3675
- logCollector?.info(`Matched ${matchedComponents.length} components for visualization`);
3876
+ logCollector?.info(`\u{1F4E6} Matched ${matchedComponents.length} components for dashboard`);
3877
+ if (suggestedComponents.length !== matchedComponents.length) {
3878
+ logCollector?.warn(`\u26A0\uFE0F Component count mismatch: Suggested ${suggestedComponents.length}, but matched ${matchedComponents.length}`);
3879
+ }
3676
3880
  logCollector?.info(`Dashboard: "${layoutTitle}"`);
3677
3881
  matchedComponents.forEach((comp, idx) => {
3678
3882
  logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
@@ -3723,6 +3927,152 @@ var BaseLLM = class {
3723
3927
  };
3724
3928
  }
3725
3929
  }
3930
+ /**
3931
+ * Execute external tools based on user request using agentic LLM tool calling
3932
+ * The LLM can directly call tools and retry on errors
3933
+ * @param userPrompt - The user's question/request
3934
+ * @param availableTools - Array of available external tools
3935
+ * @param apiKey - Optional API key for LLM
3936
+ * @param logCollector - Optional log collector
3937
+ * @returns Object containing tool execution results and summary
3938
+ */
3939
+ async executeExternalTools(userPrompt, availableTools, apiKey, logCollector) {
3940
+ const MAX_TOOL_ATTEMPTS = 3;
3941
+ const toolResults = [];
3942
+ try {
3943
+ logger.debug(`[${this.getProviderName()}] Starting agentic external tool execution`);
3944
+ logger.debug(`[${this.getProviderName()}] Available tools: ${availableTools.map((t) => t.name).join(", ")}`);
3945
+ const llmTools = availableTools.map((tool) => {
3946
+ const properties = {};
3947
+ const required = [];
3948
+ Object.entries(tool.params || {}).forEach(([key, type]) => {
3949
+ properties[key] = {
3950
+ type: String(type).toLowerCase(),
3951
+ description: `${key} parameter`
3952
+ };
3953
+ required.push(key);
3954
+ });
3955
+ return {
3956
+ name: tool.id,
3957
+ description: tool.description,
3958
+ input_schema: {
3959
+ type: "object",
3960
+ properties,
3961
+ required
3962
+ }
3963
+ };
3964
+ });
3965
+ const toolAttempts = /* @__PURE__ */ new Map();
3966
+ const toolHandler = async (toolName, toolInput) => {
3967
+ const tool = availableTools.find((t) => t.id === toolName);
3968
+ if (!tool) {
3969
+ const errorMsg = `Tool ${toolName} not found in available tools`;
3970
+ logger.error(`[${this.getProviderName()}] ${errorMsg}`);
3971
+ logCollector?.error(errorMsg);
3972
+ throw new Error(errorMsg);
3973
+ }
3974
+ const attempts = (toolAttempts.get(toolName) || 0) + 1;
3975
+ toolAttempts.set(toolName, attempts);
3976
+ logger.info(`[${this.getProviderName()}] Executing tool: ${tool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
3977
+ logCollector?.info(`Executing ${tool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
3978
+ if (attempts > MAX_TOOL_ATTEMPTS) {
3979
+ const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${tool.name}`;
3980
+ logger.error(`[${this.getProviderName()}] ${errorMsg}`);
3981
+ logCollector?.error(errorMsg);
3982
+ toolResults.push({
3983
+ toolName: tool.name,
3984
+ toolId: tool.id,
3985
+ result: null,
3986
+ error: errorMsg
3987
+ });
3988
+ throw new Error(errorMsg);
3989
+ }
3990
+ try {
3991
+ logger.debug(`[${this.getProviderName()}] Tool ${tool.name} parameters:`, toolInput);
3992
+ const result2 = await tool.fn(toolInput);
3993
+ logger.info(`[${this.getProviderName()}] Tool ${tool.name} executed successfully`);
3994
+ logCollector?.info(`\u2713 ${tool.name} completed successfully`);
3995
+ toolResults.push({
3996
+ toolName: tool.name,
3997
+ toolId: tool.id,
3998
+ result: result2
3999
+ });
4000
+ return JSON.stringify(result2, null, 2);
4001
+ } catch (error) {
4002
+ const errorMsg = error instanceof Error ? error.message : String(error);
4003
+ logger.error(`[${this.getProviderName()}] Tool ${tool.name} failed (attempt ${attempts}): ${errorMsg}`);
4004
+ logCollector?.error(`\u2717 ${tool.name} failed: ${errorMsg}`);
4005
+ if (attempts >= MAX_TOOL_ATTEMPTS) {
4006
+ toolResults.push({
4007
+ toolName: tool.name,
4008
+ toolId: tool.id,
4009
+ result: null,
4010
+ error: errorMsg
4011
+ });
4012
+ }
4013
+ throw new Error(`Tool execution failed: ${errorMsg}`);
4014
+ }
4015
+ };
4016
+ const prompts = await promptLoader.loadPrompts("execute-tools", {
4017
+ USER_PROMPT: userPrompt,
4018
+ AVAILABLE_TOOLS: availableTools.map((tool, idx) => {
4019
+ const paramsText = Object.entries(tool.params || {}).map(([key, type]) => ` - ${key}: ${type}`).join("\n");
4020
+ return `${idx + 1}. ID: ${tool.id}
4021
+ Name: ${tool.name}
4022
+ Description: ${tool.description}
4023
+ Parameters:
4024
+ ${paramsText}`;
4025
+ }).join("\n\n")
4026
+ });
4027
+ logger.debug(`[${this.getProviderName()}] Using agentic tool calling for external tools`);
4028
+ logCollector?.info("Analyzing request and executing external tools...");
4029
+ const result = await LLM.streamWithTools(
4030
+ {
4031
+ sys: prompts.system,
4032
+ user: prompts.user
4033
+ },
4034
+ llmTools,
4035
+ toolHandler,
4036
+ {
4037
+ model: this.model,
4038
+ maxTokens: 2e3,
4039
+ temperature: 0.2,
4040
+ apiKey: this.getApiKey(apiKey)
4041
+ },
4042
+ MAX_TOOL_ATTEMPTS + 2
4043
+ // max iterations: allows for retries + final response
4044
+ );
4045
+ logger.info(`[${this.getProviderName()}] External tool execution completed`);
4046
+ const successfulTools = toolResults.filter((r) => !r.error);
4047
+ const failedTools = toolResults.filter((r) => r.error);
4048
+ let summary = "";
4049
+ if (successfulTools.length > 0) {
4050
+ summary += `Successfully executed ${successfulTools.length} tool(s): ${successfulTools.map((t) => t.toolName).join(", ")}.
4051
+ `;
4052
+ }
4053
+ if (failedTools.length > 0) {
4054
+ summary += `Failed to execute ${failedTools.length} tool(s): ${failedTools.map((t) => t.toolName).join(", ")}.`;
4055
+ }
4056
+ if (toolResults.length === 0) {
4057
+ summary = "No external tools were needed for this request.";
4058
+ }
4059
+ logger.info(`[${this.getProviderName()}] Tool execution summary: ${summary}`);
4060
+ return {
4061
+ toolResults,
4062
+ summary,
4063
+ hasResults: successfulTools.length > 0
4064
+ };
4065
+ } catch (error) {
4066
+ const errorMsg = error instanceof Error ? error.message : String(error);
4067
+ logger.error(`[${this.getProviderName()}] Error in external tool execution: ${errorMsg}`);
4068
+ logCollector?.error(`Error executing external tools: ${errorMsg}`);
4069
+ return {
4070
+ toolResults,
4071
+ summary: `Error executing external tools: ${errorMsg}`,
4072
+ hasResults: false
4073
+ };
4074
+ }
4075
+ }
3726
4076
  /**
3727
4077
  * Generate text-based response for user question
3728
4078
  * This provides conversational text responses instead of component generation
@@ -3731,12 +4081,40 @@ var BaseLLM = class {
3731
4081
  * @param streamCallback - Optional callback function to receive text chunks as they stream
3732
4082
  * @param collections - Collection registry for executing database queries via database.execute
3733
4083
  * @param components - Optional list of available components for matching suggestions
4084
+ * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called
3734
4085
  */
3735
- async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components) {
4086
+ async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components, externalTools) {
3736
4087
  const errors = [];
3737
4088
  logger.debug(`[${this.getProviderName()}] Starting text response generation`);
3738
4089
  logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
3739
4090
  try {
4091
+ let externalToolContext = "No external tools were used for this request.";
4092
+ if (externalTools && externalTools.length > 0) {
4093
+ logger.info(`[${this.getProviderName()}] Executing external tools...`);
4094
+ const toolExecution = await this.executeExternalTools(
4095
+ userPrompt,
4096
+ externalTools,
4097
+ apiKey,
4098
+ logCollector
4099
+ );
4100
+ if (toolExecution.hasResults) {
4101
+ const toolResultsText = toolExecution.toolResults.map((tr) => {
4102
+ if (tr.error) {
4103
+ return `**${tr.toolName}** (Failed): ${tr.error}`;
4104
+ }
4105
+ return `**${tr.toolName}** (Success):
4106
+ ${JSON.stringify(tr.result, null, 2)}`;
4107
+ }).join("\n\n");
4108
+ externalToolContext = `## External Tool Results
4109
+
4110
+ ${toolExecution.summary}
4111
+
4112
+ ${toolResultsText}`;
4113
+ logger.info(`[${this.getProviderName()}] External tools executed, results available`);
4114
+ } else {
4115
+ logger.info(`[${this.getProviderName()}] No external tools were needed`);
4116
+ }
4117
+ }
3740
4118
  const schemaDoc = schema.generateSchemaDocumentation();
3741
4119
  const knowledgeBaseContext = await knowledge_base_default.getKnowledgeBase({
3742
4120
  prompt: userPrompt,
@@ -3744,11 +4122,13 @@ var BaseLLM = class {
3744
4122
  topK: 1
3745
4123
  });
3746
4124
  logger.file("\n=============================\nknowledge base context:", knowledgeBaseContext);
4125
+ logger.file("\n=============================\nexternal tool context:", externalToolContext);
3747
4126
  const prompts = await promptLoader.loadPrompts("text-response", {
3748
4127
  USER_PROMPT: userPrompt,
3749
4128
  CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
3750
4129
  SCHEMA_DOC: schemaDoc,
3751
- KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available."
4130
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available.",
4131
+ EXTERNAL_TOOL_CONTEXT: externalToolContext
3752
4132
  });
3753
4133
  logger.file("\n=============================\nsystem prompt:", prompts.system);
3754
4134
  logger.file("\n=============================\nuser prompt:", prompts.user);
@@ -3971,11 +4351,17 @@ ${errorMsg}
3971
4351
  let actions = [];
3972
4352
  if (components && components.length > 0) {
3973
4353
  logger.info(`[${this.getProviderName()}] Matching components from text response...`);
3974
- const matchResult = await this.matchComponentsFromTextResponse(
4354
+ const componentStreamCallback = wrappedStreamCallback ? (component) => {
4355
+ const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
4356
+ wrappedStreamCallback(answerMarker);
4357
+ logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
4358
+ } : void 0;
4359
+ const matchResult = await this.matchComponentsFromAnalysis(
3975
4360
  textResponse,
3976
4361
  components,
3977
4362
  apiKey,
3978
- logCollector
4363
+ logCollector,
4364
+ componentStreamCallback
3979
4365
  );
3980
4366
  matchedComponents = matchResult.components;
3981
4367
  layoutTitle = matchResult.layoutTitle;
@@ -4219,8 +4605,9 @@ ${errorMsg}
4219
4605
  * @param responseMode - 'component' for component generation (default), 'text' for text responses
4220
4606
  * @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
4221
4607
  * @param collections - Collection registry for executing database queries (required for text mode)
4608
+ * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called (only for text mode)
4222
4609
  */
4223
- async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) {
4610
+ async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) {
4224
4611
  const startTime = Date.now();
4225
4612
  logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
4226
4613
  if (responseMode === "text") {
@@ -4233,7 +4620,8 @@ ${errorMsg}
4233
4620
  conversationHistory,
4234
4621
  streamCallback,
4235
4622
  collections,
4236
- components
4623
+ components,
4624
+ externalTools
4237
4625
  );
4238
4626
  if (!textResponse.success) {
4239
4627
  const elapsedTime3 = Date.now() - startTime;
@@ -4379,7 +4767,7 @@ function getLLMProviders() {
4379
4767
  return DEFAULT_PROVIDERS;
4380
4768
  }
4381
4769
  }
4382
- var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
4770
+ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
4383
4771
  logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
4384
4772
  logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
4385
4773
  const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -4391,11 +4779,11 @@ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conver
4391
4779
  return { success: false, errors: [emptyMsg] };
4392
4780
  }
4393
4781
  logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
4394
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4782
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4395
4783
  logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
4396
4784
  return matchResult;
4397
4785
  };
4398
- var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
4786
+ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
4399
4787
  logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
4400
4788
  logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
4401
4789
  const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -4408,14 +4796,14 @@ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversatio
4408
4796
  return { success: false, errors: [emptyMsg] };
4409
4797
  }
4410
4798
  logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
4411
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4799
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4412
4800
  logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
4413
4801
  return matchResult;
4414
4802
  };
4415
4803
  var getUserResponseFromCache = async (prompt) => {
4416
4804
  return false;
4417
4805
  };
4418
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
4806
+ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
4419
4807
  logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
4420
4808
  logger.debug(`[get_user_response] Response mode: ${responseMode}`);
4421
4809
  logger.debug("[get_user_response] Checking cache for existing response");
@@ -4448,9 +4836,9 @@ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey,
4448
4836
  logCollector?.info(attemptMsg);
4449
4837
  let result;
4450
4838
  if (provider === "anthropic") {
4451
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4839
+ result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4452
4840
  } else if (provider === "groq") {
4453
- result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4841
+ result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4454
4842
  } else {
4455
4843
  logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
4456
4844
  errors.push(`Unknown provider: ${provider}`);
@@ -4678,7 +5066,7 @@ var CONTEXT_CONFIG = {
4678
5066
  };
4679
5067
 
4680
5068
  // src/handlers/user-prompt-request.ts
4681
- var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) => {
5069
+ var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools) => {
4682
5070
  const errors = [];
4683
5071
  logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
4684
5072
  const parseResult = UserPromptRequestMessageSchema.safeParse(data);
@@ -4758,7 +5146,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4758
5146
  conversationHistory,
4759
5147
  responseMode,
4760
5148
  streamCallback,
4761
- collections
5149
+ collections,
5150
+ externalTools
4762
5151
  );
4763
5152
  logCollector.info("User prompt request completed");
4764
5153
  const uiBlockId = existingUiBlockId;
@@ -4819,8 +5208,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4819
5208
  wsId
4820
5209
  };
4821
5210
  };
4822
- async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) {
4823
- const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections);
5211
+ async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools) {
5212
+ const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools);
4824
5213
  sendDataResponse4(
4825
5214
  response.id || data.id,
4826
5215
  {
@@ -4847,7 +5236,6 @@ function sendDataResponse4(id, res, sendMessage, clientId) {
4847
5236
  ...res
4848
5237
  }
4849
5238
  };
4850
- logger.info("sending user prompt response", response);
4851
5239
  sendMessage(response);
4852
5240
  }
4853
5241
 
@@ -5894,6 +6282,183 @@ function sendResponse5(id, res, sendMessage, clientId) {
5894
6282
  sendMessage(response);
5895
6283
  }
5896
6284
 
6285
+ // src/handlers/bookmarks.ts
6286
+ async function handleBookmarksRequest(data, collections, sendMessage) {
6287
+ const executeCollection = async (collection, op, params) => {
6288
+ const handler = collections[collection]?.[op];
6289
+ if (!handler) {
6290
+ throw new Error(`Collection operation ${collection}.${op} not found`);
6291
+ }
6292
+ return await handler(params);
6293
+ };
6294
+ try {
6295
+ const request = BookmarksRequestMessageSchema.parse(data);
6296
+ const { id, payload, from } = request;
6297
+ const { operation, data: requestData } = payload;
6298
+ const bookmarkId = requestData?.id;
6299
+ const uiblock = requestData?.uiblock;
6300
+ switch (operation) {
6301
+ case "create":
6302
+ await handleCreate4(id, uiblock, executeCollection, sendMessage, from.id);
6303
+ break;
6304
+ case "update":
6305
+ await handleUpdate4(id, bookmarkId, uiblock, executeCollection, sendMessage, from.id);
6306
+ break;
6307
+ case "delete":
6308
+ await handleDelete4(id, bookmarkId, executeCollection, sendMessage, from.id);
6309
+ break;
6310
+ case "getAll":
6311
+ await handleGetAll4(id, executeCollection, sendMessage, from.id);
6312
+ break;
6313
+ case "getOne":
6314
+ await handleGetOne4(id, bookmarkId, executeCollection, sendMessage, from.id);
6315
+ break;
6316
+ default:
6317
+ sendResponse6(id, {
6318
+ success: false,
6319
+ error: `Unknown operation: ${operation}`
6320
+ }, sendMessage, from.id);
6321
+ }
6322
+ } catch (error) {
6323
+ logger.error("Failed to handle bookmarks request:", error);
6324
+ sendResponse6(null, {
6325
+ success: false,
6326
+ error: error instanceof Error ? error.message : "Unknown error occurred"
6327
+ }, sendMessage);
6328
+ }
6329
+ }
6330
+ async function handleCreate4(id, uiblock, executeCollection, sendMessage, clientId) {
6331
+ if (!uiblock) {
6332
+ sendResponse6(id, {
6333
+ success: false,
6334
+ error: "UIBlock data is required"
6335
+ }, sendMessage, clientId);
6336
+ return;
6337
+ }
6338
+ try {
6339
+ const result = await executeCollection("bookmarks", "create", { uiblock });
6340
+ sendResponse6(id, {
6341
+ success: true,
6342
+ data: result.data,
6343
+ message: "Bookmark created successfully"
6344
+ }, sendMessage, clientId);
6345
+ logger.info(`Bookmark created: ID ${result.data.id}`);
6346
+ } catch (error) {
6347
+ sendResponse6(id, {
6348
+ success: false,
6349
+ error: error instanceof Error ? error.message : "Failed to create bookmark"
6350
+ }, sendMessage, clientId);
6351
+ }
6352
+ }
6353
+ async function handleUpdate4(id, bookmarkId, uiblock, executeCollection, sendMessage, clientId) {
6354
+ if (!bookmarkId) {
6355
+ sendResponse6(id, {
6356
+ success: false,
6357
+ error: "Bookmark ID is required"
6358
+ }, sendMessage, clientId);
6359
+ return;
6360
+ }
6361
+ if (!uiblock) {
6362
+ sendResponse6(id, {
6363
+ success: false,
6364
+ error: "UIBlock data is required"
6365
+ }, sendMessage, clientId);
6366
+ return;
6367
+ }
6368
+ try {
6369
+ const result = await executeCollection("bookmarks", "update", { id: bookmarkId, uiblock });
6370
+ sendResponse6(id, {
6371
+ success: true,
6372
+ data: result.data,
6373
+ message: "Bookmark updated successfully"
6374
+ }, sendMessage, clientId);
6375
+ logger.info(`Bookmark updated: ID ${bookmarkId}`);
6376
+ } catch (error) {
6377
+ sendResponse6(id, {
6378
+ success: false,
6379
+ error: error instanceof Error ? error.message : "Failed to update bookmark"
6380
+ }, sendMessage, clientId);
6381
+ }
6382
+ }
6383
+ async function handleDelete4(id, bookmarkId, executeCollection, sendMessage, clientId) {
6384
+ if (!bookmarkId) {
6385
+ sendResponse6(id, {
6386
+ success: false,
6387
+ error: "Bookmark ID is required"
6388
+ }, sendMessage, clientId);
6389
+ return;
6390
+ }
6391
+ try {
6392
+ const result = await executeCollection("bookmarks", "delete", { id: bookmarkId });
6393
+ sendResponse6(id, {
6394
+ success: true,
6395
+ data: result.data,
6396
+ message: "Bookmark deleted successfully"
6397
+ }, sendMessage, clientId);
6398
+ logger.info(`Bookmark deleted: ID ${bookmarkId}`);
6399
+ } catch (error) {
6400
+ sendResponse6(id, {
6401
+ success: false,
6402
+ error: error instanceof Error ? error.message : "Failed to delete bookmark"
6403
+ }, sendMessage, clientId);
6404
+ }
6405
+ }
6406
+ async function handleGetAll4(id, executeCollection, sendMessage, clientId) {
6407
+ try {
6408
+ const result = await executeCollection("bookmarks", "getAll", {});
6409
+ sendResponse6(id, {
6410
+ success: true,
6411
+ data: result.data,
6412
+ count: result.count,
6413
+ message: `Retrieved ${result.count} bookmarks`
6414
+ }, sendMessage, clientId);
6415
+ logger.info(`Retrieved all bookmarks (count: ${result.count})`);
6416
+ } catch (error) {
6417
+ sendResponse6(id, {
6418
+ success: false,
6419
+ error: error instanceof Error ? error.message : "Failed to get bookmarks"
6420
+ }, sendMessage, clientId);
6421
+ }
6422
+ }
6423
+ async function handleGetOne4(id, bookmarkId, executeCollection, sendMessage, clientId) {
6424
+ if (!bookmarkId) {
6425
+ sendResponse6(id, {
6426
+ success: false,
6427
+ error: "Bookmark ID is required"
6428
+ }, sendMessage, clientId);
6429
+ return;
6430
+ }
6431
+ try {
6432
+ const result = await executeCollection("bookmarks", "getOne", { id: bookmarkId });
6433
+ sendResponse6(id, {
6434
+ success: true,
6435
+ data: result.data,
6436
+ message: `Retrieved bookmark ID ${bookmarkId}`
6437
+ }, sendMessage, clientId);
6438
+ logger.info(`Retrieved bookmark: ID ${bookmarkId}`);
6439
+ } catch (error) {
6440
+ sendResponse6(id, {
6441
+ success: false,
6442
+ error: error instanceof Error ? error.message : "Failed to get bookmark"
6443
+ }, sendMessage, clientId);
6444
+ }
6445
+ }
6446
+ function sendResponse6(id, res, sendMessage, clientId) {
6447
+ const response = {
6448
+ id: id || "unknown",
6449
+ type: "BOOKMARKS_RES",
6450
+ from: { type: "data-agent" },
6451
+ to: {
6452
+ type: "user",
6453
+ id: clientId
6454
+ },
6455
+ payload: {
6456
+ ...res
6457
+ }
6458
+ };
6459
+ sendMessage(response);
6460
+ }
6461
+
5897
6462
  // src/auth/user-manager.ts
5898
6463
  import fs5 from "fs";
5899
6464
  import path4 from "path";
@@ -6718,6 +7283,7 @@ var SuperatomSDK = class {
6718
7283
  this.maxReconnectAttempts = 5;
6719
7284
  this.collections = {};
6720
7285
  this.components = [];
7286
+ this.tools = [];
6721
7287
  if (config.logLevel) {
6722
7288
  logger.setLogLevel(config.logLevel);
6723
7289
  }
@@ -6873,7 +7439,7 @@ var SuperatomSDK = class {
6873
7439
  });
6874
7440
  break;
6875
7441
  case "USER_PROMPT_REQ":
6876
- handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections).catch((error) => {
7442
+ handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections, this.tools).catch((error) => {
6877
7443
  logger.error("Failed to handle user prompt request:", error);
6878
7444
  });
6879
7445
  break;
@@ -6907,6 +7473,11 @@ var SuperatomSDK = class {
6907
7473
  logger.error("Failed to handle reports request:", error);
6908
7474
  });
6909
7475
  break;
7476
+ case "BOOKMARKS":
7477
+ handleBookmarksRequest(parsed, this.collections, (msg) => this.send(msg)).catch((error) => {
7478
+ logger.error("Failed to handle bookmarks request:", error);
7479
+ });
7480
+ break;
6910
7481
  default:
6911
7482
  const handler = this.messageTypeHandlers.get(message.type);
6912
7483
  if (handler) {
@@ -7013,6 +7584,19 @@ var SuperatomSDK = class {
7013
7584
  storeComponents(components) {
7014
7585
  this.components = components;
7015
7586
  }
7587
+ /**
7588
+ * Set tools for the SDK instance
7589
+ */
7590
+ setTools(tools) {
7591
+ this.tools = tools;
7592
+ logger.info(`Tools stored in SDK: ${tools.length} tools`);
7593
+ }
7594
+ /**
7595
+ * Get the stored tools
7596
+ */
7597
+ getTools() {
7598
+ return this.tools;
7599
+ }
7016
7600
  };
7017
7601
  export {
7018
7602
  CONTEXT_CONFIG,