@tencent-connect/dsh-qqbot 0.4.0 → 0.5.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.
Files changed (177) hide show
  1. package/.orange-ci.yml +2 -68
  2. package/CHANGELOG.md +45 -0
  3. package/CHANGELOG_EN.md +84 -0
  4. package/README.md +107 -17
  5. package/README_EN.md +107 -17
  6. package/cordis.patch.yml +1 -1
  7. package/cordis.yml +46 -3
  8. package/dist/commands/help.d.ts +6 -7
  9. package/dist/commands/help.d.ts.map +1 -1
  10. package/dist/commands/help.js +23 -7
  11. package/dist/commands/help.js.map +1 -1
  12. package/dist/commands/index.d.ts +7 -5
  13. package/dist/commands/index.d.ts.map +1 -1
  14. package/dist/commands/index.js +12 -12
  15. package/dist/commands/index.js.map +1 -1
  16. package/dist/commands/misc.d.ts +10 -8
  17. package/dist/commands/misc.d.ts.map +1 -1
  18. package/dist/commands/misc.js +45 -8
  19. package/dist/commands/misc.js.map +1 -1
  20. package/dist/commands/model.d.ts +3 -4
  21. package/dist/commands/model.d.ts.map +1 -1
  22. package/dist/commands/model.js +8 -7
  23. package/dist/commands/model.js.map +1 -1
  24. package/dist/commands/preset.d.ts +8 -0
  25. package/dist/commands/preset.d.ts.map +1 -0
  26. package/dist/commands/preset.js +52 -0
  27. package/dist/commands/preset.js.map +1 -0
  28. package/dist/commands/session.d.ts +11 -7
  29. package/dist/commands/session.d.ts.map +1 -1
  30. package/dist/commands/session.js +29 -13
  31. package/dist/commands/session.js.map +1 -1
  32. package/dist/commands/status.d.ts +3 -4
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +2 -1
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/commands/types.d.ts +9 -2
  37. package/dist/commands/types.d.ts.map +1 -1
  38. package/dist/config.d.ts +38 -0
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +35 -1
  41. package/dist/config.js.map +1 -1
  42. package/dist/features/answer-parser.d.ts +9 -0
  43. package/dist/features/answer-parser.d.ts.map +1 -0
  44. package/dist/features/answer-parser.js +23 -0
  45. package/dist/features/answer-parser.js.map +1 -0
  46. package/dist/features/approval-channel.d.ts +76 -0
  47. package/dist/features/approval-channel.d.ts.map +1 -0
  48. package/dist/features/approval-channel.js +142 -0
  49. package/dist/features/approval-channel.js.map +1 -0
  50. package/dist/features/approval-renderer.d.ts +18 -0
  51. package/dist/features/approval-renderer.d.ts.map +1 -0
  52. package/dist/features/approval-renderer.js +52 -0
  53. package/dist/features/approval-renderer.js.map +1 -0
  54. package/dist/features/button-utils.d.ts +33 -0
  55. package/dist/features/button-utils.d.ts.map +1 -0
  56. package/dist/features/button-utils.js +27 -0
  57. package/dist/features/button-utils.js.map +1 -0
  58. package/dist/features/history-store.d.ts.map +1 -1
  59. package/dist/features/history-store.js +0 -1
  60. package/dist/features/history-store.js.map +1 -1
  61. package/dist/features/question-channel.d.ts +96 -0
  62. package/dist/features/question-channel.d.ts.map +1 -0
  63. package/dist/features/question-channel.js +234 -0
  64. package/dist/features/question-channel.js.map +1 -0
  65. package/dist/features/question-renderer.d.ts +20 -0
  66. package/dist/features/question-renderer.d.ts.map +1 -0
  67. package/dist/features/question-renderer.js +70 -0
  68. package/dist/features/question-renderer.js.map +1 -0
  69. package/dist/gateway/bootstrap.d.ts +3 -3
  70. package/dist/gateway/bootstrap.d.ts.map +1 -1
  71. package/dist/gateway/bootstrap.js +74 -8
  72. package/dist/gateway/bootstrap.js.map +1 -1
  73. package/dist/gateway/index.d.ts +2 -2
  74. package/dist/gateway/index.js +2 -2
  75. package/dist/gateway/middleware-setup.d.ts +3 -3
  76. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  77. package/dist/gateway/middleware-setup.js +9 -7
  78. package/dist/gateway/middleware-setup.js.map +1 -1
  79. package/dist/index.d.ts +2 -2
  80. package/dist/index.js +4 -4
  81. package/dist/media/media-cleaner.d.ts +19 -0
  82. package/dist/media/media-cleaner.d.ts.map +1 -0
  83. package/dist/media/media-cleaner.js +81 -0
  84. package/dist/media/media-cleaner.js.map +1 -0
  85. package/dist/media/send-file-tool.d.ts +35 -0
  86. package/dist/media/send-file-tool.d.ts.map +1 -0
  87. package/dist/media/send-file-tool.js +150 -0
  88. package/dist/media/send-file-tool.js.map +1 -0
  89. package/dist/media/vision-tool.d.ts +16 -0
  90. package/dist/media/vision-tool.d.ts.map +1 -0
  91. package/dist/media/vision-tool.js +241 -0
  92. package/dist/media/vision-tool.js.map +1 -0
  93. package/dist/middleware/attachment.d.ts +3 -3
  94. package/dist/middleware/attachment.d.ts.map +1 -1
  95. package/dist/middleware/attachment.js +17 -4
  96. package/dist/middleware/attachment.js.map +1 -1
  97. package/dist/middleware/question-answer.d.ts +10 -0
  98. package/dist/middleware/question-answer.d.ts.map +1 -0
  99. package/dist/middleware/question-answer.js +19 -0
  100. package/dist/middleware/question-answer.js.map +1 -0
  101. package/dist/model/index.d.ts +4 -4
  102. package/dist/model/index.js +3 -3
  103. package/dist/model/model-resolver.d.ts +27 -13
  104. package/dist/model/model-resolver.d.ts.map +1 -1
  105. package/dist/model/model-resolver.js +82 -27
  106. package/dist/model/model-resolver.js.map +1 -1
  107. package/dist/model/prefs-store.d.ts +6 -1
  108. package/dist/model/prefs-store.d.ts.map +1 -1
  109. package/dist/model/prefs-store.js +25 -1
  110. package/dist/model/prefs-store.js.map +1 -1
  111. package/dist/model/settings-reader.d.ts +1 -1
  112. package/dist/session/idle-evictor.d.ts +2 -2
  113. package/dist/session/idle-evictor.d.ts.map +1 -1
  114. package/dist/session/idle-evictor.js +1 -1
  115. package/dist/session/idle-evictor.js.map +1 -1
  116. package/dist/session/index.d.ts +3 -3
  117. package/dist/session/index.d.ts.map +1 -1
  118. package/dist/session/index.js +2 -2
  119. package/dist/session/session-manager.d.ts +67 -9
  120. package/dist/session/session-manager.d.ts.map +1 -1
  121. package/dist/session/session-manager.js +216 -20
  122. package/dist/session/session-manager.js.map +1 -1
  123. package/dist/session/types.d.ts +47 -1
  124. package/dist/session/types.d.ts.map +1 -1
  125. package/dist/shared/index.d.ts +3 -3
  126. package/dist/shared/index.d.ts.map +1 -1
  127. package/dist/shared/index.js +3 -3
  128. package/dist/shared/index.js.map +1 -1
  129. package/dist/shared/send-helper.js +1 -1
  130. package/dist/shared/utils.d.ts +2 -0
  131. package/dist/shared/utils.d.ts.map +1 -1
  132. package/dist/shared/utils.js +2 -2
  133. package/dist/shared/utils.js.map +1 -1
  134. package/dist/transport/attachment.d.ts +18 -7
  135. package/dist/transport/attachment.d.ts.map +1 -1
  136. package/dist/transport/attachment.js +77 -30
  137. package/dist/transport/attachment.js.map +1 -1
  138. package/dist/transport/inbound.d.ts +5 -4
  139. package/dist/transport/inbound.d.ts.map +1 -1
  140. package/dist/transport/inbound.js +120 -80
  141. package/dist/transport/inbound.js.map +1 -1
  142. package/dist/transport/index.d.ts +4 -4
  143. package/dist/transport/index.js +4 -4
  144. package/dist/transport/msgid-cache.d.ts +23 -0
  145. package/dist/transport/msgid-cache.d.ts.map +1 -0
  146. package/dist/transport/msgid-cache.js +57 -0
  147. package/dist/transport/msgid-cache.js.map +1 -0
  148. package/dist/transport/outbound-buffer.d.ts +9 -6
  149. package/dist/transport/outbound-buffer.d.ts.map +1 -1
  150. package/dist/transport/outbound-buffer.js +5 -5
  151. package/dist/transport/outbound-buffer.js.map +1 -1
  152. package/dist/transport/outbound.d.ts +9 -9
  153. package/dist/transport/outbound.d.ts.map +1 -1
  154. package/dist/transport/outbound.js +7 -7
  155. package/dist/transport/outbound.js.map +1 -1
  156. package/dist/transport/reply-limiter.d.ts +31 -0
  157. package/dist/transport/reply-limiter.d.ts.map +1 -0
  158. package/dist/transport/reply-limiter.js +50 -0
  159. package/dist/transport/reply-limiter.js.map +1 -0
  160. package/dist/transport/reply-target.d.ts +29 -0
  161. package/dist/transport/reply-target.d.ts.map +1 -0
  162. package/dist/transport/reply-target.js +55 -0
  163. package/dist/transport/reply-target.js.map +1 -0
  164. package/dist/transport/streaming-writer.d.ts +3 -3
  165. package/dist/transport/streaming-writer.d.ts.map +1 -1
  166. package/dist/transport/streaming-writer.js +1 -1
  167. package/dist/transport/streaming-writer.js.map +1 -1
  168. package/dist/transport/tool-presenter.d.ts +0 -1
  169. package/dist/transport/tool-presenter.d.ts.map +1 -1
  170. package/dist/transport/tool-presenter.js +0 -1
  171. package/dist/transport/tool-presenter.js.map +1 -1
  172. package/dist/types.d.ts +9 -15
  173. package/dist/types.d.ts.map +1 -1
  174. package/docs/assets/qqchannel.jpg +0 -0
  175. package/docs/assets/qqgroup.jpg +0 -0
  176. package/docs/assets/qrcode.png +0 -0
  177. package/package.json +21 -4
@@ -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
- * 附件下载中间件 — 在中间件链中下载 file 附件
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.js';
9
- import type { Logger } from '../types.js';
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,EAAiB,MAAM,aAAa,CAAC;AAEzD,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,CAchF"}
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 { downloadFileAttachments } from '../transport/attachment.js';
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
- const downloaded = await downloadFileAttachments(msg.attachments, cwd, messageId, logger);
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,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAIrE,MAAM,UAAU,mBAAmB,CAAC,MAAqB,EAAE,MAAc;IACvE,OAAO,KAAK,EAAE,GAAsB,EAAE,IAAyB,EAAiB,EAAE;QAChF,MAAM,GAAG,GAAG,GAAG,CAAC,OAAgE,CAAC;QACjF,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC;QAE7C,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,uBAAuB,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;YAC1F,GAAG,CAAC,KAAK,CAAC,eAAe,GAAG,UAAU,CAAC;QACzC,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"}
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"}
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * 统一的模型发现、路由解析、per-peer 偏好管理。
5
5
  */
6
- export { ModelResolver } from './model-resolver.js';
7
- export { PrefsStore } from './prefs-store.js';
8
- export { SettingsReader } from './settings-reader.js';
9
- export type { ModelRoute, ModelEntry } from './types.js';
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
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 统一的模型发现、路由解析、per-peer 偏好管理。
5
5
  */
6
- export { ModelResolver } from './model-resolver.js';
7
- export { PrefsStore } from './prefs-store.js';
8
- export { SettingsReader } from './settings-reader.js';
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
@@ -13,16 +13,16 @@
13
13
  * > 宿主 agentDefaultModel 服务
14
14
  */
15
15
  import type { Context } from '@deepseek-ai/cordis';
16
- import type { ImQQBotConfig } from '../config.js';
17
- import type { Logger } from '../types.js';
18
- import type { ModelRoute, ModelEntry } from './types.js';
16
+ import type { ImQQBotConfig } from '../config.ts';
17
+ import type { Logger } from '../types.ts';
18
+ import type { ModelRoute, ModelEntry } from './types.ts';
19
19
  export declare class ModelResolver {
20
+ private readonly prefs;
21
+ private readonly settings;
20
22
  private readonly ctx;
21
23
  private readonly config;
22
24
  private readonly logger?;
23
- private readonly prefs;
24
- private readonly settings;
25
- constructor(ctx: Context, config: ImQQBotConfig, logger?: Logger | undefined);
25
+ constructor(ctx: Context, config: ImQQBotConfig, logger?: Logger);
26
26
  /**
27
27
  * 获取指定 sessionKey 的有效模型路由(create 用)
28
28
  *
@@ -34,12 +34,10 @@ export declare class ModelResolver {
34
34
  *
35
35
  * 优先级:per-peer 偏好 > cordis.yml 显式配置 > 默认链(settings.yaml > host)
36
36
  *
37
- * 注意:不能像 dsh-TUI 那样返回 undefined 让 session 沿用 requestHeader。
38
- * dsh-TUI 靠 installModelSelection 从 session.requestHeader 恢复 {{model}},
39
- * 而我们未装 installModelSelection,system-prompt 的 {{model}} 变量直接读
40
- * agent.options.model(agent-loop index.ts:352)——若无值会抛
37
+ * 这里不返回 undefined,因为未装 installModelSelection,system-prompt 的
38
+ * {{model}} 变量直接读 agent.options.model——若无值会抛
41
39
  * "prompt variable {{model}} has no value for this assembly"。
42
- * 因此这里兜底到默认链,确保 agent.options.model 始终有值。
40
+ * 因此兜底到默认链,确保 agent.options.model 始终有值。
43
41
  */
44
42
  getResumeRoute(sessionKey: string): ModelRoute | undefined;
45
43
  /**
@@ -66,6 +64,18 @@ export declare class ModelResolver {
66
64
  * 清除指定 sessionKey 的 sessionId 记录
67
65
  */
68
66
  clearSessionId(sessionKey: string): void;
67
+ /**
68
+ * 获取指定 sessionKey 的 per-peer preset 覆盖
69
+ */
70
+ getPreset(sessionKey: string): string | undefined;
71
+ /**
72
+ * 设置 per-peer preset 覆盖并持久化
73
+ */
74
+ setPreset(sessionKey: string, presetId: string): void;
75
+ /**
76
+ * 清除 per-peer preset 覆盖并持久化
77
+ */
78
+ clearPreset(sessionKey: string): boolean;
69
79
  /**
70
80
  * 解析默认模型路由(不含 per-peer 偏好)
71
81
  *
@@ -75,14 +85,18 @@ export declare class ModelResolver {
75
85
  resolveDefault(): ModelRoute;
76
86
  /**
77
87
  * 列出所有可用模型
88
+ *
89
+ * 优先走 ctx.llm 服务动态发现,服务不可用或失败时回退 settings.yaml 静态配置。
78
90
  */
79
- listModels(): ModelEntry[];
91
+ listModels(): Promise<ModelEntry[]>;
80
92
  /**
81
93
  * 列出可用 provider 名称
82
94
  */
83
95
  listProviders(): string[];
84
96
  private readFromHost;
85
- /** 统一的 Cordis 服务访问 */
97
+ /** 统一的 Cordis 服务访问(可选获取:服务未注入时静默返回 undefined,避免 Proxy 抛错) */
86
98
  private getService;
99
+ /** 获取 ctx.llm 服务(最小接口收窄,不可用时返回 undefined) */
100
+ private getLlmService;
87
101
  }
88
102
  //# sourceMappingURL=model-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-resolver.d.ts","sourceRoot":"","sources":["../../src/model/model-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIzD,qBAAa,aAAa;IAKtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAN1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;gBAGvB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,MAAM,YAAA;IAQlC;;;;OAIG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI7D;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAW1D;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAIxD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIxC;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIzD;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;;OAKG;IACH,cAAc,IAAI,UAAU;IAc5B;;OAEG;IACH,UAAU,IAAI,UAAU,EAAE;IAI1B;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAuBzB,OAAO,CAAC,YAAY;IAoBpB,sBAAsB;IACtB,OAAO,CAAC,UAAU;CAKnB"}
1
+ {"version":3,"file":"model-resolver.d.ts","sourceRoot":"","sources":["../../src/model/model-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAUzD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;gBAG/B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,MAAM;IAWjB;;;;OAIG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI7D;;;;;;;;;OASG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAW1D;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAIxD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIxC;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIzD;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIxC;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIrD;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIxC;;;;;OAKG;IACH,cAAc,IAAI,UAAU;IAc5B;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IA+BzC;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAgBzB,OAAO,CAAC,YAAY;IAoBpB,6DAA6D;IAC7D,OAAO,CAAC,UAAU;IASlB,6CAA6C;IAC7C,OAAO,CAAC,aAAa;CAatB"}
@@ -1,11 +1,11 @@
1
- import { PrefsStore } from './prefs-store.js';
2
- import { SettingsReader } from './settings-reader.js';
1
+ import { PrefsStore } from "./prefs-store.js";
2
+ import { SettingsReader } from "./settings-reader.js";
3
3
  export class ModelResolver {
4
+ prefs;
5
+ settings;
4
6
  ctx;
5
7
  config;
6
8
  logger;
7
- prefs;
8
- settings;
9
9
  constructor(ctx, config, logger) {
10
10
  this.ctx = ctx;
11
11
  this.config = config;
@@ -26,12 +26,10 @@ export class ModelResolver {
26
26
  *
27
27
  * 优先级:per-peer 偏好 > cordis.yml 显式配置 > 默认链(settings.yaml > host)
28
28
  *
29
- * 注意:不能像 dsh-TUI 那样返回 undefined 让 session 沿用 requestHeader。
30
- * dsh-TUI 靠 installModelSelection 从 session.requestHeader 恢复 {{model}},
31
- * 而我们未装 installModelSelection,system-prompt 的 {{model}} 变量直接读
32
- * agent.options.model(agent-loop index.ts:352)——若无值会抛
29
+ * 这里不返回 undefined,因为未装 installModelSelection,system-prompt 的
30
+ * {{model}} 变量直接读 agent.options.model——若无值会抛
33
31
  * "prompt variable {{model}} has no value for this assembly"。
34
- * 因此这里兜底到默认链,确保 agent.options.model 始终有值。
32
+ * 因此兜底到默认链,确保 agent.options.model 始终有值。
35
33
  */
36
34
  getResumeRoute(sessionKey) {
37
35
  const override = this.prefs.getOverride(sessionKey);
@@ -78,6 +76,24 @@ export class ModelResolver {
78
76
  clearSessionId(sessionKey) {
79
77
  this.prefs.clearSessionId(sessionKey);
80
78
  }
79
+ /**
80
+ * 获取指定 sessionKey 的 per-peer preset 覆盖
81
+ */
82
+ getPreset(sessionKey) {
83
+ return this.prefs.getPreset(sessionKey);
84
+ }
85
+ /**
86
+ * 设置 per-peer preset 覆盖并持久化
87
+ */
88
+ setPreset(sessionKey, presetId) {
89
+ this.prefs.setPreset(sessionKey, presetId);
90
+ }
91
+ /**
92
+ * 清除 per-peer preset 覆盖并持久化
93
+ */
94
+ clearPreset(sessionKey) {
95
+ return this.prefs.clearPreset(sessionKey);
96
+ }
81
97
  /**
82
98
  * 解析默认模型路由(不含 per-peer 偏好)
83
99
  *
@@ -98,29 +114,51 @@ export class ModelResolver {
98
114
  }
99
115
  /**
100
116
  * 列出所有可用模型
117
+ *
118
+ * 优先走 ctx.llm 服务动态发现,服务不可用或失败时回退 settings.yaml 静态配置。
101
119
  */
102
- listModels() {
120
+ async listModels() {
121
+ const llm = this.getLlmService();
122
+ if (!llm)
123
+ return this.settings.readModels();
124
+ try {
125
+ const providers = llm.listProviders();
126
+ const entries = [];
127
+ for (const provider of providers) {
128
+ const providerId = typeof provider === 'string' ? provider : provider.id;
129
+ try {
130
+ const models = await llm.listModels(providerId);
131
+ for (const model of models) {
132
+ entries.push({ provider: providerId, id: model.id, name: model.name });
133
+ }
134
+ }
135
+ catch (err) {
136
+ // 单个 provider 失败,跳过
137
+ this.logger?.debug(`ModelResolver: provider ${providerId} 模型列表获取失败: ${err instanceof Error ? err.message : String(err)}`);
138
+ }
139
+ }
140
+ if (entries.length > 0)
141
+ return entries;
142
+ }
143
+ catch (err) {
144
+ this.logger?.warn(`ModelResolver: 动态发现模型失败,回退 settings.yaml: ${err instanceof Error ? err.message : String(err)}`);
145
+ }
103
146
  return this.settings.readModels();
104
147
  }
105
148
  /**
106
149
  * 列出可用 provider 名称
107
150
  */
108
151
  listProviders() {
109
- try {
110
- const llm = this.getService('llm');
111
- if (llm && typeof llm.listProviders === 'function') {
112
- const providers = llm.listProviders();
113
- if (providers.length > 0) {
114
- const first = providers[0];
115
- if (typeof first === 'string')
116
- return providers;
117
- return providers.map((p) => p.id);
118
- }
152
+ const llm = this.getLlmService();
153
+ if (llm) {
154
+ const providers = llm.listProviders();
155
+ if (providers.length > 0) {
156
+ const first = providers[0];
157
+ if (typeof first === 'string')
158
+ return providers;
159
+ return providers.map((p) => p.id);
119
160
  }
120
161
  }
121
- catch {
122
- // 忽略
123
- }
124
162
  return this.settings.readProviders();
125
163
  }
126
164
  // ── 私有方法 ──
@@ -141,11 +179,28 @@ export class ModelResolver {
141
179
  }
142
180
  return undefined;
143
181
  }
144
- /** 统一的 Cordis 服务访问 */
182
+ /** 统一的 Cordis 服务访问(可选获取:服务未注入时静默返回 undefined,避免 Proxy 抛错) */
145
183
  getService(name) {
146
- const ctxAny = this.ctx;
147
- return ctxAny[name] ??
148
- (typeof ctxAny.get === 'function' ? ctxAny.get(name) : undefined);
184
+ try {
185
+ const ctxAny = this.ctx;
186
+ return typeof ctxAny.get === 'function' ? ctxAny.get(name) : undefined;
187
+ }
188
+ catch {
189
+ return undefined;
190
+ }
191
+ }
192
+ /** 获取 ctx.llm 服务(最小接口收窄,不可用时返回 undefined) */
193
+ getLlmService() {
194
+ try {
195
+ const llm = this.getService('llm');
196
+ if (llm && typeof llm.listProviders === 'function' && typeof llm.listModels === 'function') {
197
+ return llm;
198
+ }
199
+ }
200
+ catch (err) {
201
+ this.logger?.warn(`ModelResolver: ctx.llm 服务访问失败: ${err instanceof Error ? err.message : String(err)}`);
202
+ }
203
+ return undefined;
149
204
  }
150
205
  }
151
206
  //# sourceMappingURL=model-resolver.js.map