CodeCompanion.nvim 的 Agent Client Protocol (ACP) 支持:会话、工具与权限的完整技术解析
【免费下载链接】codecompanion.nvim✨ AI Coding, Vim Style项目地址: https://gitcode.com/GitHub_Trending/co/codecompanion.nvim
ACP(Agent Client Protocol)是 CodeCompanion.nvim 连接外部编码 Agent(如 Claude Code、Codex、Gemini CLI)的开放协议通道,它让 Neovim 用户可以直接在聊天缓冲区中驱动独立的 AI 编码 Agent。本文以仓库中的 doc/agent-client-protocol.md 为主干,结合lua/codecompanion/acp/与lua/codecompanion/interactions/chat/acp/下的源码实现,系统讲解 CodeCompanion 对 ACP 的能力覆盖、有状态会话管理、权限审批、会话恢复与模型切换机制,以及当前协议版本的已知限制,帮助你在实际配置与二次开发中快速定位所需能力。
ACP 是什么,CodeCompanion 如何接入
ACP 是一个开放的通信标准,用于规范“客户端(Client)”与“AI Agent”之间的结构化交互。CodeCompanion 作为客户端,通过 ACP 与独立的编码 Agent 进程通信,从而获得会话管理、文件系统操作、工具执行和权限处理等能力,这些能力全部在 Neovim 内部完成,无需离开编辑器。
从源码结构看,ACP 相关的实现被组织为两个层次:
- 协议核心层:lua/codecompanion/acp/init.lua 中的
Connection类负责进程生命周期、JSON-RPC 收发、会话建立与认证;lua/codecompanion/acp/methods.lua 集中定义了全部协议方法名。 - 交互层:lua/codecompanion/interactions/chat/acp/ 下的
handler.lua、commands.lua、fs.lua、request_permission.lua等模块负责把协议事件渲染到聊天缓冲区、管理 Agent 动态注册的斜杠命令、执行文件读写与权限审批 UI。
功能实现总览:哪些能力已落地
文档给出了一张完整的能力对照表,CodeCompanion 对 ACP 规范的支持覆盖了核心协议、认证、内容类型、文件系统、MCP 集成、权限、会话管理与工具调用:
| 功能类别 | 支持情况 | 说明 |
|---|---|---|
| 核心协议 | ✅ | JSON-RPC 2.0、流式响应、消息缓冲 |
| 认证 | ✅ | 多种认证方式、适配器级钩子 |
| 内容类型 | ✅ | 文本、图片、嵌入资源 |
| 文件系统 | ✅ | 支持行范围的文件读写 |
| MCP 集成 | ✅ | Stdio、HTTP 与 SSE 传输 |
| 权限 | ✅ | 带 diff 预览的交互式审批 UI |
| 会话管理 | ✅ | 创建、列出、加载与恢复会话,含状态追踪 |
| 会话模式 | ✅ | 模式切换 |
| 会话模型 | ✅ | 选择指定模型 |
| 工具调用 | ✅ | 内容块、文件 diff、状态更新 |
| Agent 计划 | ❌ | Agent 执行计划的可视化展示 |
| 终端操作 | ❌ | Agent 访问 Neovim 终端 |
其中“消息缓冲”在实现层面体现为Connection内的jsonrpc.LineBuffer:由于 JSON-RPC 的消息边界不一定与 I/O 边界对齐,lua/codecompanion/acp/init.lua 中的buffer_stdout_and_dispatch会先把 stdout 数据压入行缓冲,再逐行派发给handle_rpc_message解析,这是保证长流式响应稳定解析的关键。
支持的适配器与客户端能力声明
适配器生态
CodeCompanion 为 ACP 兼容的 CLI 工具预置了众多适配器,源码位于 lua/codecompanion/adapters/acp/,包括claude_code、codex、gemini_cli、copilot_acp、cursor_cli、opencode、cagent、cline_cli、goose、kilocode、kimi_cli、kiro、mistral_vibe、auggie_cli等。具体安装与配置方式见 doc/configuration/adapters-acp.md,那里给出了每种工具的前置安装步骤、认证方式与自定义命令示例。
每个 ACP 适配器都是一个结构化的 Lua 表,以 claude_code.lua 为例,它声明了:
commands.default:启动 Agent 的进程命令(如claude-agent-acp);env:需要注入的环境变量(如CLAUDE_CODE_OAUTH_TOKEN);parameters:初始化握手参数,包括protocolVersion = 1与clientCapabilities;handlers:setup、auth、form_messages、on_exit等生命周期钩子。
客户端能力声明
CodeCompanion 通过初始化握手向 ACP Agent 声明自身能力,文档给出了完整的声明内容:
{ fs = { readTextFile = true, -- Read files with optional line ranges writeTextFile = true -- Write/create files }, terminal = false -- Terminal operations not supported }这段声明与源码中每个 ACP 适配器的parameters.clientCapabilities一致(见 claude_code.lua 第 33-35 行):文件系统读写能力被开放,而终端能力被显式关闭,Agent 无法获得 Neovim 终端访问权。
内容类型:能收发什么
文档用一张表明确了 ACP 通道的内容类型支持:
| 内容类型 | 发送给 Agent | 从 Agent 接收 |
|---|---|---|
| 文本 | ✅ | ✅ |
| 文件 Diff | 不适用 | ✅ |
| 图片 | ✅ | ❌ |
| 音频 | ❌ | ❌ |
| 嵌入资源 | ❌ | ❌ |
在实现层面,内容类型的解析由 lua/codecompanion/acp/prompt_builder.lua 的extract_text函数完成:它根据内容块的type字段区分text、resource_link、resource、image、audio,把非文本块转换为可展示的占位符(如[image]、[resource: uri]),再交给聊天缓冲区的渲染层。
图片发送则由 lua/codecompanion/adapters/acp/helpers.lua 的form_messages处理:只有当 Agent 通过promptCapabilities声明支持图片时才会生成image内容块,否则会记录一条警告日志。这也解释了为何 Agent 到客户端的图片(如截图回传)当前不被支持。
有状态会话:与 HTTP 适配器的本质差异
文档强调了一个关键设计差异:HTTP 适配器是无状态的(每次请求都携带完整对话历史),而ACP 适配器是有状态的——对话上下文由 Agent 维护,CodeCompanion 每次提示只发送新增消息,并通过session_id在整个会话生命周期内追踪状态。
这一点在 helpers.lua 的form_messages中得到印证:它只保留role == user且_meta.sent未被标记的消息,即“Agent 还没看过的消息”,从而避免重复发送历史。相应地,连接对象Connection维护_initialized、_authenticated、session_id等状态标志,并通过is_connected()/is_ready()暴露给上层判断当前连接状态。
会话建立流程
从 lua/codecompanion/acp/init.lua 的调用链可以还原完整的会话建立流程:
connect_and_authenticate():启动 Agent 子进程(start_agent_process),发送initialize握手请求,校验双方protocolVersion,随后进入认证阶段;_authenticate():优先调用适配器自定义的handlers.auth钩子(如 Claude Code 的 OAuth token 注入);否则读取 Agent 广播的authMethods,匹配配置的auth_method并发送authenticate请求;_establish_session():根据session_id是否存在以及 Agent 是否支持loadSession,决定发送session/load还是session/new。会话建立时还会携带cwd与mcpServers参数。
认证顺序在源码中表现为“适配器钩子优先、协议认证兜底”:自定义钩子成功即完成认证,否则才尝试 Agent 广播的认证方法。若配置的auth_method未被 Agent 广播,连接会直接失败并给出可用的方法列表。
文件上下文处理:重新读取而非复用缓冲区
文档指出:当把文件作为嵌入资源发送给 Agent 时,CodeCompanion 会重新读取文件内容,而不是使用聊天缓冲区中的表示。这样做的目的是避免 HTTP 适配器专用的<attachment>标签出现在 ACP 消息中——该类标签对 LLM 适配器有意义,对 ACP Agent 则没有意义。
实际发送时(见 helpers.lua),文件与缓冲区上下文被转换为纯文本形式:
Sharing the following file as context: <path>而 Agent 侧的主动文件读取则走fs/read_text_file/fs/write_text_file协议方法,实现在 lua/codecompanion/interactions/chat/acp/fs.lua:
- 读取:支持
line与limit参数实现行范围切片,若文件不存在则返回ENOENT,CodeCompanion 会把它当作空内容返回给 Agent,从而允许 Agent 直接创建新文件; - 写入:优先写入已打开的缓冲区(保持 Neovim 内的同步),否则直接写盘,成功后触发
FileEdited事件供其他模块响应。
动态斜杠命令:Agent 自主宣传能力
ACP Agent 可以动态宣传自己的斜杠命令。CodeCompanion 在聊天缓冲区中通过\command访问这些命令,并在发送提示前自动将其转换为/command格式。
底层实现位于 lua/codecompanion/interactions/chat/acp/commands.lua:
- Agent 通过
session/update通知中的available_commands_update推送命令列表(由 acp/init.lua 的handle_available_commands_update接收); - 命令按
session_id存储,并通过link_buffer_to_session把聊天缓冲区与会话绑定,实现按缓冲区查询可用命令; - 命令更新后会触发
CodeCompanionACPCommandsUpdate用户事件,供补全插件(如 blink、cmp 等)刷新候选列表; - 缓冲区关闭时通过
CodeCompanionChatClosed自动解绑,避免内存泄漏。
会话恢复:/resume的完整链路
如果 Agent 支持session/list能力,就可以在新的聊天缓冲区中用/resume斜杠命令恢复历史会话。文档描述的流程是:调用session/list发现历史会话,再调用session/load把所选会话的对话历史恢复到聊天缓冲区。
对应实现见 lua/codecompanion/interactions/chat/slash_commands/builtin/resume.lua,其中有两个重要细节:
- 能力预检:命令的
enabled钩子会先检查can_list_sessions()与can_load_session(),二者分别对应 Agent 广播的sessionCapabilities.list与loadSession能力(见 acp/init.lua 第 282-295 行),不满足时命令直接禁用并给出原因提示; - 分页拉取:
session_list支持max_sessions上限(默认 500)与nextCursor游标分页,并且session/load加载过程中收到的session/update通知会被收集,用于在加载完成后重放渲染出完整历史(restore_session),同时恢复会话标题并触发ACPChatRestored事件。
session/list与session/load的方法名定义可参见 lua/codecompanion/acp/methods.lua。
模型选择:session/set_model与配置项联动
CodeCompanion 实现了session/set_model方法,允许为当前会话切换模型。文档特别注明:该方法不属于官方 ACP 规范(官方规范中使用的是session/set_config_option),因此在未来版本中可能变化。
从 acp/init.lua 的实现看,模型选择依赖 Agent 在会话开始时通过configOptions广播的配置项(_apply_config_options):
get_models():从category == "model"且type == "select"的配置项中提取当前模型 ID 与可选模型列表;set_model(model_id):本质上是把set_config_option(opt.id, model_id)包装了一层;flatten_config_options:处理 ACP 规范允许的分组选项(sessionconfigselectgroup),把分组内的值展开为扁平列表。
模型还可以通过配置直接预置,见 doc/configuration/adapters-acp.md 的 “Setting Default Session Config Options” 一节,支持三种方式:
-- 方式一:在 interactions 中直接指定 require("codecompanion").setup({ interactions = { chat = { adapter = { name = "codex", model = "gpt-5.4", }, }, }, }) -- 方式二:在适配器 defaults 中以静态字符串指定 -- 方式三:在适配器 defaults 中以函数动态计算指定 require("codecompanion").setup({ adapters = { acp = { codex = function() return require("codecompanion.adapters").extend("codex", { defaults = { session_config_options = { model = function(self) return "gpt-5.4" end, mode = "Full Access", -- 其他会话配置项 thought_level = "Xhigh", }, }, }) end, }, }, })若想查看某个适配器具体支持哪些会话配置项,可以打开聊天缓冲区的 调试窗口(Debug Window)查看 Agent 广播的configOptions原始数据。
权限审批:带 diff 预览的交互式 UI
权限处理是 ACP 交互中最具实战价值的环节。当 Agent 需要执行工具调用时,会发送session/request_permission请求,CodeCompanion 通过 lua/codecompanion/interactions/chat/acp/request_permission.lua 呈现审批界面。
其核心机制包括:
- 选项映射:将 ACP 的权限选项种类(
allow_once、allow_always、reject_once、reject_always)映射到 CodeCompanion 统一的审批快捷键(接受、始终接受、拒绝、取消),键位定义来自 lua/codecompanion/interactions/chat/tools/labels.lua; - diff 预览:如果工具调用的内容块类型为
diff(且 diff 非空),审批界面会优先展示旧文本/新文本的对比,支持q关闭、Next/Prev在 hunk 间跳转、以及按对应的审批键直接响应; - 响应协议:所有审批最终通过
send_result(id, { outcome = ... })回给 Agent,selected携带所选optionId,取消则返回cancelled,这与 prompt_builder.lua 中handle_permission_request的响应构造保持一致。
生命周期与清理:VimLeavePre兜底
CodeCompanion 通过监听 Neovim 的VimLeavePre自动命令保证与 ACP Agent 的干净断开(见 acp/init.lua 第 149-156 行):即使 Neovim 异常退出,也会调用disconnect()向 Agent 进程发送SIGTERM,确保子进程被正确终止。
进程意外退出时,handle_process_exit会做完整的收尾:触发适配器的on_exit钩子、释放所有挂起的异步回调(防止协程悬挂)、重置认证/初始化/会话状态,并把活动提示标记为canceled。
协议版本协商
CodeCompanion 目前实现的是ACP Protocol Version 1(protocolVersion = 1)。协议版本在初始化阶段协商:如果 Agent 选择了不同的版本,CodeCompanion 会记录一条警告日志,但仍会继续运行,并遵循 Agent 选择的版本工作。对应源码在connect_and_authenticate中的版本比对逻辑(见 acp/init.lua 第 136-144 行)。
当前限制
文档明确列出了三个尚未实现的能力,配置前值得留意:
- 终端操作:
terminal/*系列方法(terminal/create、terminal/output、terminal/release等)均未实现,CodeCompanion 不会向 Agent 声明终端能力(客户端能力中terminal = false); - Agent 计划渲染:来自 Agent 的 Plan 更新会被接收并记录日志(
handle_session_update中的plan分支),但目前不会在聊天缓冲区 UI 中渲染可视化执行计划; - 音频内容:音频既不能发送也不能接收。
相关资源
- Agent Client Protocol 官方规范:ACP 的完整协议文档
- 配置 ACP 适配器:各 CLI Agent 的安装与配置步骤、自定义适配器写法
- 在聊天中使用 Agent 与工具:聊天缓冲区中与 Agent 交互的日常用法
- MCP 配置:如何定义 MCP 服务器并通过
mcpServers = "inherit_from_config"让 ACP Agent 自动继承 - 核心实现源码:lua/codecompanion/acp/init.lua、lua/codecompanion/acp/prompt_builder.lua、lua/codecompanion/interactions/chat/acp/
- 测试用例:
tests/acp/与tests/adapters/acp/目录下的test_acp.lua、test_prompt_builder.lua等覆盖了连接、消息解析与提示构建等核心路径,可作为二次开发的行为参考
【免费下载链接】codecompanion.nvim✨ AI Coding, Vim Style项目地址: https://gitcode.com/GitHub_Trending/co/codecompanion.nvim
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考