@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.js CHANGED
@@ -1464,6 +1464,7 @@ var ThreadManager = class _ThreadManager {
1464
1464
  var QueryCache = class {
1465
1465
  constructor() {
1466
1466
  this.cache = /* @__PURE__ */ new Map();
1467
+ this.queryIdCache = /* @__PURE__ */ new Map();
1467
1468
  this.ttlMs = 5 * 60 * 1e3;
1468
1469
  // Default: 5 minutes
1469
1470
  this.cleanupInterval = null;
@@ -1558,11 +1559,64 @@ var QueryCache = class {
1558
1559
  expiredCount++;
1559
1560
  }
1560
1561
  }
1562
+ for (const [key, entry] of this.queryIdCache.entries()) {
1563
+ if (now - entry.timestamp > this.ttlMs) {
1564
+ this.queryIdCache.delete(key);
1565
+ expiredCount++;
1566
+ }
1567
+ }
1561
1568
  if (expiredCount > 0) {
1562
1569
  logger.debug(`[QueryCache] Cleaned up ${expiredCount} expired entries`);
1563
1570
  }
1564
1571
  }, 2 * 60 * 1e3);
1565
1572
  }
1573
+ // ============================================
1574
+ // Query ID Store — maps queryId → query (no SQL sent to frontend)
1575
+ // ============================================
1576
+ /**
1577
+ * Generate a unique query ID
1578
+ */
1579
+ generateQueryId() {
1580
+ return `qry_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
1581
+ }
1582
+ /**
1583
+ * Store a query by ID. Returns the generated queryId.
1584
+ * The query is stored server-side; only the queryId is sent to the frontend.
1585
+ */
1586
+ storeQuery(query, data) {
1587
+ const queryId = this.generateQueryId();
1588
+ this.queryIdCache.set(queryId, {
1589
+ queryId,
1590
+ query,
1591
+ data: data || null,
1592
+ timestamp: Date.now()
1593
+ });
1594
+ const queryPreview = typeof query === "string" ? query.substring(0, 50) : JSON.stringify(query).substring(0, 50);
1595
+ logger.debug(`[QueryCache] Stored query as ${queryId} (${queryPreview}...)`);
1596
+ return queryId;
1597
+ }
1598
+ /**
1599
+ * Get a stored query by its ID (not expired)
1600
+ */
1601
+ getQuery(queryId) {
1602
+ const entry = this.queryIdCache.get(queryId);
1603
+ if (!entry) return null;
1604
+ if (Date.now() - entry.timestamp > this.ttlMs) {
1605
+ this.queryIdCache.delete(queryId);
1606
+ return null;
1607
+ }
1608
+ return { query: entry.query, data: entry.data };
1609
+ }
1610
+ /**
1611
+ * Update cached data for a queryId
1612
+ */
1613
+ setQueryData(queryId, data) {
1614
+ const entry = this.queryIdCache.get(queryId);
1615
+ if (entry) {
1616
+ entry.data = data;
1617
+ entry.timestamp = Date.now();
1618
+ }
1619
+ }
1566
1620
  /**
1567
1621
  * Stop cleanup interval (for graceful shutdown)
1568
1622
  */
@@ -1572,6 +1626,7 @@ var QueryCache = class {
1572
1626
  this.cleanupInterval = null;
1573
1627
  }
1574
1628
  this.cache.clear();
1629
+ this.queryIdCache.clear();
1575
1630
  }
1576
1631
  };
1577
1632
  var queryCache = new QueryCache();
@@ -1934,6 +1989,24 @@ function getQueryCacheKey(query) {
1934
1989
  }
1935
1990
  return "";
1936
1991
  }
1992
+ function getCacheKey(collection, op, params) {
1993
+ if (collection === "database" && op === "execute" && params?.sql) {
1994
+ return getQueryCacheKey(params.sql);
1995
+ }
1996
+ if (collection === "external-tools" && op === "execute" && params?.sql) {
1997
+ const toolId = params.toolId || "";
1998
+ const sqlKey = getQueryCacheKey(params.sql);
1999
+ const paramsKey = params.params ? JSON.stringify(params.params) : "";
2000
+ return sqlKey ? `et:${toolId}:${sqlKey}:${paramsKey}` : "";
2001
+ }
2002
+ if (collection === "external-tools" && op === "executeByQueryId" && params?.queryId) {
2003
+ const toolId = params.toolId || "";
2004
+ const filterKey = params.filterParams ? JSON.stringify(params.filterParams) : "";
2005
+ const paramsKey = params.params ? JSON.stringify(params.params) : "";
2006
+ return `etq:${toolId}:${params.queryId}:${paramsKey}:${filterKey}`;
2007
+ }
2008
+ return "";
2009
+ }
1937
2010
  async function handleDataRequest(data, collections, sendMessage) {
1938
2011
  let requestId;
1939
2012
  let collection;
@@ -1960,31 +2033,26 @@ async function handleDataRequest(data, collections, sendMessage) {
1960
2033
  const startTime = performance.now();
1961
2034
  let result;
1962
2035
  let fromCache = false;
1963
- if (collection === "database" && op === "execute" && params?.sql) {
1964
- const cacheKey = getQueryCacheKey(params.sql);
1965
- if (cacheKey) {
1966
- const cachedResult = queryCache.get(cacheKey);
1967
- if (cachedResult !== null) {
1968
- result = cachedResult;
1969
- fromCache = true;
1970
- logger.info(`[QueryCache] Returning cached result for database.execute`);
1971
- }
2036
+ const cacheKey = getCacheKey(collection, op, params);
2037
+ if (cacheKey) {
2038
+ const cachedResult = queryCache.get(cacheKey);
2039
+ if (cachedResult !== null) {
2040
+ result = cachedResult;
2041
+ fromCache = true;
2042
+ logger.info(`[QueryCache] Returning cached result for ${collection}.${op}`);
1972
2043
  }
1973
2044
  }
1974
2045
  if (!fromCache) {
1975
2046
  const handler = collections[collection][op];
1976
2047
  let handlerParams = params || {};
1977
2048
  if (collection === "database" && op === "execute" && params?.sql && typeof params.sql !== "string") {
1978
- const cacheKey = getQueryCacheKey(params.sql);
1979
- handlerParams = { ...params, sql: cacheKey };
2049
+ const queryKey = getQueryCacheKey(params.sql);
2050
+ handlerParams = { ...params, sql: queryKey };
1980
2051
  logger.debug(`[data-request] Converted object query to JSON string for database handler`);
1981
2052
  }
1982
2053
  result = await handler(handlerParams);
1983
- if (collection === "database" && op === "execute" && params?.sql && result) {
1984
- const cacheKey = getQueryCacheKey(params.sql);
1985
- if (cacheKey) {
1986
- queryCache.set(cacheKey, result);
1987
- }
2054
+ if (cacheKey && result) {
2055
+ queryCache.set(cacheKey, result);
1988
2056
  }
1989
2057
  }
1990
2058
  const executionMs = Math.round(performance.now() - startTime);
@@ -2529,9 +2597,6 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
2529
2597
  sendMessage(response);
2530
2598
  }
2531
2599
 
2532
- // src/userResponse/groq.ts
2533
- var import_dotenv = __toESM(require("dotenv"));
2534
-
2535
2600
  // src/userResponse/prompt-loader.ts
2536
2601
  var import_fs2 = __toESM(require("fs"));
2537
2602
  var import_path = __toESM(require("path"));
@@ -3633,6 +3698,71 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
3633
3698
  `,
3634
3699
  user: `{{USER_PROMPT}}`
3635
3700
  },
3701
+ "agent-main": {
3702
+ system: `You are a data analysis agent with access to multiple data sources.
3703
+ Answer the user's question by querying the appropriate data source(s) using the tools provided.
3704
+
3705
+ ## Available Data Sources
3706
+ {{SOURCE_SUMMARIES}}
3707
+
3708
+ ## How to Use Source Tools
3709
+ Each tool represents a data source. Call a tool with:
3710
+ - **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.
3711
+ - **aggregation** (optional): Default is "raw". Use "pre-aggregate" for totals/counts/averages, "summary" for high-level metrics.
3712
+
3713
+ ## Writing Good Intents
3714
+ Describe ALL your data needs in one intent per source. The source agent handles it in a single query.
3715
+ - You can combine raw rows and aggregated totals in one request
3716
+ - Be specific about which fields, filters, sorting, and grouping you need
3717
+ - Prefer a SINGLE call per source \u2014 only call again if the result was insufficient or incorrect
3718
+
3719
+ ## Rules
3720
+ - Call the appropriate source tool(s) to get data before answering
3721
+ - You can call multiple source tools if the question requires data from different sources
3722
+ - If a query returns insufficient or incorrect data, call the tool again with a modified intent
3723
+ - If the data is marked as LIMITED, mention it in your analysis (the full dataset may be larger)
3724
+ - After getting all needed data, provide a clear, concise analysis answering the question
3725
+ - For general questions (greetings, help, chitchat), respond directly without calling any tools
3726
+ - Be precise with numbers \u2014 use the exact values from the data
3727
+ - Maximum {{MAX_ROWS}} rows per source query
3728
+
3729
+ ## Current Date/Time
3730
+ {{CURRENT_DATETIME}}
3731
+
3732
+ ---
3733
+
3734
+ ## CONTEXT (for this specific request)
3735
+
3736
+ ### Conversation History
3737
+ {{CONVERSATION_HISTORY}}`,
3738
+ user: `{{USER_PROMPT}}`
3739
+ },
3740
+ "agent-source-query": {
3741
+ system: `You are a data source agent for "{{SOURCE_NAME}}" ({{SOURCE_TYPE}}).
3742
+ Your job is to fetch the requested data using the available tool.
3743
+
3744
+ ## Data Source Schema
3745
+ {{FULL_SCHEMA}}
3746
+
3747
+ ## Rules
3748
+ - Generate the most efficient query for the given intent
3749
+ - Always include a LIMIT of {{MAX_ROWS}} rows maximum
3750
+ - Aggregation mode is "{{AGGREGATION_MODE}}":
3751
+ - "pre-aggregate": Use GROUP BY, COUNT, SUM, AVG etc. to return aggregated results instead of raw rows
3752
+ - "summary": Return a high-level overview with key metrics
3753
+ - "raw": Return individual records as-is
3754
+ - **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)
3755
+ - Return ONLY the fields needed for the intent \u2014 avoid SELECT *
3756
+ - Use the tool provided to execute your query
3757
+ - If the query fails with an ERROR, analyze the error and try a corrected query
3758
+ - **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.**
3759
+ - Call the tool exactly ONCE unless it returns an error that needs a corrected query
3760
+
3761
+ ## Current Date/Time
3762
+ {{CURRENT_DATETIME}}`,
3763
+ user: `## Task
3764
+ {{INTENT}}`
3765
+ },
3636
3766
  "dash-filter-picker": {
3637
3767
  system: `You are a dashboard filter expert that creates filter components and updates existing dashboard components to work with the filter.
3638
3768
 
@@ -4207,587 +4337,868 @@ function validateAndFixSqlQuery(query, dbType) {
4207
4337
  };
4208
4338
  }
4209
4339
 
4210
- // src/userResponse/schema.ts
4211
- var import_path2 = __toESM(require("path"));
4340
+ // src/utils/user-prompt-error-logger.ts
4212
4341
  var import_fs3 = __toESM(require("fs"));
4213
- var Schema = class {
4214
- constructor(schemaFilePath) {
4215
- this.cachedSchema = null;
4216
- this.schemaFilePath = schemaFilePath || import_path2.default.join(process.cwd(), "../analysis/data/schema.json");
4342
+ var import_path2 = __toESM(require("path"));
4343
+ var UserPromptErrorLogger = class {
4344
+ constructor() {
4345
+ this.logStream = null;
4346
+ this.hasErrors = false;
4347
+ this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH || import_path2.default.join(process.cwd(), "user-prompt-req-errors");
4348
+ this.enabled = process.env.USER_PROMPT_ERROR_LOGGING !== "false";
4217
4349
  }
4218
4350
  /**
4219
- * Gets the database schema from the schema file
4220
- * @returns Parsed schema object or null if error occurs
4351
+ * Reset the error log file for a new request
4221
4352
  */
4222
- getDatabaseSchema() {
4353
+ resetLogFile(requestContext) {
4354
+ if (!this.enabled) return;
4223
4355
  try {
4224
- const dir = import_path2.default.dirname(this.schemaFilePath);
4225
- if (!import_fs3.default.existsSync(dir)) {
4226
- logger.info(`Creating directory structure: ${dir}`);
4227
- import_fs3.default.mkdirSync(dir, { recursive: true });
4356
+ if (this.logStream) {
4357
+ this.logStream.end();
4358
+ this.logStream = null;
4228
4359
  }
4229
- if (!import_fs3.default.existsSync(this.schemaFilePath)) {
4230
- logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
4231
- const initialSchema = {
4232
- database: "",
4233
- schema: "",
4234
- description: "",
4235
- tables: [],
4236
- relationships: []
4237
- };
4238
- import_fs3.default.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
4239
- this.cachedSchema = initialSchema;
4240
- return initialSchema;
4360
+ const dir = import_path2.default.dirname(this.logPath);
4361
+ if (dir !== "." && !import_fs3.default.existsSync(dir)) {
4362
+ import_fs3.default.mkdirSync(dir, { recursive: true });
4241
4363
  }
4242
- const fileContent = import_fs3.default.readFileSync(this.schemaFilePath, "utf-8");
4243
- const schema2 = JSON.parse(fileContent);
4244
- this.cachedSchema = schema2;
4245
- return schema2;
4364
+ this.logStream = import_fs3.default.createWriteStream(this.logPath, { flags: "w" });
4365
+ this.hasErrors = false;
4366
+ const header = `================================================================================
4367
+ USER PROMPT REQUEST ERROR LOG
4368
+ Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4369
+ ${requestContext ? `Context: ${requestContext}` : ""}
4370
+ ================================================================================
4371
+
4372
+ `;
4373
+ this.logStream.write(header);
4246
4374
  } catch (error) {
4247
- logger.error("Error parsing schema file:", error);
4248
- return null;
4375
+ console.error("[UserPromptErrorLogger] Failed to reset log file:", error);
4249
4376
  }
4250
4377
  }
4251
4378
  /**
4252
- * Gets the cached schema or loads it if not cached
4253
- * @returns Cached schema or freshly loaded schema
4379
+ * Log a JSON parse error with the raw string that failed
4254
4380
  */
4255
- getSchema() {
4256
- if (this.cachedSchema) {
4257
- return this.cachedSchema;
4258
- }
4259
- return this.getDatabaseSchema();
4381
+ logJsonParseError(context, rawString, error) {
4382
+ if (!this.enabled) return;
4383
+ this.hasErrors = true;
4384
+ const entry = `
4385
+ --------------------------------------------------------------------------------
4386
+ [${(/* @__PURE__ */ new Date()).toISOString()}] JSON PARSE ERROR
4387
+ --------------------------------------------------------------------------------
4388
+ Context: ${context}
4389
+ Error: ${error.message}
4390
+
4391
+ Raw String (${rawString.length} chars):
4392
+ --------------------------------------------------------------------------------
4393
+ ${rawString}
4394
+ --------------------------------------------------------------------------------
4395
+
4396
+ Stack Trace:
4397
+ ${error.stack || "No stack trace available"}
4398
+
4399
+ `;
4400
+ this.write(entry);
4401
+ console.error(`[UserPromptError] JSON Parse Error in ${context}: ${error.message}`);
4260
4402
  }
4261
4403
  /**
4262
- * Generates database schema documentation for LLM from Snowflake JSON schema
4263
- * @returns Formatted schema documentation string
4404
+ * Log a general error with full details
4264
4405
  */
4265
- generateSchemaDocumentation() {
4266
- const schema2 = this.getSchema();
4267
- if (!schema2) {
4268
- logger.warn("No database schema found.");
4269
- return "No database schema available.";
4270
- }
4271
- const tables = [];
4272
- tables.push(`Database: ${schema2.database}`);
4273
- tables.push(`Schema: ${schema2.schema}`);
4274
- tables.push(`Description: ${schema2.description}`);
4275
- tables.push("");
4276
- tables.push("=".repeat(80));
4277
- tables.push("");
4278
- for (const table of schema2.tables) {
4279
- const tableInfo = [];
4280
- tableInfo.push(`TABLE: ${table.fullName}`);
4281
- tableInfo.push(`Description: ${table.description}`);
4282
- tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
4283
- tableInfo.push("");
4284
- tableInfo.push("Columns:");
4285
- for (const column of table.columns) {
4286
- let columnLine = ` - ${column.name}: ${column.type}`;
4287
- if (column.isPrimaryKey) {
4288
- columnLine += " (PRIMARY KEY)";
4289
- }
4290
- if (column.isForeignKey && column.references) {
4291
- columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
4292
- }
4293
- if (!column.nullable) {
4294
- columnLine += " NOT NULL";
4295
- }
4296
- if (column.description) {
4297
- columnLine += ` - ${column.description}`;
4298
- }
4299
- tableInfo.push(columnLine);
4300
- if (column.sampleValues && column.sampleValues.length > 0) {
4301
- tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
4302
- }
4303
- if (column.statistics) {
4304
- const stats = column.statistics;
4305
- if (stats.min !== void 0 && stats.max !== void 0) {
4306
- tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
4307
- }
4308
- if (stats.distinct !== void 0) {
4309
- tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
4310
- }
4311
- }
4312
- }
4313
- tableInfo.push("");
4314
- tables.push(tableInfo.join("\n"));
4406
+ logError(context, error, additionalData) {
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()}] ERROR
4414
+ --------------------------------------------------------------------------------
4415
+ Context: ${context}
4416
+ Error: ${errorMessage}
4417
+ `;
4418
+ if (additionalData) {
4419
+ entry += `
4420
+ Additional Data:
4421
+ ${JSON.stringify(additionalData, null, 2)}
4422
+ `;
4315
4423
  }
4316
- tables.push("=".repeat(80));
4317
- tables.push("");
4318
- tables.push("TABLE RELATIONSHIPS:");
4319
- tables.push("");
4320
- for (const rel of schema2.relationships) {
4321
- tables.push(`${rel.from} -> ${rel.to} (${rel.type}): ${rel.keys.join(" = ")}`);
4424
+ if (errorStack) {
4425
+ entry += `
4426
+ Stack Trace:
4427
+ ${errorStack}
4428
+ `;
4322
4429
  }
4323
- return tables.join("\n");
4430
+ entry += `--------------------------------------------------------------------------------
4431
+
4432
+ `;
4433
+ this.write(entry);
4434
+ console.error(`[UserPromptError] ${context}: ${errorMessage}`);
4324
4435
  }
4325
4436
  /**
4326
- * Clears the cached schema, forcing a reload on next access
4437
+ * Log a SQL query error with the full query
4327
4438
  */
4328
- clearCache() {
4329
- this.cachedSchema = null;
4439
+ logSqlError(query, error, params) {
4440
+ if (!this.enabled) return;
4441
+ this.hasErrors = true;
4442
+ const errorMessage = error instanceof Error ? error.message : error;
4443
+ const entry = `
4444
+ --------------------------------------------------------------------------------
4445
+ [${(/* @__PURE__ */ new Date()).toISOString()}] SQL QUERY ERROR
4446
+ --------------------------------------------------------------------------------
4447
+ Error: ${errorMessage}
4448
+
4449
+ Query (${query.length} chars):
4450
+ --------------------------------------------------------------------------------
4451
+ ${query}
4452
+ --------------------------------------------------------------------------------
4453
+ ${params ? `
4454
+ Parameters: ${JSON.stringify(params)}` : ""}
4455
+
4456
+ `;
4457
+ this.write(entry);
4458
+ console.error(`[UserPromptError] SQL Error: ${errorMessage}`);
4330
4459
  }
4331
4460
  /**
4332
- * Sets a custom schema file path
4333
- * @param filePath - Path to the schema file
4461
+ * Log an LLM API error
4334
4462
  */
4335
- setSchemaPath(filePath) {
4336
- this.schemaFilePath = filePath;
4337
- this.clearCache();
4338
- }
4339
- };
4340
- var schema = new Schema();
4341
-
4342
- // src/llm.ts
4343
- var import_sdk = __toESM(require("@anthropic-ai/sdk"));
4344
- var import_groq_sdk = __toESM(require("groq-sdk"));
4345
- var import_generative_ai = require("@google/generative-ai");
4346
- var import_openai = __toESM(require("openai"));
4347
- var import_jsonrepair = require("jsonrepair");
4348
-
4349
- // src/utils/llm-usage-logger.ts
4350
- var import_fs4 = __toESM(require("fs"));
4351
- var import_path3 = __toESM(require("path"));
4352
- var PRICING = {
4353
- // Anthropic (December 2025)
4354
- "claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4355
- "claude-opus-4-5-20251101": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4356
- "claude-sonnet-4-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4357
- "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4358
- "claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4359
- "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4360
- "claude-3-5-sonnet-20241022": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4361
- "claude-3-5-haiku-20241022": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4362
- "claude-3-opus-20240229": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
4363
- "claude-3-sonnet-20240229": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4364
- "claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
4365
- // OpenAI (December 2025)
4366
- "gpt-5": { input: 1.25, output: 10 },
4367
- "gpt-5-mini": { input: 0.25, output: 2 },
4368
- "gpt-4o": { input: 5, output: 15 },
4369
- // Updated pricing as of late 2025
4370
- "gpt-4o-mini": { input: 0.15, output: 0.6 },
4371
- "gpt-4-turbo": { input: 10, output: 30 },
4372
- "gpt-4": { input: 30, output: 60 },
4373
- "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
4374
- // Google Gemini (January 2026)
4375
- "gemini-3-pro-preview": { input: 2, output: 12 },
4376
- // New Gemini 3
4377
- "gemini-3-flash-preview": { input: 0.5, output: 3 },
4378
- // For prompts ≤200K tokens, 2x for >200K
4379
- "gemini-2.5-flash": { input: 0.3, output: 2.5 },
4380
- // Paid tier: $0.30 input (text/image/video), $2.50 output (includes thinking)
4381
- "gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
4382
- "gemini-2.0-flash": { input: 0.1, output: 0.4 },
4383
- "gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
4384
- "gemini-1.5-pro": { input: 1.25, output: 5 },
4385
- "gemini-1.5-flash": { input: 0.075, output: 0.3 },
4386
- // Groq (December 2025)
4387
- "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
4388
- "llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
4389
- "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
4390
- "llama-4-scout-17b-16e": { input: 0.11, output: 0.34 },
4391
- "llama-4-maverick-17b-128e": { input: 0.2, output: 0.6 },
4392
- "mixtral-8x7b-32768": { input: 0.27, output: 0.27 },
4393
- "qwen3-32b": { input: 0.29, output: 0.59 }
4394
- };
4395
- var DEFAULT_PRICING = { input: 3, output: 15 };
4396
- var LLMUsageLogger = class {
4397
- constructor() {
4398
- this.logStream = null;
4399
- this.sessionStats = {
4400
- totalCalls: 0,
4401
- totalInputTokens: 0,
4402
- totalOutputTokens: 0,
4403
- totalCacheReadTokens: 0,
4404
- totalCacheWriteTokens: 0,
4405
- totalCostUSD: 0,
4406
- totalDurationMs: 0
4407
- };
4408
- this.logPath = process.env.LLM_USAGE_LOG_PATH || import_path3.default.join(process.cwd(), "llm-usage-logs");
4409
- this.enabled = process.env.LLM_USAGE_LOGGING !== "false";
4410
- if (this.enabled) {
4411
- this.initLogStream();
4463
+ logLlmError(provider, model, method, error, requestData) {
4464
+ if (!this.enabled) return;
4465
+ this.hasErrors = true;
4466
+ const errorMessage = error instanceof Error ? error.message : error;
4467
+ const errorStack = error instanceof Error ? error.stack : void 0;
4468
+ let entry = `
4469
+ --------------------------------------------------------------------------------
4470
+ [${(/* @__PURE__ */ new Date()).toISOString()}] LLM API ERROR
4471
+ --------------------------------------------------------------------------------
4472
+ Provider: ${provider}
4473
+ Model: ${model}
4474
+ Method: ${method}
4475
+ Error: ${errorMessage}
4476
+ `;
4477
+ if (requestData) {
4478
+ const dataStr = JSON.stringify(requestData, null, 2);
4479
+ const truncated = dataStr.length > 5e3 ? dataStr.substring(0, 5e3) + "\n... [truncated]" : dataStr;
4480
+ entry += `
4481
+ Request Data:
4482
+ ${truncated}
4483
+ `;
4412
4484
  }
4413
- }
4414
- initLogStream() {
4415
- try {
4416
- const dir = import_path3.default.dirname(this.logPath);
4417
- if (!import_fs4.default.existsSync(dir)) {
4418
- import_fs4.default.mkdirSync(dir, { recursive: true });
4419
- }
4420
- this.logStream = import_fs4.default.createWriteStream(this.logPath, { flags: "a" });
4421
- if (!import_fs4.default.existsSync(this.logPath) || import_fs4.default.statSync(this.logPath).size === 0) {
4422
- this.writeHeader();
4423
- }
4424
- } catch (error) {
4425
- console.error("[LLM-Usage-Logger] Failed to initialize log stream:", error);
4426
- this.enabled = false;
4485
+ if (errorStack) {
4486
+ entry += `
4487
+ Stack Trace:
4488
+ ${errorStack}
4489
+ `;
4427
4490
  }
4428
- }
4429
- writeHeader() {
4430
- const header = `
4431
- ================================================================================
4432
- LLM USAGE LOG - Session Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4433
- ================================================================================
4434
- Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4435
- Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
4436
- Cost: $X.XXXXXX | Time: Xms
4437
- ================================================================================
4491
+ entry += `--------------------------------------------------------------------------------
4438
4492
 
4439
4493
  `;
4440
- this.logStream?.write(header);
4441
- }
4442
- /**
4443
- * Calculate cost based on token usage and model
4444
- */
4445
- calculateCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
4446
- let pricing = PRICING[model];
4447
- if (!pricing) {
4448
- const modelLower = model.toLowerCase();
4449
- for (const [key, value] of Object.entries(PRICING)) {
4450
- if (modelLower.includes(key.toLowerCase()) || key.toLowerCase().includes(modelLower)) {
4451
- pricing = value;
4452
- break;
4453
- }
4454
- }
4455
- }
4456
- pricing = pricing || DEFAULT_PRICING;
4457
- const inputCost = inputTokens / 1e6 * pricing.input;
4458
- const outputCost = outputTokens / 1e6 * pricing.output;
4459
- const cacheReadCost = cacheReadTokens / 1e6 * (pricing.cacheRead || pricing.input * 0.1);
4460
- const cacheWriteCost = cacheWriteTokens / 1e6 * (pricing.cacheWrite || pricing.input * 1.25);
4461
- return inputCost + outputCost + cacheReadCost + cacheWriteCost;
4494
+ this.write(entry);
4495
+ console.error(`[UserPromptError] LLM Error (${provider}/${model}): ${errorMessage}`);
4462
4496
  }
4463
4497
  /**
4464
- * Log an LLM API call
4498
+ * Log tool execution error
4465
4499
  */
4466
- log(entry) {
4500
+ logToolError(toolName, toolInput, error) {
4467
4501
  if (!this.enabled) return;
4468
- this.sessionStats.totalCalls++;
4469
- this.sessionStats.totalInputTokens += entry.inputTokens;
4470
- this.sessionStats.totalOutputTokens += entry.outputTokens;
4471
- this.sessionStats.totalCacheReadTokens += entry.cacheReadTokens || 0;
4472
- this.sessionStats.totalCacheWriteTokens += entry.cacheWriteTokens || 0;
4473
- this.sessionStats.totalCostUSD += entry.costUSD;
4474
- this.sessionStats.totalDurationMs += entry.durationMs;
4475
- const cacheInfo = entry.cacheReadTokens || entry.cacheWriteTokens ? ` CACHE_R=${entry.cacheReadTokens || 0} CACHE_W=${entry.cacheWriteTokens || 0}` : "";
4476
- const toolInfo = entry.toolCalls ? ` | Tools: ${entry.toolCalls}` : "";
4477
- const errorInfo = entry.error ? ` | ERROR: ${entry.error}` : "";
4478
- const status = entry.success ? "\u2713" : "\u2717";
4479
- let cacheStatus = "";
4480
- if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
4481
- const savedCost = entry.cacheReadTokens / 1e6 * 2.7;
4482
- cacheStatus = ` \u26A1 CACHE HIT! Saved ~$${savedCost.toFixed(4)}`;
4483
- } else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
4484
- cacheStatus = " \u{1F4DD} Cache created (next request will be cheaper)";
4485
- }
4486
- const logLine = `[${entry.timestamp}] [${entry.requestId}] ${status} ${entry.provider}/${entry.model} [${entry.method}]
4487
- Tokens: IN=${entry.inputTokens} OUT=${entry.outputTokens}${cacheInfo} TOTAL=${entry.totalTokens}
4488
- Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
4489
- `;
4490
- this.logStream?.write(logLine);
4491
- }
4492
- /**
4493
- * Log session summary (call at end of request)
4494
- */
4495
- logSessionSummary(requestContext) {
4496
- if (!this.enabled || this.sessionStats.totalCalls === 0) return;
4497
- const cacheReadSavings = this.sessionStats.totalCacheReadTokens / 1e6 * 2.7;
4498
- const hasCaching = this.sessionStats.totalCacheReadTokens > 0 || this.sessionStats.totalCacheWriteTokens > 0;
4499
- let cacheSection = "";
4500
- if (hasCaching) {
4501
- cacheSection = `
4502
- Cache Statistics:
4503
- Cache Read Tokens: ${this.sessionStats.totalCacheReadTokens.toLocaleString()}${this.sessionStats.totalCacheReadTokens > 0 ? " \u26A1" : ""}
4504
- Cache Write Tokens: ${this.sessionStats.totalCacheWriteTokens.toLocaleString()}${this.sessionStats.totalCacheWriteTokens > 0 ? " \u{1F4DD}" : ""}
4505
- Estimated Savings: $${cacheReadSavings.toFixed(4)}`;
4506
- }
4507
- const summary = `
4502
+ this.hasErrors = true;
4503
+ const errorMessage = error instanceof Error ? error.message : error;
4504
+ const errorStack = error instanceof Error ? error.stack : void 0;
4505
+ const entry = `
4508
4506
  --------------------------------------------------------------------------------
4509
- SESSION SUMMARY${requestContext ? ` (${requestContext})` : ""}
4507
+ [${(/* @__PURE__ */ new Date()).toISOString()}] TOOL EXECUTION ERROR
4510
4508
  --------------------------------------------------------------------------------
4511
- Total LLM Calls: ${this.sessionStats.totalCalls}
4512
- Total Input Tokens: ${this.sessionStats.totalInputTokens.toLocaleString()}
4513
- Total Output Tokens: ${this.sessionStats.totalOutputTokens.toLocaleString()}
4514
- Total Tokens: ${(this.sessionStats.totalInputTokens + this.sessionStats.totalOutputTokens).toLocaleString()}
4515
- Total Cost: $${this.sessionStats.totalCostUSD.toFixed(6)}
4516
- Total Time: ${this.sessionStats.totalDurationMs}ms (${(this.sessionStats.totalDurationMs / 1e3).toFixed(2)}s)
4517
- Avg Cost/Call: $${(this.sessionStats.totalCostUSD / this.sessionStats.totalCalls).toFixed(6)}
4518
- Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStats.totalCalls)}ms${cacheSection}
4509
+ Tool: ${toolName}
4510
+ Error: ${errorMessage}
4511
+
4512
+ Tool Input:
4513
+ ${JSON.stringify(toolInput, null, 2)}
4514
+ ${errorStack ? `
4515
+ Stack Trace:
4516
+ ${errorStack}` : ""}
4519
4517
  --------------------------------------------------------------------------------
4520
4518
 
4521
4519
  `;
4522
- this.logStream?.write(summary);
4523
- }
4524
- /**
4525
- * Reset session stats (call at start of new user request)
4526
- */
4527
- resetSession() {
4528
- this.sessionStats = {
4529
- totalCalls: 0,
4530
- totalInputTokens: 0,
4531
- totalOutputTokens: 0,
4532
- totalCacheReadTokens: 0,
4533
- totalCacheWriteTokens: 0,
4534
- totalCostUSD: 0,
4535
- totalDurationMs: 0
4536
- };
4520
+ this.write(entry);
4521
+ console.error(`[UserPromptError] Tool Error (${toolName}): ${errorMessage}`);
4537
4522
  }
4538
4523
  /**
4539
- * Reset the log file for a new request (clears previous logs)
4540
- * Call this at the start of each USER_PROMPT_REQ
4524
+ * Write final summary if there were errors
4541
4525
  */
4542
- resetLogFile(requestContext) {
4543
- if (!this.enabled) return;
4544
- try {
4545
- if (this.logStream) {
4546
- this.logStream.end();
4547
- this.logStream = null;
4548
- }
4549
- this.logStream = import_fs4.default.createWriteStream(this.logPath, { flags: "w" });
4550
- const header = `
4551
- ================================================================================
4552
- LLM USAGE LOG - Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4553
- ${requestContext ? `Context: ${requestContext}` : ""}
4526
+ writeSummary() {
4527
+ if (!this.enabled || !this.hasErrors) return;
4528
+ const summary = `
4554
4529
  ================================================================================
4555
- Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
4556
- Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
4557
- Cost: $X.XXXXXX | Time: Xms
4530
+ REQUEST COMPLETED WITH ERRORS
4531
+ Time: ${(/* @__PURE__ */ new Date()).toISOString()}
4558
4532
  ================================================================================
4559
-
4560
4533
  `;
4561
- this.logStream.write(header);
4562
- this.resetSession();
4563
- } catch (error) {
4564
- console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
4565
- }
4566
- }
4567
- /**
4568
- * Get current session stats
4569
- */
4570
- getSessionStats() {
4571
- return { ...this.sessionStats };
4534
+ this.write(summary);
4572
4535
  }
4573
4536
  /**
4574
- * Generate a unique request ID
4537
+ * Check if any errors were logged
4575
4538
  */
4576
- generateRequestId() {
4577
- return `req-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
4539
+ hadErrors() {
4540
+ return this.hasErrors;
4578
4541
  }
4579
- };
4580
- var llmUsageLogger = new LLMUsageLogger();
4542
+ write(content) {
4543
+ if (this.logStream) {
4544
+ this.logStream.write(content);
4545
+ }
4546
+ }
4547
+ };
4548
+ var userPromptErrorLogger = new UserPromptErrorLogger();
4549
+
4550
+ // src/utils/bm25l-reranker.ts
4551
+ var BM25L = class {
4552
+ /**
4553
+ * @param documents - Array of raw documents (strings)
4554
+ * @param opts - Optional BM25L parameters
4555
+ */
4556
+ constructor(documents = [], opts = {}) {
4557
+ if (!Array.isArray(documents)) {
4558
+ throw new Error("BM25L: documents must be an array of strings.");
4559
+ }
4560
+ this.k1 = typeof opts.k1 === "number" ? opts.k1 : 1.5;
4561
+ this.b = typeof opts.b === "number" ? opts.b : 0.75;
4562
+ this.delta = typeof opts.delta === "number" ? opts.delta : 0.5;
4563
+ this.documents = documents.map((d) => typeof d === "string" ? this.tokenize(d) : []);
4564
+ this.docLengths = this.documents.map((doc) => doc.length);
4565
+ this.avgDocLength = this.docLengths.reduce((a, b) => a + b, 0) / (this.docLengths.length || 1);
4566
+ this.termDocFreq = {};
4567
+ this.documents.forEach((doc) => {
4568
+ const seen = /* @__PURE__ */ new Set();
4569
+ doc.forEach((term) => {
4570
+ if (!seen.has(term)) {
4571
+ seen.add(term);
4572
+ this.termDocFreq[term] = (this.termDocFreq[term] || 0) + 1;
4573
+ }
4574
+ });
4575
+ });
4576
+ }
4577
+ /**
4578
+ * Tokenize text into lowercase alphanumeric tokens
4579
+ */
4580
+ tokenize(text) {
4581
+ if (typeof text !== "string") return [];
4582
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
4583
+ }
4584
+ /**
4585
+ * Compute IDF (Inverse Document Frequency) with smoothing
4586
+ */
4587
+ idf(term) {
4588
+ const df = this.termDocFreq[term] || 0;
4589
+ const N = this.documents.length || 1;
4590
+ return Math.log(1 + (N - df + 0.5) / (df + 0.5));
4591
+ }
4592
+ /**
4593
+ * Compute BM25L score for a single document
4594
+ */
4595
+ score(query, docIndex) {
4596
+ if (typeof query !== "string") return 0;
4597
+ if (docIndex < 0 || docIndex >= this.documents.length) return 0;
4598
+ const tokens = this.tokenize(query);
4599
+ if (tokens.length === 0) return 0;
4600
+ const doc = this.documents[docIndex];
4601
+ const docLength = this.docLengths[docIndex] || 1;
4602
+ const freq = {};
4603
+ for (const t of doc) {
4604
+ freq[t] = (freq[t] || 0) + 1;
4605
+ }
4606
+ let sum = 0;
4607
+ for (const term of tokens) {
4608
+ const tf = freq[term] || 0;
4609
+ if (tf === 0) continue;
4610
+ const idfVal = this.idf(term);
4611
+ let tfL = tf - this.b * (docLength / this.avgDocLength) + this.delta;
4612
+ if (tfL < 0) tfL = 0;
4613
+ sum += idfVal * (tfL / (this.k1 + tfL));
4614
+ }
4615
+ return sum;
4616
+ }
4617
+ /**
4618
+ * Search and rank all documents
4619
+ */
4620
+ search(query) {
4621
+ return this.documents.map((_, i) => ({
4622
+ index: i,
4623
+ score: this.score(query, i)
4624
+ })).sort((a, b) => b.score - a.score);
4625
+ }
4626
+ };
4627
+ function normalizeScores(scores) {
4628
+ if (scores.length === 0) return [];
4629
+ const min = Math.min(...scores);
4630
+ const max = Math.max(...scores);
4631
+ if (max === min) {
4632
+ return scores.map(() => max === 0 ? 0 : 1);
4633
+ }
4634
+ return scores.map((score) => (score - min) / (max - min));
4635
+ }
4636
+ function hybridRerank(query, items, getDocument, getSemanticScore, options = {}) {
4637
+ const {
4638
+ semanticWeight = 0.7,
4639
+ bm25Weight = 0.3,
4640
+ minScore = 0,
4641
+ k1 = 1.5,
4642
+ b = 0.75,
4643
+ delta = 0.5
4644
+ } = options;
4645
+ if (items.length === 0) return [];
4646
+ const documents = items.map(getDocument);
4647
+ const semanticScores = items.map(getSemanticScore);
4648
+ const bm25 = new BM25L(documents, { k1, b, delta });
4649
+ const bm25Scores = items.map((_, i) => bm25.score(query, i));
4650
+ const normalizedSemantic = normalizeScores(semanticScores);
4651
+ const normalizedBM25 = normalizeScores(bm25Scores);
4652
+ const results = items.map((item, i) => {
4653
+ const hybridScore = semanticWeight * normalizedSemantic[i] + bm25Weight * normalizedBM25[i];
4654
+ return {
4655
+ item,
4656
+ originalIndex: i,
4657
+ semanticScore: semanticScores[i],
4658
+ bm25Score: bm25Scores[i],
4659
+ hybridScore
4660
+ };
4661
+ });
4662
+ return results.filter((r) => r.hybridScore >= minScore).sort((a, b2) => b2.hybridScore - a.hybridScore);
4663
+ }
4664
+ function rerankChromaResults(query, chromaResults, options = {}) {
4665
+ const ids = chromaResults.ids[0] || [];
4666
+ const documents = chromaResults.documents[0] || [];
4667
+ const metadatas = chromaResults.metadatas[0] || [];
4668
+ const distances = chromaResults.distances[0] || [];
4669
+ if (ids.length === 0) return [];
4670
+ const items = ids.map((id, i) => ({
4671
+ id,
4672
+ document: documents[i],
4673
+ metadata: metadatas[i],
4674
+ distance: distances[i]
4675
+ }));
4676
+ const reranked = hybridRerank(
4677
+ query,
4678
+ items,
4679
+ (item) => item.document || "",
4680
+ // Convert L2 distance to similarity score
4681
+ (item) => 1 / (1 + item.distance),
4682
+ options
4683
+ );
4684
+ return reranked.map((r) => ({
4685
+ id: r.item.id,
4686
+ document: r.item.document,
4687
+ metadata: r.item.metadata,
4688
+ distance: r.item.distance,
4689
+ semanticScore: r.semanticScore,
4690
+ bm25Score: r.bm25Score,
4691
+ hybridScore: r.hybridScore
4692
+ }));
4693
+ }
4694
+ function rerankConversationResults(query, results, options = {}) {
4695
+ if (results.length === 0) return [];
4696
+ const reranked = hybridRerank(
4697
+ query,
4698
+ results,
4699
+ (item) => item.userPrompt || "",
4700
+ (item) => item.similarity || 0,
4701
+ options
4702
+ );
4703
+ return reranked.map((r) => ({
4704
+ ...r.item,
4705
+ hybridScore: r.hybridScore,
4706
+ bm25Score: r.bm25Score
4707
+ }));
4708
+ }
4709
+
4710
+ // src/userResponse/conversation-search.ts
4711
+ var searchConversations = async ({
4712
+ userPrompt,
4713
+ collections,
4714
+ userId,
4715
+ similarityThreshold = 0.6
4716
+ }) => {
4717
+ try {
4718
+ if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["search"]) {
4719
+ logger.info("[ConversationSearch] conversation-history.search collection not registered, skipping");
4720
+ return null;
4721
+ }
4722
+ logger.info(`[ConversationSearch] Searching conversations for: "${userPrompt.substring(0, 50)}..."`);
4723
+ logger.info(`[ConversationSearch] Using similarity threshold: ${(similarityThreshold * 100).toFixed(0)}%`);
4724
+ const result = await collections["conversation-history"]["search"]({
4725
+ userPrompt,
4726
+ userId,
4727
+ threshold: similarityThreshold
4728
+ });
4729
+ if (!result) {
4730
+ logger.info("[ConversationSearch] No matching conversations found");
4731
+ return null;
4732
+ }
4733
+ if (!result.uiBlock) {
4734
+ logger.error("[ConversationSearch] No UI block in conversation search result");
4735
+ return null;
4736
+ }
4737
+ const similarity = result.similarity || 0;
4738
+ logger.info(`[ConversationSearch] Best match similarity: ${(similarity * 100).toFixed(2)}%`);
4739
+ if (similarity < similarityThreshold) {
4740
+ logger.info(
4741
+ `[ConversationSearch] Best match has similarity ${(similarity * 100).toFixed(2)}% but below threshold ${(similarityThreshold * 100).toFixed(2)}%`
4742
+ );
4743
+ return null;
4744
+ }
4745
+ logger.info(
4746
+ `[ConversationSearch] Found matching conversation with similarity ${(similarity * 100).toFixed(2)}%`
4747
+ );
4748
+ logger.debug(`[ConversationSearch] Matched prompt: "${result.metadata?.userPrompt?.substring(0, 50)}..."`);
4749
+ return result;
4750
+ } catch (error) {
4751
+ const errorMsg = error instanceof Error ? error.message : String(error);
4752
+ logger.warn(`[ConversationSearch] Error searching conversations: ${errorMsg}`);
4753
+ return null;
4754
+ }
4755
+ };
4756
+ var searchConversationsWithReranking = async (options) => {
4757
+ const {
4758
+ userPrompt,
4759
+ collections,
4760
+ userId,
4761
+ similarityThreshold = 0.6,
4762
+ rerankCandidates = 50,
4763
+ // Fetch more candidates for better reranking
4764
+ hybridOptions = {
4765
+ semanticWeight: 0.7,
4766
+ bm25Weight: 0.3
4767
+ }
4768
+ } = options;
4769
+ try {
4770
+ if (!collections || !collections["conversation-history"]) {
4771
+ logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
4772
+ return null;
4773
+ }
4774
+ if (!collections["conversation-history"]["searchMultiple"]) {
4775
+ logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
4776
+ return searchConversations({
4777
+ userPrompt,
4778
+ collections,
4779
+ userId,
4780
+ similarityThreshold
4781
+ });
4782
+ }
4783
+ const results = await collections["conversation-history"]["searchMultiple"]({
4784
+ userPrompt,
4785
+ userId,
4786
+ limit: rerankCandidates,
4787
+ threshold: 0
4788
+ // No threshold - get all candidates for reranking
4789
+ });
4790
+ if (!results || results.length === 0) {
4791
+ logger.info("[ConversationSearch] No conversations found in database");
4792
+ return null;
4793
+ }
4794
+ logger.info(`[ConversationSearch] Retrieved ${results.length} candidates for reranking`);
4795
+ const candidatesForReranking = results.map((r) => ({
4796
+ ...r,
4797
+ userPrompt: r.metadata?.userPrompt || ""
4798
+ }));
4799
+ const reranked = rerankConversationResults(userPrompt, candidatesForReranking, hybridOptions);
4800
+ if (reranked.length === 0) {
4801
+ logger.info("[ConversationSearch] No results after reranking");
4802
+ return null;
4803
+ }
4804
+ const best = reranked[0];
4805
+ const hybridScore = best.hybridScore;
4806
+ const semanticScore = best.similarity || 0;
4807
+ const matchedUserPrompt = best.userPrompt || best.metadata?.userPrompt || "";
4808
+ logger.info(`[ConversationSearch] Best match after reranking:`);
4809
+ logger.info(` - Hybrid score: ${(hybridScore * 100).toFixed(2)}%`);
4810
+ logger.info(` - Semantic score: ${(semanticScore * 100).toFixed(2)}%`);
4811
+ logger.info(` - BM25L score: ${best.bm25Score.toFixed(4)}`);
4812
+ logger.info(` - Matched prompt: "${matchedUserPrompt}"`);
4813
+ logger.info(` - Query prompt: "${userPrompt}"`);
4814
+ if (semanticScore < similarityThreshold) {
4815
+ logger.info(
4816
+ `[ConversationSearch] Semantic score ${(semanticScore * 100).toFixed(2)}% below threshold ${(similarityThreshold * 100).toFixed(2)}% - rejecting match`
4817
+ );
4818
+ return null;
4819
+ }
4820
+ logger.info(
4821
+ `[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
4822
+ );
4823
+ return {
4824
+ uiBlock: best.uiBlock,
4825
+ similarity: semanticScore,
4826
+ hybridScore,
4827
+ bm25Score: best.bm25Score,
4828
+ metadata: best.metadata
4829
+ };
4830
+ } catch (error) {
4831
+ const errorMsg = error instanceof Error ? error.message : String(error);
4832
+ logger.warn(`[ConversationSearch] Error in hybrid search: ${errorMsg}`);
4833
+ return null;
4834
+ }
4835
+ };
4836
+ var ConversationSearch = {
4837
+ searchConversations,
4838
+ searchConversationsWithReranking
4839
+ };
4840
+ var conversation_search_default = ConversationSearch;
4581
4841
 
4582
- // src/utils/user-prompt-error-logger.ts
4583
- var import_fs5 = __toESM(require("fs"));
4584
- var import_path4 = __toESM(require("path"));
4585
- var UserPromptErrorLogger = class {
4586
- constructor() {
4587
- this.logStream = null;
4588
- this.hasErrors = false;
4589
- this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH || import_path4.default.join(process.cwd(), "user-prompt-req-errors");
4590
- this.enabled = process.env.USER_PROMPT_ERROR_LOGGING !== "false";
4842
+ // src/userResponse/constants.ts
4843
+ var MAX_QUERY_VALIDATION_RETRIES = 3;
4844
+ var MAX_QUERY_ATTEMPTS = 6;
4845
+ var MAX_TOOL_ATTEMPTS = 3;
4846
+ var STREAM_FLUSH_INTERVAL_MS = 50;
4847
+ var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
4848
+ var STREAM_DELAY_MS = 50;
4849
+ var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
4850
+ var MAX_TOKENS_QUERY_FIX = 2048;
4851
+ var MAX_TOKENS_COMPONENT_MATCHING = 8192;
4852
+ var MAX_TOKENS_CLASSIFICATION = 1500;
4853
+ var MAX_TOKENS_ADAPTATION = 8192;
4854
+ var MAX_TOKENS_TEXT_RESPONSE = 4e3;
4855
+ var MAX_TOKENS_NEXT_QUESTIONS = 1200;
4856
+ var DEFAULT_MAX_ROWS_FOR_LLM = 10;
4857
+ var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
4858
+ var STREAM_PREVIEW_MAX_ROWS = 10;
4859
+ var STREAM_PREVIEW_MAX_CHARS = 200;
4860
+ var TOOL_TRACKING_MAX_ROWS = 5;
4861
+ var TOOL_TRACKING_MAX_CHARS = 200;
4862
+ var TOOL_TRACKING_SAMPLE_ROWS = 3;
4863
+ var DEFAULT_QUERY_LIMIT = 10;
4864
+ var MAX_COMPONENT_QUERY_LIMIT = 10;
4865
+ var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
4866
+ var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
4867
+ var MAX_TOOL_CALLING_ITERATIONS = 20;
4868
+ var KNOWLEDGE_BASE_TOP_K = 3;
4869
+
4870
+ // src/userResponse/stream-buffer.ts
4871
+ var StreamBuffer = class {
4872
+ constructor(callback) {
4873
+ this.buffer = "";
4874
+ this.flushTimer = null;
4875
+ this.fullText = "";
4876
+ this.callback = callback;
4591
4877
  }
4592
4878
  /**
4593
- * Reset the error log file for a new request
4879
+ * Check if the buffer has a callback configured
4594
4880
  */
4595
- resetLogFile(requestContext) {
4596
- if (!this.enabled) return;
4597
- try {
4598
- if (this.logStream) {
4599
- this.logStream.end();
4600
- this.logStream = null;
4601
- }
4602
- const dir = import_path4.default.dirname(this.logPath);
4603
- if (dir !== "." && !import_fs5.default.existsSync(dir)) {
4604
- import_fs5.default.mkdirSync(dir, { recursive: true });
4605
- }
4606
- this.logStream = import_fs5.default.createWriteStream(this.logPath, { flags: "w" });
4607
- this.hasErrors = false;
4608
- const header = `================================================================================
4609
- USER PROMPT REQUEST ERROR LOG
4610
- Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
4611
- ${requestContext ? `Context: ${requestContext}` : ""}
4612
- ================================================================================
4613
-
4614
- `;
4615
- this.logStream.write(header);
4616
- } catch (error) {
4617
- console.error("[UserPromptErrorLogger] Failed to reset log file:", error);
4881
+ hasCallback() {
4882
+ return !!this.callback;
4883
+ }
4884
+ /**
4885
+ * Get all text that has been written (including already flushed)
4886
+ */
4887
+ getFullText() {
4888
+ return this.fullText;
4889
+ }
4890
+ /**
4891
+ * Write a chunk to the buffer
4892
+ * Large chunks or chunks with newlines are flushed immediately
4893
+ * Small chunks are batched and flushed after a short interval
4894
+ *
4895
+ * @param chunk - Text chunk to write
4896
+ */
4897
+ write(chunk) {
4898
+ this.fullText += chunk;
4899
+ if (!this.callback) {
4900
+ return;
4901
+ }
4902
+ this.buffer += chunk;
4903
+ if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
4904
+ this.flushNow();
4905
+ } else if (!this.flushTimer) {
4906
+ this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
4618
4907
  }
4619
4908
  }
4620
4909
  /**
4621
- * Log a JSON parse error with the raw string that failed
4910
+ * Flush the buffer immediately
4911
+ * Call this before tool execution or other operations that need clean output
4622
4912
  */
4623
- logJsonParseError(context, rawString, error) {
4624
- if (!this.enabled) return;
4625
- this.hasErrors = true;
4626
- const entry = `
4627
- --------------------------------------------------------------------------------
4628
- [${(/* @__PURE__ */ new Date()).toISOString()}] JSON PARSE ERROR
4629
- --------------------------------------------------------------------------------
4630
- Context: ${context}
4631
- Error: ${error.message}
4632
-
4633
- Raw String (${rawString.length} chars):
4634
- --------------------------------------------------------------------------------
4635
- ${rawString}
4636
- --------------------------------------------------------------------------------
4637
-
4638
- Stack Trace:
4639
- ${error.stack || "No stack trace available"}
4640
-
4641
- `;
4642
- this.write(entry);
4643
- console.error(`[UserPromptError] JSON Parse Error in ${context}: ${error.message}`);
4913
+ flush() {
4914
+ this.flushNow();
4644
4915
  }
4645
4916
  /**
4646
- * Log a general error with full details
4917
+ * Internal flush implementation
4647
4918
  */
4648
- logError(context, error, additionalData) {
4649
- 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()}] ERROR
4656
- --------------------------------------------------------------------------------
4657
- Context: ${context}
4658
- Error: ${errorMessage}
4659
- `;
4660
- if (additionalData) {
4661
- entry += `
4662
- Additional Data:
4663
- ${JSON.stringify(additionalData, null, 2)}
4664
- `;
4919
+ flushNow() {
4920
+ if (this.flushTimer) {
4921
+ clearTimeout(this.flushTimer);
4922
+ this.flushTimer = null;
4923
+ }
4924
+ if (this.buffer && this.callback) {
4925
+ this.callback(this.buffer);
4926
+ this.buffer = "";
4927
+ }
4928
+ }
4929
+ /**
4930
+ * Clean up resources
4931
+ * Call this when done with the buffer
4932
+ */
4933
+ dispose() {
4934
+ this.flush();
4935
+ this.callback = void 0;
4936
+ }
4937
+ };
4938
+ function streamDelay(ms = STREAM_DELAY_MS) {
4939
+ return new Promise((resolve) => setTimeout(resolve, ms));
4940
+ }
4941
+ async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
4942
+ if (!streamBuffer.hasCallback()) {
4943
+ return operation();
4944
+ }
4945
+ const startTime = Date.now();
4946
+ await streamDelay(30);
4947
+ streamBuffer.write(`\u23F3 ${progressMessage}`);
4948
+ const heartbeatInterval = setInterval(() => {
4949
+ const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
4950
+ if (elapsedSeconds >= 1) {
4951
+ streamBuffer.write(` (${elapsedSeconds}s)`);
4952
+ }
4953
+ }, intervalMs);
4954
+ try {
4955
+ const result = await operation();
4956
+ return result;
4957
+ } finally {
4958
+ clearInterval(heartbeatInterval);
4959
+ streamBuffer.write("\n\n");
4960
+ }
4961
+ }
4962
+
4963
+ // src/llm.ts
4964
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"));
4965
+ var import_groq_sdk = __toESM(require("groq-sdk"));
4966
+ var import_generative_ai = require("@google/generative-ai");
4967
+ var import_openai = __toESM(require("openai"));
4968
+ var import_jsonrepair = require("jsonrepair");
4969
+
4970
+ // src/utils/llm-usage-logger.ts
4971
+ var import_fs4 = __toESM(require("fs"));
4972
+ var import_path3 = __toESM(require("path"));
4973
+ var PRICING = {
4974
+ // Anthropic (December 2025)
4975
+ "claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4976
+ "claude-opus-4-5-20251101": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
4977
+ "claude-sonnet-4-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4978
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4979
+ "claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4980
+ "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4981
+ "claude-3-5-sonnet-20241022": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4982
+ "claude-3-5-haiku-20241022": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
4983
+ "claude-3-opus-20240229": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
4984
+ "claude-3-sonnet-20240229": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
4985
+ "claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
4986
+ // OpenAI (December 2025)
4987
+ "gpt-5": { input: 1.25, output: 10 },
4988
+ "gpt-5-mini": { input: 0.25, output: 2 },
4989
+ "gpt-4o": { input: 5, output: 15 },
4990
+ // Updated pricing as of late 2025
4991
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
4992
+ "gpt-4-turbo": { input: 10, output: 30 },
4993
+ "gpt-4": { input: 30, output: 60 },
4994
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
4995
+ // Google Gemini (January 2026)
4996
+ "gemini-3-pro-preview": { input: 2, output: 12 },
4997
+ // New Gemini 3
4998
+ "gemini-3-flash-preview": { input: 0.5, output: 3 },
4999
+ // For prompts ≤200K tokens, 2x for >200K
5000
+ "gemini-2.5-flash": { input: 0.3, output: 2.5 },
5001
+ // Paid tier: $0.30 input (text/image/video), $2.50 output (includes thinking)
5002
+ "gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
5003
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
5004
+ "gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
5005
+ "gemini-1.5-pro": { input: 1.25, output: 5 },
5006
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
5007
+ // Groq (December 2025)
5008
+ "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
5009
+ "llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
5010
+ "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
5011
+ "llama-4-scout-17b-16e": { input: 0.11, output: 0.34 },
5012
+ "llama-4-maverick-17b-128e": { input: 0.2, output: 0.6 },
5013
+ "mixtral-8x7b-32768": { input: 0.27, output: 0.27 },
5014
+ "qwen3-32b": { input: 0.29, output: 0.59 }
5015
+ };
5016
+ var DEFAULT_PRICING = { input: 3, output: 15 };
5017
+ var LLMUsageLogger = class {
5018
+ constructor() {
5019
+ this.logStream = null;
5020
+ this.sessionStats = {
5021
+ totalCalls: 0,
5022
+ totalInputTokens: 0,
5023
+ totalOutputTokens: 0,
5024
+ totalCacheReadTokens: 0,
5025
+ totalCacheWriteTokens: 0,
5026
+ totalCostUSD: 0,
5027
+ totalDurationMs: 0
5028
+ };
5029
+ this.logPath = process.env.LLM_USAGE_LOG_PATH || import_path3.default.join(process.cwd(), "llm-usage-logs");
5030
+ this.enabled = process.env.LLM_USAGE_LOGGING !== "false";
5031
+ if (this.enabled) {
5032
+ this.initLogStream();
4665
5033
  }
4666
- if (errorStack) {
4667
- entry += `
4668
- Stack Trace:
4669
- ${errorStack}
4670
- `;
5034
+ }
5035
+ initLogStream() {
5036
+ try {
5037
+ const dir = import_path3.default.dirname(this.logPath);
5038
+ if (!import_fs4.default.existsSync(dir)) {
5039
+ import_fs4.default.mkdirSync(dir, { recursive: true });
5040
+ }
5041
+ this.logStream = import_fs4.default.createWriteStream(this.logPath, { flags: "a" });
5042
+ if (!import_fs4.default.existsSync(this.logPath) || import_fs4.default.statSync(this.logPath).size === 0) {
5043
+ this.writeHeader();
5044
+ }
5045
+ } catch (error) {
5046
+ console.error("[LLM-Usage-Logger] Failed to initialize log stream:", error);
5047
+ this.enabled = false;
4671
5048
  }
4672
- entry += `--------------------------------------------------------------------------------
5049
+ }
5050
+ writeHeader() {
5051
+ const header = `
5052
+ ================================================================================
5053
+ LLM USAGE LOG - Session Started: ${(/* @__PURE__ */ new Date()).toISOString()}
5054
+ ================================================================================
5055
+ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
5056
+ Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
5057
+ Cost: $X.XXXXXX | Time: Xms
5058
+ ================================================================================
4673
5059
 
4674
5060
  `;
4675
- this.write(entry);
4676
- console.error(`[UserPromptError] ${context}: ${errorMessage}`);
5061
+ this.logStream?.write(header);
4677
5062
  }
4678
5063
  /**
4679
- * Log a SQL query error with the full query
5064
+ * Calculate cost based on token usage and model
4680
5065
  */
4681
- logSqlError(query, error, params) {
4682
- if (!this.enabled) return;
4683
- this.hasErrors = true;
4684
- const errorMessage = error instanceof Error ? error.message : error;
4685
- const entry = `
4686
- --------------------------------------------------------------------------------
4687
- [${(/* @__PURE__ */ new Date()).toISOString()}] SQL QUERY ERROR
4688
- --------------------------------------------------------------------------------
4689
- Error: ${errorMessage}
4690
-
4691
- Query (${query.length} chars):
4692
- --------------------------------------------------------------------------------
4693
- ${query}
4694
- --------------------------------------------------------------------------------
4695
- ${params ? `
4696
- Parameters: ${JSON.stringify(params)}` : ""}
4697
-
4698
- `;
4699
- this.write(entry);
4700
- console.error(`[UserPromptError] SQL Error: ${errorMessage}`);
5066
+ calculateCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
5067
+ let pricing = PRICING[model];
5068
+ if (!pricing) {
5069
+ const modelLower = model.toLowerCase();
5070
+ for (const [key, value] of Object.entries(PRICING)) {
5071
+ if (modelLower.includes(key.toLowerCase()) || key.toLowerCase().includes(modelLower)) {
5072
+ pricing = value;
5073
+ break;
5074
+ }
5075
+ }
5076
+ }
5077
+ pricing = pricing || DEFAULT_PRICING;
5078
+ const inputCost = inputTokens / 1e6 * pricing.input;
5079
+ const outputCost = outputTokens / 1e6 * pricing.output;
5080
+ const cacheReadCost = cacheReadTokens / 1e6 * (pricing.cacheRead || pricing.input * 0.1);
5081
+ const cacheWriteCost = cacheWriteTokens / 1e6 * (pricing.cacheWrite || pricing.input * 1.25);
5082
+ return inputCost + outputCost + cacheReadCost + cacheWriteCost;
4701
5083
  }
4702
5084
  /**
4703
- * Log an LLM API error
5085
+ * Log an LLM API call
4704
5086
  */
4705
- logLlmError(provider, model, method, error, requestData) {
5087
+ log(entry) {
4706
5088
  if (!this.enabled) return;
4707
- this.hasErrors = true;
4708
- const errorMessage = error instanceof Error ? error.message : error;
4709
- const errorStack = error instanceof Error ? error.stack : void 0;
4710
- let entry = `
4711
- --------------------------------------------------------------------------------
4712
- [${(/* @__PURE__ */ new Date()).toISOString()}] LLM API ERROR
4713
- --------------------------------------------------------------------------------
4714
- Provider: ${provider}
4715
- Model: ${model}
4716
- Method: ${method}
4717
- Error: ${errorMessage}
4718
- `;
4719
- if (requestData) {
4720
- const dataStr = JSON.stringify(requestData, null, 2);
4721
- const truncated = dataStr.length > 5e3 ? dataStr.substring(0, 5e3) + "\n... [truncated]" : dataStr;
4722
- entry += `
4723
- Request Data:
4724
- ${truncated}
4725
- `;
4726
- }
4727
- if (errorStack) {
4728
- entry += `
4729
- Stack Trace:
4730
- ${errorStack}
4731
- `;
5089
+ this.sessionStats.totalCalls++;
5090
+ this.sessionStats.totalInputTokens += entry.inputTokens;
5091
+ this.sessionStats.totalOutputTokens += entry.outputTokens;
5092
+ this.sessionStats.totalCacheReadTokens += entry.cacheReadTokens || 0;
5093
+ this.sessionStats.totalCacheWriteTokens += entry.cacheWriteTokens || 0;
5094
+ this.sessionStats.totalCostUSD += entry.costUSD;
5095
+ this.sessionStats.totalDurationMs += entry.durationMs;
5096
+ const cacheInfo = entry.cacheReadTokens || entry.cacheWriteTokens ? ` CACHE_R=${entry.cacheReadTokens || 0} CACHE_W=${entry.cacheWriteTokens || 0}` : "";
5097
+ const toolInfo = entry.toolCalls ? ` | Tools: ${entry.toolCalls}` : "";
5098
+ const errorInfo = entry.error ? ` | ERROR: ${entry.error}` : "";
5099
+ const status = entry.success ? "\u2713" : "\u2717";
5100
+ let cacheStatus = "";
5101
+ if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
5102
+ const savedCost = entry.cacheReadTokens / 1e6 * 2.7;
5103
+ cacheStatus = ` \u26A1 CACHE HIT! Saved ~$${savedCost.toFixed(4)}`;
5104
+ } else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
5105
+ cacheStatus = " \u{1F4DD} Cache created (next request will be cheaper)";
4732
5106
  }
4733
- entry += `--------------------------------------------------------------------------------
4734
-
5107
+ const logLine = `[${entry.timestamp}] [${entry.requestId}] ${status} ${entry.provider}/${entry.model} [${entry.method}]
5108
+ Tokens: IN=${entry.inputTokens} OUT=${entry.outputTokens}${cacheInfo} TOTAL=${entry.totalTokens}
5109
+ Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
4735
5110
  `;
4736
- this.write(entry);
4737
- console.error(`[UserPromptError] LLM Error (${provider}/${model}): ${errorMessage}`);
5111
+ this.logStream?.write(logLine);
4738
5112
  }
4739
5113
  /**
4740
- * Log tool execution error
5114
+ * Log session summary (call at end of request)
4741
5115
  */
4742
- logToolError(toolName, toolInput, error) {
4743
- if (!this.enabled) return;
4744
- this.hasErrors = true;
4745
- const errorMessage = error instanceof Error ? error.message : error;
4746
- const errorStack = error instanceof Error ? error.stack : void 0;
4747
- const entry = `
5116
+ logSessionSummary(requestContext) {
5117
+ if (!this.enabled || this.sessionStats.totalCalls === 0) return;
5118
+ const cacheReadSavings = this.sessionStats.totalCacheReadTokens / 1e6 * 2.7;
5119
+ const hasCaching = this.sessionStats.totalCacheReadTokens > 0 || this.sessionStats.totalCacheWriteTokens > 0;
5120
+ let cacheSection = "";
5121
+ if (hasCaching) {
5122
+ cacheSection = `
5123
+ Cache Statistics:
5124
+ Cache Read Tokens: ${this.sessionStats.totalCacheReadTokens.toLocaleString()}${this.sessionStats.totalCacheReadTokens > 0 ? " \u26A1" : ""}
5125
+ Cache Write Tokens: ${this.sessionStats.totalCacheWriteTokens.toLocaleString()}${this.sessionStats.totalCacheWriteTokens > 0 ? " \u{1F4DD}" : ""}
5126
+ Estimated Savings: $${cacheReadSavings.toFixed(4)}`;
5127
+ }
5128
+ const summary = `
4748
5129
  --------------------------------------------------------------------------------
4749
- [${(/* @__PURE__ */ new Date()).toISOString()}] TOOL EXECUTION ERROR
5130
+ SESSION SUMMARY${requestContext ? ` (${requestContext})` : ""}
4750
5131
  --------------------------------------------------------------------------------
4751
- Tool: ${toolName}
4752
- Error: ${errorMessage}
4753
-
4754
- Tool Input:
4755
- ${JSON.stringify(toolInput, null, 2)}
4756
- ${errorStack ? `
4757
- Stack Trace:
4758
- ${errorStack}` : ""}
5132
+ Total LLM Calls: ${this.sessionStats.totalCalls}
5133
+ Total Input Tokens: ${this.sessionStats.totalInputTokens.toLocaleString()}
5134
+ Total Output Tokens: ${this.sessionStats.totalOutputTokens.toLocaleString()}
5135
+ Total Tokens: ${(this.sessionStats.totalInputTokens + this.sessionStats.totalOutputTokens).toLocaleString()}
5136
+ Total Cost: $${this.sessionStats.totalCostUSD.toFixed(6)}
5137
+ Total Time: ${this.sessionStats.totalDurationMs}ms (${(this.sessionStats.totalDurationMs / 1e3).toFixed(2)}s)
5138
+ Avg Cost/Call: $${(this.sessionStats.totalCostUSD / this.sessionStats.totalCalls).toFixed(6)}
5139
+ Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStats.totalCalls)}ms${cacheSection}
4759
5140
  --------------------------------------------------------------------------------
4760
5141
 
4761
5142
  `;
4762
- this.write(entry);
4763
- console.error(`[UserPromptError] Tool Error (${toolName}): ${errorMessage}`);
5143
+ this.logStream?.write(summary);
4764
5144
  }
4765
5145
  /**
4766
- * Write final summary if there were errors
5146
+ * Reset session stats (call at start of new user request)
4767
5147
  */
4768
- writeSummary() {
4769
- if (!this.enabled || !this.hasErrors) return;
4770
- const summary = `
5148
+ resetSession() {
5149
+ this.sessionStats = {
5150
+ totalCalls: 0,
5151
+ totalInputTokens: 0,
5152
+ totalOutputTokens: 0,
5153
+ totalCacheReadTokens: 0,
5154
+ totalCacheWriteTokens: 0,
5155
+ totalCostUSD: 0,
5156
+ totalDurationMs: 0
5157
+ };
5158
+ }
5159
+ /**
5160
+ * Reset the log file for a new request (clears previous logs)
5161
+ * Call this at the start of each USER_PROMPT_REQ
5162
+ */
5163
+ resetLogFile(requestContext) {
5164
+ if (!this.enabled) return;
5165
+ try {
5166
+ if (this.logStream) {
5167
+ this.logStream.end();
5168
+ this.logStream = null;
5169
+ }
5170
+ this.logStream = import_fs4.default.createWriteStream(this.logPath, { flags: "w" });
5171
+ const header = `
4771
5172
  ================================================================================
4772
- REQUEST COMPLETED WITH ERRORS
4773
- Time: ${(/* @__PURE__ */ new Date()).toISOString()}
5173
+ LLM USAGE LOG - Request Started: ${(/* @__PURE__ */ new Date()).toISOString()}
5174
+ ${requestContext ? `Context: ${requestContext}` : ""}
5175
+ ================================================================================
5176
+ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
5177
+ Tokens: IN=input OUT=output CACHE_R=cache_read CACHE_W=cache_write TOTAL=total
5178
+ Cost: $X.XXXXXX | Time: Xms
4774
5179
  ================================================================================
5180
+
4775
5181
  `;
4776
- this.write(summary);
5182
+ this.logStream.write(header);
5183
+ this.resetSession();
5184
+ } catch (error) {
5185
+ console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
5186
+ }
4777
5187
  }
4778
5188
  /**
4779
- * Check if any errors were logged
5189
+ * Get current session stats
4780
5190
  */
4781
- hadErrors() {
4782
- return this.hasErrors;
5191
+ getSessionStats() {
5192
+ return { ...this.sessionStats };
4783
5193
  }
4784
- write(content) {
4785
- if (this.logStream) {
4786
- this.logStream.write(content);
4787
- }
5194
+ /**
5195
+ * Generate a unique request ID
5196
+ */
5197
+ generateRequestId() {
5198
+ return `req-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
4788
5199
  }
4789
5200
  };
4790
- var userPromptErrorLogger = new UserPromptErrorLogger();
5201
+ var llmUsageLogger = new LLMUsageLogger();
4791
5202
 
4792
5203
  // src/llm.ts
4793
5204
  var LLM = class {
@@ -5222,7 +5633,7 @@ var LLM = class {
5222
5633
  role: "user",
5223
5634
  content: []
5224
5635
  };
5225
- for (const toolUse of toolUses) {
5636
+ const toolResultEntries = await Promise.all(toolUses.map(async (toolUse) => {
5226
5637
  try {
5227
5638
  const result = await toolHandler(toolUse.name, toolUse.input);
5228
5639
  let resultContent = typeof result === "string" ? result : JSON.stringify(result);
@@ -5230,20 +5641,21 @@ var LLM = class {
5230
5641
  if (resultContent.length > MAX_RESULT_LENGTH) {
5231
5642
  resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
5232
5643
  }
5233
- toolResults.content.push({
5644
+ return {
5234
5645
  type: "tool_result",
5235
5646
  tool_use_id: toolUse.id,
5236
5647
  content: resultContent
5237
- });
5648
+ };
5238
5649
  } catch (error) {
5239
- toolResults.content.push({
5650
+ return {
5240
5651
  type: "tool_result",
5241
5652
  tool_use_id: toolUse.id,
5242
5653
  content: error instanceof Error ? error.message : String(error),
5243
5654
  is_error: true
5244
- });
5655
+ };
5245
5656
  }
5246
- }
5657
+ }));
5658
+ toolResultEntries.forEach((entry) => toolResults.content.push(entry));
5247
5659
  conversationMessages.push(toolResults);
5248
5660
  } else {
5249
5661
  break;
@@ -5741,7 +6153,7 @@ var LLM = class {
5741
6153
  break;
5742
6154
  }
5743
6155
  const functionResponses = [];
5744
- for (const fc of functionCalls) {
6156
+ const responses = await Promise.all(functionCalls.map(async (fc) => {
5745
6157
  try {
5746
6158
  const result2 = await toolHandler(fc.name, fc.args);
5747
6159
  let resultContent = typeof result2 === "string" ? result2 : JSON.stringify(result2);
@@ -5749,17 +6161,18 @@ var LLM = class {
5749
6161
  if (resultContent.length > MAX_RESULT_LENGTH) {
5750
6162
  resultContent = resultContent.substring(0, MAX_RESULT_LENGTH) + "\n\n... [Result truncated - showing first 50000 characters of " + resultContent.length + " total]";
5751
6163
  }
5752
- functionResponses.push({
6164
+ return {
5753
6165
  name: fc.name,
5754
6166
  response: { result: resultContent }
5755
- });
6167
+ };
5756
6168
  } catch (error) {
5757
- functionResponses.push({
6169
+ return {
5758
6170
  name: fc.name,
5759
6171
  response: { error: error instanceof Error ? error.message : String(error) }
5760
- });
6172
+ };
5761
6173
  }
5762
- }
6174
+ }));
6175
+ functionResponses.push(...responses);
5763
6176
  const functionResponseParts = functionResponses.map((fr) => ({
5764
6177
  functionResponse: {
5765
6178
  name: fr.name,
@@ -6015,7 +6428,7 @@ var LLM = class {
6015
6428
  }
6016
6429
  }))
6017
6430
  });
6018
- for (const tc of toolCalls) {
6431
+ const toolCallResults = await Promise.all(toolCalls.map(async (tc) => {
6019
6432
  let result;
6020
6433
  try {
6021
6434
  const args = JSON.parse(tc.arguments);
@@ -6027,13 +6440,10 @@ var LLM = class {
6027
6440
  }
6028
6441
  } catch (error) {
6029
6442
  result = JSON.stringify({ error: error instanceof Error ? error.message : String(error) });
6030
- }
6031
- conversationMessages.push({
6032
- role: "tool",
6033
- tool_call_id: tc.id,
6034
- content: result
6035
- });
6036
- }
6443
+ }
6444
+ return { role: "tool", tool_call_id: tc.id, content: result };
6445
+ }));
6446
+ toolCallResults.forEach((r) => conversationMessages.push(r));
6037
6447
  }
6038
6448
  if (iterations >= maxIterations) {
6039
6449
  throw new Error(`Max iterations (${maxIterations}) reached in tool calling loop`);
@@ -6128,583 +6538,851 @@ function getCurrentDateTimeForPrompt() {
6128
6538
  });
6129
6539
  }
6130
6540
 
6131
- // src/userResponse/knowledge-base.ts
6132
- var getKnowledgeBase = async ({
6133
- prompt,
6134
- collections,
6135
- topK = 1
6136
- }) => {
6137
- try {
6138
- if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
6139
- logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
6140
- return "";
6141
- }
6142
- const result = await collections["knowledge-base"]["query"]({
6143
- prompt,
6144
- topK
6541
+ // src/userResponse/agents/agent-prompt-builder.ts
6542
+ function buildSourceSummaries(externalTools) {
6543
+ return externalTools.map((tool) => {
6544
+ const description = tool.description || "";
6545
+ const type = extractSourceType(tool.id);
6546
+ const entityDetails = extractEntityDetails(description);
6547
+ const dataContext = extractDataContext(description, tool.name, type);
6548
+ return {
6549
+ id: extractSourceId(tool.id),
6550
+ name: tool.name,
6551
+ type,
6552
+ description: dataContext,
6553
+ entityDetails,
6554
+ toolId: tool.id
6555
+ };
6556
+ });
6557
+ }
6558
+ function formatSummariesForPrompt(summaries) {
6559
+ return summaries.map((s, idx) => {
6560
+ const totalRows = s.entityDetails.reduce((sum, e) => sum + (e.rowCount || 0), 0);
6561
+ const rowInfo = totalRows > 0 ? ` (~${totalRows.toLocaleString()} total rows)` : "";
6562
+ let entitiesBlock = "";
6563
+ if (s.entityDetails.length > 0) {
6564
+ entitiesBlock = "\n" + s.entityDetails.map((e) => {
6565
+ const rows = e.rowCount ? ` (${e.rowCount.toLocaleString()} rows)` : "";
6566
+ const cols = e.columns.length > 0 ? `: ${e.columns.join(", ")}` : "";
6567
+ return ` - ${e.name}${rows}${cols}`;
6568
+ }).join("\n");
6569
+ }
6570
+ return `${idx + 1}. **${s.name}** (tool: ${s.toolId}, type: ${s.type})${rowInfo}
6571
+ ${s.description}${entitiesBlock}`;
6572
+ }).join("\n\n");
6573
+ }
6574
+ function extractEntityDetails(description) {
6575
+ const details = [];
6576
+ const bulletSections = description.split(/(?=•\s)/);
6577
+ for (const section of bulletSections) {
6578
+ if (!section.trim().startsWith("\u2022")) continue;
6579
+ const headerMatch = section.match(/•\s+(?:\w+\.)?(.+?)\s*\[~?([\d,]+)\s*rows?\]/);
6580
+ if (!headerMatch) continue;
6581
+ const name = headerMatch[1].trim();
6582
+ const rowCount = parseInt(headerMatch[2].replace(/,/g, ""), 10);
6583
+ const columns = extractColumnNames(section);
6584
+ details.push({
6585
+ name,
6586
+ rowCount: rowCount > 0 ? rowCount : void 0,
6587
+ columns
6145
6588
  });
6146
- if (!result || !result.content) {
6147
- logger.warn("[KnowledgeBase] No knowledge base results returned");
6148
- return "";
6149
- }
6150
- logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
6151
- if (result.metadata?.sources && result.metadata.sources.length > 0) {
6152
- logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
6153
- }
6154
- return result.content;
6155
- } catch (error) {
6156
- const errorMsg = error instanceof Error ? error.message : String(error);
6157
- logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
6158
- return "";
6159
6589
  }
6160
- };
6161
- var getGlobalKnowledgeBase = async ({
6162
- collections,
6163
- limit = 100
6164
- }) => {
6165
- try {
6166
- if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
6167
- logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
6168
- return "";
6169
- }
6170
- const result = await collections["knowledge-base"]["getGlobal"]({ limit });
6171
- if (!result || !result.content) {
6172
- logger.warn("[KnowledgeBase] No global knowledge base nodes found");
6173
- return "";
6590
+ if (details.length === 0) {
6591
+ const endpointPattern = /Endpoint:\s*(\S+)/g;
6592
+ let match;
6593
+ while ((match = endpointPattern.exec(description)) !== null) {
6594
+ const columns = extractColumnNames(description);
6595
+ details.push({
6596
+ name: match[1].trim(),
6597
+ columns
6598
+ });
6174
6599
  }
6175
- logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
6176
- return result.content;
6177
- } catch (error) {
6178
- const errorMsg = error instanceof Error ? error.message : String(error);
6179
- logger.warn(`[KnowledgeBase] Error fetching global knowledge base: ${errorMsg}`);
6180
- return "";
6181
6600
  }
6182
- };
6183
- var getUserKnowledgeBase = async ({
6184
- collections,
6185
- userId,
6186
- limit = 100
6187
- }) => {
6188
- try {
6189
- if (!userId) {
6190
- logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
6191
- return "";
6601
+ return details;
6602
+ }
6603
+ function extractColumnNames(block) {
6604
+ const columns = [];
6605
+ const seen = /* @__PURE__ */ new Set();
6606
+ const colPattern = /(?:^|,)\s*([^,()\[\]{}\n]+?)\s*\((?:NUMBER|TEXT|BOOLEAN|TIMESTAMP|INTEGER|FLOAT|DECIMAL|DATE|BIGINT|VARCHAR|CHAR|DOUBLE|REAL|ARRAY|OBJECT)\)/gi;
6607
+ let match;
6608
+ while ((match = colPattern.exec(block)) !== null) {
6609
+ const col = match[1].trim();
6610
+ if (col && col.length < 60 && !col.includes("\u2022") && !seen.has(col)) {
6611
+ columns.push(col);
6612
+ seen.add(col);
6613
+ }
6614
+ }
6615
+ return columns;
6616
+ }
6617
+ function extractDataContext(description, name, type) {
6618
+ const contextMatch = description.match(/Data Context:\s*(.+?)(?:\n|$)/);
6619
+ if (contextMatch) return contextMatch[1].trim();
6620
+ const excelMatch = description.match(/Excel file:\s*(.+?)\)/);
6621
+ if (excelMatch) return `Excel file: ${excelMatch[1].trim()})`;
6622
+ const csvMatch = description.match(/CSV file:\s*(.+?)\)/);
6623
+ if (csvMatch) return `CSV file: ${csvMatch[1].trim()})`;
6624
+ const useForMatch = description.match(/Use this source for[^:]*:\s*(.+?)(?:\n|$)/);
6625
+ if (useForMatch) return useForMatch[1].trim();
6626
+ const typeLabels = {
6627
+ postgres: "PostgreSQL database",
6628
+ mysql: "MySQL database",
6629
+ mssql: "SQL Server database",
6630
+ excel: "Excel spreadsheet",
6631
+ csv: "CSV file",
6632
+ rest_api: "REST API",
6633
+ graphql: "GraphQL API"
6634
+ };
6635
+ return `${typeLabels[type] || type}: ${name}`;
6636
+ }
6637
+ function extractSourceType(toolId) {
6638
+ const match = toolId.match(/^(\w+)-[a-f0-9]+_/);
6639
+ if (match) return match[1];
6640
+ const types = ["postgres", "mysql", "mssql", "excel", "csv", "rest_api", "graphql"];
6641
+ for (const type of types) {
6642
+ if (toolId.toLowerCase().includes(type)) return type;
6643
+ }
6644
+ return "unknown";
6645
+ }
6646
+ function extractSourceId(toolId) {
6647
+ return toolId.replace(/_(query|read|call)$/, "");
6648
+ }
6649
+
6650
+ // src/userResponse/agents/source-agent.ts
6651
+ var SourceAgent = class {
6652
+ constructor(tool, config, streamBuffer) {
6653
+ this.attempts = 0;
6654
+ this.tool = tool;
6655
+ this.config = config;
6656
+ this.streamBuffer = streamBuffer;
6657
+ }
6658
+ /**
6659
+ * Execute a query against this source based on the intent from the main agent.
6660
+ *
6661
+ * Flow:
6662
+ * 1. Build prompt with full schema (from tool.description) + intent
6663
+ * 2. Source agent's OWN LLM generates query via tool calling
6664
+ * 3. Execute with retry — all handled internally
6665
+ * 4. Return SourceAgentResult with data + isLimited metadata
6666
+ */
6667
+ async execute(input) {
6668
+ const startTime = Date.now();
6669
+ const { intent, aggregation = "raw" } = input;
6670
+ logger.info(`[SourceAgent:${this.tool.name}] Starting | intent: "${intent}" | aggregation: ${aggregation}`);
6671
+ if (this.streamBuffer.hasCallback()) {
6672
+ this.streamBuffer.write(`
6673
+
6674
+ \u{1F517} **Querying ${this.tool.name}...**
6675
+
6676
+ `);
6677
+ await streamDelay();
6192
6678
  }
6193
- if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
6194
- logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
6195
- return "";
6679
+ try {
6680
+ const prompts = await this.buildPrompt(intent, aggregation);
6681
+ const llmTool = this.buildLLMToolDefinition();
6682
+ let executedTool = null;
6683
+ let resultData = [];
6684
+ let queryExecuted;
6685
+ let totalRowsMatched = 0;
6686
+ const toolHandler = async (_toolName, toolInput) => {
6687
+ this.attempts++;
6688
+ if (this.attempts > this.config.maxRetries) {
6689
+ throw new Error(`Max retry attempts (${this.config.maxRetries}) reached for ${this.tool.name}`);
6690
+ }
6691
+ if (this.attempts > 1 && this.streamBuffer.hasCallback()) {
6692
+ this.streamBuffer.write(`
6693
+
6694
+ \u{1F504} **Retrying ${this.tool.name} (attempt ${this.attempts}/${this.config.maxRetries})...**
6695
+
6696
+ `);
6697
+ await streamDelay();
6698
+ }
6699
+ const cappedInput = { ...toolInput };
6700
+ if (cappedInput.limit === void 0 || cappedInput.limit > this.config.maxRowsPerSource) {
6701
+ cappedInput.limit = this.config.maxRowsPerSource;
6702
+ }
6703
+ queryExecuted = cappedInput.sql || cappedInput.query || JSON.stringify(cappedInput);
6704
+ try {
6705
+ const result = await withProgressHeartbeat(
6706
+ () => this.tool.fn(cappedInput),
6707
+ `Running ${this.tool.name}`,
6708
+ this.streamBuffer
6709
+ );
6710
+ if (result && result.error) {
6711
+ const errorMsg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
6712
+ logger.warn(`[SourceAgent:${this.tool.name}] Tool returned error (attempt ${this.attempts}/${this.config.maxRetries}): ${errorMsg}`);
6713
+ return `\u274C ERROR: ${errorMsg}
6714
+
6715
+ Analyze the error and try again with a corrected query.`;
6716
+ }
6717
+ resultData = result.data || [];
6718
+ totalRowsMatched = result.metadata?.totalCount || result.count || resultData.length;
6719
+ const formattedResult = formatToolResultForLLM(result, {
6720
+ toolName: this.tool.name,
6721
+ maxRows: 5,
6722
+ maxCharsPerField: 200
6723
+ });
6724
+ executedTool = {
6725
+ id: this.tool.id,
6726
+ name: this.tool.name,
6727
+ params: cappedInput,
6728
+ result: {
6729
+ _totalRecords: totalRowsMatched,
6730
+ _recordsShown: resultData.length,
6731
+ _metadata: result.metadata,
6732
+ _sampleData: resultData.slice(0, 3)
6733
+ },
6734
+ outputSchema: this.tool.outputSchema
6735
+ };
6736
+ const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
6737
+ return `\u2705 Query executed successfully. ${resultData.length} rows returned (${totalRowsMatched} total matched). Data is ready \u2014 do NOT call the tool again.
6738
+
6739
+ ${formatted}`;
6740
+ } catch (execError) {
6741
+ const errorMsg = execError instanceof Error ? execError.message : typeof execError === "object" && execError !== null ? execError.message || execError.error || JSON.stringify(execError) : String(execError);
6742
+ logger.warn(`[SourceAgent:${this.tool.name}] Tool execution failed (attempt ${this.attempts}/${this.config.maxRetries}): ${errorMsg}`);
6743
+ return `\u274C ERROR: ${errorMsg}
6744
+
6745
+ Analyze the error and try again with a corrected query.`;
6746
+ }
6747
+ };
6748
+ await LLM.streamWithTools(
6749
+ { sys: prompts.system, user: prompts.user },
6750
+ [llmTool],
6751
+ toolHandler,
6752
+ {
6753
+ model: this.config.sourceAgentModel || void 0,
6754
+ maxTokens: 2048,
6755
+ temperature: 0,
6756
+ apiKey: this.config.apiKey
6757
+ },
6758
+ this.config.maxRetries
6759
+ );
6760
+ const executionTimeMs = Date.now() - startTime;
6761
+ if (!executedTool) {
6762
+ logger.warn(`[SourceAgent:${this.tool.name}] LLM did not call the tool`);
6763
+ return {
6764
+ sourceId: this.tool.id,
6765
+ sourceName: this.tool.name,
6766
+ success: false,
6767
+ data: [],
6768
+ metadata: {
6769
+ totalRowsMatched: 0,
6770
+ rowsReturned: 0,
6771
+ isLimited: false,
6772
+ executionTimeMs
6773
+ },
6774
+ executedTool: this.buildEmptyExecutedTool(),
6775
+ error: "Source agent did not execute any query"
6776
+ };
6777
+ }
6778
+ logger.info(`[SourceAgent:${this.tool.name}] Success | ${resultData.length} rows in ${executionTimeMs}ms`);
6779
+ return {
6780
+ sourceId: this.tool.id,
6781
+ sourceName: this.tool.name,
6782
+ success: true,
6783
+ data: resultData,
6784
+ metadata: {
6785
+ totalRowsMatched,
6786
+ rowsReturned: resultData.length,
6787
+ isLimited: resultData.length < totalRowsMatched,
6788
+ queryExecuted,
6789
+ executionTimeMs
6790
+ },
6791
+ executedTool
6792
+ };
6793
+ } catch (error) {
6794
+ const executionTimeMs = Date.now() - startTime;
6795
+ const errorMsg = error instanceof Error ? error.message : String(error);
6796
+ logger.error(`[SourceAgent:${this.tool.name}] Failed: ${errorMsg}`);
6797
+ if (this.streamBuffer.hasCallback()) {
6798
+ this.streamBuffer.write(`
6799
+
6800
+ \u274C **${this.tool.name} failed:** ${errorMsg}
6801
+
6802
+ `);
6803
+ }
6804
+ return {
6805
+ sourceId: this.tool.id,
6806
+ sourceName: this.tool.name,
6807
+ success: false,
6808
+ data: [],
6809
+ metadata: {
6810
+ totalRowsMatched: 0,
6811
+ rowsReturned: 0,
6812
+ isLimited: false,
6813
+ queryExecuted: void 0,
6814
+ executionTimeMs
6815
+ },
6816
+ executedTool: this.buildEmptyExecutedTool(),
6817
+ error: errorMsg
6818
+ };
6196
6819
  }
6197
- const result = await collections["knowledge-base"]["getByUser"]({
6198
- userId: Number(userId),
6199
- limit
6820
+ }
6821
+ // ============================================
6822
+ // Private Helpers
6823
+ // ============================================
6824
+ /**
6825
+ * Build prompt using the prompt loader (file system → hardcoded fallback in prompts.ts).
6826
+ */
6827
+ async buildPrompt(intent, aggregation) {
6828
+ const sourceName = this.tool.name;
6829
+ const sourceType = this.extractSourceType();
6830
+ const fullSchema = this.tool.description || "No schema available";
6831
+ const prompts = await promptLoader.loadPrompts("agent-source-query", {
6832
+ SOURCE_NAME: sourceName,
6833
+ SOURCE_TYPE: sourceType,
6834
+ FULL_SCHEMA: fullSchema,
6835
+ MAX_ROWS: String(this.config.maxRowsPerSource),
6836
+ AGGREGATION_MODE: aggregation,
6837
+ CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
6838
+ INTENT: intent
6200
6839
  });
6201
- if (!result || !result.content) {
6202
- logger.info(`[KnowledgeBase] No user knowledge base nodes found for userId: ${userId}`);
6203
- return "";
6204
- }
6205
- logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} user knowledge base nodes for userId: ${userId}`);
6206
- return result.content;
6207
- } catch (error) {
6208
- const errorMsg = error instanceof Error ? error.message : String(error);
6209
- logger.warn(`[KnowledgeBase] Error fetching user knowledge base: ${errorMsg}`);
6210
- return "";
6840
+ return { system: prompts.system, user: prompts.user };
6841
+ }
6842
+ /**
6843
+ * Build the LLM tool definition from the external tool.
6844
+ * Parses param descriptions like "string - Sheet name" or "array (optional) - Columns"
6845
+ * to extract the correct JSON schema type and required/optional status.
6846
+ */
6847
+ buildLLMToolDefinition() {
6848
+ const properties = {};
6849
+ const required = [];
6850
+ const toolParams = this.tool.params || {};
6851
+ Object.entries(toolParams).forEach(([key, typeOrValue]) => {
6852
+ const valueStr = String(typeOrValue).toLowerCase();
6853
+ let schemaType = "string";
6854
+ const typeMatch = valueStr.match(/^(string|number|integer|boolean|array|object)\b/);
6855
+ if (typeMatch) {
6856
+ schemaType = typeMatch[1];
6857
+ } else if (typeof typeOrValue === "number") {
6858
+ schemaType = Number.isInteger(typeOrValue) ? "integer" : "number";
6859
+ } else if (typeof typeOrValue === "boolean") {
6860
+ schemaType = "boolean";
6861
+ }
6862
+ const isOptional = valueStr.includes("(optional)") || valueStr.includes("optional");
6863
+ const description = typeof typeOrValue === "string" ? typeOrValue : `Parameter: ${key}`;
6864
+ if (schemaType === "array") {
6865
+ properties[key] = { type: "array", items: { type: "string" }, description };
6866
+ } else if (schemaType === "object") {
6867
+ properties[key] = { type: "object", description };
6868
+ } else {
6869
+ properties[key] = { type: schemaType, description };
6870
+ }
6871
+ if (!isOptional) {
6872
+ required.push(key);
6873
+ }
6874
+ });
6875
+ return {
6876
+ name: this.tool.id,
6877
+ description: this.tool.description || `Query ${this.tool.name}`,
6878
+ input_schema: {
6879
+ type: "object",
6880
+ properties,
6881
+ required: required.length > 0 ? required : void 0
6882
+ }
6883
+ };
6884
+ }
6885
+ /**
6886
+ * Extract source type from tool ID.
6887
+ */
6888
+ extractSourceType() {
6889
+ const match = this.tool.id.match(/^(\w+)-[a-f0-9]+_/);
6890
+ return match ? match[1] : "unknown";
6891
+ }
6892
+ /**
6893
+ * Build an empty ExecutedToolInfo for error cases.
6894
+ */
6895
+ buildEmptyExecutedTool() {
6896
+ return {
6897
+ id: this.tool.id,
6898
+ name: this.tool.name,
6899
+ params: {},
6900
+ result: {
6901
+ _totalRecords: 0,
6902
+ _recordsShown: 0,
6903
+ _sampleData: []
6904
+ },
6905
+ outputSchema: this.tool.outputSchema
6906
+ };
6211
6907
  }
6212
6908
  };
6213
- var getAllKnowledgeBase = async ({
6214
- prompt,
6215
- collections,
6216
- userId,
6217
- topK = 3
6218
- }) => {
6219
- const [globalContext, userContext, queryContext] = await Promise.all([
6220
- getGlobalKnowledgeBase({ collections }),
6221
- getUserKnowledgeBase({ collections, userId }),
6222
- getKnowledgeBase({ prompt, collections, topK })
6223
- ]);
6224
- let combinedContext = "";
6225
- if (globalContext) {
6226
- combinedContext += "## Global Knowledge Base\n";
6227
- combinedContext += "The following information applies to all queries:\n\n";
6228
- combinedContext += globalContext + "\n\n";
6909
+
6910
+ // src/userResponse/agents/main-agent.ts
6911
+ var MainAgent = class {
6912
+ constructor(externalTools, config, streamBuffer) {
6913
+ this.externalTools = externalTools;
6914
+ this.config = config;
6915
+ this.streamBuffer = streamBuffer;
6229
6916
  }
6230
- if (userContext) {
6231
- combinedContext += "## User-Specific Knowledge Base\n";
6232
- combinedContext += "The following information is specific to this user:\n\n";
6233
- combinedContext += userContext + "\n\n";
6917
+ /**
6918
+ * Handle a user question using the multi-agent system.
6919
+ *
6920
+ * This is ONE LLM.streamWithTools() call. The LLM:
6921
+ * 1. Sees source summaries in system prompt (~100 tokens each)
6922
+ * 2. Decides which source(s) to query (routing)
6923
+ * 3. Calls source tools → SourceAgent runs independently → returns data
6924
+ * 4. Sees data, decides if it needs more → calls tools again if needed
6925
+ * 5. Generates final analysis text
6926
+ */
6927
+ async handleQuestion(userPrompt, apiKey, conversationHistory, streamCallback) {
6928
+ const startTime = Date.now();
6929
+ logger.info(`[MainAgent] Starting | prompt: "${userPrompt.substring(0, 50)}..."`);
6930
+ const summaries = buildSourceSummaries(this.externalTools);
6931
+ logger.info(`[MainAgent] ${summaries.length} source(s) available`);
6932
+ const systemPrompt = await this.buildSystemPrompt(summaries, conversationHistory);
6933
+ const tools = this.buildSourceToolDefinitions(summaries);
6934
+ const sourceResults = [];
6935
+ const executedTools = [];
6936
+ const toolHandler = async (toolName, toolInput) => {
6937
+ const externalTool = this.externalTools.find((t) => t.id === toolName);
6938
+ if (!externalTool) {
6939
+ logger.error(`[MainAgent] Unknown tool called: ${toolName}`);
6940
+ return `Error: Unknown data source "${toolName}"`;
6941
+ }
6942
+ const sourceInput = {
6943
+ intent: toolInput.intent || toolInput.query || JSON.stringify(toolInput),
6944
+ aggregation: toolInput.aggregation || "raw"
6945
+ };
6946
+ logger.info(`[MainAgent] Dispatching SourceAgent for "${externalTool.name}" | intent: "${sourceInput.intent}"`);
6947
+ const sourceAgent = new SourceAgent(externalTool, this.config, this.streamBuffer);
6948
+ const result = await sourceAgent.execute(sourceInput);
6949
+ sourceResults.push(result);
6950
+ if (result.success) {
6951
+ executedTools.push(result.executedTool);
6952
+ }
6953
+ return this.formatResultForMainAgent(result);
6954
+ };
6955
+ const text = await LLM.streamWithTools(
6956
+ {
6957
+ sys: systemPrompt,
6958
+ user: userPrompt
6959
+ },
6960
+ tools,
6961
+ toolHandler,
6962
+ {
6963
+ model: this.config.mainAgentModel || void 0,
6964
+ maxTokens: 4e3,
6965
+ temperature: 0,
6966
+ apiKey: apiKey || this.config.apiKey,
6967
+ partial: streamCallback
6968
+ },
6969
+ this.config.maxIterations
6970
+ );
6971
+ const totalTime = Date.now() - startTime;
6972
+ logger.info(`[MainAgent] Complete | ${sourceResults.length} source queries, ${executedTools.length} successful | ${totalTime}ms`);
6973
+ return {
6974
+ text,
6975
+ executedTools,
6976
+ sourceResults
6977
+ };
6234
6978
  }
6235
- if (queryContext) {
6236
- combinedContext += "## Relevant Knowledge Base (Query-Matched)\n";
6237
- combinedContext += "The following information is semantically relevant to the current query:\n\n";
6238
- combinedContext += queryContext + "\n\n";
6979
+ // ============================================
6980
+ // System Prompt
6981
+ // ============================================
6982
+ /**
6983
+ * Build the main agent's system prompt with source summaries.
6984
+ * Loads from prompt loader (file system → hardcoded fallback in prompts.ts).
6985
+ */
6986
+ async buildSystemPrompt(summaries, conversationHistory) {
6987
+ const summariesText = formatSummariesForPrompt(summaries);
6988
+ const prompts = await promptLoader.loadPrompts("agent-main", {
6989
+ SOURCE_SUMMARIES: summariesText,
6990
+ MAX_ROWS: String(this.config.maxRowsPerSource),
6991
+ CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
6992
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
6993
+ });
6994
+ return prompts.system;
6995
+ }
6996
+ // ============================================
6997
+ // Tool Definitions (summary-only, no full schema)
6998
+ // ============================================
6999
+ /**
7000
+ * Build tool definitions for the main agent — one per source.
7001
+ * Descriptions include entity names with column names for routing.
7002
+ * The full schema is inside the SourceAgent which runs independently.
7003
+ */
7004
+ buildSourceToolDefinitions(summaries) {
7005
+ return summaries.map((summary) => {
7006
+ const totalRows = summary.entityDetails.reduce((sum, e) => sum + (e.rowCount || 0), 0);
7007
+ const rowInfo = totalRows > 0 ? ` (~${totalRows.toLocaleString()} total rows)` : "";
7008
+ const entitiesList = summary.entityDetails.length > 0 ? summary.entityDetails.map((e) => {
7009
+ const cols = e.columns.length > 0 ? ` [${e.columns.join(", ")}]` : "";
7010
+ return `${e.name}${cols}`;
7011
+ }).join("; ") : "no entities listed";
7012
+ return {
7013
+ name: summary.toolId,
7014
+ description: `Query "${summary.name}" (${summary.type})${rowInfo}. ${summary.description}. Contains: ${entitiesList}.`,
7015
+ input_schema: {
7016
+ type: "object",
7017
+ properties: {
7018
+ intent: {
7019
+ type: "string",
7020
+ description: "Describe what data you need from this source in natural language. Be specific about fields, filters, aggregations, and limits."
7021
+ },
7022
+ aggregation: {
7023
+ type: "string",
7024
+ enum: ["raw", "pre-aggregate", "summary"],
7025
+ description: 'How to return data. "pre-aggregate": use GROUP BY/SUM/COUNT for totals. "summary": high-level metrics. "raw": individual records.'
7026
+ }
7027
+ },
7028
+ required: ["intent"]
7029
+ }
7030
+ };
7031
+ });
7032
+ }
7033
+ // ============================================
7034
+ // Format Result for Main Agent
7035
+ // ============================================
7036
+ /**
7037
+ * Format a source agent's result as a clean string for the main agent LLM.
7038
+ * Passes through the data as-is — no server-side aggregation.
7039
+ * If the main agent needs aggregates, it should request aggregation: "pre-aggregate"
7040
+ * from the source agent, which handles it at the query level (SQL GROUP BY, etc.).
7041
+ */
7042
+ formatResultForMainAgent(result) {
7043
+ if (!result.success) {
7044
+ return `Data source "${result.sourceName}" could not fulfill the request: ${result.error}. Try rephrasing your intent or querying a different source.`;
7045
+ }
7046
+ const { data, metadata } = result;
7047
+ let output = `## Data from "${result.sourceName}"
7048
+ `;
7049
+ output += `Rows returned: ${metadata.rowsReturned}`;
7050
+ if (metadata.isLimited) {
7051
+ output += ` (LIMITED \u2014 ${metadata.totalRowsMatched} total matched, only ${metadata.rowsReturned} returned)`;
7052
+ }
7053
+ output += `
7054
+ Execution time: ${metadata.executionTimeMs}ms
7055
+
7056
+ `;
7057
+ if (data.length === 0) {
7058
+ output += "No data returned.";
7059
+ return output;
7060
+ }
7061
+ output += `### Results (${data.length} rows)
7062
+ `;
7063
+ output += "```json\n";
7064
+ output += JSON.stringify(data, null, 2);
7065
+ output += "\n```\n";
7066
+ return output;
7067
+ }
7068
+ /**
7069
+ * Get source summaries (for external inspection/debugging).
7070
+ */
7071
+ getSourceSummaries() {
7072
+ return buildSourceSummaries(this.externalTools);
6239
7073
  }
6240
- return {
6241
- globalContext,
6242
- userContext,
6243
- queryContext,
6244
- combinedContext: combinedContext.trim()
6245
- };
6246
7074
  };
6247
- var KB = {
6248
- getKnowledgeBase,
6249
- getGlobalKnowledgeBase,
6250
- getUserKnowledgeBase,
6251
- getAllKnowledgeBase
7075
+
7076
+ // src/userResponse/agents/types.ts
7077
+ var DEFAULT_AGENT_CONFIG = {
7078
+ maxRowsPerSource: 10,
7079
+ mainAgentModel: "",
7080
+ // will use the provider's default model
7081
+ sourceAgentModel: "",
7082
+ // will use the provider's default model
7083
+ maxRetries: 3,
7084
+ maxIterations: 10
6252
7085
  };
6253
- var knowledge_base_default = KB;
6254
7086
 
6255
- // src/utils/bm25l-reranker.ts
6256
- var BM25L = class {
7087
+ // src/userResponse/anthropic.ts
7088
+ var import_dotenv = __toESM(require("dotenv"));
7089
+
7090
+ // src/userResponse/schema.ts
7091
+ var import_path4 = __toESM(require("path"));
7092
+ var import_fs5 = __toESM(require("fs"));
7093
+ var Schema = class {
7094
+ constructor(schemaFilePath) {
7095
+ this.cachedSchema = null;
7096
+ this.schemaFilePath = schemaFilePath || import_path4.default.join(process.cwd(), "../analysis/data/schema.json");
7097
+ }
6257
7098
  /**
6258
- * @param documents - Array of raw documents (strings)
6259
- * @param opts - Optional BM25L parameters
7099
+ * Gets the database schema from the schema file
7100
+ * @returns Parsed schema object or null if error occurs
6260
7101
  */
6261
- constructor(documents = [], opts = {}) {
6262
- if (!Array.isArray(documents)) {
6263
- throw new Error("BM25L: documents must be an array of strings.");
7102
+ getDatabaseSchema() {
7103
+ try {
7104
+ const dir = import_path4.default.dirname(this.schemaFilePath);
7105
+ if (!import_fs5.default.existsSync(dir)) {
7106
+ logger.info(`Creating directory structure: ${dir}`);
7107
+ import_fs5.default.mkdirSync(dir, { recursive: true });
7108
+ }
7109
+ if (!import_fs5.default.existsSync(this.schemaFilePath)) {
7110
+ logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
7111
+ const initialSchema = {
7112
+ database: "",
7113
+ schema: "",
7114
+ description: "",
7115
+ tables: [],
7116
+ relationships: []
7117
+ };
7118
+ import_fs5.default.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
7119
+ this.cachedSchema = initialSchema;
7120
+ return initialSchema;
7121
+ }
7122
+ const fileContent = import_fs5.default.readFileSync(this.schemaFilePath, "utf-8");
7123
+ const schema2 = JSON.parse(fileContent);
7124
+ this.cachedSchema = schema2;
7125
+ return schema2;
7126
+ } catch (error) {
7127
+ logger.error("Error parsing schema file:", error);
7128
+ return null;
6264
7129
  }
6265
- this.k1 = typeof opts.k1 === "number" ? opts.k1 : 1.5;
6266
- this.b = typeof opts.b === "number" ? opts.b : 0.75;
6267
- this.delta = typeof opts.delta === "number" ? opts.delta : 0.5;
6268
- this.documents = documents.map((d) => typeof d === "string" ? this.tokenize(d) : []);
6269
- this.docLengths = this.documents.map((doc) => doc.length);
6270
- this.avgDocLength = this.docLengths.reduce((a, b) => a + b, 0) / (this.docLengths.length || 1);
6271
- this.termDocFreq = {};
6272
- this.documents.forEach((doc) => {
6273
- const seen = /* @__PURE__ */ new Set();
6274
- doc.forEach((term) => {
6275
- if (!seen.has(term)) {
6276
- seen.add(term);
6277
- this.termDocFreq[term] = (this.termDocFreq[term] || 0) + 1;
6278
- }
6279
- });
6280
- });
6281
7130
  }
6282
7131
  /**
6283
- * Tokenize text into lowercase alphanumeric tokens
7132
+ * Gets the cached schema or loads it if not cached
7133
+ * @returns Cached schema or freshly loaded schema
6284
7134
  */
6285
- tokenize(text) {
6286
- if (typeof text !== "string") return [];
6287
- return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
7135
+ getSchema() {
7136
+ if (this.cachedSchema) {
7137
+ return this.cachedSchema;
7138
+ }
7139
+ return this.getDatabaseSchema();
6288
7140
  }
6289
7141
  /**
6290
- * Compute IDF (Inverse Document Frequency) with smoothing
7142
+ * Generates database schema documentation for LLM from Snowflake JSON schema
7143
+ * @returns Formatted schema documentation string
6291
7144
  */
6292
- idf(term) {
6293
- const df = this.termDocFreq[term] || 0;
6294
- const N = this.documents.length || 1;
6295
- return Math.log(1 + (N - df + 0.5) / (df + 0.5));
7145
+ generateSchemaDocumentation() {
7146
+ const schema2 = this.getSchema();
7147
+ if (!schema2) {
7148
+ logger.warn("No database schema found.");
7149
+ return "No database schema available.";
7150
+ }
7151
+ const tables = [];
7152
+ tables.push(`Database: ${schema2.database}`);
7153
+ tables.push(`Schema: ${schema2.schema}`);
7154
+ tables.push(`Description: ${schema2.description}`);
7155
+ tables.push("");
7156
+ tables.push("=".repeat(80));
7157
+ tables.push("");
7158
+ for (const table of schema2.tables) {
7159
+ const tableInfo = [];
7160
+ tableInfo.push(`TABLE: ${table.fullName}`);
7161
+ tableInfo.push(`Description: ${table.description}`);
7162
+ tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
7163
+ tableInfo.push("");
7164
+ tableInfo.push("Columns:");
7165
+ for (const column of table.columns) {
7166
+ let columnLine = ` - ${column.name}: ${column.type}`;
7167
+ if (column.isPrimaryKey) {
7168
+ columnLine += " (PRIMARY KEY)";
7169
+ }
7170
+ if (column.isForeignKey && column.references) {
7171
+ columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
7172
+ }
7173
+ if (!column.nullable) {
7174
+ columnLine += " NOT NULL";
7175
+ }
7176
+ if (column.description) {
7177
+ columnLine += ` - ${column.description}`;
7178
+ }
7179
+ tableInfo.push(columnLine);
7180
+ if (column.sampleValues && column.sampleValues.length > 0) {
7181
+ tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
7182
+ }
7183
+ if (column.statistics) {
7184
+ const stats = column.statistics;
7185
+ if (stats.min !== void 0 && stats.max !== void 0) {
7186
+ tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
7187
+ }
7188
+ if (stats.distinct !== void 0) {
7189
+ tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
7190
+ }
7191
+ }
7192
+ }
7193
+ tableInfo.push("");
7194
+ tables.push(tableInfo.join("\n"));
7195
+ }
7196
+ tables.push("=".repeat(80));
7197
+ tables.push("");
7198
+ tables.push("TABLE RELATIONSHIPS:");
7199
+ tables.push("");
7200
+ for (const rel of schema2.relationships) {
7201
+ tables.push(`${rel.from} -> ${rel.to} (${rel.type}): ${rel.keys.join(" = ")}`);
7202
+ }
7203
+ return tables.join("\n");
6296
7204
  }
6297
7205
  /**
6298
- * Compute BM25L score for a single document
7206
+ * Clears the cached schema, forcing a reload on next access
6299
7207
  */
6300
- score(query, docIndex) {
6301
- if (typeof query !== "string") return 0;
6302
- if (docIndex < 0 || docIndex >= this.documents.length) return 0;
6303
- const tokens = this.tokenize(query);
6304
- if (tokens.length === 0) return 0;
6305
- const doc = this.documents[docIndex];
6306
- const docLength = this.docLengths[docIndex] || 1;
6307
- const freq = {};
6308
- for (const t of doc) {
6309
- freq[t] = (freq[t] || 0) + 1;
6310
- }
6311
- let sum = 0;
6312
- for (const term of tokens) {
6313
- const tf = freq[term] || 0;
6314
- if (tf === 0) continue;
6315
- const idfVal = this.idf(term);
6316
- let tfL = tf - this.b * (docLength / this.avgDocLength) + this.delta;
6317
- if (tfL < 0) tfL = 0;
6318
- sum += idfVal * (tfL / (this.k1 + tfL));
6319
- }
6320
- return sum;
7208
+ clearCache() {
7209
+ this.cachedSchema = null;
6321
7210
  }
6322
7211
  /**
6323
- * Search and rank all documents
7212
+ * Sets a custom schema file path
7213
+ * @param filePath - Path to the schema file
6324
7214
  */
6325
- search(query) {
6326
- return this.documents.map((_, i) => ({
6327
- index: i,
6328
- score: this.score(query, i)
6329
- })).sort((a, b) => b.score - a.score);
7215
+ setSchemaPath(filePath) {
7216
+ this.schemaFilePath = filePath;
7217
+ this.clearCache();
6330
7218
  }
6331
7219
  };
6332
- function normalizeScores(scores) {
6333
- if (scores.length === 0) return [];
6334
- const min = Math.min(...scores);
6335
- const max = Math.max(...scores);
6336
- if (max === min) {
6337
- return scores.map(() => max === 0 ? 0 : 1);
6338
- }
6339
- return scores.map((score) => (score - min) / (max - min));
6340
- }
6341
- function hybridRerank(query, items, getDocument, getSemanticScore, options = {}) {
6342
- const {
6343
- semanticWeight = 0.7,
6344
- bm25Weight = 0.3,
6345
- minScore = 0,
6346
- k1 = 1.5,
6347
- b = 0.75,
6348
- delta = 0.5
6349
- } = options;
6350
- if (items.length === 0) return [];
6351
- const documents = items.map(getDocument);
6352
- const semanticScores = items.map(getSemanticScore);
6353
- const bm25 = new BM25L(documents, { k1, b, delta });
6354
- const bm25Scores = items.map((_, i) => bm25.score(query, i));
6355
- const normalizedSemantic = normalizeScores(semanticScores);
6356
- const normalizedBM25 = normalizeScores(bm25Scores);
6357
- const results = items.map((item, i) => {
6358
- const hybridScore = semanticWeight * normalizedSemantic[i] + bm25Weight * normalizedBM25[i];
6359
- return {
6360
- item,
6361
- originalIndex: i,
6362
- semanticScore: semanticScores[i],
6363
- bm25Score: bm25Scores[i],
6364
- hybridScore
6365
- };
6366
- });
6367
- return results.filter((r) => r.hybridScore >= minScore).sort((a, b2) => b2.hybridScore - a.hybridScore);
6368
- }
6369
- function rerankChromaResults(query, chromaResults, options = {}) {
6370
- const ids = chromaResults.ids[0] || [];
6371
- const documents = chromaResults.documents[0] || [];
6372
- const metadatas = chromaResults.metadatas[0] || [];
6373
- const distances = chromaResults.distances[0] || [];
6374
- if (ids.length === 0) return [];
6375
- const items = ids.map((id, i) => ({
6376
- id,
6377
- document: documents[i],
6378
- metadata: metadatas[i],
6379
- distance: distances[i]
6380
- }));
6381
- const reranked = hybridRerank(
6382
- query,
6383
- items,
6384
- (item) => item.document || "",
6385
- // Convert L2 distance to similarity score
6386
- (item) => 1 / (1 + item.distance),
6387
- options
6388
- );
6389
- return reranked.map((r) => ({
6390
- id: r.item.id,
6391
- document: r.item.document,
6392
- metadata: r.item.metadata,
6393
- distance: r.item.distance,
6394
- semanticScore: r.semanticScore,
6395
- bm25Score: r.bm25Score,
6396
- hybridScore: r.hybridScore
6397
- }));
6398
- }
6399
- function rerankConversationResults(query, results, options = {}) {
6400
- if (results.length === 0) return [];
6401
- const reranked = hybridRerank(
6402
- query,
6403
- results,
6404
- (item) => item.userPrompt || "",
6405
- (item) => item.similarity || 0,
6406
- options
6407
- );
6408
- return reranked.map((r) => ({
6409
- ...r.item,
6410
- hybridScore: r.hybridScore,
6411
- bm25Score: r.bm25Score
6412
- }));
6413
- }
7220
+ var schema = new Schema();
6414
7221
 
6415
- // src/userResponse/conversation-search.ts
6416
- var searchConversations = async ({
6417
- userPrompt,
7222
+ // src/userResponse/knowledge-base.ts
7223
+ var getKnowledgeBase = async ({
7224
+ prompt,
6418
7225
  collections,
6419
- userId,
6420
- similarityThreshold = 0.6
7226
+ topK = 1
6421
7227
  }) => {
6422
7228
  try {
6423
- if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["search"]) {
6424
- logger.info("[ConversationSearch] conversation-history.search collection not registered, skipping");
6425
- return null;
7229
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
7230
+ logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
7231
+ return "";
6426
7232
  }
6427
- logger.info(`[ConversationSearch] Searching conversations for: "${userPrompt.substring(0, 50)}..."`);
6428
- logger.info(`[ConversationSearch] Using similarity threshold: ${(similarityThreshold * 100).toFixed(0)}%`);
6429
- const result = await collections["conversation-history"]["search"]({
6430
- userPrompt,
6431
- userId,
6432
- threshold: similarityThreshold
7233
+ const result = await collections["knowledge-base"]["query"]({
7234
+ prompt,
7235
+ topK
6433
7236
  });
6434
- if (!result) {
6435
- logger.info("[ConversationSearch] No matching conversations found");
6436
- return null;
6437
- }
6438
- if (!result.uiBlock) {
6439
- logger.error("[ConversationSearch] No UI block in conversation search result");
6440
- return null;
7237
+ if (!result || !result.content) {
7238
+ logger.warn("[KnowledgeBase] No knowledge base results returned");
7239
+ return "";
6441
7240
  }
6442
- const similarity = result.similarity || 0;
6443
- logger.info(`[ConversationSearch] Best match similarity: ${(similarity * 100).toFixed(2)}%`);
6444
- if (similarity < similarityThreshold) {
6445
- logger.info(
6446
- `[ConversationSearch] Best match has similarity ${(similarity * 100).toFixed(2)}% but below threshold ${(similarityThreshold * 100).toFixed(2)}%`
6447
- );
6448
- return null;
7241
+ logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
7242
+ if (result.metadata?.sources && result.metadata.sources.length > 0) {
7243
+ logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
6449
7244
  }
6450
- logger.info(
6451
- `[ConversationSearch] Found matching conversation with similarity ${(similarity * 100).toFixed(2)}%`
6452
- );
6453
- logger.debug(`[ConversationSearch] Matched prompt: "${result.metadata?.userPrompt?.substring(0, 50)}..."`);
6454
- return result;
7245
+ return result.content;
6455
7246
  } catch (error) {
6456
7247
  const errorMsg = error instanceof Error ? error.message : String(error);
6457
- logger.warn(`[ConversationSearch] Error searching conversations: ${errorMsg}`);
6458
- return null;
7248
+ logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
7249
+ return "";
6459
7250
  }
6460
7251
  };
6461
- var searchConversationsWithReranking = async (options) => {
6462
- const {
6463
- userPrompt,
6464
- collections,
6465
- userId,
6466
- similarityThreshold = 0.6,
6467
- rerankCandidates = 50,
6468
- // Fetch more candidates for better reranking
6469
- hybridOptions = {
6470
- semanticWeight: 0.7,
6471
- bm25Weight: 0.3
6472
- }
6473
- } = options;
7252
+ var getGlobalKnowledgeBase = async ({
7253
+ collections,
7254
+ limit = 100
7255
+ }) => {
6474
7256
  try {
6475
- if (!collections || !collections["conversation-history"]) {
6476
- logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
6477
- return null;
7257
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
7258
+ logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
7259
+ return "";
6478
7260
  }
6479
- if (!collections["conversation-history"]["searchMultiple"]) {
6480
- logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
6481
- return searchConversations({
6482
- userPrompt,
6483
- collections,
6484
- userId,
6485
- similarityThreshold
6486
- });
7261
+ const result = await collections["knowledge-base"]["getGlobal"]({ limit });
7262
+ if (!result || !result.content) {
7263
+ logger.warn("[KnowledgeBase] No global knowledge base nodes found");
7264
+ return "";
6487
7265
  }
6488
- const results = await collections["conversation-history"]["searchMultiple"]({
6489
- userPrompt,
6490
- userId,
6491
- limit: rerankCandidates,
6492
- threshold: 0
6493
- // No threshold - get all candidates for reranking
6494
- });
6495
- if (!results || results.length === 0) {
6496
- logger.info("[ConversationSearch] No conversations found in database");
6497
- return null;
7266
+ logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
7267
+ return result.content;
7268
+ } catch (error) {
7269
+ const errorMsg = error instanceof Error ? error.message : String(error);
7270
+ logger.warn(`[KnowledgeBase] Error fetching global knowledge base: ${errorMsg}`);
7271
+ return "";
7272
+ }
7273
+ };
7274
+ var getUserKnowledgeBase = async ({
7275
+ collections,
7276
+ userId,
7277
+ limit = 100
7278
+ }) => {
7279
+ try {
7280
+ if (!userId) {
7281
+ logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
7282
+ return "";
6498
7283
  }
6499
- logger.info(`[ConversationSearch] Retrieved ${results.length} candidates for reranking`);
6500
- const candidatesForReranking = results.map((r) => ({
6501
- ...r,
6502
- userPrompt: r.metadata?.userPrompt || ""
6503
- }));
6504
- const reranked = rerankConversationResults(userPrompt, candidatesForReranking, hybridOptions);
6505
- if (reranked.length === 0) {
6506
- logger.info("[ConversationSearch] No results after reranking");
6507
- return null;
7284
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
7285
+ logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
7286
+ return "";
6508
7287
  }
6509
- const best = reranked[0];
6510
- const hybridScore = best.hybridScore;
6511
- const semanticScore = best.similarity || 0;
6512
- const matchedUserPrompt = best.userPrompt || best.metadata?.userPrompt || "";
6513
- logger.info(`[ConversationSearch] Best match after reranking:`);
6514
- logger.info(` - Hybrid score: ${(hybridScore * 100).toFixed(2)}%`);
6515
- logger.info(` - Semantic score: ${(semanticScore * 100).toFixed(2)}%`);
6516
- logger.info(` - BM25L score: ${best.bm25Score.toFixed(4)}`);
6517
- logger.info(` - Matched prompt: "${matchedUserPrompt}"`);
6518
- logger.info(` - Query prompt: "${userPrompt}"`);
6519
- if (semanticScore < similarityThreshold) {
6520
- logger.info(
6521
- `[ConversationSearch] Semantic score ${(semanticScore * 100).toFixed(2)}% below threshold ${(similarityThreshold * 100).toFixed(2)}% - rejecting match`
6522
- );
6523
- return null;
7288
+ const result = await collections["knowledge-base"]["getByUser"]({
7289
+ userId: Number(userId),
7290
+ limit
7291
+ });
7292
+ if (!result || !result.content) {
7293
+ logger.info(`[KnowledgeBase] No user knowledge base nodes found for userId: ${userId}`);
7294
+ return "";
6524
7295
  }
6525
- logger.info(
6526
- `[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
6527
- );
6528
- return {
6529
- uiBlock: best.uiBlock,
6530
- similarity: semanticScore,
6531
- hybridScore,
6532
- bm25Score: best.bm25Score,
6533
- metadata: best.metadata
6534
- };
7296
+ logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} user knowledge base nodes for userId: ${userId}`);
7297
+ return result.content;
6535
7298
  } catch (error) {
6536
7299
  const errorMsg = error instanceof Error ? error.message : String(error);
6537
- logger.warn(`[ConversationSearch] Error in hybrid search: ${errorMsg}`);
6538
- return null;
6539
- }
6540
- };
6541
- var ConversationSearch = {
6542
- searchConversations,
6543
- searchConversationsWithReranking
6544
- };
6545
- var conversation_search_default = ConversationSearch;
6546
-
6547
- // src/userResponse/prompt-extractor.ts
6548
- function extractPromptText(content) {
6549
- if (content === null || content === void 0) {
7300
+ logger.warn(`[KnowledgeBase] Error fetching user knowledge base: ${errorMsg}`);
6550
7301
  return "";
6551
7302
  }
6552
- if (typeof content === "string") {
6553
- return content;
6554
- }
6555
- if (Array.isArray(content)) {
6556
- return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
6557
- }
6558
- if (content && typeof content === "object") {
6559
- return extractObjectText(content);
6560
- }
6561
- return String(content);
6562
- }
6563
- function extractContentBlockText(item) {
6564
- if (typeof item === "string") {
6565
- return item;
6566
- }
6567
- if (item && typeof item === "object") {
6568
- const obj = item;
6569
- if (typeof obj.text === "string") {
6570
- return obj.text;
6571
- }
6572
- if (typeof obj.content === "string") {
6573
- return obj.content;
6574
- }
6575
- return JSON.stringify(item, null, 2);
7303
+ };
7304
+ var getAllKnowledgeBase = async ({
7305
+ prompt,
7306
+ collections,
7307
+ userId,
7308
+ topK = 3
7309
+ }) => {
7310
+ const [globalContext, userContext, queryContext] = await Promise.all([
7311
+ getGlobalKnowledgeBase({ collections }),
7312
+ getUserKnowledgeBase({ collections, userId }),
7313
+ getKnowledgeBase({ prompt, collections, topK })
7314
+ ]);
7315
+ let combinedContext = "";
7316
+ if (globalContext) {
7317
+ combinedContext += "## Global Knowledge Base\n";
7318
+ combinedContext += "The following information applies to all queries:\n\n";
7319
+ combinedContext += globalContext + "\n\n";
6576
7320
  }
6577
- return String(item);
6578
- }
6579
- function extractObjectText(obj) {
6580
- if (typeof obj.text === "string") {
6581
- return obj.text;
7321
+ if (userContext) {
7322
+ combinedContext += "## User-Specific Knowledge Base\n";
7323
+ combinedContext += "The following information is specific to this user:\n\n";
7324
+ combinedContext += userContext + "\n\n";
6582
7325
  }
6583
- if (typeof obj.content === "string") {
6584
- return obj.content;
7326
+ if (queryContext) {
7327
+ combinedContext += "## Relevant Knowledge Base (Query-Matched)\n";
7328
+ combinedContext += "The following information is semantically relevant to the current query:\n\n";
7329
+ combinedContext += queryContext + "\n\n";
6585
7330
  }
6586
- return JSON.stringify(obj, null, 2);
6587
- }
6588
-
6589
- // src/userResponse/constants.ts
6590
- var MAX_QUERY_VALIDATION_RETRIES = 3;
6591
- var MAX_QUERY_ATTEMPTS = 6;
6592
- var MAX_TOOL_ATTEMPTS = 3;
6593
- var STREAM_FLUSH_INTERVAL_MS = 50;
6594
- var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
6595
- var STREAM_DELAY_MS = 50;
6596
- var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
6597
- var MAX_TOKENS_QUERY_FIX = 2048;
6598
- var MAX_TOKENS_COMPONENT_MATCHING = 8192;
6599
- var MAX_TOKENS_CLASSIFICATION = 1500;
6600
- var MAX_TOKENS_ADAPTATION = 8192;
6601
- var MAX_TOKENS_TEXT_RESPONSE = 4e3;
6602
- var MAX_TOKENS_NEXT_QUESTIONS = 1200;
6603
- var DEFAULT_MAX_ROWS_FOR_LLM = 10;
6604
- var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
6605
- var STREAM_PREVIEW_MAX_ROWS = 10;
6606
- var STREAM_PREVIEW_MAX_CHARS = 200;
6607
- var TOOL_TRACKING_MAX_ROWS = 5;
6608
- var TOOL_TRACKING_MAX_CHARS = 200;
6609
- var TOOL_TRACKING_SAMPLE_ROWS = 3;
6610
- var DEFAULT_QUERY_LIMIT = 10;
6611
- var MAX_COMPONENT_QUERY_LIMIT = 10;
6612
- var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
6613
- var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
6614
- var MAX_TOOL_CALLING_ITERATIONS = 20;
6615
- var KNOWLEDGE_BASE_TOP_K = 3;
7331
+ return {
7332
+ globalContext,
7333
+ userContext,
7334
+ queryContext,
7335
+ combinedContext: combinedContext.trim()
7336
+ };
7337
+ };
7338
+ var KB = {
7339
+ getKnowledgeBase,
7340
+ getGlobalKnowledgeBase,
7341
+ getUserKnowledgeBase,
7342
+ getAllKnowledgeBase
7343
+ };
7344
+ var knowledge_base_default = KB;
6616
7345
 
6617
- // src/userResponse/stream-buffer.ts
6618
- var StreamBuffer = class {
6619
- constructor(callback) {
6620
- this.buffer = "";
6621
- this.flushTimer = null;
6622
- this.fullText = "";
6623
- this.callback = callback;
7346
+ // src/userResponse/prompt-extractor.ts
7347
+ function extractPromptText(content) {
7348
+ if (content === null || content === void 0) {
7349
+ return "";
6624
7350
  }
6625
- /**
6626
- * Check if the buffer has a callback configured
6627
- */
6628
- hasCallback() {
6629
- return !!this.callback;
7351
+ if (typeof content === "string") {
7352
+ return content;
6630
7353
  }
6631
- /**
6632
- * Get all text that has been written (including already flushed)
6633
- */
6634
- getFullText() {
6635
- return this.fullText;
7354
+ if (Array.isArray(content)) {
7355
+ return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
6636
7356
  }
6637
- /**
6638
- * Write a chunk to the buffer
6639
- * Large chunks or chunks with newlines are flushed immediately
6640
- * Small chunks are batched and flushed after a short interval
6641
- *
6642
- * @param chunk - Text chunk to write
6643
- */
6644
- write(chunk) {
6645
- this.fullText += chunk;
6646
- if (!this.callback) {
6647
- return;
6648
- }
6649
- this.buffer += chunk;
6650
- if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
6651
- this.flushNow();
6652
- } else if (!this.flushTimer) {
6653
- this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
6654
- }
7357
+ if (content && typeof content === "object") {
7358
+ return extractObjectText(content);
6655
7359
  }
6656
- /**
6657
- * Flush the buffer immediately
6658
- * Call this before tool execution or other operations that need clean output
6659
- */
6660
- flush() {
6661
- this.flushNow();
7360
+ return String(content);
7361
+ }
7362
+ function extractContentBlockText(item) {
7363
+ if (typeof item === "string") {
7364
+ return item;
6662
7365
  }
6663
- /**
6664
- * Internal flush implementation
6665
- */
6666
- flushNow() {
6667
- if (this.flushTimer) {
6668
- clearTimeout(this.flushTimer);
6669
- this.flushTimer = null;
7366
+ if (item && typeof item === "object") {
7367
+ const obj = item;
7368
+ if (typeof obj.text === "string") {
7369
+ return obj.text;
6670
7370
  }
6671
- if (this.buffer && this.callback) {
6672
- this.callback(this.buffer);
6673
- this.buffer = "";
7371
+ if (typeof obj.content === "string") {
7372
+ return obj.content;
6674
7373
  }
7374
+ return JSON.stringify(item, null, 2);
6675
7375
  }
6676
- /**
6677
- * Clean up resources
6678
- * Call this when done with the buffer
6679
- */
6680
- dispose() {
6681
- this.flush();
6682
- this.callback = void 0;
6683
- }
6684
- };
6685
- function streamDelay(ms = STREAM_DELAY_MS) {
6686
- return new Promise((resolve) => setTimeout(resolve, ms));
7376
+ return String(item);
6687
7377
  }
6688
- async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
6689
- if (!streamBuffer.hasCallback()) {
6690
- return operation();
7378
+ function extractObjectText(obj) {
7379
+ if (typeof obj.text === "string") {
7380
+ return obj.text;
6691
7381
  }
6692
- const startTime = Date.now();
6693
- await streamDelay(30);
6694
- streamBuffer.write(`\u23F3 ${progressMessage}`);
6695
- const heartbeatInterval = setInterval(() => {
6696
- const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
6697
- if (elapsedSeconds >= 1) {
6698
- streamBuffer.write(` (${elapsedSeconds}s)`);
6699
- }
6700
- }, intervalMs);
6701
- try {
6702
- const result = await operation();
6703
- return result;
6704
- } finally {
6705
- clearInterval(heartbeatInterval);
6706
- streamBuffer.write("\n\n");
7382
+ if (typeof obj.content === "string") {
7383
+ return obj.content;
6707
7384
  }
7385
+ return JSON.stringify(obj, null, 2);
6708
7386
  }
6709
7387
 
6710
7388
  // src/userResponse/utils/component-props-processor.ts
@@ -8506,48 +9184,48 @@ ${executedToolsText}`);
8506
9184
  }
8507
9185
  };
8508
9186
 
8509
- // src/userResponse/groq.ts
9187
+ // src/userResponse/anthropic.ts
8510
9188
  import_dotenv.default.config();
8511
- var GroqLLM = class extends BaseLLM {
9189
+ var AnthropicLLM = class extends BaseLLM {
8512
9190
  constructor(config) {
8513
9191
  super(config);
8514
9192
  }
8515
9193
  getDefaultModel() {
8516
- return "groq/openai/gpt-oss-120b";
9194
+ return "anthropic/claude-sonnet-4-5-20250929";
8517
9195
  }
8518
9196
  getDefaultFastModel() {
8519
- return "groq/llama-3.1-8b-instant";
9197
+ return "anthropic/claude-haiku-4-5-20251001";
8520
9198
  }
8521
9199
  getDefaultApiKey() {
8522
- return process.env.GROQ_API_KEY;
9200
+ return process.env.ANTHROPIC_API_KEY;
8523
9201
  }
8524
9202
  getProviderName() {
8525
- return "Groq";
9203
+ return "Anthropic";
8526
9204
  }
8527
9205
  };
8528
- var groqLLM = new GroqLLM();
9206
+ var anthropicLLM = new AnthropicLLM();
8529
9207
 
8530
- // src/userResponse/anthropic.ts
9208
+ // src/userResponse/groq.ts
8531
9209
  var import_dotenv2 = __toESM(require("dotenv"));
8532
9210
  import_dotenv2.default.config();
8533
- var AnthropicLLM = class extends BaseLLM {
9211
+ var GroqLLM = class extends BaseLLM {
8534
9212
  constructor(config) {
8535
9213
  super(config);
8536
9214
  }
8537
9215
  getDefaultModel() {
8538
- return "anthropic/claude-sonnet-4-5-20250929";
9216
+ return "groq/openai/gpt-oss-120b";
8539
9217
  }
8540
9218
  getDefaultFastModel() {
8541
- return "anthropic/claude-haiku-4-5-20251001";
9219
+ return "groq/llama-3.1-8b-instant";
8542
9220
  }
8543
9221
  getDefaultApiKey() {
8544
- return process.env.ANTHROPIC_API_KEY;
9222
+ return process.env.GROQ_API_KEY;
8545
9223
  }
8546
9224
  getProviderName() {
8547
- return "Anthropic";
9225
+ return "Groq";
8548
9226
  }
8549
9227
  };
8550
- var anthropicLLM = new AnthropicLLM();
9228
+ var groqLLM = new GroqLLM();
8551
9229
 
8552
9230
  // src/userResponse/gemini.ts
8553
9231
  var import_dotenv3 = __toESM(require("dotenv"));
@@ -8593,115 +9271,214 @@ var OpenAILLM = class extends BaseLLM {
8593
9271
  };
8594
9272
  var openaiLLM = new OpenAILLM();
8595
9273
 
8596
- // src/userResponse/index.ts
8597
- var import_dotenv5 = __toESM(require("dotenv"));
8598
- import_dotenv5.default.config();
8599
- function getLLMProviders() {
8600
- const envProviders = process.env.LLM_PROVIDERS;
8601
- const DEFAULT_PROVIDERS = ["anthropic", "gemini", "openai", "groq"];
8602
- if (!envProviders) {
8603
- return DEFAULT_PROVIDERS;
8604
- }
9274
+ // src/userResponse/agent-user-response.ts
9275
+ function getLLMInstance(provider) {
9276
+ switch (provider) {
9277
+ case "anthropic":
9278
+ return anthropicLLM;
9279
+ case "groq":
9280
+ return groqLLM;
9281
+ case "gemini":
9282
+ return geminiLLM;
9283
+ case "openai":
9284
+ return openaiLLM;
9285
+ default:
9286
+ return anthropicLLM;
9287
+ }
9288
+ }
9289
+ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, streamCallback, collections, externalTools, userId) => {
9290
+ const startTime = Date.now();
9291
+ const providers = llmProviders || ["anthropic"];
9292
+ const provider = providers[0];
9293
+ const llmInstance = getLLMInstance(provider);
9294
+ logger.info(`[AgentFlow] Starting | provider: ${provider} | prompt: "${prompt.substring(0, 50)}..."`);
8605
9295
  try {
8606
- const providers = JSON.parse(envProviders);
8607
- const validProviders = providers.filter((p) => p === "anthropic" || p === "groq" || p === "gemini" || p === "openai");
8608
- if (validProviders.length === 0) {
8609
- return DEFAULT_PROVIDERS;
8610
- }
8611
- return validProviders;
8612
- } catch (error) {
8613
- logger.error('Failed to parse LLM_PROVIDERS, defaulting to ["anthropic"]:', error);
8614
- return DEFAULT_PROVIDERS;
8615
- }
8616
- }
8617
- var useAnthropicMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8618
- if (responseMode === "component" && components.length === 0) {
8619
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8620
- logger.error("[useAnthropicMethod] No components available");
8621
- return { success: false, errors: [emptyMsg] };
8622
- }
8623
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8624
- logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
8625
- return matchResult;
8626
- };
8627
- var useGroqMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8628
- logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
8629
- logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
8630
- if (responseMode === "component" && components.length === 0) {
8631
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8632
- logger.error("[useGroqMethod] No components available");
8633
- return { success: false, errors: [emptyMsg] };
8634
- }
8635
- logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
8636
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8637
- logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
8638
- return matchResult;
8639
- };
8640
- var useGeminiMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8641
- logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
8642
- logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
8643
- if (responseMode === "component" && components.length === 0) {
8644
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8645
- logger.error("[useGeminiMethod] No components available");
8646
- return { success: false, errors: [emptyMsg] };
8647
- }
8648
- logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
8649
- const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8650
- logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
8651
- return matchResult;
8652
- };
8653
- var useOpenAIMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8654
- logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
8655
- logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
8656
- if (responseMode === "component" && components.length === 0) {
8657
- const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
8658
- logger.error("[useOpenAIMethod] No components available");
8659
- return { success: false, errors: [emptyMsg] };
8660
- }
8661
- logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
8662
- const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8663
- logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
8664
- return matchResult;
8665
- };
8666
- var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
8667
- const providers = llmProviders || getLLMProviders();
8668
- const errors = [];
8669
- logger.info(`[get_user_response] LLM Provider order: [${providers.join(", ")}]`);
8670
- for (let i = 0; i < providers.length; i++) {
8671
- const provider = providers[i];
8672
- const isLastProvider = i === providers.length - 1;
8673
- logger.info(`[get_user_response] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
8674
- let result;
8675
- if (provider === "anthropic") {
8676
- result = await useAnthropicMethod(prompt, components, anthropicApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8677
- } else if (provider === "groq") {
8678
- result = await useGroqMethod(prompt, components, groqApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8679
- } else if (provider === "gemini") {
8680
- result = await useGeminiMethod(prompt, components, geminiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8681
- } else if (provider === "openai") {
8682
- result = await useOpenAIMethod(prompt, components, openaiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
8683
- } else {
8684
- logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
8685
- errors.push(`Unknown provider: ${provider}`);
8686
- continue;
8687
- }
8688
- if (result.success) {
8689
- logger.info(`[get_user_response] Success with provider: ${provider}`);
8690
- return result;
9296
+ const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
9297
+ userPrompt: prompt,
9298
+ collections,
9299
+ userId,
9300
+ similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
9301
+ });
9302
+ if (conversationMatch) {
9303
+ logger.info(`[AgentFlow] Found matching conversation (${(conversationMatch.similarity * 100).toFixed(2)}% similarity)`);
9304
+ const rawComponent = conversationMatch.uiBlock?.component || conversationMatch.uiBlock?.generatedComponentMetadata;
9305
+ const isValidComponent = rawComponent && typeof rawComponent === "object" && Object.keys(rawComponent).length > 0;
9306
+ const component = isValidComponent ? rawComponent : null;
9307
+ const cachedTextResponse = conversationMatch.uiBlock?.analysis || conversationMatch.uiBlock?.textResponse || conversationMatch.uiBlock?.text || "";
9308
+ if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
9309
+ if (streamCallback && cachedTextResponse) {
9310
+ streamCallback(cachedTextResponse);
9311
+ }
9312
+ const elapsedTime2 = Date.now() - startTime;
9313
+ logger.info(`[AgentFlow] Exact match \u2014 returning cached result (${elapsedTime2}ms)`);
9314
+ return {
9315
+ success: true,
9316
+ data: {
9317
+ text: cachedTextResponse,
9318
+ component,
9319
+ actions: conversationMatch.uiBlock?.actions || [],
9320
+ reasoning: `Exact match from previous conversation`,
9321
+ method: `${provider}-agent-semantic-match`,
9322
+ semanticSimilarity: conversationMatch.similarity
9323
+ },
9324
+ errors: []
9325
+ };
9326
+ }
9327
+ logger.info(`[AgentFlow] Similar match but below exact threshold \u2014 proceeding with agent`);
8691
9328
  } else {
8692
- const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
8693
- errors.push(...providerErrors);
8694
- logger.warn(`[get_user_response] Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`);
8695
- if (!isLastProvider) {
8696
- logger.info("[get_user_response] Falling back to next provider...");
9329
+ logger.info(`[AgentFlow] No matching conversations \u2014 proceeding with agent`);
9330
+ }
9331
+ const apiKey = (() => {
9332
+ switch (provider) {
9333
+ case "anthropic":
9334
+ return anthropicApiKey;
9335
+ case "groq":
9336
+ return groqApiKey;
9337
+ case "gemini":
9338
+ return geminiApiKey;
9339
+ case "openai":
9340
+ return openaiApiKey;
9341
+ default:
9342
+ return anthropicApiKey;
9343
+ }
9344
+ })();
9345
+ const agentTools = (externalTools || []).map((t) => ({
9346
+ id: t.id,
9347
+ name: t.name,
9348
+ description: t.description,
9349
+ fn: t.fn,
9350
+ limit: t.limit,
9351
+ outputSchema: t.outputSchema,
9352
+ executionType: t.executionType,
9353
+ userProvidedData: t.userProvidedData,
9354
+ params: t.params
9355
+ }));
9356
+ const agentConfig = {
9357
+ ...DEFAULT_AGENT_CONFIG,
9358
+ apiKey: apiKey || void 0
9359
+ };
9360
+ const streamBuffer = new StreamBuffer(streamCallback);
9361
+ const mainAgent = new MainAgent(agentTools, agentConfig, streamBuffer);
9362
+ const agentResponse = await mainAgent.handleQuestion(
9363
+ prompt,
9364
+ apiKey,
9365
+ conversationHistory,
9366
+ streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
9367
+ );
9368
+ const textResponse = streamBuffer.getFullText() || agentResponse.text || "I apologize, but I was unable to generate a response.";
9369
+ streamBuffer.flush();
9370
+ const hasExecutedTools = agentResponse.executedTools.length > 0;
9371
+ let matchedComponents = [];
9372
+ let layoutTitle = "Dashboard";
9373
+ let layoutDescription = "Multi-component dashboard";
9374
+ let actions = [];
9375
+ if (!hasExecutedTools) {
9376
+ logger.info(`[AgentFlow] No tools executed \u2014 general question, skipping component generation`);
9377
+ const nextQuestions = await llmInstance.generateNextQuestions(
9378
+ prompt,
9379
+ null,
9380
+ void 0,
9381
+ apiKey,
9382
+ conversationHistory,
9383
+ textResponse
9384
+ );
9385
+ actions = convertQuestionsToActions(nextQuestions);
9386
+ } else if (components && components.length > 0) {
9387
+ logger.info(`[AgentFlow] ${agentResponse.executedTools.length} tools executed \u2014 generating components`);
9388
+ if (streamBuffer.hasCallback()) {
9389
+ streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
9390
+ streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
9391
+ }
9392
+ const componentStreamCallback = streamBuffer.hasCallback() ? (component) => {
9393
+ const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
9394
+ streamBuffer.write(answerMarker);
9395
+ } : void 0;
9396
+ const sanitizedTextResponse = textResponse.replace(
9397
+ /<DataTable>[\s\S]*?<\/DataTable>/g,
9398
+ "<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>"
9399
+ );
9400
+ const matchResult = await llmInstance.matchComponentsFromAnalysis(
9401
+ sanitizedTextResponse,
9402
+ components,
9403
+ prompt,
9404
+ apiKey,
9405
+ componentStreamCallback,
9406
+ [],
9407
+ // deferredTools — MainAgent handles tool execution
9408
+ agentResponse.executedTools,
9409
+ collections,
9410
+ userId
9411
+ );
9412
+ matchedComponents = matchResult.components;
9413
+ layoutTitle = matchResult.layoutTitle;
9414
+ layoutDescription = matchResult.layoutDescription;
9415
+ actions = matchResult.actions;
9416
+ }
9417
+ const securedComponents = matchedComponents.map((comp) => {
9418
+ const props = { ...comp.props };
9419
+ if (props.externalTool?.parameters?.sql) {
9420
+ const { sql, ...restParams } = props.externalTool.parameters;
9421
+ const queryId = queryCache.storeQuery(sql);
9422
+ props.externalTool = {
9423
+ ...props.externalTool,
9424
+ parameters: { queryId, ...restParams }
9425
+ };
8697
9426
  }
9427
+ if (props.query) {
9428
+ const { query, ...restProps } = props;
9429
+ const queryId = queryCache.storeQuery(query);
9430
+ return { ...comp, props: { ...restProps, queryId } };
9431
+ }
9432
+ return { ...comp, props };
9433
+ });
9434
+ let containerComponent = null;
9435
+ if (securedComponents.length > 0) {
9436
+ containerComponent = {
9437
+ id: `container_${Date.now()}`,
9438
+ name: "MultiComponentContainer",
9439
+ type: "Container",
9440
+ description: layoutDescription,
9441
+ props: {
9442
+ config: {
9443
+ title: layoutTitle,
9444
+ description: layoutDescription,
9445
+ components: securedComponents
9446
+ },
9447
+ actions
9448
+ }
9449
+ };
8698
9450
  }
9451
+ const elapsedTime = Date.now() - startTime;
9452
+ logger.info(`[AgentFlow] Complete | ${agentResponse.executedTools.length} tools | ${matchedComponents.length} components | ${elapsedTime}ms`);
9453
+ return {
9454
+ success: true,
9455
+ data: {
9456
+ text: textResponse,
9457
+ component: containerComponent,
9458
+ actions,
9459
+ method: `${provider}-agent-response`
9460
+ },
9461
+ errors: []
9462
+ };
9463
+ } catch (error) {
9464
+ const errorMsg = error instanceof Error ? error.message : String(error);
9465
+ logger.error(`[AgentFlow] Error: ${errorMsg}`);
9466
+ userPromptErrorLogger.logError(
9467
+ "agentUserResponse",
9468
+ error instanceof Error ? error : new Error(errorMsg),
9469
+ { userPrompt: prompt }
9470
+ );
9471
+ const elapsedTime = Date.now() - startTime;
9472
+ logger.info(`[AgentFlow] Failed in ${elapsedTime}ms`);
9473
+ return {
9474
+ success: false,
9475
+ errors: [errorMsg],
9476
+ data: {
9477
+ text: "I apologize, but I encountered an error processing your request. Please try again.",
9478
+ method: `${provider}-agent-error`
9479
+ }
9480
+ };
8699
9481
  }
8700
- logger.error(`[get_user_response] All LLM providers failed. Errors: ${errors.join("; ")}`);
8701
- return {
8702
- success: false,
8703
- errors
8704
- };
8705
9482
  };
8706
9483
 
8707
9484
  // src/utils/conversation-saver.ts
@@ -8899,7 +9676,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8899
9676
  sendMessage(streamMessage);
8900
9677
  };
8901
9678
  }
8902
- const userResponse = await get_user_response(
9679
+ const userResponse = await get_agent_user_response(
8903
9680
  prompt,
8904
9681
  components,
8905
9682
  anthropicApiKey,
@@ -8908,7 +9685,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
8908
9685
  openaiApiKey,
8909
9686
  llmProviders,
8910
9687
  conversationHistory,
8911
- responseMode,
8912
9688
  streamCallback,
8913
9689
  collections,
8914
9690
  externalTools,
@@ -12796,6 +13572,28 @@ async function handleSchemaRequest(message, sendMessage) {
12796
13572
  }
12797
13573
  }
12798
13574
 
13575
+ // src/userResponse/index.ts
13576
+ var import_dotenv5 = __toESM(require("dotenv"));
13577
+ import_dotenv5.default.config();
13578
+ function getLLMProviders() {
13579
+ const envProviders = process.env.LLM_PROVIDERS;
13580
+ const DEFAULT_PROVIDERS = ["anthropic", "gemini", "openai", "groq"];
13581
+ if (!envProviders) {
13582
+ return DEFAULT_PROVIDERS;
13583
+ }
13584
+ try {
13585
+ const providers = JSON.parse(envProviders);
13586
+ const validProviders = providers.filter((p) => p === "anthropic" || p === "groq" || p === "gemini" || p === "openai");
13587
+ if (validProviders.length === 0) {
13588
+ return DEFAULT_PROVIDERS;
13589
+ }
13590
+ return validProviders;
13591
+ } catch (error) {
13592
+ logger.error('Failed to parse LLM_PROVIDERS, defaulting to ["anthropic"]:', error);
13593
+ return DEFAULT_PROVIDERS;
13594
+ }
13595
+ }
13596
+
12799
13597
  // src/auth/user-manager.ts
12800
13598
  var import_fs6 = __toESM(require("fs"));
12801
13599
  var import_path5 = __toESM(require("path"));