@tencent-connect/dsh-qqbot 0.4.0 → 0.5.1
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/.orange-ci.yml +2 -68
- package/CHANGELOG.md +56 -0
- package/CHANGELOG_EN.md +95 -0
- package/README.md +119 -18
- package/README_EN.md +107 -17
- package/cordis.patch.yml +1 -1
- package/cordis.yml +46 -3
- package/dist/commands/help.d.ts +6 -7
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +23 -7
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/index.d.ts +7 -5
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +12 -12
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/misc.d.ts +10 -8
- package/dist/commands/misc.d.ts.map +1 -1
- package/dist/commands/misc.js +45 -8
- package/dist/commands/misc.js.map +1 -1
- package/dist/commands/model.d.ts +3 -4
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +8 -7
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/preset.d.ts +8 -0
- package/dist/commands/preset.d.ts.map +1 -0
- package/dist/commands/preset.js +52 -0
- package/dist/commands/preset.js.map +1 -0
- package/dist/commands/session.d.ts +11 -7
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +29 -13
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts +3 -4
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts +9 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +45 -9
- package/dist/config.js.map +1 -1
- package/dist/features/answer-parser.d.ts +9 -0
- package/dist/features/answer-parser.d.ts.map +1 -0
- package/dist/features/answer-parser.js +23 -0
- package/dist/features/answer-parser.js.map +1 -0
- package/dist/features/approval-channel.d.ts +76 -0
- package/dist/features/approval-channel.d.ts.map +1 -0
- package/dist/features/approval-channel.js +142 -0
- package/dist/features/approval-channel.js.map +1 -0
- package/dist/features/approval-renderer.d.ts +18 -0
- package/dist/features/approval-renderer.d.ts.map +1 -0
- package/dist/features/approval-renderer.js +52 -0
- package/dist/features/approval-renderer.js.map +1 -0
- package/dist/features/button-utils.d.ts +33 -0
- package/dist/features/button-utils.d.ts.map +1 -0
- package/dist/features/button-utils.js +27 -0
- package/dist/features/button-utils.js.map +1 -0
- package/dist/features/history-store.d.ts.map +1 -1
- package/dist/features/history-store.js +0 -1
- package/dist/features/history-store.js.map +1 -1
- package/dist/features/question-channel.d.ts +96 -0
- package/dist/features/question-channel.d.ts.map +1 -0
- package/dist/features/question-channel.js +234 -0
- package/dist/features/question-channel.js.map +1 -0
- package/dist/features/question-renderer.d.ts +20 -0
- package/dist/features/question-renderer.d.ts.map +1 -0
- package/dist/features/question-renderer.js +70 -0
- package/dist/features/question-renderer.js.map +1 -0
- package/dist/gateway/bootstrap.d.ts +3 -3
- package/dist/gateway/bootstrap.d.ts.map +1 -1
- package/dist/gateway/bootstrap.js +84 -8
- package/dist/gateway/bootstrap.js.map +1 -1
- package/dist/gateway/index.d.ts +2 -2
- package/dist/gateway/index.js +2 -2
- package/dist/gateway/middleware-setup.d.ts +3 -3
- package/dist/gateway/middleware-setup.d.ts.map +1 -1
- package/dist/gateway/middleware-setup.js +9 -7
- package/dist/gateway/middleware-setup.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -25
- package/dist/index.js.map +1 -1
- package/dist/media/media-cleaner.d.ts +19 -0
- package/dist/media/media-cleaner.d.ts.map +1 -0
- package/dist/media/media-cleaner.js +81 -0
- package/dist/media/media-cleaner.js.map +1 -0
- package/dist/media/send-file-tool.d.ts +35 -0
- package/dist/media/send-file-tool.d.ts.map +1 -0
- package/dist/media/send-file-tool.js +150 -0
- package/dist/media/send-file-tool.js.map +1 -0
- package/dist/media/vision-tool.d.ts +16 -0
- package/dist/media/vision-tool.d.ts.map +1 -0
- package/dist/media/vision-tool.js +241 -0
- package/dist/media/vision-tool.js.map +1 -0
- package/dist/middleware/attachment.d.ts +3 -3
- package/dist/middleware/attachment.d.ts.map +1 -1
- package/dist/middleware/attachment.js +17 -4
- package/dist/middleware/attachment.js.map +1 -1
- package/dist/middleware/question-answer.d.ts +10 -0
- package/dist/middleware/question-answer.d.ts.map +1 -0
- package/dist/middleware/question-answer.js +19 -0
- package/dist/middleware/question-answer.js.map +1 -0
- package/dist/model/index.d.ts +4 -4
- package/dist/model/index.js +3 -3
- package/dist/model/model-resolver.d.ts +27 -13
- package/dist/model/model-resolver.d.ts.map +1 -1
- package/dist/model/model-resolver.js +82 -27
- package/dist/model/model-resolver.js.map +1 -1
- package/dist/model/prefs-store.d.ts +6 -1
- package/dist/model/prefs-store.d.ts.map +1 -1
- package/dist/model/prefs-store.js +25 -1
- package/dist/model/prefs-store.js.map +1 -1
- package/dist/model/settings-reader.d.ts +1 -1
- package/dist/session/idle-evictor.d.ts +2 -2
- package/dist/session/idle-evictor.d.ts.map +1 -1
- package/dist/session/idle-evictor.js +1 -1
- package/dist/session/idle-evictor.js.map +1 -1
- package/dist/session/index.d.ts +3 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -2
- package/dist/session/session-manager.d.ts +67 -9
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +216 -20
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +47 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/setup/bind-routes.d.ts +78 -0
- package/dist/setup/bind-routes.d.ts.map +1 -0
- package/dist/setup/bind-routes.js +311 -0
- package/dist/setup/bind-routes.js.map +1 -0
- package/dist/setup/binding-service.d.ts +86 -0
- package/dist/setup/binding-service.d.ts.map +1 -0
- package/dist/setup/binding-service.js +175 -0
- package/dist/setup/binding-service.js.map +1 -0
- package/dist/setup/index.d.ts +50 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +98 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/persist.d.ts +41 -0
- package/dist/setup/persist.d.ts.map +1 -0
- package/dist/setup/persist.js +201 -0
- package/dist/setup/persist.js.map +1 -0
- package/dist/setup/presenters.d.ts +40 -0
- package/dist/setup/presenters.d.ts.map +1 -0
- package/dist/setup/presenters.js +84 -0
- package/dist/setup/presenters.js.map +1 -0
- package/dist/setup/qr-svg.d.ts +8 -0
- package/dist/setup/qr-svg.d.ts.map +1 -0
- package/dist/setup/qr-svg.js +20 -0
- package/dist/setup/qr-svg.js.map +1 -0
- package/dist/shared/bind-protocol.d.ts +42 -0
- package/dist/shared/bind-protocol.d.ts.map +1 -0
- package/dist/shared/bind-protocol.js +19 -0
- package/dist/shared/bind-protocol.js.map +1 -0
- package/dist/shared/index.d.ts +3 -3
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +3 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/send-helper.js +1 -1
- package/dist/shared/utils.d.ts +10 -3
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +47 -6
- package/dist/shared/utils.js.map +1 -1
- package/dist/transport/attachment.d.ts +18 -7
- package/dist/transport/attachment.d.ts.map +1 -1
- package/dist/transport/attachment.js +77 -30
- package/dist/transport/attachment.js.map +1 -1
- package/dist/transport/inbound.d.ts +5 -4
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +120 -80
- package/dist/transport/inbound.js.map +1 -1
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +4 -4
- package/dist/transport/msgid-cache.d.ts +23 -0
- package/dist/transport/msgid-cache.d.ts.map +1 -0
- package/dist/transport/msgid-cache.js +57 -0
- package/dist/transport/msgid-cache.js.map +1 -0
- package/dist/transport/outbound-buffer.d.ts +9 -6
- package/dist/transport/outbound-buffer.d.ts.map +1 -1
- package/dist/transport/outbound-buffer.js +5 -5
- package/dist/transport/outbound-buffer.js.map +1 -1
- package/dist/transport/outbound.d.ts +9 -9
- package/dist/transport/outbound.d.ts.map +1 -1
- package/dist/transport/outbound.js +7 -7
- package/dist/transport/outbound.js.map +1 -1
- package/dist/transport/reply-limiter.d.ts +31 -0
- package/dist/transport/reply-limiter.d.ts.map +1 -0
- package/dist/transport/reply-limiter.js +50 -0
- package/dist/transport/reply-limiter.js.map +1 -0
- package/dist/transport/reply-target.d.ts +29 -0
- package/dist/transport/reply-target.d.ts.map +1 -0
- package/dist/transport/reply-target.js +55 -0
- package/dist/transport/reply-target.js.map +1 -0
- package/dist/transport/streaming-writer.d.ts +3 -3
- package/dist/transport/streaming-writer.d.ts.map +1 -1
- package/dist/transport/streaming-writer.js +1 -1
- package/dist/transport/streaming-writer.js.map +1 -1
- package/dist/transport/tool-presenter.d.ts +0 -1
- package/dist/transport/tool-presenter.d.ts.map +1 -1
- package/dist/transport/tool-presenter.js +0 -1
- package/dist/transport/tool-presenter.js.map +1 -1
- package/dist/types.d.ts +9 -15
- package/dist/types.d.ts.map +1 -1
- package/docs/assets/qqchannel.jpg +0 -0
- package/docs/assets/qqgroup.jpg +0 -0
- package/docs/assets/qrcode.png +0 -0
- package/lib/client.js +996 -0
- package/lib/client.js.map +1 -0
- package/package.json +44 -7
- package/pnpm-workspace.yaml +1 -0
- package/tsconfig.client.json +19 -0
- package/tsdown.client.config.ts +58 -0
- package/dist/setup.d.ts +0 -24
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js +0 -170
- package/dist/setup.js.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置 qqbot_send_file 工具 — 将本地文件发送给 QQ 用户或群。
|
|
3
|
+
*
|
|
4
|
+
* - 发送目标默认自动定位(exec.agent → SessionManager.findByAgent → replyTarget),
|
|
5
|
+
* 无需模型手动传 openid;显式 target 参数作为覆盖。
|
|
6
|
+
* - 按扩展名分发到 SDK 的 sendImage / sendVideo / sendVoice / sendFile。
|
|
7
|
+
* - 路径白名单:默认只允许访问 media 目录 + agent cwd,可开关 + 扩展白名单。
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { stat } from 'node:fs/promises';
|
|
11
|
+
import { basename, isAbsolute, relative, resolve } from 'node:path';
|
|
12
|
+
import { MEDIA_ROOT } from "./media-cleaner.js";
|
|
13
|
+
/** 工具名(qqbot 前缀避免与其他通道的同名工具冲突) */
|
|
14
|
+
export const SEND_FILE_TOOL_NAME = 'qqbot_send_file';
|
|
15
|
+
const DESCRIPTION = 'Send a local file (image, video, voice, or generic file) to the QQ user or group '
|
|
16
|
+
+ 'of the current conversation. Pass the absolute path of a file that already exists on disk. '
|
|
17
|
+
+ 'Use this when the user asks you to deliver a generated file (chart, report, exported data, '
|
|
18
|
+
+ 'screenshot, etc.) or to send an existing media/file back to them.';
|
|
19
|
+
const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']);
|
|
20
|
+
const VIDEO_EXTS = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm']);
|
|
21
|
+
const VOICE_EXTS = new Set(['.mp3', '.wav', '.ogg', '.aac', '.silk', '.amr']);
|
|
22
|
+
/** 解析显式 target("c2c:openid" / "group:openid"),非法返回 undefined */
|
|
23
|
+
function parseTarget(input) {
|
|
24
|
+
const idx = input.indexOf(':');
|
|
25
|
+
if (idx <= 0)
|
|
26
|
+
return undefined;
|
|
27
|
+
const scope = input.slice(0, idx);
|
|
28
|
+
const targetId = input.slice(idx + 1);
|
|
29
|
+
if (!targetId || (scope !== 'c2c' && scope !== 'group'))
|
|
30
|
+
return undefined;
|
|
31
|
+
return { scope, targetId };
|
|
32
|
+
}
|
|
33
|
+
/** 判断 filePath 是否位于 root 目录内(含 root 本身) */
|
|
34
|
+
function isWithinRoot(filePath, root) {
|
|
35
|
+
const rel = relative(resolve(root), filePath);
|
|
36
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
37
|
+
}
|
|
38
|
+
/** 路径白名单校验:restrictPaths 关闭时放行任意路径,否则限定 media + cwd + extraRoots */
|
|
39
|
+
function isPathAllowed(filePath, config, cwd) {
|
|
40
|
+
if (!config.restrictPaths)
|
|
41
|
+
return true;
|
|
42
|
+
const roots = [MEDIA_ROOT, cwd, ...config.extraRoots];
|
|
43
|
+
return roots.some((root) => isWithinRoot(filePath, root));
|
|
44
|
+
}
|
|
45
|
+
/** 注册 qqbot_send_file 工具(tools 服务缺失时优雅降级,不阻断插件启动) */
|
|
46
|
+
export function registerSendFileTool(ctx, bot, manager, config, logger) {
|
|
47
|
+
const tools = ctx.get('tools');
|
|
48
|
+
if (!tools?.register) {
|
|
49
|
+
logger.warn('im-qqbot: tools 服务不可用,qqbot_send_file 工具未注册');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const cwd = config.cwd || process.cwd();
|
|
53
|
+
const definition = {
|
|
54
|
+
name: SEND_FILE_TOOL_NAME,
|
|
55
|
+
description: DESCRIPTION,
|
|
56
|
+
parameters: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
file_path: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Absolute path to the local file to send (image/video/voice/generic file).',
|
|
62
|
+
},
|
|
63
|
+
target: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Optional send target as "c2c:openid" or "group:openid". Omit to send to the current conversation.',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['file_path'],
|
|
69
|
+
additionalProperties: false,
|
|
70
|
+
},
|
|
71
|
+
output: {
|
|
72
|
+
schema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
fileName: { type: 'string' },
|
|
76
|
+
fileSize: { type: 'integer' },
|
|
77
|
+
target: { type: 'string' },
|
|
78
|
+
fileUuid: { type: 'string' },
|
|
79
|
+
messageId: { type: 'string' },
|
|
80
|
+
},
|
|
81
|
+
required: ['fileName', 'fileSize', 'target', 'fileUuid'],
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
},
|
|
84
|
+
render: (_args, value) => {
|
|
85
|
+
const v = value;
|
|
86
|
+
return [{ type: 'text', text: `已发送 ${v.fileName} → ${v.target}` }];
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
async execute(args, exec) {
|
|
90
|
+
const { file_path, target } = args;
|
|
91
|
+
if (typeof file_path !== 'string' || file_path.length === 0) {
|
|
92
|
+
throw new Error('qqbot_send_file: `file_path` must be a non-empty string');
|
|
93
|
+
}
|
|
94
|
+
// 1. 解析发送目标:显式 target > 当前会话(exec.agent → SessionRecord)
|
|
95
|
+
let replyTarget;
|
|
96
|
+
if (target !== undefined && target !== '') {
|
|
97
|
+
replyTarget = parseTarget(target);
|
|
98
|
+
if (replyTarget === undefined) {
|
|
99
|
+
throw new Error(`qqbot_send_file: target 格式错误,需要 c2c:openid 或 group:openid,收到: ${target}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (exec.agent !== undefined) {
|
|
103
|
+
replyTarget = manager.findByAgent(exec.agent)?.replyTarget;
|
|
104
|
+
}
|
|
105
|
+
if (replyTarget === undefined) {
|
|
106
|
+
throw new Error('qqbot_send_file: 无法确定发送目标,请显式传 target 参数');
|
|
107
|
+
}
|
|
108
|
+
// 2. 校验文件 + 路径白名单
|
|
109
|
+
const absPath = resolve(file_path);
|
|
110
|
+
if (!isPathAllowed(absPath, config.sendFile, cwd)) {
|
|
111
|
+
throw new Error(`qqbot_send_file: file_path 不在允许的目录内: ${absPath}`);
|
|
112
|
+
}
|
|
113
|
+
if (!existsSync(absPath)) {
|
|
114
|
+
throw new Error(`qqbot_send_file: 文件不存在: ${absPath}`);
|
|
115
|
+
}
|
|
116
|
+
const info = await stat(absPath);
|
|
117
|
+
if (!info.isFile()) {
|
|
118
|
+
throw new Error(`qqbot_send_file: 路径不是普通文件: ${absPath}`);
|
|
119
|
+
}
|
|
120
|
+
// 3. 按扩展名分类发送
|
|
121
|
+
const fileName = basename(absPath);
|
|
122
|
+
const ext = absPath.slice(absPath.lastIndexOf('.')).toLowerCase();
|
|
123
|
+
const source = { localPath: absPath };
|
|
124
|
+
let result;
|
|
125
|
+
if (IMAGE_EXTS.has(ext)) {
|
|
126
|
+
result = await bot.sendImage(replyTarget, source);
|
|
127
|
+
}
|
|
128
|
+
else if (VIDEO_EXTS.has(ext)) {
|
|
129
|
+
result = await bot.sendVideo(replyTarget, source);
|
|
130
|
+
}
|
|
131
|
+
else if (VOICE_EXTS.has(ext)) {
|
|
132
|
+
result = await bot.sendVoice(replyTarget, source);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
result = await bot.sendFile(replyTarget, source, { fileName });
|
|
136
|
+
}
|
|
137
|
+
logger.info(`im-qqbot: qqbot_send_file 发送 ${fileName} (${info.size} bytes) → ${replyTarget.scope}:${replyTarget.targetId}`);
|
|
138
|
+
return {
|
|
139
|
+
fileName,
|
|
140
|
+
fileSize: info.size,
|
|
141
|
+
target: `${replyTarget.scope}:${replyTarget.targetId}`,
|
|
142
|
+
fileUuid: result.upload.file_uuid,
|
|
143
|
+
...(result.message?.id !== undefined ? { messageId: result.message.id } : {}),
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
tools.register(definition);
|
|
148
|
+
logger.info('im-qqbot: qqbot_send_file 工具已注册');
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=send-file-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-file-tool.js","sourceRoot":"","sources":["../../src/media/send-file-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { VisionConfig } from '../config.ts';
|
|
3
|
+
import type { Logger } from '../types.ts';
|
|
4
|
+
/** 工具名(qqbot 前缀避免与外部 describe-image 包的同名工具冲突) */
|
|
5
|
+
export declare const DESCRIBE_IMAGE_TOOL_NAME = "qqbot_describe_image";
|
|
6
|
+
/**
|
|
7
|
+
* 启动时检查 settings.yaml 里 vision 模型的 input 模态是否声明了 image。
|
|
8
|
+
*
|
|
9
|
+
* 模型配置界面不暴露 `input` 字段,用户通过界面配置的视觉模型默认是纯文本,
|
|
10
|
+
* 会导致 pi-ai adapter 拒绝图片输入。这里在启动时兜底:未声明 image 时自动补 `[text, image]`。
|
|
11
|
+
* 幂等(已声明则跳过),解析/写入失败仅 warn 不阻断启动。
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureVisionInputModal(vision: VisionConfig, logger: Logger): void;
|
|
14
|
+
/** 注册 qqbot_describe_image 工具(服务缺失时优雅降级,不阻断插件启动) */
|
|
15
|
+
export declare function registerDescribeImageTool(ctx: Context, vision: VisionConfig, logger: Logger): void;
|
|
16
|
+
//# sourceMappingURL=vision-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-tool.d.ts","sourceRoot":"","sources":["../../src/media/vision-tool.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AASnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA2B1C,iDAAiD;AACjD,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAwH/D;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAwCjF;AAED,oDAAoD;AACpD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAqElG"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置 qqbot_describe_image 视觉工具。
|
|
3
|
+
*
|
|
4
|
+
* 复用 dsh 生态的 llm + attachments 服务:
|
|
5
|
+
* 1. 加载图片(本地绝对路径 / http URL)→ magic bytes 嗅探 mediaType
|
|
6
|
+
* 2. attachments.saveImage → ImageAttachmentRef(字节不进 session log)
|
|
7
|
+
* 3. createUserMessage(ImageBlock + text) → llm.stream → BlockAssembler 收集文本
|
|
8
|
+
*
|
|
9
|
+
* 手写 ToolDefinition(标准 JSON Schema),避免引入 dsh-tools 的 8 个 peer 依赖。
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { basename, join, resolve } from 'node:path';
|
|
15
|
+
import yaml from 'js-yaml';
|
|
16
|
+
import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
17
|
+
/** 工具名(qqbot 前缀避免与外部 describe-image 包的同名工具冲突) */
|
|
18
|
+
export const DESCRIBE_IMAGE_TOOL_NAME = 'qqbot_describe_image';
|
|
19
|
+
const DESCRIPTION = 'Inspect one image and return the text the user needs. The image is a local absolute path '
|
|
20
|
+
+ '(downloaded by the QQ bot) or an http(s) URL. Use this when the user references an image, '
|
|
21
|
+
+ 'or when a task needs OCR, chart/diagram reading, screenshot or UI analysis, translation of '
|
|
22
|
+
+ 'image text, or photo understanding. Always pass an explicit `prompt` with a precise '
|
|
23
|
+
+ 'instruction (e.g. "transcribe all text", "extract the table as CSV", "translate the text '
|
|
24
|
+
+ 'into Chinese") instead of relying on the generic default.';
|
|
25
|
+
/** 图片 magic bytes 嗅探 → 标准 MIME(不信声明,安全底线) */
|
|
26
|
+
function sniffImageMediaType(bytes) {
|
|
27
|
+
if (bytes.length < 12)
|
|
28
|
+
return null;
|
|
29
|
+
// PNG: 89 50 4E 47
|
|
30
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
31
|
+
return 'image/png';
|
|
32
|
+
// JPEG: FF D8 FF
|
|
33
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
|
|
34
|
+
return 'image/jpeg';
|
|
35
|
+
// GIF: 47 49 46 38
|
|
36
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38)
|
|
37
|
+
return 'image/gif';
|
|
38
|
+
// WEBP: 52 49 46 46 .. 57 45 42 50
|
|
39
|
+
if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46
|
|
40
|
+
&& bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50)
|
|
41
|
+
return 'image/webp';
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/** 加载图片字节(本地路径 / http URL),校验大小 + 嗅探 MIME */
|
|
45
|
+
async function loadImageBytes(image, maxBytes, signal) {
|
|
46
|
+
let data;
|
|
47
|
+
if (/^https?:\/\//i.test(image)) {
|
|
48
|
+
const resp = await fetch(image, { signal, redirect: 'error' });
|
|
49
|
+
if (!resp.ok)
|
|
50
|
+
throw new Error(`qqbot_describe_image: download failed (HTTP ${resp.status})`);
|
|
51
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
52
|
+
if (buf.length > maxBytes)
|
|
53
|
+
throw new Error(`qqbot_describe_image: image too large (${buf.length} bytes)`);
|
|
54
|
+
data = new Uint8Array(buf);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const info = await stat(image).catch(() => null);
|
|
58
|
+
if (!info?.isFile())
|
|
59
|
+
throw new Error(`qqbot_describe_image: image file not found: ${image}`);
|
|
60
|
+
if (info.size > maxBytes)
|
|
61
|
+
throw new Error(`qqbot_describe_image: image too large (${info.size} bytes)`);
|
|
62
|
+
data = new Uint8Array(await readFile(image));
|
|
63
|
+
}
|
|
64
|
+
const mediaType = sniffImageMediaType(data);
|
|
65
|
+
if (mediaType === null)
|
|
66
|
+
throw new Error('qqbot_describe_image: unrecognized image format (png/jpeg/gif/webp only)');
|
|
67
|
+
return { data, mediaType };
|
|
68
|
+
}
|
|
69
|
+
/** 将终端 finish reason 转成辅助调用失败 */
|
|
70
|
+
function finishError(finish) {
|
|
71
|
+
switch (finish.kind) {
|
|
72
|
+
case 'error':
|
|
73
|
+
case 'aborted':
|
|
74
|
+
return new Error(finish.failure.message);
|
|
75
|
+
default:
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** 调视觉模型描述图片,返回纯文本 */
|
|
80
|
+
async function callVision(llm, vision, prompt, imageRef, signal) {
|
|
81
|
+
const message = createUserMessage({
|
|
82
|
+
content: [
|
|
83
|
+
{ type: 'image', attachment: imageRef },
|
|
84
|
+
{ type: 'text', text: prompt },
|
|
85
|
+
],
|
|
86
|
+
source: { kind: 'plugin', plugin: 'dsh-im-qqbot' },
|
|
87
|
+
});
|
|
88
|
+
const options = {
|
|
89
|
+
provider: vision.provider,
|
|
90
|
+
model: vision.model,
|
|
91
|
+
messages: [message],
|
|
92
|
+
maxTokens: vision.maxTokens,
|
|
93
|
+
signal,
|
|
94
|
+
};
|
|
95
|
+
const assembler = new BlockAssembler();
|
|
96
|
+
for await (const chunk of llm.stream(options)) {
|
|
97
|
+
assembler.push(chunk);
|
|
98
|
+
}
|
|
99
|
+
const terminalError = finishError(assembler.finish);
|
|
100
|
+
if (terminalError !== undefined)
|
|
101
|
+
throw terminalError;
|
|
102
|
+
const text = assembler.blocks()
|
|
103
|
+
.filter((block) => block.type === 'text')
|
|
104
|
+
.map((block) => block.text)
|
|
105
|
+
.join(' ')
|
|
106
|
+
.trim();
|
|
107
|
+
if (text.length === 0)
|
|
108
|
+
throw new Error('qqbot_describe_image: vision model returned no text');
|
|
109
|
+
return text;
|
|
110
|
+
}
|
|
111
|
+
/** 展开 `~`/`~/`/`~\` 前缀 */
|
|
112
|
+
function expandHomePath(path) {
|
|
113
|
+
if (path === '~')
|
|
114
|
+
return homedir();
|
|
115
|
+
if (path.startsWith('~/') || path.startsWith('~\\'))
|
|
116
|
+
return join(homedir(), path.slice(2));
|
|
117
|
+
return path;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 解析 DSH_HOME 的优先级与跨平台处理:
|
|
121
|
+
* 显式配置 > `$DSH_HOME`(空/空白视为未设置)> `~/.dsh`,展开 `~` 后 resolve 为绝对路径。
|
|
122
|
+
*/
|
|
123
|
+
function resolveDshHome() {
|
|
124
|
+
const fromEnv = process.env.DSH_HOME;
|
|
125
|
+
const selected = fromEnv !== undefined && fromEnv.trim().length > 0
|
|
126
|
+
? fromEnv
|
|
127
|
+
: join(homedir(), '.dsh');
|
|
128
|
+
return resolve(expandHomePath(selected));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 启动时检查 settings.yaml 里 vision 模型的 input 模态是否声明了 image。
|
|
132
|
+
*
|
|
133
|
+
* 模型配置界面不暴露 `input` 字段,用户通过界面配置的视觉模型默认是纯文本,
|
|
134
|
+
* 会导致 pi-ai adapter 拒绝图片输入。这里在启动时兜底:未声明 image 时自动补 `[text, image]`。
|
|
135
|
+
* 幂等(已声明则跳过),解析/写入失败仅 warn 不阻断启动。
|
|
136
|
+
*/
|
|
137
|
+
export function ensureVisionInputModal(vision, logger) {
|
|
138
|
+
if (!vision.enabled || !vision.provider || !vision.model)
|
|
139
|
+
return;
|
|
140
|
+
const settingsPath = resolve(resolveDshHome(), 'settings.yaml');
|
|
141
|
+
try {
|
|
142
|
+
if (!existsSync(settingsPath))
|
|
143
|
+
return;
|
|
144
|
+
// YAML 禁止 tab 缩进(配置界面/编辑器可能写入),读入时把行首 tab 规范化为空格
|
|
145
|
+
const raw = readFileSync(settingsPath, 'utf8');
|
|
146
|
+
const normalized = raw.replace(/^\t+/gm, (tabs) => ' '.repeat(tabs.length));
|
|
147
|
+
const doc = yaml.load(normalized);
|
|
148
|
+
if (doc === null || typeof doc !== 'object')
|
|
149
|
+
return;
|
|
150
|
+
const piAi = doc['llm-pi-ai'];
|
|
151
|
+
const provider = piAi?.providers?.[vision.provider];
|
|
152
|
+
const models = provider?.models;
|
|
153
|
+
if (!Array.isArray(models)) {
|
|
154
|
+
logger.warn(`im-qqbot: settings.yaml 未找到 llm-pi-ai.providers.${vision.provider}.models,请手动确认 vision 模型已声明 image 输入`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const model = models.find((m) => typeof m === 'object' && m !== null && m.id === vision.model);
|
|
158
|
+
if (model === undefined) {
|
|
159
|
+
logger.warn(`im-qqbot: settings.yaml 未找到 vision 模型 ${vision.model},请手动确认已声明 image 输入`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const input = model.input;
|
|
163
|
+
if (Array.isArray(input) && input.includes('image'))
|
|
164
|
+
return; // 已声明,幂等跳过
|
|
165
|
+
model.input = ['text', 'image'];
|
|
166
|
+
writeFileSync(settingsPath, yaml.dump(doc), 'utf8');
|
|
167
|
+
logger.info(`im-qqbot: 已为 vision 模型补充 input: [text, image] (${vision.provider}/${vision.model})`);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
logger.warn(`im-qqbot: 检查 settings.yaml vision input 失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** 注册 qqbot_describe_image 工具(服务缺失时优雅降级,不阻断插件启动) */
|
|
174
|
+
export function registerDescribeImageTool(ctx, vision, logger) {
|
|
175
|
+
if (!vision.enabled)
|
|
176
|
+
return;
|
|
177
|
+
if (!vision.provider || !vision.model) {
|
|
178
|
+
logger.warn('im-qqbot: vision.provider/model 未配置,qqbot_describe_image 工具未注册');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const tools = ctx.get('tools');
|
|
182
|
+
const llm = ctx.get('llm');
|
|
183
|
+
const attachments = ctx.get('attachments');
|
|
184
|
+
if (!tools?.register || !llm?.stream || !attachments?.saveImage) {
|
|
185
|
+
logger.warn('im-qqbot: tools/llm/attachments 服务不可用,qqbot_describe_image 工具未注册');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const definition = {
|
|
189
|
+
name: DESCRIBE_IMAGE_TOOL_NAME,
|
|
190
|
+
description: DESCRIPTION,
|
|
191
|
+
parameters: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
image: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
description: 'Absolute path to a local image file, or an http(s) URL of the image.',
|
|
197
|
+
},
|
|
198
|
+
prompt: {
|
|
199
|
+
type: 'string',
|
|
200
|
+
description: 'Your precise instruction for the vision model about this image.',
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
required: ['image'],
|
|
204
|
+
additionalProperties: false,
|
|
205
|
+
},
|
|
206
|
+
output: {
|
|
207
|
+
schema: {
|
|
208
|
+
type: 'object',
|
|
209
|
+
properties: {
|
|
210
|
+
text: { type: 'string' },
|
|
211
|
+
model: { type: 'string' },
|
|
212
|
+
image: { type: 'string' },
|
|
213
|
+
mimeType: { type: 'string', enum: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'] },
|
|
214
|
+
bytes: { type: 'integer' },
|
|
215
|
+
},
|
|
216
|
+
required: ['text', 'model', 'image', 'mimeType', 'bytes'],
|
|
217
|
+
additionalProperties: false,
|
|
218
|
+
},
|
|
219
|
+
render: (_args, value) => [
|
|
220
|
+
{ type: 'text', text: value.text },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
async execute(args, exec) {
|
|
224
|
+
const { image, prompt } = args;
|
|
225
|
+
if (typeof image !== 'string' || image.length === 0) {
|
|
226
|
+
throw new Error('qqbot_describe_image: `image` must be a non-empty string');
|
|
227
|
+
}
|
|
228
|
+
const loaded = await loadImageBytes(image, vision.maxBytes, exec.signal);
|
|
229
|
+
const ref = await attachments.saveImage({
|
|
230
|
+
data: loaded.data,
|
|
231
|
+
mediaType: loaded.mediaType,
|
|
232
|
+
name: /^https?:\/\//i.test(image) ? undefined : basename(image),
|
|
233
|
+
});
|
|
234
|
+
const text = await callVision(llm, vision, prompt ?? vision.defaultPrompt, ref, exec.signal);
|
|
235
|
+
return { text, model: vision.model, image, mimeType: loaded.mediaType, bytes: loaded.data.length };
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
tools.register(definition);
|
|
239
|
+
logger.info(`im-qqbot: qqbot_describe_image 工具已注册 (provider=${vision.provider} model=${vision.model})`);
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=vision-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-tool.js","sourceRoot":"","sources":["../../src/media/vision-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 富媒体下载中间件 — 在中间件链中下载 image/video/file 附件
|
|
3
3
|
*
|
|
4
4
|
* 结果写入 ctx.state.downloadedFiles,供 handleInbound 读取。
|
|
5
5
|
* 下载失败不阻断消息(记录告警后放行),确保附件异常不影响正常文本处理。
|
|
6
6
|
*/
|
|
7
7
|
import type { MiddlewareContext } from '@tencent-connect/qqbot-nodejs';
|
|
8
|
-
import type { ImQQBotConfig } from '../config.
|
|
9
|
-
import type { Logger } from '../types.
|
|
8
|
+
import type { ImQQBotConfig } from '../config.ts';
|
|
9
|
+
import type { Logger } from '../types.ts';
|
|
10
10
|
export declare function attachmentProcessor(config: ImQQBotConfig, logger: Logger): (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
11
11
|
//# sourceMappingURL=attachment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/middleware/attachment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/middleware/attachment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAmC,MAAM,aAAa,CAAC;AAE3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,IACzD,KAAK,iBAAiB,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CA4BhF"}
|
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { downloadMediaAttachments } from "../transport/attachment.js";
|
|
2
2
|
export function attachmentProcessor(config, logger) {
|
|
3
3
|
return async (ctx, next) => {
|
|
4
4
|
const msg = ctx.message;
|
|
5
|
-
const cwd = config.cwd || process.cwd();
|
|
6
|
-
const messageId = msg.messageId ?? 'unknown';
|
|
7
5
|
try {
|
|
8
|
-
|
|
6
|
+
// 当前消息附件
|
|
7
|
+
const downloaded = await downloadMediaAttachments(msg.attachments, config.media, logger);
|
|
9
8
|
ctx.state.downloadedFiles = downloaded;
|
|
9
|
+
// 引用消息附件:转成 RawAttachment 结构复用下载(voice 由 downloadMediaAttachments 自动跳过)
|
|
10
|
+
const quoteAttachments = ctx.state.quote?.attachments;
|
|
11
|
+
if (quoteAttachments && quoteAttachments.length > 0) {
|
|
12
|
+
const rawQuote = quoteAttachments
|
|
13
|
+
.filter(a => a.url)
|
|
14
|
+
.map((a) => ({
|
|
15
|
+
content_type: a.contentType ?? '',
|
|
16
|
+
filename: a.filename ?? '',
|
|
17
|
+
size: 0,
|
|
18
|
+
url: a.url,
|
|
19
|
+
}));
|
|
20
|
+
const downloadedQuote = await downloadMediaAttachments(rawQuote, config.media, logger);
|
|
21
|
+
ctx.state.downloadedQuoteFiles = downloadedQuote;
|
|
22
|
+
}
|
|
10
23
|
}
|
|
11
24
|
catch (err) {
|
|
12
25
|
logger.warn(`im-qqbot: attachment download failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/middleware/attachment.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/middleware/attachment.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAItE,MAAM,UAAU,mBAAmB,CAAC,MAAqB,EAAE,MAAc;IACvE,OAAO,KAAK,EAAE,GAAsB,EAAE,IAAyB,EAAiB,EAAE;QAChF,MAAM,GAAG,GAAG,GAAG,CAAC,OAA4C,CAAC;QAE7D,IAAI,CAAC;YACH,SAAS;YACT,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACzF,GAAG,CAAC,KAAK,CAAC,eAAe,GAAG,UAAU,CAAC;YAEvC,wEAAwE;YACxE,MAAM,gBAAgB,GAAI,GAAG,CAAC,KAA0D,CAAC,KAAK,EAAE,WAAW,CAAC;YAC5G,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpD,MAAM,QAAQ,GAAG,gBAAgB;qBAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;qBAClB,GAAG,CAAC,CAAC,CAAC,EAAiB,EAAE,CAAC,CAAC;oBAC1B,YAAY,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE;oBACjC,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;oBAC1B,IAAI,EAAE,CAAC;oBACP,GAAG,EAAE,CAAC,CAAC,GAAa;iBACrB,CAAC,CAAC,CAAC;gBACN,MAAM,eAAe,GAAG,MAAM,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBACvF,GAAG,CAAC,KAAK,CAAC,oBAAoB,GAAG,eAAe,CAAC;YACnD,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,yCAAyC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3G,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 待答问题截获中间件 — 在中间件链中截获 ask_user_question 的答案
|
|
3
|
+
*
|
|
4
|
+
* 必须放在 concurrencyGuard 之前:答案消息需绕过并发守卫的 merge 缓冲,
|
|
5
|
+
* 否则会被排队而无法到达 tryAnswer,导致「turn 等答案 ↔ 答案被缓冲」死锁。
|
|
6
|
+
*/
|
|
7
|
+
import type { MiddlewareContext } from '@tencent-connect/qqbot-nodejs';
|
|
8
|
+
import type { SessionManager } from '../session/index.ts';
|
|
9
|
+
export declare function questionAnswer(manager: SessionManager): (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
10
|
+
//# sourceMappingURL=question-answer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-answer.d.ts","sourceRoot":"","sources":["../../src/middleware/question-answer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,IACtC,KAAK,iBAAiB,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAehF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cacheMsgId } from "../transport/msgid-cache.js";
|
|
2
|
+
export function questionAnswer(manager) {
|
|
3
|
+
return async (ctx, next) => {
|
|
4
|
+
const questionChannel = manager.questionChannel;
|
|
5
|
+
if (!questionChannel) {
|
|
6
|
+
await next();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (questionChannel.tryAnswer(ctx.replyTarget.scope, ctx.replyTarget.targetId, ctx.message.content.trim())) {
|
|
10
|
+
// 答案消息被截获(不再走到 bot.on('message')),在此补录其 msgId,
|
|
11
|
+
// 供被动回复超限时切换到缓存的最近 msgId。
|
|
12
|
+
cacheMsgId(ctx.replyTarget.scope, ctx.replyTarget.targetId, ctx.replyTarget.msgId);
|
|
13
|
+
ctx.stop('question-answer');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await next();
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=question-answer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-answer.js","sourceRoot":"","sources":["../../src/middleware/question-answer.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAEzD,MAAM,UAAU,cAAc,CAAC,OAAuB;IACpD,OAAO,KAAK,EAAE,GAAsB,EAAE,IAAyB,EAAiB,EAAE;QAChF,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;QAChD,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,IAAI,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,IAAI,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC3G,+CAA+C;YAC/C,0BAA0B;YAC1B,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACnF,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/model/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 统一的模型发现、路由解析、per-peer 偏好管理。
|
|
5
5
|
*/
|
|
6
|
-
export { ModelResolver } from './model-resolver.
|
|
7
|
-
export { PrefsStore } from './prefs-store.
|
|
8
|
-
export { SettingsReader } from './settings-reader.
|
|
9
|
-
export type { ModelRoute, ModelEntry } from './types.
|
|
6
|
+
export { ModelResolver } from './model-resolver.ts';
|
|
7
|
+
export { PrefsStore } from './prefs-store.ts';
|
|
8
|
+
export { SettingsReader } from './settings-reader.ts';
|
|
9
|
+
export type { ModelRoute, ModelEntry } from './types.ts';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/model/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 统一的模型发现、路由解析、per-peer 偏好管理。
|
|
5
5
|
*/
|
|
6
|
-
export { ModelResolver } from
|
|
7
|
-
export { PrefsStore } from
|
|
8
|
-
export { SettingsReader } from
|
|
6
|
+
export { ModelResolver } from "./model-resolver.js";
|
|
7
|
+
export { PrefsStore } from "./prefs-store.js";
|
|
8
|
+
export { SettingsReader } from "./settings-reader.js";
|
|
9
9
|
//# sourceMappingURL=index.js.map
|