@yushaw/sanqian-sdk 0.2.9 → 0.2.11
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.browser.d.mts +430 -0
- package/dist/index.browser.mjs +1058 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +358 -295
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +344 -274
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -4
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ 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
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,289 +30,300 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
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
|
|
41
|
-
});
|
|
42
|
-
module.exports = __toCommonJS(index_exports);
|
|
43
|
-
|
|
44
|
-
// src/client.ts
|
|
45
|
-
var import_ws = __toESM(require("ws"));
|
|
46
|
-
|
|
47
33
|
// src/discovery.ts
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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;
|
|
34
|
+
var discovery_exports = {};
|
|
35
|
+
__export(discovery_exports, {
|
|
36
|
+
DiscoveryManager: () => DiscoveryManager
|
|
37
|
+
});
|
|
38
|
+
var import_fs, import_os, import_path, import_child_process, DiscoveryManager;
|
|
39
|
+
var init_discovery = __esm({
|
|
40
|
+
"src/discovery.ts"() {
|
|
41
|
+
"use strict";
|
|
42
|
+
import_fs = require("fs");
|
|
43
|
+
import_os = require("os");
|
|
44
|
+
import_path = require("path");
|
|
45
|
+
import_child_process = require("child_process");
|
|
46
|
+
DiscoveryManager = class {
|
|
47
|
+
connectionInfo = null;
|
|
48
|
+
watcher = null;
|
|
49
|
+
onChange = null;
|
|
50
|
+
pollInterval = null;
|
|
51
|
+
/**
|
|
52
|
+
* Cached executable path from last successful connection.json read.
|
|
53
|
+
* Persists even when Sanqian is not running, for use in auto-launch.
|
|
54
|
+
*/
|
|
55
|
+
cachedExecutable = null;
|
|
56
|
+
/**
|
|
57
|
+
* Get the path to connection.json
|
|
58
|
+
*/
|
|
59
|
+
getConnectionFilePath() {
|
|
60
|
+
return (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime", "connection.json");
|
|
92
61
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Read and validate connection info
|
|
64
|
+
*
|
|
65
|
+
* Returns null if:
|
|
66
|
+
* - File doesn't exist
|
|
67
|
+
* - File is invalid JSON
|
|
68
|
+
* - Process is not running
|
|
69
|
+
*/
|
|
70
|
+
read() {
|
|
71
|
+
const filePath = this.getConnectionFilePath();
|
|
72
|
+
if (!(0, import_fs.existsSync)(filePath)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const content = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
77
|
+
const info = JSON.parse(content);
|
|
78
|
+
if (!info.port || !info.token || !info.pid) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (info.executable) {
|
|
82
|
+
this.cachedExecutable = info.executable;
|
|
111
83
|
}
|
|
112
|
-
this.
|
|
84
|
+
if (!this.isProcessRunning(info.pid)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
this.connectionInfo = info;
|
|
88
|
+
return info;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
113
91
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
this.onChange?.(newInfo);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start watching for connection file changes
|
|
95
|
+
*/
|
|
96
|
+
startWatching(onChange) {
|
|
97
|
+
this.onChange = onChange;
|
|
98
|
+
const dir = (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime");
|
|
99
|
+
if (!(0, import_fs.existsSync)(dir)) {
|
|
100
|
+
this.pollInterval = setInterval(() => {
|
|
101
|
+
if ((0, import_fs.existsSync)(dir)) {
|
|
102
|
+
if (this.pollInterval) {
|
|
103
|
+
clearInterval(this.pollInterval);
|
|
104
|
+
this.pollInterval = null;
|
|
105
|
+
}
|
|
106
|
+
this.setupWatcher(dir);
|
|
130
107
|
}
|
|
131
|
-
},
|
|
108
|
+
}, 2e3);
|
|
109
|
+
return;
|
|
132
110
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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") {
|
|
111
|
+
this.setupWatcher(dir);
|
|
112
|
+
}
|
|
113
|
+
setupWatcher(dir) {
|
|
161
114
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
115
|
+
this.watcher = (0, import_fs.watch)(dir, (event, filename) => {
|
|
116
|
+
if (filename === "connection.json") {
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
const newInfo = this.read();
|
|
119
|
+
const oldInfoStr = JSON.stringify(this.connectionInfo);
|
|
120
|
+
const newInfoStr = JSON.stringify(newInfo);
|
|
121
|
+
if (oldInfoStr !== newInfoStr) {
|
|
122
|
+
this.connectionInfo = newInfo;
|
|
123
|
+
this.onChange?.(newInfo);
|
|
124
|
+
}
|
|
125
|
+
}, 100);
|
|
126
|
+
}
|
|
165
127
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error("Failed to watch connection file:", e);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Stop watching
|
|
134
|
+
*/
|
|
135
|
+
stopWatching() {
|
|
136
|
+
if (this.pollInterval) {
|
|
137
|
+
clearInterval(this.pollInterval);
|
|
138
|
+
this.pollInterval = null;
|
|
139
|
+
}
|
|
140
|
+
this.watcher?.close();
|
|
141
|
+
this.watcher = null;
|
|
142
|
+
this.onChange = null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if a process is running by PID
|
|
146
|
+
*
|
|
147
|
+
* Cross-platform implementation:
|
|
148
|
+
* - macOS/Linux: process.kill(pid, 0) works correctly
|
|
149
|
+
* - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
|
|
150
|
+
* so we use tasklist command for reliable checking
|
|
151
|
+
*/
|
|
152
|
+
isProcessRunning(pid) {
|
|
153
|
+
try {
|
|
154
|
+
if ((0, import_os.platform)() === "win32") {
|
|
155
|
+
try {
|
|
156
|
+
const result = (0, import_child_process.execSync)(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
157
|
+
encoding: "utf-8",
|
|
158
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
159
|
+
});
|
|
160
|
+
const trimmed = result.trim();
|
|
161
|
+
if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return new RegExp(`\\b${pid}\\b`).test(trimmed);
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
process.kill(pid, 0);
|
|
170
|
+
return true;
|
|
169
171
|
}
|
|
170
|
-
return new RegExp(`\\b${pid}\\b`).test(trimmed);
|
|
171
172
|
} catch {
|
|
172
173
|
return false;
|
|
173
174
|
}
|
|
174
|
-
} else {
|
|
175
|
-
process.kill(pid, 0);
|
|
176
|
-
return true;
|
|
177
175
|
}
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
return `ws://127.0.0.1:${info.port}${info.ws_path}?token=${info.token}`;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Build HTTP base URL from connection info
|
|
196
|
-
*/
|
|
197
|
-
buildHttpUrl(info) {
|
|
198
|
-
return `http://127.0.0.1:${info.port}`;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Find Sanqian executable path
|
|
202
|
-
* Searches in standard installation locations for each platform
|
|
203
|
-
*/
|
|
204
|
-
findSanqianPath(customPath) {
|
|
205
|
-
if (customPath) {
|
|
206
|
-
if ((0, import_fs.existsSync)(customPath)) {
|
|
207
|
-
return customPath;
|
|
176
|
+
/**
|
|
177
|
+
* Get cached connection info (may be stale)
|
|
178
|
+
*/
|
|
179
|
+
getCached() {
|
|
180
|
+
return this.connectionInfo;
|
|
208
181
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
searchPaths.push(
|
|
216
|
-
// Production: installed app
|
|
217
|
-
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
218
|
-
(0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
219
|
-
// Development: electron-builder output
|
|
220
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
221
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
222
|
-
);
|
|
223
|
-
} else if (os === "win32") {
|
|
224
|
-
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
225
|
-
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
226
|
-
const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
|
|
227
|
-
searchPaths.push(
|
|
228
|
-
// Production: NSIS installer uses lowercase directory name from package.json "name"
|
|
229
|
-
(0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
|
|
230
|
-
// Legacy/alternative paths with uppercase
|
|
231
|
-
(0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
232
|
-
(0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
|
|
233
|
-
(0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
234
|
-
// Development: electron-builder output
|
|
235
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
236
|
-
);
|
|
237
|
-
} else {
|
|
238
|
-
searchPaths.push(
|
|
239
|
-
"/usr/bin/sanqian",
|
|
240
|
-
"/usr/local/bin/sanqian",
|
|
241
|
-
(0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
|
|
242
|
-
"/opt/Sanqian/sanqian",
|
|
243
|
-
// Development
|
|
244
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
for (const path of searchPaths) {
|
|
248
|
-
if ((0, import_fs.existsSync)(path)) {
|
|
249
|
-
return path;
|
|
182
|
+
/**
|
|
183
|
+
* Build WebSocket URL from connection info
|
|
184
|
+
*/
|
|
185
|
+
buildWebSocketUrl(info) {
|
|
186
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
187
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
250
188
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
"
|
|
283
|
-
""
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Build HTTP base URL from connection info
|
|
191
|
+
*/
|
|
192
|
+
buildHttpUrl(info) {
|
|
193
|
+
return `http://127.0.0.1:${info.port}`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Find Sanqian executable path
|
|
197
|
+
* Searches in standard installation locations for each platform
|
|
198
|
+
*/
|
|
199
|
+
findSanqianPath(customPath) {
|
|
200
|
+
if (customPath) {
|
|
201
|
+
if ((0, import_fs.existsSync)(customPath)) {
|
|
202
|
+
return customPath;
|
|
203
|
+
}
|
|
204
|
+
console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const os = (0, import_os.platform)();
|
|
208
|
+
const searchPaths = [];
|
|
209
|
+
if (os === "darwin") {
|
|
210
|
+
searchPaths.push(
|
|
211
|
+
// Production: installed app
|
|
212
|
+
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
213
|
+
(0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
214
|
+
// Development: electron-builder output
|
|
215
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
216
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
217
|
+
);
|
|
218
|
+
} else if (os === "win32") {
|
|
219
|
+
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
220
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
221
|
+
const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
|
|
222
|
+
searchPaths.push(
|
|
223
|
+
// Production: NSIS installer uses lowercase directory name from package.json "name"
|
|
224
|
+
(0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
|
|
225
|
+
// Legacy/alternative paths with uppercase
|
|
226
|
+
(0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
227
|
+
(0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
|
|
228
|
+
(0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
229
|
+
// Development: electron-builder output
|
|
230
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
231
|
+
);
|
|
232
|
+
} else {
|
|
233
|
+
searchPaths.push(
|
|
234
|
+
"/usr/bin/sanqian",
|
|
235
|
+
"/usr/local/bin/sanqian",
|
|
236
|
+
(0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
|
|
237
|
+
"/opt/Sanqian/sanqian",
|
|
238
|
+
// Development
|
|
239
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
for (const path of searchPaths) {
|
|
243
|
+
if ((0, import_fs.existsSync)(path)) {
|
|
244
|
+
return path;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Launch Sanqian in hidden/tray mode
|
|
251
|
+
* Returns true if launch was initiated successfully
|
|
252
|
+
*
|
|
253
|
+
* Priority for finding Sanqian executable:
|
|
254
|
+
* 1. customPath parameter (if provided)
|
|
255
|
+
* 2. Cached executable from connection.json (most reliable)
|
|
256
|
+
* 3. Search in standard installation locations (fallback)
|
|
257
|
+
*/
|
|
258
|
+
launchSanqian(customPath) {
|
|
259
|
+
let sanqianPath = null;
|
|
260
|
+
if (customPath) {
|
|
261
|
+
sanqianPath = this.findSanqianPath(customPath);
|
|
262
|
+
} else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
|
|
263
|
+
sanqianPath = this.cachedExecutable;
|
|
264
|
+
console.log(`[SDK] Using cached executable: ${sanqianPath}`);
|
|
265
|
+
} else {
|
|
266
|
+
sanqianPath = this.findSanqianPath();
|
|
267
|
+
}
|
|
268
|
+
if (!sanqianPath) {
|
|
269
|
+
console.error("[SDK] Sanqian executable not found");
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
|
|
273
|
+
try {
|
|
274
|
+
const os = (0, import_os.platform)();
|
|
275
|
+
if (os === "darwin") {
|
|
276
|
+
const appPath = sanqianPath.replace(
|
|
277
|
+
"/Contents/MacOS/Sanqian",
|
|
278
|
+
""
|
|
279
|
+
);
|
|
280
|
+
(0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
|
|
281
|
+
detached: true,
|
|
282
|
+
stdio: "ignore"
|
|
283
|
+
}).unref();
|
|
284
|
+
} else {
|
|
285
|
+
(0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
|
|
286
|
+
detached: true,
|
|
287
|
+
stdio: "ignore",
|
|
288
|
+
// On Windows, hide the console window
|
|
289
|
+
...os === "win32" && {
|
|
290
|
+
windowsHide: true,
|
|
291
|
+
shell: false
|
|
292
|
+
}
|
|
293
|
+
}).unref();
|
|
297
294
|
}
|
|
298
|
-
|
|
295
|
+
return true;
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.error("[SDK] Failed to launch Sanqian:", e);
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
299
300
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
* Check if Sanqian is running
|
|
308
|
-
*/
|
|
309
|
-
isSanqianRunning() {
|
|
310
|
-
return this.read() !== null;
|
|
301
|
+
/**
|
|
302
|
+
* Check if Sanqian is running
|
|
303
|
+
*/
|
|
304
|
+
isSanqianRunning() {
|
|
305
|
+
return this.read() !== null;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
311
308
|
}
|
|
312
|
-
};
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// src/index.ts
|
|
312
|
+
var src_exports = {};
|
|
313
|
+
__export(src_exports, {
|
|
314
|
+
Conversation: () => Conversation,
|
|
315
|
+
DiscoveryManager: () => DiscoveryManager,
|
|
316
|
+
ErrorMessages: () => ErrorMessages,
|
|
317
|
+
SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
|
|
318
|
+
SDKErrorCode: () => SDKErrorCode,
|
|
319
|
+
SanqianSDK: () => SanqianSDK,
|
|
320
|
+
SanqianSDKError: () => SanqianSDKError,
|
|
321
|
+
createSDKError: () => createSDKError
|
|
322
|
+
});
|
|
323
|
+
module.exports = __toCommonJS(src_exports);
|
|
324
|
+
|
|
325
|
+
// src/client.ts
|
|
326
|
+
var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
|
|
313
327
|
|
|
314
328
|
// src/errors.ts
|
|
315
329
|
var SANQIAN_WEBSITE = "https://sanqian.io";
|
|
@@ -480,7 +494,8 @@ function createSDKError(code, details) {
|
|
|
480
494
|
// src/client.ts
|
|
481
495
|
var SanqianSDK = class _SanqianSDK {
|
|
482
496
|
config;
|
|
483
|
-
|
|
497
|
+
// DiscoveryManager is only initialized in Node.js environments (when connectionInfo is not provided)
|
|
498
|
+
discovery = null;
|
|
484
499
|
ws = null;
|
|
485
500
|
connectionInfo = null;
|
|
486
501
|
state = {
|
|
@@ -536,11 +551,10 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
536
551
|
autoLaunchSanqian: true,
|
|
537
552
|
...config
|
|
538
553
|
};
|
|
539
|
-
this.discovery = new DiscoveryManager();
|
|
540
554
|
for (const tool of config.tools) {
|
|
541
555
|
this.toolHandlers.set(tool.name, tool.handler);
|
|
542
556
|
}
|
|
543
|
-
this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
|
|
557
|
+
this.launchedBySanqian = typeof process !== "undefined" && (process.env?.SANQIAN_LAUNCHED === "1" || process.env?.SANQIAN_NO_RECONNECT === "1");
|
|
544
558
|
if (this.launchedBySanqian) {
|
|
545
559
|
this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
|
|
546
560
|
setTimeout(() => {
|
|
@@ -550,6 +564,26 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
550
564
|
}, 0);
|
|
551
565
|
}
|
|
552
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Build WebSocket URL from connection info
|
|
569
|
+
* This is inlined to avoid importing DiscoveryManager in browser environments
|
|
570
|
+
*/
|
|
571
|
+
buildWebSocketUrl(info) {
|
|
572
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
573
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Lazily initialize DiscoveryManager (only in Node.js environments)
|
|
577
|
+
* This uses dynamic import to avoid bundling Node.js APIs in browser builds
|
|
578
|
+
*/
|
|
579
|
+
async getDiscovery() {
|
|
580
|
+
if (this.discovery) {
|
|
581
|
+
return this.discovery;
|
|
582
|
+
}
|
|
583
|
+
const { DiscoveryManager: DiscoveryManager2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
584
|
+
this.discovery = new DiscoveryManager2();
|
|
585
|
+
return this.discovery;
|
|
586
|
+
}
|
|
553
587
|
// ============================================
|
|
554
588
|
// Lifecycle
|
|
555
589
|
// ============================================
|
|
@@ -573,15 +607,15 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
573
607
|
*/
|
|
574
608
|
async connectWithInfo(info) {
|
|
575
609
|
this.connectionInfo = info;
|
|
576
|
-
const url = this.
|
|
610
|
+
const url = this.buildWebSocketUrl(info);
|
|
577
611
|
return new Promise((resolve, reject) => {
|
|
578
612
|
this.log(`Connecting to ${url}`);
|
|
579
|
-
this.ws = new
|
|
613
|
+
this.ws = new import_isomorphic_ws.default(url);
|
|
580
614
|
const connectTimeout = setTimeout(() => {
|
|
581
615
|
reject(createSDKError("CONNECTION_TIMEOUT" /* CONNECTION_TIMEOUT */));
|
|
582
616
|
this.ws?.close();
|
|
583
617
|
}, 1e4);
|
|
584
|
-
this.ws.
|
|
618
|
+
this.ws.onopen = async () => {
|
|
585
619
|
clearTimeout(connectTimeout);
|
|
586
620
|
this.log("WebSocket connected");
|
|
587
621
|
this.state.connected = true;
|
|
@@ -593,24 +627,28 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
593
627
|
} catch (e) {
|
|
594
628
|
reject(e);
|
|
595
629
|
}
|
|
596
|
-
}
|
|
597
|
-
this.ws.
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
630
|
+
};
|
|
631
|
+
this.ws.onclose = (event) => {
|
|
632
|
+
const reasonRaw = event.reason;
|
|
633
|
+
const reason = typeof reasonRaw === "string" ? reasonRaw : reasonRaw?.toString() || "";
|
|
634
|
+
this.log(`WebSocket closed: ${event.code} ${reason}`);
|
|
635
|
+
this.handleDisconnect(reason);
|
|
636
|
+
};
|
|
637
|
+
this.ws.onerror = (event) => {
|
|
638
|
+
const error = event.error || new Error("WebSocket error");
|
|
602
639
|
console.error("[SDK] WebSocket error:", error);
|
|
603
640
|
this.state.lastError = error;
|
|
604
641
|
this.emit("error", error);
|
|
605
|
-
}
|
|
606
|
-
this.ws.
|
|
642
|
+
};
|
|
643
|
+
this.ws.onmessage = (event) => {
|
|
607
644
|
try {
|
|
608
|
-
const
|
|
645
|
+
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
646
|
+
const message = JSON.parse(data);
|
|
609
647
|
this.handleMessage(message);
|
|
610
648
|
} catch (e) {
|
|
611
649
|
this.warn("Failed to parse message:", e);
|
|
612
650
|
}
|
|
613
|
-
}
|
|
651
|
+
};
|
|
614
652
|
});
|
|
615
653
|
}
|
|
616
654
|
/**
|
|
@@ -619,7 +657,7 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
619
657
|
async disconnect() {
|
|
620
658
|
this.stopHeartbeat();
|
|
621
659
|
this.stopReconnect();
|
|
622
|
-
this.discovery
|
|
660
|
+
this.discovery?.stopWatching();
|
|
623
661
|
if (this.ws) {
|
|
624
662
|
this.ws.close(1e3, "Client disconnect");
|
|
625
663
|
this.ws = null;
|
|
@@ -907,7 +945,7 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
907
945
|
// Communication
|
|
908
946
|
// ============================================
|
|
909
947
|
send(message) {
|
|
910
|
-
if (!this.ws || this.ws.readyState !==
|
|
948
|
+
if (!this.ws || this.ws.readyState !== import_isomorphic_ws.default.OPEN) {
|
|
911
949
|
const error = new Error(
|
|
912
950
|
`WebSocket not open (state: ${this.ws?.readyState ?? "null"}), cannot send message`
|
|
913
951
|
);
|
|
@@ -995,18 +1033,25 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
995
1033
|
*/
|
|
996
1034
|
async doFullConnect() {
|
|
997
1035
|
this.log("Starting full connection flow...");
|
|
998
|
-
let info =
|
|
999
|
-
if (
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1036
|
+
let info = null;
|
|
1037
|
+
if (this.config.connectionInfo) {
|
|
1038
|
+
this.log("Using pre-configured connection info (browser mode)");
|
|
1039
|
+
info = this.config.connectionInfo;
|
|
1040
|
+
} else {
|
|
1041
|
+
const discovery = await this.getDiscovery();
|
|
1042
|
+
info = discovery.read();
|
|
1043
|
+
if (!info) {
|
|
1044
|
+
if (this.config.autoLaunchSanqian) {
|
|
1045
|
+
this.log("Sanqian not running, attempting to launch...");
|
|
1046
|
+
const launched = discovery.launchSanqian(this.config.sanqianPath);
|
|
1047
|
+
if (!launched) {
|
|
1048
|
+
throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
|
|
1049
|
+
}
|
|
1050
|
+
this.log("Sanqian launch initiated, waiting for startup...");
|
|
1051
|
+
info = await this.waitForSanqianStartup();
|
|
1052
|
+
} else {
|
|
1053
|
+
throw createSDKError("NOT_RUNNING" /* NOT_RUNNING */);
|
|
1005
1054
|
}
|
|
1006
|
-
this.log("Sanqian launch initiated, waiting for startup...");
|
|
1007
|
-
info = await this.waitForSanqianStartup();
|
|
1008
|
-
} else {
|
|
1009
|
-
throw createSDKError("NOT_RUNNING" /* NOT_RUNNING */);
|
|
1010
1055
|
}
|
|
1011
1056
|
}
|
|
1012
1057
|
await this.connectWithInfo(info);
|
|
@@ -1017,8 +1062,9 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
1017
1062
|
async waitForSanqianStartup(timeout = 12e4) {
|
|
1018
1063
|
const startTime = Date.now();
|
|
1019
1064
|
const pollInterval = 500;
|
|
1065
|
+
const discovery = await this.getDiscovery();
|
|
1020
1066
|
while (Date.now() - startTime < timeout) {
|
|
1021
|
-
const info =
|
|
1067
|
+
const info = discovery.read();
|
|
1022
1068
|
if (info) {
|
|
1023
1069
|
this.log("Sanqian started, connection info available");
|
|
1024
1070
|
return info;
|
|
@@ -1367,6 +1413,20 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
1367
1413
|
});
|
|
1368
1414
|
return this.on(event, onceWrapper);
|
|
1369
1415
|
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Remove all event listeners
|
|
1418
|
+
*
|
|
1419
|
+
* Call this to prevent memory leaks when disposing the SDK instance.
|
|
1420
|
+
* If event is specified, only removes listeners for that event.
|
|
1421
|
+
*/
|
|
1422
|
+
removeAllListeners(event) {
|
|
1423
|
+
if (event) {
|
|
1424
|
+
this.eventListeners.delete(event);
|
|
1425
|
+
} else {
|
|
1426
|
+
this.eventListeners.clear();
|
|
1427
|
+
}
|
|
1428
|
+
return this;
|
|
1429
|
+
}
|
|
1370
1430
|
emit(event, ...args) {
|
|
1371
1431
|
const listeners = this.eventListeners.get(event);
|
|
1372
1432
|
if (listeners) {
|
|
@@ -1467,6 +1527,9 @@ var Conversation = class {
|
|
|
1467
1527
|
});
|
|
1468
1528
|
}
|
|
1469
1529
|
};
|
|
1530
|
+
|
|
1531
|
+
// src/index.ts
|
|
1532
|
+
init_discovery();
|
|
1470
1533
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1471
1534
|
0 && (module.exports = {
|
|
1472
1535
|
Conversation,
|