@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.
- package/README.md +489 -0
- package/config.example.json +169 -0
- package/dist/cjs/index.js +76 -0
- package/dist/cjs/src/adapters/anthropic-adapter.js +534 -0
- package/dist/cjs/src/adapters/base-adapter.js +176 -0
- package/dist/cjs/src/adapters/deepseek-adapter.js +328 -0
- package/dist/cjs/src/adapters/dify-adapter.js +636 -0
- package/dist/cjs/src/adapters/index.js +131 -0
- package/dist/cjs/src/adapters/openai-adapter.js +361 -0
- package/dist/cjs/src/adapters/webhook-adapter.js +260 -0
- package/dist/cjs/src/agent-forwarder.js +87 -0
- package/dist/cjs/src/ca-cert.js +162 -0
- package/dist/cjs/src/config.js +169 -0
- package/dist/cjs/src/const.js +124 -0
- package/dist/cjs/src/conversation-manager.js +147 -0
- package/dist/cjs/src/dm-policy.js +46 -0
- package/dist/cjs/src/group-policy.js +95 -0
- package/dist/cjs/src/media-handler.js +136 -0
- package/dist/cjs/src/media-loader.js +271 -0
- package/dist/cjs/src/media-storage.js +165 -0
- package/dist/cjs/src/media-uploader.js +203 -0
- package/dist/cjs/src/message-parser.js +133 -0
- package/dist/cjs/src/message-sender.js +87 -0
- package/dist/cjs/src/monitor.js +849 -0
- package/dist/cjs/src/reqid-store.js +87 -0
- package/dist/cjs/src/server.js +72 -0
- package/dist/cjs/src/service-manager.js +135 -0
- package/dist/cjs/src/state-manager.js +143 -0
- package/dist/cjs/src/template-card-parser.js +498 -0
- package/dist/cjs/src/timeout.js +41 -0
- package/dist/cjs/src/version.js +25 -0
- package/dist/esm/index.js +74 -0
- package/dist/esm/src/adapters/anthropic-adapter.js +512 -0
- package/dist/esm/src/adapters/base-adapter.js +174 -0
- package/dist/esm/src/adapters/deepseek-adapter.js +326 -0
- package/dist/esm/src/adapters/dify-adapter.js +634 -0
- package/dist/esm/src/adapters/index.js +123 -0
- package/dist/esm/src/adapters/openai-adapter.js +339 -0
- package/dist/esm/src/adapters/webhook-adapter.js +258 -0
- package/dist/esm/src/agent-forwarder.js +84 -0
- package/dist/esm/src/ca-cert.js +136 -0
- package/dist/esm/src/config.js +145 -0
- package/dist/esm/src/const.js +100 -0
- package/dist/esm/src/conversation-manager.js +144 -0
- package/dist/esm/src/dm-policy.js +44 -0
- package/dist/esm/src/group-policy.js +92 -0
- package/dist/esm/src/media-handler.js +133 -0
- package/dist/esm/src/media-loader.js +246 -0
- package/dist/esm/src/media-storage.js +143 -0
- package/dist/esm/src/media-uploader.js +198 -0
- package/dist/esm/src/message-parser.js +131 -0
- package/dist/esm/src/message-sender.js +83 -0
- package/dist/esm/src/monitor.js +841 -0
- package/dist/esm/src/reqid-store.js +85 -0
- package/dist/esm/src/server.js +69 -0
- package/dist/esm/src/service-manager.js +133 -0
- package/dist/esm/src/state-manager.js +134 -0
- package/dist/esm/src/template-card-parser.js +495 -0
- package/dist/esm/src/timeout.js +38 -0
- package/dist/esm/src/version.js +22 -0
- package/dist/esm/types/index.d.ts +14 -0
- package/dist/esm/types/src/adapters/anthropic-adapter.d.ts +93 -0
- package/dist/esm/types/src/adapters/base-adapter.d.ts +76 -0
- package/dist/esm/types/src/adapters/deepseek-adapter.d.ts +87 -0
- package/dist/esm/types/src/adapters/dify-adapter.d.ts +100 -0
- package/dist/esm/types/src/adapters/index.d.ts +60 -0
- package/dist/esm/types/src/adapters/openai-adapter.d.ts +82 -0
- package/dist/esm/types/src/adapters/types.d.ts +373 -0
- package/dist/esm/types/src/adapters/webhook-adapter.d.ts +54 -0
- package/dist/esm/types/src/agent-forwarder.d.ts +32 -0
- package/dist/esm/types/src/ca-cert.d.ts +53 -0
- package/dist/esm/types/src/config.d.ts +29 -0
- package/dist/esm/types/src/const.d.ts +74 -0
- package/dist/esm/types/src/conversation-manager.d.ts +81 -0
- package/dist/esm/types/src/dm-policy.d.ts +27 -0
- package/dist/esm/types/src/group-policy.d.ts +28 -0
- package/dist/esm/types/src/interface.d.ts +332 -0
- package/dist/esm/types/src/media-handler.d.ts +36 -0
- package/dist/esm/types/src/media-loader.d.ts +47 -0
- package/dist/esm/types/src/media-storage.d.ts +35 -0
- package/dist/esm/types/src/media-uploader.d.ts +65 -0
- package/dist/esm/types/src/message-parser.d.ts +89 -0
- package/dist/esm/types/src/message-sender.d.ts +34 -0
- package/dist/esm/types/src/monitor.d.ts +30 -0
- package/dist/esm/types/src/reqid-store.d.ts +23 -0
- package/dist/esm/types/src/server.d.ts +23 -0
- package/dist/esm/types/src/service-manager.d.ts +52 -0
- package/dist/esm/types/src/state-manager.d.ts +76 -0
- package/dist/esm/types/src/template-card-parser.d.ts +18 -0
- package/dist/esm/types/src/timeout.d.ts +20 -0
- package/dist/esm/types/src/version.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- 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;
|