# Metona 内部 API 请求与响应标准

Metona Internal Representation (IR) —— 项目内所有 AI 交互的统一数据格式。无论底层对接 DeepSeek、Ollama、Agnes 还是其他 LLM，Agent Loop、UI、记忆系统均读写此标准格式。Provider Adapter 负责外部 API 与此 IR 之间的双向转换。

版本: v1.0.0 适用范围: Electron 主进程 / IPC / 渲染进程 更新日期: 2026-06-26
📋 文档层级：本文档是 类型系统与数据格式的权威定义，与《构建指南》第四章(ReAct)、第五章(Harness)对应。冲突时以本文档为准。

## 🎯 核心思想

Metona 面向多 LLM Provider，每个外部 API 的请求/响应格式各不相同（OpenAI 格式、Anthropic 格式、Ollama 原生格式等）。 如果项目各处代码直接依赖外部格式，切换 Provider 或新增模型将导致大规模改动。

Metona IR 在项目内部建立一道抽象边界：

    ┌──────────────────────────┐
    │   Agent Loop / IPC / UI   │  ← 只读写 Metona IR
    └────────────┬─────────────┘
                 │
    ┌────────────▼─────────────┐
    │     Metona IR Standard    │  ← 项目内唯一标准
    └────────────┬─────────────┘
                 │
        ┌────────┼────────┬────────┐
        │        │        │        │
    ┌───▼──┐ ┌──▼───┐ ┌─▼───┐ ┌─▼─────┐
    │DeepSeek│ │Agnes│ │Ollama│ │Anthropic│  ← Adapter 层
    └───────┘ └─────┘ └──────┘ └────────┘

铁律：`electron/harness/` 下的所有代码、`src/` 下的所有 UI 代码、IPC 通道传输的数据，只能使用 Metona IR 定义的类型。 任何外部 API 的原始类型不得穿透到这些层。

## 🏗️ 架构概览

一次完整的用户请求经过以下数据流转：

    1. UI (Renderer)
       │  构造 MetonaRequest，通过 IPC 发送到主进程
       │
    2. IPC Bridge
       │  传输 MetonaRequest JSON
       │
    3. Context Builder
       │  注入 System Prompt、会话历史、检索记忆、可用工具列表
       │  输出 MetonaContext
       │
    4. Provider Adapter
       │  将 MetonaContext 转换为目标 Provider 的原生请求格式
       │  调用外部 API
       │  将原生响应转换为 MetonaResponse / MetonaStreamEvent
       │
    5. Agent Loop Engine
       │  按 ReAct 状态机解析 MetonaResponse
       │  提取 Thought → 执行 ToolCall → 收集 Observation
       │  构建下一轮的 MetonaRequest
       │
    6. IPC → UI
       │  将 MetonaStreamEvent / MetonaResponse 推送回渲染进程

## 📤 请求标准：MetonaRequest

Agent Loop 向 Provider Adapter 发出的统一请求。每次 ReAct 迭代构造一个新的 MetonaRequest。

### 完整类型定义

    // ====== electron/harness/types/metona-request.ts ======

    export interface MetonaRequest {
      /** 请求元信息 */
      meta: MetonaRequestMeta;

      /** System Prompt（行为宪法） */
      systemPrompt: MetonaSystemPrompt;

      /** 消息列表（含历史 + 当前用户输入 + 工具结果） */
      messages: MetonaMessage[];

      /** 本轮可用的工具定义列表 */
      tools?: MetonaToolDef[];

      /** 生成参数 */
      params: MetonaGenerationParams;

      /** 安全约束 */
      constraints?: MetonaConstraints;
    }

    export interface MetonaRequestMeta {
      sessionId: string;            // 会话 ID
      iteration: number;            // 当前 ReAct 迭代轮次（从 1 开始）
      requestId: string;            // 本次请求的唯一 ID
      timestamp: number;            // Unix 毫秒时间戳
      agentVersion: string;         // Agent 引擎版本
    }

    export interface MetonaSystemPrompt {
      /** 角色定义（静态区，利用 LLM 缓存） */
      roleDefinition: string;

      /** 输出格式约束 */
      outputConstraints: string;

      /** 安全准则 */
      safetyGuidelines: string;

      /** 动态注入的尾部提醒 */
      dynamicReminders?: string;
    }

    export interface MetonaGenerationParams {
      maxTokens?: number;           // 最大生成 token 数
      temperature?: number;        // 温度（默认 0.0，Agent 需要确定性）
      topP?: number;               // 核采样
      stream?: boolean;            // 是否流式输出
      stopSequences?: string[];   // 停止序列
      thinkingEnabled?: boolean;  // 是否启用思考模式
      thinkingEffort?: 'low' | 'medium' | 'high' | 'max';  // 思考强度（替代 thinkingBudget，各 Provider 映射见 Adapter 规范）
    }

    export interface MetonaConstraints {
      allowedTools?: string[];     // 本迭代允许使用的工具白名单
      maxToolCalls?: number;      // 单轮最大工具调用数
      timeoutMs?: number;         // 本请求整体超时
    }

### 字段说明

字段 | 类型 | 必填 | 说明
--- | --- | --- | ---
meta | MetonaRequestMeta | 必填 | 请求元信息，含 sessionId、iteration、requestId、timestamp
systemPrompt | MetonaSystemPrompt | 必填 | 分区化的 System Prompt，Adaper 负责拼接为 Provider 格式
messages | MetonaMessage[] | 必填 | 统一消息列表，见下方消息格式
tools | MetonaToolDef[] | 可选 | 本轮可用工具列表
params | MetonaGenerationParams | 必填 | 生成参数
constraints | MetonaConstraints | 可选 | 安全约束和限流参数

## 💬 消息格式：MetonaMessage

Metona 统一消息结构。所有角色（system / user / assistant / tool）共用同一结构，通过 `role` 区分。

    export interface MetonaMessage {
      role: 'system' | 'user' | 'assistant' | 'tool';

      /** 文本内容（纯文本或 Markdown） */
      content: string;

      /** （仅 assistant）思考/推理内容 */
      reasoningContent?: string;

      /** （仅 assistant）工具调用请求 */
      toolCalls?: MetonaToolCall[];

      /** （仅 tool）工具执行结果 */
      toolResult?: MetonaToolResult;

      /** 时间戳 */
      timestamp: number;

      /** 所属迭代轮次 */
      iteration?: number;

      /** 图片内容（可选，用于多模态） */
      images?: MetonaImageContent[];
    }

    export interface MetonaImageContent {
      url: string;              // 图片公网 URL 或 base64 data URI
      detail?: 'low' | 'high' | 'auto';
    }

💡 设计要点：部分 LLM 的 `reasoning_content` 需要伴随 `toolCalls` 回传上下文。 MetonaMessage 统一携带 `reasoningContent`，由 Adapter 决定是否需要回传。
🖼 多模态消息转换：`MetonaMessage.content`（string）+ `images`（数组）的分离设计比 OpenAI 的 content 数组更清晰。Adapter 负责转换：
• DeepSeek/Agnes (OpenAI 兼容)：`content` 数组 = `[{type:"text", text: content}, ...images.map(i => ({type:"image_url", image_url:{url: i.url}}))]`
• Ollama：`content` 保持 string，`images` 作为消息的独立字段传入 base64 数组（Adapter 需将 URL 下载为 base64）
• 纯文本消息（无 images）：所有 Provider 的 `content` 直接传 string

## 🔧 工具定义：MetonaToolDef

统一的工具描述格式。内置工具和 MCP 动态工具都使用此结构。

    export interface MetonaToolDef {
      name: string;                // 工具唯一名称
      description: string;         // 功能描述（供 LLM 阅读）
      parameters: MetonaToolParams; // 参数 JSON Schema
      category: MetonaToolCategory; // 分类
      riskLevel: MetonaRiskLevel;  // 风险等级
      requiresPermission: boolean; // 是否需要用户授权
      timeoutMs: number;          // 超时时间
    }

    export interface MetonaToolParams {
      type: 'object';
      properties: Record<string, MetonaParamField>;
      required?: string[];
    }

    export interface MetonaParamField {
      type: 'string' | 'number' | 'boolean' | 'object' | 'array';
      description: string;
      enum?: string[];
      items?: MetonaParamField;
    }

    export enum MetonaToolCategory {
      FILESYSTEM = 'filesystem',
      SEARCH = 'search',
      CALCULATION = 'calculation',
      CODE_EXECUTION = 'code_execution',
      NETWORK = 'network',
      DATABASE = 'database',
      MCP = 'mcp',
      CUSTOM = 'custom',
    }

    export enum MetonaRiskLevel {
      SAFE = 'safe',
      LOW = 'low',
      MEDIUM = 'medium',
      HIGH = 'high',
      CRITICAL = 'critical',
    }

### MetonaToolDef 与内部 ToolDefinition 的关系

项目内部存在两种工具类型：`MetonaToolDef`（IR 标准类型）和 `ToolDefinition`（内部实现类型，使用 Zod Schema 进行运行时参数校验）。两者的关系如下：

维度 | MetonaToolDef（IR 标准） | ToolDefinition（内部实现）
--- | --- | ---
用途 | 跨进程传输、LLM 可读描述、IPC 通信 | 运行时参数校验、工具注册、安全检查
参数格式 | JSON Schema（`MetonaToolParams`） | Zod Schema（支持类型推断和运行时校验）
定义位置 | 本文档（IR 标准） | `electron/harness/tools/base-tool.ts`

🔗 转换规则：`BaseTool.getDescriptionForLLM()` 负责将 Zod Schema → JSON Schema → `MetonaToolDef`。
• `ToolDefinition.name` → `MetonaToolDef.name`
• `ToolDefinition.parameters`（Zod）→ `MetonaToolDef.parameters`（JSON Schema），使用 `zod-to-json-schema` 库转换
• `ToolDefinition.category` → `MetonaToolDef.category`（枚举值一致）
• `ToolDefinition.riskLevel` → `MetonaToolDef.riskLevel`（枚举值一致）
• IPC 通道和 Agent Loop 只使用 MetonaToolDef，不接触 ToolDefinition/Zod
• 工具注册表（`ToolRegistry`）内部使用 `IBaseTool`，对外暴露时转换为 `MetonaToolDef`
⚠️ 强制规则：架构文档中 9 个基础工具的参数定义必须以 `MetonaToolDef` 格式为准（JSON Schema），不再使用自然语言描述。内部实现时使用 Zod Schema 做运行时校验，通过 `zod-to-json-schema` 转换后对外暴露。

## 📥 响应标准：MetonaResponse

Provider Adapter 将外部 API 的原始响应转换为 MetonaResponse 后返回给 Agent Loop。

    export interface MetonaResponse {
      /** 响应元信息 */
      meta: MetonaResponseMeta;

      /** 模型输出（完整文本） */
      content: string;

      /** 思考/推理内容（Thinking 模式） */
      reasoningContent?: string;

      /** 结构化输出（如果模型原生支持 JSON Schema） */
      structuredOutput?: unknown;

      /** 工具调用请求列表 */
      toolCalls?: MetonaToolCall[];

      /** Token 使用统计 */
      usage: MetonaTokenUsage;

      /** 停止原因 */
      finishReason: MetonaFinishReason;

      /** 错误信息（如果出错） */
      error?: MetonaError;
    }

    export interface MetonaResponseMeta {
      requestId: string;          // 对应的请求 ID
      provider: string;           // Provider 标识（如 'deepseek'）
      model: string;              // 实际使用的模型名称
      latencyMs: number;          // 端到端延迟
      timestamp: number;          // 响应时间戳

      /** Provider 原生性能统计（可选，主要用于 Ollama） */
      perfStats?: {
        loadDurationMs?: number;      // 模型加载耗时
        promptEvalDurationMs?: number; // Prompt 评估耗时
        evalDurationMs?: number;       // 生成耗时
        tokensPerSecond?: number;     // 生成速率
      };
    }

    export interface MetonaTokenUsage {
      inputTokens: number;
      outputTokens: number;
      totalTokens: number;
      reasoningTokens?: number;  // Thinking 模式专用
      cacheHitTokens?: number;
      cacheMissTokens?: number;
    }

    export enum MetonaFinishReason {
      STOP = 'stop',                 // 自然结束
      LENGTH = 'length',             // 达到长度上限
      TOOL_CALLS = 'tool_calls',     // 因工具调用而停止
      CONTENT_FILTER = 'content_filter', // 内容过滤
      ERROR = 'error',               // 错误终止
    }

## ⚡ 流式响应：MetonaStreamEvent

流式输出使用统一的事件类型，Agent Loop 和 UI 均可订阅。

    /** 流式事件类型枚举 */
    export enum MetonaStreamEventType {
      TEXT_DELTA = 'text_delta',           // 文本增量
      REASONING_DELTA = 'reasoning_delta', // 推理内容增量
      TOOL_CALL_DELTA = 'tool_call_delta', // 工具调用增量
      TOOL_CALL_COMPLETE = 'tool_call_complete',
      THINKING_START = 'thinking_start',     // 思考开始
      THINKING_END = 'thinking_end',         // 思考结束
      ERROR = 'error',                     // 流中错误
      DONE = 'done',                       // 流结束
      USAGE = 'usage',                     // Token 统计（通常在 DONE 前）
    }

    export interface MetonaStreamEvent {
      type: MetonaStreamEventType;
      requestId: string;
      sessionId: string;
      iteration: number;
      seq: number;                    // 序列号
      timestamp: number;

      /** 根据 type 使用不同字段 */
      delta?: string;                // TEXT_DELTA / REASONING_DELTA
      toolCallDelta?: {
        index: number;            // 工具调用索引（同一轮可能有多个）
        name?: string;            // 工具名称片段（首个事件携带）
        argsDelta?: string;       // 参数 JSON 增量片段
      };                             // TOOL_CALL_DELTA
      toolCall?: MetonaToolCall;    // TOOL_CALL_COMPLETE（拼接完成后的完整调用）
      usage?: MetonaTokenUsage;    // USAGE
      error?: MetonaError;         // ERROR
    }

### 流式传输协议

IPC 通道使用 SSE-like 格式（Server-Sent Events），每条事件为一行 JSON：

    // 实际传输格式（IPC 通道内，每行一条事件）
    {"type":"thinking_start","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":0,"timestamp":1719000000000}
    {"type":"reasoning_delta","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":1,"timestamp":1719000000100,"delta":"让我先分析问题的关键点..."}
    {"type":"thinking_end","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":2,"timestamp":1719000000500}
    {"type":"text_delta","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":3,"timestamp":1719000000600,"delta":"根据分析，"}
    {"type":"text_delta","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":4,"timestamp":1719000000650,"delta":"答案是..."}
    {"type":"tool_call_complete","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":5,"timestamp":1719000000700,"toolCall":{...}}
    {"type":"usage","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":6,"timestamp":1719000000800,"usage":{"inputTokens":120,"outputTokens":45,"totalTokens":165}}
    {"type":"done","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":7,"timestamp":1719000000800}

### 流式工具调用拼接策略

不同 Provider 的流式工具调用返回方式不同，Adapter 负责统一处理：

Provider | 流式工具调用格式 | Adapter 处理方式
--- | --- | ---
DeepSeek / Agnes | 分片返回：`delta.tool_calls[i]` 先返回 `index` + `function.name` 片段，再返回 `function.arguments` 片段 | Adapter 缓冲拼接：每个 `delta.tool_calls` 片段转换为 `TOOL_CALL_DELTA` 事件；拼接完成后发 `TOOL_CALL_COMPLETE`
Ollama | 整块返回：`message.tool_calls` 在最后一个 chunk 中一次性返回 | Adapter 直接转换为 `TOOL_CALL_COMPLETE` 事件（无 `TOOL_CALL_DELTA`）

💡 拼接规则：Adapter 维护一个 `Map<number, {name: string, argsBuffer: string}>` 缓冲区。
• 收到 `TOOL_CALL_DELTA` 时：如果 `name` 不为空，初始化缓冲区；将 `argsDelta` 追加到 `argsBuffer`
• 收到流结束（`done`）或 `finish_reason: "tool_calls"` 时：遍历缓冲区，对每个拼接完整的工具调用发 `TOOL_CALL_COMPLETE` 事件（`JSON.parse(argsBuffer)` 作为 `args`）
• UI 端可以选择忽略 `TOOL_CALL_DELTA` 事件，只监听 `TOOL_CALL_COMPLETE`（简化实现）

## 🧠 思考内容：MetonaThinking

各 Provider 的思考/推理内容格式不同（DeepSeek 用 `reasoning_content`、Anthropic 用 `thinking` block、Ollama 在 `think` 标签内），统一为 MetonaThinking。

    export interface MetonaThinking {
      /** 思考内容文本 */
      content: string;

      /** 思考状态 */
      status: 'thinking' | 'complete';

      /** 思考耗时 (ms) */
      durationMs: number;

      /** 思考消耗的 token 数 */
      tokensUsed: number;
    }

💡 流式思考：流式输出时，通过 `thinking_start` 事件开始，一系列 `reasoning_delta` 传输增量，最后 `thinking_end` 结束。 最终在 MetonaResponse 中合并为完整的 `reasoningContent` 字符串。

## 🔌 工具调用：MetonaToolCall

    export interface MetonaToolCall {
      id: string;                      // 工具调用唯一 ID
      name: string;                    // 工具名称
      args: Record<string, unknown>; // 调用参数
      iteration: number;              // 所属 ReAct 迭代
      timestamp: number;
    }

    export interface MetonaToolResult {
      toolCallId: string;
      toolName: string;
      result: unknown;               // 工具原始返回值
      summary?: string;              // 人工可读摘要（用于 LLM 上下文注入）
      success: boolean;
      error?: string;
      durationMs: number;
      timestamp: number;
    }

工具结果注入规则：工具执行完毕后，构造 `role='tool'` 的 MetonaMessage 追加到 messages 数组中。 `summary` 字段供 LLM 阅读（精简后），`result` 保留原始值供审计和调试。

🔧 Tool Calling 模式（首选）：当使用 Tool Calling 时，Provider Adapter 在 `MetonaRequest.tools` 中传入工具列表，LLM 原生返回 `tool_calls` 结构化数据。Adapter 直接映射为 `MetonaResponse.toolCalls`，无需文本解析。
• DeepSeek：请求参数 `tools` + `tool_choice`，响应 `choices[0].message.tool_calls`
• Agnes：同 DeepSeek（OpenAI 兼容）
• Ollama：请求参数 `tools`，响应 `message.tool_calls`
三个 Provider 均原生支持 Tool Calling，正则解析仅作为 Provider 不支持时的降级方案。
🧠 解析策略优先级：
1. Tool Calling（首选）：利用 Provider 原生的 `tools` + `tool_choice` 参数，LLM 直接返回结构化 `tool_calls`。
2. Structured Output（备选）：当不需要工具调用但需要结构化输出时，使用 `response_format: json_object`。
3. 正则降级（仅兜底）：仅在 Provider 不支持 Tool Calling 时使用（当前三个 Provider 全部支持，实际不触发）。

## ❌ 错误格式：MetonaError

    export interface MetonaError {
      code: MetonaErrorCode;          // 错误码
      message: string;               // 人类可读的错误描述
      provider?: string;              // 出错的 Provider
      providerCode?: string;         // Provider 原始错误码
      retryable: boolean;            // 是否可重试
      retryAfterMs?: number;        // 建议重试等待时间
    }

    export enum MetonaErrorCode {
      // 网络层
      NETWORK_TIMEOUT = 'network_timeout',
      NETWORK_ERROR = 'network_error',

      // 认证层
      AUTH_INVALID = 'auth_invalid',
      AUTH_EXPIRED = 'auth_expired',

      // 频率限制
      RATE_LIMITED = 'rate_limited',
      QUOTA_EXCEEDED = 'quota_exceeded',

      // 模型层
      MODEL_OVERLOADED = 'model_overloaded',
      MODEL_NOT_FOUND = 'model_not_found',
      CONTEXT_LENGTH_EXCEEDED = 'context_length_exceeded',
      OUTPUT_LENGTH_EXCEEDED = 'output_length_exceeded',

      // 内容层
      CONTENT_FILTERED = 'content_filtered',

      // 解析层
      PARSE_ERROR = 'parse_error',
      INVALID_RESPONSE = 'invalid_response',

      // Agent 层
      MAX_ITERATIONS = 'max_iterations',
      USER_ABORTED = 'user_aborted',
      TIMEOUT = 'timeout',
      UNKNOWN = 'unknown',
    }

## 📋 上下文标准：MetonaContext

Context Builder 的输出，包含了完整的上下文信息，供 Agent Loop 和 Adapter 使用。

    export interface MetonaContext {
      /** 上下文唯一标识 */
      id: string;

      /** 关联的会话 */
      sessionId: string;

      /** System Prompt 分区 */
      systemPrompt: MetonaSystemPrompt;

      /** 会话历史（最近 N 轮） */
      history: MetonaMessage[];

      /** 检索到的相关记忆 */
      relevantMemories: MetonaMemoryItem[];

      /** 当前任务信息 */
      currentTask: {
        userInput: string;
        iteration: number;
        taskGoal?: string;
      };

      /** 可用工具列表 */
      availableTools: MetonaToolDef[];

      /** 预估 Token 数 */
      estimatedTokens: number;

      /** 上下文使用率（estimatedTokens / contextWindow） */
      usageRatio: number;

      /** 是否需要压缩 */
      needsCompression: boolean;
    }

💡 上下文压缩策略（Context Compression）：
当 `usageRatio > 0.8` 时 `needsCompression = true`，Agent Loop 进入 `COMPRESSING` 状态。
压缩流程（由 ContextBuilder 负责）：
1. 保留最近 N 轮对话原文（N 由配置决定，默认 5）
2. 将更早的对话轮次用 LLM 摘要为一组 "对话摘要" 消息插入上下文
3. 保留所有 `tool_calls` 和 `tool_results` 的精简版（只保留工具名 + 结果状态，省略完整输出）
4. 保留 System Prompt 和 MEMORY.md 注入内容不变
5. 压缩后重新计算 `estimatedTokens`，确保 `usageRatio < 0.5`
注意区分：上下文压缩（压缩 LLM 对话窗口）≠ 记忆压缩（清理 SQLite 记忆库），两者由不同组件负责。

## 🧩 记忆格式：MetonaMemoryItem

    export interface MetonaMemoryItem {
      id: string;
      type: 'episodic' | 'semantic' | 'working';

      /** 可被 LLM 阅读的记忆内容 */
      content: string;

      /** 精简摘要（上下文紧张时使用） */
      summary?: string;

      /** 来源 */
      source: 'user_input' | 'tool_result' | 'agent_thought' | 'imported';

      /** 重要程度 0-1 */
      importance: number;

      /** 检索相关性分数（仅在检索结果中出现） */
      relevanceScore?: number;

      sessionId?: string;
      createdAt: number;
      expiresAt?: number;
    }

## 🔗 Provider Adapter 规范

每个 LLM Provider 必须实现一个 Adapter，负责 Metona IR 和外部 API 格式之间的双向转换。

    export interface IMetonaProviderAdapter {
      /** Provider 标识 */
      readonly providerId: string;

      /** 支持的模型列表 */
      readonly supportedModels: string[];

      /** 上下文窗口大小 */
      getContextWindow(model: string): number;

      /** 健康检查 */
      healthCheck(): Promise<boolean>;

      /**
       * 核心方法：发送请求
       * @param request - Metona 标准请求
       * @returns Metona 标准响应
       */
      send(request: MetonaRequest): Promise<MetonaResponse>;

      /**
       * 核心方法：发送流式请求
       * @param request - Metona 标准请求
       * @param onEvent - 流式事件回调
       * @returns 完整响应（流结束后返回）
       */
      sendStream(
        request: MetonaRequest,
        onEvent: (event: MetonaStreamEvent) => void
      ): Promise<MetonaResponse>;

      /** 获取可用模型列表 */
      listModels(): Promise<MetonaModelInfo[]>;
    }

    export interface MetonaModelInfo {
      id: string;
      providerId: string;
      displayName: string;
      contextWindow: number;
      maxOutput: number;
      supportsStreaming: boolean;
      supportsThinking: boolean;
      supportsImages: boolean;
      supportsToolCalling: boolean;
      supportsStructuredOutput: boolean;
      pricing?: MetonaPricing;
    }

    export interface MetonaPricing {
      inputPerMillion: number;
      outputPerMillion: number;
      currency: string;
    }

### Adapter 实现清单

适配器 | providerId | 目标 API | 传输格式
--- | --- | --- | ---
DeepSeekAdapter | deepseek | https://api.deepseek.com/chat/completions | OpenAI 兼容 JSON
AgnesAdapter | agnes-ai | https://apihub.agnes-ai.com/v1/chat/completions | OpenAI 兼容 JSON
OllamaAdapter | ollama | http://localhost:11434/api/chat | Ollama 原生 JSON / NDJSON
AnthropicAdapter | anthropic | https://api.anthropic.com/v1/messages | Anthropic 原生 JSON
OpenAIAdapter | openai | https://api.openai.com/v1/chat/completions | OpenAI 原生 JSON

### Thinking 模式 Provider 映射表

各 Provider 的思考模式控制方式不同，Adapter 负责将 `MetonaGenerationParams.thinkingEffort` 映射为目标 Provider 的原生参数：

Provider | 开启方式 | thinkingEffort 映射 | 回传规则
--- | --- | --- | ---
DeepSeek | `thinking: {type: "enabled"}` + `reasoning_effort` | `low/medium → "high"`, `high → "high"`, `max → "max"` | 工具调用轮次的 `reasoning_content` 必须回传上下文
Agnes (OpenAI 兼容) | `chat_template_kwargs: {enable_thinking: true}` | `low → false`, `medium/high/max → true` | 不强制回传
Agnes (Anthropic 兼容) | `thinking: {type: "enabled", budget_tokens: N}` | `low → 1024`, `medium → 2048`, `high → 4096`, `max → 8192` | 不强制回传
Ollama | `think: true/false` 或 `"high"/"medium"/"low"` | `low → "low"`, `medium → "medium"`, `high → "high"`, `max → true` | `message.thinking` 字段，不强制回传

💡 思考内容字段映射：
• DeepSeek/Agnes(OpenAI): `reasoning_content` → `MetonaResponse.reasoningContent`
• Agnes(Anthropic): `thinking` block → `MetonaResponse.reasoningContent`
• Ollama: `message.thinking` → `MetonaResponse.reasoningContent`
流式模式下，思考内容增量统一映射为 `reasoning_delta` 事件。

### MVP 优先级

当前优先实现以下 3 个 Adapter，Anthropic 和 OpenAI 为未来计划：

优先级 | Adapter | 状态
--- | --- | ---
P0 | DeepSeekAdapter | MVP 必须实现
P0 | AgnesAdapter | MVP 必须实现
P1 | OllamaAdapter | MVP 必须实现（本地推理）
P2 | AnthropicAdapter | 未来计划
P2 | OpenAIAdapter | 未来计划

### Provider 故障转移策略

当主 Provider 不可用时，Agent Loop 按以下策略处理：

步骤 | 条件 | 动作
--- | --- | ---
1. 重试 | `MetonaError.retryable === true` | 按 `retryAfterMs` 等待后重试（最多 3 次）
2. 故障转移 | 重试仍失败 + 已配置 fallback Provider | 切换到备选 Provider 重新发送请求
3. 通知用户 | 故障转移触发时 | 通过 IPC 推送 `agent:providerSwitched` 事件，UI 显示 Toast 提示
4. 报错 | 无 fallback 或 fallback 也失败 | 返回 `MetonaError`，UI 显示错误 + 重试按钮

💡 配置项：在 `app_config` 表中增加以下配置：
• `llm.fallbackProvider`（string，备选 Provider ID）
• `llm.fallbackModel`（string，备选模型名）
• `llm.fallbackApiKey`（string，备选 API Key，加密存储）
用户可在设置界面配置备选 Provider。未配置时跳过故障转移步骤。

## 📖 完整示例

### 示例 1：普通文本对话（非流式）

用户发送问题，Agent Loop 构造请求，获得非流式回答。

    // ===== Agent Loop 构造 =====
    const request: MetonaRequest = {
      meta: { sessionId: 's_abc', iteration: 1, requestId: 'r_001', timestamp: Date.now(), agentVersion: '1.0.0' },
      systemPrompt: {
        roleDefinition: '你是一个专业的编程助手。',
        outputConstraints: '用中文回答，代码块使用 ``` 包裹。',
        safetyGuidelines: '不编造事实，不确定时如实说明。',
      },
      messages: [
        { role: 'user', content: '解释 JavaScript 的事件循环机制。', timestamp: Date.now() },
      ],
      params: { maxTokens: 4096, temperature: 0.0, stream: false, thinkingEnabled: false },
    };

    // ===== Adapter 返回 =====
    const response: MetonaResponse = {
      meta: { requestId: 'r_001', provider: 'deepseek', model: 'deepseek-v4-pro', latencyMs: 850, timestamp: Date.now() },
      content: 'JavaScript 的事件循环（Event Loop）是...\\n\\n```js\\nconsole.log(1)...\\n```',
      usage: { inputTokens: 120, outputTokens: 350, totalTokens: 470 },
      finishReason: MetonaFinishReason.STOP,
    };

### 示例 2：ReAct 工具调用（流式）

用户请求需要工具调用，Agent Loop 迭代两次。

    // ===== 第 1 轮：Agent 发送请求，LLM 返回工具调用（流式） =====
    const request1: MetonaRequest = {
      meta: { sessionId: 's_xyz', iteration: 1, requestId: 'r_002', timestamp: Date.now(), agentVersion: '1.0.0' },
      systemPrompt: { /* ... */ },
      messages: [
        { role: 'user', content: '读取 /home/user/data.csv 并分析内容。', timestamp: Date.now() },
      ],
      tools: [
        { name: 'read_file', description: '读取文件内容', /* ... */ },
        { name: 'analyze_csv', description: '分析 CSV 数据', /* ... */ },
      ],
      params: { stream: true, thinkingEnabled: true },
    };

    // 流式事件：
    // → thinking_start → reasoning_delta ... → thinking_end
    // → tool_call_complete { id: 'tc_1', name: 'read_file', args: { file_path: '/home/user/data.csv' } }
    // → done

    // ===== Agent Loop 执行工具后，构造第 2 轮请求 =====
    const request2: MetonaRequest = {
      meta: { /* ... */, iteration: 2, requestId: 'r_003' },
      // 消息包含历史 + 工具结果
      messages: [
        { role: 'user', content: '读取 /home/user/data.csv 并分析内容。', timestamp: t },
        { role: 'assistant', content: '', toolCalls: [{ id: 'tc_1', name: 'read_file', args: { file_path: '/home/user/data.csv' }, iteration: 1 }], timestamp: t },
        {
          role: 'tool',
          content: '',
          toolResult: { toolCallId: 'tc_1', toolName: 'read_file', result: '...', success: true, durationMs: 5, timestamp: t,
            summary: '文件 data.csv 包含 1000 行数据，字段为 name,age,email,city' },
          timestamp: t
        },
      ],
      tools: [/* 同上 */],
      params: { stream: true, thinkingEnabled: false },
    };

## 🔄 迁移指南

将现有代码迁移到 Metona IR 标准的步骤。

### 文件结构

    electron/harness/types/
    ├── metona-request.ts      // MetonaRequest, MetonaMessage, MetonaToolDef 等
    ├── metona-response.ts     // MetonaResponse, MetonaStreamEvent, MetonaError 等
    ├── metona-context.ts      // MetonaContext, MetonaSystemPrompt
    ├── metona-memory.ts       // MetonaMemoryItem
    ├── metona-tool.ts         // MetonaToolCall, MetonaToolResult
    ├── metona-adapter.ts      // IMetonaProviderAdapter 接口
    └── index.ts               // 统一导出

    electron/harness/adapters/
    ├── base-adapter.ts        // Adapter 基类（共享逻辑）
    ├── deepseek.adapter.ts
    ├── agnes-ai.adapter.ts
    ├── ollama.adapter.ts
    ├── anthropic.adapter.ts
    └── openai.adapter.ts

### 迁移检查清单

# | 检查项 | 涉及文件
--- | --- | ---
1 | Agent Loop Engine 只引用 MetonaRequest / MetonaResponse | agent-loop/engine.ts
2 | Context Builder 输出 MetonaContext | harness/prompts/
3 | Tool Registry 使用 MetonaToolDef / MetonaToolCall / MetonaToolResult | harness/tools/
4 | Memory Manager 使用 MetonaMemoryItem | harness/memory/
5 | IPC Preload 桥接层只传递 Metona IR 类型 | electron/preload.ts
6 | IPC Handlers 的输入输出为 Metona IR 类型 | electron/ipc/*.handlers.ts
7 | React 组件/Zustand Store 只读写 Metona IR 类型 | src/stores/, src/components/
8 | 每个 Provider Adapter 实现 IMetonaProviderAdapter | harness/adapters/
9 | 流式事件通过 MetonaStreamEvent 推送 | hooks/useAgentStream.ts
10 | 所有外部 API 原生类型不出现在 harness/ 和 src/ 中 | 全局

⚠️ 强制规则：违反第 10 条的代码不得合入主分支。Code Review 时以此标准为基准。

📜 Metona 内部 API 请求与响应标准 —— 项目端到端类型安全的基础

版本: v1.0.0 · 生效日期: 2026-06-25

所属项目: Metona (AI Agent Desktop) · 技术栈: TypeScript + React + SQLite + Electron