Cherry Studio API 设计解析:桌面端 RESTful 接口规范是如何落地的

发布时间:2026/10/26 1:24:28
Cherry Studio API 设计解析:桌面端 RESTful 接口规范是如何落地的

Cherry Studio API 设计解析:桌面端 RESTful 接口规范是如何落地的

【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio

Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端。要讲清它的 API 设计与 RESTful 接口规范,得先认清一个约束:它不跑 HTTP 服务,"服务器"就是本机的 Electron 主进程。桌面客户端如何把知识库、文件、MCP(模型上下文协议,让 AI 调用外部工具的协议)这类本地能力安全地暴露给渲染进程,是它的接口架构要回答的核心问题。

桌面端接口架构:一次调用的完整链路

渲染进程只持有 UI,任何碰磁盘、进程、外部服务的请求都要跨进程边界进入主进程。

图中分工清晰:useChat() 发出消息,经 Electron IPC transport 进入主进程,由补全服务处理,结果流分块回推,最后由消息服务落 SQLite。

更关键的是,项目在同一套 IPC(进程间通信)传输上维护着两条独立的 API 通道:

维度DataApiIpcApi
范式REST,资源导向RPC,能力导向
寻址方式/knowledge-bases 加 HTTP 方法knowledge.search 式点分路由
副作用禁止,仅 SQLite 读写窗口、文件系统、进程、外部服务
错误形态HTTP 状态码字符串错误码

DataApi 只碰 SQLite 里的持久化业务数据,形态保证未来能"换一台真远程服务器";IpcApi 绑定本地主进程,永不远程。两条通道的路由表分别在 shared/data/api/schemas/ 与 shared/ipc/schemas/,主进程侧实现在 src/main/ipc/ 与 src/main/data/。

接口契约三要素:路径、方法、状态码

DataApi 的 URL 规则写在 schema 文件里:集合用复数名词加 kebab-case,层级用嵌套,CRUD 路径不带动词。/knowledge-bases/:id/items 是标准形态,POST /getTopics 不允许存在;路径参数本身含 / 时用贪婪参数 :id*,整张路由表仍受类型检查。

方法与默认状态码由服务端推断,handler 不用写:

方法幂等默认状态码语义边界
GET是200纯读,可重试可缓存
POST否201创建;接受异步任务时返回 202
PUT是200整体替换
PATCH是200部分更新;带动效的操作应走专属端点
DELETE是204(无体)返回数据时降为 200

错误处理是"码 + 状态"两层契约:每个错误码固定一个 HTTP 状态,能否自动重试一次声明:

错误码HTTP 状态自动重试
NOT_FOUND404否
VALIDATION_ERROR422否
CONCURRENT_MODIFICATION409否
RESOURCE_LOCKED423是
SERVICE_UNAVAILABLE / TIMEOUT503 / 504是

可重试错误只在白名单内,渲染层按 isRetryable 标志决定是否重试,而不是猜状态码。

核心接口拆解:五组场景怎么跑

应用管理:链路自检的入口

app.get_info 一次返回版本、打包状态与 logsPath 等真实目录路径,配合 app.cache_cleanup.inspect/run 两段式清缓存、app.relaunch 重启。这组路由不碰数据库,是接入前的链路自检探针。

知识库:读写分离是主线

场景是"建一次库、高频检索"。读走 DataApi:GET /knowledge-bases 支持 cursor + limit(上限 100),GET /knowledge-bases/:id/items 列文档,PATCH /knowledge-bases/:id/order 调排序;建库、检索、重建走 IpcApi,如 knowledge.search:

'knowledge.search': defineRoute({ input: z.strictObject({ baseId: z.string().trim().min(1), query: z.string().trim().min(1).max(1000) }), output: z.array(KnowledgeSearchResultSchema) })

检索是运行时计算而非持久化读取,不进 DataApi——进了会被自动重试与缓存,毫无意义。

文件:只读 SQL 视图加独立 FS 通道

GET /files/entries 一族端点是纯 SQL、固定响应形状,游标分页,ref-counts 批量聚合一次上限 500 个 id。碰真实文件系统的操作(stat、绝对路径解析、写文件)全部走独立的 file IPC 通道——这个切分保证 DataApi 的重试与缓存不触发副作用。

MCP:删服务器为什么走 RPC

/mcp-servers 增改查走 DataApi:GET 支持 id/isActive/type 过滤,PATCH 整体重排顺序。删除刻意不上 DataApi——移除服务器要同时杀掉运行时 stdio 进程,这是副作用,所以走 mcp.server.remove,由主进程统一清进程、删行。工具调用进度经 mcp.tool.call_progress 事件推回渲染层。

记忆:三种机制各走各的通道

Cherry Studio 的记忆不是单一 API,而是三种并行机制:Agent 文件记忆(SOUL.md 等四个文件,按 Agent 隔离,Agent 用文件工具自主读写)、知识库(Assistant 与 Agent 共用)、MCP 记忆(内置 @cherry/memory 服务器,知识图谱形态)。接口层不发明"记忆协议",每种机制各走既有通道,没有新传输。

统一信封:请求、响应、错误共用一套形状

DataApi 的请求信封是扁平对象,桌面链路里的"头"由 id 与 metadata 承担:

export interface DataRequest { id: string // 请求 ID,响应回显 method: HttpMethod // GET | POST | PUT | PATCH | DELETE path: string // 如 '/knowledge-bases' params?: Record<string, any> body?: unknown headers?: Record<string, string> metadata?: { timestamp: number; spanContext?: unknown } }

响应与之对称:id 回显请求 ID,status 是 HTTP 状态,data 与 error 取其一,metadata 带时间戳与缓存信息。错误是序列化结构而非抛出的堆栈——堆栈不过 IPC,靠主进程日志:

{ "code": "NOT_FOUND", "message": "Topic with id 'abc123' not found", "status": 404, "details": { "resource": "Topic", "id": "abc123" }, "requestContext": { "requestId": "req_1", "path": "/topics/abc123", "method": "GET" } }

两处细节:VALIDATION_ERROR 的 details.fieldErrors 是"字段→消息数组"映射,前端可直接定位报错位置;IpcApi 则返回 { ok, data } / { ok: false, error } 而非 rejected Promise——Electron 的 invoke 在 reject 时只保留 message,code 与 details 会丢,所以这条通道永不 reject。

认证与安全防护:信任边界在进程,不在 Token

桌面应用没有用户体系,"认证"转译为"信任源校验"。IpcApi 单入口有两道门:来源校验——只信顶层 frame,webview 内容直接拒绝,生产环境 frame URL 必须是应用包内的 file: 路径,一份能摸到 ipcRenderer 的下载 HTML 过不去;输入校验——渲染到主进程的入参全部先经 zod parse,主到渲染的事件是纯类型、不做运行时校验,因为事件由主进程自己构造,校验换不来安全。

handler 看到的调用方身份只有 IpcContext.senderId 窗口 ID,由主进程从真实 sender 推导,禁止从 input 携带——否则渲染层可伪造窗口 ID 操作别的窗口。DTO 是第二道防线:写入体由实体 schema 白名单 .pick() 派生,z.strictObject 拒绝多余字段,mass assignment 在边界被挡。这里没有 Bearer Token 与 RBAC,权限模型退化为"通道收窄":路由表里声明的路由才存在,服务不能随手注册新通道。

稳定性工程:分页、版本策略与可观测

分页提供 offset(page + limit)与 cursor(cursor + limit)两种,知识库与文件列表都走 cursor——keyset 保证并发插入不错行不丢行。过滤用字段直名,排序用 sortBy + sortOrder,批量上限显式(ref-counts 500 个 id)。字段选择不靠 fields 参数,靠"固定形状 + 独立通道":派生数据各有专属端点。

版本不进 /v1 路径,进 schema 层:路由表是 TS 类型,编译器强制"schema 声明的每个路由必须有 handler",增删路由是可见的类型变更;错误码是稳定字符串契约,前端按 code 等值分支,不按 message 文本。

可观测复用请求信封:metadata.spanContext 携带 OpenTelemetry 上下文,AI 调用在 topic 的 traceId 下生成 span 树(chat.turn 为根),日志本地 JSONL 投影、不远程上报。跨窗口一致性走 contenteditable="false">【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考