@superatomai/sdk-node 0.0.10 → 0.0.12

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";
@@ -972,6 +999,7 @@ var Thread = class {
972
999
  let assistantResponse = "";
973
1000
  const hasComponent = metadata && Object.keys(metadata).length > 0 && metadata.type;
974
1001
  const hasTextResponse = textResponse && textResponse.trim().length > 0;
1002
+ const responseParts = [];
975
1003
  if (hasComponent) {
976
1004
  const parts = [];
977
1005
  if (metadata.type) {
@@ -980,21 +1008,19 @@ var Thread = class {
980
1008
  if (metadata.name) {
981
1009
  parts.push(`Name: ${metadata.name}`);
982
1010
  }
983
- if (metadata.props?.title) {
984
- parts.push(`Title: "${metadata.props.title}"`);
985
- }
986
- if (metadata.props?.query) {
987
- const query = metadata.props.query;
988
- const truncatedQuery = query.length > 200 ? query.substring(0, 200) + "..." : query;
989
- parts.push(`Query: ${truncatedQuery}`);
1011
+ if (metadata.description) {
1012
+ parts.push(`Description: ${metadata.description}`);
990
1013
  }
991
- if (metadata.props?.config?.components && Array.isArray(metadata.props.config.components)) {
992
- const componentTypes = metadata.props.config.components.map((c) => c.type).join(", ");
993
- parts.push(`Multi-component with: ${componentTypes}`);
1014
+ if (metadata.props) {
1015
+ parts.push(`Props: ${JSON.stringify(metadata.props)}`);
994
1016
  }
995
- assistantResponse = parts.join(", ");
996
- } else if (hasTextResponse) {
997
- assistantResponse = textResponse;
1017
+ responseParts.push(parts.join("\n"));
1018
+ }
1019
+ if (hasTextResponse) {
1020
+ responseParts.push(textResponse);
1021
+ }
1022
+ if (responseParts.length > 0) {
1023
+ assistantResponse = responseParts.join("\n");
998
1024
  } else {
999
1025
  assistantResponse = "No response generated";
1000
1026
  }
@@ -2346,18 +2372,16 @@ Your job is to:
2346
2372
  1. **Parse the component suggestions** from the text response (format: 1:component_type : reasoning)
2347
2373
  2. **Match each suggestion with an actual component** from the available list
2348
2374
  3. **Generate proper props** for each matched component to **visualize the analysis results** that were already fetched
2349
- 4. **SELECT the best dashboard layout component** that can accommodate all the matched components
2375
+ 4. **Generate title and description** for the dashboard container
2350
2376
  5. **Generate intelligent follow-up questions (actions)** that the user might naturally ask next based on the data analysis
2351
2377
 
2352
2378
  **CRITICAL GOAL**: Create dashboard components that display the **same data that was already analyzed** - NOT new data. The queries already ran and got results. You're just creating different visualizations of those results.
2353
2379
 
2354
- **APPROACH**: First match all the components suggested in the text response, THEN find the layout that best fits those components.
2355
-
2356
2380
  ## Available Components
2357
2381
 
2358
2382
  {{AVAILABLE_COMPONENTS}}
2359
2383
 
2360
- ## Component Matching Rules (STEP 1)
2384
+ ## Component Matching Rules
2361
2385
  For each component suggestion (c1, c2, c3, etc.) from the text response:
2362
2386
 
2363
2387
  1. **Match by type**: Find components whose \`type\` matches the suggested component type
@@ -2366,23 +2390,10 @@ For each component suggestion (c1, c2, c3, etc.) from the text response:
2366
2390
  - Best fit for the data being visualized
2367
2391
  3. **Fallback**: If no exact type match, find the closest alternative
2368
2392
 
2369
- ## Layout Selection Logic (STEP 2 - After Matching Components)
2370
-
2371
- **After you have matched all components**, select the best dashboard layout:
2372
-
2373
- 1. **Find layout components** by looking for components with \`type: "DashboardLayout"\` in the available components list
2374
- 2. **Read each layout's description** to understand:
2375
- - What structure it provides
2376
- - When it's best used (e.g., comprehensive analysis vs focused analysis)
2377
- - The number and types of components it can accommodate
2378
- 3. **Select the best layout** based on:
2379
- - Which layout can best display ALL the matched components
2380
- - The layout's capacity (how many components it supports)
2381
- - The types of matched components (KPI, charts, tables, etc.)
2382
- - The user question and data complexity
2383
- 4. **If no specific layout fits**, fall back to "MultiComponentContainer" as the default layout
2384
-
2385
- **IMPORTANT:** The layout should be chosen to FIT the matched components, not the other way around. Don't force components to fit a layout - find a layout that accommodates your components.
2393
+ ## Dashboard Container
2394
+ All matched components will be placed in the default **MultiComponentContainer** layout. Your job is to:
2395
+ 1. **Generate a clear title** for the dashboard that summarizes what it shows
2396
+ 2. **Generate a brief description** explaining the dashboard's purpose and scope
2386
2397
 
2387
2398
  ## Props Generation Rules
2388
2399
 
@@ -2496,8 +2507,8 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
2496
2507
 
2497
2508
  \`\`\`json
2498
2509
  {
2499
- "selectedLayoutId": "id_of_the_selected_layout_component",
2500
- "layoutReasoning": "Why this layout was selected based on its description and the analysis needs",
2510
+ "layoutTitle": "Clear, concise title for the overall dashboard/layout (5-10 words)",
2511
+ "layoutDescription": "Brief description of what the dashboard shows and its purpose (1-2 sentences)",
2501
2512
  "matchedComponents": [
2502
2513
  {
2503
2514
  "componentId": "id_from_available_list",
@@ -2527,17 +2538,14 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
2527
2538
  \`\`\`
2528
2539
 
2529
2540
  **CRITICAL:**
2530
- - \`matchedComponents\` MUST include ALL components suggested in the text response (match them first!)
2531
- - \`selectedLayoutId\` MUST be the ID of the selected layout component (must have type "DashboardLayout")
2532
- - \`layoutReasoning\` MUST explain:
2533
- - Why you chose this specific layout component
2534
- - How many components you matched (e.g., "Matched 3 components: 1 KPI, 1 chart, 1 table")
2535
- - Why this layout is the best fit for displaying these specific matched components
2536
- - What makes this layout appropriate for the component types and count
2537
- - The layout selection happens AFTER component matching - don't force components to fit a pre-selected layout
2541
+ - \`matchedComponents\` MUST include ALL components suggested in the text response
2542
+ - \`layoutTitle\` MUST be a clear, concise title (5-10 words) that summarizes what the entire dashboard shows
2543
+ - Examples: "Sales Performance Overview", "Customer Metrics Analysis", "Product Category Breakdown"
2544
+ - \`layoutDescription\` MUST be a brief description (1-2 sentences) explaining the purpose and scope of the dashboard
2545
+ - Should describe what insights the dashboard provides and what data it shows
2538
2546
  - \`actions\` MUST be an array of 4-5 intelligent follow-up questions based on the analysis
2539
2547
  - Return ONLY valid JSON (no markdown code blocks, no text before/after)
2540
- - Generate complete props for each component
2548
+ - Generate complete props for each component including query, title, description, and config
2541
2549
 
2542
2550
 
2543
2551
  `,
@@ -2589,6 +2597,71 @@ Format your response as a JSON object with this structure:
2589
2597
  }
2590
2598
 
2591
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
+ `
2592
2665
  }
2593
2666
  };
2594
2667
 
@@ -3633,12 +3706,14 @@ var BaseLLM = class {
3633
3706
  /**
3634
3707
  * Match components from text response suggestions and generate follow-up questions
3635
3708
  * Takes a text response with component suggestions (c1:type format) and matches with available components
3636
- * Also generates intelligent follow-up questions (actions) based on the analysis
3709
+ * Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
3710
+ * All components are placed in a default MultiComponentContainer layout
3637
3711
  * @param textResponse - The text response containing component suggestions
3638
3712
  * @param components - List of available components
3639
3713
  * @param apiKey - Optional API key
3640
3714
  * @param logCollector - Optional log collector
3641
- * @returns Object containing matched components, selected layout, reasoning, and follow-up actions
3715
+ * @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
3716
+ * @returns Object containing matched components, layout title/description, and follow-up actions
3642
3717
  */
3643
3718
  async matchComponentsFromTextResponse(textResponse, components, apiKey, logCollector) {
3644
3719
  try {
@@ -3681,24 +3756,17 @@ var BaseLLM = class {
3681
3756
  );
3682
3757
  logger.debug(`[${this.getProviderName()}] Component matching response parsed successfully`);
3683
3758
  const matchedComponents = result.matchedComponents || [];
3684
- const selectedLayoutId = result.selectedLayoutId || "multi-component-container";
3685
- const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
3759
+ const layoutTitle = result.layoutTitle || "Dashboard";
3760
+ const layoutDescription = result.layoutDescription || "Multi-component dashboard";
3686
3761
  const rawActions = result.actions || [];
3687
3762
  const actions = convertQuestionsToActions(rawActions);
3688
- let selectedLayoutComponent = null;
3689
- if (selectedLayoutId) {
3690
- selectedLayoutComponent = components.find((c) => c.id === selectedLayoutId) || null;
3691
- if (!selectedLayoutComponent) {
3692
- logger.warn(`[${this.getProviderName()}] Layout component ${selectedLayoutId} not found in available components`);
3693
- }
3694
- }
3695
3763
  logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
3696
- logger.info(`[${this.getProviderName()}] Selected layout: (ID: ${selectedLayoutId})`);
3697
- logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
3764
+ logger.info(`[${this.getProviderName()}] Layout title: "${layoutTitle}"`);
3765
+ logger.info(`[${this.getProviderName()}] Layout description: "${layoutDescription}"`);
3698
3766
  logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
3699
3767
  if (matchedComponents.length > 0) {
3700
- logCollector?.info(`Matched ${matchedComponents.length} components for visualization `);
3701
- logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
3768
+ logCollector?.info(`Matched ${matchedComponents.length} components for visualization`);
3769
+ logCollector?.info(`Dashboard: "${layoutTitle}"`);
3702
3770
  matchedComponents.forEach((comp, idx) => {
3703
3771
  logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
3704
3772
  if (comp.props?.query) {
@@ -3732,9 +3800,8 @@ var BaseLLM = class {
3732
3800
  }).filter(Boolean);
3733
3801
  return {
3734
3802
  components: finalComponents,
3735
- selectedLayoutId,
3736
- selectedLayoutComponent,
3737
- layoutReasoning,
3803
+ layoutTitle,
3804
+ layoutDescription,
3738
3805
  actions
3739
3806
  };
3740
3807
  } catch (error) {
@@ -3743,13 +3810,158 @@ var BaseLLM = class {
3743
3810
  logCollector?.error(`Failed to match components: ${errorMsg}`);
3744
3811
  return {
3745
3812
  components: [],
3746
- selectedLayoutId: "",
3747
- selectedLayoutComponent: null,
3748
- layoutReasoning: "Failed to match components due to parsing error",
3813
+ layoutTitle: "Dashboard",
3814
+ layoutDescription: "Failed to generate dashboard",
3749
3815
  actions: []
3750
3816
  };
3751
3817
  }
3752
3818
  }
3819
+ /**
3820
+ * Execute external tools based on user request using agentic LLM tool calling
3821
+ * The LLM can directly call tools and retry on errors
3822
+ * @param userPrompt - The user's question/request
3823
+ * @param availableTools - Array of available external tools
3824
+ * @param apiKey - Optional API key for LLM
3825
+ * @param logCollector - Optional log collector
3826
+ * @returns Object containing tool execution results and summary
3827
+ */
3828
+ async executeExternalTools(userPrompt, availableTools, apiKey, logCollector) {
3829
+ const MAX_TOOL_ATTEMPTS = 3;
3830
+ const toolResults = [];
3831
+ try {
3832
+ logger.debug(`[${this.getProviderName()}] Starting agentic external tool execution`);
3833
+ logger.debug(`[${this.getProviderName()}] Available tools: ${availableTools.map((t) => t.name).join(", ")}`);
3834
+ const llmTools = availableTools.map((tool) => {
3835
+ const properties = {};
3836
+ const required = [];
3837
+ Object.entries(tool.params || {}).forEach(([key, type]) => {
3838
+ properties[key] = {
3839
+ type: String(type).toLowerCase(),
3840
+ description: `${key} parameter`
3841
+ };
3842
+ required.push(key);
3843
+ });
3844
+ return {
3845
+ name: tool.id,
3846
+ description: tool.description,
3847
+ input_schema: {
3848
+ type: "object",
3849
+ properties,
3850
+ required
3851
+ }
3852
+ };
3853
+ });
3854
+ const toolAttempts = /* @__PURE__ */ new Map();
3855
+ const toolHandler = async (toolName, toolInput) => {
3856
+ const tool = availableTools.find((t) => t.id === toolName);
3857
+ if (!tool) {
3858
+ const errorMsg = `Tool ${toolName} not found in available tools`;
3859
+ logger.error(`[${this.getProviderName()}] ${errorMsg}`);
3860
+ logCollector?.error(errorMsg);
3861
+ throw new Error(errorMsg);
3862
+ }
3863
+ const attempts = (toolAttempts.get(toolName) || 0) + 1;
3864
+ toolAttempts.set(toolName, attempts);
3865
+ logger.info(`[${this.getProviderName()}] Executing tool: ${tool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
3866
+ logCollector?.info(`Executing ${tool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
3867
+ if (attempts > MAX_TOOL_ATTEMPTS) {
3868
+ const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${tool.name}`;
3869
+ logger.error(`[${this.getProviderName()}] ${errorMsg}`);
3870
+ logCollector?.error(errorMsg);
3871
+ toolResults.push({
3872
+ toolName: tool.name,
3873
+ toolId: tool.id,
3874
+ result: null,
3875
+ error: errorMsg
3876
+ });
3877
+ throw new Error(errorMsg);
3878
+ }
3879
+ try {
3880
+ logger.debug(`[${this.getProviderName()}] Tool ${tool.name} parameters:`, toolInput);
3881
+ const result2 = await tool.fn(toolInput);
3882
+ logger.info(`[${this.getProviderName()}] Tool ${tool.name} executed successfully`);
3883
+ logCollector?.info(`\u2713 ${tool.name} completed successfully`);
3884
+ toolResults.push({
3885
+ toolName: tool.name,
3886
+ toolId: tool.id,
3887
+ result: result2
3888
+ });
3889
+ return JSON.stringify(result2, null, 2);
3890
+ } catch (error) {
3891
+ const errorMsg = error instanceof Error ? error.message : String(error);
3892
+ logger.error(`[${this.getProviderName()}] Tool ${tool.name} failed (attempt ${attempts}): ${errorMsg}`);
3893
+ logCollector?.error(`\u2717 ${tool.name} failed: ${errorMsg}`);
3894
+ if (attempts >= MAX_TOOL_ATTEMPTS) {
3895
+ toolResults.push({
3896
+ toolName: tool.name,
3897
+ toolId: tool.id,
3898
+ result: null,
3899
+ error: errorMsg
3900
+ });
3901
+ }
3902
+ throw new Error(`Tool execution failed: ${errorMsg}`);
3903
+ }
3904
+ };
3905
+ const prompts = await promptLoader.loadPrompts("execute-tools", {
3906
+ USER_PROMPT: userPrompt,
3907
+ AVAILABLE_TOOLS: availableTools.map((tool, idx) => {
3908
+ const paramsText = Object.entries(tool.params || {}).map(([key, type]) => ` - ${key}: ${type}`).join("\n");
3909
+ return `${idx + 1}. ID: ${tool.id}
3910
+ Name: ${tool.name}
3911
+ Description: ${tool.description}
3912
+ Parameters:
3913
+ ${paramsText}`;
3914
+ }).join("\n\n")
3915
+ });
3916
+ logger.debug(`[${this.getProviderName()}] Using agentic tool calling for external tools`);
3917
+ logCollector?.info("Analyzing request and executing external tools...");
3918
+ const result = await LLM.streamWithTools(
3919
+ {
3920
+ sys: prompts.system,
3921
+ user: prompts.user
3922
+ },
3923
+ llmTools,
3924
+ toolHandler,
3925
+ {
3926
+ model: this.model,
3927
+ maxTokens: 2e3,
3928
+ temperature: 0.2,
3929
+ apiKey: this.getApiKey(apiKey)
3930
+ },
3931
+ MAX_TOOL_ATTEMPTS + 2
3932
+ // max iterations: allows for retries + final response
3933
+ );
3934
+ logger.info(`[${this.getProviderName()}] External tool execution completed`);
3935
+ const successfulTools = toolResults.filter((r) => !r.error);
3936
+ const failedTools = toolResults.filter((r) => r.error);
3937
+ let summary = "";
3938
+ if (successfulTools.length > 0) {
3939
+ summary += `Successfully executed ${successfulTools.length} tool(s): ${successfulTools.map((t) => t.toolName).join(", ")}.
3940
+ `;
3941
+ }
3942
+ if (failedTools.length > 0) {
3943
+ summary += `Failed to execute ${failedTools.length} tool(s): ${failedTools.map((t) => t.toolName).join(", ")}.`;
3944
+ }
3945
+ if (toolResults.length === 0) {
3946
+ summary = "No external tools were needed for this request.";
3947
+ }
3948
+ logger.info(`[${this.getProviderName()}] Tool execution summary: ${summary}`);
3949
+ return {
3950
+ toolResults,
3951
+ summary,
3952
+ hasResults: successfulTools.length > 0
3953
+ };
3954
+ } catch (error) {
3955
+ const errorMsg = error instanceof Error ? error.message : String(error);
3956
+ logger.error(`[${this.getProviderName()}] Error in external tool execution: ${errorMsg}`);
3957
+ logCollector?.error(`Error executing external tools: ${errorMsg}`);
3958
+ return {
3959
+ toolResults,
3960
+ summary: `Error executing external tools: ${errorMsg}`,
3961
+ hasResults: false
3962
+ };
3963
+ }
3964
+ }
3753
3965
  /**
3754
3966
  * Generate text-based response for user question
3755
3967
  * This provides conversational text responses instead of component generation
@@ -3758,12 +3970,40 @@ var BaseLLM = class {
3758
3970
  * @param streamCallback - Optional callback function to receive text chunks as they stream
3759
3971
  * @param collections - Collection registry for executing database queries via database.execute
3760
3972
  * @param components - Optional list of available components for matching suggestions
3973
+ * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called
3761
3974
  */
3762
- async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components) {
3975
+ async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components, externalTools) {
3763
3976
  const errors = [];
3764
3977
  logger.debug(`[${this.getProviderName()}] Starting text response generation`);
3765
3978
  logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
3766
3979
  try {
3980
+ let externalToolContext = "No external tools were used for this request.";
3981
+ if (externalTools && externalTools.length > 0) {
3982
+ logger.info(`[${this.getProviderName()}] Executing external tools...`);
3983
+ const toolExecution = await this.executeExternalTools(
3984
+ userPrompt,
3985
+ externalTools,
3986
+ apiKey,
3987
+ logCollector
3988
+ );
3989
+ if (toolExecution.hasResults) {
3990
+ const toolResultsText = toolExecution.toolResults.map((tr) => {
3991
+ if (tr.error) {
3992
+ return `**${tr.toolName}** (Failed): ${tr.error}`;
3993
+ }
3994
+ return `**${tr.toolName}** (Success):
3995
+ ${JSON.stringify(tr.result, null, 2)}`;
3996
+ }).join("\n\n");
3997
+ externalToolContext = `## External Tool Results
3998
+
3999
+ ${toolExecution.summary}
4000
+
4001
+ ${toolResultsText}`;
4002
+ logger.info(`[${this.getProviderName()}] External tools executed, results available`);
4003
+ } else {
4004
+ logger.info(`[${this.getProviderName()}] No external tools were needed`);
4005
+ }
4006
+ }
3767
4007
  const schemaDoc = schema.generateSchemaDocumentation();
3768
4008
  const knowledgeBaseContext = await knowledge_base_default.getKnowledgeBase({
3769
4009
  prompt: userPrompt,
@@ -3771,11 +4011,13 @@ var BaseLLM = class {
3771
4011
  topK: 1
3772
4012
  });
3773
4013
  logger.file("\n=============================\nknowledge base context:", knowledgeBaseContext);
4014
+ logger.file("\n=============================\nexternal tool context:", externalToolContext);
3774
4015
  const prompts = await promptLoader.loadPrompts("text-response", {
3775
4016
  USER_PROMPT: userPrompt,
3776
4017
  CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
3777
4018
  SCHEMA_DOC: schemaDoc,
3778
- KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available."
4019
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available.",
4020
+ EXTERNAL_TOOL_CONTEXT: externalToolContext
3779
4021
  });
3780
4022
  logger.file("\n=============================\nsystem prompt:", prompts.system);
3781
4023
  logger.file("\n=============================\nuser prompt:", prompts.user);
@@ -3989,9 +4231,12 @@ ${errorMsg}
3989
4231
  textLength: textResponse.length
3990
4232
  }
3991
4233
  );
4234
+ if (wrappedStreamCallback && components && components.length > 0) {
4235
+ wrappedStreamCallback("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
4236
+ }
3992
4237
  let matchedComponents = [];
3993
- let selectedLayoutComponent = null;
3994
- let layoutReasoning = "No layout selected";
4238
+ let layoutTitle = "Dashboard";
4239
+ let layoutDescription = "Multi-component dashboard";
3995
4240
  let actions = [];
3996
4241
  if (components && components.length > 0) {
3997
4242
  logger.info(`[${this.getProviderName()}] Matching components from text response...`);
@@ -4002,46 +4247,30 @@ ${errorMsg}
4002
4247
  logCollector
4003
4248
  );
4004
4249
  matchedComponents = matchResult.components;
4005
- selectedLayoutComponent = matchResult.selectedLayoutComponent;
4006
- layoutReasoning = matchResult.layoutReasoning;
4250
+ layoutTitle = matchResult.layoutTitle;
4251
+ layoutDescription = matchResult.layoutDescription;
4007
4252
  actions = matchResult.actions;
4008
4253
  }
4009
4254
  let container_componet = null;
4010
4255
  if (matchedComponents.length > 0) {
4011
- if (selectedLayoutComponent) {
4012
- container_componet = {
4013
- ...selectedLayoutComponent,
4014
- id: `${selectedLayoutComponent.id}_${Date.now()}`,
4015
- // Make ID unique for each instance
4016
- props: {
4017
- ...selectedLayoutComponent.props,
4018
- config: {
4019
- ...selectedLayoutComponent.props?.config || {},
4020
- components: matchedComponents
4021
- },
4022
- actions
4023
- }
4024
- };
4025
- logger.info(`[${this.getProviderName()}] Created ${selectedLayoutComponent.name} (${selectedLayoutComponent.type}) container with ${matchedComponents.length} components and ${actions.length} actions`);
4026
- logCollector?.info(`Created ${selectedLayoutComponent.name} with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
4027
- } else {
4028
- container_componet = {
4029
- id: `multi_container_${Date.now()}`,
4030
- name: "MultiComponentContainer",
4031
- type: "Container",
4032
- description: layoutReasoning,
4033
- category: "dynamic",
4034
- keywords: ["dashboard", "layout", "container"],
4035
- props: {
4036
- config: {
4037
- components: matchedComponents
4038
- },
4039
- actions
4040
- }
4041
- };
4042
- logger.info(`[${this.getProviderName()}] Created fallback MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions`);
4043
- logCollector?.info(`Created MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
4044
- }
4256
+ container_componet = {
4257
+ id: `multi_container_${Date.now()}`,
4258
+ name: "MultiComponentContainer",
4259
+ type: "Container",
4260
+ description: layoutDescription,
4261
+ category: "dynamic",
4262
+ keywords: ["dashboard", "layout", "container"],
4263
+ props: {
4264
+ config: {
4265
+ components: matchedComponents,
4266
+ title: layoutTitle,
4267
+ description: layoutDescription
4268
+ },
4269
+ actions
4270
+ }
4271
+ };
4272
+ logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
4273
+ logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
4045
4274
  }
4046
4275
  return {
4047
4276
  success: true,
@@ -4049,7 +4278,6 @@ ${errorMsg}
4049
4278
  text: textResponse,
4050
4279
  matchedComponents,
4051
4280
  component: container_componet,
4052
- layoutReasoning,
4053
4281
  actions,
4054
4282
  method: `${this.getProviderName()}-text-response`
4055
4283
  },
@@ -4260,8 +4488,9 @@ ${errorMsg}
4260
4488
  * @param responseMode - 'component' for component generation (default), 'text' for text responses
4261
4489
  * @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
4262
4490
  * @param collections - Collection registry for executing database queries (required for text mode)
4491
+ * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called (only for text mode)
4263
4492
  */
4264
- async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) {
4493
+ async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) {
4265
4494
  const startTime = Date.now();
4266
4495
  logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
4267
4496
  if (responseMode === "text") {
@@ -4274,7 +4503,8 @@ ${errorMsg}
4274
4503
  conversationHistory,
4275
4504
  streamCallback,
4276
4505
  collections,
4277
- components
4506
+ components,
4507
+ externalTools
4278
4508
  );
4279
4509
  if (!textResponse.success) {
4280
4510
  const elapsedTime3 = Date.now() - startTime;
@@ -4420,7 +4650,7 @@ function getLLMProviders() {
4420
4650
  return DEFAULT_PROVIDERS;
4421
4651
  }
4422
4652
  }
4423
- var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
4653
+ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
4424
4654
  logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
4425
4655
  logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
4426
4656
  const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -4432,11 +4662,11 @@ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conver
4432
4662
  return { success: false, errors: [emptyMsg] };
4433
4663
  }
4434
4664
  logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
4435
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4665
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4436
4666
  logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
4437
4667
  return matchResult;
4438
4668
  };
4439
- var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
4669
+ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
4440
4670
  logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
4441
4671
  logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
4442
4672
  const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -4449,14 +4679,14 @@ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversatio
4449
4679
  return { success: false, errors: [emptyMsg] };
4450
4680
  }
4451
4681
  logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
4452
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4682
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4453
4683
  logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
4454
4684
  return matchResult;
4455
4685
  };
4456
4686
  var getUserResponseFromCache = async (prompt) => {
4457
4687
  return false;
4458
4688
  };
4459
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
4689
+ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
4460
4690
  logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
4461
4691
  logger.debug(`[get_user_response] Response mode: ${responseMode}`);
4462
4692
  logger.debug("[get_user_response] Checking cache for existing response");
@@ -4489,9 +4719,9 @@ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey,
4489
4719
  logCollector?.info(attemptMsg);
4490
4720
  let result;
4491
4721
  if (provider === "anthropic") {
4492
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4722
+ result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4493
4723
  } else if (provider === "groq") {
4494
- result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
4724
+ result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
4495
4725
  } else {
4496
4726
  logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
4497
4727
  errors.push(`Unknown provider: ${provider}`);
@@ -4715,11 +4945,11 @@ var CONTEXT_CONFIG = {
4715
4945
  * Set to 0 to disable conversation history
4716
4946
  * Higher values provide more context but may increase token usage
4717
4947
  */
4718
- MAX_CONVERSATION_CONTEXT_BLOCKS: 2
4948
+ MAX_CONVERSATION_CONTEXT_BLOCKS: 3
4719
4949
  };
4720
4950
 
4721
4951
  // src/handlers/user-prompt-request.ts
4722
- var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) => {
4952
+ var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools) => {
4723
4953
  const errors = [];
4724
4954
  logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
4725
4955
  const parseResult = UserPromptRequestMessageSchema.safeParse(data);
@@ -4799,7 +5029,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4799
5029
  conversationHistory,
4800
5030
  responseMode,
4801
5031
  streamCallback,
4802
- collections
5032
+ collections,
5033
+ externalTools
4803
5034
  );
4804
5035
  logCollector.info("User prompt request completed");
4805
5036
  const uiBlockId = existingUiBlockId;
@@ -4860,8 +5091,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4860
5091
  wsId
4861
5092
  };
4862
5093
  };
4863
- async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) {
4864
- const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections);
5094
+ async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools) {
5095
+ const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools);
4865
5096
  sendDataResponse4(
4866
5097
  response.id || data.id,
4867
5098
  {
@@ -5935,6 +6166,183 @@ function sendResponse5(id, res, sendMessage, clientId) {
5935
6166
  sendMessage(response);
5936
6167
  }
5937
6168
 
6169
+ // src/handlers/bookmarks.ts
6170
+ async function handleBookmarksRequest(data, collections, sendMessage) {
6171
+ const executeCollection = async (collection, op, params) => {
6172
+ const handler = collections[collection]?.[op];
6173
+ if (!handler) {
6174
+ throw new Error(`Collection operation ${collection}.${op} not found`);
6175
+ }
6176
+ return await handler(params);
6177
+ };
6178
+ try {
6179
+ const request = BookmarksRequestMessageSchema.parse(data);
6180
+ const { id, payload, from } = request;
6181
+ const { operation, data: requestData } = payload;
6182
+ const bookmarkId = requestData?.id;
6183
+ const uiblock = requestData?.uiblock;
6184
+ switch (operation) {
6185
+ case "create":
6186
+ await handleCreate4(id, uiblock, executeCollection, sendMessage, from.id);
6187
+ break;
6188
+ case "update":
6189
+ await handleUpdate4(id, bookmarkId, uiblock, executeCollection, sendMessage, from.id);
6190
+ break;
6191
+ case "delete":
6192
+ await handleDelete4(id, bookmarkId, executeCollection, sendMessage, from.id);
6193
+ break;
6194
+ case "getAll":
6195
+ await handleGetAll4(id, executeCollection, sendMessage, from.id);
6196
+ break;
6197
+ case "getOne":
6198
+ await handleGetOne4(id, bookmarkId, executeCollection, sendMessage, from.id);
6199
+ break;
6200
+ default:
6201
+ sendResponse6(id, {
6202
+ success: false,
6203
+ error: `Unknown operation: ${operation}`
6204
+ }, sendMessage, from.id);
6205
+ }
6206
+ } catch (error) {
6207
+ logger.error("Failed to handle bookmarks request:", error);
6208
+ sendResponse6(null, {
6209
+ success: false,
6210
+ error: error instanceof Error ? error.message : "Unknown error occurred"
6211
+ }, sendMessage);
6212
+ }
6213
+ }
6214
+ async function handleCreate4(id, uiblock, executeCollection, sendMessage, clientId) {
6215
+ if (!uiblock) {
6216
+ sendResponse6(id, {
6217
+ success: false,
6218
+ error: "UIBlock data is required"
6219
+ }, sendMessage, clientId);
6220
+ return;
6221
+ }
6222
+ try {
6223
+ const result = await executeCollection("bookmarks", "create", { uiblock });
6224
+ sendResponse6(id, {
6225
+ success: true,
6226
+ data: result.data,
6227
+ message: "Bookmark created successfully"
6228
+ }, sendMessage, clientId);
6229
+ logger.info(`Bookmark created: ID ${result.data.id}`);
6230
+ } catch (error) {
6231
+ sendResponse6(id, {
6232
+ success: false,
6233
+ error: error instanceof Error ? error.message : "Failed to create bookmark"
6234
+ }, sendMessage, clientId);
6235
+ }
6236
+ }
6237
+ async function handleUpdate4(id, bookmarkId, uiblock, executeCollection, sendMessage, clientId) {
6238
+ if (!bookmarkId) {
6239
+ sendResponse6(id, {
6240
+ success: false,
6241
+ error: "Bookmark ID is required"
6242
+ }, sendMessage, clientId);
6243
+ return;
6244
+ }
6245
+ if (!uiblock) {
6246
+ sendResponse6(id, {
6247
+ success: false,
6248
+ error: "UIBlock data is required"
6249
+ }, sendMessage, clientId);
6250
+ return;
6251
+ }
6252
+ try {
6253
+ const result = await executeCollection("bookmarks", "update", { id: bookmarkId, uiblock });
6254
+ sendResponse6(id, {
6255
+ success: true,
6256
+ data: result.data,
6257
+ message: "Bookmark updated successfully"
6258
+ }, sendMessage, clientId);
6259
+ logger.info(`Bookmark updated: ID ${bookmarkId}`);
6260
+ } catch (error) {
6261
+ sendResponse6(id, {
6262
+ success: false,
6263
+ error: error instanceof Error ? error.message : "Failed to update bookmark"
6264
+ }, sendMessage, clientId);
6265
+ }
6266
+ }
6267
+ async function handleDelete4(id, bookmarkId, executeCollection, sendMessage, clientId) {
6268
+ if (!bookmarkId) {
6269
+ sendResponse6(id, {
6270
+ success: false,
6271
+ error: "Bookmark ID is required"
6272
+ }, sendMessage, clientId);
6273
+ return;
6274
+ }
6275
+ try {
6276
+ const result = await executeCollection("bookmarks", "delete", { id: bookmarkId });
6277
+ sendResponse6(id, {
6278
+ success: true,
6279
+ data: result.data,
6280
+ message: "Bookmark deleted successfully"
6281
+ }, sendMessage, clientId);
6282
+ logger.info(`Bookmark deleted: ID ${bookmarkId}`);
6283
+ } catch (error) {
6284
+ sendResponse6(id, {
6285
+ success: false,
6286
+ error: error instanceof Error ? error.message : "Failed to delete bookmark"
6287
+ }, sendMessage, clientId);
6288
+ }
6289
+ }
6290
+ async function handleGetAll4(id, executeCollection, sendMessage, clientId) {
6291
+ try {
6292
+ const result = await executeCollection("bookmarks", "getAll", {});
6293
+ sendResponse6(id, {
6294
+ success: true,
6295
+ data: result.data,
6296
+ count: result.count,
6297
+ message: `Retrieved ${result.count} bookmarks`
6298
+ }, sendMessage, clientId);
6299
+ logger.info(`Retrieved all bookmarks (count: ${result.count})`);
6300
+ } catch (error) {
6301
+ sendResponse6(id, {
6302
+ success: false,
6303
+ error: error instanceof Error ? error.message : "Failed to get bookmarks"
6304
+ }, sendMessage, clientId);
6305
+ }
6306
+ }
6307
+ async function handleGetOne4(id, bookmarkId, executeCollection, sendMessage, clientId) {
6308
+ if (!bookmarkId) {
6309
+ sendResponse6(id, {
6310
+ success: false,
6311
+ error: "Bookmark ID is required"
6312
+ }, sendMessage, clientId);
6313
+ return;
6314
+ }
6315
+ try {
6316
+ const result = await executeCollection("bookmarks", "getOne", { id: bookmarkId });
6317
+ sendResponse6(id, {
6318
+ success: true,
6319
+ data: result.data,
6320
+ message: `Retrieved bookmark ID ${bookmarkId}`
6321
+ }, sendMessage, clientId);
6322
+ logger.info(`Retrieved bookmark: ID ${bookmarkId}`);
6323
+ } catch (error) {
6324
+ sendResponse6(id, {
6325
+ success: false,
6326
+ error: error instanceof Error ? error.message : "Failed to get bookmark"
6327
+ }, sendMessage, clientId);
6328
+ }
6329
+ }
6330
+ function sendResponse6(id, res, sendMessage, clientId) {
6331
+ const response = {
6332
+ id: id || "unknown",
6333
+ type: "BOOKMARKS_RES",
6334
+ from: { type: "data-agent" },
6335
+ to: {
6336
+ type: "user",
6337
+ id: clientId
6338
+ },
6339
+ payload: {
6340
+ ...res
6341
+ }
6342
+ };
6343
+ sendMessage(response);
6344
+ }
6345
+
5938
6346
  // src/auth/user-manager.ts
5939
6347
  import fs5 from "fs";
5940
6348
  import path4 from "path";
@@ -6759,6 +7167,7 @@ var SuperatomSDK = class {
6759
7167
  this.maxReconnectAttempts = 5;
6760
7168
  this.collections = {};
6761
7169
  this.components = [];
7170
+ this.tools = [];
6762
7171
  if (config.logLevel) {
6763
7172
  logger.setLogLevel(config.logLevel);
6764
7173
  }
@@ -6914,7 +7323,7 @@ var SuperatomSDK = class {
6914
7323
  });
6915
7324
  break;
6916
7325
  case "USER_PROMPT_REQ":
6917
- handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections).catch((error) => {
7326
+ handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections, this.tools).catch((error) => {
6918
7327
  logger.error("Failed to handle user prompt request:", error);
6919
7328
  });
6920
7329
  break;
@@ -6948,6 +7357,11 @@ var SuperatomSDK = class {
6948
7357
  logger.error("Failed to handle reports request:", error);
6949
7358
  });
6950
7359
  break;
7360
+ case "BOOKMARKS":
7361
+ handleBookmarksRequest(parsed, this.collections, (msg) => this.send(msg)).catch((error) => {
7362
+ logger.error("Failed to handle bookmarks request:", error);
7363
+ });
7364
+ break;
6951
7365
  default:
6952
7366
  const handler = this.messageTypeHandlers.get(message.type);
6953
7367
  if (handler) {
@@ -7054,6 +7468,19 @@ var SuperatomSDK = class {
7054
7468
  storeComponents(components) {
7055
7469
  this.components = components;
7056
7470
  }
7471
+ /**
7472
+ * Set tools for the SDK instance
7473
+ */
7474
+ setTools(tools) {
7475
+ this.tools = tools;
7476
+ logger.info(`Tools stored in SDK: ${tools.length} tools`);
7477
+ }
7478
+ /**
7479
+ * Get the stored tools
7480
+ */
7481
+ getTools() {
7482
+ return this.tools;
7483
+ }
7057
7484
  };
7058
7485
  export {
7059
7486
  CONTEXT_CONFIG,