@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.mjs CHANGED
@@ -4788,20 +4788,25 @@ ${JSON.stringify(tool.requiredFields || [], null, 2)}`;
4788
4788
  logCollector?.info("Generating text response with query execution capability...");
4789
4789
  const tools = [{
4790
4790
  name: "execute_query",
4791
- 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.",
4791
+ 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.",
4792
4792
  input_schema: {
4793
4793
  type: "object",
4794
4794
  properties: {
4795
- query: {
4795
+ sql: {
4796
4796
  type: "string",
4797
- description: "The SQL query to execute. Must be valid SQL syntax using table and column names from the schema."
4797
+ 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."
4798
+ },
4799
+ params: {
4800
+ type: "object",
4801
+ 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.',
4802
+ additionalProperties: true
4798
4803
  },
4799
4804
  reasoning: {
4800
4805
  type: "string",
4801
4806
  description: "Brief explanation of what this query does and why it answers the user's question."
4802
4807
  }
4803
4808
  },
4804
- required: ["query"],
4809
+ required: ["sql"],
4805
4810
  additionalProperties: false
4806
4811
  }
4807
4812
  }];
@@ -4896,14 +4901,18 @@ ${JSON.stringify(tool.requiredFields || [], null, 2)}`;
4896
4901
  } : void 0;
4897
4902
  const toolHandler = async (toolName, toolInput) => {
4898
4903
  if (toolName === "execute_query") {
4899
- let query = toolInput.query;
4904
+ let sql = toolInput.sql;
4905
+ const params = toolInput.params || {};
4900
4906
  const reasoning = toolInput.reasoning;
4901
4907
  const { ensureQueryLimit: ensureQueryLimit2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
4902
- query = ensureQueryLimit2(query, 32, 32);
4903
- const queryKey = query.toLowerCase().replace(/\s+/g, " ").trim();
4908
+ sql = ensureQueryLimit2(sql, 32, 32);
4909
+ const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
4904
4910
  const attempts = (queryAttempts.get(queryKey) || 0) + 1;
4905
4911
  queryAttempts.set(queryKey, attempts);
4906
- logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${query.substring(0, 100)}...`);
4912
+ logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
4913
+ if (Object.keys(params).length > 0) {
4914
+ logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
4915
+ }
4907
4916
  if (reasoning) {
4908
4917
  logCollector?.info(`Query reasoning: ${reasoning}`);
4909
4918
  }
@@ -4925,6 +4934,8 @@ Please try rephrasing your question or simplifying your request.
4925
4934
  }
4926
4935
  try {
4927
4936
  if (wrappedStreamCallback) {
4937
+ const paramsDisplay = Object.keys(params).length > 0 ? `
4938
+ **Parameters:** ${JSON.stringify(params)}` : "";
4928
4939
  if (attempts === 1) {
4929
4940
  wrappedStreamCallback(`
4930
4941
 
@@ -4938,8 +4949,8 @@ Please try rephrasing your question or simplifying your request.
4938
4949
  }
4939
4950
  wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
4940
4951
  \`\`\`sql
4941
- ${query}
4942
- \`\`\`
4952
+ ${sql}
4953
+ \`\`\`${paramsDisplay}
4943
4954
 
4944
4955
  `);
4945
4956
  wrappedStreamCallback(`\u26A1 **Executing query...**
@@ -4958,8 +4969,8 @@ ${query}
4958
4969
  }
4959
4970
  wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
4960
4971
  \`\`\`sql
4961
- ${query}
4962
- \`\`\`
4972
+ ${sql}
4973
+ \`\`\`${paramsDisplay}
4963
4974
 
4964
4975
  `);
4965
4976
  wrappedStreamCallback(`\u26A1 **Executing query...**
@@ -4969,13 +4980,14 @@ ${query}
4969
4980
  }
4970
4981
  logCollector?.logQuery(
4971
4982
  `Executing SQL query (attempt ${attempts})`,
4972
- query,
4983
+ { sql, params },
4973
4984
  { reasoning, attempt: attempts }
4974
4985
  );
4975
4986
  if (!collections || !collections["database"] || !collections["database"]["execute"]) {
4976
4987
  throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
4977
4988
  }
4978
- const result2 = await collections["database"]["execute"]({ sql: query });
4989
+ const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
4990
+ const result2 = await collections["database"]["execute"](queryPayload);
4979
4991
  const data = result2?.data || result2;
4980
4992
  const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
4981
4993
  logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
@@ -9737,6 +9749,7 @@ init_logger();
9737
9749
  var SDK_VERSION = "0.0.8";
9738
9750
  var DEFAULT_WS_URL = "wss://ws.superatom.ai/websocket";
9739
9751
  var SuperatomSDK = class {
9752
+ // 3.5 minutes (PING_INTERVAL + 30s grace)
9740
9753
  constructor(config) {
9741
9754
  this.ws = null;
9742
9755
  this.messageHandlers = /* @__PURE__ */ new Map();
@@ -9747,6 +9760,12 @@ var SuperatomSDK = class {
9747
9760
  this.collections = {};
9748
9761
  this.components = [];
9749
9762
  this.tools = [];
9763
+ // Heartbeat properties for keeping WebSocket connection alive
9764
+ this.pingInterval = null;
9765
+ this.lastPong = Date.now();
9766
+ this.PING_INTERVAL_MS = 18e4;
9767
+ // 3 minutes
9768
+ this.PONG_TIMEOUT_MS = 21e4;
9750
9769
  if (config.logLevel) {
9751
9770
  logger.setLogLevel(config.logLevel);
9752
9771
  }
@@ -9860,6 +9879,7 @@ var SuperatomSDK = class {
9860
9879
  this.connected = true;
9861
9880
  this.reconnectAttempts = 0;
9862
9881
  logger.info("WebSocket connected successfully");
9882
+ this.startHeartbeat();
9863
9883
  resolve();
9864
9884
  });
9865
9885
  this.ws.addEventListener("message", (event) => {
@@ -9888,6 +9908,9 @@ var SuperatomSDK = class {
9888
9908
  const message = IncomingMessageSchema.parse(parsed);
9889
9909
  logger.debug("Received message:", message.type);
9890
9910
  switch (message.type) {
9911
+ case "PONG":
9912
+ this.handlePong();
9913
+ break;
9891
9914
  case "DATA_REQ":
9892
9915
  handleDataRequest(parsed, this.collections, (msg) => this.send(msg)).catch((error) => {
9893
9916
  logger.error("Failed to handle data request:", error);
@@ -10010,6 +10033,7 @@ var SuperatomSDK = class {
10010
10033
  * Disconnect from the WebSocket service
10011
10034
  */
10012
10035
  disconnect() {
10036
+ this.stopHeartbeat();
10013
10037
  if (this.ws) {
10014
10038
  this.ws.close();
10015
10039
  this.ws = null;
@@ -10020,6 +10044,7 @@ var SuperatomSDK = class {
10020
10044
  * Cleanup and disconnect - stops reconnection attempts and closes the connection
10021
10045
  */
10022
10046
  async destroy() {
10047
+ this.stopHeartbeat();
10023
10048
  this.maxReconnectAttempts = 0;
10024
10049
  this.reconnectAttempts = 0;
10025
10050
  this.messageHandlers.clear();
@@ -10061,6 +10086,59 @@ var SuperatomSDK = class {
10061
10086
  logger.error("Max reconnection attempts reached");
10062
10087
  }
10063
10088
  }
10089
+ /**
10090
+ * Start heartbeat to keep WebSocket connection alive
10091
+ * Sends PING every 3 minutes to prevent idle timeout from cloud infrastructure
10092
+ */
10093
+ startHeartbeat() {
10094
+ this.stopHeartbeat();
10095
+ this.lastPong = Date.now();
10096
+ this.pingInterval = setInterval(() => {
10097
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
10098
+ logger.warn("WebSocket not open during heartbeat check, stopping heartbeat");
10099
+ this.stopHeartbeat();
10100
+ return;
10101
+ }
10102
+ const timeSinceLastPong = Date.now() - this.lastPong;
10103
+ if (timeSinceLastPong > this.PONG_TIMEOUT_MS) {
10104
+ logger.warn(`No PONG received for ${timeSinceLastPong}ms, connection may be dead. Reconnecting...`);
10105
+ this.stopHeartbeat();
10106
+ this.ws.close();
10107
+ return;
10108
+ }
10109
+ try {
10110
+ const pingMessage = {
10111
+ type: "PING",
10112
+ from: { type: this.type },
10113
+ payload: { timestamp: Date.now() }
10114
+ };
10115
+ this.ws.send(JSON.stringify(pingMessage));
10116
+ logger.debug("Heartbeat PING sent");
10117
+ } catch (error) {
10118
+ logger.error("Failed to send PING:", error);
10119
+ this.stopHeartbeat();
10120
+ this.ws.close();
10121
+ }
10122
+ }, this.PING_INTERVAL_MS);
10123
+ logger.info(`Heartbeat started (PING every ${this.PING_INTERVAL_MS / 1e3}s)`);
10124
+ }
10125
+ /**
10126
+ * Stop the heartbeat interval
10127
+ */
10128
+ stopHeartbeat() {
10129
+ if (this.pingInterval) {
10130
+ clearInterval(this.pingInterval);
10131
+ this.pingInterval = null;
10132
+ logger.debug("Heartbeat stopped");
10133
+ }
10134
+ }
10135
+ /**
10136
+ * Handle PONG response from server
10137
+ */
10138
+ handlePong() {
10139
+ this.lastPong = Date.now();
10140
+ logger.debug("Heartbeat PONG received");
10141
+ }
10064
10142
  storeComponents(components) {
10065
10143
  this.components = components;
10066
10144
  }