@superatomai/sdk-node 0.0.71 → 0.0.74

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.js CHANGED
@@ -34,7 +34,6 @@ __export(index_exports, {
34
34
  CONTEXT_CONFIG: () => CONTEXT_CONFIG,
35
35
  CleanupService: () => CleanupService,
36
36
  LLM: () => LLM,
37
- SDK_VERSION: () => SDK_VERSION,
38
37
  STORAGE_CONFIG: () => STORAGE_CONFIG,
39
38
  SuperatomSDK: () => SuperatomSDK,
40
39
  Thread: () => Thread,
@@ -1516,6 +1515,351 @@ var QueryCache = class {
1516
1515
  };
1517
1516
  var queryCache = new QueryCache();
1518
1517
 
1518
+ // src/userResponse/llm-result-truncator.ts
1519
+ var DEFAULT_MAX_ROWS = 10;
1520
+ var DEFAULT_MAX_CHARS_PER_FIELD = 500;
1521
+ function inferFieldType(value) {
1522
+ if (value === null || value === void 0) {
1523
+ return "null";
1524
+ }
1525
+ if (typeof value === "string") {
1526
+ if (isDateString(value)) {
1527
+ return "date";
1528
+ }
1529
+ return "string";
1530
+ }
1531
+ if (typeof value === "number") {
1532
+ return "number";
1533
+ }
1534
+ if (typeof value === "boolean") {
1535
+ return "boolean";
1536
+ }
1537
+ if (Array.isArray(value)) {
1538
+ return "array";
1539
+ }
1540
+ if (typeof value === "object") {
1541
+ return "object";
1542
+ }
1543
+ return "unknown";
1544
+ }
1545
+ function isDateString(value) {
1546
+ const datePatterns = [
1547
+ /^\d{4}-\d{2}-\d{2}$/,
1548
+ // YYYY-MM-DD
1549
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
1550
+ // ISO 8601
1551
+ /^\d{2}\/\d{2}\/\d{4}$/,
1552
+ // MM/DD/YYYY
1553
+ /^\d{4}\/\d{2}\/\d{2}$/
1554
+ // YYYY/MM/DD
1555
+ ];
1556
+ return datePatterns.some((pattern) => pattern.test(value));
1557
+ }
1558
+ function truncateTextField(value, maxLength) {
1559
+ if (value.length <= maxLength) {
1560
+ return { text: value, wasTruncated: false };
1561
+ }
1562
+ const truncated = value.substring(0, maxLength);
1563
+ const remaining = value.length - maxLength;
1564
+ return {
1565
+ text: `${truncated}... (${remaining} more chars)`,
1566
+ wasTruncated: true
1567
+ };
1568
+ }
1569
+ function truncateFieldValue(value, maxCharsPerField) {
1570
+ if (value === null || value === void 0) {
1571
+ return { value, wasTruncated: false };
1572
+ }
1573
+ if (typeof value === "number" || typeof value === "boolean") {
1574
+ return { value, wasTruncated: false };
1575
+ }
1576
+ if (typeof value === "string") {
1577
+ const result2 = truncateTextField(value, maxCharsPerField);
1578
+ return { value: result2.text, wasTruncated: result2.wasTruncated };
1579
+ }
1580
+ if (Array.isArray(value)) {
1581
+ if (value.length === 0) {
1582
+ return { value: [], wasTruncated: false };
1583
+ }
1584
+ const preview = value.slice(0, 3);
1585
+ const hasMore = value.length > 3;
1586
+ return {
1587
+ value: hasMore ? `[${preview.join(", ")}... (${value.length} items)]` : value,
1588
+ wasTruncated: hasMore
1589
+ };
1590
+ }
1591
+ if (typeof value === "object") {
1592
+ const jsonStr = JSON.stringify(value);
1593
+ const result2 = truncateTextField(jsonStr, maxCharsPerField);
1594
+ return { value: result2.text, wasTruncated: result2.wasTruncated };
1595
+ }
1596
+ const strValue = String(value);
1597
+ const result = truncateTextField(strValue, maxCharsPerField);
1598
+ return { value: result.text, wasTruncated: result.wasTruncated };
1599
+ }
1600
+ function truncateRow(row, maxCharsPerField) {
1601
+ const truncatedRow = {};
1602
+ const truncatedFields = /* @__PURE__ */ new Set();
1603
+ for (const [key, value] of Object.entries(row)) {
1604
+ const result = truncateFieldValue(value, maxCharsPerField);
1605
+ truncatedRow[key] = result.value;
1606
+ if (result.wasTruncated) {
1607
+ truncatedFields.add(key);
1608
+ }
1609
+ }
1610
+ return { row: truncatedRow, truncatedFields };
1611
+ }
1612
+ function extractMetadataFromObject(obj, dataKey, maxCharsPerField) {
1613
+ const metadata = {};
1614
+ const truncatedFields = /* @__PURE__ */ new Set();
1615
+ for (const [key, value] of Object.entries(obj)) {
1616
+ if (key === dataKey) {
1617
+ continue;
1618
+ }
1619
+ if (Array.isArray(value)) {
1620
+ continue;
1621
+ }
1622
+ const result = truncateFieldValue(value, maxCharsPerField);
1623
+ metadata[key] = result.value;
1624
+ if (result.wasTruncated) {
1625
+ truncatedFields.add(key);
1626
+ }
1627
+ }
1628
+ return { metadata, truncatedFields };
1629
+ }
1630
+ function extractSchema(data, truncatedFields = /* @__PURE__ */ new Set()) {
1631
+ if (!data || data.length === 0) {
1632
+ return [];
1633
+ }
1634
+ const firstRow = data[0];
1635
+ const schema2 = [];
1636
+ for (const [name, value] of Object.entries(firstRow)) {
1637
+ schema2.push({
1638
+ name,
1639
+ type: inferFieldType(value),
1640
+ truncated: truncatedFields.has(name) ? true : void 0
1641
+ });
1642
+ }
1643
+ return schema2;
1644
+ }
1645
+ function truncateDataArray(data, options = {}) {
1646
+ const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
1647
+ const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
1648
+ if (!data || !Array.isArray(data)) {
1649
+ return {
1650
+ data: [],
1651
+ totalRecords: 0,
1652
+ recordsShown: 0,
1653
+ truncatedFields: /* @__PURE__ */ new Set()
1654
+ };
1655
+ }
1656
+ const totalRecords = data.length;
1657
+ const rowsToProcess = data.slice(0, maxRows);
1658
+ const truncatedData = [];
1659
+ const allTruncatedFields = /* @__PURE__ */ new Set();
1660
+ for (const row of rowsToProcess) {
1661
+ const { row: truncatedRow, truncatedFields } = truncateRow(row, maxCharsPerField);
1662
+ truncatedData.push(truncatedRow);
1663
+ for (const field of truncatedFields) {
1664
+ allTruncatedFields.add(field);
1665
+ }
1666
+ }
1667
+ return {
1668
+ data: truncatedData,
1669
+ totalRecords,
1670
+ recordsShown: truncatedData.length,
1671
+ truncatedFields: allTruncatedFields
1672
+ };
1673
+ }
1674
+ function buildTruncationNote(totalRecords, recordsShown, truncatedFields, maxCharsPerField, sourceName) {
1675
+ const parts = [];
1676
+ if (totalRecords > recordsShown) {
1677
+ const source = sourceName ? ` from ${sourceName}` : "";
1678
+ parts.push(`Showing ${recordsShown} of ${totalRecords} total records${source}`);
1679
+ }
1680
+ if (truncatedFields.size > 0) {
1681
+ const fieldList = Array.from(truncatedFields).join(", ");
1682
+ parts.push(`Fields truncated to ${maxCharsPerField} chars: ${fieldList}`);
1683
+ }
1684
+ return parts.length > 0 ? parts.join(". ") + "." : null;
1685
+ }
1686
+ function formatQueryResultForLLM(data, options = {}) {
1687
+ const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
1688
+ if (!Array.isArray(data)) {
1689
+ if (data !== null && data !== void 0) {
1690
+ return {
1691
+ summary: {
1692
+ totalRecords: 1,
1693
+ recordsShown: 1,
1694
+ schema: [{ name: "result", type: inferFieldType(data) }]
1695
+ },
1696
+ data: [{ result: data }],
1697
+ truncationNote: null
1698
+ };
1699
+ }
1700
+ return {
1701
+ summary: {
1702
+ totalRecords: 0,
1703
+ recordsShown: 0,
1704
+ schema: []
1705
+ },
1706
+ data: [],
1707
+ truncationNote: null
1708
+ };
1709
+ }
1710
+ const {
1711
+ data: truncatedData,
1712
+ totalRecords,
1713
+ recordsShown,
1714
+ truncatedFields
1715
+ } = truncateDataArray(data, options);
1716
+ const schema2 = extractSchema(truncatedData, truncatedFields);
1717
+ const truncationNote = buildTruncationNote(
1718
+ totalRecords,
1719
+ recordsShown,
1720
+ truncatedFields,
1721
+ maxCharsPerField,
1722
+ "query"
1723
+ );
1724
+ return {
1725
+ summary: {
1726
+ totalRecords,
1727
+ recordsShown,
1728
+ schema: schema2
1729
+ },
1730
+ data: truncatedData,
1731
+ truncationNote
1732
+ };
1733
+ }
1734
+ function formatToolResultForLLM(result, options = {}) {
1735
+ const { toolName, toolLimit } = options;
1736
+ const effectiveMaxRows = toolLimit ?? options.maxRows ?? DEFAULT_MAX_ROWS;
1737
+ const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
1738
+ if (result === null || result === void 0) {
1739
+ return {
1740
+ toolName,
1741
+ summary: {
1742
+ totalRecords: 0,
1743
+ recordsShown: 0,
1744
+ schema: []
1745
+ },
1746
+ data: [],
1747
+ truncationNote: null
1748
+ };
1749
+ }
1750
+ if (typeof result === "string") {
1751
+ const { text, wasTruncated } = truncateTextField(result, maxCharsPerField);
1752
+ return {
1753
+ toolName,
1754
+ summary: {
1755
+ totalRecords: 1,
1756
+ recordsShown: 1,
1757
+ schema: [{ name: "result", type: "string", truncated: wasTruncated || void 0 }]
1758
+ },
1759
+ data: [{ result: text }],
1760
+ truncationNote: wasTruncated ? `Result truncated to ${maxCharsPerField} chars.` : null
1761
+ };
1762
+ }
1763
+ if (Array.isArray(result)) {
1764
+ const {
1765
+ data: truncatedData,
1766
+ totalRecords,
1767
+ recordsShown,
1768
+ truncatedFields
1769
+ } = truncateDataArray(result, {
1770
+ maxRows: effectiveMaxRows,
1771
+ maxCharsPerField
1772
+ });
1773
+ const schema2 = extractSchema(truncatedData, truncatedFields);
1774
+ const truncationNote = buildTruncationNote(
1775
+ totalRecords,
1776
+ recordsShown,
1777
+ truncatedFields,
1778
+ maxCharsPerField,
1779
+ toolName
1780
+ );
1781
+ return {
1782
+ toolName,
1783
+ summary: {
1784
+ totalRecords,
1785
+ recordsShown,
1786
+ schema: schema2
1787
+ },
1788
+ data: truncatedData,
1789
+ truncationNote
1790
+ };
1791
+ }
1792
+ if (typeof result === "object") {
1793
+ const objResult = result;
1794
+ const dataWrapperKeys = ["data", "results", "items", "records", "rows", "list"];
1795
+ for (const key of dataWrapperKeys) {
1796
+ if (Array.isArray(objResult[key])) {
1797
+ const innerData = objResult[key];
1798
+ const {
1799
+ data: truncatedData,
1800
+ totalRecords,
1801
+ recordsShown,
1802
+ truncatedFields: dataTruncatedFields
1803
+ } = truncateDataArray(innerData, {
1804
+ maxRows: effectiveMaxRows,
1805
+ maxCharsPerField
1806
+ });
1807
+ const {
1808
+ metadata,
1809
+ truncatedFields: metadataTruncatedFields
1810
+ } = extractMetadataFromObject(objResult, key, maxCharsPerField);
1811
+ const allTruncatedFields = /* @__PURE__ */ new Set([...dataTruncatedFields, ...metadataTruncatedFields]);
1812
+ const schema3 = extractSchema(truncatedData, dataTruncatedFields);
1813
+ const truncationNote2 = buildTruncationNote(
1814
+ totalRecords,
1815
+ recordsShown,
1816
+ allTruncatedFields,
1817
+ maxCharsPerField,
1818
+ toolName
1819
+ );
1820
+ const hasMetadata = Object.keys(metadata).length > 0;
1821
+ return {
1822
+ toolName,
1823
+ summary: {
1824
+ totalRecords,
1825
+ recordsShown,
1826
+ schema: schema3
1827
+ },
1828
+ ...hasMetadata && { metadata },
1829
+ data: truncatedData,
1830
+ truncationNote: truncationNote2
1831
+ };
1832
+ }
1833
+ }
1834
+ const { row: truncatedRow, truncatedFields } = truncateRow(objResult, maxCharsPerField);
1835
+ const schema2 = extractSchema([truncatedRow], truncatedFields);
1836
+ const truncationNote = truncatedFields.size > 0 ? `Fields truncated to ${maxCharsPerField} chars: ${Array.from(truncatedFields).join(", ")}.` : null;
1837
+ return {
1838
+ toolName,
1839
+ summary: {
1840
+ totalRecords: 1,
1841
+ recordsShown: 1,
1842
+ schema: schema2
1843
+ },
1844
+ data: [truncatedRow],
1845
+ truncationNote
1846
+ };
1847
+ }
1848
+ return {
1849
+ toolName,
1850
+ summary: {
1851
+ totalRecords: 1,
1852
+ recordsShown: 1,
1853
+ schema: [{ name: "result", type: inferFieldType(result) }]
1854
+ },
1855
+ data: [{ result }],
1856
+ truncationNote: null
1857
+ };
1858
+ }
1859
+ function formatResultAsString(formattedResult) {
1860
+ return JSON.stringify(formattedResult, null, 2);
1861
+ }
1862
+
1519
1863
  // src/handlers/data-request.ts
1520
1864
  function getQueryCacheKey(query) {
1521
1865
  if (typeof query === "string") {
@@ -1597,15 +1941,25 @@ async function handleDataRequest(data, collections, sendMessage) {
1597
1941
  }
1598
1942
  }
1599
1943
  if (uiBlock) {
1944
+ const formattedResult = formatToolResultForLLM(result, {
1945
+ maxRows: 3,
1946
+ // Only need a few sample rows for summary
1947
+ maxCharsPerField: 100
1948
+ // Short truncation for summary
1949
+ });
1600
1950
  const dataSummary = {
1601
1951
  _dataReceived: true,
1602
1952
  _timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1603
1953
  _collection: collection,
1604
1954
  _operation: op,
1605
- _recordCount: Array.isArray(result) ? result.length : result?.data?.length || result?.contacts?.length || result?.salesorders?.length || "unknown"
1955
+ _totalRecords: formattedResult.summary.totalRecords,
1956
+ _recordsShown: formattedResult.summary.recordsShown,
1957
+ _metadata: formattedResult.metadata,
1958
+ // Preserve totalItems, totalDeadstockItems, etc.
1959
+ _schema: formattedResult.summary.schema
1606
1960
  };
1607
1961
  uiBlock.setComponentData(dataSummary);
1608
- logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (full data not stored to save memory)`);
1962
+ logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (${formattedResult.summary.totalRecords} total records)`);
1609
1963
  } else {
1610
1964
  logger.warn(`UIBlock ${uiBlockId} not found in threads`);
1611
1965
  }
@@ -3013,24 +3367,20 @@ If adaptation is not possible or would fundamentally change the component:
3013
3367
  "dash-comp-picker": {
3014
3368
  system: `You are a component selection expert that picks the best dashboard component and generates complete props based on user requests.
3015
3369
 
3370
+ ## CRITICAL - READ FIRST
3371
+
3372
+ 1. Your ENTIRE response must be ONLY a raw JSON object - start with { end with }
3373
+ 2. DO NOT explain or answer the user's question in natural language
3374
+ 3. DO NOT use markdown code blocks (no \`\`\`)
3375
+ 4. DO NOT add any text before or after the JSON
3376
+ 5. After executing tools (if needed), return JSON with component selection - NOT a text summary of results
3377
+
3016
3378
  ## Your Task
3017
3379
 
3018
3380
  Analyze the user's request and:
3019
3381
  1. **Select the most appropriate component** from the available components list
3020
- 2. **Determine the data source**: Database query OR External tool
3021
- 3. **Generate complete props** for the selected component
3022
-
3023
- ## Available External Tools
3024
-
3025
- The following external tools are available:
3026
-
3027
- {{AVAILABLE_TOOLS}}
3028
-
3029
- When a tool is needed to complete the user's request:
3030
- 1. **Analyze the request** to determine which tool is needed
3031
- 2. **Extract parameters** from the user's question
3032
- 3. **Execute the tool** by calling it with the extracted parameters
3033
- 4. **Use the results** to configure the component (field names for axes, columns, etc.)
3382
+ 2. **Determine the data source**: Database query OR External tool (ERP)
3383
+ 3. **Generate complete props** for the selected component including the data retrieval/modification method
3034
3384
 
3035
3385
  ## Component Selection Rules
3036
3386
 
@@ -3060,7 +3410,7 @@ The user prompt may contain an **existing component** to update. Detect this by
3060
3410
 
3061
3411
  ### Use DATABASE when:
3062
3412
  - User asks about data that exists in the database schema
3063
- - Questions about internal business data
3413
+ - Questions about internal business data
3064
3414
  - CRUD operations on database tables
3065
3415
 
3066
3416
  ### Use EXTERNAL TOOL when:
@@ -3073,6 +3423,12 @@ The user prompt may contain an **existing component** to update. Detect this by
3073
3423
 
3074
3424
  **CRITICAL**: Look at each component's "Props Structure" in the available components list. Generate ALL props that the component expects.
3075
3425
 
3426
+ **CRITICAL: Each component uses EXACTLY ONE data source - never both!**
3427
+ - If using \`query\`, set \`externalTool: null\`
3428
+ - If using \`externalTool\`, set \`query: null\`
3429
+ - NEVER copy placeholder/description text from component metadata as actual values
3430
+ - \`externalTool.parameters\` MUST be an object, never a string
3431
+
3076
3432
  ### For Data Viewing Components (charts, tables, KPIs):
3077
3433
 
3078
3434
  **Option A: Database Query** (when data is in database)
@@ -3081,21 +3437,19 @@ The user prompt may contain an **existing component** to update. Detect this by
3081
3437
  "query": {
3082
3438
  "sql": "SELECT column1, column2 FROM table WHERE condition = $param LIMIT 32",
3083
3439
  "params": { "param": "value" }
3084
- }
3440
+ },
3441
+ "externalTool": null
3085
3442
  }
3086
3443
  \`\`\`
3087
3444
 
3088
3445
  **Option B: External Tool** (when data is from ERP/external system)
3089
3446
  \`\`\`json
3090
3447
  {
3448
+ "query": null,
3091
3449
  "externalTool": {
3092
3450
  "toolId": "tool_id_from_list",
3093
3451
  "toolName": "Tool Display Name",
3094
- "action": "get",
3095
- "params": {
3096
- "param1": "value1",
3097
- "param2": "value2"
3098
- }
3452
+ "parameters": { "param1": "value1", "param2": "value2" }
3099
3453
  }
3100
3454
  }
3101
3455
  \`\`\`
@@ -3109,6 +3463,7 @@ The user prompt may contain an **existing component** to update. Detect this by
3109
3463
  "sql": "INSERT INTO table (col1, col2) VALUES ($col1, $col2)",
3110
3464
  "params": {}
3111
3465
  },
3466
+ "externalTool": null,
3112
3467
  "fields": [
3113
3468
  { "name": "col1", "type": "text", "required": true },
3114
3469
  { "name": "col2", "type": "number", "required": false }
@@ -3116,16 +3471,38 @@ The user prompt may contain an **existing component** to update. Detect this by
3116
3471
  }
3117
3472
  \`\`\`
3118
3473
 
3474
+ For UPDATE:
3475
+ \`\`\`json
3476
+ {
3477
+ "query": {
3478
+ "sql": "UPDATE table SET col1 = $col1, col2 = $col2 WHERE id = $id",
3479
+ "params": { "id": "record_id" }
3480
+ },
3481
+ "externalTool": null
3482
+ }
3483
+ \`\`\`
3484
+
3485
+ For DELETE:
3486
+ \`\`\`json
3487
+ {
3488
+ "query": {
3489
+ "sql": "DELETE FROM table WHERE id = $id",
3490
+ "params": { "id": "record_id" }
3491
+ },
3492
+ "externalTool": null,
3493
+ "submitButtonText": "Confirm Delete",
3494
+ "submitButtonColor": "danger"
3495
+ }
3496
+ \`\`\`
3497
+
3119
3498
  **Option B: External Tool Mutation**
3120
3499
  \`\`\`json
3121
3500
  {
3501
+ "query": null,
3122
3502
  "externalTool": {
3123
3503
  "toolId": "tool_id_from_list",
3124
3504
  "toolName": "Tool Display Name",
3125
- "action": "create|update|delete",
3126
- "params": {
3127
- "param1": "value_or_placeholder"
3128
- }
3505
+ "parameters": { "param1": "value_or_placeholder" }
3129
3506
  },
3130
3507
  "fields": [
3131
3508
  { "name": "param1", "type": "text", "required": true }
@@ -3140,6 +3517,7 @@ The user prompt may contain an **existing component** to update. Detect this by
3140
3517
 
3141
3518
  You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3142
3519
 
3520
+ \`\`\`json
3143
3521
  {
3144
3522
  "componentId": "id_from_available_list_or_existing_component_id",
3145
3523
  "componentName": "name_of_component",
@@ -3154,6 +3532,7 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3154
3532
  // Include all other required props (title, description, config, fields, etc.)
3155
3533
  }
3156
3534
  }
3535
+ \`\`\`
3157
3536
 
3158
3537
  **CRITICAL:**
3159
3538
  - Return ONLY valid JSON (no markdown code blocks, no text before/after)
@@ -3176,7 +3555,8 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3176
3555
 
3177
3556
  ---
3178
3557
 
3179
- ## CONTEXT`,
3558
+ ## CONTEXT
3559
+ `,
3180
3560
  user: `{{USER_PROMPT}}`
3181
3561
  },
3182
3562
  "dash-filter-picker": {
@@ -3322,9 +3702,7 @@ var PromptLoader = class {
3322
3702
  this.databaseRulesCache = /* @__PURE__ */ new Map();
3323
3703
  this.isInitialized = false;
3324
3704
  this.databaseType = "postgresql";
3325
- logger.debug("Initializing PromptLoader...");
3326
3705
  this.promptsDir = config?.promptsDir || import_path.default.join(process.cwd(), ".prompts");
3327
- logger.debug(`Prompts directory set to: ${this.promptsDir}`);
3328
3706
  }
3329
3707
  /**
3330
3708
  * Load a prompt template from file system OR fallback to hardcoded prompts
@@ -3338,7 +3716,6 @@ var PromptLoader = class {
3338
3716
  if (import_fs2.default.existsSync(systemPath) && import_fs2.default.existsSync(userPath)) {
3339
3717
  const system = import_fs2.default.readFileSync(systemPath, "utf-8");
3340
3718
  const user = import_fs2.default.readFileSync(userPath, "utf-8");
3341
- logger.info(`\u2713 Loaded prompt '${promptName}' from file system: ${this.promptsDir}`);
3342
3719
  return { system, user };
3343
3720
  }
3344
3721
  } catch (error) {
@@ -3346,7 +3723,6 @@ var PromptLoader = class {
3346
3723
  }
3347
3724
  const hardcodedPrompt = PROMPTS[promptName];
3348
3725
  if (hardcodedPrompt) {
3349
- logger.info(`\u2713 Loaded prompt '${promptName}' from hardcoded fallback`);
3350
3726
  return hardcodedPrompt;
3351
3727
  }
3352
3728
  throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or hardcoded prompts. Available prompts: ${Object.keys(PROMPTS).join(", ")}`);
@@ -3360,7 +3736,6 @@ var PromptLoader = class {
3360
3736
  logger.debug("PromptLoader already initialized, skipping...");
3361
3737
  return;
3362
3738
  }
3363
- logger.info("Loading prompts into memory...");
3364
3739
  const promptTypes = Object.keys(PROMPTS);
3365
3740
  for (const promptName of promptTypes) {
3366
3741
  try {
@@ -3372,7 +3747,6 @@ var PromptLoader = class {
3372
3747
  }
3373
3748
  }
3374
3749
  this.isInitialized = true;
3375
- logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
3376
3750
  }
3377
3751
  /**
3378
3752
  * Replace variables in a template string using {{VARIABLE_NAME}} pattern
@@ -3412,7 +3786,6 @@ var PromptLoader = class {
3412
3786
  const processedContext = this.replaceVariables(contextMarker + contextPart, variables);
3413
3787
  const staticLength = processedStatic.length;
3414
3788
  const contextLength = processedContext.length;
3415
- logger.debug(`\u2713 Prompt caching enabled for '${promptName}' (cached: ${staticLength} chars, dynamic: ${contextLength} chars)`);
3416
3789
  return {
3417
3790
  system: [
3418
3791
  {
@@ -3449,7 +3822,6 @@ var PromptLoader = class {
3449
3822
  this.promptsDir = dir;
3450
3823
  this.isInitialized = false;
3451
3824
  this.promptCache.clear();
3452
- logger.debug(`Prompts directory changed to: ${dir}`);
3453
3825
  }
3454
3826
  /**
3455
3827
  * Get current prompts directory
@@ -3477,7 +3849,6 @@ var PromptLoader = class {
3477
3849
  setDatabaseType(type) {
3478
3850
  this.databaseType = type;
3479
3851
  this.databaseRulesCache.clear();
3480
- logger.debug(`Database type set to: ${type}`);
3481
3852
  }
3482
3853
  /**
3483
3854
  * Get current database type
@@ -3493,7 +3864,6 @@ var PromptLoader = class {
3493
3864
  */
3494
3865
  async loadDatabaseRules() {
3495
3866
  if (this.databaseRulesCache.has(this.databaseType)) {
3496
- logger.debug(`\u2713 Database rules for '${this.databaseType}' loaded from cache`);
3497
3867
  return this.databaseRulesCache.get(this.databaseType);
3498
3868
  }
3499
3869
  const rulesPath = import_path.default.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
@@ -3501,7 +3871,6 @@ var PromptLoader = class {
3501
3871
  if (import_fs2.default.existsSync(rulesPath)) {
3502
3872
  const rules = import_fs2.default.readFileSync(rulesPath, "utf-8");
3503
3873
  this.databaseRulesCache.set(this.databaseType, rules);
3504
- logger.info(`\u2713 Loaded database rules for '${this.databaseType}' from ${rulesPath}`);
3505
3874
  return rules;
3506
3875
  }
3507
3876
  } catch (error) {
@@ -3777,7 +4146,6 @@ var Schema = class {
3777
4146
  * @returns Parsed schema object or null if error occurs
3778
4147
  */
3779
4148
  getDatabaseSchema() {
3780
- logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
3781
4149
  try {
3782
4150
  const dir = import_path2.default.dirname(this.schemaFilePath);
3783
4151
  if (!import_fs3.default.existsSync(dir)) {
@@ -4046,14 +4414,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4046
4414
  Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
4047
4415
  `;
4048
4416
  this.logStream?.write(logLine);
4049
- if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
4050
- console.log(`[LLM] \u26A1 CACHE HIT: ${entry.cacheReadTokens.toLocaleString()} tokens read from cache (${entry.method})`);
4051
- } else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
4052
- console.log(`[LLM] \u{1F4DD} CACHE WRITE: ${entry.cacheWriteTokens.toLocaleString()} tokens cached for future requests (${entry.method})`);
4053
- }
4054
- if (process.env.SUPERATOM_LOG_LEVEL === "verbose") {
4055
- console.log("\n[LLM-Usage]", logLine);
4056
- }
4057
4417
  }
4058
4418
  /**
4059
4419
  * Log session summary (call at end of request)
@@ -4086,11 +4446,6 @@ Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStat
4086
4446
 
4087
4447
  `;
4088
4448
  this.logStream?.write(summary);
4089
- console.log("\n[LLM-Usage] Session Summary:");
4090
- console.log(` Calls: ${this.sessionStats.totalCalls} | Tokens: ${(this.sessionStats.totalInputTokens + this.sessionStats.totalOutputTokens).toLocaleString()} | Cost: $${this.sessionStats.totalCostUSD.toFixed(4)} | Time: ${(this.sessionStats.totalDurationMs / 1e3).toFixed(2)}s`);
4091
- if (hasCaching) {
4092
- console.log(` Cache: ${this.sessionStats.totalCacheReadTokens.toLocaleString()} read, ${this.sessionStats.totalCacheWriteTokens.toLocaleString()} written | Savings: ~$${cacheReadSavings.toFixed(4)}`);
4093
- }
4094
4449
  }
4095
4450
  /**
4096
4451
  * Reset session stats (call at start of new user request)
@@ -4131,7 +4486,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4131
4486
  `;
4132
4487
  this.logStream.write(header);
4133
4488
  this.resetSession();
4134
- console.log(`[LLM-Usage] Log file reset for new request: ${this.logPath}`);
4135
4489
  } catch (error) {
4136
4490
  console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
4137
4491
  }
@@ -5605,21 +5959,20 @@ var getKnowledgeBase = async ({
5605
5959
  }) => {
5606
5960
  try {
5607
5961
  if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
5608
- logger.info("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
5962
+ logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
5609
5963
  return "";
5610
5964
  }
5611
- logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
5612
5965
  const result = await collections["knowledge-base"]["query"]({
5613
5966
  prompt,
5614
5967
  topK
5615
5968
  });
5616
5969
  if (!result || !result.content) {
5617
- logger.info("[KnowledgeBase] No knowledge base results returned");
5970
+ logger.warn("[KnowledgeBase] No knowledge base results returned");
5618
5971
  return "";
5619
5972
  }
5620
5973
  logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
5621
5974
  if (result.metadata?.sources && result.metadata.sources.length > 0) {
5622
- logger.debug(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
5975
+ logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
5623
5976
  }
5624
5977
  return result.content;
5625
5978
  } catch (error) {
@@ -5634,13 +5987,12 @@ var getGlobalKnowledgeBase = async ({
5634
5987
  }) => {
5635
5988
  try {
5636
5989
  if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
5637
- logger.info("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
5990
+ logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
5638
5991
  return "";
5639
5992
  }
5640
- logger.info("[KnowledgeBase] Fetching global knowledge base nodes...");
5641
5993
  const result = await collections["knowledge-base"]["getGlobal"]({ limit });
5642
5994
  if (!result || !result.content) {
5643
- logger.info("[KnowledgeBase] No global knowledge base nodes found");
5995
+ logger.warn("[KnowledgeBase] No global knowledge base nodes found");
5644
5996
  return "";
5645
5997
  }
5646
5998
  logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
@@ -5658,14 +6010,13 @@ var getUserKnowledgeBase = async ({
5658
6010
  }) => {
5659
6011
  try {
5660
6012
  if (!userId) {
5661
- logger.info("[KnowledgeBase] No userId provided, skipping user knowledge base");
6013
+ logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
5662
6014
  return "";
5663
6015
  }
5664
6016
  if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
5665
- logger.info("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
6017
+ logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
5666
6018
  return "";
5667
6019
  }
5668
- logger.info(`[KnowledgeBase] Fetching user knowledge base nodes for userId: ${userId}...`);
5669
6020
  const result = await collections["knowledge-base"]["getByUser"]({
5670
6021
  userId: Number(userId),
5671
6022
  limit
@@ -5688,7 +6039,6 @@ var getAllKnowledgeBase = async ({
5688
6039
  userId,
5689
6040
  topK = 3
5690
6041
  }) => {
5691
- logger.info("[KnowledgeBase] Fetching all knowledge base contexts...");
5692
6042
  const [globalContext, userContext, queryContext] = await Promise.all([
5693
6043
  getGlobalKnowledgeBase({ collections }),
5694
6044
  getUserKnowledgeBase({ collections, userId }),
@@ -5710,7 +6060,6 @@ var getAllKnowledgeBase = async ({
5710
6060
  combinedContext += "The following information is semantically relevant to the current query:\n\n";
5711
6061
  combinedContext += queryContext + "\n\n";
5712
6062
  }
5713
- logger.info(`[KnowledgeBase] Combined knowledge base context: global=${globalContext.length} chars, user=${userContext.length} chars, query=${queryContext.length} chars`);
5714
6063
  return {
5715
6064
  globalContext,
5716
6065
  userContext,
@@ -5947,11 +6296,11 @@ var searchConversationsWithReranking = async (options) => {
5947
6296
  } = options;
5948
6297
  try {
5949
6298
  if (!collections || !collections["conversation-history"]) {
5950
- logger.info("[ConversationSearch] conversation-history collection not registered, skipping");
6299
+ logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
5951
6300
  return null;
5952
6301
  }
5953
6302
  if (!collections["conversation-history"]["searchMultiple"]) {
5954
- logger.info("[ConversationSearch] searchMultiple not available, falling back to standard search");
6303
+ logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
5955
6304
  return searchConversations({
5956
6305
  userPrompt,
5957
6306
  collections,
@@ -5959,9 +6308,6 @@ var searchConversationsWithReranking = async (options) => {
5959
6308
  similarityThreshold
5960
6309
  });
5961
6310
  }
5962
- logger.info(`[ConversationSearch] Hybrid search for: "${userPrompt.substring(0, 50)}..."`);
5963
- logger.info(`[ConversationSearch] Fetching ${rerankCandidates} candidates for reranking`);
5964
- logger.info(`[ConversationSearch] Weights - Semantic: ${hybridOptions.semanticWeight}, BM25: ${hybridOptions.bm25Weight}`);
5965
6311
  const results = await collections["conversation-history"]["searchMultiple"]({
5966
6312
  userPrompt,
5967
6313
  userId,
@@ -6002,7 +6348,6 @@ var searchConversationsWithReranking = async (options) => {
6002
6348
  logger.info(
6003
6349
  `[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
6004
6350
  );
6005
- logger.info(` - Returning cached result for: "${matchedUserPrompt}"`);
6006
6351
  return {
6007
6352
  uiBlock: best.uiBlock,
6008
6353
  similarity: semanticScore,
@@ -6022,97 +6367,335 @@ var ConversationSearch = {
6022
6367
  };
6023
6368
  var conversation_search_default = ConversationSearch;
6024
6369
 
6025
- // src/userResponse/base-llm.ts
6026
- var BaseLLM = class {
6027
- constructor(config) {
6028
- this.model = config?.model || this.getDefaultModel();
6029
- this.fastModel = config?.fastModel || this.getDefaultFastModel();
6030
- this.defaultLimit = config?.defaultLimit || 10;
6031
- this.apiKey = config?.apiKey;
6032
- this.modelStrategy = config?.modelStrategy || "fast";
6033
- this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
6370
+ // src/userResponse/prompt-extractor.ts
6371
+ function extractPromptText(content) {
6372
+ if (content === null || content === void 0) {
6373
+ return "";
6034
6374
  }
6035
- /**
6036
- * Get the appropriate model based on task type and model strategy
6037
- * @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
6038
- * @returns The model string to use for this task
6039
- */
6040
- getModelForTask(taskType) {
6041
- switch (this.modelStrategy) {
6042
- case "best":
6043
- return this.model;
6044
- case "fast":
6045
- return this.fastModel;
6046
- case "balanced":
6047
- default:
6048
- return taskType === "complex" ? this.model : this.fastModel;
6375
+ if (typeof content === "string") {
6376
+ return content;
6377
+ }
6378
+ if (Array.isArray(content)) {
6379
+ return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
6380
+ }
6381
+ if (content && typeof content === "object") {
6382
+ return extractObjectText(content);
6383
+ }
6384
+ return String(content);
6385
+ }
6386
+ function extractContentBlockText(item) {
6387
+ if (typeof item === "string") {
6388
+ return item;
6389
+ }
6390
+ if (item && typeof item === "object") {
6391
+ const obj = item;
6392
+ if (typeof obj.text === "string") {
6393
+ return obj.text;
6394
+ }
6395
+ if (typeof obj.content === "string") {
6396
+ return obj.content;
6049
6397
  }
6398
+ return JSON.stringify(item, null, 2);
6399
+ }
6400
+ return String(item);
6401
+ }
6402
+ function extractObjectText(obj) {
6403
+ if (typeof obj.text === "string") {
6404
+ return obj.text;
6405
+ }
6406
+ if (typeof obj.content === "string") {
6407
+ return obj.content;
6408
+ }
6409
+ return JSON.stringify(obj, null, 2);
6410
+ }
6411
+
6412
+ // src/userResponse/constants.ts
6413
+ var MAX_QUERY_VALIDATION_RETRIES = 3;
6414
+ var MAX_QUERY_ATTEMPTS = 6;
6415
+ var MAX_TOOL_ATTEMPTS = 3;
6416
+ var STREAM_FLUSH_INTERVAL_MS = 50;
6417
+ var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
6418
+ var STREAM_DELAY_MS = 50;
6419
+ var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
6420
+ var MAX_TOKENS_QUERY_FIX = 2048;
6421
+ var MAX_TOKENS_COMPONENT_MATCHING = 8192;
6422
+ var MAX_TOKENS_CLASSIFICATION = 1500;
6423
+ var MAX_TOKENS_ADAPTATION = 8192;
6424
+ var MAX_TOKENS_TEXT_RESPONSE = 4e3;
6425
+ var MAX_TOKENS_NEXT_QUESTIONS = 1200;
6426
+ var DEFAULT_MAX_ROWS_FOR_LLM = 10;
6427
+ var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
6428
+ var STREAM_PREVIEW_MAX_ROWS = 10;
6429
+ var STREAM_PREVIEW_MAX_CHARS = 200;
6430
+ var TOOL_TRACKING_MAX_ROWS = 5;
6431
+ var TOOL_TRACKING_MAX_CHARS = 200;
6432
+ var TOOL_TRACKING_SAMPLE_ROWS = 3;
6433
+ var MAX_COMPONENT_QUERY_LIMIT = 10;
6434
+ var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
6435
+ var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
6436
+ var MAX_TOOL_CALLING_ITERATIONS = 20;
6437
+ var KNOWLEDGE_BASE_TOP_K = 3;
6438
+
6439
+ // src/userResponse/stream-buffer.ts
6440
+ var StreamBuffer = class {
6441
+ constructor(callback) {
6442
+ this.buffer = "";
6443
+ this.flushTimer = null;
6444
+ this.fullText = "";
6445
+ this.callback = callback;
6050
6446
  }
6051
6447
  /**
6052
- * Set the model strategy at runtime
6053
- * @param strategy - 'best', 'fast', or 'balanced'
6448
+ * Check if the buffer has a callback configured
6054
6449
  */
6055
- setModelStrategy(strategy) {
6056
- this.modelStrategy = strategy;
6057
- logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
6450
+ hasCallback() {
6451
+ return !!this.callback;
6058
6452
  }
6059
6453
  /**
6060
- * Get the current model strategy
6061
- * @returns The current model strategy
6454
+ * Get all text that has been written (including already flushed)
6062
6455
  */
6063
- getModelStrategy() {
6064
- return this.modelStrategy;
6456
+ getFullText() {
6457
+ return this.fullText;
6065
6458
  }
6066
6459
  /**
6067
- * Set the conversation similarity threshold at runtime
6068
- * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
6460
+ * Write a chunk to the buffer
6461
+ * Large chunks or chunks with newlines are flushed immediately
6462
+ * Small chunks are batched and flushed after a short interval
6463
+ *
6464
+ * @param chunk - Text chunk to write
6069
6465
  */
6070
- setConversationSimilarityThreshold(threshold) {
6071
- if (threshold < 0 || threshold > 1) {
6072
- logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default 0.8`);
6073
- this.conversationSimilarityThreshold = 0.8;
6466
+ write(chunk) {
6467
+ this.fullText += chunk;
6468
+ if (!this.callback) {
6074
6469
  return;
6075
6470
  }
6076
- this.conversationSimilarityThreshold = threshold;
6077
- logger.info(`[${this.getProviderName()}] Conversation similarity threshold set to: ${threshold}`);
6471
+ this.buffer += chunk;
6472
+ if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
6473
+ this.flushNow();
6474
+ } else if (!this.flushTimer) {
6475
+ this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
6476
+ }
6078
6477
  }
6079
6478
  /**
6080
- * Get the current conversation similarity threshold
6081
- * @returns The current threshold value
6479
+ * Flush the buffer immediately
6480
+ * Call this before tool execution or other operations that need clean output
6082
6481
  */
6083
- getConversationSimilarityThreshold() {
6084
- return this.conversationSimilarityThreshold;
6482
+ flush() {
6483
+ this.flushNow();
6085
6484
  }
6086
6485
  /**
6087
- * Get the API key (from instance, parameter, or environment)
6486
+ * Internal flush implementation
6088
6487
  */
6089
- getApiKey(apiKey) {
6090
- return apiKey || this.apiKey || this.getDefaultApiKey();
6488
+ flushNow() {
6489
+ if (this.flushTimer) {
6490
+ clearTimeout(this.flushTimer);
6491
+ this.flushTimer = null;
6492
+ }
6493
+ if (this.buffer && this.callback) {
6494
+ this.callback(this.buffer);
6495
+ this.buffer = "";
6496
+ }
6091
6497
  }
6092
6498
  /**
6093
- * Check if a component contains a Form (data_modification component)
6094
- * Forms have hardcoded defaultValues that become stale when cached
6095
- * This checks both single Form components and Forms inside MultiComponentContainer
6499
+ * Clean up resources
6500
+ * Call this when done with the buffer
6096
6501
  */
6097
- containsFormComponent(component) {
6098
- if (!component) return false;
6099
- if (component.type === "Form" || component.name === "DynamicForm") {
6100
- return true;
6502
+ dispose() {
6503
+ this.flush();
6504
+ this.callback = void 0;
6505
+ }
6506
+ };
6507
+ function streamDelay(ms = STREAM_DELAY_MS) {
6508
+ return new Promise((resolve) => setTimeout(resolve, ms));
6509
+ }
6510
+ async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
6511
+ if (!streamBuffer.hasCallback()) {
6512
+ return operation();
6513
+ }
6514
+ const startTime = Date.now();
6515
+ await streamDelay(30);
6516
+ streamBuffer.write(`\u23F3 ${progressMessage}`);
6517
+ const heartbeatInterval = setInterval(() => {
6518
+ const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
6519
+ if (elapsedSeconds >= 1) {
6520
+ streamBuffer.write(` (${elapsedSeconds}s)`);
6521
+ }
6522
+ }, intervalMs);
6523
+ try {
6524
+ const result = await operation();
6525
+ return result;
6526
+ } finally {
6527
+ clearInterval(heartbeatInterval);
6528
+ streamBuffer.write("\n\n");
6529
+ }
6530
+ }
6531
+
6532
+ // src/userResponse/utils/component-props-processor.ts
6533
+ var NUMERIC_CONFIG_KEYS = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
6534
+ var STRING_CONFIG_KEYS = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
6535
+ var CONFIG_FIELDS_TO_VALIDATE = [
6536
+ "xAxisKey",
6537
+ "yAxisKey",
6538
+ "valueKey",
6539
+ "nameKey",
6540
+ "labelKey",
6541
+ "groupBy",
6542
+ "aggregationField",
6543
+ "seriesKey",
6544
+ "sizeKey",
6545
+ "xAggregationField",
6546
+ "yAggregationField"
6547
+ ];
6548
+ function findMatchingField(fieldName, configKey, validFieldNames, fieldTypes, providerName) {
6549
+ if (!fieldName) return null;
6550
+ const lowerField = fieldName.toLowerCase();
6551
+ const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
6552
+ const exactIdx = validFieldNamesLower.indexOf(lowerField);
6553
+ if (exactIdx !== -1) return validFieldNames[exactIdx];
6554
+ const containsMatches = validFieldNames.filter(
6555
+ (_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
6556
+ );
6557
+ if (containsMatches.length === 1) return containsMatches[0];
6558
+ if (NUMERIC_CONFIG_KEYS.includes(configKey)) {
6559
+ const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
6560
+ const match = numericFields.find(
6561
+ (f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
6562
+ );
6563
+ if (match) return match;
6564
+ if (numericFields.length > 0) {
6565
+ logger.warn(`[${providerName}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
6566
+ return numericFields[0];
6101
6567
  }
6102
- if (component.type === "Container" || component.name === "MultiComponentContainer") {
6103
- const nestedComponents = component.props?.config?.components || [];
6104
- for (const nested of nestedComponents) {
6105
- if (nested.type === "Form" || nested.name === "DynamicForm") {
6106
- return true;
6568
+ }
6569
+ if (STRING_CONFIG_KEYS.includes(configKey)) {
6570
+ const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
6571
+ const match = stringFields.find(
6572
+ (f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
6573
+ );
6574
+ if (match) return match;
6575
+ if (stringFields.length > 0) {
6576
+ logger.warn(`[${providerName}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
6577
+ return stringFields[0];
6578
+ }
6579
+ }
6580
+ logger.warn(`[${providerName}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
6581
+ return validFieldNames[0];
6582
+ }
6583
+ function validateConfigFieldNames(config, outputSchema, providerName) {
6584
+ if (!outputSchema?.fields || !config) return config;
6585
+ const validFieldNames = outputSchema.fields.map((f) => f.name);
6586
+ const fieldTypes = outputSchema.fields.reduce((acc, f) => {
6587
+ acc[f.name] = f.type;
6588
+ return acc;
6589
+ }, {});
6590
+ const correctedConfig = { ...config };
6591
+ for (const configKey of CONFIG_FIELDS_TO_VALIDATE) {
6592
+ const fieldValue = correctedConfig[configKey];
6593
+ if (fieldValue && typeof fieldValue === "string") {
6594
+ if (!validFieldNames.includes(fieldValue)) {
6595
+ const correctedField = findMatchingField(fieldValue, configKey, validFieldNames, fieldTypes, providerName);
6596
+ if (correctedField) {
6597
+ logger.warn(`[${providerName}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
6598
+ correctedConfig[configKey] = correctedField;
6107
6599
  }
6108
6600
  }
6109
6601
  }
6110
- return false;
6602
+ }
6603
+ if (Array.isArray(correctedConfig.series)) {
6604
+ correctedConfig.series = correctedConfig.series.map((s) => {
6605
+ if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
6606
+ const correctedField = findMatchingField(s.dataKey, "yAxisKey", validFieldNames, fieldTypes, providerName);
6607
+ if (correctedField) {
6608
+ logger.warn(`[${providerName}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
6609
+ return { ...s, dataKey: correctedField };
6610
+ }
6611
+ }
6612
+ return s;
6613
+ });
6614
+ }
6615
+ return correctedConfig;
6616
+ }
6617
+ function validateExternalTool(externalTool, executedTools, providerName) {
6618
+ if (!externalTool) {
6619
+ return { valid: true };
6620
+ }
6621
+ const toolId = externalTool.toolId;
6622
+ const validToolIds = (executedTools || []).map((t) => t.id);
6623
+ const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
6624
+ if (!isValidTool) {
6625
+ logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
6626
+ return { valid: false };
6627
+ }
6628
+ const executedTool = executedTools?.find((t) => t.id === toolId);
6629
+ return { valid: true, executedTool };
6630
+ }
6631
+ function validateAndCleanQuery(query, config) {
6632
+ if (!query) {
6633
+ return { query: null, wasModified: false };
6634
+ }
6635
+ let wasModified = false;
6636
+ let cleanedQuery = query;
6637
+ const queryStr = typeof query === "string" ? query : query?.sql || "";
6638
+ if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
6639
+ logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
6640
+ return { query: null, wasModified: true };
6641
+ }
6642
+ const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
6643
+ if (fixed) {
6644
+ logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
6645
+ wasModified = true;
6646
+ if (typeof cleanedQuery === "string") {
6647
+ cleanedQuery = fixedQuery;
6648
+ } else if (cleanedQuery?.sql) {
6649
+ cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
6650
+ }
6651
+ }
6652
+ if (typeof cleanedQuery === "string") {
6653
+ const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6654
+ if (limitedQuery !== cleanedQuery) wasModified = true;
6655
+ cleanedQuery = limitedQuery;
6656
+ } else if (cleanedQuery?.sql) {
6657
+ const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6658
+ if (limitedSql !== cleanedQuery.sql) wasModified = true;
6659
+ cleanedQuery = { ...cleanedQuery, sql: limitedSql };
6660
+ }
6661
+ return { query: cleanedQuery, wasModified };
6662
+ }
6663
+ function processComponentProps(props, executedTools, config) {
6664
+ let cleanedProps = { ...props };
6665
+ if (cleanedProps.externalTool) {
6666
+ const { valid, executedTool } = validateExternalTool(
6667
+ cleanedProps.externalTool,
6668
+ executedTools,
6669
+ config.providerName
6670
+ );
6671
+ if (!valid) {
6672
+ cleanedProps.externalTool = null;
6673
+ } else if (executedTool?.outputSchema?.fields && cleanedProps.config) {
6674
+ cleanedProps.config = validateConfigFieldNames(
6675
+ cleanedProps.config,
6676
+ executedTool.outputSchema,
6677
+ config.providerName
6678
+ );
6679
+ }
6680
+ }
6681
+ if (cleanedProps.query) {
6682
+ const { query } = validateAndCleanQuery(cleanedProps.query, config);
6683
+ cleanedProps.query = query;
6684
+ }
6685
+ if (cleanedProps.query && cleanedProps.externalTool) {
6686
+ logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
6687
+ }
6688
+ return cleanedProps;
6689
+ }
6690
+
6691
+ // src/userResponse/services/query-execution-service.ts
6692
+ var QueryExecutionService = class {
6693
+ constructor(config) {
6694
+ this.config = config;
6111
6695
  }
6112
6696
  /**
6113
- * Get the cache key for a query (the exact sql param that would be sent to execute)
6697
+ * Get the cache key for a query
6114
6698
  * This ensures the cache key matches what the frontend will send
6115
- * Used for both caching and internal deduplication
6116
6699
  */
6117
6700
  getQueryCacheKey(query) {
6118
6701
  if (typeof query === "string") {
@@ -6128,17 +6711,19 @@ var BaseLLM = class {
6128
6711
  return "";
6129
6712
  }
6130
6713
  /**
6131
- * Execute a query against the database for validation and caching
6714
+ * Execute a query against the database
6132
6715
  * @param query - The SQL query to execute (string or object with sql/values)
6133
6716
  * @param collections - Collections object containing database execute function
6134
6717
  * @returns Object with result data and cache key
6135
- * @throws Error if query execution fails
6136
6718
  */
6137
- async executeQueryForValidation(query, collections) {
6719
+ async executeQuery(query, collections) {
6138
6720
  const cacheKey = this.getQueryCacheKey(query);
6139
6721
  if (!cacheKey) {
6140
6722
  throw new Error("Invalid query format: expected string or object with sql property");
6141
6723
  }
6724
+ if (!collections?.["database"]?.["execute"]) {
6725
+ throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
6726
+ }
6142
6727
  const result = await collections["database"]["execute"]({ sql: cacheKey });
6143
6728
  return { result, cacheKey };
6144
6729
  }
@@ -6146,7 +6731,7 @@ var BaseLLM = class {
6146
6731
  * Request the LLM to fix a failed SQL query
6147
6732
  * @param failedQuery - The query that failed execution
6148
6733
  * @param errorMessage - The error message from the failed execution
6149
- * @param componentContext - Context about the component (name, type, title)
6734
+ * @param componentContext - Context about the component
6150
6735
  * @param apiKey - Optional API key
6151
6736
  * @returns Fixed query string
6152
6737
  */
@@ -6187,10 +6772,10 @@ Fixed SQL query:`;
6187
6772
  user: prompt
6188
6773
  },
6189
6774
  {
6190
- model: this.getModelForTask("simple"),
6191
- maxTokens: 2048,
6775
+ model: this.config.getModelForTask("simple"),
6776
+ maxTokens: MAX_TOKENS_QUERY_FIX,
6192
6777
  temperature: 0,
6193
- apiKey: this.getApiKey(apiKey)
6778
+ apiKey: this.config.getApiKey(apiKey)
6194
6779
  }
6195
6780
  );
6196
6781
  let fixedQuery = response.trim();
@@ -6200,23 +6785,561 @@ Fixed SQL query:`;
6200
6785
  return validatedQuery;
6201
6786
  }
6202
6787
  /**
6203
- * Match components from text response suggestions and generate follow-up questions
6204
- * Takes a text response with component suggestions (c1:type format) and matches with available components
6205
- * Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
6206
- * All components are placed in a default MultiComponentContainer layout
6207
- * @param analysisContent - The text response containing component suggestions
6208
- * @param components - List of available components
6209
- * @param apiKey - Optional API key
6210
- * @param logCollector - Optional log collector
6211
- * @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
6212
- * @returns Object containing matched components, layout title/description, and follow-up actions
6788
+ * Validate a single component's query with retry logic
6789
+ * @param component - The component to validate
6790
+ * @param collections - Collections object containing database execute function
6791
+ * @param apiKey - Optional API key for LLM calls
6792
+ * @returns Validation result with component, query key, and result
6213
6793
  */
6214
- async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, logCollector, componentStreamCallback, deferredTools, executedTools, collections, userId) {
6215
- const methodStartTime = Date.now();
6216
- const methodName = "matchComponentsFromAnalysis";
6217
- logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
6218
- try {
6219
- logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
6794
+ async validateSingleQuery(component, collections, apiKey) {
6795
+ const query = component.props?.query;
6796
+ const originalQueryKey = this.getQueryCacheKey(query);
6797
+ const queryStr = typeof query === "string" ? query : query?.sql || "";
6798
+ let finalQueryKey = originalQueryKey;
6799
+ let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
6800
+ let currentQueryStr = queryStr;
6801
+ let validated = false;
6802
+ let lastError = "";
6803
+ let result = null;
6804
+ let attempts = 0;
6805
+ logger.info(`[${this.config.providerName}] Validating query for component: ${component.name} (${component.type})`);
6806
+ while (attempts < MAX_QUERY_VALIDATION_RETRIES && !validated) {
6807
+ attempts++;
6808
+ try {
6809
+ logger.debug(`[${this.config.providerName}] Query validation attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES} for ${component.name}`);
6810
+ const validationResult = await this.executeQuery(currentQuery, collections);
6811
+ result = validationResult.result;
6812
+ validated = true;
6813
+ queryCache.set(validationResult.cacheKey, result);
6814
+ logger.info(`[${this.config.providerName}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
6815
+ if (currentQueryStr !== queryStr) {
6816
+ const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
6817
+ component.props = {
6818
+ ...component.props,
6819
+ query: fixedQuery
6820
+ };
6821
+ finalQueryKey = this.getQueryCacheKey(fixedQuery);
6822
+ logger.info(`[${this.config.providerName}] Updated ${component.name} with fixed query`);
6823
+ }
6824
+ } catch (error) {
6825
+ lastError = error instanceof Error ? error.message : String(error);
6826
+ logger.warn(`[${this.config.providerName}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES}): ${lastError}`);
6827
+ if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
6828
+ logger.error(`[${this.config.providerName}] \u2717 Max retries reached for ${component.name}, excluding from response`);
6829
+ break;
6830
+ }
6831
+ logger.info(`[${this.config.providerName}] Requesting query fix from LLM for ${component.name}...`);
6832
+ try {
6833
+ const fixedQueryStr = await this.requestQueryFix(
6834
+ currentQueryStr,
6835
+ lastError,
6836
+ {
6837
+ name: component.name,
6838
+ type: component.type,
6839
+ title: component.props?.title
6840
+ },
6841
+ apiKey
6842
+ );
6843
+ if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
6844
+ logger.info(`[${this.config.providerName}] Received fixed query for ${component.name}, retrying...`);
6845
+ const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6846
+ currentQueryStr = limitedFixedQuery;
6847
+ if (typeof currentQuery === "string") {
6848
+ currentQuery = limitedFixedQuery;
6849
+ } else {
6850
+ currentQuery = { ...currentQuery, sql: limitedFixedQuery };
6851
+ }
6852
+ } else {
6853
+ logger.warn(`[${this.config.providerName}] LLM returned same or empty query, stopping retries`);
6854
+ break;
6855
+ }
6856
+ } catch (fixError) {
6857
+ const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
6858
+ logger.error(`[${this.config.providerName}] Failed to get query fix from LLM: ${fixErrorMsg}`);
6859
+ break;
6860
+ }
6861
+ }
6862
+ }
6863
+ if (!validated) {
6864
+ logger.warn(`[${this.config.providerName}] Component ${component.name} excluded from response due to failed query validation`);
6865
+ }
6866
+ return {
6867
+ component: validated ? component : null,
6868
+ queryKey: finalQueryKey,
6869
+ result,
6870
+ validated
6871
+ };
6872
+ }
6873
+ /**
6874
+ * Validate multiple component queries in parallel
6875
+ * @param components - Array of components with potential queries
6876
+ * @param collections - Collections object containing database execute function
6877
+ * @param apiKey - Optional API key for LLM calls
6878
+ * @returns Object with validated components and query results map
6879
+ */
6880
+ async validateComponentQueries(components, collections, apiKey) {
6881
+ const queryResults = /* @__PURE__ */ new Map();
6882
+ const validatedComponents = [];
6883
+ const componentsWithoutQuery = [];
6884
+ const componentsWithQuery = [];
6885
+ for (const component of components) {
6886
+ if (!component.props?.query) {
6887
+ componentsWithoutQuery.push(component);
6888
+ } else {
6889
+ componentsWithQuery.push(component);
6890
+ }
6891
+ }
6892
+ validatedComponents.push(...componentsWithoutQuery);
6893
+ if (componentsWithQuery.length === 0) {
6894
+ return { components: validatedComponents, queryResults };
6895
+ }
6896
+ logger.info(`[${this.config.providerName}] Validating ${componentsWithQuery.length} component queries in parallel...`);
6897
+ const validationPromises = componentsWithQuery.map(
6898
+ (component) => this.validateSingleQuery(component, collections, apiKey)
6899
+ );
6900
+ const results = await Promise.allSettled(validationPromises);
6901
+ for (let i = 0; i < results.length; i++) {
6902
+ const result = results[i];
6903
+ const component = componentsWithQuery[i];
6904
+ if (result.status === "fulfilled") {
6905
+ const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
6906
+ if (validated && validatedComponent) {
6907
+ validatedComponents.push(validatedComponent);
6908
+ if (queryResult) {
6909
+ queryResults.set(queryKey, queryResult);
6910
+ queryResults.set(`${component.id}:${queryKey}`, queryResult);
6911
+ }
6912
+ }
6913
+ } else {
6914
+ logger.error(`[${this.config.providerName}] Unexpected error validating ${component.name}: ${result.reason}`);
6915
+ }
6916
+ }
6917
+ logger.info(`[${this.config.providerName}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
6918
+ return {
6919
+ components: validatedComponents,
6920
+ queryResults
6921
+ };
6922
+ }
6923
+ };
6924
+
6925
+ // src/userResponse/services/tool-executor-service.ts
6926
+ var ToolExecutorService = class {
6927
+ constructor(config) {
6928
+ this.queryAttempts = /* @__PURE__ */ new Map();
6929
+ this.toolAttempts = /* @__PURE__ */ new Map();
6930
+ this.executedToolsList = [];
6931
+ this.maxAttemptsReached = false;
6932
+ this.config = config;
6933
+ }
6934
+ /**
6935
+ * Reset state for a new execution
6936
+ */
6937
+ reset() {
6938
+ this.queryAttempts.clear();
6939
+ this.toolAttempts.clear();
6940
+ this.executedToolsList = [];
6941
+ this.maxAttemptsReached = false;
6942
+ }
6943
+ /**
6944
+ * Get list of successfully executed tools
6945
+ */
6946
+ getExecutedTools() {
6947
+ return this.executedToolsList;
6948
+ }
6949
+ /**
6950
+ * Check if max attempts were reached
6951
+ */
6952
+ isMaxAttemptsReached() {
6953
+ return this.maxAttemptsReached;
6954
+ }
6955
+ /**
6956
+ * Create a tool handler function for LLM.streamWithTools
6957
+ * @param externalTools - List of available external tools
6958
+ * @returns Tool handler function
6959
+ */
6960
+ createToolHandler(externalTools) {
6961
+ return async (toolName, toolInput) => {
6962
+ if (toolName === "execute_query") {
6963
+ return this.executeQuery(toolInput);
6964
+ } else {
6965
+ return this.executeExternalTool(toolName, toolInput, externalTools);
6966
+ }
6967
+ };
6968
+ }
6969
+ /**
6970
+ * Execute a SQL query with retry tracking and streaming feedback
6971
+ */
6972
+ async executeQuery(toolInput) {
6973
+ let sql = toolInput.sql;
6974
+ const params = toolInput.params || {};
6975
+ const reasoning = toolInput.reasoning;
6976
+ const { streamBuffer, collections, providerName } = this.config;
6977
+ sql = ensureQueryLimit(sql, MAX_COMPONENT_QUERY_LIMIT, MAX_COMPONENT_QUERY_LIMIT);
6978
+ const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
6979
+ const attempts = (this.queryAttempts.get(queryKey) || 0) + 1;
6980
+ this.queryAttempts.set(queryKey, attempts);
6981
+ if (Object.keys(params).length > 0) {
6982
+ logger.info(`[${providerName}] Query params: ${JSON.stringify(params)}`);
6983
+ }
6984
+ if (attempts > MAX_QUERY_ATTEMPTS) {
6985
+ const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
6986
+ logger.error(`[${providerName}] ${errorMsg}`);
6987
+ this.maxAttemptsReached = true;
6988
+ if (streamBuffer.hasCallback()) {
6989
+ streamBuffer.write(`
6990
+
6991
+ \u274C ${errorMsg}
6992
+
6993
+ Please try rephrasing your question or simplifying your request.
6994
+
6995
+ `);
6996
+ }
6997
+ throw new Error(errorMsg);
6998
+ }
6999
+ try {
7000
+ streamBuffer.flush();
7001
+ if (streamBuffer.hasCallback()) {
7002
+ const paramsDisplay = Object.keys(params).length > 0 ? `
7003
+ **Parameters:** ${JSON.stringify(params)}` : "";
7004
+ if (attempts === 1) {
7005
+ streamBuffer.write(`
7006
+
7007
+ \u{1F50D} **Analyzing your question...**
7008
+
7009
+ `);
7010
+ await streamDelay();
7011
+ if (reasoning) {
7012
+ streamBuffer.write(`\u{1F4AD} ${reasoning}
7013
+
7014
+ `);
7015
+ await streamDelay();
7016
+ }
7017
+ streamBuffer.write(`\u{1F4DD} **Generated SQL Query:**
7018
+ \`\`\`sql
7019
+ ${sql}
7020
+ \`\`\`${paramsDisplay}
7021
+
7022
+ `);
7023
+ await streamDelay();
7024
+ } else {
7025
+ streamBuffer.write(`
7026
+
7027
+ \u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
7028
+
7029
+ `);
7030
+ await streamDelay();
7031
+ if (reasoning) {
7032
+ streamBuffer.write(`\u{1F4AD} ${reasoning}
7033
+
7034
+ `);
7035
+ await streamDelay();
7036
+ }
7037
+ streamBuffer.write(`\u{1F4DD} **Corrected SQL Query:**
7038
+ \`\`\`sql
7039
+ ${sql}
7040
+ \`\`\`${paramsDisplay}
7041
+
7042
+ `);
7043
+ await streamDelay();
7044
+ }
7045
+ }
7046
+ if (!collections?.["database"]?.["execute"]) {
7047
+ throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
7048
+ }
7049
+ const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
7050
+ const result = await withProgressHeartbeat(
7051
+ () => collections["database"]["execute"](queryPayload),
7052
+ "Executing database query",
7053
+ streamBuffer
7054
+ );
7055
+ const data = result?.data || result;
7056
+ const rowCount = result?.count ?? (Array.isArray(data) ? data.length : "N/A");
7057
+ if (streamBuffer.hasCallback()) {
7058
+ streamBuffer.write(`
7059
+ \u2705 **Query executed successfully!**
7060
+
7061
+ `);
7062
+ await streamDelay();
7063
+ if (Array.isArray(data) && data.length > 0) {
7064
+ const firstRow = data[0];
7065
+ const columns = Object.keys(firstRow);
7066
+ if (data.length === 1 && columns.length === 1) {
7067
+ const value = firstRow[columns[0]];
7068
+ streamBuffer.write(`**Result:** ${value}
7069
+
7070
+ `);
7071
+ await streamDelay();
7072
+ } else if (data.length > 0) {
7073
+ streamBuffer.write(`**Retrieved ${rowCount} rows**
7074
+
7075
+ `);
7076
+ await streamDelay();
7077
+ const streamPreview = formatQueryResultForLLM(data, {
7078
+ maxRows: STREAM_PREVIEW_MAX_ROWS,
7079
+ maxCharsPerField: STREAM_PREVIEW_MAX_CHARS
7080
+ });
7081
+ streamBuffer.write(`<DataTable>${JSON.stringify(streamPreview.data)}</DataTable>
7082
+
7083
+ `);
7084
+ if (streamPreview.truncationNote) {
7085
+ streamBuffer.write(`*${streamPreview.truncationNote}*
7086
+
7087
+ `);
7088
+ }
7089
+ await streamDelay();
7090
+ }
7091
+ } else if (Array.isArray(data) && data.length === 0) {
7092
+ streamBuffer.write(`**No rows returned.**
7093
+
7094
+ `);
7095
+ await streamDelay();
7096
+ }
7097
+ streamBuffer.write(`\u{1F4CA} **Analyzing results...**
7098
+
7099
+ `);
7100
+ }
7101
+ const formattedResult = formatQueryResultForLLM(data, {
7102
+ maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
7103
+ maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
7104
+ });
7105
+ if (formattedResult.truncationNote) {
7106
+ logger.info(`[${providerName}] Truncation: ${formattedResult.truncationNote}`);
7107
+ }
7108
+ return formatResultAsString(formattedResult);
7109
+ } catch (error) {
7110
+ const errorMsg = error instanceof Error ? error.message : String(error);
7111
+ logger.error(`[${providerName}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
7112
+ userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
7113
+ if (streamBuffer.hasCallback()) {
7114
+ streamBuffer.write(`\u274C **Query execution failed:**
7115
+ \`\`\`
7116
+ ${errorMsg}
7117
+ \`\`\`
7118
+
7119
+ `);
7120
+ if (attempts < MAX_QUERY_ATTEMPTS) {
7121
+ streamBuffer.write(`\u{1F527} **Generating corrected query...**
7122
+
7123
+ `);
7124
+ }
7125
+ }
7126
+ throw new Error(`Query execution failed: ${errorMsg}`);
7127
+ }
7128
+ }
7129
+ /**
7130
+ * Execute an external tool with retry tracking and streaming feedback
7131
+ */
7132
+ async executeExternalTool(toolName, toolInput, externalTools) {
7133
+ const { streamBuffer, providerName } = this.config;
7134
+ const externalTool = externalTools?.find((t) => t.id === toolName);
7135
+ if (!externalTool) {
7136
+ throw new Error(`Unknown tool: ${toolName}`);
7137
+ }
7138
+ const attempts = (this.toolAttempts.get(toolName) || 0) + 1;
7139
+ this.toolAttempts.set(toolName, attempts);
7140
+ if (attempts > MAX_TOOL_ATTEMPTS) {
7141
+ const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
7142
+ logger.error(`[${providerName}] ${errorMsg}`);
7143
+ if (streamBuffer.hasCallback()) {
7144
+ streamBuffer.write(`
7145
+
7146
+ \u274C ${errorMsg}
7147
+
7148
+ Please try rephrasing your request or contact support.
7149
+
7150
+ `);
7151
+ }
7152
+ throw new Error(errorMsg);
7153
+ }
7154
+ try {
7155
+ streamBuffer.flush();
7156
+ if (streamBuffer.hasCallback()) {
7157
+ if (attempts === 1) {
7158
+ streamBuffer.write(`
7159
+
7160
+ \u{1F517} **Executing ${externalTool.name}...**
7161
+
7162
+ `);
7163
+ } else {
7164
+ streamBuffer.write(`
7165
+
7166
+ \u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
7167
+
7168
+ `);
7169
+ }
7170
+ await streamDelay();
7171
+ }
7172
+ const result = await withProgressHeartbeat(
7173
+ () => externalTool.fn(toolInput),
7174
+ `Running ${externalTool.name}`,
7175
+ streamBuffer
7176
+ );
7177
+ if (!this.executedToolsList.find((t) => t.id === externalTool.id)) {
7178
+ const formattedForTracking = formatToolResultForLLM(result, {
7179
+ toolName: externalTool.name,
7180
+ toolLimit: externalTool.limit,
7181
+ maxRows: TOOL_TRACKING_MAX_ROWS,
7182
+ maxCharsPerField: TOOL_TRACKING_MAX_CHARS
7183
+ });
7184
+ this.executedToolsList.push({
7185
+ id: externalTool.id,
7186
+ name: externalTool.name,
7187
+ params: toolInput,
7188
+ result: {
7189
+ _totalRecords: formattedForTracking.summary.totalRecords,
7190
+ _recordsShown: formattedForTracking.summary.recordsShown,
7191
+ _metadata: formattedForTracking.metadata,
7192
+ _sampleData: formattedForTracking.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS)
7193
+ },
7194
+ outputSchema: externalTool.outputSchema
7195
+ });
7196
+ }
7197
+ if (streamBuffer.hasCallback()) {
7198
+ streamBuffer.write(`\u2705 **${externalTool.name} completed successfully**
7199
+
7200
+ `);
7201
+ await streamDelay();
7202
+ }
7203
+ const formattedToolResult = formatToolResultForLLM(result, {
7204
+ toolName: externalTool.name,
7205
+ toolLimit: externalTool.limit,
7206
+ maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
7207
+ maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
7208
+ });
7209
+ if (formattedToolResult.truncationNote) {
7210
+ logger.info(`[${providerName}] Truncation: ${formattedToolResult.truncationNote}`);
7211
+ }
7212
+ return formatResultAsString(formattedToolResult);
7213
+ } catch (error) {
7214
+ const errorMsg = error instanceof Error ? error.message : String(error);
7215
+ logger.error(`[${providerName}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
7216
+ userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
7217
+ if (streamBuffer.hasCallback()) {
7218
+ streamBuffer.write(`\u274C **${externalTool.name} failed:**
7219
+ \`\`\`
7220
+ ${errorMsg}
7221
+ \`\`\`
7222
+
7223
+ `);
7224
+ if (attempts < MAX_TOOL_ATTEMPTS) {
7225
+ streamBuffer.write(`\u{1F527} **Retrying with adjusted parameters...**
7226
+
7227
+ `);
7228
+ }
7229
+ }
7230
+ throw new Error(`Tool execution failed: ${errorMsg}`);
7231
+ }
7232
+ }
7233
+ };
7234
+
7235
+ // src/userResponse/base-llm.ts
7236
+ var BaseLLM = class {
7237
+ constructor(config) {
7238
+ this.model = config?.model || this.getDefaultModel();
7239
+ this.fastModel = config?.fastModel || this.getDefaultFastModel();
7240
+ this.defaultLimit = config?.defaultLimit || 10;
7241
+ this.apiKey = config?.apiKey;
7242
+ this.modelStrategy = config?.modelStrategy || "fast";
7243
+ this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
7244
+ this.queryService = new QueryExecutionService({
7245
+ defaultLimit: this.defaultLimit,
7246
+ getModelForTask: (taskType) => this.getModelForTask(taskType),
7247
+ getApiKey: (apiKey) => this.getApiKey(apiKey),
7248
+ providerName: this.getProviderName()
7249
+ });
7250
+ }
7251
+ /**
7252
+ * Get the appropriate model based on task type and model strategy
7253
+ * @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
7254
+ * @returns The model string to use for this task
7255
+ */
7256
+ getModelForTask(taskType) {
7257
+ switch (this.modelStrategy) {
7258
+ case "best":
7259
+ return this.model;
7260
+ case "fast":
7261
+ return this.fastModel;
7262
+ case "balanced":
7263
+ default:
7264
+ return taskType === "complex" ? this.model : this.fastModel;
7265
+ }
7266
+ }
7267
+ /**
7268
+ * Set the model strategy at runtime
7269
+ * @param strategy - 'best', 'fast', or 'balanced'
7270
+ */
7271
+ setModelStrategy(strategy) {
7272
+ this.modelStrategy = strategy;
7273
+ logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
7274
+ }
7275
+ /**
7276
+ * Get the current model strategy
7277
+ * @returns The current model strategy
7278
+ */
7279
+ getModelStrategy() {
7280
+ return this.modelStrategy;
7281
+ }
7282
+ /**
7283
+ * Set the conversation similarity threshold at runtime
7284
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
7285
+ */
7286
+ setConversationSimilarityThreshold(threshold) {
7287
+ if (threshold < 0 || threshold > 1) {
7288
+ logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default ${DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD}`);
7289
+ this.conversationSimilarityThreshold = DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
7290
+ return;
7291
+ }
7292
+ this.conversationSimilarityThreshold = threshold;
7293
+ }
7294
+ /**
7295
+ * Get the current conversation similarity threshold
7296
+ * @returns The current threshold value
7297
+ */
7298
+ getConversationSimilarityThreshold() {
7299
+ return this.conversationSimilarityThreshold;
7300
+ }
7301
+ /**
7302
+ * Get the API key (from instance, parameter, or environment)
7303
+ */
7304
+ getApiKey(apiKey) {
7305
+ return apiKey || this.apiKey || this.getDefaultApiKey();
7306
+ }
7307
+ /**
7308
+ * Check if a component contains a Form (data_modification component)
7309
+ * Forms have hardcoded defaultValues that become stale when cached
7310
+ * This checks both single Form components and Forms inside MultiComponentContainer
7311
+ */
7312
+ containsFormComponent(component) {
7313
+ if (!component) return false;
7314
+ if (component.type === "Form" || component.name === "DynamicForm") {
7315
+ return true;
7316
+ }
7317
+ if (component.type === "Container" || component.name === "MultiComponentContainer") {
7318
+ const nestedComponents = component.props?.config?.components || [];
7319
+ for (const nested of nestedComponents) {
7320
+ if (nested.type === "Form" || nested.name === "DynamicForm") {
7321
+ return true;
7322
+ }
7323
+ }
7324
+ }
7325
+ return false;
7326
+ }
7327
+ /**
7328
+ * Match components from text response suggestions and generate follow-up questions
7329
+ * Takes a text response with component suggestions (c1:type format) and matches with available components
7330
+ * Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
7331
+ * All components are placed in a default MultiComponentContainer layout
7332
+ * @param analysisContent - The text response containing component suggestions
7333
+ * @param components - List of available components
7334
+ * @param apiKey - Optional API key
7335
+ * @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
7336
+ * @returns Object containing matched components, layout title/description, and follow-up actions
7337
+ */
7338
+ async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, componentStreamCallback, deferredTools, executedTools, collections, userId) {
7339
+ const methodStartTime = Date.now();
7340
+ const methodName = "matchComponentsFromAnalysis";
7341
+ logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
7342
+ try {
6220
7343
  let availableComponentsText = "No components available";
6221
7344
  if (components && components.length > 0) {
6222
7345
  availableComponentsText = components.map((comp, idx) => {
@@ -6232,7 +7355,6 @@ Fixed SQL query:`;
6232
7355
  }
6233
7356
  let deferredToolsText = "No deferred external tools for this request.";
6234
7357
  if (deferredTools && deferredTools.length > 0) {
6235
- logger.info(`[${this.getProviderName()}] Passing ${deferredTools.length} deferred tools to component matching`);
6236
7358
  deferredToolsText = "The following external tools need user input via a Form component.\n**IMPORTANT: Use these EXACT values when generating Form externalTool prop.**\n\n" + deferredTools.map((tool, idx) => {
6237
7359
  return `${idx + 1}. **${tool.name}**
6238
7360
  toolId: "${tool.id}" (USE THIS EXACT VALUE - do not modify!)
@@ -6244,21 +7366,23 @@ Fixed SQL query:`;
6244
7366
  }
6245
7367
  let executedToolsText = "No external tools were executed for data fetching.";
6246
7368
  if (executedTools && executedTools.length > 0) {
6247
- logger.info(`[${this.getProviderName()}] Passing ${executedTools.length} executed tools to component matching`);
6248
- executedToolsText = "The following external tools were executed to fetch data.\n" + // '**IMPORTANT: For components displaying this data, use externalTool prop instead of query.**\n' +
6249
- // '**IMPORTANT: Use ONLY the field names listed in outputSchema for config keys.**\n\n' +
6250
- executedTools.map((tool, idx) => {
7369
+ executedToolsText = "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
6251
7370
  let outputSchemaText = "Not available";
6252
7371
  let fieldNamesList = "";
6253
- let recordCount = "unknown";
7372
+ const recordCount = tool.result?._totalRecords ?? "unknown";
7373
+ let metadataText = "";
7374
+ if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
7375
+ const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
7376
+ metadataText = `
7377
+ \u{1F4CB} METADATA: ${metadataEntries}`;
7378
+ }
6254
7379
  if (tool.outputSchema) {
6255
7380
  const fields = tool.outputSchema.fields || [];
6256
- recordCount = tool.result?._recordCount || (Array.isArray(tool.result) ? tool.result.length : "unknown");
6257
7381
  const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
6258
7382
  const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
6259
7383
  fieldNamesList = `
6260
- \u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
6261
- \u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
7384
+ \u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
7385
+ \u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
6262
7386
  const fieldsText = fields.map(
6263
7387
  (f) => ` "${f.name}" (${f.type}): ${f.description}`
6264
7388
  ).join("\n");
@@ -6267,11 +7391,11 @@ Fixed SQL query:`;
6267
7391
  ${fieldsText}`;
6268
7392
  }
6269
7393
  return `${idx + 1}. **${tool.name}**
6270
- toolId: "${tool.id}"
6271
- toolName: "${tool.name}"
6272
- parameters: ${JSON.stringify(tool.params || {})}
6273
- recordCount: ${recordCount} rows returned
6274
- outputSchema: ${outputSchemaText}${fieldNamesList}`;
7394
+ toolId: "${tool.id}"
7395
+ toolName: "${tool.name}"
7396
+ parameters: ${JSON.stringify(tool.params || {})}
7397
+ recordCount: ${recordCount} rows returned${metadataText}
7398
+ outputSchema: ${outputSchemaText}${fieldNamesList}`;
6275
7399
  }).join("\n\n");
6276
7400
  }
6277
7401
  const schemaDoc = schema.generateSchemaDocumentation();
@@ -6282,7 +7406,7 @@ ${fieldsText}`;
6282
7406
  prompt: userPrompt || analysisContent,
6283
7407
  collections,
6284
7408
  userId,
6285
- topK: 3
7409
+ topK: KNOWLEDGE_BASE_TOP_K
6286
7410
  });
6287
7411
  knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
6288
7412
  }
@@ -6297,30 +7421,12 @@ ${fieldsText}`;
6297
7421
  KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext,
6298
7422
  CURRENT_DATETIME: getCurrentDateTimeForPrompt()
6299
7423
  });
6300
- logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
6301
- const extractPromptText = (content) => {
6302
- if (typeof content === "string") return content;
6303
- if (Array.isArray(content)) {
6304
- return content.map((item) => {
6305
- if (typeof item === "string") return item;
6306
- if (item && typeof item.text === "string") return item.text;
6307
- if (item && item.content && typeof item.content === "string") return item.content;
6308
- return JSON.stringify(item, null, 2);
6309
- }).join("\n\n---\n\n");
6310
- }
6311
- if (content && typeof content === "object") {
6312
- if (typeof content.text === "string") return content.text;
6313
- return JSON.stringify(content, null, 2);
6314
- }
6315
- return String(content);
6316
- };
6317
7424
  logger.logLLMPrompt("matchComponentsFromAnalysis", "system", extractPromptText(prompts.system));
6318
7425
  logger.logLLMPrompt("matchComponentsFromAnalysis", "user", `Text Analysis:
6319
7426
  ${analysisContent}
6320
7427
 
6321
7428
  Executed Tools:
6322
7429
  ${executedToolsText}`);
6323
- logCollector?.info("Matching components from text response...");
6324
7430
  let fullResponseText = "";
6325
7431
  let answerComponentExtracted = false;
6326
7432
  const answerCallback = componentStreamCallback;
@@ -6380,52 +7486,44 @@ ${executedToolsText}`);
6380
7486
  ...answerComponentData.props
6381
7487
  }
6382
7488
  };
6383
- const streamTime = (/* @__PURE__ */ new Date()).toISOString();
6384
- logger.info(`[${this.getProviderName()}] \u2713 [${streamTime}] Answer component detected in stream: ${answerComponent.name} (${answerComponent.type})`);
6385
- logCollector?.info(`\u2713 Answer component: ${answerComponent.name} (${answerComponent.type}) - detected at ${streamTime}`);
6386
- if (answerComponentData.props?.query) {
6387
- logCollector?.logQuery(
6388
- "Answer component query",
6389
- answerComponentData.props.query,
6390
- { componentName: answerComponent.name, componentType: answerComponent.type, reasoning: answerComponentData.reasoning }
6391
- );
7489
+ let answerQuery = answerComponent.props?.query;
7490
+ if (answerQuery) {
7491
+ if (typeof answerQuery === "string") {
7492
+ answerQuery = ensureQueryLimit(answerQuery, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
7493
+ } else if (answerQuery?.sql) {
7494
+ const queryObj = answerQuery;
7495
+ answerQuery = { ...queryObj, sql: ensureQueryLimit(queryObj.sql, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT) };
7496
+ }
7497
+ answerComponent.props.query = answerQuery;
6392
7498
  }
6393
- const answerQuery = answerComponent.props?.query;
6394
- logger.info(`[${this.getProviderName()}] Answer component detected: ${answerComponent.name} (${answerComponent.type}), hasQuery: ${!!answerQuery}, hasDbExecute: ${!!collections?.["database"]?.["execute"]}`);
6395
7499
  if (answerQuery && collections?.["database"]?.["execute"]) {
6396
7500
  (async () => {
6397
- const MAX_RETRIES = 3;
7501
+ const maxRetries = MAX_QUERY_VALIDATION_RETRIES;
6398
7502
  let attempts = 0;
6399
7503
  let validated = false;
6400
7504
  let currentQuery = answerQuery;
6401
7505
  let currentQueryStr = typeof answerQuery === "string" ? answerQuery : answerQuery?.sql || "";
6402
7506
  let lastError = "";
6403
- logger.info(`[${this.getProviderName()}] Validating answer component query before streaming...`);
6404
- while (attempts < MAX_RETRIES && !validated) {
7507
+ while (attempts < maxRetries && !validated) {
6405
7508
  attempts++;
6406
7509
  try {
6407
- const cacheKey = this.getQueryCacheKey(currentQuery);
7510
+ const cacheKey = this.queryService.getQueryCacheKey(currentQuery);
6408
7511
  if (cacheKey) {
6409
- logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${MAX_RETRIES}`);
6410
7512
  const result2 = await collections["database"]["execute"]({ sql: cacheKey });
6411
7513
  queryCache.set(cacheKey, result2);
6412
7514
  validated = true;
6413
7515
  if (currentQuery !== answerQuery) {
6414
7516
  answerComponent.props.query = currentQuery;
6415
7517
  }
6416
- logger.info(`[${this.getProviderName()}] \u2713 Answer component query validated (attempt ${attempts}) - STREAMING TO FRONTEND NOW`);
6417
- logCollector?.info(`\u2713 Answer component query validated - streaming to frontend`);
6418
- logger.info(`[${this.getProviderName()}] Calling answerCallback for: ${answerComponent.name}`);
6419
7518
  answerCallback(answerComponent);
6420
- logger.info(`[${this.getProviderName()}] answerCallback completed for: ${answerComponent.name}`);
6421
7519
  }
6422
7520
  } catch (validationError) {
6423
7521
  lastError = validationError instanceof Error ? validationError.message : String(validationError);
6424
- logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
6425
- if (attempts < MAX_RETRIES) {
7522
+ logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${maxRetries}): ${lastError}`);
7523
+ if (attempts < maxRetries) {
6426
7524
  try {
6427
7525
  logger.info(`[${this.getProviderName()}] Requesting LLM to fix answer component query...`);
6428
- const fixedQueryStr = await this.requestQueryFix(
7526
+ const fixedQueryStr = await this.queryService.requestQueryFix(
6429
7527
  currentQueryStr,
6430
7528
  lastError,
6431
7529
  {
@@ -6435,7 +7533,7 @@ ${executedToolsText}`);
6435
7533
  },
6436
7534
  apiKey
6437
7535
  );
6438
- const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
7536
+ const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6439
7537
  if (typeof currentQuery === "string") {
6440
7538
  currentQuery = limitedFixedQuery;
6441
7539
  } else {
@@ -6453,7 +7551,6 @@ ${executedToolsText}`);
6453
7551
  }
6454
7552
  if (!validated) {
6455
7553
  logger.warn(`[${this.getProviderName()}] Answer component query validation failed after ${attempts} attempts - component will be excluded`);
6456
- logCollector?.warn(`Answer component query validation failed: ${lastError} - component will be excluded from response`);
6457
7554
  }
6458
7555
  })();
6459
7556
  } else {
@@ -6464,7 +7561,7 @@ ${executedToolsText}`);
6464
7561
  }
6465
7562
  }
6466
7563
  } catch (e) {
6467
- logger.debug(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
7564
+ logger.error(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
6468
7565
  }
6469
7566
  }
6470
7567
  }
@@ -6476,7 +7573,7 @@ ${executedToolsText}`);
6476
7573
  },
6477
7574
  {
6478
7575
  model: this.getModelForTask("complex"),
6479
- maxTokens: 8192,
7576
+ maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
6480
7577
  temperature: 0,
6481
7578
  apiKey: this.getApiKey(apiKey),
6482
7579
  partial: partialCallback
@@ -6494,155 +7591,20 @@ ${executedToolsText}`);
6494
7591
  logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
6495
7592
  const rawActions = result.actions || [];
6496
7593
  const actions = convertQuestionsToActions(rawActions);
6497
- if (matchedComponents.length > 0) {
6498
- matchedComponents.forEach((comp, idx) => {
6499
- logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
6500
- if (comp.props?.query) {
6501
- logCollector?.logQuery(
6502
- `Component ${idx + 1} query`,
6503
- comp.props.query,
6504
- { componentName: comp.componentName, title: comp.props.title }
6505
- );
6506
- }
6507
- });
6508
- }
6509
7594
  const finalComponents = matchedComponents.map((mc) => {
6510
7595
  const originalComponent = components.find((c) => c.id === mc.componentId);
6511
7596
  if (!originalComponent) {
6512
- logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
6513
- return null;
6514
- }
6515
- let cleanedProps = { ...mc.props };
6516
- if (cleanedProps.externalTool) {
6517
- const toolId = cleanedProps.externalTool.toolId;
6518
- const validToolIds = (executedTools || []).map((t) => t.id);
6519
- const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
6520
- if (!isValidTool) {
6521
- logger.warn(`[${this.getProviderName()}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
6522
- cleanedProps.externalTool = null;
6523
- } else {
6524
- const executedTool = executedTools?.find((t) => t.id === toolId);
6525
- if (executedTool?.outputSchema?.fields && cleanedProps.config) {
6526
- const validFieldNames = executedTool.outputSchema.fields.map((f) => f.name);
6527
- const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
6528
- const findMatchingField = (fieldName, configKey) => {
6529
- if (!fieldName) return null;
6530
- const lowerField = fieldName.toLowerCase();
6531
- const exactIdx = validFieldNamesLower.indexOf(lowerField);
6532
- if (exactIdx !== -1) return validFieldNames[exactIdx];
6533
- const containsMatches = validFieldNames.filter(
6534
- (_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
6535
- );
6536
- if (containsMatches.length === 1) return containsMatches[0];
6537
- const fieldTypes = executedTool.outputSchema.fields.reduce((acc, f) => {
6538
- acc[f.name] = f.type;
6539
- return acc;
6540
- }, {});
6541
- const numericConfigKeys = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
6542
- const stringConfigKeys = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
6543
- if (numericConfigKeys.includes(configKey)) {
6544
- const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
6545
- const match = numericFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
6546
- if (match) return match;
6547
- if (numericFields.length > 0) {
6548
- logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
6549
- return numericFields[0];
6550
- }
6551
- }
6552
- if (stringConfigKeys.includes(configKey)) {
6553
- const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
6554
- const match = stringFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
6555
- if (match) return match;
6556
- if (stringFields.length > 0) {
6557
- logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
6558
- return stringFields[0];
6559
- }
6560
- }
6561
- logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
6562
- return validFieldNames[0];
6563
- };
6564
- const configFieldsToValidate = [
6565
- "xAxisKey",
6566
- "yAxisKey",
6567
- "valueKey",
6568
- "nameKey",
6569
- "labelKey",
6570
- "groupBy",
6571
- "aggregationField",
6572
- "seriesKey",
6573
- "sizeKey",
6574
- "xAggregationField",
6575
- "yAggregationField"
6576
- ];
6577
- for (const configKey of configFieldsToValidate) {
6578
- const fieldValue = cleanedProps.config[configKey];
6579
- if (fieldValue && typeof fieldValue === "string") {
6580
- if (!validFieldNames.includes(fieldValue)) {
6581
- const correctedField = findMatchingField(fieldValue, configKey);
6582
- if (correctedField) {
6583
- logger.warn(`[${this.getProviderName()}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
6584
- cleanedProps.config[configKey] = correctedField;
6585
- }
6586
- }
6587
- }
6588
- }
6589
- if (Array.isArray(cleanedProps.config.series)) {
6590
- cleanedProps.config.series = cleanedProps.config.series.map((s) => {
6591
- if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
6592
- const correctedField = findMatchingField(s.dataKey, "yAxisKey");
6593
- if (correctedField) {
6594
- logger.warn(`[${this.getProviderName()}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
6595
- return { ...s, dataKey: correctedField };
6596
- }
6597
- }
6598
- return s;
6599
- });
6600
- }
6601
- }
6602
- }
6603
- }
6604
- if (cleanedProps.query) {
6605
- const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
6606
- if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
6607
- logger.warn(`[${this.getProviderName()}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
6608
- cleanedProps.query = null;
6609
- }
6610
- }
6611
- if (cleanedProps.query) {
6612
- const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
6613
- const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
6614
- if (fixed) {
6615
- logger.warn(`[${this.getProviderName()}] SQL fixes applied to component query: ${fixes.join("; ")}`);
6616
- if (typeof cleanedProps.query === "string") {
6617
- cleanedProps.query = fixedQuery;
6618
- } else if (cleanedProps.query?.sql) {
6619
- cleanedProps.query.sql = fixedQuery;
6620
- }
6621
- }
6622
- }
6623
- if (cleanedProps.query) {
6624
- if (typeof cleanedProps.query === "string") {
6625
- cleanedProps.query = ensureQueryLimit(
6626
- cleanedProps.query,
6627
- this.defaultLimit,
6628
- 10
6629
- // maxLimit - enforce maximum of 10 rows for component queries
6630
- );
6631
- } else if (cleanedProps.query?.sql) {
6632
- cleanedProps.query = {
6633
- ...cleanedProps.query,
6634
- sql: ensureQueryLimit(
6635
- cleanedProps.query.sql,
6636
- this.defaultLimit,
6637
- 10
6638
- // maxLimit - enforce maximum of 10 rows for component queries
6639
- )
6640
- };
6641
- }
6642
- }
6643
- if (cleanedProps.query && cleanedProps.externalTool) {
6644
- logger.info(`[${this.getProviderName()}] Both query and externalTool exist, keeping both - frontend will decide`);
7597
+ logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
7598
+ return null;
6645
7599
  }
7600
+ const cleanedProps = processComponentProps(
7601
+ mc.props,
7602
+ executedTools,
7603
+ {
7604
+ providerName: this.getProviderName(),
7605
+ defaultLimit: this.defaultLimit
7606
+ }
7607
+ );
6646
7608
  return {
6647
7609
  ...originalComponent,
6648
7610
  props: {
@@ -6653,27 +7615,22 @@ ${executedToolsText}`);
6653
7615
  }).filter(Boolean);
6654
7616
  let validatedComponents = finalComponents;
6655
7617
  if (collections?.["database"]?.["execute"]) {
6656
- logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
6657
- logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
6658
7618
  try {
6659
- const validationResult = await this.validateAndRetryComponentQueries(
7619
+ const validationResult = await this.queryService.validateComponentQueries(
6660
7620
  finalComponents,
6661
7621
  collections,
6662
- apiKey,
6663
- logCollector
7622
+ apiKey
6664
7623
  );
6665
7624
  validatedComponents = validationResult.components;
6666
7625
  const queriedComponents = finalComponents.filter((c) => c.props?.query);
6667
7626
  const validatedQueries = validatedComponents.filter((c) => c.props?.query);
6668
7627
  logger.info(`[${this.getProviderName()}] Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6669
- logCollector?.info(`Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6670
7628
  } catch (validationError) {
6671
7629
  const validationErrorMsg = validationError instanceof Error ? validationError.message : String(validationError);
6672
7630
  logger.error(`[${this.getProviderName()}] Query validation error: ${validationErrorMsg}`);
6673
- logCollector?.error(`Query validation error: ${validationErrorMsg}`);
6674
7631
  }
6675
7632
  } else {
6676
- logger.debug(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
7633
+ logger.error(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
6677
7634
  }
6678
7635
  const methodDuration = Date.now() - methodStartTime;
6679
7636
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${validatedComponents.length} | actions: ${actions.length}`);
@@ -6687,7 +7644,6 @@ ${executedToolsText}`);
6687
7644
  const methodDuration = Date.now() - methodStartTime;
6688
7645
  const errorMsg = error instanceof Error ? error.message : String(error);
6689
7646
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
6690
- logCollector?.error(`Failed to match components: ${errorMsg}`);
6691
7647
  return {
6692
7648
  components: [],
6693
7649
  layoutTitle: "Dashboard",
@@ -6696,158 +7652,11 @@ ${executedToolsText}`);
6696
7652
  };
6697
7653
  }
6698
7654
  }
6699
- /**
6700
- * Validate a single component's query with retry logic
6701
- * @param component - The component to validate
6702
- * @param collections - Collections object containing database execute function
6703
- * @param apiKey - Optional API key for LLM calls
6704
- * @param logCollector - Optional log collector for logging
6705
- * @returns Object with validated component (or null if failed) and query result
6706
- */
6707
- async validateSingleComponentQuery(component, collections, apiKey, logCollector) {
6708
- const MAX_RETRIES = 3;
6709
- const query = component.props?.query;
6710
- const originalQueryKey = this.getQueryCacheKey(query);
6711
- const queryStr = typeof query === "string" ? query : query?.sql || "";
6712
- let finalQueryKey = originalQueryKey;
6713
- let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
6714
- let currentQueryStr = queryStr;
6715
- let validated = false;
6716
- let lastError = "";
6717
- let result = null;
6718
- let attempts = 0;
6719
- logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
6720
- while (attempts < MAX_RETRIES && !validated) {
6721
- attempts++;
6722
- try {
6723
- logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
6724
- const validationResult = await this.executeQueryForValidation(currentQuery, collections);
6725
- result = validationResult.result;
6726
- validated = true;
6727
- queryCache.set(validationResult.cacheKey, result);
6728
- logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
6729
- logCollector?.info(`\u2713 Query validated for ${component.name}`);
6730
- if (currentQueryStr !== queryStr) {
6731
- const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
6732
- component.props = {
6733
- ...component.props,
6734
- query: fixedQuery
6735
- };
6736
- finalQueryKey = this.getQueryCacheKey(fixedQuery);
6737
- logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
6738
- }
6739
- } catch (error) {
6740
- lastError = error instanceof Error ? error.message : String(error);
6741
- logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
6742
- logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
6743
- if (attempts >= MAX_RETRIES) {
6744
- logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, excluding from response`);
6745
- logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
6746
- break;
6747
- }
6748
- logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
6749
- logCollector?.info(`Requesting query fix for ${component.name}...`);
6750
- try {
6751
- const fixedQueryStr = await this.requestQueryFix(
6752
- currentQueryStr,
6753
- lastError,
6754
- {
6755
- name: component.name,
6756
- type: component.type,
6757
- title: component.props?.title
6758
- },
6759
- apiKey
6760
- );
6761
- if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
6762
- logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
6763
- const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
6764
- currentQueryStr = limitedFixedQuery;
6765
- if (typeof currentQuery === "string") {
6766
- currentQuery = limitedFixedQuery;
6767
- } else {
6768
- currentQuery = { ...currentQuery, sql: limitedFixedQuery };
6769
- }
6770
- } else {
6771
- logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
6772
- break;
6773
- }
6774
- } catch (fixError) {
6775
- const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
6776
- logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
6777
- break;
6778
- }
6779
- }
6780
- }
6781
- if (!validated) {
6782
- logger.warn(`[${this.getProviderName()}] Component ${component.name} excluded from response due to failed query validation`);
6783
- logCollector?.warn(`Component ${component.name} excluded from response`);
6784
- }
6785
- return {
6786
- component: validated ? component : null,
6787
- queryKey: finalQueryKey,
6788
- result,
6789
- validated
6790
- };
6791
- }
6792
- /**
6793
- * Validate component queries against the database and retry with LLM fixes if they fail
6794
- * Uses parallel execution for faster validation
6795
- * @param components - Array of components with potential queries
6796
- * @param collections - Collections object containing database execute function
6797
- * @param apiKey - Optional API key for LLM calls
6798
- * @param logCollector - Optional log collector for logging
6799
- * @returns Object with validated components and a map of query results
6800
- */
6801
- async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
6802
- const queryResults = /* @__PURE__ */ new Map();
6803
- const validatedComponents = [];
6804
- const componentsWithoutQuery = [];
6805
- const componentsWithQuery = [];
6806
- for (const component of components) {
6807
- if (!component.props?.query) {
6808
- componentsWithoutQuery.push(component);
6809
- } else {
6810
- componentsWithQuery.push(component);
6811
- }
6812
- }
6813
- validatedComponents.push(...componentsWithoutQuery);
6814
- if (componentsWithQuery.length === 0) {
6815
- return { components: validatedComponents, queryResults };
6816
- }
6817
- logger.info(`[${this.getProviderName()}] Validating ${componentsWithQuery.length} component queries in parallel...`);
6818
- logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
6819
- const validationPromises = componentsWithQuery.map(
6820
- (component) => this.validateSingleComponentQuery(component, collections, apiKey, logCollector)
6821
- );
6822
- const results = await Promise.allSettled(validationPromises);
6823
- for (let i = 0; i < results.length; i++) {
6824
- const result = results[i];
6825
- const component = componentsWithQuery[i];
6826
- if (result.status === "fulfilled") {
6827
- const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
6828
- if (validated && validatedComponent) {
6829
- validatedComponents.push(validatedComponent);
6830
- if (queryResult) {
6831
- queryResults.set(queryKey, queryResult);
6832
- queryResults.set(`${component.id}:${queryKey}`, queryResult);
6833
- }
6834
- }
6835
- } else {
6836
- logger.error(`[${this.getProviderName()}] Unexpected error validating ${component.name}: ${result.reason}`);
6837
- logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
6838
- }
6839
- }
6840
- logger.info(`[${this.getProviderName()}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
6841
- return {
6842
- components: validatedComponents,
6843
- queryResults
6844
- };
6845
- }
6846
7655
  /**
6847
7656
  * Classify user question into category and detect external tools needed
6848
7657
  * Determines if question is for data analysis, requires external tools, or needs text response
6849
7658
  */
6850
- async classifyQuestionCategory(userPrompt, apiKey, logCollector, conversationHistory, externalTools) {
7659
+ async classifyQuestionCategory(userPrompt, apiKey, conversationHistory, externalTools) {
6851
7660
  const methodStartTime = Date.now();
6852
7661
  const methodName = "classifyQuestionCategory";
6853
7662
  const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
@@ -6867,24 +7676,8 @@ ${executedToolsText}`);
6867
7676
  SCHEMA_DOC: schemaDoc || "No database schema available",
6868
7677
  CURRENT_DATETIME: getCurrentDateTimeForPrompt()
6869
7678
  });
6870
- const extractTextContent = (content) => {
6871
- if (typeof content === "string") return content;
6872
- if (Array.isArray(content)) {
6873
- return content.map((item) => {
6874
- if (typeof item === "string") return item;
6875
- if (item && typeof item.text === "string") return item.text;
6876
- if (item && item.content && typeof item.content === "string") return item.content;
6877
- return JSON.stringify(item, null, 2);
6878
- }).join("\n\n---\n\n");
6879
- }
6880
- if (content && typeof content === "object") {
6881
- if (typeof content.text === "string") return content.text;
6882
- return JSON.stringify(content, null, 2);
6883
- }
6884
- return String(content);
6885
- };
6886
- logger.logLLMPrompt("classifyQuestionCategory", "system", extractTextContent(prompts.system));
6887
- logger.logLLMPrompt("classifyQuestionCategory", "user", extractTextContent(prompts.user));
7679
+ logger.logLLMPrompt("classifyQuestionCategory", "system", extractPromptText(prompts.system));
7680
+ logger.logLLMPrompt("classifyQuestionCategory", "user", extractPromptText(prompts.user));
6888
7681
  const result = await LLM.stream(
6889
7682
  {
6890
7683
  sys: prompts.system,
@@ -6892,23 +7685,13 @@ ${executedToolsText}`);
6892
7685
  },
6893
7686
  {
6894
7687
  model: this.getModelForTask("simple"),
6895
- maxTokens: 1500,
7688
+ maxTokens: MAX_TOKENS_CLASSIFICATION,
6896
7689
  temperature: 0,
6897
7690
  apiKey: this.getApiKey(apiKey)
6898
7691
  },
6899
7692
  true
6900
7693
  // Parse as JSON
6901
7694
  );
6902
- logCollector?.logExplanation(
6903
- "Question category classified",
6904
- result.reasoning || "No reasoning provided",
6905
- {
6906
- category: result.category,
6907
- externalTools: result.externalTools || [],
6908
- dataAnalysisType: result.dataAnalysisType,
6909
- confidence: result.confidence
6910
- }
6911
- );
6912
7695
  const methodDuration = Date.now() - methodStartTime;
6913
7696
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | category: ${result.category} | confidence: ${result.confidence}% | tools: ${(result.externalTools || []).length}`);
6914
7697
  return {
@@ -6922,7 +7705,6 @@ ${executedToolsText}`);
6922
7705
  const methodDuration = Date.now() - methodStartTime;
6923
7706
  const errorMsg = error instanceof Error ? error.message : String(error);
6924
7707
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
6925
- logger.debug(`[${this.getProviderName()}] Category classification error details:`, error);
6926
7708
  throw error;
6927
7709
  }
6928
7710
  }
@@ -6931,7 +7713,7 @@ ${executedToolsText}`);
6931
7713
  * Takes a matched UI block from semantic search and modifies its props to answer the new question
6932
7714
  * Also adapts the cached text response to match the new question
6933
7715
  */
6934
- async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, logCollector, cachedTextResponse) {
7716
+ async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, cachedTextResponse) {
6935
7717
  const methodStartTime = Date.now();
6936
7718
  const methodName = "adaptUIBlockParameters";
6937
7719
  const promptPreview = currentUserPrompt.substring(0, 50) + (currentUserPrompt.length > 50 ? "..." : "");
@@ -6965,7 +7747,7 @@ ${executedToolsText}`);
6965
7747
  },
6966
7748
  {
6967
7749
  model: this.getModelForTask("complex"),
6968
- maxTokens: 8192,
7750
+ maxTokens: MAX_TOKENS_ADAPTATION,
6969
7751
  temperature: 0,
6970
7752
  apiKey: this.getApiKey(apiKey)
6971
7753
  },
@@ -6975,11 +7757,6 @@ ${executedToolsText}`);
6975
7757
  if (!result.success) {
6976
7758
  const methodDuration2 = Date.now() - methodStartTime;
6977
7759
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: adaptation failed - ${result.reason}`);
6978
- logCollector?.warn(
6979
- "Could not adapt matched UI block",
6980
- "explanation",
6981
- { reason: result.reason }
6982
- );
6983
7760
  return {
6984
7761
  success: false,
6985
7762
  explanation: result.explanation || "Adaptation not possible"
@@ -6991,14 +7768,6 @@ ${executedToolsText}`);
6991
7768
  this.defaultLimit
6992
7769
  );
6993
7770
  }
6994
- logCollector?.logExplanation(
6995
- "UI block parameters adapted",
6996
- result.explanation || "Parameters adapted successfully",
6997
- {
6998
- parametersChanged: result.parametersChanged || [],
6999
- componentType: result.adaptedComponent?.type
7000
- }
7001
- );
7002
7771
  const methodDuration = Date.now() - methodStartTime;
7003
7772
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | result: success | changes: ${(result.parametersChanged || []).length}`);
7004
7773
  return {
@@ -7012,7 +7781,6 @@ ${executedToolsText}`);
7012
7781
  const methodDuration = Date.now() - methodStartTime;
7013
7782
  const errorMsg = error instanceof Error ? error.message : String(error);
7014
7783
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
7015
- logger.debug(`[${this.getProviderName()}] Adaptation error details:`, error);
7016
7784
  return {
7017
7785
  success: false,
7018
7786
  explanation: `Error adapting parameters: ${errorMsg}`
@@ -7025,14 +7793,12 @@ ${executedToolsText}`);
7025
7793
  * Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
7026
7794
  * After generating text response, if components are provided, matches suggested components
7027
7795
  */
7028
- async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
7796
+ async generateTextResponse(userPrompt, apiKey, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
7029
7797
  const methodStartTime = Date.now();
7030
7798
  const methodName = "generateTextResponse";
7031
7799
  const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
7032
7800
  logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")} | category: ${category} | prompt: "${promptPreview}"`);
7033
7801
  const errors = [];
7034
- logger.debug(`[${this.getProviderName()}] Starting text response generation`);
7035
- logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
7036
7802
  try {
7037
7803
  let availableToolsDoc = "No external tools are available for this request.";
7038
7804
  if (externalTools && externalTools.length > 0) {
@@ -7082,7 +7848,7 @@ ${executedToolsText}`);
7082
7848
  prompt: userPrompt,
7083
7849
  collections,
7084
7850
  userId,
7085
- topK: 3
7851
+ topK: KNOWLEDGE_BASE_TOP_K
7086
7852
  });
7087
7853
  const knowledgeBaseContext = kbResult.combinedContext;
7088
7854
  const prompts = await promptLoader.loadPrompts("text-response", {
@@ -7094,27 +7860,8 @@ ${executedToolsText}`);
7094
7860
  AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc,
7095
7861
  CURRENT_DATETIME: getCurrentDateTimeForPrompt()
7096
7862
  });
7097
- const extractText = (content) => {
7098
- if (typeof content === "string") return content;
7099
- if (Array.isArray(content)) {
7100
- return content.map((item) => {
7101
- if (typeof item === "string") return item;
7102
- if (item && typeof item.text === "string") return item.text;
7103
- if (item && item.content && typeof item.content === "string") return item.content;
7104
- return JSON.stringify(item, null, 2);
7105
- }).join("\n\n---\n\n");
7106
- }
7107
- if (content && typeof content === "object") {
7108
- if (typeof content.text === "string") return content.text;
7109
- return JSON.stringify(content, null, 2);
7110
- }
7111
- return String(content);
7112
- };
7113
- logger.logLLMPrompt("generateTextResponse", "system", extractText(prompts.system));
7114
- logger.logLLMPrompt("generateTextResponse", "user", extractText(prompts.user));
7115
- logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
7116
- logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
7117
- logCollector?.info("Generating text response with query execution capability...");
7863
+ logger.logLLMPrompt("generateTextResponse", "system", extractPromptText(prompts.system));
7864
+ logger.logLLMPrompt("generateTextResponse", "user", extractPromptText(prompts.user));
7118
7865
  const tools = [{
7119
7866
  name: "execute_query",
7120
7867
  description: "Executes a parameterized SQL query against the database. CRITICAL: NEVER hardcode literal values in WHERE/HAVING conditions - ALWAYS use $paramName placeholders and pass actual values in params object.",
@@ -7143,7 +7890,6 @@ ${executedToolsText}`);
7143
7890
  const executableTools = externalTools.filter(
7144
7891
  (t) => t.executionType === "immediate" || t.executionType === "deferred" && t.userProvidedData
7145
7892
  );
7146
- logger.info(`[${this.getProviderName()}] Executable tools: ${executableTools.length} of ${externalTools.length} total`);
7147
7893
  const addedToolIds = /* @__PURE__ */ new Set();
7148
7894
  executableTools.forEach((tool) => {
7149
7895
  if (addedToolIds.has(tool.id)) {
@@ -7151,7 +7897,6 @@ ${executedToolsText}`);
7151
7897
  return;
7152
7898
  }
7153
7899
  addedToolIds.add(tool.id);
7154
- logger.info(`[${this.getProviderName()}] Processing executable tool:`, JSON.stringify(tool, null, 2));
7155
7900
  const properties = {};
7156
7901
  const required = [];
7157
7902
  Object.entries(tool.params || {}).forEach(([key, typeOrValue]) => {
@@ -7220,327 +7965,26 @@ ${executedToolsText}`);
7220
7965
  input_schema: inputSchema
7221
7966
  });
7222
7967
  });
7223
- logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
7224
- logger.info(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
7225
- }
7226
- const queryAttempts = /* @__PURE__ */ new Map();
7227
- const MAX_QUERY_ATTEMPTS = 6;
7228
- const toolAttempts = /* @__PURE__ */ new Map();
7229
- const MAX_TOOL_ATTEMPTS = 3;
7230
- const executedToolsList = [];
7231
- let maxAttemptsReached = false;
7232
- let fullStreamedText = "";
7233
- let streamBuffer = "";
7234
- let flushTimer = null;
7235
- const FLUSH_INTERVAL = 50;
7236
- const flushStreamBuffer = () => {
7237
- if (streamBuffer && streamCallback) {
7238
- streamCallback(streamBuffer);
7239
- streamBuffer = "";
7240
- }
7241
- flushTimer = null;
7242
- };
7243
- const wrappedStreamCallback = streamCallback ? (chunk) => {
7244
- fullStreamedText += chunk;
7245
- streamBuffer += chunk;
7246
- if (chunk.includes("\n") || chunk.length > 100) {
7247
- if (flushTimer) {
7248
- clearTimeout(flushTimer);
7249
- flushTimer = null;
7250
- }
7251
- flushStreamBuffer();
7252
- } else if (!flushTimer) {
7253
- flushTimer = setTimeout(flushStreamBuffer, FLUSH_INTERVAL);
7254
- }
7255
- } : void 0;
7256
- const flushStream = () => {
7257
- if (flushTimer) {
7258
- clearTimeout(flushTimer);
7259
- flushTimer = null;
7260
- }
7261
- flushStreamBuffer();
7262
- };
7263
- const streamDelay = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
7264
- const withProgressHeartbeat = async (operation, progressMessage, intervalMs = 1e3) => {
7265
- if (!wrappedStreamCallback) {
7266
- return operation();
7267
- }
7268
- const startTime = Date.now();
7269
- await streamDelay(30);
7270
- wrappedStreamCallback(`\u23F3 ${progressMessage}`);
7271
- const heartbeatInterval = setInterval(() => {
7272
- const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
7273
- if (elapsedSeconds >= 1) {
7274
- wrappedStreamCallback(` (${elapsedSeconds}s)`);
7275
- }
7276
- }, intervalMs);
7277
- try {
7278
- const result2 = await operation();
7279
- return result2;
7280
- } finally {
7281
- clearInterval(heartbeatInterval);
7282
- wrappedStreamCallback("\n\n");
7283
- }
7284
- };
7285
- const toolHandler = async (toolName, toolInput) => {
7286
- if (toolName === "execute_query") {
7287
- let sql = toolInput.sql;
7288
- const params = toolInput.params || {};
7289
- const reasoning = toolInput.reasoning;
7290
- sql = ensureQueryLimit(sql, 10, 10);
7291
- const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
7292
- const attempts = (queryAttempts.get(queryKey) || 0) + 1;
7293
- queryAttempts.set(queryKey, attempts);
7294
- logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
7295
- if (Object.keys(params).length > 0) {
7296
- logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
7297
- }
7298
- if (reasoning) {
7299
- logCollector?.info(`Query reasoning: ${reasoning}`);
7300
- }
7301
- if (attempts > MAX_QUERY_ATTEMPTS) {
7302
- const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
7303
- logger.error(`[${this.getProviderName()}] ${errorMsg}`);
7304
- logCollector?.error(errorMsg);
7305
- maxAttemptsReached = true;
7306
- if (wrappedStreamCallback) {
7307
- wrappedStreamCallback(`
7308
-
7309
- \u274C ${errorMsg}
7310
-
7311
- Please try rephrasing your question or simplifying your request.
7312
-
7313
- `);
7314
- }
7315
- throw new Error(errorMsg);
7316
- }
7317
- try {
7318
- flushStream();
7319
- if (wrappedStreamCallback) {
7320
- const paramsDisplay = Object.keys(params).length > 0 ? `
7321
- **Parameters:** ${JSON.stringify(params)}` : "";
7322
- if (attempts === 1) {
7323
- wrappedStreamCallback(`
7324
-
7325
- \u{1F50D} **Analyzing your question...**
7326
-
7327
- `);
7328
- await streamDelay(50);
7329
- if (reasoning) {
7330
- wrappedStreamCallback(`\u{1F4AD} ${reasoning}
7331
-
7332
- `);
7333
- await streamDelay(50);
7334
- }
7335
- wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
7336
- \`\`\`sql
7337
- ${sql}
7338
- \`\`\`${paramsDisplay}
7339
-
7340
- `);
7341
- await streamDelay(50);
7342
- } else {
7343
- wrappedStreamCallback(`
7344
-
7345
- \u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
7346
-
7347
- `);
7348
- await streamDelay(50);
7349
- if (reasoning) {
7350
- wrappedStreamCallback(`\u{1F4AD} ${reasoning}
7351
-
7352
- `);
7353
- await streamDelay(50);
7354
- }
7355
- wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
7356
- \`\`\`sql
7357
- ${sql}
7358
- \`\`\`${paramsDisplay}
7359
-
7360
- `);
7361
- await streamDelay(50);
7362
- }
7363
- }
7364
- logCollector?.logQuery(
7365
- `Executing SQL query (attempt ${attempts})`,
7366
- { sql, params },
7367
- { reasoning, attempt: attempts }
7368
- );
7369
- if (!collections || !collections["database"] || !collections["database"]["execute"]) {
7370
- throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
7371
- }
7372
- const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
7373
- const result2 = await withProgressHeartbeat(
7374
- () => collections["database"]["execute"](queryPayload),
7375
- "Executing database query",
7376
- 800
7377
- // Send heartbeat every 800ms for responsive feedback
7378
- );
7379
- const data = result2?.data || result2;
7380
- const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
7381
- logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
7382
- logCollector?.info(`Query successful, returned ${rowCount} rows`);
7383
- if (wrappedStreamCallback) {
7384
- wrappedStreamCallback(`
7385
- \u2705 **Query executed successfully!**
7386
-
7387
- `);
7388
- await streamDelay(50);
7389
- if (Array.isArray(data) && data.length > 0) {
7390
- const firstRow = data[0];
7391
- const columns = Object.keys(firstRow);
7392
- if (data.length === 1 && columns.length === 1) {
7393
- const value = firstRow[columns[0]];
7394
- wrappedStreamCallback(`**Result:** ${value}
7395
-
7396
- `);
7397
- await streamDelay(50);
7398
- } else if (data.length > 0) {
7399
- wrappedStreamCallback(`**Retrieved ${rowCount} rows**
7400
-
7401
- `);
7402
- await streamDelay(50);
7403
- wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
7404
-
7405
- `);
7406
- await streamDelay(50);
7407
- }
7408
- } else if (Array.isArray(data) && data.length === 0) {
7409
- wrappedStreamCallback(`**No rows returned.**
7410
-
7411
- `);
7412
- await streamDelay(50);
7413
- }
7414
- wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
7415
-
7416
- `);
7417
- }
7418
- return JSON.stringify(data, null, 2);
7419
- } catch (error) {
7420
- const errorMsg = error instanceof Error ? error.message : String(error);
7421
- logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
7422
- logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
7423
- userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
7424
- if (wrappedStreamCallback) {
7425
- wrappedStreamCallback(`\u274C **Query execution failed:**
7426
- \`\`\`
7427
- ${errorMsg}
7428
- \`\`\`
7429
-
7430
- `);
7431
- if (attempts < MAX_QUERY_ATTEMPTS) {
7432
- wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
7433
-
7434
- `);
7435
- }
7436
- }
7437
- throw new Error(`Query execution failed: ${errorMsg}`);
7438
- }
7439
- } else {
7440
- const externalTool = externalTools?.find((t) => t.id === toolName);
7441
- if (externalTool) {
7442
- const attempts = (toolAttempts.get(toolName) || 0) + 1;
7443
- toolAttempts.set(toolName, attempts);
7444
- logger.info(`[${this.getProviderName()}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
7445
- logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
7446
- if (attempts > MAX_TOOL_ATTEMPTS) {
7447
- const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
7448
- logger.error(`[${this.getProviderName()}] ${errorMsg}`);
7449
- logCollector?.error(errorMsg);
7450
- if (wrappedStreamCallback) {
7451
- wrappedStreamCallback(`
7452
-
7453
- \u274C ${errorMsg}
7454
-
7455
- Please try rephrasing your request or contact support.
7456
-
7457
- `);
7458
- }
7459
- throw new Error(errorMsg);
7460
- }
7461
- try {
7462
- flushStream();
7463
- if (wrappedStreamCallback) {
7464
- if (attempts === 1) {
7465
- wrappedStreamCallback(`
7466
-
7467
- \u{1F517} **Executing ${externalTool.name}...**
7468
-
7469
- `);
7470
- } else {
7471
- wrappedStreamCallback(`
7472
-
7473
- \u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
7474
-
7475
- `);
7476
- }
7477
- await streamDelay(50);
7478
- }
7479
- const result2 = await withProgressHeartbeat(
7480
- () => externalTool.fn(toolInput),
7481
- `Running ${externalTool.name}`,
7482
- 800
7483
- // Send heartbeat every 800ms
7484
- );
7485
- logger.info(`[${this.getProviderName()}] External tool ${externalTool.name} executed successfully`);
7486
- logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
7487
- if (!executedToolsList.find((t) => t.id === externalTool.id)) {
7488
- let resultSummary = null;
7489
- if (result2) {
7490
- const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2);
7491
- if (resultStr.length > 1e3) {
7492
- resultSummary = {
7493
- _preview: resultStr.substring(0, 1e3) + "... (truncated)",
7494
- _totalLength: resultStr.length,
7495
- _recordCount: Array.isArray(result2) ? result2.length : result2?.data?.length || result2?.contacts?.length || result2?.salesorders?.length || "unknown"
7496
- };
7497
- } else {
7498
- resultSummary = result2;
7499
- }
7500
- }
7501
- executedToolsList.push({
7502
- id: externalTool.id,
7503
- name: externalTool.name,
7504
- params: toolInput,
7505
- // The actual parameters used in this execution
7506
- result: resultSummary,
7507
- // Store summary instead of full result to save memory
7508
- outputSchema: externalTool.outputSchema
7509
- // Include output schema for component config generation
7510
- });
7511
- logger.info(`[${this.getProviderName()}] Tracked executed tool: ${externalTool.name} with params: ${JSON.stringify(toolInput)}`);
7512
- }
7513
- if (wrappedStreamCallback) {
7514
- wrappedStreamCallback(`\u2705 **${externalTool.name} completed successfully**
7515
-
7516
- `);
7517
- await streamDelay(50);
7518
- }
7519
- return JSON.stringify(result2, null, 2);
7520
- } catch (error) {
7521
- const errorMsg = error instanceof Error ? error.message : String(error);
7522
- logger.error(`[${this.getProviderName()}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
7523
- logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
7524
- userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
7525
- if (wrappedStreamCallback) {
7526
- wrappedStreamCallback(`\u274C **${externalTool.name} failed:**
7527
- \`\`\`
7528
- ${errorMsg}
7529
- \`\`\`
7530
-
7531
- `);
7532
- if (attempts < MAX_TOOL_ATTEMPTS) {
7533
- wrappedStreamCallback(`\u{1F527} **Retrying with adjusted parameters...**
7534
-
7535
- `);
7536
- }
7537
- }
7538
- throw new Error(`Tool execution failed: ${errorMsg}`);
7539
- }
7540
- }
7541
- throw new Error(`Unknown tool: ${toolName}`);
7542
- }
7543
- };
7968
+ logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
7969
+ logger.debug(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
7970
+ }
7971
+ const streamBuffer = new StreamBuffer(streamCallback);
7972
+ const toolExecutor = new ToolExecutorService({
7973
+ providerName: this.getProviderName(),
7974
+ collections,
7975
+ streamBuffer
7976
+ });
7977
+ const executableExternalTools = externalTools?.map((t) => ({
7978
+ id: t.id,
7979
+ name: t.name,
7980
+ description: t.description,
7981
+ fn: t.fn,
7982
+ limit: t.limit,
7983
+ outputSchema: t.outputSchema,
7984
+ executionType: t.executionType,
7985
+ userProvidedData: t.userProvidedData
7986
+ })) || [];
7987
+ const toolHandler = toolExecutor.createToolHandler(executableExternalTools);
7544
7988
  const result = await LLM.streamWithTools(
7545
7989
  {
7546
7990
  sys: prompts.system,
@@ -7550,21 +7994,17 @@ ${errorMsg}
7550
7994
  toolHandler,
7551
7995
  {
7552
7996
  model: this.getModelForTask("complex"),
7553
- maxTokens: 4e3,
7997
+ maxTokens: MAX_TOKENS_TEXT_RESPONSE,
7554
7998
  temperature: 0,
7555
7999
  apiKey: this.getApiKey(apiKey),
7556
- partial: wrappedStreamCallback
7557
- // Pass the wrapped streaming callback to LLM
8000
+ partial: streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
7558
8001
  },
7559
- 20
7560
- // max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
8002
+ MAX_TOOL_CALLING_ITERATIONS
7561
8003
  );
7562
- logger.info(`[${this.getProviderName()}] Text response stream completed`);
7563
- const textResponse = fullStreamedText || result || "I apologize, but I was unable to generate a response.";
7564
- if (maxAttemptsReached) {
8004
+ const textResponse = streamBuffer.getFullText() || result || "I apologize, but I was unable to generate a response.";
8005
+ if (toolExecutor.isMaxAttemptsReached()) {
7565
8006
  const methodDuration2 = Date.now() - methodStartTime;
7566
8007
  logger.warn(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: max attempts reached`);
7567
- logCollector?.error("Failed to generate valid query after maximum attempts");
7568
8008
  return {
7569
8009
  success: false,
7570
8010
  errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
@@ -7576,18 +8016,10 @@ ${errorMsg}
7576
8016
  }
7577
8017
  };
7578
8018
  }
7579
- logCollector?.info(`Text response: ${textResponse.substring(0, 100)}${textResponse.length > 100 ? "..." : ""}`);
7580
- logCollector?.logExplanation(
7581
- "Text response generated",
7582
- "Generated plain text response with component suggestions",
7583
- {
7584
- textLength: textResponse.length
7585
- }
7586
- );
7587
- flushStream();
7588
- if (wrappedStreamCallback && components && components.length > 0 && category !== "general") {
7589
- wrappedStreamCallback("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
7590
- wrappedStreamCallback("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
8019
+ streamBuffer.flush();
8020
+ if (streamBuffer.hasCallback() && components && components.length > 0 && category !== "general") {
8021
+ streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
8022
+ streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
7591
8023
  }
7592
8024
  let matchedComponents = [];
7593
8025
  let layoutTitle = "Dashboard";
@@ -7595,8 +8027,6 @@ ${errorMsg}
7595
8027
  let actions = [];
7596
8028
  if (category === "general") {
7597
8029
  logger.info(`[${this.getProviderName()}] Skipping component generation for general/conversational question`);
7598
- logCollector?.info("Skipping component generation for general question");
7599
- logger.info(`[${this.getProviderName()}] Generating actions for general question...`);
7600
8030
  const nextQuestions = await this.generateNextQuestions(
7601
8031
  userPrompt,
7602
8032
  null,
@@ -7604,23 +8034,16 @@ ${errorMsg}
7604
8034
  void 0,
7605
8035
  // no component data
7606
8036
  apiKey,
7607
- logCollector,
7608
8037
  conversationHistory,
7609
8038
  textResponse
7610
8039
  // pass text response as context
7611
8040
  );
7612
8041
  actions = convertQuestionsToActions(nextQuestions);
7613
- logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions for general question`);
7614
8042
  } else if (components && components.length > 0) {
7615
- logger.info(`[${this.getProviderName()}] Matching components from text response...`);
7616
- logger.info(`[${this.getProviderName()}] componentStreamCallback setup: wrappedStreamCallback=${!!wrappedStreamCallback}, category=${category}`);
7617
- const componentStreamCallback = wrappedStreamCallback && category !== "data_modification" ? (component) => {
7618
- logger.info(`[${this.getProviderName()}] componentStreamCallback INVOKED for: ${component.name} (${component.type})`);
8043
+ const componentStreamCallback = streamBuffer.hasCallback() && category === "data_analysis" ? (component) => {
7619
8044
  const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
7620
- wrappedStreamCallback(answerMarker);
7621
- logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
8045
+ streamBuffer.write(answerMarker);
7622
8046
  } : void 0;
7623
- logger.info(`[${this.getProviderName()}] componentStreamCallback created: ${!!componentStreamCallback}`);
7624
8047
  const deferredTools = externalTools?.filter((t) => {
7625
8048
  if (t.executionType === "deferred" && !t.userProvidedData) return true;
7626
8049
  if (category === "data_modification" && !t.userProvidedData) {
@@ -7641,10 +8064,9 @@ ${errorMsg}
7641
8064
  components,
7642
8065
  userPrompt,
7643
8066
  apiKey,
7644
- logCollector,
7645
8067
  componentStreamCallback,
7646
8068
  deferredTools,
7647
- executedToolsList,
8069
+ toolExecutor.getExecutedTools(),
7648
8070
  collections,
7649
8071
  userId
7650
8072
  );
@@ -7655,8 +8077,6 @@ ${errorMsg}
7655
8077
  }
7656
8078
  let container_componet = null;
7657
8079
  if (matchedComponents.length > 0) {
7658
- logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
7659
- logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
7660
8080
  container_componet = {
7661
8081
  id: `container_${Date.now()}`,
7662
8082
  name: "MultiComponentContainer",
@@ -7688,7 +8108,6 @@ ${errorMsg}
7688
8108
  const methodDuration = Date.now() - methodStartTime;
7689
8109
  const errorMsg = error instanceof Error ? error.message : String(error);
7690
8110
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
7691
- logCollector?.error(`Error generating text response: ${errorMsg}`);
7692
8111
  userPromptErrorLogger.logLlmError(
7693
8112
  this.getProviderName(),
7694
8113
  this.model,
@@ -7716,38 +8135,29 @@ ${errorMsg}
7716
8135
  * 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
7717
8136
  * 3. Route appropriately based on category and response mode
7718
8137
  */
7719
- async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
8138
+ async handleUserRequest(userPrompt, components, apiKey, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
7720
8139
  const startTime = Date.now();
7721
- logger.info(`[${this.getProviderName()}] handleUserRequest called for user prompt: ${userPrompt}`);
7722
- logCollector?.info(`Starting request processing with mode: ${responseMode}`);
7723
8140
  logger.clearFile();
7724
8141
  logger.logLLMPrompt("handleUserRequest", "user", `User Prompt: ${userPrompt}`);
7725
8142
  try {
7726
- logger.info(`[${this.getProviderName()}] Step 1: Searching previous conversations...`);
7727
8143
  const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
7728
8144
  userPrompt,
7729
8145
  collections,
7730
8146
  userId,
7731
- similarityThreshold: 0.99
8147
+ similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
7732
8148
  });
7733
8149
  if (conversationMatch) {
7734
8150
  logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
7735
- logCollector?.info(
7736
- `\u2713 Found similar conversation (${(conversationMatch.similarity * 100).toFixed(2)}% match)`
7737
- );
7738
8151
  const rawComponent = conversationMatch.uiBlock?.component || conversationMatch.uiBlock?.generatedComponentMetadata;
7739
8152
  const isValidComponent = rawComponent && typeof rawComponent === "object" && Object.keys(rawComponent).length > 0;
7740
8153
  const component = isValidComponent ? rawComponent : null;
7741
8154
  const cachedTextResponse = conversationMatch.uiBlock?.analysis || conversationMatch.uiBlock?.textResponse || conversationMatch.uiBlock?.text || "";
7742
8155
  if (this.containsFormComponent(component)) {
7743
8156
  logger.info(`[${this.getProviderName()}] Skipping cached result - Form components contain stale defaultValues, fetching fresh data`);
7744
- logCollector?.info("Skipping cache for form - fetching current values from database...");
7745
8157
  } else if (!component) {
7746
- if (conversationMatch.similarity >= 0.99) {
8158
+ if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
7747
8159
  const elapsedTime2 = Date.now() - startTime;
7748
- logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response`);
7749
- logCollector?.info(`\u2713 Exact match for general question - returning cached response`);
7750
- logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
8160
+ logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response (${elapsedTime2}ms)`);
7751
8161
  return {
7752
8162
  success: true,
7753
8163
  data: {
@@ -7762,14 +8172,11 @@ ${errorMsg}
7762
8172
  };
7763
8173
  } else {
7764
8174
  logger.info(`[${this.getProviderName()}] Similar match but no component (general question) - processing fresh`);
7765
- logCollector?.info("Similar match found but was a general conversation - processing as new question");
7766
8175
  }
7767
8176
  } else {
7768
- if (conversationMatch.similarity >= 0.99) {
8177
+ if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
7769
8178
  const elapsedTime2 = Date.now() - startTime;
7770
- logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
7771
- logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
7772
- logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
8179
+ logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation (${elapsedTime2}ms)`);
7773
8180
  if (streamCallback && cachedTextResponse) {
7774
8181
  logger.info(`[${this.getProviderName()}] Streaming cached text response to frontend`);
7775
8182
  streamCallback(cachedTextResponse);
@@ -7788,22 +8195,18 @@ ${errorMsg}
7788
8195
  errors: []
7789
8196
  };
7790
8197
  }
7791
- logCollector?.info(`Adapting parameters for similar question...`);
8198
+ logger.info(`[${this.getProviderName()}] Adapting parameters for similar question...`);
7792
8199
  const originalPrompt = conversationMatch.metadata?.userPrompt || "Previous question";
7793
8200
  const adaptResult = await this.adaptUIBlockParameters(
7794
8201
  userPrompt,
7795
8202
  originalPrompt,
7796
8203
  conversationMatch.uiBlock,
7797
8204
  apiKey,
7798
- logCollector,
7799
8205
  cachedTextResponse
7800
8206
  );
7801
8207
  if (adaptResult.success && adaptResult.adaptedComponent) {
7802
8208
  const elapsedTime2 = Date.now() - startTime;
7803
- logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters`);
7804
- logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
7805
- logCollector?.info(`\u2713 UI block adapted successfully`);
7806
- logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
8209
+ logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters (${elapsedTime2}ms)`);
7807
8210
  const textResponseToUse = adaptResult.adaptedTextResponse || cachedTextResponse;
7808
8211
  if (streamCallback && textResponseToUse) {
7809
8212
  logger.info(`[${this.getProviderName()}] Streaming ${adaptResult.adaptedTextResponse ? "adapted" : "cached"} text response to frontend`);
@@ -7824,65 +8227,57 @@ ${errorMsg}
7824
8227
  errors: []
7825
8228
  };
7826
8229
  } else {
7827
- logger.info(`[${this.getProviderName()}] Could not adapt matched conversation, continuing to category classification`);
7828
- logCollector?.warn(`Could not adapt matched conversation: ${adaptResult.explanation}`);
8230
+ logger.info(`[${this.getProviderName()}] Could not adapt matched conversation: ${adaptResult.explanation}, continuing to category classification`);
7829
8231
  }
7830
8232
  }
7831
8233
  } else {
7832
8234
  logger.info(`[${this.getProviderName()}] No matching previous conversations found, proceeding to category classification`);
7833
- logCollector?.info("No similar previous conversations found. Proceeding to category classification...");
7834
8235
  }
7835
8236
  logger.info(`[${this.getProviderName()}] Step 2: Classifying question category...`);
7836
- logCollector?.info("Step 2: Classifying question category...");
7837
8237
  const categoryClassification = await this.classifyQuestionCategory(
7838
8238
  userPrompt,
7839
8239
  apiKey,
7840
- logCollector,
7841
8240
  conversationHistory,
7842
8241
  externalTools
7843
8242
  );
7844
8243
  logger.info(
7845
8244
  `[${this.getProviderName()}] Question classified as: ${categoryClassification.category} (confidence: ${categoryClassification.confidence}%)`
7846
8245
  );
7847
- logCollector?.info(
7848
- `Category: ${categoryClassification.category} | Confidence: ${categoryClassification.confidence}%`
7849
- );
7850
8246
  let toolsToUse = [];
7851
8247
  if (categoryClassification.externalTools && categoryClassification.externalTools.length > 0) {
7852
- logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed`);
7853
- logCollector?.info(`Identified external tools: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
7854
- logger.info(`[${this.getProviderName()}] Raw external tools from classification: ${JSON.stringify(categoryClassification.externalTools, null, 2)}`);
7855
- toolsToUse = categoryClassification.externalTools?.map((t) => {
8248
+ logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
8249
+ logger.debug(`[${this.getProviderName()}] Raw external tools from classification: ${JSON.stringify(categoryClassification.externalTools, null, 2)}`);
8250
+ toolsToUse = categoryClassification.externalTools.reduce((acc, t) => {
7856
8251
  const realTool = externalTools?.find((tool) => tool.id === t.type);
7857
- logger.info(`[${this.getProviderName()}] Tool ${t.name}: executionType=${t.executionType}, userProvidedData=${t.userProvidedData ? "present" : "null"}`);
7858
- return {
8252
+ if (!realTool) {
8253
+ logger.warn(`[${this.getProviderName()}] Tool ${t.type} (${t.name}) not found in registered tools - skipping (likely hallucinated)`);
8254
+ return acc;
8255
+ }
8256
+ acc.push({
7859
8257
  id: t.type,
7860
8258
  name: t.name,
7861
8259
  description: t.description,
7862
8260
  params: t.parameters || {},
7863
- // NEW: Include execution type info from category classification
8261
+ // Include execution type info from category classification
7864
8262
  executionType: t.executionType || "immediate",
7865
8263
  executionReason: t.executionReason || "",
7866
8264
  requiredFields: t.requiredFields || [],
7867
8265
  userProvidedData: t.userProvidedData || null,
7868
- // CRITICAL: Include outputSchema from real tool for component config generation
7869
- outputSchema: realTool?.outputSchema,
7870
- fn: (() => {
7871
- if (realTool) {
7872
- logger.info(`[${this.getProviderName()}] Using real tool implementation for ${t.type}`);
7873
- return realTool.fn;
7874
- } else {
7875
- logger.warn(`[${this.getProviderName()}] Tool ${t.type} not found in registered tools`);
7876
- return async () => ({ success: false, message: `Tool ${t.name || t.type} not registered` });
7877
- }
7878
- })()
7879
- };
7880
- }) || [];
8266
+ // Include outputSchema from real tool for component config generation
8267
+ outputSchema: realTool.outputSchema,
8268
+ fn: realTool.fn
8269
+ });
8270
+ return acc;
8271
+ }, []);
8272
+ const validCount = toolsToUse.length;
8273
+ const hallucinatedCount = categoryClassification.externalTools.length - validCount;
8274
+ if (hallucinatedCount > 0) {
8275
+ logger.warn(`[${this.getProviderName()}] Filtered out ${hallucinatedCount} hallucinated/non-existent tools, ${validCount} valid tools remaining`);
8276
+ }
7881
8277
  }
7882
8278
  const textResponse = await this.generateTextResponse(
7883
8279
  userPrompt,
7884
8280
  apiKey,
7885
- logCollector,
7886
8281
  conversationHistory,
7887
8282
  streamCallback,
7888
8283
  collections,
@@ -7893,13 +8288,10 @@ ${errorMsg}
7893
8288
  );
7894
8289
  const elapsedTime = Date.now() - startTime;
7895
8290
  logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7896
- logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7897
8291
  return textResponse;
7898
8292
  } catch (error) {
7899
8293
  const errorMsg = error instanceof Error ? error.message : String(error);
7900
8294
  logger.error(`[${this.getProviderName()}] Error in handleUserRequest: ${errorMsg}`);
7901
- logger.debug(`[${this.getProviderName()}] Error details:`, error);
7902
- logCollector?.error(`Error processing request: ${errorMsg}`);
7903
8295
  userPromptErrorLogger.logError(
7904
8296
  "handleUserRequest",
7905
8297
  error instanceof Error ? error : new Error(errorMsg),
@@ -7907,7 +8299,6 @@ ${errorMsg}
7907
8299
  );
7908
8300
  const elapsedTime = Date.now() - startTime;
7909
8301
  logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7910
- logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7911
8302
  return {
7912
8303
  success: false,
7913
8304
  errors: [errorMsg],
@@ -7923,7 +8314,7 @@ ${errorMsg}
7923
8314
  * This helps provide intelligent suggestions for follow-up queries
7924
8315
  * For general/conversational questions without components, pass textResponse instead
7925
8316
  */
7926
- async generateNextQuestions(originalUserPrompt, component, componentData, apiKey, logCollector, conversationHistory, textResponse) {
8317
+ async generateNextQuestions(originalUserPrompt, component, componentData, apiKey, conversationHistory, textResponse) {
7927
8318
  const methodStartTime = Date.now();
7928
8319
  const methodName = "generateNextQuestions";
7929
8320
  const promptPreview = originalUserPrompt.substring(0, 50) + (originalUserPrompt.length > 50 ? "..." : "");
@@ -7960,7 +8351,7 @@ ${errorMsg}
7960
8351
  },
7961
8352
  {
7962
8353
  model: this.getModelForTask("simple"),
7963
- maxTokens: 1200,
8354
+ maxTokens: MAX_TOKENS_NEXT_QUESTIONS,
7964
8355
  temperature: 0,
7965
8356
  apiKey: this.getApiKey(apiKey)
7966
8357
  },
@@ -7968,14 +8359,6 @@ ${errorMsg}
7968
8359
  // Parse as JSON
7969
8360
  );
7970
8361
  const nextQuestions = result.nextQuestions || [];
7971
- logCollector?.logExplanation(
7972
- "Next questions generated",
7973
- "Generated intelligent follow-up questions based on component",
7974
- {
7975
- count: nextQuestions.length,
7976
- questions: nextQuestions
7977
- }
7978
- );
7979
8362
  const methodDuration = Date.now() - methodStartTime;
7980
8363
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | questions: ${nextQuestions.length}`);
7981
8364
  return nextQuestions;
@@ -7983,8 +8366,6 @@ ${errorMsg}
7983
8366
  const methodDuration = Date.now() - methodStartTime;
7984
8367
  const errorMsg = error instanceof Error ? error.message : String(error);
7985
8368
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
7986
- logger.debug(`[${this.getProviderName()}] Next questions generation error details:`, error);
7987
- logCollector?.error(`Error generating next questions: ${errorMsg}`);
7988
8369
  return [];
7989
8370
  }
7990
8371
  }
@@ -8098,330 +8479,94 @@ function getLLMProviders() {
8098
8479
  return DEFAULT_PROVIDERS;
8099
8480
  }
8100
8481
  }
8101
- var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8102
- logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
8103
- logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
8104
- const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
8105
- logCollector?.info(msg);
8482
+ var useAnthropicMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8106
8483
  if (responseMode === "component" && components.length === 0) {
8107
8484
  const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8108
8485
  logger.error("[useAnthropicMethod] No components available");
8109
- logCollector?.error(emptyMsg);
8110
8486
  return { success: false, errors: [emptyMsg] };
8111
8487
  }
8112
- logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
8113
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8488
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8114
8489
  logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
8115
8490
  return matchResult;
8116
8491
  };
8117
- var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8492
+ var useGroqMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8118
8493
  logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
8119
8494
  logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
8120
- const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
8121
- logger.info(msg);
8122
- logCollector?.info(msg);
8123
- if (responseMode === "component" && components.length === 0) {
8124
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8125
- logger.error("[useGroqMethod] No components available");
8126
- logCollector?.error(emptyMsg);
8127
- return { success: false, errors: [emptyMsg] };
8128
- }
8129
- logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
8130
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8131
- logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
8132
- return matchResult;
8133
- };
8134
- var useGeminiMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8135
- logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
8136
- logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
8137
- const msg = `Using Gemini LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
8138
- logger.info(msg);
8139
- logCollector?.info(msg);
8140
- if (responseMode === "component" && components.length === 0) {
8141
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8142
- logger.error("[useGeminiMethod] No components available");
8143
- logCollector?.error(emptyMsg);
8144
- return { success: false, errors: [emptyMsg] };
8145
- }
8146
- logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
8147
- const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8148
- logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
8149
- return matchResult;
8150
- };
8151
- var useOpenAIMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8152
- logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
8153
- logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
8154
- const msg = `Using OpenAI GPT ${responseMode === "text" ? "text response" : "matching"} method...`;
8155
- logger.info(msg);
8156
- logCollector?.info(msg);
8157
8495
  if (responseMode === "component" && components.length === 0) {
8158
8496
  const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8159
- logger.error("[useOpenAIMethod] No components available");
8160
- logCollector?.error(emptyMsg);
8161
- return { success: false, errors: [emptyMsg] };
8162
- }
8163
- logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
8164
- const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8165
- logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
8166
- return matchResult;
8167
- };
8168
- var getUserResponseFromCache = async (prompt) => {
8169
- return false;
8170
- };
8171
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8172
- logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
8173
- logger.debug(`[get_user_response] Response mode: ${responseMode}`);
8174
- logger.debug("[get_user_response] Checking cache for existing response");
8175
- const userResponse = await getUserResponseFromCache(prompt);
8176
- if (userResponse) {
8177
- logger.info("[get_user_response] User response found in cache - returning cached result");
8178
- logCollector?.info("User response found in cache");
8179
- return {
8180
- success: true,
8181
- data: userResponse,
8182
- errors: []
8183
- };
8184
- }
8185
- logger.debug("[get_user_response] No cached response found, proceeding with LLM providers");
8186
- const providers = llmProviders || getLLMProviders();
8187
- const errors = [];
8188
- const providerOrder = providers.join(", ");
8189
- logCollector?.info(`LLM Provider order: [${providerOrder}]`);
8190
- if (conversationHistory && conversationHistory.length > 0) {
8191
- const exchangeCount = conversationHistory.split("\n").filter((l) => l.startsWith("Q")).length;
8192
- logger.debug(`[get_user_response] Using conversation history with ${exchangeCount} previous exchanges`);
8193
- logCollector?.info(`Using conversation history with ${exchangeCount} previous exchanges`);
8194
- } else {
8195
- logger.debug("[get_user_response] No conversation history available");
8196
- }
8197
- for (let i = 0; i < providers.length; i++) {
8198
- const provider = providers[i];
8199
- const isLastProvider = i === providers.length - 1;
8200
- const attemptMsg = `Attempting provider: ${provider} (${i + 1}/${providers.length})`;
8201
- logCollector?.info(attemptMsg);
8202
- let result;
8203
- if (provider === "anthropic") {
8204
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8205
- } else if (provider === "groq") {
8206
- result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8207
- } else if (provider === "gemini") {
8208
- result = await useGeminiMethod(prompt, components, geminiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8209
- } else if (provider === "openai") {
8210
- result = await useOpenAIMethod(prompt, components, openaiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8211
- } else {
8212
- logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
8213
- errors.push(`Unknown provider: ${provider}`);
8214
- continue;
8215
- }
8216
- if (result.success) {
8217
- const successMsg = `Success with provider: ${provider}`;
8218
- logger.info(`${successMsg}`);
8219
- logCollector?.info(successMsg);
8220
- return result;
8221
- } else {
8222
- const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
8223
- errors.push(...providerErrors);
8224
- const warnMsg = `Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`;
8225
- logger.warn(`[get_user_response] ${warnMsg}`);
8226
- logCollector?.warn(warnMsg);
8227
- if (!isLastProvider) {
8228
- const fallbackMsg = "Falling back to next provider...";
8229
- logger.info(`[get_user_response] ${fallbackMsg}`);
8230
- logCollector?.info(fallbackMsg);
8231
- }
8232
- }
8233
- }
8234
- const failureMsg = `All LLM providers failed`;
8235
- logger.error(`[get_user_response] ${failureMsg}. Errors: ${errors.join("; ")}`);
8236
- logCollector?.error(`${failureMsg}. Errors: ${errors.join("; ")}`);
8237
- return {
8238
- success: false,
8239
- errors
8240
- };
8241
- };
8242
-
8243
- // src/utils/log-collector.ts
8244
- var LOG_LEVEL_PRIORITY2 = {
8245
- errors: 0,
8246
- warnings: 1,
8247
- info: 2,
8248
- verbose: 3
8249
- };
8250
- var MESSAGE_LEVEL_PRIORITY2 = {
8251
- error: 0,
8252
- warn: 1,
8253
- info: 2,
8254
- debug: 3
8255
- };
8256
- var UILogCollector = class {
8257
- constructor(clientId, sendMessage, uiBlockId) {
8258
- this.logs = [];
8259
- this.uiBlockId = uiBlockId || null;
8260
- this.clientId = clientId;
8261
- this.sendMessage = sendMessage;
8262
- this.currentLogLevel = logger.getLogLevel();
8263
- }
8264
- /**
8265
- * Check if logging is enabled (uiBlockId is provided)
8266
- */
8267
- isEnabled() {
8268
- return this.uiBlockId !== null;
8269
- }
8270
- /**
8271
- * Check if a message should be logged based on current log level
8272
- */
8273
- shouldLog(messageLevel) {
8274
- const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
8275
- const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
8276
- return messagePriority <= currentLevelPriority;
8277
- }
8278
- /**
8279
- * Add a log entry with timestamp and immediately send to runtime
8280
- * Only logs that pass the log level filter are captured and sent
8281
- */
8282
- addLog(level, message, type, data) {
8283
- if (!this.shouldLog(level)) {
8284
- return;
8285
- }
8286
- const log = {
8287
- timestamp: Date.now(),
8288
- level,
8289
- message,
8290
- ...type && { type },
8291
- ...data && { data }
8292
- };
8293
- this.logs.push(log);
8294
- this.sendLogImmediately(log);
8295
- switch (level) {
8296
- case "error":
8297
- logger.error("UILogCollector:", log);
8298
- break;
8299
- case "warn":
8300
- logger.warn("UILogCollector:", log);
8301
- break;
8302
- case "info":
8303
- logger.info("UILogCollector:", log);
8304
- break;
8305
- case "debug":
8306
- logger.debug("UILogCollector:", log);
8307
- break;
8308
- }
8309
- }
8310
- /**
8311
- * Send a single log to runtime immediately
8312
- */
8313
- sendLogImmediately(log) {
8314
- if (!this.isEnabled()) {
8315
- return;
8316
- }
8317
- const response = {
8318
- id: this.uiBlockId,
8319
- type: "UI_LOGS",
8320
- from: { type: "data-agent" },
8321
- to: {
8322
- type: "runtime",
8323
- id: this.clientId
8324
- },
8325
- payload: {
8326
- logs: [log]
8327
- // Send single log in array
8328
- }
8329
- };
8330
- this.sendMessage(response);
8331
- }
8332
- /**
8333
- * Log info message
8334
- */
8335
- info(message, type, data) {
8336
- if (this.isEnabled()) {
8337
- this.addLog("info", message, type, data);
8338
- }
8339
- }
8340
- /**
8341
- * Log error message
8342
- */
8343
- error(message, type, data) {
8344
- if (this.isEnabled()) {
8345
- this.addLog("error", message, type, data);
8346
- }
8347
- }
8348
- /**
8349
- * Log warning message
8350
- */
8351
- warn(message, type, data) {
8352
- if (this.isEnabled()) {
8353
- this.addLog("warn", message, type, data);
8354
- }
8355
- }
8356
- /**
8357
- * Log debug message
8358
- */
8359
- debug(message, type, data) {
8360
- if (this.isEnabled()) {
8361
- this.addLog("debug", message, type, data);
8362
- }
8363
- }
8364
- /**
8365
- * Log LLM explanation with typed metadata
8366
- */
8367
- logExplanation(message, explanation, data) {
8368
- if (this.isEnabled()) {
8369
- this.addLog("info", message, "explanation", {
8370
- explanation,
8371
- ...data
8372
- });
8373
- }
8374
- }
8375
- /**
8376
- * Log generated query with typed metadata
8377
- */
8378
- logQuery(message, query, data) {
8379
- if (this.isEnabled()) {
8380
- this.addLog("info", message, "query", {
8381
- query,
8382
- ...data
8383
- });
8384
- }
8385
- }
8386
- /**
8387
- * Send all collected logs at once (optional, for final summary)
8388
- */
8389
- sendAllLogs() {
8390
- if (!this.isEnabled() || this.logs.length === 0) {
8391
- return;
8392
- }
8393
- const response = {
8394
- id: this.uiBlockId,
8395
- type: "UI_LOGS",
8396
- from: { type: "data-agent" },
8397
- to: {
8398
- type: "runtime",
8399
- id: this.clientId
8400
- },
8401
- payload: {
8402
- logs: this.logs
8403
- }
8404
- };
8405
- this.sendMessage(response);
8406
- }
8407
- /**
8408
- * Get all collected logs
8409
- */
8410
- getLogs() {
8411
- return [...this.logs];
8497
+ logger.error("[useGroqMethod] No components available");
8498
+ return { success: false, errors: [emptyMsg] };
8412
8499
  }
8413
- /**
8414
- * Clear all logs
8415
- */
8416
- clearLogs() {
8417
- this.logs = [];
8500
+ logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
8501
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8502
+ logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
8503
+ return matchResult;
8504
+ };
8505
+ var useGeminiMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8506
+ logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
8507
+ logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
8508
+ if (responseMode === "component" && components.length === 0) {
8509
+ const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8510
+ logger.error("[useGeminiMethod] No components available");
8511
+ return { success: false, errors: [emptyMsg] };
8418
8512
  }
8419
- /**
8420
- * Set uiBlockId (in case it's provided later)
8421
- */
8422
- setUIBlockId(uiBlockId) {
8423
- this.uiBlockId = uiBlockId;
8513
+ logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
8514
+ const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8515
+ logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
8516
+ return matchResult;
8517
+ };
8518
+ var useOpenAIMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8519
+ logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
8520
+ logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
8521
+ if (responseMode === "component" && components.length === 0) {
8522
+ const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8523
+ logger.error("[useOpenAIMethod] No components available");
8524
+ return { success: false, errors: [emptyMsg] };
8424
8525
  }
8526
+ logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
8527
+ const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8528
+ logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
8529
+ return matchResult;
8530
+ };
8531
+ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8532
+ const providers = llmProviders || getLLMProviders();
8533
+ const errors = [];
8534
+ logger.info(`[get_user_response] LLM Provider order: [${providers.join(", ")}]`);
8535
+ for (let i = 0; i < providers.length; i++) {
8536
+ const provider = providers[i];
8537
+ const isLastProvider = i === providers.length - 1;
8538
+ logger.info(`[get_user_response] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
8539
+ let result;
8540
+ if (provider === "anthropic") {
8541
+ result = await useAnthropicMethod(prompt, components, anthropicApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8542
+ } else if (provider === "groq") {
8543
+ result = await useGroqMethod(prompt, components, groqApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8544
+ } else if (provider === "gemini") {
8545
+ result = await useGeminiMethod(prompt, components, geminiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8546
+ } else if (provider === "openai") {
8547
+ result = await useOpenAIMethod(prompt, components, openaiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8548
+ } else {
8549
+ logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
8550
+ errors.push(`Unknown provider: ${provider}`);
8551
+ continue;
8552
+ }
8553
+ if (result.success) {
8554
+ logger.info(`[get_user_response] Success with provider: ${provider}`);
8555
+ return result;
8556
+ } else {
8557
+ const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
8558
+ errors.push(...providerErrors);
8559
+ logger.warn(`[get_user_response] Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`);
8560
+ if (!isLastProvider) {
8561
+ logger.info("[get_user_response] Falling back to next provider...");
8562
+ }
8563
+ }
8564
+ }
8565
+ logger.error(`[get_user_response] All LLM providers failed. Errors: ${errors.join("; ")}`);
8566
+ return {
8567
+ success: false,
8568
+ errors
8569
+ };
8425
8570
  };
8426
8571
 
8427
8572
  // src/utils/conversation-saver.ts
@@ -8554,7 +8699,6 @@ var CONTEXT_CONFIG = {
8554
8699
  // src/handlers/user-prompt-request.ts
8555
8700
  var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools) => {
8556
8701
  const errors = [];
8557
- logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
8558
8702
  const parseResult = UserPromptRequestMessageSchema.safeParse(data);
8559
8703
  if (!parseResult.success) {
8560
8704
  const zodError = parseResult.error;
@@ -8586,27 +8730,23 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8586
8730
  if (!prompt) {
8587
8731
  errors.push("Prompt not found");
8588
8732
  }
8589
- logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
8590
8733
  if (errors.length > 0) {
8591
8734
  return { success: false, errors, id, wsId };
8592
8735
  }
8593
- const logCollector = new UILogCollector(wsId, sendMessage, existingUiBlockId);
8594
8736
  const threadManager = ThreadManager.getInstance();
8595
8737
  let thread = threadManager.getThread(threadId);
8596
8738
  if (!thread) {
8597
8739
  thread = threadManager.createThread(threadId);
8598
8740
  logger.info(`Created new thread: ${threadId}`);
8599
8741
  }
8600
- logCollector.info(`Starting user prompt request with ${components.length} components`);
8742
+ logger.info(`Starting user prompt request with ${components.length} components`);
8601
8743
  const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
8602
8744
  const responseMode = payload.responseMode || "component";
8603
- logger.info("responseMode", responseMode);
8604
8745
  let streamCallback;
8605
8746
  let accumulatedStreamResponse = "";
8606
8747
  if (responseMode === "text") {
8607
8748
  streamCallback = (chunk) => {
8608
8749
  accumulatedStreamResponse += chunk;
8609
- logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
8610
8750
  const streamMessage = {
8611
8751
  id: `stream_${existingUiBlockId}`,
8612
8752
  // Different ID pattern for streaming
@@ -8622,7 +8762,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8622
8762
  }
8623
8763
  };
8624
8764
  sendMessage(streamMessage);
8625
- logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
8626
8765
  };
8627
8766
  }
8628
8767
  const userResponse = await get_user_response(
@@ -8633,7 +8772,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8633
8772
  geminiApiKey,
8634
8773
  openaiApiKey,
8635
8774
  llmProviders,
8636
- logCollector,
8637
8775
  conversationHistory,
8638
8776
  responseMode,
8639
8777
  streamCallback,
@@ -8641,7 +8779,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8641
8779
  externalTools,
8642
8780
  userId
8643
8781
  );
8644
- logCollector.info("User prompt request completed");
8782
+ logger.info("User prompt request completed");
8645
8783
  const uiBlockId = existingUiBlockId;
8646
8784
  if (!userResponse.success) {
8647
8785
  logger.error(`User prompt request failed with errors: ${userResponse.errors.join(", ")}`);
@@ -8708,9 +8846,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8708
8846
  logger.info(
8709
8847
  `Skipping conversation save - response from exact semantic match (${(semanticSimilarity * 100).toFixed(2)}% similarity)`
8710
8848
  );
8711
- logCollector.info(
8712
- `Using exact cached result (${(semanticSimilarity * 100).toFixed(2)}% match) - not saving duplicate conversation`
8713
- );
8714
8849
  } else {
8715
8850
  const uiBlockData = uiBlock.toJSON();
8716
8851
  const saveResult = await saveConversation({
@@ -8918,7 +9053,7 @@ function sendResponse(id, res, sendMessage, clientId) {
8918
9053
  }
8919
9054
 
8920
9055
  // src/userResponse/next-questions.ts
8921
- async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, logCollector, conversationHistory) {
9056
+ async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory) {
8922
9057
  try {
8923
9058
  logger.debug("[generateNextQuestions] Starting next questions generation");
8924
9059
  logger.debug(`[generateNextQuestions] User prompt: "${originalUserPrompt?.substring(0, 50)}..."`);
@@ -8937,7 +9072,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8937
9072
  const isLastProvider = i === providers.length - 1;
8938
9073
  try {
8939
9074
  logger.info(`[generateNextQuestions] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
8940
- logCollector?.info(`Generating questions with ${provider}...`);
8941
9075
  let result = [];
8942
9076
  if (provider === "groq") {
8943
9077
  logger.debug("[generateNextQuestions] Using Groq LLM for next questions");
@@ -8946,7 +9080,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8946
9080
  component,
8947
9081
  componentData,
8948
9082
  groqApiKey,
8949
- logCollector,
8950
9083
  conversationHistory
8951
9084
  );
8952
9085
  } else if (provider === "gemini") {
@@ -8956,7 +9089,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8956
9089
  component,
8957
9090
  componentData,
8958
9091
  geminiApiKey,
8959
- logCollector,
8960
9092
  conversationHistory
8961
9093
  );
8962
9094
  } else if (provider === "openai") {
@@ -8966,7 +9098,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8966
9098
  component,
8967
9099
  componentData,
8968
9100
  openaiApiKey,
8969
- logCollector,
8970
9101
  conversationHistory
8971
9102
  );
8972
9103
  } else {
@@ -8976,44 +9107,32 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8976
9107
  component,
8977
9108
  componentData,
8978
9109
  anthropicApiKey,
8979
- logCollector,
8980
9110
  conversationHistory
8981
9111
  );
8982
9112
  }
8983
9113
  if (result && result.length > 0) {
8984
9114
  logger.info(`[generateNextQuestions] Successfully generated ${result.length} questions with ${provider}`);
8985
9115
  logger.debug(`[generateNextQuestions] Questions: ${JSON.stringify(result)}`);
8986
- logCollector?.info(`Generated ${result.length} follow-up questions`);
8987
9116
  return result;
8988
9117
  }
8989
- const warnMsg = `No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`;
8990
- logger.warn(`[generateNextQuestions] ${warnMsg}`);
8991
- if (!isLastProvider) {
8992
- logCollector?.warn(warnMsg);
8993
- }
9118
+ logger.warn(`[generateNextQuestions] No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`);
8994
9119
  } catch (providerError) {
8995
9120
  const errorMsg = providerError instanceof Error ? providerError.message : String(providerError);
8996
9121
  logger.error(`[generateNextQuestions] Provider ${provider} failed: ${errorMsg}`);
8997
9122
  logger.debug(`[generateNextQuestions] Provider error details:`, providerError);
8998
9123
  if (!isLastProvider) {
8999
- const fallbackMsg = `Provider ${provider} failed, trying next provider...`;
9000
- logger.info(`[generateNextQuestions] ${fallbackMsg}`);
9001
- logCollector?.warn(fallbackMsg);
9002
- } else {
9003
- logCollector?.error(`Failed to generate questions with ${provider}`);
9124
+ logger.info(`[generateNextQuestions] Provider ${provider} failed, trying next provider...`);
9004
9125
  }
9005
9126
  continue;
9006
9127
  }
9007
9128
  }
9008
9129
  logger.warn("[generateNextQuestions] All providers failed or returned no questions");
9009
- logCollector?.warn("Unable to generate follow-up questions");
9010
9130
  return [];
9011
9131
  } catch (error) {
9012
9132
  const errorMsg = error instanceof Error ? error.message : String(error);
9013
9133
  const errorStack = error instanceof Error ? error.stack : void 0;
9014
9134
  logger.error(`[generateNextQuestions] Error generating next questions: ${errorMsg}`);
9015
9135
  logger.debug("[generateNextQuestions] Error stack trace:", errorStack);
9016
- logCollector?.error(`Error generating next questions: ${errorMsg}`);
9017
9136
  return [];
9018
9137
  }
9019
9138
  }
@@ -9061,9 +9180,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
9061
9180
  return;
9062
9181
  }
9063
9182
  logger.info(`[ACTIONS_REQ ${id}] UIBlock retrieved successfully`);
9064
- logger.debug(`[ACTIONS_REQ ${id}] Creating UILogCollector for uiBlockId: ${uiBlockId}`);
9065
- const logCollector = new UILogCollector(wsId, sendMessage, uiBlockId);
9066
- logger.info(`[ACTIONS_REQ ${id}] UILogCollector initialized`);
9067
9183
  logger.debug(`[ACTIONS_REQ ${id}] Extracting data from UIBlock`);
9068
9184
  const userQuestion = uiBlock.getUserQuestion();
9069
9185
  const component = uiBlock.getComponentMetadata();
@@ -9077,13 +9193,11 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
9077
9193
  logger.info(`[ACTIONS_REQ ${id}] Conversation history extracted: ${historyLineCount} lines`);
9078
9194
  logger.debug(`[ACTIONS_REQ ${id}] Conversation history preview:
9079
9195
  ${conversationHistory.substring(0, 200)}...`);
9080
- logCollector.info(`Generating actions for UIBlock: ${uiBlockId}`);
9081
- logger.info(`[ACTIONS_REQ ${id}] Generating actions for component: ${component?.name || "unknown"}`);
9196
+ logger.info(`[ACTIONS_REQ ${id}] Generating actions for UIBlock: ${uiBlockId}, component: ${component?.name || "unknown"}`);
9082
9197
  logger.debug(`[ACTIONS_REQ ${id}] Checking if actions are already cached`);
9083
9198
  const startTime = Date.now();
9084
9199
  const actions = await uiBlock.getOrFetchActions(async () => {
9085
9200
  logger.info(`[ACTIONS_REQ ${id}] Actions not cached, generating new actions...`);
9086
- logCollector.info("Generating follow-up questions...");
9087
9201
  logger.info(`[ACTIONS_REQ ${id}] Starting next questions generation with ${llmProviders?.join(", ") || "default"} providers`);
9088
9202
  const nextQuestions = await generateNextQuestions(
9089
9203
  userQuestion,
@@ -9094,7 +9208,6 @@ ${conversationHistory.substring(0, 200)}...`);
9094
9208
  geminiApiKey,
9095
9209
  openaiApiKey,
9096
9210
  llmProviders,
9097
- logCollector,
9098
9211
  conversationHistory
9099
9212
  );
9100
9213
  logger.info(`[ACTIONS_REQ ${id}] Generated ${nextQuestions.length} questions`);
@@ -9112,11 +9225,10 @@ ${conversationHistory.substring(0, 200)}...`);
9112
9225
  const processingTime = Date.now() - startTime;
9113
9226
  logger.info(`[ACTIONS_REQ ${id}] Actions retrieved in ${processingTime}ms - ${actions.length} actions total`);
9114
9227
  if (actions.length > 0) {
9115
- logCollector.info(`Generated ${actions.length} follow-up questions successfully`);
9228
+ logger.info(`[ACTIONS_REQ ${id}] Generated ${actions.length} follow-up questions successfully`);
9116
9229
  logger.debug(`[ACTIONS_REQ ${id}] Actions: ${actions.map((a) => a.name).join(", ")}`);
9117
9230
  } else {
9118
9231
  logger.warn(`[ACTIONS_REQ ${id}] No actions generated`);
9119
- logCollector.warn("No follow-up questions could be generated");
9120
9232
  }
9121
9233
  logger.debug(`[ACTIONS_REQ ${id}] Sending successful response to client`);
9122
9234
  sendResponse2(id, {
@@ -9135,15 +9247,6 @@ ${conversationHistory.substring(0, 200)}...`);
9135
9247
  const errorStack = error instanceof Error ? error.stack : void 0;
9136
9248
  logger.error(`[ACTIONS_REQ] Failed to handle actions request: ${errorMessage}`);
9137
9249
  logger.debug(`[ACTIONS_REQ] Error stack trace:`, errorStack);
9138
- try {
9139
- const parsedData = data;
9140
- if (parsedData?.id && parsedData?.from?.id) {
9141
- const logCollector = parsedData?.payload?.SA_RUNTIME?.uiBlockId ? new UILogCollector(parsedData.from.id, sendMessage, parsedData.payload.SA_RUNTIME.uiBlockId) : void 0;
9142
- logCollector?.error(`Failed to generate actions: ${errorMessage}`);
9143
- }
9144
- } catch (logError) {
9145
- logger.debug("[ACTIONS_REQ] Failed to send error logs to UI:", logError);
9146
- }
9147
9250
  sendResponse2(null, {
9148
9251
  success: false,
9149
9252
  error: errorMessage
@@ -9724,7 +9827,6 @@ function sendResponse3(id, res, sendMessage, clientId) {
9724
9827
  var dashboardManager = null;
9725
9828
  function setDashboardManager(manager) {
9726
9829
  dashboardManager = manager;
9727
- logger.info("DashboardManager instance set");
9728
9830
  }
9729
9831
  function getDashboardManager() {
9730
9832
  if (!dashboardManager) {
@@ -13051,6 +13153,190 @@ var ReportManager = class {
13051
13153
  }
13052
13154
  };
13053
13155
 
13156
+ // src/utils/log-collector.ts
13157
+ var LOG_LEVEL_PRIORITY2 = {
13158
+ errors: 0,
13159
+ warnings: 1,
13160
+ info: 2,
13161
+ verbose: 3
13162
+ };
13163
+ var MESSAGE_LEVEL_PRIORITY2 = {
13164
+ error: 0,
13165
+ warn: 1,
13166
+ info: 2,
13167
+ debug: 3
13168
+ };
13169
+ var UILogCollector = class {
13170
+ constructor(clientId, sendMessage, uiBlockId) {
13171
+ this.logs = [];
13172
+ this.uiBlockId = uiBlockId || null;
13173
+ this.clientId = clientId;
13174
+ this.sendMessage = sendMessage;
13175
+ this.currentLogLevel = logger.getLogLevel();
13176
+ }
13177
+ /**
13178
+ * Check if logging is enabled (uiBlockId is provided)
13179
+ */
13180
+ isEnabled() {
13181
+ return this.uiBlockId !== null;
13182
+ }
13183
+ /**
13184
+ * Check if a message should be logged based on current log level
13185
+ */
13186
+ shouldLog(messageLevel) {
13187
+ const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
13188
+ const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
13189
+ return messagePriority <= currentLevelPriority;
13190
+ }
13191
+ /**
13192
+ * Add a log entry with timestamp and immediately send to runtime
13193
+ * Only logs that pass the log level filter are captured and sent
13194
+ */
13195
+ addLog(level, message, type, data) {
13196
+ if (!this.shouldLog(level)) {
13197
+ return;
13198
+ }
13199
+ const log = {
13200
+ timestamp: Date.now(),
13201
+ level,
13202
+ message,
13203
+ ...type && { type },
13204
+ ...data && { data }
13205
+ };
13206
+ this.logs.push(log);
13207
+ this.sendLogImmediately(log);
13208
+ switch (level) {
13209
+ case "error":
13210
+ logger.error("UILogCollector:", log);
13211
+ break;
13212
+ case "warn":
13213
+ logger.warn("UILogCollector:", log);
13214
+ break;
13215
+ case "info":
13216
+ logger.info("UILogCollector:", log);
13217
+ break;
13218
+ case "debug":
13219
+ logger.debug("UILogCollector:", log);
13220
+ break;
13221
+ }
13222
+ }
13223
+ /**
13224
+ * Send a single log to runtime immediately
13225
+ */
13226
+ sendLogImmediately(log) {
13227
+ if (!this.isEnabled()) {
13228
+ return;
13229
+ }
13230
+ const response = {
13231
+ id: this.uiBlockId,
13232
+ type: "UI_LOGS",
13233
+ from: { type: "data-agent" },
13234
+ to: {
13235
+ type: "runtime",
13236
+ id: this.clientId
13237
+ },
13238
+ payload: {
13239
+ logs: [log]
13240
+ // Send single log in array
13241
+ }
13242
+ };
13243
+ this.sendMessage(response);
13244
+ }
13245
+ /**
13246
+ * Log info message
13247
+ */
13248
+ info(message, type, data) {
13249
+ if (this.isEnabled()) {
13250
+ this.addLog("info", message, type, data);
13251
+ }
13252
+ }
13253
+ /**
13254
+ * Log error message
13255
+ */
13256
+ error(message, type, data) {
13257
+ if (this.isEnabled()) {
13258
+ this.addLog("error", message, type, data);
13259
+ }
13260
+ }
13261
+ /**
13262
+ * Log warning message
13263
+ */
13264
+ warn(message, type, data) {
13265
+ if (this.isEnabled()) {
13266
+ this.addLog("warn", message, type, data);
13267
+ }
13268
+ }
13269
+ /**
13270
+ * Log debug message
13271
+ */
13272
+ debug(message, type, data) {
13273
+ if (this.isEnabled()) {
13274
+ this.addLog("debug", message, type, data);
13275
+ }
13276
+ }
13277
+ /**
13278
+ * Log LLM explanation with typed metadata
13279
+ */
13280
+ logExplanation(message, explanation, data) {
13281
+ if (this.isEnabled()) {
13282
+ this.addLog("info", message, "explanation", {
13283
+ explanation,
13284
+ ...data
13285
+ });
13286
+ }
13287
+ }
13288
+ /**
13289
+ * Log generated query with typed metadata
13290
+ */
13291
+ logQuery(message, query, data) {
13292
+ if (this.isEnabled()) {
13293
+ this.addLog("info", message, "query", {
13294
+ query,
13295
+ ...data
13296
+ });
13297
+ }
13298
+ }
13299
+ /**
13300
+ * Send all collected logs at once (optional, for final summary)
13301
+ */
13302
+ sendAllLogs() {
13303
+ if (!this.isEnabled() || this.logs.length === 0) {
13304
+ return;
13305
+ }
13306
+ const response = {
13307
+ id: this.uiBlockId,
13308
+ type: "UI_LOGS",
13309
+ from: { type: "data-agent" },
13310
+ to: {
13311
+ type: "runtime",
13312
+ id: this.clientId
13313
+ },
13314
+ payload: {
13315
+ logs: this.logs
13316
+ }
13317
+ };
13318
+ this.sendMessage(response);
13319
+ }
13320
+ /**
13321
+ * Get all collected logs
13322
+ */
13323
+ getLogs() {
13324
+ return [...this.logs];
13325
+ }
13326
+ /**
13327
+ * Clear all logs
13328
+ */
13329
+ clearLogs() {
13330
+ this.logs = [];
13331
+ }
13332
+ /**
13333
+ * Set uiBlockId (in case it's provided later)
13334
+ */
13335
+ setUIBlockId(uiBlockId) {
13336
+ this.uiBlockId = uiBlockId;
13337
+ }
13338
+ };
13339
+
13054
13340
  // src/services/cleanup-service.ts
13055
13341
  var CleanupService = class _CleanupService {
13056
13342
  constructor() {
@@ -13231,7 +13517,6 @@ var CleanupService = class _CleanupService {
13231
13517
  };
13232
13518
 
13233
13519
  // src/index.ts
13234
- var SDK_VERSION = "0.0.8";
13235
13520
  var DEFAULT_WS_URL = "wss://ws.superatom.ai/websocket";
13236
13521
  var SuperatomSDK = class {
13237
13522
  // 3.5 minutes (PING_INTERVAL + 30s grace)
@@ -13272,7 +13557,7 @@ var SuperatomSDK = class {
13272
13557
  if (config.queryCacheTTL !== void 0) {
13273
13558
  queryCache.setTTL(config.queryCacheTTL);
13274
13559
  }
13275
- logger.info(`Initializing Superatom SDK v${SDK_VERSION} for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}, conversation similarity threshold: ${this.conversationSimilarityThreshold}, query cache TTL: ${queryCache.getTTL()} minutes`);
13560
+ logger.info(`Initializing Superatom SDK for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}, query cache TTL: ${queryCache.getTTL()} minutes`);
13276
13561
  this.userManager = new UserManager(this.projectId, 5e3);
13277
13562
  this.dashboardManager = new DashboardManager(this.projectId);
13278
13563
  this.reportManager = new ReportManager(this.projectId);
@@ -13326,7 +13611,6 @@ var SuperatomSDK = class {
13326
13611
  */
13327
13612
  initializeDashboardManager() {
13328
13613
  setDashboardManager(this.dashboardManager);
13329
- logger.info(`DashboardManager initialized for project: ${this.projectId}`);
13330
13614
  }
13331
13615
  /**
13332
13616
  * Get the DashboardManager instance for this SDK
@@ -13339,7 +13623,6 @@ var SuperatomSDK = class {
13339
13623
  */
13340
13624
  initializeReportManager() {
13341
13625
  setReportManager(this.reportManager);
13342
- logger.info(`ReportManager initialized for project: ${this.projectId}`);
13343
13626
  }
13344
13627
  /**
13345
13628
  * Get the ReportManager instance for this SDK
@@ -13717,7 +14000,6 @@ var SuperatomSDK = class {
13717
14000
  CONTEXT_CONFIG,
13718
14001
  CleanupService,
13719
14002
  LLM,
13720
- SDK_VERSION,
13721
14003
  STORAGE_CONFIG,
13722
14004
  SuperatomSDK,
13723
14005
  Thread,