@tencent-connect/dsh-qqbot 0.1.0 → 0.3.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/CHANGELOG.md +39 -0
- package/dist/commands/model.js +1 -1
- package/dist/commands/model.js.map +1 -1
- package/dist/config.d.ts +6 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/features/history-store.d.ts +8 -0
- package/dist/features/history-store.d.ts.map +1 -0
- package/dist/features/history-store.js +24 -0
- package/dist/features/history-store.js.map +1 -0
- package/dist/gateway/bootstrap.d.ts +12 -0
- package/dist/gateway/bootstrap.d.ts.map +1 -0
- package/dist/gateway/bootstrap.js +71 -0
- package/dist/gateway/bootstrap.js.map +1 -0
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.d.ts.map +1 -0
- package/dist/gateway/index.js +3 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/middleware-setup.d.ts +12 -0
- package/dist/gateway/middleware-setup.d.ts.map +1 -0
- package/dist/gateway/middleware-setup.js +80 -0
- package/dist/gateway/middleware-setup.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -120
- package/dist/index.js.map +1 -1
- package/dist/middleware/attachment.d.ts +11 -0
- package/dist/middleware/attachment.d.ts.map +1 -0
- package/dist/middleware/attachment.js +17 -0
- package/dist/middleware/attachment.js.map +1 -0
- package/dist/model/model-resolver.d.ts +11 -5
- package/dist/model/model-resolver.d.ts.map +1 -1
- package/dist/model/model-resolver.js +15 -6
- package/dist/model/model-resolver.js.map +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +4 -5
- package/dist/session/session-manager.js.map +1 -1
- package/dist/setup.js +6 -6
- package/dist/setup.js.map +1 -1
- package/dist/transport/attachment.d.ts +18 -0
- package/dist/transport/attachment.d.ts.map +1 -0
- package/dist/transport/attachment.js +106 -0
- package/dist/transport/attachment.js.map +1 -0
- package/dist/transport/events.d.ts +80 -0
- package/dist/transport/events.d.ts.map +1 -0
- package/dist/transport/events.js +62 -0
- package/dist/transport/events.js.map +1 -0
- package/dist/transport/inbound.d.ts +1 -1
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +39 -14
- package/dist/transport/inbound.js.map +1 -1
- package/dist/transport/outbound-buffer.d.ts +12 -7
- package/dist/transport/outbound-buffer.d.ts.map +1 -1
- package/dist/transport/outbound-buffer.js +20 -22
- package/dist/transport/outbound-buffer.js.map +1 -1
- package/dist/transport/outbound.d.ts +11 -7
- package/dist/transport/outbound.d.ts.map +1 -1
- package/dist/transport/outbound.js +120 -69
- package/dist/transport/outbound.js.map +1 -1
- package/dist/transport/streaming-writer.d.ts +47 -0
- package/dist/transport/streaming-writer.d.ts.map +1 -0
- package/dist/transport/streaming-writer.js +110 -0
- package/dist/transport/streaming-writer.js.map +1 -0
- package/dist/transport/tool-presenter.d.ts +38 -0
- package/dist/transport/tool-presenter.d.ts.map +1 -0
- package/dist/transport/tool-presenter.js +117 -0
- package/dist/transport/tool-presenter.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -4
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { clearGroupHistory } from '../features/history-store.js';
|
|
2
3
|
// ── 主处理函数 ──
|
|
3
4
|
/**
|
|
4
5
|
* 处理 QQ 入站消息(已经过 SDK 中间件链)
|
|
5
6
|
*/
|
|
6
|
-
export async function handleInbound(rawMsg, manager,
|
|
7
|
+
export async function handleInbound(rawMsg, manager, config, logger, state) {
|
|
7
8
|
const msg = rawMsg;
|
|
8
9
|
const mwState = (state ?? {});
|
|
9
10
|
const scope = msg.kind === 'group' ? 'group' : 'c2c';
|
|
@@ -13,8 +14,10 @@ export async function handleInbound(rawMsg, manager, _config, logger, state) {
|
|
|
13
14
|
targetId: peerId,
|
|
14
15
|
msgId: msg.messageId,
|
|
15
16
|
};
|
|
17
|
+
// ── 读取中间件已下载的文件(attachmentProcessor 写入 state.downloadedFiles) ──
|
|
18
|
+
const downloaded = mwState.downloadedFiles ?? [];
|
|
16
19
|
// ── 组装 agentBody(对齐 openclaw-qqbot body-assembler) ──
|
|
17
|
-
const agentBody = assembleAgentBody(msg, mwState, scope, logger);
|
|
20
|
+
const agentBody = assembleAgentBody(msg, mwState, scope, logger, downloaded);
|
|
18
21
|
if (!agentBody)
|
|
19
22
|
return;
|
|
20
23
|
logger.info(`Processing: scope=${scope} peerId=${peerId} body="${agentBody.slice(0, 200)}"`);
|
|
@@ -35,6 +38,10 @@ export async function handleInbound(rawMsg, manager, _config, logger, state) {
|
|
|
35
38
|
});
|
|
36
39
|
record.agent.followup(message);
|
|
37
40
|
logger.info(`→ followup sent: key=${scope}:${peerId}`);
|
|
41
|
+
// 群消息回复后清空历史缓存(避免下次 @ 时重复组包,对齐 openclaw-qqbot dispatch)
|
|
42
|
+
if (scope === 'group') {
|
|
43
|
+
clearGroupHistory(config.appId, msg.groupOpenid ?? msg.senderId);
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
// ══════════════════════════════════════════════════════════════
|
|
40
47
|
// Body Assembly(对齐 openclaw-qqbot 5 层组装)
|
|
@@ -42,15 +49,15 @@ export async function handleInbound(rawMsg, manager, _config, logger, state) {
|
|
|
42
49
|
/**
|
|
43
50
|
* 组装 agentBody — AI 实际看到的完整上下文
|
|
44
51
|
*/
|
|
45
|
-
function assembleAgentBody(msg, state, scope, logger) {
|
|
52
|
+
function assembleAgentBody(msg, state, scope, logger, downloaded) {
|
|
46
53
|
const userContent = buildUserContent(msg, state, logger);
|
|
47
54
|
if (!userContent && (!msg.attachments || msg.attachments.length === 0))
|
|
48
55
|
return null;
|
|
49
56
|
const quotePart = buildQuotePart(state.quote);
|
|
50
57
|
const isGroup = scope === 'group';
|
|
51
58
|
const wasMentioned = state.mention?.wasMentioned ?? false;
|
|
52
|
-
const userMessage = buildUserMessage(userContent, quotePart, msg.senderId, isGroup, wasMentioned);
|
|
53
|
-
const dynamicCtx = buildDynamicCtx(msg, state);
|
|
59
|
+
const userMessage = buildUserMessage(userContent, quotePart, msg.senderId, msg.senderName, isGroup, wasMentioned);
|
|
60
|
+
const dynamicCtx = buildDynamicCtx(msg, state, downloaded);
|
|
54
61
|
const base = dynamicCtx ? `${dynamicCtx}${userMessage}` : userMessage;
|
|
55
62
|
const agentBody = buildAgentBody(base, state.history, isGroup, wasMentioned);
|
|
56
63
|
return agentBody;
|
|
@@ -89,18 +96,19 @@ function buildQuotePart(quote) {
|
|
|
89
96
|
/**
|
|
90
97
|
* Layer 3: 带发送者标签的用户消息
|
|
91
98
|
*/
|
|
92
|
-
function buildUserMessage(userContent, quotePart, senderId, isGroup, wasMentioned) {
|
|
99
|
+
function buildUserMessage(userContent, quotePart, senderId, senderName, isGroup, wasMentioned) {
|
|
93
100
|
if (!isGroup) {
|
|
94
101
|
return `${quotePart}${userContent}`;
|
|
95
102
|
}
|
|
96
103
|
const mentionTag = wasMentioned ? ' (@you)' : '';
|
|
97
|
-
const
|
|
104
|
+
const displayName = senderName ?? shortSenderId(senderId);
|
|
105
|
+
const senderTag = `[${displayName} (${senderId})]`;
|
|
98
106
|
return `${quotePart}${senderTag} ${userContent}${mentionTag}`;
|
|
99
107
|
}
|
|
100
108
|
/**
|
|
101
109
|
* Layer 4: 媒体元数据上下文
|
|
102
110
|
*/
|
|
103
|
-
function buildDynamicCtx(msg, state) {
|
|
111
|
+
function buildDynamicCtx(msg, state, downloaded) {
|
|
104
112
|
const lines = [];
|
|
105
113
|
if (!msg.attachments || msg.attachments.length === 0)
|
|
106
114
|
return '';
|
|
@@ -113,14 +121,17 @@ function buildDynamicCtx(msg, state) {
|
|
|
113
121
|
}
|
|
114
122
|
const voices = msg.attachments.filter(a => a.content_type === 'voice');
|
|
115
123
|
if (voices.length > 0) {
|
|
116
|
-
const urls = voices.map(a => a.url).filter(Boolean);
|
|
117
|
-
if (urls.length > 0) {
|
|
118
|
-
lines.push(`- Voice: ${urls.join(', ')}`);
|
|
119
|
-
}
|
|
120
124
|
const asrTexts = voices.map(a => a.asr_refer_text).filter(Boolean);
|
|
121
125
|
if (asrTexts.length > 0) {
|
|
122
126
|
lines.push(`- ASR: ${asrTexts.join(' | ')}`);
|
|
123
127
|
}
|
|
128
|
+
else {
|
|
129
|
+
// 无 ASR 识别结果时才带链接(纯文本模型无法消费音频,链接无意义)
|
|
130
|
+
const urls = voices.map(a => a.url).filter(Boolean);
|
|
131
|
+
if (urls.length > 0) {
|
|
132
|
+
lines.push(`- Voice: ${urls.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
124
135
|
}
|
|
125
136
|
const videos = msg.attachments.filter(a => a.content_type === 'video');
|
|
126
137
|
if (videos.length > 0) {
|
|
@@ -128,7 +139,15 @@ function buildDynamicCtx(msg, state) {
|
|
|
128
139
|
}
|
|
129
140
|
const files = msg.attachments.filter(a => a.content_type === 'file');
|
|
130
141
|
if (files.length > 0) {
|
|
131
|
-
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const d = downloaded.find(x => x.filename === file.filename);
|
|
144
|
+
if (d) {
|
|
145
|
+
lines.push(`- File: ${file.filename} → ${d.displayPath}`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
lines.push(`- File: ${file.filename} (${formatFileSize(file.size)})`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
132
151
|
}
|
|
133
152
|
if (lines.length === 0)
|
|
134
153
|
return '';
|
|
@@ -150,7 +169,7 @@ function buildAgentBody(base, history, isGroup, wasMentioned) {
|
|
|
150
169
|
return base;
|
|
151
170
|
}
|
|
152
171
|
const historyLines = history.map(h => {
|
|
153
|
-
const name = h.senderName ?? h.senderId
|
|
172
|
+
const name = h.senderName ?? shortSenderId(h.senderId);
|
|
154
173
|
return `[${name} (${h.senderId})] ${h.content}`;
|
|
155
174
|
});
|
|
156
175
|
return [
|
|
@@ -220,4 +239,10 @@ function formatFileSize(bytes) {
|
|
|
220
239
|
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
221
240
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
222
241
|
}
|
|
242
|
+
/** 发送者短标识长度(openid 前 N 位,无昵称时兜底) */
|
|
243
|
+
const SENDER_SHORT_ID_LEN = 8;
|
|
244
|
+
/** 无昵称时用 openid 前 N 位作为匿名标识 */
|
|
245
|
+
function shortSenderId(senderId) {
|
|
246
|
+
return senderId.slice(0, SENDER_SHORT_ID_LEN);
|
|
247
|
+
}
|
|
223
248
|
//# sourceMappingURL=inbound.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../src/transport/inbound.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../src/transport/inbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OutboundBuffer — 出站文本缓冲
|
|
3
3
|
*
|
|
4
|
-
* 收集流式 chunk
|
|
4
|
+
* 收集流式 chunk,流式优先投递(StreamingWriter),降级为静态发送。
|
|
5
|
+
* 独立文件便于单测。
|
|
5
6
|
*/
|
|
6
7
|
import type { SessionRecord } from '../session/index.js';
|
|
7
8
|
import type { Logger, ReplyTarget } from '../types.js';
|
|
9
|
+
/** QQ 流式会话(openStream 返回) */
|
|
10
|
+
export interface StreamSessionLike {
|
|
11
|
+
update(content: string): Promise<unknown>;
|
|
12
|
+
complete(): Promise<unknown>;
|
|
13
|
+
}
|
|
8
14
|
/** QQ Bot 发送接口 */
|
|
9
15
|
export interface QQBotSender {
|
|
10
16
|
sendMarkdown(target: ReplyTarget, content: string): Promise<unknown>;
|
|
17
|
+
openStream(target: ReplyTarget): StreamSessionLike;
|
|
11
18
|
}
|
|
12
19
|
export declare class OutboundBuffer {
|
|
13
20
|
private readonly record;
|
|
@@ -15,18 +22,16 @@ export declare class OutboundBuffer {
|
|
|
15
22
|
private readonly limit;
|
|
16
23
|
private readonly logger;
|
|
17
24
|
private buffer;
|
|
18
|
-
private timer;
|
|
19
25
|
private flushing;
|
|
20
|
-
|
|
26
|
+
private readonly writer;
|
|
27
|
+
constructor(record: SessionRecord, bot: QQBotSender, limit: number, logger: Logger, streamingEnabled: boolean);
|
|
21
28
|
/** 追加文本增量 */
|
|
22
29
|
append(text: string): void;
|
|
23
30
|
/** 获取当前累积文本 */
|
|
24
31
|
get text(): string;
|
|
25
|
-
/**
|
|
26
|
-
scheduleSend(delayMs: number): void;
|
|
27
|
-
/** 立即发送所有累积文本 */
|
|
32
|
+
/** 发送所有累积文本:流式优先,降级静态 */
|
|
28
33
|
flush(): Promise<void>;
|
|
29
|
-
/**
|
|
34
|
+
/** 取消(异常/丢弃),中止流式并清空缓冲 */
|
|
30
35
|
cancel(): void;
|
|
31
36
|
}
|
|
32
37
|
//# sourceMappingURL=outbound-buffer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbound-buffer.d.ts","sourceRoot":"","sources":["../../src/transport/outbound-buffer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"outbound-buffer.d.ts","sourceRoot":"","sources":["../../src/transport/outbound-buffer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOvD,6BAA6B;AAC7B,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9B;AAED,kBAAkB;AAClB,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAAC;CACpD;AAED,qBAAa,cAAc;IAMvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IARzB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;gBAG7B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAC/B,gBAAgB,EAAE,OAAO;IAO3B,aAAa;IACN,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKjC,eAAe;IACf,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,yBAAyB;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBnC,0BAA0B;IACnB,MAAM,IAAI,IAAI;CAItB"}
|
|
@@ -1,64 +1,62 @@
|
|
|
1
1
|
import { chunkMarkdownText } from './chunker.js';
|
|
2
|
+
import { StreamingWriter } from './streaming-writer.js';
|
|
3
|
+
/** 流式输出节流间隔(ms):连续 chunk 累积后停顿该间隔才推送 */
|
|
4
|
+
const STREAM_THROTTLE_MS = 200;
|
|
2
5
|
export class OutboundBuffer {
|
|
3
6
|
record;
|
|
4
7
|
bot;
|
|
5
8
|
limit;
|
|
6
9
|
logger;
|
|
7
10
|
buffer = '';
|
|
8
|
-
timer = null;
|
|
9
11
|
flushing = false;
|
|
10
|
-
|
|
12
|
+
writer;
|
|
13
|
+
constructor(record, bot, limit, logger, streamingEnabled) {
|
|
11
14
|
this.record = record;
|
|
12
15
|
this.bot = bot;
|
|
13
16
|
this.limit = limit;
|
|
14
17
|
this.logger = logger;
|
|
18
|
+
this.writer = streamingEnabled
|
|
19
|
+
? new StreamingWriter({ bot, target: record.replyTarget, logger, throttleMs: STREAM_THROTTLE_MS })
|
|
20
|
+
: null;
|
|
15
21
|
}
|
|
16
22
|
/** 追加文本增量 */
|
|
17
23
|
append(text) {
|
|
18
24
|
this.buffer += text;
|
|
25
|
+
this.writer?.append(text);
|
|
19
26
|
}
|
|
20
27
|
/** 获取当前累积文本 */
|
|
21
28
|
get text() {
|
|
22
29
|
return this.buffer;
|
|
23
30
|
}
|
|
24
|
-
/**
|
|
25
|
-
scheduleSend(delayMs) {
|
|
26
|
-
if (this.timer)
|
|
27
|
-
return;
|
|
28
|
-
this.timer = setTimeout(() => {
|
|
29
|
-
this.timer = null;
|
|
30
|
-
void this.flush();
|
|
31
|
-
}, delayMs);
|
|
32
|
-
}
|
|
33
|
-
/** 立即发送所有累积文本 */
|
|
31
|
+
/** 发送所有累积文本:流式优先,降级静态 */
|
|
34
32
|
async flush() {
|
|
35
33
|
if (this.flushing || !this.buffer.trim())
|
|
36
34
|
return;
|
|
37
35
|
this.flushing = true;
|
|
38
|
-
if (this.timer) {
|
|
39
|
-
clearTimeout(this.timer);
|
|
40
|
-
this.timer = null;
|
|
41
|
-
}
|
|
42
36
|
try {
|
|
37
|
+
if (this.writer) {
|
|
38
|
+
await this.writer.finish();
|
|
39
|
+
// 流式成功(未降级)→ 直接返回
|
|
40
|
+
if (!this.writer.shouldFallback)
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// 降级:静态发送(writer 不存在 or 流式失败)
|
|
43
44
|
const chunks = chunkMarkdownText(this.buffer, this.limit);
|
|
44
45
|
for (const chunk of chunks) {
|
|
45
46
|
await this.bot.sendMarkdown(this.record.replyTarget, chunk);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
catch (err) {
|
|
49
|
-
this.logger.error(`im-qqbot:
|
|
50
|
+
this.logger.error(`im-qqbot: flush failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
50
51
|
}
|
|
51
52
|
finally {
|
|
52
53
|
this.buffer = '';
|
|
53
54
|
this.flushing = false;
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
|
-
/**
|
|
57
|
+
/** 取消(异常/丢弃),中止流式并清空缓冲 */
|
|
57
58
|
cancel() {
|
|
58
|
-
|
|
59
|
-
clearTimeout(this.timer);
|
|
60
|
-
this.timer = null;
|
|
61
|
-
}
|
|
59
|
+
this.writer?.abort();
|
|
62
60
|
this.buffer = '';
|
|
63
61
|
}
|
|
64
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbound-buffer.js","sourceRoot":"","sources":["../../src/transport/outbound-buffer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbound-buffer.js","sourceRoot":"","sources":["../../src/transport/outbound-buffer.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,wCAAwC;AACxC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAc/B,MAAM,OAAO,cAAc;IAMN;IACA;IACA;IACA;IARX,MAAM,GAAG,EAAE,CAAC;IACZ,QAAQ,GAAG,KAAK,CAAC;IACR,MAAM,CAAyB;IAEhD,YACmB,MAAqB,EACrB,GAAgB,EAChB,KAAa,EACb,MAAc,EAC/B,gBAAyB;QAJR,WAAM,GAAN,MAAM,CAAe;QACrB,QAAG,GAAH,GAAG,CAAa;QAChB,UAAK,GAAL,KAAK,CAAQ;QACb,WAAM,GAAN,MAAM,CAAQ;QAG/B,IAAI,CAAC,MAAM,GAAG,gBAAgB;YAC5B,CAAC,CAAC,IAAI,eAAe,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC;YAClG,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAED,aAAa;IACN,MAAM,CAAC,IAAY;QACxB,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC;QACpB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,eAAe;IACf,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,yBAAyB;IAClB,KAAK,CAAC,KAAK;QAChB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO;QACjD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC3B,kBAAkB;gBAClB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc;oBAAE,OAAO;YAC1C,CAAC;YAED,8BAA8B;YAC9B,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnG,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;IACH,CAAC;IAED,0BAA0B;IACnB,MAAM;QACX,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 出站处理器 — dsh session/event → QQ 消息发送
|
|
3
|
+
*
|
|
4
|
+
* 采用路由器模式:OutboundRouter 持有会话级状态(文本缓冲、工具调用记录),
|
|
5
|
+
* 事件解析归一化在 events.ts,路由按事件类型分发到私有方法。
|
|
3
6
|
*/
|
|
4
7
|
import type { SessionManager } from '../session/index.js';
|
|
5
8
|
import type { ImQQBotConfig } from '../config.js';
|
|
6
9
|
import type { Logger } from '../types.js';
|
|
7
10
|
import { type QQBotSender } from './outbound-buffer.js';
|
|
11
|
+
import { type ToolsRegistryLike } from './tool-presenter.js';
|
|
12
|
+
import { type RawSessionEvent } from './events.js';
|
|
8
13
|
export type { QQBotSender } from './outbound-buffer.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
data: Record<string, unknown>;
|
|
13
|
-
}
|
|
14
|
+
export type { ToolsRegistryLike } from './tool-presenter.js';
|
|
15
|
+
/** 出站处理器签名(注册到 ctx.on('session/event')) */
|
|
16
|
+
export type OutboundHandler = (session: SessionLike, event: RawSessionEvent) => void;
|
|
14
17
|
/** dsh Session 简化类型 */
|
|
15
18
|
export interface SessionLike {
|
|
16
19
|
header: {
|
|
@@ -20,7 +23,8 @@ export interface SessionLike {
|
|
|
20
23
|
/**
|
|
21
24
|
* 创建出站事件处理器
|
|
22
25
|
*
|
|
23
|
-
* 返回一个 handler 函数,应注册到 ctx.on('session/event', handler)
|
|
26
|
+
* 返回一个 handler 函数,应注册到 ctx.on('session/event', handler)。
|
|
27
|
+
* toolsRegistry 用于工具结果的结构化展示(参考 dsh-TUI 的 presentResult)。
|
|
24
28
|
*/
|
|
25
|
-
export declare function createOutboundHandler(manager: SessionManager, bot: QQBotSender, config: ImQQBotConfig, logger: Logger
|
|
29
|
+
export declare function createOutboundHandler(manager: SessionManager, bot: QQBotSender, config: ImQQBotConfig, logger: Logger, toolsRegistry?: ToolsRegistryLike): OutboundHandler;
|
|
26
30
|
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/transport/outbound.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/transport/outbound.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAoB,KAAK,iBAAiB,EAAuB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAQL,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,2CAA2C;AAC3C,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;AAErF,uBAAuB;AACvB,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACxB;AAoJD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,iBAAiB,GAChC,eAAe,CAGjB"}
|
|
@@ -1,92 +1,143 @@
|
|
|
1
1
|
import { chunkMarkdownText } from './chunker.js';
|
|
2
2
|
import { OutboundBuffer } from './outbound-buffer.js';
|
|
3
|
+
import { formatToolResult } from './tool-presenter.js';
|
|
4
|
+
import { parseEvent, extractTurnError, } from './events.js';
|
|
5
|
+
/** 不展示给用户的轮次错误码(底层传输/网络错误,对用户无意义,且常被重试兜住) */
|
|
6
|
+
const SILENT_TURN_ERROR_CODES = new Set(['STREAM_CLOSED']);
|
|
3
7
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 返回一个 handler 函数,应注册到 ctx.on('session/event', handler)
|
|
8
|
+
* 出站路由器:持有会话级状态,按事件类型分发到处理器
|
|
7
9
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
class OutboundRouter {
|
|
11
|
+
manager;
|
|
12
|
+
bot;
|
|
13
|
+
config;
|
|
14
|
+
logger;
|
|
15
|
+
toolsRegistry;
|
|
16
|
+
buffers = new Map();
|
|
17
|
+
toolCalls = new Map();
|
|
18
|
+
constructor(manager, bot, config, logger, toolsRegistry) {
|
|
19
|
+
this.manager = manager;
|
|
20
|
+
this.bot = bot;
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.toolsRegistry = toolsRegistry;
|
|
24
|
+
}
|
|
25
|
+
/** 事件分发入口 */
|
|
26
|
+
route(session, raw) {
|
|
27
|
+
const event = parseEvent(raw);
|
|
28
|
+
if (event === undefined)
|
|
29
|
+
return;
|
|
30
|
+
const record = this.manager.findBySessionId(session.header.id);
|
|
31
|
+
if (record === undefined)
|
|
15
32
|
return;
|
|
16
33
|
switch (event.type) {
|
|
17
|
-
case 'assistant/chunk':
|
|
18
|
-
|
|
34
|
+
case 'assistant/chunk':
|
|
35
|
+
this.onChunk(session.header.id, record, event);
|
|
19
36
|
break;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
handleMessage(sessionId, record, event, buffers, bot, limit, logger);
|
|
37
|
+
case 'assistant/message':
|
|
38
|
+
this.onMessage(session.header.id, record, event);
|
|
23
39
|
break;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
case 'tool/call':
|
|
41
|
+
this.onToolCall(event);
|
|
42
|
+
break;
|
|
43
|
+
case 'tool/result':
|
|
44
|
+
this.onToolResult(record, event);
|
|
45
|
+
break;
|
|
46
|
+
case 'turn/end':
|
|
47
|
+
this.onTurnEnd(session.header.id, record, event);
|
|
27
48
|
break;
|
|
28
|
-
}
|
|
29
49
|
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** 处理流式文本增量 */
|
|
33
|
-
function handleChunk(sessionId, record, event, buffers, bot, limit, logger) {
|
|
34
|
-
const chunk = event.data.chunk;
|
|
35
|
-
if (!chunk || chunk.type !== 'text-delta' || !chunk.text)
|
|
36
|
-
return;
|
|
37
|
-
let buffer = buffers.get(sessionId);
|
|
38
|
-
if (!buffer) {
|
|
39
|
-
buffer = new OutboundBuffer(record, bot, limit, logger);
|
|
40
|
-
buffers.set(sessionId, buffer);
|
|
41
50
|
}
|
|
42
|
-
buffer
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
/** 流式文本增量:累积到会话 buffer */
|
|
52
|
+
onChunk(sessionId, record, event) {
|
|
53
|
+
let buffer = this.buffers.get(sessionId);
|
|
54
|
+
if (buffer === undefined) {
|
|
55
|
+
buffer = new OutboundBuffer(record, this.bot, this.config.textChunkLimit, this.logger, this.shouldStream(record));
|
|
56
|
+
this.buffers.set(sessionId, buffer);
|
|
57
|
+
}
|
|
58
|
+
buffer.append(event.text);
|
|
59
|
+
}
|
|
60
|
+
/** 是否启用流式:配置开启 + c2c + 有 msgId(群聊不支持流式) */
|
|
61
|
+
shouldStream(record) {
|
|
62
|
+
return this.config.streaming
|
|
63
|
+
&& record.replyTarget.scope === 'c2c'
|
|
64
|
+
&& !!record.replyTarget.msgId;
|
|
65
|
+
}
|
|
66
|
+
/** 完整 assistant 消息:有流式 buffer 则 flush,否则直接发送文本块 */
|
|
67
|
+
onMessage(sessionId, record, event) {
|
|
68
|
+
const buffer = this.buffers.get(sessionId);
|
|
69
|
+
if (buffer !== undefined && buffer.text.trim()) {
|
|
70
|
+
void buffer.flush();
|
|
71
|
+
this.buffers.delete(sessionId);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const textParts = [];
|
|
75
|
+
for (const block of event.content) {
|
|
76
|
+
if (block.type === 'text' && block.text)
|
|
77
|
+
textParts.push(block.text);
|
|
78
|
+
}
|
|
79
|
+
const fullText = textParts.join('\n');
|
|
80
|
+
if (!fullText.trim())
|
|
81
|
+
return;
|
|
82
|
+
void this.send(record, fullText, 'sendMarkdown');
|
|
83
|
+
this.buffers.delete(sessionId);
|
|
84
|
+
}
|
|
85
|
+
/** 工具调用:仅记录,不发送(避免刷屏,等待结果) */
|
|
86
|
+
onToolCall(event) {
|
|
87
|
+
this.toolCalls.set(event.callId, { name: event.name, args: event.arguments });
|
|
51
88
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
89
|
+
/** 工具结果:错误始终发送,成功结果按开关 */
|
|
90
|
+
onToolResult(record, event) {
|
|
91
|
+
const call = this.toolCalls.get(event.callId);
|
|
92
|
+
this.toolCalls.delete(event.callId);
|
|
93
|
+
if (call === undefined)
|
|
94
|
+
return;
|
|
95
|
+
if (event.error === undefined && !this.config.showToolResults)
|
|
96
|
+
return;
|
|
97
|
+
const text = formatToolResult(call.name, call.args, event.raw, this.toolsRegistry, record.agent);
|
|
98
|
+
if (!text)
|
|
99
|
+
return;
|
|
100
|
+
void this.send(record, text, 'sendToolResult');
|
|
101
|
+
}
|
|
102
|
+
/** 轮次结束:清理 buffer,异常结束时告知用户 */
|
|
103
|
+
onTurnEnd(sessionId, record, event) {
|
|
104
|
+
const buffer = this.buffers.get(sessionId);
|
|
105
|
+
if (buffer !== undefined) {
|
|
106
|
+
if (buffer.text.trim()) {
|
|
107
|
+
void buffer.flush();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
buffer.cancel();
|
|
111
|
+
}
|
|
112
|
+
this.buffers.delete(sessionId);
|
|
113
|
+
}
|
|
114
|
+
const failure = extractTurnError(event.reason);
|
|
115
|
+
if (failure !== undefined && !SILENT_TURN_ERROR_CODES.has(failure.code)) {
|
|
116
|
+
void this.send(record, `⚠️ 本轮异常结束\n\`${failure.code}\`: ${failure.message}`, 'sendTurnEndError');
|
|
60
117
|
}
|
|
118
|
+
this.logger.debug(`im-qqbot: turn/end sessionId=${sessionId}`);
|
|
61
119
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const chunks = chunkMarkdownText(fullText, limit);
|
|
66
|
-
void (async () => {
|
|
120
|
+
/** 统一发送:切分 + 逐 chunk 发送 + 错误记录 */
|
|
121
|
+
async send(record, text, tag) {
|
|
122
|
+
const chunks = chunkMarkdownText(text, this.config.textChunkLimit);
|
|
67
123
|
for (const chunk of chunks) {
|
|
68
124
|
try {
|
|
69
|
-
await bot.sendMarkdown(record.replyTarget, chunk);
|
|
125
|
+
await this.bot.sendMarkdown(record.replyTarget, chunk);
|
|
70
126
|
}
|
|
71
127
|
catch (err) {
|
|
72
|
-
logger.error(`im-qqbot:
|
|
128
|
+
this.logger.error(`im-qqbot: ${tag} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
73
129
|
}
|
|
74
130
|
}
|
|
75
|
-
})();
|
|
76
|
-
buffers.delete(sessionId);
|
|
77
|
-
}
|
|
78
|
-
/** 处理轮次结束,清理 buffer */
|
|
79
|
-
function handleTurnEnd(sessionId, buffers, logger) {
|
|
80
|
-
const buffer = buffers.get(sessionId);
|
|
81
|
-
if (buffer) {
|
|
82
|
-
if (buffer.text.trim()) {
|
|
83
|
-
void buffer.flush();
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
buffer.cancel();
|
|
87
|
-
}
|
|
88
|
-
buffers.delete(sessionId);
|
|
89
131
|
}
|
|
90
|
-
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 创建出站事件处理器
|
|
135
|
+
*
|
|
136
|
+
* 返回一个 handler 函数,应注册到 ctx.on('session/event', handler)。
|
|
137
|
+
* toolsRegistry 用于工具结果的结构化展示(参考 dsh-TUI 的 presentResult)。
|
|
138
|
+
*/
|
|
139
|
+
export function createOutboundHandler(manager, bot, config, logger, toolsRegistry) {
|
|
140
|
+
const router = new OutboundRouter(manager, bot, config, logger, toolsRegistry);
|
|
141
|
+
return (session, event) => router.route(session, event);
|
|
91
142
|
}
|
|
92
143
|
//# sourceMappingURL=outbound.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/transport/outbound.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/transport/outbound.ts"],"names":[],"mappings":"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"}
|