@superatomai/sdk-node 0.0.8 → 0.0.9

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
@@ -2674,6 +2674,7 @@ When the user asks about data
2674
2674
 
2675
2675
  After analyzing the query results, you MUST suggest appropriate dashboard components for displaying the data. Use this format:
2676
2676
 
2677
+ <DashboardComponents>
2677
2678
  **Dashboard Components:**
2678
2679
  Format: \`{number}.{component_type} : {clear reasoning}\`
2679
2680
 
@@ -2682,9 +2683,9 @@ Format: \`{number}.{component_type} : {clear reasoning}\`
2682
2683
  1. Analyze the query results structure and data type
2683
2684
  2. Suggest components that would best visualize the data
2684
2685
  3. Each component suggestion must be on a new line
2686
+ </DashboardComponents>
2685
2687
 
2686
-
2687
- IMPORTANT: Always include the **Dashboard Components:** section with at least one component suggestion when data is returned.
2688
+ IMPORTANT: Always wrap component suggestions with <DashboardComponents> tags and include at least one component suggestion when data is returned.
2688
2689
 
2689
2690
  ## Output Format
2690
2691
 
@@ -2692,7 +2693,7 @@ Respond with plain text that includes:
2692
2693
 
2693
2694
  1. **Query Analysis** (if applicable): Brief explanation of what data was fetched
2694
2695
  2. **Results Summary**: Present the data in a clear, readable format
2695
- 3. **Dashboard Components**: List suggested components in the c1:type format
2696
+ 3. **Dashboard Components**: List suggested components wrapped in <DashboardComponents> tags
2696
2697
 
2697
2698
 
2698
2699
  **CRITICAL:**
@@ -2769,39 +2770,22 @@ You will receive a text response containing:
2769
2770
  3. **Dashboard Components:** suggestions (1:component_type : reasoning format)
2770
2771
 
2771
2772
  Your job is to:
2772
- 1. **DISCOVER and SELECT the best dashboard layout component** from the available components list
2773
- 2. Parse the component suggestions from the text response
2774
- 3. Match each suggestion with an actual component from the available list
2775
- 4. Generate the RIGHT NUMBER of components to fit the selected layout perfectly (based on the layout component's description)
2776
- 5. Generate proper props to **visualize the analysis results** that were already fetched
2777
- 6. **Generate intelligent follow-up questions (actions)** that the user might naturally ask next based on the data analysis
2773
+ 1. **Parse the component suggestions** from the text response (format: 1:component_type : reasoning)
2774
+ 2. **Match each suggestion with an actual component** from the available list
2775
+ 3. **Generate proper props** for each matched component to **visualize the analysis results** that were already fetched
2776
+ 4. **SELECT the best dashboard layout component** that can accommodate all the matched components
2777
+ 5. **Generate intelligent follow-up questions (actions)** that the user might naturally ask next based on the data analysis
2778
2778
 
2779
2779
  **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.
2780
2780
 
2781
-
2782
-
2783
- ## Layout Component Discovery
2784
- **Find layout components** by looking for components with \`type: "DashboardLayout"\`
2785
-
2786
- ## Layout Selection Logic
2787
- 1. **Read each layout's description** to understand:
2788
- - What structure it provides
2789
- - When it's best used (e.g., comprehensive analysis vs focused analysis)
2790
- - The exact number and types of components it requires
2791
- 2. **Select the best layout** based on:
2792
- - Which layout structure best fits the analysis needs
2793
- - The component suggestions from the text response
2794
- - The user question and data complexity
2795
- 3. **Generate EXACTLY the components specified** in the selected layout's description
2796
-
2797
- **IMPORTANT:** Always prefer structured dashboard layouts when available. The layout component's description will tell you exactly what components to generate.
2781
+ **APPROACH**: First match all the components suggested in the text response, THEN find the layout that best fits those components.
2798
2782
 
2799
2783
  ## Available Components
2800
2784
 
2801
2785
  {{AVAILABLE_COMPONENTS}}
2802
2786
 
2803
- ## Component Matching Rules
2804
- For each component suggestion (c1, c2, c3, etc.):
2787
+ ## Component Matching Rules (STEP 1)
2788
+ For each component suggestion (c1, c2, c3, etc.) from the text response:
2805
2789
 
2806
2790
  1. **Match by type**: Find components whose \`type\` matches the suggested component type
2807
2791
  2. **Refine by relevance**: If multiple components match, choose based on:
@@ -2809,6 +2793,24 @@ For each component suggestion (c1, c2, c3, etc.):
2809
2793
  - Best fit for the data being visualized
2810
2794
  3. **Fallback**: If no exact type match, find the closest alternative
2811
2795
 
2796
+ ## Layout Selection Logic (STEP 2 - After Matching Components)
2797
+
2798
+ **After you have matched all components**, select the best dashboard layout:
2799
+
2800
+ 1. **Find layout components** by looking for components with \`type: "DashboardLayout"\` in the available components list
2801
+ 2. **Read each layout's description** to understand:
2802
+ - What structure it provides
2803
+ - When it's best used (e.g., comprehensive analysis vs focused analysis)
2804
+ - The number and types of components it can accommodate
2805
+ 3. **Select the best layout** based on:
2806
+ - Which layout can best display ALL the matched components
2807
+ - The layout's capacity (how many components it supports)
2808
+ - The types of matched components (KPI, charts, tables, etc.)
2809
+ - The user question and data complexity
2810
+ 4. **If no specific layout fits**, fall back to "MultiComponentContainer" as the default layout
2811
+
2812
+ **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.
2813
+
2812
2814
  ## Props Generation Rules
2813
2815
 
2814
2816
  For each matched component, generate complete props:
@@ -2921,7 +2923,6 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
2921
2923
 
2922
2924
  \`\`\`json
2923
2925
  {
2924
- "selectedLayout": "Name of the selected layout component from available components",
2925
2926
  "selectedLayoutId": "id_of_the_selected_layout_component",
2926
2927
  "layoutReasoning": "Why this layout was selected based on its description and the analysis needs",
2927
2928
  "matchedComponents": [
@@ -2953,13 +2954,14 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
2953
2954
  \`\`\`
2954
2955
 
2955
2956
  **CRITICAL:**
2956
- - \`selectedLayout\` MUST be the NAME of a layout component from the available components list (must have type "DashboardLayout")
2957
- - \`selectedLayoutId\` MUST be the ID of the selected layout component
2957
+ - \`matchedComponents\` MUST include ALL components suggested in the text response (match them first!)
2958
+ - \`selectedLayoutId\` MUST be the ID of the selected layout component (must have type "DashboardLayout")
2958
2959
  - \`layoutReasoning\` MUST explain:
2959
2960
  - Why you chose this specific layout component
2960
- - How its structure (from its description) matches the analysis needs
2961
- - What components it requires based on its description
2962
- - \`matchedComponents\` array length and types MUST match what the selected layout's description specifies
2961
+ - How many components you matched (e.g., "Matched 3 components: 1 KPI, 1 chart, 1 table")
2962
+ - Why this layout is the best fit for displaying these specific matched components
2963
+ - What makes this layout appropriate for the component types and count
2964
+ - The layout selection happens AFTER component matching - don't force components to fit a pre-selected layout
2963
2965
  - \`actions\` MUST be an array of 4-5 intelligent follow-up questions based on the analysis
2964
2966
  - Return ONLY valid JSON (no markdown code blocks, no text before/after)
2965
2967
  - Generate complete props for each component
@@ -3155,6 +3157,7 @@ var promptLoader = new PromptLoader({
3155
3157
  // src/llm.ts
3156
3158
  import Anthropic from "@anthropic-ai/sdk";
3157
3159
  import Groq from "groq-sdk";
3160
+ import { jsonrepair } from "jsonrepair";
3158
3161
  var LLM = class {
3159
3162
  /* Get a complete text response from an LLM (Anthropic or Groq) */
3160
3163
  static async text(messages, options = {}) {
@@ -3276,65 +3279,110 @@ var LLM = class {
3276
3279
  let finalText = "";
3277
3280
  while (iterations < maxIterations) {
3278
3281
  iterations++;
3279
- const response = await client.messages.create({
3282
+ const stream = await client.messages.create({
3280
3283
  model: modelName,
3281
3284
  max_tokens: options.maxTokens || 4e3,
3282
3285
  temperature: options.temperature,
3283
3286
  system: messages.sys,
3284
3287
  messages: conversationMessages,
3285
- tools
3288
+ tools,
3289
+ stream: true
3290
+ // Enable streaming
3286
3291
  });
3287
- if (response.stop_reason === "end_turn") {
3288
- const textBlock = response.content.find((block) => block.type === "text");
3289
- if (textBlock && textBlock.type === "text") {
3290
- finalText = textBlock.text;
3291
- if (options.partial) {
3292
- options.partial(finalText);
3292
+ let stopReason = null;
3293
+ const contentBlocks = [];
3294
+ let currentTextBlock = "";
3295
+ let currentToolUse = null;
3296
+ for await (const chunk of stream) {
3297
+ if (chunk.type === "message_start") {
3298
+ contentBlocks.length = 0;
3299
+ currentTextBlock = "";
3300
+ currentToolUse = null;
3301
+ }
3302
+ if (chunk.type === "content_block_start") {
3303
+ if (chunk.content_block.type === "text") {
3304
+ currentTextBlock = "";
3305
+ } else if (chunk.content_block.type === "tool_use") {
3306
+ currentToolUse = {
3307
+ type: "tool_use",
3308
+ id: chunk.content_block.id,
3309
+ name: chunk.content_block.name,
3310
+ input: {}
3311
+ };
3312
+ }
3313
+ }
3314
+ if (chunk.type === "content_block_delta") {
3315
+ if (chunk.delta.type === "text_delta") {
3316
+ const text = chunk.delta.text;
3317
+ currentTextBlock += text;
3318
+ if (options.partial) {
3319
+ options.partial(text);
3320
+ }
3321
+ } else if (chunk.delta.type === "input_json_delta" && currentToolUse) {
3322
+ currentToolUse.inputJson = (currentToolUse.inputJson || "") + chunk.delta.partial_json;
3323
+ }
3324
+ }
3325
+ if (chunk.type === "content_block_stop") {
3326
+ if (currentTextBlock) {
3327
+ contentBlocks.push({
3328
+ type: "text",
3329
+ text: currentTextBlock
3330
+ });
3331
+ finalText = currentTextBlock;
3332
+ currentTextBlock = "";
3333
+ } else if (currentToolUse) {
3334
+ try {
3335
+ currentToolUse.input = currentToolUse.inputJson ? JSON.parse(currentToolUse.inputJson) : {};
3336
+ } catch (error) {
3337
+ currentToolUse.input = {};
3338
+ }
3339
+ delete currentToolUse.inputJson;
3340
+ contentBlocks.push(currentToolUse);
3341
+ currentToolUse = null;
3293
3342
  }
3294
3343
  }
3344
+ if (chunk.type === "message_delta") {
3345
+ stopReason = chunk.delta.stop_reason || stopReason;
3346
+ }
3347
+ if (chunk.type === "message_stop") {
3348
+ break;
3349
+ }
3350
+ }
3351
+ if (stopReason === "end_turn") {
3295
3352
  break;
3296
3353
  }
3297
- if (response.stop_reason === "tool_use") {
3298
- const toolUses = response.content.filter((block) => block.type === "tool_use");
3354
+ if (stopReason === "tool_use") {
3355
+ const toolUses = contentBlocks.filter((block) => block.type === "tool_use");
3299
3356
  if (toolUses.length === 0) {
3300
3357
  break;
3301
3358
  }
3302
3359
  conversationMessages.push({
3303
3360
  role: "assistant",
3304
- content: response.content
3361
+ content: contentBlocks
3305
3362
  });
3306
3363
  const toolResults = {
3307
3364
  role: "user",
3308
3365
  content: []
3309
3366
  };
3310
3367
  for (const toolUse of toolUses) {
3311
- if (toolUse.type === "tool_use") {
3312
- try {
3313
- const result = await toolHandler(toolUse.name, toolUse.input);
3314
- toolResults.content.push({
3315
- type: "tool_result",
3316
- tool_use_id: toolUse.id,
3317
- content: typeof result === "string" ? result : JSON.stringify(result)
3318
- });
3319
- } catch (error) {
3320
- toolResults.content.push({
3321
- type: "tool_result",
3322
- tool_use_id: toolUse.id,
3323
- content: error instanceof Error ? error.message : String(error),
3324
- is_error: true
3325
- });
3326
- }
3368
+ try {
3369
+ const result = await toolHandler(toolUse.name, toolUse.input);
3370
+ toolResults.content.push({
3371
+ type: "tool_result",
3372
+ tool_use_id: toolUse.id,
3373
+ content: typeof result === "string" ? result : JSON.stringify(result)
3374
+ });
3375
+ } catch (error) {
3376
+ toolResults.content.push({
3377
+ type: "tool_result",
3378
+ tool_use_id: toolUse.id,
3379
+ content: error instanceof Error ? error.message : String(error),
3380
+ is_error: true
3381
+ });
3327
3382
  }
3328
3383
  }
3329
3384
  conversationMessages.push(toolResults);
3330
3385
  } else {
3331
- const textBlock = response.content.find((block) => block.type === "text");
3332
- if (textBlock && textBlock.type === "text") {
3333
- finalText = textBlock.text;
3334
- if (options.partial) {
3335
- options.partial(finalText);
3336
- }
3337
- }
3338
3386
  break;
3339
3387
  }
3340
3388
  }
@@ -3397,9 +3445,9 @@ var LLM = class {
3397
3445
  // ============================================================
3398
3446
  /**
3399
3447
  * Parse JSON string, handling markdown code blocks and surrounding text
3400
- * Enhanced version from anthropic.ts implementation
3448
+ * Enhanced version with jsonrepair to handle malformed JSON from LLMs
3401
3449
  * @param text - Text that may contain JSON wrapped in ```json...``` or with surrounding text
3402
- * @returns Parsed JSON object
3450
+ * @returns Parsed JSON object or array
3403
3451
  */
3404
3452
  static _parseJSON(text) {
3405
3453
  let jsonText = text.trim();
@@ -3409,11 +3457,29 @@ var LLM = class {
3409
3457
  jsonText = jsonText.replace(/^```\s*\n?/, "").replace(/\n?```\s*$/, "");
3410
3458
  }
3411
3459
  const firstBrace = jsonText.indexOf("{");
3460
+ const firstBracket = jsonText.indexOf("[");
3412
3461
  const lastBrace = jsonText.lastIndexOf("}");
3413
- if (firstBrace !== -1 && lastBrace !== -1 && firstBrace < lastBrace) {
3414
- jsonText = jsonText.substring(firstBrace, lastBrace + 1);
3462
+ const lastBracket = jsonText.lastIndexOf("]");
3463
+ let startIdx = -1;
3464
+ let endIdx = -1;
3465
+ if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
3466
+ startIdx = firstBrace;
3467
+ endIdx = lastBrace;
3468
+ } else if (firstBracket !== -1) {
3469
+ startIdx = firstBracket;
3470
+ endIdx = lastBracket;
3471
+ }
3472
+ if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
3473
+ jsonText = jsonText.substring(startIdx, endIdx + 1);
3474
+ }
3475
+ try {
3476
+ const repairedJson = jsonrepair(jsonText);
3477
+ return JSON.parse(repairedJson);
3478
+ } catch (error) {
3479
+ throw new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}
3480
+
3481
+ Original text: ${jsonText.substring(0, 200)}...`);
3415
3482
  }
3416
- return JSON.parse(jsonText);
3417
3483
  }
3418
3484
  };
3419
3485
 
@@ -4026,7 +4092,7 @@ var BaseLLM = class {
4026
4092
  logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
4027
4093
  logger.file("\n=============================\nmatch text components system prompt:", prompts.system);
4028
4094
  logCollector?.info("Matching components from text response...");
4029
- const rawResponse = await LLM.stream(
4095
+ const result = await LLM.stream(
4030
4096
  {
4031
4097
  sys: prompts.system,
4032
4098
  user: prompts.user
@@ -4037,60 +4103,12 @@ var BaseLLM = class {
4037
4103
  temperature: 0.2,
4038
4104
  apiKey: this.getApiKey(apiKey)
4039
4105
  },
4040
- false
4041
- // Don't parse as JSON yet, get raw response
4106
+ true
4107
+ // Parse as JSON
4042
4108
  );
4043
- logger.debug(`[${this.getProviderName()}] Raw component matching response length: ${rawResponse?.length || 0}`);
4044
- let result;
4045
- try {
4046
- let cleanedResponse = rawResponse || "";
4047
- cleanedResponse = cleanedResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
4048
- const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
4049
- if (jsonMatch) {
4050
- cleanedResponse = jsonMatch[0];
4051
- }
4052
- result = JSON.parse(cleanedResponse);
4053
- } catch (parseError) {
4054
- logger.error(`[${this.getProviderName()}] Failed to parse component matching JSON response`);
4055
- const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
4056
- const posMatch = errorMsg.match(/position (\d+)/);
4057
- const errorPos = posMatch ? parseInt(posMatch[1]) : -1;
4058
- if (errorPos > 0 && rawResponse) {
4059
- const start = Math.max(0, errorPos - 200);
4060
- const end = Math.min(rawResponse.length, errorPos + 200);
4061
- logger.debug(`[${this.getProviderName()}] Error context (position ${errorPos}):`);
4062
- logger.debug(rawResponse.substring(start, end));
4063
- logger.debug(" ".repeat(Math.min(200, errorPos - start)) + "^--- Error here");
4064
- }
4065
- logger.debug(`[${this.getProviderName()}] Raw response (first 2000 chars):`, rawResponse?.substring(0, 2e3));
4066
- logger.debug(`[${this.getProviderName()}] Parse error:`, parseError);
4067
- logCollector?.error(`Failed to parse component matching response: ${errorMsg}`);
4068
- try {
4069
- logger.info(`[${this.getProviderName()}] Attempting aggressive JSON cleanup...`);
4070
- let aggressive = rawResponse || "";
4071
- aggressive = aggressive.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
4072
- const match = aggressive.match(/\{[\s\S]*\}/);
4073
- if (match) {
4074
- aggressive = match[0];
4075
- }
4076
- aggressive = aggressive.replace(/,(\s*[}\]])/g, "$1");
4077
- result = JSON.parse(aggressive);
4078
- logger.info(`[${this.getProviderName()}] Aggressive cleanup succeeded!`);
4079
- } catch (secondError) {
4080
- logger.error(`[${this.getProviderName()}] Aggressive cleanup also failed`);
4081
- return {
4082
- components: [],
4083
- selectedLayout: "MultiComponentContainer",
4084
- selectedLayoutId: "",
4085
- selectedLayoutComponent: null,
4086
- layoutReasoning: "No layout selected",
4087
- actions: []
4088
- };
4089
- }
4090
- }
4109
+ logger.debug(`[${this.getProviderName()}] Component matching response parsed successfully`);
4091
4110
  const matchedComponents = result.matchedComponents || [];
4092
- const selectedLayout = result.selectedLayout || "MultiComponentContainer";
4093
- const selectedLayoutId = result.selectedLayoutId || "";
4111
+ const selectedLayoutId = result.selectedLayoutId || "multi-component-container";
4094
4112
  const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
4095
4113
  const rawActions = result.actions || [];
4096
4114
  const actions = convertQuestionsToActions(rawActions);
@@ -4102,11 +4120,11 @@ var BaseLLM = class {
4102
4120
  }
4103
4121
  }
4104
4122
  logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
4105
- logger.info(`[${this.getProviderName()}] Selected layout: ${selectedLayout} (ID: ${selectedLayoutId})`);
4123
+ logger.info(`[${this.getProviderName()}] Selected layout: (ID: ${selectedLayoutId})`);
4106
4124
  logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
4107
4125
  logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
4108
4126
  if (matchedComponents.length > 0) {
4109
- logCollector?.info(`Matched ${matchedComponents.length} components for visualization using ${selectedLayout}`);
4127
+ logCollector?.info(`Matched ${matchedComponents.length} components for visualization `);
4110
4128
  logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
4111
4129
  matchedComponents.forEach((comp, idx) => {
4112
4130
  logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
@@ -4141,7 +4159,6 @@ var BaseLLM = class {
4141
4159
  }).filter(Boolean);
4142
4160
  return {
4143
4161
  components: finalComponents,
4144
- selectedLayout,
4145
4162
  selectedLayoutId,
4146
4163
  selectedLayoutComponent,
4147
4164
  layoutReasoning,
@@ -4149,15 +4166,13 @@ var BaseLLM = class {
4149
4166
  };
4150
4167
  } catch (error) {
4151
4168
  const errorMsg = error instanceof Error ? error.message : String(error);
4152
- logger.error(`[${this.getProviderName()}] Error matching components from text response: ${errorMsg}`);
4153
- logger.debug(`[${this.getProviderName()}] Component matching error details:`, error);
4154
- logCollector?.error(`Error matching components: ${errorMsg}`);
4169
+ logger.error(`[${this.getProviderName()}] Error matching components: ${errorMsg}`);
4170
+ logCollector?.error(`Failed to match components: ${errorMsg}`);
4155
4171
  return {
4156
4172
  components: [],
4157
- selectedLayout: "MultiComponentContainer",
4158
4173
  selectedLayoutId: "",
4159
4174
  selectedLayoutComponent: null,
4160
- layoutReasoning: "Error occurred during component matching",
4175
+ layoutReasoning: "Failed to match components due to parsing error",
4161
4176
  actions: []
4162
4177
  };
4163
4178
  }
@@ -4424,7 +4439,7 @@ ${errorMsg}
4424
4439
  container_componet = {
4425
4440
  ...selectedLayoutComponent,
4426
4441
  id: `${selectedLayoutComponent.id}_${Date.now()}`,
4427
- description: layoutReasoning,
4442
+ // Make ID unique for each instance
4428
4443
  props: {
4429
4444
  ...selectedLayoutComponent.props,
4430
4445
  config: {
@@ -7271,7 +7286,7 @@ var SuperatomSDK = class {
7271
7286
  url.searchParams.set("projectId", this.projectId);
7272
7287
  url.searchParams.set("userId", this.userId);
7273
7288
  url.searchParams.set("type", this.type);
7274
- logger.info(`Connecting to WebSocket: ${url.host}`);
7289
+ logger.info(`Connecting to WebSocket: ${url.toString()}`);
7275
7290
  this.ws = createWebSocket(url.toString());
7276
7291
  this.ws.addEventListener("open", () => {
7277
7292
  this.connected = true;