@yushaw/sanqian-sdk 0.2.10 → 0.2.12

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.js CHANGED
@@ -5,6 +5,12 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
8
14
  var __export = (target, all) => {
9
15
  for (var name in all)
10
16
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,290 +33,308 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
33
  ));
28
34
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
35
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- Conversation: () => Conversation,
34
- DiscoveryManager: () => DiscoveryManager,
35
- ErrorMessages: () => ErrorMessages,
36
- SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
37
- SDKErrorCode: () => SDKErrorCode,
38
- SanqianSDK: () => SanqianSDK,
39
- SanqianSDKError: () => SanqianSDKError,
40
- createSDKError: () => createSDKError
36
+ // node_modules/isomorphic-ws/node.js
37
+ var require_node = __commonJS({
38
+ "node_modules/isomorphic-ws/node.js"(exports2, module2) {
39
+ "use strict";
40
+ module2.exports = require("ws");
41
+ }
41
42
  });
42
- module.exports = __toCommonJS(index_exports);
43
-
44
- // src/client.ts
45
- var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
46
43
 
47
44
  // src/discovery.ts
48
- var import_fs = require("fs");
49
- var import_os = require("os");
50
- var import_path = require("path");
51
- var import_child_process = require("child_process");
52
- var DiscoveryManager = class {
53
- connectionInfo = null;
54
- watcher = null;
55
- onChange = null;
56
- pollInterval = null;
57
- /**
58
- * Cached executable path from last successful connection.json read.
59
- * Persists even when Sanqian is not running, for use in auto-launch.
60
- */
61
- cachedExecutable = null;
62
- /**
63
- * Get the path to connection.json
64
- */
65
- getConnectionFilePath() {
66
- return (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime", "connection.json");
67
- }
68
- /**
69
- * Read and validate connection info
70
- *
71
- * Returns null if:
72
- * - File doesn't exist
73
- * - File is invalid JSON
74
- * - Process is not running
75
- */
76
- read() {
77
- const filePath = this.getConnectionFilePath();
78
- if (!(0, import_fs.existsSync)(filePath)) {
79
- return null;
80
- }
81
- try {
82
- const content = (0, import_fs.readFileSync)(filePath, "utf-8");
83
- const info = JSON.parse(content);
84
- if (!info.port || !info.token || !info.pid) {
85
- return null;
86
- }
87
- if (info.executable) {
88
- this.cachedExecutable = info.executable;
89
- }
90
- if (!this.isProcessRunning(info.pid)) {
91
- return null;
45
+ var discovery_exports = {};
46
+ __export(discovery_exports, {
47
+ DiscoveryManager: () => DiscoveryManager
48
+ });
49
+ var import_fs, import_os, import_path, import_child_process, DiscoveryManager;
50
+ var init_discovery = __esm({
51
+ "src/discovery.ts"() {
52
+ "use strict";
53
+ import_fs = require("fs");
54
+ import_os = require("os");
55
+ import_path = require("path");
56
+ import_child_process = require("child_process");
57
+ DiscoveryManager = class {
58
+ connectionInfo = null;
59
+ watcher = null;
60
+ onChange = null;
61
+ pollInterval = null;
62
+ /**
63
+ * Cached executable path from last successful connection.json read.
64
+ * Persists even when Sanqian is not running, for use in auto-launch.
65
+ */
66
+ cachedExecutable = null;
67
+ /**
68
+ * Get the path to connection.json
69
+ */
70
+ getConnectionFilePath() {
71
+ return (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime", "connection.json");
92
72
  }
93
- this.connectionInfo = info;
94
- return info;
95
- } catch {
96
- return null;
97
- }
98
- }
99
- /**
100
- * Start watching for connection file changes
101
- */
102
- startWatching(onChange) {
103
- this.onChange = onChange;
104
- const dir = (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime");
105
- if (!(0, import_fs.existsSync)(dir)) {
106
- this.pollInterval = setInterval(() => {
107
- if ((0, import_fs.existsSync)(dir)) {
108
- if (this.pollInterval) {
109
- clearInterval(this.pollInterval);
110
- this.pollInterval = null;
73
+ /**
74
+ * Read and validate connection info
75
+ *
76
+ * Returns null if:
77
+ * - File doesn't exist
78
+ * - File is invalid JSON
79
+ * - Process is not running
80
+ */
81
+ read() {
82
+ const filePath = this.getConnectionFilePath();
83
+ if (!(0, import_fs.existsSync)(filePath)) {
84
+ return null;
85
+ }
86
+ try {
87
+ const content = (0, import_fs.readFileSync)(filePath, "utf-8");
88
+ const info = JSON.parse(content);
89
+ if (!info.port || !info.token || !info.pid) {
90
+ return null;
111
91
  }
112
- this.setupWatcher(dir);
92
+ if (info.executable) {
93
+ this.cachedExecutable = info.executable;
94
+ }
95
+ if (!this.isProcessRunning(info.pid)) {
96
+ return null;
97
+ }
98
+ this.connectionInfo = info;
99
+ return info;
100
+ } catch {
101
+ return null;
113
102
  }
114
- }, 2e3);
115
- return;
116
- }
117
- this.setupWatcher(dir);
118
- }
119
- setupWatcher(dir) {
120
- try {
121
- this.watcher = (0, import_fs.watch)(dir, (event, filename) => {
122
- if (filename === "connection.json") {
123
- setTimeout(() => {
124
- const newInfo = this.read();
125
- const oldInfoStr = JSON.stringify(this.connectionInfo);
126
- const newInfoStr = JSON.stringify(newInfo);
127
- if (oldInfoStr !== newInfoStr) {
128
- this.connectionInfo = newInfo;
129
- this.onChange?.(newInfo);
103
+ }
104
+ /**
105
+ * Start watching for connection file changes
106
+ */
107
+ startWatching(onChange) {
108
+ this.onChange = onChange;
109
+ const dir = (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime");
110
+ if (!(0, import_fs.existsSync)(dir)) {
111
+ this.pollInterval = setInterval(() => {
112
+ if ((0, import_fs.existsSync)(dir)) {
113
+ if (this.pollInterval) {
114
+ clearInterval(this.pollInterval);
115
+ this.pollInterval = null;
116
+ }
117
+ this.setupWatcher(dir);
130
118
  }
131
- }, 100);
119
+ }, 2e3);
120
+ return;
132
121
  }
133
- });
134
- } catch (e) {
135
- console.error("Failed to watch connection file:", e);
136
- }
137
- }
138
- /**
139
- * Stop watching
140
- */
141
- stopWatching() {
142
- if (this.pollInterval) {
143
- clearInterval(this.pollInterval);
144
- this.pollInterval = null;
145
- }
146
- this.watcher?.close();
147
- this.watcher = null;
148
- this.onChange = null;
149
- }
150
- /**
151
- * Check if a process is running by PID
152
- *
153
- * Cross-platform implementation:
154
- * - macOS/Linux: process.kill(pid, 0) works correctly
155
- * - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
156
- * so we use tasklist command for reliable checking
157
- */
158
- isProcessRunning(pid) {
159
- try {
160
- if ((0, import_os.platform)() === "win32") {
122
+ this.setupWatcher(dir);
123
+ }
124
+ setupWatcher(dir) {
161
125
  try {
162
- const result = (0, import_child_process.execSync)(`tasklist /FI "PID eq ${pid}" /NH`, {
163
- encoding: "utf-8",
164
- stdio: ["pipe", "pipe", "pipe"]
126
+ this.watcher = (0, import_fs.watch)(dir, (event, filename) => {
127
+ if (filename === "connection.json") {
128
+ setTimeout(() => {
129
+ const newInfo = this.read();
130
+ const oldInfoStr = JSON.stringify(this.connectionInfo);
131
+ const newInfoStr = JSON.stringify(newInfo);
132
+ if (oldInfoStr !== newInfoStr) {
133
+ this.connectionInfo = newInfo;
134
+ this.onChange?.(newInfo);
135
+ }
136
+ }, 100);
137
+ }
165
138
  });
166
- const trimmed = result.trim();
167
- if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
168
- return false;
139
+ } catch (e) {
140
+ console.error("Failed to watch connection file:", e);
141
+ }
142
+ }
143
+ /**
144
+ * Stop watching
145
+ */
146
+ stopWatching() {
147
+ if (this.pollInterval) {
148
+ clearInterval(this.pollInterval);
149
+ this.pollInterval = null;
150
+ }
151
+ this.watcher?.close();
152
+ this.watcher = null;
153
+ this.onChange = null;
154
+ }
155
+ /**
156
+ * Check if a process is running by PID
157
+ *
158
+ * Cross-platform implementation:
159
+ * - macOS/Linux: process.kill(pid, 0) works correctly
160
+ * - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
161
+ * so we use tasklist command for reliable checking
162
+ */
163
+ isProcessRunning(pid) {
164
+ try {
165
+ if ((0, import_os.platform)() === "win32") {
166
+ try {
167
+ const result = (0, import_child_process.execSync)(`tasklist /FI "PID eq ${pid}" /NH`, {
168
+ encoding: "utf-8",
169
+ stdio: ["pipe", "pipe", "pipe"]
170
+ });
171
+ const trimmed = result.trim();
172
+ if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
173
+ return false;
174
+ }
175
+ return new RegExp(`\\b${pid}\\b`).test(trimmed);
176
+ } catch {
177
+ return false;
178
+ }
179
+ } else {
180
+ process.kill(pid, 0);
181
+ return true;
169
182
  }
170
- return new RegExp(`\\b${pid}\\b`).test(trimmed);
171
183
  } catch {
172
184
  return false;
173
185
  }
174
- } else {
175
- process.kill(pid, 0);
176
- return true;
177
186
  }
178
- } catch {
179
- return false;
180
- }
181
- }
182
- /**
183
- * Get cached connection info (may be stale)
184
- */
185
- getCached() {
186
- return this.connectionInfo;
187
- }
188
- /**
189
- * Build WebSocket URL from connection info
190
- */
191
- buildWebSocketUrl(info) {
192
- const wsPath = info.ws_path || "/ws/apps";
193
- return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
194
- }
195
- /**
196
- * Build HTTP base URL from connection info
197
- */
198
- buildHttpUrl(info) {
199
- return `http://127.0.0.1:${info.port}`;
200
- }
201
- /**
202
- * Find Sanqian executable path
203
- * Searches in standard installation locations for each platform
204
- */
205
- findSanqianPath(customPath) {
206
- if (customPath) {
207
- if ((0, import_fs.existsSync)(customPath)) {
208
- return customPath;
187
+ /**
188
+ * Get cached connection info (may be stale)
189
+ */
190
+ getCached() {
191
+ return this.connectionInfo;
209
192
  }
210
- console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
211
- return null;
212
- }
213
- const os = (0, import_os.platform)();
214
- const searchPaths = [];
215
- if (os === "darwin") {
216
- searchPaths.push(
217
- // Production: installed app
218
- "/Applications/Sanqian.app/Contents/MacOS/Sanqian",
219
- (0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
220
- // Development: electron-builder output
221
- (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
222
- (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
223
- );
224
- } else if (os === "win32") {
225
- const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
226
- const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
227
- const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
228
- searchPaths.push(
229
- // Production: NSIS installer uses lowercase directory name from package.json "name"
230
- (0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
231
- // Legacy/alternative paths with uppercase
232
- (0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
233
- (0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
234
- (0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
235
- // Development: electron-builder output
236
- (0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
237
- );
238
- } else {
239
- searchPaths.push(
240
- "/usr/bin/sanqian",
241
- "/usr/local/bin/sanqian",
242
- (0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
243
- "/opt/Sanqian/sanqian",
244
- // Development
245
- (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
246
- );
247
- }
248
- for (const path of searchPaths) {
249
- if ((0, import_fs.existsSync)(path)) {
250
- return path;
193
+ /**
194
+ * Build WebSocket URL from connection info
195
+ */
196
+ buildWebSocketUrl(info) {
197
+ const wsPath = info.ws_path || "/ws/apps";
198
+ return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
251
199
  }
252
- }
253
- return null;
254
- }
255
- /**
256
- * Launch Sanqian in hidden/tray mode
257
- * Returns true if launch was initiated successfully
258
- *
259
- * Priority for finding Sanqian executable:
260
- * 1. customPath parameter (if provided)
261
- * 2. Cached executable from connection.json (most reliable)
262
- * 3. Search in standard installation locations (fallback)
263
- */
264
- launchSanqian(customPath) {
265
- let sanqianPath = null;
266
- if (customPath) {
267
- sanqianPath = this.findSanqianPath(customPath);
268
- } else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
269
- sanqianPath = this.cachedExecutable;
270
- console.log(`[SDK] Using cached executable: ${sanqianPath}`);
271
- } else {
272
- sanqianPath = this.findSanqianPath();
273
- }
274
- if (!sanqianPath) {
275
- console.error("[SDK] Sanqian executable not found");
276
- return false;
277
- }
278
- console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
279
- try {
280
- const os = (0, import_os.platform)();
281
- if (os === "darwin") {
282
- const appPath = sanqianPath.replace(
283
- "/Contents/MacOS/Sanqian",
284
- ""
285
- );
286
- (0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
287
- detached: true,
288
- stdio: "ignore"
289
- }).unref();
290
- } else {
291
- (0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
292
- detached: true,
293
- stdio: "ignore",
294
- // On Windows, hide the console window
295
- ...os === "win32" && {
296
- windowsHide: true,
297
- shell: false
200
+ /**
201
+ * Build HTTP base URL from connection info
202
+ */
203
+ buildHttpUrl(info) {
204
+ return `http://127.0.0.1:${info.port}`;
205
+ }
206
+ /**
207
+ * Find Sanqian executable path
208
+ * Searches in standard installation locations for each platform
209
+ */
210
+ findSanqianPath(customPath) {
211
+ if (customPath) {
212
+ if ((0, import_fs.existsSync)(customPath)) {
213
+ return customPath;
298
214
  }
299
- }).unref();
215
+ console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
216
+ return null;
217
+ }
218
+ const os = (0, import_os.platform)();
219
+ const searchPaths = [];
220
+ if (os === "darwin") {
221
+ searchPaths.push(
222
+ // Production: installed app
223
+ "/Applications/Sanqian.app/Contents/MacOS/Sanqian",
224
+ (0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
225
+ // Development: electron-builder output
226
+ (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
227
+ (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
228
+ );
229
+ } else if (os === "win32") {
230
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
231
+ const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
232
+ const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
233
+ searchPaths.push(
234
+ // Production: NSIS installer uses lowercase directory name from package.json "name"
235
+ (0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
236
+ // Legacy/alternative paths with uppercase
237
+ (0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
238
+ (0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
239
+ (0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
240
+ // Development: electron-builder output
241
+ (0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
242
+ );
243
+ } else {
244
+ searchPaths.push(
245
+ "/usr/bin/sanqian",
246
+ "/usr/local/bin/sanqian",
247
+ (0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
248
+ "/opt/Sanqian/sanqian",
249
+ // Development
250
+ (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
251
+ );
252
+ }
253
+ for (const path of searchPaths) {
254
+ if ((0, import_fs.existsSync)(path)) {
255
+ return path;
256
+ }
257
+ }
258
+ return null;
300
259
  }
301
- return true;
302
- } catch (e) {
303
- console.error("[SDK] Failed to launch Sanqian:", e);
304
- return false;
305
- }
306
- }
307
- /**
308
- * Check if Sanqian is running
309
- */
310
- isSanqianRunning() {
311
- return this.read() !== null;
260
+ /**
261
+ * Launch Sanqian in hidden/tray mode
262
+ * Returns true if launch was initiated successfully
263
+ *
264
+ * Priority for finding Sanqian executable:
265
+ * 1. customPath parameter (if provided)
266
+ * 2. Cached executable from connection.json (most reliable)
267
+ * 3. Search in standard installation locations (fallback)
268
+ */
269
+ launchSanqian(customPath) {
270
+ let sanqianPath = null;
271
+ if (customPath) {
272
+ sanqianPath = this.findSanqianPath(customPath);
273
+ } else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
274
+ sanqianPath = this.cachedExecutable;
275
+ console.log(`[SDK] Using cached executable: ${sanqianPath}`);
276
+ } else {
277
+ sanqianPath = this.findSanqianPath();
278
+ }
279
+ if (!sanqianPath) {
280
+ console.error("[SDK] Sanqian executable not found");
281
+ return false;
282
+ }
283
+ console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
284
+ try {
285
+ const os = (0, import_os.platform)();
286
+ if (os === "darwin") {
287
+ const appPath = sanqianPath.replace(
288
+ "/Contents/MacOS/Sanqian",
289
+ ""
290
+ );
291
+ (0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
292
+ detached: true,
293
+ stdio: "ignore"
294
+ }).unref();
295
+ } else {
296
+ (0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
297
+ detached: true,
298
+ stdio: "ignore",
299
+ // On Windows, hide the console window
300
+ ...os === "win32" && {
301
+ windowsHide: true,
302
+ shell: false
303
+ }
304
+ }).unref();
305
+ }
306
+ return true;
307
+ } catch (e) {
308
+ console.error("[SDK] Failed to launch Sanqian:", e);
309
+ return false;
310
+ }
311
+ }
312
+ /**
313
+ * Check if Sanqian is running
314
+ */
315
+ isSanqianRunning() {
316
+ return this.read() !== null;
317
+ }
318
+ };
312
319
  }
313
- };
320
+ });
321
+
322
+ // src/index.ts
323
+ var src_exports = {};
324
+ __export(src_exports, {
325
+ Conversation: () => Conversation,
326
+ DiscoveryManager: () => DiscoveryManager,
327
+ ErrorMessages: () => ErrorMessages,
328
+ SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
329
+ SDKErrorCode: () => SDKErrorCode,
330
+ SanqianSDK: () => SanqianSDK,
331
+ SanqianSDKError: () => SanqianSDKError,
332
+ createSDKError: () => createSDKError
333
+ });
334
+ module.exports = __toCommonJS(src_exports);
335
+
336
+ // src/client.ts
337
+ var import_isomorphic_ws = __toESM(require_node());
314
338
 
315
339
  // src/errors.ts
316
340
  var SANQIAN_WEBSITE = "https://sanqian.io";
@@ -481,7 +505,8 @@ function createSDKError(code, details) {
481
505
  // src/client.ts
482
506
  var SanqianSDK = class _SanqianSDK {
483
507
  config;
484
- discovery;
508
+ // DiscoveryManager is only initialized in Node.js environments (when connectionInfo is not provided)
509
+ discovery = null;
485
510
  ws = null;
486
511
  connectionInfo = null;
487
512
  state = {
@@ -537,11 +562,10 @@ var SanqianSDK = class _SanqianSDK {
537
562
  autoLaunchSanqian: true,
538
563
  ...config
539
564
  };
540
- this.discovery = new DiscoveryManager();
541
565
  for (const tool of config.tools) {
542
566
  this.toolHandlers.set(tool.name, tool.handler);
543
567
  }
544
- this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
568
+ this.launchedBySanqian = typeof process !== "undefined" && (process.env?.SANQIAN_LAUNCHED === "1" || process.env?.SANQIAN_NO_RECONNECT === "1");
545
569
  if (this.launchedBySanqian) {
546
570
  this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
547
571
  setTimeout(() => {
@@ -551,6 +575,26 @@ var SanqianSDK = class _SanqianSDK {
551
575
  }, 0);
552
576
  }
553
577
  }
578
+ /**
579
+ * Build WebSocket URL from connection info
580
+ * This is inlined to avoid importing DiscoveryManager in browser environments
581
+ */
582
+ buildWebSocketUrl(info) {
583
+ const wsPath = info.ws_path || "/ws/apps";
584
+ return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
585
+ }
586
+ /**
587
+ * Lazily initialize DiscoveryManager (only in Node.js environments)
588
+ * This uses dynamic import to avoid bundling Node.js APIs in browser builds
589
+ */
590
+ async getDiscovery() {
591
+ if (this.discovery) {
592
+ return this.discovery;
593
+ }
594
+ const { DiscoveryManager: DiscoveryManager2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
595
+ this.discovery = new DiscoveryManager2();
596
+ return this.discovery;
597
+ }
554
598
  // ============================================
555
599
  // Lifecycle
556
600
  // ============================================
@@ -574,7 +618,7 @@ var SanqianSDK = class _SanqianSDK {
574
618
  */
575
619
  async connectWithInfo(info) {
576
620
  this.connectionInfo = info;
577
- const url = this.discovery.buildWebSocketUrl(info);
621
+ const url = this.buildWebSocketUrl(info);
578
622
  return new Promise((resolve, reject) => {
579
623
  this.log(`Connecting to ${url}`);
580
624
  this.ws = new import_isomorphic_ws.default(url);
@@ -624,7 +668,7 @@ var SanqianSDK = class _SanqianSDK {
624
668
  async disconnect() {
625
669
  this.stopHeartbeat();
626
670
  this.stopReconnect();
627
- this.discovery.stopWatching();
671
+ this.discovery?.stopWatching();
628
672
  if (this.ws) {
629
673
  this.ws.close(1e3, "Client disconnect");
630
674
  this.ws = null;
@@ -715,22 +759,23 @@ var SanqianSDK = class _SanqianSDK {
715
759
  case "text":
716
760
  handler.onEvent({ type: "text", content });
717
761
  break;
762
+ case "thinking":
763
+ handler.onEvent({ type: "thinking", content });
764
+ break;
718
765
  case "tool_call":
719
766
  handler.onEvent({ type: "tool_call", tool_call });
720
767
  break;
721
768
  case "tool_result":
722
769
  if (tool_result) {
723
770
  handler.onEvent({
724
- type: "tool_call",
725
- tool_call: {
726
- id: tool_result.call_id,
727
- type: "function",
728
- function: {
729
- name: "tool_result",
730
- arguments: JSON.stringify(tool_result)
731
- }
732
- }
771
+ type: "tool_result",
772
+ tool_call_id: tool_result.call_id,
773
+ result: tool_result.result,
774
+ success: tool_result.success,
775
+ error: tool_result.error
733
776
  });
777
+ } else {
778
+ this.log("Received tool_result event without tool_result data");
734
779
  }
735
780
  break;
736
781
  case "done":
@@ -1005,11 +1050,12 @@ var SanqianSDK = class _SanqianSDK {
1005
1050
  this.log("Using pre-configured connection info (browser mode)");
1006
1051
  info = this.config.connectionInfo;
1007
1052
  } else {
1008
- info = this.discovery.read();
1053
+ const discovery = await this.getDiscovery();
1054
+ info = discovery.read();
1009
1055
  if (!info) {
1010
1056
  if (this.config.autoLaunchSanqian) {
1011
1057
  this.log("Sanqian not running, attempting to launch...");
1012
- const launched = this.discovery.launchSanqian(this.config.sanqianPath);
1058
+ const launched = discovery.launchSanqian(this.config.sanqianPath);
1013
1059
  if (!launched) {
1014
1060
  throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
1015
1061
  }
@@ -1028,8 +1074,9 @@ var SanqianSDK = class _SanqianSDK {
1028
1074
  async waitForSanqianStartup(timeout = 12e4) {
1029
1075
  const startTime = Date.now();
1030
1076
  const pollInterval = 500;
1077
+ const discovery = await this.getDiscovery();
1031
1078
  while (Date.now() - startTime < timeout) {
1032
- const info = this.discovery.read();
1079
+ const info = discovery.read();
1033
1080
  if (info) {
1034
1081
  this.log("Sanqian started, connection info available");
1035
1082
  return info;
@@ -1378,6 +1425,20 @@ var SanqianSDK = class _SanqianSDK {
1378
1425
  });
1379
1426
  return this.on(event, onceWrapper);
1380
1427
  }
1428
+ /**
1429
+ * Remove all event listeners
1430
+ *
1431
+ * Call this to prevent memory leaks when disposing the SDK instance.
1432
+ * If event is specified, only removes listeners for that event.
1433
+ */
1434
+ removeAllListeners(event) {
1435
+ if (event) {
1436
+ this.eventListeners.delete(event);
1437
+ } else {
1438
+ this.eventListeners.clear();
1439
+ }
1440
+ return this;
1441
+ }
1381
1442
  emit(event, ...args) {
1382
1443
  const listeners = this.eventListeners.get(event);
1383
1444
  if (listeners) {
@@ -1478,6 +1539,9 @@ var Conversation = class {
1478
1539
  });
1479
1540
  }
1480
1541
  };
1542
+
1543
+ // src/index.ts
1544
+ init_discovery();
1481
1545
  // Annotate the CommonJS export names for ESM import in node:
1482
1546
  0 && (module.exports = {
1483
1547
  Conversation,