@superatomai/sdk-node 0.0.45-mds → 0.0.46-mds

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
@@ -1678,6 +1678,21 @@ var QueryCache = class {
1678
1678
  };
1679
1679
  var queryCache = new QueryCache();
1680
1680
 
1681
+ // src/utils/surrogate.ts
1682
+ var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1683
+ function stripLoneSurrogates(value) {
1684
+ if (typeof value !== "string") return value;
1685
+ if (!/[\uD800-\uDFFF]/.test(value)) return value;
1686
+ return value.replace(LONE_SURROGATE_RE, "\uFFFD");
1687
+ }
1688
+ function safeTruncate(text, maxUnits) {
1689
+ if (typeof text !== "string" || text.length <= maxUnits || maxUnits < 0) return text;
1690
+ let end = maxUnits;
1691
+ const lastCode = text.charCodeAt(end - 1);
1692
+ if (lastCode >= 55296 && lastCode <= 56319) end -= 1;
1693
+ return text.slice(0, end);
1694
+ }
1695
+
1681
1696
  // src/userResponse/llm-result-truncator.ts
1682
1697
  var DEFAULT_MAX_ROWS = 10;
1683
1698
  var DEFAULT_MAX_CHARS_PER_FIELD = 500;
@@ -1720,12 +1735,12 @@ function isDateString(value) {
1720
1735
  }
1721
1736
  function truncateTextField(value, maxLength) {
1722
1737
  if (value.length <= maxLength) {
1723
- return { text: value, wasTruncated: false };
1738
+ return { text: stripLoneSurrogates(value), wasTruncated: false };
1724
1739
  }
1725
- const truncated = value.substring(0, maxLength);
1726
- const remaining = value.length - maxLength;
1740
+ const truncated = safeTruncate(value, maxLength);
1741
+ const remaining = value.length - truncated.length;
1727
1742
  return {
1728
- text: `${truncated}... (${remaining} more chars)`,
1743
+ text: `${stripLoneSurrogates(truncated)}... (${remaining} more chars)`,
1729
1744
  wasTruncated: true
1730
1745
  };
1731
1746
  }
@@ -5582,6 +5597,7 @@ var LLM = class {
5582
5597
  /* Get a complete text response from an LLM (Anthropic or Groq) */
5583
5598
  static async text(messages, options = {}) {
5584
5599
  const [provider, modelName] = this._parseModel(options.model);
5600
+ messages = this._sanitizeMessages(messages);
5585
5601
  if (provider === "anthropic") {
5586
5602
  return this._anthropicText(messages, modelName, options);
5587
5603
  } else if (provider === "groq") {
@@ -5597,6 +5613,7 @@ var LLM = class {
5597
5613
  /* Stream response from an LLM (Anthropic or Groq) */
5598
5614
  static async stream(messages, options = {}, json) {
5599
5615
  const [provider, modelName] = this._parseModel(options.model);
5616
+ messages = this._sanitizeMessages(messages);
5600
5617
  if (provider === "anthropic") {
5601
5618
  return this._anthropicStream(messages, modelName, options, json);
5602
5619
  } else if (provider === "groq") {
@@ -5612,6 +5629,7 @@ var LLM = class {
5612
5629
  /* Stream response with tool calling support (Anthropic and Gemini) */
5613
5630
  static async streamWithTools(messages, tools, toolHandler, options = {}, maxIterations = 3) {
5614
5631
  const [provider, modelName] = this._parseModel(options.model);
5632
+ messages = this._sanitizeMessages(messages);
5615
5633
  if (provider === "anthropic") {
5616
5634
  return this._anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations);
5617
5635
  } else if (provider === "gemini") {
@@ -5637,6 +5655,26 @@ var LLM = class {
5637
5655
  }
5638
5656
  return sys;
5639
5657
  }
5658
+ /**
5659
+ * Strip unpaired UTF-16 surrogates from every text field of a message set.
5660
+ *
5661
+ * A lone surrogate (from mid-pair string slicing or corrupt source data)
5662
+ * serializes to a bare `\udXXX` escape that strict JSON parsers — including
5663
+ * the one on Anthropic's API — reject with "no low surrogate in string",
5664
+ * failing the whole request. Sanitizing here, at the single boundary every
5665
+ * provider call flows through, guarantees no request can carry one.
5666
+ */
5667
+ static _sanitizeMessages(messages) {
5668
+ const sys = typeof messages.sys === "string" ? stripLoneSurrogates(messages.sys) : messages.sys.map(
5669
+ (block) => block?.type === "text" && typeof block.text === "string" ? { ...block, text: stripLoneSurrogates(block.text) } : block
5670
+ );
5671
+ return {
5672
+ ...messages,
5673
+ sys,
5674
+ user: stripLoneSurrogates(messages.user),
5675
+ ...messages.prefill !== void 0 && { prefill: stripLoneSurrogates(messages.prefill) }
5676
+ };
5677
+ }
5640
5678
  /**
5641
5679
  * Log cache usage metrics from Anthropic API response
5642
5680
  * Shows cache hits, costs, and savings
@@ -6016,12 +6054,14 @@ var LLM = class {
6016
6054
  let resultContent = typeof result === "string" ? result : JSON.stringify(result);
6017
6055
  const MAX_RESULT_LENGTH = 5e4;
6018
6056
  if (resultContent.length > MAX_RESULT_LENGTH) {
6019
- resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6057
+ resultContent = safeTruncate(resultContent, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6020
6058
  }
6021
6059
  return {
6022
6060
  type: "tool_result",
6023
6061
  tool_use_id: toolUse.id,
6024
- content: resultContent
6062
+ // Final safety net: tool results carry source data and are built
6063
+ // mid-loop (after entry-point sanitize), so strip lone surrogates here.
6064
+ content: stripLoneSurrogates(resultContent)
6025
6065
  };
6026
6066
  } catch (error) {
6027
6067
  return {
@@ -6536,11 +6576,12 @@ var LLM = class {
6536
6576
  let resultContent = typeof result2 === "string" ? result2 : JSON.stringify(result2);
6537
6577
  const MAX_RESULT_LENGTH = 5e4;
6538
6578
  if (resultContent.length > MAX_RESULT_LENGTH) {
6539
- resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6579
+ resultContent = safeTruncate(resultContent, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6540
6580
  }
6541
6581
  return {
6542
6582
  name: fc.name,
6543
- response: { result: resultContent }
6583
+ // Final safety net: strip lone surrogates from source-data results.
6584
+ response: { result: stripLoneSurrogates(resultContent) }
6544
6585
  };
6545
6586
  } catch (error) {
6546
6587
  return {
@@ -6813,12 +6854,12 @@ var LLM = class {
6813
6854
  result = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult);
6814
6855
  const MAX_RESULT_LENGTH = 5e4;
6815
6856
  if (result.length > MAX_RESULT_LENGTH) {
6816
- result = result.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + result.length + " total]";
6857
+ result = safeTruncate(result, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + result.length + " total]";
6817
6858
  }
6818
6859
  } catch (error) {
6819
6860
  result = JSON.stringify({ error: error instanceof Error ? error.message : String(error) });
6820
6861
  }
6821
- return { role: "tool", tool_call_id: tc.id, content: result };
6862
+ return { role: "tool", tool_call_id: tc.id, content: stripLoneSurrogates(result) };
6822
6863
  }));
6823
6864
  toolCallResults.forEach((r) => conversationMessages.push(r));
6824
6865
  }
@@ -7922,7 +7963,7 @@ Execution time: ${metadata.executionTimeMs}ms
7922
7963
  const truncatedRow = {};
7923
7964
  for (const [key, value] of Object.entries(row)) {
7924
7965
  if (typeof value === "string" && value.length > 200) {
7925
- truncatedRow[key] = value.substring(0, 200) + "...";
7966
+ truncatedRow[key] = safeTruncate(value, 200) + "...";
7926
7967
  } else {
7927
7968
  truncatedRow[key] = value;
7928
7969
  }
@@ -15059,7 +15100,7 @@ Fixed SQL query:`;
15059
15100
  }
15060
15101
 
15061
15102
  // src/dashComp/create-filter.ts
15062
- async function createFilterWithLLM(prompt, components, existingComponents, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, tools, dashCompModels, collections) {
15103
+ async function createFilterWithLLM(prompt, components, existingComponents, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, tools, dashCompModels, collections, userId) {
15063
15104
  const errors = [];
15064
15105
  try {
15065
15106
  const filterComponents = components.filter((c) => c.type.startsWith("Filter"));
@@ -15079,6 +15120,25 @@ async function createFilterWithLLM(prompt, components, existingComponents, anthr
15079
15120
  schemaDoc = schema.generateSchemaDocumentation();
15080
15121
  }
15081
15122
  const databaseRules = await promptLoader.loadDatabaseRules();
15123
+ let globalKnowledgeBase = "No global knowledge base available.";
15124
+ let knowledgeBaseContext = "No additional knowledge base context available.";
15125
+ if (collections) {
15126
+ const kbResult = await knowledge_base_default.getAllKnowledgeBase({
15127
+ prompt,
15128
+ collections,
15129
+ userId,
15130
+ topK: KNOWLEDGE_BASE_TOP_K
15131
+ });
15132
+ globalKnowledgeBase = kbResult.globalContext || globalKnowledgeBase;
15133
+ const dynamicParts = [];
15134
+ if (kbResult.userContext) {
15135
+ dynamicParts.push("## User-Specific Knowledge Base\n" + kbResult.userContext);
15136
+ }
15137
+ if (kbResult.queryContext) {
15138
+ dynamicParts.push("## Relevant Knowledge Base (Query-Matched)\n" + kbResult.queryContext);
15139
+ }
15140
+ knowledgeBaseContext = dynamicParts.join("\n\n") || knowledgeBaseContext;
15141
+ }
15082
15142
  const prompts = await promptLoader.loadPrompts("dash-filter-picker", {
15083
15143
  USER_PROMPT: prompt,
15084
15144
  AVAILABLE_COMPONENTS: formatComponentsForPrompt(filterComponents),
@@ -15086,8 +15146,12 @@ async function createFilterWithLLM(prompt, components, existingComponents, anthr
15086
15146
  SCHEMA_DOC: schemaDoc || "No database schema available",
15087
15147
  DATABASE_RULES: databaseRules,
15088
15148
  AVAILABLE_TOOLS: formatToolsForPrompt(tools),
15089
- CURRENT_DATETIME: getCurrentDateTimeForPrompt()
15149
+ CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
15150
+ GLOBAL_KNOWLEDGE_BASE: globalKnowledgeBase,
15151
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext
15090
15152
  });
15153
+ logger.logLLMPrompt("dashFilterPicker", "system", extractPromptText(prompts.system));
15154
+ logger.logLLMPrompt("dashFilterPicker", "user", prompts.user);
15091
15155
  logger.debug("[DASH_COMP_REQ:FILTER] Loaded dash-filter-picker prompts");
15092
15156
  const { apiKey, model } = getApiKeyAndModel(
15093
15157
  anthropicApiKey,
@@ -15461,7 +15525,8 @@ var processDashCompRequest = async (data, components, _sendMessage, anthropicApi
15461
15525
  llmProviders,
15462
15526
  tools,
15463
15527
  dashCompModels,
15464
- collections
15528
+ collections,
15529
+ userId
15465
15530
  );
15466
15531
  } else {
15467
15532
  llmResponse = await pickComponentWithLLM(
@@ -15615,7 +15680,7 @@ function sendReportCompResponse(id, res, sendMessage, clientId) {
15615
15680
  }
15616
15681
 
15617
15682
  // src/reportComp/generate-report.ts
15618
- async function generateReportComponents(prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, tools, modelConfig, conversationHistory) {
15683
+ async function generateReportComponents(prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, tools, modelConfig, conversationHistory, userId) {
15619
15684
  const errors = [];
15620
15685
  const availableComponentsText = formatComponentsForPrompt2(components);
15621
15686
  const availableToolsText = formatToolsForPrompt2(tools);
@@ -15631,6 +15696,25 @@ async function generateReportComponents(prompt, components, anthropicApiKey, gro
15631
15696
  schemaDoc = schema.generateSchemaDocumentation();
15632
15697
  }
15633
15698
  const databaseRules = await promptLoader.loadDatabaseRules();
15699
+ let globalKnowledgeBase = "No global knowledge base available.";
15700
+ let knowledgeBaseContext = "No additional knowledge base context available.";
15701
+ if (collections) {
15702
+ const kbResult = await knowledge_base_default.getAllKnowledgeBase({
15703
+ prompt,
15704
+ collections,
15705
+ userId,
15706
+ topK: KNOWLEDGE_BASE_TOP_K
15707
+ });
15708
+ globalKnowledgeBase = kbResult.globalContext || globalKnowledgeBase;
15709
+ const dynamicParts = [];
15710
+ if (kbResult.userContext) {
15711
+ dynamicParts.push("## User-Specific Knowledge Base\n" + kbResult.userContext);
15712
+ }
15713
+ if (kbResult.queryContext) {
15714
+ dynamicParts.push("## Relevant Knowledge Base (Query-Matched)\n" + kbResult.queryContext);
15715
+ }
15716
+ knowledgeBaseContext = dynamicParts.join("\n\n") || knowledgeBaseContext;
15717
+ }
15634
15718
  const prompts = await promptLoader.loadPrompts("report-comp-picker", {
15635
15719
  USER_PROMPT: prompt,
15636
15720
  AVAILABLE_COMPONENTS: availableComponentsText,
@@ -15638,8 +15722,12 @@ async function generateReportComponents(prompt, components, anthropicApiKey, gro
15638
15722
  DATABASE_RULES: databaseRules,
15639
15723
  AVAILABLE_TOOLS: availableToolsText,
15640
15724
  CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
15641
- CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
15725
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
15726
+ GLOBAL_KNOWLEDGE_BASE: globalKnowledgeBase,
15727
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext
15642
15728
  });
15729
+ logger.logLLMPrompt("reportCompPicker", "system", extractPromptText(prompts.system));
15730
+ logger.logLLMPrompt("reportCompPicker", "user", prompts.user);
15643
15731
  logger.debug("[REPORT_COMP_REQ] Loaded report-comp-picker prompts with schema and tools");
15644
15732
  const { apiKey, model } = getApiKeyAndModel2(
15645
15733
  anthropicApiKey,
@@ -15864,13 +15952,21 @@ async function validateAllExternalToolQueries(components, collections, tools, mo
15864
15952
  data: {}
15865
15953
  });
15866
15954
  if (result?.success !== false && !result?.error) {
15867
- const resultData = result?.data?.data ?? result?.data ?? [];
15868
- const dataArray = Array.isArray(resultData) ? resultData : [resultData];
15955
+ const toolResult = result?.data ?? result;
15956
+ const valueKey = comp.props?.config?.valueKey;
15957
+ const isKpi = comp.type === "KPICard" || comp.name === "DynamicKPICard";
15958
+ let dataArray;
15959
+ if (isKpi && valueKey && toolResult && typeof toolResult === "object" && !Array.isArray(toolResult) && toolResult[valueKey] !== void 0) {
15960
+ dataArray = [toolResult];
15961
+ } else {
15962
+ const resultData = toolResult?.data ?? toolResult ?? [];
15963
+ dataArray = Array.isArray(resultData) ? resultData : [resultData];
15964
+ }
15869
15965
  if (!comp.props.config) {
15870
15966
  comp.props.config = {};
15871
15967
  }
15872
15968
  comp.props.config.data = dataArray;
15873
- logger.info(`[REPORT_COMP_REQ] \u2713 ${comp.name} prefetched ${dataArray.length} rows (non-SQL tool)`);
15969
+ logger.info(`[REPORT_COMP_REQ] \u2713 ${comp.name} prefetched ${dataArray.length} ${dataArray.length === 1 && isKpi ? "aggregate" : "rows"} (non-SQL tool)`);
15874
15970
  }
15875
15971
  } catch (err) {
15876
15972
  logger.warn(`[REPORT_COMP_REQ] \u26A0 ${comp.name} non-SQL prefetch failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -16010,7 +16106,8 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
16010
16106
  collections,
16011
16107
  tools,
16012
16108
  modelConfig,
16013
- conversationHistory
16109
+ conversationHistory,
16110
+ userId
16014
16111
  );
16015
16112
  if (llmResponse.success && reportId && prompt) {
16016
16113
  const comps = llmResponse.data?.components;