@superatomai/sdk-node 0.0.2-mds → 0.0.3-mds

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1407,6 +1407,7 @@ var ThreadManager = class _ThreadManager {
1407
1407
  var QueryCache = class {
1408
1408
  constructor() {
1409
1409
  this.cache = /* @__PURE__ */ new Map();
1410
+ this.queryIdCache = /* @__PURE__ */ new Map();
1410
1411
  this.ttlMs = 5 * 60 * 1e3;
1411
1412
  // Default: 5 minutes
1412
1413
  this.cleanupInterval = null;
@@ -1501,11 +1502,64 @@ var QueryCache = class {
1501
1502
  expiredCount++;
1502
1503
  }
1503
1504
  }
1505
+ for (const [key, entry] of this.queryIdCache.entries()) {
1506
+ if (now - entry.timestamp > this.ttlMs) {
1507
+ this.queryIdCache.delete(key);
1508
+ expiredCount++;
1509
+ }
1510
+ }
1504
1511
  if (expiredCount > 0) {
1505
1512
  logger.debug(`[QueryCache] Cleaned up ${expiredCount} expired entries`);
1506
1513
  }
1507
1514
  }, 2 * 60 * 1e3);
1508
1515
  }
1516
+ // ============================================
1517
+ // Query ID Store — maps queryId → query (no SQL sent to frontend)
1518
+ // ============================================
1519
+ /**
1520
+ * Generate a unique query ID
1521
+ */
1522
+ generateQueryId() {
1523
+ return `qry_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
1524
+ }
1525
+ /**
1526
+ * Store a query by ID. Returns the generated queryId.
1527
+ * The query is stored server-side; only the queryId is sent to the frontend.
1528
+ */
1529
+ storeQuery(query, data) {
1530
+ const queryId = this.generateQueryId();
1531
+ this.queryIdCache.set(queryId, {
1532
+ queryId,
1533
+ query,
1534
+ data: data || null,
1535
+ timestamp: Date.now()
1536
+ });
1537
+ const queryPreview = typeof query === "string" ? query.substring(0, 50) : JSON.stringify(query).substring(0, 50);
1538
+ logger.debug(`[QueryCache] Stored query as ${queryId} (${queryPreview}...)`);
1539
+ return queryId;
1540
+ }
1541
+ /**
1542
+ * Get a stored query by its ID (not expired)
1543
+ */
1544
+ getQuery(queryId) {
1545
+ const entry = this.queryIdCache.get(queryId);
1546
+ if (!entry) return null;
1547
+ if (Date.now() - entry.timestamp > this.ttlMs) {
1548
+ this.queryIdCache.delete(queryId);
1549
+ return null;
1550
+ }
1551
+ return { query: entry.query, data: entry.data };
1552
+ }
1553
+ /**
1554
+ * Update cached data for a queryId
1555
+ */
1556
+ setQueryData(queryId, data) {
1557
+ const entry = this.queryIdCache.get(queryId);
1558
+ if (entry) {
1559
+ entry.data = data;
1560
+ entry.timestamp = Date.now();
1561
+ }
1562
+ }
1509
1563
  /**
1510
1564
  * Stop cleanup interval (for graceful shutdown)
1511
1565
  */
@@ -1515,6 +1569,7 @@ var QueryCache = class {
1515
1569
  this.cleanupInterval = null;
1516
1570
  }
1517
1571
  this.cache.clear();
1572
+ this.queryIdCache.clear();
1518
1573
  }
1519
1574
  };
1520
1575
  var queryCache = new QueryCache();
@@ -1877,6 +1932,24 @@ function getQueryCacheKey(query) {
1877
1932
  }
1878
1933
  return "";
1879
1934
  }
1935
+ function getCacheKey(collection, op, params) {
1936
+ if (collection === "database" && op === "execute" && params?.sql) {
1937
+ return getQueryCacheKey(params.sql);
1938
+ }
1939
+ if (collection === "external-tools" && op === "execute" && params?.sql) {
1940
+ const toolId = params.toolId || "";
1941
+ const sqlKey = getQueryCacheKey(params.sql);
1942
+ const paramsKey = params.params ? JSON.stringify(params.params) : "";
1943
+ return sqlKey ? `et:${toolId}:${sqlKey}:${paramsKey}` : "";
1944
+ }
1945
+ if (collection === "external-tools" && op === "executeByQueryId" && params?.queryId) {
1946
+ const toolId = params.toolId || "";
1947
+ const filterKey = params.filterParams ? JSON.stringify(params.filterParams) : "";
1948
+ const paramsKey = params.params ? JSON.stringify(params.params) : "";
1949
+ return `etq:${toolId}:${params.queryId}:${paramsKey}:${filterKey}`;
1950
+ }
1951
+ return "";
1952
+ }
1880
1953
  async function handleDataRequest(data, collections, sendMessage) {
1881
1954
  let requestId;
1882
1955
  let collection;
@@ -1903,31 +1976,26 @@ async function handleDataRequest(data, collections, sendMessage) {
1903
1976
  const startTime = performance.now();
1904
1977
  let result;
1905
1978
  let fromCache = false;
1906
- if (collection === "database" && op === "execute" && params?.sql) {
1907
- const cacheKey = getQueryCacheKey(params.sql);
1908
- if (cacheKey) {
1909
- const cachedResult = queryCache.get(cacheKey);
1910
- if (cachedResult !== null) {
1911
- result = cachedResult;
1912
- fromCache = true;
1913
- logger.info(`[QueryCache] Returning cached result for database.execute`);
1914
- }
1979
+ const cacheKey = getCacheKey(collection, op, params);
1980
+ if (cacheKey) {
1981
+ const cachedResult = queryCache.get(cacheKey);
1982
+ if (cachedResult !== null) {
1983
+ result = cachedResult;
1984
+ fromCache = true;
1985
+ logger.info(`[QueryCache] Returning cached result for ${collection}.${op}`);
1915
1986
  }
1916
1987
  }
1917
1988
  if (!fromCache) {
1918
1989
  const handler = collections[collection][op];
1919
1990
  let handlerParams = params || {};
1920
1991
  if (collection === "database" && op === "execute" && params?.sql && typeof params.sql !== "string") {
1921
- const cacheKey = getQueryCacheKey(params.sql);
1922
- handlerParams = { ...params, sql: cacheKey };
1992
+ const queryKey = getQueryCacheKey(params.sql);
1993
+ handlerParams = { ...params, sql: queryKey };
1923
1994
  logger.debug(`[data-request] Converted object query to JSON string for database handler`);
1924
1995
  }
1925
1996
  result = await handler(handlerParams);
1926
- if (collection === "database" && op === "execute" && params?.sql && result) {
1927
- const cacheKey = getQueryCacheKey(params.sql);
1928
- if (cacheKey) {
1929
- queryCache.set(cacheKey, result);
1930
- }
1997
+ if (cacheKey && result) {
1998
+ queryCache.set(cacheKey, result);
1931
1999
  }
1932
2000
  }
1933
2001
  const executionMs = Math.round(performance.now() - startTime);
@@ -2472,9 +2540,6 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
2472
2540
  sendMessage(response);
2473
2541
  }
2474
2542
 
2475
- // src/userResponse/groq.ts
2476
- import dotenv from "dotenv";
2477
-
2478
2543
  // src/userResponse/prompt-loader.ts
2479
2544
  import fs3 from "fs";
2480
2545
  import path2 from "path";
@@ -3576,6 +3641,71 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3576
3641
  `,
3577
3642
  user: `{{USER_PROMPT}}`
3578
3643
  },
3644
+ "agent-main": {
3645
+ system: `You are a data analysis agent with access to multiple data sources.
3646
+ Answer the user's question by querying the appropriate data source(s) using the tools provided.
3647
+
3648
+ ## Available Data Sources
3649
+ {{SOURCE_SUMMARIES}}
3650
+
3651
+ ## How to Use Source Tools
3652
+ Each tool represents a data source. Call a tool with:
3653
+ - **intent**: Describe what data you need in natural language. Be specific about fields, filters, grouping, sorting, and limits. You can request both raw rows AND aggregated values in a single intent.
3654
+ - **aggregation** (optional): Default is "raw". Use "pre-aggregate" for totals/counts/averages, "summary" for high-level metrics.
3655
+
3656
+ ## Writing Good Intents
3657
+ Describe ALL your data needs in one intent per source. The source agent handles it in a single query.
3658
+ - You can combine raw rows and aggregated totals in one request
3659
+ - Be specific about which fields, filters, sorting, and grouping you need
3660
+ - Prefer a SINGLE call per source \u2014 only call again if the result was insufficient or incorrect
3661
+
3662
+ ## Rules
3663
+ - Call the appropriate source tool(s) to get data before answering
3664
+ - You can call multiple source tools if the question requires data from different sources
3665
+ - If a query returns insufficient or incorrect data, call the tool again with a modified intent
3666
+ - If the data is marked as LIMITED, mention it in your analysis (the full dataset may be larger)
3667
+ - After getting all needed data, provide a clear, concise analysis answering the question
3668
+ - For general questions (greetings, help, chitchat), respond directly without calling any tools
3669
+ - Be precise with numbers \u2014 use the exact values from the data
3670
+ - Maximum {{MAX_ROWS}} rows per source query
3671
+
3672
+ ## Current Date/Time
3673
+ {{CURRENT_DATETIME}}
3674
+
3675
+ ---
3676
+
3677
+ ## CONTEXT (for this specific request)
3678
+
3679
+ ### Conversation History
3680
+ {{CONVERSATION_HISTORY}}`,
3681
+ user: `{{USER_PROMPT}}`
3682
+ },
3683
+ "agent-source-query": {
3684
+ system: `You are a data source agent for "{{SOURCE_NAME}}" ({{SOURCE_TYPE}}).
3685
+ Your job is to fetch the requested data using the available tool.
3686
+
3687
+ ## Data Source Schema
3688
+ {{FULL_SCHEMA}}
3689
+
3690
+ ## Rules
3691
+ - Generate the most efficient query for the given intent
3692
+ - Always include a LIMIT of {{MAX_ROWS}} rows maximum
3693
+ - Aggregation mode is "{{AGGREGATION_MODE}}":
3694
+ - "pre-aggregate": Use GROUP BY, COUNT, SUM, AVG etc. to return aggregated results instead of raw rows
3695
+ - "summary": Return a high-level overview with key metrics
3696
+ - "raw": Return individual records as-is
3697
+ - **Combined requests**: The intent may ask for both raw rows and aggregated values together. Handle this in a single query using the capabilities of the data source (e.g., UNION, window functions, subqueries for SQL sources)
3698
+ - Return ONLY the fields needed for the intent \u2014 avoid SELECT *
3699
+ - Use the tool provided to execute your query
3700
+ - If the query fails with an ERROR, analyze the error and try a corrected query
3701
+ - **IMPORTANT: If the tool returns data successfully, STOP. Do NOT call the tool again to get more rows or paginate. The returned data is sufficient.**
3702
+ - Call the tool exactly ONCE unless it returns an error that needs a corrected query
3703
+
3704
+ ## Current Date/Time
3705
+ {{CURRENT_DATETIME}}`,
3706
+ user: `## Task
3707
+ {{INTENT}}`
3708
+ },
3579
3709
  "dash-filter-picker": {
3580
3710
  system: `You are a dashboard filter expert that creates filter components and updates existing dashboard components to work with the filter.
3581
3711
 
@@ -4150,587 +4280,868 @@ function validateAndFixSqlQuery(query, dbType) {
4150
4280
  };
4151
4281
  }
4152
4282
 
4153
- // src/userResponse/schema.ts
4154
- import path3 from "path";
4283
+ // src/utils/user-prompt-error-logger.ts
4155
4284
  import fs4 from "fs";
4156
- var Schema = class {
4157
- constructor(schemaFilePath) {
4158
- this.cachedSchema = null;
4159
- this.schemaFilePath = schemaFilePath || path3.join(process.cwd(), "../analysis/data/schema.json");
4285
+ import path3 from "path";
4286
+ var UserPromptErrorLogger = class {
4287
+ constructor() {
4288
+ this.logStream = null;
4289
+ this.hasErrors = false;
4290
+ this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH || path3.join(process.cwd(), "user-prompt-req-errors");
4291
+ this.enabled = process.env.USER_PROMPT_ERROR_LOGGING !== "false";
4160
4292
  }
4161
4293
  /**
4162
- * Gets the database schema from the schema file
4163
- * @returns Parsed schema object or null if error occurs
4294
+ * Reset the error log file for a new request
4164
4295
  */
4165
- getDatabaseSchema() {
4296
+ resetLogFile(requestContext) {
4297
+ if (!this.enabled) return;
4166
4298
  try {
4167
- const dir = path3.dirname(this.schemaFilePath);
4168
- if (!fs4.existsSync(dir)) {
4169
- logger.info(`Creating directory structure: ${dir}`);
4170
- fs4.mkdirSync(dir, { recursive: true });
4299
+ if (this.logStream) {
4300
+ this.logStream.end();
4301
+ this.logStream = null;
4171
4302
  }
4172
- if (!fs4.existsSync(this.schemaFilePath)) {
4173
- logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
4174
- const initialSchema = {
4175
- database: "",
4176
- schema: "",
4177
- description: "",
4178
- tables: [],
4179
- relationships: []
4180
- };
4181
- fs4.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
4182
- this.cachedSchema = initialSchema;
4183
- return initialSchema;
4303
+ const dir = path3.dirname(this.logPath);
4304
+ if (dir !== "." && !fs4.existsSync(dir)) {
4305
+ fs4.mkdirSync(dir, { recursive: true });
4184
4306
  }
4185
- const fileContent = fs4.readFileSync(this.schemaFilePath, "utf-8");
4186
- const schema2 = JSON.parse(fileContent);
4187
- this.cachedSchema = schema2;
4188
- return schema2;
4307
+ this.logStream = fs4.createWriteStream(this.logPath, { flags: "w" });
4308
+ this.hasErrors = false;
4309
+ const header = `================================================================================
4310
+ USER PROMPT REQUEST ERROR LOG
4311
+ Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4312
+ ${requestContext ? `Context: ${requestContext}` : ""}
4313
+ ================================================================================
4314
+
4315
+ `;
4316
+ this.logStream.write(header);
4189
4317
  } catch (error) {
4190
- logger.error("Error parsing schema file:", error);
4191
- return null;
4318
+ console.error("[UserPromptErrorLogger] Failed to reset log file:", error);
4192
4319
  }
4193
4320
  }
4194
4321
  /**
4195
- * Gets the cached schema or loads it if not cached
4196
- * @returns Cached schema or freshly loaded schema
4322
+ * Log a JSON parse error with the raw string that failed
4197
4323
  */
4198
- getSchema() {
4199
- if (this.cachedSchema) {
4200
- return this.cachedSchema;
4201
- }
4202
- return this.getDatabaseSchema();
4324
+ logJsonParseError(context, rawString, error) {
4325
+ if (!this.enabled) return;
4326
+ this.hasErrors = true;
4327
+ const entry = `
4328
+ --------------------------------------------------------------------------------
4329
+ [${(/* @__PURE__ */ new Date()).toISOString()}] JSON PARSE ERROR
4330
+ --------------------------------------------------------------------------------
4331
+ Context: ${context}
4332
+ Error: ${error.message}
4333
+
4334
+ Raw String (${rawString.length} chars):
4335
+ --------------------------------------------------------------------------------
4336
+ ${rawString}
4337
+ --------------------------------------------------------------------------------
4338
+
4339
+ Stack Trace:
4340
+ ${error.stack || "No stack trace available"}
4341
+
4342
+ `;
4343
+ this.write(entry);
4344
+ console.error(`[UserPromptError] JSON Parse Error in ${context}: ${error.message}`);
4203
4345
  }
4204
4346
  /**
4205
- * Generates database schema documentation for LLM from Snowflake JSON schema
4206
- * @returns Formatted schema documentation string
4347
+ * Log a general error with full details
4207
4348
  */
4208
- generateSchemaDocumentation() {
4209
- const schema2 = this.getSchema();
4210
- if (!schema2) {
4211
- logger.warn("No database schema found.");
4212
- return "No database schema available.";
4213
- }
4214
- const tables = [];
4215
- tables.push(`Database: ${schema2.database}`);
4216
- tables.push(`Schema: ${schema2.schema}`);
4217
- tables.push(`Description: ${schema2.description}`);
4218
- tables.push("");
4219
- tables.push("=".repeat(80));
4220
- tables.push("");
4221
- for (const table of schema2.tables) {
4222
- const tableInfo = [];
4223
- tableInfo.push(`TABLE: ${table.fullName}`);
4224
- tableInfo.push(`Description: ${table.description}`);
4225
- tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
4226
- tableInfo.push("");
4227
- tableInfo.push("Columns:");
4228
- for (const column of table.columns) {
4229
- let columnLine = ` - ${column.name}: ${column.type}`;
4230
- if (column.isPrimaryKey) {
4231
- columnLine += " (PRIMARY KEY)";
4232
- }
4233
- if (column.isForeignKey && column.references) {
4234
- columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
4235
- }
4236
- if (!column.nullable) {
4237
- columnLine += " NOT NULL";
4238
- }
4239
- if (column.description) {
4240
- columnLine += ` - ${column.description}`;
4241
- }
4242
- tableInfo.push(columnLine);
4243
- if (column.sampleValues && column.sampleValues.length > 0) {
4244
- tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
4245
- }
4246
- if (column.statistics) {
4247
- const stats = column.statistics;
4248
- if (stats.min !== void 0 && stats.max !== void 0) {
4249
- tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
4250
- }
4251
- if (stats.distinct !== void 0) {
4252
- tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
4253
- }
4254
- }
4255
- }
4256
- tableInfo.push("");
4257
- tables.push(tableInfo.join("\n"));
4349
+ logError(context, error, additionalData) {
4350
+ if (!this.enabled) return;
4351
+ this.hasErrors = true;
4352
+ const errorMessage = error instanceof Error ? error.message : error;
4353
+ const errorStack = error instanceof Error ? error.stack : void 0;
4354
+ let entry = `
4355
+ --------------------------------------------------------------------------------
4356
+ [${(/* @__PURE__ */ new Date()).toISOString()}] ERROR
4357
+ --------------------------------------------------------------------------------
4358
+ Context: ${context}
4359
+ Error: ${errorMessage}
4360
+ `;
4361
+ if (additionalData) {
4362
+ entry += `
4363
+ Additional Data:
4364
+ ${JSON.stringify(additionalData, null, 2)}
4365
+ `;
4258
4366
  }
4259
- tables.push("=".repeat(80));
4260
- tables.push("");
4261
- tables.push("TABLE RELATIONSHIPS:");
4262
- tables.push("");
4263
- for (const rel of schema2.relationships) {
4264
- tables.push(`${rel.from} -> ${rel.to} (${rel.type}): ${rel.keys.join(" = ")}`);
4367
+ if (errorStack) {
4368
+ entry += `
4369
+ Stack Trace:
4370
+ ${errorStack}
4371
+ `;
4265
4372
  }
4266
- return tables.join("\n");
4373
+ entry += `--------------------------------------------------------------------------------
4374
+
4375
+ `;
4376
+ this.write(entry);
4377
+ console.error(`[UserPromptError] ${context}: ${errorMessage}`);
4267
4378
  }
4268
4379
  /**
4269
- * Clears the cached schema, forcing a reload on next access
4380
+ * Log a SQL query error with the full query
4270
4381
  */
4271
- clearCache() {
4272
- this.cachedSchema = null;
4382
+ logSqlError(query, error, params) {
4383
+ if (!this.enabled) return;
4384
+ this.hasErrors = true;
4385
+ const errorMessage = error instanceof Error ? error.message : error;
4386
+ const entry = `
4387
+ --------------------------------------------------------------------------------
4388
+ [${(/* @__PURE__ */ new Date()).toISOString()}] SQL QUERY ERROR
4389
+ --------------------------------------------------------------------------------
4390
+ Error: ${errorMessage}
4391
+
4392
+ Query (${query.length} chars):
4393
+ --------------------------------------------------------------------------------
4394
+ ${query}
4395
+ --------------------------------------------------------------------------------
4396
+ ${params ? `
4397
+ Parameters: ${JSON.stringify(params)}` : ""}
4398
+
4399
+ `;
4400
+ this.write(entry);
4401
+ console.error(`[UserPromptError] SQL Error: ${errorMessage}`);
4273
4402
  }
4274
4403
  /**
4275
- * Sets a custom schema file path
4276
- * @param filePath - Path to the schema file
4404
+ * Log an LLM API error
4277
4405
  */
4278
- setSchemaPath(filePath) {
4279
- this.schemaFilePath = filePath;
4280
- this.clearCache();
4281
- }
4282
- };
4283
- var schema = new Schema();
4284
-
4285
- // src/llm.ts
4286
- import Anthropic from "@anthropic-ai/sdk";
4287
- import Groq from "groq-sdk";
4288
- import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai";
4289
- import OpenAI from "openai";
4290
- import { jsonrepair } from "jsonrepair";
4291
-
4292
- // src/utils/llm-usage-logger.ts
4293
- import fs5 from "fs";
4294
- import path4 from "path";
4295
- var PRICING = {
4296
- // Anthropic (December 2025)
4297
- "claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4298
- "claude-opus-4-5-20251101": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4299
- "claude-sonnet-4-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4300
- "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4301
- "claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4302
- "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4303
- "claude-3-5-sonnet-20241022": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4304
- "claude-3-5-haiku-20241022": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4305
- "claude-3-opus-20240229": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
4306
- "claude-3-sonnet-20240229": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4307
- "claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
4308
- // OpenAI (December 2025)
4309
- "gpt-5": { input: 1.25, output: 10 },
4310
- "gpt-5-mini": { input: 0.25, output: 2 },
4311
- "gpt-4o": { input: 5, output: 15 },
4312
- // Updated pricing as of late 2025
4313
- "gpt-4o-mini": { input: 0.15, output: 0.6 },
4314
- "gpt-4-turbo": { input: 10, output: 30 },
4315
- "gpt-4": { input: 30, output: 60 },
4316
- "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
4317
- // Google Gemini (January 2026)
4318
- "gemini-3-pro-preview": { input: 2, output: 12 },
4319
- // New Gemini 3
4320
- "gemini-3-flash-preview": { input: 0.5, output: 3 },
4321
- // For prompts ≤200K tokens, 2x for >200K
4322
- "gemini-2.5-flash": { input: 0.3, output: 2.5 },
4323
- // Paid tier: $0.30 input (text/image/video), $2.50 output (includes thinking)
4324
- "gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
4325
- "gemini-2.0-flash": { input: 0.1, output: 0.4 },
4326
- "gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
4327
- "gemini-1.5-pro": { input: 1.25, output: 5 },
4328
- "gemini-1.5-flash": { input: 0.075, output: 0.3 },
4329
- // Groq (December 2025)
4330
- "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
4331
- "llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
4332
- "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
4333
- "llama-4-scout-17b-16e": { input: 0.11, output: 0.34 },
4334
- "llama-4-maverick-17b-128e": { input: 0.2, output: 0.6 },
4335
- "mixtral-8x7b-32768": { input: 0.27, output: 0.27 },
4336
- "qwen3-32b": { input: 0.29, output: 0.59 }
4337
- };
4338
- var DEFAULT_PRICING = { input: 3, output: 15 };
4339
- var LLMUsageLogger = class {
4340
- constructor() {
4341
- this.logStream = null;
4342
- this.sessionStats = {
4343
- totalCalls: 0,
4344
- totalInputTokens: 0,
4345
- totalOutputTokens: 0,
4346
- totalCacheReadTokens: 0,
4347
- totalCacheWriteTokens: 0,
4348
- totalCostUSD: 0,
4349
- totalDurationMs: 0
4350
- };
4351
- this.logPath = process.env.LLM_USAGE_LOG_PATH || path4.join(process.cwd(), "llm-usage-logs");
4352
- this.enabled = process.env.LLM_USAGE_LOGGING !== "false";
4353
- if (this.enabled) {
4354
- this.initLogStream();
4406
+ logLlmError(provider, model, method, error, requestData) {
4407
+ if (!this.enabled) return;
4408
+ this.hasErrors = true;
4409
+ const errorMessage = error instanceof Error ? error.message : error;
4410
+ const errorStack = error instanceof Error ? error.stack : void 0;
4411
+ let entry = `
4412
+ --------------------------------------------------------------------------------
4413
+ [${(/* @__PURE__ */ new Date()).toISOString()}] LLM API ERROR
4414
+ --------------------------------------------------------------------------------
4415
+ Provider: ${provider}
4416
+ Model: ${model}
4417
+ Method: ${method}
4418
+ Error: ${errorMessage}
4419
+ `;
4420
+ if (requestData) {
4421
+ const dataStr = JSON.stringify(requestData, null, 2);
4422
+ const truncated = dataStr.length > 5e3 ? dataStr.substring(0, 5e3) + "\n... [truncated]" : dataStr;
4423
+ entry += `
4424
+ Request Data:
4425
+ ${truncated}
4426
+ `;
4355
4427
  }
4356
- }
4357
- initLogStream() {
4358
- try {
4359
- const dir = path4.dirname(this.logPath);
4360
- if (!fs5.existsSync(dir)) {
4361
- fs5.mkdirSync(dir, { recursive: true });
4362
- }
4363
- this.logStream = fs5.createWriteStream(this.logPath, { flags: "a" });
4364
- if (!fs5.existsSync(this.logPath) || fs5.statSync(this.logPath).size === 0) {
4365
- this.writeHeader();
4366
- }
4367
- } catch (error) {
4368
- console.error("[LLM-Usage-Logger] Failed to initialize log stream:", error);
4369
- this.enabled = false;
4428
+ if (errorStack) {
4429
+ entry += `
4430
+ Stack Trace:
4431
+ ${errorStack}
4432
+ `;
4370
4433
  }
4371
- }
4372
- writeHeader() {
4373
- const header = `
4374
- ================================================================================
4375
- LLM USAGE LOG - Session Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4376
- ================================================================================
4377
- Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4378
- Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
4379
- Cost: $X.XXXXXX | Time: Xms
4380
- ================================================================================
4434
+ entry += `--------------------------------------------------------------------------------
4381
4435
 
4382
4436
  `;
4383
- this.logStream?.write(header);
4384
- }
4385
- /**
4386
- * Calculate cost based on token usage and model
4387
- */
4388
- calculateCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
4389
- let pricing = PRICING[model];
4390
- if (!pricing) {
4391
- const modelLower = model.toLowerCase();
4392
- for (const [key, value] of Object.entries(PRICING)) {
4393
- if (modelLower.includes(key.toLowerCase()) || key.toLowerCase().includes(modelLower)) {
4394
- pricing = value;
4395
- break;
4396
- }
4397
- }
4398
- }
4399
- pricing = pricing || DEFAULT_PRICING;
4400
- const inputCost = inputTokens / 1e6 * pricing.input;
4401
- const outputCost = outputTokens / 1e6 * pricing.output;
4402
- const cacheReadCost = cacheReadTokens / 1e6 * (pricing.cacheRead || pricing.input * 0.1);
4403
- const cacheWriteCost = cacheWriteTokens / 1e6 * (pricing.cacheWrite || pricing.input * 1.25);
4404
- return inputCost + outputCost + cacheReadCost + cacheWriteCost;
4437
+ this.write(entry);
4438
+ console.error(`[UserPromptError] LLM Error (${provider}/${model}): ${errorMessage}`);
4405
4439
  }
4406
4440
  /**
4407
- * Log an LLM API call
4441
+ * Log tool execution error
4408
4442
  */
4409
- log(entry) {
4443
+ logToolError(toolName, toolInput, error) {
4410
4444
  if (!this.enabled) return;
4411
- this.sessionStats.totalCalls++;
4412
- this.sessionStats.totalInputTokens += entry.inputTokens;
4413
- this.sessionStats.totalOutputTokens += entry.outputTokens;
4414
- this.sessionStats.totalCacheReadTokens += entry.cacheReadTokens || 0;
4415
- this.sessionStats.totalCacheWriteTokens += entry.cacheWriteTokens || 0;
4416
- this.sessionStats.totalCostUSD += entry.costUSD;
4417
- this.sessionStats.totalDurationMs += entry.durationMs;
4418
- const cacheInfo = entry.cacheReadTokens || entry.cacheWriteTokens ? ` CACHE_R=${entry.cacheReadTokens || 0} CACHE_W=${entry.cacheWriteTokens || 0}` : "";
4419
- const toolInfo = entry.toolCalls ? ` | Tools: ${entry.toolCalls}` : "";
4420
- const errorInfo = entry.error ? ` | ERROR: ${entry.error}` : "";
4421
- const status = entry.success ? "\u2713" : "\u2717";
4422
- let cacheStatus = "";
4423
- if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
4424
- const savedCost = entry.cacheReadTokens / 1e6 * 2.7;
4425
- cacheStatus = ` \u26A1 CACHE HIT! Saved ~$${savedCost.toFixed(4)}`;
4426
- } else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
4427
- cacheStatus = " \u{1F4DD} Cache created (next request will be cheaper)";
4428
- }
4429
- const logLine = `[${entry.timestamp}] [${entry.requestId}] ${status} ${entry.provider}/${entry.model} [${entry.method}]
4430
- Tokens: IN=${entry.inputTokens} OUT=${entry.outputTokens}${cacheInfo} TOTAL=${entry.totalTokens}
4431
- Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
4432
- `;
4433
- this.logStream?.write(logLine);
4434
- }
4435
- /**
4436
- * Log session summary (call at end of request)
4437
- */
4438
- logSessionSummary(requestContext) {
4439
- if (!this.enabled || this.sessionStats.totalCalls === 0) return;
4440
- const cacheReadSavings = this.sessionStats.totalCacheReadTokens / 1e6 * 2.7;
4441
- const hasCaching = this.sessionStats.totalCacheReadTokens > 0 || this.sessionStats.totalCacheWriteTokens > 0;
4442
- let cacheSection = "";
4443
- if (hasCaching) {
4444
- cacheSection = `
4445
- Cache Statistics:
4446
- Cache Read Tokens: ${this.sessionStats.totalCacheReadTokens.toLocaleString()}${this.sessionStats.totalCacheReadTokens > 0 ? " \u26A1" : ""}
4447
- Cache Write Tokens: ${this.sessionStats.totalCacheWriteTokens.toLocaleString()}${this.sessionStats.totalCacheWriteTokens > 0 ? " \u{1F4DD}" : ""}
4448
- Estimated Savings: $${cacheReadSavings.toFixed(4)}`;
4449
- }
4450
- const summary = `
4445
+ this.hasErrors = true;
4446
+ const errorMessage = error instanceof Error ? error.message : error;
4447
+ const errorStack = error instanceof Error ? error.stack : void 0;
4448
+ const entry = `
4451
4449
  --------------------------------------------------------------------------------
4452
- SESSION SUMMARY${requestContext ? ` (${requestContext})` : ""}
4450
+ [${(/* @__PURE__ */ new Date()).toISOString()}] TOOL EXECUTION ERROR
4453
4451
  --------------------------------------------------------------------------------
4454
- Total LLM Calls: ${this.sessionStats.totalCalls}
4455
- Total Input Tokens: ${this.sessionStats.totalInputTokens.toLocaleString()}
4456
- Total Output Tokens: ${this.sessionStats.totalOutputTokens.toLocaleString()}
4457
- Total Tokens: ${(this.sessionStats.totalInputTokens + this.sessionStats.totalOutputTokens).toLocaleString()}
4458
- Total Cost: $${this.sessionStats.totalCostUSD.toFixed(6)}
4459
- Total Time: ${this.sessionStats.totalDurationMs}ms (${(this.sessionStats.totalDurationMs / 1e3).toFixed(2)}s)
4460
- Avg Cost/Call: $${(this.sessionStats.totalCostUSD / this.sessionStats.totalCalls).toFixed(6)}
4461
- Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStats.totalCalls)}ms${cacheSection}
4452
+ Tool: ${toolName}
4453
+ Error: ${errorMessage}
4454
+
4455
+ Tool Input:
4456
+ ${JSON.stringify(toolInput, null, 2)}
4457
+ ${errorStack ? `
4458
+ Stack Trace:
4459
+ ${errorStack}` : ""}
4462
4460
  --------------------------------------------------------------------------------
4463
4461
 
4464
4462
  `;
4465
- this.logStream?.write(summary);
4466
- }
4467
- /**
4468
- * Reset session stats (call at start of new user request)
4469
- */
4470
- resetSession() {
4471
- this.sessionStats = {
4472
- totalCalls: 0,
4473
- totalInputTokens: 0,
4474
- totalOutputTokens: 0,
4475
- totalCacheReadTokens: 0,
4476
- totalCacheWriteTokens: 0,
4477
- totalCostUSD: 0,
4478
- totalDurationMs: 0
4479
- };
4463
+ this.write(entry);
4464
+ console.error(`[UserPromptError] Tool Error (${toolName}): ${errorMessage}`);
4480
4465
  }
4481
4466
  /**
4482
- * Reset the log file for a new request (clears previous logs)
4483
- * Call this at the start of each USER_PROMPT_REQ
4467
+ * Write final summary if there were errors
4484
4468
  */
4485
- resetLogFile(requestContext) {
4486
- if (!this.enabled) return;
4487
- try {
4488
- if (this.logStream) {
4489
- this.logStream.end();
4490
- this.logStream = null;
4491
- }
4492
- this.logStream = fs5.createWriteStream(this.logPath, { flags: "w" });
4493
- const header = `
4494
- ================================================================================
4495
- LLM USAGE LOG - Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4496
- ${requestContext ? `Context: ${requestContext}` : ""}
4469
+ writeSummary() {
4470
+ if (!this.enabled || !this.hasErrors) return;
4471
+ const summary = `
4497
4472
  ================================================================================
4498
- Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4499
- Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
4500
- Cost: $X.XXXXXX | Time: Xms
4473
+ REQUEST COMPLETED WITH ERRORS
4474
+ Time: ${(/* @__PURE__ */ new Date()).toISOString()}
4501
4475
  ================================================================================
4502
-
4503
4476
  `;
4504
- this.logStream.write(header);
4505
- this.resetSession();
4506
- } catch (error) {
4507
- console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
4508
- }
4509
- }
4510
- /**
4511
- * Get current session stats
4512
- */
4513
- getSessionStats() {
4514
- return { ...this.sessionStats };
4477
+ this.write(summary);
4515
4478
  }
4516
4479
  /**
4517
- * Generate a unique request ID
4480
+ * Check if any errors were logged
4518
4481
  */
4519
- generateRequestId() {
4520
- return `req-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
4482
+ hadErrors() {
4483
+ return this.hasErrors;
4521
4484
  }
4522
- };
4523
- var llmUsageLogger = new LLMUsageLogger();
4485
+ write(content) {
4486
+ if (this.logStream) {
4487
+ this.logStream.write(content);
4488
+ }
4489
+ }
4490
+ };
4491
+ var userPromptErrorLogger = new UserPromptErrorLogger();
4492
+
4493
+ // src/utils/bm25l-reranker.ts
4494
+ var BM25L = class {
4495
+ /**
4496
+ * @param documents - Array of raw documents (strings)
4497
+ * @param opts - Optional BM25L parameters
4498
+ */
4499
+ constructor(documents = [], opts = {}) {
4500
+ if (!Array.isArray(documents)) {
4501
+ throw new Error("BM25L: documents must be an array of strings.");
4502
+ }
4503
+ this.k1 = typeof opts.k1 === "number" ? opts.k1 : 1.5;
4504
+ this.b = typeof opts.b === "number" ? opts.b : 0.75;
4505
+ this.delta = typeof opts.delta === "number" ? opts.delta : 0.5;
4506
+ this.documents = documents.map((d) => typeof d === "string" ? this.tokenize(d) : []);
4507
+ this.docLengths = this.documents.map((doc) => doc.length);
4508
+ this.avgDocLength = this.docLengths.reduce((a, b) => a + b, 0) / (this.docLengths.length || 1);
4509
+ this.termDocFreq = {};
4510
+ this.documents.forEach((doc) => {
4511
+ const seen = /* @__PURE__ */ new Set();
4512
+ doc.forEach((term) => {
4513
+ if (!seen.has(term)) {
4514
+ seen.add(term);
4515
+ this.termDocFreq[term] = (this.termDocFreq[term] || 0) + 1;
4516
+ }
4517
+ });
4518
+ });
4519
+ }
4520
+ /**
4521
+ * Tokenize text into lowercase alphanumeric tokens
4522
+ */
4523
+ tokenize(text) {
4524
+ if (typeof text !== "string") return [];
4525
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
4526
+ }
4527
+ /**
4528
+ * Compute IDF (Inverse Document Frequency) with smoothing
4529
+ */
4530
+ idf(term) {
4531
+ const df = this.termDocFreq[term] || 0;
4532
+ const N = this.documents.length || 1;
4533
+ return Math.log(1 + (N - df + 0.5) / (df + 0.5));
4534
+ }
4535
+ /**
4536
+ * Compute BM25L score for a single document
4537
+ */
4538
+ score(query, docIndex) {
4539
+ if (typeof query !== "string") return 0;
4540
+ if (docIndex < 0 || docIndex >= this.documents.length) return 0;
4541
+ const tokens = this.tokenize(query);
4542
+ if (tokens.length === 0) return 0;
4543
+ const doc = this.documents[docIndex];
4544
+ const docLength = this.docLengths[docIndex] || 1;
4545
+ const freq = {};
4546
+ for (const t of doc) {
4547
+ freq[t] = (freq[t] || 0) + 1;
4548
+ }
4549
+ let sum = 0;
4550
+ for (const term of tokens) {
4551
+ const tf = freq[term] || 0;
4552
+ if (tf === 0) continue;
4553
+ const idfVal = this.idf(term);
4554
+ let tfL = tf - this.b * (docLength / this.avgDocLength) + this.delta;
4555
+ if (tfL < 0) tfL = 0;
4556
+ sum += idfVal * (tfL / (this.k1 + tfL));
4557
+ }
4558
+ return sum;
4559
+ }
4560
+ /**
4561
+ * Search and rank all documents
4562
+ */
4563
+ search(query) {
4564
+ return this.documents.map((_, i) => ({
4565
+ index: i,
4566
+ score: this.score(query, i)
4567
+ })).sort((a, b) => b.score - a.score);
4568
+ }
4569
+ };
4570
+ function normalizeScores(scores) {
4571
+ if (scores.length === 0) return [];
4572
+ const min = Math.min(...scores);
4573
+ const max = Math.max(...scores);
4574
+ if (max === min) {
4575
+ return scores.map(() => max === 0 ? 0 : 1);
4576
+ }
4577
+ return scores.map((score) => (score - min) / (max - min));
4578
+ }
4579
+ function hybridRerank(query, items, getDocument, getSemanticScore, options = {}) {
4580
+ const {
4581
+ semanticWeight = 0.7,
4582
+ bm25Weight = 0.3,
4583
+ minScore = 0,
4584
+ k1 = 1.5,
4585
+ b = 0.75,
4586
+ delta = 0.5
4587
+ } = options;
4588
+ if (items.length === 0) return [];
4589
+ const documents = items.map(getDocument);
4590
+ const semanticScores = items.map(getSemanticScore);
4591
+ const bm25 = new BM25L(documents, { k1, b, delta });
4592
+ const bm25Scores = items.map((_, i) => bm25.score(query, i));
4593
+ const normalizedSemantic = normalizeScores(semanticScores);
4594
+ const normalizedBM25 = normalizeScores(bm25Scores);
4595
+ const results = items.map((item, i) => {
4596
+ const hybridScore = semanticWeight * normalizedSemantic[i] + bm25Weight * normalizedBM25[i];
4597
+ return {
4598
+ item,
4599
+ originalIndex: i,
4600
+ semanticScore: semanticScores[i],
4601
+ bm25Score: bm25Scores[i],
4602
+ hybridScore
4603
+ };
4604
+ });
4605
+ return results.filter((r) => r.hybridScore >= minScore).sort((a, b2) => b2.hybridScore - a.hybridScore);
4606
+ }
4607
+ function rerankChromaResults(query, chromaResults, options = {}) {
4608
+ const ids = chromaResults.ids[0] || [];
4609
+ const documents = chromaResults.documents[0] || [];
4610
+ const metadatas = chromaResults.metadatas[0] || [];
4611
+ const distances = chromaResults.distances[0] || [];
4612
+ if (ids.length === 0) return [];
4613
+ const items = ids.map((id, i) => ({
4614
+ id,
4615
+ document: documents[i],
4616
+ metadata: metadatas[i],
4617
+ distance: distances[i]
4618
+ }));
4619
+ const reranked = hybridRerank(
4620
+ query,
4621
+ items,
4622
+ (item) => item.document || "",
4623
+ // Convert L2 distance to similarity score
4624
+ (item) => 1 / (1 + item.distance),
4625
+ options
4626
+ );
4627
+ return reranked.map((r) => ({
4628
+ id: r.item.id,
4629
+ document: r.item.document,
4630
+ metadata: r.item.metadata,
4631
+ distance: r.item.distance,
4632
+ semanticScore: r.semanticScore,
4633
+ bm25Score: r.bm25Score,
4634
+ hybridScore: r.hybridScore
4635
+ }));
4636
+ }
4637
+ function rerankConversationResults(query, results, options = {}) {
4638
+ if (results.length === 0) return [];
4639
+ const reranked = hybridRerank(
4640
+ query,
4641
+ results,
4642
+ (item) => item.userPrompt || "",
4643
+ (item) => item.similarity || 0,
4644
+ options
4645
+ );
4646
+ return reranked.map((r) => ({
4647
+ ...r.item,
4648
+ hybridScore: r.hybridScore,
4649
+ bm25Score: r.bm25Score
4650
+ }));
4651
+ }
4652
+
4653
+ // src/userResponse/conversation-search.ts
4654
+ var searchConversations = async ({
4655
+ userPrompt,
4656
+ collections,
4657
+ userId,
4658
+ similarityThreshold = 0.6
4659
+ }) => {
4660
+ try {
4661
+ if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["search"]) {
4662
+ logger.info("[ConversationSearch] conversation-history.search collection not registered, skipping");
4663
+ return null;
4664
+ }
4665
+ logger.info(`[ConversationSearch] Searching conversations for: "${userPrompt.substring(0, 50)}..."`);
4666
+ logger.info(`[ConversationSearch] Using similarity threshold: ${(similarityThreshold * 100).toFixed(0)}%`);
4667
+ const result = await collections["conversation-history"]["search"]({
4668
+ userPrompt,
4669
+ userId,
4670
+ threshold: similarityThreshold
4671
+ });
4672
+ if (!result) {
4673
+ logger.info("[ConversationSearch] No matching conversations found");
4674
+ return null;
4675
+ }
4676
+ if (!result.uiBlock) {
4677
+ logger.error("[ConversationSearch] No UI block in conversation search result");
4678
+ return null;
4679
+ }
4680
+ const similarity = result.similarity || 0;
4681
+ logger.info(`[ConversationSearch] Best match similarity: ${(similarity * 100).toFixed(2)}%`);
4682
+ if (similarity < similarityThreshold) {
4683
+ logger.info(
4684
+ `[ConversationSearch] Best match has similarity ${(similarity * 100).toFixed(2)}% but below threshold ${(similarityThreshold * 100).toFixed(2)}%`
4685
+ );
4686
+ return null;
4687
+ }
4688
+ logger.info(
4689
+ `[ConversationSearch] Found matching conversation with similarity ${(similarity * 100).toFixed(2)}%`
4690
+ );
4691
+ logger.debug(`[ConversationSearch] Matched prompt: "${result.metadata?.userPrompt?.substring(0, 50)}..."`);
4692
+ return result;
4693
+ } catch (error) {
4694
+ const errorMsg = error instanceof Error ? error.message : String(error);
4695
+ logger.warn(`[ConversationSearch] Error searching conversations: ${errorMsg}`);
4696
+ return null;
4697
+ }
4698
+ };
4699
+ var searchConversationsWithReranking = async (options) => {
4700
+ const {
4701
+ userPrompt,
4702
+ collections,
4703
+ userId,
4704
+ similarityThreshold = 0.6,
4705
+ rerankCandidates = 50,
4706
+ // Fetch more candidates for better reranking
4707
+ hybridOptions = {
4708
+ semanticWeight: 0.7,
4709
+ bm25Weight: 0.3
4710
+ }
4711
+ } = options;
4712
+ try {
4713
+ if (!collections || !collections["conversation-history"]) {
4714
+ logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
4715
+ return null;
4716
+ }
4717
+ if (!collections["conversation-history"]["searchMultiple"]) {
4718
+ logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
4719
+ return searchConversations({
4720
+ userPrompt,
4721
+ collections,
4722
+ userId,
4723
+ similarityThreshold
4724
+ });
4725
+ }
4726
+ const results = await collections["conversation-history"]["searchMultiple"]({
4727
+ userPrompt,
4728
+ userId,
4729
+ limit: rerankCandidates,
4730
+ threshold: 0
4731
+ // No threshold - get all candidates for reranking
4732
+ });
4733
+ if (!results || results.length === 0) {
4734
+ logger.info("[ConversationSearch] No conversations found in database");
4735
+ return null;
4736
+ }
4737
+ logger.info(`[ConversationSearch] Retrieved ${results.length} candidates for reranking`);
4738
+ const candidatesForReranking = results.map((r) => ({
4739
+ ...r,
4740
+ userPrompt: r.metadata?.userPrompt || ""
4741
+ }));
4742
+ const reranked = rerankConversationResults(userPrompt, candidatesForReranking, hybridOptions);
4743
+ if (reranked.length === 0) {
4744
+ logger.info("[ConversationSearch] No results after reranking");
4745
+ return null;
4746
+ }
4747
+ const best = reranked[0];
4748
+ const hybridScore = best.hybridScore;
4749
+ const semanticScore = best.similarity || 0;
4750
+ const matchedUserPrompt = best.userPrompt || best.metadata?.userPrompt || "";
4751
+ logger.info(`[ConversationSearch] Best match after reranking:`);
4752
+ logger.info(` - Hybrid score: ${(hybridScore * 100).toFixed(2)}%`);
4753
+ logger.info(` - Semantic score: ${(semanticScore * 100).toFixed(2)}%`);
4754
+ logger.info(` - BM25L score: ${best.bm25Score.toFixed(4)}`);
4755
+ logger.info(` - Matched prompt: "${matchedUserPrompt}"`);
4756
+ logger.info(` - Query prompt: "${userPrompt}"`);
4757
+ if (semanticScore < similarityThreshold) {
4758
+ logger.info(
4759
+ `[ConversationSearch] Semantic score ${(semanticScore * 100).toFixed(2)}% below threshold ${(similarityThreshold * 100).toFixed(2)}% - rejecting match`
4760
+ );
4761
+ return null;
4762
+ }
4763
+ logger.info(
4764
+ `[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
4765
+ );
4766
+ return {
4767
+ uiBlock: best.uiBlock,
4768
+ similarity: semanticScore,
4769
+ hybridScore,
4770
+ bm25Score: best.bm25Score,
4771
+ metadata: best.metadata
4772
+ };
4773
+ } catch (error) {
4774
+ const errorMsg = error instanceof Error ? error.message : String(error);
4775
+ logger.warn(`[ConversationSearch] Error in hybrid search: ${errorMsg}`);
4776
+ return null;
4777
+ }
4778
+ };
4779
+ var ConversationSearch = {
4780
+ searchConversations,
4781
+ searchConversationsWithReranking
4782
+ };
4783
+ var conversation_search_default = ConversationSearch;
4524
4784
 
4525
- // src/utils/user-prompt-error-logger.ts
4526
- import fs6 from "fs";
4527
- import path5 from "path";
4528
- var UserPromptErrorLogger = class {
4529
- constructor() {
4530
- this.logStream = null;
4531
- this.hasErrors = false;
4532
- this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH || path5.join(process.cwd(), "user-prompt-req-errors");
4533
- this.enabled = process.env.USER_PROMPT_ERROR_LOGGING !== "false";
4785
+ // src/userResponse/constants.ts
4786
+ var MAX_QUERY_VALIDATION_RETRIES = 3;
4787
+ var MAX_QUERY_ATTEMPTS = 6;
4788
+ var MAX_TOOL_ATTEMPTS = 3;
4789
+ var STREAM_FLUSH_INTERVAL_MS = 50;
4790
+ var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
4791
+ var STREAM_DELAY_MS = 50;
4792
+ var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
4793
+ var MAX_TOKENS_QUERY_FIX = 2048;
4794
+ var MAX_TOKENS_COMPONENT_MATCHING = 8192;
4795
+ var MAX_TOKENS_CLASSIFICATION = 1500;
4796
+ var MAX_TOKENS_ADAPTATION = 8192;
4797
+ var MAX_TOKENS_TEXT_RESPONSE = 4e3;
4798
+ var MAX_TOKENS_NEXT_QUESTIONS = 1200;
4799
+ var DEFAULT_MAX_ROWS_FOR_LLM = 10;
4800
+ var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
4801
+ var STREAM_PREVIEW_MAX_ROWS = 10;
4802
+ var STREAM_PREVIEW_MAX_CHARS = 200;
4803
+ var TOOL_TRACKING_MAX_ROWS = 5;
4804
+ var TOOL_TRACKING_MAX_CHARS = 200;
4805
+ var TOOL_TRACKING_SAMPLE_ROWS = 3;
4806
+ var DEFAULT_QUERY_LIMIT = 10;
4807
+ var MAX_COMPONENT_QUERY_LIMIT = 10;
4808
+ var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
4809
+ var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
4810
+ var MAX_TOOL_CALLING_ITERATIONS = 20;
4811
+ var KNOWLEDGE_BASE_TOP_K = 3;
4812
+
4813
+ // src/userResponse/stream-buffer.ts
4814
+ var StreamBuffer = class {
4815
+ constructor(callback) {
4816
+ this.buffer = "";
4817
+ this.flushTimer = null;
4818
+ this.fullText = "";
4819
+ this.callback = callback;
4534
4820
  }
4535
4821
  /**
4536
- * Reset the error log file for a new request
4822
+ * Check if the buffer has a callback configured
4537
4823
  */
4538
- resetLogFile(requestContext) {
4539
- if (!this.enabled) return;
4540
- try {
4541
- if (this.logStream) {
4542
- this.logStream.end();
4543
- this.logStream = null;
4544
- }
4545
- const dir = path5.dirname(this.logPath);
4546
- if (dir !== "." && !fs6.existsSync(dir)) {
4547
- fs6.mkdirSync(dir, { recursive: true });
4548
- }
4549
- this.logStream = fs6.createWriteStream(this.logPath, { flags: "w" });
4550
- this.hasErrors = false;
4551
- const header = `================================================================================
4552
- USER PROMPT REQUEST ERROR LOG
4553
- Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4554
- ${requestContext ? `Context: ${requestContext}` : ""}
4555
- ================================================================================
4556
-
4557
- `;
4558
- this.logStream.write(header);
4559
- } catch (error) {
4560
- console.error("[UserPromptErrorLogger] Failed to reset log file:", error);
4824
+ hasCallback() {
4825
+ return !!this.callback;
4826
+ }
4827
+ /**
4828
+ * Get all text that has been written (including already flushed)
4829
+ */
4830
+ getFullText() {
4831
+ return this.fullText;
4832
+ }
4833
+ /**
4834
+ * Write a chunk to the buffer
4835
+ * Large chunks or chunks with newlines are flushed immediately
4836
+ * Small chunks are batched and flushed after a short interval
4837
+ *
4838
+ * @param chunk - Text chunk to write
4839
+ */
4840
+ write(chunk) {
4841
+ this.fullText += chunk;
4842
+ if (!this.callback) {
4843
+ return;
4844
+ }
4845
+ this.buffer += chunk;
4846
+ if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
4847
+ this.flushNow();
4848
+ } else if (!this.flushTimer) {
4849
+ this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
4561
4850
  }
4562
4851
  }
4563
4852
  /**
4564
- * Log a JSON parse error with the raw string that failed
4853
+ * Flush the buffer immediately
4854
+ * Call this before tool execution or other operations that need clean output
4565
4855
  */
4566
- logJsonParseError(context, rawString, error) {
4567
- if (!this.enabled) return;
4568
- this.hasErrors = true;
4569
- const entry = `
4570
- --------------------------------------------------------------------------------
4571
- [${(/* @__PURE__ */ new Date()).toISOString()}] JSON PARSE ERROR
4572
- --------------------------------------------------------------------------------
4573
- Context: ${context}
4574
- Error: ${error.message}
4575
-
4576
- Raw String (${rawString.length} chars):
4577
- --------------------------------------------------------------------------------
4578
- ${rawString}
4579
- --------------------------------------------------------------------------------
4580
-
4581
- Stack Trace:
4582
- ${error.stack || "No stack trace available"}
4583
-
4584
- `;
4585
- this.write(entry);
4586
- console.error(`[UserPromptError] JSON Parse Error in ${context}: ${error.message}`);
4856
+ flush() {
4857
+ this.flushNow();
4587
4858
  }
4588
4859
  /**
4589
- * Log a general error with full details
4860
+ * Internal flush implementation
4590
4861
  */
4591
- logError(context, error, additionalData) {
4592
- if (!this.enabled) return;
4593
- this.hasErrors = true;
4594
- const errorMessage = error instanceof Error ? error.message : error;
4595
- const errorStack = error instanceof Error ? error.stack : void 0;
4596
- let entry = `
4597
- --------------------------------------------------------------------------------
4598
- [${(/* @__PURE__ */ new Date()).toISOString()}] ERROR
4599
- --------------------------------------------------------------------------------
4600
- Context: ${context}
4601
- Error: ${errorMessage}
4602
- `;
4603
- if (additionalData) {
4604
- entry += `
4605
- Additional Data:
4606
- ${JSON.stringify(additionalData, null, 2)}
4607
- `;
4862
+ flushNow() {
4863
+ if (this.flushTimer) {
4864
+ clearTimeout(this.flushTimer);
4865
+ this.flushTimer = null;
4866
+ }
4867
+ if (this.buffer && this.callback) {
4868
+ this.callback(this.buffer);
4869
+ this.buffer = "";
4870
+ }
4871
+ }
4872
+ /**
4873
+ * Clean up resources
4874
+ * Call this when done with the buffer
4875
+ */
4876
+ dispose() {
4877
+ this.flush();
4878
+ this.callback = void 0;
4879
+ }
4880
+ };
4881
+ function streamDelay(ms = STREAM_DELAY_MS) {
4882
+ return new Promise((resolve) => setTimeout(resolve, ms));
4883
+ }
4884
+ async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
4885
+ if (!streamBuffer.hasCallback()) {
4886
+ return operation();
4887
+ }
4888
+ const startTime = Date.now();
4889
+ await streamDelay(30);
4890
+ streamBuffer.write(`\u23F3 ${progressMessage}`);
4891
+ const heartbeatInterval = setInterval(() => {
4892
+ const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
4893
+ if (elapsedSeconds >= 1) {
4894
+ streamBuffer.write(` (${elapsedSeconds}s)`);
4895
+ }
4896
+ }, intervalMs);
4897
+ try {
4898
+ const result = await operation();
4899
+ return result;
4900
+ } finally {
4901
+ clearInterval(heartbeatInterval);
4902
+ streamBuffer.write("\n\n");
4903
+ }
4904
+ }
4905
+
4906
+ // src/llm.ts
4907
+ import Anthropic from "@anthropic-ai/sdk";
4908
+ import Groq from "groq-sdk";
4909
+ import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai";
4910
+ import OpenAI from "openai";
4911
+ import { jsonrepair } from "jsonrepair";
4912
+
4913
+ // src/utils/llm-usage-logger.ts
4914
+ import fs5 from "fs";
4915
+ import path4 from "path";
4916
+ var PRICING = {
4917
+ // Anthropic (December 2025)
4918
+ "claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4919
+ "claude-opus-4-5-20251101": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4920
+ "claude-sonnet-4-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4921
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4922
+ "claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4923
+ "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4924
+ "claude-3-5-sonnet-20241022": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4925
+ "claude-3-5-haiku-20241022": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4926
+ "claude-3-opus-20240229": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
4927
+ "claude-3-sonnet-20240229": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4928
+ "claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
4929
+ // OpenAI (December 2025)
4930
+ "gpt-5": { input: 1.25, output: 10 },
4931
+ "gpt-5-mini": { input: 0.25, output: 2 },
4932
+ "gpt-4o": { input: 5, output: 15 },
4933
+ // Updated pricing as of late 2025
4934
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
4935
+ "gpt-4-turbo": { input: 10, output: 30 },
4936
+ "gpt-4": { input: 30, output: 60 },
4937
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
4938
+ // Google Gemini (January 2026)
4939
+ "gemini-3-pro-preview": { input: 2, output: 12 },
4940
+ // New Gemini 3
4941
+ "gemini-3-flash-preview": { input: 0.5, output: 3 },
4942
+ // For prompts ≤200K tokens, 2x for >200K
4943
+ "gemini-2.5-flash": { input: 0.3, output: 2.5 },
4944
+ // Paid tier: $0.30 input (text/image/video), $2.50 output (includes thinking)
4945
+ "gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
4946
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
4947
+ "gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
4948
+ "gemini-1.5-pro": { input: 1.25, output: 5 },
4949
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
4950
+ // Groq (December 2025)
4951
+ "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
4952
+ "llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
4953
+ "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
4954
+ "llama-4-scout-17b-16e": { input: 0.11, output: 0.34 },
4955
+ "llama-4-maverick-17b-128e": { input: 0.2, output: 0.6 },
4956
+ "mixtral-8x7b-32768": { input: 0.27, output: 0.27 },
4957
+ "qwen3-32b": { input: 0.29, output: 0.59 }
4958
+ };
4959
+ var DEFAULT_PRICING = { input: 3, output: 15 };
4960
+ var LLMUsageLogger = class {
4961
+ constructor() {
4962
+ this.logStream = null;
4963
+ this.sessionStats = {
4964
+ totalCalls: 0,
4965
+ totalInputTokens: 0,
4966
+ totalOutputTokens: 0,
4967
+ totalCacheReadTokens: 0,
4968
+ totalCacheWriteTokens: 0,
4969
+ totalCostUSD: 0,
4970
+ totalDurationMs: 0
4971
+ };
4972
+ this.logPath = process.env.LLM_USAGE_LOG_PATH || path4.join(process.cwd(), "llm-usage-logs");
4973
+ this.enabled = process.env.LLM_USAGE_LOGGING !== "false";
4974
+ if (this.enabled) {
4975
+ this.initLogStream();
4608
4976
  }
4609
- if (errorStack) {
4610
- entry += `
4611
- Stack Trace:
4612
- ${errorStack}
4613
- `;
4977
+ }
4978
+ initLogStream() {
4979
+ try {
4980
+ const dir = path4.dirname(this.logPath);
4981
+ if (!fs5.existsSync(dir)) {
4982
+ fs5.mkdirSync(dir, { recursive: true });
4983
+ }
4984
+ this.logStream = fs5.createWriteStream(this.logPath, { flags: "a" });
4985
+ if (!fs5.existsSync(this.logPath) || fs5.statSync(this.logPath).size === 0) {
4986
+ this.writeHeader();
4987
+ }
4988
+ } catch (error) {
4989
+ console.error("[LLM-Usage-Logger] Failed to initialize log stream:", error);
4990
+ this.enabled = false;
4614
4991
  }
4615
- entry += `--------------------------------------------------------------------------------
4992
+ }
4993
+ writeHeader() {
4994
+ const header = `
4995
+ ================================================================================
4996
+ LLM USAGE LOG - Session Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4997
+ ================================================================================
4998
+ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4999
+ Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
5000
+ Cost: $X.XXXXXX | Time: Xms
5001
+ ================================================================================
4616
5002
 
4617
5003
  `;
4618
- this.write(entry);
4619
- console.error(`[UserPromptError] ${context}: ${errorMessage}`);
5004
+ this.logStream?.write(header);
4620
5005
  }
4621
5006
  /**
4622
- * Log a SQL query error with the full query
5007
+ * Calculate cost based on token usage and model
4623
5008
  */
4624
- logSqlError(query, error, params) {
4625
- if (!this.enabled) return;
4626
- this.hasErrors = true;
4627
- const errorMessage = error instanceof Error ? error.message : error;
4628
- const entry = `
4629
- --------------------------------------------------------------------------------
4630
- [${(/* @__PURE__ */ new Date()).toISOString()}] SQL QUERY ERROR
4631
- --------------------------------------------------------------------------------
4632
- Error: ${errorMessage}
4633
-
4634
- Query (${query.length} chars):
4635
- --------------------------------------------------------------------------------
4636
- ${query}
4637
- --------------------------------------------------------------------------------
4638
- ${params ? `
4639
- Parameters: ${JSON.stringify(params)}` : ""}
4640
-
4641
- `;
4642
- this.write(entry);
4643
- console.error(`[UserPromptError] SQL Error: ${errorMessage}`);
5009
+ calculateCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
5010
+ let pricing = PRICING[model];
5011
+ if (!pricing) {
5012
+ const modelLower = model.toLowerCase();
5013
+ for (const [key, value] of Object.entries(PRICING)) {
5014
+ if (modelLower.includes(key.toLowerCase()) || key.toLowerCase().includes(modelLower)) {
5015
+ pricing = value;
5016
+ break;
5017
+ }
5018
+ }
5019
+ }
5020
+ pricing = pricing || DEFAULT_PRICING;
5021
+ const inputCost = inputTokens / 1e6 * pricing.input;
5022
+ const outputCost = outputTokens / 1e6 * pricing.output;
5023
+ const cacheReadCost = cacheReadTokens / 1e6 * (pricing.cacheRead || pricing.input * 0.1);
5024
+ const cacheWriteCost = cacheWriteTokens / 1e6 * (pricing.cacheWrite || pricing.input * 1.25);
5025
+ return inputCost + outputCost + cacheReadCost + cacheWriteCost;
4644
5026
  }
4645
5027
  /**
4646
- * Log an LLM API error
5028
+ * Log an LLM API call
4647
5029
  */
4648
- logLlmError(provider, model, method, error, requestData) {
5030
+ log(entry) {
4649
5031
  if (!this.enabled) return;
4650
- this.hasErrors = true;
4651
- const errorMessage = error instanceof Error ? error.message : error;
4652
- const errorStack = error instanceof Error ? error.stack : void 0;
4653
- let entry = `
4654
- --------------------------------------------------------------------------------
4655
- [${(/* @__PURE__ */ new Date()).toISOString()}] LLM API ERROR
4656
- --------------------------------------------------------------------------------
4657
- Provider: ${provider}
4658
- Model: ${model}
4659
- Method: ${method}
4660
- Error: ${errorMessage}
4661
- `;
4662
- if (requestData) {
4663
- const dataStr = JSON.stringify(requestData, null, 2);
4664
- const truncated = dataStr.length > 5e3 ? dataStr.substring(0, 5e3) + "\n... [truncated]" : dataStr;
4665
- entry += `
4666
- Request Data:
4667
- ${truncated}
4668
- `;
4669
- }
4670
- if (errorStack) {
4671
- entry += `
4672
- Stack Trace:
4673
- ${errorStack}
4674
- `;
5032
+ this.sessionStats.totalCalls++;
5033
+ this.sessionStats.totalInputTokens += entry.inputTokens;
5034
+ this.sessionStats.totalOutputTokens += entry.outputTokens;
5035
+ this.sessionStats.totalCacheReadTokens += entry.cacheReadTokens || 0;
5036
+ this.sessionStats.totalCacheWriteTokens += entry.cacheWriteTokens || 0;
5037
+ this.sessionStats.totalCostUSD += entry.costUSD;
5038
+ this.sessionStats.totalDurationMs += entry.durationMs;
5039
+ const cacheInfo = entry.cacheReadTokens || entry.cacheWriteTokens ? ` CACHE_R=${entry.cacheReadTokens || 0} CACHE_W=${entry.cacheWriteTokens || 0}` : "";
5040
+ const toolInfo = entry.toolCalls ? ` | Tools: ${entry.toolCalls}` : "";
5041
+ const errorInfo = entry.error ? ` | ERROR: ${entry.error}` : "";
5042
+ const status = entry.success ? "\u2713" : "\u2717";
5043
+ let cacheStatus = "";
5044
+ if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
5045
+ const savedCost = entry.cacheReadTokens / 1e6 * 2.7;
5046
+ cacheStatus = ` \u26A1 CACHE HIT! Saved ~$${savedCost.toFixed(4)}`;
5047
+ } else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
5048
+ cacheStatus = " \u{1F4DD} Cache created (next request will be cheaper)";
4675
5049
  }
4676
- entry += `--------------------------------------------------------------------------------
4677
-
5050
+ const logLine = `[${entry.timestamp}] [${entry.requestId}] ${status} ${entry.provider}/${entry.model} [${entry.method}]
5051
+ Tokens: IN=${entry.inputTokens} OUT=${entry.outputTokens}${cacheInfo} TOTAL=${entry.totalTokens}
5052
+ Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
4678
5053
  `;
4679
- this.write(entry);
4680
- console.error(`[UserPromptError] LLM Error (${provider}/${model}): ${errorMessage}`);
5054
+ this.logStream?.write(logLine);
4681
5055
  }
4682
5056
  /**
4683
- * Log tool execution error
5057
+ * Log session summary (call at end of request)
4684
5058
  */
4685
- logToolError(toolName, toolInput, error) {
4686
- if (!this.enabled) return;
4687
- this.hasErrors = true;
4688
- const errorMessage = error instanceof Error ? error.message : error;
4689
- const errorStack = error instanceof Error ? error.stack : void 0;
4690
- const entry = `
5059
+ logSessionSummary(requestContext) {
5060
+ if (!this.enabled || this.sessionStats.totalCalls === 0) return;
5061
+ const cacheReadSavings = this.sessionStats.totalCacheReadTokens / 1e6 * 2.7;
5062
+ const hasCaching = this.sessionStats.totalCacheReadTokens > 0 || this.sessionStats.totalCacheWriteTokens > 0;
5063
+ let cacheSection = "";
5064
+ if (hasCaching) {
5065
+ cacheSection = `
5066
+ Cache Statistics:
5067
+ Cache Read Tokens: ${this.sessionStats.totalCacheReadTokens.toLocaleString()}${this.sessionStats.totalCacheReadTokens > 0 ? " \u26A1" : ""}
5068
+ Cache Write Tokens: ${this.sessionStats.totalCacheWriteTokens.toLocaleString()}${this.sessionStats.totalCacheWriteTokens > 0 ? " \u{1F4DD}" : ""}
5069
+ Estimated Savings: $${cacheReadSavings.toFixed(4)}`;
5070
+ }
5071
+ const summary = `
4691
5072
  --------------------------------------------------------------------------------
4692
- [${(/* @__PURE__ */ new Date()).toISOString()}] TOOL EXECUTION ERROR
5073
+ SESSION SUMMARY${requestContext ? ` (${requestContext})` : ""}
4693
5074
  --------------------------------------------------------------------------------
4694
- Tool: ${toolName}
4695
- Error: ${errorMessage}
4696
-
4697
- Tool Input:
4698
- ${JSON.stringify(toolInput, null, 2)}
4699
- ${errorStack ? `
4700
- Stack Trace:
4701
- ${errorStack}` : ""}
5075
+ Total LLM Calls: ${this.sessionStats.totalCalls}
5076
+ Total Input Tokens: ${this.sessionStats.totalInputTokens.toLocaleString()}
5077
+ Total Output Tokens: ${this.sessionStats.totalOutputTokens.toLocaleString()}
5078
+ Total Tokens: ${(this.sessionStats.totalInputTokens + this.sessionStats.totalOutputTokens).toLocaleString()}
5079
+ Total Cost: $${this.sessionStats.totalCostUSD.toFixed(6)}
5080
+ Total Time: ${this.sessionStats.totalDurationMs}ms (${(this.sessionStats.totalDurationMs / 1e3).toFixed(2)}s)
5081
+ Avg Cost/Call: $${(this.sessionStats.totalCostUSD / this.sessionStats.totalCalls).toFixed(6)}
5082
+ Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStats.totalCalls)}ms${cacheSection}
4702
5083
  --------------------------------------------------------------------------------
4703
5084
 
4704
5085
  `;
4705
- this.write(entry);
4706
- console.error(`[UserPromptError] Tool Error (${toolName}): ${errorMessage}`);
5086
+ this.logStream?.write(summary);
4707
5087
  }
4708
5088
  /**
4709
- * Write final summary if there were errors
5089
+ * Reset session stats (call at start of new user request)
4710
5090
  */
4711
- writeSummary() {
4712
- if (!this.enabled || !this.hasErrors) return;
4713
- const summary = `
5091
+ resetSession() {
5092
+ this.sessionStats = {
5093
+ totalCalls: 0,
5094
+ totalInputTokens: 0,
5095
+ totalOutputTokens: 0,
5096
+ totalCacheReadTokens: 0,
5097
+ totalCacheWriteTokens: 0,
5098
+ totalCostUSD: 0,
5099
+ totalDurationMs: 0
5100
+ };
5101
+ }
5102
+ /**
5103
+ * Reset the log file for a new request (clears previous logs)
5104
+ * Call this at the start of each USER_PROMPT_REQ
5105
+ */
5106
+ resetLogFile(requestContext) {
5107
+ if (!this.enabled) return;
5108
+ try {
5109
+ if (this.logStream) {
5110
+ this.logStream.end();
5111
+ this.logStream = null;
5112
+ }
5113
+ this.logStream = fs5.createWriteStream(this.logPath, { flags: "w" });
5114
+ const header = `
4714
5115
  ================================================================================
4715
- REQUEST COMPLETED WITH ERRORS
4716
- Time: ${(/* @__PURE__ */ new Date()).toISOString()}
5116
+ LLM USAGE LOG - Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
5117
+ ${requestContext ? `Context: ${requestContext}` : ""}
5118
+ ================================================================================
5119
+ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
5120
+ Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
5121
+ Cost: $X.XXXXXX | Time: Xms
4717
5122
  ================================================================================
5123
+
4718
5124
  `;
4719
- this.write(summary);
5125
+ this.logStream.write(header);
5126
+ this.resetSession();
5127
+ } catch (error) {
5128
+ console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
5129
+ }
4720
5130
  }
4721
5131
  /**
4722
- * Check if any errors were logged
5132
+ * Get current session stats
4723
5133
  */
4724
- hadErrors() {
4725
- return this.hasErrors;
5134
+ getSessionStats() {
5135
+ return { ...this.sessionStats };
4726
5136
  }
4727
- write(content) {
4728
- if (this.logStream) {
4729
- this.logStream.write(content);
4730
- }
5137
+ /**
5138
+ * Generate a unique request ID
5139
+ */
5140
+ generateRequestId() {
5141
+ return `req-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
4731
5142
  }
4732
5143
  };
4733
- var userPromptErrorLogger = new UserPromptErrorLogger();
5144
+ var llmUsageLogger = new LLMUsageLogger();
4734
5145
 
4735
5146
  // src/llm.ts
4736
5147
  var LLM = class {
@@ -5165,7 +5576,7 @@ var LLM = class {
5165
5576
  role: "user",
5166
5577
  content: []
5167
5578
  };
5168
- for (const toolUse of toolUses) {
5579
+ const toolResultEntries = await Promise.all(toolUses.map(async (toolUse) => {
5169
5580
  try {
5170
5581
  const result = await toolHandler(toolUse.name, toolUse.input);
5171
5582
  let resultContent = typeof result === "string" ? result : JSON.stringify(result);
@@ -5173,20 +5584,21 @@ var LLM = class {
5173
5584
  if (resultContent.length > MAX_RESULT_LENGTH) {
5174
5585
  resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
5175
5586
  }
5176
- toolResults.content.push({
5587
+ return {
5177
5588
  type: "tool_result",
5178
5589
  tool_use_id: toolUse.id,
5179
5590
  content: resultContent
5180
- });
5591
+ };
5181
5592
  } catch (error) {
5182
- toolResults.content.push({
5593
+ return {
5183
5594
  type: "tool_result",
5184
5595
  tool_use_id: toolUse.id,
5185
5596
  content: error instanceof Error ? error.message : String(error),
5186
5597
  is_error: true
5187
- });
5598
+ };
5188
5599
  }
5189
- }
5600
+ }));
5601
+ toolResultEntries.forEach((entry) => toolResults.content.push(entry));
5190
5602
  conversationMessages.push(toolResults);
5191
5603
  } else {
5192
5604
  break;
@@ -5684,7 +6096,7 @@ var LLM = class {
5684
6096
  break;
5685
6097
  }
5686
6098
  const functionResponses = [];
5687
- for (const fc of functionCalls) {
6099
+ const responses = await Promise.all(functionCalls.map(async (fc) => {
5688
6100
  try {
5689
6101
  const result2 = await toolHandler(fc.name, fc.args);
5690
6102
  let resultContent = typeof result2 === "string" ? result2 : JSON.stringify(result2);
@@ -5692,17 +6104,18 @@ var LLM = class {
5692
6104
  if (resultContent.length > MAX_RESULT_LENGTH) {
5693
6105
  resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
5694
6106
  }
5695
- functionResponses.push({
6107
+ return {
5696
6108
  name: fc.name,
5697
6109
  response: { result: resultContent }
5698
- });
6110
+ };
5699
6111
  } catch (error) {
5700
- functionResponses.push({
6112
+ return {
5701
6113
  name: fc.name,
5702
6114
  response: { error: error instanceof Error ? error.message : String(error) }
5703
- });
6115
+ };
5704
6116
  }
5705
- }
6117
+ }));
6118
+ functionResponses.push(...responses);
5706
6119
  const functionResponseParts = functionResponses.map((fr) => ({
5707
6120
  functionResponse: {
5708
6121
  name: fr.name,
@@ -5958,7 +6371,7 @@ var LLM = class {
5958
6371
  }
5959
6372
  }))
5960
6373
  });
5961
- for (const tc of toolCalls) {
6374
+ const toolCallResults = await Promise.all(toolCalls.map(async (tc) => {
5962
6375
  let result;
5963
6376
  try {
5964
6377
  const args = JSON.parse(tc.arguments);
@@ -5970,13 +6383,10 @@ var LLM = class {
5970
6383
  }
5971
6384
  } catch (error) {
5972
6385
  result = JSON.stringify({ error: error instanceof Error ? error.message : String(error) });
5973
- }
5974
- conversationMessages.push({
5975
- role: "tool",
5976
- tool_call_id: tc.id,
5977
- content: result
5978
- });
5979
- }
6386
+ }
6387
+ return { role: "tool", tool_call_id: tc.id, content: result };
6388
+ }));
6389
+ toolCallResults.forEach((r) => conversationMessages.push(r));
5980
6390
  }
5981
6391
  if (iterations >= maxIterations) {
5982
6392
  throw new Error(`Max iterations (${maxIterations}) reached in tool calling loop`);
@@ -6071,583 +6481,851 @@ function getCurrentDateTimeForPrompt() {
6071
6481
  });
6072
6482
  }
6073
6483
 
6074
- // src/userResponse/knowledge-base.ts
6075
- var getKnowledgeBase = async ({
6076
- prompt,
6077
- collections,
6078
- topK = 1
6079
- }) => {
6080
- try {
6081
- if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
6082
- logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
6083
- return "";
6084
- }
6085
- const result = await collections["knowledge-base"]["query"]({
6086
- prompt,
6087
- topK
6484
+ // src/userResponse/agents/agent-prompt-builder.ts
6485
+ function buildSourceSummaries(externalTools) {
6486
+ return externalTools.map((tool) => {
6487
+ const description = tool.description || "";
6488
+ const type = extractSourceType(tool.id);
6489
+ const entityDetails = extractEntityDetails(description);
6490
+ const dataContext = extractDataContext(description, tool.name, type);
6491
+ return {
6492
+ id: extractSourceId(tool.id),
6493
+ name: tool.name,
6494
+ type,
6495
+ description: dataContext,
6496
+ entityDetails,
6497
+ toolId: tool.id
6498
+ };
6499
+ });
6500
+ }
6501
+ function formatSummariesForPrompt(summaries) {
6502
+ return summaries.map((s, idx) => {
6503
+ const totalRows = s.entityDetails.reduce((sum, e) => sum + (e.rowCount || 0), 0);
6504
+ const rowInfo = totalRows > 0 ? ` (~${totalRows.toLocaleString()} total rows)` : "";
6505
+ let entitiesBlock = "";
6506
+ if (s.entityDetails.length > 0) {
6507
+ entitiesBlock = "\n" + s.entityDetails.map((e) => {
6508
+ const rows = e.rowCount ? ` (${e.rowCount.toLocaleString()} rows)` : "";
6509
+ const cols = e.columns.length > 0 ? `: ${e.columns.join(", ")}` : "";
6510
+ return ` - ${e.name}${rows}${cols}`;
6511
+ }).join("\n");
6512
+ }
6513
+ return `${idx + 1}. **${s.name}** (tool: ${s.toolId}, type: ${s.type})${rowInfo}
6514
+ ${s.description}${entitiesBlock}`;
6515
+ }).join("\n\n");
6516
+ }
6517
+ function extractEntityDetails(description) {
6518
+ const details = [];
6519
+ const bulletSections = description.split(/(?=•\s)/);
6520
+ for (const section of bulletSections) {
6521
+ if (!section.trim().startsWith("\u2022")) continue;
6522
+ const headerMatch = section.match(/•\s+(?:\w+\.)?(.+?)\s*\[~?([\d,]+)\s*rows?\]/);
6523
+ if (!headerMatch) continue;
6524
+ const name = headerMatch[1].trim();
6525
+ const rowCount = parseInt(headerMatch[2].replace(/,/g, ""), 10);
6526
+ const columns = extractColumnNames(section);
6527
+ details.push({
6528
+ name,
6529
+ rowCount: rowCount > 0 ? rowCount : void 0,
6530
+ columns
6088
6531
  });
6089
- if (!result || !result.content) {
6090
- logger.warn("[KnowledgeBase] No knowledge base results returned");
6091
- return "";
6092
- }
6093
- logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
6094
- if (result.metadata?.sources && result.metadata.sources.length > 0) {
6095
- logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
6096
- }
6097
- return result.content;
6098
- } catch (error) {
6099
- const errorMsg = error instanceof Error ? error.message : String(error);
6100
- logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
6101
- return "";
6102
6532
  }
6103
- };
6104
- var getGlobalKnowledgeBase = async ({
6105
- collections,
6106
- limit = 100
6107
- }) => {
6108
- try {
6109
- if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
6110
- logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
6111
- return "";
6112
- }
6113
- const result = await collections["knowledge-base"]["getGlobal"]({ limit });
6114
- if (!result || !result.content) {
6115
- logger.warn("[KnowledgeBase] No global knowledge base nodes found");
6116
- return "";
6533
+ if (details.length === 0) {
6534
+ const endpointPattern = /Endpoint:\s*(\S+)/g;
6535
+ let match;
6536
+ while ((match = endpointPattern.exec(description)) !== null) {
6537
+ const columns = extractColumnNames(description);
6538
+ details.push({
6539
+ name: match[1].trim(),
6540
+ columns
6541
+ });
6117
6542
  }
6118
- logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
6119
- return result.content;
6120
- } catch (error) {
6121
- const errorMsg = error instanceof Error ? error.message : String(error);
6122
- logger.warn(`[KnowledgeBase] Error fetching global knowledge base: ${errorMsg}`);
6123
- return "";
6124
6543
  }
6125
- };
6126
- var getUserKnowledgeBase = async ({
6127
- collections,
6128
- userId,
6129
- limit = 100
6130
- }) => {
6131
- try {
6132
- if (!userId) {
6133
- logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
6134
- return "";
6544
+ return details;
6545
+ }
6546
+ function extractColumnNames(block) {
6547
+ const columns = [];
6548
+ const seen = /* @__PURE__ */ new Set();
6549
+ const colPattern = /(?:^|,)\s*([^,()\[\]{}\n]+?)\s*\((?:NUMBER|TEXT|BOOLEAN|TIMESTAMP|INTEGER|FLOAT|DECIMAL|DATE|BIGINT|VARCHAR|CHAR|DOUBLE|REAL|ARRAY|OBJECT)\)/gi;
6550
+ let match;
6551
+ while ((match = colPattern.exec(block)) !== null) {
6552
+ const col = match[1].trim();
6553
+ if (col && col.length < 60 && !col.includes("\u2022") && !seen.has(col)) {
6554
+ columns.push(col);
6555
+ seen.add(col);
6556
+ }
6557
+ }
6558
+ return columns;
6559
+ }
6560
+ function extractDataContext(description, name, type) {
6561
+ const contextMatch = description.match(/Data Context:\s*(.+?)(?:\n|$)/);
6562
+ if (contextMatch) return contextMatch[1].trim();
6563
+ const excelMatch = description.match(/Excel file:\s*(.+?)\)/);
6564
+ if (excelMatch) return `Excel file: ${excelMatch[1].trim()})`;
6565
+ const csvMatch = description.match(/CSV file:\s*(.+?)\)/);
6566
+ if (csvMatch) return `CSV file: ${csvMatch[1].trim()})`;
6567
+ const useForMatch = description.match(/Use this source for[^:]*:\s*(.+?)(?:\n|$)/);
6568
+ if (useForMatch) return useForMatch[1].trim();
6569
+ const typeLabels = {
6570
+ postgres: "PostgreSQL database",
6571
+ mysql: "MySQL database",
6572
+ mssql: "SQL Server database",
6573
+ excel: "Excel spreadsheet",
6574
+ csv: "CSV file",
6575
+ rest_api: "REST API",
6576
+ graphql: "GraphQL API"
6577
+ };
6578
+ return `${typeLabels[type] || type}: ${name}`;
6579
+ }
6580
+ function extractSourceType(toolId) {
6581
+ const match = toolId.match(/^(\w+)-[a-f0-9]+_/);
6582
+ if (match) return match[1];
6583
+ const types = ["postgres", "mysql", "mssql", "excel", "csv", "rest_api", "graphql"];
6584
+ for (const type of types) {
6585
+ if (toolId.toLowerCase().includes(type)) return type;
6586
+ }
6587
+ return "unknown";
6588
+ }
6589
+ function extractSourceId(toolId) {
6590
+ return toolId.replace(/_(query|read|call)$/, "");
6591
+ }
6592
+
6593
+ // src/userResponse/agents/source-agent.ts
6594
+ var SourceAgent = class {
6595
+ constructor(tool, config, streamBuffer) {
6596
+ this.attempts = 0;
6597
+ this.tool = tool;
6598
+ this.config = config;
6599
+ this.streamBuffer = streamBuffer;
6600
+ }
6601
+ /**
6602
+ * Execute a query against this source based on the intent from the main agent.
6603
+ *
6604
+ * Flow:
6605
+ * 1. Build prompt with full schema (from tool.description) + intent
6606
+ * 2. Source agent's OWN LLM generates query via tool calling
6607
+ * 3. Execute with retry — all handled internally
6608
+ * 4. Return SourceAgentResult with data + isLimited metadata
6609
+ */
6610
+ async execute(input) {
6611
+ const startTime = Date.now();
6612
+ const { intent, aggregation = "raw" } = input;
6613
+ logger.info(`[SourceAgent:${this.tool.name}] Starting | intent: "${intent}" | aggregation: ${aggregation}`);
6614
+ if (this.streamBuffer.hasCallback()) {
6615
+ this.streamBuffer.write(`
6616
+
6617
+ \u{1F517} **Querying ${this.tool.name}...**
6618
+
6619
+ `);
6620
+ await streamDelay();
6135
6621
  }
6136
- if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
6137
- logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
6138
- return "";
6622
+ try {
6623
+ const prompts = await this.buildPrompt(intent, aggregation);
6624
+ const llmTool = this.buildLLMToolDefinition();
6625
+ let executedTool = null;
6626
+ let resultData = [];
6627
+ let queryExecuted;
6628
+ let totalRowsMatched = 0;
6629
+ const toolHandler = async (_toolName, toolInput) => {
6630
+ this.attempts++;
6631
+ if (this.attempts > this.config.maxRetries) {
6632
+ throw new Error(`Max retry attempts (${this.config.maxRetries}) reached for ${this.tool.name}`);
6633
+ }
6634
+ if (this.attempts > 1 && this.streamBuffer.hasCallback()) {
6635
+ this.streamBuffer.write(`
6636
+
6637
+ \u{1F504} **Retrying ${this.tool.name} (attempt ${this.attempts}/${this.config.maxRetries})...**
6638
+
6639
+ `);
6640
+ await streamDelay();
6641
+ }
6642
+ const cappedInput = { ...toolInput };
6643
+ if (cappedInput.limit === void 0 || cappedInput.limit > this.config.maxRowsPerSource) {
6644
+ cappedInput.limit = this.config.maxRowsPerSource;
6645
+ }
6646
+ queryExecuted = cappedInput.sql || cappedInput.query || JSON.stringify(cappedInput);
6647
+ try {
6648
+ const result = await withProgressHeartbeat(
6649
+ () => this.tool.fn(cappedInput),
6650
+ `Running ${this.tool.name}`,
6651
+ this.streamBuffer
6652
+ );
6653
+ if (result && result.error) {
6654
+ const errorMsg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
6655
+ logger.warn(`[SourceAgent:${this.tool.name}] Tool returned error (attempt ${this.attempts}/${this.config.maxRetries}): ${errorMsg}`);
6656
+ return `\u274C ERROR: ${errorMsg}
6657
+
6658
+ Analyze the error and try again with a corrected query.`;
6659
+ }
6660
+ resultData = result.data || [];
6661
+ totalRowsMatched = result.metadata?.totalCount || result.count || resultData.length;
6662
+ const formattedResult = formatToolResultForLLM(result, {
6663
+ toolName: this.tool.name,
6664
+ maxRows: 5,
6665
+ maxCharsPerField: 200
6666
+ });
6667
+ executedTool = {
6668
+ id: this.tool.id,
6669
+ name: this.tool.name,
6670
+ params: cappedInput,
6671
+ result: {
6672
+ _totalRecords: totalRowsMatched,
6673
+ _recordsShown: resultData.length,
6674
+ _metadata: result.metadata,
6675
+ _sampleData: resultData.slice(0, 3)
6676
+ },
6677
+ outputSchema: this.tool.outputSchema
6678
+ };
6679
+ const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
6680
+ return `\u2705 Query executed successfully. ${resultData.length} rows returned (${totalRowsMatched} total matched). Data is ready \u2014 do NOT call the tool again.
6681
+
6682
+ ${formatted}`;
6683
+ } catch (execError) {
6684
+ const errorMsg = execError instanceof Error ? execError.message : typeof execError === "object" && execError !== null ? execError.message || execError.error || JSON.stringify(execError) : String(execError);
6685
+ logger.warn(`[SourceAgent:${this.tool.name}] Tool execution failed (attempt ${this.attempts}/${this.config.maxRetries}): ${errorMsg}`);
6686
+ return `\u274C ERROR: ${errorMsg}
6687
+
6688
+ Analyze the error and try again with a corrected query.`;
6689
+ }
6690
+ };
6691
+ await LLM.streamWithTools(
6692
+ { sys: prompts.system, user: prompts.user },
6693
+ [llmTool],
6694
+ toolHandler,
6695
+ {
6696
+ model: this.config.sourceAgentModel || void 0,
6697
+ maxTokens: 2048,
6698
+ temperature: 0,
6699
+ apiKey: this.config.apiKey
6700
+ },
6701
+ this.config.maxRetries
6702
+ );
6703
+ const executionTimeMs = Date.now() - startTime;
6704
+ if (!executedTool) {
6705
+ logger.warn(`[SourceAgent:${this.tool.name}] LLM did not call the tool`);
6706
+ return {
6707
+ sourceId: this.tool.id,
6708
+ sourceName: this.tool.name,
6709
+ success: false,
6710
+ data: [],
6711
+ metadata: {
6712
+ totalRowsMatched: 0,
6713
+ rowsReturned: 0,
6714
+ isLimited: false,
6715
+ executionTimeMs
6716
+ },
6717
+ executedTool: this.buildEmptyExecutedTool(),
6718
+ error: "Source agent did not execute any query"
6719
+ };
6720
+ }
6721
+ logger.info(`[SourceAgent:${this.tool.name}] Success | ${resultData.length} rows in ${executionTimeMs}ms`);
6722
+ return {
6723
+ sourceId: this.tool.id,
6724
+ sourceName: this.tool.name,
6725
+ success: true,
6726
+ data: resultData,
6727
+ metadata: {
6728
+ totalRowsMatched,
6729
+ rowsReturned: resultData.length,
6730
+ isLimited: resultData.length < totalRowsMatched,
6731
+ queryExecuted,
6732
+ executionTimeMs
6733
+ },
6734
+ executedTool
6735
+ };
6736
+ } catch (error) {
6737
+ const executionTimeMs = Date.now() - startTime;
6738
+ const errorMsg = error instanceof Error ? error.message : String(error);
6739
+ logger.error(`[SourceAgent:${this.tool.name}] Failed: ${errorMsg}`);
6740
+ if (this.streamBuffer.hasCallback()) {
6741
+ this.streamBuffer.write(`
6742
+
6743
+ \u274C **${this.tool.name} failed:** ${errorMsg}
6744
+
6745
+ `);
6746
+ }
6747
+ return {
6748
+ sourceId: this.tool.id,
6749
+ sourceName: this.tool.name,
6750
+ success: false,
6751
+ data: [],
6752
+ metadata: {
6753
+ totalRowsMatched: 0,
6754
+ rowsReturned: 0,
6755
+ isLimited: false,
6756
+ queryExecuted: void 0,
6757
+ executionTimeMs
6758
+ },
6759
+ executedTool: this.buildEmptyExecutedTool(),
6760
+ error: errorMsg
6761
+ };
6139
6762
  }
6140
- const result = await collections["knowledge-base"]["getByUser"]({
6141
- userId: Number(userId),
6142
- limit
6763
+ }
6764
+ // ============================================
6765
+ // Private Helpers
6766
+ // ============================================
6767
+ /**
6768
+ * Build prompt using the prompt loader (file system → hardcoded fallback in prompts.ts).
6769
+ */
6770
+ async buildPrompt(intent, aggregation) {
6771
+ const sourceName = this.tool.name;
6772
+ const sourceType = this.extractSourceType();
6773
+ const fullSchema = this.tool.description || "No schema available";
6774
+ const prompts = await promptLoader.loadPrompts("agent-source-query", {
6775
+ SOURCE_NAME: sourceName,
6776
+ SOURCE_TYPE: sourceType,
6777
+ FULL_SCHEMA: fullSchema,
6778
+ MAX_ROWS: String(this.config.maxRowsPerSource),
6779
+ AGGREGATION_MODE: aggregation,
6780
+ CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
6781
+ INTENT: intent
6143
6782
  });
6144
- if (!result || !result.content) {
6145
- logger.info(`[KnowledgeBase] No user knowledge base nodes found for userId: ${userId}`);
6146
- return "";
6147
- }
6148
- logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} user knowledge base nodes for userId: ${userId}`);
6149
- return result.content;
6150
- } catch (error) {
6151
- const errorMsg = error instanceof Error ? error.message : String(error);
6152
- logger.warn(`[KnowledgeBase] Error fetching user knowledge base: ${errorMsg}`);
6153
- return "";
6783
+ return { system: prompts.system, user: prompts.user };
6784
+ }
6785
+ /**
6786
+ * Build the LLM tool definition from the external tool.
6787
+ * Parses param descriptions like "string - Sheet name" or "array (optional) - Columns"
6788
+ * to extract the correct JSON schema type and required/optional status.
6789
+ */
6790
+ buildLLMToolDefinition() {
6791
+ const properties = {};
6792
+ const required = [];
6793
+ const toolParams = this.tool.params || {};
6794
+ Object.entries(toolParams).forEach(([key, typeOrValue]) => {
6795
+ const valueStr = String(typeOrValue).toLowerCase();
6796
+ let schemaType = "string";
6797
+ const typeMatch = valueStr.match(/^(string|number|integer|boolean|array|object)\b/);
6798
+ if (typeMatch) {
6799
+ schemaType = typeMatch[1];
6800
+ } else if (typeof typeOrValue === "number") {
6801
+ schemaType = Number.isInteger(typeOrValue) ? "integer" : "number";
6802
+ } else if (typeof typeOrValue === "boolean") {
6803
+ schemaType = "boolean";
6804
+ }
6805
+ const isOptional = valueStr.includes("(optional)") || valueStr.includes("optional");
6806
+ const description = typeof typeOrValue === "string" ? typeOrValue : `Parameter: ${key}`;
6807
+ if (schemaType === "array") {
6808
+ properties[key] = { type: "array", items: { type: "string" }, description };
6809
+ } else if (schemaType === "object") {
6810
+ properties[key] = { type: "object", description };
6811
+ } else {
6812
+ properties[key] = { type: schemaType, description };
6813
+ }
6814
+ if (!isOptional) {
6815
+ required.push(key);
6816
+ }
6817
+ });
6818
+ return {
6819
+ name: this.tool.id,
6820
+ description: this.tool.description || `Query ${this.tool.name}`,
6821
+ input_schema: {
6822
+ type: "object",
6823
+ properties,
6824
+ required: required.length > 0 ? required : void 0
6825
+ }
6826
+ };
6827
+ }
6828
+ /**
6829
+ * Extract source type from tool ID.
6830
+ */
6831
+ extractSourceType() {
6832
+ const match = this.tool.id.match(/^(\w+)-[a-f0-9]+_/);
6833
+ return match ? match[1] : "unknown";
6834
+ }
6835
+ /**
6836
+ * Build an empty ExecutedToolInfo for error cases.
6837
+ */
6838
+ buildEmptyExecutedTool() {
6839
+ return {
6840
+ id: this.tool.id,
6841
+ name: this.tool.name,
6842
+ params: {},
6843
+ result: {
6844
+ _totalRecords: 0,
6845
+ _recordsShown: 0,
6846
+ _sampleData: []
6847
+ },
6848
+ outputSchema: this.tool.outputSchema
6849
+ };
6154
6850
  }
6155
6851
  };
6156
- var getAllKnowledgeBase = async ({
6157
- prompt,
6158
- collections,
6159
- userId,
6160
- topK = 3
6161
- }) => {
6162
- const [globalContext, userContext, queryContext] = await Promise.all([
6163
- getGlobalKnowledgeBase({ collections }),
6164
- getUserKnowledgeBase({ collections, userId }),
6165
- getKnowledgeBase({ prompt, collections, topK })
6166
- ]);
6167
- let combinedContext = "";
6168
- if (globalContext) {
6169
- combinedContext += "## Global Knowledge Base\n";
6170
- combinedContext += "The following information applies to all queries:\n\n";
6171
- combinedContext += globalContext + "\n\n";
6852
+
6853
+ // src/userResponse/agents/main-agent.ts
6854
+ var MainAgent = class {
6855
+ constructor(externalTools, config, streamBuffer) {
6856
+ this.externalTools = externalTools;
6857
+ this.config = config;
6858
+ this.streamBuffer = streamBuffer;
6172
6859
  }
6173
- if (userContext) {
6174
- combinedContext += "## User-Specific Knowledge Base\n";
6175
- combinedContext += "The following information is specific to this user:\n\n";
6176
- combinedContext += userContext + "\n\n";
6860
+ /**
6861
+ * Handle a user question using the multi-agent system.
6862
+ *
6863
+ * This is ONE LLM.streamWithTools() call. The LLM:
6864
+ * 1. Sees source summaries in system prompt (~100 tokens each)
6865
+ * 2. Decides which source(s) to query (routing)
6866
+ * 3. Calls source tools → SourceAgent runs independently → returns data
6867
+ * 4. Sees data, decides if it needs more → calls tools again if needed
6868
+ * 5. Generates final analysis text
6869
+ */
6870
+ async handleQuestion(userPrompt, apiKey, conversationHistory, streamCallback) {
6871
+ const startTime = Date.now();
6872
+ logger.info(`[MainAgent] Starting | prompt: "${userPrompt.substring(0, 50)}..."`);
6873
+ const summaries = buildSourceSummaries(this.externalTools);
6874
+ logger.info(`[MainAgent] ${summaries.length} source(s) available`);
6875
+ const systemPrompt = await this.buildSystemPrompt(summaries, conversationHistory);
6876
+ const tools = this.buildSourceToolDefinitions(summaries);
6877
+ const sourceResults = [];
6878
+ const executedTools = [];
6879
+ const toolHandler = async (toolName, toolInput) => {
6880
+ const externalTool = this.externalTools.find((t) => t.id === toolName);
6881
+ if (!externalTool) {
6882
+ logger.error(`[MainAgent] Unknown tool called: ${toolName}`);
6883
+ return `Error: Unknown data source "${toolName}"`;
6884
+ }
6885
+ const sourceInput = {
6886
+ intent: toolInput.intent || toolInput.query || JSON.stringify(toolInput),
6887
+ aggregation: toolInput.aggregation || "raw"
6888
+ };
6889
+ logger.info(`[MainAgent] Dispatching SourceAgent for "${externalTool.name}" | intent: "${sourceInput.intent}"`);
6890
+ const sourceAgent = new SourceAgent(externalTool, this.config, this.streamBuffer);
6891
+ const result = await sourceAgent.execute(sourceInput);
6892
+ sourceResults.push(result);
6893
+ if (result.success) {
6894
+ executedTools.push(result.executedTool);
6895
+ }
6896
+ return this.formatResultForMainAgent(result);
6897
+ };
6898
+ const text = await LLM.streamWithTools(
6899
+ {
6900
+ sys: systemPrompt,
6901
+ user: userPrompt
6902
+ },
6903
+ tools,
6904
+ toolHandler,
6905
+ {
6906
+ model: this.config.mainAgentModel || void 0,
6907
+ maxTokens: 4e3,
6908
+ temperature: 0,
6909
+ apiKey: apiKey || this.config.apiKey,
6910
+ partial: streamCallback
6911
+ },
6912
+ this.config.maxIterations
6913
+ );
6914
+ const totalTime = Date.now() - startTime;
6915
+ logger.info(`[MainAgent] Complete | ${sourceResults.length} source queries, ${executedTools.length} successful | ${totalTime}ms`);
6916
+ return {
6917
+ text,
6918
+ executedTools,
6919
+ sourceResults
6920
+ };
6177
6921
  }
6178
- if (queryContext) {
6179
- combinedContext += "## Relevant Knowledge Base (Query-Matched)\n";
6180
- combinedContext += "The following information is semantically relevant to the current query:\n\n";
6181
- combinedContext += queryContext + "\n\n";
6922
+ // ============================================
6923
+ // System Prompt
6924
+ // ============================================
6925
+ /**
6926
+ * Build the main agent's system prompt with source summaries.
6927
+ * Loads from prompt loader (file system → hardcoded fallback in prompts.ts).
6928
+ */
6929
+ async buildSystemPrompt(summaries, conversationHistory) {
6930
+ const summariesText = formatSummariesForPrompt(summaries);
6931
+ const prompts = await promptLoader.loadPrompts("agent-main", {
6932
+ SOURCE_SUMMARIES: summariesText,
6933
+ MAX_ROWS: String(this.config.maxRowsPerSource),
6934
+ CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
6935
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
6936
+ });
6937
+ return prompts.system;
6938
+ }
6939
+ // ============================================
6940
+ // Tool Definitions (summary-only, no full schema)
6941
+ // ============================================
6942
+ /**
6943
+ * Build tool definitions for the main agent — one per source.
6944
+ * Descriptions include entity names with column names for routing.
6945
+ * The full schema is inside the SourceAgent which runs independently.
6946
+ */
6947
+ buildSourceToolDefinitions(summaries) {
6948
+ return summaries.map((summary) => {
6949
+ const totalRows = summary.entityDetails.reduce((sum, e) => sum + (e.rowCount || 0), 0);
6950
+ const rowInfo = totalRows > 0 ? ` (~${totalRows.toLocaleString()} total rows)` : "";
6951
+ const entitiesList = summary.entityDetails.length > 0 ? summary.entityDetails.map((e) => {
6952
+ const cols = e.columns.length > 0 ? ` [${e.columns.join(", ")}]` : "";
6953
+ return `${e.name}${cols}`;
6954
+ }).join("; ") : "no entities listed";
6955
+ return {
6956
+ name: summary.toolId,
6957
+ description: `Query "${summary.name}" (${summary.type})${rowInfo}. ${summary.description}. Contains: ${entitiesList}.`,
6958
+ input_schema: {
6959
+ type: "object",
6960
+ properties: {
6961
+ intent: {
6962
+ type: "string",
6963
+ description: "Describe what data you need from this source in natural language. Be specific about fields, filters, aggregations, and limits."
6964
+ },
6965
+ aggregation: {
6966
+ type: "string",
6967
+ enum: ["raw", "pre-aggregate", "summary"],
6968
+ description: 'How to return data. "pre-aggregate": use GROUP BY/SUM/COUNT for totals. "summary": high-level metrics. "raw": individual records.'
6969
+ }
6970
+ },
6971
+ required: ["intent"]
6972
+ }
6973
+ };
6974
+ });
6975
+ }
6976
+ // ============================================
6977
+ // Format Result for Main Agent
6978
+ // ============================================
6979
+ /**
6980
+ * Format a source agent's result as a clean string for the main agent LLM.
6981
+ * Passes through the data as-is — no server-side aggregation.
6982
+ * If the main agent needs aggregates, it should request aggregation: "pre-aggregate"
6983
+ * from the source agent, which handles it at the query level (SQL GROUP BY, etc.).
6984
+ */
6985
+ formatResultForMainAgent(result) {
6986
+ if (!result.success) {
6987
+ return `Data source "${result.sourceName}" could not fulfill the request: ${result.error}. Try rephrasing your intent or querying a different source.`;
6988
+ }
6989
+ const { data, metadata } = result;
6990
+ let output = `## Data from "${result.sourceName}"
6991
+ `;
6992
+ output += `Rows returned: ${metadata.rowsReturned}`;
6993
+ if (metadata.isLimited) {
6994
+ output += ` (LIMITED \u2014 ${metadata.totalRowsMatched} total matched, only ${metadata.rowsReturned} returned)`;
6995
+ }
6996
+ output += `
6997
+ Execution time: ${metadata.executionTimeMs}ms
6998
+
6999
+ `;
7000
+ if (data.length === 0) {
7001
+ output += "No data returned.";
7002
+ return output;
7003
+ }
7004
+ output += `### Results (${data.length} rows)
7005
+ `;
7006
+ output += "```json\n";
7007
+ output += JSON.stringify(data, null, 2);
7008
+ output += "\n```\n";
7009
+ return output;
7010
+ }
7011
+ /**
7012
+ * Get source summaries (for external inspection/debugging).
7013
+ */
7014
+ getSourceSummaries() {
7015
+ return buildSourceSummaries(this.externalTools);
6182
7016
  }
6183
- return {
6184
- globalContext,
6185
- userContext,
6186
- queryContext,
6187
- combinedContext: combinedContext.trim()
6188
- };
6189
7017
  };
6190
- var KB = {
6191
- getKnowledgeBase,
6192
- getGlobalKnowledgeBase,
6193
- getUserKnowledgeBase,
6194
- getAllKnowledgeBase
7018
+
7019
+ // src/userResponse/agents/types.ts
7020
+ var DEFAULT_AGENT_CONFIG = {
7021
+ maxRowsPerSource: 10,
7022
+ mainAgentModel: "",
7023
+ // will use the provider's default model
7024
+ sourceAgentModel: "",
7025
+ // will use the provider's default model
7026
+ maxRetries: 3,
7027
+ maxIterations: 10
6195
7028
  };
6196
- var knowledge_base_default = KB;
6197
7029
 
6198
- // src/utils/bm25l-reranker.ts
6199
- var BM25L = class {
7030
+ // src/userResponse/anthropic.ts
7031
+ import dotenv from "dotenv";
7032
+
7033
+ // src/userResponse/schema.ts
7034
+ import path5 from "path";
7035
+ import fs6 from "fs";
7036
+ var Schema = class {
7037
+ constructor(schemaFilePath) {
7038
+ this.cachedSchema = null;
7039
+ this.schemaFilePath = schemaFilePath || path5.join(process.cwd(), "../analysis/data/schema.json");
7040
+ }
6200
7041
  /**
6201
- * @param documents - Array of raw documents (strings)
6202
- * @param opts - Optional BM25L parameters
7042
+ * Gets the database schema from the schema file
7043
+ * @returns Parsed schema object or null if error occurs
6203
7044
  */
6204
- constructor(documents = [], opts = {}) {
6205
- if (!Array.isArray(documents)) {
6206
- throw new Error("BM25L: documents must be an array of strings.");
7045
+ getDatabaseSchema() {
7046
+ try {
7047
+ const dir = path5.dirname(this.schemaFilePath);
7048
+ if (!fs6.existsSync(dir)) {
7049
+ logger.info(`Creating directory structure: ${dir}`);
7050
+ fs6.mkdirSync(dir, { recursive: true });
7051
+ }
7052
+ if (!fs6.existsSync(this.schemaFilePath)) {
7053
+ logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
7054
+ const initialSchema = {
7055
+ database: "",
7056
+ schema: "",
7057
+ description: "",
7058
+ tables: [],
7059
+ relationships: []
7060
+ };
7061
+ fs6.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
7062
+ this.cachedSchema = initialSchema;
7063
+ return initialSchema;
7064
+ }
7065
+ const fileContent = fs6.readFileSync(this.schemaFilePath, "utf-8");
7066
+ const schema2 = JSON.parse(fileContent);
7067
+ this.cachedSchema = schema2;
7068
+ return schema2;
7069
+ } catch (error) {
7070
+ logger.error("Error parsing schema file:", error);
7071
+ return null;
6207
7072
  }
6208
- this.k1 = typeof opts.k1 === "number" ? opts.k1 : 1.5;
6209
- this.b = typeof opts.b === "number" ? opts.b : 0.75;
6210
- this.delta = typeof opts.delta === "number" ? opts.delta : 0.5;
6211
- this.documents = documents.map((d) => typeof d === "string" ? this.tokenize(d) : []);
6212
- this.docLengths = this.documents.map((doc) => doc.length);
6213
- this.avgDocLength = this.docLengths.reduce((a, b) => a + b, 0) / (this.docLengths.length || 1);
6214
- this.termDocFreq = {};
6215
- this.documents.forEach((doc) => {
6216
- const seen = /* @__PURE__ */ new Set();
6217
- doc.forEach((term) => {
6218
- if (!seen.has(term)) {
6219
- seen.add(term);
6220
- this.termDocFreq[term] = (this.termDocFreq[term] || 0) + 1;
6221
- }
6222
- });
6223
- });
6224
7073
  }
6225
7074
  /**
6226
- * Tokenize text into lowercase alphanumeric tokens
7075
+ * Gets the cached schema or loads it if not cached
7076
+ * @returns Cached schema or freshly loaded schema
6227
7077
  */
6228
- tokenize(text) {
6229
- if (typeof text !== "string") return [];
6230
- return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
7078
+ getSchema() {
7079
+ if (this.cachedSchema) {
7080
+ return this.cachedSchema;
7081
+ }
7082
+ return this.getDatabaseSchema();
6231
7083
  }
6232
7084
  /**
6233
- * Compute IDF (Inverse Document Frequency) with smoothing
7085
+ * Generates database schema documentation for LLM from Snowflake JSON schema
7086
+ * @returns Formatted schema documentation string
6234
7087
  */
6235
- idf(term) {
6236
- const df = this.termDocFreq[term] || 0;
6237
- const N = this.documents.length || 1;
6238
- return Math.log(1 + (N - df + 0.5) / (df + 0.5));
7088
+ generateSchemaDocumentation() {
7089
+ const schema2 = this.getSchema();
7090
+ if (!schema2) {
7091
+ logger.warn("No database schema found.");
7092
+ return "No database schema available.";
7093
+ }
7094
+ const tables = [];
7095
+ tables.push(`Database: ${schema2.database}`);
7096
+ tables.push(`Schema: ${schema2.schema}`);
7097
+ tables.push(`Description: ${schema2.description}`);
7098
+ tables.push("");
7099
+ tables.push("=".repeat(80));
7100
+ tables.push("");
7101
+ for (const table of schema2.tables) {
7102
+ const tableInfo = [];
7103
+ tableInfo.push(`TABLE: ${table.fullName}`);
7104
+ tableInfo.push(`Description: ${table.description}`);
7105
+ tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
7106
+ tableInfo.push("");
7107
+ tableInfo.push("Columns:");
7108
+ for (const column of table.columns) {
7109
+ let columnLine = ` - ${column.name}: ${column.type}`;
7110
+ if (column.isPrimaryKey) {
7111
+ columnLine += " (PRIMARY KEY)";
7112
+ }
7113
+ if (column.isForeignKey && column.references) {
7114
+ columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
7115
+ }
7116
+ if (!column.nullable) {
7117
+ columnLine += " NOT NULL";
7118
+ }
7119
+ if (column.description) {
7120
+ columnLine += ` - ${column.description}`;
7121
+ }
7122
+ tableInfo.push(columnLine);
7123
+ if (column.sampleValues && column.sampleValues.length > 0) {
7124
+ tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
7125
+ }
7126
+ if (column.statistics) {
7127
+ const stats = column.statistics;
7128
+ if (stats.min !== void 0 && stats.max !== void 0) {
7129
+ tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
7130
+ }
7131
+ if (stats.distinct !== void 0) {
7132
+ tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
7133
+ }
7134
+ }
7135
+ }
7136
+ tableInfo.push("");
7137
+ tables.push(tableInfo.join("\n"));
7138
+ }
7139
+ tables.push("=".repeat(80));
7140
+ tables.push("");
7141
+ tables.push("TABLE RELATIONSHIPS:");
7142
+ tables.push("");
7143
+ for (const rel of schema2.relationships) {
7144
+ tables.push(`${rel.from} -> ${rel.to} (${rel.type}): ${rel.keys.join(" = ")}`);
7145
+ }
7146
+ return tables.join("\n");
6239
7147
  }
6240
7148
  /**
6241
- * Compute BM25L score for a single document
7149
+ * Clears the cached schema, forcing a reload on next access
6242
7150
  */
6243
- score(query, docIndex) {
6244
- if (typeof query !== "string") return 0;
6245
- if (docIndex < 0 || docIndex >= this.documents.length) return 0;
6246
- const tokens = this.tokenize(query);
6247
- if (tokens.length === 0) return 0;
6248
- const doc = this.documents[docIndex];
6249
- const docLength = this.docLengths[docIndex] || 1;
6250
- const freq = {};
6251
- for (const t of doc) {
6252
- freq[t] = (freq[t] || 0) + 1;
6253
- }
6254
- let sum = 0;
6255
- for (const term of tokens) {
6256
- const tf = freq[term] || 0;
6257
- if (tf === 0) continue;
6258
- const idfVal = this.idf(term);
6259
- let tfL = tf - this.b * (docLength / this.avgDocLength) + this.delta;
6260
- if (tfL < 0) tfL = 0;
6261
- sum += idfVal * (tfL / (this.k1 + tfL));
6262
- }
6263
- return sum;
7151
+ clearCache() {
7152
+ this.cachedSchema = null;
6264
7153
  }
6265
7154
  /**
6266
- * Search and rank all documents
7155
+ * Sets a custom schema file path
7156
+ * @param filePath - Path to the schema file
6267
7157
  */
6268
- search(query) {
6269
- return this.documents.map((_, i) => ({
6270
- index: i,
6271
- score: this.score(query, i)
6272
- })).sort((a, b) => b.score - a.score);
7158
+ setSchemaPath(filePath) {
7159
+ this.schemaFilePath = filePath;
7160
+ this.clearCache();
6273
7161
  }
6274
7162
  };
6275
- function normalizeScores(scores) {
6276
- if (scores.length === 0) return [];
6277
- const min = Math.min(...scores);
6278
- const max = Math.max(...scores);
6279
- if (max === min) {
6280
- return scores.map(() => max === 0 ? 0 : 1);
6281
- }
6282
- return scores.map((score) => (score - min) / (max - min));
6283
- }
6284
- function hybridRerank(query, items, getDocument, getSemanticScore, options = {}) {
6285
- const {
6286
- semanticWeight = 0.7,
6287
- bm25Weight = 0.3,
6288
- minScore = 0,
6289
- k1 = 1.5,
6290
- b = 0.75,
6291
- delta = 0.5
6292
- } = options;
6293
- if (items.length === 0) return [];
6294
- const documents = items.map(getDocument);
6295
- const semanticScores = items.map(getSemanticScore);
6296
- const bm25 = new BM25L(documents, { k1, b, delta });
6297
- const bm25Scores = items.map((_, i) => bm25.score(query, i));
6298
- const normalizedSemantic = normalizeScores(semanticScores);
6299
- const normalizedBM25 = normalizeScores(bm25Scores);
6300
- const results = items.map((item, i) => {
6301
- const hybridScore = semanticWeight * normalizedSemantic[i] + bm25Weight * normalizedBM25[i];
6302
- return {
6303
- item,
6304
- originalIndex: i,
6305
- semanticScore: semanticScores[i],
6306
- bm25Score: bm25Scores[i],
6307
- hybridScore
6308
- };
6309
- });
6310
- return results.filter((r) => r.hybridScore >= minScore).sort((a, b2) => b2.hybridScore - a.hybridScore);
6311
- }
6312
- function rerankChromaResults(query, chromaResults, options = {}) {
6313
- const ids = chromaResults.ids[0] || [];
6314
- const documents = chromaResults.documents[0] || [];
6315
- const metadatas = chromaResults.metadatas[0] || [];
6316
- const distances = chromaResults.distances[0] || [];
6317
- if (ids.length === 0) return [];
6318
- const items = ids.map((id, i) => ({
6319
- id,
6320
- document: documents[i],
6321
- metadata: metadatas[i],
6322
- distance: distances[i]
6323
- }));
6324
- const reranked = hybridRerank(
6325
- query,
6326
- items,
6327
- (item) => item.document || "",
6328
- // Convert L2 distance to similarity score
6329
- (item) => 1 / (1 + item.distance),
6330
- options
6331
- );
6332
- return reranked.map((r) => ({
6333
- id: r.item.id,
6334
- document: r.item.document,
6335
- metadata: r.item.metadata,
6336
- distance: r.item.distance,
6337
- semanticScore: r.semanticScore,
6338
- bm25Score: r.bm25Score,
6339
- hybridScore: r.hybridScore
6340
- }));
6341
- }
6342
- function rerankConversationResults(query, results, options = {}) {
6343
- if (results.length === 0) return [];
6344
- const reranked = hybridRerank(
6345
- query,
6346
- results,
6347
- (item) => item.userPrompt || "",
6348
- (item) => item.similarity || 0,
6349
- options
6350
- );
6351
- return reranked.map((r) => ({
6352
- ...r.item,
6353
- hybridScore: r.hybridScore,
6354
- bm25Score: r.bm25Score
6355
- }));
6356
- }
7163
+ var schema = new Schema();
6357
7164
 
6358
- // src/userResponse/conversation-search.ts
6359
- var searchConversations = async ({
6360
- userPrompt,
7165
+ // src/userResponse/knowledge-base.ts
7166
+ var getKnowledgeBase = async ({
7167
+ prompt,
6361
7168
  collections,
6362
- userId,
6363
- similarityThreshold = 0.6
7169
+ topK = 1
6364
7170
  }) => {
6365
7171
  try {
6366
- if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["search"]) {
6367
- logger.info("[ConversationSearch] conversation-history.search collection not registered, skipping");
6368
- return null;
7172
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
7173
+ logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
7174
+ return "";
6369
7175
  }
6370
- logger.info(`[ConversationSearch] Searching conversations for: "${userPrompt.substring(0, 50)}..."`);
6371
- logger.info(`[ConversationSearch] Using similarity threshold: ${(similarityThreshold * 100).toFixed(0)}%`);
6372
- const result = await collections["conversation-history"]["search"]({
6373
- userPrompt,
6374
- userId,
6375
- threshold: similarityThreshold
7176
+ const result = await collections["knowledge-base"]["query"]({
7177
+ prompt,
7178
+ topK
6376
7179
  });
6377
- if (!result) {
6378
- logger.info("[ConversationSearch] No matching conversations found");
6379
- return null;
6380
- }
6381
- if (!result.uiBlock) {
6382
- logger.error("[ConversationSearch] No UI block in conversation search result");
6383
- return null;
7180
+ if (!result || !result.content) {
7181
+ logger.warn("[KnowledgeBase] No knowledge base results returned");
7182
+ return "";
6384
7183
  }
6385
- const similarity = result.similarity || 0;
6386
- logger.info(`[ConversationSearch] Best match similarity: ${(similarity * 100).toFixed(2)}%`);
6387
- if (similarity < similarityThreshold) {
6388
- logger.info(
6389
- `[ConversationSearch] Best match has similarity ${(similarity * 100).toFixed(2)}% but below threshold ${(similarityThreshold * 100).toFixed(2)}%`
6390
- );
6391
- return null;
7184
+ logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
7185
+ if (result.metadata?.sources && result.metadata.sources.length > 0) {
7186
+ logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
6392
7187
  }
6393
- logger.info(
6394
- `[ConversationSearch] Found matching conversation with similarity ${(similarity * 100).toFixed(2)}%`
6395
- );
6396
- logger.debug(`[ConversationSearch] Matched prompt: "${result.metadata?.userPrompt?.substring(0, 50)}..."`);
6397
- return result;
7188
+ return result.content;
6398
7189
  } catch (error) {
6399
7190
  const errorMsg = error instanceof Error ? error.message : String(error);
6400
- logger.warn(`[ConversationSearch] Error searching conversations: ${errorMsg}`);
6401
- return null;
7191
+ logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
7192
+ return "";
6402
7193
  }
6403
7194
  };
6404
- var searchConversationsWithReranking = async (options) => {
6405
- const {
6406
- userPrompt,
6407
- collections,
6408
- userId,
6409
- similarityThreshold = 0.6,
6410
- rerankCandidates = 50,
6411
- // Fetch more candidates for better reranking
6412
- hybridOptions = {
6413
- semanticWeight: 0.7,
6414
- bm25Weight: 0.3
6415
- }
6416
- } = options;
7195
+ var getGlobalKnowledgeBase = async ({
7196
+ collections,
7197
+ limit = 100
7198
+ }) => {
6417
7199
  try {
6418
- if (!collections || !collections["conversation-history"]) {
6419
- logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
6420
- return null;
7200
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
7201
+ logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
7202
+ return "";
6421
7203
  }
6422
- if (!collections["conversation-history"]["searchMultiple"]) {
6423
- logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
6424
- return searchConversations({
6425
- userPrompt,
6426
- collections,
6427
- userId,
6428
- similarityThreshold
6429
- });
7204
+ const result = await collections["knowledge-base"]["getGlobal"]({ limit });
7205
+ if (!result || !result.content) {
7206
+ logger.warn("[KnowledgeBase] No global knowledge base nodes found");
7207
+ return "";
6430
7208
  }
6431
- const results = await collections["conversation-history"]["searchMultiple"]({
6432
- userPrompt,
6433
- userId,
6434
- limit: rerankCandidates,
6435
- threshold: 0
6436
- // No threshold - get all candidates for reranking
6437
- });
6438
- if (!results || results.length === 0) {
6439
- logger.info("[ConversationSearch] No conversations found in database");
6440
- return null;
7209
+ logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
7210
+ return result.content;
7211
+ } catch (error) {
7212
+ const errorMsg = error instanceof Error ? error.message : String(error);
7213
+ logger.warn(`[KnowledgeBase] Error fetching global knowledge base: ${errorMsg}`);
7214
+ return "";
7215
+ }
7216
+ };
7217
+ var getUserKnowledgeBase = async ({
7218
+ collections,
7219
+ userId,
7220
+ limit = 100
7221
+ }) => {
7222
+ try {
7223
+ if (!userId) {
7224
+ logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
7225
+ return "";
6441
7226
  }
6442
- logger.info(`[ConversationSearch] Retrieved ${results.length} candidates for reranking`);
6443
- const candidatesForReranking = results.map((r) => ({
6444
- ...r,
6445
- userPrompt: r.metadata?.userPrompt || ""
6446
- }));
6447
- const reranked = rerankConversationResults(userPrompt, candidatesForReranking, hybridOptions);
6448
- if (reranked.length === 0) {
6449
- logger.info("[ConversationSearch] No results after reranking");
6450
- return null;
7227
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
7228
+ logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
7229
+ return "";
6451
7230
  }
6452
- const best = reranked[0];
6453
- const hybridScore = best.hybridScore;
6454
- const semanticScore = best.similarity || 0;
6455
- const matchedUserPrompt = best.userPrompt || best.metadata?.userPrompt || "";
6456
- logger.info(`[ConversationSearch] Best match after reranking:`);
6457
- logger.info(` - Hybrid score: ${(hybridScore * 100).toFixed(2)}%`);
6458
- logger.info(` - Semantic score: ${(semanticScore * 100).toFixed(2)}%`);
6459
- logger.info(` - BM25L score: ${best.bm25Score.toFixed(4)}`);
6460
- logger.info(` - Matched prompt: "${matchedUserPrompt}"`);
6461
- logger.info(` - Query prompt: "${userPrompt}"`);
6462
- if (semanticScore < similarityThreshold) {
6463
- logger.info(
6464
- `[ConversationSearch] Semantic score ${(semanticScore * 100).toFixed(2)}% below threshold ${(similarityThreshold * 100).toFixed(2)}% - rejecting match`
6465
- );
6466
- return null;
7231
+ const result = await collections["knowledge-base"]["getByUser"]({
7232
+ userId: Number(userId),
7233
+ limit
7234
+ });
7235
+ if (!result || !result.content) {
7236
+ logger.info(`[KnowledgeBase] No user knowledge base nodes found for userId: ${userId}`);
7237
+ return "";
6467
7238
  }
6468
- logger.info(
6469
- `[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
6470
- );
6471
- return {
6472
- uiBlock: best.uiBlock,
6473
- similarity: semanticScore,
6474
- hybridScore,
6475
- bm25Score: best.bm25Score,
6476
- metadata: best.metadata
6477
- };
7239
+ logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} user knowledge base nodes for userId: ${userId}`);
7240
+ return result.content;
6478
7241
  } catch (error) {
6479
7242
  const errorMsg = error instanceof Error ? error.message : String(error);
6480
- logger.warn(`[ConversationSearch] Error in hybrid search: ${errorMsg}`);
6481
- return null;
6482
- }
6483
- };
6484
- var ConversationSearch = {
6485
- searchConversations,
6486
- searchConversationsWithReranking
6487
- };
6488
- var conversation_search_default = ConversationSearch;
6489
-
6490
- // src/userResponse/prompt-extractor.ts
6491
- function extractPromptText(content) {
6492
- if (content === null || content === void 0) {
7243
+ logger.warn(`[KnowledgeBase] Error fetching user knowledge base: ${errorMsg}`);
6493
7244
  return "";
6494
7245
  }
6495
- if (typeof content === "string") {
6496
- return content;
6497
- }
6498
- if (Array.isArray(content)) {
6499
- return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
6500
- }
6501
- if (content && typeof content === "object") {
6502
- return extractObjectText(content);
6503
- }
6504
- return String(content);
6505
- }
6506
- function extractContentBlockText(item) {
6507
- if (typeof item === "string") {
6508
- return item;
6509
- }
6510
- if (item && typeof item === "object") {
6511
- const obj = item;
6512
- if (typeof obj.text === "string") {
6513
- return obj.text;
6514
- }
6515
- if (typeof obj.content === "string") {
6516
- return obj.content;
6517
- }
6518
- return JSON.stringify(item, null, 2);
7246
+ };
7247
+ var getAllKnowledgeBase = async ({
7248
+ prompt,
7249
+ collections,
7250
+ userId,
7251
+ topK = 3
7252
+ }) => {
7253
+ const [globalContext, userContext, queryContext] = await Promise.all([
7254
+ getGlobalKnowledgeBase({ collections }),
7255
+ getUserKnowledgeBase({ collections, userId }),
7256
+ getKnowledgeBase({ prompt, collections, topK })
7257
+ ]);
7258
+ let combinedContext = "";
7259
+ if (globalContext) {
7260
+ combinedContext += "## Global Knowledge Base\n";
7261
+ combinedContext += "The following information applies to all queries:\n\n";
7262
+ combinedContext += globalContext + "\n\n";
6519
7263
  }
6520
- return String(item);
6521
- }
6522
- function extractObjectText(obj) {
6523
- if (typeof obj.text === "string") {
6524
- return obj.text;
7264
+ if (userContext) {
7265
+ combinedContext += "## User-Specific Knowledge Base\n";
7266
+ combinedContext += "The following information is specific to this user:\n\n";
7267
+ combinedContext += userContext + "\n\n";
6525
7268
  }
6526
- if (typeof obj.content === "string") {
6527
- return obj.content;
7269
+ if (queryContext) {
7270
+ combinedContext += "## Relevant Knowledge Base (Query-Matched)\n";
7271
+ combinedContext += "The following information is semantically relevant to the current query:\n\n";
7272
+ combinedContext += queryContext + "\n\n";
6528
7273
  }
6529
- return JSON.stringify(obj, null, 2);
6530
- }
6531
-
6532
- // src/userResponse/constants.ts
6533
- var MAX_QUERY_VALIDATION_RETRIES = 3;
6534
- var MAX_QUERY_ATTEMPTS = 6;
6535
- var MAX_TOOL_ATTEMPTS = 3;
6536
- var STREAM_FLUSH_INTERVAL_MS = 50;
6537
- var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
6538
- var STREAM_DELAY_MS = 50;
6539
- var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
6540
- var MAX_TOKENS_QUERY_FIX = 2048;
6541
- var MAX_TOKENS_COMPONENT_MATCHING = 8192;
6542
- var MAX_TOKENS_CLASSIFICATION = 1500;
6543
- var MAX_TOKENS_ADAPTATION = 8192;
6544
- var MAX_TOKENS_TEXT_RESPONSE = 4e3;
6545
- var MAX_TOKENS_NEXT_QUESTIONS = 1200;
6546
- var DEFAULT_MAX_ROWS_FOR_LLM = 10;
6547
- var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
6548
- var STREAM_PREVIEW_MAX_ROWS = 10;
6549
- var STREAM_PREVIEW_MAX_CHARS = 200;
6550
- var TOOL_TRACKING_MAX_ROWS = 5;
6551
- var TOOL_TRACKING_MAX_CHARS = 200;
6552
- var TOOL_TRACKING_SAMPLE_ROWS = 3;
6553
- var DEFAULT_QUERY_LIMIT = 10;
6554
- var MAX_COMPONENT_QUERY_LIMIT = 10;
6555
- var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
6556
- var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
6557
- var MAX_TOOL_CALLING_ITERATIONS = 20;
6558
- var KNOWLEDGE_BASE_TOP_K = 3;
7274
+ return {
7275
+ globalContext,
7276
+ userContext,
7277
+ queryContext,
7278
+ combinedContext: combinedContext.trim()
7279
+ };
7280
+ };
7281
+ var KB = {
7282
+ getKnowledgeBase,
7283
+ getGlobalKnowledgeBase,
7284
+ getUserKnowledgeBase,
7285
+ getAllKnowledgeBase
7286
+ };
7287
+ var knowledge_base_default = KB;
6559
7288
 
6560
- // src/userResponse/stream-buffer.ts
6561
- var StreamBuffer = class {
6562
- constructor(callback) {
6563
- this.buffer = "";
6564
- this.flushTimer = null;
6565
- this.fullText = "";
6566
- this.callback = callback;
7289
+ // src/userResponse/prompt-extractor.ts
7290
+ function extractPromptText(content) {
7291
+ if (content === null || content === void 0) {
7292
+ return "";
6567
7293
  }
6568
- /**
6569
- * Check if the buffer has a callback configured
6570
- */
6571
- hasCallback() {
6572
- return !!this.callback;
7294
+ if (typeof content === "string") {
7295
+ return content;
6573
7296
  }
6574
- /**
6575
- * Get all text that has been written (including already flushed)
6576
- */
6577
- getFullText() {
6578
- return this.fullText;
7297
+ if (Array.isArray(content)) {
7298
+ return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
6579
7299
  }
6580
- /**
6581
- * Write a chunk to the buffer
6582
- * Large chunks or chunks with newlines are flushed immediately
6583
- * Small chunks are batched and flushed after a short interval
6584
- *
6585
- * @param chunk - Text chunk to write
6586
- */
6587
- write(chunk) {
6588
- this.fullText += chunk;
6589
- if (!this.callback) {
6590
- return;
6591
- }
6592
- this.buffer += chunk;
6593
- if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
6594
- this.flushNow();
6595
- } else if (!this.flushTimer) {
6596
- this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
6597
- }
7300
+ if (content && typeof content === "object") {
7301
+ return extractObjectText(content);
6598
7302
  }
6599
- /**
6600
- * Flush the buffer immediately
6601
- * Call this before tool execution or other operations that need clean output
6602
- */
6603
- flush() {
6604
- this.flushNow();
7303
+ return String(content);
7304
+ }
7305
+ function extractContentBlockText(item) {
7306
+ if (typeof item === "string") {
7307
+ return item;
6605
7308
  }
6606
- /**
6607
- * Internal flush implementation
6608
- */
6609
- flushNow() {
6610
- if (this.flushTimer) {
6611
- clearTimeout(this.flushTimer);
6612
- this.flushTimer = null;
7309
+ if (item && typeof item === "object") {
7310
+ const obj = item;
7311
+ if (typeof obj.text === "string") {
7312
+ return obj.text;
6613
7313
  }
6614
- if (this.buffer && this.callback) {
6615
- this.callback(this.buffer);
6616
- this.buffer = "";
7314
+ if (typeof obj.content === "string") {
7315
+ return obj.content;
6617
7316
  }
7317
+ return JSON.stringify(item, null, 2);
6618
7318
  }
6619
- /**
6620
- * Clean up resources
6621
- * Call this when done with the buffer
6622
- */
6623
- dispose() {
6624
- this.flush();
6625
- this.callback = void 0;
6626
- }
6627
- };
6628
- function streamDelay(ms = STREAM_DELAY_MS) {
6629
- return new Promise((resolve) => setTimeout(resolve, ms));
7319
+ return String(item);
6630
7320
  }
6631
- async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
6632
- if (!streamBuffer.hasCallback()) {
6633
- return operation();
7321
+ function extractObjectText(obj) {
7322
+ if (typeof obj.text === "string") {
7323
+ return obj.text;
6634
7324
  }
6635
- const startTime = Date.now();
6636
- await streamDelay(30);
6637
- streamBuffer.write(`\u23F3 ${progressMessage}`);
6638
- const heartbeatInterval = setInterval(() => {
6639
- const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
6640
- if (elapsedSeconds >= 1) {
6641
- streamBuffer.write(` (${elapsedSeconds}s)`);
6642
- }
6643
- }, intervalMs);
6644
- try {
6645
- const result = await operation();
6646
- return result;
6647
- } finally {
6648
- clearInterval(heartbeatInterval);
6649
- streamBuffer.write("\n\n");
7325
+ if (typeof obj.content === "string") {
7326
+ return obj.content;
6650
7327
  }
7328
+ return JSON.stringify(obj, null, 2);
6651
7329
  }
6652
7330
 
6653
7331
  // src/userResponse/utils/component-props-processor.ts
@@ -8449,48 +9127,48 @@ ${executedToolsText}`);
8449
9127
  }
8450
9128
  };
8451
9129
 
8452
- // src/userResponse/groq.ts
9130
+ // src/userResponse/anthropic.ts
8453
9131
  dotenv.config();
8454
- var GroqLLM = class extends BaseLLM {
9132
+ var AnthropicLLM = class extends BaseLLM {
8455
9133
  constructor(config) {
8456
9134
  super(config);
8457
9135
  }
8458
9136
  getDefaultModel() {
8459
- return "groq/openai/gpt-oss-120b";
9137
+ return "anthropic/claude-sonnet-4-5-20250929";
8460
9138
  }
8461
9139
  getDefaultFastModel() {
8462
- return "groq/llama-3.1-8b-instant";
9140
+ return "anthropic/claude-haiku-4-5-20251001";
8463
9141
  }
8464
9142
  getDefaultApiKey() {
8465
- return process.env.GROQ_API_KEY;
9143
+ return process.env.ANTHROPIC_API_KEY;
8466
9144
  }
8467
9145
  getProviderName() {
8468
- return "Groq";
9146
+ return "Anthropic";
8469
9147
  }
8470
9148
  };
8471
- var groqLLM = new GroqLLM();
9149
+ var anthropicLLM = new AnthropicLLM();
8472
9150
 
8473
- // src/userResponse/anthropic.ts
9151
+ // src/userResponse/groq.ts
8474
9152
  import dotenv2 from "dotenv";
8475
9153
  dotenv2.config();
8476
- var AnthropicLLM = class extends BaseLLM {
9154
+ var GroqLLM = class extends BaseLLM {
8477
9155
  constructor(config) {
8478
9156
  super(config);
8479
9157
  }
8480
9158
  getDefaultModel() {
8481
- return "anthropic/claude-sonnet-4-5-20250929";
9159
+ return "groq/openai/gpt-oss-120b";
8482
9160
  }
8483
9161
  getDefaultFastModel() {
8484
- return "anthropic/claude-haiku-4-5-20251001";
9162
+ return "groq/llama-3.1-8b-instant";
8485
9163
  }
8486
9164
  getDefaultApiKey() {
8487
- return process.env.ANTHROPIC_API_KEY;
9165
+ return process.env.GROQ_API_KEY;
8488
9166
  }
8489
9167
  getProviderName() {
8490
- return "Anthropic";
9168
+ return "Groq";
8491
9169
  }
8492
9170
  };
8493
- var anthropicLLM = new AnthropicLLM();
9171
+ var groqLLM = new GroqLLM();
8494
9172
 
8495
9173
  // src/userResponse/gemini.ts
8496
9174
  import dotenv3 from "dotenv";
@@ -8536,115 +9214,214 @@ var OpenAILLM = class extends BaseLLM {
8536
9214
  };
8537
9215
  var openaiLLM = new OpenAILLM();
8538
9216
 
8539
- // src/userResponse/index.ts
8540
- import dotenv5 from "dotenv";
8541
- dotenv5.config();
8542
- function getLLMProviders() {
8543
- const envProviders = process.env.LLM_PROVIDERS;
8544
- const DEFAULT_PROVIDERS = ["anthropic", "gemini", "openai", "groq"];
8545
- if (!envProviders) {
8546
- return DEFAULT_PROVIDERS;
8547
- }
9217
+ // src/userResponse/agent-user-response.ts
9218
+ function getLLMInstance(provider) {
9219
+ switch (provider) {
9220
+ case "anthropic":
9221
+ return anthropicLLM;
9222
+ case "groq":
9223
+ return groqLLM;
9224
+ case "gemini":
9225
+ return geminiLLM;
9226
+ case "openai":
9227
+ return openaiLLM;
9228
+ default:
9229
+ return anthropicLLM;
9230
+ }
9231
+ }
9232
+ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, streamCallback, collections, externalTools, userId) => {
9233
+ const startTime = Date.now();
9234
+ const providers = llmProviders || ["anthropic"];
9235
+ const provider = providers[0];
9236
+ const llmInstance = getLLMInstance(provider);
9237
+ logger.info(`[AgentFlow] Starting | provider: ${provider} | prompt: "${prompt.substring(0, 50)}..."`);
8548
9238
  try {
8549
- const providers = JSON.parse(envProviders);
8550
- const validProviders = providers.filter((p) => p === "anthropic" || p === "groq" || p === "gemini" || p === "openai");
8551
- if (validProviders.length === 0) {
8552
- return DEFAULT_PROVIDERS;
8553
- }
8554
- return validProviders;
8555
- } catch (error) {
8556
- logger.error('Failed to parse LLM_PROVIDERS, defaulting to ["anthropic"]:', error);
8557
- return DEFAULT_PROVIDERS;
8558
- }
8559
- }
8560
- var useAnthropicMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8561
- if (responseMode === "component" && components.length === 0) {
8562
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8563
- logger.error("[useAnthropicMethod] No components available");
8564
- return { success: false, errors: [emptyMsg] };
8565
- }
8566
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8567
- logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
8568
- return matchResult;
8569
- };
8570
- var useGroqMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8571
- logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
8572
- logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
8573
- if (responseMode === "component" && components.length === 0) {
8574
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8575
- logger.error("[useGroqMethod] No components available");
8576
- return { success: false, errors: [emptyMsg] };
8577
- }
8578
- logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
8579
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8580
- logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
8581
- return matchResult;
8582
- };
8583
- var useGeminiMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8584
- logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
8585
- logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
8586
- if (responseMode === "component" && components.length === 0) {
8587
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8588
- logger.error("[useGeminiMethod] No components available");
8589
- return { success: false, errors: [emptyMsg] };
8590
- }
8591
- logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
8592
- const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8593
- logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
8594
- return matchResult;
8595
- };
8596
- var useOpenAIMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8597
- logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
8598
- logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
8599
- if (responseMode === "component" && components.length === 0) {
8600
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8601
- logger.error("[useOpenAIMethod] No components available");
8602
- return { success: false, errors: [emptyMsg] };
8603
- }
8604
- logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
8605
- const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8606
- logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
8607
- return matchResult;
8608
- };
8609
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8610
- const providers = llmProviders || getLLMProviders();
8611
- const errors = [];
8612
- logger.info(`[get_user_response] LLM Provider order: [${providers.join(", ")}]`);
8613
- for (let i = 0; i < providers.length; i++) {
8614
- const provider = providers[i];
8615
- const isLastProvider = i === providers.length - 1;
8616
- logger.info(`[get_user_response] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
8617
- let result;
8618
- if (provider === "anthropic") {
8619
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8620
- } else if (provider === "groq") {
8621
- result = await useGroqMethod(prompt, components, groqApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8622
- } else if (provider === "gemini") {
8623
- result = await useGeminiMethod(prompt, components, geminiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8624
- } else if (provider === "openai") {
8625
- result = await useOpenAIMethod(prompt, components, openaiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8626
- } else {
8627
- logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
8628
- errors.push(`Unknown provider: ${provider}`);
8629
- continue;
8630
- }
8631
- if (result.success) {
8632
- logger.info(`[get_user_response] Success with provider: ${provider}`);
8633
- return result;
9239
+ const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
9240
+ userPrompt: prompt,
9241
+ collections,
9242
+ userId,
9243
+ similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
9244
+ });
9245
+ if (conversationMatch) {
9246
+ logger.info(`[AgentFlow] Found matching conversation (${(conversationMatch.similarity * 100).toFixed(2)}% similarity)`);
9247
+ const rawComponent = conversationMatch.uiBlock?.component || conversationMatch.uiBlock?.generatedComponentMetadata;
9248
+ const isValidComponent = rawComponent && typeof rawComponent === "object" && Object.keys(rawComponent).length > 0;
9249
+ const component = isValidComponent ? rawComponent : null;
9250
+ const cachedTextResponse = conversationMatch.uiBlock?.analysis || conversationMatch.uiBlock?.textResponse || conversationMatch.uiBlock?.text || "";
9251
+ if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
9252
+ if (streamCallback && cachedTextResponse) {
9253
+ streamCallback(cachedTextResponse);
9254
+ }
9255
+ const elapsedTime2 = Date.now() - startTime;
9256
+ logger.info(`[AgentFlow] Exact match \u2014 returning cached result (${elapsedTime2}ms)`);
9257
+ return {
9258
+ success: true,
9259
+ data: {
9260
+ text: cachedTextResponse,
9261
+ component,
9262
+ actions: conversationMatch.uiBlock?.actions || [],
9263
+ reasoning: `Exact match from previous conversation`,
9264
+ method: `${provider}-agent-semantic-match`,
9265
+ semanticSimilarity: conversationMatch.similarity
9266
+ },
9267
+ errors: []
9268
+ };
9269
+ }
9270
+ logger.info(`[AgentFlow] Similar match but below exact threshold \u2014 proceeding with agent`);
8634
9271
  } else {
8635
- const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
8636
- errors.push(...providerErrors);
8637
- logger.warn(`[get_user_response] Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`);
8638
- if (!isLastProvider) {
8639
- logger.info("[get_user_response] Falling back to next provider...");
9272
+ logger.info(`[AgentFlow] No matching conversations \u2014 proceeding with agent`);
9273
+ }
9274
+ const apiKey = (() => {
9275
+ switch (provider) {
9276
+ case "anthropic":
9277
+ return anthropicApiKey;
9278
+ case "groq":
9279
+ return groqApiKey;
9280
+ case "gemini":
9281
+ return geminiApiKey;
9282
+ case "openai":
9283
+ return openaiApiKey;
9284
+ default:
9285
+ return anthropicApiKey;
9286
+ }
9287
+ })();
9288
+ const agentTools = (externalTools || []).map((t) => ({
9289
+ id: t.id,
9290
+ name: t.name,
9291
+ description: t.description,
9292
+ fn: t.fn,
9293
+ limit: t.limit,
9294
+ outputSchema: t.outputSchema,
9295
+ executionType: t.executionType,
9296
+ userProvidedData: t.userProvidedData,
9297
+ params: t.params
9298
+ }));
9299
+ const agentConfig = {
9300
+ ...DEFAULT_AGENT_CONFIG,
9301
+ apiKey: apiKey || void 0
9302
+ };
9303
+ const streamBuffer = new StreamBuffer(streamCallback);
9304
+ const mainAgent = new MainAgent(agentTools, agentConfig, streamBuffer);
9305
+ const agentResponse = await mainAgent.handleQuestion(
9306
+ prompt,
9307
+ apiKey,
9308
+ conversationHistory,
9309
+ streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
9310
+ );
9311
+ const textResponse = streamBuffer.getFullText() || agentResponse.text || "I apologize, but I was unable to generate a response.";
9312
+ streamBuffer.flush();
9313
+ const hasExecutedTools = agentResponse.executedTools.length > 0;
9314
+ let matchedComponents = [];
9315
+ let layoutTitle = "Dashboard";
9316
+ let layoutDescription = "Multi-component dashboard";
9317
+ let actions = [];
9318
+ if (!hasExecutedTools) {
9319
+ logger.info(`[AgentFlow] No tools executed \u2014 general question, skipping component generation`);
9320
+ const nextQuestions = await llmInstance.generateNextQuestions(
9321
+ prompt,
9322
+ null,
9323
+ void 0,
9324
+ apiKey,
9325
+ conversationHistory,
9326
+ textResponse
9327
+ );
9328
+ actions = convertQuestionsToActions(nextQuestions);
9329
+ } else if (components && components.length > 0) {
9330
+ logger.info(`[AgentFlow] ${agentResponse.executedTools.length} tools executed \u2014 generating components`);
9331
+ if (streamBuffer.hasCallback()) {
9332
+ streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
9333
+ streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
9334
+ }
9335
+ const componentStreamCallback = streamBuffer.hasCallback() ? (component) => {
9336
+ const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
9337
+ streamBuffer.write(answerMarker);
9338
+ } : void 0;
9339
+ const sanitizedTextResponse = textResponse.replace(
9340
+ /<DataTable>[\s\S]*?<\/DataTable>/g,
9341
+ "<DataTable>[Data preview removed - for table components, REUSE the exact SQL query shown above (the one that returned these results). Do NOT write a new query or embed data in props.]</DataTable>"
9342
+ );
9343
+ const matchResult = await llmInstance.matchComponentsFromAnalysis(
9344
+ sanitizedTextResponse,
9345
+ components,
9346
+ prompt,
9347
+ apiKey,
9348
+ componentStreamCallback,
9349
+ [],
9350
+ // deferredTools — MainAgent handles tool execution
9351
+ agentResponse.executedTools,
9352
+ collections,
9353
+ userId
9354
+ );
9355
+ matchedComponents = matchResult.components;
9356
+ layoutTitle = matchResult.layoutTitle;
9357
+ layoutDescription = matchResult.layoutDescription;
9358
+ actions = matchResult.actions;
9359
+ }
9360
+ const securedComponents = matchedComponents.map((comp) => {
9361
+ const props = { ...comp.props };
9362
+ if (props.externalTool?.parameters?.sql) {
9363
+ const { sql, ...restParams } = props.externalTool.parameters;
9364
+ const queryId = queryCache.storeQuery(sql);
9365
+ props.externalTool = {
9366
+ ...props.externalTool,
9367
+ parameters: { queryId, ...restParams }
9368
+ };
8640
9369
  }
9370
+ if (props.query) {
9371
+ const { query, ...restProps } = props;
9372
+ const queryId = queryCache.storeQuery(query);
9373
+ return { ...comp, props: { ...restProps, queryId } };
9374
+ }
9375
+ return { ...comp, props };
9376
+ });
9377
+ let containerComponent = null;
9378
+ if (securedComponents.length > 0) {
9379
+ containerComponent = {
9380
+ id: `container_${Date.now()}`,
9381
+ name: "MultiComponentContainer",
9382
+ type: "Container",
9383
+ description: layoutDescription,
9384
+ props: {
9385
+ config: {
9386
+ title: layoutTitle,
9387
+ description: layoutDescription,
9388
+ components: securedComponents
9389
+ },
9390
+ actions
9391
+ }
9392
+ };
8641
9393
  }
9394
+ const elapsedTime = Date.now() - startTime;
9395
+ logger.info(`[AgentFlow] Complete | ${agentResponse.executedTools.length} tools | ${matchedComponents.length} components | ${elapsedTime}ms`);
9396
+ return {
9397
+ success: true,
9398
+ data: {
9399
+ text: textResponse,
9400
+ component: containerComponent,
9401
+ actions,
9402
+ method: `${provider}-agent-response`
9403
+ },
9404
+ errors: []
9405
+ };
9406
+ } catch (error) {
9407
+ const errorMsg = error instanceof Error ? error.message : String(error);
9408
+ logger.error(`[AgentFlow] Error: ${errorMsg}`);
9409
+ userPromptErrorLogger.logError(
9410
+ "agentUserResponse",
9411
+ error instanceof Error ? error : new Error(errorMsg),
9412
+ { userPrompt: prompt }
9413
+ );
9414
+ const elapsedTime = Date.now() - startTime;
9415
+ logger.info(`[AgentFlow] Failed in ${elapsedTime}ms`);
9416
+ return {
9417
+ success: false,
9418
+ errors: [errorMsg],
9419
+ data: {
9420
+ text: "I apologize, but I encountered an error processing your request. Please try again.",
9421
+ method: `${provider}-agent-error`
9422
+ }
9423
+ };
8642
9424
  }
8643
- logger.error(`[get_user_response] All LLM providers failed. Errors: ${errors.join("; ")}`);
8644
- return {
8645
- success: false,
8646
- errors
8647
- };
8648
9425
  };
8649
9426
 
8650
9427
  // src/utils/conversation-saver.ts
@@ -8842,7 +9619,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8842
9619
  sendMessage(streamMessage);
8843
9620
  };
8844
9621
  }
8845
- const userResponse = await get_user_response(
9622
+ const userResponse = await get_agent_user_response(
8846
9623
  prompt,
8847
9624
  components,
8848
9625
  anthropicApiKey,
@@ -8851,7 +9628,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8851
9628
  openaiApiKey,
8852
9629
  llmProviders,
8853
9630
  conversationHistory,
8854
- responseMode,
8855
9631
  streamCallback,
8856
9632
  collections,
8857
9633
  externalTools,
@@ -12739,6 +13515,28 @@ async function handleSchemaRequest(message, sendMessage) {
12739
13515
  }
12740
13516
  }
12741
13517
 
13518
+ // src/userResponse/index.ts
13519
+ import dotenv5 from "dotenv";
13520
+ dotenv5.config();
13521
+ function getLLMProviders() {
13522
+ const envProviders = process.env.LLM_PROVIDERS;
13523
+ const DEFAULT_PROVIDERS = ["anthropic", "gemini", "openai", "groq"];
13524
+ if (!envProviders) {
13525
+ return DEFAULT_PROVIDERS;
13526
+ }
13527
+ try {
13528
+ const providers = JSON.parse(envProviders);
13529
+ const validProviders = providers.filter((p) => p === "anthropic" || p === "groq" || p === "gemini" || p === "openai");
13530
+ if (validProviders.length === 0) {
13531
+ return DEFAULT_PROVIDERS;
13532
+ }
13533
+ return validProviders;
13534
+ } catch (error) {
13535
+ logger.error('Failed to parse LLM_PROVIDERS, defaulting to ["anthropic"]:', error);
13536
+ return DEFAULT_PROVIDERS;
13537
+ }
13538
+ }
13539
+
12742
13540
  // src/auth/user-manager.ts
12743
13541
  import fs7 from "fs";
12744
13542
  import path6 from "path";