refactor: 解耦 Provider Adapter 继承关系

问题:AgnesAdapter extends DeepSeekAdapter 在架构上不合理。
DeepSeek、Agnes AI、Ollama 是三个完全不同的 API,不应有继承关系。

重构:
  BaseAdapter
  ├── DeepSeekAdapter  (独立)
  ├── AgnesAdapter     (独立)
  └── OllamaAdapter    (独立)

变更:
- 新增 shared/openai-format.ts — 提取 OpenAI 兼容消息/工具格式构建
- 新增 shared/sse-stream.ts   — 提取 SSE 流式解析逻辑
- DeepSeekAdapter  重写为独立继承 BaseAdapter,使用共享工具
- AgnesAdapter     重写为独立继承 BaseAdapter,使用共享工具
- OllamaAdapter    无需变更(原本就独立继承)
- adapters/index.ts 清理导出,移除隐式耦合
This commit is contained in:
thzxx
2026-06-27 21:41:43 +08:00
parent a879b4f38a
commit bd6856c7e4
5 changed files with 521 additions and 307 deletions
@@ -0,0 +1,100 @@
/**
* OpenAI 兼容 API 格式构建工具
*
* 将 MetonaRequest 转换为 OpenAI /chat/completions 兼容的原生请求格式。
* DeepSeek 和 Agnes AI 共享此工具,各自 Adapter 只需处理 Provider 特有的差异参数。
*
* @see electron/harness/types/metona-request.ts — MetonaRequest 定义
* @see apis/deepseek-api-docs-20260518.html
* @see apis/agnes-ai-api-docs-20260625.html
*/
import type { MetonaRequest, MetonaToolDef } from '../../types';
/**
* 构建 OpenAI 兼容的 messages 数组
*
* 处理:
* - System Prompt 拼接(静态区 + 动态区 + 安全准则)
* - 图片 → 多模态 content 数组 [{type:"text"}, {type:"image_url"}]
* - 工具调用历史保留(reasoning_content + tool_calls
* - 工具结果注入(tool_call_id + content
*/
export function buildOpenAICompatibleMessages(
request: MetonaRequest,
): Array<Record<string, unknown>> {
const systemContent = [
request.systemPrompt.roleDefinition,
request.systemPrompt.outputConstraints,
request.systemPrompt.safetyGuidelines,
request.systemPrompt.dynamicReminders,
]
.filter(Boolean)
.join('\n\n');
const nonSystemMessages = request.messages
.filter((m) => m.role !== 'system')
.map((m) => {
const msg: Record<string, unknown> = { role: m.role };
// === 多模态图片处理 ===
if (m.images && m.images.length > 0) {
const contentParts: Array<Record<string, unknown>> = [];
if (m.content) {
contentParts.push({ type: 'text', text: m.content });
}
for (const img of m.images) {
contentParts.push({
type: 'image_url',
image_url: { url: img.url, detail: img.detail ?? 'auto' },
});
}
msg.content = contentParts;
} else {
msg.content = m.content;
}
// === Assistant 工具调用历史 ===
if (m.role === 'assistant' && m.toolCalls?.length) {
msg.tool_calls = m.toolCalls.map((tc) => ({
id: tc.id,
type: 'function',
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
}));
// 推理内容必须带回上下文(否则 LLM 丢失思考链)
if (m.reasoningContent) {
msg.reasoning_content = m.reasoningContent;
}
}
// === 工具执行结果 ===
if (m.role === 'tool' && m.toolResult) {
msg.tool_call_id = m.toolResult.toolCallId;
msg.content =
typeof m.toolResult.result === 'string'
? m.toolResult.result
: JSON.stringify(m.toolResult.result);
}
return msg;
});
return [{ role: 'system', content: systemContent }, ...nonSystemMessages];
}
/**
* 构建 OpenAI 兼容的 tools 数组
*/
export function buildOpenAICompatibleTools(
tools?: MetonaToolDef[],
): Array<Record<string, unknown>> | undefined {
if (!tools?.length) return undefined;
return tools.map((t) => ({
type: 'function',
function: {
name: t.name,
description: t.description,
parameters: t.parameters,
},
}));
}