@superatomai/sdk-node 0.0.29 → 0.0.31

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 CHANGED
@@ -1650,6 +1650,10 @@ declare class SuperatomSDK {
1650
1650
  private userManager;
1651
1651
  private dashboardManager;
1652
1652
  private reportManager;
1653
+ private pingInterval;
1654
+ private lastPong;
1655
+ private readonly PING_INTERVAL_MS;
1656
+ private readonly PONG_TIMEOUT_MS;
1653
1657
  constructor(config: SuperatomSDKConfig);
1654
1658
  /**
1655
1659
  * Initialize PromptLoader and load prompts into memory
@@ -1717,6 +1721,19 @@ declare class SuperatomSDK {
1717
1721
  */
1718
1722
  addCollection<TParams = any, TResult = any>(collectionName: string, operation: CollectionOperation | string, handler: CollectionHandler<TParams, TResult>): void;
1719
1723
  private handleReconnect;
1724
+ /**
1725
+ * Start heartbeat to keep WebSocket connection alive
1726
+ * Sends PING every 3 minutes to prevent idle timeout from cloud infrastructure
1727
+ */
1728
+ private startHeartbeat;
1729
+ /**
1730
+ * Stop the heartbeat interval
1731
+ */
1732
+ private stopHeartbeat;
1733
+ /**
1734
+ * Handle PONG response from server
1735
+ */
1736
+ private handlePong;
1720
1737
  private storeComponents;
1721
1738
  /**
1722
1739
  * Set tools for the SDK instance
package/dist/index.d.ts CHANGED
@@ -1650,6 +1650,10 @@ declare class SuperatomSDK {
1650
1650
  private userManager;
1651
1651
  private dashboardManager;
1652
1652
  private reportManager;
1653
+ private pingInterval;
1654
+ private lastPong;
1655
+ private readonly PING_INTERVAL_MS;
1656
+ private readonly PONG_TIMEOUT_MS;
1653
1657
  constructor(config: SuperatomSDKConfig);
1654
1658
  /**
1655
1659
  * Initialize PromptLoader and load prompts into memory
@@ -1717,6 +1721,19 @@ declare class SuperatomSDK {
1717
1721
  */
1718
1722
  addCollection<TParams = any, TResult = any>(collectionName: string, operation: CollectionOperation | string, handler: CollectionHandler<TParams, TResult>): void;
1719
1723
  private handleReconnect;
1724
+ /**
1725
+ * Start heartbeat to keep WebSocket connection alive
1726
+ * Sends PING every 3 minutes to prevent idle timeout from cloud infrastructure
1727
+ */
1728
+ private startHeartbeat;
1729
+ /**
1730
+ * Stop the heartbeat interval
1731
+ */
1732
+ private stopHeartbeat;
1733
+ /**
1734
+ * Handle PONG response from server
1735
+ */
1736
+ private handlePong;
1720
1737
  private storeComponents;
1721
1738
  /**
1722
1739
  * Set tools for the SDK instance
package/dist/index.js CHANGED
@@ -4832,20 +4832,25 @@ ${JSON.stringify(tool.requiredFields || [], null, 2)}`;
4832
4832
  logCollector?.info("Generating text response with query execution capability...");
4833
4833
  const tools = [{
4834
4834
  name: "execute_query",
4835
- description: "Executes a SQL query against the database and returns the results. Use this when the user asks for data. If the query fails, you will receive the error and can retry with a corrected query.",
4835
+ description: "Executes a parameterized SQL query against the database. CRITICAL: NEVER hardcode literal values in WHERE/HAVING conditions - ALWAYS use $paramName placeholders and pass actual values in params object.",
4836
4836
  input_schema: {
4837
4837
  type: "object",
4838
4838
  properties: {
4839
- query: {
4839
+ sql: {
4840
4840
  type: "string",
4841
- description: "The SQL query to execute. Must be valid SQL syntax using table and column names from the schema."
4841
+ description: "SQL query with $paramName placeholders for ALL literal values in WHERE/HAVING conditions. NEVER hardcode values like WHERE status = 'Delivered' - instead use WHERE status = $status. Table names, column names, and SQL keywords stay as-is."
4842
+ },
4843
+ params: {
4844
+ type: "object",
4845
+ description: 'REQUIRED when SQL has WHERE/HAVING conditions. Maps each $paramName placeholder (without $) to its actual value. Pattern: WHERE col = $name \u2192 params: { "name": "value" }. Every placeholder in SQL MUST have a corresponding entry here.',
4846
+ additionalProperties: true
4842
4847
  },
4843
4848
  reasoning: {
4844
4849
  type: "string",
4845
4850
  description: "Brief explanation of what this query does and why it answers the user's question."
4846
4851
  }
4847
4852
  },
4848
- required: ["query"],
4853
+ required: ["sql"],
4849
4854
  additionalProperties: false
4850
4855
  }
4851
4856
  }];
@@ -4940,14 +4945,18 @@ ${JSON.stringify(tool.requiredFields || [], null, 2)}`;
4940
4945
  } : void 0;
4941
4946
  const toolHandler = async (toolName, toolInput) => {
4942
4947
  if (toolName === "execute_query") {
4943
- let query = toolInput.query;
4948
+ let sql = toolInput.sql;
4949
+ const params = toolInput.params || {};
4944
4950
  const reasoning = toolInput.reasoning;
4945
4951
  const { ensureQueryLimit: ensureQueryLimit2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
4946
- query = ensureQueryLimit2(query, 32, 32);
4947
- const queryKey = query.toLowerCase().replace(/\s+/g, " ").trim();
4952
+ sql = ensureQueryLimit2(sql, 32, 32);
4953
+ const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
4948
4954
  const attempts = (queryAttempts.get(queryKey) || 0) + 1;
4949
4955
  queryAttempts.set(queryKey, attempts);
4950
- logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${query.substring(0, 100)}...`);
4956
+ logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
4957
+ if (Object.keys(params).length > 0) {
4958
+ logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
4959
+ }
4951
4960
  if (reasoning) {
4952
4961
  logCollector?.info(`Query reasoning: ${reasoning}`);
4953
4962
  }
@@ -4969,6 +4978,8 @@ Please try rephrasing your question or simplifying your request.
4969
4978
  }
4970
4979
  try {
4971
4980
  if (wrappedStreamCallback) {
4981
+ const paramsDisplay = Object.keys(params).length > 0 ? `
4982
+ **Parameters:** ${JSON.stringify(params)}` : "";
4972
4983
  if (attempts === 1) {
4973
4984
  wrappedStreamCallback(`
4974
4985
 
@@ -4982,8 +4993,8 @@ Please try rephrasing your question or simplifying your request.
4982
4993
  }
4983
4994
  wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
4984
4995
  \`\`\`sql
4985
- ${query}
4986
- \`\`\`
4996
+ ${sql}
4997
+ \`\`\`${paramsDisplay}
4987
4998
 
4988
4999
  `);
4989
5000
  wrappedStreamCallback(`\u26A1 **Executing query...**
@@ -5002,8 +5013,8 @@ ${query}
5002
5013
  }
5003
5014
  wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
5004
5015
  \`\`\`sql
5005
- ${query}
5006
- \`\`\`
5016
+ ${sql}
5017
+ \`\`\`${paramsDisplay}
5007
5018
 
5008
5019
  `);
5009
5020
  wrappedStreamCallback(`\u26A1 **Executing query...**
@@ -5013,13 +5024,14 @@ ${query}
5013
5024
  }
5014
5025
  logCollector?.logQuery(
5015
5026
  `Executing SQL query (attempt ${attempts})`,
5016
- query,
5027
+ { sql, params },
5017
5028
  { reasoning, attempt: attempts }
5018
5029
  );
5019
5030
  if (!collections || !collections["database"] || !collections["database"]["execute"]) {
5020
5031
  throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
5021
5032
  }
5022
- const result2 = await collections["database"]["execute"]({ sql: query });
5033
+ const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
5034
+ const result2 = await collections["database"]["execute"](queryPayload);
5023
5035
  const data = result2?.data || result2;
5024
5036
  const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
5025
5037
  logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
@@ -9781,6 +9793,7 @@ init_logger();
9781
9793
  var SDK_VERSION = "0.0.8";
9782
9794
  var DEFAULT_WS_URL = "wss://ws.superatom.ai/websocket";
9783
9795
  var SuperatomSDK = class {
9796
+ // 3.5 minutes (PING_INTERVAL + 30s grace)
9784
9797
  constructor(config) {
9785
9798
  this.ws = null;
9786
9799
  this.messageHandlers = /* @__PURE__ */ new Map();
@@ -9791,6 +9804,12 @@ var SuperatomSDK = class {
9791
9804
  this.collections = {};
9792
9805
  this.components = [];
9793
9806
  this.tools = [];
9807
+ // Heartbeat properties for keeping WebSocket connection alive
9808
+ this.pingInterval = null;
9809
+ this.lastPong = Date.now();
9810
+ this.PING_INTERVAL_MS = 18e4;
9811
+ // 3 minutes
9812
+ this.PONG_TIMEOUT_MS = 21e4;
9794
9813
  if (config.logLevel) {
9795
9814
  logger.setLogLevel(config.logLevel);
9796
9815
  }
@@ -9904,6 +9923,7 @@ var SuperatomSDK = class {
9904
9923
  this.connected = true;
9905
9924
  this.reconnectAttempts = 0;
9906
9925
  logger.info("WebSocket connected successfully");
9926
+ this.startHeartbeat();
9907
9927
  resolve();
9908
9928
  });
9909
9929
  this.ws.addEventListener("message", (event) => {
@@ -9932,6 +9952,9 @@ var SuperatomSDK = class {
9932
9952
  const message = IncomingMessageSchema.parse(parsed);
9933
9953
  logger.debug("Received message:", message.type);
9934
9954
  switch (message.type) {
9955
+ case "PONG":
9956
+ this.handlePong();
9957
+ break;
9935
9958
  case "DATA_REQ":
9936
9959
  handleDataRequest(parsed, this.collections, (msg) => this.send(msg)).catch((error) => {
9937
9960
  logger.error("Failed to handle data request:", error);
@@ -10054,6 +10077,7 @@ var SuperatomSDK = class {
10054
10077
  * Disconnect from the WebSocket service
10055
10078
  */
10056
10079
  disconnect() {
10080
+ this.stopHeartbeat();
10057
10081
  if (this.ws) {
10058
10082
  this.ws.close();
10059
10083
  this.ws = null;
@@ -10064,6 +10088,7 @@ var SuperatomSDK = class {
10064
10088
  * Cleanup and disconnect - stops reconnection attempts and closes the connection
10065
10089
  */
10066
10090
  async destroy() {
10091
+ this.stopHeartbeat();
10067
10092
  this.maxReconnectAttempts = 0;
10068
10093
  this.reconnectAttempts = 0;
10069
10094
  this.messageHandlers.clear();
@@ -10105,6 +10130,59 @@ var SuperatomSDK = class {
10105
10130
  logger.error("Max reconnection attempts reached");
10106
10131
  }
10107
10132
  }
10133
+ /**
10134
+ * Start heartbeat to keep WebSocket connection alive
10135
+ * Sends PING every 3 minutes to prevent idle timeout from cloud infrastructure
10136
+ */
10137
+ startHeartbeat() {
10138
+ this.stopHeartbeat();
10139
+ this.lastPong = Date.now();
10140
+ this.pingInterval = setInterval(() => {
10141
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
10142
+ logger.warn("WebSocket not open during heartbeat check, stopping heartbeat");
10143
+ this.stopHeartbeat();
10144
+ return;
10145
+ }
10146
+ const timeSinceLastPong = Date.now() - this.lastPong;
10147
+ if (timeSinceLastPong > this.PONG_TIMEOUT_MS) {
10148
+ logger.warn(`No PONG received for ${timeSinceLastPong}ms, connection may be dead. Reconnecting...`);
10149
+ this.stopHeartbeat();
10150
+ this.ws.close();
10151
+ return;
10152
+ }
10153
+ try {
10154
+ const pingMessage = {
10155
+ type: "PING",
10156
+ from: { type: this.type },
10157
+ payload: { timestamp: Date.now() }
10158
+ };
10159
+ this.ws.send(JSON.stringify(pingMessage));
10160
+ logger.debug("Heartbeat PING sent");
10161
+ } catch (error) {
10162
+ logger.error("Failed to send PING:", error);
10163
+ this.stopHeartbeat();
10164
+ this.ws.close();
10165
+ }
10166
+ }, this.PING_INTERVAL_MS);
10167
+ logger.info(`Heartbeat started (PING every ${this.PING_INTERVAL_MS / 1e3}s)`);
10168
+ }
10169
+ /**
10170
+ * Stop the heartbeat interval
10171
+ */
10172
+ stopHeartbeat() {
10173
+ if (this.pingInterval) {
10174
+ clearInterval(this.pingInterval);
10175
+ this.pingInterval = null;
10176
+ logger.debug("Heartbeat stopped");
10177
+ }
10178
+ }
10179
+ /**
10180
+ * Handle PONG response from server
10181
+ */
10182
+ handlePong() {
10183
+ this.lastPong = Date.now();
10184
+ logger.debug("Heartbeat PONG received");
10185
+ }
10108
10186
  storeComponents(components) {
10109
10187
  this.components = components;
10110
10188
  }