@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,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var openaiAdapter = require('./openai-adapter.js');
|
|
4
|
+
var deepseekAdapter = require('./deepseek-adapter.js');
|
|
5
|
+
var anthropicAdapter = require('./anthropic-adapter.js');
|
|
6
|
+
var difyAdapter = require('./dify-adapter.js');
|
|
7
|
+
var webhookAdapter = require('./webhook-adapter.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 适配器注册表
|
|
11
|
+
*
|
|
12
|
+
* 管理所有智能体适配器的注册与发现
|
|
13
|
+
*/
|
|
14
|
+
// 导入内置适配器
|
|
15
|
+
/**
|
|
16
|
+
* 适配器注册表实现
|
|
17
|
+
*/
|
|
18
|
+
class AdapterRegistry {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.adapters = new Map();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 注册适配器
|
|
24
|
+
*/
|
|
25
|
+
register(adapter) {
|
|
26
|
+
if (this.adapters.has(adapter.name)) {
|
|
27
|
+
console.warn(`[wecom][adapter] Adapter "${adapter.name}" is already registered, overwriting.`);
|
|
28
|
+
}
|
|
29
|
+
this.adapters.set(adapter.name, adapter);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 按 provider 名称获取适配器
|
|
33
|
+
*/
|
|
34
|
+
get(provider) {
|
|
35
|
+
return this.adapters.get(provider);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 列出所有已注册适配器
|
|
39
|
+
*/
|
|
40
|
+
list() {
|
|
41
|
+
return Array.from(this.adapters.values());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 解析并返回适配器
|
|
45
|
+
*
|
|
46
|
+
* 解析优先级:
|
|
47
|
+
* 1. 优先使用 endpoint.provider 指定的适配器
|
|
48
|
+
* 2. 若 provider 未配置,尝试从 url 自动推断
|
|
49
|
+
* 3. 兜底使用 openai 适配器
|
|
50
|
+
*/
|
|
51
|
+
resolve(endpoint) {
|
|
52
|
+
// 1. 优先使用显式配置的 provider
|
|
53
|
+
if (endpoint.provider) {
|
|
54
|
+
const adapter = this.adapters.get(endpoint.provider);
|
|
55
|
+
if (adapter) {
|
|
56
|
+
return adapter;
|
|
57
|
+
}
|
|
58
|
+
// provider 配置了但找不到对应适配器,抛出错误
|
|
59
|
+
const availableProviders = this.list().map((a) => a.name).join(", ");
|
|
60
|
+
throw new Error(`Unknown provider "${endpoint.provider}" for endpoint "${endpoint.name}". ` +
|
|
61
|
+
`Available providers: ${availableProviders}`);
|
|
62
|
+
}
|
|
63
|
+
// 2. 尝试从 URL 自动推断
|
|
64
|
+
const inferredProvider = this.inferProviderFromUrl(endpoint.url);
|
|
65
|
+
if (inferredProvider) {
|
|
66
|
+
const adapter = this.adapters.get(inferredProvider);
|
|
67
|
+
if (adapter) {
|
|
68
|
+
return adapter;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 3. 兜底使用 openai 适配器
|
|
72
|
+
const defaultAdapter = this.adapters.get("openai");
|
|
73
|
+
if (!defaultAdapter) {
|
|
74
|
+
throw new Error("Default OpenAI adapter not found. This should not happen.");
|
|
75
|
+
}
|
|
76
|
+
return defaultAdapter;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 从 URL 推断 provider 类型
|
|
80
|
+
*/
|
|
81
|
+
inferProviderFromUrl(url) {
|
|
82
|
+
const lowerUrl = url.toLowerCase();
|
|
83
|
+
// DeepSeek
|
|
84
|
+
if (lowerUrl.includes("deepseek")) {
|
|
85
|
+
return "deepseek";
|
|
86
|
+
}
|
|
87
|
+
// Anthropic(原生 API 或包含 /v1/messages 路径的兼容接口)
|
|
88
|
+
if (lowerUrl.includes("anthropic.com") || lowerUrl.includes("anthropic")) {
|
|
89
|
+
return "anthropic";
|
|
90
|
+
}
|
|
91
|
+
// Dify
|
|
92
|
+
if (lowerUrl.includes("dify") || lowerUrl.includes("chat-messages")) {
|
|
93
|
+
return "dify";
|
|
94
|
+
}
|
|
95
|
+
// 无法推断
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// 全局注册表实例
|
|
101
|
+
// ============================================================================
|
|
102
|
+
/**
|
|
103
|
+
* 全局适配器注册表
|
|
104
|
+
*/
|
|
105
|
+
const adapterRegistry = new AdapterRegistry();
|
|
106
|
+
/**
|
|
107
|
+
* 注册内置适配器
|
|
108
|
+
*/
|
|
109
|
+
function registerBuiltinAdapters() {
|
|
110
|
+
adapterRegistry.register(new openaiAdapter.OpenAIAdapter());
|
|
111
|
+
adapterRegistry.register(new deepseekAdapter.DeepSeekAdapter());
|
|
112
|
+
adapterRegistry.register(new anthropicAdapter.AnthropicAdapter());
|
|
113
|
+
adapterRegistry.register(new difyAdapter.DifyAdapter());
|
|
114
|
+
adapterRegistry.register(new webhookAdapter.WebhookAdapter());
|
|
115
|
+
}
|
|
116
|
+
// 自动注册内置适配器
|
|
117
|
+
registerBuiltinAdapters();
|
|
118
|
+
/**
|
|
119
|
+
* 根据端点配置解析适配器
|
|
120
|
+
*/
|
|
121
|
+
function resolveAdapter(endpoint) {
|
|
122
|
+
return adapterRegistry.resolve(endpoint);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
exports.OpenAIAdapter = openaiAdapter.OpenAIAdapter;
|
|
126
|
+
exports.DeepSeekAdapter = deepseekAdapter.DeepSeekAdapter;
|
|
127
|
+
exports.AnthropicAdapter = anthropicAdapter.AnthropicAdapter;
|
|
128
|
+
exports.DifyAdapter = difyAdapter.DifyAdapter;
|
|
129
|
+
exports.WebhookAdapter = webhookAdapter.WebhookAdapter;
|
|
130
|
+
exports.adapterRegistry = adapterRegistry;
|
|
131
|
+
exports.resolveAdapter = resolveAdapter;
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('node:fs');
|
|
4
|
+
var path = require('node:path');
|
|
5
|
+
var baseAdapter = require('./base-adapter.js');
|
|
6
|
+
|
|
7
|
+
function _interopNamespaceDefault(e) {
|
|
8
|
+
var n = Object.create(null);
|
|
9
|
+
if (e) {
|
|
10
|
+
Object.keys(e).forEach(function (k) {
|
|
11
|
+
if (k !== 'default') {
|
|
12
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () { return e[k]; }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
n.default = e;
|
|
21
|
+
return Object.freeze(n);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
25
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* OpenAI 适配器
|
|
29
|
+
*
|
|
30
|
+
* 适用于:OpenAI、Azure OpenAI、Ollama、vLLM、LocalAI 及任何 OpenAI Chat Completions API 兼容服务
|
|
31
|
+
*
|
|
32
|
+
* 特性:
|
|
33
|
+
* - 请求:{ messages, model, stream }
|
|
34
|
+
* - 流式响应:SSE data: {...} → 提取 choices[0].delta.content
|
|
35
|
+
* - 非流式响应:JSON → 提取 choices[0].message.content
|
|
36
|
+
* - 认证:Authorization: Bearer {apiKey}
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* OpenAI 适配器
|
|
40
|
+
*/
|
|
41
|
+
class OpenAIAdapter extends baseAdapter.BaseAdapter {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
this.name = "openai";
|
|
45
|
+
this.displayName = "OpenAI";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 转发请求到 OpenAI 兼容 API
|
|
49
|
+
*/
|
|
50
|
+
async forward(request, endpoint, callbacks) {
|
|
51
|
+
const { text, mediaPaths, quoteContent, abortSignal, runtime, historyMessages } = request;
|
|
52
|
+
const { deliver, onReplyStart, onError } = callbacks;
|
|
53
|
+
this.log(runtime, `Forwarding to: url=${endpoint.url}, stream=${endpoint.stream}`);
|
|
54
|
+
// 构建消息
|
|
55
|
+
const messages = this.buildMessages({
|
|
56
|
+
text,
|
|
57
|
+
mediaPaths,
|
|
58
|
+
quoteContent,
|
|
59
|
+
systemPrompt: endpoint.systemPrompt,
|
|
60
|
+
historyMessages,
|
|
61
|
+
});
|
|
62
|
+
// 构建请求体
|
|
63
|
+
const requestBody = this.buildRequestBody(messages, endpoint);
|
|
64
|
+
// 构建请求头
|
|
65
|
+
const headers = this.buildHeaders(endpoint);
|
|
66
|
+
// 超时控制
|
|
67
|
+
const timeoutMs = endpoint.timeoutMs || 300000;
|
|
68
|
+
const { controller, timeoutId } = this.createTimeoutController(timeoutMs, abortSignal);
|
|
69
|
+
try {
|
|
70
|
+
// 发起请求
|
|
71
|
+
const response = await this.fetchWithErrorHandling(endpoint.url, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers,
|
|
74
|
+
body: JSON.stringify(requestBody),
|
|
75
|
+
signal: controller.signal,
|
|
76
|
+
}, runtime);
|
|
77
|
+
let finalText;
|
|
78
|
+
if (requestBody.stream) {
|
|
79
|
+
// 流式响应
|
|
80
|
+
finalText = await this.handleStreamResponse({
|
|
81
|
+
response,
|
|
82
|
+
deliver,
|
|
83
|
+
onReplyStart,
|
|
84
|
+
onError,
|
|
85
|
+
runtime,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// 非流式响应
|
|
90
|
+
finalText = await this.handleNonStreamResponse({
|
|
91
|
+
response,
|
|
92
|
+
deliver,
|
|
93
|
+
onReplyStart,
|
|
94
|
+
onError,
|
|
95
|
+
runtime,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// 最终 deliver
|
|
99
|
+
if (finalText) {
|
|
100
|
+
try {
|
|
101
|
+
await deliver({ text: finalText }, { kind: "final" });
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
onError?.(e instanceof Error ? e : new Error(String(e)), { kind: "final" });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.log(runtime, `Response complete: textLength=${finalText.length}`);
|
|
108
|
+
return finalText;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (err?.name === "AbortError") {
|
|
112
|
+
const error = new Error(`Request timed out after ${timeoutMs}ms`);
|
|
113
|
+
this.logError(runtime, error.message);
|
|
114
|
+
onError?.(error, { kind: "timeout" });
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
clearTimeout(timeoutId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ==========================================================================
|
|
124
|
+
// 消息构建
|
|
125
|
+
// ==========================================================================
|
|
126
|
+
/**
|
|
127
|
+
* 将企微消息构建为 OpenAI messages 数组
|
|
128
|
+
*/
|
|
129
|
+
buildMessages(options) {
|
|
130
|
+
const messages = [];
|
|
131
|
+
// 系统提示词
|
|
132
|
+
if (options.systemPrompt) {
|
|
133
|
+
messages.push({
|
|
134
|
+
role: "system",
|
|
135
|
+
content: options.systemPrompt,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// 注入对话历史(在 system 之后、当前 user 消息之前)
|
|
139
|
+
if (options.historyMessages && options.historyMessages.length > 0) {
|
|
140
|
+
for (const msg of options.historyMessages) {
|
|
141
|
+
messages.push({
|
|
142
|
+
role: msg.role,
|
|
143
|
+
content: msg.content,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 用户消息
|
|
148
|
+
const hasMedia = options.mediaPaths && options.mediaPaths.length > 0;
|
|
149
|
+
if (hasMedia) {
|
|
150
|
+
// 多模态消息:文本 + 图片/文件
|
|
151
|
+
const contentParts = [];
|
|
152
|
+
// 添加引用内容
|
|
153
|
+
if (options.quoteContent) {
|
|
154
|
+
contentParts.push({
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `[引用消息] ${options.quoteContent}`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// 添加文本
|
|
160
|
+
if (options.text) {
|
|
161
|
+
contentParts.push({
|
|
162
|
+
type: "text",
|
|
163
|
+
text: options.text,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// 添加媒体文件
|
|
167
|
+
for (const media of options.mediaPaths) {
|
|
168
|
+
const isImage = media.contentType?.startsWith("image/");
|
|
169
|
+
if (isImage) {
|
|
170
|
+
try {
|
|
171
|
+
const buffer = fs__namespace.readFileSync(media.path);
|
|
172
|
+
const base64 = buffer.toString("base64");
|
|
173
|
+
const mimeType = media.contentType || "image/jpeg";
|
|
174
|
+
contentParts.push({
|
|
175
|
+
type: "image_url",
|
|
176
|
+
image_url: {
|
|
177
|
+
url: `data:${mimeType};base64,${base64}`,
|
|
178
|
+
detail: "auto",
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
contentParts.push({
|
|
184
|
+
type: "text",
|
|
185
|
+
text: `[图片文件: ${path__namespace.basename(media.path)}] (读取失败)`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// 非图片文件,以文本描述形式传入
|
|
191
|
+
const fileName = path__namespace.basename(media.path);
|
|
192
|
+
const fileType = media.contentType || "unknown";
|
|
193
|
+
contentParts.push({
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `[文件: ${fileName}, 类型: ${fileType}]`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 确保至少有一个 content part
|
|
200
|
+
if (contentParts.length === 0) {
|
|
201
|
+
contentParts.push({ type: "text", text: "(空消息)" });
|
|
202
|
+
}
|
|
203
|
+
messages.push({ role: "user", content: contentParts });
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// 纯文本消息
|
|
207
|
+
let content = "";
|
|
208
|
+
if (options.quoteContent) {
|
|
209
|
+
content += `[引用消息] ${options.quoteContent}\n\n`;
|
|
210
|
+
}
|
|
211
|
+
content += options.text || "(空消息)";
|
|
212
|
+
messages.push({ role: "user", content });
|
|
213
|
+
}
|
|
214
|
+
return messages;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 构建请求体
|
|
218
|
+
*/
|
|
219
|
+
buildRequestBody(messages, endpoint) {
|
|
220
|
+
const requestBody = {
|
|
221
|
+
messages,
|
|
222
|
+
stream: endpoint.stream !== false,
|
|
223
|
+
};
|
|
224
|
+
if (endpoint.model) {
|
|
225
|
+
requestBody.model = endpoint.model;
|
|
226
|
+
}
|
|
227
|
+
return requestBody;
|
|
228
|
+
}
|
|
229
|
+
// ==========================================================================
|
|
230
|
+
// 流式响应处理
|
|
231
|
+
// ==========================================================================
|
|
232
|
+
/**
|
|
233
|
+
* 处理流式响应
|
|
234
|
+
*
|
|
235
|
+
* 注意:节流逻辑已移至 monitor 层统一控制,适配器层每个有效 delta 都直接 deliver
|
|
236
|
+
*/
|
|
237
|
+
async handleStreamResponse(params) {
|
|
238
|
+
const { response, deliver, onReplyStart, onError, runtime } = params;
|
|
239
|
+
const body = response.body;
|
|
240
|
+
if (!body) {
|
|
241
|
+
throw new Error("Response has no body for streaming");
|
|
242
|
+
}
|
|
243
|
+
const reader = body.getReader();
|
|
244
|
+
let accumulatedText = "";
|
|
245
|
+
let replyStarted = false;
|
|
246
|
+
try {
|
|
247
|
+
for await (const { data } of this.readSSEStream(reader, runtime)) {
|
|
248
|
+
// [DONE] 标记
|
|
249
|
+
if (data === "[DONE]") {
|
|
250
|
+
this.log(runtime, "SSE stream [DONE]");
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
// 解析 JSON
|
|
254
|
+
let parsed;
|
|
255
|
+
try {
|
|
256
|
+
parsed = JSON.parse(data);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
this.log(runtime, `Failed to parse SSE JSON: ${data.slice(0, 200)}`);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// 解析 delta 内容(子类可覆写此方法)
|
|
263
|
+
const { content, reasoningContent } = this.parseStreamDelta(parsed);
|
|
264
|
+
if (content || reasoningContent) {
|
|
265
|
+
// 首次收到内容时触发 onReplyStart
|
|
266
|
+
if (!replyStarted) {
|
|
267
|
+
replyStarted = true;
|
|
268
|
+
try {
|
|
269
|
+
await onReplyStart?.();
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
this.logError(runtime, `onReplyStart error: ${String(e)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// 累积文本(子类可自定义累积逻辑)
|
|
276
|
+
accumulatedText = this.accumulateContent(accumulatedText, content, reasoningContent);
|
|
277
|
+
// 直接 deliver,节流由 monitor 层统一控制
|
|
278
|
+
try {
|
|
279
|
+
await deliver({ text: accumulatedText }, { kind: "block" });
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
onError?.(e instanceof Error ? e : new Error(String(e)), { kind: "block" });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// 检查 finish_reason
|
|
286
|
+
const finishReason = parsed.choices?.[0]?.finish_reason;
|
|
287
|
+
if (finishReason && finishReason !== "null") {
|
|
288
|
+
this.log(runtime, `SSE finish_reason: ${finishReason}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
this.logError(runtime, `SSE stream error: ${String(err)}`);
|
|
294
|
+
onError?.(err instanceof Error ? err : new Error(String(err)), { kind: "stream" });
|
|
295
|
+
}
|
|
296
|
+
return accumulatedText;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 解析流式 delta 内容(子类可覆写)
|
|
300
|
+
*/
|
|
301
|
+
parseStreamDelta(parsed) {
|
|
302
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
303
|
+
return {
|
|
304
|
+
content: delta?.content,
|
|
305
|
+
reasoningContent: delta?.reasoning_content,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 累积内容(子类可覆写以自定义格式)
|
|
310
|
+
*/
|
|
311
|
+
accumulateContent(accumulated, content, _reasoningContent) {
|
|
312
|
+
// OpenAI 适配器只累积 content,忽略 reasoning_content
|
|
313
|
+
if (content) {
|
|
314
|
+
return accumulated + content;
|
|
315
|
+
}
|
|
316
|
+
return accumulated;
|
|
317
|
+
}
|
|
318
|
+
// ==========================================================================
|
|
319
|
+
// 非流式响应处理
|
|
320
|
+
// ==========================================================================
|
|
321
|
+
/**
|
|
322
|
+
* 处理非流式响应
|
|
323
|
+
*/
|
|
324
|
+
async handleNonStreamResponse(params) {
|
|
325
|
+
const { response, deliver, onReplyStart, onError, runtime } = params;
|
|
326
|
+
let data;
|
|
327
|
+
try {
|
|
328
|
+
data = (await response.json());
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
const error = new Error(`Failed to parse response JSON: ${String(err)}`);
|
|
332
|
+
onError?.(error, { kind: "parse" });
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
// 解析响应内容(子类可覆写)
|
|
336
|
+
const content = this.parseNonStreamResponse(data);
|
|
337
|
+
if (content) {
|
|
338
|
+
try {
|
|
339
|
+
await onReplyStart?.();
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
this.logError(runtime, `onReplyStart error: ${String(e)}`);
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
await deliver({ text: content }, { kind: "block" });
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
onError?.(e instanceof Error ? e : new Error(String(e)), { kind: "block" });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return content;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* 解析非流式响应内容(子类可覆写)
|
|
355
|
+
*/
|
|
356
|
+
parseNonStreamResponse(data) {
|
|
357
|
+
return data.choices?.[0]?.message?.content || "";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
exports.OpenAIAdapter = OpenAIAdapter;
|