@yushaw/sanqian-sdk 0.2.6 → 0.2.8

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
@@ -28,6 +28,11 @@ interface SDKConfig {
28
28
  * If not provided, SDK will search in standard installation locations
29
29
  */
30
30
  sanqianPath?: string;
31
+ /**
32
+ * Enable debug logging (default: false)
33
+ * When enabled, SDK will output detailed logs to console
34
+ */
35
+ debug?: boolean;
31
36
  }
32
37
  interface ToolDefinition {
33
38
  /** Tool name (without app prefix) */
@@ -218,7 +223,17 @@ declare class SanqianSDK {
218
223
  private missedHeartbeats;
219
224
  private static readonly MAX_MISSED_HEARTBEATS;
220
225
  private connectingPromise;
226
+ private reconnectRefCount;
227
+ private launchedBySanqian;
221
228
  private eventListeners;
229
+ /**
230
+ * Log debug message (only when config.debug is true)
231
+ */
232
+ private log;
233
+ /**
234
+ * Log warning message (only when config.debug is true)
235
+ */
236
+ private warn;
222
237
  constructor(config: SDKConfig);
223
238
  /**
224
239
  * Connect to Sanqian
@@ -247,6 +262,23 @@ declare class SanqianSDK {
247
262
  private handleToolCall;
248
263
  private sendToolResult;
249
264
  private handleDisconnect;
265
+ /**
266
+ * Check if auto-reconnect should be enabled
267
+ * Returns true if any component has requested persistent connection
268
+ */
269
+ private shouldAutoReconnect;
270
+ /**
271
+ * Request persistent connection (enables auto-reconnect)
272
+ * Call this when a component needs the connection to stay alive (e.g., chat panel opens)
273
+ * Must be paired with releaseReconnect() when the component no longer needs it
274
+ */
275
+ acquireReconnect(): void;
276
+ /**
277
+ * Release persistent connection request
278
+ * Call this when a component no longer needs the connection (e.g., chat panel closes)
279
+ * When refCount reaches 0, auto-reconnect is disabled
280
+ */
281
+ releaseReconnect(): void;
250
282
  private scheduleReconnect;
251
283
  private stopReconnect;
252
284
  private startHeartbeat;
package/dist/index.d.ts CHANGED
@@ -28,6 +28,11 @@ interface SDKConfig {
28
28
  * If not provided, SDK will search in standard installation locations
29
29
  */
30
30
  sanqianPath?: string;
31
+ /**
32
+ * Enable debug logging (default: false)
33
+ * When enabled, SDK will output detailed logs to console
34
+ */
35
+ debug?: boolean;
31
36
  }
32
37
  interface ToolDefinition {
33
38
  /** Tool name (without app prefix) */
@@ -218,7 +223,17 @@ declare class SanqianSDK {
218
223
  private missedHeartbeats;
219
224
  private static readonly MAX_MISSED_HEARTBEATS;
220
225
  private connectingPromise;
226
+ private reconnectRefCount;
227
+ private launchedBySanqian;
221
228
  private eventListeners;
229
+ /**
230
+ * Log debug message (only when config.debug is true)
231
+ */
232
+ private log;
233
+ /**
234
+ * Log warning message (only when config.debug is true)
235
+ */
236
+ private warn;
222
237
  constructor(config: SDKConfig);
223
238
  /**
224
239
  * Connect to Sanqian
@@ -247,6 +262,23 @@ declare class SanqianSDK {
247
262
  private handleToolCall;
248
263
  private sendToolResult;
249
264
  private handleDisconnect;
265
+ /**
266
+ * Check if auto-reconnect should be enabled
267
+ * Returns true if any component has requested persistent connection
268
+ */
269
+ private shouldAutoReconnect;
270
+ /**
271
+ * Request persistent connection (enables auto-reconnect)
272
+ * Call this when a component needs the connection to stay alive (e.g., chat panel opens)
273
+ * Must be paired with releaseReconnect() when the component no longer needs it
274
+ */
275
+ acquireReconnect(): void;
276
+ /**
277
+ * Release persistent connection request
278
+ * Call this when a component no longer needs the connection (e.g., chat panel closes)
279
+ * When refCount reaches 0, auto-reconnect is disabled
280
+ */
281
+ releaseReconnect(): void;
250
282
  private scheduleReconnect;
251
283
  private stopReconnect;
252
284
  private startHeartbeat;
package/dist/index.js CHANGED
@@ -501,19 +501,54 @@ var SanqianSDK = class _SanqianSDK {
501
501
  static MAX_MISSED_HEARTBEATS = 2;
502
502
  // Connection promise for deduplication (prevents multiple concurrent connect attempts)
503
503
  connectingPromise = null;
504
+ // Reconnect reference count - when > 0, auto-reconnect is enabled
505
+ // This allows multiple components to request persistent connections
506
+ reconnectRefCount = 0;
507
+ // Whether this app was launched by Sanqian (detected via environment variable)
508
+ launchedBySanqian = false;
504
509
  // Event listeners
505
510
  eventListeners = /* @__PURE__ */ new Map();
511
+ // ============================================
512
+ // Debug Logging
513
+ // ============================================
514
+ /**
515
+ * Log debug message (only when config.debug is true)
516
+ */
517
+ log(...args) {
518
+ if (this.config.debug) {
519
+ console.log("[SDK]", ...args);
520
+ }
521
+ }
522
+ /**
523
+ * Log warning message (only when config.debug is true)
524
+ */
525
+ warn(...args) {
526
+ if (this.config.debug) {
527
+ console.warn("[SDK]", ...args);
528
+ }
529
+ }
506
530
  constructor(config) {
507
531
  this.config = {
508
532
  reconnectInterval: 5e3,
509
533
  heartbeatInterval: 3e4,
510
534
  toolExecutionTimeout: 3e4,
535
+ debug: false,
536
+ autoLaunchSanqian: true,
511
537
  ...config
512
538
  };
513
539
  this.discovery = new DiscoveryManager();
514
540
  for (const tool of config.tools) {
515
541
  this.toolHandlers.set(tool.name, tool.handler);
516
542
  }
543
+ this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
544
+ if (this.launchedBySanqian) {
545
+ this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
546
+ setTimeout(() => {
547
+ this.connect().catch((err) => {
548
+ console.error("[SDK] Failed to connect after Sanqian launch:", err);
549
+ });
550
+ }, 0);
551
+ }
517
552
  }
518
553
  // ============================================
519
554
  // Lifecycle
@@ -540,7 +575,7 @@ var SanqianSDK = class _SanqianSDK {
540
575
  this.connectionInfo = info;
541
576
  const url = this.discovery.buildWebSocketUrl(info);
542
577
  return new Promise((resolve, reject) => {
543
- console.log(`[SDK] Connecting to ${url}`);
578
+ this.log(`Connecting to ${url}`);
544
579
  this.ws = new import_ws.default(url);
545
580
  const connectTimeout = setTimeout(() => {
546
581
  reject(createSDKError("CONNECTION_TIMEOUT" /* CONNECTION_TIMEOUT */));
@@ -548,7 +583,7 @@ var SanqianSDK = class _SanqianSDK {
548
583
  }, 1e4);
549
584
  this.ws.on("open", async () => {
550
585
  clearTimeout(connectTimeout);
551
- console.log("[SDK] WebSocket connected");
586
+ this.log("WebSocket connected");
552
587
  this.state.connected = true;
553
588
  this.state.reconnectAttempts = 0;
554
589
  this.emit("connected");
@@ -560,7 +595,7 @@ var SanqianSDK = class _SanqianSDK {
560
595
  }
561
596
  });
562
597
  this.ws.on("close", (code, reason) => {
563
- console.log(`[SDK] WebSocket closed: ${code} ${reason.toString()}`);
598
+ this.log(`WebSocket closed: ${code} ${reason.toString()}`);
564
599
  this.handleDisconnect(reason.toString());
565
600
  });
566
601
  this.ws.on("error", (error) => {
@@ -573,7 +608,7 @@ var SanqianSDK = class _SanqianSDK {
573
608
  const message = JSON.parse(data.toString());
574
609
  this.handleMessage(message);
575
610
  } catch (e) {
576
- console.error("[SDK] Failed to parse message:", e);
611
+ this.warn("Failed to parse message:", e);
577
612
  }
578
613
  });
579
614
  });
@@ -630,7 +665,7 @@ var SanqianSDK = class _SanqianSDK {
630
665
  this.state.registered = true;
631
666
  this.startHeartbeat();
632
667
  this.emit("registered");
633
- console.log(`[SDK] Registered as '${this.config.appName}'`);
668
+ this.log(`Registered as '${this.config.appName}'`);
634
669
  } catch (e) {
635
670
  this.state.registering = false;
636
671
  throw e;
@@ -659,7 +694,7 @@ var SanqianSDK = class _SanqianSDK {
659
694
  this.handleChatStream(message);
660
695
  break;
661
696
  default:
662
- console.warn(`[SDK] Unknown message type: ${type}`);
697
+ this.warn(`Unknown message type: ${type}`);
663
698
  }
664
699
  }
665
700
  handleChatStream(message) {
@@ -667,7 +702,7 @@ var SanqianSDK = class _SanqianSDK {
667
702
  if (!id) return;
668
703
  const handler = this.streamHandlers.get(id);
669
704
  if (!handler) {
670
- console.warn(`[SDK] No stream handler for message ${id}`);
705
+ this.warn(`No stream handler for message ${id}`);
671
706
  return;
672
707
  }
673
708
  switch (event) {
@@ -706,11 +741,11 @@ var SanqianSDK = class _SanqianSDK {
706
741
  }
707
742
  }
708
743
  async handleToolCall(message) {
709
- console.log(`[SDK] handleToolCall received:`, JSON.stringify(message));
744
+ this.log(`handleToolCall received:`, JSON.stringify(message));
710
745
  const { id, call_id, name, arguments: args } = message;
711
746
  const msgId = id || call_id;
712
747
  const toolName = name.includes(":") ? name.split(":")[1] : name;
713
- console.log(`[SDK] Looking for handler: '${toolName}', available handlers:`, Array.from(this.toolHandlers.keys()));
748
+ this.log(`Looking for handler: '${toolName}', available handlers:`, Array.from(this.toolHandlers.keys()));
714
749
  const handler = this.toolHandlers.get(toolName);
715
750
  this.emit("tool_call", { name: toolName, arguments: args });
716
751
  if (!handler) {
@@ -718,19 +753,19 @@ var SanqianSDK = class _SanqianSDK {
718
753
  return;
719
754
  }
720
755
  try {
721
- console.log(`[SDK] Executing tool '${toolName}' with args:`, args);
756
+ this.log(`Executing tool '${toolName}' with args:`, args);
722
757
  const result = await Promise.race([
723
758
  handler(args),
724
759
  this.createTimeout(this.config.toolExecutionTimeout)
725
760
  ]);
726
- console.log(`[SDK] Tool '${toolName}' completed successfully`);
761
+ this.log(`Tool '${toolName}' completed successfully`);
727
762
  await this.sendToolResult(msgId, call_id, true, result);
728
763
  } catch (e) {
729
764
  const errorMessage = e instanceof Error ? e.message : String(e);
730
765
  const errorStack = e instanceof Error ? e.stack : void 0;
731
- console.log(`[SDK] Tool '${toolName}' failed:`, errorMessage);
766
+ this.log(`Tool '${toolName}' failed:`, errorMessage);
732
767
  if (errorStack) {
733
- console.log(`[SDK] Tool error stack:`, errorStack);
768
+ this.log(`Tool error stack:`, errorStack);
734
769
  }
735
770
  await this.sendToolResult(msgId, call_id, false, void 0, errorMessage);
736
771
  }
@@ -744,7 +779,7 @@ var SanqianSDK = class _SanqianSDK {
744
779
  result,
745
780
  error
746
781
  };
747
- console.log(`[SDK] Sending tool_result for ${callId}:`, success ? "success" : `error: ${error}`);
782
+ this.log(`Sending tool_result for ${callId}:`, success ? "success" : `error: ${error}`);
748
783
  this.send(message);
749
784
  }
750
785
  // ============================================
@@ -760,19 +795,48 @@ var SanqianSDK = class _SanqianSDK {
760
795
  }
761
796
  this.pendingRequests.clear();
762
797
  if (reason === "Client disconnect") {
763
- console.log("[SDK] Client disconnected intentionally, skipping auto-reconnect");
798
+ this.log("Client disconnected intentionally, skipping auto-reconnect");
764
799
  return;
765
800
  }
766
801
  if (reason === "Replaced by new connection") {
767
- console.log("[SDK] Connection replaced by newer one, skipping auto-reconnect");
802
+ this.log("Connection replaced by newer one, skipping auto-reconnect");
768
803
  return;
769
804
  }
770
805
  if (this.connectingPromise) {
771
- console.log("[SDK] Connection attempt already in progress, skipping auto-reconnect");
806
+ this.log("Connection attempt already in progress, skipping auto-reconnect");
807
+ return;
808
+ }
809
+ if (!this.shouldAutoReconnect()) {
810
+ this.log("No components require reconnection (refCount=0), skipping auto-reconnect");
772
811
  return;
773
812
  }
774
813
  this.scheduleReconnect();
775
814
  }
815
+ /**
816
+ * Check if auto-reconnect should be enabled
817
+ * Returns true if any component has requested persistent connection
818
+ */
819
+ shouldAutoReconnect() {
820
+ return this.reconnectRefCount > 0;
821
+ }
822
+ /**
823
+ * Request persistent connection (enables auto-reconnect)
824
+ * Call this when a component needs the connection to stay alive (e.g., chat panel opens)
825
+ * Must be paired with releaseReconnect() when the component no longer needs it
826
+ */
827
+ acquireReconnect() {
828
+ this.reconnectRefCount++;
829
+ this.log(`acquireReconnect: refCount=${this.reconnectRefCount}`);
830
+ }
831
+ /**
832
+ * Release persistent connection request
833
+ * Call this when a component no longer needs the connection (e.g., chat panel closes)
834
+ * When refCount reaches 0, auto-reconnect is disabled
835
+ */
836
+ releaseReconnect() {
837
+ this.reconnectRefCount = Math.max(0, this.reconnectRefCount - 1);
838
+ this.log(`releaseReconnect: refCount=${this.reconnectRefCount}`);
839
+ }
776
840
  scheduleReconnect() {
777
841
  if (this.reconnectTimer) return;
778
842
  const baseDelay = Math.min(
@@ -782,7 +846,7 @@ var SanqianSDK = class _SanqianSDK {
782
846
  );
783
847
  const jitter = Math.random() * 500;
784
848
  const delay = baseDelay + jitter;
785
- console.log(`[SDK] Scheduling reconnect in ${Math.round(delay)}ms (attempt ${this.state.reconnectAttempts + 1})`);
849
+ this.log(`Scheduling reconnect in ${Math.round(delay)}ms (attempt ${this.state.reconnectAttempts + 1})`);
786
850
  this.reconnectTimer = setTimeout(async () => {
787
851
  this.reconnectTimer = null;
788
852
  this.state.reconnectAttempts++;
@@ -812,9 +876,9 @@ var SanqianSDK = class _SanqianSDK {
812
876
  this.heartbeatTimer = setInterval(() => {
813
877
  if (this.heartbeatAckPending) {
814
878
  this.missedHeartbeats++;
815
- console.warn(`[SDK] Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`);
879
+ this.warn(`Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`);
816
880
  if (this.missedHeartbeats >= _SanqianSDK.MAX_MISSED_HEARTBEATS) {
817
- console.error("[SDK] Too many missed heartbeat acks, connection may be dead");
881
+ this.warn("Too many missed heartbeat acks, connection may be dead");
818
882
  this.ws?.close(4e3, "Heartbeat timeout");
819
883
  return;
820
884
  }
@@ -850,7 +914,7 @@ var SanqianSDK = class _SanqianSDK {
850
914
  throw error;
851
915
  }
852
916
  const data = JSON.stringify(message);
853
- console.log(`[SDK] WebSocket send:`, data.substring(0, 200));
917
+ this.log(`WebSocket send:`, data.substring(0, 200));
854
918
  this.ws.send(data);
855
919
  }
856
920
  sendAndWait(message, id, timeout) {
@@ -911,7 +975,7 @@ var SanqianSDK = class _SanqianSDK {
911
975
  return;
912
976
  }
913
977
  if (this.connectingPromise) {
914
- console.log("[SDK] Connection already in progress, waiting...");
978
+ this.log("Connection already in progress, waiting...");
915
979
  return this.connectingPromise;
916
980
  }
917
981
  this.connectingPromise = this.doFullConnect();
@@ -929,16 +993,16 @@ var SanqianSDK = class _SanqianSDK {
929
993
  * so there's no need for additional launch-in-progress tracking within a single instance.
930
994
  */
931
995
  async doFullConnect() {
932
- console.log("[SDK] Starting full connection flow...");
996
+ this.log("Starting full connection flow...");
933
997
  let info = this.discovery.read();
934
998
  if (!info) {
935
999
  if (this.config.autoLaunchSanqian) {
936
- console.log("[SDK] Sanqian not running, attempting to launch...");
1000
+ this.log("Sanqian not running, attempting to launch...");
937
1001
  const launched = this.discovery.launchSanqian(this.config.sanqianPath);
938
1002
  if (!launched) {
939
1003
  throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
940
1004
  }
941
- console.log("[SDK] Sanqian launch initiated, waiting for startup...");
1005
+ this.log("Sanqian launch initiated, waiting for startup...");
942
1006
  info = await this.waitForSanqianStartup();
943
1007
  } else {
944
1008
  throw createSDKError("NOT_RUNNING" /* NOT_RUNNING */);
@@ -955,7 +1019,7 @@ var SanqianSDK = class _SanqianSDK {
955
1019
  while (Date.now() - startTime < timeout) {
956
1020
  const info = this.discovery.read();
957
1021
  if (info) {
958
- console.log("[SDK] Sanqian started, connection info available");
1022
+ this.log("Sanqian started, connection info available");
959
1023
  return info;
960
1024
  }
961
1025
  await new Promise((resolve) => setTimeout(resolve, pollInterval));