@yushaw/sanqian-sdk 0.2.10 → 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 +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +326 -274
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -255
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -4
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
// src/client.browser.ts
|
|
2
|
+
import WebSocket from "isomorphic-ws";
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var SANQIAN_WEBSITE = "https://sanqian.io";
|
|
6
|
+
var SDKErrorCode = /* @__PURE__ */ ((SDKErrorCode2) => {
|
|
7
|
+
SDKErrorCode2["NOT_INSTALLED"] = "NOT_INSTALLED";
|
|
8
|
+
SDKErrorCode2["NOT_RUNNING"] = "NOT_RUNNING";
|
|
9
|
+
SDKErrorCode2["CONNECTION_TIMEOUT"] = "CONNECTION_TIMEOUT";
|
|
10
|
+
SDKErrorCode2["STARTUP_TIMEOUT"] = "STARTUP_TIMEOUT";
|
|
11
|
+
SDKErrorCode2["REGISTRATION_FAILED"] = "REGISTRATION_FAILED";
|
|
12
|
+
SDKErrorCode2["REQUEST_TIMEOUT"] = "REQUEST_TIMEOUT";
|
|
13
|
+
SDKErrorCode2["REQUEST_FAILED"] = "REQUEST_FAILED";
|
|
14
|
+
SDKErrorCode2["DISCONNECTED"] = "DISCONNECTED";
|
|
15
|
+
SDKErrorCode2["WEBSOCKET_ERROR"] = "WEBSOCKET_ERROR";
|
|
16
|
+
SDKErrorCode2["AGENT_NOT_FOUND"] = "AGENT_NOT_FOUND";
|
|
17
|
+
SDKErrorCode2["CONVERSATION_NOT_FOUND"] = "CONVERSATION_NOT_FOUND";
|
|
18
|
+
SDKErrorCode2["TOOL_NOT_FOUND"] = "TOOL_NOT_FOUND";
|
|
19
|
+
SDKErrorCode2["TOOL_EXECUTION_TIMEOUT"] = "TOOL_EXECUTION_TIMEOUT";
|
|
20
|
+
return SDKErrorCode2;
|
|
21
|
+
})(SDKErrorCode || {});
|
|
22
|
+
var ErrorMessages = {
|
|
23
|
+
["NOT_INSTALLED" /* NOT_INSTALLED */]: {
|
|
24
|
+
en: `Sanqian is not installed on this computer.`,
|
|
25
|
+
zh: `Sanqian \u5C1A\u672A\u5B89\u88C5\u5728\u6B64\u7535\u8111\u4E0A\u3002`,
|
|
26
|
+
hint: {
|
|
27
|
+
en: `Please download and install Sanqian from ${SANQIAN_WEBSITE}`,
|
|
28
|
+
zh: `\u8BF7\u8BBF\u95EE ${SANQIAN_WEBSITE} \u4E0B\u8F7D\u5B89\u88C5 Sanqian`
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
["NOT_RUNNING" /* NOT_RUNNING */]: {
|
|
32
|
+
en: `Sanqian is not running.`,
|
|
33
|
+
zh: `Sanqian \u672A\u5728\u8FD0\u884C\u3002`,
|
|
34
|
+
hint: {
|
|
35
|
+
en: `Please start Sanqian first, or enable autoLaunchSanqian option in SDK config.`,
|
|
36
|
+
zh: `\u8BF7\u5148\u542F\u52A8 Sanqian\uFF0C\u6216\u5728 SDK \u914D\u7F6E\u4E2D\u542F\u7528 autoLaunchSanqian \u9009\u9879\u3002`
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
["CONNECTION_TIMEOUT" /* CONNECTION_TIMEOUT */]: {
|
|
40
|
+
en: `Failed to connect to Sanqian (connection timeout).`,
|
|
41
|
+
zh: `\u8FDE\u63A5 Sanqian \u5931\u8D25\uFF08\u8FDE\u63A5\u8D85\u65F6\uFF09\u3002`,
|
|
42
|
+
hint: {
|
|
43
|
+
en: `Please check if Sanqian is running properly. If the problem persists, try restarting Sanqian.`,
|
|
44
|
+
zh: `\u8BF7\u68C0\u67E5 Sanqian \u662F\u5426\u6B63\u5E38\u8FD0\u884C\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u5C1D\u8BD5\u91CD\u542F Sanqian\u3002`
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
["STARTUP_TIMEOUT" /* STARTUP_TIMEOUT */]: {
|
|
48
|
+
en: `Sanqian failed to start within 2 minutes.`,
|
|
49
|
+
zh: `Sanqian \u5728 2 \u5206\u949F\u5185\u672A\u80FD\u542F\u52A8\u3002`,
|
|
50
|
+
hint: {
|
|
51
|
+
en: `Please try starting Sanqian manually. If it fails to start, reinstall from ${SANQIAN_WEBSITE}`,
|
|
52
|
+
zh: `\u8BF7\u5C1D\u8BD5\u624B\u52A8\u542F\u52A8 Sanqian\u3002\u5982\u4ECD\u65E0\u6CD5\u542F\u52A8\uFF0C\u8BF7\u4ECE ${SANQIAN_WEBSITE} \u91CD\u65B0\u5B89\u88C5\u3002`
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
["REGISTRATION_FAILED" /* REGISTRATION_FAILED */]: {
|
|
56
|
+
en: `Failed to register with Sanqian.`,
|
|
57
|
+
zh: `\u5411 Sanqian \u6CE8\u518C\u5931\u8D25\u3002`,
|
|
58
|
+
hint: {
|
|
59
|
+
en: `Please check your SDK configuration (appName, tools). If the problem persists, try restarting Sanqian.`,
|
|
60
|
+
zh: `\u8BF7\u68C0\u67E5 SDK \u914D\u7F6E\uFF08appName\u3001tools\uFF09\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u5C1D\u8BD5\u91CD\u542F Sanqian\u3002`
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
["REQUEST_TIMEOUT" /* REQUEST_TIMEOUT */]: {
|
|
64
|
+
en: `Request timed out.`,
|
|
65
|
+
zh: `\u8BF7\u6C42\u8D85\u65F6\u3002`,
|
|
66
|
+
hint: {
|
|
67
|
+
en: `The operation took too long. Please try again.`,
|
|
68
|
+
zh: `\u64CD\u4F5C\u8017\u65F6\u8FC7\u957F\uFF0C\u8BF7\u91CD\u8BD5\u3002`
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
["REQUEST_FAILED" /* REQUEST_FAILED */]: {
|
|
72
|
+
en: `Request failed.`,
|
|
73
|
+
zh: `\u8BF7\u6C42\u5931\u8D25\u3002`,
|
|
74
|
+
hint: {
|
|
75
|
+
en: `Please check the error details and try again.`,
|
|
76
|
+
zh: `\u8BF7\u68C0\u67E5\u9519\u8BEF\u8BE6\u60C5\u540E\u91CD\u8BD5\u3002`
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
["DISCONNECTED" /* DISCONNECTED */]: {
|
|
80
|
+
en: `Disconnected from Sanqian.`,
|
|
81
|
+
zh: `\u4E0E Sanqian \u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00\u3002`,
|
|
82
|
+
hint: {
|
|
83
|
+
en: `The SDK will automatically reconnect. If problems persist, check if Sanqian is still running.`,
|
|
84
|
+
zh: `SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u68C0\u67E5 Sanqian \u662F\u5426\u4ECD\u5728\u8FD0\u884C\u3002`
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
["WEBSOCKET_ERROR" /* WEBSOCKET_ERROR */]: {
|
|
88
|
+
en: `WebSocket connection error.`,
|
|
89
|
+
zh: `WebSocket \u8FDE\u63A5\u9519\u8BEF\u3002`,
|
|
90
|
+
hint: {
|
|
91
|
+
en: `Please check your network and firewall settings. Sanqian uses local WebSocket on port shown in settings.`,
|
|
92
|
+
zh: `\u8BF7\u68C0\u67E5\u7F51\u7EDC\u548C\u9632\u706B\u5899\u8BBE\u7F6E\u3002Sanqian \u4F7F\u7528\u672C\u5730 WebSocket\uFF0C\u7AEF\u53E3\u53EF\u5728\u8BBE\u7F6E\u4E2D\u67E5\u770B\u3002`
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
["AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */]: {
|
|
96
|
+
en: `Agent not found.`,
|
|
97
|
+
zh: `\u627E\u4E0D\u5230\u8BE5 Agent\u3002`,
|
|
98
|
+
hint: {
|
|
99
|
+
en: `Please check the agent ID. Use listAgents() to see available agents.`,
|
|
100
|
+
zh: `\u8BF7\u68C0\u67E5 Agent ID\u3002\u4F7F\u7528 listAgents() \u67E5\u770B\u53EF\u7528\u7684 Agent\u3002`
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
["CONVERSATION_NOT_FOUND" /* CONVERSATION_NOT_FOUND */]: {
|
|
104
|
+
en: `Conversation not found.`,
|
|
105
|
+
zh: `\u627E\u4E0D\u5230\u8BE5\u5BF9\u8BDD\u3002`,
|
|
106
|
+
hint: {
|
|
107
|
+
en: `The conversation may have been deleted. Start a new conversation with startConversation().`,
|
|
108
|
+
zh: `\u8BE5\u5BF9\u8BDD\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\u3002\u4F7F\u7528 startConversation() \u5F00\u59CB\u65B0\u5BF9\u8BDD\u3002`
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
["TOOL_NOT_FOUND" /* TOOL_NOT_FOUND */]: {
|
|
112
|
+
en: `Tool not found.`,
|
|
113
|
+
zh: `\u627E\u4E0D\u5230\u8BE5\u5DE5\u5177\u3002`,
|
|
114
|
+
hint: {
|
|
115
|
+
en: `Please check that the tool is registered in your SDK config.`,
|
|
116
|
+
zh: `\u8BF7\u68C0\u67E5\u8BE5\u5DE5\u5177\u662F\u5426\u5DF2\u5728 SDK \u914D\u7F6E\u4E2D\u6CE8\u518C\u3002`
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
["TOOL_EXECUTION_TIMEOUT" /* TOOL_EXECUTION_TIMEOUT */]: {
|
|
120
|
+
en: `Tool execution timed out.`,
|
|
121
|
+
zh: `\u5DE5\u5177\u6267\u884C\u8D85\u65F6\u3002`,
|
|
122
|
+
hint: {
|
|
123
|
+
en: `The tool took too long to execute. Consider increasing toolExecutionTimeout in SDK config.`,
|
|
124
|
+
zh: `\u5DE5\u5177\u6267\u884C\u65F6\u95F4\u8FC7\u957F\u3002\u53EF\u5728 SDK \u914D\u7F6E\u4E2D\u589E\u52A0 toolExecutionTimeout\u3002`
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var SanqianSDKError = class extends Error {
|
|
129
|
+
code;
|
|
130
|
+
messageZh;
|
|
131
|
+
hint;
|
|
132
|
+
hintZh;
|
|
133
|
+
constructor(code, details) {
|
|
134
|
+
const msg = ErrorMessages[code];
|
|
135
|
+
const fullMessage = details ? `${msg.en} ${details}` : msg.en;
|
|
136
|
+
super(fullMessage);
|
|
137
|
+
this.name = "SanqianSDKError";
|
|
138
|
+
this.code = code;
|
|
139
|
+
this.messageZh = details ? `${msg.zh} ${details}` : msg.zh;
|
|
140
|
+
this.hint = msg.hint?.en;
|
|
141
|
+
this.hintZh = msg.hint?.zh;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get full error message with hint (English)
|
|
145
|
+
*/
|
|
146
|
+
getFullMessage() {
|
|
147
|
+
return this.hint ? `${this.message}
|
|
148
|
+
${this.hint}` : this.message;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get full error message with hint (Chinese)
|
|
152
|
+
*/
|
|
153
|
+
getFullMessageZh() {
|
|
154
|
+
return this.hintZh ? `${this.messageZh}
|
|
155
|
+
${this.hintZh}` : this.messageZh;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get bilingual error message
|
|
159
|
+
*/
|
|
160
|
+
getBilingualMessage() {
|
|
161
|
+
return `${this.getFullMessage()}
|
|
162
|
+
|
|
163
|
+
${this.getFullMessageZh()}`;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
function createSDKError(code, details) {
|
|
167
|
+
return new SanqianSDKError(code, details);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/client.browser.ts
|
|
171
|
+
var SanqianSDK = class _SanqianSDK {
|
|
172
|
+
config;
|
|
173
|
+
ws = null;
|
|
174
|
+
connectionInfo = null;
|
|
175
|
+
state = {
|
|
176
|
+
connected: false,
|
|
177
|
+
registering: false,
|
|
178
|
+
registered: false,
|
|
179
|
+
reconnectAttempts: 0
|
|
180
|
+
};
|
|
181
|
+
// Tool handlers by name
|
|
182
|
+
toolHandlers = /* @__PURE__ */ new Map();
|
|
183
|
+
// Pending request futures
|
|
184
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
185
|
+
// Timers
|
|
186
|
+
heartbeatTimer = null;
|
|
187
|
+
reconnectTimer = null;
|
|
188
|
+
heartbeatAckPending = false;
|
|
189
|
+
missedHeartbeats = 0;
|
|
190
|
+
static MAX_MISSED_HEARTBEATS = 2;
|
|
191
|
+
// Connection promise for deduplication
|
|
192
|
+
connectingPromise = null;
|
|
193
|
+
// Reconnect reference count
|
|
194
|
+
reconnectRefCount = 0;
|
|
195
|
+
// Event listeners
|
|
196
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
197
|
+
// ============================================
|
|
198
|
+
// Debug Logging
|
|
199
|
+
// ============================================
|
|
200
|
+
log(...args) {
|
|
201
|
+
if (this.config.debug) {
|
|
202
|
+
console.log("[SDK]", ...args);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
warn(...args) {
|
|
206
|
+
if (this.config.debug) {
|
|
207
|
+
console.warn("[SDK]", ...args);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
constructor(config) {
|
|
211
|
+
if (!config.connectionInfo) {
|
|
212
|
+
throw new SanqianSDKError(
|
|
213
|
+
"NOT_RUNNING" /* NOT_RUNNING */,
|
|
214
|
+
"Browser build requires connectionInfo in config. Use the Node.js build for auto-discovery, or provide connectionInfo manually."
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
this.config = {
|
|
218
|
+
reconnectInterval: 5e3,
|
|
219
|
+
heartbeatInterval: 3e4,
|
|
220
|
+
toolExecutionTimeout: 3e4,
|
|
221
|
+
debug: false,
|
|
222
|
+
autoLaunchSanqian: false,
|
|
223
|
+
// Not supported in browser
|
|
224
|
+
...config
|
|
225
|
+
};
|
|
226
|
+
for (const tool of config.tools) {
|
|
227
|
+
this.toolHandlers.set(tool.name, tool.handler);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Build WebSocket URL from connection info
|
|
232
|
+
*/
|
|
233
|
+
buildWebSocketUrl(info) {
|
|
234
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
235
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
236
|
+
}
|
|
237
|
+
// ============================================
|
|
238
|
+
// Lifecycle
|
|
239
|
+
// ============================================
|
|
240
|
+
async connect() {
|
|
241
|
+
return this.ensureReady();
|
|
242
|
+
}
|
|
243
|
+
async connectWithInfo(info) {
|
|
244
|
+
this.connectionInfo = info;
|
|
245
|
+
const url = this.buildWebSocketUrl(info);
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
this.log(`Connecting to ${url}`);
|
|
248
|
+
this.ws = new WebSocket(url);
|
|
249
|
+
const connectTimeout = setTimeout(() => {
|
|
250
|
+
reject(createSDKError("CONNECTION_TIMEOUT" /* CONNECTION_TIMEOUT */));
|
|
251
|
+
this.ws?.close();
|
|
252
|
+
}, 1e4);
|
|
253
|
+
this.ws.onopen = async () => {
|
|
254
|
+
clearTimeout(connectTimeout);
|
|
255
|
+
this.log("WebSocket connected");
|
|
256
|
+
this.state.connected = true;
|
|
257
|
+
this.state.reconnectAttempts = 0;
|
|
258
|
+
this.emit("connected");
|
|
259
|
+
try {
|
|
260
|
+
await this.register();
|
|
261
|
+
resolve();
|
|
262
|
+
} catch (e) {
|
|
263
|
+
reject(e);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
this.ws.onclose = (event) => {
|
|
267
|
+
const reasonRaw = event.reason;
|
|
268
|
+
const reason = typeof reasonRaw === "string" ? reasonRaw : reasonRaw?.toString() || "";
|
|
269
|
+
this.log(`WebSocket closed: ${event.code} ${reason}`);
|
|
270
|
+
this.handleDisconnect(reason);
|
|
271
|
+
};
|
|
272
|
+
this.ws.onerror = (event) => {
|
|
273
|
+
const error = event.error || new Error("WebSocket error");
|
|
274
|
+
console.error("[SDK] WebSocket error:", error);
|
|
275
|
+
this.state.lastError = error;
|
|
276
|
+
this.emit("error", error);
|
|
277
|
+
};
|
|
278
|
+
this.ws.onmessage = (event) => {
|
|
279
|
+
try {
|
|
280
|
+
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
281
|
+
const message = JSON.parse(data);
|
|
282
|
+
this.handleMessage(message);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
this.warn("Failed to parse message:", e);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async disconnect() {
|
|
290
|
+
this.stopHeartbeat();
|
|
291
|
+
this.stopReconnect();
|
|
292
|
+
if (this.ws) {
|
|
293
|
+
this.ws.close(1e3, "Client disconnect");
|
|
294
|
+
this.ws = null;
|
|
295
|
+
}
|
|
296
|
+
this.state = {
|
|
297
|
+
connected: false,
|
|
298
|
+
registering: false,
|
|
299
|
+
registered: false,
|
|
300
|
+
reconnectAttempts: 0
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// ============================================
|
|
304
|
+
// Registration
|
|
305
|
+
// ============================================
|
|
306
|
+
async register() {
|
|
307
|
+
this.state.registering = true;
|
|
308
|
+
const msgId = this.generateId();
|
|
309
|
+
const message = {
|
|
310
|
+
id: msgId,
|
|
311
|
+
type: "register",
|
|
312
|
+
app: {
|
|
313
|
+
name: this.config.appName,
|
|
314
|
+
version: this.config.appVersion,
|
|
315
|
+
display_name: this.config.displayName,
|
|
316
|
+
launch_command: this.config.launchCommand
|
|
317
|
+
},
|
|
318
|
+
tools: this.config.tools.map((t) => ({
|
|
319
|
+
name: `${this.config.appName}:${t.name}`,
|
|
320
|
+
description: t.description,
|
|
321
|
+
parameters: t.parameters,
|
|
322
|
+
searchable: t.searchable ?? true
|
|
323
|
+
}))
|
|
324
|
+
};
|
|
325
|
+
try {
|
|
326
|
+
const response = await this.sendAndWait(
|
|
327
|
+
message,
|
|
328
|
+
msgId,
|
|
329
|
+
1e4
|
|
330
|
+
);
|
|
331
|
+
if (!response.success) {
|
|
332
|
+
throw createSDKError("REGISTRATION_FAILED" /* REGISTRATION_FAILED */, response.error);
|
|
333
|
+
}
|
|
334
|
+
this.state.registering = false;
|
|
335
|
+
this.state.registered = true;
|
|
336
|
+
this.startHeartbeat();
|
|
337
|
+
this.emit("registered");
|
|
338
|
+
this.log(`Registered as '${this.config.appName}'`);
|
|
339
|
+
} catch (e) {
|
|
340
|
+
this.state.registering = false;
|
|
341
|
+
throw e;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// ============================================
|
|
345
|
+
// Message Handling
|
|
346
|
+
// ============================================
|
|
347
|
+
handleMessage(message) {
|
|
348
|
+
const { id, type } = message;
|
|
349
|
+
if (id && this.pendingRequests.has(id)) {
|
|
350
|
+
const pending = this.pendingRequests.get(id);
|
|
351
|
+
this.pendingRequests.delete(id);
|
|
352
|
+
pending.resolve(message);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
switch (type) {
|
|
356
|
+
case "tool_call":
|
|
357
|
+
this.handleToolCall(message);
|
|
358
|
+
break;
|
|
359
|
+
case "heartbeat_ack":
|
|
360
|
+
this.heartbeatAckPending = false;
|
|
361
|
+
this.missedHeartbeats = 0;
|
|
362
|
+
break;
|
|
363
|
+
case "chat_stream":
|
|
364
|
+
this.handleChatStream(message);
|
|
365
|
+
break;
|
|
366
|
+
default:
|
|
367
|
+
this.warn(`Unknown message type: ${type}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
handleChatStream(message) {
|
|
371
|
+
const {
|
|
372
|
+
id,
|
|
373
|
+
event,
|
|
374
|
+
content,
|
|
375
|
+
tool_call,
|
|
376
|
+
tool_result,
|
|
377
|
+
conversation_id,
|
|
378
|
+
title,
|
|
379
|
+
usage,
|
|
380
|
+
error
|
|
381
|
+
} = message;
|
|
382
|
+
if (!id) return;
|
|
383
|
+
const handler = this.streamHandlers.get(id);
|
|
384
|
+
if (!handler) {
|
|
385
|
+
this.warn(`No stream handler for message ${id}`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
switch (event) {
|
|
389
|
+
case "text":
|
|
390
|
+
handler.onEvent({ type: "text", content });
|
|
391
|
+
break;
|
|
392
|
+
case "tool_call":
|
|
393
|
+
handler.onEvent({ type: "tool_call", tool_call });
|
|
394
|
+
break;
|
|
395
|
+
case "tool_result":
|
|
396
|
+
if (tool_result) {
|
|
397
|
+
handler.onEvent({
|
|
398
|
+
type: "tool_call",
|
|
399
|
+
tool_call: {
|
|
400
|
+
id: tool_result.call_id,
|
|
401
|
+
type: "function",
|
|
402
|
+
function: {
|
|
403
|
+
name: "tool_result",
|
|
404
|
+
arguments: JSON.stringify(tool_result)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
case "done":
|
|
411
|
+
handler.onDone({
|
|
412
|
+
message: message.message || { role: "assistant", content: "" },
|
|
413
|
+
conversationId: conversation_id || "",
|
|
414
|
+
title,
|
|
415
|
+
usage
|
|
416
|
+
});
|
|
417
|
+
break;
|
|
418
|
+
case "error":
|
|
419
|
+
handler.onError(new Error(error || "Unknown stream error"));
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async handleToolCall(message) {
|
|
424
|
+
this.log(`handleToolCall received:`, JSON.stringify(message));
|
|
425
|
+
const { id, call_id, name, arguments: args } = message;
|
|
426
|
+
const msgId = id || call_id;
|
|
427
|
+
const toolName = name.includes(":") ? name.split(":")[1] : name;
|
|
428
|
+
this.log(
|
|
429
|
+
`Looking for handler: '${toolName}', available handlers:`,
|
|
430
|
+
Array.from(this.toolHandlers.keys())
|
|
431
|
+
);
|
|
432
|
+
const handler = this.toolHandlers.get(toolName);
|
|
433
|
+
this.emit("tool_call", { name: toolName, arguments: args });
|
|
434
|
+
if (!handler) {
|
|
435
|
+
await this.sendToolResult(
|
|
436
|
+
msgId,
|
|
437
|
+
call_id,
|
|
438
|
+
false,
|
|
439
|
+
void 0,
|
|
440
|
+
`Tool '${toolName}' not found`
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
this.log(`Executing tool '${toolName}' with args:`, args);
|
|
446
|
+
const result = await Promise.race([
|
|
447
|
+
handler(args),
|
|
448
|
+
this.createTimeout(this.config.toolExecutionTimeout)
|
|
449
|
+
]);
|
|
450
|
+
this.log(`Tool '${toolName}' completed successfully`);
|
|
451
|
+
await this.sendToolResult(msgId, call_id, true, result);
|
|
452
|
+
} catch (e) {
|
|
453
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
454
|
+
const errorStack = e instanceof Error ? e.stack : void 0;
|
|
455
|
+
this.log(`Tool '${toolName}' failed:`, errorMessage);
|
|
456
|
+
if (errorStack) {
|
|
457
|
+
this.log(`Tool error stack:`, errorStack);
|
|
458
|
+
}
|
|
459
|
+
await this.sendToolResult(msgId, call_id, false, void 0, errorMessage);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async sendToolResult(id, callId, success, result, error) {
|
|
463
|
+
const message = {
|
|
464
|
+
id,
|
|
465
|
+
type: "tool_result",
|
|
466
|
+
call_id: callId,
|
|
467
|
+
success,
|
|
468
|
+
result,
|
|
469
|
+
error
|
|
470
|
+
};
|
|
471
|
+
this.log(
|
|
472
|
+
`Sending tool_result for ${callId}:`,
|
|
473
|
+
success ? "success" : `error: ${error}`
|
|
474
|
+
);
|
|
475
|
+
this.send(message);
|
|
476
|
+
}
|
|
477
|
+
// ============================================
|
|
478
|
+
// Connection Management
|
|
479
|
+
// ============================================
|
|
480
|
+
handleDisconnect(reason) {
|
|
481
|
+
this.stopHeartbeat();
|
|
482
|
+
this.state.connected = false;
|
|
483
|
+
this.state.registered = false;
|
|
484
|
+
this.emit("disconnected", reason);
|
|
485
|
+
for (const [, pending] of this.pendingRequests) {
|
|
486
|
+
pending.reject(createSDKError("DISCONNECTED" /* DISCONNECTED */));
|
|
487
|
+
}
|
|
488
|
+
this.pendingRequests.clear();
|
|
489
|
+
if (reason === "Client disconnect") {
|
|
490
|
+
this.log("Client disconnected intentionally, skipping auto-reconnect");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (reason === "Replaced by new connection") {
|
|
494
|
+
this.log("Connection replaced by newer one, skipping auto-reconnect");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (this.connectingPromise) {
|
|
498
|
+
this.log(
|
|
499
|
+
"Connection attempt already in progress, skipping auto-reconnect"
|
|
500
|
+
);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (!this.shouldAutoReconnect()) {
|
|
504
|
+
this.log(
|
|
505
|
+
"No components require reconnection (refCount=0), skipping auto-reconnect"
|
|
506
|
+
);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this.scheduleReconnect();
|
|
510
|
+
}
|
|
511
|
+
shouldAutoReconnect() {
|
|
512
|
+
return this.reconnectRefCount > 0;
|
|
513
|
+
}
|
|
514
|
+
acquireReconnect() {
|
|
515
|
+
this.reconnectRefCount++;
|
|
516
|
+
this.log(`acquireReconnect: refCount=${this.reconnectRefCount}`);
|
|
517
|
+
}
|
|
518
|
+
releaseReconnect() {
|
|
519
|
+
this.reconnectRefCount = Math.max(0, this.reconnectRefCount - 1);
|
|
520
|
+
this.log(`releaseReconnect: refCount=${this.reconnectRefCount}`);
|
|
521
|
+
}
|
|
522
|
+
scheduleReconnect() {
|
|
523
|
+
if (this.reconnectTimer) return;
|
|
524
|
+
const baseDelay = Math.min(
|
|
525
|
+
500 * Math.pow(2, this.state.reconnectAttempts),
|
|
526
|
+
5e3
|
|
527
|
+
);
|
|
528
|
+
const jitter = Math.random() * 500;
|
|
529
|
+
const delay = baseDelay + jitter;
|
|
530
|
+
this.log(
|
|
531
|
+
`Scheduling reconnect in ${Math.round(delay)}ms (attempt ${this.state.reconnectAttempts + 1})`
|
|
532
|
+
);
|
|
533
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
534
|
+
this.reconnectTimer = null;
|
|
535
|
+
this.state.reconnectAttempts++;
|
|
536
|
+
try {
|
|
537
|
+
await this.ensureReady();
|
|
538
|
+
} catch (e) {
|
|
539
|
+
console.error("[SDK] Reconnect failed:", e);
|
|
540
|
+
if (!this.isConnected()) {
|
|
541
|
+
this.scheduleReconnect();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}, delay);
|
|
545
|
+
}
|
|
546
|
+
stopReconnect() {
|
|
547
|
+
if (this.reconnectTimer) {
|
|
548
|
+
clearTimeout(this.reconnectTimer);
|
|
549
|
+
this.reconnectTimer = null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// ============================================
|
|
553
|
+
// Heartbeat
|
|
554
|
+
// ============================================
|
|
555
|
+
startHeartbeat() {
|
|
556
|
+
this.stopHeartbeat();
|
|
557
|
+
this.missedHeartbeats = 0;
|
|
558
|
+
this.heartbeatAckPending = false;
|
|
559
|
+
this.heartbeatTimer = setInterval(() => {
|
|
560
|
+
if (this.heartbeatAckPending) {
|
|
561
|
+
this.missedHeartbeats++;
|
|
562
|
+
this.warn(
|
|
563
|
+
`Missed heartbeat ack (${this.missedHeartbeats}/${_SanqianSDK.MAX_MISSED_HEARTBEATS})`
|
|
564
|
+
);
|
|
565
|
+
if (this.missedHeartbeats >= _SanqianSDK.MAX_MISSED_HEARTBEATS) {
|
|
566
|
+
this.warn("Too many missed heartbeat acks, connection may be dead");
|
|
567
|
+
this.ws?.close(4e3, "Heartbeat timeout");
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const message = {
|
|
572
|
+
type: "heartbeat",
|
|
573
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
574
|
+
};
|
|
575
|
+
this.heartbeatAckPending = true;
|
|
576
|
+
try {
|
|
577
|
+
this.send(message);
|
|
578
|
+
} catch (e) {
|
|
579
|
+
console.error("[SDK] Heartbeat send failed:", e);
|
|
580
|
+
this.ws?.close(4e3, "Heartbeat send failed");
|
|
581
|
+
}
|
|
582
|
+
}, this.config.heartbeatInterval);
|
|
583
|
+
}
|
|
584
|
+
stopHeartbeat() {
|
|
585
|
+
if (this.heartbeatTimer) {
|
|
586
|
+
clearInterval(this.heartbeatTimer);
|
|
587
|
+
this.heartbeatTimer = null;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// ============================================
|
|
591
|
+
// Communication
|
|
592
|
+
// ============================================
|
|
593
|
+
send(message) {
|
|
594
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
595
|
+
const error = new Error(
|
|
596
|
+
`WebSocket not open (state: ${this.ws?.readyState ?? "null"}), cannot send message`
|
|
597
|
+
);
|
|
598
|
+
console.error(`[SDK] ${error.message}:`, message);
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
601
|
+
const data = JSON.stringify(message);
|
|
602
|
+
this.log(`WebSocket send:`, data.substring(0, 200));
|
|
603
|
+
this.ws.send(data);
|
|
604
|
+
}
|
|
605
|
+
sendAndWait(message, id, timeout) {
|
|
606
|
+
return new Promise((resolve, reject) => {
|
|
607
|
+
const timer = setTimeout(() => {
|
|
608
|
+
this.pendingRequests.delete(id);
|
|
609
|
+
reject(createSDKError("REQUEST_TIMEOUT" /* REQUEST_TIMEOUT */));
|
|
610
|
+
}, timeout);
|
|
611
|
+
this.pendingRequests.set(id, {
|
|
612
|
+
resolve: (value) => {
|
|
613
|
+
clearTimeout(timer);
|
|
614
|
+
resolve(value);
|
|
615
|
+
},
|
|
616
|
+
reject: (error) => {
|
|
617
|
+
clearTimeout(timer);
|
|
618
|
+
reject(error);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
try {
|
|
622
|
+
this.send(message);
|
|
623
|
+
} catch (e) {
|
|
624
|
+
clearTimeout(timer);
|
|
625
|
+
this.pendingRequests.delete(id);
|
|
626
|
+
reject(e);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
// ============================================
|
|
631
|
+
// Public API
|
|
632
|
+
// ============================================
|
|
633
|
+
getState() {
|
|
634
|
+
return { ...this.state };
|
|
635
|
+
}
|
|
636
|
+
isConnected() {
|
|
637
|
+
return this.state.connected && this.state.registered;
|
|
638
|
+
}
|
|
639
|
+
async ensureReady() {
|
|
640
|
+
if (this.isConnected()) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (this.connectingPromise) {
|
|
644
|
+
this.log("Connection already in progress, waiting...");
|
|
645
|
+
return this.connectingPromise;
|
|
646
|
+
}
|
|
647
|
+
this.connectingPromise = this.doFullConnect();
|
|
648
|
+
try {
|
|
649
|
+
await this.connectingPromise;
|
|
650
|
+
} finally {
|
|
651
|
+
this.connectingPromise = null;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async doFullConnect() {
|
|
655
|
+
this.log("Starting connection (browser mode)...");
|
|
656
|
+
const info = this.config.connectionInfo;
|
|
657
|
+
await this.connectWithInfo(info);
|
|
658
|
+
}
|
|
659
|
+
async updateTools(tools) {
|
|
660
|
+
this.toolHandlers.clear();
|
|
661
|
+
for (const tool of tools) {
|
|
662
|
+
this.toolHandlers.set(tool.name, tool.handler);
|
|
663
|
+
}
|
|
664
|
+
if (this.isConnected()) {
|
|
665
|
+
const msgId = this.generateId();
|
|
666
|
+
const message = {
|
|
667
|
+
id: msgId,
|
|
668
|
+
type: "tools_update",
|
|
669
|
+
tools: tools.map((t) => ({
|
|
670
|
+
name: `${this.config.appName}:${t.name}`,
|
|
671
|
+
description: t.description,
|
|
672
|
+
parameters: t.parameters,
|
|
673
|
+
searchable: t.searchable ?? true
|
|
674
|
+
}))
|
|
675
|
+
};
|
|
676
|
+
await this.sendAndWait(message, msgId, 5e3);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// ============================================
|
|
680
|
+
// Private Agent API
|
|
681
|
+
// ============================================
|
|
682
|
+
async createAgent(config) {
|
|
683
|
+
await this.ensureReady();
|
|
684
|
+
const msgId = this.generateId();
|
|
685
|
+
const message = {
|
|
686
|
+
id: msgId,
|
|
687
|
+
type: "create_agent",
|
|
688
|
+
agent: config
|
|
689
|
+
};
|
|
690
|
+
const response = await this.sendAndWait(
|
|
691
|
+
message,
|
|
692
|
+
msgId,
|
|
693
|
+
1e4
|
|
694
|
+
);
|
|
695
|
+
if (!response.success) {
|
|
696
|
+
throw createSDKError("AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */, response.error);
|
|
697
|
+
}
|
|
698
|
+
if (response.agent) {
|
|
699
|
+
return response.agent;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
agent_id: response.agent_id,
|
|
703
|
+
name: config.name,
|
|
704
|
+
description: config.description,
|
|
705
|
+
tools: config.tools || []
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async listAgents() {
|
|
709
|
+
await this.ensureReady();
|
|
710
|
+
const msgId = this.generateId();
|
|
711
|
+
const message = {
|
|
712
|
+
id: msgId,
|
|
713
|
+
type: "list_agents"
|
|
714
|
+
};
|
|
715
|
+
const response = await this.sendAndWait(
|
|
716
|
+
message,
|
|
717
|
+
msgId,
|
|
718
|
+
1e4
|
|
719
|
+
);
|
|
720
|
+
if (response.error) {
|
|
721
|
+
throw new Error(response.error);
|
|
722
|
+
}
|
|
723
|
+
return response.agents;
|
|
724
|
+
}
|
|
725
|
+
async deleteAgent(agentId) {
|
|
726
|
+
await this.ensureReady();
|
|
727
|
+
const msgId = this.generateId();
|
|
728
|
+
const message = {
|
|
729
|
+
id: msgId,
|
|
730
|
+
type: "delete_agent",
|
|
731
|
+
agent_id: agentId
|
|
732
|
+
};
|
|
733
|
+
const response = await this.sendAndWait(
|
|
734
|
+
message,
|
|
735
|
+
msgId,
|
|
736
|
+
1e4
|
|
737
|
+
);
|
|
738
|
+
if (!response.success) {
|
|
739
|
+
throw createSDKError("AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */, response.error);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
async updateAgent(agentId, updates) {
|
|
743
|
+
await this.ensureReady();
|
|
744
|
+
const msgId = this.generateId();
|
|
745
|
+
const message = {
|
|
746
|
+
id: msgId,
|
|
747
|
+
type: "update_agent",
|
|
748
|
+
agent_id: agentId,
|
|
749
|
+
updates
|
|
750
|
+
};
|
|
751
|
+
const response = await this.sendAndWait(
|
|
752
|
+
message,
|
|
753
|
+
msgId,
|
|
754
|
+
1e4
|
|
755
|
+
);
|
|
756
|
+
if (!response.success || !response.agent) {
|
|
757
|
+
throw createSDKError("AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */, response.error);
|
|
758
|
+
}
|
|
759
|
+
return response.agent;
|
|
760
|
+
}
|
|
761
|
+
// ============================================
|
|
762
|
+
// Conversation API
|
|
763
|
+
// ============================================
|
|
764
|
+
async listConversations(options) {
|
|
765
|
+
await this.ensureReady();
|
|
766
|
+
const msgId = this.generateId();
|
|
767
|
+
const message = {
|
|
768
|
+
id: msgId,
|
|
769
|
+
type: "list_conversations",
|
|
770
|
+
agent_id: options?.agentId,
|
|
771
|
+
limit: options?.limit,
|
|
772
|
+
offset: options?.offset
|
|
773
|
+
};
|
|
774
|
+
const response = await this.sendAndWait(
|
|
775
|
+
message,
|
|
776
|
+
msgId,
|
|
777
|
+
1e4
|
|
778
|
+
);
|
|
779
|
+
if (response.error) {
|
|
780
|
+
throw new Error(response.error);
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
conversations: response.conversations,
|
|
784
|
+
total: response.total
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
async getConversation(conversationId, options) {
|
|
788
|
+
await this.ensureReady();
|
|
789
|
+
const msgId = this.generateId();
|
|
790
|
+
const message = {
|
|
791
|
+
id: msgId,
|
|
792
|
+
type: "get_conversation",
|
|
793
|
+
conversation_id: conversationId,
|
|
794
|
+
include_messages: options?.includeMessages ?? true,
|
|
795
|
+
message_limit: options?.messageLimit,
|
|
796
|
+
message_offset: options?.messageOffset
|
|
797
|
+
};
|
|
798
|
+
const response = await this.sendAndWait(
|
|
799
|
+
message,
|
|
800
|
+
msgId,
|
|
801
|
+
1e4
|
|
802
|
+
);
|
|
803
|
+
if (!response.success || !response.conversation) {
|
|
804
|
+
throw createSDKError("CONVERSATION_NOT_FOUND" /* CONVERSATION_NOT_FOUND */, response.error);
|
|
805
|
+
}
|
|
806
|
+
return response.conversation;
|
|
807
|
+
}
|
|
808
|
+
async deleteConversation(conversationId) {
|
|
809
|
+
await this.ensureReady();
|
|
810
|
+
const msgId = this.generateId();
|
|
811
|
+
const message = {
|
|
812
|
+
id: msgId,
|
|
813
|
+
type: "delete_conversation",
|
|
814
|
+
conversation_id: conversationId
|
|
815
|
+
};
|
|
816
|
+
const response = await this.sendAndWait(
|
|
817
|
+
message,
|
|
818
|
+
msgId,
|
|
819
|
+
1e4
|
|
820
|
+
);
|
|
821
|
+
if (!response.success) {
|
|
822
|
+
throw createSDKError("CONVERSATION_NOT_FOUND" /* CONVERSATION_NOT_FOUND */, response.error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// ============================================
|
|
826
|
+
// Chat API
|
|
827
|
+
// ============================================
|
|
828
|
+
streamHandlers = /* @__PURE__ */ new Map();
|
|
829
|
+
async chat(agentId, messages, options) {
|
|
830
|
+
await this.ensureReady();
|
|
831
|
+
const msgId = this.generateId();
|
|
832
|
+
const message = {
|
|
833
|
+
id: msgId,
|
|
834
|
+
type: "chat",
|
|
835
|
+
agent_id: agentId,
|
|
836
|
+
messages,
|
|
837
|
+
conversation_id: options?.conversationId,
|
|
838
|
+
stream: false,
|
|
839
|
+
remote_tools: options?.remoteTools?.map((t) => ({
|
|
840
|
+
name: t.name,
|
|
841
|
+
description: t.description,
|
|
842
|
+
parameters: t.parameters
|
|
843
|
+
}))
|
|
844
|
+
};
|
|
845
|
+
const response = await this.sendAndWait(
|
|
846
|
+
message,
|
|
847
|
+
msgId,
|
|
848
|
+
6e5
|
|
849
|
+
);
|
|
850
|
+
if (!response.success) {
|
|
851
|
+
const errorLower = (response.error || "").toLowerCase();
|
|
852
|
+
if (errorLower.includes("agent") && errorLower.includes("not found")) {
|
|
853
|
+
throw createSDKError("AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */, response.error);
|
|
854
|
+
}
|
|
855
|
+
throw createSDKError("REQUEST_FAILED" /* REQUEST_FAILED */, response.error);
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
message: response.message,
|
|
859
|
+
conversationId: response.conversation_id,
|
|
860
|
+
title: response.title,
|
|
861
|
+
usage: response.usage
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
async *chatStream(agentId, messages, options) {
|
|
865
|
+
await this.ensureReady();
|
|
866
|
+
const msgId = this.generateId();
|
|
867
|
+
const message = {
|
|
868
|
+
id: msgId,
|
|
869
|
+
type: "chat",
|
|
870
|
+
agent_id: agentId,
|
|
871
|
+
messages,
|
|
872
|
+
conversation_id: options?.conversationId,
|
|
873
|
+
stream: true,
|
|
874
|
+
remote_tools: options?.remoteTools?.map((t) => ({
|
|
875
|
+
name: t.name,
|
|
876
|
+
description: t.description,
|
|
877
|
+
parameters: t.parameters
|
|
878
|
+
}))
|
|
879
|
+
};
|
|
880
|
+
const eventQueue = [];
|
|
881
|
+
let done = false;
|
|
882
|
+
let error = null;
|
|
883
|
+
let resolveNext = null;
|
|
884
|
+
this.streamHandlers.set(msgId, {
|
|
885
|
+
onEvent: (event) => {
|
|
886
|
+
eventQueue.push(event);
|
|
887
|
+
resolveNext?.();
|
|
888
|
+
},
|
|
889
|
+
onDone: (response) => {
|
|
890
|
+
eventQueue.push({
|
|
891
|
+
type: "done",
|
|
892
|
+
conversationId: response.conversationId,
|
|
893
|
+
title: response.title
|
|
894
|
+
});
|
|
895
|
+
done = true;
|
|
896
|
+
resolveNext?.();
|
|
897
|
+
},
|
|
898
|
+
onError: (e) => {
|
|
899
|
+
error = e;
|
|
900
|
+
resolveNext?.();
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
try {
|
|
904
|
+
this.send(message);
|
|
905
|
+
while (!done && !error) {
|
|
906
|
+
if (eventQueue.length > 0) {
|
|
907
|
+
yield eventQueue.shift();
|
|
908
|
+
} else {
|
|
909
|
+
await new Promise((resolve) => {
|
|
910
|
+
resolveNext = resolve;
|
|
911
|
+
});
|
|
912
|
+
resolveNext = null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
while (eventQueue.length > 0) {
|
|
916
|
+
yield eventQueue.shift();
|
|
917
|
+
}
|
|
918
|
+
if (error) {
|
|
919
|
+
throw error;
|
|
920
|
+
}
|
|
921
|
+
} finally {
|
|
922
|
+
this.streamHandlers.delete(msgId);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
startConversation(agentId) {
|
|
926
|
+
return new Conversation(this, agentId);
|
|
927
|
+
}
|
|
928
|
+
// ============================================
|
|
929
|
+
// Events
|
|
930
|
+
// ============================================
|
|
931
|
+
on(event, listener) {
|
|
932
|
+
if (!this.eventListeners.has(event)) {
|
|
933
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
934
|
+
}
|
|
935
|
+
this.eventListeners.get(event).add(listener);
|
|
936
|
+
return this;
|
|
937
|
+
}
|
|
938
|
+
off(event, listener) {
|
|
939
|
+
this.eventListeners.get(event)?.delete(listener);
|
|
940
|
+
return this;
|
|
941
|
+
}
|
|
942
|
+
once(event, listener) {
|
|
943
|
+
const onceWrapper = ((...args) => {
|
|
944
|
+
this.off(event, onceWrapper);
|
|
945
|
+
listener(...args);
|
|
946
|
+
});
|
|
947
|
+
return this.on(event, onceWrapper);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Remove all event listeners
|
|
951
|
+
*
|
|
952
|
+
* Call this to prevent memory leaks when disposing the SDK instance.
|
|
953
|
+
* If event is specified, only removes listeners for that event.
|
|
954
|
+
*/
|
|
955
|
+
removeAllListeners(event) {
|
|
956
|
+
if (event) {
|
|
957
|
+
this.eventListeners.delete(event);
|
|
958
|
+
} else {
|
|
959
|
+
this.eventListeners.clear();
|
|
960
|
+
}
|
|
961
|
+
return this;
|
|
962
|
+
}
|
|
963
|
+
emit(event, ...args) {
|
|
964
|
+
const listeners = this.eventListeners.get(event);
|
|
965
|
+
if (listeners) {
|
|
966
|
+
for (const listener of listeners) {
|
|
967
|
+
try {
|
|
968
|
+
listener(...args);
|
|
969
|
+
} catch (e) {
|
|
970
|
+
console.error(`[SDK] Event listener error for '${event}':`, e);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// ============================================
|
|
976
|
+
// Utilities
|
|
977
|
+
// ============================================
|
|
978
|
+
generateId() {
|
|
979
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
980
|
+
}
|
|
981
|
+
createTimeout(ms) {
|
|
982
|
+
return new Promise((_, reject) => {
|
|
983
|
+
setTimeout(
|
|
984
|
+
() => reject(createSDKError("TOOL_EXECUTION_TIMEOUT" /* TOOL_EXECUTION_TIMEOUT */)),
|
|
985
|
+
ms
|
|
986
|
+
);
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
var Conversation = class {
|
|
991
|
+
sdk;
|
|
992
|
+
agentId;
|
|
993
|
+
_conversationId = null;
|
|
994
|
+
constructor(sdk, agentId, conversationId) {
|
|
995
|
+
this.sdk = sdk;
|
|
996
|
+
this.agentId = agentId;
|
|
997
|
+
this._conversationId = conversationId || null;
|
|
998
|
+
}
|
|
999
|
+
get id() {
|
|
1000
|
+
return this._conversationId;
|
|
1001
|
+
}
|
|
1002
|
+
async send(content, options) {
|
|
1003
|
+
const response = await this.sdk.chat(
|
|
1004
|
+
this.agentId,
|
|
1005
|
+
[{ role: "user", content }],
|
|
1006
|
+
{
|
|
1007
|
+
conversationId: this._conversationId || void 0,
|
|
1008
|
+
remoteTools: options?.remoteTools
|
|
1009
|
+
}
|
|
1010
|
+
);
|
|
1011
|
+
if (response.conversationId && !this._conversationId) {
|
|
1012
|
+
this._conversationId = response.conversationId;
|
|
1013
|
+
}
|
|
1014
|
+
return response;
|
|
1015
|
+
}
|
|
1016
|
+
async *sendStream(content, options) {
|
|
1017
|
+
const stream = this.sdk.chatStream(
|
|
1018
|
+
this.agentId,
|
|
1019
|
+
[{ role: "user", content }],
|
|
1020
|
+
{
|
|
1021
|
+
conversationId: this._conversationId || void 0,
|
|
1022
|
+
remoteTools: options?.remoteTools
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
for await (const event of stream) {
|
|
1026
|
+
if (event.type === "done" && event.conversationId && !this._conversationId) {
|
|
1027
|
+
this._conversationId = event.conversationId;
|
|
1028
|
+
}
|
|
1029
|
+
yield event;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async delete() {
|
|
1033
|
+
if (!this._conversationId) {
|
|
1034
|
+
throw new Error("No conversation to delete");
|
|
1035
|
+
}
|
|
1036
|
+
await this.sdk.deleteConversation(this._conversationId);
|
|
1037
|
+
this._conversationId = null;
|
|
1038
|
+
}
|
|
1039
|
+
async getDetails(options) {
|
|
1040
|
+
if (!this._conversationId) {
|
|
1041
|
+
throw new Error("No conversation to get details for");
|
|
1042
|
+
}
|
|
1043
|
+
return this.sdk.getConversation(this._conversationId, {
|
|
1044
|
+
includeMessages: true,
|
|
1045
|
+
messageLimit: options?.messageLimit
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
export {
|
|
1050
|
+
Conversation,
|
|
1051
|
+
ErrorMessages,
|
|
1052
|
+
SANQIAN_WEBSITE,
|
|
1053
|
+
SDKErrorCode,
|
|
1054
|
+
SanqianSDK,
|
|
1055
|
+
SanqianSDKError,
|
|
1056
|
+
createSDKError
|
|
1057
|
+
};
|
|
1058
|
+
//# sourceMappingURL=index.browser.mjs.map
|