@superatomai/sdk-node 0.0.61 → 0.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3124,7 +3124,136 @@ var ThreadManager = class _ThreadManager {
3124
3124
  }
3125
3125
  };
3126
3126
 
3127
+ // src/utils/query-cache.ts
3128
+ init_logger();
3129
+ var QueryCache = class {
3130
+ constructor() {
3131
+ this.cache = /* @__PURE__ */ new Map();
3132
+ this.ttlMs = 5 * 60 * 1e3;
3133
+ // Default: 5 minutes
3134
+ this.cleanupInterval = null;
3135
+ this.startCleanup();
3136
+ }
3137
+ /**
3138
+ * Set the cache TTL (Time To Live)
3139
+ * @param minutes - TTL in minutes (default: 5)
3140
+ */
3141
+ setTTL(minutes) {
3142
+ this.ttlMs = minutes * 60 * 1e3;
3143
+ logger.info(`[QueryCache] TTL set to ${minutes} minutes`);
3144
+ }
3145
+ /**
3146
+ * Get the current TTL in minutes
3147
+ */
3148
+ getTTL() {
3149
+ return this.ttlMs / 60 / 1e3;
3150
+ }
3151
+ /**
3152
+ * Store query result in cache
3153
+ * Key is the exact query string (or JSON for parameterized queries)
3154
+ */
3155
+ set(query, data) {
3156
+ this.cache.set(query, {
3157
+ query,
3158
+ data,
3159
+ timestamp: Date.now()
3160
+ });
3161
+ logger.debug(`[QueryCache] Stored result for query (${query.substring(0, 50)}...)`);
3162
+ }
3163
+ /**
3164
+ * Get cached result if exists and not expired
3165
+ */
3166
+ get(query) {
3167
+ const entry = this.cache.get(query);
3168
+ if (!entry) {
3169
+ return null;
3170
+ }
3171
+ if (Date.now() - entry.timestamp > this.ttlMs) {
3172
+ this.cache.delete(query);
3173
+ logger.debug(`[QueryCache] Entry expired for query (${query.substring(0, 50)}...)`);
3174
+ return null;
3175
+ }
3176
+ logger.info(`[QueryCache] Cache HIT for query (${query.substring(0, 50)}...)`);
3177
+ return entry.data;
3178
+ }
3179
+ /**
3180
+ * Check if query exists in cache (not expired)
3181
+ */
3182
+ has(query) {
3183
+ return this.get(query) !== null;
3184
+ }
3185
+ /**
3186
+ * Remove a specific query from cache
3187
+ */
3188
+ delete(query) {
3189
+ this.cache.delete(query);
3190
+ }
3191
+ /**
3192
+ * Clear all cached entries
3193
+ */
3194
+ clear() {
3195
+ this.cache.clear();
3196
+ logger.info("[QueryCache] Cache cleared");
3197
+ }
3198
+ /**
3199
+ * Get cache statistics
3200
+ */
3201
+ getStats() {
3202
+ let oldestTimestamp = null;
3203
+ for (const entry of this.cache.values()) {
3204
+ if (oldestTimestamp === null || entry.timestamp < oldestTimestamp) {
3205
+ oldestTimestamp = entry.timestamp;
3206
+ }
3207
+ }
3208
+ return {
3209
+ size: this.cache.size,
3210
+ oldestEntryAge: oldestTimestamp ? Date.now() - oldestTimestamp : null
3211
+ };
3212
+ }
3213
+ /**
3214
+ * Start periodic cleanup of expired entries
3215
+ */
3216
+ startCleanup() {
3217
+ this.cleanupInterval = setInterval(() => {
3218
+ const now = Date.now();
3219
+ let expiredCount = 0;
3220
+ for (const [key, entry] of this.cache.entries()) {
3221
+ if (now - entry.timestamp > this.ttlMs) {
3222
+ this.cache.delete(key);
3223
+ expiredCount++;
3224
+ }
3225
+ }
3226
+ if (expiredCount > 0) {
3227
+ logger.debug(`[QueryCache] Cleaned up ${expiredCount} expired entries`);
3228
+ }
3229
+ }, 2 * 60 * 1e3);
3230
+ }
3231
+ /**
3232
+ * Stop cleanup interval (for graceful shutdown)
3233
+ */
3234
+ destroy() {
3235
+ if (this.cleanupInterval) {
3236
+ clearInterval(this.cleanupInterval);
3237
+ this.cleanupInterval = null;
3238
+ }
3239
+ this.cache.clear();
3240
+ }
3241
+ };
3242
+ var queryCache = new QueryCache();
3243
+
3127
3244
  // src/handlers/data-request.ts
3245
+ function getQueryCacheKey(query) {
3246
+ if (typeof query === "string") {
3247
+ return query;
3248
+ } else if (query?.sql) {
3249
+ const values = query.values || query.params;
3250
+ if (values && Object.keys(values).length > 0) {
3251
+ return JSON.stringify({ sql: query.sql, values });
3252
+ }
3253
+ return query.sql;
3254
+ }
3255
+ return "";
3256
+ }
3128
3257
  async function handleDataRequest(data, collections, sendMessage) {
3129
3258
  try {
3130
3259
  const dataRequest = DataRequestMessageSchema.parse(data);
@@ -3143,10 +3272,37 @@ async function handleDataRequest(data, collections, sendMessage) {
3143
3272
  return;
3144
3273
  }
3145
3274
  const startTime = performance.now();
3146
- const handler = collections[collection][op];
3147
- const result = await handler(params || {});
3275
+ let result;
3276
+ let fromCache = false;
3277
+ if (collection === "database" && op === "execute" && params?.sql) {
3278
+ const cacheKey = getQueryCacheKey(params.sql);
3279
+ if (cacheKey) {
3280
+ const cachedResult = queryCache.get(cacheKey);
3281
+ if (cachedResult !== null) {
3282
+ result = cachedResult;
3283
+ fromCache = true;
3284
+ logger.info(`[QueryCache] Returning cached result for database.execute`);
3285
+ }
3286
+ }
3287
+ }
3288
+ if (!fromCache) {
3289
+ const handler = collections[collection][op];
3290
+ let handlerParams = params || {};
3291
+ if (collection === "database" && op === "execute" && params?.sql && typeof params.sql !== "string") {
3292
+ const cacheKey = getQueryCacheKey(params.sql);
3293
+ handlerParams = { ...params, sql: cacheKey };
3294
+ logger.debug(`[data-request] Converted object query to JSON string for database handler`);
3295
+ }
3296
+ result = await handler(handlerParams);
3297
+ if (collection === "database" && op === "execute" && params?.sql && result) {
3298
+ const cacheKey = getQueryCacheKey(params.sql);
3299
+ if (cacheKey) {
3300
+ queryCache.set(cacheKey, result);
3301
+ }
3302
+ }
3303
+ }
3148
3304
  const executionMs = Math.round(performance.now() - startTime);
3149
- logger.info(`Executed ${collection}.${op} in ${executionMs}ms`);
3305
+ logger.info(`Executed ${collection}.${op} in ${executionMs}ms${fromCache ? " (from cache)" : ""}`);
3150
3306
  if (SA_RUNTIME && typeof SA_RUNTIME === "object" && "uiBlockId" in SA_RUNTIME) {
3151
3307
  const uiBlockId = SA_RUNTIME.uiBlockId;
3152
3308
  const threadId = SA_RUNTIME.threadId;
@@ -5960,6 +6116,7 @@ var BaseLLM = class {
5960
6116
  this.defaultLimit = config?.defaultLimit || 50;
5961
6117
  this.apiKey = config?.apiKey;
5962
6118
  this.modelStrategy = config?.modelStrategy || "fast";
6119
+ this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
5963
6120
  }
5964
6121
  /**
5965
6122
  * Get the appropriate model based on task type and model strategy
@@ -5992,6 +6149,26 @@ var BaseLLM = class {
5992
6149
  getModelStrategy() {
5993
6150
  return this.modelStrategy;
5994
6151
  }
6152
+ /**
6153
+ * Set the conversation similarity threshold at runtime
6154
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
6155
+ */
6156
+ setConversationSimilarityThreshold(threshold) {
6157
+ if (threshold < 0 || threshold > 1) {
6158
+ logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default 0.8`);
6159
+ this.conversationSimilarityThreshold = 0.8;
6160
+ return;
6161
+ }
6162
+ this.conversationSimilarityThreshold = threshold;
6163
+ logger.info(`[${this.getProviderName()}] Conversation similarity threshold set to: ${threshold}`);
6164
+ }
6165
+ /**
6166
+ * Get the current conversation similarity threshold
6167
+ * @returns The current threshold value
6168
+ */
6169
+ getConversationSimilarityThreshold() {
6170
+ return this.conversationSimilarityThreshold;
6171
+ }
5995
6172
  /**
5996
6173
  * Get the API key (from instance, parameter, or environment)
5997
6174
  */
@@ -6018,6 +6195,96 @@ var BaseLLM = class {
6018
6195
  }
6019
6196
  return false;
6020
6197
  }
6198
+ /**
6199
+ * Get the cache key for a query (the exact sql param that would be sent to execute)
6200
+ * This ensures the cache key matches what the frontend will send
6201
+ * Used for both caching and internal deduplication
6202
+ */
6203
+ getQueryCacheKey(query) {
6204
+ if (typeof query === "string") {
6205
+ return query;
6206
+ } else if (query?.sql) {
6207
+ const values = query.values || query.params;
6208
+ if (values && Object.keys(values).length > 0) {
6209
+ return JSON.stringify({ sql: query.sql, values });
6210
+ } else {
6211
+ return query.sql;
6212
+ }
6213
+ }
6214
+ return "";
6215
+ }
6216
+ /**
6217
+ * Execute a query against the database for validation and caching
6218
+ * @param query - The SQL query to execute (string or object with sql/values)
6219
+ * @param collections - Collections object containing database execute function
6220
+ * @returns Object with result data and cache key
6221
+ * @throws Error if query execution fails
6222
+ */
6223
+ async executeQueryForValidation(query, collections) {
6224
+ const cacheKey = this.getQueryCacheKey(query);
6225
+ if (!cacheKey) {
6226
+ throw new Error("Invalid query format: expected string or object with sql property");
6227
+ }
6228
+ const result = await collections["database"]["execute"]({ sql: cacheKey });
6229
+ return { result, cacheKey };
6230
+ }
6231
+ /**
6232
+ * Request the LLM to fix a failed SQL query
6233
+ * @param failedQuery - The query that failed execution
6234
+ * @param errorMessage - The error message from the failed execution
6235
+ * @param componentContext - Context about the component (name, type, title)
6236
+ * @param apiKey - Optional API key
6237
+ * @returns Fixed query string
6238
+ */
6239
+ async requestQueryFix(failedQuery, errorMessage, componentContext, apiKey) {
6240
+ const schemaDoc = schema.generateSchemaDocumentation();
6241
+ const databaseRules = await promptLoader.loadDatabaseRules();
6242
+ const prompt = `You are a SQL expert. Fix the following SQL query that failed execution.
6243
+
6244
+ ## Database Schema
6245
+ ${schemaDoc}
6246
+
6247
+ ## Database-Specific SQL Rules
6248
+ ${databaseRules}
6249
+
6250
+ ## Component Context
6251
+ - Component Name: ${componentContext.name}
6252
+ - Component Type: ${componentContext.type}
6253
+ - Title: ${componentContext.title || "N/A"}
6254
+
6255
+ ## Failed Query
6256
+ \`\`\`sql
6257
+ ${failedQuery}
6258
+ \`\`\`
6259
+
6260
+ ## Error Message
6261
+ ${errorMessage}
6262
+
6263
+ ## Instructions
6264
+ 1. Analyze the error message and identify what caused the query to fail
6265
+ 2. Fix the query to resolve the error while preserving the original intent
6266
+ 3. Ensure the fixed query follows the database-specific SQL rules above
6267
+ 4. Return ONLY the fixed SQL query, no explanations or markdown
6268
+
6269
+ Fixed SQL query:`;
6270
+ const response = await LLM.text(
6271
+ {
6272
+ sys: "You are a SQL expert. Return only the fixed SQL query with no additional text, explanations, or markdown formatting.",
6273
+ user: prompt
6274
+ },
6275
+ {
6276
+ model: this.getModelForTask("simple"),
6277
+ maxTokens: 2048,
6278
+ temperature: 0.1,
6279
+ apiKey: this.getApiKey(apiKey)
6280
+ }
6281
+ );
6282
+ let fixedQuery = response.trim();
6283
+ fixedQuery = fixedQuery.replace(/^```sql\s*/i, "").replace(/\s*```$/i, "");
6284
+ fixedQuery = fixedQuery.replace(/^```\s*/i, "").replace(/\s*```$/i, "");
6285
+ const { query: validatedQuery } = validateAndFixSqlQuery(fixedQuery);
6286
+ return validatedQuery;
6287
+ }
6021
6288
  /**
6022
6289
  * Match components from text response suggestions and generate follow-up questions
6023
6290
  * Takes a text response with component suggestions (c1:type format) and matches with available components
@@ -6378,10 +6645,34 @@ ${executedToolsText}`);
6378
6645
  }
6379
6646
  };
6380
6647
  }).filter(Boolean);
6648
+ let validatedComponents = finalComponents;
6649
+ if (collections?.["database"]?.["execute"]) {
6650
+ logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
6651
+ logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
6652
+ try {
6653
+ const validationResult = await this.validateAndRetryComponentQueries(
6654
+ finalComponents,
6655
+ collections,
6656
+ apiKey,
6657
+ logCollector
6658
+ );
6659
+ validatedComponents = validationResult.components;
6660
+ const queriedComponents = finalComponents.filter((c) => c.props?.query);
6661
+ const validatedQueries = validatedComponents.filter((c) => c.props?.query);
6662
+ logger.info(`[${this.getProviderName()}] Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6663
+ logCollector?.info(`Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6664
+ } catch (validationError) {
6665
+ const validationErrorMsg = validationError instanceof Error ? validationError.message : String(validationError);
6666
+ logger.error(`[${this.getProviderName()}] Query validation error: ${validationErrorMsg}`);
6667
+ logCollector?.error(`Query validation error: ${validationErrorMsg}`);
6668
+ }
6669
+ } else {
6670
+ logger.debug(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
6671
+ }
6381
6672
  const methodDuration = Date.now() - methodStartTime;
6382
- logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${finalComponents.length} | actions: ${actions.length}`);
6673
+ logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${validatedComponents.length} | actions: ${actions.length}`);
6383
6674
  return {
6384
- components: finalComponents,
6675
+ components: validatedComponents,
6385
6676
  layoutTitle,
6386
6677
  layoutDescription,
6387
6678
  actions
@@ -6399,6 +6690,117 @@ ${executedToolsText}`);
6399
6690
  };
6400
6691
  }
6401
6692
  }
6693
+ /**
6694
+ * Validate component queries against the database and retry with LLM fixes if they fail
6695
+ * @param components - Array of components with potential queries
6696
+ * @param collections - Collections object containing database execute function
6697
+ * @param apiKey - Optional API key for LLM calls
6698
+ * @param logCollector - Optional log collector for logging
6699
+ * @returns Object with validated components and a map of query results
6700
+ */
6701
+ async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
6702
+ const MAX_RETRIES = 3;
6703
+ const queryResults = /* @__PURE__ */ new Map();
6704
+ const validatedComponents = [];
6705
+ const queryAttempts = /* @__PURE__ */ new Map();
6706
+ const queryValidationStatus = /* @__PURE__ */ new Map();
6707
+ for (const component of components) {
6708
+ const query = component.props?.query;
6709
+ if (!query) {
6710
+ validatedComponents.push(component);
6711
+ continue;
6712
+ }
6713
+ const queryKey = this.getQueryCacheKey(query);
6714
+ const queryStr = typeof query === "string" ? query : query?.sql || "";
6715
+ if (queryValidationStatus.has(queryKey)) {
6716
+ if (queryValidationStatus.get(queryKey)) {
6717
+ validatedComponents.push(component);
6718
+ if (queryResults.has(queryKey)) {
6719
+ queryResults.set(`${component.id}:${queryKey}`, queryResults.get(queryKey));
6720
+ }
6721
+ } else {
6722
+ logger.warn(`[${this.getProviderName()}] Component ${component.name} has previously failed query, keeping as-is`);
6723
+ validatedComponents.push(component);
6724
+ }
6725
+ continue;
6726
+ }
6727
+ let attempts = queryAttempts.get(queryKey) || 0;
6728
+ let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
6729
+ let currentQueryStr = queryStr;
6730
+ let validated = false;
6731
+ let lastError = "";
6732
+ logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
6733
+ while (attempts < MAX_RETRIES && !validated) {
6734
+ attempts++;
6735
+ queryAttempts.set(queryKey, attempts);
6736
+ try {
6737
+ logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
6738
+ const { result, cacheKey } = await this.executeQueryForValidation(currentQuery, collections);
6739
+ validated = true;
6740
+ queryValidationStatus.set(queryKey, true);
6741
+ queryResults.set(queryKey, result);
6742
+ queryResults.set(`${component.id}:${queryKey}`, result);
6743
+ queryCache.set(cacheKey, result);
6744
+ logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
6745
+ logCollector?.info(`\u2713 Query validated for ${component.name}`);
6746
+ if (currentQueryStr !== queryStr) {
6747
+ component.props = {
6748
+ ...component.props,
6749
+ query: typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr }
6750
+ };
6751
+ logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
6752
+ }
6753
+ } catch (error) {
6754
+ lastError = error instanceof Error ? error.message : String(error);
6755
+ logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
6756
+ logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
6757
+ if (attempts >= MAX_RETRIES) {
6758
+ logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, keeping original component`);
6759
+ logCollector?.error(`Max retries reached for ${component.name}, query may fail at runtime`);
6760
+ queryValidationStatus.set(queryKey, false);
6761
+ break;
6762
+ }
6763
+ logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
6764
+ logCollector?.info(`Requesting query fix for ${component.name}...`);
6765
+ try {
6766
+ const fixedQueryStr = await this.requestQueryFix(
6767
+ currentQueryStr,
6768
+ lastError,
6769
+ {
6770
+ name: component.name,
6771
+ type: component.type,
6772
+ title: component.props?.title
6773
+ },
6774
+ apiKey
6775
+ );
6776
+ if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
6777
+ logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
6778
+ currentQueryStr = fixedQueryStr;
6779
+ if (typeof currentQuery === "string") {
6780
+ currentQuery = fixedQueryStr;
6781
+ } else {
6782
+ currentQuery = { ...currentQuery, sql: fixedQueryStr };
6783
+ }
6784
+ } else {
6785
+ logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
6786
+ queryValidationStatus.set(queryKey, false);
6787
+ break;
6788
+ }
6789
+ } catch (fixError) {
6790
+ const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
6791
+ logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
6792
+ queryValidationStatus.set(queryKey, false);
6793
+ break;
6794
+ }
6795
+ }
6796
+ }
6797
+ validatedComponents.push(component);
6798
+ }
6799
+ return {
6800
+ components: validatedComponents,
6801
+ queryResults
6802
+ };
6803
+ }
6402
6804
  /**
6403
6805
  * Classify user question into category and detect external tools needed
6404
6806
  * Determines if question is for data analysis, requires external tools, or needs text response
@@ -6580,12 +6982,6 @@ ${executedToolsText}`);
6580
6982
  * This provides conversational text responses instead of component generation
6581
6983
  * Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
6582
6984
  * After generating text response, if components are provided, matches suggested components
6583
- * @param streamCallback - Optional callback function to receive text chunks as they stream
6584
- * @param collections - Collection registry for executing database queries via database.execute
6585
- * @param components - Optional list of available components for matching suggestions
6586
- * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called
6587
- * @param category - Question category ('data_analysis' | 'data_modification' | 'general'). For data_modification, answer component streaming is skipped. For general, component generation is skipped entirely.
6588
- * @param userId - Optional user ID for fetching user-specific knowledge base nodes
6589
6985
  */
6590
6986
  async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
6591
6987
  const methodStartTime = Date.now();
@@ -7275,11 +7671,6 @@ ${errorMsg}
7275
7671
  * - If match found → Adapt UI block parameters and return
7276
7672
  * 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
7277
7673
  * 3. Route appropriately based on category and response mode
7278
- *
7279
- * @param responseMode - 'component' for component generation (default), 'text' for text responses
7280
- * @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
7281
- * @param collections - Collection registry for executing database queries (required for text mode)
7282
- * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called (only for text mode)
7283
7674
  */
7284
7675
  async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
7285
7676
  const startTime = Date.now();
@@ -7293,8 +7684,7 @@ ${errorMsg}
7293
7684
  userPrompt,
7294
7685
  collections,
7295
7686
  userId,
7296
- similarityThreshold: 0.8
7297
- // 80% threshold
7687
+ similarityThreshold: 0.99
7298
7688
  });
7299
7689
  if (conversationMatch) {
7300
7690
  logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
@@ -7445,16 +7835,6 @@ ${errorMsg}
7445
7835
  };
7446
7836
  }) || [];
7447
7837
  }
7448
- if (categoryClassification.category === "general") {
7449
- logger.info(`[${this.getProviderName()}] Routing to general conversation (no database operations)`);
7450
- logCollector?.info("Routing to general conversation...");
7451
- } else if (categoryClassification.category === "data_analysis") {
7452
- logger.info(`[${this.getProviderName()}] Routing to data analysis (SELECT operations)`);
7453
- logCollector?.info("Routing to data analysis...");
7454
- } else if (categoryClassification.category === "data_modification") {
7455
- logger.info(`[${this.getProviderName()}] Routing to data modification (INSERT/UPDATE/DELETE operations)`);
7456
- logCollector?.info("Routing to data modification...");
7457
- }
7458
7838
  const textResponse = await this.generateTextResponse(
7459
7839
  userPrompt,
7460
7840
  apiKey,
@@ -12874,8 +13254,13 @@ var SuperatomSDK = class {
12874
13254
  this.llmProviders = config.LLM_PROVIDERS || getLLMProviders();
12875
13255
  this.databaseType = config.databaseType || "postgresql";
12876
13256
  this.modelStrategy = config.modelStrategy || "fast";
13257
+ this.conversationSimilarityThreshold = config.conversationSimilarityThreshold ?? 0.8;
12877
13258
  this.applyModelStrategy(this.modelStrategy);
12878
- logger.info(`Initializing Superatom SDK v${SDK_VERSION} for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}`);
13259
+ this.applyConversationSimilarityThreshold(this.conversationSimilarityThreshold);
13260
+ if (config.queryCacheTTL !== void 0) {
13261
+ queryCache.setTTL(config.queryCacheTTL);
13262
+ }
13263
+ logger.info(`Initializing Superatom SDK v${SDK_VERSION} for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}, conversation similarity threshold: ${this.conversationSimilarityThreshold}, query cache TTL: ${queryCache.getTTL()} minutes`);
12879
13264
  this.userManager = new UserManager(this.projectId, 5e3);
12880
13265
  this.dashboardManager = new DashboardManager(this.projectId);
12881
13266
  this.reportManager = new ReportManager(this.projectId);
@@ -13288,6 +13673,31 @@ var SuperatomSDK = class {
13288
13673
  getModelStrategy() {
13289
13674
  return this.modelStrategy;
13290
13675
  }
13676
+ /**
13677
+ * Apply conversation similarity threshold to all LLM provider singletons
13678
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
13679
+ */
13680
+ applyConversationSimilarityThreshold(threshold) {
13681
+ anthropicLLM.setConversationSimilarityThreshold(threshold);
13682
+ groqLLM.setConversationSimilarityThreshold(threshold);
13683
+ geminiLLM.setConversationSimilarityThreshold(threshold);
13684
+ openaiLLM.setConversationSimilarityThreshold(threshold);
13685
+ logger.info(`Conversation similarity threshold '${threshold}' applied to all LLM providers`);
13686
+ }
13687
+ /**
13688
+ * Set conversation similarity threshold at runtime
13689
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
13690
+ */
13691
+ setConversationSimilarityThreshold(threshold) {
13692
+ this.conversationSimilarityThreshold = threshold;
13693
+ this.applyConversationSimilarityThreshold(threshold);
13694
+ }
13695
+ /**
13696
+ * Get current conversation similarity threshold
13697
+ */
13698
+ getConversationSimilarityThreshold() {
13699
+ return this.conversationSimilarityThreshold;
13700
+ }
13291
13701
  };
13292
13702
  export {
13293
13703
  BM25L,
@@ -13309,6 +13719,7 @@ export {
13309
13719
  llmUsageLogger,
13310
13720
  logger,
13311
13721
  openaiLLM,
13722
+ queryCache,
13312
13723
  rerankChromaResults,
13313
13724
  rerankConversationResults,
13314
13725
  userPromptErrorLogger