@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.d.mts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1956 -1158
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1956 -1158
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
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
|
|
1979
|
-
handlerParams = { ...params, sql:
|
|
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 (
|
|
1984
|
-
|
|
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/
|
|
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
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
this.
|
|
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
|
-
*
|
|
4220
|
-
* @returns Parsed schema object or null if error occurs
|
|
4351
|
+
* Reset the error log file for a new request
|
|
4221
4352
|
*/
|
|
4222
|
-
|
|
4353
|
+
resetLogFile(requestContext) {
|
|
4354
|
+
if (!this.enabled) return;
|
|
4223
4355
|
try {
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
4356
|
+
if (this.logStream) {
|
|
4357
|
+
this.logStream.end();
|
|
4358
|
+
this.logStream = null;
|
|
4228
4359
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
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
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
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
|
-
|
|
4248
|
-
return null;
|
|
4375
|
+
console.error("[UserPromptErrorLogger] Failed to reset log file:", error);
|
|
4249
4376
|
}
|
|
4250
4377
|
}
|
|
4251
4378
|
/**
|
|
4252
|
-
*
|
|
4253
|
-
* @returns Cached schema or freshly loaded schema
|
|
4379
|
+
* Log a JSON parse error with the raw string that failed
|
|
4254
4380
|
*/
|
|
4255
|
-
|
|
4256
|
-
if (this.
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
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
|
-
*
|
|
4263
|
-
* @returns Formatted schema documentation string
|
|
4404
|
+
* Log a general error with full details
|
|
4264
4405
|
*/
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
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
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
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
|
-
|
|
4430
|
+
entry += `--------------------------------------------------------------------------------
|
|
4431
|
+
|
|
4432
|
+
`;
|
|
4433
|
+
this.write(entry);
|
|
4434
|
+
console.error(`[UserPromptError] ${context}: ${errorMessage}`);
|
|
4324
4435
|
}
|
|
4325
4436
|
/**
|
|
4326
|
-
*
|
|
4437
|
+
* Log a SQL query error with the full query
|
|
4327
4438
|
*/
|
|
4328
|
-
|
|
4329
|
-
this.
|
|
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
|
-
*
|
|
4333
|
-
* @param filePath - Path to the schema file
|
|
4461
|
+
* Log an LLM API error
|
|
4334
4462
|
*/
|
|
4335
|
-
|
|
4336
|
-
this.
|
|
4337
|
-
this.
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
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
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
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.
|
|
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
|
|
4498
|
+
* Log tool execution error
|
|
4465
4499
|
*/
|
|
4466
|
-
|
|
4500
|
+
logToolError(toolName, toolInput, error) {
|
|
4467
4501
|
if (!this.enabled) return;
|
|
4468
|
-
this.
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
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
|
-
|
|
4507
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] TOOL EXECUTION ERROR
|
|
4510
4508
|
--------------------------------------------------------------------------------
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
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.
|
|
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
|
-
*
|
|
4540
|
-
* Call this at the start of each USER_PROMPT_REQ
|
|
4524
|
+
* Write final summary if there were errors
|
|
4541
4525
|
*/
|
|
4542
|
-
|
|
4543
|
-
if (!this.enabled) return;
|
|
4544
|
-
|
|
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
|
-
|
|
4556
|
-
|
|
4557
|
-
Cost: $X.XXXXXX | Time: Xms
|
|
4530
|
+
REQUEST COMPLETED WITH ERRORS
|
|
4531
|
+
Time: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
4558
4532
|
================================================================================
|
|
4559
|
-
|
|
4560
4533
|
`;
|
|
4561
|
-
|
|
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
|
-
*
|
|
4537
|
+
* Check if any errors were logged
|
|
4575
4538
|
*/
|
|
4576
|
-
|
|
4577
|
-
return
|
|
4539
|
+
hadErrors() {
|
|
4540
|
+
return this.hasErrors;
|
|
4578
4541
|
}
|
|
4579
|
-
|
|
4580
|
-
|
|
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/
|
|
4583
|
-
var
|
|
4584
|
-
var
|
|
4585
|
-
var
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
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
|
-
*
|
|
4879
|
+
* Check if the buffer has a callback configured
|
|
4594
4880
|
*/
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
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
|
-
*
|
|
4910
|
+
* Flush the buffer immediately
|
|
4911
|
+
* Call this before tool execution or other operations that need clean output
|
|
4622
4912
|
*/
|
|
4623
|
-
|
|
4624
|
-
|
|
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
|
-
*
|
|
4917
|
+
* Internal flush implementation
|
|
4647
4918
|
*/
|
|
4648
|
-
|
|
4649
|
-
if (
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
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
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
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
|
-
|
|
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(
|
|
4676
|
-
console.error(`[UserPromptError] ${context}: ${errorMessage}`);
|
|
5061
|
+
this.logStream?.write(header);
|
|
4677
5062
|
}
|
|
4678
5063
|
/**
|
|
4679
|
-
*
|
|
5064
|
+
* Calculate cost based on token usage and model
|
|
4680
5065
|
*/
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
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
|
|
5085
|
+
* Log an LLM API call
|
|
4704
5086
|
*/
|
|
4705
|
-
|
|
5087
|
+
log(entry) {
|
|
4706
5088
|
if (!this.enabled) return;
|
|
4707
|
-
this.
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
if (
|
|
4720
|
-
const
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
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(
|
|
4737
|
-
console.error(`[UserPromptError] LLM Error (${provider}/${model}): ${errorMessage}`);
|
|
5111
|
+
this.logStream?.write(logLine);
|
|
4738
5112
|
}
|
|
4739
5113
|
/**
|
|
4740
|
-
* Log
|
|
5114
|
+
* Log session summary (call at end of request)
|
|
4741
5115
|
*/
|
|
4742
|
-
|
|
4743
|
-
if (!this.enabled) return;
|
|
4744
|
-
this.
|
|
4745
|
-
const
|
|
4746
|
-
|
|
4747
|
-
|
|
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
|
-
|
|
5130
|
+
SESSION SUMMARY${requestContext ? ` (${requestContext})` : ""}
|
|
4750
5131
|
--------------------------------------------------------------------------------
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
${
|
|
4757
|
-
|
|
4758
|
-
${
|
|
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(
|
|
4763
|
-
console.error(`[UserPromptError] Tool Error (${toolName}): ${errorMessage}`);
|
|
5143
|
+
this.logStream?.write(summary);
|
|
4764
5144
|
}
|
|
4765
5145
|
/**
|
|
4766
|
-
*
|
|
5146
|
+
* Reset session stats (call at start of new user request)
|
|
4767
5147
|
*/
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
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
|
-
|
|
4773
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
5189
|
+
* Get current session stats
|
|
4780
5190
|
*/
|
|
4781
|
-
|
|
4782
|
-
return this.
|
|
5191
|
+
getSessionStats() {
|
|
5192
|
+
return { ...this.sessionStats };
|
|
4783
5193
|
}
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6164
|
+
return {
|
|
5753
6165
|
name: fc.name,
|
|
5754
6166
|
response: { result: resultContent }
|
|
5755
|
-
}
|
|
6167
|
+
};
|
|
5756
6168
|
} catch (error) {
|
|
5757
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
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/
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
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
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
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
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
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
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
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
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
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
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
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
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
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
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
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
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
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
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
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/
|
|
6256
|
-
var
|
|
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
|
-
*
|
|
6259
|
-
* @
|
|
7099
|
+
* Gets the database schema from the schema file
|
|
7100
|
+
* @returns Parsed schema object or null if error occurs
|
|
6260
7101
|
*/
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
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
|
-
*
|
|
7132
|
+
* Gets the cached schema or loads it if not cached
|
|
7133
|
+
* @returns Cached schema or freshly loaded schema
|
|
6284
7134
|
*/
|
|
6285
|
-
|
|
6286
|
-
if (
|
|
6287
|
-
|
|
7135
|
+
getSchema() {
|
|
7136
|
+
if (this.cachedSchema) {
|
|
7137
|
+
return this.cachedSchema;
|
|
7138
|
+
}
|
|
7139
|
+
return this.getDatabaseSchema();
|
|
6288
7140
|
}
|
|
6289
7141
|
/**
|
|
6290
|
-
*
|
|
7142
|
+
* Generates database schema documentation for LLM from Snowflake JSON schema
|
|
7143
|
+
* @returns Formatted schema documentation string
|
|
6291
7144
|
*/
|
|
6292
|
-
|
|
6293
|
-
const
|
|
6294
|
-
|
|
6295
|
-
|
|
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
|
-
*
|
|
7206
|
+
* Clears the cached schema, forcing a reload on next access
|
|
6299
7207
|
*/
|
|
6300
|
-
|
|
6301
|
-
|
|
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
|
-
*
|
|
7212
|
+
* Sets a custom schema file path
|
|
7213
|
+
* @param filePath - Path to the schema file
|
|
6324
7214
|
*/
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
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
|
-
|
|
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/
|
|
6416
|
-
var
|
|
6417
|
-
|
|
7222
|
+
// src/userResponse/knowledge-base.ts
|
|
7223
|
+
var getKnowledgeBase = async ({
|
|
7224
|
+
prompt,
|
|
6418
7225
|
collections,
|
|
6419
|
-
|
|
6420
|
-
similarityThreshold = 0.6
|
|
7226
|
+
topK = 1
|
|
6421
7227
|
}) => {
|
|
6422
7228
|
try {
|
|
6423
|
-
if (!collections || !collections["
|
|
6424
|
-
logger.
|
|
6425
|
-
return
|
|
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
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
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.
|
|
6436
|
-
return
|
|
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
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
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
|
-
|
|
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(`[
|
|
6458
|
-
return
|
|
7248
|
+
logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
|
|
7249
|
+
return "";
|
|
6459
7250
|
}
|
|
6460
7251
|
};
|
|
6461
|
-
var
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
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["
|
|
6476
|
-
logger.warn("[
|
|
6477
|
-
return
|
|
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
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
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
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
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
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
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
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
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
|
-
|
|
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(`[
|
|
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
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
}
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
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
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
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 (
|
|
6584
|
-
|
|
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
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
var
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
var
|
|
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/
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
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
|
-
|
|
6627
|
-
*/
|
|
6628
|
-
hasCallback() {
|
|
6629
|
-
return !!this.callback;
|
|
7351
|
+
if (typeof content === "string") {
|
|
7352
|
+
return content;
|
|
6630
7353
|
}
|
|
6631
|
-
|
|
6632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
this.flushNow();
|
|
7360
|
+
return String(content);
|
|
7361
|
+
}
|
|
7362
|
+
function extractContentBlockText(item) {
|
|
7363
|
+
if (typeof item === "string") {
|
|
7364
|
+
return item;
|
|
6662
7365
|
}
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
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 (
|
|
6672
|
-
|
|
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
|
-
|
|
6689
|
-
if (
|
|
6690
|
-
return
|
|
7378
|
+
function extractObjectText(obj) {
|
|
7379
|
+
if (typeof obj.text === "string") {
|
|
7380
|
+
return obj.text;
|
|
6691
7381
|
}
|
|
6692
|
-
|
|
6693
|
-
|
|
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/
|
|
9187
|
+
// src/userResponse/anthropic.ts
|
|
8510
9188
|
import_dotenv.default.config();
|
|
8511
|
-
var
|
|
9189
|
+
var AnthropicLLM = class extends BaseLLM {
|
|
8512
9190
|
constructor(config) {
|
|
8513
9191
|
super(config);
|
|
8514
9192
|
}
|
|
8515
9193
|
getDefaultModel() {
|
|
8516
|
-
return "
|
|
9194
|
+
return "anthropic/claude-sonnet-4-5-20250929";
|
|
8517
9195
|
}
|
|
8518
9196
|
getDefaultFastModel() {
|
|
8519
|
-
return "
|
|
9197
|
+
return "anthropic/claude-haiku-4-5-20251001";
|
|
8520
9198
|
}
|
|
8521
9199
|
getDefaultApiKey() {
|
|
8522
|
-
return process.env.
|
|
9200
|
+
return process.env.ANTHROPIC_API_KEY;
|
|
8523
9201
|
}
|
|
8524
9202
|
getProviderName() {
|
|
8525
|
-
return "
|
|
9203
|
+
return "Anthropic";
|
|
8526
9204
|
}
|
|
8527
9205
|
};
|
|
8528
|
-
var
|
|
9206
|
+
var anthropicLLM = new AnthropicLLM();
|
|
8529
9207
|
|
|
8530
|
-
// src/userResponse/
|
|
9208
|
+
// src/userResponse/groq.ts
|
|
8531
9209
|
var import_dotenv2 = __toESM(require("dotenv"));
|
|
8532
9210
|
import_dotenv2.default.config();
|
|
8533
|
-
var
|
|
9211
|
+
var GroqLLM = class extends BaseLLM {
|
|
8534
9212
|
constructor(config) {
|
|
8535
9213
|
super(config);
|
|
8536
9214
|
}
|
|
8537
9215
|
getDefaultModel() {
|
|
8538
|
-
return "
|
|
9216
|
+
return "groq/openai/gpt-oss-120b";
|
|
8539
9217
|
}
|
|
8540
9218
|
getDefaultFastModel() {
|
|
8541
|
-
return "
|
|
9219
|
+
return "groq/llama-3.1-8b-instant";
|
|
8542
9220
|
}
|
|
8543
9221
|
getDefaultApiKey() {
|
|
8544
|
-
return process.env.
|
|
9222
|
+
return process.env.GROQ_API_KEY;
|
|
8545
9223
|
}
|
|
8546
9224
|
getProviderName() {
|
|
8547
|
-
return "
|
|
9225
|
+
return "Groq";
|
|
8548
9226
|
}
|
|
8549
9227
|
};
|
|
8550
|
-
var
|
|
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/
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
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
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
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
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
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
|
|
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"));
|