- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
本指南围绕 Operit(Android 端 AI Agent / AI Chat 客户端)中 OpenCode(Zen / Go)接入的架构重构展开,讲解如何把路由、认证与思考参数从公共 Provider 中剥离、收敛到 OpenCode 专用实现,并逐一拆解 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages、Gemini 四条协议路由的隔离边界与实现细节。读完本文,你将理解 Operit 的 LLM Provider 分层设计,掌握"以受控扩展点承载协议差异、不让公共请求路径承担特例"的改造方法,并能对照源码与测试完成同类多协议网关接入的隔离验证。
改造背景:OpenCode 特例侵入公共 Provider 的问题
OpenCode 首版接入时,把路由、认证和思考参数通过OpenCodeReasoningParametersmarker 直接注入到 Claude、Gemini、OpenAI 与 Responses 四个 provider 中。其结果是:这个只为 OpenCode 一个 provider 服务的 marker,进入了所有普通请求的构建路径,导致 ClaudeProvider、GeminiProvider、OpenAIProvider、OpenAIResponsesProvider 这些公共实现必须识别 OpenCode 特例,公共请求路径被迫承担了本不属于自身的协议差异。
从源码结构看,Operit 的 LLM 接入层集中在 llmprovider 目录 下,包含 ClaudeProvider.kt、GeminiProvider.kt、OpenAIProvider.kt、OpenAIResponsesProvider.kt、OpenCodeProvider.kt 等四十余个 provider 实现,以及 ThinkingQualityMapping.kt、ModelListFetcher.kt、AIServiceFactory.kt 等支撑模块。任何特例分支一旦写入公共文件,都会随公共路径被所有兼容 provider 共享,既增加维护成本,也容易在后续改动中互相干扰。
修正意图与作用域
本次重构的核心意图是:让 OpenCode 的协议适配完全留在 OpenCode 专用实现中;普通 provider 只保留自己的通用行为;必要的继承点只表达稳定的请求构建扩展,不引用 OpenCode 类型或分支判断。
官方作用域记录于 index.md:
- 恢复公共 provider 的原有 reasoning、认证和端点行为;
- 为 OpenCode 的 Chat Completions、Responses、Anthropic 和 Gemini 路由提供隔离实现;
- 修正 OpenCode Gemini 的认证头和 SSE URL;
- 保留现有设置、模型目录和路由测试,并补充隔离边界说明。
改造完成后,OpenCode Chat、Responses、Anthropic、Gemini 的专用实现已全部合并到 OpenCodeProvider.kt 单文件中,公共 Provider 不再包含 OpenCode marker、端点判断和认证分支。
公共 Provider 恢复边界
恢复边界记录在 01-public-provider-baseline.md:
- 旧实现:公共 provider 通过
OpenCodeReasoningParametersmarker 改变 reasoning 自动注入、参数过滤和 Gemini 请求端点,该 marker 进入所有普通请求构建路径; - 目标实现:公共 provider 不再导入或识别 OpenCode 类型;OpenCode 请求使用专用子类或专用请求构建钩子;普通请求的认证、端点和 thinking 行为保持原样;
- 完成标准:
ClaudeProvider.kt、GeminiProvider.kt、OpenAIProvider.kt、OpenAIResponsesProvider.kt不包含 OpenCode 特例判断;OpenCode 专用代码承担显式 reasoning 参数和 Geminix-goog-api-key/ SSE URL。
在现有源码中搜索OpenCode关键字,命中的文件仅剩OpenCodeProvider.kt、AIServiceFactory.kt、ModelListFetcher.kt与CodexProvider.kt(后者用于模型目录解析),四个公共 provider 文件均已不再引用 OpenCode 类型,与完成标准一致。公共 provider 的 thinking 注入统一走 ThinkingQualityMapping.kt 中的ThinkingConfigurationApplier.apply()通用机制——该机制按 providerTypeId + modelName + apiEndpoint 解析 thinking 配置,本身与 OpenCode 解耦。
OpenCode 路由隔离:单文件四协议
路由隔离记录于 02-opencode-routing.md:OpenCodeProvider负责根据模型选择协议,并把请求交给专用 provider 实现;专用实现可以复用通用 provider 的消息、工具和流处理,但通过受控扩展点覆盖请求体和请求 URL,不把 OpenCode 条件写回公共 provider。
总体结构
OpenCodeProvider.kt 内部包含三层:
OpenCodeProvider(门面):class OpenCodeProvider(private val delegate: AIService, ...) : AIService by delegate,以委托方式持有协议专用实现,对外保持统一的AIService接口;sendMessage()中先调用ThinkingConfigurationApplier.modelParameters()把思考配置转成协议可见的显式参数,再转发给 delegate;OpenCodeRouting(路由器):负责协议判定、端点规范化与模型目录端点构造;- 四个专用 Provider 子类:
OpenCodeChatProvider、OpenCodeResponsesProvider、OpenCodeClaudeProvider、OpenCodeGeminiProvider,分别继承公共的OpenAIProvider、ClaudeProvider、GeminiProvider,通过 override 扩展点改写请求。
创建入口在 AIServiceFactory.kt:当ApiProviderType.OPENCODE时调用OpenCodeProvider.create()。create()内部先去除模型名opencode/、opencode-go/前缀,再依据端点与模型名路由到四个专用实现。
协议路由判定表
路由判定集中在OpenCodeRouting.protocolFor(),规则如下(按模型 ID 判定):
| 模型 ID 前缀 | 路由协议 |
|---|---|
gemini- | GEMINI_GENERIC |
claude-、qwen3.;minimax-(Go 端点全量,非 Go 端点仅-free后缀) | ANTHROPIC_GENERIC |
gpt-、grok-、muse-spark- | OPENAI_RESPONSES_GENERIC |
非 Go 端点下的minimax- | OPENAI_GENERIC |
big-pickle-、deepseek-、glm-、hy3、hy4-、kimi-、ling-、longcat-、mimo-、nemotron-、omen-、qwen3-coder、ring-、north-、laguna-、trinity-、x-preview- | OPENAI_GENERIC |
| 其他 | 抛出IllegalArgumentException("Unsupported OpenCode model protocol: ...") |
端点构造由OpenCodeRouting.endpointFor()完成:先对 base 做规范化(去尾部/,若不以/v1结尾则补上),随后按协议拼接:
- Responses:
{base}/responses - Anthropic:
{base}/messages - Gemini:
{base}/models/{modelName} - 其余(Chat Completions):
{base}/chat/completions
模型目录端点modelsEndpoint()为{base}/models;catalogProviderId()依据是否为 Go 端点返回opencode-go或opencode。Go 端点的判定isGo()检查 endpoint 是否以/zen/go或/zen/go/v1结尾。
各协议的请求边界与实现细节
OpenAI Chat Completions(OpenCodeChatProvider):继承OpenAIProvider,providerType = OPENAI_GENERIC。显式传入reasoning_effort;同时针对 OpenCode Chat 路由"后端透传 DeepSeek 风格 thinking 输出"的特性做了收敛处理——该协议要求历史 assistant 消息必须原样回传reasoning_content,否则模型完成工具调用后的下一轮请求会返回 400("The reasoning_content in the thinking mode must be passed back to the API.")。实现上:
- 强制
preserveThinkInHistory = true,让父类的buildMessagesAndCountTokens保留历史 assistant 消息中的<think>内容; - override
customizeFinalRequestObject()(公共基类 OpenAIProvider.kt 中为空实现、专供子类扩展的受控钩子),在 messagesArray 后处理阶段调用ChatUtils.extractThinkingContent()(见 ChatUtils.kt)将<think>内容拆分为reasoning_content字段并回填。该实现完全收敛在子类内部,不动通用OpenAIProvider。
OpenAI Responses(OpenCodeResponsesProvider):同样继承OpenAIProvider,useResponsesApi = true,providerType = OPENAI_RESPONSES_GENERIC。显式传入reasoning而不触发公共自动注入;在createRequestBody()中,当未开启 thinking 时调用ChatUtils.stripOpenAiResponsesReasoningMetaTurns()(ChatUtils.kt)剥离历史消息中的 Responses reasoning 元信息,避免关闭 thinking 后历史污染请求。
Anthropic Messages(OpenCodeClaudeProvider):继承ClaudeProvider,providerType = ANTHROPIC_GENERIC,传入空 thinking 配置"[]"(思考参数由 OpenCode 侧显式下发,不触发公共自动注入)。overrideaddParameters(),仅放行thinking、budget_tokens、output_config三类参数,并按ParameterValueType(OBJECT/STRING/INT/FLOAT/BOOLEAN)将参数值写入请求 JSON——OBJECT 类型的非法 JSON 会被runCatching捕获并记录警告而非抛错。
Gemini(OpenCodeGeminiProvider):继承GeminiProvider,providerType = GEMINI_GENERIC。overridecreateRequest(),与公共 GeminiProvider 的两处关键差异(即本次"修正认证头和 SSE URL"的直接落点):
- 认证头:公共 GeminiProvider.kt 使用 URL 查询参数
?key=...传递 API Key;OpenCode 网关要求x-goog-api-key请求头,因此OpenCodeGeminiProvider改为.addHeader("x-goog-api-key", opencodeApiKeyProvider.getApiKey()); - SSE URL:公共实现使用
streamGenerateContent/generateContent方法;OpenCode 在流式场景要求追加?alt=sse后缀。实现中method = if (isStreaming) "streamGenerateContent" else "generateContent",suffix = if (isStreaming) "?alt=sse" else "",最终 URL 为{apiBase}/models/{modelName}:{method}{suffix}(apiBase由OpenCodeRouting.apiBase()从 endpoint 中剥离/models/片段后规范化为/v1结尾)。
Go 端点专属头部
Go 端点(/zen/go)需要"agent 自有身份"以维持网关侧 prompt-cache 路由稳定,create()中对 routedHeaders 统一追加:
User-Agent: Operit/{VERSION_NAME}(所有 OpenCode 路由生效);x-opencode-session: operit-{config.id}(仅 Go 端点)。
模型目录请求同样携带Authorization: Bearer {apiKey}与User-Agent: Operit/{VERSION_NAME}(见 ModelListFetcher.kt),且模型列表端点由OpenCodeRouting.modelsEndpoint()提供(ModelListFetcher.kt),与其他 provider 的/v1/models约定区分。
思考参数:从 marker 到协议显式参数
隔离改造的关键点之一是 thinking 表达方式。公共路径不再识别任何 OpenCode marker,改为:OpenCodeProvider.sendMessage()调用ThinkingConfigurationApplier.modelParameters()(ThinkingQualityMapping.kt),该方法把思考配置解析结果转成一组ModelParameter显式参数,随modelParameters + opencodeParameters传入 delegate;同时enableThinking = enableThinking || thinkingMapping.reasoningRequired,并在 Responses 协议下才把 enableThinking 透传(enableThinking && protocol == OPENAI_RESPONSES_GENERIC),避免其他协议触发公共自动注入。
各协议对应的思考参数由 ModelThinkingConfigDefaultsCollect.kt 中的默认配置声明,均为providers: ["OPENCODE"]下的规则:
| 配置规则 ID | 匹配范围 | 参数标签 | 档位 |
|---|---|---|---|
opencode-gemini-thinking-level | google/gemini-* | thinkingLevel(含thinkingConfig.includeThoughts) | LOW/MEDIUM/HIGH |
opencode-anthropic-effort | anthropic、minimax/claude-*、minimax-* | output_config.effort(含thinking.type=adaptive、thinking.display=summarized) | low/medium/high |
opencode-zhipu-glm-effort | zhipu、zai-org、thudm/ 含glm | reasoning_effort(含thinking.type=enabled/disabled) | low/high/max |
opencode-responses-effort | openai、azure、xai/ 含gpt-、grok-、codex | reasoning.effort(含reasoning.summary=auto、include) | low/medium/high/xhigh |
opencode-chat-effort | 兜底(其他 Chat 模型) | reasoning_effort | low/medium/high |
用户可在模型配置中以自定义 thinkingConfigurations JSON 覆盖这些默认规则(customConfigurationControlsOpenCodeOptionCount测试用例即验证了自定义规则对档位数量的控制)。
静态验证与测试覆盖
验证记录见 03-verification.md:本次按仓库执行准则不运行构建、Gradle 或测试命令,完成后执行静态 diff 检查(git diff --check),并核对公共 provider 不再引用 OpenCode 专用类型。
仓库现有测试可从侧面印证隔离边界:
- OpenCodeThinkingConfigurationTest.kt 覆盖 5 个用例:
zhipu/glm-5.3与zai-org/glm-4.6走reasoning_effort(low/high/max)档位、google/gemini-2.5-pro走thinkingLevel(LOW/MEDIUM/HIGH)档位、未知 provider 兜底reasoning_effort(low/medium/high),以及自定义配置控制档位数量; - CodexModelListParserTest.kt 与 CodexModelPolicyTest.kt 覆盖 OpenCode 模型目录解析与显式模型放行策略。
配置视角:OpenCode 在 Operit 中的接入方式
从用户/配置视角看,OpenCode 以独立 Provider 类型注册于 ApiProviderConfigCollect.kt:
providerType = OPENCODE,默认端点https://opencode.ai/zen,端点选项含Zen(https://opencode.ai/zen)与Go(https://opencode.ai/zen/go)两种,Go 端点即路由表与x-opencode-session头部生效的前提;- 默认模型名为空,需从模型目录拉取(模型 ID 形如
zhipu/glm-5.3、google/gemini-2.5-pro,去除opencode/或opencode-go/前缀后参与路由判定); ApiProviderType.OPENCODE注释明确说明"按基础路径选择服务,按模型 ID 选择协议"(ModelConfigData.kt)。
隔离边界小结
本次改造形成的清晰边界可以概括为三层约定:
- 公共 provider 只保留通用行为:ClaudeProvider、GeminiProvider、OpenAIProvider、OpenAIResponsesProvider 不再出现 OpenCode 特例判断,thinking 注入统一走
ThinkingConfigurationApplier通用机制; - OpenCode 专用代码集中收敛:路由、端点、认证、思考参数、reasoning_content 回传等全部落在 OpenCodeProvider.kt 内,以继承 + override 公共基类中受控扩展点(
createRequestBody、customizeFinalRequestObject、addParameters、createRequest)的方式实现,不修改父类公共逻辑; - 可验证性:通过静态 grep 公共 provider 文件中的 OpenCode 引用、运行 OpenCodeThinkingConfigurationTest.kt 等既有测试、以及
git diff --check静态检查,即可确认隔离边界未被破坏。
对于后续需要接入类似"一个网关、多协议后端"的 provider,这套"门面路由 + 路由表 + 四协议子类 + 受控扩展点"的架构可以直接复用:协议差异下沉到专用子类,公共请求路径永远只理解自身的协议语义。
- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
相关推荐
Operit 的 OpenCode Provider 隔离改造:公共 Provider 恢复基线、协议专用路由与静态验证指南
Operit 的 OpenCode Provider 隔离改造:公共 Provider 恢复基线、协议专用路由与静态验证指南 本文以 docs/TODO/ope
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Operit 中 OpenCode Provider 隔离重构:公共 Provider 基线恢复与专用路由实现解析
Operit 中 OpenCode Provider 隔离重构:公共 Provider 基线恢复与专用路由实现解析 导读 本文基于 Operit 仓库中 ope
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Operit OpenCode Provider 路由隔离:多协议请求边界与专用适配实现解析
Operit OpenCode Provider 路由隔离:多协议请求边界与专用适配实现解析 导读 本文基于 Operit 仓库的 docs/TODO/open
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考