十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

KiloCode 多项目 API 设计:单实例如何同时服务多个项目与 Worktree

KiloCode 多项目 API 设计:单实例如何同时服务多个项目与 Worktree KiloCode 多项目 API 设计单实例如何同时服务多个项目与 Worktree【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode本文基于仓库中的设计规范 specs/project.md讲解 Kiloopencode 包如何设计 HTTP API让同一个服务端实例为多个项目、每个项目的多个 worktree 并发运行会话。读完本篇你会理解规范中列出的完整端点清单、请求通过directory参数路由到正确项目实例的中间件机制以及Project服务在解析、落库与迁移上的源码级实现。一、设计目标一个实例多项目多 worktreespecs/project.md 开篇即给出核心目标The goal is to let a single instance of OpenCode run sessions for multiple projects and different worktrees per project.即启动一个 Kilo/opencode 服务进程后客户端不必为每个项目分别拉起服务而是通过 API 参数声明“当前要操作哪个项目、哪个工作目录”由服务端在内存中维护每个目录对应的运行时实例实例上下文。这带来两个关键概念项目Project以版本库主要是 git 仓库为边界的逻辑单元拥有唯一id、主worktree路径、VCS 类型、图标、自定义命令等属性Worktree / Sandbox同一个项目的多个检出目录。规范标题里 “different worktrees per project” 就是指一个项目可以同时有多个 checkout会话可以落在其中任意一个目录。从源码结构看这两个概念直接对应Project服务的接口定义见 project.tsreadonly fromDirectory: (directory: string) Effect.Effect{ project: Info; sandbox: string } readonly discover: (input: Info) Effect.Effectvoid readonly list: () Effect.EffectInfo[] readonly get: (id: ProjectV2.ID) Effect.EffectInfo | undefined readonly update: (input: UpdateInput) Effect.EffectInfo, NotFoundError readonly initGit: (input: { directory: string; project: Info }) Effect.EffectInfo readonly sandboxes: (id: ProjectV2.ID) Effect.Effectstring[] readonly addSandbox: (id: ProjectV2.ID, directory: string) Effect.Effectvoid readonly removeSandbox: (id: ProjectV2.ID, directory: string) Effect.Effectvoid其中fromDirectory是核心入口给定任意一个目录返回它所属的project以及该目录下实际生效的沙箱目录sandbox。sandboxes字段持久化在ProjectTable.sandboxes就是“一个项目多个 worktree”的落地形式——凡是归属于该项目、但不等于主 worktree 的检出目录都会被追加进sandboxes列表project.ts。二、规范中的完整端点清单specs/project.md 给出的 API 草案如下原样继承后文逐一说明其在当前仓库中的演进状态GET /project - 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/: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 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[] } POST /project/:projectID/session/:sessionID/revert - Session POST /project/:projectID/session/:sessionID/unrevert - Session POST /project/:projectID/session/:sessionID/permission/:permissionID - Session 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[] POST /log // These are awkward GET /provider?directoryresolve path - Provider GET /config?directoryresolve path - Config GET /project/:projectID/agent?directoryresolve path - Agent GET /project/:projectID/find/file?directoryresolve path - File规范的设计意图很清晰会话及其一切子资源消息、文件、权限、共享、压缩都挂在projectID路径下用 URL 显式表达“会话属于哪个项目”创建会话的 body 里还带有directory字段用于指定会话落在该项目的哪个 worktree。规范作者自己也标注了尾部四个?directoryresolve path端点 “are awkward”——因为directory是路径参数之外的查询参数语义上不如直接放在 URL 里自然。当前仓库的最终取舍下文第三节详述恰恰是反过来的把directory统一提升为全局查询参数会话则收敛到独立的/session根路径。三、directory参数请求如何被路由到正确的项目当前实现中所有需要“知道自己在哪个目录”的端点都继承同一套查询参数。定义见 workspace-routing.tsexport const WorkspaceRoutingQueryFields { directory: Schema.optional(Schema.String), workspace: Schema.optional(Schema.String), } export const WorkspaceRoutingQuery Schema.Struct(WorkspaceRoutingQueryFields)WorkspaceRoutingMiddleware是路由的第一层。它先为请求计算“计划”RequestPlan本地执行还是代理到远端 workspace本地场景下工作目录的解析优先级为workspace-routing.tsURL 查询参数?directory请求头x-kilo-directory进程当前工作目录process.cwd()兜底。此外还有两个与“多项目/多工作区”直接相关的机制workspace参数与远端代理请求携带workspace参数且对应 workspace 的目标是 remote 时中间件不会本地执行而是把请求含 WebSocket代理到远端目标地址并在同步栅栏Fence上等待数据一致后才放行见 workspace-routing.tsfork 目录覆盖Kilo 的定制源码中以kilocode_change注释标记当请求是 fork 会话且带有显式目标目录例如 fork 到某个 worktree时目标目录优先于源会话的目录继承见 workspace-routing.ts。也就是说规范里被批评为 awkward 的?directoryresolve path模式被保留了下来但不再零散地附着在个别端点上而是成为整个实例路由的统一约定项目类端点挂WorkspaceRoutingQuery会话类端点ListQuery、MessagesQuery等见 session.ts同样展开这套字段。规范中的GET /provider?directory...、GET /config?directory...对应的 provider、config 端点也依旧存在于同一 HttpApi 分组目录下groups/provider.ts、groups/config.ts。四、InstanceContextMiddleware 与 InstanceStore按目录维护实例生命周期目录解析完成后instance-context.ts 负责把目录变成完整的运行时上下文const route yield* WorkspaceRouteContext const ctx yield* store.load({ directory: decode(route.directory) }) return yield* effect.pipe( Effect.provideService(InstanceRef, ctx), Effect.provideService(WorkspaceRef, route.workspaceID), )这里InstanceStoreinstance-store.ts对外暴露load / reload / dispose / disposeDirectory / disposeAll / provide六个操作内部用Mapdirectory, Entry做实例缓存。load未命中时执行bootinstance-store.ts若调用方已显式给出project与worktree例如 reload 场景直接使用否则调用project.fromDirectory(input.directory)见第五节用返回的sandbox作为该实例的 worktree随后在InstanceRef作用域内运行InstanceBootstrapKilo 定制把 bootstrap 放进 Instance ALS 中执行保证 fork 出的子逻辑能看到Instance.directory整个 boot 用Deferred收敛同一目录的并发请求共享同一次启动且无论成功还是失败含 fiber 被中断缓存项都会被清理或正确完成避免“卡死在启动中导致后续请求永远挂起”的问题源码注释明确说明了这一动机。实例被回收时InstanceStore会运行该目录注册的 disposer 并广播server.instance.disposed事件instance-store.ts。至此“单实例多项目”的内存模型完整成立一个进程 一个 InstanceStore 若干以目录为键的实例上下文每个上下文内持有该目录的项目信息与各类服务状态。实例路由的中间件链顺序在 groups/project.ts 中固定为.middleware(InstanceContextMiddleware) .middleware(WorkspaceRoutingMiddleware) .middleware(Authorization)即先解析 workspace/目录再装载实例上下文最后做鉴权。五、Project 服务目录解析、落库与 ID 迁移Project.fromDirectory是“任意目录 → 项目”的核心实现project.ts流程如下解析projectV2.resolve(absolutePath)向上查找版本库根得到{ id, directory, vcs, previous }全局无 VCS且非项目 id 时 worktree 取/ID 迁移若解析出新的项目 id 与历史 id 不同典型场景仓库根变化或首次识别到 VCSmigrateProjectId在单个数据库事务中复制项目行、清空ProjectDirectoryTable旧目录列表、把SessionTable与WorkspaceTable中挂旧 id 的记录整体改挂新 id最后删除旧行project.ts。这保证了“同一项目 ID 变化时历史会话与工作区不会丢失归属”沙箱归集当前目录若不属于ProjectV2.ID.global、不等于主 worktree、且不在sandboxes中就追加进去随后过滤掉磁盘上已不存在的沙箱路径再upsert回ProjectTable会话归属修正将project_id为global且directory匹配当前目录的会话批量改挂到新项目project.ts目录登记与事件saveProjectDirectory写入项目-目录关联表global 项目跳过emitUpdated通过GlobalBus广播project.updated事件project.ts前端/插件即可感知项目信息变化VCS 快照git 项目还会调用projectV2.commit更新 VCS 存储project.ts。返回的{ project, sandbox }中sandbox的选择规则值得注意有 VCS 时取实际检出目录无 VCS 时取 worktreeproject.ts。其余接口要点initGitproject.ts先确认系统装有 gitwhich(git)执行git init --quiet再重新走一遍fromDirectory返回刷新后的项目信息。这对应规范中的POST /project/init当前 HttpApi 中端点名为project.initGit路径POST /project/git/initupdate修改name、icon、commands未命中返回Project.NotFoundErrorproject.ts图标发现开启experimentalIconDiscovery标志后discover会在 worktree 内 glob**/favicon.{ico,png,svg,jpg,jpeg,webp}取路径最短的一个转成 data URL 写回项目图标project.ts。六、当前实验性 HttpApi 中的落地形态规范草案按“项目挂会话”的方式组织路由当前仓库的实验性 HttpApipackages/opencode/src/server/routes/instance/httpapi/落地时做了结构调整项目端点groups/project.ts端点规范对应说明GET /project→Project[]GET /project列出所有已打开的项目project.listGET /project/current无演进新增返回当前实例上下文中的项目project.currentPOST /project/git/initPOST /project/init初始化 git 并返回刷新后的项目PATCH /project/:projectID演进新增更新name/icon/commandsGET /project/:projectID/directories演进新增列出该项目已知的所有本地绝对目录处理逻辑在 handlers/project.ts 中。其中initGit有一个细节值得注意当 git 初始化导致项目id、vcs或worktree发生变化时handler 会调用markInstanceForReload标记当前实例需要重载handlers/project.ts让后续请求拿到刷新后的实例上下文。会话端点则从/project/:projectID/session/*收敛为独立的/session根路径session.ts规范中列出的会话子资源大多可以找到对应项规范端点当前对应POST /project/:projectID/sessionbody 含directoryPOST /session目录由WorkspaceRoutingQuery携带GET/DELETE .../session/:sessionIDGET /session/:sessionID、DELETE /session/:sessionID.../abort、.../sharePOST/DELETEPOST /session/:sessionID/abort、POST /session/:sessionID/share.../messageGET 列表 / GET 单条 / POST 提交GET /session/:sessionID/message、GET .../message/:messageID、POST /session/:sessionID/message.../revert、.../unrevertPOST /session/:sessionID/revert、POST /session/:sessionID/unrevert.../permission/:permissionIDPOST /session/:sessionID/permissions/:permissionID会话列表GET /session查询参数支持scope: project按项目过滤session.ts规范里没有、当前实现中额外提供的相关端点包括fork、summarize对应规范compact语义的演进、command、shell、todo、diff、status等abort还新增了scope: session | tree参数可选择只停当前会话还是连同子代理整树停止session.ts。文件类端点规范中的find/file、file、file/status由同一 HttpApi 分组目录下的 file 端点组承担groups/file.ts。七、实践要点如何用 API 操作多项目结合前述机制调用方在操作不同项目时的实际用法是对需要区分项目的请求统一携带?directoryabsolute path或x-kilo-directory请求头服务端据此解析项目并装载对应实例上下文目录缺省时回退到进程 cwd需要跨 workspace含远端同步的工作区操作时追加?workspaceworkspaceID远端目标由中间件自动代理会话相关操作走/session/*端点同一 URL 模板可服务任意项目区分维度完全由directory参数表达按项目筛选会话时用scopeproject项目元数据名称、图标、命令用PATCH /project/:projectID更新目录与项目的关联关系可用GET /project/:projectID/directories查询所有事件广播project.updated、server.instance.disposed等都带有project与directory维度客户端可按项目订阅过滤。需要注意的适用前提以上HttpApi路由在源码中明确标注为 Experimental 表面groups/project.ts 的 OpenAPI 描述为 “Experimental HttpApi surface for selected instance routes”端点名称与路径可能随版本演进调整集成时建议以仓库中当前的分组定义与 OpenAPI 注解为准。参考文件设计规范specs/project.md项目端点定义与 OpenAPI 注解groups/project.ts项目端点处理逻辑handlers/project.ts目录/workspace 路由中间件middleware/workspace-routing.ts实例上下文中间件middleware/instance-context.ts项目服务解析、迁移、initGit、图标发现project/project.ts实例缓存与生命周期project/instance-store.ts会话端点定义groups/session.ts【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表