/** * OpenAI 兼容 API 格式构建工具 * * 将 MetonaRequest 转换为 OpenAI /chat/completions 兼容的原生请求格式。 * DeepSeek、Agnes AI 和 MiMo 共享此工具,各自 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 * @see apis/mimo-api-docs-20260715.html */ import type { MetonaRequest, MetonaToolDef } from '../../types'; /** * 构建 OpenAI 兼容的 messages 数组 * * 处理: * - System Prompt 拼接(静态区 + 动态区 + 安全准则) * - 工具调用历史保留(reasoning_content + tool_calls) * - 工具结果注入(tool_call_id + content) * * 注意:图片(多模态)处理不属于此共享函数。 * 各 Provider 对多模态的支持不同(DeepSeek 不支持,Agnes/Ollama 支持但格式各异), * 应在各自 Adapter 的 toNativeRequest 中处理。 */ export function buildOpenAICompatibleMessages( request: MetonaRequest, ): Array> { 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) => { // v0.3.0 修复: assistant 消息有 tool_calls 但 content 为空时,content 设为 null // DeepSeek/OpenAI API 要求有 tool_calls 的 assistant 消息 content 必须为 null 而非空字符串 const msg: Record = { role: m.role, content: m.content, }; // === Assistant 消息 === if (m.role === 'assistant') { // 工具调用历史 if (m.toolCalls?.length) { msg.tool_calls = m.toolCalls.map((tc) => ({ id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.args) }, })); // 有 tool_calls 时 content 必须为 null(API 规范) if (!m.content) msg.content = null; } // 推理内容(无论是否有工具调用,都保留 reasoning_content) if (m.reasoningContent) { msg.reasoning_content = m.reasoningContent; } } // === 工具执行结果 === if (m.role === 'tool' && m.toolResult) { msg.tool_call_id = m.toolResult.toolCallId; // CE-2 修复: 工具失败时 result 为 null,优先用 error 字段作为 content // 否则 LLM 看到 "null" 不知道失败原因,可能重复调用导致死循环 msg.content = m.toolResult.error ? m.toolResult.error : (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> | undefined { if (!tools?.length) return undefined; return tools.map((t) => ({ type: 'function', function: { name: t.name, description: t.description, parameters: t.parameters, }, })); }