@superatomai/sdk-node 0.0.5 → 0.0.7

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
@@ -10,9 +10,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  if (typeof require !== "undefined") return require.apply(this, arguments);
11
11
  throw Error('Dynamic require of "' + x + '" is not supported');
12
12
  });
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
13
16
  var __commonJS = (cb, mod) => function __require2() {
14
17
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
15
18
  };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, { get: all[name], enumerable: true });
22
+ };
16
23
  var __copyProps = (to, from, except, desc) => {
17
24
  if (from && typeof from === "object" || typeof from === "function") {
18
25
  for (let key of __getOwnPropNames(from))
@@ -102,7 +109,7 @@ var require_package = __commonJS({
102
109
  var require_main = __commonJS({
103
110
  "node_modules/dotenv/lib/main.js"(exports, module) {
104
111
  "use strict";
105
- var fs7 = __require("fs");
112
+ var fs8 = __require("fs");
106
113
  var path7 = __require("path");
107
114
  var os4 = __require("os");
108
115
  var crypto2 = __require("crypto");
@@ -244,7 +251,7 @@ var require_main = __commonJS({
244
251
  if (options && options.path && options.path.length > 0) {
245
252
  if (Array.isArray(options.path)) {
246
253
  for (const filepath of options.path) {
247
- if (fs7.existsSync(filepath)) {
254
+ if (fs8.existsSync(filepath)) {
248
255
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
249
256
  }
250
257
  }
@@ -254,7 +261,7 @@ var require_main = __commonJS({
254
261
  } else {
255
262
  possibleVaultPath = path7.resolve(process.cwd(), ".env.vault");
256
263
  }
257
- if (fs7.existsSync(possibleVaultPath)) {
264
+ if (fs8.existsSync(possibleVaultPath)) {
258
265
  return possibleVaultPath;
259
266
  }
260
267
  return null;
@@ -307,7 +314,7 @@ var require_main = __commonJS({
307
314
  const parsedAll = {};
308
315
  for (const path8 of optionPaths) {
309
316
  try {
310
- const parsed = DotenvModule.parse(fs7.readFileSync(path8, { encoding }));
317
+ const parsed = DotenvModule.parse(fs8.readFileSync(path8, { encoding }));
311
318
  DotenvModule.populate(parsedAll, parsed, options);
312
319
  } catch (e) {
313
320
  if (debug) {
@@ -428,6 +435,137 @@ var require_main = __commonJS({
428
435
  }
429
436
  });
430
437
 
438
+ // src/userResponse/utils.ts
439
+ var utils_exports = {};
440
+ __export(utils_exports, {
441
+ convertQuestionsToActions: () => convertQuestionsToActions,
442
+ convertTopToLimit: () => convertTopToLimit,
443
+ ensureQueryLimit: () => ensureQueryLimit,
444
+ fixScalarSubqueries: () => fixScalarSubqueries,
445
+ getJsonSizeInBytes: () => getJsonSizeInBytes,
446
+ validateMessageSize: () => validateMessageSize
447
+ });
448
+ function convertTopToLimit(query) {
449
+ if (!query || query.trim().length === 0) {
450
+ return query;
451
+ }
452
+ let modifiedQuery = query.replace(/\bSELECT\s+TOP\s+(\d+)\b/gi, "SELECT");
453
+ if (modifiedQuery !== query) {
454
+ console.warn(`\u26A0\uFE0F Query had TOP syntax. Converting to LIMIT for Snowflake compatibility.`);
455
+ }
456
+ return modifiedQuery;
457
+ }
458
+ function ensureQueryLimit(query, defaultLimit = 32, maxLimit = 32) {
459
+ if (!query || query.trim().length === 0) {
460
+ return query;
461
+ }
462
+ let trimmedQuery = query.trim();
463
+ const isSelectQuery = /^\s*SELECT\b/i.test(trimmedQuery) || /^\s*WITH\b.*\bSELECT\b/is.test(trimmedQuery);
464
+ if (!isSelectQuery) {
465
+ return query;
466
+ }
467
+ trimmedQuery = convertTopToLimit(trimmedQuery);
468
+ const hadSemicolon = trimmedQuery.endsWith(";");
469
+ if (hadSemicolon) {
470
+ trimmedQuery = trimmedQuery.slice(0, -1).trim();
471
+ }
472
+ const limitMatches = trimmedQuery.match(/\bLIMIT\s+(\d+)\b/gi);
473
+ if (limitMatches && limitMatches.length > 0) {
474
+ if (limitMatches.length > 1) {
475
+ console.warn(`\u26A0\uFE0F Query had ${limitMatches.length} LIMIT clauses. Removing duplicates...`);
476
+ trimmedQuery = trimmedQuery.replace(/\s*\bLIMIT\s+\d+\b/gi, "").trim();
477
+ } else {
478
+ const existingLimitMatch = trimmedQuery.match(/\bLIMIT\s+(\d+)\b/i);
479
+ if (existingLimitMatch) {
480
+ const existingLimit = parseInt(existingLimitMatch[1], 10);
481
+ if (existingLimit <= maxLimit) {
482
+ if (hadSemicolon) {
483
+ trimmedQuery += ";";
484
+ }
485
+ return trimmedQuery;
486
+ }
487
+ console.warn(`\u26A0\uFE0F Query LIMIT ${existingLimit} exceeds maximum of ${maxLimit}. Reducing to ${maxLimit}...`);
488
+ trimmedQuery = trimmedQuery.replace(/\bLIMIT\s+\d+\b/i, `LIMIT ${maxLimit}`);
489
+ if (hadSemicolon) {
490
+ trimmedQuery += ";";
491
+ }
492
+ return trimmedQuery;
493
+ }
494
+ }
495
+ }
496
+ trimmedQuery = `${trimmedQuery} LIMIT ${defaultLimit}`;
497
+ if (hadSemicolon) {
498
+ trimmedQuery += ";";
499
+ }
500
+ return trimmedQuery;
501
+ }
502
+ function fixScalarSubqueries(query) {
503
+ if (!query || query.trim().length === 0) {
504
+ return query;
505
+ }
506
+ let modifiedQuery = query;
507
+ let hasChanges = false;
508
+ const scalarOperatorPattern = /([=<>!]=?|<>)\s*\(\s*SELECT\s/gi;
509
+ const matches = [...modifiedQuery.matchAll(scalarOperatorPattern)];
510
+ for (let i = matches.length - 1; i >= 0; i--) {
511
+ const match = matches[i];
512
+ const startPos = match.index + match[0].length - "SELECT ".length;
513
+ let parenDepth = 1;
514
+ let endPos = startPos;
515
+ let foundEnd = false;
516
+ for (let j = startPos; j < modifiedQuery.length; j++) {
517
+ const char = modifiedQuery[j];
518
+ if (char === "(") parenDepth++;
519
+ if (char === ")") {
520
+ parenDepth--;
521
+ if (parenDepth === 0) {
522
+ endPos = j;
523
+ foundEnd = true;
524
+ break;
525
+ }
526
+ }
527
+ }
528
+ if (!foundEnd) continue;
529
+ const subquery = modifiedQuery.substring(startPos, endPos);
530
+ if (/\bLIMIT\s+\d+/i.test(subquery)) {
531
+ continue;
532
+ }
533
+ const fixedSubquery = subquery.trim() + " LIMIT 1";
534
+ modifiedQuery = modifiedQuery.substring(0, startPos) + fixedSubquery + modifiedQuery.substring(endPos);
535
+ hasChanges = true;
536
+ console.warn(`\u26A0\uFE0F Fixed scalar subquery: added LIMIT 1 to prevent multiple row error`);
537
+ }
538
+ if (hasChanges) {
539
+ console.log("\u2713 Query validated and fixed for PostgreSQL scalar subquery compatibility");
540
+ }
541
+ return modifiedQuery;
542
+ }
543
+ function convertQuestionsToActions(questions) {
544
+ return questions.map((question, index) => ({
545
+ id: `action_${index}_${Date.now()}`,
546
+ name: question,
547
+ type: "next_question",
548
+ question
549
+ }));
550
+ }
551
+ function getJsonSizeInBytes(obj) {
552
+ const jsonString = JSON.stringify(obj);
553
+ return Buffer.byteLength(jsonString, "utf8");
554
+ }
555
+ function validateMessageSize(message, maxSize = 1048576) {
556
+ const size = getJsonSizeInBytes(message);
557
+ return {
558
+ isValid: size <= maxSize,
559
+ size,
560
+ maxSize
561
+ };
562
+ }
563
+ var init_utils = __esm({
564
+ "src/userResponse/utils.ts"() {
565
+ "use strict";
566
+ }
567
+ });
568
+
431
569
  // src/websocket.ts
432
570
  import WebSocket from "ws";
433
571
  function createWebSocket(url) {
@@ -689,7 +827,8 @@ var UserPromptRequestPayloadSchema = z3.object({
689
827
  SA_RUNTIME: z3.object({
690
828
  threadId: z3.string(),
691
829
  uiBlockId: z3.string()
692
- }).optional()
830
+ }).optional(),
831
+ responseMode: z3.enum(["component", "text"]).optional()
693
832
  });
694
833
  var UserPromptRequestMessageSchema = z3.object({
695
834
  id: z3.string(),
@@ -711,7 +850,8 @@ var ComponentPropsSchema = z3.object({
711
850
  query: z3.string().optional(),
712
851
  title: z3.string().optional(),
713
852
  description: z3.string().optional(),
714
- config: z3.record(z3.unknown()).optional()
853
+ config: z3.record(z3.unknown()).optional(),
854
+ actions: z3.array(z3.any()).optional()
715
855
  });
716
856
  var ComponentSchema = z3.object({
717
857
  id: z3.string(),
@@ -804,7 +944,9 @@ var ReportsRequestMessageSchema = z3.object({
804
944
  });
805
945
 
806
946
  // src/utils/logger.ts
947
+ import fs from "fs";
807
948
  var PREFIX = "[SuperatomSDK]";
949
+ var LOGSTREAM = fs.createWriteStream("superatom-sdk.log", { flags: "a" });
808
950
  var LOG_LEVEL_PRIORITY = {
809
951
  errors: 0,
810
952
  warnings: 1,
@@ -888,6 +1030,9 @@ var Logger = class {
888
1030
  console.log(PREFIX, "[DEBUG]", ...args);
889
1031
  }
890
1032
  }
1033
+ file(...args) {
1034
+ LOGSTREAM.write(args.join(" ") + "\n");
1035
+ }
891
1036
  };
892
1037
  var logger = new Logger();
893
1038
 
@@ -959,6 +1104,9 @@ var UIBlock = class {
959
1104
  getComponentMetadata() {
960
1105
  return this.generatedComponentMetadata;
961
1106
  }
1107
+ getTextResponse() {
1108
+ return this.textResponse || "";
1109
+ }
962
1110
  /**
963
1111
  * Set or update component metadata
964
1112
  */
@@ -1051,12 +1199,6 @@ var UIBlock = class {
1051
1199
  const processedData = this.processDataForStorage(data);
1052
1200
  this.componentData = { ...this.componentData, ...processedData };
1053
1201
  }
1054
- /**
1055
- * Get text response
1056
- */
1057
- getTextResponse() {
1058
- return this.textResponse;
1059
- }
1060
1202
  /**
1061
1203
  * Set or update text response
1062
1204
  */
@@ -1092,6 +1234,12 @@ var UIBlock = class {
1092
1234
  throw error;
1093
1235
  }
1094
1236
  }
1237
+ /**
1238
+ * Set or replace all actions
1239
+ */
1240
+ setActions(actions) {
1241
+ this.actions = actions;
1242
+ }
1095
1243
  /**
1096
1244
  * Add a single action (only if actions are resolved)
1097
1245
  */
@@ -1247,8 +1395,11 @@ var Thread = class {
1247
1395
  const questionNum = index + 1;
1248
1396
  const question = block.getUserQuestion();
1249
1397
  const metadata = block.getComponentMetadata();
1250
- let componentSummary = "No component generated";
1251
- if (metadata && Object.keys(metadata).length > 0) {
1398
+ const textResponse = block.getTextResponse();
1399
+ let assistantResponse = "";
1400
+ const hasComponent = metadata && Object.keys(metadata).length > 0 && metadata.type;
1401
+ const hasTextResponse = textResponse && textResponse.trim().length > 0;
1402
+ if (hasComponent) {
1252
1403
  const parts = [];
1253
1404
  if (metadata.type) {
1254
1405
  parts.push(`Component Type: ${metadata.type}`);
@@ -1268,11 +1419,17 @@ var Thread = class {
1268
1419
  const componentTypes = metadata.props.config.components.map((c) => c.type).join(", ");
1269
1420
  parts.push(`Multi-component with: ${componentTypes}`);
1270
1421
  }
1271
- componentSummary = parts.join(", ");
1422
+ assistantResponse = parts.join(", ");
1423
+ } else if (hasTextResponse) {
1424
+ assistantResponse = textResponse;
1425
+ } else {
1426
+ assistantResponse = "No response generated";
1272
1427
  }
1273
- contextLines.push(`Q${questionNum}: ${question}`);
1274
- contextLines.push(`A${questionNum}: ${componentSummary}`);
1275
- contextLines.push("");
1428
+ contextLines.push(`User:
1429
+ ${question}`);
1430
+ contextLines.push(`Assistant:
1431
+ ${assistantResponse}`);
1432
+ contextLines.push("---");
1276
1433
  });
1277
1434
  return contextLines.join("\n").trim();
1278
1435
  }
@@ -1448,7 +1605,7 @@ function sendDataResponse(id, collection, op, data, meta, sendMessage) {
1448
1605
  }
1449
1606
 
1450
1607
  // src/bundle.ts
1451
- import * as fs from "fs";
1608
+ import * as fs2 from "fs";
1452
1609
  import * as path from "path";
1453
1610
  function getBundleDir(configDir) {
1454
1611
  const bundleDir = configDir || process.env.SA_BUNDLE_DIR;
@@ -1461,16 +1618,16 @@ function getBundleDir(configDir) {
1461
1618
  }
1462
1619
  function getJS(bundleDir) {
1463
1620
  try {
1464
- if (!fs.existsSync(bundleDir)) {
1621
+ if (!fs2.existsSync(bundleDir)) {
1465
1622
  throw new Error(`Bundle directory does not exist: ${bundleDir}`);
1466
1623
  }
1467
- const stats = fs.statSync(bundleDir);
1624
+ const stats = fs2.statSync(bundleDir);
1468
1625
  if (!stats.isDirectory()) {
1469
1626
  throw new Error(`Bundle path is not a directory: ${bundleDir}`);
1470
1627
  }
1471
1628
  let files;
1472
1629
  try {
1473
- files = fs.readdirSync(bundleDir);
1630
+ files = fs2.readdirSync(bundleDir);
1474
1631
  } catch (error) {
1475
1632
  const errorMessage = error instanceof Error ? error.message : String(error);
1476
1633
  throw new Error(`Failed to read bundle directory: ${errorMessage}`);
@@ -1485,7 +1642,7 @@ function getJS(bundleDir) {
1485
1642
  const filePath = path.join(bundleDir, indexFile);
1486
1643
  logger.info(`Loading bundle from ${filePath}`);
1487
1644
  try {
1488
- return fs.readFileSync(filePath, "utf8");
1645
+ return fs2.readFileSync(filePath, "utf8");
1489
1646
  } catch (error) {
1490
1647
  const errorMessage = error instanceof Error ? error.message : String(error);
1491
1648
  throw new Error(`Failed to read bundle file: ${errorMessage}`);
@@ -1891,94 +2048,12 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
1891
2048
  // src/userResponse/groq.ts
1892
2049
  var import_dotenv = __toESM(require_main());
1893
2050
 
1894
- // src/userResponse/utils.ts
1895
- function convertTopToLimit(query) {
1896
- if (!query || query.trim().length === 0) {
1897
- return query;
1898
- }
1899
- let modifiedQuery = query.replace(/\bSELECT\s+TOP\s+(\d+)\b/gi, "SELECT");
1900
- if (modifiedQuery !== query) {
1901
- console.warn(`\u26A0\uFE0F Query had TOP syntax. Converting to LIMIT for Snowflake compatibility.`);
1902
- }
1903
- return modifiedQuery;
1904
- }
1905
- function ensureQueryLimit(query, defaultLimit = 50) {
1906
- if (!query || query.trim().length === 0) {
1907
- return query;
1908
- }
1909
- let trimmedQuery = query.trim();
1910
- const isSelectQuery = /^\s*SELECT\b/i.test(trimmedQuery) || /^\s*WITH\b.*\bSELECT\b/is.test(trimmedQuery);
1911
- if (!isSelectQuery) {
1912
- return query;
1913
- }
1914
- trimmedQuery = convertTopToLimit(trimmedQuery);
1915
- const hadSemicolon = trimmedQuery.endsWith(";");
1916
- if (hadSemicolon) {
1917
- trimmedQuery = trimmedQuery.slice(0, -1).trim();
1918
- }
1919
- const limitMatches = trimmedQuery.match(/\bLIMIT\s+\d+\b/gi);
1920
- if (limitMatches && limitMatches.length > 0) {
1921
- if (limitMatches.length > 1) {
1922
- console.warn(`\u26A0\uFE0F Query had ${limitMatches.length} LIMIT clauses. Removing duplicates...`);
1923
- trimmedQuery = trimmedQuery.replace(/\s*\bLIMIT\s+\d+\b/gi, "").trim();
1924
- } else {
1925
- if (hadSemicolon) {
1926
- trimmedQuery += ";";
1927
- }
1928
- return trimmedQuery;
1929
- }
1930
- }
1931
- trimmedQuery = `${trimmedQuery} LIMIT ${defaultLimit}`;
1932
- if (hadSemicolon) {
1933
- trimmedQuery += ";";
1934
- }
1935
- return trimmedQuery;
1936
- }
1937
- function fixScalarSubqueries(query) {
1938
- if (!query || query.trim().length === 0) {
1939
- return query;
1940
- }
1941
- let modifiedQuery = query;
1942
- let hasChanges = false;
1943
- const scalarOperatorPattern = /([=<>!]=?|<>)\s*\(\s*SELECT\s/gi;
1944
- const matches = [...modifiedQuery.matchAll(scalarOperatorPattern)];
1945
- for (let i = matches.length - 1; i >= 0; i--) {
1946
- const match = matches[i];
1947
- const startPos = match.index + match[0].length - "SELECT ".length;
1948
- let parenDepth = 1;
1949
- let endPos = startPos;
1950
- let foundEnd = false;
1951
- for (let j = startPos; j < modifiedQuery.length; j++) {
1952
- const char = modifiedQuery[j];
1953
- if (char === "(") parenDepth++;
1954
- if (char === ")") {
1955
- parenDepth--;
1956
- if (parenDepth === 0) {
1957
- endPos = j;
1958
- foundEnd = true;
1959
- break;
1960
- }
1961
- }
1962
- }
1963
- if (!foundEnd) continue;
1964
- const subquery = modifiedQuery.substring(startPos, endPos);
1965
- if (/\bLIMIT\s+\d+/i.test(subquery)) {
1966
- continue;
1967
- }
1968
- const fixedSubquery = subquery.trim() + " LIMIT 1";
1969
- modifiedQuery = modifiedQuery.substring(0, startPos) + fixedSubquery + modifiedQuery.substring(endPos);
1970
- hasChanges = true;
1971
- console.warn(`\u26A0\uFE0F Fixed scalar subquery: added LIMIT 1 to prevent multiple row error`);
1972
- }
1973
- if (hasChanges) {
1974
- console.log("\u2713 Query validated and fixed for PostgreSQL scalar subquery compatibility");
1975
- }
1976
- return modifiedQuery;
1977
- }
2051
+ // src/userResponse/base-llm.ts
2052
+ init_utils();
1978
2053
 
1979
2054
  // src/userResponse/schema.ts
1980
2055
  import path2 from "path";
1981
- import fs2 from "fs";
2056
+ import fs3 from "fs";
1982
2057
  var Schema = class {
1983
2058
  constructor(schemaFilePath) {
1984
2059
  this.cachedSchema = null;
@@ -1992,11 +2067,11 @@ var Schema = class {
1992
2067
  logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
1993
2068
  try {
1994
2069
  const dir = path2.dirname(this.schemaFilePath);
1995
- if (!fs2.existsSync(dir)) {
2070
+ if (!fs3.existsSync(dir)) {
1996
2071
  logger.info(`Creating directory structure: ${dir}`);
1997
- fs2.mkdirSync(dir, { recursive: true });
2072
+ fs3.mkdirSync(dir, { recursive: true });
1998
2073
  }
1999
- if (!fs2.existsSync(this.schemaFilePath)) {
2074
+ if (!fs3.existsSync(this.schemaFilePath)) {
2000
2075
  logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
2001
2076
  const initialSchema = {
2002
2077
  database: "",
@@ -2005,11 +2080,11 @@ var Schema = class {
2005
2080
  tables: [],
2006
2081
  relationships: []
2007
2082
  };
2008
- fs2.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
2083
+ fs3.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
2009
2084
  this.cachedSchema = initialSchema;
2010
2085
  return initialSchema;
2011
2086
  }
2012
- const fileContent = fs2.readFileSync(this.schemaFilePath, "utf-8");
2087
+ const fileContent = fs3.readFileSync(this.schemaFilePath, "utf-8");
2013
2088
  const schema2 = JSON.parse(fileContent);
2014
2089
  this.cachedSchema = schema2;
2015
2090
  return schema2;
@@ -2110,7 +2185,7 @@ var Schema = class {
2110
2185
  var schema = new Schema();
2111
2186
 
2112
2187
  // src/userResponse/prompt-loader.ts
2113
- import fs3 from "fs";
2188
+ import fs4 from "fs";
2114
2189
  import path3 from "path";
2115
2190
  var PromptLoader = class {
2116
2191
  constructor(config) {
@@ -2138,7 +2213,8 @@ var PromptLoader = class {
2138
2213
  "mutli-component",
2139
2214
  "actions",
2140
2215
  "container-metadata",
2141
- "text-response"
2216
+ "text-response",
2217
+ "match-text-components"
2142
2218
  ];
2143
2219
  for (const promptType of promptTypes) {
2144
2220
  try {
@@ -2163,9 +2239,9 @@ var PromptLoader = class {
2163
2239
  try {
2164
2240
  const systemPath = path3.join(dir, promptName, "system.md");
2165
2241
  const userPath = path3.join(dir, promptName, "user.md");
2166
- if (fs3.existsSync(systemPath) && fs3.existsSync(userPath)) {
2167
- const system = fs3.readFileSync(systemPath, "utf-8");
2168
- const user = fs3.readFileSync(userPath, "utf-8");
2242
+ if (fs4.existsSync(systemPath) && fs4.existsSync(userPath)) {
2243
+ const system = fs4.readFileSync(systemPath, "utf-8");
2244
+ const user = fs4.readFileSync(userPath, "utf-8");
2169
2245
  logger.debug(`Loaded prompt '${promptName}' from ${dir}`);
2170
2246
  return { system, user };
2171
2247
  }
@@ -2287,6 +2363,14 @@ var LLM = class {
2287
2363
  throw new Error(`Unsupported provider: ${provider}. Use "anthropic" or "groq"`);
2288
2364
  }
2289
2365
  }
2366
+ /* Stream response with tool calling support (Anthropic only for now) */
2367
+ static async streamWithTools(messages, tools, toolHandler, options = {}, maxIterations = 3) {
2368
+ const [provider, modelName] = this._parseModel(options.model);
2369
+ if (provider !== "anthropic") {
2370
+ throw new Error(`Tool calling is only supported for Anthropic models`);
2371
+ }
2372
+ return this._anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations);
2373
+ }
2290
2374
  // ============================================================
2291
2375
  // PRIVATE HELPER METHODS
2292
2376
  // ============================================================
@@ -2364,6 +2448,86 @@ var LLM = class {
2364
2448
  }
2365
2449
  return fullText;
2366
2450
  }
2451
+ static async _anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations) {
2452
+ const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || "";
2453
+ const client = new Anthropic({
2454
+ apiKey
2455
+ });
2456
+ const conversationMessages = [{
2457
+ role: "user",
2458
+ content: messages.user
2459
+ }];
2460
+ let iterations = 0;
2461
+ let finalText = "";
2462
+ while (iterations < maxIterations) {
2463
+ iterations++;
2464
+ const response = await client.messages.create({
2465
+ model: modelName,
2466
+ max_tokens: options.maxTokens || 4e3,
2467
+ temperature: options.temperature,
2468
+ system: messages.sys,
2469
+ messages: conversationMessages,
2470
+ tools
2471
+ });
2472
+ if (response.stop_reason === "end_turn") {
2473
+ const textBlock = response.content.find((block) => block.type === "text");
2474
+ if (textBlock && textBlock.type === "text") {
2475
+ finalText = textBlock.text;
2476
+ if (options.partial) {
2477
+ options.partial(finalText);
2478
+ }
2479
+ }
2480
+ break;
2481
+ }
2482
+ if (response.stop_reason === "tool_use") {
2483
+ const toolUses = response.content.filter((block) => block.type === "tool_use");
2484
+ if (toolUses.length === 0) {
2485
+ break;
2486
+ }
2487
+ conversationMessages.push({
2488
+ role: "assistant",
2489
+ content: response.content
2490
+ });
2491
+ const toolResults = {
2492
+ role: "user",
2493
+ content: []
2494
+ };
2495
+ for (const toolUse of toolUses) {
2496
+ if (toolUse.type === "tool_use") {
2497
+ try {
2498
+ const result = await toolHandler(toolUse.name, toolUse.input);
2499
+ toolResults.content.push({
2500
+ type: "tool_result",
2501
+ tool_use_id: toolUse.id,
2502
+ content: typeof result === "string" ? result : JSON.stringify(result)
2503
+ });
2504
+ } catch (error) {
2505
+ toolResults.content.push({
2506
+ type: "tool_result",
2507
+ tool_use_id: toolUse.id,
2508
+ content: error instanceof Error ? error.message : String(error),
2509
+ is_error: true
2510
+ });
2511
+ }
2512
+ }
2513
+ }
2514
+ conversationMessages.push(toolResults);
2515
+ } else {
2516
+ const textBlock = response.content.find((block) => block.type === "text");
2517
+ if (textBlock && textBlock.type === "text") {
2518
+ finalText = textBlock.text;
2519
+ if (options.partial) {
2520
+ options.partial(finalText);
2521
+ }
2522
+ }
2523
+ break;
2524
+ }
2525
+ }
2526
+ if (iterations >= maxIterations) {
2527
+ throw new Error(`Max iterations (${maxIterations}) reached in tool calling loop`);
2528
+ }
2529
+ return finalText;
2530
+ }
2367
2531
  // ============================================================
2368
2532
  // GROQ IMPLEMENTATION
2369
2533
  // ============================================================
@@ -2438,6 +2602,42 @@ var LLM = class {
2438
2602
  }
2439
2603
  };
2440
2604
 
2605
+ // src/userResponse/knowledge-base.ts
2606
+ var getKnowledgeBase = async ({
2607
+ prompt,
2608
+ collections,
2609
+ topK = 1
2610
+ }) => {
2611
+ try {
2612
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
2613
+ logger.info("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
2614
+ return "";
2615
+ }
2616
+ logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
2617
+ const result = await collections["knowledge-base"]["query"]({
2618
+ prompt,
2619
+ topK
2620
+ });
2621
+ if (!result || !result.content) {
2622
+ logger.error("[KnowledgeBase] No knowledge base results returned");
2623
+ return "";
2624
+ }
2625
+ logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
2626
+ if (result.metadata?.sources && result.metadata.sources.length > 0) {
2627
+ logger.debug(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
2628
+ }
2629
+ return result.content;
2630
+ } catch (error) {
2631
+ const errorMsg = error instanceof Error ? error.message : String(error);
2632
+ logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
2633
+ return "";
2634
+ }
2635
+ };
2636
+ var KB = {
2637
+ getKnowledgeBase
2638
+ };
2639
+ var knowledge_base_default = KB;
2640
+
2441
2641
  // src/userResponse/base-llm.ts
2442
2642
  var BaseLLM = class {
2443
2643
  constructor(config) {
@@ -2976,62 +3176,485 @@ var BaseLLM = class {
2976
3176
  throw error;
2977
3177
  }
2978
3178
  }
3179
+ /**
3180
+ * Match components from text response suggestions and generate follow-up questions
3181
+ * Takes a text response with component suggestions (c1:type format) and matches with available components
3182
+ * Also generates intelligent follow-up questions (actions) based on the analysis
3183
+ * @param textResponse - The text response containing component suggestions
3184
+ * @param components - List of available components
3185
+ * @param apiKey - Optional API key
3186
+ * @param logCollector - Optional log collector
3187
+ * @returns Object containing matched components, selected layout, reasoning, and follow-up actions
3188
+ */
3189
+ async matchComponentsFromTextResponse(textResponse, components, apiKey, logCollector) {
3190
+ try {
3191
+ logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
3192
+ let availableComponentsText = "No components available";
3193
+ if (components && components.length > 0) {
3194
+ availableComponentsText = components.map((comp, idx) => {
3195
+ const keywords = comp.keywords ? comp.keywords.join(", ") : "";
3196
+ const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
3197
+ return `${idx + 1}. ID: ${comp.id}
3198
+ Name: ${comp.name}
3199
+ Type: ${comp.type}
3200
+ Description: ${comp.description || "No description"}
3201
+ Keywords: ${keywords}
3202
+ Props Structure: ${propsPreview}`;
3203
+ }).join("\n\n");
3204
+ }
3205
+ const schemaDoc = schema.generateSchemaDocumentation();
3206
+ const prompts = await promptLoader.loadPrompts("match-text-components", {
3207
+ TEXT_RESPONSE: textResponse,
3208
+ AVAILABLE_COMPONENTS: availableComponentsText,
3209
+ SCHEMA_DOC: schemaDoc
3210
+ });
3211
+ logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
3212
+ logger.file("\n=============================\nmatch text components system prompt:", prompts.system);
3213
+ logCollector?.info("Matching components from text response...");
3214
+ const rawResponse = await LLM.stream(
3215
+ {
3216
+ sys: prompts.system,
3217
+ user: prompts.user
3218
+ },
3219
+ {
3220
+ model: this.model,
3221
+ maxTokens: 3e3,
3222
+ temperature: 0.2,
3223
+ apiKey: this.getApiKey(apiKey)
3224
+ },
3225
+ false
3226
+ // Don't parse as JSON yet, get raw response
3227
+ );
3228
+ logger.debug(`[${this.getProviderName()}] Raw component matching response length: ${rawResponse?.length || 0}`);
3229
+ let result;
3230
+ try {
3231
+ let cleanedResponse = rawResponse || "";
3232
+ cleanedResponse = cleanedResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
3233
+ const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
3234
+ if (jsonMatch) {
3235
+ cleanedResponse = jsonMatch[0];
3236
+ }
3237
+ result = JSON.parse(cleanedResponse);
3238
+ } catch (parseError) {
3239
+ logger.error(`[${this.getProviderName()}] Failed to parse component matching JSON response`);
3240
+ const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
3241
+ const posMatch = errorMsg.match(/position (\d+)/);
3242
+ const errorPos = posMatch ? parseInt(posMatch[1]) : -1;
3243
+ if (errorPos > 0 && rawResponse) {
3244
+ const start = Math.max(0, errorPos - 200);
3245
+ const end = Math.min(rawResponse.length, errorPos + 200);
3246
+ logger.debug(`[${this.getProviderName()}] Error context (position ${errorPos}):`);
3247
+ logger.debug(rawResponse.substring(start, end));
3248
+ logger.debug(" ".repeat(Math.min(200, errorPos - start)) + "^--- Error here");
3249
+ }
3250
+ logger.debug(`[${this.getProviderName()}] Raw response (first 2000 chars):`, rawResponse?.substring(0, 2e3));
3251
+ logger.debug(`[${this.getProviderName()}] Parse error:`, parseError);
3252
+ logCollector?.error(`Failed to parse component matching response: ${errorMsg}`);
3253
+ try {
3254
+ logger.info(`[${this.getProviderName()}] Attempting aggressive JSON cleanup...`);
3255
+ let aggressive = rawResponse || "";
3256
+ aggressive = aggressive.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
3257
+ const match = aggressive.match(/\{[\s\S]*\}/);
3258
+ if (match) {
3259
+ aggressive = match[0];
3260
+ }
3261
+ aggressive = aggressive.replace(/,(\s*[}\]])/g, "$1");
3262
+ result = JSON.parse(aggressive);
3263
+ logger.info(`[${this.getProviderName()}] Aggressive cleanup succeeded!`);
3264
+ } catch (secondError) {
3265
+ logger.error(`[${this.getProviderName()}] Aggressive cleanup also failed`);
3266
+ return {
3267
+ components: [],
3268
+ selectedLayout: "MultiComponentContainer",
3269
+ selectedLayoutId: "",
3270
+ selectedLayoutComponent: null,
3271
+ layoutReasoning: "No layout selected",
3272
+ actions: []
3273
+ };
3274
+ }
3275
+ }
3276
+ const matchedComponents = result.matchedComponents || [];
3277
+ const selectedLayout = result.selectedLayout || "MultiComponentContainer";
3278
+ const selectedLayoutId = result.selectedLayoutId || "";
3279
+ const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
3280
+ const rawActions = result.actions || [];
3281
+ const actions = convertQuestionsToActions(rawActions);
3282
+ let selectedLayoutComponent = null;
3283
+ if (selectedLayoutId) {
3284
+ selectedLayoutComponent = components.find((c) => c.id === selectedLayoutId) || null;
3285
+ if (!selectedLayoutComponent) {
3286
+ logger.warn(`[${this.getProviderName()}] Layout component ${selectedLayoutId} not found in available components`);
3287
+ }
3288
+ }
3289
+ logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
3290
+ logger.info(`[${this.getProviderName()}] Selected layout: ${selectedLayout} (ID: ${selectedLayoutId})`);
3291
+ logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
3292
+ logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
3293
+ if (matchedComponents.length > 0) {
3294
+ logCollector?.info(`Matched ${matchedComponents.length} components for visualization using ${selectedLayout}`);
3295
+ logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
3296
+ matchedComponents.forEach((comp, idx) => {
3297
+ logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
3298
+ if (comp.props?.query) {
3299
+ logCollector?.logQuery(
3300
+ `Component ${idx + 1} query`,
3301
+ comp.props.query,
3302
+ { componentName: comp.componentName, title: comp.props.title }
3303
+ );
3304
+ }
3305
+ });
3306
+ }
3307
+ if (actions.length > 0) {
3308
+ logCollector?.info(`Generated ${actions.length} follow-up questions`);
3309
+ actions.forEach((action, idx) => {
3310
+ logCollector?.info(` ${idx + 1}. ${action.name}`);
3311
+ });
3312
+ }
3313
+ const finalComponents = matchedComponents.map((mc) => {
3314
+ const originalComponent = components.find((c) => c.id === mc.componentId);
3315
+ if (!originalComponent) {
3316
+ logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
3317
+ return null;
3318
+ }
3319
+ return {
3320
+ ...originalComponent,
3321
+ props: {
3322
+ ...originalComponent.props,
3323
+ ...mc.props
3324
+ }
3325
+ };
3326
+ }).filter(Boolean);
3327
+ return {
3328
+ components: finalComponents,
3329
+ selectedLayout,
3330
+ selectedLayoutId,
3331
+ selectedLayoutComponent,
3332
+ layoutReasoning,
3333
+ actions
3334
+ };
3335
+ } catch (error) {
3336
+ const errorMsg = error instanceof Error ? error.message : String(error);
3337
+ logger.error(`[${this.getProviderName()}] Error matching components from text response: ${errorMsg}`);
3338
+ logger.debug(`[${this.getProviderName()}] Component matching error details:`, error);
3339
+ logCollector?.error(`Error matching components: ${errorMsg}`);
3340
+ return {
3341
+ components: [],
3342
+ selectedLayout: "MultiComponentContainer",
3343
+ selectedLayoutId: "",
3344
+ selectedLayoutComponent: null,
3345
+ layoutReasoning: "Error occurred during component matching",
3346
+ actions: []
3347
+ };
3348
+ }
3349
+ }
2979
3350
  /**
2980
3351
  * Generate text-based response for user question
2981
3352
  * This provides conversational text responses instead of component generation
3353
+ * Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
3354
+ * After generating text response, if components are provided, matches suggested components
3355
+ * @param streamCallback - Optional callback function to receive text chunks as they stream
3356
+ * @param collections - Collection registry for executing database queries via database.execute
3357
+ * @param components - Optional list of available components for matching suggestions
2982
3358
  */
2983
- async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory) {
3359
+ async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components) {
2984
3360
  const errors = [];
2985
3361
  logger.debug(`[${this.getProviderName()}] Starting text response generation`);
2986
3362
  logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
2987
3363
  try {
3364
+ const schemaDoc = schema.generateSchemaDocumentation();
3365
+ const knowledgeBaseContext = await knowledge_base_default.getKnowledgeBase({
3366
+ prompt: userPrompt,
3367
+ collections,
3368
+ topK: 1
3369
+ });
3370
+ logger.file("\n=============================\nknowledge base context:", knowledgeBaseContext);
2988
3371
  const prompts = await promptLoader.loadPrompts("text-response", {
2989
3372
  USER_PROMPT: userPrompt,
2990
- CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
3373
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
3374
+ SCHEMA_DOC: schemaDoc,
3375
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available."
2991
3376
  });
2992
- logger.debug(`[${this.getProviderName()}] Loaded text-response prompts`);
3377
+ logger.file("\n=============================\nsystem prompt:", prompts.system);
3378
+ logger.file("\n=============================\nuser prompt:", prompts.user);
3379
+ logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
2993
3380
  logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
2994
- logCollector?.info("Generating text response...");
2995
- const result = await LLM.stream(
3381
+ logCollector?.info("Generating text response with query execution capability...");
3382
+ const tools = [{
3383
+ name: "execute_query",
3384
+ description: "Executes a SQL query against the database and returns the results. Use this when the user asks for data. If the query fails, you will receive the error and can retry with a corrected query.",
3385
+ input_schema: {
3386
+ type: "object",
3387
+ properties: {
3388
+ query: {
3389
+ type: "string",
3390
+ description: "The SQL query to execute. Must be valid SQL syntax using table and column names from the schema."
3391
+ },
3392
+ reasoning: {
3393
+ type: "string",
3394
+ description: "Brief explanation of what this query does and why it answers the user's question."
3395
+ }
3396
+ },
3397
+ required: ["query"]
3398
+ }
3399
+ }];
3400
+ const queryAttempts = /* @__PURE__ */ new Map();
3401
+ const MAX_QUERY_ATTEMPTS = 6;
3402
+ let maxAttemptsReached = false;
3403
+ let fullStreamedText = "";
3404
+ const wrappedStreamCallback = streamCallback ? (chunk) => {
3405
+ fullStreamedText += chunk;
3406
+ streamCallback(chunk);
3407
+ } : void 0;
3408
+ const toolHandler = async (toolName, toolInput) => {
3409
+ if (toolName === "execute_query") {
3410
+ let query = toolInput.query;
3411
+ const reasoning = toolInput.reasoning;
3412
+ const { ensureQueryLimit: ensureQueryLimit2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
3413
+ query = ensureQueryLimit2(query, 32, 32);
3414
+ const queryKey = query.toLowerCase().replace(/\s+/g, " ").trim();
3415
+ const attempts = (queryAttempts.get(queryKey) || 0) + 1;
3416
+ queryAttempts.set(queryKey, attempts);
3417
+ logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${query.substring(0, 100)}...`);
3418
+ if (reasoning) {
3419
+ logCollector?.info(`Query reasoning: ${reasoning}`);
3420
+ }
3421
+ if (attempts > MAX_QUERY_ATTEMPTS) {
3422
+ const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
3423
+ logger.error(`[${this.getProviderName()}] ${errorMsg}`);
3424
+ logCollector?.error(errorMsg);
3425
+ maxAttemptsReached = true;
3426
+ if (wrappedStreamCallback) {
3427
+ wrappedStreamCallback(`
3428
+
3429
+ \u274C ${errorMsg}
3430
+
3431
+ Please try rephrasing your question or simplifying your request.
3432
+
3433
+ `);
3434
+ }
3435
+ throw new Error(errorMsg);
3436
+ }
3437
+ try {
3438
+ if (wrappedStreamCallback) {
3439
+ if (attempts === 1) {
3440
+ wrappedStreamCallback(`
3441
+
3442
+ \u{1F50D} **Analyzing your question...**
3443
+
3444
+ `);
3445
+ if (reasoning) {
3446
+ wrappedStreamCallback(`\u{1F4AD} ${reasoning}
3447
+
3448
+ `);
3449
+ }
3450
+ wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
3451
+ \`\`\`sql
3452
+ ${query}
3453
+ \`\`\`
3454
+
3455
+ `);
3456
+ wrappedStreamCallback(`\u26A1 **Executing query...**
3457
+
3458
+ `);
3459
+ } else {
3460
+ wrappedStreamCallback(`
3461
+
3462
+ \u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
3463
+
3464
+ `);
3465
+ if (reasoning) {
3466
+ wrappedStreamCallback(`\u{1F4AD} ${reasoning}
3467
+
3468
+ `);
3469
+ }
3470
+ wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
3471
+ \`\`\`sql
3472
+ ${query}
3473
+ \`\`\`
3474
+
3475
+ `);
3476
+ wrappedStreamCallback(`\u26A1 **Executing query...**
3477
+
3478
+ `);
3479
+ }
3480
+ }
3481
+ logCollector?.logQuery(
3482
+ `Executing SQL query (attempt ${attempts})`,
3483
+ query,
3484
+ { reasoning, attempt: attempts }
3485
+ );
3486
+ if (!collections || !collections["database"] || !collections["database"]["execute"]) {
3487
+ throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
3488
+ }
3489
+ const result2 = await collections["database"]["execute"]({ sql: query });
3490
+ const data = result2?.data || result2;
3491
+ const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
3492
+ logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
3493
+ logCollector?.info(`Query successful, returned ${rowCount} rows`);
3494
+ if (wrappedStreamCallback) {
3495
+ wrappedStreamCallback(`\u2705 **Query executed successfully!**
3496
+
3497
+ `);
3498
+ if (Array.isArray(data) && data.length > 0) {
3499
+ const firstRow = data[0];
3500
+ const columns = Object.keys(firstRow);
3501
+ if (data.length === 1 && columns.length === 1) {
3502
+ const value = firstRow[columns[0]];
3503
+ wrappedStreamCallback(`**Result:** ${value}
3504
+
3505
+ `);
3506
+ } else if (data.length > 0) {
3507
+ wrappedStreamCallback(`**Retrieved ${rowCount} rows**
3508
+
3509
+ `);
3510
+ wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
3511
+
3512
+ `);
3513
+ }
3514
+ } else if (Array.isArray(data) && data.length === 0) {
3515
+ wrappedStreamCallback(`**No rows returned.**
3516
+
3517
+ `);
3518
+ }
3519
+ wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
3520
+
3521
+ `);
3522
+ }
3523
+ return JSON.stringify(data, null, 2);
3524
+ } catch (error) {
3525
+ const errorMsg = error instanceof Error ? error.message : String(error);
3526
+ logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
3527
+ logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
3528
+ if (wrappedStreamCallback) {
3529
+ wrappedStreamCallback(`\u274C **Query execution failed:**
3530
+ \`\`\`
3531
+ ${errorMsg}
3532
+ \`\`\`
3533
+
3534
+ `);
3535
+ if (attempts < MAX_QUERY_ATTEMPTS) {
3536
+ wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
3537
+
3538
+ `);
3539
+ }
3540
+ }
3541
+ throw new Error(`Query execution failed: ${errorMsg}`);
3542
+ }
3543
+ }
3544
+ throw new Error(`Unknown tool: ${toolName}`);
3545
+ };
3546
+ const result = await LLM.streamWithTools(
2996
3547
  {
2997
3548
  sys: prompts.system,
2998
3549
  user: prompts.user
2999
3550
  },
3551
+ tools,
3552
+ toolHandler,
3000
3553
  {
3001
3554
  model: this.model,
3002
- maxTokens: 1e3,
3555
+ maxTokens: 4e3,
3003
3556
  temperature: 0.7,
3004
- apiKey: this.getApiKey(apiKey)
3557
+ apiKey: this.getApiKey(apiKey),
3558
+ partial: wrappedStreamCallback
3559
+ // Pass the wrapped streaming callback to LLM
3005
3560
  },
3006
- true
3007
- // Parse as JSON
3561
+ 10
3562
+ // max iterations: allows for 6 retries + final response + buffer
3008
3563
  );
3009
- logger.info(`[${this.getProviderName()}] Text response generated successfully`);
3010
- logger.debug(`[${this.getProviderName()}] Response type: ${result.responseType}, Confidence: ${result.confidence}`);
3011
- logCollector?.info(`Text response: ${result.text?.substring(0, 100)}${result.text?.length > 100 ? "..." : ""}`);
3564
+ logger.info(`[${this.getProviderName()}] Text response stream completed`);
3565
+ const textResponse = fullStreamedText || result || "I apologize, but I was unable to generate a response.";
3566
+ if (maxAttemptsReached) {
3567
+ logger.warn(`[${this.getProviderName()}] Max query attempts reached, returning failure response`);
3568
+ logCollector?.error("Failed to generate valid query after maximum attempts");
3569
+ return {
3570
+ success: false,
3571
+ errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
3572
+ data: {
3573
+ text: textResponse,
3574
+ // Include the streamed text showing all attempts
3575
+ matchedComponents: [],
3576
+ actions: [],
3577
+ method: `${this.getProviderName()}-text-response-max-attempts`
3578
+ }
3579
+ };
3580
+ }
3581
+ logCollector?.info(`Text response: ${textResponse.substring(0, 100)}${textResponse.length > 100 ? "..." : ""}`);
3012
3582
  logCollector?.logExplanation(
3013
3583
  "Text response generated",
3014
- result.reasoning || "Generated text response for user question",
3584
+ "Generated plain text response with component suggestions",
3015
3585
  {
3016
- responseType: result.responseType,
3017
- confidence: result.confidence,
3018
- textLength: result.text?.length || 0
3586
+ textLength: textResponse.length
3019
3587
  }
3020
3588
  );
3589
+ let matchedComponents = [];
3590
+ let selectedLayoutComponent = null;
3591
+ let layoutReasoning = "No layout selected";
3592
+ let actions = [];
3593
+ if (components && components.length > 0) {
3594
+ logger.info(`[${this.getProviderName()}] Matching components from text response...`);
3595
+ const matchResult = await this.matchComponentsFromTextResponse(
3596
+ textResponse,
3597
+ components,
3598
+ apiKey,
3599
+ logCollector
3600
+ );
3601
+ matchedComponents = matchResult.components;
3602
+ selectedLayoutComponent = matchResult.selectedLayoutComponent;
3603
+ layoutReasoning = matchResult.layoutReasoning;
3604
+ actions = matchResult.actions;
3605
+ }
3606
+ let container_componet = null;
3607
+ if (matchedComponents.length > 0) {
3608
+ if (selectedLayoutComponent) {
3609
+ container_componet = {
3610
+ ...selectedLayoutComponent,
3611
+ id: `${selectedLayoutComponent.id}_${Date.now()}`,
3612
+ description: layoutReasoning,
3613
+ props: {
3614
+ ...selectedLayoutComponent.props,
3615
+ config: {
3616
+ ...selectedLayoutComponent.props?.config || {},
3617
+ components: matchedComponents
3618
+ },
3619
+ actions
3620
+ }
3621
+ };
3622
+ logger.info(`[${this.getProviderName()}] Created ${selectedLayoutComponent.name} (${selectedLayoutComponent.type}) container with ${matchedComponents.length} components and ${actions.length} actions`);
3623
+ logCollector?.info(`Created ${selectedLayoutComponent.name} with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
3624
+ } else {
3625
+ container_componet = {
3626
+ id: `multi_container_${Date.now()}`,
3627
+ name: "MultiComponentContainer",
3628
+ type: "Container",
3629
+ description: layoutReasoning,
3630
+ category: "dynamic",
3631
+ keywords: ["dashboard", "layout", "container"],
3632
+ props: {
3633
+ config: {
3634
+ components: matchedComponents
3635
+ },
3636
+ actions
3637
+ }
3638
+ };
3639
+ logger.info(`[${this.getProviderName()}] Created fallback MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions`);
3640
+ logCollector?.info(`Created MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
3641
+ }
3642
+ }
3021
3643
  return {
3022
3644
  success: true,
3023
3645
  data: {
3024
- text: result.text || "I apologize, but I was unable to generate a response.",
3025
- responseType: result.responseType || "answer",
3026
- confidence: result.confidence || 0.5,
3027
- reasoning: result.reasoning || "No reasoning provided"
3646
+ text: textResponse,
3647
+ matchedComponents,
3648
+ component: container_componet,
3649
+ layoutReasoning,
3650
+ actions,
3651
+ method: `${this.getProviderName()}-text-response`
3028
3652
  },
3029
3653
  errors: []
3030
3654
  };
3031
3655
  } catch (error) {
3032
3656
  const errorMsg = error instanceof Error ? error.message : String(error);
3033
3657
  logger.error(`[${this.getProviderName()}] Error generating text response: ${errorMsg}`);
3034
- logger.debug(`[${this.getProviderName()}] Text response generation error details:`, error);
3035
3658
  logCollector?.error(`Error generating text response: ${errorMsg}`);
3036
3659
  errors.push(errorMsg);
3037
3660
  return {
@@ -3039,9 +3662,9 @@ var BaseLLM = class {
3039
3662
  errors,
3040
3663
  data: {
3041
3664
  text: "I apologize, but I encountered an error while processing your question. Please try rephrasing or ask something else.",
3042
- responseType: "error",
3043
- confidence: 0,
3044
- reasoning: `Error: ${errorMsg}`
3665
+ matchedComponents: [],
3666
+ actions: [],
3667
+ method: `${this.getProviderName()}-text-response-error`
3045
3668
  }
3046
3669
  };
3047
3670
  }
@@ -3232,8 +3855,11 @@ var BaseLLM = class {
3232
3855
  * Supports both component generation and text response modes
3233
3856
  *
3234
3857
  * @param responseMode - 'component' for component generation (default), 'text' for text responses
3858
+ * @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
3859
+ * @param collections - Collection registry for executing database queries (required for text mode)
3235
3860
  */
3236
- async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component") {
3861
+ async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) {
3862
+ const startTime = Date.now();
3237
3863
  logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
3238
3864
  if (responseMode === "text") {
3239
3865
  logger.info(`[${this.getProviderName()}] Using text response mode`);
@@ -3242,25 +3868,23 @@ var BaseLLM = class {
3242
3868
  userPrompt,
3243
3869
  apiKey,
3244
3870
  logCollector,
3245
- conversationHistory
3871
+ conversationHistory,
3872
+ streamCallback,
3873
+ collections,
3874
+ components
3246
3875
  );
3247
3876
  if (!textResponse.success) {
3877
+ const elapsedTime3 = Date.now() - startTime;
3248
3878
  logger.error(`[${this.getProviderName()}] Text response generation failed`);
3879
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
3880
+ logCollector?.info(`Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
3249
3881
  return textResponse;
3250
3882
  }
3883
+ const elapsedTime2 = Date.now() - startTime;
3251
3884
  logger.info(`[${this.getProviderName()}] Text response generated successfully`);
3252
- return {
3253
- success: true,
3254
- data: {
3255
- textResponse: textResponse.data.text,
3256
- text: textResponse.data.text,
3257
- responseType: textResponse.data.responseType,
3258
- confidence: textResponse.data.confidence,
3259
- reasoning: textResponse.data.reasoning,
3260
- method: `${this.getProviderName()}-text-response`
3261
- },
3262
- errors: []
3263
- };
3885
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
3886
+ logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
3887
+ return textResponse;
3264
3888
  }
3265
3889
  const componentResponse = await this.generateComponentResponse(
3266
3890
  userPrompt,
@@ -3270,24 +3894,17 @@ var BaseLLM = class {
3270
3894
  conversationHistory
3271
3895
  );
3272
3896
  if (!componentResponse.success) {
3897
+ const elapsedTime2 = Date.now() - startTime;
3273
3898
  logger.error(`[${this.getProviderName()}] Component response generation failed`);
3899
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
3900
+ logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
3274
3901
  return componentResponse;
3275
3902
  }
3903
+ const elapsedTime = Date.now() - startTime;
3276
3904
  logger.info(`[${this.getProviderName()}] Component response generated successfully`);
3277
- return {
3278
- success: true,
3279
- data: {
3280
- component: componentResponse.data.component,
3281
- reasoning: componentResponse.data.reasoning,
3282
- method: componentResponse.data.method,
3283
- // Preserve the original method name
3284
- questionType: componentResponse.data.questionType,
3285
- needsMultipleComponents: componentResponse.data.needsMultipleComponents,
3286
- propsModified: componentResponse.data.propsModified,
3287
- queryModified: componentResponse.data.queryModified
3288
- },
3289
- errors: []
3290
- };
3905
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
3906
+ logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
3907
+ return componentResponse;
3291
3908
  }
3292
3909
  /**
3293
3910
  * Generate next questions that the user might ask based on the original prompt and generated component
@@ -3400,7 +4017,7 @@ function getLLMProviders() {
3400
4017
  return DEFAULT_PROVIDERS;
3401
4018
  }
3402
4019
  }
3403
- var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component") => {
4020
+ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
3404
4021
  logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
3405
4022
  logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
3406
4023
  const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -3412,11 +4029,11 @@ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conver
3412
4029
  return { success: false, errors: [emptyMsg] };
3413
4030
  }
3414
4031
  logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
3415
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode);
4032
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3416
4033
  logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
3417
4034
  return matchResult;
3418
4035
  };
3419
- var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component") => {
4036
+ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
3420
4037
  logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
3421
4038
  logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
3422
4039
  const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -3429,16 +4046,16 @@ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversatio
3429
4046
  return { success: false, errors: [emptyMsg] };
3430
4047
  }
3431
4048
  logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
3432
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode);
4049
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3433
4050
  logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
3434
4051
  return matchResult;
3435
4052
  };
3436
4053
  var getUserResponseFromCache = async (prompt) => {
3437
4054
  return false;
3438
4055
  };
3439
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory) => {
3440
- const responseMode = "component";
4056
+ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
3441
4057
  logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
4058
+ logger.debug(`[get_user_response] Response mode: ${responseMode}`);
3442
4059
  logger.debug("[get_user_response] Checking cache for existing response");
3443
4060
  const userResponse = await getUserResponseFromCache(prompt);
3444
4061
  if (userResponse) {
@@ -3469,16 +4086,16 @@ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey,
3469
4086
  logCollector?.info(attemptMsg);
3470
4087
  let result;
3471
4088
  if (provider === "anthropic") {
3472
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode);
4089
+ result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3473
4090
  } else if (provider === "groq") {
3474
- result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode);
4091
+ result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3475
4092
  } else {
3476
4093
  logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
3477
4094
  errors.push(`Unknown provider: ${provider}`);
3478
4095
  continue;
3479
4096
  }
3480
4097
  if (result.success) {
3481
- const successMsg = `Success with provider: ${provider} result: ${JSON.stringify(result)}`;
4098
+ const successMsg = `Success with provider: ${provider}`;
3482
4099
  logger.info(`${successMsg}`);
3483
4100
  logCollector?.info(successMsg);
3484
4101
  return result;
@@ -3699,8 +4316,7 @@ var CONTEXT_CONFIG = {
3699
4316
  };
3700
4317
 
3701
4318
  // src/handlers/user-prompt-request.ts
3702
- var processedMessageIds = /* @__PURE__ */ new Set();
3703
- var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders) => {
4319
+ var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) => {
3704
4320
  const errors = [];
3705
4321
  logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
3706
4322
  const parseResult = UserPromptRequestMessageSchema.safeParse(data);
@@ -3716,19 +4332,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3716
4332
  const prompt = payload.prompt;
3717
4333
  const SA_RUNTIME = payload.SA_RUNTIME;
3718
4334
  const wsId = userPromptRequest.from.id || "unknown";
3719
- logger.debug(`[REQUEST ${id}] Full request details - wsId: ${wsId}, prompt length: ${prompt.length}`);
3720
- if (processedMessageIds.has(id)) {
3721
- logger.warn(`[REQUEST ${id}] Duplicate request detected - ignoring`);
3722
- }
3723
- processedMessageIds.add(id);
3724
- logger.debug(`[REQUEST ${id}] Message ID marked as processed (${processedMessageIds.size} total)`);
3725
- if (processedMessageIds.size > 100) {
3726
- const firstId = processedMessageIds.values().next().value;
3727
- if (firstId) {
3728
- processedMessageIds.delete(firstId);
3729
- logger.debug(`[REQUEST ${id}] Cleaned up old message ID from cache`);
3730
- }
3731
- }
3732
4335
  if (!SA_RUNTIME) {
3733
4336
  errors.push("SA_RUNTIME is required");
3734
4337
  }
@@ -3743,9 +4346,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3743
4346
  if (!prompt) {
3744
4347
  errors.push("Prompt not found");
3745
4348
  }
3746
- if (!components || components.length === 0) {
3747
- errors.push("Components not found");
3748
- }
4349
+ logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
3749
4350
  if (errors.length > 0) {
3750
4351
  return { success: false, errors, id, wsId };
3751
4352
  }
@@ -3759,8 +4360,44 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3759
4360
  logCollector.info(`Starting user prompt request with ${components.length} components`);
3760
4361
  const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
3761
4362
  logger.info("conversationHistory", conversationHistory);
3762
- const userResponse = await get_user_response(prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory);
3763
- logger.info("llm userResponse", userResponse);
4363
+ const responseMode = payload.responseMode || "component";
4364
+ logger.info("responseMode", responseMode);
4365
+ let streamCallback;
4366
+ let accumulatedStreamResponse = "";
4367
+ if (responseMode === "text") {
4368
+ streamCallback = (chunk) => {
4369
+ accumulatedStreamResponse += chunk;
4370
+ logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
4371
+ const streamMessage = {
4372
+ id: `stream_${existingUiBlockId}`,
4373
+ // Different ID pattern for streaming
4374
+ type: "USER_PROMPT_STREAM",
4375
+ from: { type: "data-agent" },
4376
+ to: {
4377
+ type: "runtime",
4378
+ id: wsId
4379
+ },
4380
+ payload: {
4381
+ uiBlockId: existingUiBlockId,
4382
+ chunk
4383
+ }
4384
+ };
4385
+ sendMessage(streamMessage);
4386
+ logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
4387
+ };
4388
+ }
4389
+ const userResponse = await get_user_response(
4390
+ prompt,
4391
+ components,
4392
+ anthropicApiKey,
4393
+ groqApiKey,
4394
+ llmProviders,
4395
+ logCollector,
4396
+ conversationHistory,
4397
+ responseMode,
4398
+ streamCallback,
4399
+ collections
4400
+ );
3764
4401
  logCollector.info("User prompt request completed");
3765
4402
  const uiBlockId = existingUiBlockId;
3766
4403
  if (!userResponse.success) {
@@ -3777,16 +4414,21 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3777
4414
  }
3778
4415
  let component = null;
3779
4416
  let textResponse = null;
4417
+ let actions = [];
3780
4418
  if (userResponse.data) {
3781
4419
  if (typeof userResponse.data === "object") {
3782
4420
  if ("component" in userResponse.data) {
3783
4421
  component = userResponse.data.component;
3784
4422
  }
3785
4423
  if ("textResponse" in userResponse.data) {
3786
- textResponse = userResponse.data.textResponse;
4424
+ textResponse = userResponse.data.text;
4425
+ }
4426
+ if ("actions" in userResponse.data) {
4427
+ actions = userResponse.data.actions || [];
3787
4428
  }
3788
4429
  }
3789
4430
  }
4431
+ const finalTextResponse = responseMode === "text" && accumulatedStreamResponse ? accumulatedStreamResponse : textResponse;
3790
4432
  const uiBlock = new UIBlock(
3791
4433
  prompt,
3792
4434
  {},
@@ -3794,11 +4436,15 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3794
4436
  component,
3795
4437
  // generatedComponentMetadata: full component object (ComponentSchema)
3796
4438
  [],
3797
- // actions: empty initially
4439
+ // actions: empty initially, will be set below
3798
4440
  uiBlockId,
3799
- textResponse
3800
- // textResponse: text response from LLM
4441
+ finalTextResponse
4442
+ // textResponse: FULL streaming response including all intermediate messages
3801
4443
  );
4444
+ if (actions.length > 0) {
4445
+ uiBlock.setActions(actions);
4446
+ logger.info(`Stored ${actions.length} actions in UIBlock: ${uiBlockId}`);
4447
+ }
3802
4448
  thread.addUIBlock(uiBlock);
3803
4449
  logger.info(`Created UIBlock: ${uiBlockId} in Thread: ${threadId}`);
3804
4450
  return {
@@ -3811,8 +4457,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3811
4457
  wsId
3812
4458
  };
3813
4459
  };
3814
- async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders) {
3815
- const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders);
4460
+ async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) {
4461
+ const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections);
3816
4462
  sendDataResponse4(
3817
4463
  response.id || data.id,
3818
4464
  {
@@ -4036,7 +4682,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
4036
4682
  const uiBlockId = SA_RUNTIME.uiBlockId;
4037
4683
  const threadId = SA_RUNTIME.threadId;
4038
4684
  logger.debug(`[ACTIONS_REQ ${id}] SA_RUNTIME validated - threadId: ${threadId}, uiBlockId: ${uiBlockId}`);
4039
- logger.debug(`[ACTIONS_REQ ${id}] Retrieving thread: ${threadId}`);
4040
4685
  const threadManager = ThreadManager.getInstance();
4041
4686
  const thread = threadManager.getThread(threadId);
4042
4687
  if (!thread) {
@@ -4888,7 +5533,7 @@ function sendResponse5(id, res, sendMessage, clientId) {
4888
5533
  }
4889
5534
 
4890
5535
  // src/auth/user-manager.ts
4891
- import fs4 from "fs";
5536
+ import fs5 from "fs";
4892
5537
  import path4 from "path";
4893
5538
  import os from "os";
4894
5539
  var UserManager = class {
@@ -4928,19 +5573,19 @@ var UserManager = class {
4928
5573
  async loadUsersFromFile() {
4929
5574
  try {
4930
5575
  const dir = path4.dirname(this.filePath);
4931
- if (!fs4.existsSync(dir)) {
5576
+ if (!fs5.existsSync(dir)) {
4932
5577
  logger.info(`Creating directory structure: ${dir}`);
4933
- fs4.mkdirSync(dir, { recursive: true });
5578
+ fs5.mkdirSync(dir, { recursive: true });
4934
5579
  }
4935
- if (!fs4.existsSync(this.filePath)) {
5580
+ if (!fs5.existsSync(this.filePath)) {
4936
5581
  logger.info(`Users file does not exist at ${this.filePath}, creating with empty users`);
4937
5582
  const initialData = { users: [] };
4938
- fs4.writeFileSync(this.filePath, JSON.stringify(initialData, null, 4));
5583
+ fs5.writeFileSync(this.filePath, JSON.stringify(initialData, null, 4));
4939
5584
  this.users = [];
4940
5585
  this.hasChanged = false;
4941
5586
  return;
4942
5587
  }
4943
- const fileContent = fs4.readFileSync(this.filePath, "utf-8");
5588
+ const fileContent = fs5.readFileSync(this.filePath, "utf-8");
4944
5589
  const rawData = JSON.parse(fileContent);
4945
5590
  const validatedData = UsersDataSchema.parse(rawData);
4946
5591
  this.users = validatedData.users;
@@ -4960,15 +5605,15 @@ var UserManager = class {
4960
5605
  }
4961
5606
  try {
4962
5607
  const dir = path4.dirname(this.filePath);
4963
- if (!fs4.existsSync(dir)) {
4964
- fs4.mkdirSync(dir, { recursive: true });
5608
+ if (!fs5.existsSync(dir)) {
5609
+ fs5.mkdirSync(dir, { recursive: true });
4965
5610
  }
4966
5611
  const usersToSave = this.users.map((user) => {
4967
5612
  const { wsIds, ...userWithoutWsIds } = user;
4968
5613
  return userWithoutWsIds;
4969
5614
  });
4970
5615
  const data = { users: usersToSave };
4971
- fs4.writeFileSync(this.filePath, JSON.stringify(data, null, 4));
5616
+ fs5.writeFileSync(this.filePath, JSON.stringify(data, null, 4));
4972
5617
  this.hasChanged = false;
4973
5618
  logger.debug(`Synced ${this.users.length} users to file (wsIds excluded)`);
4974
5619
  } catch (error) {
@@ -5186,7 +5831,7 @@ var UserManager = class {
5186
5831
  };
5187
5832
 
5188
5833
  // src/dashboards/dashboard-manager.ts
5189
- import fs5 from "fs";
5834
+ import fs6 from "fs";
5190
5835
  import path5 from "path";
5191
5836
  import os2 from "os";
5192
5837
  var DashboardManager = class {
@@ -5221,12 +5866,12 @@ var DashboardManager = class {
5221
5866
  createDashboard(dashboardId, dashboard) {
5222
5867
  const dashboardPath = this.getDashboardPath(dashboardId);
5223
5868
  const dashboardDir = path5.dirname(dashboardPath);
5224
- if (fs5.existsSync(dashboardPath)) {
5869
+ if (fs6.existsSync(dashboardPath)) {
5225
5870
  throw new Error(`Dashboard '${dashboardId}' already exists`);
5226
5871
  }
5227
5872
  const validated = DSLRendererPropsSchema.parse(dashboard);
5228
- fs5.mkdirSync(dashboardDir, { recursive: true });
5229
- fs5.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5873
+ fs6.mkdirSync(dashboardDir, { recursive: true });
5874
+ fs6.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5230
5875
  logger.info(`Dashboard created: ${dashboardId}`);
5231
5876
  return validated;
5232
5877
  }
@@ -5237,12 +5882,12 @@ var DashboardManager = class {
5237
5882
  */
5238
5883
  getDashboard(dashboardId) {
5239
5884
  const dashboardPath = this.getDashboardPath(dashboardId);
5240
- if (!fs5.existsSync(dashboardPath)) {
5885
+ if (!fs6.existsSync(dashboardPath)) {
5241
5886
  logger.warn(`Dashboard not found: ${dashboardId}`);
5242
5887
  return null;
5243
5888
  }
5244
5889
  try {
5245
- const fileContent = fs5.readFileSync(dashboardPath, "utf-8");
5890
+ const fileContent = fs6.readFileSync(dashboardPath, "utf-8");
5246
5891
  const dashboard = JSON.parse(fileContent);
5247
5892
  const validated = DSLRendererPropsSchema.parse(dashboard);
5248
5893
  return validated;
@@ -5256,16 +5901,16 @@ var DashboardManager = class {
5256
5901
  * @returns Array of dashboard objects with their IDs
5257
5902
  */
5258
5903
  getAllDashboards() {
5259
- if (!fs5.existsSync(this.dashboardsBasePath)) {
5260
- fs5.mkdirSync(this.dashboardsBasePath, { recursive: true });
5904
+ if (!fs6.existsSync(this.dashboardsBasePath)) {
5905
+ fs6.mkdirSync(this.dashboardsBasePath, { recursive: true });
5261
5906
  return [];
5262
5907
  }
5263
5908
  const dashboards = [];
5264
5909
  try {
5265
- const dashboardDirs = fs5.readdirSync(this.dashboardsBasePath);
5910
+ const dashboardDirs = fs6.readdirSync(this.dashboardsBasePath);
5266
5911
  for (const dashboardId of dashboardDirs) {
5267
5912
  const dashboardPath = this.getDashboardPath(dashboardId);
5268
- if (fs5.existsSync(dashboardPath)) {
5913
+ if (fs6.existsSync(dashboardPath)) {
5269
5914
  const dashboard = this.getDashboard(dashboardId);
5270
5915
  if (dashboard) {
5271
5916
  dashboards.push({ dashboardId, dashboard });
@@ -5287,13 +5932,13 @@ var DashboardManager = class {
5287
5932
  */
5288
5933
  updateDashboard(dashboardId, dashboard) {
5289
5934
  const dashboardPath = this.getDashboardPath(dashboardId);
5290
- if (!fs5.existsSync(dashboardPath)) {
5935
+ if (!fs6.existsSync(dashboardPath)) {
5291
5936
  logger.warn(`Dashboard not found for update: ${dashboardId}`);
5292
5937
  return null;
5293
5938
  }
5294
5939
  try {
5295
5940
  const validated = DSLRendererPropsSchema.parse(dashboard);
5296
- fs5.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5941
+ fs6.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5297
5942
  logger.info(`Dashboard updated: ${dashboardId}`);
5298
5943
  return validated;
5299
5944
  } catch (error) {
@@ -5309,12 +5954,12 @@ var DashboardManager = class {
5309
5954
  deleteDashboard(dashboardId) {
5310
5955
  const dashboardPath = this.getDashboardPath(dashboardId);
5311
5956
  const dashboardDir = path5.dirname(dashboardPath);
5312
- if (!fs5.existsSync(dashboardPath)) {
5957
+ if (!fs6.existsSync(dashboardPath)) {
5313
5958
  logger.warn(`Dashboard not found for deletion: ${dashboardId}`);
5314
5959
  return false;
5315
5960
  }
5316
5961
  try {
5317
- fs5.rmSync(dashboardDir, { recursive: true, force: true });
5962
+ fs6.rmSync(dashboardDir, { recursive: true, force: true });
5318
5963
  logger.info(`Dashboard deleted: ${dashboardId}`);
5319
5964
  return true;
5320
5965
  } catch (error) {
@@ -5329,21 +5974,21 @@ var DashboardManager = class {
5329
5974
  */
5330
5975
  dashboardExists(dashboardId) {
5331
5976
  const dashboardPath = this.getDashboardPath(dashboardId);
5332
- return fs5.existsSync(dashboardPath);
5977
+ return fs6.existsSync(dashboardPath);
5333
5978
  }
5334
5979
  /**
5335
5980
  * Get dashboard count
5336
5981
  * @returns Number of dashboards
5337
5982
  */
5338
5983
  getDashboardCount() {
5339
- if (!fs5.existsSync(this.dashboardsBasePath)) {
5984
+ if (!fs6.existsSync(this.dashboardsBasePath)) {
5340
5985
  return 0;
5341
5986
  }
5342
5987
  try {
5343
- const dashboardDirs = fs5.readdirSync(this.dashboardsBasePath);
5988
+ const dashboardDirs = fs6.readdirSync(this.dashboardsBasePath);
5344
5989
  return dashboardDirs.filter((dir) => {
5345
5990
  const dashboardPath = this.getDashboardPath(dir);
5346
- return fs5.existsSync(dashboardPath);
5991
+ return fs6.existsSync(dashboardPath);
5347
5992
  }).length;
5348
5993
  } catch (error) {
5349
5994
  logger.error("Failed to get dashboard count:", error);
@@ -5353,7 +5998,7 @@ var DashboardManager = class {
5353
5998
  };
5354
5999
 
5355
6000
  // src/reports/report-manager.ts
5356
- import fs6 from "fs";
6001
+ import fs7 from "fs";
5357
6002
  import path6 from "path";
5358
6003
  import os3 from "os";
5359
6004
  var ReportManager = class {
@@ -5388,12 +6033,12 @@ var ReportManager = class {
5388
6033
  createReport(reportId, report) {
5389
6034
  const reportPath = this.getReportPath(reportId);
5390
6035
  const reportDir = path6.dirname(reportPath);
5391
- if (fs6.existsSync(reportPath)) {
6036
+ if (fs7.existsSync(reportPath)) {
5392
6037
  throw new Error(`Report '${reportId}' already exists`);
5393
6038
  }
5394
6039
  const validated = DSLRendererPropsSchema2.parse(report);
5395
- fs6.mkdirSync(reportDir, { recursive: true });
5396
- fs6.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
6040
+ fs7.mkdirSync(reportDir, { recursive: true });
6041
+ fs7.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
5397
6042
  logger.info(`Report created: ${reportId}`);
5398
6043
  return validated;
5399
6044
  }
@@ -5404,12 +6049,12 @@ var ReportManager = class {
5404
6049
  */
5405
6050
  getReport(reportId) {
5406
6051
  const reportPath = this.getReportPath(reportId);
5407
- if (!fs6.existsSync(reportPath)) {
6052
+ if (!fs7.existsSync(reportPath)) {
5408
6053
  logger.warn(`Report not found: ${reportId}`);
5409
6054
  return null;
5410
6055
  }
5411
6056
  try {
5412
- const fileContent = fs6.readFileSync(reportPath, "utf-8");
6057
+ const fileContent = fs7.readFileSync(reportPath, "utf-8");
5413
6058
  const report = JSON.parse(fileContent);
5414
6059
  const validated = DSLRendererPropsSchema2.parse(report);
5415
6060
  return validated;
@@ -5423,16 +6068,16 @@ var ReportManager = class {
5423
6068
  * @returns Array of report objects with their IDs
5424
6069
  */
5425
6070
  getAllReports() {
5426
- if (!fs6.existsSync(this.reportsBasePath)) {
5427
- fs6.mkdirSync(this.reportsBasePath, { recursive: true });
6071
+ if (!fs7.existsSync(this.reportsBasePath)) {
6072
+ fs7.mkdirSync(this.reportsBasePath, { recursive: true });
5428
6073
  return [];
5429
6074
  }
5430
6075
  const reports = [];
5431
6076
  try {
5432
- const reportDirs = fs6.readdirSync(this.reportsBasePath);
6077
+ const reportDirs = fs7.readdirSync(this.reportsBasePath);
5433
6078
  for (const reportId of reportDirs) {
5434
6079
  const reportPath = this.getReportPath(reportId);
5435
- if (fs6.existsSync(reportPath)) {
6080
+ if (fs7.existsSync(reportPath)) {
5436
6081
  const report = this.getReport(reportId);
5437
6082
  if (report) {
5438
6083
  reports.push({ reportId, report });
@@ -5454,13 +6099,13 @@ var ReportManager = class {
5454
6099
  */
5455
6100
  updateReport(reportId, report) {
5456
6101
  const reportPath = this.getReportPath(reportId);
5457
- if (!fs6.existsSync(reportPath)) {
6102
+ if (!fs7.existsSync(reportPath)) {
5458
6103
  logger.warn(`Report not found for update: ${reportId}`);
5459
6104
  return null;
5460
6105
  }
5461
6106
  try {
5462
6107
  const validated = DSLRendererPropsSchema2.parse(report);
5463
- fs6.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
6108
+ fs7.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
5464
6109
  logger.info(`Report updated: ${reportId}`);
5465
6110
  return validated;
5466
6111
  } catch (error) {
@@ -5476,12 +6121,12 @@ var ReportManager = class {
5476
6121
  deleteReport(reportId) {
5477
6122
  const reportPath = this.getReportPath(reportId);
5478
6123
  const reportDir = path6.dirname(reportPath);
5479
- if (!fs6.existsSync(reportPath)) {
6124
+ if (!fs7.existsSync(reportPath)) {
5480
6125
  logger.warn(`Report not found for deletion: ${reportId}`);
5481
6126
  return false;
5482
6127
  }
5483
6128
  try {
5484
- fs6.rmSync(reportDir, { recursive: true, force: true });
6129
+ fs7.rmSync(reportDir, { recursive: true, force: true });
5485
6130
  logger.info(`Report deleted: ${reportId}`);
5486
6131
  return true;
5487
6132
  } catch (error) {
@@ -5496,21 +6141,21 @@ var ReportManager = class {
5496
6141
  */
5497
6142
  reportExists(reportId) {
5498
6143
  const reportPath = this.getReportPath(reportId);
5499
- return fs6.existsSync(reportPath);
6144
+ return fs7.existsSync(reportPath);
5500
6145
  }
5501
6146
  /**
5502
6147
  * Get report count
5503
6148
  * @returns Number of reports
5504
6149
  */
5505
6150
  getReportCount() {
5506
- if (!fs6.existsSync(this.reportsBasePath)) {
6151
+ if (!fs7.existsSync(this.reportsBasePath)) {
5507
6152
  return 0;
5508
6153
  }
5509
6154
  try {
5510
- const reportDirs = fs6.readdirSync(this.reportsBasePath);
6155
+ const reportDirs = fs7.readdirSync(this.reportsBasePath);
5511
6156
  return reportDirs.filter((dir) => {
5512
6157
  const reportPath = this.getReportPath(dir);
5513
- return fs6.existsSync(reportPath);
6158
+ return fs7.existsSync(reportPath);
5514
6159
  }).length;
5515
6160
  } catch (error) {
5516
6161
  logger.error("Failed to get report count:", error);
@@ -5734,9 +6379,6 @@ var SuperatomSDK = class {
5734
6379
  });
5735
6380
  this.initializeDashboardManager();
5736
6381
  this.initializeReportManager();
5737
- this.connect().catch((error) => {
5738
- logger.error("Failed to connect to Superatom:", error);
5739
- });
5740
6382
  }
5741
6383
  /**
5742
6384
  * Initialize PromptLoader and load prompts into memory
@@ -5802,6 +6444,10 @@ var SuperatomSDK = class {
5802
6444
  * Connect to the Superatom WebSocket service
5803
6445
  */
5804
6446
  async connect() {
6447
+ if (this.connected && this.ws && this.ws.readyState === this.ws.OPEN) {
6448
+ logger.info("Already connected to WebSocket");
6449
+ return Promise.resolve();
6450
+ }
5805
6451
  return new Promise((resolve, reject) => {
5806
6452
  try {
5807
6453
  const url = new URL(this.url);
@@ -5864,7 +6510,7 @@ var SuperatomSDK = class {
5864
6510
  });
5865
6511
  break;
5866
6512
  case "USER_PROMPT_REQ":
5867
- handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders).catch((error) => {
6513
+ handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections).catch((error) => {
5868
6514
  logger.error("Failed to handle user prompt request:", error);
5869
6515
  });
5870
6516
  break;