@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.d.mts CHANGED
@@ -1708,6 +1708,16 @@ declare class LLM {
1708
1708
  * @returns Normalized system prompt for Anthropic API
1709
1709
  */
1710
1710
  private static _normalizeSystemPrompt;
1711
+ /**
1712
+ * Strip unpaired UTF-16 surrogates from every text field of a message set.
1713
+ *
1714
+ * A lone surrogate (from mid-pair string slicing or corrupt source data)
1715
+ * serializes to a bare `\udXXX` escape that strict JSON parsers — including
1716
+ * the one on Anthropic's API — reject with "no low surrogate in string",
1717
+ * failing the whole request. Sanitizing here, at the single boundary every
1718
+ * provider call flows through, guarantees no request can carry one.
1719
+ */
1720
+ private static _sanitizeMessages;
1711
1721
  /**
1712
1722
  * Log cache usage metrics from Anthropic API response
1713
1723
  * Shows cache hits, costs, and savings
package/dist/index.d.ts CHANGED
@@ -1708,6 +1708,16 @@ declare class LLM {
1708
1708
  * @returns Normalized system prompt for Anthropic API
1709
1709
  */
1710
1710
  private static _normalizeSystemPrompt;
1711
+ /**
1712
+ * Strip unpaired UTF-16 surrogates from every text field of a message set.
1713
+ *
1714
+ * A lone surrogate (from mid-pair string slicing or corrupt source data)
1715
+ * serializes to a bare `\udXXX` escape that strict JSON parsers — including
1716
+ * the one on Anthropic's API — reject with "no low surrogate in string",
1717
+ * failing the whole request. Sanitizing here, at the single boundary every
1718
+ * provider call flows through, guarantees no request can carry one.
1719
+ */
1720
+ private static _sanitizeMessages;
1711
1721
  /**
1712
1722
  * Log cache usage metrics from Anthropic API response
1713
1723
  * Shows cache hits, costs, and savings
package/dist/index.js CHANGED
@@ -1738,6 +1738,21 @@ var QueryCache = class {
1738
1738
  };
1739
1739
  var queryCache = new QueryCache();
1740
1740
 
1741
+ // src/utils/surrogate.ts
1742
+ var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1743
+ function stripLoneSurrogates(value) {
1744
+ if (typeof value !== "string") return value;
1745
+ if (!/[\uD800-\uDFFF]/.test(value)) return value;
1746
+ return value.replace(LONE_SURROGATE_RE, "\uFFFD");
1747
+ }
1748
+ function safeTruncate(text, maxUnits) {
1749
+ if (typeof text !== "string" || text.length <= maxUnits || maxUnits < 0) return text;
1750
+ let end = maxUnits;
1751
+ const lastCode = text.charCodeAt(end - 1);
1752
+ if (lastCode >= 55296 && lastCode <= 56319) end -= 1;
1753
+ return text.slice(0, end);
1754
+ }
1755
+
1741
1756
  // src/userResponse/llm-result-truncator.ts
1742
1757
  var DEFAULT_MAX_ROWS = 10;
1743
1758
  var DEFAULT_MAX_CHARS_PER_FIELD = 500;
@@ -1780,12 +1795,12 @@ function isDateString(value) {
1780
1795
  }
1781
1796
  function truncateTextField(value, maxLength) {
1782
1797
  if (value.length <= maxLength) {
1783
- return { text: value, wasTruncated: false };
1798
+ return { text: stripLoneSurrogates(value), wasTruncated: false };
1784
1799
  }
1785
- const truncated = value.substring(0, maxLength);
1786
- const remaining = value.length - maxLength;
1800
+ const truncated = safeTruncate(value, maxLength);
1801
+ const remaining = value.length - truncated.length;
1787
1802
  return {
1788
- text: `${truncated}... (${remaining} more chars)`,
1803
+ text: `${stripLoneSurrogates(truncated)}... (${remaining} more chars)`,
1789
1804
  wasTruncated: true
1790
1805
  };
1791
1806
  }
@@ -5642,6 +5657,7 @@ var LLM = class {
5642
5657
  /* Get a complete text response from an LLM (Anthropic or Groq) */
5643
5658
  static async text(messages, options = {}) {
5644
5659
  const [provider, modelName] = this._parseModel(options.model);
5660
+ messages = this._sanitizeMessages(messages);
5645
5661
  if (provider === "anthropic") {
5646
5662
  return this._anthropicText(messages, modelName, options);
5647
5663
  } else if (provider === "groq") {
@@ -5657,6 +5673,7 @@ var LLM = class {
5657
5673
  /* Stream response from an LLM (Anthropic or Groq) */
5658
5674
  static async stream(messages, options = {}, json) {
5659
5675
  const [provider, modelName] = this._parseModel(options.model);
5676
+ messages = this._sanitizeMessages(messages);
5660
5677
  if (provider === "anthropic") {
5661
5678
  return this._anthropicStream(messages, modelName, options, json);
5662
5679
  } else if (provider === "groq") {
@@ -5672,6 +5689,7 @@ var LLM = class {
5672
5689
  /* Stream response with tool calling support (Anthropic and Gemini) */
5673
5690
  static async streamWithTools(messages, tools, toolHandler, options = {}, maxIterations = 3) {
5674
5691
  const [provider, modelName] = this._parseModel(options.model);
5692
+ messages = this._sanitizeMessages(messages);
5675
5693
  if (provider === "anthropic") {
5676
5694
  return this._anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations);
5677
5695
  } else if (provider === "gemini") {
@@ -5697,6 +5715,26 @@ var LLM = class {
5697
5715
  }
5698
5716
  return sys;
5699
5717
  }
5718
+ /**
5719
+ * Strip unpaired UTF-16 surrogates from every text field of a message set.
5720
+ *
5721
+ * A lone surrogate (from mid-pair string slicing or corrupt source data)
5722
+ * serializes to a bare `\udXXX` escape that strict JSON parsers — including
5723
+ * the one on Anthropic's API — reject with "no low surrogate in string",
5724
+ * failing the whole request. Sanitizing here, at the single boundary every
5725
+ * provider call flows through, guarantees no request can carry one.
5726
+ */
5727
+ static _sanitizeMessages(messages) {
5728
+ const sys = typeof messages.sys === "string" ? stripLoneSurrogates(messages.sys) : messages.sys.map(
5729
+ (block) => block?.type === "text" && typeof block.text === "string" ? { ...block, text: stripLoneSurrogates(block.text) } : block
5730
+ );
5731
+ return {
5732
+ ...messages,
5733
+ sys,
5734
+ user: stripLoneSurrogates(messages.user),
5735
+ ...messages.prefill !== void 0 && { prefill: stripLoneSurrogates(messages.prefill) }
5736
+ };
5737
+ }
5700
5738
  /**
5701
5739
  * Log cache usage metrics from Anthropic API response
5702
5740
  * Shows cache hits, costs, and savings
@@ -6076,12 +6114,14 @@ var LLM = class {
6076
6114
  let resultContent = typeof result === "string" ? result : JSON.stringify(result);
6077
6115
  const MAX_RESULT_LENGTH = 5e4;
6078
6116
  if (resultContent.length > MAX_RESULT_LENGTH) {
6079
- resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6117
+ resultContent = safeTruncate(resultContent, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6080
6118
  }
6081
6119
  return {
6082
6120
  type: "tool_result",
6083
6121
  tool_use_id: toolUse.id,
6084
- content: resultContent
6122
+ // Final safety net: tool results carry source data and are built
6123
+ // mid-loop (after entry-point sanitize), so strip lone surrogates here.
6124
+ content: stripLoneSurrogates(resultContent)
6085
6125
  };
6086
6126
  } catch (error) {
6087
6127
  return {
@@ -6596,11 +6636,12 @@ var LLM = class {
6596
6636
  let resultContent = typeof result2 === "string" ? result2 : JSON.stringify(result2);
6597
6637
  const MAX_RESULT_LENGTH = 5e4;
6598
6638
  if (resultContent.length > MAX_RESULT_LENGTH) {
6599
- resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6639
+ resultContent = safeTruncate(resultContent, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
6600
6640
  }
6601
6641
  return {
6602
6642
  name: fc.name,
6603
- response: { result: resultContent }
6643
+ // Final safety net: strip lone surrogates from source-data results.
6644
+ response: { result: stripLoneSurrogates(resultContent) }
6604
6645
  };
6605
6646
  } catch (error) {
6606
6647
  return {
@@ -6873,12 +6914,12 @@ var LLM = class {
6873
6914
  result = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult);
6874
6915
  const MAX_RESULT_LENGTH = 5e4;
6875
6916
  if (result.length > MAX_RESULT_LENGTH) {
6876
- result = result.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + result.length + " total]";
6917
+ result = safeTruncate(result, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + result.length + " total]";
6877
6918
  }
6878
6919
  } catch (error) {
6879
6920
  result = JSON.stringify({ error: error instanceof Error ? error.message : String(error) });
6880
6921
  }
6881
- return { role: "tool", tool_call_id: tc.id, content: result };
6922
+ return { role: "tool", tool_call_id: tc.id, content: stripLoneSurrogates(result) };
6882
6923
  }));
6883
6924
  toolCallResults.forEach((r) => conversationMessages.push(r));
6884
6925
  }
@@ -7982,7 +8023,7 @@ Execution time: ${metadata.executionTimeMs}ms
7982
8023
  const truncatedRow = {};
7983
8024
  for (const [key, value] of Object.entries(row)) {
7984
8025
  if (typeof value === "string" && value.length > 200) {
7985
- truncatedRow[key] = value.substring(0, 200) + "...";
8026
+ truncatedRow[key] = safeTruncate(value, 200) + "...";
7986
8027
  } else {
7987
8028
  truncatedRow[key] = value;
7988
8029
  }
@@ -15119,7 +15160,7 @@ Fixed SQL query:`;
15119
15160
  }
15120
15161
 
15121
15162
  // src/dashComp/create-filter.ts
15122
- async function createFilterWithLLM(prompt, components, existingComponents, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, tools, dashCompModels, collections) {
15163
+ async function createFilterWithLLM(prompt, components, existingComponents, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, tools, dashCompModels, collections, userId) {
15123
15164
  const errors = [];
15124
15165
  try {
15125
15166
  const filterComponents = components.filter((c) => c.type.startsWith("Filter"));
@@ -15139,6 +15180,25 @@ async function createFilterWithLLM(prompt, components, existingComponents, anthr
15139
15180
  schemaDoc = schema.generateSchemaDocumentation();
15140
15181
  }
15141
15182
  const databaseRules = await promptLoader.loadDatabaseRules();
15183
+ let globalKnowledgeBase = "No global knowledge base available.";
15184
+ let knowledgeBaseContext = "No additional knowledge base context available.";
15185
+ if (collections) {
15186
+ const kbResult = await knowledge_base_default.getAllKnowledgeBase({
15187
+ prompt,
15188
+ collections,
15189
+ userId,
15190
+ topK: KNOWLEDGE_BASE_TOP_K
15191
+ });
15192
+ globalKnowledgeBase = kbResult.globalContext || globalKnowledgeBase;
15193
+ const dynamicParts = [];
15194
+ if (kbResult.userContext) {
15195
+ dynamicParts.push("## User-Specific Knowledge Base\n" + kbResult.userContext);
15196
+ }
15197
+ if (kbResult.queryContext) {
15198
+ dynamicParts.push("## Relevant Knowledge Base (Query-Matched)\n" + kbResult.queryContext);
15199
+ }
15200
+ knowledgeBaseContext = dynamicParts.join("\n\n") || knowledgeBaseContext;
15201
+ }
15142
15202
  const prompts = await promptLoader.loadPrompts("dash-filter-picker", {
15143
15203
  USER_PROMPT: prompt,
15144
15204
  AVAILABLE_COMPONENTS: formatComponentsForPrompt(filterComponents),
@@ -15146,8 +15206,12 @@ async function createFilterWithLLM(prompt, components, existingComponents, anthr
15146
15206
  SCHEMA_DOC: schemaDoc || "No database schema available",
15147
15207
  DATABASE_RULES: databaseRules,
15148
15208
  AVAILABLE_TOOLS: formatToolsForPrompt(tools),
15149
- CURRENT_DATETIME: getCurrentDateTimeForPrompt()
15209
+ CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
15210
+ GLOBAL_KNOWLEDGE_BASE: globalKnowledgeBase,
15211
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext
15150
15212
  });
15213
+ logger.logLLMPrompt("dashFilterPicker", "system", extractPromptText(prompts.system));
15214
+ logger.logLLMPrompt("dashFilterPicker", "user", prompts.user);
15151
15215
  logger.debug("[DASH_COMP_REQ:FILTER] Loaded dash-filter-picker prompts");
15152
15216
  const { apiKey, model } = getApiKeyAndModel(
15153
15217
  anthropicApiKey,
@@ -15521,7 +15585,8 @@ var processDashCompRequest = async (data, components, _sendMessage, anthropicApi
15521
15585
  llmProviders,
15522
15586
  tools,
15523
15587
  dashCompModels,
15524
- collections
15588
+ collections,
15589
+ userId
15525
15590
  );
15526
15591
  } else {
15527
15592
  llmResponse = await pickComponentWithLLM(
@@ -15675,7 +15740,7 @@ function sendReportCompResponse(id, res, sendMessage, clientId) {
15675
15740
  }
15676
15741
 
15677
15742
  // src/reportComp/generate-report.ts
15678
- async function generateReportComponents(prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, tools, modelConfig, conversationHistory) {
15743
+ async function generateReportComponents(prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, tools, modelConfig, conversationHistory, userId) {
15679
15744
  const errors = [];
15680
15745
  const availableComponentsText = formatComponentsForPrompt2(components);
15681
15746
  const availableToolsText = formatToolsForPrompt2(tools);
@@ -15691,6 +15756,25 @@ async function generateReportComponents(prompt, components, anthropicApiKey, gro
15691
15756
  schemaDoc = schema.generateSchemaDocumentation();
15692
15757
  }
15693
15758
  const databaseRules = await promptLoader.loadDatabaseRules();
15759
+ let globalKnowledgeBase = "No global knowledge base available.";
15760
+ let knowledgeBaseContext = "No additional knowledge base context available.";
15761
+ if (collections) {
15762
+ const kbResult = await knowledge_base_default.getAllKnowledgeBase({
15763
+ prompt,
15764
+ collections,
15765
+ userId,
15766
+ topK: KNOWLEDGE_BASE_TOP_K
15767
+ });
15768
+ globalKnowledgeBase = kbResult.globalContext || globalKnowledgeBase;
15769
+ const dynamicParts = [];
15770
+ if (kbResult.userContext) {
15771
+ dynamicParts.push("## User-Specific Knowledge Base\n" + kbResult.userContext);
15772
+ }
15773
+ if (kbResult.queryContext) {
15774
+ dynamicParts.push("## Relevant Knowledge Base (Query-Matched)\n" + kbResult.queryContext);
15775
+ }
15776
+ knowledgeBaseContext = dynamicParts.join("\n\n") || knowledgeBaseContext;
15777
+ }
15694
15778
  const prompts = await promptLoader.loadPrompts("report-comp-picker", {
15695
15779
  USER_PROMPT: prompt,
15696
15780
  AVAILABLE_COMPONENTS: availableComponentsText,
@@ -15698,8 +15782,12 @@ async function generateReportComponents(prompt, components, anthropicApiKey, gro
15698
15782
  DATABASE_RULES: databaseRules,
15699
15783
  AVAILABLE_TOOLS: availableToolsText,
15700
15784
  CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
15701
- CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
15785
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
15786
+ GLOBAL_KNOWLEDGE_BASE: globalKnowledgeBase,
15787
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext
15702
15788
  });
15789
+ logger.logLLMPrompt("reportCompPicker", "system", extractPromptText(prompts.system));
15790
+ logger.logLLMPrompt("reportCompPicker", "user", prompts.user);
15703
15791
  logger.debug("[REPORT_COMP_REQ] Loaded report-comp-picker prompts with schema and tools");
15704
15792
  const { apiKey, model } = getApiKeyAndModel2(
15705
15793
  anthropicApiKey,
@@ -15924,13 +16012,21 @@ async function validateAllExternalToolQueries(components, collections, tools, mo
15924
16012
  data: {}
15925
16013
  });
15926
16014
  if (result?.success !== false && !result?.error) {
15927
- const resultData = result?.data?.data ?? result?.data ?? [];
15928
- const dataArray = Array.isArray(resultData) ? resultData : [resultData];
16015
+ const toolResult = result?.data ?? result;
16016
+ const valueKey = comp.props?.config?.valueKey;
16017
+ const isKpi = comp.type === "KPICard" || comp.name === "DynamicKPICard";
16018
+ let dataArray;
16019
+ if (isKpi && valueKey && toolResult && typeof toolResult === "object" && !Array.isArray(toolResult) && toolResult[valueKey] !== void 0) {
16020
+ dataArray = [toolResult];
16021
+ } else {
16022
+ const resultData = toolResult?.data ?? toolResult ?? [];
16023
+ dataArray = Array.isArray(resultData) ? resultData : [resultData];
16024
+ }
15929
16025
  if (!comp.props.config) {
15930
16026
  comp.props.config = {};
15931
16027
  }
15932
16028
  comp.props.config.data = dataArray;
15933
- logger.info(`[REPORT_COMP_REQ] \u2713 ${comp.name} prefetched ${dataArray.length} rows (non-SQL tool)`);
16029
+ logger.info(`[REPORT_COMP_REQ] \u2713 ${comp.name} prefetched ${dataArray.length} ${dataArray.length === 1 && isKpi ? "aggregate" : "rows"} (non-SQL tool)`);
15934
16030
  }
15935
16031
  } catch (err) {
15936
16032
  logger.warn(`[REPORT_COMP_REQ] \u26A0 ${comp.name} non-SQL prefetch failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -16070,7 +16166,8 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
16070
16166
  collections,
16071
16167
  tools,
16072
16168
  modelConfig,
16073
- conversationHistory
16169
+ conversationHistory,
16170
+ userId
16074
16171
  );
16075
16172
  if (llmResponse.success && reportId && prompt) {
16076
16173
  const comps = llmResponse.data?.components;