rol-websocket-channel 1.8.3 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +77 -2
- package/index.ts +84 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,6 +11,10 @@ import { registerArtifactsTools } from "./src/admin/tools/artifacts-tools.js";
|
|
|
11
11
|
// ============================================
|
|
12
12
|
import { getContext, initializeContext } from "./src/shared/context.js";
|
|
13
13
|
let pluginRuntime = null;
|
|
14
|
+
// 记录最近活跃的 session 上下文,便于发信时注入 traceId 和 meta
|
|
15
|
+
const sessionContextMap = new Map();
|
|
16
|
+
// 以 senderId 为键的上下文缓存,供 sendText/sendMedia 反查 sessionKey
|
|
17
|
+
const senderContextMap = new Map();
|
|
14
18
|
function isRecord(value) {
|
|
15
19
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
16
20
|
}
|
|
@@ -274,15 +278,27 @@ const WebSocketChannel = {
|
|
|
274
278
|
// MQTT 是 topic-based 路由,不依赖 to 做地址解析
|
|
275
279
|
// 使用 "direct" 模式:AI reply 走 deliver 回调;message 工具走 sendText
|
|
276
280
|
deliveryMode: "direct",
|
|
277
|
-
sendText
|
|
281
|
+
// sendText:框架传入字段见 ChannelMessageSendTextContext(to = senderId,无 sessionKey/trace_id)
|
|
282
|
+
sendText: async ({ to, text, accountId }) => {
|
|
278
283
|
const conn = ConnectionManager.getGlobalConnection();
|
|
279
284
|
if (!conn || !conn.ws || !conn.ws.connected) {
|
|
280
285
|
return { ok: false, error: "No MQTT connection" };
|
|
281
286
|
}
|
|
287
|
+
// 通过 to(senderId)反查 session 上下文
|
|
288
|
+
const senderCtx = to ? senderContextMap.get(to) : undefined;
|
|
289
|
+
const finalTraceId = senderCtx?.traceId;
|
|
290
|
+
const finalAgentId = accountId || senderCtx?.agentId || "unknown";
|
|
291
|
+
const finalSessionKey = senderCtx?.sessionKey;
|
|
282
292
|
// 与 deliver 回调保持一致,使用 receiver 格式发布消息
|
|
283
293
|
const message = JSON.stringify({
|
|
284
294
|
type: "receiver",
|
|
295
|
+
trace_id: finalTraceId,
|
|
285
296
|
source: "ai",
|
|
297
|
+
meta: {
|
|
298
|
+
'type': "sendText",
|
|
299
|
+
agentId: finalAgentId,
|
|
300
|
+
sessionKey: finalSessionKey,
|
|
301
|
+
},
|
|
286
302
|
data: { text },
|
|
287
303
|
timestamp: Date.now(),
|
|
288
304
|
});
|
|
@@ -294,14 +310,26 @@ const WebSocketChannel = {
|
|
|
294
310
|
conn.ws.publish(targetTopic, message, { retain: true });
|
|
295
311
|
return { ok: true };
|
|
296
312
|
},
|
|
297
|
-
sendMedia
|
|
313
|
+
// sendMedia:框架传入字段见 ChannelMessageSendMediaContext(to = senderId,无 sessionKey/trace_id)
|
|
314
|
+
sendMedia: async ({ to, text, mediaUrl, accountId }) => {
|
|
298
315
|
const conn = ConnectionManager.getGlobalConnection();
|
|
299
316
|
if (!conn || !conn.ws || !conn.ws.connected) {
|
|
300
317
|
return { ok: false, error: "No MQTT connection" };
|
|
301
318
|
}
|
|
319
|
+
// 通过 to(senderId)反查 session 上下文
|
|
320
|
+
const senderCtx = to ? senderContextMap.get(to) : undefined;
|
|
321
|
+
const finalTraceId = senderCtx?.traceId;
|
|
322
|
+
const finalAgentId = accountId || senderCtx?.agentId || "unknown";
|
|
323
|
+
const finalSessionKey = senderCtx?.sessionKey;
|
|
302
324
|
const message = JSON.stringify({
|
|
303
325
|
type: "receiver",
|
|
326
|
+
trace_id: finalTraceId,
|
|
304
327
|
source: "ai",
|
|
328
|
+
meta: {
|
|
329
|
+
'type': "sendMedia",
|
|
330
|
+
agentId: finalAgentId,
|
|
331
|
+
sessionKey: finalSessionKey,
|
|
332
|
+
},
|
|
305
333
|
data: { text, mediaUrl },
|
|
306
334
|
timestamp: Date.now(),
|
|
307
335
|
});
|
|
@@ -345,6 +373,10 @@ const WebSocketChannel = {
|
|
|
345
373
|
const text = typeof params.message === "string" ? params.message
|
|
346
374
|
: typeof params.text === "string" ? params.text
|
|
347
375
|
: "";
|
|
376
|
+
const sessionKey = ctx.sessionKey;
|
|
377
|
+
const sessionCtx = sessionKey ? sessionContextMap.get(sessionKey) : undefined;
|
|
378
|
+
const trace_id = params.trace_id || params.traceId || ctx.traceId || sessionCtx?.traceId || ctx.runId;
|
|
379
|
+
const agentId = ctx.accountId || ctx.agentId || sessionCtx?.agentId || "unknown";
|
|
348
380
|
const conn = ConnectionManager.getGlobalConnection();
|
|
349
381
|
if (!conn || !conn.ws || !conn.ws.connected) {
|
|
350
382
|
return {
|
|
@@ -354,7 +386,13 @@ const WebSocketChannel = {
|
|
|
354
386
|
}
|
|
355
387
|
const replyMessage = JSON.stringify({
|
|
356
388
|
type: "receiver",
|
|
389
|
+
trace_id: trace_id,
|
|
357
390
|
source: "ai",
|
|
391
|
+
meta: {
|
|
392
|
+
'type': "handleAction",
|
|
393
|
+
agentId: agentId,
|
|
394
|
+
sessionKey: sessionKey
|
|
395
|
+
},
|
|
358
396
|
data: { text },
|
|
359
397
|
timestamp: Date.now(),
|
|
360
398
|
});
|
|
@@ -536,6 +574,9 @@ async function handleIncomingMessage(payload, account, cfg, runtime, log, mqttTo
|
|
|
536
574
|
// 用户传参覆盖自动路由结果
|
|
537
575
|
const resolvedAccountId = targetAgentId ?? route.accountId;
|
|
538
576
|
const resolvedSessionKey = targetSessionId ?? route.sessionKey;
|
|
577
|
+
// 缓存最新请求上下文(双键:sessionKey 和 senderId)
|
|
578
|
+
sessionContextMap.set(resolvedSessionKey, { traceId, agentId: resolvedAccountId });
|
|
579
|
+
senderContextMap.set(normalizedMessage.senderId, { sessionKey: resolvedSessionKey, traceId, agentId: resolvedAccountId });
|
|
539
580
|
// 构建消息上下文
|
|
540
581
|
// To 必须设置为 senderId:框架用它确定 message 工具的回复目标
|
|
541
582
|
// From 是发送方;To 是「这条对话的对端」(即 bot 应回复给谁)
|
|
@@ -899,6 +940,22 @@ function publishToolEvent(event) {
|
|
|
899
940
|
console.error("[mqtt] Error publishing tool event:", err);
|
|
900
941
|
}
|
|
901
942
|
}
|
|
943
|
+
function truncateDeep(obj, maxLen = 1000) {
|
|
944
|
+
if (typeof obj === "string") {
|
|
945
|
+
return obj.length > maxLen ? obj.slice(0, maxLen) + "...[truncated]" : obj;
|
|
946
|
+
}
|
|
947
|
+
if (Array.isArray(obj)) {
|
|
948
|
+
return obj.map(v => truncateDeep(v, maxLen));
|
|
949
|
+
}
|
|
950
|
+
if (obj !== null && typeof obj === "object") {
|
|
951
|
+
const res = {};
|
|
952
|
+
for (const k of Object.keys(obj)) {
|
|
953
|
+
res[k] = truncateDeep(obj[k], maxLen);
|
|
954
|
+
}
|
|
955
|
+
return res;
|
|
956
|
+
}
|
|
957
|
+
return obj;
|
|
958
|
+
}
|
|
902
959
|
function registerToolEventHooks(api) {
|
|
903
960
|
// before_tool_call:AI 即将调用某个工具时触发
|
|
904
961
|
if (typeof api.on === 'function') {
|
|
@@ -937,6 +994,24 @@ function registerToolEventHooks(api) {
|
|
|
937
994
|
},
|
|
938
995
|
});
|
|
939
996
|
});
|
|
997
|
+
// llm_output:大模型生成回复后触发,可获取 thinking 内容
|
|
998
|
+
api.on("llm_output", (event, ctx) => {
|
|
999
|
+
publishToolEvent({
|
|
1000
|
+
type: "receiver",
|
|
1001
|
+
source: "thinking",
|
|
1002
|
+
event: "llm_output",
|
|
1003
|
+
timestamp: Date.now(),
|
|
1004
|
+
data: {
|
|
1005
|
+
runId: event.runId ?? ctx.runId,
|
|
1006
|
+
sessionKey: ctx.sessionKey ?? event.sessionId,
|
|
1007
|
+
provider: event.provider,
|
|
1008
|
+
model: event.model,
|
|
1009
|
+
assistantTexts: truncateDeep(event.assistantTexts, 1000),
|
|
1010
|
+
lastAssistant: truncateDeep(event.lastAssistant, 1000),
|
|
1011
|
+
usage: event.usage,
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
940
1015
|
}
|
|
941
1016
|
else {
|
|
942
1017
|
console.error("[mqtt] api.on is not available. Cannot register tool event hooks.");
|
package/index.ts
CHANGED
|
@@ -40,6 +40,11 @@ import { getContext, initializeContext } from "./src/shared/context.js";
|
|
|
40
40
|
|
|
41
41
|
let pluginRuntime: any = null;
|
|
42
42
|
|
|
43
|
+
// 记录最近活跃的 session 上下文,便于发信时注入 traceId 和 meta
|
|
44
|
+
const sessionContextMap = new Map<string, { traceId: string; agentId: string; }>();
|
|
45
|
+
// 以 senderId 为键的上下文缓存,供 sendText/sendMedia 反查 sessionKey
|
|
46
|
+
const senderContextMap = new Map<string, { sessionKey: string; traceId: string; agentId: string; }>();
|
|
47
|
+
|
|
43
48
|
function isRecord(value: unknown): value is Record<string, any> {
|
|
44
49
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
45
50
|
}
|
|
@@ -337,16 +342,29 @@ const WebSocketChannel: any = {
|
|
|
337
342
|
// 使用 "direct" 模式:AI reply 走 deliver 回调;message 工具走 sendText
|
|
338
343
|
deliveryMode: "direct",
|
|
339
344
|
|
|
340
|
-
sendText
|
|
345
|
+
// sendText:框架传入字段见 ChannelMessageSendTextContext(to = senderId,无 sessionKey/trace_id)
|
|
346
|
+
sendText: async ({ to, text, accountId }: any) => {
|
|
341
347
|
const conn = ConnectionManager.getGlobalConnection();
|
|
342
348
|
if (!conn || !conn.ws || !conn.ws.connected) {
|
|
343
349
|
return { ok: false, error: "No MQTT connection" };
|
|
344
350
|
}
|
|
345
351
|
|
|
352
|
+
// 通过 to(senderId)反查 session 上下文
|
|
353
|
+
const senderCtx = to ? senderContextMap.get(to) : undefined;
|
|
354
|
+
const finalTraceId = senderCtx?.traceId;
|
|
355
|
+
const finalAgentId = accountId || senderCtx?.agentId || "unknown";
|
|
356
|
+
const finalSessionKey = senderCtx?.sessionKey;
|
|
357
|
+
|
|
346
358
|
// 与 deliver 回调保持一致,使用 receiver 格式发布消息
|
|
347
359
|
const message = JSON.stringify({
|
|
348
360
|
type: "receiver",
|
|
361
|
+
trace_id: finalTraceId,
|
|
349
362
|
source: "ai",
|
|
363
|
+
meta: {
|
|
364
|
+
'type': "sendText",
|
|
365
|
+
agentId: finalAgentId,
|
|
366
|
+
sessionKey: finalSessionKey,
|
|
367
|
+
},
|
|
350
368
|
data: { text },
|
|
351
369
|
timestamp: Date.now(),
|
|
352
370
|
});
|
|
@@ -361,15 +379,28 @@ const WebSocketChannel: any = {
|
|
|
361
379
|
return { ok: true };
|
|
362
380
|
},
|
|
363
381
|
|
|
364
|
-
sendMedia
|
|
382
|
+
// sendMedia:框架传入字段见 ChannelMessageSendMediaContext(to = senderId,无 sessionKey/trace_id)
|
|
383
|
+
sendMedia: async ({ to, text, mediaUrl, accountId }: any) => {
|
|
365
384
|
const conn = ConnectionManager.getGlobalConnection();
|
|
366
385
|
if (!conn || !conn.ws || !conn.ws.connected) {
|
|
367
386
|
return { ok: false, error: "No MQTT connection" };
|
|
368
387
|
}
|
|
369
388
|
|
|
389
|
+
// 通过 to(senderId)反查 session 上下文
|
|
390
|
+
const senderCtx = to ? senderContextMap.get(to) : undefined;
|
|
391
|
+
const finalTraceId = senderCtx?.traceId;
|
|
392
|
+
const finalAgentId = accountId || senderCtx?.agentId || "unknown";
|
|
393
|
+
const finalSessionKey = senderCtx?.sessionKey;
|
|
394
|
+
|
|
370
395
|
const message = JSON.stringify({
|
|
371
396
|
type: "receiver",
|
|
397
|
+
trace_id: finalTraceId,
|
|
372
398
|
source: "ai",
|
|
399
|
+
meta: {
|
|
400
|
+
'type': "sendMedia",
|
|
401
|
+
agentId: finalAgentId,
|
|
402
|
+
sessionKey: finalSessionKey,
|
|
403
|
+
},
|
|
373
404
|
data: { text, mediaUrl },
|
|
374
405
|
timestamp: Date.now(),
|
|
375
406
|
});
|
|
@@ -419,6 +450,11 @@ const WebSocketChannel: any = {
|
|
|
419
450
|
: typeof params.text === "string" ? params.text
|
|
420
451
|
: "";
|
|
421
452
|
|
|
453
|
+
const sessionKey = ctx.sessionKey;
|
|
454
|
+
const sessionCtx = sessionKey ? sessionContextMap.get(sessionKey) : undefined;
|
|
455
|
+
const trace_id = params.trace_id || params.traceId || ctx.traceId || sessionCtx?.traceId || ctx.runId;
|
|
456
|
+
const agentId = ctx.accountId || ctx.agentId || sessionCtx?.agentId || "unknown";
|
|
457
|
+
|
|
422
458
|
const conn = ConnectionManager.getGlobalConnection();
|
|
423
459
|
if (!conn || !conn.ws || !conn.ws.connected) {
|
|
424
460
|
return {
|
|
@@ -429,7 +465,13 @@ const WebSocketChannel: any = {
|
|
|
429
465
|
|
|
430
466
|
const replyMessage = JSON.stringify({
|
|
431
467
|
type: "receiver",
|
|
468
|
+
trace_id: trace_id,
|
|
432
469
|
source: "ai",
|
|
470
|
+
meta: {
|
|
471
|
+
'type': "handleAction",
|
|
472
|
+
agentId: agentId,
|
|
473
|
+
sessionKey: sessionKey
|
|
474
|
+
},
|
|
433
475
|
data: { text },
|
|
434
476
|
timestamp: Date.now(),
|
|
435
477
|
});
|
|
@@ -685,6 +727,10 @@ async function handleIncomingMessage(
|
|
|
685
727
|
const resolvedAccountId = targetAgentId ?? route.accountId;
|
|
686
728
|
const resolvedSessionKey = targetSessionId ?? route.sessionKey;
|
|
687
729
|
|
|
730
|
+
// 缓存最新请求上下文(双键:sessionKey 和 senderId)
|
|
731
|
+
sessionContextMap.set(resolvedSessionKey, { traceId, agentId: resolvedAccountId });
|
|
732
|
+
senderContextMap.set(normalizedMessage.senderId, { sessionKey: resolvedSessionKey, traceId, agentId: resolvedAccountId });
|
|
733
|
+
|
|
688
734
|
// 构建消息上下文
|
|
689
735
|
// To 必须设置为 senderId:框架用它确定 message 工具的回复目标
|
|
690
736
|
// From 是发送方;To 是「这条对话的对端」(即 bot 应回复给谁)
|
|
@@ -1151,6 +1197,23 @@ function publishToolEvent(event: object): void {
|
|
|
1151
1197
|
}
|
|
1152
1198
|
}
|
|
1153
1199
|
|
|
1200
|
+
function truncateDeep(obj: any, maxLen = 1000): any {
|
|
1201
|
+
if (typeof obj === "string") {
|
|
1202
|
+
return obj.length > maxLen ? obj.slice(0, maxLen) + "...[truncated]" : obj;
|
|
1203
|
+
}
|
|
1204
|
+
if (Array.isArray(obj)) {
|
|
1205
|
+
return obj.map(v => truncateDeep(v, maxLen));
|
|
1206
|
+
}
|
|
1207
|
+
if (obj !== null && typeof obj === "object") {
|
|
1208
|
+
const res: any = {};
|
|
1209
|
+
for (const k of Object.keys(obj)) {
|
|
1210
|
+
res[k] = truncateDeep(obj[k], maxLen);
|
|
1211
|
+
}
|
|
1212
|
+
return res;
|
|
1213
|
+
}
|
|
1214
|
+
return obj;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1154
1217
|
function registerToolEventHooks(api: any): void {
|
|
1155
1218
|
// before_tool_call:AI 即将调用某个工具时触发
|
|
1156
1219
|
if (typeof api.on === 'function') {
|
|
@@ -1190,6 +1253,25 @@ function registerToolEventHooks(api: any): void {
|
|
|
1190
1253
|
},
|
|
1191
1254
|
});
|
|
1192
1255
|
});
|
|
1256
|
+
|
|
1257
|
+
// llm_output:大模型生成回复后触发,可获取 thinking 内容
|
|
1258
|
+
api.on("llm_output", (event: any, ctx: any) => {
|
|
1259
|
+
publishToolEvent({
|
|
1260
|
+
type: "receiver",
|
|
1261
|
+
source: "thinking",
|
|
1262
|
+
event: "llm_output",
|
|
1263
|
+
timestamp: Date.now(),
|
|
1264
|
+
data: {
|
|
1265
|
+
runId: event.runId ?? ctx.runId,
|
|
1266
|
+
sessionKey: ctx.sessionKey ?? event.sessionId,
|
|
1267
|
+
provider: event.provider,
|
|
1268
|
+
model: event.model,
|
|
1269
|
+
assistantTexts: truncateDeep(event.assistantTexts, 1000),
|
|
1270
|
+
lastAssistant: truncateDeep(event.lastAssistant, 1000),
|
|
1271
|
+
usage: event.usage,
|
|
1272
|
+
},
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
1193
1275
|
} else {
|
|
1194
1276
|
console.error("[mqtt] api.on is not available. Cannot register tool event hooks.");
|
|
1195
1277
|
}
|