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