@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.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __commonJS = (cb, mod) => function __require() {
9
12
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
13
  };
@@ -102,7 +105,7 @@ var require_package = __commonJS({
102
105
  var require_main = __commonJS({
103
106
  "node_modules/dotenv/lib/main.js"(exports2, module2) {
104
107
  "use strict";
105
- var fs7 = require("fs");
108
+ var fs8 = require("fs");
106
109
  var path7 = require("path");
107
110
  var os4 = require("os");
108
111
  var crypto2 = require("crypto");
@@ -244,7 +247,7 @@ var require_main = __commonJS({
244
247
  if (options && options.path && options.path.length > 0) {
245
248
  if (Array.isArray(options.path)) {
246
249
  for (const filepath of options.path) {
247
- if (fs7.existsSync(filepath)) {
250
+ if (fs8.existsSync(filepath)) {
248
251
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
249
252
  }
250
253
  }
@@ -254,7 +257,7 @@ var require_main = __commonJS({
254
257
  } else {
255
258
  possibleVaultPath = path7.resolve(process.cwd(), ".env.vault");
256
259
  }
257
- if (fs7.existsSync(possibleVaultPath)) {
260
+ if (fs8.existsSync(possibleVaultPath)) {
258
261
  return possibleVaultPath;
259
262
  }
260
263
  return null;
@@ -307,7 +310,7 @@ var require_main = __commonJS({
307
310
  const parsedAll = {};
308
311
  for (const path8 of optionPaths) {
309
312
  try {
310
- const parsed = DotenvModule.parse(fs7.readFileSync(path8, { encoding }));
313
+ const parsed = DotenvModule.parse(fs8.readFileSync(path8, { encoding }));
311
314
  DotenvModule.populate(parsedAll, parsed, options);
312
315
  } catch (e) {
313
316
  if (debug) {
@@ -428,6 +431,137 @@ var require_main = __commonJS({
428
431
  }
429
432
  });
430
433
 
434
+ // src/userResponse/utils.ts
435
+ var utils_exports = {};
436
+ __export(utils_exports, {
437
+ convertQuestionsToActions: () => convertQuestionsToActions,
438
+ convertTopToLimit: () => convertTopToLimit,
439
+ ensureQueryLimit: () => ensureQueryLimit,
440
+ fixScalarSubqueries: () => fixScalarSubqueries,
441
+ getJsonSizeInBytes: () => getJsonSizeInBytes,
442
+ validateMessageSize: () => validateMessageSize
443
+ });
444
+ function convertTopToLimit(query) {
445
+ if (!query || query.trim().length === 0) {
446
+ return query;
447
+ }
448
+ let modifiedQuery = query.replace(/\bSELECT\s+TOP\s+(\d+)\b/gi, "SELECT");
449
+ if (modifiedQuery !== query) {
450
+ console.warn(`\u26A0\uFE0F Query had TOP syntax. Converting to LIMIT for Snowflake compatibility.`);
451
+ }
452
+ return modifiedQuery;
453
+ }
454
+ function ensureQueryLimit(query, defaultLimit = 32, maxLimit = 32) {
455
+ if (!query || query.trim().length === 0) {
456
+ return query;
457
+ }
458
+ let trimmedQuery = query.trim();
459
+ const isSelectQuery = /^\s*SELECT\b/i.test(trimmedQuery) || /^\s*WITH\b.*\bSELECT\b/is.test(trimmedQuery);
460
+ if (!isSelectQuery) {
461
+ return query;
462
+ }
463
+ trimmedQuery = convertTopToLimit(trimmedQuery);
464
+ const hadSemicolon = trimmedQuery.endsWith(";");
465
+ if (hadSemicolon) {
466
+ trimmedQuery = trimmedQuery.slice(0, -1).trim();
467
+ }
468
+ const limitMatches = trimmedQuery.match(/\bLIMIT\s+(\d+)\b/gi);
469
+ if (limitMatches && limitMatches.length > 0) {
470
+ if (limitMatches.length > 1) {
471
+ console.warn(`\u26A0\uFE0F Query had ${limitMatches.length} LIMIT clauses. Removing duplicates...`);
472
+ trimmedQuery = trimmedQuery.replace(/\s*\bLIMIT\s+\d+\b/gi, "").trim();
473
+ } else {
474
+ const existingLimitMatch = trimmedQuery.match(/\bLIMIT\s+(\d+)\b/i);
475
+ if (existingLimitMatch) {
476
+ const existingLimit = parseInt(existingLimitMatch[1], 10);
477
+ if (existingLimit <= maxLimit) {
478
+ if (hadSemicolon) {
479
+ trimmedQuery += ";";
480
+ }
481
+ return trimmedQuery;
482
+ }
483
+ console.warn(`\u26A0\uFE0F Query LIMIT ${existingLimit} exceeds maximum of ${maxLimit}. Reducing to ${maxLimit}...`);
484
+ trimmedQuery = trimmedQuery.replace(/\bLIMIT\s+\d+\b/i, `LIMIT ${maxLimit}`);
485
+ if (hadSemicolon) {
486
+ trimmedQuery += ";";
487
+ }
488
+ return trimmedQuery;
489
+ }
490
+ }
491
+ }
492
+ trimmedQuery = `${trimmedQuery} LIMIT ${defaultLimit}`;
493
+ if (hadSemicolon) {
494
+ trimmedQuery += ";";
495
+ }
496
+ return trimmedQuery;
497
+ }
498
+ function fixScalarSubqueries(query) {
499
+ if (!query || query.trim().length === 0) {
500
+ return query;
501
+ }
502
+ let modifiedQuery = query;
503
+ let hasChanges = false;
504
+ const scalarOperatorPattern = /([=<>!]=?|<>)\s*\(\s*SELECT\s/gi;
505
+ const matches = [...modifiedQuery.matchAll(scalarOperatorPattern)];
506
+ for (let i = matches.length - 1; i >= 0; i--) {
507
+ const match = matches[i];
508
+ const startPos = match.index + match[0].length - "SELECT ".length;
509
+ let parenDepth = 1;
510
+ let endPos = startPos;
511
+ let foundEnd = false;
512
+ for (let j = startPos; j < modifiedQuery.length; j++) {
513
+ const char = modifiedQuery[j];
514
+ if (char === "(") parenDepth++;
515
+ if (char === ")") {
516
+ parenDepth--;
517
+ if (parenDepth === 0) {
518
+ endPos = j;
519
+ foundEnd = true;
520
+ break;
521
+ }
522
+ }
523
+ }
524
+ if (!foundEnd) continue;
525
+ const subquery = modifiedQuery.substring(startPos, endPos);
526
+ if (/\bLIMIT\s+\d+/i.test(subquery)) {
527
+ continue;
528
+ }
529
+ const fixedSubquery = subquery.trim() + " LIMIT 1";
530
+ modifiedQuery = modifiedQuery.substring(0, startPos) + fixedSubquery + modifiedQuery.substring(endPos);
531
+ hasChanges = true;
532
+ console.warn(`\u26A0\uFE0F Fixed scalar subquery: added LIMIT 1 to prevent multiple row error`);
533
+ }
534
+ if (hasChanges) {
535
+ console.log("\u2713 Query validated and fixed for PostgreSQL scalar subquery compatibility");
536
+ }
537
+ return modifiedQuery;
538
+ }
539
+ function convertQuestionsToActions(questions) {
540
+ return questions.map((question, index) => ({
541
+ id: `action_${index}_${Date.now()}`,
542
+ name: question,
543
+ type: "next_question",
544
+ question
545
+ }));
546
+ }
547
+ function getJsonSizeInBytes(obj) {
548
+ const jsonString = JSON.stringify(obj);
549
+ return Buffer.byteLength(jsonString, "utf8");
550
+ }
551
+ function validateMessageSize(message, maxSize = 1048576) {
552
+ const size = getJsonSizeInBytes(message);
553
+ return {
554
+ isValid: size <= maxSize,
555
+ size,
556
+ maxSize
557
+ };
558
+ }
559
+ var init_utils = __esm({
560
+ "src/userResponse/utils.ts"() {
561
+ "use strict";
562
+ }
563
+ });
564
+
431
565
  // src/index.ts
432
566
  var index_exports = {};
433
567
  __export(index_exports, {
@@ -707,7 +841,8 @@ var UserPromptRequestPayloadSchema = import_zod3.z.object({
707
841
  SA_RUNTIME: import_zod3.z.object({
708
842
  threadId: import_zod3.z.string(),
709
843
  uiBlockId: import_zod3.z.string()
710
- }).optional()
844
+ }).optional(),
845
+ responseMode: import_zod3.z.enum(["component", "text"]).optional()
711
846
  });
712
847
  var UserPromptRequestMessageSchema = import_zod3.z.object({
713
848
  id: import_zod3.z.string(),
@@ -729,7 +864,8 @@ var ComponentPropsSchema = import_zod3.z.object({
729
864
  query: import_zod3.z.string().optional(),
730
865
  title: import_zod3.z.string().optional(),
731
866
  description: import_zod3.z.string().optional(),
732
- config: import_zod3.z.record(import_zod3.z.unknown()).optional()
867
+ config: import_zod3.z.record(import_zod3.z.unknown()).optional(),
868
+ actions: import_zod3.z.array(import_zod3.z.any()).optional()
733
869
  });
734
870
  var ComponentSchema = import_zod3.z.object({
735
871
  id: import_zod3.z.string(),
@@ -822,7 +958,9 @@ var ReportsRequestMessageSchema = import_zod3.z.object({
822
958
  });
823
959
 
824
960
  // src/utils/logger.ts
961
+ var import_fs = __toESM(require("fs"));
825
962
  var PREFIX = "[SuperatomSDK]";
963
+ var LOGSTREAM = import_fs.default.createWriteStream("superatom-sdk.log", { flags: "a" });
826
964
  var LOG_LEVEL_PRIORITY = {
827
965
  errors: 0,
828
966
  warnings: 1,
@@ -906,6 +1044,9 @@ var Logger = class {
906
1044
  console.log(PREFIX, "[DEBUG]", ...args);
907
1045
  }
908
1046
  }
1047
+ file(...args) {
1048
+ LOGSTREAM.write(args.join(" ") + "\n");
1049
+ }
909
1050
  };
910
1051
  var logger = new Logger();
911
1052
 
@@ -977,6 +1118,9 @@ var UIBlock = class {
977
1118
  getComponentMetadata() {
978
1119
  return this.generatedComponentMetadata;
979
1120
  }
1121
+ getTextResponse() {
1122
+ return this.textResponse || "";
1123
+ }
980
1124
  /**
981
1125
  * Set or update component metadata
982
1126
  */
@@ -1069,12 +1213,6 @@ var UIBlock = class {
1069
1213
  const processedData = this.processDataForStorage(data);
1070
1214
  this.componentData = { ...this.componentData, ...processedData };
1071
1215
  }
1072
- /**
1073
- * Get text response
1074
- */
1075
- getTextResponse() {
1076
- return this.textResponse;
1077
- }
1078
1216
  /**
1079
1217
  * Set or update text response
1080
1218
  */
@@ -1110,6 +1248,12 @@ var UIBlock = class {
1110
1248
  throw error;
1111
1249
  }
1112
1250
  }
1251
+ /**
1252
+ * Set or replace all actions
1253
+ */
1254
+ setActions(actions) {
1255
+ this.actions = actions;
1256
+ }
1113
1257
  /**
1114
1258
  * Add a single action (only if actions are resolved)
1115
1259
  */
@@ -1265,8 +1409,11 @@ var Thread = class {
1265
1409
  const questionNum = index + 1;
1266
1410
  const question = block.getUserQuestion();
1267
1411
  const metadata = block.getComponentMetadata();
1268
- let componentSummary = "No component generated";
1269
- if (metadata && Object.keys(metadata).length > 0) {
1412
+ const textResponse = block.getTextResponse();
1413
+ let assistantResponse = "";
1414
+ const hasComponent = metadata && Object.keys(metadata).length > 0 && metadata.type;
1415
+ const hasTextResponse = textResponse && textResponse.trim().length > 0;
1416
+ if (hasComponent) {
1270
1417
  const parts = [];
1271
1418
  if (metadata.type) {
1272
1419
  parts.push(`Component Type: ${metadata.type}`);
@@ -1286,11 +1433,17 @@ var Thread = class {
1286
1433
  const componentTypes = metadata.props.config.components.map((c) => c.type).join(", ");
1287
1434
  parts.push(`Multi-component with: ${componentTypes}`);
1288
1435
  }
1289
- componentSummary = parts.join(", ");
1436
+ assistantResponse = parts.join(", ");
1437
+ } else if (hasTextResponse) {
1438
+ assistantResponse = textResponse;
1439
+ } else {
1440
+ assistantResponse = "No response generated";
1290
1441
  }
1291
- contextLines.push(`Q${questionNum}: ${question}`);
1292
- contextLines.push(`A${questionNum}: ${componentSummary}`);
1293
- contextLines.push("");
1442
+ contextLines.push(`User:
1443
+ ${question}`);
1444
+ contextLines.push(`Assistant:
1445
+ ${assistantResponse}`);
1446
+ contextLines.push("---");
1294
1447
  });
1295
1448
  return contextLines.join("\n").trim();
1296
1449
  }
@@ -1466,7 +1619,7 @@ function sendDataResponse(id, collection, op, data, meta, sendMessage) {
1466
1619
  }
1467
1620
 
1468
1621
  // src/bundle.ts
1469
- var fs = __toESM(require("fs"));
1622
+ var fs2 = __toESM(require("fs"));
1470
1623
  var path = __toESM(require("path"));
1471
1624
  function getBundleDir(configDir) {
1472
1625
  const bundleDir = configDir || process.env.SA_BUNDLE_DIR;
@@ -1479,16 +1632,16 @@ function getBundleDir(configDir) {
1479
1632
  }
1480
1633
  function getJS(bundleDir) {
1481
1634
  try {
1482
- if (!fs.existsSync(bundleDir)) {
1635
+ if (!fs2.existsSync(bundleDir)) {
1483
1636
  throw new Error(`Bundle directory does not exist: ${bundleDir}`);
1484
1637
  }
1485
- const stats = fs.statSync(bundleDir);
1638
+ const stats = fs2.statSync(bundleDir);
1486
1639
  if (!stats.isDirectory()) {
1487
1640
  throw new Error(`Bundle path is not a directory: ${bundleDir}`);
1488
1641
  }
1489
1642
  let files;
1490
1643
  try {
1491
- files = fs.readdirSync(bundleDir);
1644
+ files = fs2.readdirSync(bundleDir);
1492
1645
  } catch (error) {
1493
1646
  const errorMessage = error instanceof Error ? error.message : String(error);
1494
1647
  throw new Error(`Failed to read bundle directory: ${errorMessage}`);
@@ -1503,7 +1656,7 @@ function getJS(bundleDir) {
1503
1656
  const filePath = path.join(bundleDir, indexFile);
1504
1657
  logger.info(`Loading bundle from ${filePath}`);
1505
1658
  try {
1506
- return fs.readFileSync(filePath, "utf8");
1659
+ return fs2.readFileSync(filePath, "utf8");
1507
1660
  } catch (error) {
1508
1661
  const errorMessage = error instanceof Error ? error.message : String(error);
1509
1662
  throw new Error(`Failed to read bundle file: ${errorMessage}`);
@@ -1909,94 +2062,12 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
1909
2062
  // src/userResponse/groq.ts
1910
2063
  var import_dotenv = __toESM(require_main());
1911
2064
 
1912
- // src/userResponse/utils.ts
1913
- function convertTopToLimit(query) {
1914
- if (!query || query.trim().length === 0) {
1915
- return query;
1916
- }
1917
- let modifiedQuery = query.replace(/\bSELECT\s+TOP\s+(\d+)\b/gi, "SELECT");
1918
- if (modifiedQuery !== query) {
1919
- console.warn(`\u26A0\uFE0F Query had TOP syntax. Converting to LIMIT for Snowflake compatibility.`);
1920
- }
1921
- return modifiedQuery;
1922
- }
1923
- function ensureQueryLimit(query, defaultLimit = 50) {
1924
- if (!query || query.trim().length === 0) {
1925
- return query;
1926
- }
1927
- let trimmedQuery = query.trim();
1928
- const isSelectQuery = /^\s*SELECT\b/i.test(trimmedQuery) || /^\s*WITH\b.*\bSELECT\b/is.test(trimmedQuery);
1929
- if (!isSelectQuery) {
1930
- return query;
1931
- }
1932
- trimmedQuery = convertTopToLimit(trimmedQuery);
1933
- const hadSemicolon = trimmedQuery.endsWith(";");
1934
- if (hadSemicolon) {
1935
- trimmedQuery = trimmedQuery.slice(0, -1).trim();
1936
- }
1937
- const limitMatches = trimmedQuery.match(/\bLIMIT\s+\d+\b/gi);
1938
- if (limitMatches && limitMatches.length > 0) {
1939
- if (limitMatches.length > 1) {
1940
- console.warn(`\u26A0\uFE0F Query had ${limitMatches.length} LIMIT clauses. Removing duplicates...`);
1941
- trimmedQuery = trimmedQuery.replace(/\s*\bLIMIT\s+\d+\b/gi, "").trim();
1942
- } else {
1943
- if (hadSemicolon) {
1944
- trimmedQuery += ";";
1945
- }
1946
- return trimmedQuery;
1947
- }
1948
- }
1949
- trimmedQuery = `${trimmedQuery} LIMIT ${defaultLimit}`;
1950
- if (hadSemicolon) {
1951
- trimmedQuery += ";";
1952
- }
1953
- return trimmedQuery;
1954
- }
1955
- function fixScalarSubqueries(query) {
1956
- if (!query || query.trim().length === 0) {
1957
- return query;
1958
- }
1959
- let modifiedQuery = query;
1960
- let hasChanges = false;
1961
- const scalarOperatorPattern = /([=<>!]=?|<>)\s*\(\s*SELECT\s/gi;
1962
- const matches = [...modifiedQuery.matchAll(scalarOperatorPattern)];
1963
- for (let i = matches.length - 1; i >= 0; i--) {
1964
- const match = matches[i];
1965
- const startPos = match.index + match[0].length - "SELECT ".length;
1966
- let parenDepth = 1;
1967
- let endPos = startPos;
1968
- let foundEnd = false;
1969
- for (let j = startPos; j < modifiedQuery.length; j++) {
1970
- const char = modifiedQuery[j];
1971
- if (char === "(") parenDepth++;
1972
- if (char === ")") {
1973
- parenDepth--;
1974
- if (parenDepth === 0) {
1975
- endPos = j;
1976
- foundEnd = true;
1977
- break;
1978
- }
1979
- }
1980
- }
1981
- if (!foundEnd) continue;
1982
- const subquery = modifiedQuery.substring(startPos, endPos);
1983
- if (/\bLIMIT\s+\d+/i.test(subquery)) {
1984
- continue;
1985
- }
1986
- const fixedSubquery = subquery.trim() + " LIMIT 1";
1987
- modifiedQuery = modifiedQuery.substring(0, startPos) + fixedSubquery + modifiedQuery.substring(endPos);
1988
- hasChanges = true;
1989
- console.warn(`\u26A0\uFE0F Fixed scalar subquery: added LIMIT 1 to prevent multiple row error`);
1990
- }
1991
- if (hasChanges) {
1992
- console.log("\u2713 Query validated and fixed for PostgreSQL scalar subquery compatibility");
1993
- }
1994
- return modifiedQuery;
1995
- }
2065
+ // src/userResponse/base-llm.ts
2066
+ init_utils();
1996
2067
 
1997
2068
  // src/userResponse/schema.ts
1998
2069
  var import_path = __toESM(require("path"));
1999
- var import_fs = __toESM(require("fs"));
2070
+ var import_fs2 = __toESM(require("fs"));
2000
2071
  var Schema = class {
2001
2072
  constructor(schemaFilePath) {
2002
2073
  this.cachedSchema = null;
@@ -2010,11 +2081,11 @@ var Schema = class {
2010
2081
  logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
2011
2082
  try {
2012
2083
  const dir = import_path.default.dirname(this.schemaFilePath);
2013
- if (!import_fs.default.existsSync(dir)) {
2084
+ if (!import_fs2.default.existsSync(dir)) {
2014
2085
  logger.info(`Creating directory structure: ${dir}`);
2015
- import_fs.default.mkdirSync(dir, { recursive: true });
2086
+ import_fs2.default.mkdirSync(dir, { recursive: true });
2016
2087
  }
2017
- if (!import_fs.default.existsSync(this.schemaFilePath)) {
2088
+ if (!import_fs2.default.existsSync(this.schemaFilePath)) {
2018
2089
  logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
2019
2090
  const initialSchema = {
2020
2091
  database: "",
@@ -2023,11 +2094,11 @@ var Schema = class {
2023
2094
  tables: [],
2024
2095
  relationships: []
2025
2096
  };
2026
- import_fs.default.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
2097
+ import_fs2.default.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
2027
2098
  this.cachedSchema = initialSchema;
2028
2099
  return initialSchema;
2029
2100
  }
2030
- const fileContent = import_fs.default.readFileSync(this.schemaFilePath, "utf-8");
2101
+ const fileContent = import_fs2.default.readFileSync(this.schemaFilePath, "utf-8");
2031
2102
  const schema2 = JSON.parse(fileContent);
2032
2103
  this.cachedSchema = schema2;
2033
2104
  return schema2;
@@ -2128,7 +2199,7 @@ var Schema = class {
2128
2199
  var schema = new Schema();
2129
2200
 
2130
2201
  // src/userResponse/prompt-loader.ts
2131
- var import_fs2 = __toESM(require("fs"));
2202
+ var import_fs3 = __toESM(require("fs"));
2132
2203
  var import_path2 = __toESM(require("path"));
2133
2204
  var PromptLoader = class {
2134
2205
  constructor(config) {
@@ -2156,7 +2227,8 @@ var PromptLoader = class {
2156
2227
  "mutli-component",
2157
2228
  "actions",
2158
2229
  "container-metadata",
2159
- "text-response"
2230
+ "text-response",
2231
+ "match-text-components"
2160
2232
  ];
2161
2233
  for (const promptType of promptTypes) {
2162
2234
  try {
@@ -2181,9 +2253,9 @@ var PromptLoader = class {
2181
2253
  try {
2182
2254
  const systemPath = import_path2.default.join(dir, promptName, "system.md");
2183
2255
  const userPath = import_path2.default.join(dir, promptName, "user.md");
2184
- if (import_fs2.default.existsSync(systemPath) && import_fs2.default.existsSync(userPath)) {
2185
- const system = import_fs2.default.readFileSync(systemPath, "utf-8");
2186
- const user = import_fs2.default.readFileSync(userPath, "utf-8");
2256
+ if (import_fs3.default.existsSync(systemPath) && import_fs3.default.existsSync(userPath)) {
2257
+ const system = import_fs3.default.readFileSync(systemPath, "utf-8");
2258
+ const user = import_fs3.default.readFileSync(userPath, "utf-8");
2187
2259
  logger.debug(`Loaded prompt '${promptName}' from ${dir}`);
2188
2260
  return { system, user };
2189
2261
  }
@@ -2305,6 +2377,14 @@ var LLM = class {
2305
2377
  throw new Error(`Unsupported provider: ${provider}. Use "anthropic" or "groq"`);
2306
2378
  }
2307
2379
  }
2380
+ /* Stream response with tool calling support (Anthropic only for now) */
2381
+ static async streamWithTools(messages, tools, toolHandler, options = {}, maxIterations = 3) {
2382
+ const [provider, modelName] = this._parseModel(options.model);
2383
+ if (provider !== "anthropic") {
2384
+ throw new Error(`Tool calling is only supported for Anthropic models`);
2385
+ }
2386
+ return this._anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations);
2387
+ }
2308
2388
  // ============================================================
2309
2389
  // PRIVATE HELPER METHODS
2310
2390
  // ============================================================
@@ -2382,6 +2462,86 @@ var LLM = class {
2382
2462
  }
2383
2463
  return fullText;
2384
2464
  }
2465
+ static async _anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations) {
2466
+ const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || "";
2467
+ const client = new import_sdk.default({
2468
+ apiKey
2469
+ });
2470
+ const conversationMessages = [{
2471
+ role: "user",
2472
+ content: messages.user
2473
+ }];
2474
+ let iterations = 0;
2475
+ let finalText = "";
2476
+ while (iterations < maxIterations) {
2477
+ iterations++;
2478
+ const response = await client.messages.create({
2479
+ model: modelName,
2480
+ max_tokens: options.maxTokens || 4e3,
2481
+ temperature: options.temperature,
2482
+ system: messages.sys,
2483
+ messages: conversationMessages,
2484
+ tools
2485
+ });
2486
+ if (response.stop_reason === "end_turn") {
2487
+ const textBlock = response.content.find((block) => block.type === "text");
2488
+ if (textBlock && textBlock.type === "text") {
2489
+ finalText = textBlock.text;
2490
+ if (options.partial) {
2491
+ options.partial(finalText);
2492
+ }
2493
+ }
2494
+ break;
2495
+ }
2496
+ if (response.stop_reason === "tool_use") {
2497
+ const toolUses = response.content.filter((block) => block.type === "tool_use");
2498
+ if (toolUses.length === 0) {
2499
+ break;
2500
+ }
2501
+ conversationMessages.push({
2502
+ role: "assistant",
2503
+ content: response.content
2504
+ });
2505
+ const toolResults = {
2506
+ role: "user",
2507
+ content: []
2508
+ };
2509
+ for (const toolUse of toolUses) {
2510
+ if (toolUse.type === "tool_use") {
2511
+ try {
2512
+ const result = await toolHandler(toolUse.name, toolUse.input);
2513
+ toolResults.content.push({
2514
+ type: "tool_result",
2515
+ tool_use_id: toolUse.id,
2516
+ content: typeof result === "string" ? result : JSON.stringify(result)
2517
+ });
2518
+ } catch (error) {
2519
+ toolResults.content.push({
2520
+ type: "tool_result",
2521
+ tool_use_id: toolUse.id,
2522
+ content: error instanceof Error ? error.message : String(error),
2523
+ is_error: true
2524
+ });
2525
+ }
2526
+ }
2527
+ }
2528
+ conversationMessages.push(toolResults);
2529
+ } else {
2530
+ const textBlock = response.content.find((block) => block.type === "text");
2531
+ if (textBlock && textBlock.type === "text") {
2532
+ finalText = textBlock.text;
2533
+ if (options.partial) {
2534
+ options.partial(finalText);
2535
+ }
2536
+ }
2537
+ break;
2538
+ }
2539
+ }
2540
+ if (iterations >= maxIterations) {
2541
+ throw new Error(`Max iterations (${maxIterations}) reached in tool calling loop`);
2542
+ }
2543
+ return finalText;
2544
+ }
2385
2545
  // ============================================================
2386
2546
  // GROQ IMPLEMENTATION
2387
2547
  // ============================================================
@@ -2456,6 +2616,42 @@ var LLM = class {
2456
2616
  }
2457
2617
  };
2458
2618
 
2619
+ // src/userResponse/knowledge-base.ts
2620
+ var getKnowledgeBase = async ({
2621
+ prompt,
2622
+ collections,
2623
+ topK = 1
2624
+ }) => {
2625
+ try {
2626
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
2627
+ logger.info("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
2628
+ return "";
2629
+ }
2630
+ logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
2631
+ const result = await collections["knowledge-base"]["query"]({
2632
+ prompt,
2633
+ topK
2634
+ });
2635
+ if (!result || !result.content) {
2636
+ logger.error("[KnowledgeBase] No knowledge base results returned");
2637
+ return "";
2638
+ }
2639
+ logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
2640
+ if (result.metadata?.sources && result.metadata.sources.length > 0) {
2641
+ logger.debug(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
2642
+ }
2643
+ return result.content;
2644
+ } catch (error) {
2645
+ const errorMsg = error instanceof Error ? error.message : String(error);
2646
+ logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
2647
+ return "";
2648
+ }
2649
+ };
2650
+ var KB = {
2651
+ getKnowledgeBase
2652
+ };
2653
+ var knowledge_base_default = KB;
2654
+
2459
2655
  // src/userResponse/base-llm.ts
2460
2656
  var BaseLLM = class {
2461
2657
  constructor(config) {
@@ -2994,62 +3190,485 @@ var BaseLLM = class {
2994
3190
  throw error;
2995
3191
  }
2996
3192
  }
3193
+ /**
3194
+ * Match components from text response suggestions and generate follow-up questions
3195
+ * Takes a text response with component suggestions (c1:type format) and matches with available components
3196
+ * Also generates intelligent follow-up questions (actions) based on the analysis
3197
+ * @param textResponse - The text response containing component suggestions
3198
+ * @param components - List of available components
3199
+ * @param apiKey - Optional API key
3200
+ * @param logCollector - Optional log collector
3201
+ * @returns Object containing matched components, selected layout, reasoning, and follow-up actions
3202
+ */
3203
+ async matchComponentsFromTextResponse(textResponse, components, apiKey, logCollector) {
3204
+ try {
3205
+ logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
3206
+ let availableComponentsText = "No components available";
3207
+ if (components && components.length > 0) {
3208
+ availableComponentsText = components.map((comp, idx) => {
3209
+ const keywords = comp.keywords ? comp.keywords.join(", ") : "";
3210
+ const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
3211
+ return `${idx + 1}. ID: ${comp.id}
3212
+ Name: ${comp.name}
3213
+ Type: ${comp.type}
3214
+ Description: ${comp.description || "No description"}
3215
+ Keywords: ${keywords}
3216
+ Props Structure: ${propsPreview}`;
3217
+ }).join("\n\n");
3218
+ }
3219
+ const schemaDoc = schema.generateSchemaDocumentation();
3220
+ const prompts = await promptLoader.loadPrompts("match-text-components", {
3221
+ TEXT_RESPONSE: textResponse,
3222
+ AVAILABLE_COMPONENTS: availableComponentsText,
3223
+ SCHEMA_DOC: schemaDoc
3224
+ });
3225
+ logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
3226
+ logger.file("\n=============================\nmatch text components system prompt:", prompts.system);
3227
+ logCollector?.info("Matching components from text response...");
3228
+ const rawResponse = await LLM.stream(
3229
+ {
3230
+ sys: prompts.system,
3231
+ user: prompts.user
3232
+ },
3233
+ {
3234
+ model: this.model,
3235
+ maxTokens: 3e3,
3236
+ temperature: 0.2,
3237
+ apiKey: this.getApiKey(apiKey)
3238
+ },
3239
+ false
3240
+ // Don't parse as JSON yet, get raw response
3241
+ );
3242
+ logger.debug(`[${this.getProviderName()}] Raw component matching response length: ${rawResponse?.length || 0}`);
3243
+ let result;
3244
+ try {
3245
+ let cleanedResponse = rawResponse || "";
3246
+ cleanedResponse = cleanedResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
3247
+ const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
3248
+ if (jsonMatch) {
3249
+ cleanedResponse = jsonMatch[0];
3250
+ }
3251
+ result = JSON.parse(cleanedResponse);
3252
+ } catch (parseError) {
3253
+ logger.error(`[${this.getProviderName()}] Failed to parse component matching JSON response`);
3254
+ const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
3255
+ const posMatch = errorMsg.match(/position (\d+)/);
3256
+ const errorPos = posMatch ? parseInt(posMatch[1]) : -1;
3257
+ if (errorPos > 0 && rawResponse) {
3258
+ const start = Math.max(0, errorPos - 200);
3259
+ const end = Math.min(rawResponse.length, errorPos + 200);
3260
+ logger.debug(`[${this.getProviderName()}] Error context (position ${errorPos}):`);
3261
+ logger.debug(rawResponse.substring(start, end));
3262
+ logger.debug(" ".repeat(Math.min(200, errorPos - start)) + "^--- Error here");
3263
+ }
3264
+ logger.debug(`[${this.getProviderName()}] Raw response (first 2000 chars):`, rawResponse?.substring(0, 2e3));
3265
+ logger.debug(`[${this.getProviderName()}] Parse error:`, parseError);
3266
+ logCollector?.error(`Failed to parse component matching response: ${errorMsg}`);
3267
+ try {
3268
+ logger.info(`[${this.getProviderName()}] Attempting aggressive JSON cleanup...`);
3269
+ let aggressive = rawResponse || "";
3270
+ aggressive = aggressive.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
3271
+ const match = aggressive.match(/\{[\s\S]*\}/);
3272
+ if (match) {
3273
+ aggressive = match[0];
3274
+ }
3275
+ aggressive = aggressive.replace(/,(\s*[}\]])/g, "$1");
3276
+ result = JSON.parse(aggressive);
3277
+ logger.info(`[${this.getProviderName()}] Aggressive cleanup succeeded!`);
3278
+ } catch (secondError) {
3279
+ logger.error(`[${this.getProviderName()}] Aggressive cleanup also failed`);
3280
+ return {
3281
+ components: [],
3282
+ selectedLayout: "MultiComponentContainer",
3283
+ selectedLayoutId: "",
3284
+ selectedLayoutComponent: null,
3285
+ layoutReasoning: "No layout selected",
3286
+ actions: []
3287
+ };
3288
+ }
3289
+ }
3290
+ const matchedComponents = result.matchedComponents || [];
3291
+ const selectedLayout = result.selectedLayout || "MultiComponentContainer";
3292
+ const selectedLayoutId = result.selectedLayoutId || "";
3293
+ const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
3294
+ const rawActions = result.actions || [];
3295
+ const actions = convertQuestionsToActions(rawActions);
3296
+ let selectedLayoutComponent = null;
3297
+ if (selectedLayoutId) {
3298
+ selectedLayoutComponent = components.find((c) => c.id === selectedLayoutId) || null;
3299
+ if (!selectedLayoutComponent) {
3300
+ logger.warn(`[${this.getProviderName()}] Layout component ${selectedLayoutId} not found in available components`);
3301
+ }
3302
+ }
3303
+ logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
3304
+ logger.info(`[${this.getProviderName()}] Selected layout: ${selectedLayout} (ID: ${selectedLayoutId})`);
3305
+ logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
3306
+ logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
3307
+ if (matchedComponents.length > 0) {
3308
+ logCollector?.info(`Matched ${matchedComponents.length} components for visualization using ${selectedLayout}`);
3309
+ logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
3310
+ matchedComponents.forEach((comp, idx) => {
3311
+ logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
3312
+ if (comp.props?.query) {
3313
+ logCollector?.logQuery(
3314
+ `Component ${idx + 1} query`,
3315
+ comp.props.query,
3316
+ { componentName: comp.componentName, title: comp.props.title }
3317
+ );
3318
+ }
3319
+ });
3320
+ }
3321
+ if (actions.length > 0) {
3322
+ logCollector?.info(`Generated ${actions.length} follow-up questions`);
3323
+ actions.forEach((action, idx) => {
3324
+ logCollector?.info(` ${idx + 1}. ${action.name}`);
3325
+ });
3326
+ }
3327
+ const finalComponents = matchedComponents.map((mc) => {
3328
+ const originalComponent = components.find((c) => c.id === mc.componentId);
3329
+ if (!originalComponent) {
3330
+ logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
3331
+ return null;
3332
+ }
3333
+ return {
3334
+ ...originalComponent,
3335
+ props: {
3336
+ ...originalComponent.props,
3337
+ ...mc.props
3338
+ }
3339
+ };
3340
+ }).filter(Boolean);
3341
+ return {
3342
+ components: finalComponents,
3343
+ selectedLayout,
3344
+ selectedLayoutId,
3345
+ selectedLayoutComponent,
3346
+ layoutReasoning,
3347
+ actions
3348
+ };
3349
+ } catch (error) {
3350
+ const errorMsg = error instanceof Error ? error.message : String(error);
3351
+ logger.error(`[${this.getProviderName()}] Error matching components from text response: ${errorMsg}`);
3352
+ logger.debug(`[${this.getProviderName()}] Component matching error details:`, error);
3353
+ logCollector?.error(`Error matching components: ${errorMsg}`);
3354
+ return {
3355
+ components: [],
3356
+ selectedLayout: "MultiComponentContainer",
3357
+ selectedLayoutId: "",
3358
+ selectedLayoutComponent: null,
3359
+ layoutReasoning: "Error occurred during component matching",
3360
+ actions: []
3361
+ };
3362
+ }
3363
+ }
2997
3364
  /**
2998
3365
  * Generate text-based response for user question
2999
3366
  * This provides conversational text responses instead of component generation
3367
+ * Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
3368
+ * After generating text response, if components are provided, matches suggested components
3369
+ * @param streamCallback - Optional callback function to receive text chunks as they stream
3370
+ * @param collections - Collection registry for executing database queries via database.execute
3371
+ * @param components - Optional list of available components for matching suggestions
3000
3372
  */
3001
- async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory) {
3373
+ async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components) {
3002
3374
  const errors = [];
3003
3375
  logger.debug(`[${this.getProviderName()}] Starting text response generation`);
3004
3376
  logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
3005
3377
  try {
3378
+ const schemaDoc = schema.generateSchemaDocumentation();
3379
+ const knowledgeBaseContext = await knowledge_base_default.getKnowledgeBase({
3380
+ prompt: userPrompt,
3381
+ collections,
3382
+ topK: 1
3383
+ });
3384
+ logger.file("\n=============================\nknowledge base context:", knowledgeBaseContext);
3006
3385
  const prompts = await promptLoader.loadPrompts("text-response", {
3007
3386
  USER_PROMPT: userPrompt,
3008
- CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
3387
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
3388
+ SCHEMA_DOC: schemaDoc,
3389
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available."
3009
3390
  });
3010
- logger.debug(`[${this.getProviderName()}] Loaded text-response prompts`);
3391
+ logger.file("\n=============================\nsystem prompt:", prompts.system);
3392
+ logger.file("\n=============================\nuser prompt:", prompts.user);
3393
+ logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
3011
3394
  logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
3012
- logCollector?.info("Generating text response...");
3013
- const result = await LLM.stream(
3395
+ logCollector?.info("Generating text response with query execution capability...");
3396
+ const tools = [{
3397
+ name: "execute_query",
3398
+ 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.",
3399
+ input_schema: {
3400
+ type: "object",
3401
+ properties: {
3402
+ query: {
3403
+ type: "string",
3404
+ description: "The SQL query to execute. Must be valid SQL syntax using table and column names from the schema."
3405
+ },
3406
+ reasoning: {
3407
+ type: "string",
3408
+ description: "Brief explanation of what this query does and why it answers the user's question."
3409
+ }
3410
+ },
3411
+ required: ["query"]
3412
+ }
3413
+ }];
3414
+ const queryAttempts = /* @__PURE__ */ new Map();
3415
+ const MAX_QUERY_ATTEMPTS = 6;
3416
+ let maxAttemptsReached = false;
3417
+ let fullStreamedText = "";
3418
+ const wrappedStreamCallback = streamCallback ? (chunk) => {
3419
+ fullStreamedText += chunk;
3420
+ streamCallback(chunk);
3421
+ } : void 0;
3422
+ const toolHandler = async (toolName, toolInput) => {
3423
+ if (toolName === "execute_query") {
3424
+ let query = toolInput.query;
3425
+ const reasoning = toolInput.reasoning;
3426
+ const { ensureQueryLimit: ensureQueryLimit2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
3427
+ query = ensureQueryLimit2(query, 32, 32);
3428
+ const queryKey = query.toLowerCase().replace(/\s+/g, " ").trim();
3429
+ const attempts = (queryAttempts.get(queryKey) || 0) + 1;
3430
+ queryAttempts.set(queryKey, attempts);
3431
+ logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${query.substring(0, 100)}...`);
3432
+ if (reasoning) {
3433
+ logCollector?.info(`Query reasoning: ${reasoning}`);
3434
+ }
3435
+ if (attempts > MAX_QUERY_ATTEMPTS) {
3436
+ const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
3437
+ logger.error(`[${this.getProviderName()}] ${errorMsg}`);
3438
+ logCollector?.error(errorMsg);
3439
+ maxAttemptsReached = true;
3440
+ if (wrappedStreamCallback) {
3441
+ wrappedStreamCallback(`
3442
+
3443
+ \u274C ${errorMsg}
3444
+
3445
+ Please try rephrasing your question or simplifying your request.
3446
+
3447
+ `);
3448
+ }
3449
+ throw new Error(errorMsg);
3450
+ }
3451
+ try {
3452
+ if (wrappedStreamCallback) {
3453
+ if (attempts === 1) {
3454
+ wrappedStreamCallback(`
3455
+
3456
+ \u{1F50D} **Analyzing your question...**
3457
+
3458
+ `);
3459
+ if (reasoning) {
3460
+ wrappedStreamCallback(`\u{1F4AD} ${reasoning}
3461
+
3462
+ `);
3463
+ }
3464
+ wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
3465
+ \`\`\`sql
3466
+ ${query}
3467
+ \`\`\`
3468
+
3469
+ `);
3470
+ wrappedStreamCallback(`\u26A1 **Executing query...**
3471
+
3472
+ `);
3473
+ } else {
3474
+ wrappedStreamCallback(`
3475
+
3476
+ \u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
3477
+
3478
+ `);
3479
+ if (reasoning) {
3480
+ wrappedStreamCallback(`\u{1F4AD} ${reasoning}
3481
+
3482
+ `);
3483
+ }
3484
+ wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
3485
+ \`\`\`sql
3486
+ ${query}
3487
+ \`\`\`
3488
+
3489
+ `);
3490
+ wrappedStreamCallback(`\u26A1 **Executing query...**
3491
+
3492
+ `);
3493
+ }
3494
+ }
3495
+ logCollector?.logQuery(
3496
+ `Executing SQL query (attempt ${attempts})`,
3497
+ query,
3498
+ { reasoning, attempt: attempts }
3499
+ );
3500
+ if (!collections || !collections["database"] || !collections["database"]["execute"]) {
3501
+ throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
3502
+ }
3503
+ const result2 = await collections["database"]["execute"]({ sql: query });
3504
+ const data = result2?.data || result2;
3505
+ const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
3506
+ logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
3507
+ logCollector?.info(`Query successful, returned ${rowCount} rows`);
3508
+ if (wrappedStreamCallback) {
3509
+ wrappedStreamCallback(`\u2705 **Query executed successfully!**
3510
+
3511
+ `);
3512
+ if (Array.isArray(data) && data.length > 0) {
3513
+ const firstRow = data[0];
3514
+ const columns = Object.keys(firstRow);
3515
+ if (data.length === 1 && columns.length === 1) {
3516
+ const value = firstRow[columns[0]];
3517
+ wrappedStreamCallback(`**Result:** ${value}
3518
+
3519
+ `);
3520
+ } else if (data.length > 0) {
3521
+ wrappedStreamCallback(`**Retrieved ${rowCount} rows**
3522
+
3523
+ `);
3524
+ wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
3525
+
3526
+ `);
3527
+ }
3528
+ } else if (Array.isArray(data) && data.length === 0) {
3529
+ wrappedStreamCallback(`**No rows returned.**
3530
+
3531
+ `);
3532
+ }
3533
+ wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
3534
+
3535
+ `);
3536
+ }
3537
+ return JSON.stringify(data, null, 2);
3538
+ } catch (error) {
3539
+ const errorMsg = error instanceof Error ? error.message : String(error);
3540
+ logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
3541
+ logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
3542
+ if (wrappedStreamCallback) {
3543
+ wrappedStreamCallback(`\u274C **Query execution failed:**
3544
+ \`\`\`
3545
+ ${errorMsg}
3546
+ \`\`\`
3547
+
3548
+ `);
3549
+ if (attempts < MAX_QUERY_ATTEMPTS) {
3550
+ wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
3551
+
3552
+ `);
3553
+ }
3554
+ }
3555
+ throw new Error(`Query execution failed: ${errorMsg}`);
3556
+ }
3557
+ }
3558
+ throw new Error(`Unknown tool: ${toolName}`);
3559
+ };
3560
+ const result = await LLM.streamWithTools(
3014
3561
  {
3015
3562
  sys: prompts.system,
3016
3563
  user: prompts.user
3017
3564
  },
3565
+ tools,
3566
+ toolHandler,
3018
3567
  {
3019
3568
  model: this.model,
3020
- maxTokens: 1e3,
3569
+ maxTokens: 4e3,
3021
3570
  temperature: 0.7,
3022
- apiKey: this.getApiKey(apiKey)
3571
+ apiKey: this.getApiKey(apiKey),
3572
+ partial: wrappedStreamCallback
3573
+ // Pass the wrapped streaming callback to LLM
3023
3574
  },
3024
- true
3025
- // Parse as JSON
3575
+ 10
3576
+ // max iterations: allows for 6 retries + final response + buffer
3026
3577
  );
3027
- logger.info(`[${this.getProviderName()}] Text response generated successfully`);
3028
- logger.debug(`[${this.getProviderName()}] Response type: ${result.responseType}, Confidence: ${result.confidence}`);
3029
- logCollector?.info(`Text response: ${result.text?.substring(0, 100)}${result.text?.length > 100 ? "..." : ""}`);
3578
+ logger.info(`[${this.getProviderName()}] Text response stream completed`);
3579
+ const textResponse = fullStreamedText || result || "I apologize, but I was unable to generate a response.";
3580
+ if (maxAttemptsReached) {
3581
+ logger.warn(`[${this.getProviderName()}] Max query attempts reached, returning failure response`);
3582
+ logCollector?.error("Failed to generate valid query after maximum attempts");
3583
+ return {
3584
+ success: false,
3585
+ errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
3586
+ data: {
3587
+ text: textResponse,
3588
+ // Include the streamed text showing all attempts
3589
+ matchedComponents: [],
3590
+ actions: [],
3591
+ method: `${this.getProviderName()}-text-response-max-attempts`
3592
+ }
3593
+ };
3594
+ }
3595
+ logCollector?.info(`Text response: ${textResponse.substring(0, 100)}${textResponse.length > 100 ? "..." : ""}`);
3030
3596
  logCollector?.logExplanation(
3031
3597
  "Text response generated",
3032
- result.reasoning || "Generated text response for user question",
3598
+ "Generated plain text response with component suggestions",
3033
3599
  {
3034
- responseType: result.responseType,
3035
- confidence: result.confidence,
3036
- textLength: result.text?.length || 0
3600
+ textLength: textResponse.length
3037
3601
  }
3038
3602
  );
3603
+ let matchedComponents = [];
3604
+ let selectedLayoutComponent = null;
3605
+ let layoutReasoning = "No layout selected";
3606
+ let actions = [];
3607
+ if (components && components.length > 0) {
3608
+ logger.info(`[${this.getProviderName()}] Matching components from text response...`);
3609
+ const matchResult = await this.matchComponentsFromTextResponse(
3610
+ textResponse,
3611
+ components,
3612
+ apiKey,
3613
+ logCollector
3614
+ );
3615
+ matchedComponents = matchResult.components;
3616
+ selectedLayoutComponent = matchResult.selectedLayoutComponent;
3617
+ layoutReasoning = matchResult.layoutReasoning;
3618
+ actions = matchResult.actions;
3619
+ }
3620
+ let container_componet = null;
3621
+ if (matchedComponents.length > 0) {
3622
+ if (selectedLayoutComponent) {
3623
+ container_componet = {
3624
+ ...selectedLayoutComponent,
3625
+ id: `${selectedLayoutComponent.id}_${Date.now()}`,
3626
+ description: layoutReasoning,
3627
+ props: {
3628
+ ...selectedLayoutComponent.props,
3629
+ config: {
3630
+ ...selectedLayoutComponent.props?.config || {},
3631
+ components: matchedComponents
3632
+ },
3633
+ actions
3634
+ }
3635
+ };
3636
+ logger.info(`[${this.getProviderName()}] Created ${selectedLayoutComponent.name} (${selectedLayoutComponent.type}) container with ${matchedComponents.length} components and ${actions.length} actions`);
3637
+ logCollector?.info(`Created ${selectedLayoutComponent.name} with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
3638
+ } else {
3639
+ container_componet = {
3640
+ id: `multi_container_${Date.now()}`,
3641
+ name: "MultiComponentContainer",
3642
+ type: "Container",
3643
+ description: layoutReasoning,
3644
+ category: "dynamic",
3645
+ keywords: ["dashboard", "layout", "container"],
3646
+ props: {
3647
+ config: {
3648
+ components: matchedComponents
3649
+ },
3650
+ actions
3651
+ }
3652
+ };
3653
+ logger.info(`[${this.getProviderName()}] Created fallback MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions`);
3654
+ logCollector?.info(`Created MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
3655
+ }
3656
+ }
3039
3657
  return {
3040
3658
  success: true,
3041
3659
  data: {
3042
- text: result.text || "I apologize, but I was unable to generate a response.",
3043
- responseType: result.responseType || "answer",
3044
- confidence: result.confidence || 0.5,
3045
- reasoning: result.reasoning || "No reasoning provided"
3660
+ text: textResponse,
3661
+ matchedComponents,
3662
+ component: container_componet,
3663
+ layoutReasoning,
3664
+ actions,
3665
+ method: `${this.getProviderName()}-text-response`
3046
3666
  },
3047
3667
  errors: []
3048
3668
  };
3049
3669
  } catch (error) {
3050
3670
  const errorMsg = error instanceof Error ? error.message : String(error);
3051
3671
  logger.error(`[${this.getProviderName()}] Error generating text response: ${errorMsg}`);
3052
- logger.debug(`[${this.getProviderName()}] Text response generation error details:`, error);
3053
3672
  logCollector?.error(`Error generating text response: ${errorMsg}`);
3054
3673
  errors.push(errorMsg);
3055
3674
  return {
@@ -3057,9 +3676,9 @@ var BaseLLM = class {
3057
3676
  errors,
3058
3677
  data: {
3059
3678
  text: "I apologize, but I encountered an error while processing your question. Please try rephrasing or ask something else.",
3060
- responseType: "error",
3061
- confidence: 0,
3062
- reasoning: `Error: ${errorMsg}`
3679
+ matchedComponents: [],
3680
+ actions: [],
3681
+ method: `${this.getProviderName()}-text-response-error`
3063
3682
  }
3064
3683
  };
3065
3684
  }
@@ -3250,8 +3869,11 @@ var BaseLLM = class {
3250
3869
  * Supports both component generation and text response modes
3251
3870
  *
3252
3871
  * @param responseMode - 'component' for component generation (default), 'text' for text responses
3872
+ * @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
3873
+ * @param collections - Collection registry for executing database queries (required for text mode)
3253
3874
  */
3254
- async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component") {
3875
+ async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) {
3876
+ const startTime = Date.now();
3255
3877
  logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
3256
3878
  if (responseMode === "text") {
3257
3879
  logger.info(`[${this.getProviderName()}] Using text response mode`);
@@ -3260,25 +3882,23 @@ var BaseLLM = class {
3260
3882
  userPrompt,
3261
3883
  apiKey,
3262
3884
  logCollector,
3263
- conversationHistory
3885
+ conversationHistory,
3886
+ streamCallback,
3887
+ collections,
3888
+ components
3264
3889
  );
3265
3890
  if (!textResponse.success) {
3891
+ const elapsedTime3 = Date.now() - startTime;
3266
3892
  logger.error(`[${this.getProviderName()}] Text response generation failed`);
3893
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
3894
+ logCollector?.info(`Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
3267
3895
  return textResponse;
3268
3896
  }
3897
+ const elapsedTime2 = Date.now() - startTime;
3269
3898
  logger.info(`[${this.getProviderName()}] Text response generated successfully`);
3270
- return {
3271
- success: true,
3272
- data: {
3273
- textResponse: textResponse.data.text,
3274
- text: textResponse.data.text,
3275
- responseType: textResponse.data.responseType,
3276
- confidence: textResponse.data.confidence,
3277
- reasoning: textResponse.data.reasoning,
3278
- method: `${this.getProviderName()}-text-response`
3279
- },
3280
- errors: []
3281
- };
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)`);
3901
+ return textResponse;
3282
3902
  }
3283
3903
  const componentResponse = await this.generateComponentResponse(
3284
3904
  userPrompt,
@@ -3288,24 +3908,17 @@ var BaseLLM = class {
3288
3908
  conversationHistory
3289
3909
  );
3290
3910
  if (!componentResponse.success) {
3911
+ const elapsedTime2 = Date.now() - startTime;
3291
3912
  logger.error(`[${this.getProviderName()}] Component response generation failed`);
3913
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
3914
+ logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
3292
3915
  return componentResponse;
3293
3916
  }
3917
+ const elapsedTime = Date.now() - startTime;
3294
3918
  logger.info(`[${this.getProviderName()}] Component response generated successfully`);
3295
- return {
3296
- success: true,
3297
- data: {
3298
- component: componentResponse.data.component,
3299
- reasoning: componentResponse.data.reasoning,
3300
- method: componentResponse.data.method,
3301
- // Preserve the original method name
3302
- questionType: componentResponse.data.questionType,
3303
- needsMultipleComponents: componentResponse.data.needsMultipleComponents,
3304
- propsModified: componentResponse.data.propsModified,
3305
- queryModified: componentResponse.data.queryModified
3306
- },
3307
- errors: []
3308
- };
3919
+ logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
3920
+ logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
3921
+ return componentResponse;
3309
3922
  }
3310
3923
  /**
3311
3924
  * Generate next questions that the user might ask based on the original prompt and generated component
@@ -3418,7 +4031,7 @@ function getLLMProviders() {
3418
4031
  return DEFAULT_PROVIDERS;
3419
4032
  }
3420
4033
  }
3421
- var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component") => {
4034
+ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
3422
4035
  logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
3423
4036
  logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
3424
4037
  const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -3430,11 +4043,11 @@ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conver
3430
4043
  return { success: false, errors: [emptyMsg] };
3431
4044
  }
3432
4045
  logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
3433
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode);
4046
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3434
4047
  logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
3435
4048
  return matchResult;
3436
4049
  };
3437
- var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component") => {
4050
+ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
3438
4051
  logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
3439
4052
  logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
3440
4053
  const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
@@ -3447,16 +4060,16 @@ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversatio
3447
4060
  return { success: false, errors: [emptyMsg] };
3448
4061
  }
3449
4062
  logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
3450
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode);
4063
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3451
4064
  logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
3452
4065
  return matchResult;
3453
4066
  };
3454
4067
  var getUserResponseFromCache = async (prompt) => {
3455
4068
  return false;
3456
4069
  };
3457
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory) => {
3458
- const responseMode = "component";
4070
+ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
3459
4071
  logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
4072
+ logger.debug(`[get_user_response] Response mode: ${responseMode}`);
3460
4073
  logger.debug("[get_user_response] Checking cache for existing response");
3461
4074
  const userResponse = await getUserResponseFromCache(prompt);
3462
4075
  if (userResponse) {
@@ -3487,16 +4100,16 @@ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey,
3487
4100
  logCollector?.info(attemptMsg);
3488
4101
  let result;
3489
4102
  if (provider === "anthropic") {
3490
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode);
4103
+ result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3491
4104
  } else if (provider === "groq") {
3492
- result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode);
4105
+ result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
3493
4106
  } else {
3494
4107
  logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
3495
4108
  errors.push(`Unknown provider: ${provider}`);
3496
4109
  continue;
3497
4110
  }
3498
4111
  if (result.success) {
3499
- const successMsg = `Success with provider: ${provider} result: ${JSON.stringify(result)}`;
4112
+ const successMsg = `Success with provider: ${provider}`;
3500
4113
  logger.info(`${successMsg}`);
3501
4114
  logCollector?.info(successMsg);
3502
4115
  return result;
@@ -3717,8 +4330,7 @@ var CONTEXT_CONFIG = {
3717
4330
  };
3718
4331
 
3719
4332
  // src/handlers/user-prompt-request.ts
3720
- var processedMessageIds = /* @__PURE__ */ new Set();
3721
- var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders) => {
4333
+ var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) => {
3722
4334
  const errors = [];
3723
4335
  logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
3724
4336
  const parseResult = UserPromptRequestMessageSchema.safeParse(data);
@@ -3734,19 +4346,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3734
4346
  const prompt = payload.prompt;
3735
4347
  const SA_RUNTIME = payload.SA_RUNTIME;
3736
4348
  const wsId = userPromptRequest.from.id || "unknown";
3737
- logger.debug(`[REQUEST ${id}] Full request details - wsId: ${wsId}, prompt length: ${prompt.length}`);
3738
- if (processedMessageIds.has(id)) {
3739
- logger.warn(`[REQUEST ${id}] Duplicate request detected - ignoring`);
3740
- }
3741
- processedMessageIds.add(id);
3742
- logger.debug(`[REQUEST ${id}] Message ID marked as processed (${processedMessageIds.size} total)`);
3743
- if (processedMessageIds.size > 100) {
3744
- const firstId = processedMessageIds.values().next().value;
3745
- if (firstId) {
3746
- processedMessageIds.delete(firstId);
3747
- logger.debug(`[REQUEST ${id}] Cleaned up old message ID from cache`);
3748
- }
3749
- }
3750
4349
  if (!SA_RUNTIME) {
3751
4350
  errors.push("SA_RUNTIME is required");
3752
4351
  }
@@ -3761,9 +4360,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3761
4360
  if (!prompt) {
3762
4361
  errors.push("Prompt not found");
3763
4362
  }
3764
- if (!components || components.length === 0) {
3765
- errors.push("Components not found");
3766
- }
4363
+ logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
3767
4364
  if (errors.length > 0) {
3768
4365
  return { success: false, errors, id, wsId };
3769
4366
  }
@@ -3777,8 +4374,44 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3777
4374
  logCollector.info(`Starting user prompt request with ${components.length} components`);
3778
4375
  const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
3779
4376
  logger.info("conversationHistory", conversationHistory);
3780
- const userResponse = await get_user_response(prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory);
3781
- logger.info("llm userResponse", userResponse);
4377
+ const responseMode = payload.responseMode || "component";
4378
+ logger.info("responseMode", responseMode);
4379
+ let streamCallback;
4380
+ let accumulatedStreamResponse = "";
4381
+ if (responseMode === "text") {
4382
+ streamCallback = (chunk) => {
4383
+ accumulatedStreamResponse += chunk;
4384
+ logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
4385
+ const streamMessage = {
4386
+ id: `stream_${existingUiBlockId}`,
4387
+ // Different ID pattern for streaming
4388
+ type: "USER_PROMPT_STREAM",
4389
+ from: { type: "data-agent" },
4390
+ to: {
4391
+ type: "runtime",
4392
+ id: wsId
4393
+ },
4394
+ payload: {
4395
+ uiBlockId: existingUiBlockId,
4396
+ chunk
4397
+ }
4398
+ };
4399
+ sendMessage(streamMessage);
4400
+ logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
4401
+ };
4402
+ }
4403
+ const userResponse = await get_user_response(
4404
+ prompt,
4405
+ components,
4406
+ anthropicApiKey,
4407
+ groqApiKey,
4408
+ llmProviders,
4409
+ logCollector,
4410
+ conversationHistory,
4411
+ responseMode,
4412
+ streamCallback,
4413
+ collections
4414
+ );
3782
4415
  logCollector.info("User prompt request completed");
3783
4416
  const uiBlockId = existingUiBlockId;
3784
4417
  if (!userResponse.success) {
@@ -3795,16 +4428,21 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3795
4428
  }
3796
4429
  let component = null;
3797
4430
  let textResponse = null;
4431
+ let actions = [];
3798
4432
  if (userResponse.data) {
3799
4433
  if (typeof userResponse.data === "object") {
3800
4434
  if ("component" in userResponse.data) {
3801
4435
  component = userResponse.data.component;
3802
4436
  }
3803
4437
  if ("textResponse" in userResponse.data) {
3804
- textResponse = userResponse.data.textResponse;
4438
+ textResponse = userResponse.data.text;
4439
+ }
4440
+ if ("actions" in userResponse.data) {
4441
+ actions = userResponse.data.actions || [];
3805
4442
  }
3806
4443
  }
3807
4444
  }
4445
+ const finalTextResponse = responseMode === "text" && accumulatedStreamResponse ? accumulatedStreamResponse : textResponse;
3808
4446
  const uiBlock = new UIBlock(
3809
4447
  prompt,
3810
4448
  {},
@@ -3812,11 +4450,15 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3812
4450
  component,
3813
4451
  // generatedComponentMetadata: full component object (ComponentSchema)
3814
4452
  [],
3815
- // actions: empty initially
4453
+ // actions: empty initially, will be set below
3816
4454
  uiBlockId,
3817
- textResponse
3818
- // textResponse: text response from LLM
4455
+ finalTextResponse
4456
+ // textResponse: FULL streaming response including all intermediate messages
3819
4457
  );
4458
+ if (actions.length > 0) {
4459
+ uiBlock.setActions(actions);
4460
+ logger.info(`Stored ${actions.length} actions in UIBlock: ${uiBlockId}`);
4461
+ }
3820
4462
  thread.addUIBlock(uiBlock);
3821
4463
  logger.info(`Created UIBlock: ${uiBlockId} in Thread: ${threadId}`);
3822
4464
  return {
@@ -3829,8 +4471,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
3829
4471
  wsId
3830
4472
  };
3831
4473
  };
3832
- async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders) {
3833
- const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders);
4474
+ async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) {
4475
+ const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections);
3834
4476
  sendDataResponse4(
3835
4477
  response.id || data.id,
3836
4478
  {
@@ -4054,7 +4696,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
4054
4696
  const uiBlockId = SA_RUNTIME.uiBlockId;
4055
4697
  const threadId = SA_RUNTIME.threadId;
4056
4698
  logger.debug(`[ACTIONS_REQ ${id}] SA_RUNTIME validated - threadId: ${threadId}, uiBlockId: ${uiBlockId}`);
4057
- logger.debug(`[ACTIONS_REQ ${id}] Retrieving thread: ${threadId}`);
4058
4699
  const threadManager = ThreadManager.getInstance();
4059
4700
  const thread = threadManager.getThread(threadId);
4060
4701
  if (!thread) {
@@ -4906,7 +5547,7 @@ function sendResponse5(id, res, sendMessage, clientId) {
4906
5547
  }
4907
5548
 
4908
5549
  // src/auth/user-manager.ts
4909
- var import_fs3 = __toESM(require("fs"));
5550
+ var import_fs4 = __toESM(require("fs"));
4910
5551
  var import_path3 = __toESM(require("path"));
4911
5552
  var import_os = __toESM(require("os"));
4912
5553
  var UserManager = class {
@@ -4946,19 +5587,19 @@ var UserManager = class {
4946
5587
  async loadUsersFromFile() {
4947
5588
  try {
4948
5589
  const dir = import_path3.default.dirname(this.filePath);
4949
- if (!import_fs3.default.existsSync(dir)) {
5590
+ if (!import_fs4.default.existsSync(dir)) {
4950
5591
  logger.info(`Creating directory structure: ${dir}`);
4951
- import_fs3.default.mkdirSync(dir, { recursive: true });
5592
+ import_fs4.default.mkdirSync(dir, { recursive: true });
4952
5593
  }
4953
- if (!import_fs3.default.existsSync(this.filePath)) {
5594
+ if (!import_fs4.default.existsSync(this.filePath)) {
4954
5595
  logger.info(`Users file does not exist at ${this.filePath}, creating with empty users`);
4955
5596
  const initialData = { users: [] };
4956
- import_fs3.default.writeFileSync(this.filePath, JSON.stringify(initialData, null, 4));
5597
+ import_fs4.default.writeFileSync(this.filePath, JSON.stringify(initialData, null, 4));
4957
5598
  this.users = [];
4958
5599
  this.hasChanged = false;
4959
5600
  return;
4960
5601
  }
4961
- const fileContent = import_fs3.default.readFileSync(this.filePath, "utf-8");
5602
+ const fileContent = import_fs4.default.readFileSync(this.filePath, "utf-8");
4962
5603
  const rawData = JSON.parse(fileContent);
4963
5604
  const validatedData = UsersDataSchema.parse(rawData);
4964
5605
  this.users = validatedData.users;
@@ -4978,15 +5619,15 @@ var UserManager = class {
4978
5619
  }
4979
5620
  try {
4980
5621
  const dir = import_path3.default.dirname(this.filePath);
4981
- if (!import_fs3.default.existsSync(dir)) {
4982
- import_fs3.default.mkdirSync(dir, { recursive: true });
5622
+ if (!import_fs4.default.existsSync(dir)) {
5623
+ import_fs4.default.mkdirSync(dir, { recursive: true });
4983
5624
  }
4984
5625
  const usersToSave = this.users.map((user) => {
4985
5626
  const { wsIds, ...userWithoutWsIds } = user;
4986
5627
  return userWithoutWsIds;
4987
5628
  });
4988
5629
  const data = { users: usersToSave };
4989
- import_fs3.default.writeFileSync(this.filePath, JSON.stringify(data, null, 4));
5630
+ import_fs4.default.writeFileSync(this.filePath, JSON.stringify(data, null, 4));
4990
5631
  this.hasChanged = false;
4991
5632
  logger.debug(`Synced ${this.users.length} users to file (wsIds excluded)`);
4992
5633
  } catch (error) {
@@ -5204,7 +5845,7 @@ var UserManager = class {
5204
5845
  };
5205
5846
 
5206
5847
  // src/dashboards/dashboard-manager.ts
5207
- var import_fs4 = __toESM(require("fs"));
5848
+ var import_fs5 = __toESM(require("fs"));
5208
5849
  var import_path4 = __toESM(require("path"));
5209
5850
  var import_os2 = __toESM(require("os"));
5210
5851
  var DashboardManager = class {
@@ -5239,12 +5880,12 @@ var DashboardManager = class {
5239
5880
  createDashboard(dashboardId, dashboard) {
5240
5881
  const dashboardPath = this.getDashboardPath(dashboardId);
5241
5882
  const dashboardDir = import_path4.default.dirname(dashboardPath);
5242
- if (import_fs4.default.existsSync(dashboardPath)) {
5883
+ if (import_fs5.default.existsSync(dashboardPath)) {
5243
5884
  throw new Error(`Dashboard '${dashboardId}' already exists`);
5244
5885
  }
5245
5886
  const validated = DSLRendererPropsSchema.parse(dashboard);
5246
- import_fs4.default.mkdirSync(dashboardDir, { recursive: true });
5247
- import_fs4.default.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5887
+ import_fs5.default.mkdirSync(dashboardDir, { recursive: true });
5888
+ import_fs5.default.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5248
5889
  logger.info(`Dashboard created: ${dashboardId}`);
5249
5890
  return validated;
5250
5891
  }
@@ -5255,12 +5896,12 @@ var DashboardManager = class {
5255
5896
  */
5256
5897
  getDashboard(dashboardId) {
5257
5898
  const dashboardPath = this.getDashboardPath(dashboardId);
5258
- if (!import_fs4.default.existsSync(dashboardPath)) {
5899
+ if (!import_fs5.default.existsSync(dashboardPath)) {
5259
5900
  logger.warn(`Dashboard not found: ${dashboardId}`);
5260
5901
  return null;
5261
5902
  }
5262
5903
  try {
5263
- const fileContent = import_fs4.default.readFileSync(dashboardPath, "utf-8");
5904
+ const fileContent = import_fs5.default.readFileSync(dashboardPath, "utf-8");
5264
5905
  const dashboard = JSON.parse(fileContent);
5265
5906
  const validated = DSLRendererPropsSchema.parse(dashboard);
5266
5907
  return validated;
@@ -5274,16 +5915,16 @@ var DashboardManager = class {
5274
5915
  * @returns Array of dashboard objects with their IDs
5275
5916
  */
5276
5917
  getAllDashboards() {
5277
- if (!import_fs4.default.existsSync(this.dashboardsBasePath)) {
5278
- import_fs4.default.mkdirSync(this.dashboardsBasePath, { recursive: true });
5918
+ if (!import_fs5.default.existsSync(this.dashboardsBasePath)) {
5919
+ import_fs5.default.mkdirSync(this.dashboardsBasePath, { recursive: true });
5279
5920
  return [];
5280
5921
  }
5281
5922
  const dashboards = [];
5282
5923
  try {
5283
- const dashboardDirs = import_fs4.default.readdirSync(this.dashboardsBasePath);
5924
+ const dashboardDirs = import_fs5.default.readdirSync(this.dashboardsBasePath);
5284
5925
  for (const dashboardId of dashboardDirs) {
5285
5926
  const dashboardPath = this.getDashboardPath(dashboardId);
5286
- if (import_fs4.default.existsSync(dashboardPath)) {
5927
+ if (import_fs5.default.existsSync(dashboardPath)) {
5287
5928
  const dashboard = this.getDashboard(dashboardId);
5288
5929
  if (dashboard) {
5289
5930
  dashboards.push({ dashboardId, dashboard });
@@ -5305,13 +5946,13 @@ var DashboardManager = class {
5305
5946
  */
5306
5947
  updateDashboard(dashboardId, dashboard) {
5307
5948
  const dashboardPath = this.getDashboardPath(dashboardId);
5308
- if (!import_fs4.default.existsSync(dashboardPath)) {
5949
+ if (!import_fs5.default.existsSync(dashboardPath)) {
5309
5950
  logger.warn(`Dashboard not found for update: ${dashboardId}`);
5310
5951
  return null;
5311
5952
  }
5312
5953
  try {
5313
5954
  const validated = DSLRendererPropsSchema.parse(dashboard);
5314
- import_fs4.default.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5955
+ import_fs5.default.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
5315
5956
  logger.info(`Dashboard updated: ${dashboardId}`);
5316
5957
  return validated;
5317
5958
  } catch (error) {
@@ -5327,12 +5968,12 @@ var DashboardManager = class {
5327
5968
  deleteDashboard(dashboardId) {
5328
5969
  const dashboardPath = this.getDashboardPath(dashboardId);
5329
5970
  const dashboardDir = import_path4.default.dirname(dashboardPath);
5330
- if (!import_fs4.default.existsSync(dashboardPath)) {
5971
+ if (!import_fs5.default.existsSync(dashboardPath)) {
5331
5972
  logger.warn(`Dashboard not found for deletion: ${dashboardId}`);
5332
5973
  return false;
5333
5974
  }
5334
5975
  try {
5335
- import_fs4.default.rmSync(dashboardDir, { recursive: true, force: true });
5976
+ import_fs5.default.rmSync(dashboardDir, { recursive: true, force: true });
5336
5977
  logger.info(`Dashboard deleted: ${dashboardId}`);
5337
5978
  return true;
5338
5979
  } catch (error) {
@@ -5347,21 +5988,21 @@ var DashboardManager = class {
5347
5988
  */
5348
5989
  dashboardExists(dashboardId) {
5349
5990
  const dashboardPath = this.getDashboardPath(dashboardId);
5350
- return import_fs4.default.existsSync(dashboardPath);
5991
+ return import_fs5.default.existsSync(dashboardPath);
5351
5992
  }
5352
5993
  /**
5353
5994
  * Get dashboard count
5354
5995
  * @returns Number of dashboards
5355
5996
  */
5356
5997
  getDashboardCount() {
5357
- if (!import_fs4.default.existsSync(this.dashboardsBasePath)) {
5998
+ if (!import_fs5.default.existsSync(this.dashboardsBasePath)) {
5358
5999
  return 0;
5359
6000
  }
5360
6001
  try {
5361
- const dashboardDirs = import_fs4.default.readdirSync(this.dashboardsBasePath);
6002
+ const dashboardDirs = import_fs5.default.readdirSync(this.dashboardsBasePath);
5362
6003
  return dashboardDirs.filter((dir) => {
5363
6004
  const dashboardPath = this.getDashboardPath(dir);
5364
- return import_fs4.default.existsSync(dashboardPath);
6005
+ return import_fs5.default.existsSync(dashboardPath);
5365
6006
  }).length;
5366
6007
  } catch (error) {
5367
6008
  logger.error("Failed to get dashboard count:", error);
@@ -5371,7 +6012,7 @@ var DashboardManager = class {
5371
6012
  };
5372
6013
 
5373
6014
  // src/reports/report-manager.ts
5374
- var import_fs5 = __toESM(require("fs"));
6015
+ var import_fs6 = __toESM(require("fs"));
5375
6016
  var import_path5 = __toESM(require("path"));
5376
6017
  var import_os3 = __toESM(require("os"));
5377
6018
  var ReportManager = class {
@@ -5406,12 +6047,12 @@ var ReportManager = class {
5406
6047
  createReport(reportId, report) {
5407
6048
  const reportPath = this.getReportPath(reportId);
5408
6049
  const reportDir = import_path5.default.dirname(reportPath);
5409
- if (import_fs5.default.existsSync(reportPath)) {
6050
+ if (import_fs6.default.existsSync(reportPath)) {
5410
6051
  throw new Error(`Report '${reportId}' already exists`);
5411
6052
  }
5412
6053
  const validated = DSLRendererPropsSchema2.parse(report);
5413
- import_fs5.default.mkdirSync(reportDir, { recursive: true });
5414
- import_fs5.default.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
6054
+ import_fs6.default.mkdirSync(reportDir, { recursive: true });
6055
+ import_fs6.default.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
5415
6056
  logger.info(`Report created: ${reportId}`);
5416
6057
  return validated;
5417
6058
  }
@@ -5422,12 +6063,12 @@ var ReportManager = class {
5422
6063
  */
5423
6064
  getReport(reportId) {
5424
6065
  const reportPath = this.getReportPath(reportId);
5425
- if (!import_fs5.default.existsSync(reportPath)) {
6066
+ if (!import_fs6.default.existsSync(reportPath)) {
5426
6067
  logger.warn(`Report not found: ${reportId}`);
5427
6068
  return null;
5428
6069
  }
5429
6070
  try {
5430
- const fileContent = import_fs5.default.readFileSync(reportPath, "utf-8");
6071
+ const fileContent = import_fs6.default.readFileSync(reportPath, "utf-8");
5431
6072
  const report = JSON.parse(fileContent);
5432
6073
  const validated = DSLRendererPropsSchema2.parse(report);
5433
6074
  return validated;
@@ -5441,16 +6082,16 @@ var ReportManager = class {
5441
6082
  * @returns Array of report objects with their IDs
5442
6083
  */
5443
6084
  getAllReports() {
5444
- if (!import_fs5.default.existsSync(this.reportsBasePath)) {
5445
- import_fs5.default.mkdirSync(this.reportsBasePath, { recursive: true });
6085
+ if (!import_fs6.default.existsSync(this.reportsBasePath)) {
6086
+ import_fs6.default.mkdirSync(this.reportsBasePath, { recursive: true });
5446
6087
  return [];
5447
6088
  }
5448
6089
  const reports = [];
5449
6090
  try {
5450
- const reportDirs = import_fs5.default.readdirSync(this.reportsBasePath);
6091
+ const reportDirs = import_fs6.default.readdirSync(this.reportsBasePath);
5451
6092
  for (const reportId of reportDirs) {
5452
6093
  const reportPath = this.getReportPath(reportId);
5453
- if (import_fs5.default.existsSync(reportPath)) {
6094
+ if (import_fs6.default.existsSync(reportPath)) {
5454
6095
  const report = this.getReport(reportId);
5455
6096
  if (report) {
5456
6097
  reports.push({ reportId, report });
@@ -5472,13 +6113,13 @@ var ReportManager = class {
5472
6113
  */
5473
6114
  updateReport(reportId, report) {
5474
6115
  const reportPath = this.getReportPath(reportId);
5475
- if (!import_fs5.default.existsSync(reportPath)) {
6116
+ if (!import_fs6.default.existsSync(reportPath)) {
5476
6117
  logger.warn(`Report not found for update: ${reportId}`);
5477
6118
  return null;
5478
6119
  }
5479
6120
  try {
5480
6121
  const validated = DSLRendererPropsSchema2.parse(report);
5481
- import_fs5.default.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
6122
+ import_fs6.default.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
5482
6123
  logger.info(`Report updated: ${reportId}`);
5483
6124
  return validated;
5484
6125
  } catch (error) {
@@ -5494,12 +6135,12 @@ var ReportManager = class {
5494
6135
  deleteReport(reportId) {
5495
6136
  const reportPath = this.getReportPath(reportId);
5496
6137
  const reportDir = import_path5.default.dirname(reportPath);
5497
- if (!import_fs5.default.existsSync(reportPath)) {
6138
+ if (!import_fs6.default.existsSync(reportPath)) {
5498
6139
  logger.warn(`Report not found for deletion: ${reportId}`);
5499
6140
  return false;
5500
6141
  }
5501
6142
  try {
5502
- import_fs5.default.rmSync(reportDir, { recursive: true, force: true });
6143
+ import_fs6.default.rmSync(reportDir, { recursive: true, force: true });
5503
6144
  logger.info(`Report deleted: ${reportId}`);
5504
6145
  return true;
5505
6146
  } catch (error) {
@@ -5514,21 +6155,21 @@ var ReportManager = class {
5514
6155
  */
5515
6156
  reportExists(reportId) {
5516
6157
  const reportPath = this.getReportPath(reportId);
5517
- return import_fs5.default.existsSync(reportPath);
6158
+ return import_fs6.default.existsSync(reportPath);
5518
6159
  }
5519
6160
  /**
5520
6161
  * Get report count
5521
6162
  * @returns Number of reports
5522
6163
  */
5523
6164
  getReportCount() {
5524
- if (!import_fs5.default.existsSync(this.reportsBasePath)) {
6165
+ if (!import_fs6.default.existsSync(this.reportsBasePath)) {
5525
6166
  return 0;
5526
6167
  }
5527
6168
  try {
5528
- const reportDirs = import_fs5.default.readdirSync(this.reportsBasePath);
6169
+ const reportDirs = import_fs6.default.readdirSync(this.reportsBasePath);
5529
6170
  return reportDirs.filter((dir) => {
5530
6171
  const reportPath = this.getReportPath(dir);
5531
- return import_fs5.default.existsSync(reportPath);
6172
+ return import_fs6.default.existsSync(reportPath);
5532
6173
  }).length;
5533
6174
  } catch (error) {
5534
6175
  logger.error("Failed to get report count:", error);
@@ -5752,9 +6393,6 @@ var SuperatomSDK = class {
5752
6393
  });
5753
6394
  this.initializeDashboardManager();
5754
6395
  this.initializeReportManager();
5755
- this.connect().catch((error) => {
5756
- logger.error("Failed to connect to Superatom:", error);
5757
- });
5758
6396
  }
5759
6397
  /**
5760
6398
  * Initialize PromptLoader and load prompts into memory
@@ -5820,6 +6458,10 @@ var SuperatomSDK = class {
5820
6458
  * Connect to the Superatom WebSocket service
5821
6459
  */
5822
6460
  async connect() {
6461
+ if (this.connected && this.ws && this.ws.readyState === this.ws.OPEN) {
6462
+ logger.info("Already connected to WebSocket");
6463
+ return Promise.resolve();
6464
+ }
5823
6465
  return new Promise((resolve, reject) => {
5824
6466
  try {
5825
6467
  const url = new URL(this.url);
@@ -5882,7 +6524,7 @@ var SuperatomSDK = class {
5882
6524
  });
5883
6525
  break;
5884
6526
  case "USER_PROMPT_REQ":
5885
- handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders).catch((error) => {
6527
+ handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections).catch((error) => {
5886
6528
  logger.error("Failed to handle user prompt request:", error);
5887
6529
  });
5888
6530
  break;