ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

opencode 项目级 API 设计解析:让单实例同时驾驭多项目与 Git Worktree

opencode 项目级 API 设计解析:让单实例同时驾驭多项目与 Git Worktree opencode 项目级 API 设计解析让单实例同时驾驭多项目与 Git Worktree【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode在 opencode 的 V2 架构设计中specs/project.md 定义了一个关键目标让单个 OpenCode 实例同时为多个项目、以及每个项目的不同 worktree 运行会话Session。本文围绕该设计文档展开完整继承其 API 规划并结合 core 项目服务、项目数据表 与 worktree 服务 等源码说明这一设计的落地方式、当前实现状态与关键约束帮助读者理解 opencode 如何用一套 API 支撑多项目 多 worktree的工作负载。设计目标单实例多项目多 worktreespecs/project.md 开篇即给出核心诉求The goal is to let a single instance of OpenCode run sessions for multiple projects and different worktrees per project.这意味着不再以一个进程对应一个项目目录为前提。同一 OpenCode 进程需要维护多个项目的上下文并为每个项目持有独立的项目级状态如 VCS 仓库位置、沙箱目录、启动命令等。worktree 成为一等概念。一个项目例如一个 Git 仓库可以同时存在多个 worktree 检出每个 worktree 都可以承载独立的会话项目 ID 必须在这些 worktree 之间保持稳定。理解后续所有 API 的设计动机都要回到这两点项目是跨 worktree 的稳定标识而 session 挂在具体目录可能是某个 worktree上运行。完整 API 规划project 与 session 两级资源设计文档中的 API 规划将资源组织成两级项目级端点与嵌套在/project/:projectID/session/...下的会话级端点。下面按文档原文完整保留并分组说明。项目级端点GET /project - Project[] POST /project/init - ProjectGET /project列出当前实例可见的所有项目POST /project/init初始化一个项目并返回Project。会话生命周期端点GET /project/:projectID/session - Session[] GET /project/:projectID/session/:sessionID - Session POST /project/:projectID/session - Session { id?: string parentID?: string directory: string } DELETE /project/:projectID/session/:sessionID注意POST /project/:projectID/session的请求体directory是必填字段即会话绑定到一个具体目录可以是主检出也可以是某个 worktreeid与parentID可选支持调用方指定会话 ID 和父子会话关系。这正对应每个项目下不同 worktree 各自开会话的目标。会话控制端点POST /project/:projectID/session/:sessionID/init POST /project/:projectID/session/:sessionID/abort POST /project/:projectID/session/:sessionID/share DELETE /project/:projectID/session/:sessionID/share POST /project/:projectID/session/:sessionID/compact POST /project/:projectID/session/:sessionID/revert - Session POST /project/:projectID/session/:sessionID/unrevert - Session POST /project/:projectID/session/:sessionID/permission/:permissionID - Session这一组端点覆盖了会话的初始化、中止、共享开关、上下文压缩、撤销/恢复撤销以及对具体权限请求permissionID的裁决。消息端点GET /project/:projectID/session/:sessionID/message - { info: Message, parts: Part[] }[] GET /project/:projectID/session/:sessionID/message/:messageID - { info: Message, parts: Part[] } POST /project/:projectID/session/:sessionID/message - { info: Message, parts: Part[] }消息以{ info: Message, parts: Part[] }的形态传输即消息元信息 若干片段文本、工具调用、文件变更等。文件检索端点GET /project/:projectID/session/:sessionID/find/file - string[] GET /project/:projectID/session/:sessionID/file - { type: raw | patch, content: string } GET /project/:projectID/session/:sessionID/file/status - File[]其他与awkward端点文档还保留了两个全局端点并明确标注了一批作者自己认为是awkward别扭的端点POST /log // These are awkward GET /provider?directoryresolve path - Provider GET /config?directoryresolve path - Config // think only tui uses this? GET /project/:projectID/agent?directoryresolve path - Agent GET /project/:projectID/find/file?directoryresolve path - File这些端点需要显式传directory查询参数来定位项目/工作区上下文。文档作者用注释点出了设计上的不满足感例如config端点似乎只有 TUI 在用这属于设计阶段的自我审视也为后续演进如统一的路由中间件留下了动机。项目 ID 的解析从源码看项目如何被识别与保持唯一API 中反复出现的:projectID其背后是 packages/core/src/project.ts 中ProjectV2服务的resolve逻辑。从源码结构看解析优先级如下见 resolve 实现发现 Git 仓库对输入目录执行git.repo.discover若找不到仓库则返回全局 IDID.global目录回退到文件系统根。git remote 归一化哈希读取仓库originremote把 URL含userhost:path的 SCP 形式归一化为host/pathname小写、去.git后缀、file:协议视为无效再用ID.make(Hash.fast(git-remote:${normalized}))生成 ID。这样同一远程仓库的所有 worktree/克隆会解析出相同的项目 ID——这正是不同 worktree 归属同一项目的关键。仓库本地缓存读取.git公共目录下的opencode文件cached这是旧版项目服务持久化的 IDresolve会同时返回previous缓存值与新id便于旧数据迁移。根提交兜底若既无 remote 也无缓存则取仓库最早一次提交的 commit hash 作为 ID。此外Interface 还声明了一个commit方法源码注释明确说明它是临时桥接由 core 解析 ID由旧项目服务负责落库注释还提到旧服务应在新迁移完成后调用commit把 ID 写回仓库本地缓存即.git/opencode文件这与上面第 3 步的cached读取形成闭环。项目 ID 与 VCS 的 Schema 定义在 packages/core/src/project/schema.tsVcs目前是一个只含git变体的联合类型{ type: git, store: AbsolutePath }store指向 Git 公共目录。项目数据模型worktree 是一级字段packages/core/src/project/sql.ts 中的 SQLite 表结构印证了设计目标中的 worktree 语义export const ProjectTable sqliteTable(project, { id: text().$typeProjectSchema.ID().primaryKey(), worktree: DatabasePath.absoluteColumn().notNull(), // 项目主 worktree 绝对路径 vcs: text(), name: text(), ... sandboxes: DatabasePath.absoluteArrayColumn().notNull(), // 沙箱目录数组 commands: text({ mode: json }).$type{ start?: string }(), // 启动命令 }) export const ProjectDirectoryTable sqliteTable( project_directory, { project_id: ..., // 级联删除外键 directory: DatabasePath.absoluteColumn().notNull(), type: text().$typemain | root | git_worktree(), // 目录角色 strategy: text(), time_created: ..., }, (table) [primaryKey({ columns: [table.project_id, table.directory] })], )两个要点project.worktree是 NOT NULL 的绝对路径代表该项目的主检出project_directory表以(project_id, directory)为主键记录项目下的每个已知目录并用type区分main主检出、root与git_worktree派生 worktree。这与 API 设计中一个 projectID 下可存在多个会话目录完全对应worktree 不是另一个项目而是同一项目下的一类目录。当前实现project 路由与 worktree 实验端点设计文档描述的是 V2 目标形态而当前仓库中已存在与之对应的部分实现可据此判断落地进度。已实现的 project 路由packages/opencode/src/server/routes/instance/httpapi/groups/project.ts 定义了以下端点方法路径OpenAPI identifier说明GET/projectproject.list列出用 OpenCode 打开过的项目GET/project/currentproject.current获取当前活动项目POST/project/git/initproject.initGit为当前项目创建 git 仓库PATCH/project/:projectIDproject.update更新 name / icon / commandsGET/project/:projectID/directoriesproject.directories列出项目的已知本地绝对目录可以看到设计中的GET /project已实现而POST /project/init在当前形态下拆成了更具体的POST /project/git/init/:projectID/directories端点则直接对应project_directory表的查询能力经由 ProjectV2.directories。该路由组还挂了三类中间件InstanceContextMiddleware、WorkspaceRoutingMiddleware与Authorization见 路由组定义其中 workspace 路由中间件正是多项目/多工作区场景下把请求分发到正确实例上下文的机制。worktree 服务与实验端点packages/opencode/src/worktree/index.ts 实现了 worktree 的完整生命周期服务Info结构为{ name, branch?, directory }定义见 L23-L28create接受可选name与startCommand在项目启动命令之后追加运行的启动脚本非 Git 目录、名称生成失败、启动命令失败等均有独立错误类型NotGitError、NameGenerationFailedError、StartCommandFailedError等remove按目录移除 worktreereset将 worktree 分支重置回主默认分支。对应的 HTTP 端点位于 实验路由组GET /experimental/worktree # 列出项目所有 sandbox worktree POST /experimental/worktree # 创建 git worktree 并运行配置的启动脚本 DELETE /experimental/worktree # 移除 worktree 并删除其分支 POST /experimental/worktree/reset # 将 worktree 分支重置回主默认分支注意其 OpenAPI 描述措辞List allsandbox worktreesfor thecurrent project——这与project表中的sandboxes字段和project_directory表的git_worktree类型相互印证。worktree 相关能力目前仍在experimental命名空间下说明该部分属于渐进开放中。会话执行如何支撑多项目单实例项目 API 的最终服务对象是会话。specs/v2/session.md 中给出的执行路由解释了单实例如何为不同项目的会话正确分发执行SessionExecution.resume(sessionID) - SessionStore.get(sessionID) - LocationServiceMap.get(session.location) - SessionRunner.run({ sessionID, force? })从源码结构看SessionExecution与读侧SessionStore是进程全局的而SessionRunner、catalog、模型解析器、工具注册表、权限状态和文件系统都按Location即会话所属的项目位置/目录缓存任何一层都不接收 Session ID 作为入口参数。这意味着同一个进程内不同项目、不同 worktree 的会话可以并发运行而它们各自命中自己 Location 的服务实例互不串扰。会话创建时的directory如前文POST .../session请求体所要求就是会话定位到具体 worktree 的依据。设计状态小结哪些已落地哪些是规划综合 specs/project.md 与当前源码可以这样把握该设计的成熟度已落地GET /project列表、/project/current、/project/git/init、PATCH /project/:projectID、/project/:projectID/directories项目 ID 的多级解析remote 哈希 → 本地缓存 → 根提交project/project_directory数据表及 worktree 类型枚举worktree 的创建/删除/重置服务与实验端点基于 Location 的多会话执行路由。规划/部分落地文档中POST /project/init、/project/:projectID/session/...这一整套嵌套会话端点与当前仓库中以 Location/Session 独立路由为主的形态存在差异属于设计文档描述的目标 API 形态作者自注awkward的?directoryresolve path端点体现了该层 API 仍在收敛过程中。适用前提worktree 能力仅对 Git 仓库项目有效非 Git 目录会触发WorktreeNotGitErrorremote 归一化依赖originremote 可解析file:协议 remote 会退化为缓存或根提交策略experimental命名空间下的端点语义可能随迭代调整。通过这份设计文档与其源码实现的对照可以看到 opencode 用稳定的项目 ID 目录角色表 Location 级服务缓存 worktree 生命周期端点这条链路把一个实例服务多个项目、每个项目多 worktree从一句目标拆解成了可验证的 API 契约与数据结构。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表