# 第二阶段接口契约与开发规划 阶段 F 实现更新(2026-09-04):新增持久化媒体任务、附件上传/清理、修订与笔记导出、本地模型管理、Token 用量及提供商请求 JSON。详细路径、字段语义和验证边界见 [多模态管线与模型运行开发说明](../development/多模态管线与模型运行开发说明.md),以下旧阶段规划与实现不一致时以该说明和 OpenAPI 为准。 > 文档状态:接口冻结草案 > > 更新日期:2026-09-03 > > 依据:`../architecture/第二阶段团队分工表.md`、`../architecture/AI笔记软件技术栈说明-团队版-v2.3.md`、`后端接口契约-开发版.md` 本文统一第二阶段新增能力的 HTTP、SSE、前端 Service、桌面 Host 和内部模块接口。文中标记为“计划新增”的路径尚未实现,不能据此判断当前服务已经支持;实现完成后以 FastAPI `/openapi.json`、TypeScript Wire DTO 和自动化测试共同作为最终依据。 人员分工和任务优先级不在本文重复维护,以第二阶段分工表为准。 --- ## 1. 规划原则 1. 第一阶段已经发布的路径和字段保持兼容,第二阶段优先增加可选字段、子资源和新事件。 2. 前端只消费项目 Contract,不解析 Plugin Manifest、MCP 原始消息或模型厂商响应。 3. Agent、Benchmark 和 Trace 共用同一运行事件,不建立测试专用的旁路执行协议。 4. 长任务统一建模为 Job,创建接口快速返回,进度通过查询或 SSE 获取。 5. 文件、附件、插件包和 Dataset 均使用受控 ID;HTTP 请求不接受任意本地绝对路径。 6. Secret 只经专用写入接口提交,普通读取接口只返回是否配置和引用 ID。 7. Mermaid、函数图像和导出器共享静态渲染 Contract,不从 Vue 组件 DOM 抓取结果。 8. 所有第三方协议在 Adapter 边界转换,错误进入前端前映射为项目错误码。 ## 2. 状态说明 | 标记 | 含义 | | --- | --- | | 已实现 | 接口已经落地并由当前 OpenAPI 与自动化测试覆盖 | | 扩展 | 路径已存在,第二阶段增加字段、事件或行为 | | 计划新增 | 第二阶段需要新增实现 | | 内部 Contract | 不直接暴露 HTTP,由两个模块共同遵守 | | Host Contract | 由 Tauri/Rust Host 提供,Web 开发模式可使用 Mock Adapter | ## 3. 第二阶段接口总览 | 域 | 方法 | 路径或 Contract | 状态 | 用途 | | --- | --- | --- | --- | --- | | Transcription | POST | `/api/media/transcriptions` | 扩展 | 创建真实音频转写任务 | | Transcription | GET | `/api/media/transcriptions` | 计划新增 | 分页获取转写任务 | | Transcription | GET | `/api/media/transcriptions/{job_id}` | 扩展 | 获取分段转写和任务状态 | | Transcription | GET | `/api/media/transcriptions/{job_id}/events` | 计划新增 | 订阅模型加载、分离和转写进度 | | Transcription | POST | `/api/media/transcriptions/{job_id}/cancel` | 计划新增 | 取消音频任务 | | Transcription | POST | `/api/media/transcriptions/{job_id}/notes` | 计划新增 | 将 Transcript 写入 Knowledge Core | | Agent Trace | GET | `/api/agent/runs/{run_id}/events` | 已实现 | 支持游标恢复并增加模型与权限事件 | | Agent Trace | GET | `/api/agent/runs/{run_id}/trace` | 已实现 | 分页读取可回放 Trace 快照 | | Plugin Host | GET | `/api/plugins/{plugin_id}/host` | 已实现 | 获取 MCP Host 健康状态 | | Plugin Host | POST | `/api/plugins/{plugin_id}/host/restart` | 已实现 | 重启异常 Host 并重新发现 Tool | | MCP Server | GET/POST | `/api/mcp/servers` | 已实现(C.1) | 列出、创建独立 MCP Server 配置 | | MCP Server | GET/PUT/DELETE | `/api/mcp/servers/{server_id}` | 已实现(C.1) | 读取、版本化修改、删除独立配置 | | MCP Server | GET | `/api/mcp/servers/{server_id}/tools` | 已实现(C.1) | 获取映射后的 Tool 摘要 | | MCP Server | POST | `/api/mcp/servers/{server_id}/trust` | 已实现(C.1) | 确认当前连接配置摘要 | | MCP Server | POST | `/api/mcp/servers/{server_id}/test` | 已实现(C.1) | 临时连接、握手、发现工具后关闭 | | MCP Server | POST | `/api/mcp/servers/{server_id}/enable`、`disable` | 已实现(C.1) | 控制连接与动态 Tool 生命周期 | | MCP Server | PUT/DELETE | `/api/mcp/servers/{server_id}/secrets/{key}` | 已实现(C.1) | 按 `kind` 写入或删除加密环境变量/Header | | Plugin Command | GET | `/api/plugin-contributions/commands` | 已实现 | 获取前端可展示的 Command | | Plugin Command | POST | `/api/plugin-contributions/commands/{command_id}/execute` | 已实现 | 受控执行 Command | | Plugin Settings | GET | `/api/plugins/{plugin_id}/settings` | 已实现 | 获取 Schema 与非敏感配置 | | Plugin Settings | PUT | `/api/plugins/{plugin_id}/settings` | 已实现 | 更新非敏感配置 | | Plugin Settings | PUT/DELETE | `/api/plugins/{plugin_id}/settings/{key}/secret` | 已实现 | 写入或删除 Secret Reference | | Provider | 现有路径 | `/api/providers/*`、`POST /api/chat` | 扩展 | 补齐协议能力和统一行为 | | Retrieval | GET/POST | `/api/index/status`、`/api/index/rebuild` | 扩展 | 暴露 Embedding 兼容状态并安全重建向量 | | Benchmark | GET | `/api/benchmarks/datasets` | 计划新增 | 枚举受控 Dataset | | Benchmark | POST | `/api/benchmarks/rag/runs` | 已实现 | 创建 RAG Benchmark | | Benchmark | POST | `/api/benchmarks/agent/runs` | 暂缓 | 创建 Agent Benchmark(依赖 Agent Runtime 完成后交付) | | Benchmark | GET | `/api/benchmarks/runs` | 已实现 | 分页获取 Benchmark Run | | Benchmark | GET/POST | `/api/benchmarks/runs/{run_id}/*` | 计划新增 | 查询、订阅、取消和读取报告 | | Export | POST | `/api/exports` | 已实现(HTML/PDF/DOCX) | 创建导出任务;`html`/`pdf`/`docx` 三格式均已支持 | | Export | GET | `/api/exports` | 已实现 | 分页获取导出任务 | | Export | GET | `/api/exports/{job_id}` | 已实现 | 查询导出任务 | | Export | GET | `/api/exports/{job_id}/file` | 已实现 | 下载已完成产物 | | Export | POST | `/api/exports/{job_id}/cancel` | 已实现 | 取消导出任务 | | Theme | Host Contract | `ThemePackageService` | 计划新增 | 导入、预览、启停和卸载主题包 | | Renderer | 内部 Contract | `StaticRenderer` | 已实现 | Function Plot 后端静态 SVG 渲染 + PDF 矢量内嵌(共享几何);Mermaid 返回占位;DOCX 保留源码占位 | --- ## 4. 公共协议 ### 4.1 JSON 与时间 - HTTP JSON 字段统一使用 `snake_case`。 - TypeScript Wire DTO 与 JSON 保持同名,View Model 可在 Service 层转换。 - 时间使用 UTC ISO 8601,例如 `2026-08-31T10:30:00Z`。 - 耗时统一使用 `duration_ms`,音视频时间使用秒数浮点值。 - ID 是不透明字符串,调用方不得解析前缀或依赖生成规则。 - 枚举新增值时前端必须提供 unknown fallback,不能使整个页面渲染失败。 ### 4.2 Job 状态 第二阶段异步任务统一使用: ```text queued running completed failed cancelled ``` 第一阶段 Transcription 曾使用 `processing`。迁移期间前端 Wire DTO 同时接受 `processing` 和 `running`,Service 统一映射为 `running`;所有消费者更新后,后端再停止返回旧值。 通用进度结构: ```json { "phase": "transcribing", "current": 42, "total": 100, "percent": 42.0, "message": "正在转写音频" } ``` `percent` 可以为 `null`,调用方不能以 100 代替 `completed`。终态 Job 必须有 `completed_at`;失败 Job 必须提供项目错误码,不直接暴露第三方堆栈。 ### 4.3 分页和游标 普通资源列表继续使用: ```json { "items": [], "page": { "total": 0, "limit": 50, "offset": 0 } } ``` 事件和 Trace 使用序列游标: ```json { "items": [], "next_sequence": 120, "has_more": false } ``` `after_sequence` 表示只返回大于该值的事件。事件序号只保证在一个资源内单调递增,客户端不得假设从 0 或 1 开始。 ### 4.4 SSE SSE 事件统一包含 `id`、`event` 和 JSON `data`: ```text id: 42 event: Progress data: {"sequence":42,"resource_id":"job_123","data":{"percent":50},"timestamp":"2026-08-31T10:30:00Z"} ``` - `id` 等于可恢复的事件 sequence。 - 客户端重连时发送 `Last-Event-ID`;开发环境也可以使用 `?after_sequence=42`。 - 服务端先回放缺失事件,再切换为实时事件。 - 重放与实时交界处允许重复,客户端按资源 ID 与 sequence 去重。 - `Completed`、`Failed`、`Cancelled` 是终止事件;正常断流但没有终止事件时,客户端按可重连处理。 - 心跳使用 SSE 注释行,不创建业务事件。 ### 4.5 统一错误 沿用第一阶段结构: ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Resource was not found.", "details": {} } } ``` `message` 用于用户提示,`details` 只放可安全展示和定位的结构化信息。前端业务分支只判断 `code`。 --- ## 5. Multimodal / Transcription ### 5.1 创建转写任务 `POST /api/media/transcriptions`,扩展现有接口,成功返回 `202 Accepted`。 请求: ```json { "attachment_id": "attachment_123", "language": null, "diarization": true, "word_timestamps": false, "profile_id": "default", "metadata": {} } ``` 约束: - `attachment_id` 必须属于当前 Vault 的 Host 管理附件。 - `language = null` 表示自动识别。 - `profile_id` 引用服务端模型配置,不允许请求直接传 CUDA 路径或任意模型目录。 - `metadata` 只用于关联业务上下文,不进入模型参数。 响应: ```json { "job_id": "transcription_123", "attachment_id": "attachment_123", "status": "queued", "progress": null, "language": null, "diarization_enabled": true, "segments": [], "text": null, "warnings": [], "error": null, "created_at": "2026-08-31T10:30:00Z", "started_at": null, "updated_at": "2026-08-31T10:30:00Z", "completed_at": null } ``` ### 5.2 Transcript Segment ```json { "segment_id": "segment_001", "speaker": "SPEAKER_00", "start_time": 12.4, "end_time": 18.9, "text": "这一部分介绍进程调度。", "confidence": 0.93, "words": [] } ``` `speaker`、`confidence` 和 `words` 可以为空。Segment 按 `start_time` 排序且不得重叠为负时长。没有 pyannote 能力时任务可以完成,但必须在 `warnings` 中返回 `DIARIZATION_UNAVAILABLE`。 ### 5.3 查询、事件和取消 | 方法 | 路径 | 响应 | | --- | --- | --- | | GET | `/api/media/transcriptions?status=&limit=&offset=` | `TranscriptionJobListResponse` | | GET | `/api/media/transcriptions/{job_id}` | 完整 `TranscriptionJob` | | GET | `/api/media/transcriptions/{job_id}/events` | `TranscriptionEvent` SSE | | POST | `/api/media/transcriptions/{job_id}/cancel` | `OperationResponse` | 事件类型: ```text Queued ModelLoading DiarizationStarted DiarizationCompleted TranscriptionStarted Progress Completed Failed Cancelled ``` 取消必须向正在运行的模型任务传播。接口返回 accepted 只表示取消请求已记录,最终状态以 Job 或终止事件为准。 ### 5.4 写入 Knowledge Core `POST /api/media/transcriptions/{job_id}/notes` ```json { "title": "操作系统课程录音", "folder": "课程/操作系统", "include_timestamps": true, "include_speakers": true, "template_id": null } ``` 仅 completed Job 可以转换。响应返回第一阶段 `Note` Contract。生成的 Block Citation 需要保留 `source_audio`、`start_time`、`end_time` 和 `speaker`,使搜索与 Agent 引用能够跳回音频。 ### 5.5 Multimodal 错误码 ```text ATTACHMENT_NOT_FOUND UNSUPPORTED_AUDIO_FORMAT TRANSCRIPTION_PROFILE_NOT_FOUND TRANSCRIPTION_MODEL_UNAVAILABLE DIARIZATION_MODEL_UNAVAILABLE TRANSCRIPTION_FAILED TRANSCRIPTION_CANCELLED TRANSCRIPT_NOT_READY ``` --- ## 6. Agent Trace ### 6.1 兼容现有 AgentEvent 保留现有事件: ```text RunStarted TextDelta ThinkingDelta ToolCall ToolResult PermissionRequired Usage Citation RunCompleted RunFailed RunCancelled ``` 第二阶段增加: ```text ModelCallStarted ModelCallCompleted ModelCallFailed PermissionResolved ``` `GET /api/agent/runs/{run_id}/events` 增加 `Last-Event-ID` 和 `after_sequence` 支持,不改变现有事件 envelope。 ### 6.2 Trace 快照 `GET /api/agent/runs/{run_id}/trace?after_sequence=0&limit=200` ```json { "run_id": "run_123", "status": "running", "items": [], "next_sequence": 42, "has_more": false, "summary": { "model_calls": 2, "tool_calls": 3, "duration_ms": 1530, "token_usage": 2048, "errors": 0 }, "config_snapshot": {} } ``` Trace API 返回事件事实,不返回前端树形布局。前端根据 parent ID 和 sequence 构建时间线或树,Benchmark 使用相同事件计算指标。 ### 6.3 新增事件数据 `ModelCallStarted`: ```json { "model_call_id": "model_call_01", "provider_id": "deepseek", "model": "deepseek-chat", "step": 2 } ``` `ModelCallCompleted`: ```json { "model_call_id": "model_call_01", "duration_ms": 820, "finish_reason": "tool_calls", "input_tokens": 1200, "output_tokens": 240 } ``` `PermissionResolved`: ```json { "request_id": "permission_01", "permission": "notes.write", "decision": "allow_once" } ``` ToolCall 和 ToolResult 增加可选 `parent_model_call_id`、`duration_ms` 和经过截断/净化的摘要字段。完整敏感参数不进入 Trace。 ### 6.4 Trace 保留和脱敏 - `run_id + sequence` 是事件幂等键。 - 运行记录和事件由持久化 Repository 管理,不能只保存在 SSE 订阅队列。 - API Key、Authorization Header、Secret Setting、完整附件正文默认脱敏。 - 大型 Tool Result 保存摘要与受控 artifact 引用,不直接塞入事件 JSON。 - Trace 被 Benchmark 引用时保存运行配置快照,避免后续 Provider 配置变更导致报告不可解释。 ### 6.5 Trace 错误码 ```text AGENT_RUN_NOT_FOUND TRACE_NOT_AVAILABLE TRACE_CURSOR_EXPIRED TRACE_CURSOR_INVALID ``` --- ## 7. MCP Bridge 与 Plugin Contribution ### 7.1 MCP 内部 Adapter Agent Runtime 不使用 MCP 原始类型。MCP Bridge 必须完成: ```text MCP tools/list → JSON Schema 校验 → 项目 ToolDefinition → Tool Registry 项目 ToolCall → MCP tools/call → MCP Content / Error → 项目 ToolResult ``` 进入 Tool Registry 的名称使用 `.` 命名空间。重复名称、无效 Schema、未声明权限和超过结果大小上限时拒绝注册。 内部接口示意: ```python class McpBridge(Protocol): async def start(self, plugin: PluginManifest) -> "PluginHostStatus": ... async def discover_tools(self, plugin_id: str) -> list[ToolDefinition]: ... async def call_tool(self, plugin_id: str, call: ToolCall) -> ToolResult: ... async def cancel(self, plugin_id: str, request_id: str) -> None: ... async def stop(self, plugin_id: str) -> None: ... ``` 首个实现已支持本地 `stdio`,按 MCP `2025-11-25` 发起 initialize,并兼容 `2025-06-18`、`2025-03-26` 和 `2024-11-05` 协商结果。Host 负责 capability negotiation、分页 `tools/list`、进程生命周期、超时取消、stderr 隔离和异常退出后的 Tool 注销。stdio 消息使用 UTF-8 单行 JSON-RPC,并在完整行进入内存前执行有界读取;当前不实现 Streamable HTTP。 MCP Plugin 的 `backend` 增加: ```yaml backend: type: mcp transport: stdio command: uvx args: [--isolated, --from, example-mcp==1.2.3, example-mcp] startup_timeout_seconds: 60 tool_timeout_seconds: 30 ``` 命令通过参数数组直接启动,不经过 Shell。带路径的 executable 必须位于 Plugin 包内;PATH 中的命令可以按名称引用。Python 包形式的 MCP 推荐使用固定版本的 `uvx --isolated --from`,但 `uvx` 只隔离依赖而不是文件/网络/系统调用安全沙箱,非 Python Server 不强制使用。开发模式首次运行未缓存的 uvx 包可能联网解析,因此 startup 示例使用 60 秒;生产安装阶段必须预取并验证,运行阶段不得临时解析依赖。子进程只继承运行所需的系统环境变量,不继承 `OPENAI_API_KEY`、`APP_DB_PATH`、Vault 路径等宿主状态。Secret 注入留给阶段 D 的专用引用接口。 在平台沙箱和可信命令许可完成前,`APP_ENVIRONMENT != development` 时启用 MCP Plugin 必须返回 `403 MCP_TRUST_APPROVAL_REQUIRED`,不得启动进程或注册 Tool。该门禁由后端执行,不能只依赖前端提示或文档约定。 远端 Tool 的可选项目权限放在 MCP `_meta`: ```json { "_meta": { "notesagent/permission": "notes.read" } } ``` 该权限必须属于项目已知权限并同时出现在 Plugin Manifest 中。发现结果必须与 `contributes.tools` 的命名空间 ID 完全一致;校验全部成功后才一次性发布到 Tool Registry。 当前 Web 开发接口 `POST /api/plugins/install` 使用 `package_path`。桌面 Host 接入后,`ExtensionInstallRequest` 增加 `package_id`,由文件选择器产生临时包句柄;`package_path` 只在明确的 development 环境保留并标记 deprecated,生产构建拒绝任意前端路径。 ### 7.2 Plugin Host 状态 `GET /api/plugins/{plugin_id}/host` ```json { "plugin_id": "example-plugin", "backend_type": "mcp", "transport": "stdio", "status": "ready", "tools_count": 3, "started_at": "2026-08-31T10:30:00Z", "last_seen_at": "2026-08-31T10:31:00Z", "error": null } ``` Host 状态: ```text stopped starting ready unhealthy error ``` `POST /api/plugins/{plugin_id}/host/restart` 返回 `202 OperationResponse`。重启期间先注销旧 Tool,发现和校验全部成功后再一次性发布新 Tool 集合,避免半注册状态。 当前实现还返回协商后的 `protocol_version`、`server_name` 和 `server_version`。单条协议消息上限为 2 MiB,单次 Tool Result 上限为 256 KiB;超限分别按 Host/Result 错误处理。Server 异常退出或发送无效 stdout 消息时,Host 进入 `unhealthy`,Plugin 进入 `error`,相关 Tool 立即注销。取消会同时通知 Server、移除 pending request 并唤醒本地等待线程。Restart 不得把 `installed`、`disabled` 或 `permission_required` Plugin 隐式启用,这些状态必须走 Enable。 ### 7.3 Command Contribution 列表 `GET /api/plugin-contributions/commands?location=command_palette` ```json { "items": [ { "command_id": "example-plugin.open-search", "plugin_id": "example-plugin", "title": "打开外部搜索", "description": "在插件服务中搜索当前选区", "icon": "search", "locations": ["command_palette", "context_menu"], "when": ["workspace.has_vault", "editor.has_selection"], "parameters": { "type": "object", "properties": {} }, "enabled": true } ] } ``` `icon` 只能引用宿主图标 ID 或插件包内已验证资源。`when` 是宿主支持的条件 token 列表,不执行插件提供的 JavaScript 表达式。 ### 7.4 执行 Command `POST /api/plugin-contributions/commands/{command_id}/execute` ```json { "arguments": {}, "context": { "note_id": "note_123", "selection": "选中的文本" } } ``` 宿主根据 Command 声明裁剪 Context,插件不能要求前端上传整个 Pinia 状态。响应: ```json { "command_id": "example-plugin.open-search", "status": "completed", "effect": { "type": "notification", "payload": { "level": "success", "message": "命令已完成" } } } ``` 允许的 effect 首批为 `none`、`notification`、`navigate`、`refresh` 和 `job`。每类 payload 也是契约的一部分:`none` 必须为空;`notification` 只接受 `level`(`info/success/warning/error`)和非空 `message`;`navigate` 只接受宿主路由名 `route`;`refresh` 只接受 `workspace/commands/settings/plugins` 范围;`job` 只接受受限格式的 `job_id`。后端拒绝未知字段和不匹配的 payload,前端仍须按判别联合穷尽处理,不得把 effect 当作任意代码执行。 当前宿主只注册已启用且已满足权限授权的 Plugin Command。执行前按 JSON Schema 校验参数、按 `when` 校验上下文,再根据 Command 声明裁剪 Context;单次执行默认超时 30 秒,effect 的 JSON 编码结果不得超过 64 KiB。宿主保留最多 500 条轻量审计事件,仅记录 Command、Plugin、状态、耗时和错误码,不记录 arguments、Context、effect 或 Secret。 需要 Secret 的 Command 必须在 `commands.yaml` 的内部 `secrets` 数组中声明对应 Setting Key,并在 Plugin Manifest 声明 `secrets.use` 权限。安装时宿主校验该字段确实属于当前 Plugin Settings Schema 的 `secret` 类型;只有权限已授予并启用后,运行时才向受控 handler 或 MCP Command Target 提供声明过的 Secret。未声明字段返回 `PLUGIN_SECRET_ACCESS_DENIED`,必填 Secret 未配置返回 `PLUGIN_SECRET_REQUIRED`。`secrets` 不属于前端 `PluginCommand` DTO,Secret 明文也不会并入普通 Settings 字典。 `commands.yaml` 中的执行目标必须在宿主白名单 `handler` 与当前 Plugin 命名空间的 `mcp_tool` 之间二选一。MCP Command Target 不注册为 Agent Tool;宿主用 `_notesagent` 保留包装传入 Command ID、参数、裁剪后的 Context、已校验的非敏感 Settings 和声明过的 Secret,并将 MCP structured result 再校验为白名单 effect。作为宿主协议标记,目标 MCP Tool 的 `inputSchema` 必须在顶层 `properties` 中直接声明 `_notesagent: { type: object }`;不得用顶层组合或引用替代该标记。`_notesagent` 对象内部仍可使用完整 Draft 2020-12 约束、文档内引用和组合 Schema。启用阶段只检查协议标记,不尝试求解 Schema 或伪造业务值;宿主会保留完整 Schema,并在每次调用前用官方 Validator 校验真实信封。Command 与 Tool Schema 仅允许 `#...` 文档内引用,任何通过 `$ref` 或 `$dynamicRef` 指向文件、HTTP 或其他外部资源的 Schema 都会在注册前被拒绝。文档内引用遵循 Draft 2020-12 的嵌套 `$id` 与 Anchor 资源作用域,不能解析的引用不得进入运行时。 ### 7.5 Settings Schema `GET /api/plugins/{plugin_id}/settings` ```json { "plugin_id": "example-plugin", "schema_version": 1, "fields": [ { "key": "result_limit", "label": "结果数量", "description": "每次最多返回的结果数", "type": "number", "required": true, "default": 10, "minimum": 1, "maximum": 100, "options": [] }, { "key": "api_key", "label": "API Key", "type": "secret", "required": true, "options": [] } ], "values": { "result_limit": 10 }, "secrets": { "api_key": { "configured": false } } } ``` 字段类型首批固定为: ```text string number boolean select secret ``` Number 字段的 `minimum` 和 `maximum` 必须是有限数值;`NaN`、正无穷和负无穷均视为无效 Settings Schema。 ### 7.6 更新 Settings 和 Secret `PUT /api/plugins/{plugin_id}/settings` ```json { "schema_version": 1, "values": { "result_limit": 20 } } ``` 该接口拒绝 secret 字段。Schema 版本过期返回 `PLUGIN_SETTINGS_VERSION_CONFLICT` 并附当前版本。没有默认值的必填普通字段必须先通过该接口配置;否则 Plugin Enable 和 Command Execute 返回 `PLUGIN_SETTINGS_REQUIRED`,MCP 或内部 handler 不会收到残缺配置。 Secret 使用: ```text PUT /api/plugins/{plugin_id}/settings/{key}/secret DELETE /api/plugins/{plugin_id}/settings/{key}/secret ``` 写入请求: ```json { "secret": "仅在本次请求中出现的明文" } ``` 响应只返回: ```json { "plugin_id": "example-plugin", "key": "api_key", "configured": true } ``` Secret 明文不进入普通 Settings、日志、Trace、Benchmark Dataset 或前端持久化。 非敏感值按 `plugin_id` 写入 `APP_DATA_DIR/plugins/settings.json`。该文件只保存普通值、Schema 版本和确定性的定长 Secret Reference,格式为 `plugin.`;Secret 本身由宿主凭据存储加密保存。卸载 Plugin 时同时清理它的 Settings 命名空间和 Secret Reference。当前开发阶段使用 Fernet 文件凭据存储,第三阶段接入桌面 Host 后应迁移到 Stronghold 或系统 Keychain。 `plugin.*` 为宿主保留凭据命名空间。`/api/credentials/{credential_id}`、Provider 持久配置、Provider 临时测试凭据和 Provider Resolver 均拒绝该前缀,防止通过 Provider 链路覆盖、删除或向外部 Base URL 发送 Plugin Secret。 ### 7.7 Plugin/MCP 错误码 ```text PLUGIN_HOST_UNAVAILABLE PLUGIN_HOST_START_FAILED PLUGIN_HOST_UNHEALTHY MCP_INITIALIZE_FAILED MCP_CAPABILITY_UNSUPPORTED MCP_TOOL_SCHEMA_INVALID MCP_TOOL_CALL_FAILED MCP_TOOL_RESULT_TOO_LARGE MCP_TRUST_APPROVAL_REQUIRED MCP_TRUST_DIGEST_STALE MCP_SANDBOX_REQUIRED MCP_TRANSPORT_UNSUPPORTED MCP_SERVER_NOT_FOUND MCP_SERVER_NAME_INVALID MCP_SERVER_ALREADY_ENABLED MCP_SERVER_VERSION_CONFLICT MCP_SERVER_LIMIT_REACHED MCP_REGISTRY_WRITE_FAILED MCP_REGISTRY_INVALID MCP_CONNECTION_TEST_REQUIRED MCP_CONFIG_INVALID MCP_COMMAND_INVALID MCP_URL_INVALID MCP_HEADER_INVALID MCP_HTTP_REQUEST_FAILED MCP_HTTP_RESPONSE_INVALID MCP_SECRET_REQUIRED MCP_SECRET_NOT_DECLARED MCP_SECRET_KIND_INVALID MCP_SECRET_STORE_ERROR MCP_ENVIRONMENT_INVALID MCP_PERMISSION_INVALID PLUGIN_COMMAND_NOT_FOUND PLUGIN_COMMAND_CONFLICT PLUGIN_COMMAND_INVALID PLUGIN_COMMAND_ARGUMENT_INVALID PLUGIN_COMMAND_CONTEXT_INVALID PLUGIN_COMMAND_TIMEOUT PLUGIN_COMMAND_EXECUTION_FAILED PLUGIN_COMMAND_RESULT_INVALID PLUGIN_COMMAND_RESULT_TOO_LARGE PLUGIN_COMMAND_TARGET_SCHEMA_MISMATCH PLUGIN_SETTINGS_SCHEMA_INVALID PLUGIN_SETTINGS_VERSION_CONFLICT PLUGIN_SETTINGS_FIELD_INVALID PLUGIN_SETTINGS_REQUIRED PLUGIN_SECRET_FIELD_NOT_FOUND PLUGIN_SECRET_ACCESS_DENIED PLUGIN_SECRET_REQUIRED PLUGIN_SECRET_VALUE_INVALID PLUGIN_SECRET_STORE_ERROR PLUGIN_STORAGE_ERROR CREDENTIAL_NAMESPACE_RESERVED ``` ### 7.8 独立 MCP Server Registry(C.1) 独立 Server 不依附 Plugin Manifest,配置持久化于 `APP_DATA_DIR/mcp/servers.json`。`transport` 支持 `stdio`、`streamable_http` 和兼容旧服务的 `sse`。敏感环境变量与认证 Header 只以 `mcp.*` 引用进入加密凭据存储;读取响应以 `secret_environment`、`secret_headers` 的布尔值表示配置状态,不返回明文。动态工具使用 `mcp.{server_id}.{remote_tool}` 命名空间,来源标记为 `mcp_server`,仍通过统一 Tool Registry、Permission Manager 与 Agent Trace。 stdio 配置使用 `command`、`args`、`environment` 和 `secret_environment_keys`;HTTP/SSE 配置使用 `url`、`headers` 和 `secret_header_keys`,两组 Transport 字段不可混用。更新请求必须携带当前 `version`,成功后版本递增;过期版本返回 `409 MCP_SERVER_VERSION_CONFLICT`。`GET /tools` 返回 `name`、`remote_name`、`description` 和可选 `permission`。 创建或编辑配置后,调用方必须向 `/trust` 回传服务端计算的 `command_digest`。后端只接受与当前 Transport、连接参数、环境/Header 及权限完全一致的摘要;配置变化会撤销旧信任和测试结果。只有当前摘要通过 `/test`,才能调用 `/enable`。测试失败也会持久化时间和失败状态。 Secret 明文变化无法进入摘要,因此 Secret 写入和删除采用更严格规则:若 Server 已启用则先停用并注销 Tool,随后清除 `tested_digest` 和最近测试状态。调用方必须用新 Secret 再次执行 `/test`,不能沿用旧凭据的测试结果。 Streamable HTTP 支持 Session ID、`MCP-Protocol-Version`、JSON 或 SSE POST 响应、可选 GET 事件流及 `Last-Event-ID`;旧 SSE 按 endpoint 事件确定 POST 地址,并要求与初始 URL 同源。Secret 接口用 `?kind=environment` 或 `?kind=header` 区分类型。HTTP URL 不允许内嵌凭据或 Fragment,配置不得覆盖协议保留 Header。 stdio 命令始终以 executable 与 args 数组通过 `shell=False` 启动;普通环境变量和加密 Secret 显式注入,不继承 Provider Key、数据库或 Vault 路径。当前 Python Host 仅在 `APP_ENVIRONMENT=development` 时允许启动 stdio;其他环境返回 `403 MCP_SANDBOX_REQUIRED`。远程 HTTP Transport 不创建本机进程,但仍受摘要确认、成功测试、超时、消息限长与 Secret 隔离约束。 --- ## 8. Provider Adapter 扩展 > 阶段 E 实施更新(2026-09-04):OpenAI Responses、Anthropic Messages、Chat Completions 与 Ollama Adapter 已接入;国内提供商 logo 预设、独立凭据输入、配置恢复、Embedding / 转写 / 声纹 API 路由已实现。真实本地语音模型仍属于阶段 F。实现细节见 [模型提供商与模型发现开发说明](../development/模型提供商与模型发现开发说明.md)。 第二阶段不新增平行 Provider CRUD,继续使用第一阶段接口: ```text GET /api/providers GET /api/providers/presets POST /api/providers GET /api/providers/{provider_id} PATCH /api/providers/{provider_id} DELETE /api/providers/{provider_id} GET /api/providers/{provider_id}/models POST /api/providers/test POST /api/chat ``` ### 8.1 ModelInfo 扩展 以下为后续计划的可选字段;阶段 E 的 `GET /api/providers/{provider_id}/models` 实际 item 仍只包含 `model`、`display_name`、`capabilities`: ```json { "model": "example-model", "display_name": "Example Model", "capabilities": ["chat", "streaming", "tool_calling"], "context_window": 128000, "max_output_tokens": 8192, "deprecated": false, "metadata": {} } ``` 模型列表无法可靠获取 Capability 时返回保守集合,不能默认声称支持 Tool Calling 或 Vision。 ### 8.2 Streaming 行为 继续使用现有 ModelEvent: ```text TextDelta ThinkingDelta ToolCallStart ToolCallDelta ToolCallEnd Usage Error Done ``` 第二阶段统一以下规则: - 每个事件包含单调递增 `sequence`。 - `ToolCallDelta` 按 `tool_call_id` 聚合,完成时参数必须是合法 JSON Object。 - `Usage` 字段统一为 `input_tokens`、`output_tokens`、`total_tokens`。 - Model Stream 在成功或失败后都发送且只发送一个终止事件 `Done`;失败顺序为 `Error` 后 `Done`,`Done.data.status` 为 `completed`、`failed` 或 `cancelled`。 - 浏览器取消 Fetch 或 SSE 后,服务端必须取消上游 Provider 请求。 - 不支持 reasoning 的 Provider 不发送伪造 ThinkingDelta。 阶段 E 补充:取消或关闭迭代器直接关闭上游连接并传播取消,不向已断开的客户端继续发送 Done。内部带点号、长名称的工具映射为合法的 64 字符以内名称,响应恢复原命名空间,映射在请求内隔离。实际流中断错误码为 `PROVIDER_STREAM_TRUNCATED`;`PROVIDER_INVALID_RESPONSE` 用于无效结构/参数。上面的 `Done.data.status` 适用于真实 HTTP Adapter;开发 Mock 保留原有测试事件。 ### 8.3 Provider 一致性测试 Contract 每个 Adapter 使用相同 Case 描述: ```yaml id: provider-tool-call-basic capabilities: - chat - streaming - tool_calling request_fixture: tool_call_weather assertions: terminal_event: Done tool_calls: 1 usage_required: false ``` 测试报告记录 Provider 类型、模型、协议、运行时间和跳过原因。真实网络测试与离线 Adapter 单元测试分组,CI 默认不要求外部 API Key。 ### 8.4 Provider 错误码 沿用并补齐: ```text PROVIDER_AUTH_FAILED PROVIDER_RATE_LIMITED PROVIDER_TIMEOUT PROVIDER_UNAVAILABLE PROVIDER_PROTOCOL_ERROR PROVIDER_STREAM_INTERRUPTED MODEL_NOT_FOUND MODEL_CAPABILITY_MISMATCH MODEL_CONTEXT_LENGTH_EXCEEDED ``` --- ### 8.5 阶段 E 模型路由接口(已实现) | 方法 | 路径 | 契约 | | --- | --- | --- | | GET | `/api/model-routing` | `{config, local_backends}` | | PUT | `/api/model-routing` | 提交 ModelRoutingConfig,返回递增版本配置 | | POST | `/api/models/embeddings` | `{texts: string[]}` → `{vectors, source, model_id, dimensions, fallback_reason}` | | POST | `/api/media/speaker-matches` | `{attachment_id, reference_attachment_id}` → `{score, source, fallback_reason}` | `ModelRoutingConfig` 包含 `version`、`embedding`、`transcription`、`speaker_matching`。每个能力为 null 或 `{provider_id, model, endpoint, dimensions?}`。dimensions 仅 Embedding 使用,范围 1–16384;endpoint 是选定 Provider 下不带查询的路径,不能传第二个 URL。PUT 不提交 GET 返回的 local_backends;版本冲突返回 409 `MODEL_ROUTING_VERSION_CONFLICT`。删除仍被引用的 Provider 返回 409 `PROVIDER_IN_USE`。 本阶段远程能力仅接受 OpenAI Chat / Compatible HTTP 配置,默认路径分别是 `/embeddings`、`/audio/transcriptions`、`/audio/speaker-matches`。最后一个是本项目自定义 multipart 接口,**不是公共 OpenAI 标准协议**:请求 model、file、reference_file,响应有限 0–1 的 score。转写采用 multipart model、file、可选 language,必须返回非空 text。文件来自受控附件目录,限制 25 MiB。 `TranscriptionJob` 新增可选 `source: api|local|sidecar` 和 `fallback_reason`。保留已有转写 Job 路径;`diarization=true` 返回失败 Job,错误为 `DIARIZATION_NOT_IMPLEMENTED`,不能静默忽略。 无配置时调用本地接口;有配置时 API 优先,网络/鉴权/限流/结果无效时回退。本地 Embedding 当前为 hash 占位;本地 ASR / 声纹后端尚未安装时返回 `LOCAL_MODEL_NOT_INSTALLED`,而非伪成功。远程 Embedding 独立索引并检查完整覆盖,模型变化后需重建;不与本地向量混算。 Provider PATCH 支持 provider_type;普通配置持久化到 SQLite,凭据继续独立加密。预设新增 logo_id、description、capabilities,前端图标随应用打包。 ## 9. RAG / Agent Benchmark Benchmark Service 同时提供 Python 调用接口和本地 HTTP 接口。CLI、测试和前端报告页调用同一 Service,不各自实现指标。 ### 9.1 Dataset 列表 `GET /api/benchmarks/datasets?kind=rag|agent` ```json { "items": [ { "dataset_id": "rag-core-v1", "kind": "rag", "version": "1.0.0", "description": "基础中文笔记检索集", "case_count": 50, "content_hash": "sha256:..." } ] } ``` Dataset 从仓库或受控导入目录注册。API 不接受调用方提交任意文件路径。 ### 9.2 RAG Dataset Case ```json { "case_id": "rag-os-001", "query": "死锁发生需要什么条件", "expected_note_ids": ["note_deadlock"], "expected_block_ids": ["block_conditions"], "citation_required": true, "tags": ["os", "deadlock"] } ``` ### 9.3 Agent Dataset Case ```json { "case_id": "agent-add-001", "prompt": "调用 math.add 计算17加25,然后回答42。", "allowed_tools": ["math.add"], "expected_tools": [{"name": "math.add", "arguments": {"left": 17, "right": 25}}], "output_contains": ["42"], "citation_required": false, "tasks_created": null, "tags": ["math"] } ``` 每个Dataset最多100例且case_id唯一;Case至少声明工具、输出子串、引用或任务数量之一作为客观断言。预期工具必须属于allowed_tools。参数按声明键的值匹配,重复/额外调用计入错误,任务创建按成功Tool Result计数。 有写操作的 Agent Case 必须运行在隔离 Vault/数据库中,测试结束后清理 Fixture,禁止作用于用户真实知识库。 ### 9.4 创建 RAG Benchmark `POST /api/benchmarks/rag/runs` ```json { "dataset_id": "rag-core-v1", "modes": ["fts", "vector", "hybrid"], "retrieval": { "top_k": 10, "fusion": "rrf", "rrf_k": 60, "rerank": true, "rerank_candidates": 20, "score_threshold": 0.0 }, "repeat": 1, "metadata": {} } ``` 配置快照必须记录 Embedding model ID/version/dimension、Reranker、索引版本、Dataset Hash 和运行环境。 ### 9.5 创建 Agent Benchmark(已实现) `POST /api/benchmarks/agent/runs` ```json { "dataset_id": "agent-core-v1", "provider_id": "已有启用的Provider ID", "model": "已有模型", "max_steps": 6, "timeout_seconds": 90, "token_budget": 6000, "repeat": 1, "allow_network": false, "offline": false } ``` `max_steps` 1–20、`timeout_seconds` 1–300、`token_budget` 1–30000、`repeat` 1–3。顺序执行以限制额度;默认拒绝mock。仅显式offline=true可使用mock,且offline不能选真实Provider。创建返回202;无Provider为404,模式冲突为422,容量超限为429。权限由用户在真实Trace中处理,不自动批准。共用运行查询/SSE/取消/报告端点。 报告包含逐例agent_run_id、success/checks、调用/匹配/无效数量、steps、latency_ms、token_usage。汇总task_success_rate以计划案例总数为分母,total_cases/evaluated_cases区分未完成样本;选择/参数准确率以max(实际调用总数,预期调用总数)为分母,无调用时为null。无效调用率以实际调用数为分母。平均步骤/耗时采用计划数分母,取消/失败报告不可当完整性能测量。配置冻结dataset hash/version、Provider类型/引用、模型、预算、评分版本与权限策略。 Benchmark Runner 通过正式 Agent Runtime 创建 Run,并从 Trace 计算结果,不能直接调用 Tool Executor 绕过权限和步骤控制。 ### 9.6 Benchmark Run RAG 和 Agent 创建接口均返回 `202 BenchmarkRun`: ```json { "run_id": "benchmark_123", "kind": "rag", "dataset_id": "rag-core-v1", "dataset_hash": "sha256:...", "status": "queued", "progress": null, "metrics": null, "config_snapshot": {}, "error": null, "error_code": null, "created_at": "2026-08-31T10:30:00Z", "started_at": null, "completed_at": null } ``` `status` 取值:`queued` → `running` → `completed` | `failed` | `cancelled`。失败/取消时 `error` 与 `error_code` 只返回项目错误码与安全消息,不暴露第三方堆栈。 公共接口: | 方法 | 路径 | 用途 | | --- | --- | --- | | GET | `/api/benchmarks/runs?kind=&status=&limit=&offset=` | 分页获取运行记录 | | GET | `/api/benchmarks/runs/{run_id}` | 获取状态和指标摘要 | | GET | `/api/benchmarks/runs/{run_id}/events` | SSE 进度和 Case 结果 | | POST | `/api/benchmarks/runs/{run_id}/cancel` | 取消运行 | | GET | `/api/benchmarks/runs/{run_id}/report` | 获取结构化完整报告 | SSE 事件流(`RunStarted` → `CaseCompleted`* → `RunCompleted` | `RunFailed` | `RunCancelled`): `GET /api/benchmarks/runs/{run_id}/events` 支持 `Last-Event-ID` 与 `?after_sequence=` 游标恢复, `RunCompleted` / `RunFailed` / `RunCancelled` 为终止事件,收到后即断流。 ### 9.7 指标 Contract RAG: ```json { "hit_at_1": 0.72, "hit_at_5": 0.91, "recall_at_k": 0.88, "mrr": 0.81, "citation_hit_rate": 0.89, "p50_latency_ms": 24.5, "p95_latency_ms": 67.3, "total_cases": 50, "successful_cases": 48, "failed_cases": 2, "failure_rate": 0.04 } ``` 失败样本按零分计入质量指标分母,`total_cases` / `successful_cases` / `failed_cases` / `failure_rate` 让报告明确实际分母;延迟仅统计成功样本。 Agent: ```json { "task_success_rate": 0.80, "tool_selection_accuracy": 0.92, "tool_argument_accuracy": 0.86, "invalid_tool_call_rate": 0.02, "average_steps": 4.3, "average_latency_ms": 2300, "average_token_usage": 3200 } ``` 报告同时返回逐 Case 结果、失败原因和 Trace Run ID,汇总指标不能覆盖失败样本。 ### 9.8 Benchmark 错误码 ```text BENCHMARK_DATASET_NOT_FOUND BENCHMARK_DATASET_INVALID BENCHMARK_CONFIG_INVALID BENCHMARK_INDEX_INCOMPATIBLE BENCHMARK_CAPACITY_EXCEEDED BENCHMARK_RUN_NOT_FOUND BENCHMARK_RUN_FAILED BENCHMARK_CASE_EVALUATION_FAILED ``` ### 9.9 Retrieval Profile 与索引兼容 Benchmark、Search 和 RAG Engine 共享内部 `RetrievalProfile`,不得各自硬编码参数: ```json { "profile_id": "default-v2", "version": 2, "fts_recall": 20, "vector_recall": 20, "rrf_k": 60, "rerank": true, "rerank_candidates": 20, "context_top_k": 8, "score_threshold": 0.0 } ``` 第二阶段扩展 `GET /api/index/status`: ```json { "status": "idle", "pending_jobs": 0, "active_job_id": null, "embedding": { "model_id": "bge-m3", "model_version": "...", "dimension": 1024, "normalization": "l2" }, "vector_index": { "model_id": "hash-v1", "dimension": 128, "compatible": false }, "last_completed_at": null, "error_message": null } ``` 模型 ID、版本、维度或归一化方式不一致时,Vector/Hybrid 搜索返回 `INDEX_MODEL_MISMATCH`,前端降级到 FTS;`POST /api/index/rebuild` 使用现有 `scope = vectors` 重建,不能在查询时混用旧向量。 相关错误码: ```text EMBEDDING_MODEL_UNAVAILABLE EMBEDDING_DIMENSION_MISMATCH INDEX_MODEL_MISMATCH VECTOR_INDEX_REBUILD_REQUIRED ``` --- ## 10. Export Service > 当前实现:三格式已有编辑器快照入口、任务/取消/下载/warning。函数图为HTML SVG、PDF共享几何矢量、DOCX PNG;Mermaid通过前端准备并按源码摘要绑定PNG后进入三格式。未提供静态资源的直接API Mermaid请求仍保留源码并warning,不能假称服务器独立运行Mermaid。受支持MathText公式及Vault图片内嵌,完整限制见本节末补充。 ### 10.1 创建导出任务 `POST /api/exports`,返回 `202 ExportJob`。 ```json { "source": { "type": "note", "note_id": "note_123" }, "format": "html", "options": { "theme_id": "light", "include_title": true, "include_metadata": false, "page_size": "A4", "code_theme": "github-light" } } ``` `source.type` 首批支持 `note` 和 `markdown`。`note` 来源通过 `source.note_id` 引用已建索引笔记;`markdown` 来源用于尚未保存的预览,内容放在 `source.markdown` 字段,大小限制为 200 000 字符、不持久化到 Trace。`format` 可取 `html`、`pdf`、`docx`,三格式均已实现。 响应: ```json { "job_id": "export_123", "status": "queued", "format": "html", "progress": null, "file": null, "warnings": [], "error": null, "error_code": null, "created_at": "2026-08-31T10:30:00Z", "started_at": null, "completed_at": null } ``` ### 10.2 查询、取消和下载 | 方法 | 路径 | 响应 | | --- | --- | --- | | GET | `/api/exports?status=&format=&limit=&offset=` | `ExportJobListResponse` | | GET | `/api/exports/{job_id}` | `ExportJob` | | POST | `/api/exports/{job_id}/cancel` | `OperationResponse` | | GET | `/api/exports/{job_id}/file` | 文件流 | 下载响应设置正确 `Content-Type`、经过清理的 `Content-Disposition` 文件名和 `Content-Length`。未完成、失败或过期的 Job 不返回空文件:未完成/失败返回 `EXPORT_JOB_NOT_FOUND`(404),产物过期(超过 `expires_at`)返回 `EXPORT_FILE_EXPIRED`(410)。 完成 Job 的 file: ```json { "file_name": "操作系统复习.html", "mime_type": "text/html", "size": 1048576, "sha256": "...", "expires_at": "2026-09-01T10:30:00Z" } ``` ### 10.3 Document AST 内部 Contract ```python class DocumentExporter(Protocol): async def export( self, document: Document, options: ExportOptions, ) -> ExportResult: ... ``` Document Node 使用稳定判别字段: ```json { "node_id": "node_01", "type": "heading", "attributes": { "level": 2 }, "children": [], "text": "进程调度" } ``` 首批 node type: ```text document heading paragraph text emphasis strong link list list_item table image blockquote code_block math_inline math_block mermaid function_plot ``` Exporter 对无法表示的节点添加 warning,不允许静默丢失。严重缺失由 `EXPORT_UNSUPPORTED_CONTENT` 失败。 ### 10.4 Static Render Contract Mermaid 和函数图像共享: ```ts interface StaticRenderRequest { kind: 'mermaid' | 'function_plot' source: string sourceHash: string theme: 'light' | 'dark' width?: number height?: number } interface StaticRenderResult { mimeType: 'image/svg+xml' | 'image/png' content: string | Uint8Array width: number height: number warnings: string[] } ``` 渲染实现可以位于前端共享 Renderer、Host 或后端适配进程,但 Export Service 只依赖上述 Contract。SVG 进入导出器前必须净化;缓存键至少包含 source hash、主题和 renderer version。 ### 10.5 Export 错误码 ```text EXPORT_SOURCE_NOT_FOUND EXPORT_OPTIONS_INVALID EXPORT_RENDER_FAILED EXPORT_UNSUPPORTED_CONTENT EXPORT_JOB_NOT_FOUND EXPORT_FILE_EXPIRED EXPORT_OUTPUT_TOO_LARGE ``` --- ## 11. Theme Package Host Contract Theme 不经过 Python AI Core。正式桌面版通过 Tauri Host,Web 开发模式由相同 TypeScript Service 的 Mock Adapter 提供。 ### 11.1 Theme Manifest `theme.yaml` 对应: ```ts interface ThemeManifest { theme_id: string name: string version: string author: string description?: string min_app_version: string is_dark: boolean css_entry: string preview?: string } ``` ### 11.2 ThemePackageService ```ts interface ThemePackageService { selectPackage(): Promise inspectPackage(packageId: string): Promise install(packageId: string): Promise list(): Promise enable(themeId: string): Promise disable(themeId: string): Promise uninstall(themeId: string): Promise } ``` `packageId` 是 Host 通过文件选择器生成的临时句柄,不是前端传入的绝对路径。 ### 11.3 Inspection ```json { "package_id": "theme_package_123", "manifest": {}, "preview_url": "app-theme-preview://theme_package_123", "warnings": [], "compatible": true } ``` 安装前检查: ```text Manifest Schema Theme ID / Version min_app_version 包内相对路径 CSS 语法与大小 禁止远程 URL、@import、脚本和越界资源 预览资源 MIME ``` 预览运行在隔离容器,不能直接将未验证 CSS 注入主页面。 ### 11.4 Theme 错误码 ```text THEME_PACKAGE_NOT_FOUND THEME_MANIFEST_INVALID THEME_PACKAGE_INCOMPATIBLE THEME_RESOURCE_OUTSIDE_PACKAGE THEME_CSS_UNSAFE THEME_ALREADY_INSTALLED THEME_NOT_FOUND THEME_BUILTIN_PROTECTED ``` --- ## 12. Mermaid 与 Function Plot 前端 Contract ### 12.1 MermaidRenderer ```ts interface MermaidRenderer { render( source: string, options: { theme: 'light' | 'dark' mode: 'interactive' | 'static' }, ): Promise<{ svg: string width: number height: number warnings: string[] }> } ``` Renderer 返回净化后的 SVG 或明确错误,不直接操作调用组件之外的 DOM。组件销毁时释放事件监听和临时节点;编辑快速变化时以前一次请求的 AbortSignal 或 render version 丢弃旧结果。 ### 12.2 FunctionPlot Model ```ts interface FunctionPlot { version: 1 expressions: Array<{ expression: string label?: string color?: string }> domain?: [number, number] range?: [number, number] axes: { xLabel?: string yLabel?: string grid: boolean } } ``` Markdown parser 将 fenced block 转换为 `FunctionPlotParseResult`: ```ts interface FunctionPlotParseResult { plot?: FunctionPlot diagnostics: Array<{ severity: 'warning' | 'error' code: string message: string line?: number column?: number }> } ``` 表达式解析使用白名单数学语法,不执行 `eval`、函数构造器、网络请求或对象属性访问。无法解析时保留原始 fenced block,并在预览中显示可定位诊断。 ### 12.3 FunctionPlotRenderer ```ts interface FunctionPlotRenderer { renderInteractive(plot: FunctionPlot, target: HTMLElement): Promise renderStatic(plot: FunctionPlot, options: StaticRenderOptions): Promise } interface RenderHandle { update(plot: FunctionPlot): Promise resize(width: number, height: number): void destroy(): void } ``` 编辑器、Markdown 预览和 Export Service 只能依赖上述 Model/Renderer,不直接依赖具体绘图库的数据结构。 ### 12.4 可视化错误码 ```text MERMAID_PARSE_FAILED MERMAID_RENDER_FAILED MERMAID_SVG_UNSAFE FUNCTION_PLOT_PARSE_FAILED FUNCTION_PLOT_EXPRESSION_UNSAFE FUNCTION_PLOT_RENDER_FAILED ``` --- ## 13. 跨模块 Contract 对照 | Contract | 提供模块 | 消费模块 | 稳定字段 | | --- | --- | --- | --- | | `TranscriptSegment` | Multimodal | Knowledge、Frontend | speaker、time range、text | | `AgentEvent` / Trace | Agent Runtime | Frontend、Agent Benchmark | run_id、sequence、event、timestamp、data | | `ToolDefinition` / `ToolResult` | Tool Registry | Agent、MCP Bridge | name、parameters、permission、success/error | | `PluginCommand` | Extension Core | Command Palette、Context Menu | command_id、locations、when、parameters | | `PluginSettingsSchema` | Extension Core | Settings UI | schema_version、fields、values、secret status | | `ModelEvent` | Provider Adapter | Chat、Agent、Frontend | event、sequence、data、timestamp | | `BenchmarkRun` | Benchmark Service | CLI、报告页 | dataset hash、config snapshot、metrics、status | | `Document AST` | Export Service | HTML/PDF/DOCX Exporter | node_id、type、attributes、children/text | | `StaticRenderResult` | Mermaid/Function Renderer | Export Service | mime、content、size、warnings | | `ThemeManifest` | Theme Host | Theme UI | ID、version、compatibility、entry、preview | 跨模块字段需要修改时: 1. 先修改本文和对应 Pydantic/TypeScript Contract。 2. 新字段优先可选并提供默认行为。 3. 同一提交增加 Provider/Consumer 两侧契约测试。 4. 若必须破坏兼容,增加版本字段或新路径并记录迁移窗口。 --- ## 14. HTTP 状态码约定 | 状态码 | 使用场景 | | --- | --- | | 200 | 查询、同步更新、已完成命令 | | 201 | 同步创建持久资源 | | 202 | 创建异步 Job、请求取消或 Host 重启 | | 204 | 删除成功且无响应正文 | | 400 | 语义无效但 JSON 结构合法 | | 401/403 | 本地 Session 或权限不足 | | 404 | 资源不存在 | | 409 | 状态冲突、依赖缺失、Schema 版本冲突 | | 413 | 上传正文、结果或包超过限制 | | 422 | Pydantic/Schema 校验失败 | | 429 | 并发或速率容量已满 | | 501 | Contract 已预留但当前宿主能力未实现 | | 502/503/504 | 外部 Provider、MCP Host 或模型服务异常 | `501` 只用于明确存在于当前版本 Contract、但运行环境尚不具备的能力;未定义路由仍返回普通 404。 --- ## 15. 接口冻结与开发顺序 这里描述依赖顺序,不代表人员优先级。 ### 15.1 公共契约先行 先冻结并实现: ```text JobStatus / JobProgress SSE sequence 与恢复规则 Agent Trace 新事件 Document AST StaticRenderResult Plugin Command / Settings Schema Benchmark Dataset / Run ``` 这些类型同时进入后端 Pydantic 和前端 Wire DTO,并增加序列化样例测试。 ### 15.2 Adapter 和 Service 在公共 Contract 后分别实现: ```text Whisper / Pyannote Adapter MCP Bridge / Plugin Host Provider 行为适配 RAG / Agent Benchmark Runner HTML / PDF / DOCX Exporter Mermaid / FunctionPlot Renderer ThemePackageService ``` Service 先以 Fake/Mock Adapter 完成状态机和错误测试,再接真实运行时,便于其他模块并行联调。 ### 15.3 前后端联调 - 前端 Service 只依赖本文 Wire DTO。 - 后端未完成时使用与 Contract 相同的 Mock,不在组件中硬编码第二套字段。 - SSE 联调覆盖分片、重复事件、断线恢复、取消和终止状态。 - Job 页面覆盖刷新后恢复,不依赖只存在于 Pinia 的进度。 - Secret 输入在提交后立即清空,不进入浏览器持久化。 ### 15.4 集成验收链路 ```text Audio Attachment → Transcription Job / Trace → Transcript Note → Index / RAG → Agent Run → MCP Tool → Agent Trace → Markdown with Mermaid / Function Plot → HTML / PDF / DOCX → Community Theme Preview ``` 链路中每个箭头都必须通过本文定义的 Contract 或已有第一阶段接口,禁止测试脚本直接写数据库来伪造完成状态。 --- ## 16. Definition of Done 一个第二阶段接口完成需要同时满足: - [ ] Pydantic Request/Response Model 已实现并进入 OpenAPI; - [ ] 前端 Wire DTO 与 Service 显式映射已实现; - [ ] 成功、校验失败、资源不存在、状态冲突和运行时失败均有测试; - [ ] 异步任务支持查询终态,取消语义明确; - [ ] SSE 支持分片解析、sequence 去重、重连和唯一终止事件; - [ ] 第三方错误已映射,响应和日志不包含 Secret 或堆栈; - [ ] Mock/Fake 与真实 Adapter 遵守相同 Contract; - [ ] 接口已在 `/docs` 和 `/openapi.json` 可见; - [ ] 本文状态由“计划新增”更新为“已实现”或“扩展完成”; - [ ] 相关开发说明、测试手册和问题修复文档已同步。 --- ## 17. 实现文件建议 在不破坏现有目录的前提下,第二阶段可以逐步拆分: ```text backend/app/ ├── contracts.py # 现有公共 Contract;稳定后可按域拆分 ├── api/ │ ├── media.py │ ├── agent_trace.py │ ├── plugin_contributions.py │ ├── benchmarks.py │ └── exports.py ├── media/ │ ├── transcription.py │ ├── whisper_adapter.py │ └── diarization_adapter.py ├── extensions/ │ ├── mcp_bridge.py │ ├── command_registry.py │ └── settings.py ├── benchmarks/ │ ├── rag.py │ ├── agent.py │ └── reports.py └── export/ ├── document.py ├── service.py └── exporters/ frontend/src/ ├── contracts/ ├── services/ │ ├── transcriptionService.ts │ ├── pluginContributionService.ts │ ├── benchmarkService.ts │ ├── exportService.ts │ └── themePackageService.ts └── features/ ├── agent-trace/ ├── themes/ └── export/ ``` 目录调整应按实际代码规模渐进进行。Router 只做参数接收和错误映射,状态机、第三方 SDK 与文件处理继续放在 Service/Adapter 层。 ### Benchmark Embedding 运行归属(阶段 E 集成修复) `config_snapshot.local_embedding` 仅表示本地基线;`config_snapshot.embedding` 为 `{ "policy": "per_case", "details": "cases[].embedding" }`。报告与 CaseCompleted 事件的逐样本 `embedding` 包含实际 source(api/local/not_used/unavailable)、model_id、dimensions,以及可选 version、fallback_reason、requested_route、route_version、attempted_space。requested_route 仅含提供商引用、模型、相对端点和维度,不包含 API Key 或凭据引用。FTS 不使用 Embedding,标记 not_used;远程失败或索引不完整回退时记录实际本地模型及原因。 ### 阶段 F 收尾接口补充(2026-09-05) CUDA 组件:`GET /api/local-models/runtime-components/cuda` 返回 status、stage、supported、custom_interpreter、cuda_available、可选 torch/error。status 为 checking/not_installed/installing/installed/failed/interrupted;读取只检查现有环境,不下载安装。`POST` 同路径明确触发后台安装,返回 202;重复请求复用当前安装任务。正在推理/排队返回 409 MODEL_IN_USE,缺少 uv 返回 422 UV_NOT_INSTALLED,不支持的平台返回 422 PLATFORM_UNSUPPORTED。阶段进度不冒充字节百分比。关闭后端时回收安装进程树,重启后重新验证环境。 | 接口/字段 | 行为 | | --- | --- | | `POST /api/media/attachments` | 可选 `Idempotency-Key` Header,16–100 位字母、数字、下划线或连字符。后端持久保存键、文件名、attachment_id 和内容摘要;同键同文件同内容返回同 attachment_id,文件名(含扩展名)或内容不一致返回 409 `IDEMPOTENCY_CONFLICT`。对应附件已清理时返回 409 `IDEMPOTENCY_EXPIRED`,客户端需开始新提交。上传仍受 25 MiB 限制。 | | `TranscriptNoteRequest.update_existing` | 默认 false;true 时将新修订安全写入相同导出选项对应的笔记。无基线返回 409 `NOTE_UPDATE_BASELINE_MISSING`;正文改变返回 409 `NOTE_CONTENT_CONFLICT`。同修订重复调用保持幂等。 | | 本地模型 `disk_bytes` | 权重目录实际字节数;无法读取为 null。与下载 bytes/total 分开。 | | `GET /api/local-models/diagnostics` | `scope=application_last_200_attempts`,应用 SQLite 中最近 200 条诊断,包含调用及回退事件。未实际开始推理时不伪造 actual_device。 | | `GET /api/usage` | 增加 `audio_request_count`、可空的 `audio_seconds`、`audio_covered_requests`,适用原有时间/提供商/模型/来源过滤。次数按 transcription/speaker_matching 实际 attempt;未报告时长不估算。 | | `POST /api/providers/request-rules/validate` | 输入/输出 `{version:1, request_overrides:[...]}`;最多 100 条,复用请求扩展校验,不保存提供商。 | | `POST /api/providers/request-probe` | 输入 `{provider:ProviderCreateRequest, stream:boolean}`;固定短消息真实聊天推理,45 秒超时。成功返回 success/stream/model/message;空响应 422、供应商错误 502、超时 504。只使用 credential_id,不接收明文密钥。 | 请求预览新增 capability 选择(chat/embedding/transcription/speaker_matching),仍只返回隐藏正文的请求体。实际扩展字段是否被供应商接受,以推理响应为准。 # 聊天检索与 Markdown 工具补充(2026-09-06) `/api/chat` 在 `use_rag=true` 且 Provider 声明 `tool_calling` 时允许最多 3 轮只读补检索。SSE 事件类型不变,只有最终轮发送 `Done`;`Usage` 为模型轮次累计值。`Citation.number` 在同一回复内稳定,新增来源追加编号;候选来源不等于已引用来源,前端按正文 `[n]` 展示。`ToolCallEnd.data.status` 可为 `completed` 或 `failed`,表示执行结果而非参数接收完成。 工具目录新增 `markdown.catalog`、`markdown.compose`、`notes.patch_markdown`。`notes.read` 输出新增 `content_hash`;局部修改须携带 SHA-256 `expected_content_hash`、唯一匹配的 `old_text` 和替换值 `new_text`,沿用 `notes.write` 权限。详细边界及验证方法见 [聊天按需检索与 Markdown 工具](../development/聊天按需检索与Markdown工具.md)。 ## 工作区聊天与智能体委托补充(2026-09-06) - `ChatRequest.workspace_context`:可选 `{ file_path, content }`,传递当前编辑器快照,含未保存编辑。内容上限 200 万字符。 - `ChatRequest.allow_agent`:默认 `false`;开启且 Provider 支持工具调用时提供 `agent.create` 与 `agent.status`。每个回答最多创建一次,执行仍受原有工具白名单、预算和权限机制约束。 - `ChatMessage.workspace_context`:保存发送时的文件快照,列表和版本恢复接口返回同一数据;现有聊天记录接口供工作区浮窗与完整聊天页共享。 - `ToolCallEnd.data.result`:智能体工具返回 `{ run_id, status, output?, error? }`,消息工具记录以 JSON 字符串持久化此结果,客户端展示运行入口。 ## 聊天附件补充(2026-09-06) `/api/media/attachments` 新增允许 DOCX、PPTX、PPT、PNG、JPG/JPEG、WebP 后缀。聊天通过 `ChatRequest.attachments` 提交最多 8 个持久化附件 ID,并通过 `ChatMessage.attachments` 恢复记录。`image_fallback_tools` 最多两个注册工具名,服务端固定 MCP 优先、Plugin 次之,不接受任意命令或远程下载 URL。 内部模型 `Message.images` 使用有大小限制的 PNG/JPEG/WebP base64 data URI,Provider 适配器转换为各自原生协议。文档和音频提取为参考文本后才交给普通聊天,清除已解析的二进制附件标记,使文本上下文检测仍可工作。附件失败返回 `CHAT_ATTACHMENT_FAILED`,进度与截断提示使用 `ContextStatus`,不将失败附件当作已读取内容。 #### 回答版本的上下文快照(2026-09-07) `ChatMessage.context_captured` 为布尔值,旧记录默认 false。新 assistant 消息保存本次请求的 `workspace_context` 和 `attachments`,并设置 context_captured 为 true;此时 null 文件上下文和空附件列表都是明确快照。重新生成不覆盖原 user 消息的快照。客户端恢复旧记录时仅在 context_captured 为 false 时回退到对应父用户消息。 ## 2026-09-07 预览与静态资源增量契约 `POST /api/plots/function`:请求`{source, theme_id}`,source最多20000字符;响应`{result: {content,mime_type,width,height,warnings} | null, diagnostics: [{severity,code,message,line}], node_count}`。语法错误为200诊断、请求字段违规422。共享plot白名单解释器,不执行eval;每块16表达式、8000累计节点、并发2。主题映射当前六个Theme ID并提供CSS图表Token;未知主题回退light。 `ExportRequest`新增可选title(最多200字符)、assets(HTML/DOCX 最多64,PDF 不设数量上限);`source.file_path`为未保存快照中相对图片的基准位置,不能用于任意文件读。每个asset为`{kind: mermaid|math_block|math_inline|image, source_hash: 64位sha256十六进制, png_base64}`;摘要为strip后UTF-8源码(image为src)的SHA256。只接受有效PNG并重编码;HTML/DOCX 每图4百万像素、总16百万像素/8MiB,PDF 不使用这些预算。重复kind/hash或无效PNG返回422 EXPORT_ASSET_INVALID。摘要失配不替换当前节点,不接受客户端SVG/XML/URL执行。 未带资源的公式由 MathText 转换,仅解析 Vault 范围内 PNG/JPEG/WebP,拒绝远端与越界路径。HTML/DOCX 保留 512 字符/20 层/64 资源、单文件 2MB 的预算;PDF 不使用这些预算,也不限制导出源长度、产物字节数、函数图数量、表达式数量及累计复杂度。表达式白名单、有效图片校验、数值采样的收敛控制和队列并发调度仍保留。资源无法表示时保留源码/替代文字和 warning。 PDF 使用当前主题配色。`ExportOptions.palette` 可选,包含 page/surface/text/muted/code/border/accent 七个必填 `#RRGGBB` 值,由客户端在点击导出时冻结,用于自定义主题。未传 palette 时按 theme_id 解析六套内置配色,未知 ID 回退 light 并警告。PDF 页背景、正文、代码、表格、引用、链接、公式、Mermaid 和函数图均主题化;不会执行主题 CSS。DOCX 仍采用浅色打印样式;HTML 保留有限主题调色板。DOCX 图片为静态内容,不提供可编辑公式对象。 关闭导出窗口仅停止 UI 轮询,已发起的导出继续。主动取消在准备阶段停止提交;创建请求期间取消会等待任务 ID,调用后台取消接口并读取实际状态。 RAG `retrieval.fusion`接受rrf(默认)或weighted(归一化FTS/vector各50%),参数写入config_snapshot。真实本地单查询Embedding可命中有界进程缓存,provenance.query_embedding_cache为hit/miss;比较延迟须分别报告冷暖样本。HashEmbedding仍仅为确定性单元测试,不是当前生产检索模型。