@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.mjs CHANGED
@@ -1458,6 +1458,351 @@ var QueryCache = class {
1458
1458
  };
1459
1459
  var queryCache = new QueryCache();
1460
1460
 
1461
+ // src/userResponse/llm-result-truncator.ts
1462
+ var DEFAULT_MAX_ROWS = 10;
1463
+ var DEFAULT_MAX_CHARS_PER_FIELD = 500;
1464
+ function inferFieldType(value) {
1465
+ if (value === null || value === void 0) {
1466
+ return "null";
1467
+ }
1468
+ if (typeof value === "string") {
1469
+ if (isDateString(value)) {
1470
+ return "date";
1471
+ }
1472
+ return "string";
1473
+ }
1474
+ if (typeof value === "number") {
1475
+ return "number";
1476
+ }
1477
+ if (typeof value === "boolean") {
1478
+ return "boolean";
1479
+ }
1480
+ if (Array.isArray(value)) {
1481
+ return "array";
1482
+ }
1483
+ if (typeof value === "object") {
1484
+ return "object";
1485
+ }
1486
+ return "unknown";
1487
+ }
1488
+ function isDateString(value) {
1489
+ const datePatterns = [
1490
+ /^\d{4}-\d{2}-\d{2}$/,
1491
+ // YYYY-MM-DD
1492
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
1493
+ // ISO 8601
1494
+ /^\d{2}\/\d{2}\/\d{4}$/,
1495
+ // MM/DD/YYYY
1496
+ /^\d{4}\/\d{2}\/\d{2}$/
1497
+ // YYYY/MM/DD
1498
+ ];
1499
+ return datePatterns.some((pattern) => pattern.test(value));
1500
+ }
1501
+ function truncateTextField(value, maxLength) {
1502
+ if (value.length <= maxLength) {
1503
+ return { text: value, wasTruncated: false };
1504
+ }
1505
+ const truncated = value.substring(0, maxLength);
1506
+ const remaining = value.length - maxLength;
1507
+ return {
1508
+ text: `${truncated}... (${remaining} more chars)`,
1509
+ wasTruncated: true
1510
+ };
1511
+ }
1512
+ function truncateFieldValue(value, maxCharsPerField) {
1513
+ if (value === null || value === void 0) {
1514
+ return { value, wasTruncated: false };
1515
+ }
1516
+ if (typeof value === "number" || typeof value === "boolean") {
1517
+ return { value, wasTruncated: false };
1518
+ }
1519
+ if (typeof value === "string") {
1520
+ const result2 = truncateTextField(value, maxCharsPerField);
1521
+ return { value: result2.text, wasTruncated: result2.wasTruncated };
1522
+ }
1523
+ if (Array.isArray(value)) {
1524
+ if (value.length === 0) {
1525
+ return { value: [], wasTruncated: false };
1526
+ }
1527
+ const preview = value.slice(0, 3);
1528
+ const hasMore = value.length > 3;
1529
+ return {
1530
+ value: hasMore ? `[${preview.join(", ")}... (${value.length} items)]` : value,
1531
+ wasTruncated: hasMore
1532
+ };
1533
+ }
1534
+ if (typeof value === "object") {
1535
+ const jsonStr = JSON.stringify(value);
1536
+ const result2 = truncateTextField(jsonStr, maxCharsPerField);
1537
+ return { value: result2.text, wasTruncated: result2.wasTruncated };
1538
+ }
1539
+ const strValue = String(value);
1540
+ const result = truncateTextField(strValue, maxCharsPerField);
1541
+ return { value: result.text, wasTruncated: result.wasTruncated };
1542
+ }
1543
+ function truncateRow(row, maxCharsPerField) {
1544
+ const truncatedRow = {};
1545
+ const truncatedFields = /* @__PURE__ */ new Set();
1546
+ for (const [key, value] of Object.entries(row)) {
1547
+ const result = truncateFieldValue(value, maxCharsPerField);
1548
+ truncatedRow[key] = result.value;
1549
+ if (result.wasTruncated) {
1550
+ truncatedFields.add(key);
1551
+ }
1552
+ }
1553
+ return { row: truncatedRow, truncatedFields };
1554
+ }
1555
+ function extractMetadataFromObject(obj, dataKey, maxCharsPerField) {
1556
+ const metadata = {};
1557
+ const truncatedFields = /* @__PURE__ */ new Set();
1558
+ for (const [key, value] of Object.entries(obj)) {
1559
+ if (key === dataKey) {
1560
+ continue;
1561
+ }
1562
+ if (Array.isArray(value)) {
1563
+ continue;
1564
+ }
1565
+ const result = truncateFieldValue(value, maxCharsPerField);
1566
+ metadata[key] = result.value;
1567
+ if (result.wasTruncated) {
1568
+ truncatedFields.add(key);
1569
+ }
1570
+ }
1571
+ return { metadata, truncatedFields };
1572
+ }
1573
+ function extractSchema(data, truncatedFields = /* @__PURE__ */ new Set()) {
1574
+ if (!data || data.length === 0) {
1575
+ return [];
1576
+ }
1577
+ const firstRow = data[0];
1578
+ const schema2 = [];
1579
+ for (const [name, value] of Object.entries(firstRow)) {
1580
+ schema2.push({
1581
+ name,
1582
+ type: inferFieldType(value),
1583
+ truncated: truncatedFields.has(name) ? true : void 0
1584
+ });
1585
+ }
1586
+ return schema2;
1587
+ }
1588
+ function truncateDataArray(data, options = {}) {
1589
+ const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
1590
+ const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
1591
+ if (!data || !Array.isArray(data)) {
1592
+ return {
1593
+ data: [],
1594
+ totalRecords: 0,
1595
+ recordsShown: 0,
1596
+ truncatedFields: /* @__PURE__ */ new Set()
1597
+ };
1598
+ }
1599
+ const totalRecords = data.length;
1600
+ const rowsToProcess = data.slice(0, maxRows);
1601
+ const truncatedData = [];
1602
+ const allTruncatedFields = /* @__PURE__ */ new Set();
1603
+ for (const row of rowsToProcess) {
1604
+ const { row: truncatedRow, truncatedFields } = truncateRow(row, maxCharsPerField);
1605
+ truncatedData.push(truncatedRow);
1606
+ for (const field of truncatedFields) {
1607
+ allTruncatedFields.add(field);
1608
+ }
1609
+ }
1610
+ return {
1611
+ data: truncatedData,
1612
+ totalRecords,
1613
+ recordsShown: truncatedData.length,
1614
+ truncatedFields: allTruncatedFields
1615
+ };
1616
+ }
1617
+ function buildTruncationNote(totalRecords, recordsShown, truncatedFields, maxCharsPerField, sourceName) {
1618
+ const parts = [];
1619
+ if (totalRecords > recordsShown) {
1620
+ const source = sourceName ? ` from ${sourceName}` : "";
1621
+ parts.push(`Showing ${recordsShown} of ${totalRecords} total records${source}`);
1622
+ }
1623
+ if (truncatedFields.size > 0) {
1624
+ const fieldList = Array.from(truncatedFields).join(", ");
1625
+ parts.push(`Fields truncated to ${maxCharsPerField} chars: ${fieldList}`);
1626
+ }
1627
+ return parts.length > 0 ? parts.join(". ") + "." : null;
1628
+ }
1629
+ function formatQueryResultForLLM(data, options = {}) {
1630
+ const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
1631
+ if (!Array.isArray(data)) {
1632
+ if (data !== null && data !== void 0) {
1633
+ return {
1634
+ summary: {
1635
+ totalRecords: 1,
1636
+ recordsShown: 1,
1637
+ schema: [{ name: "result", type: inferFieldType(data) }]
1638
+ },
1639
+ data: [{ result: data }],
1640
+ truncationNote: null
1641
+ };
1642
+ }
1643
+ return {
1644
+ summary: {
1645
+ totalRecords: 0,
1646
+ recordsShown: 0,
1647
+ schema: []
1648
+ },
1649
+ data: [],
1650
+ truncationNote: null
1651
+ };
1652
+ }
1653
+ const {
1654
+ data: truncatedData,
1655
+ totalRecords,
1656
+ recordsShown,
1657
+ truncatedFields
1658
+ } = truncateDataArray(data, options);
1659
+ const schema2 = extractSchema(truncatedData, truncatedFields);
1660
+ const truncationNote = buildTruncationNote(
1661
+ totalRecords,
1662
+ recordsShown,
1663
+ truncatedFields,
1664
+ maxCharsPerField,
1665
+ "query"
1666
+ );
1667
+ return {
1668
+ summary: {
1669
+ totalRecords,
1670
+ recordsShown,
1671
+ schema: schema2
1672
+ },
1673
+ data: truncatedData,
1674
+ truncationNote
1675
+ };
1676
+ }
1677
+ function formatToolResultForLLM(result, options = {}) {
1678
+ const { toolName, toolLimit } = options;
1679
+ const effectiveMaxRows = toolLimit ?? options.maxRows ?? DEFAULT_MAX_ROWS;
1680
+ const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
1681
+ if (result === null || result === void 0) {
1682
+ return {
1683
+ toolName,
1684
+ summary: {
1685
+ totalRecords: 0,
1686
+ recordsShown: 0,
1687
+ schema: []
1688
+ },
1689
+ data: [],
1690
+ truncationNote: null
1691
+ };
1692
+ }
1693
+ if (typeof result === "string") {
1694
+ const { text, wasTruncated } = truncateTextField(result, maxCharsPerField);
1695
+ return {
1696
+ toolName,
1697
+ summary: {
1698
+ totalRecords: 1,
1699
+ recordsShown: 1,
1700
+ schema: [{ name: "result", type: "string", truncated: wasTruncated || void 0 }]
1701
+ },
1702
+ data: [{ result: text }],
1703
+ truncationNote: wasTruncated ? `Result truncated to ${maxCharsPerField} chars.` : null
1704
+ };
1705
+ }
1706
+ if (Array.isArray(result)) {
1707
+ const {
1708
+ data: truncatedData,
1709
+ totalRecords,
1710
+ recordsShown,
1711
+ truncatedFields
1712
+ } = truncateDataArray(result, {
1713
+ maxRows: effectiveMaxRows,
1714
+ maxCharsPerField
1715
+ });
1716
+ const schema2 = extractSchema(truncatedData, truncatedFields);
1717
+ const truncationNote = buildTruncationNote(
1718
+ totalRecords,
1719
+ recordsShown,
1720
+ truncatedFields,
1721
+ maxCharsPerField,
1722
+ toolName
1723
+ );
1724
+ return {
1725
+ toolName,
1726
+ summary: {
1727
+ totalRecords,
1728
+ recordsShown,
1729
+ schema: schema2
1730
+ },
1731
+ data: truncatedData,
1732
+ truncationNote
1733
+ };
1734
+ }
1735
+ if (typeof result === "object") {
1736
+ const objResult = result;
1737
+ const dataWrapperKeys = ["data", "results", "items", "records", "rows", "list"];
1738
+ for (const key of dataWrapperKeys) {
1739
+ if (Array.isArray(objResult[key])) {
1740
+ const innerData = objResult[key];
1741
+ const {
1742
+ data: truncatedData,
1743
+ totalRecords,
1744
+ recordsShown,
1745
+ truncatedFields: dataTruncatedFields
1746
+ } = truncateDataArray(innerData, {
1747
+ maxRows: effectiveMaxRows,
1748
+ maxCharsPerField
1749
+ });
1750
+ const {
1751
+ metadata,
1752
+ truncatedFields: metadataTruncatedFields
1753
+ } = extractMetadataFromObject(objResult, key, maxCharsPerField);
1754
+ const allTruncatedFields = /* @__PURE__ */ new Set([...dataTruncatedFields, ...metadataTruncatedFields]);
1755
+ const schema3 = extractSchema(truncatedData, dataTruncatedFields);
1756
+ const truncationNote2 = buildTruncationNote(
1757
+ totalRecords,
1758
+ recordsShown,
1759
+ allTruncatedFields,
1760
+ maxCharsPerField,
1761
+ toolName
1762
+ );
1763
+ const hasMetadata = Object.keys(metadata).length > 0;
1764
+ return {
1765
+ toolName,
1766
+ summary: {
1767
+ totalRecords,
1768
+ recordsShown,
1769
+ schema: schema3
1770
+ },
1771
+ ...hasMetadata && { metadata },
1772
+ data: truncatedData,
1773
+ truncationNote: truncationNote2
1774
+ };
1775
+ }
1776
+ }
1777
+ const { row: truncatedRow, truncatedFields } = truncateRow(objResult, maxCharsPerField);
1778
+ const schema2 = extractSchema([truncatedRow], truncatedFields);
1779
+ const truncationNote = truncatedFields.size > 0 ? `Fields truncated to ${maxCharsPerField} chars: ${Array.from(truncatedFields).join(", ")}.` : null;
1780
+ return {
1781
+ toolName,
1782
+ summary: {
1783
+ totalRecords: 1,
1784
+ recordsShown: 1,
1785
+ schema: schema2
1786
+ },
1787
+ data: [truncatedRow],
1788
+ truncationNote
1789
+ };
1790
+ }
1791
+ return {
1792
+ toolName,
1793
+ summary: {
1794
+ totalRecords: 1,
1795
+ recordsShown: 1,
1796
+ schema: [{ name: "result", type: inferFieldType(result) }]
1797
+ },
1798
+ data: [{ result }],
1799
+ truncationNote: null
1800
+ };
1801
+ }
1802
+ function formatResultAsString(formattedResult) {
1803
+ return JSON.stringify(formattedResult, null, 2);
1804
+ }
1805
+
1461
1806
  // src/handlers/data-request.ts
1462
1807
  function getQueryCacheKey(query) {
1463
1808
  if (typeof query === "string") {
@@ -1539,15 +1884,25 @@ async function handleDataRequest(data, collections, sendMessage) {
1539
1884
  }
1540
1885
  }
1541
1886
  if (uiBlock) {
1887
+ const formattedResult = formatToolResultForLLM(result, {
1888
+ maxRows: 3,
1889
+ // Only need a few sample rows for summary
1890
+ maxCharsPerField: 100
1891
+ // Short truncation for summary
1892
+ });
1542
1893
  const dataSummary = {
1543
1894
  _dataReceived: true,
1544
1895
  _timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1545
1896
  _collection: collection,
1546
1897
  _operation: op,
1547
- _recordCount: Array.isArray(result) ? result.length : result?.data?.length || result?.contacts?.length || result?.salesorders?.length || "unknown"
1898
+ _totalRecords: formattedResult.summary.totalRecords,
1899
+ _recordsShown: formattedResult.summary.recordsShown,
1900
+ _metadata: formattedResult.metadata,
1901
+ // Preserve totalItems, totalDeadstockItems, etc.
1902
+ _schema: formattedResult.summary.schema
1548
1903
  };
1549
1904
  uiBlock.setComponentData(dataSummary);
1550
- logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (full data not stored to save memory)`);
1905
+ logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (${formattedResult.summary.totalRecords} total records)`);
1551
1906
  } else {
1552
1907
  logger.warn(`UIBlock ${uiBlockId} not found in threads`);
1553
1908
  }
@@ -2955,24 +3310,20 @@ If adaptation is not possible or would fundamentally change the component:
2955
3310
  "dash-comp-picker": {
2956
3311
  system: `You are a component selection expert that picks the best dashboard component and generates complete props based on user requests.
2957
3312
 
3313
+ ## CRITICAL - READ FIRST
3314
+
3315
+ 1. Your ENTIRE response must be ONLY a raw JSON object - start with { end with }
3316
+ 2. DO NOT explain or answer the user's question in natural language
3317
+ 3. DO NOT use markdown code blocks (no \`\`\`)
3318
+ 4. DO NOT add any text before or after the JSON
3319
+ 5. After executing tools (if needed), return JSON with component selection - NOT a text summary of results
3320
+
2958
3321
  ## Your Task
2959
3322
 
2960
3323
  Analyze the user's request and:
2961
3324
  1. **Select the most appropriate component** from the available components list
2962
- 2. **Determine the data source**: Database query OR External tool
2963
- 3. **Generate complete props** for the selected component
2964
-
2965
- ## Available External Tools
2966
-
2967
- The following external tools are available:
2968
-
2969
- {{AVAILABLE_TOOLS}}
2970
-
2971
- When a tool is needed to complete the user's request:
2972
- 1. **Analyze the request** to determine which tool is needed
2973
- 2. **Extract parameters** from the user's question
2974
- 3. **Execute the tool** by calling it with the extracted parameters
2975
- 4. **Use the results** to configure the component (field names for axes, columns, etc.)
3325
+ 2. **Determine the data source**: Database query OR External tool (ERP)
3326
+ 3. **Generate complete props** for the selected component including the data retrieval/modification method
2976
3327
 
2977
3328
  ## Component Selection Rules
2978
3329
 
@@ -3002,7 +3353,7 @@ The user prompt may contain an **existing component** to update. Detect this by
3002
3353
 
3003
3354
  ### Use DATABASE when:
3004
3355
  - User asks about data that exists in the database schema
3005
- - Questions about internal business data
3356
+ - Questions about internal business data
3006
3357
  - CRUD operations on database tables
3007
3358
 
3008
3359
  ### Use EXTERNAL TOOL when:
@@ -3015,6 +3366,12 @@ The user prompt may contain an **existing component** to update. Detect this by
3015
3366
 
3016
3367
  **CRITICAL**: Look at each component's "Props Structure" in the available components list. Generate ALL props that the component expects.
3017
3368
 
3369
+ **CRITICAL: Each component uses EXACTLY ONE data source - never both!**
3370
+ - If using \`query\`, set \`externalTool: null\`
3371
+ - If using \`externalTool\`, set \`query: null\`
3372
+ - NEVER copy placeholder/description text from component metadata as actual values
3373
+ - \`externalTool.parameters\` MUST be an object, never a string
3374
+
3018
3375
  ### For Data Viewing Components (charts, tables, KPIs):
3019
3376
 
3020
3377
  **Option A: Database Query** (when data is in database)
@@ -3023,21 +3380,19 @@ The user prompt may contain an **existing component** to update. Detect this by
3023
3380
  "query": {
3024
3381
  "sql": "SELECT column1, column2 FROM table WHERE condition = $param LIMIT 32",
3025
3382
  "params": { "param": "value" }
3026
- }
3383
+ },
3384
+ "externalTool": null
3027
3385
  }
3028
3386
  \`\`\`
3029
3387
 
3030
3388
  **Option B: External Tool** (when data is from ERP/external system)
3031
3389
  \`\`\`json
3032
3390
  {
3391
+ "query": null,
3033
3392
  "externalTool": {
3034
3393
  "toolId": "tool_id_from_list",
3035
3394
  "toolName": "Tool Display Name",
3036
- "action": "get",
3037
- "params": {
3038
- "param1": "value1",
3039
- "param2": "value2"
3040
- }
3395
+ "parameters": { "param1": "value1", "param2": "value2" }
3041
3396
  }
3042
3397
  }
3043
3398
  \`\`\`
@@ -3051,6 +3406,7 @@ The user prompt may contain an **existing component** to update. Detect this by
3051
3406
  "sql": "INSERT INTO table (col1, col2) VALUES ($col1, $col2)",
3052
3407
  "params": {}
3053
3408
  },
3409
+ "externalTool": null,
3054
3410
  "fields": [
3055
3411
  { "name": "col1", "type": "text", "required": true },
3056
3412
  { "name": "col2", "type": "number", "required": false }
@@ -3058,16 +3414,38 @@ The user prompt may contain an **existing component** to update. Detect this by
3058
3414
  }
3059
3415
  \`\`\`
3060
3416
 
3417
+ For UPDATE:
3418
+ \`\`\`json
3419
+ {
3420
+ "query": {
3421
+ "sql": "UPDATE table SET col1 = $col1, col2 = $col2 WHERE id = $id",
3422
+ "params": { "id": "record_id" }
3423
+ },
3424
+ "externalTool": null
3425
+ }
3426
+ \`\`\`
3427
+
3428
+ For DELETE:
3429
+ \`\`\`json
3430
+ {
3431
+ "query": {
3432
+ "sql": "DELETE FROM table WHERE id = $id",
3433
+ "params": { "id": "record_id" }
3434
+ },
3435
+ "externalTool": null,
3436
+ "submitButtonText": "Confirm Delete",
3437
+ "submitButtonColor": "danger"
3438
+ }
3439
+ \`\`\`
3440
+
3061
3441
  **Option B: External Tool Mutation**
3062
3442
  \`\`\`json
3063
3443
  {
3444
+ "query": null,
3064
3445
  "externalTool": {
3065
3446
  "toolId": "tool_id_from_list",
3066
3447
  "toolName": "Tool Display Name",
3067
- "action": "create|update|delete",
3068
- "params": {
3069
- "param1": "value_or_placeholder"
3070
- }
3448
+ "parameters": { "param1": "value_or_placeholder" }
3071
3449
  },
3072
3450
  "fields": [
3073
3451
  { "name": "param1", "type": "text", "required": true }
@@ -3082,6 +3460,7 @@ The user prompt may contain an **existing component** to update. Detect this by
3082
3460
 
3083
3461
  You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3084
3462
 
3463
+ \`\`\`json
3085
3464
  {
3086
3465
  "componentId": "id_from_available_list_or_existing_component_id",
3087
3466
  "componentName": "name_of_component",
@@ -3096,6 +3475,7 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3096
3475
  // Include all other required props (title, description, config, fields, etc.)
3097
3476
  }
3098
3477
  }
3478
+ \`\`\`
3099
3479
 
3100
3480
  **CRITICAL:**
3101
3481
  - Return ONLY valid JSON (no markdown code blocks, no text before/after)
@@ -3118,7 +3498,8 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3118
3498
 
3119
3499
  ---
3120
3500
 
3121
- ## CONTEXT`,
3501
+ ## CONTEXT
3502
+ `,
3122
3503
  user: `{{USER_PROMPT}}`
3123
3504
  },
3124
3505
  "dash-filter-picker": {
@@ -3264,9 +3645,7 @@ var PromptLoader = class {
3264
3645
  this.databaseRulesCache = /* @__PURE__ */ new Map();
3265
3646
  this.isInitialized = false;
3266
3647
  this.databaseType = "postgresql";
3267
- logger.debug("Initializing PromptLoader...");
3268
3648
  this.promptsDir = config?.promptsDir || path2.join(process.cwd(), ".prompts");
3269
- logger.debug(`Prompts directory set to: ${this.promptsDir}`);
3270
3649
  }
3271
3650
  /**
3272
3651
  * Load a prompt template from file system OR fallback to hardcoded prompts
@@ -3280,7 +3659,6 @@ var PromptLoader = class {
3280
3659
  if (fs3.existsSync(systemPath) && fs3.existsSync(userPath)) {
3281
3660
  const system = fs3.readFileSync(systemPath, "utf-8");
3282
3661
  const user = fs3.readFileSync(userPath, "utf-8");
3283
- logger.info(`\u2713 Loaded prompt '${promptName}' from file system: ${this.promptsDir}`);
3284
3662
  return { system, user };
3285
3663
  }
3286
3664
  } catch (error) {
@@ -3288,7 +3666,6 @@ var PromptLoader = class {
3288
3666
  }
3289
3667
  const hardcodedPrompt = PROMPTS[promptName];
3290
3668
  if (hardcodedPrompt) {
3291
- logger.info(`\u2713 Loaded prompt '${promptName}' from hardcoded fallback`);
3292
3669
  return hardcodedPrompt;
3293
3670
  }
3294
3671
  throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or hardcoded prompts. Available prompts: ${Object.keys(PROMPTS).join(", ")}`);
@@ -3302,7 +3679,6 @@ var PromptLoader = class {
3302
3679
  logger.debug("PromptLoader already initialized, skipping...");
3303
3680
  return;
3304
3681
  }
3305
- logger.info("Loading prompts into memory...");
3306
3682
  const promptTypes = Object.keys(PROMPTS);
3307
3683
  for (const promptName of promptTypes) {
3308
3684
  try {
@@ -3314,7 +3690,6 @@ var PromptLoader = class {
3314
3690
  }
3315
3691
  }
3316
3692
  this.isInitialized = true;
3317
- logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
3318
3693
  }
3319
3694
  /**
3320
3695
  * Replace variables in a template string using {{VARIABLE_NAME}} pattern
@@ -3354,7 +3729,6 @@ var PromptLoader = class {
3354
3729
  const processedContext = this.replaceVariables(contextMarker + contextPart, variables);
3355
3730
  const staticLength = processedStatic.length;
3356
3731
  const contextLength = processedContext.length;
3357
- logger.debug(`\u2713 Prompt caching enabled for '${promptName}' (cached: ${staticLength} chars, dynamic: ${contextLength} chars)`);
3358
3732
  return {
3359
3733
  system: [
3360
3734
  {
@@ -3391,7 +3765,6 @@ var PromptLoader = class {
3391
3765
  this.promptsDir = dir;
3392
3766
  this.isInitialized = false;
3393
3767
  this.promptCache.clear();
3394
- logger.debug(`Prompts directory changed to: ${dir}`);
3395
3768
  }
3396
3769
  /**
3397
3770
  * Get current prompts directory
@@ -3419,7 +3792,6 @@ var PromptLoader = class {
3419
3792
  setDatabaseType(type) {
3420
3793
  this.databaseType = type;
3421
3794
  this.databaseRulesCache.clear();
3422
- logger.debug(`Database type set to: ${type}`);
3423
3795
  }
3424
3796
  /**
3425
3797
  * Get current database type
@@ -3435,7 +3807,6 @@ var PromptLoader = class {
3435
3807
  */
3436
3808
  async loadDatabaseRules() {
3437
3809
  if (this.databaseRulesCache.has(this.databaseType)) {
3438
- logger.debug(`\u2713 Database rules for '${this.databaseType}' loaded from cache`);
3439
3810
  return this.databaseRulesCache.get(this.databaseType);
3440
3811
  }
3441
3812
  const rulesPath = path2.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
@@ -3443,7 +3814,6 @@ var PromptLoader = class {
3443
3814
  if (fs3.existsSync(rulesPath)) {
3444
3815
  const rules = fs3.readFileSync(rulesPath, "utf-8");
3445
3816
  this.databaseRulesCache.set(this.databaseType, rules);
3446
- logger.info(`\u2713 Loaded database rules for '${this.databaseType}' from ${rulesPath}`);
3447
3817
  return rules;
3448
3818
  }
3449
3819
  } catch (error) {
@@ -3719,7 +4089,6 @@ var Schema = class {
3719
4089
  * @returns Parsed schema object or null if error occurs
3720
4090
  */
3721
4091
  getDatabaseSchema() {
3722
- logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
3723
4092
  try {
3724
4093
  const dir = path3.dirname(this.schemaFilePath);
3725
4094
  if (!fs4.existsSync(dir)) {
@@ -3988,14 +4357,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
3988
4357
  Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
3989
4358
  `;
3990
4359
  this.logStream?.write(logLine);
3991
- if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
3992
- console.log(`[LLM] \u26A1 CACHE HIT: ${entry.cacheReadTokens.toLocaleString()} tokens read from cache (${entry.method})`);
3993
- } else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
3994
- console.log(`[LLM] \u{1F4DD} CACHE WRITE: ${entry.cacheWriteTokens.toLocaleString()} tokens cached for future requests (${entry.method})`);
3995
- }
3996
- if (process.env.SUPERATOM_LOG_LEVEL === "verbose") {
3997
- console.log("\n[LLM-Usage]", logLine);
3998
- }
3999
4360
  }
4000
4361
  /**
4001
4362
  * Log session summary (call at end of request)
@@ -4028,11 +4389,6 @@ Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStat
4028
4389
 
4029
4390
  `;
4030
4391
  this.logStream?.write(summary);
4031
- console.log("\n[LLM-Usage] Session Summary:");
4032
- 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`);
4033
- if (hasCaching) {
4034
- console.log(` Cache: ${this.sessionStats.totalCacheReadTokens.toLocaleString()} read, ${this.sessionStats.totalCacheWriteTokens.toLocaleString()} written | Savings: ~$${cacheReadSavings.toFixed(4)}`);
4035
- }
4036
4392
  }
4037
4393
  /**
4038
4394
  * Reset session stats (call at start of new user request)
@@ -4073,7 +4429,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4073
4429
  `;
4074
4430
  this.logStream.write(header);
4075
4431
  this.resetSession();
4076
- console.log(`[LLM-Usage] Log file reset for new request: ${this.logPath}`);
4077
4432
  } catch (error) {
4078
4433
  console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
4079
4434
  }
@@ -5547,21 +5902,20 @@ var getKnowledgeBase = async ({
5547
5902
  }) => {
5548
5903
  try {
5549
5904
  if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
5550
- logger.info("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
5905
+ logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
5551
5906
  return "";
5552
5907
  }
5553
- logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
5554
5908
  const result = await collections["knowledge-base"]["query"]({
5555
5909
  prompt,
5556
5910
  topK
5557
5911
  });
5558
5912
  if (!result || !result.content) {
5559
- logger.info("[KnowledgeBase] No knowledge base results returned");
5913
+ logger.warn("[KnowledgeBase] No knowledge base results returned");
5560
5914
  return "";
5561
5915
  }
5562
5916
  logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
5563
5917
  if (result.metadata?.sources && result.metadata.sources.length > 0) {
5564
- logger.debug(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
5918
+ logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
5565
5919
  }
5566
5920
  return result.content;
5567
5921
  } catch (error) {
@@ -5576,13 +5930,12 @@ var getGlobalKnowledgeBase = async ({
5576
5930
  }) => {
5577
5931
  try {
5578
5932
  if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
5579
- logger.info("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
5933
+ logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
5580
5934
  return "";
5581
5935
  }
5582
- logger.info("[KnowledgeBase] Fetching global knowledge base nodes...");
5583
5936
  const result = await collections["knowledge-base"]["getGlobal"]({ limit });
5584
5937
  if (!result || !result.content) {
5585
- logger.info("[KnowledgeBase] No global knowledge base nodes found");
5938
+ logger.warn("[KnowledgeBase] No global knowledge base nodes found");
5586
5939
  return "";
5587
5940
  }
5588
5941
  logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
@@ -5600,14 +5953,13 @@ var getUserKnowledgeBase = async ({
5600
5953
  }) => {
5601
5954
  try {
5602
5955
  if (!userId) {
5603
- logger.info("[KnowledgeBase] No userId provided, skipping user knowledge base");
5956
+ logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
5604
5957
  return "";
5605
5958
  }
5606
5959
  if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
5607
- logger.info("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
5960
+ logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
5608
5961
  return "";
5609
5962
  }
5610
- logger.info(`[KnowledgeBase] Fetching user knowledge base nodes for userId: ${userId}...`);
5611
5963
  const result = await collections["knowledge-base"]["getByUser"]({
5612
5964
  userId: Number(userId),
5613
5965
  limit
@@ -5630,7 +5982,6 @@ var getAllKnowledgeBase = async ({
5630
5982
  userId,
5631
5983
  topK = 3
5632
5984
  }) => {
5633
- logger.info("[KnowledgeBase] Fetching all knowledge base contexts...");
5634
5985
  const [globalContext, userContext, queryContext] = await Promise.all([
5635
5986
  getGlobalKnowledgeBase({ collections }),
5636
5987
  getUserKnowledgeBase({ collections, userId }),
@@ -5652,7 +6003,6 @@ var getAllKnowledgeBase = async ({
5652
6003
  combinedContext += "The following information is semantically relevant to the current query:\n\n";
5653
6004
  combinedContext += queryContext + "\n\n";
5654
6005
  }
5655
- logger.info(`[KnowledgeBase] Combined knowledge base context: global=${globalContext.length} chars, user=${userContext.length} chars, query=${queryContext.length} chars`);
5656
6006
  return {
5657
6007
  globalContext,
5658
6008
  userContext,
@@ -5889,11 +6239,11 @@ var searchConversationsWithReranking = async (options) => {
5889
6239
  } = options;
5890
6240
  try {
5891
6241
  if (!collections || !collections["conversation-history"]) {
5892
- logger.info("[ConversationSearch] conversation-history collection not registered, skipping");
6242
+ logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
5893
6243
  return null;
5894
6244
  }
5895
6245
  if (!collections["conversation-history"]["searchMultiple"]) {
5896
- logger.info("[ConversationSearch] searchMultiple not available, falling back to standard search");
6246
+ logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
5897
6247
  return searchConversations({
5898
6248
  userPrompt,
5899
6249
  collections,
@@ -5901,9 +6251,6 @@ var searchConversationsWithReranking = async (options) => {
5901
6251
  similarityThreshold
5902
6252
  });
5903
6253
  }
5904
- logger.info(`[ConversationSearch] Hybrid search for: "${userPrompt.substring(0, 50)}..."`);
5905
- logger.info(`[ConversationSearch] Fetching ${rerankCandidates} candidates for reranking`);
5906
- logger.info(`[ConversationSearch] Weights - Semantic: ${hybridOptions.semanticWeight}, BM25: ${hybridOptions.bm25Weight}`);
5907
6254
  const results = await collections["conversation-history"]["searchMultiple"]({
5908
6255
  userPrompt,
5909
6256
  userId,
@@ -5944,7 +6291,6 @@ var searchConversationsWithReranking = async (options) => {
5944
6291
  logger.info(
5945
6292
  `[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
5946
6293
  );
5947
- logger.info(` - Returning cached result for: "${matchedUserPrompt}"`);
5948
6294
  return {
5949
6295
  uiBlock: best.uiBlock,
5950
6296
  similarity: semanticScore,
@@ -5964,97 +6310,335 @@ var ConversationSearch = {
5964
6310
  };
5965
6311
  var conversation_search_default = ConversationSearch;
5966
6312
 
5967
- // src/userResponse/base-llm.ts
5968
- var BaseLLM = class {
5969
- constructor(config) {
5970
- this.model = config?.model || this.getDefaultModel();
5971
- this.fastModel = config?.fastModel || this.getDefaultFastModel();
5972
- this.defaultLimit = config?.defaultLimit || 10;
5973
- this.apiKey = config?.apiKey;
5974
- this.modelStrategy = config?.modelStrategy || "fast";
5975
- this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
6313
+ // src/userResponse/prompt-extractor.ts
6314
+ function extractPromptText(content) {
6315
+ if (content === null || content === void 0) {
6316
+ return "";
5976
6317
  }
5977
- /**
5978
- * Get the appropriate model based on task type and model strategy
5979
- * @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
5980
- * @returns The model string to use for this task
5981
- */
5982
- getModelForTask(taskType) {
5983
- switch (this.modelStrategy) {
5984
- case "best":
5985
- return this.model;
5986
- case "fast":
5987
- return this.fastModel;
5988
- case "balanced":
5989
- default:
5990
- return taskType === "complex" ? this.model : this.fastModel;
6318
+ if (typeof content === "string") {
6319
+ return content;
6320
+ }
6321
+ if (Array.isArray(content)) {
6322
+ return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
6323
+ }
6324
+ if (content && typeof content === "object") {
6325
+ return extractObjectText(content);
6326
+ }
6327
+ return String(content);
6328
+ }
6329
+ function extractContentBlockText(item) {
6330
+ if (typeof item === "string") {
6331
+ return item;
6332
+ }
6333
+ if (item && typeof item === "object") {
6334
+ const obj = item;
6335
+ if (typeof obj.text === "string") {
6336
+ return obj.text;
6337
+ }
6338
+ if (typeof obj.content === "string") {
6339
+ return obj.content;
5991
6340
  }
6341
+ return JSON.stringify(item, null, 2);
6342
+ }
6343
+ return String(item);
6344
+ }
6345
+ function extractObjectText(obj) {
6346
+ if (typeof obj.text === "string") {
6347
+ return obj.text;
6348
+ }
6349
+ if (typeof obj.content === "string") {
6350
+ return obj.content;
6351
+ }
6352
+ return JSON.stringify(obj, null, 2);
6353
+ }
6354
+
6355
+ // src/userResponse/constants.ts
6356
+ var MAX_QUERY_VALIDATION_RETRIES = 3;
6357
+ var MAX_QUERY_ATTEMPTS = 6;
6358
+ var MAX_TOOL_ATTEMPTS = 3;
6359
+ var STREAM_FLUSH_INTERVAL_MS = 50;
6360
+ var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
6361
+ var STREAM_DELAY_MS = 50;
6362
+ var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
6363
+ var MAX_TOKENS_QUERY_FIX = 2048;
6364
+ var MAX_TOKENS_COMPONENT_MATCHING = 8192;
6365
+ var MAX_TOKENS_CLASSIFICATION = 1500;
6366
+ var MAX_TOKENS_ADAPTATION = 8192;
6367
+ var MAX_TOKENS_TEXT_RESPONSE = 4e3;
6368
+ var MAX_TOKENS_NEXT_QUESTIONS = 1200;
6369
+ var DEFAULT_MAX_ROWS_FOR_LLM = 10;
6370
+ var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
6371
+ var STREAM_PREVIEW_MAX_ROWS = 10;
6372
+ var STREAM_PREVIEW_MAX_CHARS = 200;
6373
+ var TOOL_TRACKING_MAX_ROWS = 5;
6374
+ var TOOL_TRACKING_MAX_CHARS = 200;
6375
+ var TOOL_TRACKING_SAMPLE_ROWS = 3;
6376
+ var MAX_COMPONENT_QUERY_LIMIT = 10;
6377
+ var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
6378
+ var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
6379
+ var MAX_TOOL_CALLING_ITERATIONS = 20;
6380
+ var KNOWLEDGE_BASE_TOP_K = 3;
6381
+
6382
+ // src/userResponse/stream-buffer.ts
6383
+ var StreamBuffer = class {
6384
+ constructor(callback) {
6385
+ this.buffer = "";
6386
+ this.flushTimer = null;
6387
+ this.fullText = "";
6388
+ this.callback = callback;
5992
6389
  }
5993
6390
  /**
5994
- * Set the model strategy at runtime
5995
- * @param strategy - 'best', 'fast', or 'balanced'
6391
+ * Check if the buffer has a callback configured
5996
6392
  */
5997
- setModelStrategy(strategy) {
5998
- this.modelStrategy = strategy;
5999
- logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
6393
+ hasCallback() {
6394
+ return !!this.callback;
6000
6395
  }
6001
6396
  /**
6002
- * Get the current model strategy
6003
- * @returns The current model strategy
6397
+ * Get all text that has been written (including already flushed)
6004
6398
  */
6005
- getModelStrategy() {
6006
- return this.modelStrategy;
6399
+ getFullText() {
6400
+ return this.fullText;
6007
6401
  }
6008
6402
  /**
6009
- * Set the conversation similarity threshold at runtime
6010
- * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
6403
+ * Write a chunk to the buffer
6404
+ * Large chunks or chunks with newlines are flushed immediately
6405
+ * Small chunks are batched and flushed after a short interval
6406
+ *
6407
+ * @param chunk - Text chunk to write
6011
6408
  */
6012
- setConversationSimilarityThreshold(threshold) {
6013
- if (threshold < 0 || threshold > 1) {
6014
- logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default 0.8`);
6015
- this.conversationSimilarityThreshold = 0.8;
6409
+ write(chunk) {
6410
+ this.fullText += chunk;
6411
+ if (!this.callback) {
6016
6412
  return;
6017
6413
  }
6018
- this.conversationSimilarityThreshold = threshold;
6019
- logger.info(`[${this.getProviderName()}] Conversation similarity threshold set to: ${threshold}`);
6414
+ this.buffer += chunk;
6415
+ if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
6416
+ this.flushNow();
6417
+ } else if (!this.flushTimer) {
6418
+ this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
6419
+ }
6020
6420
  }
6021
6421
  /**
6022
- * Get the current conversation similarity threshold
6023
- * @returns The current threshold value
6422
+ * Flush the buffer immediately
6423
+ * Call this before tool execution or other operations that need clean output
6024
6424
  */
6025
- getConversationSimilarityThreshold() {
6026
- return this.conversationSimilarityThreshold;
6425
+ flush() {
6426
+ this.flushNow();
6027
6427
  }
6028
6428
  /**
6029
- * Get the API key (from instance, parameter, or environment)
6429
+ * Internal flush implementation
6030
6430
  */
6031
- getApiKey(apiKey) {
6032
- return apiKey || this.apiKey || this.getDefaultApiKey();
6431
+ flushNow() {
6432
+ if (this.flushTimer) {
6433
+ clearTimeout(this.flushTimer);
6434
+ this.flushTimer = null;
6435
+ }
6436
+ if (this.buffer && this.callback) {
6437
+ this.callback(this.buffer);
6438
+ this.buffer = "";
6439
+ }
6033
6440
  }
6034
6441
  /**
6035
- * Check if a component contains a Form (data_modification component)
6036
- * Forms have hardcoded defaultValues that become stale when cached
6037
- * This checks both single Form components and Forms inside MultiComponentContainer
6442
+ * Clean up resources
6443
+ * Call this when done with the buffer
6038
6444
  */
6039
- containsFormComponent(component) {
6040
- if (!component) return false;
6041
- if (component.type === "Form" || component.name === "DynamicForm") {
6042
- return true;
6445
+ dispose() {
6446
+ this.flush();
6447
+ this.callback = void 0;
6448
+ }
6449
+ };
6450
+ function streamDelay(ms = STREAM_DELAY_MS) {
6451
+ return new Promise((resolve) => setTimeout(resolve, ms));
6452
+ }
6453
+ async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
6454
+ if (!streamBuffer.hasCallback()) {
6455
+ return operation();
6456
+ }
6457
+ const startTime = Date.now();
6458
+ await streamDelay(30);
6459
+ streamBuffer.write(`\u23F3 ${progressMessage}`);
6460
+ const heartbeatInterval = setInterval(() => {
6461
+ const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
6462
+ if (elapsedSeconds >= 1) {
6463
+ streamBuffer.write(` (${elapsedSeconds}s)`);
6464
+ }
6465
+ }, intervalMs);
6466
+ try {
6467
+ const result = await operation();
6468
+ return result;
6469
+ } finally {
6470
+ clearInterval(heartbeatInterval);
6471
+ streamBuffer.write("\n\n");
6472
+ }
6473
+ }
6474
+
6475
+ // src/userResponse/utils/component-props-processor.ts
6476
+ var NUMERIC_CONFIG_KEYS = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
6477
+ var STRING_CONFIG_KEYS = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
6478
+ var CONFIG_FIELDS_TO_VALIDATE = [
6479
+ "xAxisKey",
6480
+ "yAxisKey",
6481
+ "valueKey",
6482
+ "nameKey",
6483
+ "labelKey",
6484
+ "groupBy",
6485
+ "aggregationField",
6486
+ "seriesKey",
6487
+ "sizeKey",
6488
+ "xAggregationField",
6489
+ "yAggregationField"
6490
+ ];
6491
+ function findMatchingField(fieldName, configKey, validFieldNames, fieldTypes, providerName) {
6492
+ if (!fieldName) return null;
6493
+ const lowerField = fieldName.toLowerCase();
6494
+ const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
6495
+ const exactIdx = validFieldNamesLower.indexOf(lowerField);
6496
+ if (exactIdx !== -1) return validFieldNames[exactIdx];
6497
+ const containsMatches = validFieldNames.filter(
6498
+ (_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
6499
+ );
6500
+ if (containsMatches.length === 1) return containsMatches[0];
6501
+ if (NUMERIC_CONFIG_KEYS.includes(configKey)) {
6502
+ const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
6503
+ const match = numericFields.find(
6504
+ (f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
6505
+ );
6506
+ if (match) return match;
6507
+ if (numericFields.length > 0) {
6508
+ logger.warn(`[${providerName}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
6509
+ return numericFields[0];
6043
6510
  }
6044
- if (component.type === "Container" || component.name === "MultiComponentContainer") {
6045
- const nestedComponents = component.props?.config?.components || [];
6046
- for (const nested of nestedComponents) {
6047
- if (nested.type === "Form" || nested.name === "DynamicForm") {
6048
- return true;
6511
+ }
6512
+ if (STRING_CONFIG_KEYS.includes(configKey)) {
6513
+ const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
6514
+ const match = stringFields.find(
6515
+ (f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
6516
+ );
6517
+ if (match) return match;
6518
+ if (stringFields.length > 0) {
6519
+ logger.warn(`[${providerName}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
6520
+ return stringFields[0];
6521
+ }
6522
+ }
6523
+ logger.warn(`[${providerName}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
6524
+ return validFieldNames[0];
6525
+ }
6526
+ function validateConfigFieldNames(config, outputSchema, providerName) {
6527
+ if (!outputSchema?.fields || !config) return config;
6528
+ const validFieldNames = outputSchema.fields.map((f) => f.name);
6529
+ const fieldTypes = outputSchema.fields.reduce((acc, f) => {
6530
+ acc[f.name] = f.type;
6531
+ return acc;
6532
+ }, {});
6533
+ const correctedConfig = { ...config };
6534
+ for (const configKey of CONFIG_FIELDS_TO_VALIDATE) {
6535
+ const fieldValue = correctedConfig[configKey];
6536
+ if (fieldValue && typeof fieldValue === "string") {
6537
+ if (!validFieldNames.includes(fieldValue)) {
6538
+ const correctedField = findMatchingField(fieldValue, configKey, validFieldNames, fieldTypes, providerName);
6539
+ if (correctedField) {
6540
+ logger.warn(`[${providerName}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
6541
+ correctedConfig[configKey] = correctedField;
6049
6542
  }
6050
6543
  }
6051
6544
  }
6052
- return false;
6545
+ }
6546
+ if (Array.isArray(correctedConfig.series)) {
6547
+ correctedConfig.series = correctedConfig.series.map((s) => {
6548
+ if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
6549
+ const correctedField = findMatchingField(s.dataKey, "yAxisKey", validFieldNames, fieldTypes, providerName);
6550
+ if (correctedField) {
6551
+ logger.warn(`[${providerName}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
6552
+ return { ...s, dataKey: correctedField };
6553
+ }
6554
+ }
6555
+ return s;
6556
+ });
6557
+ }
6558
+ return correctedConfig;
6559
+ }
6560
+ function validateExternalTool(externalTool, executedTools, providerName) {
6561
+ if (!externalTool) {
6562
+ return { valid: true };
6563
+ }
6564
+ const toolId = externalTool.toolId;
6565
+ const validToolIds = (executedTools || []).map((t) => t.id);
6566
+ const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
6567
+ if (!isValidTool) {
6568
+ logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
6569
+ return { valid: false };
6570
+ }
6571
+ const executedTool = executedTools?.find((t) => t.id === toolId);
6572
+ return { valid: true, executedTool };
6573
+ }
6574
+ function validateAndCleanQuery(query, config) {
6575
+ if (!query) {
6576
+ return { query: null, wasModified: false };
6577
+ }
6578
+ let wasModified = false;
6579
+ let cleanedQuery = query;
6580
+ const queryStr = typeof query === "string" ? query : query?.sql || "";
6581
+ if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
6582
+ logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
6583
+ return { query: null, wasModified: true };
6584
+ }
6585
+ const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
6586
+ if (fixed) {
6587
+ logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
6588
+ wasModified = true;
6589
+ if (typeof cleanedQuery === "string") {
6590
+ cleanedQuery = fixedQuery;
6591
+ } else if (cleanedQuery?.sql) {
6592
+ cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
6593
+ }
6594
+ }
6595
+ if (typeof cleanedQuery === "string") {
6596
+ const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6597
+ if (limitedQuery !== cleanedQuery) wasModified = true;
6598
+ cleanedQuery = limitedQuery;
6599
+ } else if (cleanedQuery?.sql) {
6600
+ const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6601
+ if (limitedSql !== cleanedQuery.sql) wasModified = true;
6602
+ cleanedQuery = { ...cleanedQuery, sql: limitedSql };
6603
+ }
6604
+ return { query: cleanedQuery, wasModified };
6605
+ }
6606
+ function processComponentProps(props, executedTools, config) {
6607
+ let cleanedProps = { ...props };
6608
+ if (cleanedProps.externalTool) {
6609
+ const { valid, executedTool } = validateExternalTool(
6610
+ cleanedProps.externalTool,
6611
+ executedTools,
6612
+ config.providerName
6613
+ );
6614
+ if (!valid) {
6615
+ cleanedProps.externalTool = null;
6616
+ } else if (executedTool?.outputSchema?.fields && cleanedProps.config) {
6617
+ cleanedProps.config = validateConfigFieldNames(
6618
+ cleanedProps.config,
6619
+ executedTool.outputSchema,
6620
+ config.providerName
6621
+ );
6622
+ }
6623
+ }
6624
+ if (cleanedProps.query) {
6625
+ const { query } = validateAndCleanQuery(cleanedProps.query, config);
6626
+ cleanedProps.query = query;
6627
+ }
6628
+ if (cleanedProps.query && cleanedProps.externalTool) {
6629
+ logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
6630
+ }
6631
+ return cleanedProps;
6632
+ }
6633
+
6634
+ // src/userResponse/services/query-execution-service.ts
6635
+ var QueryExecutionService = class {
6636
+ constructor(config) {
6637
+ this.config = config;
6053
6638
  }
6054
6639
  /**
6055
- * Get the cache key for a query (the exact sql param that would be sent to execute)
6640
+ * Get the cache key for a query
6056
6641
  * This ensures the cache key matches what the frontend will send
6057
- * Used for both caching and internal deduplication
6058
6642
  */
6059
6643
  getQueryCacheKey(query) {
6060
6644
  if (typeof query === "string") {
@@ -6070,17 +6654,19 @@ var BaseLLM = class {
6070
6654
  return "";
6071
6655
  }
6072
6656
  /**
6073
- * Execute a query against the database for validation and caching
6657
+ * Execute a query against the database
6074
6658
  * @param query - The SQL query to execute (string or object with sql/values)
6075
6659
  * @param collections - Collections object containing database execute function
6076
6660
  * @returns Object with result data and cache key
6077
- * @throws Error if query execution fails
6078
6661
  */
6079
- async executeQueryForValidation(query, collections) {
6662
+ async executeQuery(query, collections) {
6080
6663
  const cacheKey = this.getQueryCacheKey(query);
6081
6664
  if (!cacheKey) {
6082
6665
  throw new Error("Invalid query format: expected string or object with sql property");
6083
6666
  }
6667
+ if (!collections?.["database"]?.["execute"]) {
6668
+ throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
6669
+ }
6084
6670
  const result = await collections["database"]["execute"]({ sql: cacheKey });
6085
6671
  return { result, cacheKey };
6086
6672
  }
@@ -6088,7 +6674,7 @@ var BaseLLM = class {
6088
6674
  * Request the LLM to fix a failed SQL query
6089
6675
  * @param failedQuery - The query that failed execution
6090
6676
  * @param errorMessage - The error message from the failed execution
6091
- * @param componentContext - Context about the component (name, type, title)
6677
+ * @param componentContext - Context about the component
6092
6678
  * @param apiKey - Optional API key
6093
6679
  * @returns Fixed query string
6094
6680
  */
@@ -6129,10 +6715,10 @@ Fixed SQL query:`;
6129
6715
  user: prompt
6130
6716
  },
6131
6717
  {
6132
- model: this.getModelForTask("simple"),
6133
- maxTokens: 2048,
6718
+ model: this.config.getModelForTask("simple"),
6719
+ maxTokens: MAX_TOKENS_QUERY_FIX,
6134
6720
  temperature: 0,
6135
- apiKey: this.getApiKey(apiKey)
6721
+ apiKey: this.config.getApiKey(apiKey)
6136
6722
  }
6137
6723
  );
6138
6724
  let fixedQuery = response.trim();
@@ -6142,39 +6728,576 @@ Fixed SQL query:`;
6142
6728
  return validatedQuery;
6143
6729
  }
6144
6730
  /**
6145
- * Match components from text response suggestions and generate follow-up questions
6146
- * Takes a text response with component suggestions (c1:type format) and matches with available components
6147
- * Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
6148
- * All components are placed in a default MultiComponentContainer layout
6149
- * @param analysisContent - The text response containing component suggestions
6150
- * @param components - List of available components
6151
- * @param apiKey - Optional API key
6152
- * @param logCollector - Optional log collector
6153
- * @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
6154
- * @returns Object containing matched components, layout title/description, and follow-up actions
6731
+ * Validate a single component's query with retry logic
6732
+ * @param component - The component to validate
6733
+ * @param collections - Collections object containing database execute function
6734
+ * @param apiKey - Optional API key for LLM calls
6735
+ * @returns Validation result with component, query key, and result
6155
6736
  */
6156
- async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, logCollector, componentStreamCallback, deferredTools, executedTools, collections, userId) {
6157
- const methodStartTime = Date.now();
6158
- const methodName = "matchComponentsFromAnalysis";
6159
- logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
6160
- try {
6161
- logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
6162
- let availableComponentsText = "No components available";
6163
- if (components && components.length > 0) {
6164
- availableComponentsText = components.map((comp, idx) => {
6165
- const keywords = comp.keywords ? comp.keywords.join(", ") : "";
6166
- const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
6167
- return `${idx + 1}. ID: ${comp.id}
6168
- Name: ${comp.name}
6169
- Type: ${comp.type}
6170
- Description: ${comp.description || "No description"}
6171
- Keywords: ${keywords}
6737
+ async validateSingleQuery(component, collections, apiKey) {
6738
+ const query = component.props?.query;
6739
+ const originalQueryKey = this.getQueryCacheKey(query);
6740
+ const queryStr = typeof query === "string" ? query : query?.sql || "";
6741
+ let finalQueryKey = originalQueryKey;
6742
+ let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
6743
+ let currentQueryStr = queryStr;
6744
+ let validated = false;
6745
+ let lastError = "";
6746
+ let result = null;
6747
+ let attempts = 0;
6748
+ logger.info(`[${this.config.providerName}] Validating query for component: ${component.name} (${component.type})`);
6749
+ while (attempts < MAX_QUERY_VALIDATION_RETRIES && !validated) {
6750
+ attempts++;
6751
+ try {
6752
+ logger.debug(`[${this.config.providerName}] Query validation attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES} for ${component.name}`);
6753
+ const validationResult = await this.executeQuery(currentQuery, collections);
6754
+ result = validationResult.result;
6755
+ validated = true;
6756
+ queryCache.set(validationResult.cacheKey, result);
6757
+ logger.info(`[${this.config.providerName}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
6758
+ if (currentQueryStr !== queryStr) {
6759
+ const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
6760
+ component.props = {
6761
+ ...component.props,
6762
+ query: fixedQuery
6763
+ };
6764
+ finalQueryKey = this.getQueryCacheKey(fixedQuery);
6765
+ logger.info(`[${this.config.providerName}] Updated ${component.name} with fixed query`);
6766
+ }
6767
+ } catch (error) {
6768
+ lastError = error instanceof Error ? error.message : String(error);
6769
+ logger.warn(`[${this.config.providerName}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES}): ${lastError}`);
6770
+ if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
6771
+ logger.error(`[${this.config.providerName}] \u2717 Max retries reached for ${component.name}, excluding from response`);
6772
+ break;
6773
+ }
6774
+ logger.info(`[${this.config.providerName}] Requesting query fix from LLM for ${component.name}...`);
6775
+ try {
6776
+ const fixedQueryStr = await this.requestQueryFix(
6777
+ currentQueryStr,
6778
+ lastError,
6779
+ {
6780
+ name: component.name,
6781
+ type: component.type,
6782
+ title: component.props?.title
6783
+ },
6784
+ apiKey
6785
+ );
6786
+ if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
6787
+ logger.info(`[${this.config.providerName}] Received fixed query for ${component.name}, retrying...`);
6788
+ const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6789
+ currentQueryStr = limitedFixedQuery;
6790
+ if (typeof currentQuery === "string") {
6791
+ currentQuery = limitedFixedQuery;
6792
+ } else {
6793
+ currentQuery = { ...currentQuery, sql: limitedFixedQuery };
6794
+ }
6795
+ } else {
6796
+ logger.warn(`[${this.config.providerName}] LLM returned same or empty query, stopping retries`);
6797
+ break;
6798
+ }
6799
+ } catch (fixError) {
6800
+ const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
6801
+ logger.error(`[${this.config.providerName}] Failed to get query fix from LLM: ${fixErrorMsg}`);
6802
+ break;
6803
+ }
6804
+ }
6805
+ }
6806
+ if (!validated) {
6807
+ logger.warn(`[${this.config.providerName}] Component ${component.name} excluded from response due to failed query validation`);
6808
+ }
6809
+ return {
6810
+ component: validated ? component : null,
6811
+ queryKey: finalQueryKey,
6812
+ result,
6813
+ validated
6814
+ };
6815
+ }
6816
+ /**
6817
+ * Validate multiple component queries in parallel
6818
+ * @param components - Array of components with potential queries
6819
+ * @param collections - Collections object containing database execute function
6820
+ * @param apiKey - Optional API key for LLM calls
6821
+ * @returns Object with validated components and query results map
6822
+ */
6823
+ async validateComponentQueries(components, collections, apiKey) {
6824
+ const queryResults = /* @__PURE__ */ new Map();
6825
+ const validatedComponents = [];
6826
+ const componentsWithoutQuery = [];
6827
+ const componentsWithQuery = [];
6828
+ for (const component of components) {
6829
+ if (!component.props?.query) {
6830
+ componentsWithoutQuery.push(component);
6831
+ } else {
6832
+ componentsWithQuery.push(component);
6833
+ }
6834
+ }
6835
+ validatedComponents.push(...componentsWithoutQuery);
6836
+ if (componentsWithQuery.length === 0) {
6837
+ return { components: validatedComponents, queryResults };
6838
+ }
6839
+ logger.info(`[${this.config.providerName}] Validating ${componentsWithQuery.length} component queries in parallel...`);
6840
+ const validationPromises = componentsWithQuery.map(
6841
+ (component) => this.validateSingleQuery(component, collections, apiKey)
6842
+ );
6843
+ const results = await Promise.allSettled(validationPromises);
6844
+ for (let i = 0; i < results.length; i++) {
6845
+ const result = results[i];
6846
+ const component = componentsWithQuery[i];
6847
+ if (result.status === "fulfilled") {
6848
+ const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
6849
+ if (validated && validatedComponent) {
6850
+ validatedComponents.push(validatedComponent);
6851
+ if (queryResult) {
6852
+ queryResults.set(queryKey, queryResult);
6853
+ queryResults.set(`${component.id}:${queryKey}`, queryResult);
6854
+ }
6855
+ }
6856
+ } else {
6857
+ logger.error(`[${this.config.providerName}] Unexpected error validating ${component.name}: ${result.reason}`);
6858
+ }
6859
+ }
6860
+ logger.info(`[${this.config.providerName}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
6861
+ return {
6862
+ components: validatedComponents,
6863
+ queryResults
6864
+ };
6865
+ }
6866
+ };
6867
+
6868
+ // src/userResponse/services/tool-executor-service.ts
6869
+ var ToolExecutorService = class {
6870
+ constructor(config) {
6871
+ this.queryAttempts = /* @__PURE__ */ new Map();
6872
+ this.toolAttempts = /* @__PURE__ */ new Map();
6873
+ this.executedToolsList = [];
6874
+ this.maxAttemptsReached = false;
6875
+ this.config = config;
6876
+ }
6877
+ /**
6878
+ * Reset state for a new execution
6879
+ */
6880
+ reset() {
6881
+ this.queryAttempts.clear();
6882
+ this.toolAttempts.clear();
6883
+ this.executedToolsList = [];
6884
+ this.maxAttemptsReached = false;
6885
+ }
6886
+ /**
6887
+ * Get list of successfully executed tools
6888
+ */
6889
+ getExecutedTools() {
6890
+ return this.executedToolsList;
6891
+ }
6892
+ /**
6893
+ * Check if max attempts were reached
6894
+ */
6895
+ isMaxAttemptsReached() {
6896
+ return this.maxAttemptsReached;
6897
+ }
6898
+ /**
6899
+ * Create a tool handler function for LLM.streamWithTools
6900
+ * @param externalTools - List of available external tools
6901
+ * @returns Tool handler function
6902
+ */
6903
+ createToolHandler(externalTools) {
6904
+ return async (toolName, toolInput) => {
6905
+ if (toolName === "execute_query") {
6906
+ return this.executeQuery(toolInput);
6907
+ } else {
6908
+ return this.executeExternalTool(toolName, toolInput, externalTools);
6909
+ }
6910
+ };
6911
+ }
6912
+ /**
6913
+ * Execute a SQL query with retry tracking and streaming feedback
6914
+ */
6915
+ async executeQuery(toolInput) {
6916
+ let sql = toolInput.sql;
6917
+ const params = toolInput.params || {};
6918
+ const reasoning = toolInput.reasoning;
6919
+ const { streamBuffer, collections, providerName } = this.config;
6920
+ sql = ensureQueryLimit(sql, MAX_COMPONENT_QUERY_LIMIT, MAX_COMPONENT_QUERY_LIMIT);
6921
+ const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
6922
+ const attempts = (this.queryAttempts.get(queryKey) || 0) + 1;
6923
+ this.queryAttempts.set(queryKey, attempts);
6924
+ if (Object.keys(params).length > 0) {
6925
+ logger.info(`[${providerName}] Query params: ${JSON.stringify(params)}`);
6926
+ }
6927
+ if (attempts > MAX_QUERY_ATTEMPTS) {
6928
+ const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
6929
+ logger.error(`[${providerName}] ${errorMsg}`);
6930
+ this.maxAttemptsReached = true;
6931
+ if (streamBuffer.hasCallback()) {
6932
+ streamBuffer.write(`
6933
+
6934
+ \u274C ${errorMsg}
6935
+
6936
+ Please try rephrasing your question or simplifying your request.
6937
+
6938
+ `);
6939
+ }
6940
+ throw new Error(errorMsg);
6941
+ }
6942
+ try {
6943
+ streamBuffer.flush();
6944
+ if (streamBuffer.hasCallback()) {
6945
+ const paramsDisplay = Object.keys(params).length > 0 ? `
6946
+ **Parameters:** ${JSON.stringify(params)}` : "";
6947
+ if (attempts === 1) {
6948
+ streamBuffer.write(`
6949
+
6950
+ \u{1F50D} **Analyzing your question...**
6951
+
6952
+ `);
6953
+ await streamDelay();
6954
+ if (reasoning) {
6955
+ streamBuffer.write(`\u{1F4AD} ${reasoning}
6956
+
6957
+ `);
6958
+ await streamDelay();
6959
+ }
6960
+ streamBuffer.write(`\u{1F4DD} **Generated SQL Query:**
6961
+ \`\`\`sql
6962
+ ${sql}
6963
+ \`\`\`${paramsDisplay}
6964
+
6965
+ `);
6966
+ await streamDelay();
6967
+ } else {
6968
+ streamBuffer.write(`
6969
+
6970
+ \u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
6971
+
6972
+ `);
6973
+ await streamDelay();
6974
+ if (reasoning) {
6975
+ streamBuffer.write(`\u{1F4AD} ${reasoning}
6976
+
6977
+ `);
6978
+ await streamDelay();
6979
+ }
6980
+ streamBuffer.write(`\u{1F4DD} **Corrected SQL Query:**
6981
+ \`\`\`sql
6982
+ ${sql}
6983
+ \`\`\`${paramsDisplay}
6984
+
6985
+ `);
6986
+ await streamDelay();
6987
+ }
6988
+ }
6989
+ if (!collections?.["database"]?.["execute"]) {
6990
+ throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
6991
+ }
6992
+ const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
6993
+ const result = await withProgressHeartbeat(
6994
+ () => collections["database"]["execute"](queryPayload),
6995
+ "Executing database query",
6996
+ streamBuffer
6997
+ );
6998
+ const data = result?.data || result;
6999
+ const rowCount = result?.count ?? (Array.isArray(data) ? data.length : "N/A");
7000
+ if (streamBuffer.hasCallback()) {
7001
+ streamBuffer.write(`
7002
+ \u2705 **Query executed successfully!**
7003
+
7004
+ `);
7005
+ await streamDelay();
7006
+ if (Array.isArray(data) && data.length > 0) {
7007
+ const firstRow = data[0];
7008
+ const columns = Object.keys(firstRow);
7009
+ if (data.length === 1 && columns.length === 1) {
7010
+ const value = firstRow[columns[0]];
7011
+ streamBuffer.write(`**Result:** ${value}
7012
+
7013
+ `);
7014
+ await streamDelay();
7015
+ } else if (data.length > 0) {
7016
+ streamBuffer.write(`**Retrieved ${rowCount} rows**
7017
+
7018
+ `);
7019
+ await streamDelay();
7020
+ const streamPreview = formatQueryResultForLLM(data, {
7021
+ maxRows: STREAM_PREVIEW_MAX_ROWS,
7022
+ maxCharsPerField: STREAM_PREVIEW_MAX_CHARS
7023
+ });
7024
+ streamBuffer.write(`<DataTable>${JSON.stringify(streamPreview.data)}</DataTable>
7025
+
7026
+ `);
7027
+ if (streamPreview.truncationNote) {
7028
+ streamBuffer.write(`*${streamPreview.truncationNote}*
7029
+
7030
+ `);
7031
+ }
7032
+ await streamDelay();
7033
+ }
7034
+ } else if (Array.isArray(data) && data.length === 0) {
7035
+ streamBuffer.write(`**No rows returned.**
7036
+
7037
+ `);
7038
+ await streamDelay();
7039
+ }
7040
+ streamBuffer.write(`\u{1F4CA} **Analyzing results...**
7041
+
7042
+ `);
7043
+ }
7044
+ const formattedResult = formatQueryResultForLLM(data, {
7045
+ maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
7046
+ maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
7047
+ });
7048
+ if (formattedResult.truncationNote) {
7049
+ logger.info(`[${providerName}] Truncation: ${formattedResult.truncationNote}`);
7050
+ }
7051
+ return formatResultAsString(formattedResult);
7052
+ } catch (error) {
7053
+ const errorMsg = error instanceof Error ? error.message : String(error);
7054
+ logger.error(`[${providerName}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
7055
+ userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
7056
+ if (streamBuffer.hasCallback()) {
7057
+ streamBuffer.write(`\u274C **Query execution failed:**
7058
+ \`\`\`
7059
+ ${errorMsg}
7060
+ \`\`\`
7061
+
7062
+ `);
7063
+ if (attempts < MAX_QUERY_ATTEMPTS) {
7064
+ streamBuffer.write(`\u{1F527} **Generating corrected query...**
7065
+
7066
+ `);
7067
+ }
7068
+ }
7069
+ throw new Error(`Query execution failed: ${errorMsg}`);
7070
+ }
7071
+ }
7072
+ /**
7073
+ * Execute an external tool with retry tracking and streaming feedback
7074
+ */
7075
+ async executeExternalTool(toolName, toolInput, externalTools) {
7076
+ const { streamBuffer, providerName } = this.config;
7077
+ const externalTool = externalTools?.find((t) => t.id === toolName);
7078
+ if (!externalTool) {
7079
+ throw new Error(`Unknown tool: ${toolName}`);
7080
+ }
7081
+ const attempts = (this.toolAttempts.get(toolName) || 0) + 1;
7082
+ this.toolAttempts.set(toolName, attempts);
7083
+ if (attempts > MAX_TOOL_ATTEMPTS) {
7084
+ const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
7085
+ logger.error(`[${providerName}] ${errorMsg}`);
7086
+ if (streamBuffer.hasCallback()) {
7087
+ streamBuffer.write(`
7088
+
7089
+ \u274C ${errorMsg}
7090
+
7091
+ Please try rephrasing your request or contact support.
7092
+
7093
+ `);
7094
+ }
7095
+ throw new Error(errorMsg);
7096
+ }
7097
+ try {
7098
+ streamBuffer.flush();
7099
+ if (streamBuffer.hasCallback()) {
7100
+ if (attempts === 1) {
7101
+ streamBuffer.write(`
7102
+
7103
+ \u{1F517} **Executing ${externalTool.name}...**
7104
+
7105
+ `);
7106
+ } else {
7107
+ streamBuffer.write(`
7108
+
7109
+ \u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
7110
+
7111
+ `);
7112
+ }
7113
+ await streamDelay();
7114
+ }
7115
+ const result = await withProgressHeartbeat(
7116
+ () => externalTool.fn(toolInput),
7117
+ `Running ${externalTool.name}`,
7118
+ streamBuffer
7119
+ );
7120
+ if (!this.executedToolsList.find((t) => t.id === externalTool.id)) {
7121
+ const formattedForTracking = formatToolResultForLLM(result, {
7122
+ toolName: externalTool.name,
7123
+ toolLimit: externalTool.limit,
7124
+ maxRows: TOOL_TRACKING_MAX_ROWS,
7125
+ maxCharsPerField: TOOL_TRACKING_MAX_CHARS
7126
+ });
7127
+ this.executedToolsList.push({
7128
+ id: externalTool.id,
7129
+ name: externalTool.name,
7130
+ params: toolInput,
7131
+ result: {
7132
+ _totalRecords: formattedForTracking.summary.totalRecords,
7133
+ _recordsShown: formattedForTracking.summary.recordsShown,
7134
+ _metadata: formattedForTracking.metadata,
7135
+ _sampleData: formattedForTracking.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS)
7136
+ },
7137
+ outputSchema: externalTool.outputSchema
7138
+ });
7139
+ }
7140
+ if (streamBuffer.hasCallback()) {
7141
+ streamBuffer.write(`\u2705 **${externalTool.name} completed successfully**
7142
+
7143
+ `);
7144
+ await streamDelay();
7145
+ }
7146
+ const formattedToolResult = formatToolResultForLLM(result, {
7147
+ toolName: externalTool.name,
7148
+ toolLimit: externalTool.limit,
7149
+ maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
7150
+ maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
7151
+ });
7152
+ if (formattedToolResult.truncationNote) {
7153
+ logger.info(`[${providerName}] Truncation: ${formattedToolResult.truncationNote}`);
7154
+ }
7155
+ return formatResultAsString(formattedToolResult);
7156
+ } catch (error) {
7157
+ const errorMsg = error instanceof Error ? error.message : String(error);
7158
+ logger.error(`[${providerName}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
7159
+ userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
7160
+ if (streamBuffer.hasCallback()) {
7161
+ streamBuffer.write(`\u274C **${externalTool.name} failed:**
7162
+ \`\`\`
7163
+ ${errorMsg}
7164
+ \`\`\`
7165
+
7166
+ `);
7167
+ if (attempts < MAX_TOOL_ATTEMPTS) {
7168
+ streamBuffer.write(`\u{1F527} **Retrying with adjusted parameters...**
7169
+
7170
+ `);
7171
+ }
7172
+ }
7173
+ throw new Error(`Tool execution failed: ${errorMsg}`);
7174
+ }
7175
+ }
7176
+ };
7177
+
7178
+ // src/userResponse/base-llm.ts
7179
+ var BaseLLM = class {
7180
+ constructor(config) {
7181
+ this.model = config?.model || this.getDefaultModel();
7182
+ this.fastModel = config?.fastModel || this.getDefaultFastModel();
7183
+ this.defaultLimit = config?.defaultLimit || 10;
7184
+ this.apiKey = config?.apiKey;
7185
+ this.modelStrategy = config?.modelStrategy || "fast";
7186
+ this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
7187
+ this.queryService = new QueryExecutionService({
7188
+ defaultLimit: this.defaultLimit,
7189
+ getModelForTask: (taskType) => this.getModelForTask(taskType),
7190
+ getApiKey: (apiKey) => this.getApiKey(apiKey),
7191
+ providerName: this.getProviderName()
7192
+ });
7193
+ }
7194
+ /**
7195
+ * Get the appropriate model based on task type and model strategy
7196
+ * @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
7197
+ * @returns The model string to use for this task
7198
+ */
7199
+ getModelForTask(taskType) {
7200
+ switch (this.modelStrategy) {
7201
+ case "best":
7202
+ return this.model;
7203
+ case "fast":
7204
+ return this.fastModel;
7205
+ case "balanced":
7206
+ default:
7207
+ return taskType === "complex" ? this.model : this.fastModel;
7208
+ }
7209
+ }
7210
+ /**
7211
+ * Set the model strategy at runtime
7212
+ * @param strategy - 'best', 'fast', or 'balanced'
7213
+ */
7214
+ setModelStrategy(strategy) {
7215
+ this.modelStrategy = strategy;
7216
+ logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
7217
+ }
7218
+ /**
7219
+ * Get the current model strategy
7220
+ * @returns The current model strategy
7221
+ */
7222
+ getModelStrategy() {
7223
+ return this.modelStrategy;
7224
+ }
7225
+ /**
7226
+ * Set the conversation similarity threshold at runtime
7227
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
7228
+ */
7229
+ setConversationSimilarityThreshold(threshold) {
7230
+ if (threshold < 0 || threshold > 1) {
7231
+ logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default ${DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD}`);
7232
+ this.conversationSimilarityThreshold = DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
7233
+ return;
7234
+ }
7235
+ this.conversationSimilarityThreshold = threshold;
7236
+ }
7237
+ /**
7238
+ * Get the current conversation similarity threshold
7239
+ * @returns The current threshold value
7240
+ */
7241
+ getConversationSimilarityThreshold() {
7242
+ return this.conversationSimilarityThreshold;
7243
+ }
7244
+ /**
7245
+ * Get the API key (from instance, parameter, or environment)
7246
+ */
7247
+ getApiKey(apiKey) {
7248
+ return apiKey || this.apiKey || this.getDefaultApiKey();
7249
+ }
7250
+ /**
7251
+ * Check if a component contains a Form (data_modification component)
7252
+ * Forms have hardcoded defaultValues that become stale when cached
7253
+ * This checks both single Form components and Forms inside MultiComponentContainer
7254
+ */
7255
+ containsFormComponent(component) {
7256
+ if (!component) return false;
7257
+ if (component.type === "Form" || component.name === "DynamicForm") {
7258
+ return true;
7259
+ }
7260
+ if (component.type === "Container" || component.name === "MultiComponentContainer") {
7261
+ const nestedComponents = component.props?.config?.components || [];
7262
+ for (const nested of nestedComponents) {
7263
+ if (nested.type === "Form" || nested.name === "DynamicForm") {
7264
+ return true;
7265
+ }
7266
+ }
7267
+ }
7268
+ return false;
7269
+ }
7270
+ /**
7271
+ * Match components from text response suggestions and generate follow-up questions
7272
+ * Takes a text response with component suggestions (c1:type format) and matches with available components
7273
+ * Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
7274
+ * All components are placed in a default MultiComponentContainer layout
7275
+ * @param analysisContent - The text response containing component suggestions
7276
+ * @param components - List of available components
7277
+ * @param apiKey - Optional API key
7278
+ * @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
7279
+ * @returns Object containing matched components, layout title/description, and follow-up actions
7280
+ */
7281
+ async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, componentStreamCallback, deferredTools, executedTools, collections, userId) {
7282
+ const methodStartTime = Date.now();
7283
+ const methodName = "matchComponentsFromAnalysis";
7284
+ logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
7285
+ try {
7286
+ let availableComponentsText = "No components available";
7287
+ if (components && components.length > 0) {
7288
+ availableComponentsText = components.map((comp, idx) => {
7289
+ const keywords = comp.keywords ? comp.keywords.join(", ") : "";
7290
+ const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
7291
+ return `${idx + 1}. ID: ${comp.id}
7292
+ Name: ${comp.name}
7293
+ Type: ${comp.type}
7294
+ Description: ${comp.description || "No description"}
7295
+ Keywords: ${keywords}
6172
7296
  Props Structure: ${propsPreview}`;
6173
7297
  }).join("\n\n");
6174
7298
  }
6175
7299
  let deferredToolsText = "No deferred external tools for this request.";
6176
7300
  if (deferredTools && deferredTools.length > 0) {
6177
- logger.info(`[${this.getProviderName()}] Passing ${deferredTools.length} deferred tools to component matching`);
6178
7301
  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) => {
6179
7302
  return `${idx + 1}. **${tool.name}**
6180
7303
  toolId: "${tool.id}" (USE THIS EXACT VALUE - do not modify!)
@@ -6186,21 +7309,23 @@ Fixed SQL query:`;
6186
7309
  }
6187
7310
  let executedToolsText = "No external tools were executed for data fetching.";
6188
7311
  if (executedTools && executedTools.length > 0) {
6189
- logger.info(`[${this.getProviderName()}] Passing ${executedTools.length} executed tools to component matching`);
6190
- executedToolsText = "The following external tools were executed to fetch data.\n" + // '**IMPORTANT: For components displaying this data, use externalTool prop instead of query.**\n' +
6191
- // '**IMPORTANT: Use ONLY the field names listed in outputSchema for config keys.**\n\n' +
6192
- executedTools.map((tool, idx) => {
7312
+ executedToolsText = "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
6193
7313
  let outputSchemaText = "Not available";
6194
7314
  let fieldNamesList = "";
6195
- let recordCount = "unknown";
7315
+ const recordCount = tool.result?._totalRecords ?? "unknown";
7316
+ let metadataText = "";
7317
+ if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
7318
+ const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
7319
+ metadataText = `
7320
+ \u{1F4CB} METADATA: ${metadataEntries}`;
7321
+ }
6196
7322
  if (tool.outputSchema) {
6197
7323
  const fields = tool.outputSchema.fields || [];
6198
- recordCount = tool.result?._recordCount || (Array.isArray(tool.result) ? tool.result.length : "unknown");
6199
7324
  const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
6200
7325
  const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
6201
7326
  fieldNamesList = `
6202
- \u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
6203
- \u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
7327
+ \u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
7328
+ \u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
6204
7329
  const fieldsText = fields.map(
6205
7330
  (f) => ` "${f.name}" (${f.type}): ${f.description}`
6206
7331
  ).join("\n");
@@ -6209,11 +7334,11 @@ Fixed SQL query:`;
6209
7334
  ${fieldsText}`;
6210
7335
  }
6211
7336
  return `${idx + 1}. **${tool.name}**
6212
- toolId: "${tool.id}"
6213
- toolName: "${tool.name}"
6214
- parameters: ${JSON.stringify(tool.params || {})}
6215
- recordCount: ${recordCount} rows returned
6216
- outputSchema: ${outputSchemaText}${fieldNamesList}`;
7337
+ toolId: "${tool.id}"
7338
+ toolName: "${tool.name}"
7339
+ parameters: ${JSON.stringify(tool.params || {})}
7340
+ recordCount: ${recordCount} rows returned${metadataText}
7341
+ outputSchema: ${outputSchemaText}${fieldNamesList}`;
6217
7342
  }).join("\n\n");
6218
7343
  }
6219
7344
  const schemaDoc = schema.generateSchemaDocumentation();
@@ -6224,7 +7349,7 @@ ${fieldsText}`;
6224
7349
  prompt: userPrompt || analysisContent,
6225
7350
  collections,
6226
7351
  userId,
6227
- topK: 3
7352
+ topK: KNOWLEDGE_BASE_TOP_K
6228
7353
  });
6229
7354
  knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
6230
7355
  }
@@ -6239,30 +7364,12 @@ ${fieldsText}`;
6239
7364
  KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext,
6240
7365
  CURRENT_DATETIME: getCurrentDateTimeForPrompt()
6241
7366
  });
6242
- logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
6243
- const extractPromptText = (content) => {
6244
- if (typeof content === "string") return content;
6245
- if (Array.isArray(content)) {
6246
- return content.map((item) => {
6247
- if (typeof item === "string") return item;
6248
- if (item && typeof item.text === "string") return item.text;
6249
- if (item && item.content && typeof item.content === "string") return item.content;
6250
- return JSON.stringify(item, null, 2);
6251
- }).join("\n\n---\n\n");
6252
- }
6253
- if (content && typeof content === "object") {
6254
- if (typeof content.text === "string") return content.text;
6255
- return JSON.stringify(content, null, 2);
6256
- }
6257
- return String(content);
6258
- };
6259
7367
  logger.logLLMPrompt("matchComponentsFromAnalysis", "system", extractPromptText(prompts.system));
6260
7368
  logger.logLLMPrompt("matchComponentsFromAnalysis", "user", `Text Analysis:
6261
7369
  ${analysisContent}
6262
7370
 
6263
7371
  Executed Tools:
6264
7372
  ${executedToolsText}`);
6265
- logCollector?.info("Matching components from text response...");
6266
7373
  let fullResponseText = "";
6267
7374
  let answerComponentExtracted = false;
6268
7375
  const answerCallback = componentStreamCallback;
@@ -6322,52 +7429,44 @@ ${executedToolsText}`);
6322
7429
  ...answerComponentData.props
6323
7430
  }
6324
7431
  };
6325
- const streamTime = (/* @__PURE__ */ new Date()).toISOString();
6326
- logger.info(`[${this.getProviderName()}] \u2713 [${streamTime}] Answer component detected in stream: ${answerComponent.name} (${answerComponent.type})`);
6327
- logCollector?.info(`\u2713 Answer component: ${answerComponent.name} (${answerComponent.type}) - detected at ${streamTime}`);
6328
- if (answerComponentData.props?.query) {
6329
- logCollector?.logQuery(
6330
- "Answer component query",
6331
- answerComponentData.props.query,
6332
- { componentName: answerComponent.name, componentType: answerComponent.type, reasoning: answerComponentData.reasoning }
6333
- );
7432
+ let answerQuery = answerComponent.props?.query;
7433
+ if (answerQuery) {
7434
+ if (typeof answerQuery === "string") {
7435
+ answerQuery = ensureQueryLimit(answerQuery, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
7436
+ } else if (answerQuery?.sql) {
7437
+ const queryObj = answerQuery;
7438
+ answerQuery = { ...queryObj, sql: ensureQueryLimit(queryObj.sql, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT) };
7439
+ }
7440
+ answerComponent.props.query = answerQuery;
6334
7441
  }
6335
- const answerQuery = answerComponent.props?.query;
6336
- logger.info(`[${this.getProviderName()}] Answer component detected: ${answerComponent.name} (${answerComponent.type}), hasQuery: ${!!answerQuery}, hasDbExecute: ${!!collections?.["database"]?.["execute"]}`);
6337
7442
  if (answerQuery && collections?.["database"]?.["execute"]) {
6338
7443
  (async () => {
6339
- const MAX_RETRIES = 3;
7444
+ const maxRetries = MAX_QUERY_VALIDATION_RETRIES;
6340
7445
  let attempts = 0;
6341
7446
  let validated = false;
6342
7447
  let currentQuery = answerQuery;
6343
7448
  let currentQueryStr = typeof answerQuery === "string" ? answerQuery : answerQuery?.sql || "";
6344
7449
  let lastError = "";
6345
- logger.info(`[${this.getProviderName()}] Validating answer component query before streaming...`);
6346
- while (attempts < MAX_RETRIES && !validated) {
7450
+ while (attempts < maxRetries && !validated) {
6347
7451
  attempts++;
6348
7452
  try {
6349
- const cacheKey = this.getQueryCacheKey(currentQuery);
7453
+ const cacheKey = this.queryService.getQueryCacheKey(currentQuery);
6350
7454
  if (cacheKey) {
6351
- logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${MAX_RETRIES}`);
6352
7455
  const result2 = await collections["database"]["execute"]({ sql: cacheKey });
6353
7456
  queryCache.set(cacheKey, result2);
6354
7457
  validated = true;
6355
7458
  if (currentQuery !== answerQuery) {
6356
7459
  answerComponent.props.query = currentQuery;
6357
7460
  }
6358
- logger.info(`[${this.getProviderName()}] \u2713 Answer component query validated (attempt ${attempts}) - STREAMING TO FRONTEND NOW`);
6359
- logCollector?.info(`\u2713 Answer component query validated - streaming to frontend`);
6360
- logger.info(`[${this.getProviderName()}] Calling answerCallback for: ${answerComponent.name}`);
6361
7461
  answerCallback(answerComponent);
6362
- logger.info(`[${this.getProviderName()}] answerCallback completed for: ${answerComponent.name}`);
6363
7462
  }
6364
7463
  } catch (validationError) {
6365
7464
  lastError = validationError instanceof Error ? validationError.message : String(validationError);
6366
- logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
6367
- if (attempts < MAX_RETRIES) {
7465
+ logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${maxRetries}): ${lastError}`);
7466
+ if (attempts < maxRetries) {
6368
7467
  try {
6369
7468
  logger.info(`[${this.getProviderName()}] Requesting LLM to fix answer component query...`);
6370
- const fixedQueryStr = await this.requestQueryFix(
7469
+ const fixedQueryStr = await this.queryService.requestQueryFix(
6371
7470
  currentQueryStr,
6372
7471
  lastError,
6373
7472
  {
@@ -6377,7 +7476,7 @@ ${executedToolsText}`);
6377
7476
  },
6378
7477
  apiKey
6379
7478
  );
6380
- const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
7479
+ const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
6381
7480
  if (typeof currentQuery === "string") {
6382
7481
  currentQuery = limitedFixedQuery;
6383
7482
  } else {
@@ -6395,7 +7494,6 @@ ${executedToolsText}`);
6395
7494
  }
6396
7495
  if (!validated) {
6397
7496
  logger.warn(`[${this.getProviderName()}] Answer component query validation failed after ${attempts} attempts - component will be excluded`);
6398
- logCollector?.warn(`Answer component query validation failed: ${lastError} - component will be excluded from response`);
6399
7497
  }
6400
7498
  })();
6401
7499
  } else {
@@ -6406,7 +7504,7 @@ ${executedToolsText}`);
6406
7504
  }
6407
7505
  }
6408
7506
  } catch (e) {
6409
- logger.debug(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
7507
+ logger.error(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
6410
7508
  }
6411
7509
  }
6412
7510
  }
@@ -6418,7 +7516,7 @@ ${executedToolsText}`);
6418
7516
  },
6419
7517
  {
6420
7518
  model: this.getModelForTask("complex"),
6421
- maxTokens: 8192,
7519
+ maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
6422
7520
  temperature: 0,
6423
7521
  apiKey: this.getApiKey(apiKey),
6424
7522
  partial: partialCallback
@@ -6436,155 +7534,20 @@ ${executedToolsText}`);
6436
7534
  logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
6437
7535
  const rawActions = result.actions || [];
6438
7536
  const actions = convertQuestionsToActions(rawActions);
6439
- if (matchedComponents.length > 0) {
6440
- matchedComponents.forEach((comp, idx) => {
6441
- logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
6442
- if (comp.props?.query) {
6443
- logCollector?.logQuery(
6444
- `Component ${idx + 1} query`,
6445
- comp.props.query,
6446
- { componentName: comp.componentName, title: comp.props.title }
6447
- );
6448
- }
6449
- });
6450
- }
6451
7537
  const finalComponents = matchedComponents.map((mc) => {
6452
7538
  const originalComponent = components.find((c) => c.id === mc.componentId);
6453
7539
  if (!originalComponent) {
6454
- logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
6455
- return null;
6456
- }
6457
- let cleanedProps = { ...mc.props };
6458
- if (cleanedProps.externalTool) {
6459
- const toolId = cleanedProps.externalTool.toolId;
6460
- const validToolIds = (executedTools || []).map((t) => t.id);
6461
- const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
6462
- if (!isValidTool) {
6463
- logger.warn(`[${this.getProviderName()}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
6464
- cleanedProps.externalTool = null;
6465
- } else {
6466
- const executedTool = executedTools?.find((t) => t.id === toolId);
6467
- if (executedTool?.outputSchema?.fields && cleanedProps.config) {
6468
- const validFieldNames = executedTool.outputSchema.fields.map((f) => f.name);
6469
- const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
6470
- const findMatchingField = (fieldName, configKey) => {
6471
- if (!fieldName) return null;
6472
- const lowerField = fieldName.toLowerCase();
6473
- const exactIdx = validFieldNamesLower.indexOf(lowerField);
6474
- if (exactIdx !== -1) return validFieldNames[exactIdx];
6475
- const containsMatches = validFieldNames.filter(
6476
- (_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
6477
- );
6478
- if (containsMatches.length === 1) return containsMatches[0];
6479
- const fieldTypes = executedTool.outputSchema.fields.reduce((acc, f) => {
6480
- acc[f.name] = f.type;
6481
- return acc;
6482
- }, {});
6483
- const numericConfigKeys = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
6484
- const stringConfigKeys = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
6485
- if (numericConfigKeys.includes(configKey)) {
6486
- const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
6487
- const match = numericFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
6488
- if (match) return match;
6489
- if (numericFields.length > 0) {
6490
- logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
6491
- return numericFields[0];
6492
- }
6493
- }
6494
- if (stringConfigKeys.includes(configKey)) {
6495
- const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
6496
- const match = stringFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
6497
- if (match) return match;
6498
- if (stringFields.length > 0) {
6499
- logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
6500
- return stringFields[0];
6501
- }
6502
- }
6503
- logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
6504
- return validFieldNames[0];
6505
- };
6506
- const configFieldsToValidate = [
6507
- "xAxisKey",
6508
- "yAxisKey",
6509
- "valueKey",
6510
- "nameKey",
6511
- "labelKey",
6512
- "groupBy",
6513
- "aggregationField",
6514
- "seriesKey",
6515
- "sizeKey",
6516
- "xAggregationField",
6517
- "yAggregationField"
6518
- ];
6519
- for (const configKey of configFieldsToValidate) {
6520
- const fieldValue = cleanedProps.config[configKey];
6521
- if (fieldValue && typeof fieldValue === "string") {
6522
- if (!validFieldNames.includes(fieldValue)) {
6523
- const correctedField = findMatchingField(fieldValue, configKey);
6524
- if (correctedField) {
6525
- logger.warn(`[${this.getProviderName()}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
6526
- cleanedProps.config[configKey] = correctedField;
6527
- }
6528
- }
6529
- }
6530
- }
6531
- if (Array.isArray(cleanedProps.config.series)) {
6532
- cleanedProps.config.series = cleanedProps.config.series.map((s) => {
6533
- if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
6534
- const correctedField = findMatchingField(s.dataKey, "yAxisKey");
6535
- if (correctedField) {
6536
- logger.warn(`[${this.getProviderName()}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
6537
- return { ...s, dataKey: correctedField };
6538
- }
6539
- }
6540
- return s;
6541
- });
6542
- }
6543
- }
6544
- }
6545
- }
6546
- if (cleanedProps.query) {
6547
- const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
6548
- if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
6549
- logger.warn(`[${this.getProviderName()}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
6550
- cleanedProps.query = null;
6551
- }
6552
- }
6553
- if (cleanedProps.query) {
6554
- const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
6555
- const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
6556
- if (fixed) {
6557
- logger.warn(`[${this.getProviderName()}] SQL fixes applied to component query: ${fixes.join("; ")}`);
6558
- if (typeof cleanedProps.query === "string") {
6559
- cleanedProps.query = fixedQuery;
6560
- } else if (cleanedProps.query?.sql) {
6561
- cleanedProps.query.sql = fixedQuery;
6562
- }
6563
- }
6564
- }
6565
- if (cleanedProps.query) {
6566
- if (typeof cleanedProps.query === "string") {
6567
- cleanedProps.query = ensureQueryLimit(
6568
- cleanedProps.query,
6569
- this.defaultLimit,
6570
- 10
6571
- // maxLimit - enforce maximum of 10 rows for component queries
6572
- );
6573
- } else if (cleanedProps.query?.sql) {
6574
- cleanedProps.query = {
6575
- ...cleanedProps.query,
6576
- sql: ensureQueryLimit(
6577
- cleanedProps.query.sql,
6578
- this.defaultLimit,
6579
- 10
6580
- // maxLimit - enforce maximum of 10 rows for component queries
6581
- )
6582
- };
6583
- }
6584
- }
6585
- if (cleanedProps.query && cleanedProps.externalTool) {
6586
- logger.info(`[${this.getProviderName()}] Both query and externalTool exist, keeping both - frontend will decide`);
7540
+ logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
7541
+ return null;
6587
7542
  }
7543
+ const cleanedProps = processComponentProps(
7544
+ mc.props,
7545
+ executedTools,
7546
+ {
7547
+ providerName: this.getProviderName(),
7548
+ defaultLimit: this.defaultLimit
7549
+ }
7550
+ );
6588
7551
  return {
6589
7552
  ...originalComponent,
6590
7553
  props: {
@@ -6595,27 +7558,22 @@ ${executedToolsText}`);
6595
7558
  }).filter(Boolean);
6596
7559
  let validatedComponents = finalComponents;
6597
7560
  if (collections?.["database"]?.["execute"]) {
6598
- logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
6599
- logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
6600
7561
  try {
6601
- const validationResult = await this.validateAndRetryComponentQueries(
7562
+ const validationResult = await this.queryService.validateComponentQueries(
6602
7563
  finalComponents,
6603
7564
  collections,
6604
- apiKey,
6605
- logCollector
7565
+ apiKey
6606
7566
  );
6607
7567
  validatedComponents = validationResult.components;
6608
7568
  const queriedComponents = finalComponents.filter((c) => c.props?.query);
6609
7569
  const validatedQueries = validatedComponents.filter((c) => c.props?.query);
6610
7570
  logger.info(`[${this.getProviderName()}] Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6611
- logCollector?.info(`Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6612
7571
  } catch (validationError) {
6613
7572
  const validationErrorMsg = validationError instanceof Error ? validationError.message : String(validationError);
6614
7573
  logger.error(`[${this.getProviderName()}] Query validation error: ${validationErrorMsg}`);
6615
- logCollector?.error(`Query validation error: ${validationErrorMsg}`);
6616
7574
  }
6617
7575
  } else {
6618
- logger.debug(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
7576
+ logger.error(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
6619
7577
  }
6620
7578
  const methodDuration = Date.now() - methodStartTime;
6621
7579
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${validatedComponents.length} | actions: ${actions.length}`);
@@ -6629,7 +7587,6 @@ ${executedToolsText}`);
6629
7587
  const methodDuration = Date.now() - methodStartTime;
6630
7588
  const errorMsg = error instanceof Error ? error.message : String(error);
6631
7589
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
6632
- logCollector?.error(`Failed to match components: ${errorMsg}`);
6633
7590
  return {
6634
7591
  components: [],
6635
7592
  layoutTitle: "Dashboard",
@@ -6638,158 +7595,11 @@ ${executedToolsText}`);
6638
7595
  };
6639
7596
  }
6640
7597
  }
6641
- /**
6642
- * Validate a single component's query with retry logic
6643
- * @param component - The component to validate
6644
- * @param collections - Collections object containing database execute function
6645
- * @param apiKey - Optional API key for LLM calls
6646
- * @param logCollector - Optional log collector for logging
6647
- * @returns Object with validated component (or null if failed) and query result
6648
- */
6649
- async validateSingleComponentQuery(component, collections, apiKey, logCollector) {
6650
- const MAX_RETRIES = 3;
6651
- const query = component.props?.query;
6652
- const originalQueryKey = this.getQueryCacheKey(query);
6653
- const queryStr = typeof query === "string" ? query : query?.sql || "";
6654
- let finalQueryKey = originalQueryKey;
6655
- let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
6656
- let currentQueryStr = queryStr;
6657
- let validated = false;
6658
- let lastError = "";
6659
- let result = null;
6660
- let attempts = 0;
6661
- logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
6662
- while (attempts < MAX_RETRIES && !validated) {
6663
- attempts++;
6664
- try {
6665
- logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
6666
- const validationResult = await this.executeQueryForValidation(currentQuery, collections);
6667
- result = validationResult.result;
6668
- validated = true;
6669
- queryCache.set(validationResult.cacheKey, result);
6670
- logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
6671
- logCollector?.info(`\u2713 Query validated for ${component.name}`);
6672
- if (currentQueryStr !== queryStr) {
6673
- const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
6674
- component.props = {
6675
- ...component.props,
6676
- query: fixedQuery
6677
- };
6678
- finalQueryKey = this.getQueryCacheKey(fixedQuery);
6679
- logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
6680
- }
6681
- } catch (error) {
6682
- lastError = error instanceof Error ? error.message : String(error);
6683
- logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
6684
- logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
6685
- if (attempts >= MAX_RETRIES) {
6686
- logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, excluding from response`);
6687
- logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
6688
- break;
6689
- }
6690
- logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
6691
- logCollector?.info(`Requesting query fix for ${component.name}...`);
6692
- try {
6693
- const fixedQueryStr = await this.requestQueryFix(
6694
- currentQueryStr,
6695
- lastError,
6696
- {
6697
- name: component.name,
6698
- type: component.type,
6699
- title: component.props?.title
6700
- },
6701
- apiKey
6702
- );
6703
- if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
6704
- logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
6705
- const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
6706
- currentQueryStr = limitedFixedQuery;
6707
- if (typeof currentQuery === "string") {
6708
- currentQuery = limitedFixedQuery;
6709
- } else {
6710
- currentQuery = { ...currentQuery, sql: limitedFixedQuery };
6711
- }
6712
- } else {
6713
- logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
6714
- break;
6715
- }
6716
- } catch (fixError) {
6717
- const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
6718
- logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
6719
- break;
6720
- }
6721
- }
6722
- }
6723
- if (!validated) {
6724
- logger.warn(`[${this.getProviderName()}] Component ${component.name} excluded from response due to failed query validation`);
6725
- logCollector?.warn(`Component ${component.name} excluded from response`);
6726
- }
6727
- return {
6728
- component: validated ? component : null,
6729
- queryKey: finalQueryKey,
6730
- result,
6731
- validated
6732
- };
6733
- }
6734
- /**
6735
- * Validate component queries against the database and retry with LLM fixes if they fail
6736
- * Uses parallel execution for faster validation
6737
- * @param components - Array of components with potential queries
6738
- * @param collections - Collections object containing database execute function
6739
- * @param apiKey - Optional API key for LLM calls
6740
- * @param logCollector - Optional log collector for logging
6741
- * @returns Object with validated components and a map of query results
6742
- */
6743
- async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
6744
- const queryResults = /* @__PURE__ */ new Map();
6745
- const validatedComponents = [];
6746
- const componentsWithoutQuery = [];
6747
- const componentsWithQuery = [];
6748
- for (const component of components) {
6749
- if (!component.props?.query) {
6750
- componentsWithoutQuery.push(component);
6751
- } else {
6752
- componentsWithQuery.push(component);
6753
- }
6754
- }
6755
- validatedComponents.push(...componentsWithoutQuery);
6756
- if (componentsWithQuery.length === 0) {
6757
- return { components: validatedComponents, queryResults };
6758
- }
6759
- logger.info(`[${this.getProviderName()}] Validating ${componentsWithQuery.length} component queries in parallel...`);
6760
- logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
6761
- const validationPromises = componentsWithQuery.map(
6762
- (component) => this.validateSingleComponentQuery(component, collections, apiKey, logCollector)
6763
- );
6764
- const results = await Promise.allSettled(validationPromises);
6765
- for (let i = 0; i < results.length; i++) {
6766
- const result = results[i];
6767
- const component = componentsWithQuery[i];
6768
- if (result.status === "fulfilled") {
6769
- const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
6770
- if (validated && validatedComponent) {
6771
- validatedComponents.push(validatedComponent);
6772
- if (queryResult) {
6773
- queryResults.set(queryKey, queryResult);
6774
- queryResults.set(`${component.id}:${queryKey}`, queryResult);
6775
- }
6776
- }
6777
- } else {
6778
- logger.error(`[${this.getProviderName()}] Unexpected error validating ${component.name}: ${result.reason}`);
6779
- logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
6780
- }
6781
- }
6782
- logger.info(`[${this.getProviderName()}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
6783
- return {
6784
- components: validatedComponents,
6785
- queryResults
6786
- };
6787
- }
6788
7598
  /**
6789
7599
  * Classify user question into category and detect external tools needed
6790
7600
  * Determines if question is for data analysis, requires external tools, or needs text response
6791
7601
  */
6792
- async classifyQuestionCategory(userPrompt, apiKey, logCollector, conversationHistory, externalTools) {
7602
+ async classifyQuestionCategory(userPrompt, apiKey, conversationHistory, externalTools) {
6793
7603
  const methodStartTime = Date.now();
6794
7604
  const methodName = "classifyQuestionCategory";
6795
7605
  const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
@@ -6809,24 +7619,8 @@ ${executedToolsText}`);
6809
7619
  SCHEMA_DOC: schemaDoc || "No database schema available",
6810
7620
  CURRENT_DATETIME: getCurrentDateTimeForPrompt()
6811
7621
  });
6812
- const extractTextContent = (content) => {
6813
- if (typeof content === "string") return content;
6814
- if (Array.isArray(content)) {
6815
- return content.map((item) => {
6816
- if (typeof item === "string") return item;
6817
- if (item && typeof item.text === "string") return item.text;
6818
- if (item && item.content && typeof item.content === "string") return item.content;
6819
- return JSON.stringify(item, null, 2);
6820
- }).join("\n\n---\n\n");
6821
- }
6822
- if (content && typeof content === "object") {
6823
- if (typeof content.text === "string") return content.text;
6824
- return JSON.stringify(content, null, 2);
6825
- }
6826
- return String(content);
6827
- };
6828
- logger.logLLMPrompt("classifyQuestionCategory", "system", extractTextContent(prompts.system));
6829
- logger.logLLMPrompt("classifyQuestionCategory", "user", extractTextContent(prompts.user));
7622
+ logger.logLLMPrompt("classifyQuestionCategory", "system", extractPromptText(prompts.system));
7623
+ logger.logLLMPrompt("classifyQuestionCategory", "user", extractPromptText(prompts.user));
6830
7624
  const result = await LLM.stream(
6831
7625
  {
6832
7626
  sys: prompts.system,
@@ -6834,23 +7628,13 @@ ${executedToolsText}`);
6834
7628
  },
6835
7629
  {
6836
7630
  model: this.getModelForTask("simple"),
6837
- maxTokens: 1500,
7631
+ maxTokens: MAX_TOKENS_CLASSIFICATION,
6838
7632
  temperature: 0,
6839
7633
  apiKey: this.getApiKey(apiKey)
6840
7634
  },
6841
7635
  true
6842
7636
  // Parse as JSON
6843
7637
  );
6844
- logCollector?.logExplanation(
6845
- "Question category classified",
6846
- result.reasoning || "No reasoning provided",
6847
- {
6848
- category: result.category,
6849
- externalTools: result.externalTools || [],
6850
- dataAnalysisType: result.dataAnalysisType,
6851
- confidence: result.confidence
6852
- }
6853
- );
6854
7638
  const methodDuration = Date.now() - methodStartTime;
6855
7639
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | category: ${result.category} | confidence: ${result.confidence}% | tools: ${(result.externalTools || []).length}`);
6856
7640
  return {
@@ -6864,7 +7648,6 @@ ${executedToolsText}`);
6864
7648
  const methodDuration = Date.now() - methodStartTime;
6865
7649
  const errorMsg = error instanceof Error ? error.message : String(error);
6866
7650
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
6867
- logger.debug(`[${this.getProviderName()}] Category classification error details:`, error);
6868
7651
  throw error;
6869
7652
  }
6870
7653
  }
@@ -6873,7 +7656,7 @@ ${executedToolsText}`);
6873
7656
  * Takes a matched UI block from semantic search and modifies its props to answer the new question
6874
7657
  * Also adapts the cached text response to match the new question
6875
7658
  */
6876
- async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, logCollector, cachedTextResponse) {
7659
+ async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, cachedTextResponse) {
6877
7660
  const methodStartTime = Date.now();
6878
7661
  const methodName = "adaptUIBlockParameters";
6879
7662
  const promptPreview = currentUserPrompt.substring(0, 50) + (currentUserPrompt.length > 50 ? "..." : "");
@@ -6907,7 +7690,7 @@ ${executedToolsText}`);
6907
7690
  },
6908
7691
  {
6909
7692
  model: this.getModelForTask("complex"),
6910
- maxTokens: 8192,
7693
+ maxTokens: MAX_TOKENS_ADAPTATION,
6911
7694
  temperature: 0,
6912
7695
  apiKey: this.getApiKey(apiKey)
6913
7696
  },
@@ -6917,11 +7700,6 @@ ${executedToolsText}`);
6917
7700
  if (!result.success) {
6918
7701
  const methodDuration2 = Date.now() - methodStartTime;
6919
7702
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: adaptation failed - ${result.reason}`);
6920
- logCollector?.warn(
6921
- "Could not adapt matched UI block",
6922
- "explanation",
6923
- { reason: result.reason }
6924
- );
6925
7703
  return {
6926
7704
  success: false,
6927
7705
  explanation: result.explanation || "Adaptation not possible"
@@ -6933,14 +7711,6 @@ ${executedToolsText}`);
6933
7711
  this.defaultLimit
6934
7712
  );
6935
7713
  }
6936
- logCollector?.logExplanation(
6937
- "UI block parameters adapted",
6938
- result.explanation || "Parameters adapted successfully",
6939
- {
6940
- parametersChanged: result.parametersChanged || [],
6941
- componentType: result.adaptedComponent?.type
6942
- }
6943
- );
6944
7714
  const methodDuration = Date.now() - methodStartTime;
6945
7715
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | result: success | changes: ${(result.parametersChanged || []).length}`);
6946
7716
  return {
@@ -6954,7 +7724,6 @@ ${executedToolsText}`);
6954
7724
  const methodDuration = Date.now() - methodStartTime;
6955
7725
  const errorMsg = error instanceof Error ? error.message : String(error);
6956
7726
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
6957
- logger.debug(`[${this.getProviderName()}] Adaptation error details:`, error);
6958
7727
  return {
6959
7728
  success: false,
6960
7729
  explanation: `Error adapting parameters: ${errorMsg}`
@@ -6967,14 +7736,12 @@ ${executedToolsText}`);
6967
7736
  * Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
6968
7737
  * After generating text response, if components are provided, matches suggested components
6969
7738
  */
6970
- async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
7739
+ async generateTextResponse(userPrompt, apiKey, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
6971
7740
  const methodStartTime = Date.now();
6972
7741
  const methodName = "generateTextResponse";
6973
7742
  const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
6974
7743
  logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")} | category: ${category} | prompt: "${promptPreview}"`);
6975
7744
  const errors = [];
6976
- logger.debug(`[${this.getProviderName()}] Starting text response generation`);
6977
- logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
6978
7745
  try {
6979
7746
  let availableToolsDoc = "No external tools are available for this request.";
6980
7747
  if (externalTools && externalTools.length > 0) {
@@ -7024,7 +7791,7 @@ ${executedToolsText}`);
7024
7791
  prompt: userPrompt,
7025
7792
  collections,
7026
7793
  userId,
7027
- topK: 3
7794
+ topK: KNOWLEDGE_BASE_TOP_K
7028
7795
  });
7029
7796
  const knowledgeBaseContext = kbResult.combinedContext;
7030
7797
  const prompts = await promptLoader.loadPrompts("text-response", {
@@ -7036,27 +7803,8 @@ ${executedToolsText}`);
7036
7803
  AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc,
7037
7804
  CURRENT_DATETIME: getCurrentDateTimeForPrompt()
7038
7805
  });
7039
- const extractText = (content) => {
7040
- if (typeof content === "string") return content;
7041
- if (Array.isArray(content)) {
7042
- return content.map((item) => {
7043
- if (typeof item === "string") return item;
7044
- if (item && typeof item.text === "string") return item.text;
7045
- if (item && item.content && typeof item.content === "string") return item.content;
7046
- return JSON.stringify(item, null, 2);
7047
- }).join("\n\n---\n\n");
7048
- }
7049
- if (content && typeof content === "object") {
7050
- if (typeof content.text === "string") return content.text;
7051
- return JSON.stringify(content, null, 2);
7052
- }
7053
- return String(content);
7054
- };
7055
- logger.logLLMPrompt("generateTextResponse", "system", extractText(prompts.system));
7056
- logger.logLLMPrompt("generateTextResponse", "user", extractText(prompts.user));
7057
- logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
7058
- logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
7059
- logCollector?.info("Generating text response with query execution capability...");
7806
+ logger.logLLMPrompt("generateTextResponse", "system", extractPromptText(prompts.system));
7807
+ logger.logLLMPrompt("generateTextResponse", "user", extractPromptText(prompts.user));
7060
7808
  const tools = [{
7061
7809
  name: "execute_query",
7062
7810
  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.",
@@ -7085,7 +7833,6 @@ ${executedToolsText}`);
7085
7833
  const executableTools = externalTools.filter(
7086
7834
  (t) => t.executionType === "immediate" || t.executionType === "deferred" && t.userProvidedData
7087
7835
  );
7088
- logger.info(`[${this.getProviderName()}] Executable tools: ${executableTools.length} of ${externalTools.length} total`);
7089
7836
  const addedToolIds = /* @__PURE__ */ new Set();
7090
7837
  executableTools.forEach((tool) => {
7091
7838
  if (addedToolIds.has(tool.id)) {
@@ -7093,7 +7840,6 @@ ${executedToolsText}`);
7093
7840
  return;
7094
7841
  }
7095
7842
  addedToolIds.add(tool.id);
7096
- logger.info(`[${this.getProviderName()}] Processing executable tool:`, JSON.stringify(tool, null, 2));
7097
7843
  const properties = {};
7098
7844
  const required = [];
7099
7845
  Object.entries(tool.params || {}).forEach(([key, typeOrValue]) => {
@@ -7162,327 +7908,26 @@ ${executedToolsText}`);
7162
7908
  input_schema: inputSchema
7163
7909
  });
7164
7910
  });
7165
- logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
7166
- logger.info(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
7167
- }
7168
- const queryAttempts = /* @__PURE__ */ new Map();
7169
- const MAX_QUERY_ATTEMPTS = 6;
7170
- const toolAttempts = /* @__PURE__ */ new Map();
7171
- const MAX_TOOL_ATTEMPTS = 3;
7172
- const executedToolsList = [];
7173
- let maxAttemptsReached = false;
7174
- let fullStreamedText = "";
7175
- let streamBuffer = "";
7176
- let flushTimer = null;
7177
- const FLUSH_INTERVAL = 50;
7178
- const flushStreamBuffer = () => {
7179
- if (streamBuffer && streamCallback) {
7180
- streamCallback(streamBuffer);
7181
- streamBuffer = "";
7182
- }
7183
- flushTimer = null;
7184
- };
7185
- const wrappedStreamCallback = streamCallback ? (chunk) => {
7186
- fullStreamedText += chunk;
7187
- streamBuffer += chunk;
7188
- if (chunk.includes("\n") || chunk.length > 100) {
7189
- if (flushTimer) {
7190
- clearTimeout(flushTimer);
7191
- flushTimer = null;
7192
- }
7193
- flushStreamBuffer();
7194
- } else if (!flushTimer) {
7195
- flushTimer = setTimeout(flushStreamBuffer, FLUSH_INTERVAL);
7196
- }
7197
- } : void 0;
7198
- const flushStream = () => {
7199
- if (flushTimer) {
7200
- clearTimeout(flushTimer);
7201
- flushTimer = null;
7202
- }
7203
- flushStreamBuffer();
7204
- };
7205
- const streamDelay = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
7206
- const withProgressHeartbeat = async (operation, progressMessage, intervalMs = 1e3) => {
7207
- if (!wrappedStreamCallback) {
7208
- return operation();
7209
- }
7210
- const startTime = Date.now();
7211
- await streamDelay(30);
7212
- wrappedStreamCallback(`\u23F3 ${progressMessage}`);
7213
- const heartbeatInterval = setInterval(() => {
7214
- const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
7215
- if (elapsedSeconds >= 1) {
7216
- wrappedStreamCallback(` (${elapsedSeconds}s)`);
7217
- }
7218
- }, intervalMs);
7219
- try {
7220
- const result2 = await operation();
7221
- return result2;
7222
- } finally {
7223
- clearInterval(heartbeatInterval);
7224
- wrappedStreamCallback("\n\n");
7225
- }
7226
- };
7227
- const toolHandler = async (toolName, toolInput) => {
7228
- if (toolName === "execute_query") {
7229
- let sql = toolInput.sql;
7230
- const params = toolInput.params || {};
7231
- const reasoning = toolInput.reasoning;
7232
- sql = ensureQueryLimit(sql, 10, 10);
7233
- const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
7234
- const attempts = (queryAttempts.get(queryKey) || 0) + 1;
7235
- queryAttempts.set(queryKey, attempts);
7236
- logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
7237
- if (Object.keys(params).length > 0) {
7238
- logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
7239
- }
7240
- if (reasoning) {
7241
- logCollector?.info(`Query reasoning: ${reasoning}`);
7242
- }
7243
- if (attempts > MAX_QUERY_ATTEMPTS) {
7244
- const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
7245
- logger.error(`[${this.getProviderName()}] ${errorMsg}`);
7246
- logCollector?.error(errorMsg);
7247
- maxAttemptsReached = true;
7248
- if (wrappedStreamCallback) {
7249
- wrappedStreamCallback(`
7250
-
7251
- \u274C ${errorMsg}
7252
-
7253
- Please try rephrasing your question or simplifying your request.
7254
-
7255
- `);
7256
- }
7257
- throw new Error(errorMsg);
7258
- }
7259
- try {
7260
- flushStream();
7261
- if (wrappedStreamCallback) {
7262
- const paramsDisplay = Object.keys(params).length > 0 ? `
7263
- **Parameters:** ${JSON.stringify(params)}` : "";
7264
- if (attempts === 1) {
7265
- wrappedStreamCallback(`
7266
-
7267
- \u{1F50D} **Analyzing your question...**
7268
-
7269
- `);
7270
- await streamDelay(50);
7271
- if (reasoning) {
7272
- wrappedStreamCallback(`\u{1F4AD} ${reasoning}
7273
-
7274
- `);
7275
- await streamDelay(50);
7276
- }
7277
- wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
7278
- \`\`\`sql
7279
- ${sql}
7280
- \`\`\`${paramsDisplay}
7281
-
7282
- `);
7283
- await streamDelay(50);
7284
- } else {
7285
- wrappedStreamCallback(`
7286
-
7287
- \u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
7288
-
7289
- `);
7290
- await streamDelay(50);
7291
- if (reasoning) {
7292
- wrappedStreamCallback(`\u{1F4AD} ${reasoning}
7293
-
7294
- `);
7295
- await streamDelay(50);
7296
- }
7297
- wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
7298
- \`\`\`sql
7299
- ${sql}
7300
- \`\`\`${paramsDisplay}
7301
-
7302
- `);
7303
- await streamDelay(50);
7304
- }
7305
- }
7306
- logCollector?.logQuery(
7307
- `Executing SQL query (attempt ${attempts})`,
7308
- { sql, params },
7309
- { reasoning, attempt: attempts }
7310
- );
7311
- if (!collections || !collections["database"] || !collections["database"]["execute"]) {
7312
- throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
7313
- }
7314
- const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
7315
- const result2 = await withProgressHeartbeat(
7316
- () => collections["database"]["execute"](queryPayload),
7317
- "Executing database query",
7318
- 800
7319
- // Send heartbeat every 800ms for responsive feedback
7320
- );
7321
- const data = result2?.data || result2;
7322
- const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
7323
- logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
7324
- logCollector?.info(`Query successful, returned ${rowCount} rows`);
7325
- if (wrappedStreamCallback) {
7326
- wrappedStreamCallback(`
7327
- \u2705 **Query executed successfully!**
7328
-
7329
- `);
7330
- await streamDelay(50);
7331
- if (Array.isArray(data) && data.length > 0) {
7332
- const firstRow = data[0];
7333
- const columns = Object.keys(firstRow);
7334
- if (data.length === 1 && columns.length === 1) {
7335
- const value = firstRow[columns[0]];
7336
- wrappedStreamCallback(`**Result:** ${value}
7337
-
7338
- `);
7339
- await streamDelay(50);
7340
- } else if (data.length > 0) {
7341
- wrappedStreamCallback(`**Retrieved ${rowCount} rows**
7342
-
7343
- `);
7344
- await streamDelay(50);
7345
- wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
7346
-
7347
- `);
7348
- await streamDelay(50);
7349
- }
7350
- } else if (Array.isArray(data) && data.length === 0) {
7351
- wrappedStreamCallback(`**No rows returned.**
7352
-
7353
- `);
7354
- await streamDelay(50);
7355
- }
7356
- wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
7357
-
7358
- `);
7359
- }
7360
- return JSON.stringify(data, null, 2);
7361
- } catch (error) {
7362
- const errorMsg = error instanceof Error ? error.message : String(error);
7363
- logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
7364
- logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
7365
- userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
7366
- if (wrappedStreamCallback) {
7367
- wrappedStreamCallback(`\u274C **Query execution failed:**
7368
- \`\`\`
7369
- ${errorMsg}
7370
- \`\`\`
7371
-
7372
- `);
7373
- if (attempts < MAX_QUERY_ATTEMPTS) {
7374
- wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
7375
-
7376
- `);
7377
- }
7378
- }
7379
- throw new Error(`Query execution failed: ${errorMsg}`);
7380
- }
7381
- } else {
7382
- const externalTool = externalTools?.find((t) => t.id === toolName);
7383
- if (externalTool) {
7384
- const attempts = (toolAttempts.get(toolName) || 0) + 1;
7385
- toolAttempts.set(toolName, attempts);
7386
- logger.info(`[${this.getProviderName()}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
7387
- logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
7388
- if (attempts > MAX_TOOL_ATTEMPTS) {
7389
- const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
7390
- logger.error(`[${this.getProviderName()}] ${errorMsg}`);
7391
- logCollector?.error(errorMsg);
7392
- if (wrappedStreamCallback) {
7393
- wrappedStreamCallback(`
7394
-
7395
- \u274C ${errorMsg}
7396
-
7397
- Please try rephrasing your request or contact support.
7398
-
7399
- `);
7400
- }
7401
- throw new Error(errorMsg);
7402
- }
7403
- try {
7404
- flushStream();
7405
- if (wrappedStreamCallback) {
7406
- if (attempts === 1) {
7407
- wrappedStreamCallback(`
7408
-
7409
- \u{1F517} **Executing ${externalTool.name}...**
7410
-
7411
- `);
7412
- } else {
7413
- wrappedStreamCallback(`
7414
-
7415
- \u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
7416
-
7417
- `);
7418
- }
7419
- await streamDelay(50);
7420
- }
7421
- const result2 = await withProgressHeartbeat(
7422
- () => externalTool.fn(toolInput),
7423
- `Running ${externalTool.name}`,
7424
- 800
7425
- // Send heartbeat every 800ms
7426
- );
7427
- logger.info(`[${this.getProviderName()}] External tool ${externalTool.name} executed successfully`);
7428
- logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
7429
- if (!executedToolsList.find((t) => t.id === externalTool.id)) {
7430
- let resultSummary = null;
7431
- if (result2) {
7432
- const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2);
7433
- if (resultStr.length > 1e3) {
7434
- resultSummary = {
7435
- _preview: resultStr.substring(0, 1e3) + "... (truncated)",
7436
- _totalLength: resultStr.length,
7437
- _recordCount: Array.isArray(result2) ? result2.length : result2?.data?.length || result2?.contacts?.length || result2?.salesorders?.length || "unknown"
7438
- };
7439
- } else {
7440
- resultSummary = result2;
7441
- }
7442
- }
7443
- executedToolsList.push({
7444
- id: externalTool.id,
7445
- name: externalTool.name,
7446
- params: toolInput,
7447
- // The actual parameters used in this execution
7448
- result: resultSummary,
7449
- // Store summary instead of full result to save memory
7450
- outputSchema: externalTool.outputSchema
7451
- // Include output schema for component config generation
7452
- });
7453
- logger.info(`[${this.getProviderName()}] Tracked executed tool: ${externalTool.name} with params: ${JSON.stringify(toolInput)}`);
7454
- }
7455
- if (wrappedStreamCallback) {
7456
- wrappedStreamCallback(`\u2705 **${externalTool.name} completed successfully**
7457
-
7458
- `);
7459
- await streamDelay(50);
7460
- }
7461
- return JSON.stringify(result2, null, 2);
7462
- } catch (error) {
7463
- const errorMsg = error instanceof Error ? error.message : String(error);
7464
- logger.error(`[${this.getProviderName()}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
7465
- logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
7466
- userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
7467
- if (wrappedStreamCallback) {
7468
- wrappedStreamCallback(`\u274C **${externalTool.name} failed:**
7469
- \`\`\`
7470
- ${errorMsg}
7471
- \`\`\`
7472
-
7473
- `);
7474
- if (attempts < MAX_TOOL_ATTEMPTS) {
7475
- wrappedStreamCallback(`\u{1F527} **Retrying with adjusted parameters...**
7476
-
7477
- `);
7478
- }
7479
- }
7480
- throw new Error(`Tool execution failed: ${errorMsg}`);
7481
- }
7482
- }
7483
- throw new Error(`Unknown tool: ${toolName}`);
7484
- }
7485
- };
7911
+ logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
7912
+ logger.debug(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
7913
+ }
7914
+ const streamBuffer = new StreamBuffer(streamCallback);
7915
+ const toolExecutor = new ToolExecutorService({
7916
+ providerName: this.getProviderName(),
7917
+ collections,
7918
+ streamBuffer
7919
+ });
7920
+ const executableExternalTools = externalTools?.map((t) => ({
7921
+ id: t.id,
7922
+ name: t.name,
7923
+ description: t.description,
7924
+ fn: t.fn,
7925
+ limit: t.limit,
7926
+ outputSchema: t.outputSchema,
7927
+ executionType: t.executionType,
7928
+ userProvidedData: t.userProvidedData
7929
+ })) || [];
7930
+ const toolHandler = toolExecutor.createToolHandler(executableExternalTools);
7486
7931
  const result = await LLM.streamWithTools(
7487
7932
  {
7488
7933
  sys: prompts.system,
@@ -7492,21 +7937,17 @@ ${errorMsg}
7492
7937
  toolHandler,
7493
7938
  {
7494
7939
  model: this.getModelForTask("complex"),
7495
- maxTokens: 4e3,
7940
+ maxTokens: MAX_TOKENS_TEXT_RESPONSE,
7496
7941
  temperature: 0,
7497
7942
  apiKey: this.getApiKey(apiKey),
7498
- partial: wrappedStreamCallback
7499
- // Pass the wrapped streaming callback to LLM
7943
+ partial: streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
7500
7944
  },
7501
- 20
7502
- // max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
7945
+ MAX_TOOL_CALLING_ITERATIONS
7503
7946
  );
7504
- logger.info(`[${this.getProviderName()}] Text response stream completed`);
7505
- const textResponse = fullStreamedText || result || "I apologize, but I was unable to generate a response.";
7506
- if (maxAttemptsReached) {
7947
+ const textResponse = streamBuffer.getFullText() || result || "I apologize, but I was unable to generate a response.";
7948
+ if (toolExecutor.isMaxAttemptsReached()) {
7507
7949
  const methodDuration2 = Date.now() - methodStartTime;
7508
7950
  logger.warn(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: max attempts reached`);
7509
- logCollector?.error("Failed to generate valid query after maximum attempts");
7510
7951
  return {
7511
7952
  success: false,
7512
7953
  errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
@@ -7518,18 +7959,10 @@ ${errorMsg}
7518
7959
  }
7519
7960
  };
7520
7961
  }
7521
- logCollector?.info(`Text response: ${textResponse.substring(0, 100)}${textResponse.length > 100 ? "..." : ""}`);
7522
- logCollector?.logExplanation(
7523
- "Text response generated",
7524
- "Generated plain text response with component suggestions",
7525
- {
7526
- textLength: textResponse.length
7527
- }
7528
- );
7529
- flushStream();
7530
- if (wrappedStreamCallback && components && components.length > 0 && category !== "general") {
7531
- wrappedStreamCallback("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
7532
- wrappedStreamCallback("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
7962
+ streamBuffer.flush();
7963
+ if (streamBuffer.hasCallback() && components && components.length > 0 && category !== "general") {
7964
+ streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
7965
+ streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
7533
7966
  }
7534
7967
  let matchedComponents = [];
7535
7968
  let layoutTitle = "Dashboard";
@@ -7537,8 +7970,6 @@ ${errorMsg}
7537
7970
  let actions = [];
7538
7971
  if (category === "general") {
7539
7972
  logger.info(`[${this.getProviderName()}] Skipping component generation for general/conversational question`);
7540
- logCollector?.info("Skipping component generation for general question");
7541
- logger.info(`[${this.getProviderName()}] Generating actions for general question...`);
7542
7973
  const nextQuestions = await this.generateNextQuestions(
7543
7974
  userPrompt,
7544
7975
  null,
@@ -7546,23 +7977,16 @@ ${errorMsg}
7546
7977
  void 0,
7547
7978
  // no component data
7548
7979
  apiKey,
7549
- logCollector,
7550
7980
  conversationHistory,
7551
7981
  textResponse
7552
7982
  // pass text response as context
7553
7983
  );
7554
7984
  actions = convertQuestionsToActions(nextQuestions);
7555
- logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions for general question`);
7556
7985
  } else if (components && components.length > 0) {
7557
- logger.info(`[${this.getProviderName()}] Matching components from text response...`);
7558
- logger.info(`[${this.getProviderName()}] componentStreamCallback setup: wrappedStreamCallback=${!!wrappedStreamCallback}, category=${category}`);
7559
- const componentStreamCallback = wrappedStreamCallback && category !== "data_modification" ? (component) => {
7560
- logger.info(`[${this.getProviderName()}] componentStreamCallback INVOKED for: ${component.name} (${component.type})`);
7986
+ const componentStreamCallback = streamBuffer.hasCallback() && category === "data_analysis" ? (component) => {
7561
7987
  const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
7562
- wrappedStreamCallback(answerMarker);
7563
- logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
7988
+ streamBuffer.write(answerMarker);
7564
7989
  } : void 0;
7565
- logger.info(`[${this.getProviderName()}] componentStreamCallback created: ${!!componentStreamCallback}`);
7566
7990
  const deferredTools = externalTools?.filter((t) => {
7567
7991
  if (t.executionType === "deferred" && !t.userProvidedData) return true;
7568
7992
  if (category === "data_modification" && !t.userProvidedData) {
@@ -7583,10 +8007,9 @@ ${errorMsg}
7583
8007
  components,
7584
8008
  userPrompt,
7585
8009
  apiKey,
7586
- logCollector,
7587
8010
  componentStreamCallback,
7588
8011
  deferredTools,
7589
- executedToolsList,
8012
+ toolExecutor.getExecutedTools(),
7590
8013
  collections,
7591
8014
  userId
7592
8015
  );
@@ -7597,8 +8020,6 @@ ${errorMsg}
7597
8020
  }
7598
8021
  let container_componet = null;
7599
8022
  if (matchedComponents.length > 0) {
7600
- logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
7601
- logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
7602
8023
  container_componet = {
7603
8024
  id: `container_${Date.now()}`,
7604
8025
  name: "MultiComponentContainer",
@@ -7630,7 +8051,6 @@ ${errorMsg}
7630
8051
  const methodDuration = Date.now() - methodStartTime;
7631
8052
  const errorMsg = error instanceof Error ? error.message : String(error);
7632
8053
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
7633
- logCollector?.error(`Error generating text response: ${errorMsg}`);
7634
8054
  userPromptErrorLogger.logLlmError(
7635
8055
  this.getProviderName(),
7636
8056
  this.model,
@@ -7658,38 +8078,29 @@ ${errorMsg}
7658
8078
  * 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
7659
8079
  * 3. Route appropriately based on category and response mode
7660
8080
  */
7661
- async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
8081
+ async handleUserRequest(userPrompt, components, apiKey, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
7662
8082
  const startTime = Date.now();
7663
- logger.info(`[${this.getProviderName()}] handleUserRequest called for user prompt: ${userPrompt}`);
7664
- logCollector?.info(`Starting request processing with mode: ${responseMode}`);
7665
8083
  logger.clearFile();
7666
8084
  logger.logLLMPrompt("handleUserRequest", "user", `User Prompt: ${userPrompt}`);
7667
8085
  try {
7668
- logger.info(`[${this.getProviderName()}] Step 1: Searching previous conversations...`);
7669
8086
  const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
7670
8087
  userPrompt,
7671
8088
  collections,
7672
8089
  userId,
7673
- similarityThreshold: 0.99
8090
+ similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
7674
8091
  });
7675
8092
  if (conversationMatch) {
7676
8093
  logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
7677
- logCollector?.info(
7678
- `\u2713 Found similar conversation (${(conversationMatch.similarity * 100).toFixed(2)}% match)`
7679
- );
7680
8094
  const rawComponent = conversationMatch.uiBlock?.component || conversationMatch.uiBlock?.generatedComponentMetadata;
7681
8095
  const isValidComponent = rawComponent && typeof rawComponent === "object" && Object.keys(rawComponent).length > 0;
7682
8096
  const component = isValidComponent ? rawComponent : null;
7683
8097
  const cachedTextResponse = conversationMatch.uiBlock?.analysis || conversationMatch.uiBlock?.textResponse || conversationMatch.uiBlock?.text || "";
7684
8098
  if (this.containsFormComponent(component)) {
7685
8099
  logger.info(`[${this.getProviderName()}] Skipping cached result - Form components contain stale defaultValues, fetching fresh data`);
7686
- logCollector?.info("Skipping cache for form - fetching current values from database...");
7687
8100
  } else if (!component) {
7688
- if (conversationMatch.similarity >= 0.99) {
8101
+ if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
7689
8102
  const elapsedTime2 = Date.now() - startTime;
7690
- logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response`);
7691
- logCollector?.info(`\u2713 Exact match for general question - returning cached response`);
7692
- logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
8103
+ logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response (${elapsedTime2}ms)`);
7693
8104
  return {
7694
8105
  success: true,
7695
8106
  data: {
@@ -7704,14 +8115,11 @@ ${errorMsg}
7704
8115
  };
7705
8116
  } else {
7706
8117
  logger.info(`[${this.getProviderName()}] Similar match but no component (general question) - processing fresh`);
7707
- logCollector?.info("Similar match found but was a general conversation - processing as new question");
7708
8118
  }
7709
8119
  } else {
7710
- if (conversationMatch.similarity >= 0.99) {
8120
+ if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
7711
8121
  const elapsedTime2 = Date.now() - startTime;
7712
- logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
7713
- logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
7714
- logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
8122
+ logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation (${elapsedTime2}ms)`);
7715
8123
  if (streamCallback && cachedTextResponse) {
7716
8124
  logger.info(`[${this.getProviderName()}] Streaming cached text response to frontend`);
7717
8125
  streamCallback(cachedTextResponse);
@@ -7730,22 +8138,18 @@ ${errorMsg}
7730
8138
  errors: []
7731
8139
  };
7732
8140
  }
7733
- logCollector?.info(`Adapting parameters for similar question...`);
8141
+ logger.info(`[${this.getProviderName()}] Adapting parameters for similar question...`);
7734
8142
  const originalPrompt = conversationMatch.metadata?.userPrompt || "Previous question";
7735
8143
  const adaptResult = await this.adaptUIBlockParameters(
7736
8144
  userPrompt,
7737
8145
  originalPrompt,
7738
8146
  conversationMatch.uiBlock,
7739
8147
  apiKey,
7740
- logCollector,
7741
8148
  cachedTextResponse
7742
8149
  );
7743
8150
  if (adaptResult.success && adaptResult.adaptedComponent) {
7744
8151
  const elapsedTime2 = Date.now() - startTime;
7745
- logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters`);
7746
- logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
7747
- logCollector?.info(`\u2713 UI block adapted successfully`);
7748
- logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
8152
+ logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters (${elapsedTime2}ms)`);
7749
8153
  const textResponseToUse = adaptResult.adaptedTextResponse || cachedTextResponse;
7750
8154
  if (streamCallback && textResponseToUse) {
7751
8155
  logger.info(`[${this.getProviderName()}] Streaming ${adaptResult.adaptedTextResponse ? "adapted" : "cached"} text response to frontend`);
@@ -7766,65 +8170,57 @@ ${errorMsg}
7766
8170
  errors: []
7767
8171
  };
7768
8172
  } else {
7769
- logger.info(`[${this.getProviderName()}] Could not adapt matched conversation, continuing to category classification`);
7770
- logCollector?.warn(`Could not adapt matched conversation: ${adaptResult.explanation}`);
8173
+ logger.info(`[${this.getProviderName()}] Could not adapt matched conversation: ${adaptResult.explanation}, continuing to category classification`);
7771
8174
  }
7772
8175
  }
7773
8176
  } else {
7774
8177
  logger.info(`[${this.getProviderName()}] No matching previous conversations found, proceeding to category classification`);
7775
- logCollector?.info("No similar previous conversations found. Proceeding to category classification...");
7776
8178
  }
7777
8179
  logger.info(`[${this.getProviderName()}] Step 2: Classifying question category...`);
7778
- logCollector?.info("Step 2: Classifying question category...");
7779
8180
  const categoryClassification = await this.classifyQuestionCategory(
7780
8181
  userPrompt,
7781
8182
  apiKey,
7782
- logCollector,
7783
8183
  conversationHistory,
7784
8184
  externalTools
7785
8185
  );
7786
8186
  logger.info(
7787
8187
  `[${this.getProviderName()}] Question classified as: ${categoryClassification.category} (confidence: ${categoryClassification.confidence}%)`
7788
8188
  );
7789
- logCollector?.info(
7790
- `Category: ${categoryClassification.category} | Confidence: ${categoryClassification.confidence}%`
7791
- );
7792
8189
  let toolsToUse = [];
7793
8190
  if (categoryClassification.externalTools && categoryClassification.externalTools.length > 0) {
7794
- logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed`);
7795
- logCollector?.info(`Identified external tools: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
7796
- logger.info(`[${this.getProviderName()}] Raw external tools from classification: ${JSON.stringify(categoryClassification.externalTools, null, 2)}`);
7797
- toolsToUse = categoryClassification.externalTools?.map((t) => {
8191
+ logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
8192
+ logger.debug(`[${this.getProviderName()}] Raw external tools from classification: ${JSON.stringify(categoryClassification.externalTools, null, 2)}`);
8193
+ toolsToUse = categoryClassification.externalTools.reduce((acc, t) => {
7798
8194
  const realTool = externalTools?.find((tool) => tool.id === t.type);
7799
- logger.info(`[${this.getProviderName()}] Tool ${t.name}: executionType=${t.executionType}, userProvidedData=${t.userProvidedData ? "present" : "null"}`);
7800
- return {
8195
+ if (!realTool) {
8196
+ logger.warn(`[${this.getProviderName()}] Tool ${t.type} (${t.name}) not found in registered tools - skipping (likely hallucinated)`);
8197
+ return acc;
8198
+ }
8199
+ acc.push({
7801
8200
  id: t.type,
7802
8201
  name: t.name,
7803
8202
  description: t.description,
7804
8203
  params: t.parameters || {},
7805
- // NEW: Include execution type info from category classification
8204
+ // Include execution type info from category classification
7806
8205
  executionType: t.executionType || "immediate",
7807
8206
  executionReason: t.executionReason || "",
7808
8207
  requiredFields: t.requiredFields || [],
7809
8208
  userProvidedData: t.userProvidedData || null,
7810
- // CRITICAL: Include outputSchema from real tool for component config generation
7811
- outputSchema: realTool?.outputSchema,
7812
- fn: (() => {
7813
- if (realTool) {
7814
- logger.info(`[${this.getProviderName()}] Using real tool implementation for ${t.type}`);
7815
- return realTool.fn;
7816
- } else {
7817
- logger.warn(`[${this.getProviderName()}] Tool ${t.type} not found in registered tools`);
7818
- return async () => ({ success: false, message: `Tool ${t.name || t.type} not registered` });
7819
- }
7820
- })()
7821
- };
7822
- }) || [];
8209
+ // Include outputSchema from real tool for component config generation
8210
+ outputSchema: realTool.outputSchema,
8211
+ fn: realTool.fn
8212
+ });
8213
+ return acc;
8214
+ }, []);
8215
+ const validCount = toolsToUse.length;
8216
+ const hallucinatedCount = categoryClassification.externalTools.length - validCount;
8217
+ if (hallucinatedCount > 0) {
8218
+ logger.warn(`[${this.getProviderName()}] Filtered out ${hallucinatedCount} hallucinated/non-existent tools, ${validCount} valid tools remaining`);
8219
+ }
7823
8220
  }
7824
8221
  const textResponse = await this.generateTextResponse(
7825
8222
  userPrompt,
7826
8223
  apiKey,
7827
- logCollector,
7828
8224
  conversationHistory,
7829
8225
  streamCallback,
7830
8226
  collections,
@@ -7835,13 +8231,10 @@ ${errorMsg}
7835
8231
  );
7836
8232
  const elapsedTime = Date.now() - startTime;
7837
8233
  logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7838
- logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7839
8234
  return textResponse;
7840
8235
  } catch (error) {
7841
8236
  const errorMsg = error instanceof Error ? error.message : String(error);
7842
8237
  logger.error(`[${this.getProviderName()}] Error in handleUserRequest: ${errorMsg}`);
7843
- logger.debug(`[${this.getProviderName()}] Error details:`, error);
7844
- logCollector?.error(`Error processing request: ${errorMsg}`);
7845
8238
  userPromptErrorLogger.logError(
7846
8239
  "handleUserRequest",
7847
8240
  error instanceof Error ? error : new Error(errorMsg),
@@ -7849,7 +8242,6 @@ ${errorMsg}
7849
8242
  );
7850
8243
  const elapsedTime = Date.now() - startTime;
7851
8244
  logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7852
- logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
7853
8245
  return {
7854
8246
  success: false,
7855
8247
  errors: [errorMsg],
@@ -7865,7 +8257,7 @@ ${errorMsg}
7865
8257
  * This helps provide intelligent suggestions for follow-up queries
7866
8258
  * For general/conversational questions without components, pass textResponse instead
7867
8259
  */
7868
- async generateNextQuestions(originalUserPrompt, component, componentData, apiKey, logCollector, conversationHistory, textResponse) {
8260
+ async generateNextQuestions(originalUserPrompt, component, componentData, apiKey, conversationHistory, textResponse) {
7869
8261
  const methodStartTime = Date.now();
7870
8262
  const methodName = "generateNextQuestions";
7871
8263
  const promptPreview = originalUserPrompt.substring(0, 50) + (originalUserPrompt.length > 50 ? "..." : "");
@@ -7902,7 +8294,7 @@ ${errorMsg}
7902
8294
  },
7903
8295
  {
7904
8296
  model: this.getModelForTask("simple"),
7905
- maxTokens: 1200,
8297
+ maxTokens: MAX_TOKENS_NEXT_QUESTIONS,
7906
8298
  temperature: 0,
7907
8299
  apiKey: this.getApiKey(apiKey)
7908
8300
  },
@@ -7910,14 +8302,6 @@ ${errorMsg}
7910
8302
  // Parse as JSON
7911
8303
  );
7912
8304
  const nextQuestions = result.nextQuestions || [];
7913
- logCollector?.logExplanation(
7914
- "Next questions generated",
7915
- "Generated intelligent follow-up questions based on component",
7916
- {
7917
- count: nextQuestions.length,
7918
- questions: nextQuestions
7919
- }
7920
- );
7921
8305
  const methodDuration = Date.now() - methodStartTime;
7922
8306
  logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | questions: ${nextQuestions.length}`);
7923
8307
  return nextQuestions;
@@ -7925,8 +8309,6 @@ ${errorMsg}
7925
8309
  const methodDuration = Date.now() - methodStartTime;
7926
8310
  const errorMsg = error instanceof Error ? error.message : String(error);
7927
8311
  logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
7928
- logger.debug(`[${this.getProviderName()}] Next questions generation error details:`, error);
7929
- logCollector?.error(`Error generating next questions: ${errorMsg}`);
7930
8312
  return [];
7931
8313
  }
7932
8314
  }
@@ -8040,330 +8422,94 @@ function getLLMProviders() {
8040
8422
  return DEFAULT_PROVIDERS;
8041
8423
  }
8042
8424
  }
8043
- var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8044
- logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
8045
- logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
8046
- const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
8047
- logCollector?.info(msg);
8425
+ var useAnthropicMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8048
8426
  if (responseMode === "component" && components.length === 0) {
8049
8427
  const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8050
8428
  logger.error("[useAnthropicMethod] No components available");
8051
- logCollector?.error(emptyMsg);
8052
8429
  return { success: false, errors: [emptyMsg] };
8053
8430
  }
8054
- logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
8055
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8431
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8056
8432
  logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
8057
8433
  return matchResult;
8058
8434
  };
8059
- var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8435
+ var useGroqMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8060
8436
  logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
8061
8437
  logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
8062
- const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
8063
- logger.info(msg);
8064
- logCollector?.info(msg);
8065
- if (responseMode === "component" && components.length === 0) {
8066
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8067
- logger.error("[useGroqMethod] No components available");
8068
- logCollector?.error(emptyMsg);
8069
- return { success: false, errors: [emptyMsg] };
8070
- }
8071
- logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
8072
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8073
- logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
8074
- return matchResult;
8075
- };
8076
- var useGeminiMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8077
- logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
8078
- logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
8079
- const msg = `Using Gemini LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
8080
- logger.info(msg);
8081
- logCollector?.info(msg);
8082
- if (responseMode === "component" && components.length === 0) {
8083
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8084
- logger.error("[useGeminiMethod] No components available");
8085
- logCollector?.error(emptyMsg);
8086
- return { success: false, errors: [emptyMsg] };
8087
- }
8088
- logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
8089
- const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8090
- logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
8091
- return matchResult;
8092
- };
8093
- var useOpenAIMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8094
- logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
8095
- logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
8096
- const msg = `Using OpenAI GPT ${responseMode === "text" ? "text response" : "matching"} method...`;
8097
- logger.info(msg);
8098
- logCollector?.info(msg);
8099
8438
  if (responseMode === "component" && components.length === 0) {
8100
8439
  const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8101
- logger.error("[useOpenAIMethod] No components available");
8102
- logCollector?.error(emptyMsg);
8103
- return { success: false, errors: [emptyMsg] };
8104
- }
8105
- logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
8106
- const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8107
- logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
8108
- return matchResult;
8109
- };
8110
- var getUserResponseFromCache = async (prompt) => {
8111
- return false;
8112
- };
8113
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8114
- logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
8115
- logger.debug(`[get_user_response] Response mode: ${responseMode}`);
8116
- logger.debug("[get_user_response] Checking cache for existing response");
8117
- const userResponse = await getUserResponseFromCache(prompt);
8118
- if (userResponse) {
8119
- logger.info("[get_user_response] User response found in cache - returning cached result");
8120
- logCollector?.info("User response found in cache");
8121
- return {
8122
- success: true,
8123
- data: userResponse,
8124
- errors: []
8125
- };
8126
- }
8127
- logger.debug("[get_user_response] No cached response found, proceeding with LLM providers");
8128
- const providers = llmProviders || getLLMProviders();
8129
- const errors = [];
8130
- const providerOrder = providers.join(", ");
8131
- logCollector?.info(`LLM Provider order: [${providerOrder}]`);
8132
- if (conversationHistory && conversationHistory.length > 0) {
8133
- const exchangeCount = conversationHistory.split("\n").filter((l) => l.startsWith("Q")).length;
8134
- logger.debug(`[get_user_response] Using conversation history with ${exchangeCount} previous exchanges`);
8135
- logCollector?.info(`Using conversation history with ${exchangeCount} previous exchanges`);
8136
- } else {
8137
- logger.debug("[get_user_response] No conversation history available");
8138
- }
8139
- for (let i = 0; i < providers.length; i++) {
8140
- const provider = providers[i];
8141
- const isLastProvider = i === providers.length - 1;
8142
- const attemptMsg = `Attempting provider: ${provider} (${i + 1}/${providers.length})`;
8143
- logCollector?.info(attemptMsg);
8144
- let result;
8145
- if (provider === "anthropic") {
8146
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8147
- } else if (provider === "groq") {
8148
- result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8149
- } else if (provider === "gemini") {
8150
- result = await useGeminiMethod(prompt, components, geminiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8151
- } else if (provider === "openai") {
8152
- result = await useOpenAIMethod(prompt, components, openaiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8153
- } else {
8154
- logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
8155
- errors.push(`Unknown provider: ${provider}`);
8156
- continue;
8157
- }
8158
- if (result.success) {
8159
- const successMsg = `Success with provider: ${provider}`;
8160
- logger.info(`${successMsg}`);
8161
- logCollector?.info(successMsg);
8162
- return result;
8163
- } else {
8164
- const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
8165
- errors.push(...providerErrors);
8166
- const warnMsg = `Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`;
8167
- logger.warn(`[get_user_response] ${warnMsg}`);
8168
- logCollector?.warn(warnMsg);
8169
- if (!isLastProvider) {
8170
- const fallbackMsg = "Falling back to next provider...";
8171
- logger.info(`[get_user_response] ${fallbackMsg}`);
8172
- logCollector?.info(fallbackMsg);
8173
- }
8174
- }
8175
- }
8176
- const failureMsg = `All LLM providers failed`;
8177
- logger.error(`[get_user_response] ${failureMsg}. Errors: ${errors.join("; ")}`);
8178
- logCollector?.error(`${failureMsg}. Errors: ${errors.join("; ")}`);
8179
- return {
8180
- success: false,
8181
- errors
8182
- };
8183
- };
8184
-
8185
- // src/utils/log-collector.ts
8186
- var LOG_LEVEL_PRIORITY2 = {
8187
- errors: 0,
8188
- warnings: 1,
8189
- info: 2,
8190
- verbose: 3
8191
- };
8192
- var MESSAGE_LEVEL_PRIORITY2 = {
8193
- error: 0,
8194
- warn: 1,
8195
- info: 2,
8196
- debug: 3
8197
- };
8198
- var UILogCollector = class {
8199
- constructor(clientId, sendMessage, uiBlockId) {
8200
- this.logs = [];
8201
- this.uiBlockId = uiBlockId || null;
8202
- this.clientId = clientId;
8203
- this.sendMessage = sendMessage;
8204
- this.currentLogLevel = logger.getLogLevel();
8205
- }
8206
- /**
8207
- * Check if logging is enabled (uiBlockId is provided)
8208
- */
8209
- isEnabled() {
8210
- return this.uiBlockId !== null;
8211
- }
8212
- /**
8213
- * Check if a message should be logged based on current log level
8214
- */
8215
- shouldLog(messageLevel) {
8216
- const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
8217
- const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
8218
- return messagePriority <= currentLevelPriority;
8219
- }
8220
- /**
8221
- * Add a log entry with timestamp and immediately send to runtime
8222
- * Only logs that pass the log level filter are captured and sent
8223
- */
8224
- addLog(level, message, type, data) {
8225
- if (!this.shouldLog(level)) {
8226
- return;
8227
- }
8228
- const log = {
8229
- timestamp: Date.now(),
8230
- level,
8231
- message,
8232
- ...type && { type },
8233
- ...data && { data }
8234
- };
8235
- this.logs.push(log);
8236
- this.sendLogImmediately(log);
8237
- switch (level) {
8238
- case "error":
8239
- logger.error("UILogCollector:", log);
8240
- break;
8241
- case "warn":
8242
- logger.warn("UILogCollector:", log);
8243
- break;
8244
- case "info":
8245
- logger.info("UILogCollector:", log);
8246
- break;
8247
- case "debug":
8248
- logger.debug("UILogCollector:", log);
8249
- break;
8250
- }
8251
- }
8252
- /**
8253
- * Send a single log to runtime immediately
8254
- */
8255
- sendLogImmediately(log) {
8256
- if (!this.isEnabled()) {
8257
- return;
8258
- }
8259
- const response = {
8260
- id: this.uiBlockId,
8261
- type: "UI_LOGS",
8262
- from: { type: "data-agent" },
8263
- to: {
8264
- type: "runtime",
8265
- id: this.clientId
8266
- },
8267
- payload: {
8268
- logs: [log]
8269
- // Send single log in array
8270
- }
8271
- };
8272
- this.sendMessage(response);
8273
- }
8274
- /**
8275
- * Log info message
8276
- */
8277
- info(message, type, data) {
8278
- if (this.isEnabled()) {
8279
- this.addLog("info", message, type, data);
8280
- }
8281
- }
8282
- /**
8283
- * Log error message
8284
- */
8285
- error(message, type, data) {
8286
- if (this.isEnabled()) {
8287
- this.addLog("error", message, type, data);
8288
- }
8289
- }
8290
- /**
8291
- * Log warning message
8292
- */
8293
- warn(message, type, data) {
8294
- if (this.isEnabled()) {
8295
- this.addLog("warn", message, type, data);
8296
- }
8297
- }
8298
- /**
8299
- * Log debug message
8300
- */
8301
- debug(message, type, data) {
8302
- if (this.isEnabled()) {
8303
- this.addLog("debug", message, type, data);
8304
- }
8305
- }
8306
- /**
8307
- * Log LLM explanation with typed metadata
8308
- */
8309
- logExplanation(message, explanation, data) {
8310
- if (this.isEnabled()) {
8311
- this.addLog("info", message, "explanation", {
8312
- explanation,
8313
- ...data
8314
- });
8315
- }
8316
- }
8317
- /**
8318
- * Log generated query with typed metadata
8319
- */
8320
- logQuery(message, query, data) {
8321
- if (this.isEnabled()) {
8322
- this.addLog("info", message, "query", {
8323
- query,
8324
- ...data
8325
- });
8326
- }
8327
- }
8328
- /**
8329
- * Send all collected logs at once (optional, for final summary)
8330
- */
8331
- sendAllLogs() {
8332
- if (!this.isEnabled() || this.logs.length === 0) {
8333
- return;
8334
- }
8335
- const response = {
8336
- id: this.uiBlockId,
8337
- type: "UI_LOGS",
8338
- from: { type: "data-agent" },
8339
- to: {
8340
- type: "runtime",
8341
- id: this.clientId
8342
- },
8343
- payload: {
8344
- logs: this.logs
8345
- }
8346
- };
8347
- this.sendMessage(response);
8348
- }
8349
- /**
8350
- * Get all collected logs
8351
- */
8352
- getLogs() {
8353
- return [...this.logs];
8440
+ logger.error("[useGroqMethod] No components available");
8441
+ return { success: false, errors: [emptyMsg] };
8354
8442
  }
8355
- /**
8356
- * Clear all logs
8357
- */
8358
- clearLogs() {
8359
- this.logs = [];
8443
+ logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
8444
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8445
+ logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
8446
+ return matchResult;
8447
+ };
8448
+ var useGeminiMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8449
+ logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
8450
+ logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
8451
+ if (responseMode === "component" && components.length === 0) {
8452
+ const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8453
+ logger.error("[useGeminiMethod] No components available");
8454
+ return { success: false, errors: [emptyMsg] };
8360
8455
  }
8361
- /**
8362
- * Set uiBlockId (in case it's provided later)
8363
- */
8364
- setUIBlockId(uiBlockId) {
8365
- this.uiBlockId = uiBlockId;
8456
+ logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
8457
+ const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8458
+ logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
8459
+ return matchResult;
8460
+ };
8461
+ var useOpenAIMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8462
+ logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
8463
+ logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
8464
+ if (responseMode === "component" && components.length === 0) {
8465
+ const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8466
+ logger.error("[useOpenAIMethod] No components available");
8467
+ return { success: false, errors: [emptyMsg] };
8366
8468
  }
8469
+ logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
8470
+ const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8471
+ logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
8472
+ return matchResult;
8473
+ };
8474
+ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8475
+ const providers = llmProviders || getLLMProviders();
8476
+ const errors = [];
8477
+ logger.info(`[get_user_response] LLM Provider order: [${providers.join(", ")}]`);
8478
+ for (let i = 0; i < providers.length; i++) {
8479
+ const provider = providers[i];
8480
+ const isLastProvider = i === providers.length - 1;
8481
+ logger.info(`[get_user_response] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
8482
+ let result;
8483
+ if (provider === "anthropic") {
8484
+ result = await useAnthropicMethod(prompt, components, anthropicApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8485
+ } else if (provider === "groq") {
8486
+ result = await useGroqMethod(prompt, components, groqApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8487
+ } else if (provider === "gemini") {
8488
+ result = await useGeminiMethod(prompt, components, geminiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8489
+ } else if (provider === "openai") {
8490
+ result = await useOpenAIMethod(prompt, components, openaiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8491
+ } else {
8492
+ logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
8493
+ errors.push(`Unknown provider: ${provider}`);
8494
+ continue;
8495
+ }
8496
+ if (result.success) {
8497
+ logger.info(`[get_user_response] Success with provider: ${provider}`);
8498
+ return result;
8499
+ } else {
8500
+ const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
8501
+ errors.push(...providerErrors);
8502
+ logger.warn(`[get_user_response] Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`);
8503
+ if (!isLastProvider) {
8504
+ logger.info("[get_user_response] Falling back to next provider...");
8505
+ }
8506
+ }
8507
+ }
8508
+ logger.error(`[get_user_response] All LLM providers failed. Errors: ${errors.join("; ")}`);
8509
+ return {
8510
+ success: false,
8511
+ errors
8512
+ };
8367
8513
  };
8368
8514
 
8369
8515
  // src/utils/conversation-saver.ts
@@ -8496,7 +8642,6 @@ var CONTEXT_CONFIG = {
8496
8642
  // src/handlers/user-prompt-request.ts
8497
8643
  var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools) => {
8498
8644
  const errors = [];
8499
- logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
8500
8645
  const parseResult = UserPromptRequestMessageSchema.safeParse(data);
8501
8646
  if (!parseResult.success) {
8502
8647
  const zodError = parseResult.error;
@@ -8528,27 +8673,23 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8528
8673
  if (!prompt) {
8529
8674
  errors.push("Prompt not found");
8530
8675
  }
8531
- logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
8532
8676
  if (errors.length > 0) {
8533
8677
  return { success: false, errors, id, wsId };
8534
8678
  }
8535
- const logCollector = new UILogCollector(wsId, sendMessage, existingUiBlockId);
8536
8679
  const threadManager = ThreadManager.getInstance();
8537
8680
  let thread = threadManager.getThread(threadId);
8538
8681
  if (!thread) {
8539
8682
  thread = threadManager.createThread(threadId);
8540
8683
  logger.info(`Created new thread: ${threadId}`);
8541
8684
  }
8542
- logCollector.info(`Starting user prompt request with ${components.length} components`);
8685
+ logger.info(`Starting user prompt request with ${components.length} components`);
8543
8686
  const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
8544
8687
  const responseMode = payload.responseMode || "component";
8545
- logger.info("responseMode", responseMode);
8546
8688
  let streamCallback;
8547
8689
  let accumulatedStreamResponse = "";
8548
8690
  if (responseMode === "text") {
8549
8691
  streamCallback = (chunk) => {
8550
8692
  accumulatedStreamResponse += chunk;
8551
- logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
8552
8693
  const streamMessage = {
8553
8694
  id: `stream_${existingUiBlockId}`,
8554
8695
  // Different ID pattern for streaming
@@ -8564,7 +8705,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8564
8705
  }
8565
8706
  };
8566
8707
  sendMessage(streamMessage);
8567
- logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
8568
8708
  };
8569
8709
  }
8570
8710
  const userResponse = await get_user_response(
@@ -8575,7 +8715,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8575
8715
  geminiApiKey,
8576
8716
  openaiApiKey,
8577
8717
  llmProviders,
8578
- logCollector,
8579
8718
  conversationHistory,
8580
8719
  responseMode,
8581
8720
  streamCallback,
@@ -8583,7 +8722,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8583
8722
  externalTools,
8584
8723
  userId
8585
8724
  );
8586
- logCollector.info("User prompt request completed");
8725
+ logger.info("User prompt request completed");
8587
8726
  const uiBlockId = existingUiBlockId;
8588
8727
  if (!userResponse.success) {
8589
8728
  logger.error(`User prompt request failed with errors: ${userResponse.errors.join(", ")}`);
@@ -8650,9 +8789,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8650
8789
  logger.info(
8651
8790
  `Skipping conversation save - response from exact semantic match (${(semanticSimilarity * 100).toFixed(2)}% similarity)`
8652
8791
  );
8653
- logCollector.info(
8654
- `Using exact cached result (${(semanticSimilarity * 100).toFixed(2)}% match) - not saving duplicate conversation`
8655
- );
8656
8792
  } else {
8657
8793
  const uiBlockData = uiBlock.toJSON();
8658
8794
  const saveResult = await saveConversation({
@@ -8860,7 +8996,7 @@ function sendResponse(id, res, sendMessage, clientId) {
8860
8996
  }
8861
8997
 
8862
8998
  // src/userResponse/next-questions.ts
8863
- async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, logCollector, conversationHistory) {
8999
+ async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory) {
8864
9000
  try {
8865
9001
  logger.debug("[generateNextQuestions] Starting next questions generation");
8866
9002
  logger.debug(`[generateNextQuestions] User prompt: "${originalUserPrompt?.substring(0, 50)}..."`);
@@ -8879,7 +9015,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8879
9015
  const isLastProvider = i === providers.length - 1;
8880
9016
  try {
8881
9017
  logger.info(`[generateNextQuestions] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
8882
- logCollector?.info(`Generating questions with ${provider}...`);
8883
9018
  let result = [];
8884
9019
  if (provider === "groq") {
8885
9020
  logger.debug("[generateNextQuestions] Using Groq LLM for next questions");
@@ -8888,7 +9023,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8888
9023
  component,
8889
9024
  componentData,
8890
9025
  groqApiKey,
8891
- logCollector,
8892
9026
  conversationHistory
8893
9027
  );
8894
9028
  } else if (provider === "gemini") {
@@ -8898,7 +9032,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8898
9032
  component,
8899
9033
  componentData,
8900
9034
  geminiApiKey,
8901
- logCollector,
8902
9035
  conversationHistory
8903
9036
  );
8904
9037
  } else if (provider === "openai") {
@@ -8908,7 +9041,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8908
9041
  component,
8909
9042
  componentData,
8910
9043
  openaiApiKey,
8911
- logCollector,
8912
9044
  conversationHistory
8913
9045
  );
8914
9046
  } else {
@@ -8918,44 +9050,32 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
8918
9050
  component,
8919
9051
  componentData,
8920
9052
  anthropicApiKey,
8921
- logCollector,
8922
9053
  conversationHistory
8923
9054
  );
8924
9055
  }
8925
9056
  if (result && result.length > 0) {
8926
9057
  logger.info(`[generateNextQuestions] Successfully generated ${result.length} questions with ${provider}`);
8927
9058
  logger.debug(`[generateNextQuestions] Questions: ${JSON.stringify(result)}`);
8928
- logCollector?.info(`Generated ${result.length} follow-up questions`);
8929
9059
  return result;
8930
9060
  }
8931
- const warnMsg = `No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`;
8932
- logger.warn(`[generateNextQuestions] ${warnMsg}`);
8933
- if (!isLastProvider) {
8934
- logCollector?.warn(warnMsg);
8935
- }
9061
+ logger.warn(`[generateNextQuestions] No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`);
8936
9062
  } catch (providerError) {
8937
9063
  const errorMsg = providerError instanceof Error ? providerError.message : String(providerError);
8938
9064
  logger.error(`[generateNextQuestions] Provider ${provider} failed: ${errorMsg}`);
8939
9065
  logger.debug(`[generateNextQuestions] Provider error details:`, providerError);
8940
9066
  if (!isLastProvider) {
8941
- const fallbackMsg = `Provider ${provider} failed, trying next provider...`;
8942
- logger.info(`[generateNextQuestions] ${fallbackMsg}`);
8943
- logCollector?.warn(fallbackMsg);
8944
- } else {
8945
- logCollector?.error(`Failed to generate questions with ${provider}`);
9067
+ logger.info(`[generateNextQuestions] Provider ${provider} failed, trying next provider...`);
8946
9068
  }
8947
9069
  continue;
8948
9070
  }
8949
9071
  }
8950
9072
  logger.warn("[generateNextQuestions] All providers failed or returned no questions");
8951
- logCollector?.warn("Unable to generate follow-up questions");
8952
9073
  return [];
8953
9074
  } catch (error) {
8954
9075
  const errorMsg = error instanceof Error ? error.message : String(error);
8955
9076
  const errorStack = error instanceof Error ? error.stack : void 0;
8956
9077
  logger.error(`[generateNextQuestions] Error generating next questions: ${errorMsg}`);
8957
9078
  logger.debug("[generateNextQuestions] Error stack trace:", errorStack);
8958
- logCollector?.error(`Error generating next questions: ${errorMsg}`);
8959
9079
  return [];
8960
9080
  }
8961
9081
  }
@@ -9003,9 +9123,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
9003
9123
  return;
9004
9124
  }
9005
9125
  logger.info(`[ACTIONS_REQ ${id}] UIBlock retrieved successfully`);
9006
- logger.debug(`[ACTIONS_REQ ${id}] Creating UILogCollector for uiBlockId: ${uiBlockId}`);
9007
- const logCollector = new UILogCollector(wsId, sendMessage, uiBlockId);
9008
- logger.info(`[ACTIONS_REQ ${id}] UILogCollector initialized`);
9009
9126
  logger.debug(`[ACTIONS_REQ ${id}] Extracting data from UIBlock`);
9010
9127
  const userQuestion = uiBlock.getUserQuestion();
9011
9128
  const component = uiBlock.getComponentMetadata();
@@ -9019,13 +9136,11 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
9019
9136
  logger.info(`[ACTIONS_REQ ${id}] Conversation history extracted: ${historyLineCount} lines`);
9020
9137
  logger.debug(`[ACTIONS_REQ ${id}] Conversation history preview:
9021
9138
  ${conversationHistory.substring(0, 200)}...`);
9022
- logCollector.info(`Generating actions for UIBlock: ${uiBlockId}`);
9023
- logger.info(`[ACTIONS_REQ ${id}] Generating actions for component: ${component?.name || "unknown"}`);
9139
+ logger.info(`[ACTIONS_REQ ${id}] Generating actions for UIBlock: ${uiBlockId}, component: ${component?.name || "unknown"}`);
9024
9140
  logger.debug(`[ACTIONS_REQ ${id}] Checking if actions are already cached`);
9025
9141
  const startTime = Date.now();
9026
9142
  const actions = await uiBlock.getOrFetchActions(async () => {
9027
9143
  logger.info(`[ACTIONS_REQ ${id}] Actions not cached, generating new actions...`);
9028
- logCollector.info("Generating follow-up questions...");
9029
9144
  logger.info(`[ACTIONS_REQ ${id}] Starting next questions generation with ${llmProviders?.join(", ") || "default"} providers`);
9030
9145
  const nextQuestions = await generateNextQuestions(
9031
9146
  userQuestion,
@@ -9036,7 +9151,6 @@ ${conversationHistory.substring(0, 200)}...`);
9036
9151
  geminiApiKey,
9037
9152
  openaiApiKey,
9038
9153
  llmProviders,
9039
- logCollector,
9040
9154
  conversationHistory
9041
9155
  );
9042
9156
  logger.info(`[ACTIONS_REQ ${id}] Generated ${nextQuestions.length} questions`);
@@ -9054,11 +9168,10 @@ ${conversationHistory.substring(0, 200)}...`);
9054
9168
  const processingTime = Date.now() - startTime;
9055
9169
  logger.info(`[ACTIONS_REQ ${id}] Actions retrieved in ${processingTime}ms - ${actions.length} actions total`);
9056
9170
  if (actions.length > 0) {
9057
- logCollector.info(`Generated ${actions.length} follow-up questions successfully`);
9171
+ logger.info(`[ACTIONS_REQ ${id}] Generated ${actions.length} follow-up questions successfully`);
9058
9172
  logger.debug(`[ACTIONS_REQ ${id}] Actions: ${actions.map((a) => a.name).join(", ")}`);
9059
9173
  } else {
9060
9174
  logger.warn(`[ACTIONS_REQ ${id}] No actions generated`);
9061
- logCollector.warn("No follow-up questions could be generated");
9062
9175
  }
9063
9176
  logger.debug(`[ACTIONS_REQ ${id}] Sending successful response to client`);
9064
9177
  sendResponse2(id, {
@@ -9077,15 +9190,6 @@ ${conversationHistory.substring(0, 200)}...`);
9077
9190
  const errorStack = error instanceof Error ? error.stack : void 0;
9078
9191
  logger.error(`[ACTIONS_REQ] Failed to handle actions request: ${errorMessage}`);
9079
9192
  logger.debug(`[ACTIONS_REQ] Error stack trace:`, errorStack);
9080
- try {
9081
- const parsedData = data;
9082
- if (parsedData?.id && parsedData?.from?.id) {
9083
- const logCollector = parsedData?.payload?.SA_RUNTIME?.uiBlockId ? new UILogCollector(parsedData.from.id, sendMessage, parsedData.payload.SA_RUNTIME.uiBlockId) : void 0;
9084
- logCollector?.error(`Failed to generate actions: ${errorMessage}`);
9085
- }
9086
- } catch (logError) {
9087
- logger.debug("[ACTIONS_REQ] Failed to send error logs to UI:", logError);
9088
- }
9089
9193
  sendResponse2(null, {
9090
9194
  success: false,
9091
9195
  error: errorMessage
@@ -9666,7 +9770,6 @@ function sendResponse3(id, res, sendMessage, clientId) {
9666
9770
  var dashboardManager = null;
9667
9771
  function setDashboardManager(manager) {
9668
9772
  dashboardManager = manager;
9669
- logger.info("DashboardManager instance set");
9670
9773
  }
9671
9774
  function getDashboardManager() {
9672
9775
  if (!dashboardManager) {
@@ -12993,6 +13096,190 @@ var ReportManager = class {
12993
13096
  }
12994
13097
  };
12995
13098
 
13099
+ // src/utils/log-collector.ts
13100
+ var LOG_LEVEL_PRIORITY2 = {
13101
+ errors: 0,
13102
+ warnings: 1,
13103
+ info: 2,
13104
+ verbose: 3
13105
+ };
13106
+ var MESSAGE_LEVEL_PRIORITY2 = {
13107
+ error: 0,
13108
+ warn: 1,
13109
+ info: 2,
13110
+ debug: 3
13111
+ };
13112
+ var UILogCollector = class {
13113
+ constructor(clientId, sendMessage, uiBlockId) {
13114
+ this.logs = [];
13115
+ this.uiBlockId = uiBlockId || null;
13116
+ this.clientId = clientId;
13117
+ this.sendMessage = sendMessage;
13118
+ this.currentLogLevel = logger.getLogLevel();
13119
+ }
13120
+ /**
13121
+ * Check if logging is enabled (uiBlockId is provided)
13122
+ */
13123
+ isEnabled() {
13124
+ return this.uiBlockId !== null;
13125
+ }
13126
+ /**
13127
+ * Check if a message should be logged based on current log level
13128
+ */
13129
+ shouldLog(messageLevel) {
13130
+ const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
13131
+ const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
13132
+ return messagePriority <= currentLevelPriority;
13133
+ }
13134
+ /**
13135
+ * Add a log entry with timestamp and immediately send to runtime
13136
+ * Only logs that pass the log level filter are captured and sent
13137
+ */
13138
+ addLog(level, message, type, data) {
13139
+ if (!this.shouldLog(level)) {
13140
+ return;
13141
+ }
13142
+ const log = {
13143
+ timestamp: Date.now(),
13144
+ level,
13145
+ message,
13146
+ ...type && { type },
13147
+ ...data && { data }
13148
+ };
13149
+ this.logs.push(log);
13150
+ this.sendLogImmediately(log);
13151
+ switch (level) {
13152
+ case "error":
13153
+ logger.error("UILogCollector:", log);
13154
+ break;
13155
+ case "warn":
13156
+ logger.warn("UILogCollector:", log);
13157
+ break;
13158
+ case "info":
13159
+ logger.info("UILogCollector:", log);
13160
+ break;
13161
+ case "debug":
13162
+ logger.debug("UILogCollector:", log);
13163
+ break;
13164
+ }
13165
+ }
13166
+ /**
13167
+ * Send a single log to runtime immediately
13168
+ */
13169
+ sendLogImmediately(log) {
13170
+ if (!this.isEnabled()) {
13171
+ return;
13172
+ }
13173
+ const response = {
13174
+ id: this.uiBlockId,
13175
+ type: "UI_LOGS",
13176
+ from: { type: "data-agent" },
13177
+ to: {
13178
+ type: "runtime",
13179
+ id: this.clientId
13180
+ },
13181
+ payload: {
13182
+ logs: [log]
13183
+ // Send single log in array
13184
+ }
13185
+ };
13186
+ this.sendMessage(response);
13187
+ }
13188
+ /**
13189
+ * Log info message
13190
+ */
13191
+ info(message, type, data) {
13192
+ if (this.isEnabled()) {
13193
+ this.addLog("info", message, type, data);
13194
+ }
13195
+ }
13196
+ /**
13197
+ * Log error message
13198
+ */
13199
+ error(message, type, data) {
13200
+ if (this.isEnabled()) {
13201
+ this.addLog("error", message, type, data);
13202
+ }
13203
+ }
13204
+ /**
13205
+ * Log warning message
13206
+ */
13207
+ warn(message, type, data) {
13208
+ if (this.isEnabled()) {
13209
+ this.addLog("warn", message, type, data);
13210
+ }
13211
+ }
13212
+ /**
13213
+ * Log debug message
13214
+ */
13215
+ debug(message, type, data) {
13216
+ if (this.isEnabled()) {
13217
+ this.addLog("debug", message, type, data);
13218
+ }
13219
+ }
13220
+ /**
13221
+ * Log LLM explanation with typed metadata
13222
+ */
13223
+ logExplanation(message, explanation, data) {
13224
+ if (this.isEnabled()) {
13225
+ this.addLog("info", message, "explanation", {
13226
+ explanation,
13227
+ ...data
13228
+ });
13229
+ }
13230
+ }
13231
+ /**
13232
+ * Log generated query with typed metadata
13233
+ */
13234
+ logQuery(message, query, data) {
13235
+ if (this.isEnabled()) {
13236
+ this.addLog("info", message, "query", {
13237
+ query,
13238
+ ...data
13239
+ });
13240
+ }
13241
+ }
13242
+ /**
13243
+ * Send all collected logs at once (optional, for final summary)
13244
+ */
13245
+ sendAllLogs() {
13246
+ if (!this.isEnabled() || this.logs.length === 0) {
13247
+ return;
13248
+ }
13249
+ const response = {
13250
+ id: this.uiBlockId,
13251
+ type: "UI_LOGS",
13252
+ from: { type: "data-agent" },
13253
+ to: {
13254
+ type: "runtime",
13255
+ id: this.clientId
13256
+ },
13257
+ payload: {
13258
+ logs: this.logs
13259
+ }
13260
+ };
13261
+ this.sendMessage(response);
13262
+ }
13263
+ /**
13264
+ * Get all collected logs
13265
+ */
13266
+ getLogs() {
13267
+ return [...this.logs];
13268
+ }
13269
+ /**
13270
+ * Clear all logs
13271
+ */
13272
+ clearLogs() {
13273
+ this.logs = [];
13274
+ }
13275
+ /**
13276
+ * Set uiBlockId (in case it's provided later)
13277
+ */
13278
+ setUIBlockId(uiBlockId) {
13279
+ this.uiBlockId = uiBlockId;
13280
+ }
13281
+ };
13282
+
12996
13283
  // src/services/cleanup-service.ts
12997
13284
  var CleanupService = class _CleanupService {
12998
13285
  constructor() {
@@ -13173,7 +13460,6 @@ var CleanupService = class _CleanupService {
13173
13460
  };
13174
13461
 
13175
13462
  // src/index.ts
13176
- var SDK_VERSION = "0.0.8";
13177
13463
  var DEFAULT_WS_URL = "wss://ws.superatom.ai/websocket";
13178
13464
  var SuperatomSDK = class {
13179
13465
  // 3.5 minutes (PING_INTERVAL + 30s grace)
@@ -13214,7 +13500,7 @@ var SuperatomSDK = class {
13214
13500
  if (config.queryCacheTTL !== void 0) {
13215
13501
  queryCache.setTTL(config.queryCacheTTL);
13216
13502
  }
13217
- 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`);
13503
+ 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`);
13218
13504
  this.userManager = new UserManager(this.projectId, 5e3);
13219
13505
  this.dashboardManager = new DashboardManager(this.projectId);
13220
13506
  this.reportManager = new ReportManager(this.projectId);
@@ -13268,7 +13554,6 @@ var SuperatomSDK = class {
13268
13554
  */
13269
13555
  initializeDashboardManager() {
13270
13556
  setDashboardManager(this.dashboardManager);
13271
- logger.info(`DashboardManager initialized for project: ${this.projectId}`);
13272
13557
  }
13273
13558
  /**
13274
13559
  * Get the DashboardManager instance for this SDK
@@ -13281,7 +13566,6 @@ var SuperatomSDK = class {
13281
13566
  */
13282
13567
  initializeReportManager() {
13283
13568
  setReportManager(this.reportManager);
13284
- logger.info(`ReportManager initialized for project: ${this.projectId}`);
13285
13569
  }
13286
13570
  /**
13287
13571
  * Get the ReportManager instance for this SDK
@@ -13658,7 +13942,6 @@ export {
13658
13942
  CONTEXT_CONFIG,
13659
13943
  CleanupService,
13660
13944
  LLM,
13661
- SDK_VERSION,
13662
13945
  STORAGE_CONFIG,
13663
13946
  SuperatomSDK,
13664
13947
  Thread,