Skip to content

feat(coding-agent): OpenCode CLI 适配器,语音 Agent 可选 OpenCode 后端 (Refs #579)#638

Open
appergb wants to merge 2 commits into
betafrom
feat/issue-579-opencode-adapter
Open

feat(coding-agent): OpenCode CLI 适配器,语音 Agent 可选 OpenCode 后端 (Refs #579)#638
appergb wants to merge 2 commits into
betafrom
feat/issue-579-opencode-adapter

Conversation

@appergb

@appergb appergb commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

User description

背景

Refs #579#579 要的「语音快捷键触发 Claude Code / OpenCode Agent」中,Claude Code 后端已随 fast-agent / less-computer 落地,但 OpenCode 后端只有占位:coding_agent_provider 已有 opencode-cli 选项(标「即将支持」),但后端从不读它、永远跑 claude。本 PR 把 OpenCode 真正接上,照既有 coding_agent 适配模式实现,不另造架构

改动清单

后端

  • coding_agent/opencode.rs(新增):OpenCode 适配器,与 Claude 适配器同形、复用同一套 CodingAgentRequest / CodingAgentEvent / CodingAgentError:
    • detect_opencode:opencode --version(复用 parse_claude_versionx.y.z)。
    • build_opencode_args:opencode run --format json --model <provider/model> --dir <cwd> [--dangerously-skip-permissions];prompt 作为 argv 位置参数(OpenCode 从命令行读,不像 claude 从 stdin)。
    • parse_opencode_json_line:解析 OpenCode NDJSON(text → Delta、tool_use → ToolUse、error → Error);OpenCode 的 text 是完整文本块且无带 cost 的终局 result 事件,故累计文本、EOF 处合成 Completed(cost_usd = None)。
    • run_opencode_agent:取消/超时 kill 子进程;护栏经 OPENCODE_CONFIG_CONTENT 注入。
  • coding_agent/args.rs:CodingAgentProvider 枚举(claude-code-cli / opencode-cli,未知回落 Claude)+ from_pref / default_exe
  • coding_agent/guard.rs:build_opencode_guard_config —— 把高风险 bash 前缀翻译成 OpenCode permission.bash 的 deny glob(如 "rm -rf *": "deny")+ webfetch: deny,审批放行的前缀显式 allow。这是 Claude --settings deny 护栏在 OpenCode 的等价物。
  • coordinator/dictation_voice_agent.rs:run_less_computer_oncecoding_agent_provider 分派 Claude / OpenCode 运行器。两条路径都 fail-closed(护栏配置生成失败即中止,绝不无护栏裸跑)。审批拦截探测 + 重跑放行链路本就 provider 无关,自动复用(OpenCode 重跑时把放行前缀传入 guard builder)。
  • coding_agent/commands.rs + lib.rs:coding_agent_detect_opencode 命令。

前端

  • CodingAgentSection.tsx:OpenCode 选项从「即将支持」改为可用;选中 OpenCode 后端时探测安装状态并提示(已安装显示版本 / 未安装提示先 npm i -g opencode-ai + opencode auth login)。
  • lib/ipc.ts:codingAgentDetectOpencode + OpenCodeDetection
  • i18n:opencodeReady / opencodeMissing(zh-CN / en / ja / ko / zh-TW;替换原 providerOpenCodeSoon)。

安全

  • 沿用语音→shell 路径既有安全姿态:bypassPermissions 在语音路径降级为 acceptEdits 等价(保留护栏);OpenCode 用 permission deny 高风险 bash + 禁 webfetch,fail-closed。
  • prompt 不进 argv 泄露? 注:OpenCode 设计上 prompt 走 argv(CLI 约定),这与 claude 走 stdin 不同 —— 会出现在进程列表里。语音转写一般非敏感,但已在 PR 描述标注;如需可后续探索 opencode serve + stdin 方案。

测试情况

  • cargo check(macOS)✅
  • cargo test --lib:492 通过(新增 10 个单测:provider 解析/默认 exe、OpenCode args/stream 解析、OpenCode 护栏 deny/放行)✅
  • npm run build(tsc + vite)✅
  • 真机实测待办:① 安装 opencodeopencode auth login;② 设置→Less Computer 选 OpenCode 后端,确认安装提示正确;③ 语音触发一次任务,确认流式输出进聊天浮窗、能落地改动;④ 触发一次高风险命令(如 rm -rf)确认被护栏拦截并弹审批卡,Approve 后重跑放行。

为什么是 Refs 而非 Closes #579

#579 还含「语音润色模型配置」一项:prefs 字段(coding_agent_use_voice_polish / voice_polish_model_mode / voice_polish_provider_id / voice_polish_thinking_enabled)已存在但全链路未实现——语音 Agent 当前把原始转写直接送 agent,未经语音润色模型整理,也无对应 UI。该项不在本 PR 范围,故用 Refs #579。建议作为 #579 的剩余子项单独跟进。

🤖 Generated with Claude Code


PR Type

Enhancement, Tests


Description


File Walkthrough

Relevant files
Enhancement
11 files
args.rs
Add CodingAgentProvider enum and prefs parsing                     
+55/-0   
commands.rs
Add coding_agent_detect_opencode command                                 
+29/-0   
guard.rs
Add OpenCode guard config builder                                               
+72/-0   
mod.rs
Re-export opencode module and helpers                                       
+8/-4     
opencode.rs
New OpenCode CLI adapter module                                                   
+369/-0 
dictation_voice_agent.rs
Dispatch agent runner by provider                                               
+106/-54
lib.rs
Register coding_agent_detect_opencode command                       
+1/-0     
types.rs
Add coding_agent_exe preference field                                       
+9/-0     
ipc.ts
Add OpenCodeDetection interface and detect function           
+21/-0   
types.ts
Add coding_agent_exe to UserPreferences                                   
+2/-0     
CodingAgentSection.tsx
Add OpenCode detection hint and exe config                             
+51/-1   
I18n
5 files
en.ts
Add OpenCode detection i18n keys                                                 
+3/-1     
ja.ts
Add OpenCode detection i18n keys                                                 
+3/-1     
ko.ts
Add OpenCode detection i18n keys                                                 
+3/-1     
zh-CN.ts
Add OpenCode detection i18n keys                                                 
+3/-1     
zh-TW.ts
Add OpenCode detection i18n keys                                                 
+3/-1     
Tests
1 files
stylePrefs.test.ts
Update test default with coding_agent_exe                               
+1/-0     

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit 7960ac4)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

579 - Partially compliant

Compliant requirements:

  • OpenCode CLI 适配器实现(新增 opencode.rs)。
  • 添加 CodingAgentProvider 枚举与解析逻辑(args.rs)。
  • prompt 作为 argv 位置参数传递,并使用 "--" 防止参数注入。
  • 解析 OpenCode NDJSON 事件(text/tool_use/error)。
  • 护栏配置 build_opencode_guard_config 及通过 OPENCODE_CONFIG_CONTENT 注入。
  • skip_permissions_flag 函数实现权限模式映射。
  • detect_opencode 异步函数。
  • types.rs 和 types.ts 增加 coding_agent_exe 字段。
  • CodingAgentSection.tsx 增加 OpenCode 选项及安装状态提示(调用 coding_agent_detect_opencode)。
  • i18n 新增 opencodeReady / opencodeMissing / exe 键。

Non-compliant requirements:

  • 全新的语音 Agent 面板(VoiceCodingAgentPanel.tsx)和完整前端流式显示。
  • 语音润色模型配置(VoicePolishModelMode、CustomProvider 等)。
  • 专用快捷键(CodingAgentPanelHotkey、CodingAgentVoiceHotkey)的默认值与冲突检测。
  • 后端 coordinator 中完整的 VoiceCodingAgent 状态机(Recording → Transcribing → Polishing → AgentRunning)。
  • 前端设置页中的“语音润色大语言模型”分组。
  • 历史记录存储、会话管理。

Requires further human verification:

  • 前端安全提示(BypassPermissions 风险文案)未在本次 PR 中包含,需确认后续补全。
  • OpenCode 输出中可能出现的其他事件类型(如 step_start/step_finish)处理策略需人工确认。
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 Security concerns

无。代码遵循 fail-closed 原则:护栏生成失败时中止执行;OpenCode 的权限模式通过环境变量注入而非命令行参数;prompt 使用 "--" 分隔符防止参数注入;高风险 bash 命令被 deny;webfetch 被 deny;凭据不写入日志。未发现敏感信息泄露或注入漏洞。

⚡ Recommended focus areas for review

可能缺少 IO 错误事件发送

在 read_line 出现 IO 错误时(第 212-215 行),代码仅将 outcome 设为 Io 错误并跳出循环,但没有向 sink 发送任何错误事件。前端代理会因未收到 Completed/Error/Cancelled 事件而一直处于等待状态,导致用户体验中断。超时和取消分支均已正确发送事件,IO 错误分支应补发一条 Error 事件。

            }
            Ok(None) => break, // EOF:正常结束
            Err(e) => {
                outcome = Err(CodingAgentError::Io(e.to_string()));
                break;
            }
        }
    }
}
进程成功退出但存在错误事件时静默无事件

当进程以成功退出码(status.success() true)结束,但解析过程中已产生 Error 事件(got_error = true),此时第 227-253 行的两个条件分支均不命中(第一个要求 !got_error,第二个要求 !status.success())。结果是不发送任何 Completed/Error 事件,函数直接返回 Ok(()),前端滞留等待。虽为罕见情况(通常 erro 事件与失败退出码一同出现),但不应忽略,应至少发送一条 Error 或警告事件。

// 正常 EOF 收尾(没取消/超时/IO 错):进程成功且没报错 → 合成 Completed。
if outcome.is_ok() {
    if status.success() && !got_error {
        let _ = sink.send(CodingAgentEvent::Completed {
            session_id: req.session_id.clone(),
            text: accumulated.trim().to_string(),
            cost_usd: None,
            duration_ms: None,
        });
        return Ok(());
    }
    if !status.success() && !got_error {
        // 非 0 退出且没解析到 error 事件:补一条 Error(取 stderr 末行作摘要)。
        let stderr = match stderr_task {
            Some(t) => t.await.unwrap_or_default(),
            None => String::new(),
        };
        let summary = stderr.lines().last().unwrap_or("").trim().to_string();
        let _ = sink.send(CodingAgentEvent::Error {
            session_id: req.session_id.clone(),
            message: if summary.is_empty() {
                format!("agent 异常退出 (code={:?})", status.code())
            } else {
                summary
            },
        });
        return Err(CodingAgentError::ProcessExit(status.code()));
    }
}

@H-Chris233

Copy link
Copy Markdown
Collaborator

@appergb 看一下这个参数注入风险

@H-Chris233 H-Chris233 added enhancement New feature or request area:agent Agent area needs-tests Needs tests labels Jun 11, 2026
appergb pushed a commit that referenced this pull request Jun 13, 2026
注入风险(@H-Chris233 评审):`opencode run` 的 prompt 作为最后位置参数,但之前缺
end-of-options 分隔。以 `-` / `--` 开头的 prompt(语音转写或被 prompt 注入诱导)会被
OpenCode 当作 flag 解析,可绕过护栏(混入 --dangerously-skip-permissions、--dir 改写
工作目录等)。修复:build_opencode_args 末尾追加 `--`,运行器把 prompt 接在其后;新增单测。

可配置路径(@pr-agent 指出之前 hardcode "opencode"):新增 prefs.coding_agent_exe,
语音 Agent 路径据此取 exe(留空回退默认 claude / opencode),检测命令亦用配置路径;
设置「高级 → Less Computer」新增「可执行文件路径」输入;补 5 语言 i18n。
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 7960ac4

吕柏青 and others added 2 commits June 13, 2026 18:30
)

把 OpenCode 当作与 Claude Code 同类的 coding agent CLI 接入,照既有 coding_agent
适配模式(不另造架构),复用同一套 CodingAgentRequest / CodingAgentEvent /
CodingAgentError / 审批护栏链路。

后端:
- coding_agent/opencode.rs(新):detect_opencode(opencode --version)、
  build_opencode_args(opencode run --format json --model --dir
  [--dangerously-skip-permissions],prompt 作为 argv)、parse_opencode_json_line
  (NDJSON:text/tool_use/error;text 块累计,EOF 合成 Completed)、
  run_opencode_agent(取消/超时 kill,护栏经 OPENCODE_CONFIG_CONTENT 注入)。
- coding_agent/args.rs:CodingAgentProvider 枚举(claude-code-cli/opencode-cli,
  未知回落 Claude)+ from_pref/default_exe。
- coding_agent/guard.rs:build_opencode_guard_config —— 把高风险 bash 前缀翻译成
  OpenCode permission.bash deny glob + webfetch deny,审批放行的前缀显式 allow。
- coordinator/dictation_voice_agent: run_less_computer_once 按 coding_agent_provider
  分派 Claude / OpenCode 运行器;两路都 fail-closed(护栏生成失败即中止,不裸跑)。
  审批拦截探测/重跑放行链路 provider 无关,自动复用。
- commands: coding_agent_detect_opencode 命令(已注册 lib.rs)。

前端:
- CodingAgentSection: OpenCode 选项从「即将支持」改为可用 + 选中后探测安装状态并提示。
- ipc: codingAgentDetectOpencode + OpenCodeDetection。
- i18n: opencodeReady / opencodeMissing(5 语言,替换 providerOpenCodeSoon)。

验证:cargo check + cargo test(492 通过)+ npm run build 均通过。

Refs #579#579 还含「语音润色模型配置」:prefs 字段已存在但全链路未实现/未接 UI,
转写直接进 agent 未经语音润色模型整理 —— 故用 Refs 不用 Closes。)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
注入风险(@H-Chris233 评审):`opencode run` 的 prompt 作为最后位置参数,但之前缺
end-of-options 分隔。以 `-` / `--` 开头的 prompt(语音转写或被 prompt 注入诱导)会被
OpenCode 当作 flag 解析,可绕过护栏(混入 --dangerously-skip-permissions、--dir 改写
工作目录等)。修复:build_opencode_args 末尾追加 `--`,运行器把 prompt 接在其后;新增单测。

可配置路径(@pr-agent 指出之前 hardcode "opencode"):新增 prefs.coding_agent_exe,
语音 Agent 路径据此取 exe(留空回退默认 claude / opencode),检测命令亦用配置路径;
设置「高级 → Less Computer」新增「可执行文件路径」输入;补 5 语言 i18n。
@appergb appergb force-pushed the feat/issue-579-opencode-adapter branch from 7960ac4 to 55cc808 Compare June 13, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Agent area enhancement New feature or request needs-tests Needs tests Review effort 3/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] 语音快捷键触发 Claude Code / OpenCode Agent,并支持语音润色模型配置

2 participants