opi-coding-agent 0.5.0

Interactive coding agent CLI with file editing and shell execution
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# opi-coding-agent

[![Crates.io](https://img.shields.io/crates/v/opi-coding-agent.svg)](https://crates.io/crates/opi-coding-agent)
[![Docs.rs](https://docs.rs/opi-coding-agent/badge.svg)](https://docs.rs/opi-coding-agent)

> `opi` 二进制:基于 `opi-ai`、`opi-agent` 和 `opi-tui` 构建的交互式与非交互式终端编程 Agent。

[English](README.md) | [opi workspace](../../README.zh.md)

## 当前状态

当前 crate 版本:`0.4.0`。

本 crate 产出 `opi` CLI,同时也把编程 harness 暴露为 Rust library。当前支持交互式 TUI、位置参数非交互模式、NDJSON 输出、RPC JSONL 模式、9 个 Provider 前缀、8 个可用内置工具、pi 对齐的交互式默认工具、保守的非交互默认工具、图片附件、模型/会话/分支选择器、shell 补全生成、上下文文件加载、会话持久化、会话 resume/list/delete、上下文压缩、可配置按键/主题、按 Provider 配置代理、packages/extensions/skills/fragments/themes 的渐进式资源发现、retry、token 用量统计,以及尽力而为的费用摘要。

## 安装

```sh
cargo install opi-coding-agent
opi --version
```

也可以从 [GitHub Release](https://github.com/OdradekAI/opi/releases) 下载预编译二进制。

## 快速开始

```sh
export ANTHROPIC_API_KEY=sk-ant-...

# 交互式 TUI
opi

# 单次提示词,助手文本输出到 stdout
opi "找出这个仓库里的所有 TODO 注释。"

# 为自动化输出 NDJSON 事件流
opi --json "总结这个 workspace。"

# 指定 provider/model
opi -m openai:gpt-4o "解释 crates/opi-coding-agent/src/main.rs"

# 为第一条提示词附加图片
opi --image screenshot.png "审查这张截图。"

# 在非交互自动化中允许修改性工具
opi --allow-mutating "更新 README。"
```

## CLI 参数

| 参数 | 说明 |
|------|------|
| `[PROMPT]...` | 位置参数提示词;非空时进入非交互模式 |
| `-m, --model <SPEC>` | 模型 spec,例如 `anthropic:claude-sonnet-4-5-20250514` |
| `-c, --config <FILE>` | 显式 TOML 配置文件;必须存在 |
| `-s, --system <FILE>` | 用户系统提示词文件,会追加到内置编程提示词 |
| `--non-interactive` | 强制非交互模式;仍需提示词文本 |
| `--allow-mutating` | 在非交互模式中允许 `write`、`edit` 和 `bash` |
| `--json` | 输出 NDJSON 事件到 stdout;同时使用非交互模式 |
| `--list-sessions` | 列出已保存会话并退出 |
| `--resume <ID>` | 按 id 恢复会话 |
| `--delete-session <ID>` | 按 id 删除会话并退出 |
| `--generate-completion <SHELL>` | 为 `bash`、`zsh`、`fish`、`powershell` 或 `elvish` 生成 shell 补全 |
| `-v, --verbose` | 启用 debug tracing |
| `--tools <TOOLS>` | 逗号分隔的启用工具 allowlist,例如 `read,grep` |
| `--no-tools` | 禁用所有工具 |
| `--no-builtin-tools` | 禁用内置工具;为扩展/自定义工具预留 |
| `--image <IMAGE>` | 给初始提示词附加一张图片;可重复 |
| `--list-models` | 列出已配置 Provider 可用模型并退出 |
| `--rpc` | RPC JSONL 模式:通过 stdin/stdout 双向命令/事件协议 |

## Provider

`opi-coding-agent` 会根据配置的模型前缀构建 Provider。

| 前缀 | Provider | 默认凭据/配置 |
|------|----------|---------------|
| `anthropic:` | `AnthropicProvider` | `ANTHROPIC_API_KEY` |
| `openai:` | `OpenAiChatProvider` | `OPENAI_API_KEY` |
| `openai-responses:` | `OpenAiResponsesProvider` | `OPENAI_API_KEY` |
| `openrouter:` | OpenRouter profile | `OPENROUTER_API_KEY` |
| `mistral:` | Mistral profile | `MISTRAL_API_KEY` |
| `gemini:` | `GeminiProvider` | `GEMINI_API_KEY` |
| `bedrock:` | `BedrockProvider` | AWS 环境变量或共享 AWS profile/config |
| `azure:` | `AzureOpenAIProvider` | `AZURE_OPENAI_API_KEY`;endpoint/deployments 在配置中设置 |
| `vertex:` | `VertexProvider` | `VERTEX_ACCESS_TOKEN`;project/location 在配置中设置 |

环境变量名、base URL、Provider 专用字段和代理都可以在配置中覆盖。

## 配置

配置层按顺序合并:用户配置、项目配置、显式 `--config` 文件。后面的层覆盖前面的同名字段。

模型优先级:

1. `--model`
2. 未传入 `--config` 时的 `OPI_MODEL`
3. `--config <FILE>` 中的 `model`
4. `<CWD>/.opi/config.toml`
5. 用户配置
6. 内置默认值

常用默认结构:

```toml
[defaults]
model = "anthropic:claude-sonnet-4"
max_iterations = 50
tool_timeout_ms = 30000
max_image_bytes = 20971520
theme = "default"
allow_mutating_tools = false

[thinking]
enabled = true
budget_tokens = 10000

[retry]
max_attempts = 3
initial_delay_ms = 1000
max_delay_ms = 60000

[compaction]
enabled = true
threshold_tokens = 100000

[keybindings]
submit = "enter"
abort = "escape"
new_line = "alt+enter"

[providers.anthropic]
api_key_env = "ANTHROPIC_API_KEY"
# base_url = "https://api.anthropic.com"

[providers.openai]
api_key_env = "OPENAI_API_KEY"
# base_url = "https://api.openai.com"

[providers.openai_responses]
api_key_env = "OPENAI_API_KEY"
# base_url = "https://api.openai.com"

[providers.openrouter]
api_key_env = "OPENROUTER_API_KEY"
# base_url = "https://openrouter.ai/api"
# referer = "https://example.com"

[providers.mistral]
api_key_env = "MISTRAL_API_KEY"
# base_url = "https://api.mistral.ai"

[providers.gemini]
api_key_env = "GEMINI_API_KEY"
# base_url = "https://generativelanguage.googleapis.com"

[providers.bedrock]
region = "us-east-1"
# profile = "default"
# base_url = "https://bedrock-runtime.us-east-1.amazonaws.com"
# secret_access_key_env = "AWS_SECRET_ACCESS_KEY"
# session_token_env = "AWS_SESSION_TOKEN"

[providers.azure]
api_key_env = "AZURE_OPENAI_API_KEY"
endpoint = "https://my-resource.openai.azure.com"
api_version = "2024-06-01"
deployments = ["my-deployment"]

[providers.vertex]
access_token_env = "VERTEX_ACCESS_TOKEN"
project = "my-gcp-project"
location = "us-central1"
models = ["gemini-2.5-flash", "gemini-2.5-pro"]

[providers.openai.proxy]
url = "http://proxy.example.com:8080"
no_proxy = "localhost,127.0.0.1"

[extensions]
paths = ["vendor/my-extension"]

[packages]
paths = ["vendor/my-package"]
```

如果没有为 Provider 单独配置代理,HTTP client 会回退到 `HTTP_PROXY`、`HTTPS_PROXY` 和 `NO_PROXY`。

## 内置工具

工具位于 `src/tool/`。

| 工具 | 参数 | 说明 |
|------|------|------|
| `read` | `path`,可选 `offset`、`limit` | 1-based 行偏移;并行 |
| `ls` | `path`,可选 `max_entries`、`max_depth` | 确定性目录列表;遵守 gitignore;并行 |
| `glob` | `pattern` | 遵守 gitignore 的文件发现;并行 |
| `find` | `pattern`,可选 `path` | 遵守 gitignore 的文件发现,可限制到子目录;并行 |
| `grep` | `pattern` | 遵守 gitignore 的正则搜索;并行 |
| `write` | `path`、`content` | 创建父目录;串行;修改性 |
| `edit` | `path`、`old_string`、`new_string` | 替换第一个精确匹配,并记录 before/after details;串行;修改性 |
| `bash` | `command`,可选 `timeout_secs` | 在 workspace 根目录运行;Windows 使用 `cmd /C`,Unix 使用 `sh -c`;串行;修改性 |

可用内置工具包括 `read`、`write`、`edit`、`bash`、`grep`、`find`、`ls` 和 `glob`。

默认启用工具取决于运行模式:

- 交互模式:`read`、`write`、`edit`、`bash`。
- 非交互模式:`read`、`grep`、`find`、`ls`、`glob`。
- 非交互模式带 `--allow-mutating` 或 `defaults.allow_mutating_tools = true`:`read`、`write`、`edit`、`bash`。

`--tools <TOOLS>` 用于显式指定启用工具 allowlist。非交互模式下,如果 allowlist 包含 `write`、`edit` 或 `bash`,必须同时设置 `--allow-mutating` 或 `defaults.allow_mutating_tools = true`。

路径策略按模式区分。写入和编辑默认限制在 harness workspace 根目录内。交互模式的 `read` 可以解析绝对路径和 workspace 外路径;非交互模式的文件工具默认保持 workspace-only。文件工具 details 会记录 `workspace_root`、`resolved_path` 和 `inside_workspace`。

工具选择优先级是 `--no-tools` > `--tools` > `--no-builtin-tools` > 默认。

## 图片

`--image <PATH>` 会把图片附加到交互或非交互模式的第一条提示词。该参数可重复。交互模式也支持 `/image <path>`,用于把图片排队到下一条提示词。

支持 PNG、JPEG、GIF 和 WebP。默认文件大小限制为 20 MiB,可通过 `defaults.max_image_bytes` 修改。

## 会话

会话由 `SessionCoordinator` 自动持久化。

默认存储位置:

- Windows:`%LOCALAPPDATA%\opi\sessions\`
- Unix:`~/.local/share/opi/sessions/`

可以用 `OPI_SESSIONS_DIR` 覆盖。

```sh
opi --list-sessions
opi --resume <session-id> "继续这项工作。"
opi --delete-session <session-id>
```

Resume 会从 session JSONL 条目重建活跃分支。如果会话中包含 compaction marker,恢复后的上下文会包含压缩摘要和保留尾部。

## 运行模式

### 交互式

没有提示词参数时,`opi` 启动 ratatui TUI。它使用 `opi-tui` 组件渲染对话记录、输入编辑器、状态、Markdown、工具调用、编辑 diff、主题、按键绑定、模型/会话/分支选择器和终端图片输出。

Slash 命令:

| 命令 | 作用 |
|------|------|
| `/model` | 打开当前 Provider 的模型选择器 |
| `/session` | 打开会话选择器 |
| `/branch` | 打开当前会话的分支选择器 |
| `/image <path>` | 为下一条提示词排队一张图片 |
| `exit` 或 `quit` | 退出 |

### 文本非交互

带提示词参数或 `--non-interactive` 时,`NonInteractiveRunner::run()` 把助手文本写到 stdout,把诊断信息写到 stderr。

退出码:

| Code | 含义 |
|------|------|
| `0` | 成功 |
| `1` | 运行时失败 |
| `2` | 配置错误 |
| `3` | 鉴权失败 |
| `4` | Provider 失败 |
| `5` | 工具失败 |
| `130` | 被中断 |

### JSON 非交互

`--json` 会把 NDJSON 输出到 stdout。第一行是 schema header,随后是序列化的 session/agent 事件,最后输出带 token 总量和可选费用总量的 `session_summary`。

### RPC JSONL 模式

`--rpc` 启动一个通过 stdin/stdout 进行双向 JSONL 通信的持久会话。这是 IDE、自定义 UI 和外部工具集成的推荐嵌入模式。

**这是一个不稳定的 0.x 协议。** schema 可能在次版本之间变更。客户端必须在 `rpc_ready` 头中检查 `schema_version`。

```sh
opi --rpc
```

启动时,`opi` 会输出 `rpc_ready` 头:

```json
{"type":"rpc_ready","schema_version":2,"mode":"rpc","version":"0.4.0"}
```

命令是以 JSON 对象形式发送到 stdin(每行一个)。响应和事件是以 JSON 对象形式输出到 stdout(每行一个)。诊断信息输出到 stderr。

#### 命令

| 命令 | 说明 |
|------|------|
| `prompt` | 发送用户提示词;agent 事件异步流式输出 |
| `continue` | 用附加文本继续对话 |
| `steer` | 在 agent 运行期间排队转向消息 |
| `follow_up` | 排队后续消息,在 agent 停止后处理 |
| `abort` | 取消当前 agent 操作 |
| `set_model` | 切换 provider:model |
| `set_thinking_level` | 设置推理/思考级别 |
| `compact` | 触发手动压缩 |
| `session_info` | 查询会话元数据 |
| `quit` | 关闭 RPC 会话 |

所有命令都支持可选的 `id` 字段用于请求/响应关联。

对于 `prompt` 和 `continue`,`success: true` 表示命令已被接受。agent 事件(包括接受后的错误)以异步事件行到达。

## 上下文文件

`CodingHarness` 会从 workspace 目录向上查找 `AGENTS.md` 和 `CLAUDE.md`,直到 git root,然后再查找用户配置目录。空文件和超过 128 KiB 的文件会被跳过。

## 资源与 Package

Harness 会从用户、项目、显式和 package 层发现资源元数据,并把它暴露到系统提示词和 RPC/session metadata 中。发现范围包括:

- Extensions:包含 `extension.toml` 的目录。
- Packages:包含 `package.toml` 的目录;package 可以从约定子目录组合 extensions、skills、prompt fragments 和 themes。
- Skills:包含带 YAML frontmatter 的 `SKILL.md` 的目录。
- Prompt fragments:包含带 YAML frontmatter 的 `FRAGMENT.md` 的目录。
- Themes:包含 `theme.toml` 的目录,会在回退到内置主题前解析。

用户级资源位于用户配置目录下(Unix:`~/.config/opi/`;Windows:`%APPDATA%\opi\`)。项目级资源位于 workspace 根目录的 `.opi/` 下。显式 extension 和 package 路径来自配置。高优先级层会覆盖低优先级层;同一层内的重复项会作为 diagnostics 暴露。

## 技能(Skills)

技能通过渐进式发现从项目、用户、显式和包资源中加载。每个技能是一个包含 `SKILL.md` 文件的目录,`SKILL.md` 使用 YAML frontmatter。

**这是一个不稳定的 0.x API。** 技能格式和发现规则可能在次版本之间变更。

### 技能格式

技能目录包含一个 `SKILL.md`:

```markdown
---
name: my-skill
description: 技能功能描述和适用场景。
disable-model-invocation: false
---

完整技能指令写在这里。
```

字段:

| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | 是 | 小写 `a-z`、`0-9`、连字符。最长 64 字符。 |
| `description` | 是 | 最长 1024 字符。 |
| `disable-model-invocation` | 否 | 默认 `false`。设为 `true` 时,技能不会被模型自动调用,但仍可由用户手动使用。 |

### 发现位置

技能从多个层级发现,采用基于优先级的去重(高优先级在名称冲突时覆盖):

1. **用户级**(Unix: `~/.config/opi/skills/`,Windows: `%APPDATA%\opi\skills\`)— 优先级 0
2. **项目级**(workspace 根目录的 `.opi/skills/`)— 优先级 1
3. **显式资源层**,由嵌入方传入 — 优先级 2
4. **Package 组合资源**,来自已发现的 package,并使用 package 所在层的优先级

每个技能是扫描位置下的一个子目录,包含 `SKILL.md` 文件。

### 渐进式披露

技能元数据(名称、描述)无需加载完整技能正文即可使用。完整指令仅在技能被调用时按需加载。这保持了初始上下文的精简,同时支持丰富的专业化指令。

## 提示词片段(Prompt Fragments)

提示词片段(模板)通过渐进式发现从项目、用户、显式和包资源中加载。每个片段是一个包含 `FRAGMENT.md` 文件的目录,`FRAGMENT.md` 使用 YAML frontmatter。

**这是一个不稳定的 0.x API。** 片段格式和发现规则可能在次版本之间变更。

### 片段格式

片段目录包含一个 `FRAGMENT.md`:

```markdown
---
name: translate
description: 在语言之间翻译文本。
arguments: text, from=en, to=fr
---

将 {{text}} 从 {{from}} 翻译为 {{to}}。
```

字段:

| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | 是 | 小写 `a-z`、`0-9`、连字符。最长 64 字符。 |
| `description` | 是 | 最长 1024 字符。 |
| `arguments` | 否 | 逗号分隔列表。必填参数:`name`。可选参数:`name=default`。 |

### 参数展开

在 frontmatter 中声明的参数在正文中以 `{{name}}` 占位符引用。展开时:

- 必填参数必须提供。
- 可选参数未提供时使用声明的默认值。
- 未声明的占位符保持原样。

### 发现位置

片段使用与技能和扩展相同的基于优先级的发现机制(高优先级在名称冲突时覆盖):

1. **用户级**(Unix: `~/.config/opi/fragments/`,Windows: `%APPDATA%\opi\fragments\`)— 优先级 0
2. **项目级**(workspace 根目录的 `.opi/fragments/`)— 优先级 1
3. **显式资源层**,由嵌入方传入 — 优先级 2
4. **Package 组合资源**,来自已发现的 package,并使用 package 所在层的优先级

## 主题(Themes)

Themes 从用户、项目、显式和 package 层的 `theme.toml` 文件中发现。主题文件包含元数据和可选颜色 token 覆盖:

```toml
name = "operator"
description = "Operator theme"

[colors]
role_user = "Green"
status_bg = "#1a1a2e"
```

未知 token 和非法颜色会产生 diagnostics。未指定的颜色 token 会继承默认主题。运行时会先解析发现的主题,再回退到内置 `default` 和 `monokai`。

## 作为库使用

```rust
use opi_coding_agent::config::OpiConfig;
use opi_coding_agent::harness::CodingHarness;

# async fn example(provider: Box<dyn opi_ai::Provider>) -> anyhow::Result<()> {
let config = OpiConfig::default();
let mut harness = CodingHarness::new(
    provider,
    config.defaults.model.clone(),
    config,
    std::env::current_dir()?,
);
let _messages = harness.prompt("你好").await?;
# Ok(()) }
```

嵌入自定义应用时,可以使用 `builder`、`new_with_hooks`、`new_with_hooks_and_resume`、`new_with_selection`、`subscribe`、`cancel`、`queue_images`、`prompt_with_content`、`model_picker_items`、`branch_picker_items`、`set_model`、`resource_metadata`、`resolve_theme` 和 `session`。

## 许可证

MIT。详见 workspace [LICENSE](../../LICENSE)。