核心引擎修复: - CE-1: 上下文压缩摘要 role 从 system 改为 user,避免被 adapter 过滤 - CE-2: 工具失败时优先使用 error 字段(engine/openai-format/ollama 三处) - P0-1: DeadLoopError 终止时正确传 error 参数,前端可见 ERROR 事件 - MT-1: 新增 waitForAbort 方法,abortSession 等待 run 结束再返回 - MT-2: TERMINATED 状态到达时标记步骤完成,避免 Trace Viewer 转圈 - MT-3: 压缩边界检测孤立 tool 消息,避免 API 400 错误 - isRetryableError 与 catch 分支统一 toLowerCase IPC 与主进程修复: - P0-2: createAdapter 配置缺失返回 null,FALLBACK_ADAPTER 兜底 - P0-3: reloadAdapter 失败返回 success:false 通知前端 - P1-5: 校验失败发 ERROR+DONE 流事件,防止 isStreaming 卡死 - P1-6: configLoaded 标志,配置加载前禁用发送按钮 - P1-7: MCP initialize 移到 agentLoop 后,完成后同步工具 - P2-11: beforeLoad 在 loadURL 前注册 IPC handler - P2-12: provider 切换竞态保护 前端状态同步修复: - clearSessions 后同步清空前端会话与消息状态 - clearMemories 通过 memoryVersion 触发 MemoryViewer 重新加载 - ContextMenu 4 个 session 操作补全 IPC 调用与 try/catch - useConfig 配置保存失败回滚 UI 并提示 - handleToggle 工具切换失败回滚单个工具状态 错误处理全量补全: - 所有 await window.metona 调用补全 try/catch 与 toast 反馈 - MCP addServer/toggleServer/removeServer 检查返回值 - showItemInFolder 检查返回值(handleOpen/handleOpenInFolder) - sse-stream/ollama NDJSON 解析失败改为 log.warn - adapter throwHttpError 带 status 属性供 isRetryableError 判断
101 lines
3.4 KiB
TypeScript
101 lines
3.4 KiB
TypeScript
/**
|
||
* 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<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) => {
|
||
// v0.3.0 修复: assistant 消息有 tool_calls 但 content 为空时,content 设为 null
|
||
// DeepSeek/OpenAI API 要求有 tool_calls 的 assistant 消息 content 必须为 null 而非空字符串
|
||
const msg: Record<string, unknown> = {
|
||
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<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,
|
||
},
|
||
}));
|
||
}
|