Skip to content

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

@HKLHaoBin

Description

@HKLHaoBin

背景

OpenLess 当前已经支持热键录音、ASR、LLM 润色、文本插入、Selection Ask QA 面板。希望在此基础上新增一个“语音调用代码 Agent”的独立能力:

  1. 用户触发专用快捷键后,OpenLess 开始录音。
  2. 再次触发快捷键后结束录音,ASR 得到文本。
  3. 系统可按配置先用“语音润色大语言模型”整理语音转写内容。
  4. 后端把最终文本发送给 Claude Code 或开源 OpenCode Agent。
  5. Agent 输出结果流式返回前端面板展示。

当前 OpenLess README 中说明产品主要负责文本整理,不负责回答问题、执行任务或分析项目。本功能会扩展为独立的 coding-agent 模式,应保持和普通听写、翻译、Selection Ask QA 分离,避免改变现有默认听写行为。

目标

新增一个独立功能:Voice Coding Agent。

默认行为:

  • 默认关闭,需要用户在设置页开启。
  • 使用独立快捷键,默认建议:
    • Windows/Linux: Ctrl+Shift+Enter
    • macOS: Cmd+Shift+Enter
  • 第一次按下开始录音。
  • 第二次按下停止录音并进入 ASR。
  • ASR 结果可先经过语音润色模型整理。
  • 整理后的指令发送给 configured coding agent。
  • 前端显示:
    • 原始 ASR 文本
    • 润色后的指令
    • Agent 当前状态
    • 流式输出
    • 错误、超时、取消状态
  • 默认不自动插入文本到当前光标位置。
  • 默认不允许 Agent 修改本地文件;权限模式默认为只读/计划模式。
  • 用户可在高级设置中切换为更强权限模式。

非目标

  • 不改变普通听写热键行为。
  • 不改变 Selection Ask QA 的现有交互。
  • 不把 Claude Code 输出默认插入到其他应用。
  • 不默认启用 bypassPermissions
  • 不把 API key、access token、命令输出中的敏感字段写入前端事件、日志或历史记录。

调研结论

  • OpenLess 当前主处理路径已经具备热键录音、ASR、LLM 润色、历史保存、QA 面板流式回答能力,可复用大部分基础设施。
  • 现有 Selection Ask QA 功能最接近目标:qa_hotkey 负责打开面板,主听写热键在面板可见时开始/结束 QA 录音,后端用 ASR 得到文本,再调用当前 LLM provider,前端通过 qa:state 接收流式回答。
  • 当前 UserPreferences 已有 active_llm_providerllm_thinking_enabledqa_hotkey 等字段;凭据通过 OS vault 保存,LLM provider 支持 Ark、OpenAI-compatible、Gemini、Codex OAuth、custom 等。
  • 官方 Claude Code 可通过 CLI 和 SDK 集成,关键参数包括 claude -p/--print--output-format text|json|stream-json--model--fallback-model--permission-mode--allowedTools--disallowedTools--mcp-config
  • 官方 Claude Code 未定位为可 fork 的开源实现;开源优先方案应增加 opencode 适配器。OpenCode 是开源终端 coding agent,适合做二次开发优先落点,同时保留官方 claude-code-cli 适配器满足 Claude Code 用户。
  • 相关 Issue 不构成重复:[insertion] 新增送出前文字處理 Hook #575 是“送出前文字处理 Hook”,[area] 无法现在大模型,也不知道哪个处于启用状态 #578 是模型选择状态问题;本需求是独立的语音触发代码 Agent 功能,但需要兼容 [insertion] 新增送出前文字處理 Hook #575 的文本处理扩展思路。

参考资料:

Claude Code / OpenCode 集成策略

Provider 抽象

新增后端 trait:

#[async_trait]
pub trait CodingAgentProvider: Send + Sync {
    async fn run(
        &self,
        request: CodingAgentRequest,
        sink: CodingAgentEventSink,
        cancel: CancellationToken,
    ) -> Result<CodingAgentResult, CodingAgentError>;
}

核心类型:

pub struct CodingAgentRequest {
    pub session_id: String,
    pub prompt: String,
    pub raw_transcript: String,
    pub polished_prompt: Option<String>,
    pub cwd: PathBuf,
    pub model: Option<String>,
    pub fallback_model: Option<String>,
    pub permission_mode: CodingAgentPermissionMode,
    pub allowed_tools: Vec<String>,
    pub disallowed_tools: Vec<String>,
    pub timeout_secs: u64,
    pub extra_system_prompt: Option<String>,
}

pub enum CodingAgentEvent {
    Started { session_id: String },
    TranscriptReady { text: String },
    PromptReady { text: String },
    Delta { text: String },
    ToolEvent { name: String, status: String },
    Completed { text: String },
    Cancelled,
    Error { message: String },
}

Provider IDs:

  • claude-code-cli
  • opencode-cli
  • 预留:claude-code-sdk

Claude Code CLI 适配器

新增 ClaudeCodeCliProvider

  • 默认可执行文件:claude
  • 支持用户配置可执行文件路径。
  • 使用非交互 print 模式:
    • claude -p
    • --output-format stream-json
    • --model <model>
    • --fallback-model <fallback_model>
    • --permission-mode <mode>
    • --add-dir <cwd> 如用户配置了额外目录
    • --mcp-config <path> 如用户配置 MCP
    • --append-system-prompt <prompt> 如配置了额外系统提示词
  • stdin 写入最终 prompt。
  • stdout 按行解析 stream-json。
  • stderr 独立收集,只在错误状态中返回摘要。
  • timeout 到期后 kill 子进程并发送 agent:error
  • 用户取消时 kill 子进程并发送 agent:cancelled
  • 子进程退出码非 0 时返回 CodingAgentError::ProcessExit

默认权限:

permission_mode = CodingAgentPermissionMode::Plan
timeout_secs = 300
allowed_tools = []
disallowed_tools = []

高级权限选项:

  • plan
  • default
  • acceptEdits
  • bypassPermissions

UI 中 bypassPermissions 必须放在高级区,附带明确风险文案,不能作为默认值。

OpenCode 适配器

新增 OpenCodeCliProvider

  • provider id: opencode-cli
  • 作为开源优先落点。
  • 默认可执行文件:opencode
  • 先实现同一 CodingAgentProvider trait。
  • 命令参数由适配器内部封装,保持前端配置和 Claude Code 适配器一致:
    • cwd
    • model
    • permission_mode
    • timeout_secs
    • extra_system_prompt
  • 若当前 OpenCode CLI 版本缺少稳定的非交互 stream 输出协议,先以实验 provider 暴露,并在 UI 中显示“experimental”。

后端实现

Preferences

UserPreferences 增加字段:

pub coding_agent_enabled: bool,
pub coding_agent_hotkey: Option<ShortcutBinding>,
pub coding_agent_provider: CodingAgentProviderId,
pub coding_agent_workdir: Option<String>,
pub coding_agent_permission_mode: CodingAgentPermissionMode,
pub coding_agent_model: Option<String>,
pub coding_agent_fallback_model: Option<String>,
pub coding_agent_timeout_secs: u64,
pub coding_agent_show_panel_on_start: bool,
pub coding_agent_save_history: bool,
pub coding_agent_use_voice_polish: bool,
pub voice_polish_model_mode: VoicePolishModelMode,
pub voice_polish_provider_id: Option<String>,
pub voice_polish_thinking_enabled: bool,

默认值:

coding_agent_enabled = false
coding_agent_hotkey = CtrlOrCmd + Shift + Enter
coding_agent_provider = "claude-code-cli"
coding_agent_workdir = None
coding_agent_permission_mode = Plan
coding_agent_timeout_secs = 300
coding_agent_show_panel_on_start = true
coding_agent_save_history = true
coding_agent_use_voice_polish = true
voice_polish_model_mode = SystemDefault
voice_polish_provider_id = None
voice_polish_thinking_enabled = false

新增 enum:

pub enum VoicePolishModelMode {
    SystemDefault,
    CustomProvider,
}

pub enum CodingAgentPermissionMode {
    Plan,
    Default,
    AcceptEdits,
    BypassPermissions,
}

同步更新:

  • Rust defaults
  • serde rename
  • TypeScript UserPreferences
  • IPC mock settings
  • 设置页初始值
  • 所有 i18n locale key

语音润色模型配置层

新增“语音润色大语言模型”配置,不直接复用全局 active LLM 的写入命令,避免改变普通听写配置。

配置模式:

  1. SystemDefault
    • 读取现有 active LLM provider。
    • 使用现有润色 prompt 和 style pack。
    • 不新增凭据。
  2. CustomProvider
    • 使用独立 provider id:voice-polish-custom
    • 凭据仍写入 OS vault 的 LLM providers map。
    • 保存字段:
      • displayName
      • apiKey
      • baseURL
      • model
      • temperature
      • extraHeaders
    • 前端只显示 hasApiKey,不回传明文 key。
    • 提供校验按钮,调用后端真实发起轻量模型请求。

新增后端命令:

get_voice_polish_model_config() -> VoicePolishModelConfigWire
set_voice_polish_model_mode(mode: VoicePolishModelMode)
set_voice_polish_provider_config(config: VoicePolishProviderConfigWire)
validate_voice_polish_provider_config(config: VoicePolishProviderConfigWire) -> ProviderValidationResult
list_voice_polish_provider_models(config: VoicePolishProviderConfigWire) -> Vec<ModelInfo>

新增 provider factory:

fn build_voice_polish_provider(
    prefs: &UserPreferences,
    vault: &CredentialsVault,
) -> Result<Box<dyn LLMProvider>, ProviderError>

规则:

  • SystemDefault 走现有 ActiveLLMProvider
  • CustomProvider 通过 provider id 精确读取 vault entry。
  • 不修改 active_llm_provider
  • 不使用 ...storedSettings 展开旧字段。
  • 保存 preferences 时仅写白名单字段。

Hotkey

新增 hotkey watcher 字段:

coding_agent_hotkey: Arc<Mutex<Option<ShortcutBinding>>>

新增命令:

get_coding_agent_hotkey_label()
set_coding_agent_hotkey(binding: Option<ShortcutBinding>)

更新冲突检测:

  • dictation hotkey
  • translation hotkey
  • selection ask hotkey
  • switch style hotkey
  • open app hotkey
  • coding agent hotkey

冲突时返回当前已有的快捷键名称。

Coordinator 状态机

新增状态:

pub enum CodingAgentPhase {
    Idle,
    Recording,
    Transcribing,
    Polishing,
    AgentRunning,
}

新增 session state:

pub struct CodingAgentSessionState {
    pub session_id: String,
    pub phase: CodingAgentPhase,
    pub started_at: Instant,
    pub raw_transcript: Option<String>,
    pub polished_prompt: Option<String>,
    pub cancel_token: CancellationToken,
}

快捷键处理:

  1. Idle 且 feature enabled:
    • 校验 workdir 已配置且存在。
    • 校验 ASR provider 可用。
    • 打开 Agent panel。
    • 启动 recorder + ASR session。
    • phase -> Recording
  2. Recording
    • stop recorder。
    • send last frame。
    • phase -> Transcribing
    • 等待 ASR final。
  3. ASR final 为空:
    • phase -> Idle
    • 前端显示空转写错误。
  4. coding_agent_use_voice_polish = true
    • phase -> Polishing
    • 调用 voice polish provider。
    • 失败时按配置 fallback:
      • 默认 fallback 到 raw transcript,并在 UI 显示 warning。
  5. 调用 coding agent:
    • phase -> AgentRunning
    • 将 stream event 转成 Tauri event。
  6. 完成:
    • 保存历史。
    • phase -> Idle
    • 前端显示完成。

事件名建议:

  • coding-agent:state
  • coding-agent:level
  • coding-agent:dismiss
  • coding-agent:history

前端事件 payload:

type CodingAgentStateEvent =
  | { kind: "phase"; phase: CodingAgentPhase; sessionId: string }
  | { kind: "transcript"; text: string; sessionId: string }
  | { kind: "prompt"; text: string; sessionId: string }
  | { kind: "delta"; text: string; sessionId: string }
  | { kind: "answer"; text: string; sessionId: string }
  | { kind: "error"; message: string; sessionId: string }
  | { kind: "cancelled"; sessionId: string };

所有事件必须带 sessionId,前端丢弃旧 session 的延迟事件。

前端

新增页面/组件:

  • VoiceCodingAgentPanel.tsx
  • VoiceCodingAgentSettings.tsx
  • 设置页新增分组:“语音调用代码 Agent”
  • provider 设置页新增分组:“语音润色大语言模型”

面板展示:

  • phase badge
  • raw ASR transcript collapsible block
  • polished prompt collapsible block
  • streaming markdown answer
  • stop button
  • copy answer button
  • copy prompt button
  • open settings button
  • error banner

设置项:

  • 启用 Voice Coding Agent
  • 快捷键录制器
  • Agent provider:Claude Code CLI / OpenCode CLI
  • Agent executable path
  • 工作目录选择
  • model
  • fallback model
  • permission mode
  • timeout
  • 是否先语音润色
  • 语音润色模型模式:系统默认 / 自定义
  • 自定义 base URL
  • 自定义 model
  • 自定义 API key
  • 校验模型按钮

安全与错误处理

  • 默认权限为 Plan
  • 未配置 workdir 时不启动 Agent,前端提示用户先选择项目目录。
  • 未找到 claudeopencode 时显示安装提示。
  • API key 永不通过普通 state event 回传。
  • 日志中屏蔽 bearer token、API key、account id。
  • 子进程必须支持取消和 timeout。
  • 快速连续按快捷键时只允许当前 session 生效。
  • ASR、润色、Agent 任一阶段失败都要恢复 phase 到 Idle
  • 旧 session 的 late event 必须被丢弃。
  • 历史记录只保存用户允许保存的 raw transcript、polished prompt、answer、provider id、model、时间戳,不保存 secret。

测试计划

Rust:

  • preferences 默认值和 serde 兼容测试。
  • hotkey collision 测试,覆盖所有已有快捷键。
  • voice polish custom provider 精确读取 provider id,不修改 active LLM。
  • Claude Code CLI args 构造测试。
  • stream-json parser 测试。
  • timeout kill 子进程测试。
  • cancel kill 子进程测试。
  • session id guard 测试。
  • 空 ASR 文本测试。
  • Agent process exit 非 0 测试。

Frontend:

  • UserPreferences 类型同步测试。
  • settings 初始渲染测试。
  • system default/custom voice polish 切换测试。
  • API key redacted 显示测试。
  • panel stream delta 合并测试。
  • stale session event 丢弃测试。
  • error banner 和 phase reset 测试。

手动验收:

  • 普通听写热键仍可录音、润色、插入。
  • Selection Ask QA 仍可打开、录音、返回回答。
  • Voice Coding Agent 关闭时快捷键无动作。
  • Voice Coding Agent 启用但未配置 workdir 时给出设置提示。
  • 使用系统默认语音润色模型能完成一次完整流程。
  • 使用自定义强模型能完成一次完整流程。
  • Claude Code CLI 不存在时给出可理解错误。
  • Claude Code 输出能流式显示。
  • 取消运行能终止子进程。
  • 超时能终止子进程。
  • 权限模式默认为计划/只读,不默认修改文件。
  • 切换到 OpenCode provider 后能走同一 UI 与事件协议。

验收标准

  • 用户可以在前端配置“语音润色大语言模型”,支持系统默认和自定义强模型。
  • 用户可以配置 Voice Coding Agent 的快捷键、provider、模型、工作目录、权限模式。
  • 触发快捷键后,ASR 内容能发送给 Claude Code CLI。
  • Claude Code 处理结果能返回前端并流式展示。
  • OpenCode provider 作为开源优先方案接入同一 provider trait。
  • 功能不破坏普通听写、翻译、Selection Ask QA。
  • 所有 secret 仍由 OS vault 保存,不进入前端事件和历史。
  • 默认权限不会修改用户项目文件。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions