@wwlocal/aibot-plugin-node 20260409.20.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 (93) hide show
  1. package/README.md +489 -0
  2. package/config.example.json +169 -0
  3. package/dist/cjs/index.js +76 -0
  4. package/dist/cjs/src/adapters/anthropic-adapter.js +534 -0
  5. package/dist/cjs/src/adapters/base-adapter.js +176 -0
  6. package/dist/cjs/src/adapters/deepseek-adapter.js +328 -0
  7. package/dist/cjs/src/adapters/dify-adapter.js +636 -0
  8. package/dist/cjs/src/adapters/index.js +131 -0
  9. package/dist/cjs/src/adapters/openai-adapter.js +361 -0
  10. package/dist/cjs/src/adapters/webhook-adapter.js +260 -0
  11. package/dist/cjs/src/agent-forwarder.js +87 -0
  12. package/dist/cjs/src/ca-cert.js +162 -0
  13. package/dist/cjs/src/config.js +169 -0
  14. package/dist/cjs/src/const.js +124 -0
  15. package/dist/cjs/src/conversation-manager.js +147 -0
  16. package/dist/cjs/src/dm-policy.js +46 -0
  17. package/dist/cjs/src/group-policy.js +95 -0
  18. package/dist/cjs/src/media-handler.js +136 -0
  19. package/dist/cjs/src/media-loader.js +271 -0
  20. package/dist/cjs/src/media-storage.js +165 -0
  21. package/dist/cjs/src/media-uploader.js +203 -0
  22. package/dist/cjs/src/message-parser.js +133 -0
  23. package/dist/cjs/src/message-sender.js +87 -0
  24. package/dist/cjs/src/monitor.js +849 -0
  25. package/dist/cjs/src/reqid-store.js +87 -0
  26. package/dist/cjs/src/server.js +72 -0
  27. package/dist/cjs/src/service-manager.js +135 -0
  28. package/dist/cjs/src/state-manager.js +143 -0
  29. package/dist/cjs/src/template-card-parser.js +498 -0
  30. package/dist/cjs/src/timeout.js +41 -0
  31. package/dist/cjs/src/version.js +25 -0
  32. package/dist/esm/index.js +74 -0
  33. package/dist/esm/src/adapters/anthropic-adapter.js +512 -0
  34. package/dist/esm/src/adapters/base-adapter.js +174 -0
  35. package/dist/esm/src/adapters/deepseek-adapter.js +326 -0
  36. package/dist/esm/src/adapters/dify-adapter.js +634 -0
  37. package/dist/esm/src/adapters/index.js +123 -0
  38. package/dist/esm/src/adapters/openai-adapter.js +339 -0
  39. package/dist/esm/src/adapters/webhook-adapter.js +258 -0
  40. package/dist/esm/src/agent-forwarder.js +84 -0
  41. package/dist/esm/src/ca-cert.js +136 -0
  42. package/dist/esm/src/config.js +145 -0
  43. package/dist/esm/src/const.js +100 -0
  44. package/dist/esm/src/conversation-manager.js +144 -0
  45. package/dist/esm/src/dm-policy.js +44 -0
  46. package/dist/esm/src/group-policy.js +92 -0
  47. package/dist/esm/src/media-handler.js +133 -0
  48. package/dist/esm/src/media-loader.js +246 -0
  49. package/dist/esm/src/media-storage.js +143 -0
  50. package/dist/esm/src/media-uploader.js +198 -0
  51. package/dist/esm/src/message-parser.js +131 -0
  52. package/dist/esm/src/message-sender.js +83 -0
  53. package/dist/esm/src/monitor.js +841 -0
  54. package/dist/esm/src/reqid-store.js +85 -0
  55. package/dist/esm/src/server.js +69 -0
  56. package/dist/esm/src/service-manager.js +133 -0
  57. package/dist/esm/src/state-manager.js +134 -0
  58. package/dist/esm/src/template-card-parser.js +495 -0
  59. package/dist/esm/src/timeout.js +38 -0
  60. package/dist/esm/src/version.js +22 -0
  61. package/dist/esm/types/index.d.ts +14 -0
  62. package/dist/esm/types/src/adapters/anthropic-adapter.d.ts +93 -0
  63. package/dist/esm/types/src/adapters/base-adapter.d.ts +76 -0
  64. package/dist/esm/types/src/adapters/deepseek-adapter.d.ts +87 -0
  65. package/dist/esm/types/src/adapters/dify-adapter.d.ts +100 -0
  66. package/dist/esm/types/src/adapters/index.d.ts +60 -0
  67. package/dist/esm/types/src/adapters/openai-adapter.d.ts +82 -0
  68. package/dist/esm/types/src/adapters/types.d.ts +373 -0
  69. package/dist/esm/types/src/adapters/webhook-adapter.d.ts +54 -0
  70. package/dist/esm/types/src/agent-forwarder.d.ts +32 -0
  71. package/dist/esm/types/src/ca-cert.d.ts +53 -0
  72. package/dist/esm/types/src/config.d.ts +29 -0
  73. package/dist/esm/types/src/const.d.ts +74 -0
  74. package/dist/esm/types/src/conversation-manager.d.ts +81 -0
  75. package/dist/esm/types/src/dm-policy.d.ts +27 -0
  76. package/dist/esm/types/src/group-policy.d.ts +28 -0
  77. package/dist/esm/types/src/interface.d.ts +332 -0
  78. package/dist/esm/types/src/media-handler.d.ts +36 -0
  79. package/dist/esm/types/src/media-loader.d.ts +47 -0
  80. package/dist/esm/types/src/media-storage.d.ts +35 -0
  81. package/dist/esm/types/src/media-uploader.d.ts +65 -0
  82. package/dist/esm/types/src/message-parser.d.ts +89 -0
  83. package/dist/esm/types/src/message-sender.d.ts +34 -0
  84. package/dist/esm/types/src/monitor.d.ts +30 -0
  85. package/dist/esm/types/src/reqid-store.d.ts +23 -0
  86. package/dist/esm/types/src/server.d.ts +23 -0
  87. package/dist/esm/types/src/service-manager.d.ts +52 -0
  88. package/dist/esm/types/src/state-manager.d.ts +76 -0
  89. package/dist/esm/types/src/template-card-parser.d.ts +18 -0
  90. package/dist/esm/types/src/timeout.d.ts +20 -0
  91. package/dist/esm/types/src/version.d.ts +2 -0
  92. package/dist/index.d.ts +2 -0
  93. package/package.json +51 -0
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 适配器基础抽象类
5
+ *
6
+ * 提供所有适配器共用的能力:
7
+ * - 超时控制
8
+ * - 请求头构建
9
+ * - SSE 流式解析工具方法
10
+ * - 日志工具
11
+ */
12
+ /**
13
+ * 适配器基础抽象类
14
+ */
15
+ class BaseAdapter {
16
+ // ==========================================================================
17
+ // 请求头构建
18
+ // ==========================================================================
19
+ /**
20
+ * 构建通用请求头
21
+ */
22
+ buildHeaders(endpoint) {
23
+ const headers = {
24
+ "Content-Type": "application/json",
25
+ ...(endpoint.headers || {}),
26
+ };
27
+ if (endpoint.apiKey) {
28
+ headers["Authorization"] = `Bearer ${endpoint.apiKey}`;
29
+ }
30
+ return headers;
31
+ }
32
+ // ==========================================================================
33
+ // 超时控制
34
+ // ==========================================================================
35
+ /**
36
+ * 创建带超时的 AbortController
37
+ *
38
+ * @param timeoutMs 超时时间(毫秒)
39
+ * @param externalSignal 外部中止信号(可选)
40
+ * @returns { controller, timeoutId }
41
+ */
42
+ createTimeoutController(timeoutMs, externalSignal) {
43
+ const controller = new AbortController();
44
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
45
+ // 合并外部 abortSignal
46
+ if (externalSignal) {
47
+ externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
48
+ }
49
+ return { controller, timeoutId };
50
+ }
51
+ /**
52
+ * 包装 Promise 添加超时控制
53
+ */
54
+ async withTimeout(promise, timeoutMs, timeoutMessage = "Request timed out") {
55
+ let timeoutId;
56
+ const timeoutPromise = new Promise((_, reject) => {
57
+ timeoutId = setTimeout(() => {
58
+ reject(new Error(`${timeoutMessage} after ${timeoutMs}ms`));
59
+ }, timeoutMs);
60
+ });
61
+ try {
62
+ return await Promise.race([promise, timeoutPromise]);
63
+ }
64
+ finally {
65
+ clearTimeout(timeoutId);
66
+ }
67
+ }
68
+ // ==========================================================================
69
+ // SSE 解析工具
70
+ // ==========================================================================
71
+ /**
72
+ * 解析单行 SSE 数据
73
+ *
74
+ * @param line 原始 SSE 行
75
+ * @returns { event, data } 或 null(非 data 行或空行)
76
+ */
77
+ parseSSELine(line) {
78
+ const trimmed = line.trim();
79
+ // 跳过空行和注释
80
+ if (!trimmed || trimmed.startsWith(":")) {
81
+ return null;
82
+ }
83
+ // 解析 event: 行
84
+ if (trimmed.startsWith("event:")) {
85
+ return { event: trimmed.slice(6).trim() };
86
+ }
87
+ // 解析 data: 行
88
+ if (trimmed.startsWith("data:")) {
89
+ return { data: trimmed.slice(5).trim() };
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * 创建 SSE 流读取器
95
+ *
96
+ * 返回一个异步生成器,逐行产出 SSE 数据
97
+ */
98
+ async *readSSEStream(reader, runtime) {
99
+ const decoder = new TextDecoder("utf-8");
100
+ let buffer = "";
101
+ let currentEvent;
102
+ try {
103
+ while (true) {
104
+ const { done, value } = await reader.read();
105
+ if (done)
106
+ break;
107
+ buffer += decoder.decode(value, { stream: true });
108
+ // 逐行处理
109
+ const lines = buffer.split("\n");
110
+ buffer = lines.pop() || "";
111
+ for (const line of lines) {
112
+ const parsed = this.parseSSELine(line);
113
+ if (!parsed)
114
+ continue;
115
+ if (parsed.event !== undefined) {
116
+ currentEvent = parsed.event;
117
+ }
118
+ else if (parsed.data !== undefined) {
119
+ yield { event: currentEvent, data: parsed.data };
120
+ currentEvent = undefined; // 重置 event
121
+ }
122
+ }
123
+ }
124
+ // 处理残留 buffer
125
+ if (buffer.trim()) {
126
+ const parsed = this.parseSSELine(buffer);
127
+ if (parsed?.data !== undefined) {
128
+ yield { event: currentEvent, data: parsed.data };
129
+ }
130
+ }
131
+ }
132
+ catch (err) {
133
+ runtime?.error?.(`[wecom][adapter] SSE stream error: ${String(err)}`);
134
+ throw err;
135
+ }
136
+ }
137
+ // ==========================================================================
138
+ // HTTP 请求工具
139
+ // ==========================================================================
140
+ /**
141
+ * 发起 HTTP 请求并处理错误
142
+ */
143
+ async fetchWithErrorHandling(url, options, runtime) {
144
+ const response = await fetch(url, options);
145
+ if (!response.ok) {
146
+ const errorBody = await response.text().catch(() => "(unreadable)");
147
+ const error = new Error(`API error: HTTP ${response.status} ${response.statusText} — ${errorBody.slice(0, 500)}`);
148
+ runtime?.error?.(`[wecom][adapter] ${error.message}`);
149
+ throw error;
150
+ }
151
+ return response;
152
+ }
153
+ // ==========================================================================
154
+ // 日志工具
155
+ // ==========================================================================
156
+ /**
157
+ * 日志前缀
158
+ */
159
+ get logPrefix() {
160
+ return `[wecom][adapter:${this.name}]`;
161
+ }
162
+ /**
163
+ * 打印日志
164
+ */
165
+ log(runtime, message) {
166
+ runtime?.log?.(`${this.logPrefix} ${message}`);
167
+ }
168
+ /**
169
+ * 打印错误日志
170
+ */
171
+ logError(runtime, message) {
172
+ runtime?.error?.(`${this.logPrefix} ${message}`);
173
+ }
174
+ }
175
+
176
+ exports.BaseAdapter = BaseAdapter;
@@ -0,0 +1,328 @@
1
+ 'use strict';
2
+
3
+ var openaiAdapter = require('./openai-adapter.js');
4
+
5
+ /**
6
+ * DeepSeek 适配器
7
+ *
8
+ * 适用于:DeepSeek Chat (deepseek-chat) 和 DeepSeek Reasoner (deepseek-reasoner) 模型
9
+ *
10
+ * 与 OpenAI 适配器的差异:
11
+ * - 响应中额外包含 reasoning_content 字段(思维链)
12
+ * - 支持 reasoningDisplay 配置:show/hide/collapse(默认)
13
+ * - Reasoner 模型自动添加 thinking 参数
14
+ *
15
+ * 推理内容折叠展示格式(collapse 模式):
16
+ * <think>
17
+ * {reasoning_content}
18
+ * </think>
19
+ *
20
+ * {content}
21
+ */
22
+ /**
23
+ * DeepSeek 适配器
24
+ */
25
+ class DeepSeekAdapter extends openaiAdapter.OpenAIAdapter {
26
+ constructor() {
27
+ super(...arguments);
28
+ this.name = "deepseek";
29
+ this.displayName = "DeepSeek";
30
+ // 累积的推理内容(用于流式处理)
31
+ this.accumulatedReasoning = "";
32
+ }
33
+ /**
34
+ * 获取推理内容展示模式
35
+ */
36
+ getReasoningDisplayMode(endpoint) {
37
+ const options = endpoint.providerOptions;
38
+ return options?.reasoningDisplay || "collapse";
39
+ }
40
+ /**
41
+ * 是否为 Reasoner 模型
42
+ */
43
+ isReasonerModel(endpoint) {
44
+ return endpoint.model?.includes("reasoner") || false;
45
+ }
46
+ /**
47
+ * 构建请求体(覆写以添加 thinking 参数)
48
+ */
49
+ buildRequestBody(messages, endpoint) {
50
+ const requestBody = super.buildRequestBody(messages, endpoint);
51
+ // Reasoner 模型自动启用 thinking
52
+ if (this.isReasonerModel(endpoint)) {
53
+ requestBody.thinking = { type: "enabled" };
54
+ }
55
+ return requestBody;
56
+ }
57
+ /**
58
+ * 解析流式 delta 内容(覆写以处理 reasoning_content)
59
+ */
60
+ parseStreamDelta(parsed) {
61
+ const delta = parsed.choices?.[0]?.delta;
62
+ return {
63
+ content: delta?.content,
64
+ reasoningContent: delta?.reasoning_content,
65
+ };
66
+ }
67
+ /**
68
+ * 累积内容(覆写以处理推理内容)
69
+ *
70
+ * 注意:此方法需要知道 endpoint 配置,但父类签名不包含
71
+ * 因此使用实例变量 accumulatedReasoning 来跨调用保持状态
72
+ */
73
+ accumulateContent(accumulated, content, reasoningContent) {
74
+ // 累积推理内容
75
+ if (reasoningContent) {
76
+ this.accumulatedReasoning += reasoningContent;
77
+ }
78
+ // 累积主内容
79
+ if (content) {
80
+ // 注意:这里无法直接获取 displayMode
81
+ // 实际的格式化在 formatFinalOutput 中处理
82
+ return accumulated + content;
83
+ }
84
+ return accumulated;
85
+ }
86
+ /**
87
+ * 格式化最终输出(根据 displayMode 配置)
88
+ */
89
+ formatOutput(content, reasoningContent, displayMode) {
90
+ if (!reasoningContent) {
91
+ return content;
92
+ }
93
+ switch (displayMode) {
94
+ case "show":
95
+ // 直接展示推理内容和回答
96
+ return `${reasoningContent}\n\n${content}`;
97
+ case "hide":
98
+ // 仅展示回答,隐藏推理过程
99
+ return content;
100
+ case "collapse":
101
+ default:
102
+ // 折叠展示
103
+ return `<think>\n${reasoningContent}\n</think>\n\n${content}`;
104
+ }
105
+ }
106
+ /**
107
+ * 处理流式响应(覆写以支持推理内容格式化)
108
+ */
109
+ async handleStreamResponse(params) {
110
+ // 重置累积状态
111
+ this.accumulatedReasoning = "";
112
+ const result = await super.handleStreamResponse(params);
113
+ // 如果有推理内容,需要重新格式化输出
114
+ if (this.accumulatedReasoning && params.endpoint) {
115
+ const displayMode = this.getReasoningDisplayMode(params.endpoint);
116
+ return this.formatOutput(result, this.accumulatedReasoning, displayMode);
117
+ }
118
+ return result;
119
+ }
120
+ /**
121
+ * 解析非流式响应内容(覆写以处理 reasoning_content)
122
+ */
123
+ parseNonStreamResponse(data) {
124
+ const message = data.choices?.[0]?.message;
125
+ const content = message?.content || "";
126
+ const reasoningContent = message?.reasoning_content || "";
127
+ // 非流式时无法获取 endpoint,使用默认 collapse 模式
128
+ // 实际调用时应通过 forward 方法传递正确的 displayMode
129
+ if (reasoningContent) {
130
+ return this.formatOutput(content, reasoningContent, "collapse");
131
+ }
132
+ return content;
133
+ }
134
+ /**
135
+ * 转发请求(覆写以传递 endpoint 到流式处理)
136
+ */
137
+ async forward(request, endpoint, callbacks) {
138
+ const { text, mediaPaths, quoteContent, abortSignal, runtime, historyMessages } = request;
139
+ const { deliver, onReplyStart, onError } = callbacks;
140
+ this.log(runtime, `Forwarding to DeepSeek: url=${endpoint.url}, model=${endpoint.model}`);
141
+ // 重置状态
142
+ this.accumulatedReasoning = "";
143
+ // 构建消息
144
+ const messages = this.buildMessages({
145
+ text,
146
+ mediaPaths,
147
+ quoteContent,
148
+ systemPrompt: endpoint.systemPrompt,
149
+ historyMessages,
150
+ });
151
+ // 构建请求体
152
+ const requestBody = this.buildRequestBody(messages, endpoint);
153
+ // 构建请求头
154
+ const headers = this.buildHeaders(endpoint);
155
+ // 超时控制
156
+ const timeoutMs = endpoint.timeoutMs || 300000;
157
+ const { controller, timeoutId } = this.createTimeoutController(timeoutMs, abortSignal);
158
+ try {
159
+ const response = await this.fetchWithErrorHandling(endpoint.url, {
160
+ method: "POST",
161
+ headers,
162
+ body: JSON.stringify(requestBody),
163
+ signal: controller.signal,
164
+ }, runtime);
165
+ let finalText;
166
+ const displayMode = this.getReasoningDisplayMode(endpoint);
167
+ if (requestBody.stream) {
168
+ // 流式响应
169
+ finalText = await this.handleDeepSeekStreamResponse({
170
+ response,
171
+ deliver,
172
+ onReplyStart,
173
+ onError,
174
+ runtime,
175
+ displayMode,
176
+ });
177
+ }
178
+ else {
179
+ // 非流式响应
180
+ finalText = await this.handleDeepSeekNonStreamResponse({
181
+ response,
182
+ deliver,
183
+ onReplyStart,
184
+ onError,
185
+ runtime,
186
+ displayMode,
187
+ });
188
+ }
189
+ // 最终 deliver
190
+ if (finalText) {
191
+ try {
192
+ await deliver({ text: finalText }, { kind: "final" });
193
+ }
194
+ catch (e) {
195
+ onError?.(e instanceof Error ? e : new Error(String(e)), { kind: "final" });
196
+ }
197
+ }
198
+ this.log(runtime, `Response complete: textLength=${finalText.length}`);
199
+ return finalText;
200
+ }
201
+ catch (err) {
202
+ if (err?.name === "AbortError") {
203
+ const error = new Error(`Request timed out after ${timeoutMs}ms`);
204
+ this.logError(runtime, error.message);
205
+ onError?.(error, { kind: "timeout" });
206
+ throw error;
207
+ }
208
+ throw err;
209
+ }
210
+ finally {
211
+ clearTimeout(timeoutId);
212
+ }
213
+ }
214
+ /**
215
+ * 处理 DeepSeek 流式响应
216
+ *
217
+ * 注意:节流逻辑已移至 monitor 层统一控制,适配器层每个有效 delta 都直接 deliver
218
+ */
219
+ async handleDeepSeekStreamResponse(params) {
220
+ const { response, deliver, onReplyStart, onError, runtime, displayMode } = params;
221
+ const body = response.body;
222
+ if (!body) {
223
+ throw new Error("Response has no body for streaming");
224
+ }
225
+ const reader = body.getReader();
226
+ let accumulatedContent = "";
227
+ let accumulatedReasoning = "";
228
+ let replyStarted = false;
229
+ try {
230
+ for await (const { data } of this.readSSEStream(reader, runtime)) {
231
+ if (data === "[DONE]") {
232
+ this.log(runtime, "SSE stream [DONE]");
233
+ continue;
234
+ }
235
+ let parsed;
236
+ try {
237
+ parsed = JSON.parse(data);
238
+ }
239
+ catch {
240
+ this.log(runtime, `Failed to parse SSE JSON: ${data.slice(0, 200)}`);
241
+ continue;
242
+ }
243
+ const { content, reasoningContent } = this.parseStreamDelta(parsed);
244
+ // 累积推理内容
245
+ if (reasoningContent) {
246
+ accumulatedReasoning += reasoningContent;
247
+ }
248
+ // 累积主内容
249
+ if (content) {
250
+ accumulatedContent += content;
251
+ }
252
+ // 判断是否有新增内容
253
+ // - hide 模式下:仅当有主内容 (content) 时才考虑 deliver
254
+ // - show/collapse 模式下:有推理内容或主内容都考虑
255
+ const hasNewContent = displayMode === "hide" ? !!content : !!(content || reasoningContent);
256
+ if (hasNewContent) {
257
+ // 首次收到有效内容时触发 onReplyStart
258
+ if (!replyStarted) {
259
+ replyStarted = true;
260
+ try {
261
+ await onReplyStart?.();
262
+ }
263
+ catch (e) {
264
+ this.logError(runtime, `onReplyStart error: ${String(e)}`);
265
+ }
266
+ }
267
+ // 直接 deliver,节流由 monitor 层统一控制
268
+ const formattedText = this.formatOutput(accumulatedContent, accumulatedReasoning, displayMode);
269
+ try {
270
+ await deliver({ text: formattedText }, { kind: "block" });
271
+ }
272
+ catch (e) {
273
+ onError?.(e instanceof Error ? e : new Error(String(e)), { kind: "block" });
274
+ }
275
+ }
276
+ const finishReason = parsed.choices?.[0]?.finish_reason;
277
+ if (finishReason && finishReason !== "null") {
278
+ this.log(runtime, `SSE finish_reason: ${finishReason}`);
279
+ }
280
+ }
281
+ }
282
+ catch (err) {
283
+ this.logError(runtime, `SSE stream error: ${String(err)}`);
284
+ onError?.(err instanceof Error ? err : new Error(String(err)), { kind: "stream" });
285
+ }
286
+ return this.formatOutput(accumulatedContent, accumulatedReasoning, displayMode);
287
+ }
288
+ /**
289
+ * 处理 DeepSeek 非流式响应
290
+ */
291
+ async handleDeepSeekNonStreamResponse(params) {
292
+ const { response, deliver, onReplyStart, onError, runtime, displayMode } = params;
293
+ let data;
294
+ try {
295
+ data = (await response.json());
296
+ }
297
+ catch (err) {
298
+ const error = new Error(`Failed to parse response JSON: ${String(err)}`);
299
+ onError?.(error, { kind: "parse" });
300
+ throw error;
301
+ }
302
+ const message = data.choices?.[0]?.message;
303
+ const content = message?.content || "";
304
+ const reasoningContent = message?.reasoning_content || "";
305
+ const formattedText = this.formatOutput(content, reasoningContent, displayMode);
306
+ if (formattedText) {
307
+ try {
308
+ await onReplyStart?.();
309
+ }
310
+ catch (e) {
311
+ this.logError(runtime, `onReplyStart error: ${String(e)}`);
312
+ }
313
+ try {
314
+ await deliver({ text: formattedText }, { kind: "block" });
315
+ }
316
+ catch (e) {
317
+ onError?.(e instanceof Error ? e : new Error(String(e)), { kind: "block" });
318
+ }
319
+ }
320
+ // 记录 token 使用情况(含推理 token)
321
+ if (data.usage?.completion_tokens_details?.reasoning_tokens) {
322
+ this.log(runtime, `Token usage: reasoning_tokens=${data.usage.completion_tokens_details.reasoning_tokens}, total=${data.usage.total_tokens}`);
323
+ }
324
+ return formattedText;
325
+ }
326
+ }
327
+
328
+ exports.DeepSeekAdapter = DeepSeekAdapter;