@yushaw/sanqian-sdk 0.1.0
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.d.mts +471 -0
- package/dist/index.d.ts +471 -0
- package/dist/index.js +1108 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1069 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
|
|
4
|
+
// src/discovery.ts
|
|
5
|
+
import { existsSync, readFileSync, watch } from "fs";
|
|
6
|
+
import { homedir, platform } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
var DiscoveryManager = class {
|
|
10
|
+
connectionInfo = null;
|
|
11
|
+
watcher = null;
|
|
12
|
+
onChange = null;
|
|
13
|
+
pollInterval = null;
|
|
14
|
+
/**
|
|
15
|
+
* Get the path to connection.json
|
|
16
|
+
*/
|
|
17
|
+
getConnectionFilePath() {
|
|
18
|
+
return join(homedir(), ".sanqian", "runtime", "connection.json");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read and validate connection info
|
|
22
|
+
*
|
|
23
|
+
* Returns null if:
|
|
24
|
+
* - File doesn't exist
|
|
25
|
+
* - File is invalid JSON
|
|
26
|
+
* - Process is not running
|
|
27
|
+
*/
|
|
28
|
+
read() {
|
|
29
|
+
const filePath = this.getConnectionFilePath();
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const content = readFileSync(filePath, "utf-8");
|
|
35
|
+
const info = JSON.parse(content);
|
|
36
|
+
if (!info.port || !info.token || !info.pid) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (!this.isProcessRunning(info.pid)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
this.connectionInfo = info;
|
|
43
|
+
return info;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Start watching for connection file changes
|
|
50
|
+
*/
|
|
51
|
+
startWatching(onChange) {
|
|
52
|
+
this.onChange = onChange;
|
|
53
|
+
const dir = join(homedir(), ".sanqian", "runtime");
|
|
54
|
+
if (!existsSync(dir)) {
|
|
55
|
+
this.pollInterval = setInterval(() => {
|
|
56
|
+
if (existsSync(dir)) {
|
|
57
|
+
if (this.pollInterval) {
|
|
58
|
+
clearInterval(this.pollInterval);
|
|
59
|
+
this.pollInterval = null;
|
|
60
|
+
}
|
|
61
|
+
this.setupWatcher(dir);
|
|
62
|
+
}
|
|
63
|
+
}, 2e3);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.setupWatcher(dir);
|
|
67
|
+
}
|
|
68
|
+
setupWatcher(dir) {
|
|
69
|
+
try {
|
|
70
|
+
this.watcher = watch(dir, (event, filename) => {
|
|
71
|
+
if (filename === "connection.json") {
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
const newInfo = this.read();
|
|
74
|
+
const oldInfoStr = JSON.stringify(this.connectionInfo);
|
|
75
|
+
const newInfoStr = JSON.stringify(newInfo);
|
|
76
|
+
if (oldInfoStr !== newInfoStr) {
|
|
77
|
+
this.connectionInfo = newInfo;
|
|
78
|
+
this.onChange?.(newInfo);
|
|
79
|
+
}
|
|
80
|
+
}, 100);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error("Failed to watch connection file:", e);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Stop watching
|
|
89
|
+
*/
|
|
90
|
+
stopWatching() {
|
|
91
|
+
if (this.pollInterval) {
|
|
92
|
+
clearInterval(this.pollInterval);
|
|
93
|
+
this.pollInterval = null;
|
|
94
|
+
}
|
|
95
|
+
this.watcher?.close();
|
|
96
|
+
this.watcher = null;
|
|
97
|
+
this.onChange = null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a process is running by PID
|
|
101
|
+
*/
|
|
102
|
+
isProcessRunning(pid) {
|
|
103
|
+
try {
|
|
104
|
+
process.kill(pid, 0);
|
|
105
|
+
return true;
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get cached connection info (may be stale)
|
|
112
|
+
*/
|
|
113
|
+
getCached() {
|
|
114
|
+
return this.connectionInfo;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Build WebSocket URL from connection info
|
|
118
|
+
*/
|
|
119
|
+
buildWebSocketUrl(info) {
|
|
120
|
+
return `ws://127.0.0.1:${info.port}${info.ws_path}?token=${info.token}`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build HTTP base URL from connection info
|
|
124
|
+
*/
|
|
125
|
+
buildHttpUrl(info) {
|
|
126
|
+
return `http://127.0.0.1:${info.port}`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Find Sanqian executable path
|
|
130
|
+
* Searches in standard installation locations for each platform
|
|
131
|
+
*/
|
|
132
|
+
findSanqianPath(customPath) {
|
|
133
|
+
if (customPath) {
|
|
134
|
+
if (existsSync(customPath)) {
|
|
135
|
+
return customPath;
|
|
136
|
+
}
|
|
137
|
+
console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const os = platform();
|
|
141
|
+
const searchPaths = [];
|
|
142
|
+
if (os === "darwin") {
|
|
143
|
+
searchPaths.push(
|
|
144
|
+
// Production: installed app
|
|
145
|
+
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
146
|
+
join(homedir(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
147
|
+
// Development: electron-builder output
|
|
148
|
+
join(homedir(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
149
|
+
join(homedir(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
150
|
+
);
|
|
151
|
+
} else if (os === "win32") {
|
|
152
|
+
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
153
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
154
|
+
const localAppData = process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
|
|
155
|
+
searchPaths.push(
|
|
156
|
+
// Production: installed app
|
|
157
|
+
join(programFiles, "Sanqian", "Sanqian.exe"),
|
|
158
|
+
join(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
159
|
+
join(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
160
|
+
// Development: electron-builder output
|
|
161
|
+
join(homedir(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
searchPaths.push(
|
|
165
|
+
"/usr/bin/sanqian",
|
|
166
|
+
"/usr/local/bin/sanqian",
|
|
167
|
+
join(homedir(), ".local/bin/sanqian"),
|
|
168
|
+
"/opt/Sanqian/sanqian",
|
|
169
|
+
// Development
|
|
170
|
+
join(homedir(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
for (const path of searchPaths) {
|
|
174
|
+
if (existsSync(path)) {
|
|
175
|
+
return path;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Launch Sanqian in hidden/tray mode
|
|
182
|
+
* Returns true if launch was initiated successfully
|
|
183
|
+
*/
|
|
184
|
+
launchSanqian(customPath) {
|
|
185
|
+
const sanqianPath = this.findSanqianPath(customPath);
|
|
186
|
+
if (!sanqianPath) {
|
|
187
|
+
console.error("[SDK] Sanqian executable not found");
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
|
|
191
|
+
try {
|
|
192
|
+
const os = platform();
|
|
193
|
+
if (os === "darwin") {
|
|
194
|
+
const appPath = sanqianPath.replace(
|
|
195
|
+
"/Contents/MacOS/Sanqian",
|
|
196
|
+
""
|
|
197
|
+
);
|
|
198
|
+
spawn("open", ["-a", appPath, "--args", "--hidden"], {
|
|
199
|
+
detached: true,
|
|
200
|
+
stdio: "ignore"
|
|
201
|
+
}).unref();
|
|
202
|
+
} else {
|
|
203
|
+
spawn(sanqianPath, ["--hidden"], {
|
|
204
|
+
detached: true,
|
|
205
|
+
stdio: "ignore",
|
|
206
|
+
// On Windows, hide the console window
|
|
207
|
+
...os === "win32" && {
|
|
208
|
+
windowsHide: true,
|
|
209
|
+
shell: false
|
|
210
|
+
}
|
|
211
|
+
}).unref();
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
console.error("[SDK] Failed to launch Sanqian:", e);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check if Sanqian is running
|
|
221
|
+
*/
|
|
222
|
+
isSanqianRunning() {
|
|
223
|
+
return this.read() !== null;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/client.ts
|
|
228
|
+
var SanqianSDK = class {
|
|
229
|
+
config;
|
|
230
|
+
discovery;
|
|
231
|
+
ws = null;
|
|
232
|
+
connectionInfo = null;
|
|
233
|
+
state = {
|
|
234
|
+
connected: false,
|
|
235
|
+
registering: false,
|
|
236
|
+
registered: false,
|
|
237
|
+
reconnectAttempts: 0
|
|
238
|
+
};
|
|
239
|
+
// Tool handlers by name
|
|
240
|
+
toolHandlers = /* @__PURE__ */ new Map();
|
|
241
|
+
// Pending request futures
|
|
242
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
243
|
+
// Timers
|
|
244
|
+
heartbeatTimer = null;
|
|
245
|
+
reconnectTimer = null;
|
|
246
|
+
// Event listeners
|
|
247
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
248
|
+
constructor(config) {
|
|
249
|
+
this.config = {
|
|
250
|
+
reconnectInterval: 5e3,
|
|
251
|
+
heartbeatInterval: 3e4,
|
|
252
|
+
toolExecutionTimeout: 3e4,
|
|
253
|
+
...config
|
|
254
|
+
};
|
|
255
|
+
this.discovery = new DiscoveryManager();
|
|
256
|
+
for (const tool of config.tools) {
|
|
257
|
+
this.toolHandlers.set(tool.name, tool.handler);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// ============================================
|
|
261
|
+
// Lifecycle
|
|
262
|
+
// ============================================
|
|
263
|
+
/**
|
|
264
|
+
* Connect to Sanqian
|
|
265
|
+
*
|
|
266
|
+
* Reads connection info, establishes WebSocket, and registers app.
|
|
267
|
+
* Returns when registration is complete.
|
|
268
|
+
*
|
|
269
|
+
* If autoLaunchSanqian is enabled and Sanqian is not running,
|
|
270
|
+
* SDK will attempt to start it in hidden/tray mode.
|
|
271
|
+
*/
|
|
272
|
+
async connect() {
|
|
273
|
+
const info = this.discovery.read();
|
|
274
|
+
if (!info) {
|
|
275
|
+
if (this.config.autoLaunchSanqian) {
|
|
276
|
+
console.log("[SDK] Sanqian not running, attempting to launch...");
|
|
277
|
+
const launched = this.discovery.launchSanqian(this.config.sanqianPath);
|
|
278
|
+
if (launched) {
|
|
279
|
+
console.log("[SDK] Sanqian launch initiated, waiting for startup...");
|
|
280
|
+
} else {
|
|
281
|
+
console.warn("[SDK] Failed to launch Sanqian, will wait for manual start");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
console.log("[SDK] Waiting for Sanqian...");
|
|
286
|
+
const timeout = setTimeout(() => {
|
|
287
|
+
this.discovery.stopWatching();
|
|
288
|
+
reject(new Error("Sanqian connection timeout"));
|
|
289
|
+
}, 6e4);
|
|
290
|
+
this.discovery.startWatching(async (newInfo) => {
|
|
291
|
+
if (newInfo) {
|
|
292
|
+
clearTimeout(timeout);
|
|
293
|
+
this.discovery.stopWatching();
|
|
294
|
+
try {
|
|
295
|
+
await this.connectWithInfo(newInfo);
|
|
296
|
+
resolve();
|
|
297
|
+
} catch (e) {
|
|
298
|
+
reject(e);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
await this.connectWithInfo(info);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Connect with known connection info
|
|
308
|
+
*/
|
|
309
|
+
async connectWithInfo(info) {
|
|
310
|
+
this.connectionInfo = info;
|
|
311
|
+
const url = this.discovery.buildWebSocketUrl(info);
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
console.log(`[SDK] Connecting to ${url}`);
|
|
314
|
+
this.ws = new WebSocket(url);
|
|
315
|
+
const connectTimeout = setTimeout(() => {
|
|
316
|
+
reject(new Error("WebSocket connection timeout"));
|
|
317
|
+
this.ws?.close();
|
|
318
|
+
}, 1e4);
|
|
319
|
+
this.ws.on("open", async () => {
|
|
320
|
+
clearTimeout(connectTimeout);
|
|
321
|
+
console.log("[SDK] WebSocket connected");
|
|
322
|
+
this.state.connected = true;
|
|
323
|
+
this.state.reconnectAttempts = 0;
|
|
324
|
+
this.emit("connected");
|
|
325
|
+
try {
|
|
326
|
+
await this.register();
|
|
327
|
+
resolve();
|
|
328
|
+
} catch (e) {
|
|
329
|
+
reject(e);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
this.ws.on("close", (code, reason) => {
|
|
333
|
+
console.log(`[SDK] WebSocket closed: ${code} ${reason.toString()}`);
|
|
334
|
+
this.handleDisconnect(reason.toString());
|
|
335
|
+
});
|
|
336
|
+
this.ws.on("error", (error) => {
|
|
337
|
+
console.error("[SDK] WebSocket error:", error);
|
|
338
|
+
this.state.lastError = error;
|
|
339
|
+
this.emit("error", error);
|
|
340
|
+
});
|
|
341
|
+
this.ws.on("message", (data) => {
|
|
342
|
+
try {
|
|
343
|
+
const message = JSON.parse(data.toString());
|
|
344
|
+
this.handleMessage(message);
|
|
345
|
+
} catch (e) {
|
|
346
|
+
console.error("[SDK] Failed to parse message:", e);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Disconnect from Sanqian
|
|
353
|
+
*/
|
|
354
|
+
async disconnect() {
|
|
355
|
+
this.stopHeartbeat();
|
|
356
|
+
this.stopReconnect();
|
|
357
|
+
this.discovery.stopWatching();
|
|
358
|
+
if (this.ws) {
|
|
359
|
+
this.ws.close(1e3, "Client disconnect");
|
|
360
|
+
this.ws = null;
|
|
361
|
+
}
|
|
362
|
+
this.state = {
|
|
363
|
+
connected: false,
|
|
364
|
+
registering: false,
|
|
365
|
+
registered: false,
|
|
366
|
+
reconnectAttempts: 0
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// ============================================
|
|
370
|
+
// Registration
|
|
371
|
+
// ============================================
|
|
372
|
+
async register() {
|
|
373
|
+
this.state.registering = true;
|
|
374
|
+
const msgId = this.generateId();
|
|
375
|
+
const message = {
|
|
376
|
+
id: msgId,
|
|
377
|
+
type: "register",
|
|
378
|
+
app: {
|
|
379
|
+
name: this.config.appName,
|
|
380
|
+
version: this.config.appVersion,
|
|
381
|
+
display_name: this.config.displayName,
|
|
382
|
+
launch_command: this.config.launchCommand
|
|
383
|
+
},
|
|
384
|
+
tools: this.config.tools.map((t) => ({
|
|
385
|
+
name: `${this.config.appName}:${t.name}`,
|
|
386
|
+
description: t.description,
|
|
387
|
+
parameters: t.parameters
|
|
388
|
+
}))
|
|
389
|
+
};
|
|
390
|
+
try {
|
|
391
|
+
const response = await this.sendAndWait(
|
|
392
|
+
message,
|
|
393
|
+
msgId,
|
|
394
|
+
1e4
|
|
395
|
+
);
|
|
396
|
+
if (!response.success) {
|
|
397
|
+
throw new Error(response.error || "Registration failed");
|
|
398
|
+
}
|
|
399
|
+
this.state.registering = false;
|
|
400
|
+
this.state.registered = true;
|
|
401
|
+
this.startHeartbeat();
|
|
402
|
+
this.emit("registered");
|
|
403
|
+
console.log(`[SDK] Registered as '${this.config.appName}'`);
|
|
404
|
+
} catch (e) {
|
|
405
|
+
this.state.registering = false;
|
|
406
|
+
throw e;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// ============================================
|
|
410
|
+
// Message Handling
|
|
411
|
+
// ============================================
|
|
412
|
+
handleMessage(message) {
|
|
413
|
+
const { id, type } = message;
|
|
414
|
+
if (id && this.pendingRequests.has(id)) {
|
|
415
|
+
const pending = this.pendingRequests.get(id);
|
|
416
|
+
this.pendingRequests.delete(id);
|
|
417
|
+
pending.resolve(message);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
switch (type) {
|
|
421
|
+
case "tool_call":
|
|
422
|
+
this.handleToolCall(message);
|
|
423
|
+
break;
|
|
424
|
+
case "heartbeat_ack":
|
|
425
|
+
break;
|
|
426
|
+
case "chat_stream":
|
|
427
|
+
this.handleChatStream(message);
|
|
428
|
+
break;
|
|
429
|
+
default:
|
|
430
|
+
console.warn(`[SDK] Unknown message type: ${type}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
handleChatStream(message) {
|
|
434
|
+
const { id, event, content, tool_call, tool_result, conversation_id, usage, error } = message;
|
|
435
|
+
if (!id) return;
|
|
436
|
+
const handler = this.streamHandlers.get(id);
|
|
437
|
+
if (!handler) {
|
|
438
|
+
console.warn(`[SDK] No stream handler for message ${id}`);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
switch (event) {
|
|
442
|
+
case "text":
|
|
443
|
+
handler.onEvent({ type: "text", content });
|
|
444
|
+
break;
|
|
445
|
+
case "tool_call":
|
|
446
|
+
handler.onEvent({ type: "tool_call", tool_call });
|
|
447
|
+
break;
|
|
448
|
+
case "tool_result":
|
|
449
|
+
if (tool_result) {
|
|
450
|
+
handler.onEvent({
|
|
451
|
+
type: "tool_call",
|
|
452
|
+
tool_call: {
|
|
453
|
+
id: tool_result.call_id,
|
|
454
|
+
type: "function",
|
|
455
|
+
function: {
|
|
456
|
+
name: "tool_result",
|
|
457
|
+
arguments: JSON.stringify(tool_result)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
case "done":
|
|
464
|
+
handler.onDone({
|
|
465
|
+
message: message.message || { role: "assistant", content: "" },
|
|
466
|
+
conversationId: conversation_id || "",
|
|
467
|
+
usage
|
|
468
|
+
});
|
|
469
|
+
break;
|
|
470
|
+
case "error":
|
|
471
|
+
handler.onError(new Error(error || "Unknown stream error"));
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async handleToolCall(message) {
|
|
476
|
+
console.log(`[SDK] handleToolCall received:`, JSON.stringify(message));
|
|
477
|
+
const { id, call_id, name, arguments: args } = message;
|
|
478
|
+
const msgId = id || call_id;
|
|
479
|
+
const toolName = name.includes(":") ? name.split(":")[1] : name;
|
|
480
|
+
console.log(`[SDK] Looking for handler: '${toolName}', available handlers:`, Array.from(this.toolHandlers.keys()));
|
|
481
|
+
const handler = this.toolHandlers.get(toolName);
|
|
482
|
+
this.emit("tool_call", { name: toolName, arguments: args });
|
|
483
|
+
if (!handler) {
|
|
484
|
+
await this.sendToolResult(msgId, call_id, false, void 0, `Tool '${toolName}' not found`);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
console.log(`[SDK] Executing tool '${toolName}' with args:`, args);
|
|
489
|
+
const result = await Promise.race([
|
|
490
|
+
handler(args),
|
|
491
|
+
this.createTimeout(this.config.toolExecutionTimeout)
|
|
492
|
+
]);
|
|
493
|
+
console.log(`[SDK] Tool '${toolName}' completed successfully`);
|
|
494
|
+
await this.sendToolResult(msgId, call_id, true, result);
|
|
495
|
+
} catch (e) {
|
|
496
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
497
|
+
console.log(`[SDK] Tool '${toolName}' failed:`, error);
|
|
498
|
+
await this.sendToolResult(msgId, call_id, false, void 0, error);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async sendToolResult(id, callId, success, result, error) {
|
|
502
|
+
const message = {
|
|
503
|
+
id,
|
|
504
|
+
type: "tool_result",
|
|
505
|
+
call_id: callId,
|
|
506
|
+
success,
|
|
507
|
+
result,
|
|
508
|
+
error
|
|
509
|
+
};
|
|
510
|
+
console.log(`[SDK] Sending tool_result for ${callId}:`, success ? "success" : `error: ${error}`);
|
|
511
|
+
this.send(message);
|
|
512
|
+
}
|
|
513
|
+
// ============================================
|
|
514
|
+
// Connection Management
|
|
515
|
+
// ============================================
|
|
516
|
+
handleDisconnect(reason) {
|
|
517
|
+
this.stopHeartbeat();
|
|
518
|
+
this.state.connected = false;
|
|
519
|
+
this.state.registered = false;
|
|
520
|
+
this.emit("disconnected", reason);
|
|
521
|
+
for (const [, pending] of this.pendingRequests) {
|
|
522
|
+
pending.reject(new Error("Disconnected"));
|
|
523
|
+
}
|
|
524
|
+
this.pendingRequests.clear();
|
|
525
|
+
this.scheduleReconnect();
|
|
526
|
+
}
|
|
527
|
+
scheduleReconnect() {
|
|
528
|
+
if (this.reconnectTimer) return;
|
|
529
|
+
const delay = Math.min(
|
|
530
|
+
1e3 * Math.pow(2, this.state.reconnectAttempts),
|
|
531
|
+
3e4
|
|
532
|
+
);
|
|
533
|
+
console.log(`[SDK] Scheduling reconnect in ${delay}ms`);
|
|
534
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
535
|
+
this.reconnectTimer = null;
|
|
536
|
+
this.state.reconnectAttempts++;
|
|
537
|
+
const info = this.discovery.read();
|
|
538
|
+
if (info) {
|
|
539
|
+
try {
|
|
540
|
+
await this.connectWithInfo(info);
|
|
541
|
+
} catch (e) {
|
|
542
|
+
console.error("[SDK] Reconnect failed:", e);
|
|
543
|
+
this.scheduleReconnect();
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
this.scheduleReconnect();
|
|
547
|
+
}
|
|
548
|
+
}, delay);
|
|
549
|
+
}
|
|
550
|
+
stopReconnect() {
|
|
551
|
+
if (this.reconnectTimer) {
|
|
552
|
+
clearTimeout(this.reconnectTimer);
|
|
553
|
+
this.reconnectTimer = null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// ============================================
|
|
557
|
+
// Heartbeat
|
|
558
|
+
// ============================================
|
|
559
|
+
startHeartbeat() {
|
|
560
|
+
this.stopHeartbeat();
|
|
561
|
+
this.heartbeatTimer = setInterval(() => {
|
|
562
|
+
const message = {
|
|
563
|
+
type: "heartbeat",
|
|
564
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
565
|
+
};
|
|
566
|
+
this.send(message);
|
|
567
|
+
}, this.config.heartbeatInterval);
|
|
568
|
+
}
|
|
569
|
+
stopHeartbeat() {
|
|
570
|
+
if (this.heartbeatTimer) {
|
|
571
|
+
clearInterval(this.heartbeatTimer);
|
|
572
|
+
this.heartbeatTimer = null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// ============================================
|
|
576
|
+
// Communication
|
|
577
|
+
// ============================================
|
|
578
|
+
send(message) {
|
|
579
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
580
|
+
const data = JSON.stringify(message);
|
|
581
|
+
console.log(`[SDK] WebSocket send:`, data.substring(0, 200));
|
|
582
|
+
this.ws.send(data);
|
|
583
|
+
} else {
|
|
584
|
+
console.error(`[SDK] WebSocket not open, cannot send:`, message);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
sendAndWait(message, id, timeout) {
|
|
588
|
+
return new Promise((resolve, reject) => {
|
|
589
|
+
const timer = setTimeout(() => {
|
|
590
|
+
this.pendingRequests.delete(id);
|
|
591
|
+
reject(new Error("Request timeout"));
|
|
592
|
+
}, timeout);
|
|
593
|
+
this.pendingRequests.set(id, {
|
|
594
|
+
resolve: (value) => {
|
|
595
|
+
clearTimeout(timer);
|
|
596
|
+
resolve(value);
|
|
597
|
+
},
|
|
598
|
+
reject: (error) => {
|
|
599
|
+
clearTimeout(timer);
|
|
600
|
+
reject(error);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
this.send(message);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
// ============================================
|
|
607
|
+
// Public API
|
|
608
|
+
// ============================================
|
|
609
|
+
/**
|
|
610
|
+
* Get current connection state
|
|
611
|
+
*/
|
|
612
|
+
getState() {
|
|
613
|
+
return { ...this.state };
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Check if connected and registered
|
|
617
|
+
*/
|
|
618
|
+
isConnected() {
|
|
619
|
+
return this.state.connected && this.state.registered;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Update tool list dynamically
|
|
623
|
+
*/
|
|
624
|
+
async updateTools(tools) {
|
|
625
|
+
this.toolHandlers.clear();
|
|
626
|
+
for (const tool of tools) {
|
|
627
|
+
this.toolHandlers.set(tool.name, tool.handler);
|
|
628
|
+
}
|
|
629
|
+
if (this.isConnected()) {
|
|
630
|
+
const msgId = this.generateId();
|
|
631
|
+
const message = {
|
|
632
|
+
id: msgId,
|
|
633
|
+
type: "tools_update",
|
|
634
|
+
tools: tools.map((t) => ({
|
|
635
|
+
name: `${this.config.appName}:${t.name}`,
|
|
636
|
+
description: t.description,
|
|
637
|
+
parameters: t.parameters
|
|
638
|
+
}))
|
|
639
|
+
};
|
|
640
|
+
await this.sendAndWait(message, msgId, 5e3);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// ============================================
|
|
644
|
+
// Private Agent API
|
|
645
|
+
// ============================================
|
|
646
|
+
/**
|
|
647
|
+
* Create or update a private agent
|
|
648
|
+
*
|
|
649
|
+
* Private agents are only visible to this SDK app, not in Sanqian UI.
|
|
650
|
+
* If agent with same ID exists, it will be updated.
|
|
651
|
+
*
|
|
652
|
+
* @param config - Agent configuration
|
|
653
|
+
* @returns Full agent info (or agent_id string for backward compatibility)
|
|
654
|
+
*/
|
|
655
|
+
async createAgent(config) {
|
|
656
|
+
if (!this.isConnected()) {
|
|
657
|
+
throw new Error("Not connected to Sanqian");
|
|
658
|
+
}
|
|
659
|
+
const msgId = this.generateId();
|
|
660
|
+
const message = {
|
|
661
|
+
id: msgId,
|
|
662
|
+
type: "create_agent",
|
|
663
|
+
agent: config
|
|
664
|
+
};
|
|
665
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
666
|
+
if (!response.success) {
|
|
667
|
+
throw new Error(response.error || "Failed to create agent");
|
|
668
|
+
}
|
|
669
|
+
if (response.agent) {
|
|
670
|
+
return response.agent;
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
agent_id: response.agent_id,
|
|
674
|
+
name: config.name,
|
|
675
|
+
description: config.description,
|
|
676
|
+
tools: config.tools || []
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* List all private agents owned by this app
|
|
681
|
+
*/
|
|
682
|
+
async listAgents() {
|
|
683
|
+
if (!this.isConnected()) {
|
|
684
|
+
throw new Error("Not connected to Sanqian");
|
|
685
|
+
}
|
|
686
|
+
const msgId = this.generateId();
|
|
687
|
+
const message = {
|
|
688
|
+
id: msgId,
|
|
689
|
+
type: "list_agents"
|
|
690
|
+
};
|
|
691
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
692
|
+
if (response.error) {
|
|
693
|
+
throw new Error(response.error);
|
|
694
|
+
}
|
|
695
|
+
return response.agents;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Delete a private agent
|
|
699
|
+
*/
|
|
700
|
+
async deleteAgent(agentId) {
|
|
701
|
+
if (!this.isConnected()) {
|
|
702
|
+
throw new Error("Not connected to Sanqian");
|
|
703
|
+
}
|
|
704
|
+
const msgId = this.generateId();
|
|
705
|
+
const message = {
|
|
706
|
+
id: msgId,
|
|
707
|
+
type: "delete_agent",
|
|
708
|
+
agent_id: agentId
|
|
709
|
+
};
|
|
710
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
711
|
+
if (!response.success) {
|
|
712
|
+
throw new Error(response.error || "Failed to delete agent");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Update a private agent
|
|
717
|
+
*
|
|
718
|
+
* Only updates the fields that are provided.
|
|
719
|
+
* @param agentId - Agent ID (short name or full)
|
|
720
|
+
* @param updates - Fields to update
|
|
721
|
+
* @returns Updated agent info
|
|
722
|
+
*/
|
|
723
|
+
async updateAgent(agentId, updates) {
|
|
724
|
+
if (!this.isConnected()) {
|
|
725
|
+
throw new Error("Not connected to Sanqian");
|
|
726
|
+
}
|
|
727
|
+
const msgId = this.generateId();
|
|
728
|
+
const message = {
|
|
729
|
+
id: msgId,
|
|
730
|
+
type: "update_agent",
|
|
731
|
+
agent: {
|
|
732
|
+
agent_id: agentId,
|
|
733
|
+
...updates
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
737
|
+
if (!response.success || !response.agent) {
|
|
738
|
+
throw new Error(response.error || "Failed to update agent");
|
|
739
|
+
}
|
|
740
|
+
return response.agent;
|
|
741
|
+
}
|
|
742
|
+
// ============================================
|
|
743
|
+
// Conversation API
|
|
744
|
+
// ============================================
|
|
745
|
+
/**
|
|
746
|
+
* List conversations for this app
|
|
747
|
+
*/
|
|
748
|
+
async listConversations(options) {
|
|
749
|
+
if (!this.isConnected()) {
|
|
750
|
+
throw new Error("Not connected to Sanqian");
|
|
751
|
+
}
|
|
752
|
+
const msgId = this.generateId();
|
|
753
|
+
const message = {
|
|
754
|
+
id: msgId,
|
|
755
|
+
type: "list_conversations",
|
|
756
|
+
agent_id: options?.agentId,
|
|
757
|
+
limit: options?.limit,
|
|
758
|
+
offset: options?.offset
|
|
759
|
+
};
|
|
760
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
761
|
+
if (response.error) {
|
|
762
|
+
throw new Error(response.error);
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
conversations: response.conversations,
|
|
766
|
+
total: response.total
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Get conversation details with messages
|
|
771
|
+
*/
|
|
772
|
+
async getConversation(conversationId, options) {
|
|
773
|
+
if (!this.isConnected()) {
|
|
774
|
+
throw new Error("Not connected to Sanqian");
|
|
775
|
+
}
|
|
776
|
+
const msgId = this.generateId();
|
|
777
|
+
const message = {
|
|
778
|
+
id: msgId,
|
|
779
|
+
type: "get_conversation",
|
|
780
|
+
conversation_id: conversationId,
|
|
781
|
+
include_messages: options?.includeMessages ?? true,
|
|
782
|
+
message_limit: options?.messageLimit,
|
|
783
|
+
message_offset: options?.messageOffset
|
|
784
|
+
};
|
|
785
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
786
|
+
if (!response.success || !response.conversation) {
|
|
787
|
+
throw new Error(response.error || "Failed to get conversation");
|
|
788
|
+
}
|
|
789
|
+
return response.conversation;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Delete a conversation
|
|
793
|
+
*/
|
|
794
|
+
async deleteConversation(conversationId) {
|
|
795
|
+
if (!this.isConnected()) {
|
|
796
|
+
throw new Error("Not connected to Sanqian");
|
|
797
|
+
}
|
|
798
|
+
const msgId = this.generateId();
|
|
799
|
+
const message = {
|
|
800
|
+
id: msgId,
|
|
801
|
+
type: "delete_conversation",
|
|
802
|
+
conversation_id: conversationId
|
|
803
|
+
};
|
|
804
|
+
const response = await this.sendAndWait(message, msgId, 1e4);
|
|
805
|
+
if (!response.success) {
|
|
806
|
+
throw new Error(response.error || "Failed to delete conversation");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
// ============================================
|
|
810
|
+
// Chat API
|
|
811
|
+
// ============================================
|
|
812
|
+
// Pending stream handlers for streaming chat
|
|
813
|
+
streamHandlers = /* @__PURE__ */ new Map();
|
|
814
|
+
/**
|
|
815
|
+
* Send a chat message to an agent (non-streaming)
|
|
816
|
+
*
|
|
817
|
+
* Supports two modes:
|
|
818
|
+
* - Stateless (no conversationId): Caller maintains message history
|
|
819
|
+
* - Stateful (with conversationId): Server stores messages in conversations table
|
|
820
|
+
*
|
|
821
|
+
* @param agentId - Agent ID (short name or full, SDK auto-prefixes)
|
|
822
|
+
* @param messages - Messages to send
|
|
823
|
+
* @param options - Optional settings including conversationId and remoteTools
|
|
824
|
+
* @returns Chat response with assistant message and conversation ID
|
|
825
|
+
*/
|
|
826
|
+
async chat(agentId, messages, options) {
|
|
827
|
+
if (!this.isConnected()) {
|
|
828
|
+
throw new Error("Not connected to Sanqian");
|
|
829
|
+
}
|
|
830
|
+
const msgId = this.generateId();
|
|
831
|
+
const message = {
|
|
832
|
+
id: msgId,
|
|
833
|
+
type: "chat",
|
|
834
|
+
agent_id: agentId,
|
|
835
|
+
messages,
|
|
836
|
+
conversation_id: options?.conversationId,
|
|
837
|
+
stream: false,
|
|
838
|
+
remote_tools: options?.remoteTools?.map((t) => ({
|
|
839
|
+
name: t.name,
|
|
840
|
+
description: t.description,
|
|
841
|
+
parameters: t.parameters
|
|
842
|
+
}))
|
|
843
|
+
};
|
|
844
|
+
const response = await this.sendAndWait(message, msgId, 6e5);
|
|
845
|
+
if (!response.success) {
|
|
846
|
+
throw new Error(response.error || "Chat request failed");
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
message: response.message,
|
|
850
|
+
conversationId: response.conversation_id,
|
|
851
|
+
usage: response.usage
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Send a chat message to an agent with streaming response
|
|
856
|
+
*
|
|
857
|
+
* Supports two modes:
|
|
858
|
+
* - Stateless (no conversationId): Caller maintains message history
|
|
859
|
+
* - Stateful (with conversationId): Server stores messages in conversations table
|
|
860
|
+
*
|
|
861
|
+
* @param agentId - Agent ID (short name or full, SDK auto-prefixes)
|
|
862
|
+
* @param messages - Messages to send
|
|
863
|
+
* @param options - Optional settings including conversationId and remoteTools
|
|
864
|
+
* @returns AsyncIterable of stream events
|
|
865
|
+
*/
|
|
866
|
+
async *chatStream(agentId, messages, options) {
|
|
867
|
+
if (!this.isConnected()) {
|
|
868
|
+
throw new Error("Not connected to Sanqian");
|
|
869
|
+
}
|
|
870
|
+
const msgId = this.generateId();
|
|
871
|
+
const message = {
|
|
872
|
+
id: msgId,
|
|
873
|
+
type: "chat",
|
|
874
|
+
agent_id: agentId,
|
|
875
|
+
messages,
|
|
876
|
+
conversation_id: options?.conversationId,
|
|
877
|
+
stream: true,
|
|
878
|
+
remote_tools: options?.remoteTools?.map((t) => ({
|
|
879
|
+
name: t.name,
|
|
880
|
+
description: t.description,
|
|
881
|
+
parameters: t.parameters
|
|
882
|
+
}))
|
|
883
|
+
};
|
|
884
|
+
const eventQueue = [];
|
|
885
|
+
let done = false;
|
|
886
|
+
let error = null;
|
|
887
|
+
let resolveNext = null;
|
|
888
|
+
this.streamHandlers.set(msgId, {
|
|
889
|
+
onEvent: (event) => {
|
|
890
|
+
eventQueue.push(event);
|
|
891
|
+
resolveNext?.();
|
|
892
|
+
},
|
|
893
|
+
onDone: (response) => {
|
|
894
|
+
eventQueue.push({
|
|
895
|
+
type: "done",
|
|
896
|
+
conversationId: response.conversationId
|
|
897
|
+
});
|
|
898
|
+
done = true;
|
|
899
|
+
resolveNext?.();
|
|
900
|
+
},
|
|
901
|
+
onError: (e) => {
|
|
902
|
+
error = e;
|
|
903
|
+
resolveNext?.();
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
try {
|
|
907
|
+
this.send(message);
|
|
908
|
+
while (!done && !error) {
|
|
909
|
+
if (eventQueue.length > 0) {
|
|
910
|
+
yield eventQueue.shift();
|
|
911
|
+
} else {
|
|
912
|
+
await new Promise((resolve) => {
|
|
913
|
+
resolveNext = resolve;
|
|
914
|
+
});
|
|
915
|
+
resolveNext = null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
while (eventQueue.length > 0) {
|
|
919
|
+
yield eventQueue.shift();
|
|
920
|
+
}
|
|
921
|
+
if (error) {
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
} finally {
|
|
925
|
+
this.streamHandlers.delete(msgId);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Start a new conversation with an agent
|
|
930
|
+
*
|
|
931
|
+
* Returns a Conversation object for convenient multi-turn chat.
|
|
932
|
+
*
|
|
933
|
+
* @example
|
|
934
|
+
* ```typescript
|
|
935
|
+
* const conv = sdk.startConversation('assistant')
|
|
936
|
+
* const r1 = await conv.send('Hello')
|
|
937
|
+
* const r2 = await conv.send('Follow up question')
|
|
938
|
+
* console.log(conv.id) // conversation ID
|
|
939
|
+
* ```
|
|
940
|
+
*/
|
|
941
|
+
startConversation(agentId) {
|
|
942
|
+
return new Conversation(this, agentId);
|
|
943
|
+
}
|
|
944
|
+
// ============================================
|
|
945
|
+
// Events
|
|
946
|
+
// ============================================
|
|
947
|
+
/**
|
|
948
|
+
* Add event listener
|
|
949
|
+
*/
|
|
950
|
+
on(event, listener) {
|
|
951
|
+
if (!this.eventListeners.has(event)) {
|
|
952
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
953
|
+
}
|
|
954
|
+
this.eventListeners.get(event).add(listener);
|
|
955
|
+
return this;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Remove event listener
|
|
959
|
+
*/
|
|
960
|
+
off(event, listener) {
|
|
961
|
+
this.eventListeners.get(event)?.delete(listener);
|
|
962
|
+
return this;
|
|
963
|
+
}
|
|
964
|
+
emit(event, ...args) {
|
|
965
|
+
const listeners = this.eventListeners.get(event);
|
|
966
|
+
if (listeners) {
|
|
967
|
+
for (const listener of listeners) {
|
|
968
|
+
try {
|
|
969
|
+
listener(...args);
|
|
970
|
+
} catch (e) {
|
|
971
|
+
console.error(`[SDK] Event listener error for '${event}':`, e);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
// ============================================
|
|
977
|
+
// Utilities
|
|
978
|
+
// ============================================
|
|
979
|
+
generateId() {
|
|
980
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
981
|
+
}
|
|
982
|
+
createTimeout(ms) {
|
|
983
|
+
return new Promise((_, reject) => {
|
|
984
|
+
setTimeout(() => reject(new Error("Timeout")), ms);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
var Conversation = class {
|
|
989
|
+
sdk;
|
|
990
|
+
agentId;
|
|
991
|
+
_conversationId = null;
|
|
992
|
+
constructor(sdk, agentId, conversationId) {
|
|
993
|
+
this.sdk = sdk;
|
|
994
|
+
this.agentId = agentId;
|
|
995
|
+
this._conversationId = conversationId || null;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get the conversation ID (available after first message)
|
|
999
|
+
*/
|
|
1000
|
+
get id() {
|
|
1001
|
+
return this._conversationId;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Send a message and get a response
|
|
1005
|
+
*
|
|
1006
|
+
* First call creates a new conversation, subsequent calls continue it.
|
|
1007
|
+
*/
|
|
1008
|
+
async send(content, options) {
|
|
1009
|
+
const response = await this.sdk.chat(
|
|
1010
|
+
this.agentId,
|
|
1011
|
+
[{ role: "user", content }],
|
|
1012
|
+
{
|
|
1013
|
+
conversationId: this._conversationId || void 0,
|
|
1014
|
+
remoteTools: options?.remoteTools
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
if (response.conversationId && !this._conversationId) {
|
|
1018
|
+
this._conversationId = response.conversationId;
|
|
1019
|
+
}
|
|
1020
|
+
return response;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Send a message with streaming response
|
|
1024
|
+
*/
|
|
1025
|
+
async *sendStream(content, options) {
|
|
1026
|
+
const stream = this.sdk.chatStream(
|
|
1027
|
+
this.agentId,
|
|
1028
|
+
[{ role: "user", content }],
|
|
1029
|
+
{
|
|
1030
|
+
conversationId: this._conversationId || void 0,
|
|
1031
|
+
remoteTools: options?.remoteTools
|
|
1032
|
+
}
|
|
1033
|
+
);
|
|
1034
|
+
for await (const event of stream) {
|
|
1035
|
+
if (event.type === "done" && event.conversationId && !this._conversationId) {
|
|
1036
|
+
this._conversationId = event.conversationId;
|
|
1037
|
+
}
|
|
1038
|
+
yield event;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Delete this conversation
|
|
1043
|
+
*/
|
|
1044
|
+
async delete() {
|
|
1045
|
+
if (!this._conversationId) {
|
|
1046
|
+
throw new Error("No conversation to delete");
|
|
1047
|
+
}
|
|
1048
|
+
await this.sdk.deleteConversation(this._conversationId);
|
|
1049
|
+
this._conversationId = null;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Get conversation details including message history
|
|
1053
|
+
*/
|
|
1054
|
+
async getDetails(options) {
|
|
1055
|
+
if (!this._conversationId) {
|
|
1056
|
+
throw new Error("No conversation to get details for");
|
|
1057
|
+
}
|
|
1058
|
+
return this.sdk.getConversation(this._conversationId, {
|
|
1059
|
+
includeMessages: true,
|
|
1060
|
+
messageLimit: options?.messageLimit
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
export {
|
|
1065
|
+
Conversation,
|
|
1066
|
+
DiscoveryManager,
|
|
1067
|
+
SanqianSDK
|
|
1068
|
+
};
|
|
1069
|
+
//# sourceMappingURL=index.mjs.map
|