CodeCompanion Adapter 架构全解析:Handler 结构、向后兼容与 HTTP 客户端实现
CodeCompanion Adapter 架构全解析Handler 结构、向后兼容与 HTTP 客户端实现【免费下载链接】codecompanion.nvim✨ AI Coding, Vim Style项目地址: https://gitcode.com/GitHub_Trending/co/codecompanion.nvim导读CodeCompanion 通过 Adapter 屏蔽不同 LLM/Agent 提供商的差异使 ChatGPT、Anthropic、OpenAI、Ollama、Gemini 等数十种后端能以统一方式接入聊天缓冲区。本文以仓库文档 .codecompanion/adapters/adapters.md 为骨架结合 lua/codecompanion/adapters 目录下的真实实现系统讲解 Adapter 的嵌套 Handler 结构、规范工具结果格式、call_handler()的向后兼容机制、HTTP 客户端 http.lua 的驱动流程并通过 OpenAI Responses 与 Anthropic 两个实例对比新旧两种 Handler 写法。读完你将能够理解现有内置 Adapter 的运行原理并具备自定义、扩展和调试 Adapter 的完整知识。一、Adapter 是什么连接 LLM 与 Agent 的桥梁在 CodeCompanion 中Adapter 用于连接 LLM 或 Agent。HTTP 类型的 Adapter 包含两类核心内容LLM 端点endpoint选项请求 URL、请求头、环境变量、额外的 curl 参数等schema 参数定义对model、temperature、top_k、top_p等模型采样属性的声明式配置用户可以在聊天缓冲区中直接调整这些参数。同时HTTP Adapter 还包含一组handler 函数它们定义了发送给 LLM 的消息应该如何格式化例如将聊天缓冲区中的消息转换为 Anthropic 的tool_use/tool_result块以及 LLM 返回的输出应如何被接收并展示在聊天缓冲区中。所有 Adapter 都定义在 lua/codecompanion/adapters 目录下其中lua/codecompanion/adapters/http 存放 HTTP 类 Adapteranthropic.lua、openai.lua、openai_responses.lua、gemini.lua、ollama.lua、deepseek.lua、mistral.lua等lua/codecompanion/adapters/acp 存放 ACPAgent Client Protocol类 Adapterclaude_code.lua、codex.lua、gemini_cli.lua、opencode.lua等。从 adapters/init.lua 的adapter_type()函数可以看出一个 Adapter 的类型判定遵循以下顺序未指定时取配置项config.interactions.chat.adapter的默认值若传入的是带type字段的 table则直接使用该类型否则按名称在config.adapters.acp与config.adapters.http两张表中查找命中即返回对应类型兜底类型为http。在 config.lua 中可以看到内置的完整注册表HTTP 侧包括anthropic、azure_openai、copilot、deepseek、gemini、githubmodels、huggingface、kimi、novita、mistral、ollama、openai、openai_responses、openrouter、xai、jina、tavilyACP 侧包括auggie_cli、cagent、claude_code、cline_cli、codex、cursor_cli、copilot_acp、gemini_cli、goose、kimi_cli、kiro、mistral_vibe、opencode。两者都支持extend表用于按 Adapter 做配置覆盖以及opts表用于全局选项如allow_insecure、cache_models_for、proxy、show_presets、show_model_choices。一个解析后的 HTTP Adapter 是一个CodeCompanion.HTTPAdapter对象其字段在 adapters/http/init.lua 的类注释中有完整定义name、vendor、type、formatted_name、available_tools、roles角色映射、features、url、env/env_replaced环境变量及替换结果、body、headers、parameters、raw额外 curl 参数、handlers、meta上下文窗口等模型元数据、methods、model、opts、schema、temp不随请求发送的临时存储。二、Handler 结构按职责分组的嵌套设计Adapter 使用嵌套的 handler 结构按用途组织函数。完整结构如下摘自文档字段注释与 http/init.lua 中的类注释保持一致handlers { -- Lifecycle hooks (side effects) lifecycle { ---Called when adapter is resolved ---param self CodeCompanion.HTTPAdapter ---return boolean success setup function(self) end, ---Called after request completes ---param self CodeCompanion.HTTPAdapter ---param data table ---return nil on_exit function(self, data) end, ---Called during adapter cleanup ---param self CodeCompanion.HTTPAdapter ---return nil teardown function(self) end, }, -- Request builders (pure transforms) request { ---Build request parameters ---param self CodeCompanion.HTTPAdapter ---param params table ---param messages table ---return table build_parameters function(self, params, messages) end, ---Build message format for LLM ---param self CodeCompanion.HTTPAdapter ---param messages table ---return table build_messages function(self, messages) end, ---Build tools schema ---param self CodeCompanion.HTTPAdapter ---param tools table ---return table|nil build_tools function(self, tools) end, ---Build reasoning parameters (for models that support it) ---param self CodeCompanion.HTTPAdapter ---param messages table ---return nil|{ content: string, _data: table } build_reasoning function(self, messages) end, ---Set additional body parameters ---param self CodeCompanion.HTTPAdapter ---param data table ---return table|nil build_body function(self, data) end, }, -- Response parsers (pure transforms) response { ---Parse chat response ---param self CodeCompanion.HTTPAdapter ---param data string|table ---param tools? table ---return { status: string, output: table }|nil parse_chat function(self, data, tools) end, ---Parse inline response ---param self CodeCompanion.HTTPAdapter ---param data string|table ---param context? table ---return { status: string, output: string }|nil parse_inline function(self, data, context) end, ---Extract token count ---param self CodeCompanion.HTTPAdapter ---param data table ---return number|nil parse_tokens function(self, data) end, }, -- Tool handlers (grouped functionality) tools { ---Format tool calls for inclusion in request ---param self CodeCompanion.HTTPAdapter ---param tools table ---return table format_calls function(self, tools) end, ---Format tool response for LLM ---param self CodeCompanion.HTTPAdapter ---param tool_call table ---param output string ---return table format_response function(self, tool_call, output) end, }, }旧版扁平结构中的form_structured_output/parse_message_meta等函数在新结构中同样有对应映射详见下文“向后兼容”。这种结构实现了清晰的关注点分离lifecycle副作用与初始化setup、teardown、请求完成后的清理on_exitrequest构建请求的纯变换参数、消息、工具、结构化输出、推理、bodyresponse解析响应的纯变换聊天输出、内联输出、token 数tools工具相关的操作格式化工具调用与工具结果。这样的划分让每个函数职责单一、便于单测也降低了接入新提供商时的心智负担——你只需要实现对应类目下的函数其余由框架调用。三、规范的工具结果结构Canonical Tool-Result Shape在插件内部由format_response产生的工具结果消息会存储在chat.messages中并且会被每一个Adapter 的build_messages/form_messages重新读取。为了让消息在不同 Adapter 之间可移植例如把一段包含工具调用的对话从一个提供商切换到另一个所有 Adapter 都必须写入同一种规范结构{ role tool, content output, tools { call_id tool_call.id, -- required: matches the LLMs tool call name tool_call[function].name, -- required: function name (Gemini uses this) is_error false, -- optional: Anthropic uses this }, opts { visible false }, }字段说明role固定为tool是读取方判断工具结果的唯一依据content为工具执行后的输出文本tools.call_id是必填字段必须与 LLM 发出的工具调用 ID 一一对应Anthropic 用它映射tool_use_idOpenAI Responses 用它映射function_call_output的call_idtools.name是必填的函数名Gemini 依赖它做匹配tools.is_error为可选字段Anthropic 用它标记工具执行失败会在tool_result中携带is_erroropts.visible false告诉聊天缓冲区这条工具结果不需要展示给用户。Adapter 特有的扩展字段例如 OpenAI Responses 的id是允许的但会被其他 Adapter 忽略。在回读这些消息时Adapter 应当只依据role tool来识别工具结果绝不要以任何 Adapter 特有的字段作为判断条件。以 openai_responses.lua 的format_response为例它返回的正是上述规范形状额外附带了tools.idanthropic.lua 的output_response则额外携带tools.is_error false并在注释中说明role刻意设为tool是为了在form_messages中更易识别并与 user 消息合并。四、调用 Handlercall_handler()与向后兼容机制在整个插件中handler 通过adapters.call_handler()函数调用该函数负责向后兼容local adapters require(codecompanion.adapters) -- Call a handler local result adapters.call_handler(adapter, parse_chat, data, tools) local tokens adapters.call_handler(adapter, parse_tokens, data) -- Handler automatically receives adapter as first argument local setup_ok adapters.call_handler(adapter, setup)其实现位于 adapters/init.lua先通过get_handler()解析出真正的 handler 函数若存在则以handler(adapter, ...)形式调用——也就是说adapter 自身总是作为第一个参数自动传入无需手动传递。get_handler()的解析逻辑在 adapters/http/init.lua核心分两步新格式通过uses_new_handlers()检测判断handlers.lifecycle、handlers.request、handlers.response任一存在然后在lifecycle、request、response、tools四个类目中依次查找同名函数旧格式按下表把新名字映射回旧名字后在扁平的handlers表中查找新格式名称旧格式名称所属类目setup/on_exit/teardownsetup/on_exit/teardownlifecyclebuild_parametersform_parametersrequestbuild_messagesform_messagesrequestbuild_toolsform_toolsrequestbuild_structured_outputform_structured_outputrequestbuild_bodyset_bodyrequestbuild_reasoningform_reasoningrequestparse_chatchat_outputresponseparse_inlineinline_outputresponseparse_tokenstokensresponseparse_metaparse_message_metaresponseformat_callsformat_tool_callstoolsformat_responseoutput_responsetools旧格式示例仍然受支持-- Old format (still supported) handlers { setup function(self) end, form_parameters function(self, params, messages) end, form_messages function(self, messages) end, chat_output function(self, data, tools) end, tools { format_tool_calls function(self, tools) end, output_response function(self, tool_call, output) end, } }注意tools命名空间在新旧两种格式中都一直存在因此不能仅凭tools是否存在来判断格式检测必须看lifecycle、request或response这三个类目。五、工厂方法Adapter 的解析、扩展与安全序列化adapters/init.lua 对外暴露一组工厂方法统一分发到 http 或 acp 实现方法作用resolve(adapter, opts)将字符串名、table 或函数解析为CodeCompanion.HTTPAdapter/CodeCompanion.ACPAdapter对象resolved(adapter)判断 Adapter 是否已经完成解析检查 metatable 是否为 Adapter 类见 http/init.luaextend(adapter, opts)在既有 Adapter 配置上深合并用户自定义选项后生成新 Adaptermake_safe(adapter)生成适合序列化的精简副本过滤掉schema.model避免递归问题见 http/init.luaset_model(args)便捷方法将 schema 中的模型默认值/选择表写入adapter.modelcall_handler(adapter, name, ...)向后兼容的 handler 调用入口resolve的完整流程http/init.lua值得细读未传 Adapter 时使用config.interactions.chat.adapter默认值若传入的是已解析的 table带name、schema且resolved()为真直接复用并调用set_model若传入的是{ name ..., model ... }形式的 table则递归解析name并附带指定model若传入的是字符串先尝试require(codecompanion.adapters.http. .. name)失败则回退到config.adapters.http[name]再与opts做深合并vim.tbl_deep_extend(force, ...)若传入的是函数直接执行函数获取配置表最后补全type http、执行旧的handlers.resolve若存在并通过 shared.apply_extend 将用户config.adapters.http.extend中的按 Adapter 配置覆盖keyed by config key深合并进去。shared.lua 中还有几个 http/acp 共用的工具函数值得了解map_roles(adapter, messages)按 Adapter 定义的roles表替换消息中的角色名apply_extend(adapter, opts)将用户的 extend 配置深度合入已解析的 Adapter嵌套 table 用vim.tbl_deep_extend(force, ...)普通值直接覆盖context_window(adapter)解析当前模型的上下文窗口大小优先取adapter.model.meta.context_window否则回退到schema.model.default/schema.model.choices函数形式会在pcall中安全求值manages_own_context(adapter)判断当前提供商是否在服务端自行管理上下文压缩依赖model.opts.can_manage_context并且会通过pcall刷新可能过期的模型缓存。六、实例剖析新结构与旧结构的对比文档特意给出了两个代表性示例分别演示新旧两种 handler 结构。6.1 OpenAI Responses完整使用新嵌套结构openai_responses.lua 是使用新结构的范例其顶层定义包括name openai_responses、vendor openai、url https://api.openai.com/v1/responses、env { api_key OPENAI_API_KEY }、rolesllm/user/tool 分别映射到assistant/user/tool。lifecycle.setup中会根据所选模型动态修正能力开关例如从model_opts.opts中深合并has_vision、can_use_tools、can_manage_context等并开启流式参数self.parameters.stream trueopenai_responses.lua。request.build_messages展示了 Responses API 的消息组织方式openai_responses.luasystem 消息被抽取为顶层instructions图片消息带tags.IMAGE标记被合并为input_image块并与相邻的同角色文本消息合并PDF 文档带tags.DOCUMENT且filetype pdf被编码为input_file块工具结果按role tool转换为function_call_outputLLM 发出的工具调用被展开为多个function_call项若模型支持服务端上下文管理can_manage_context还会附带context_management压缩策略。response.parse_chat则同时处理流式response.output_text.delta、response.reasoning_summary_text.delta、response.completed等事件与非流式json.output中的message/reasoning/function_call/compaction两种返回并负责把工具调用写入tools表、把压缩块写入meta.compaction。其schema还给出了参数定义的完整写法openai_responses.lua每个字段都带order、mapping、type、optional、default、desc、可选的choices与validate参数类型默认值约束/说明modelenumgpt-5.6-lunachoices 中每个模型都带meta.context_window与opts是否支持工具、视觉、推理、结构化输出、上下文管理reasoning.effortstringmedium可选xhigh/high/medium/low/none仅推理模型启用reasoning.summarystringauto可选auto/concise/detailed推理摘要级别temperaturenumber10~2validate会校验top_logprobsnumbernil0~20top_pnumber10~1默认enabled falsemax_output_tokensintegernil必须大于 0verbositystringmedium可选low/medium/high控制输出 token 数量6.2 Anthropic旧扁平结构仍在生产环境服役anthropic.lua 是旧格式的活标本所有函数都平铺在handlers顶层setup开启流式、合并模型能力、按需注入anthropic-beta请求头如compact-2026-01-12、context-management-2025-06-27以启用服务端压缩form_messages这是最复杂的部分anthropic.lua依次完成 system 消息抽取、图片/PDF 转 base64 块、filter_out_messages清理、空 user 提示占位、字符串 content 转{ { type text, text ... } }数组、工具结果转tool_result块、LLM 工具调用转tool_use块、推理内容转thinking块含signature、压缩块回填、连续同角色消息合并最后附带cache_control { type ephemeral }启用自动提示缓存form_parameters针对扩展思维extended thinking处理thinking参数并遵循 Anthropic 的兼容性约束禁用top_k、将top_p收敛到 0.95~1form_tools/form_structured_output工具 schema 经 tool_transformers 转换结构化输出经 structured_outputs 转换chat_output/inline_output/tokens分别解析聊天输出、内联输出与 token 用量message_start累计输入message_delta结算输出tools.format_tool_calls/tools.output_response工具调用格式化为 OpenAI 风格工具结果写入规范形状on_exit请求完成后的错误日志捕获。其schema.model的choices是一个动态函数通过 adapters/utils/models/fetch.lua 请求https://api.anthropic.com/v1/models实时拉取模型列表其他参数extended_thinking、thinking_budget默认 16000、max_tokens默认 4096、top_p、top_k、stop_sequences也都带有各自的enabled/validate逻辑。此外两个 Adapter 都展示了available_tools的用法Anthropic 内置code_execution、memory、web_fetch、web_search各自注入对应的anthropic-beta头OpenAI Responses 内置web_search。这些“适配器级工具”在build_tools/form_tools中通过schema._meta.adapter_tool标记被识别并回调。七、HTTP 客户端http.lua如何驱动 Adapter文档明确指出 lua/codecompanion/http.lua 实现了一个与提供商无关的 HTTP 客户端集中处理请求构造、流式传输、调度与可测试性并且同样通过adapters.call_handler()以向后兼容的方式调用各 handler。从源码看其设计要点如下可测试的静态方法Client.static.methodshttp.lua把post、get、encode、schedule、schedule_wrap声明为可替换的默认实现测试时可以整体 mock请求前准备prepare_adapter()先vim.deepcopyAdapter然后调用setuphandler失败则返回错误最后解析环境变量http.luabody 合并顺序Client.merge_body()http.lua按build_parameters→build_messages→build_tools→build_structured_output→adapter.body→build_body的顺序用vim.tbl_deep_extend(keep, ...)合并出完整请求体curl 参数build_curl_args()http.lua默认加入--retry 3、--retry-delay 1、--keepalive-time 60、--connect-timeout 10流式模式下追加--tcp-nodelay与--no-buffer请求头写入临时 headers 文件通过--header file传入因此headers中类似Authorization Bearer ${api_key}的占位符会在发送前由adapter_utils.set_env_vars替换为真实环境变量底层依赖 plenary.curl 的env机制异步与同步Client:send()返回带cancel()/status()的RequestHandle流式数据通过on_chunk逐块回调非流式在on_done中返回Client:send_sync()用于内联补全等同步场景会临时关闭流式、校验结构化输出支持。八、模型列表与动态获取文档提到部分 Adapter 支持动态拉取模型列表相关说明见 .codecompanion/adapters/models_list.md。典型实现OpenRouter请求https://openrouter.ai/api/v1/modelsopenrouter.luaCopilot请求 Copilot 模型端点get_models.luaAnthropic请求https://api.anthropic.com/v1/models携带x-api-key与anthropic-version头Ollama请求本地http://localhost:11434/api/tags。各端点的响应样例保存在 tests/adapters/http/stubs/model_list 目录下anthropic.json、copilot.json、ollama.json、openrouter.json可作为接入新提供商时格式参考。动态模型的缓存时长由配置config.adapters.http.opts.cache_models_for默认 1800 秒控制环境变量解析类命令的超时由config.adapters.opts.cmd_timeout默认 20000ms控制。九、测试与验证文档给出了两组测试入口它们是理解 handler 契约的最佳阅读材料tests/adapters/test_adapters.lua定义了一组测试用 Adaptertest_adapter、test_adapter2与chat_buffer_settings覆盖 schema 映射、${...}环境变量占位符替换、mapping parameters.options这类点分路径嵌套写入等行为是校验map_schema_to_paramshttp/init.lua的基准tests/adapters/http/test_openai_responses.lua直接通过require(codecompanion.adapters).resolve(openai_responses)解析真实 Adapter然后逐项断言build_reasoning对增量内容与encrypted_content的拼接、format_response产出的规范工具结果形状等。仓库中还有 test_anthropic.lua、test_openai.lua、test_gemini.lua、test_ollama.lua 等覆盖各提供商的流式/非流式、工具调用、推理输出与压缩场景其输入桩stub均位于 tests/adapters/http/stubs 目录适合做回归验证时的对照样本。十、如何开始自定义 Adapter结合以上机制自定义一个 Adapter 的推荐路径是复制一个最接近目标 API 的现有实现作为起点新结构参考 openai_responses.lua旧结构参考 anthropic.lua实现lifecycle可选、request必选至少build_parameters与build_messages、response必选至少parse_chat、tools若需工具调用遵守“规范工具结果结构”一节中的约定确保工具结果可跨 Adapter 移植在 config.lua 的adapters.http或adapters.acp表中注册或通过extend字段按 key 覆盖既有 Adapter 的env、headers、schema等参考 tests/adapters/test_adapters.lua 的写法补充测试。需要说明的是本文所述结构嵌套 handler、call_handler映射表、规范工具结果均来自当前仓库 lua/codecompanion/adapters 与 lua/codecompanion/http.lua 的实际实现接入新的 LLM 服务商时请以仓库内对应 Adapter 文件的最新代码为准。【免费下载链接】codecompanion.nvim✨ AI Coding, Vim Style项目地址: https://gitcode.com/GitHub_Trending/co/codecompanion.nvim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →