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