@yushaw/sanqian-sdk 0.2.5 → 0.2.7

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
@@ -458,19 +458,53 @@ var SanqianSDK = class _SanqianSDK {
458
458
  static MAX_MISSED_HEARTBEATS = 2;
459
459
  // Connection promise for deduplication (prevents multiple concurrent connect attempts)
460
460
  connectingPromise = null;
461
+ // Reconnect reference count - when > 0, auto-reconnect is enabled
462
+ // This allows multiple components to request persistent connections
463
+ reconnectRefCount = 0;
464
+ // Whether this app was launched by Sanqian (detected via environment variable)
465
+ launchedBySanqian = false;
461
466
  // Event listeners
462
467
  eventListeners = /* @__PURE__ */ new Map();
468
+ // ============================================
469
+ // Debug Logging
470
+ // ============================================
471
+ /**
472
+ * Log debug message (only when config.debug is true)
473
+ */
474
+ log(...args) {
475
+ if (this.config.debug) {
476
+ console.log("[SDK]", ...args);
477
+ }
478
+ }
479
+ /**
480
+ * Log warning message (only when config.debug is true)
481
+ */
482
+ warn(...args) {
483
+ if (this.config.debug) {
484
+ console.warn("[SDK]", ...args);
485
+ }
486
+ }
463
487
  constructor(config) {
464
488
  this.config = {
465
489
  reconnectInterval: 5e3,
466
490
  heartbeatInterval: 3e4,
467
491
  toolExecutionTimeout: 3e4,
492
+ debug: false,
468
493
  ...config
469
494
  };
470
495
  this.discovery = new DiscoveryManager();
471
496
  for (const tool of config.tools) {
472
497
  this.toolHandlers.set(tool.name, tool.handler);
473
498
  }
499
+ this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
500
+ if (this.launchedBySanqian) {
501
+ this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
502
+ setTimeout(() => {
503
+ this.connect().catch((err) => {
504
+ console.error("[SDK] Failed to connect after Sanqian launch:", err);
505
+ });
506
+ }, 0);
507
+ }
474
508
  }
475
509
  // ============================================
476
510
  // Lifecycle
@@ -497,7 +531,7 @@ var SanqianSDK = class _SanqianSDK {
497
531
  this.connectionInfo = info;
498
532
  const url = this.discovery.buildWebSocketUrl(info);
499
533
  return new Promise((resolve, reject) => {
500
- console.log(`[SDK] Connecting to ${url}`);
534
+ this.log(`Connecting to ${url}`);
501
535
  this.ws = new WebSocket(url);
502
536
  const connectTimeout = setTimeout(() => {
503
537
  reject(createSDKError("CONNECTION_TIMEOUT" /* CONNECTION_TIMEOUT */));
@@ -505,7 +539,7 @@ var SanqianSDK = class _SanqianSDK {
505
539
  }, 1e4);
506
540
  this.ws.on("open", async () => {
507
541
  clearTimeout(connectTimeout);
508
- console.log("[SDK] WebSocket connected");
542
+ this.log("WebSocket connected");
509
543
  this.state.connected = true;
510
544
  this.state.reconnectAttempts = 0;
511
545
  this.emit("connected");
@@ -517,7 +551,7 @@ var SanqianSDK = class _SanqianSDK {
517
551
  }
518
552
  });
519
553
  this.ws.on("close", (code, reason) => {
520
- console.log(`[SDK] WebSocket closed: ${code} ${reason.toString()}`);
554
+ this.log(`WebSocket closed: ${code} ${reason.toString()}`);
521
555
  this.handleDisconnect(reason.toString());
522
556
  });
523
557
  this.ws.on("error", (error) => {
@@ -530,7 +564,7 @@ var SanqianSDK = class _SanqianSDK {
530
564
  const message = JSON.parse(data.toString());
531
565
  this.handleMessage(message);
532
566
  } catch (e) {
533
- console.error("[SDK] Failed to parse message:", e);
567
+ this.warn("Failed to parse message:", e);
534
568
  }
535
569
  });
536
570
  });
@@ -587,7 +621,7 @@ var SanqianSDK = class _SanqianSDK {
587
621
  this.state.registered = true;
588
622
  this.startHeartbeat();
589
623
  this.emit("registered");
590
- console.log(`[SDK] Registered as '${this.config.appName}'`);
624
+ this.log(`Registered as '${this.config.appName}'`);
591
625
  } catch (e) {
592
626
  this.state.registering = false;
593
627
  throw e;
@@ -616,7 +650,7 @@ var SanqianSDK = class _SanqianSDK {
616
650
  this.handleChatStream(message);
617
651
  break;
618
652
  default:
619
- console.warn(`[SDK] Unknown message type: ${type}`);
653
+ this.warn(`Unknown message type: ${type}`);
620
654
  }
621
655
  }
622
656
  handleChatStream(message) {
@@ -624,7 +658,7 @@ var SanqianSDK = class _SanqianSDK {
624
658
  if (!id) return;
625
659
  const handler = this.streamHandlers.get(id);
626
660
  if (!handler) {
627
- console.warn(`[SDK] No stream handler for message ${id}`);
661
+ this.warn(`No stream handler for message ${id}`);
628
662
  return;
629
663
  }
630
664
  switch (event) {
@@ -663,11 +697,11 @@ var SanqianSDK = class _SanqianSDK {
663
697
  }
664
698
  }
665
699
  async handleToolCall(message) {
666
- console.log(`[SDK] handleToolCall received:`, JSON.stringify(message));
700
+ this.log(`handleToolCall received:`, JSON.stringify(message));
667
701
  const { id, call_id, name, arguments: args } = message;
668
702
  const msgId = id || call_id;
669
703
  const toolName = name.includes(":") ? name.split(":")[1] : name;
670
- console.log(`[SDK] Looking for handler: '${toolName}', available handlers:`, Array.from(this.toolHandlers.keys()));
704
+ this.log(`Looking for handler: '${toolName}', available handlers:`, Array.from(this.toolHandlers.keys()));
671
705
  const handler = this.toolHandlers.get(toolName);
672
706
  this.emit("tool_call", { name: toolName, arguments: args });
673
707
  if (!handler) {
@@ -675,19 +709,19 @@ var SanqianSDK = class _SanqianSDK {
675
709
  return;
676
710
  }
677
711
  try {
678
- console.log(`[SDK] Executing tool '${toolName}' with args:`, args);
712
+ this.log(`Executing tool '${toolName}' with args:`, args);
679
713
  const result = await Promise.race([
680
714
  handler(args),
681
715
  this.createTimeout(this.config.toolExecutionTimeout)
682
716
  ]);
683
- console.log(`[SDK] Tool '${toolName}' completed successfully`);
717
+ this.log(`Tool '${toolName}' completed successfully`);
684
718
  await this.sendToolResult(msgId, call_id, true, result);
685
719
  } catch (e) {
686
720
  const errorMessage = e instanceof Error ? e.message : String(e);
687
721
  const errorStack = e instanceof Error ? e.stack : void 0;
688
- console.log(`[SDK] Tool '${toolName}' failed:`, errorMessage);
722
+ this.log(`Tool '${toolName}' failed:`, errorMessage);
689
723
  if (errorStack) {
690
- console.log(`[SDK] Tool error stack:`, errorStack);
724
+ this.log(`Tool error stack:`, errorStack);
691
725
  }
692
726
  await this.sendToolResult(msgId, call_id, false, void 0, errorMessage);
693
727
  }
@@ -701,7 +735,7 @@ var SanqianSDK = class _SanqianSDK {
701
735
  result,
702
736
  error
703
737
  };
704
- console.log(`[SDK] Sending tool_result for ${callId}:`, success ? "success" : `error: ${error}`);
738
+ this.log(`Sending tool_result for ${callId}:`, success ? "success" : `error: ${error}`);
705
739
  this.send(message);
706
740
  }
707
741
  // ============================================
@@ -716,16 +750,49 @@ var SanqianSDK = class _SanqianSDK {
716
750
  pending.reject(createSDKError("DISCONNECTED" /* DISCONNECTED */));
717
751
  }
718
752
  this.pendingRequests.clear();
753
+ if (reason === "Client disconnect") {
754
+ this.log("Client disconnected intentionally, skipping auto-reconnect");
755
+ return;
756
+ }
719
757
  if (reason === "Replaced by new connection") {
720
- console.log("[SDK] Connection replaced by newer one, skipping auto-reconnect");
758
+ this.log("Connection replaced by newer one, skipping auto-reconnect");
721
759
  return;
722
760
  }
723
761
  if (this.connectingPromise) {
724
- console.log("[SDK] Connection attempt already in progress, skipping auto-reconnect");
762
+ this.log("Connection attempt already in progress, skipping auto-reconnect");
763
+ return;
764
+ }
765
+ if (!this.shouldAutoReconnect()) {
766
+ this.log("No components require reconnection (refCount=0), skipping auto-reconnect");
725
767
  return;
726
768
  }
727
769
  this.scheduleReconnect();
728
770
  }
771
+ /**
772
+ * Check if auto-reconnect should be enabled
773
+ * Returns true if any component has requested persistent connection
774
+ */
775
+ shouldAutoReconnect() {
776
+ return this.reconnectRefCount > 0;
777
+ }
778
+ /**
779
+ * Request persistent connection (enables auto-reconnect)
780
+ * Call this when a component needs the connection to stay alive (e.g., chat panel opens)
781
+ * Must be paired with releaseReconnect() when the component no longer needs it
782
+ */
783
+ acquireReconnect() {
784
+ this.reconnectRefCount++;
785
+ this.log(`acquireReconnect: refCount=${this.reconnectRefCount}`);
786
+ }
787
+ /**
788
+ * Release persistent connection request
789
+ * Call this when a component no longer needs the connection (e.g., chat panel closes)
790
+ * When refCount reaches 0, auto-reconnect is disabled
791
+ */
792
+ releaseReconnect() {
793
+ this.reconnectRefCount = Math.max(0, this.reconnectRefCount - 1);
794
+ this.log(`releaseReconnect: refCount=${this.reconnectRefCount}`);
795
+ }
729
796
  scheduleReconnect() {
730
797
  if (this.reconnectTimer) return;
731
798
  const baseDelay = Math.min(
@@ -735,7 +802,7 @@ var SanqianSDK = class _SanqianSDK {
735
802
  );
736
803
  const jitter = Math.random() * 500;
737
804
  const delay = baseDelay + jitter;
738
- console.log(`[SDK] Scheduling reconnect in ${Math.round(delay)}ms (attempt ${this.state.reconnectAttempts + 1})`);
805
+ this.log(`Scheduling reconnect in ${Math.round(delay)}ms (attempt ${this.state.reconnectAttempts + 1})`);
739
806
  this.reconnectTimer = setTimeout(async () => {
740
807
  this.reconnectTimer = null;
741
808
  this.state.reconnectAttempts++;
@@ -765,9 +832,9 @@ var SanqianSDK = class _SanqianSDK {
765
832
  this.heartbeatTimer = setInterval(() => {
766
833
  if (this.heartbeatAckPending) {
767
834
  this.missedHeartbeats++;
768
- console.warn(`[SDK] Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`);
835
+ this.warn(`Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`);
769
836
  if (this.missedHeartbeats >= _SanqianSDK.MAX_MISSED_HEARTBEATS) {
770
- console.error("[SDK] Too many missed heartbeat acks, connection may be dead");
837
+ this.warn("Too many missed heartbeat acks, connection may be dead");
771
838
  this.ws?.close(4e3, "Heartbeat timeout");
772
839
  return;
773
840
  }
@@ -803,7 +870,7 @@ var SanqianSDK = class _SanqianSDK {
803
870
  throw error;
804
871
  }
805
872
  const data = JSON.stringify(message);
806
- console.log(`[SDK] WebSocket send:`, data.substring(0, 200));
873
+ this.log(`WebSocket send:`, data.substring(0, 200));
807
874
  this.ws.send(data);
808
875
  }
809
876
  sendAndWait(message, id, timeout) {
@@ -864,7 +931,7 @@ var SanqianSDK = class _SanqianSDK {
864
931
  return;
865
932
  }
866
933
  if (this.connectingPromise) {
867
- console.log("[SDK] Connection already in progress, waiting...");
934
+ this.log("Connection already in progress, waiting...");
868
935
  return this.connectingPromise;
869
936
  }
870
937
  this.connectingPromise = this.doFullConnect();
@@ -882,16 +949,16 @@ var SanqianSDK = class _SanqianSDK {
882
949
  * so there's no need for additional launch-in-progress tracking within a single instance.
883
950
  */
884
951
  async doFullConnect() {
885
- console.log("[SDK] Starting full connection flow...");
952
+ this.log("Starting full connection flow...");
886
953
  let info = this.discovery.read();
887
954
  if (!info) {
888
955
  if (this.config.autoLaunchSanqian) {
889
- console.log("[SDK] Sanqian not running, attempting to launch...");
956
+ this.log("Sanqian not running, attempting to launch...");
890
957
  const launched = this.discovery.launchSanqian(this.config.sanqianPath);
891
958
  if (!launched) {
892
959
  throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
893
960
  }
894
- console.log("[SDK] Sanqian launch initiated, waiting for startup...");
961
+ this.log("Sanqian launch initiated, waiting for startup...");
895
962
  info = await this.waitForSanqianStartup();
896
963
  } else {
897
964
  throw createSDKError("NOT_RUNNING" /* NOT_RUNNING */);
@@ -908,7 +975,7 @@ var SanqianSDK = class _SanqianSDK {
908
975
  while (Date.now() - startTime < timeout) {
909
976
  const info = this.discovery.read();
910
977
  if (info) {
911
- console.log("[SDK] Sanqian started, connection info available");
978
+ this.log("Sanqian started, connection info available");
912
979
  return info;
913
980
  }
914
981
  await new Promise((resolve) => setTimeout(resolve, pollInterval));