@yushaw/sanqian-sdk 0.2.6 → 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
  // ============================================
@@ -717,19 +751,48 @@ var SanqianSDK = class _SanqianSDK {
717
751
  }
718
752
  this.pendingRequests.clear();
719
753
  if (reason === "Client disconnect") {
720
- console.log("[SDK] Client disconnected intentionally, skipping auto-reconnect");
754
+ this.log("Client disconnected intentionally, skipping auto-reconnect");
721
755
  return;
722
756
  }
723
757
  if (reason === "Replaced by new connection") {
724
- console.log("[SDK] Connection replaced by newer one, skipping auto-reconnect");
758
+ this.log("Connection replaced by newer one, skipping auto-reconnect");
725
759
  return;
726
760
  }
727
761
  if (this.connectingPromise) {
728
- 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");
729
767
  return;
730
768
  }
731
769
  this.scheduleReconnect();
732
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
+ }
733
796
  scheduleReconnect() {
734
797
  if (this.reconnectTimer) return;
735
798
  const baseDelay = Math.min(
@@ -739,7 +802,7 @@ var SanqianSDK = class _SanqianSDK {
739
802
  );
740
803
  const jitter = Math.random() * 500;
741
804
  const delay = baseDelay + jitter;
742
- 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})`);
743
806
  this.reconnectTimer = setTimeout(async () => {
744
807
  this.reconnectTimer = null;
745
808
  this.state.reconnectAttempts++;
@@ -769,9 +832,9 @@ var SanqianSDK = class _SanqianSDK {
769
832
  this.heartbeatTimer = setInterval(() => {
770
833
  if (this.heartbeatAckPending) {
771
834
  this.missedHeartbeats++;
772
- console.warn(`[SDK] Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`);
835
+ this.warn(`Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`);
773
836
  if (this.missedHeartbeats >= _SanqianSDK.MAX_MISSED_HEARTBEATS) {
774
- console.error("[SDK] Too many missed heartbeat acks, connection may be dead");
837
+ this.warn("Too many missed heartbeat acks, connection may be dead");
775
838
  this.ws?.close(4e3, "Heartbeat timeout");
776
839
  return;
777
840
  }
@@ -807,7 +870,7 @@ var SanqianSDK = class _SanqianSDK {
807
870
  throw error;
808
871
  }
809
872
  const data = JSON.stringify(message);
810
- console.log(`[SDK] WebSocket send:`, data.substring(0, 200));
873
+ this.log(`WebSocket send:`, data.substring(0, 200));
811
874
  this.ws.send(data);
812
875
  }
813
876
  sendAndWait(message, id, timeout) {
@@ -868,7 +931,7 @@ var SanqianSDK = class _SanqianSDK {
868
931
  return;
869
932
  }
870
933
  if (this.connectingPromise) {
871
- console.log("[SDK] Connection already in progress, waiting...");
934
+ this.log("Connection already in progress, waiting...");
872
935
  return this.connectingPromise;
873
936
  }
874
937
  this.connectingPromise = this.doFullConnect();
@@ -886,16 +949,16 @@ var SanqianSDK = class _SanqianSDK {
886
949
  * so there's no need for additional launch-in-progress tracking within a single instance.
887
950
  */
888
951
  async doFullConnect() {
889
- console.log("[SDK] Starting full connection flow...");
952
+ this.log("Starting full connection flow...");
890
953
  let info = this.discovery.read();
891
954
  if (!info) {
892
955
  if (this.config.autoLaunchSanqian) {
893
- console.log("[SDK] Sanqian not running, attempting to launch...");
956
+ this.log("Sanqian not running, attempting to launch...");
894
957
  const launched = this.discovery.launchSanqian(this.config.sanqianPath);
895
958
  if (!launched) {
896
959
  throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
897
960
  }
898
- console.log("[SDK] Sanqian launch initiated, waiting for startup...");
961
+ this.log("Sanqian launch initiated, waiting for startup...");
899
962
  info = await this.waitForSanqianStartup();
900
963
  } else {
901
964
  throw createSDKError("NOT_RUNNING" /* NOT_RUNNING */);
@@ -912,7 +975,7 @@ var SanqianSDK = class _SanqianSDK {
912
975
  while (Date.now() - startTime < timeout) {
913
976
  const info = this.discovery.read();
914
977
  if (info) {
915
- console.log("[SDK] Sanqian started, connection info available");
978
+ this.log("Sanqian started, connection info available");
916
979
  return info;
917
980
  }
918
981
  await new Promise((resolve) => setTimeout(resolve, pollInterval));