Files
mambaandGitHub 570d06419c feat[qoder cli] add Qoder CLI agent support (#626)
* chore(agent): 增加对 Qoder CLI 的支持和识别

- 在 QUICKSTART 文档中添加 Qoder CLI 为可选本地 agent CLI
- 更新代码中 agents.ts 注释包含 Qoder CLI 扫描支持
- 修改首次加载时检测的可用 CLI 列表,加入 Qoder CLI
- 在多个语言版本的 README 中增加 Qoder CLI 支持及相关徽章统计
- 更新 agent 适配器与事件解析相关的代码注释和文档,包含 qoder-stream-json 解析器
- 调整 Windows 下 spawn 行为以支持 Qoder CLI 的 stdin 提供 prompt
- 修复多语言文档对支持的 CLI 数量描述错误,确保数据保持同步

Change-Id: I388f2f61c60ce8faa7cef5d84eb407950f8bdbfb
Co-developed-by: Qoder <noreply@qoder.com>

* chore(agent): 增加对 Qoder CLI 的支持和识别

- 在 QUICKSTART 文档中添加 Qoder CLI 为可选本地 agent CLI
- 更新代码中 agents.ts 注释包含 Qoder CLI 扫描支持
- 修改首次加载时检测的可用 CLI 列表,加入 Qoder CLI
- 在多个语言版本的 README 中增加 Qoder CLI 支持及相关徽章统计
- 更新 agent 适配器与事件解析相关的代码注释和文档,包含 qoder-stream-json 解析器
- 调整 Windows 下 spawn 行为以支持 Qoder CLI 的 stdin 提供 prompt
- 修复多语言文档对支持的 CLI 数量描述错误,确保数据保持同步

Change-Id: Id33f125b7c0b1a1c0b0274073da74d1578c324f7
Co-developed-by: Qoder <noreply@qoder.com>

* feat(agent-icon): 添加新的Qoder徽标SVG图形组件

- 新增qoderGlyph函数,返回指定大小的SVG格式图形
- 图形包含多路径定义,颜色使用深灰和绿色填充
- 该组件可用于替代或补充现有AgentIcon图标功能
- 提升应用程序的品牌标识和视觉表现力

Change-Id: I4eca18166b5e33bc6229b40b2531d5a54607a560
Co-developed-by: Qoder <noreply@qoder.com>

* Translate to English:

---

**docs(readme): update to expand CLI agents to 16**

- Increased the number of coding agent CLIs from 11 to 16
- New agents included: Devin for Terminal, Kiro CLI, Kilo, Mistral Vibe CLI, DeepSeek TUI

**docs(readme): update to expand supported coding agents to 16**

- Increased the number of supported code agent CLIs from 11 to 16
- Added support for new CLI tools: Devin for Terminal, Kiro CLI, Kilo, Mistral Vibe CLI, DeepSeek CLI
- Added automatic CLI detection and switching while maintaining support for more agents
- Added BYOK proxy TUI
- Expanded compatibility and support coverage in the README’s multiple language versions
- Reflected changes across all README translations (Arabic, German, French, Japanese, Korean)
- Updated badges and descriptions to reflect CLI count and feature changes
- Added event parsers and protocols for the new CLIs in the agent transport implementation
- Updated the BYOK proxy and tool exploration features to be compatible with the expanded CLIs

Change-Id: I89786b4a0b09bd279fb23265c2177076206fc5af
Co-developed-by: Qoder <noreply@qoder.com>

* feat(daemon): 支持 imagePaths 参数作为附件路径传递给 Qoder

- 修改 buildArgs 函数,添加 --attachment 参数处理 imagePaths 中的绝对路径
- 过滤并忽略空字符串、非字符串及相对路径的 imagePaths 项
- 在单元测试中覆盖 imagePaths 参数支持及无效项过滤逻辑
- 在文档中补充 Qoder 运行时适配器对 --attachment 参数的说明

Change-Id: Ibfc3583ba86c6d258d524912559e97b77bf1dc87
Co-developed-by: Qoder <noreply@qoder.com>

* docs(runtime): 说明Qoder适配器继承用户令牌的环境变量

- 添加文档说明检测代理仅为可用性探针,不进行身份验证
- 说明Qoder CLI账号状态独立,认证通过运行时错误路径反馈
- 详细描述子进程环境继承机制及静态环境变量与用户私密令牌区分
- 明确QODER_PERSONAL_ACCESS_TOKEN通过守护进程环境传递,不写入静态环境
- 解释Qoder验证由Qoder CLI负责,支持持久登录和自动化环境变量注入

test(agent): 添加QODER_PERSONAL_ACCESS_TOKEN环境变量继承测试

- 验证qoder适配器环境继承守护进程中的QODER_PERSONAL_ACCESS_TOKEN
- 确认qoder适配器未在静态环境变量中定义用户令牌
- 保证用户私密令牌不会被写入静态适配器环境配置

Change-Id: Ie61869afbe497df1b16879b4e47b35123f758ed8
Co-developed-by: Qoder <noreply@qoder.com>

* fix(daemon): 改进Qoder模式支持及错误处理机制

- 更新Qoder CLI参数,使用`--yolo`替代`--permission-mode bypass_permissions`
- 将工作目录参数从`--cwd`改为`-w`以符合Qoder文档
- 在agent流事件处理中新增错误捕获并通过SSE错误事件发送
- 运行结束时若检测到agent流错误,则标记运行失败
- 测试中fix(daemon): 优化Qoder代理参数与错误处理

- 调整Qoder启动参数,改用`--yolo`和`-w`替代旧参数,避开argv长度限制
- 增强代理流事件处理,捕获并通过SSE错误通同步更新Qoder参数使用及相应断言
- 新增端到端测试,覆盖Qoder助手错误通过SSE错误通道反馈及运行状态失败处理
- 补充工具函数辅助测试事件流读取与运行状态轮询

Change-Id: I5d933745c3659e093b0d2d807f22726e7f83eb48
Co-developed-by: Qoder <noreply@qoder.com>

* feat(qoder-stream): 识别并报告Qoder运行错误事件

- 新增messageFromResult函数以从结果对象提取错误信息
- 在处理result事件时根据is_error字段触发error事件
- error事件携带具体错误消息和原始数据
- 添加测试验证Qoder运行返回is_error且退出码为0时正确触发错误事件
- 更新qoder流解析测试以校验错误事件映射
- 在聊天路由测试中增加针对Qoder错误运行的端到端场景验证

Change-Id: Ie98ac518135dbec3181c52de5a49afdea993e279
Co-developed-by: Qoder <noreply@qoder.com>
2026-05-06 19:54:03 +08:00

14 KiB

Runtime Adapter Current State

Purpose

Runtime Adapter is the daemon layer responsible for adapting local AI agent CLIs. It converts Open Design's unified generation requests into the actual command-line invocations for each CLI, and converts CLI output into streaming events that the frontend can consume.

The current implementation is concentrated in:

  • apps/daemon/src/agents.ts: agent definitions, detection, model lists, argument construction, model validation.
  • apps/daemon/src/server.ts: /api/chat request orchestration, prompt composition, spawn() subprocesses, SSE forwarding.
  • apps/daemon/src/claude-stream.ts: parsing Claude Code structured JSONL output.
  • apps/daemon/src/json-event-stream.ts: parsing structured JSON/JSONL output from Codex, Gemini, OpenCode, and Cursor Agent.
  • apps/daemon/src/acp.ts: model detection and streaming session orchestration for the ACP JSON-RPC runtime.

Currently Supported Runtimes

AGENT_DEFS in apps/daemon/src/agents.ts defines 8 local runtimes:

id Name CLI Output format Model list source
claude Claude Code claude claude-stream-json Static fallback
codex Codex CLI codex json-event-stream Static fallback
gemini Gemini CLI gemini json-event-stream Static fallback
opencode OpenCode opencode json-event-stream opencode models + fallback
hermes Hermes hermes acp-json-rpc session/new from hermes acp + fallback
kimi Kimi CLI kimi acp-json-rpc session/new from kimi acp + fallback
cursor-agent Cursor Agent cursor-agent json-event-stream cursor-agent models + fallback
qwen Qwen Code qwen plain Static fallback

Each runtime definition contains:

  • id / name / bin: used for frontend display and process startup.
  • versionArgs: used to detect the version.
  • fallbackModels: static fallback options for the model selector.
  • listModels: optional model discovery command.
  • fetchModels: optional custom model detection logic, suitable for runtimes such as ACP that require a handshake before the model list is available.
  • reasoningOptions: optional reasoning effort options, currently used by Codex.
  • buildArgs(): converts unified input into the CLI's argv; it can also read runtimeContext at runtime, currently used to explicitly pass execution context such as cwd.
  • streamFormat: tells the daemon how to interpret stdout.

Detection Flow

The detection entry point is detectAgents().

Flow:

  1. Iterate over AGENT_DEFS.
  2. Use resolveOnPath() to locate the CLI binary in PATH.
  3. After locating it, run versionArgs to get the version.
  4. Generate the model list through listModels, fetchModels, or fallbackModels, depending on runtime capabilities.
  5. Return the result to the frontend and refresh the runtime's model validation cache.

The detection result includes:

  • available: whether the CLI is available.
  • path: the actual binary path.
  • version: version string.
  • models: model list used by the frontend model menu.
  • reasoningOptions: reasoning effort menu.
  • streamFormat: output format hint.

Runtime Flow

Actual execution happens in POST /api/chat in apps/daemon/src/server.ts.

Flow:

  1. The frontend submits agentId, user message, system prompt, project ID, attachments, model, and reasoning options.
  2. The daemon uses getAgentDef(agentId) to find the runtime definition.
  3. The daemon creates or locates .od/projects/<projectId>/ as the agent working directory.
  4. The daemon validates uploaded image paths and project attachment paths.
  5. The daemon combines the system prompt, working directory hint, existing file list, attachment list, and user request into one prompt.
  6. The daemon prepares additional readable directories: skills/ and design-systems/.
  7. The daemon validates the model and reasoning option.
  8. It calls def.buildArgs(...) to generate CLI arguments; currently it also passes runtimeContext = { cwd } for CLIs that need an explicit workspace argument.
  9. It starts the local runtime with spawn(def.bin, args, { cwd }); plain / Claude use read-only stdin, and ACP runtimes use writable stdin.
  10. The daemon forwards runtime output to the frontend through SSE.

Output Stream Handling

There are currently four output formats:

Claude Code: Structured JSONL

Claude Code uses:

claude -p <prompt> --output-format stream-json --verbose --include-partial-messages

The daemon parses stdout through createClaudeStreamHandler() and converts Claude Code JSONL events into UI events:

  • status
  • text_delta
  • thinking_delta
  • thinking_start
  • tool_use
  • tool_result
  • usage

These events are sent to the frontend through the SSE agent event.

Codex / Gemini / OpenCode / Cursor Agent: Structured JSON Event Stream

These four runtimes currently use the unified json-event-stream output format, with stdout parsed by apps/daemon/src/json-event-stream.ts.

Codex

Codex currently uses:

codex exec --json --skip-git-repo-check --sandbox workspace-write -c sandbox_workspace_write.network_access=true -C <cwd>

The current integration uses the lightweight structured path through exec --json. Compared with the original plain-text codex exec, this path adds:

  • --json: structured event output
  • --skip-git-repo-check: allows running in a temporary working directory
  • --sandbox workspace-write: allows Codex to edit within the project workspace without using the deprecated --full-auto shortcut
  • -c sandbox_workspace_write.network_access=true: keeps network access enabled inside the workspace-write sandbox
  • -C <cwd>: explicit working directory

The daemon currently maps:

  • thread.started → status(initializing)
  • turn.started → status(running)
  • item.completed(agent_message) → text_delta
  • turn.completed.usage → usage

Gemini

Gemini currently uses:

GEMINI_CLI_TRUST_WORKSPACE=true gemini --output-format stream-json --yolo

The daemon delivers the prompt over stdin rather than argv. It currently maps:

  • init → status(initializing)
  • message(role=assistant) → text_delta
  • result.stats → usage

Gemini may still output some workspace scan warnings on stderr at runtime; the main flow remains unaffected.

OpenCode

OpenCode currently uses:

opencode run --format json --dangerously-skip-permissions <prompt>

When the user selects a model, --model <id> is appended.

The daemon currently maps:

  • step_start → status(running)
  • text → text_delta
  • tool_use → tool_use
  • Completed tool_use.state → tool_result
  • step_finish.part.tokens → usage

Cursor Agent

Cursor Agent currently uses:

cursor-agent --print --output-format stream-json --stream-partial-output --force --trust --workspace <cwd> -p <prompt>

When the user selects a model, --model <id> is appended.

The daemon currently maps:

  • system(subtype=init) → status(initializing)
  • assistant partial chunks with timestamp_ms → text_delta
  • result.usage → usage

Cursor outputs both partial assistant chunks and the final aggregated assistant message. The daemon currently prioritizes partial chunks and ignores the final aggregated text after partial chunks have appeared, avoiding duplicate rendering.

Qoder

Qoder currently uses:

qodercli -p --output-format stream-json --permission-mode bypass_permissions

The daemon delivers the composed prompt over stdin rather than argv. When runtime context is available, --cwd <cwd> is appended. When the user selects a model, --model <id> is appended. Additional readable directories are passed as repeated --add-dir <dir> pairs.

Validated uploaded image paths are passed as repeated --attachment <path> pairs so Qoder receives the original multimodal context in addition to the textual @path prompt hint.

The daemon parses Qoder stream-json output through apps/daemon/src/qoder-stream.ts and currently maps:

  • system(subtype=init) → status(initializing)
  • assistant text content blocks → text_delta
  • thinking content blocks → thinking_start / thinking_delta
  • assistant error records → error
  • result usage metadata → usage

Qwen: Plain Text Pass-through

Qwen currently still uses the plain output format.

The daemon directly forwards stdout chunks to the frontend through the SSE stdout event, and stderr chunks through the stderr event.

Hermes / Kimi: ACP JSON-RPC

Hermes uses:

hermes acp --accept-hooks

Kimi uses:

kimi acp

The daemon starts an ACP session over stdio through apps/daemon/src/acp.ts:

  1. initialize
  2. session/new
  3. Optional session/set_model
  4. session/prompt

When an ACP runtime actively emits session/request_permission, the daemon prefers approve_for_session, which supports headless automatic approval for CLIs such as Kimi that require approval before tool calls.

The session/new response returns sessionId, models.availableModels, and models.currentModelId. The daemon reuses this information for model detection and runtime status reporting.

It then converts Hermes / Kimi session/update events into frontend-consumable agent events:

  • agent_thought_chunk → thinking_start / thinking_delta
  • agent_message_chunk → text_delta
  • Final usage from session/prompt → usage

At runtime, two additional status events are added:

  • Emit status(model) after session/new returns the default model.
  • Emit status(streaming) when the first text token arrives, including ttftMs.

Model detection also reuses ACP: during detection, the daemon reads models.availableModels and models.currentModelId from the session/new response.

The current Kimi MVP integration directly reuses the Hermes ACP orchestrator. Automatic permission approval has been added to the shared ACP layer. multica also contains Kimi-specific tool title normalization and provider error sniffing; this repository currently keeps a lighter implementation.

Prompt Injection Approach

Local CLIs currently use a unified approach of folding the system prompt into the user message.

The reason is that most local code-agent CLI command-line entry points lack an independent system channel. The daemon composes the following content into a single input:

  • systemPrompt: base output contract + skill content + design system content.
  • cwdHint: current working directory and file writing rules.
  • filesListBlock: existing file list in the project directory.
  • attachmentHint: attachments uploaded or selected by the user.
  • message: original user request.
  • safeImages: temporary uploaded image paths appended in @path form.

Claude Code additionally exposes skills/ and design-systems/ through --add-dir, making it easier for the agent to read skill seeds, templates, and design system files.

Safety and Validation

Existing protections include:

  • Process startup uses spawn() argument arrays, avoiding shell string concatenation.
  • Model IDs are first compared with the model list exposed by the most recent /api/agents response.
  • Custom model IDs are validated by sanitizeCustomModel(), limiting length, character set, and starting character.
  • Reasoning options must exist in the runtime definition's reasoningOptions.
  • Image paths must be located inside the daemon temporary upload directory.
  • Attachment paths must be located inside the project working directory.
  • Agent working directories are constrained to .od/projects/<projectId>/.
  • ACP runtimes have timeout protection for the initialize, session/new, session/set_model, and session/prompt stages.
  • ACP runtimes listen for stdin errors and proactively clean up detection processes after model detection completes.
  • When the SSE connection closes, the daemon sends SIGTERM to the subprocess.

Current Capability Boundaries

The current runtime adapter is a lightweight adaptation layer that already covers discovery, startup, argument construction, model selection, and streaming forwarding.

Main boundaries:

  • The adapter is still a declarative object array and has not yet been split into independent adapter classes or directories.
  • The capability model is thin and currently mainly exposes models, reasoning, and output format.
  • Claude Code, Codex, Gemini, OpenCode, Cursor Agent, Hermes, and Kimi already have structured event parsing.
  • Qwen currently still uses plain text pass-through.
  • Skill injection mainly relies on prompt composition; only Claude Code uses --add-dir to support reading external directories.
  • Hermes currently only integrates the core ACP text session path and has not mapped more session/update types into unified UI events.
  • Cancellation is triggered by HTTP connection closure and SIGTERM; there is no explicit runId / cancel API yet.
  • Resume, auth state, permission modes, and capability gating have not yet formed a unified interface.
  • API fallback belongs to the frontend provider path and is currently outside the daemon runtime adapter layer.

Gap from the Target Architecture

docs/agent-adapters.md describes a more complete target shape: each agent adapter has interfaces such as detect(), capabilities(), run(), cancel(), and resume(), and outputs unified AgentEvents.

The current implementation already has the core outline of the target architecture:

  • detectAgents() corresponds to detect().
  • AGENT_DEFS corresponds to the adapter registry.
  • buildArgs() corresponds to runtime-specific invocation.
  • streamFormat + claude-stream.ts + json-event-stream.ts + acp.ts correspond to stream normalization.
  • /api/chat corresponds to unified run orchestration.