reasonix 0.11.1 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -4
- package/README.zh-CN.md +118 -3
- package/dist/cli/index.js +122 -17
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
[](https://www.npmjs.com/package/reasonix)
|
|
19
19
|
[](./package.json)
|
|
20
20
|
|
|
21
|
-
**A DeepSeek-native AI coding agent in your terminal.**
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
**A DeepSeek-native AI coding agent in your terminal.** ~30× cheaper
|
|
22
|
+
per task than Claude Code, with a cache-first loop engineered for
|
|
23
|
+
DeepSeek's pricing model. Edits as reviewable SEARCH/REPLACE blocks.
|
|
24
|
+
MIT-licensed. No IDE lock-in. MCP first-class.
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
@@ -71,6 +72,137 @@ command list.
|
|
|
71
72
|
|
|
72
73
|
---
|
|
73
74
|
|
|
75
|
+
## Why Reasonix? (vs Cursor / Claude Code / Cline / Aider)
|
|
76
|
+
|
|
77
|
+
Three things you'd come to Reasonix for, that nothing else combines:
|
|
78
|
+
|
|
79
|
+
- **The cost economics actually land in your bill.** DeepSeek V4 is
|
|
80
|
+
~30× cheaper than Claude Sonnet per token. Cheaper tokens alone
|
|
81
|
+
isn't the win — *cheap tokens with a 90%+ prefix-cache hit* is.
|
|
82
|
+
Reasonix's loop is engineered around append-only prompt growth so
|
|
83
|
+
the cache-stable prefix survives every tool call, which the
|
|
84
|
+
benchmarks section below verifies end-to-end (94.4% live, vs 46.6%
|
|
85
|
+
for a generic harness against the same workload). The `/stats`
|
|
86
|
+
panel tracks "vs Claude Sonnet 4.6" savings every turn so you can
|
|
87
|
+
watch your bill not happen.
|
|
88
|
+
|
|
89
|
+
- **It lives in your terminal.** Pure CLI — no Electron, no VS Code
|
|
90
|
+
extension, no IDE plugin to wedge into your editor. Sits next to
|
|
91
|
+
git, tmux, and your shell history. macOS / Linux / Windows
|
|
92
|
+
(PowerShell, Git Bash, Windows Terminal all tested). The only
|
|
93
|
+
network call is to the DeepSeek API itself; no vendor server in
|
|
94
|
+
the middle.
|
|
95
|
+
|
|
96
|
+
- **Open source and hackable, end to end.** MIT-licensed TypeScript.
|
|
97
|
+
The entire loop, tool registry, cache-stable prefix, TUI, MCP
|
|
98
|
+
bridge — all in `src/` under 30k lines. Fork it, ship a private
|
|
99
|
+
build, drop it into CI. No SaaS layer, no enterprise tier, no
|
|
100
|
+
feature gates.
|
|
101
|
+
|
|
102
|
+
| | Reasonix | Claude Code | Cursor | Cline | Aider |
|
|
103
|
+
|---|---|---|---|---|---|
|
|
104
|
+
| Backend | DeepSeek V4 only | Anthropic only | OpenAI / Anthropic | any (OpenRouter) | any (OpenRouter) |
|
|
105
|
+
| Cost / typical task | **~$0.001–$0.005** | ~$0.05–$0.50 | $20/mo + usage | varies | varies |
|
|
106
|
+
| Where it runs | terminal | terminal + IDE | IDE (Electron) | VS Code only | terminal |
|
|
107
|
+
| License | **MIT** | closed | closed | Apache 2 | Apache 2 |
|
|
108
|
+
| Cache-first prefix loop | **engineered (94% hit)** | basic | n/a | n/a | basic |
|
|
109
|
+
| MCP servers | **first-class** | first-class | — | beta | — |
|
|
110
|
+
| Plan mode (read-only audit gate) | **yes** | yes | — | yes | — |
|
|
111
|
+
| User-authored skills | **yes** | yes | — | — | — |
|
|
112
|
+
| Edit review (no auto-write) | **yes** (`/apply`) | yes | partial | yes | yes |
|
|
113
|
+
| Workspace switch (`/cwd`, `change_workspace`) | **yes** | — | n/a (per-window) | — | — |
|
|
114
|
+
| Cross-session cost dashboard | **yes** (`/stats`) | — | — | — | — |
|
|
115
|
+
| Sandbox boundary enforcement | **strict** (refuses `..` escape) | yes | partial | yes | partial |
|
|
116
|
+
|
|
117
|
+
### Pick something else when
|
|
118
|
+
|
|
119
|
+
- **You want multi-provider flexibility** (mix Claude / GPT / Gemini /
|
|
120
|
+
local Llama in one tool). Try [Aider](https://aider.chat) or
|
|
121
|
+
[Cline](https://cline.bot). Reasonix is DeepSeek-only on purpose —
|
|
122
|
+
every layer (cache-first loop, R1 harvesting, JSON-mode tool repair,
|
|
123
|
+
reasoning-effort cap) is tuned against DeepSeek-specific behavior
|
|
124
|
+
and economics. Coupling to one backend is the feature, not a
|
|
125
|
+
limitation we'll grow out of.
|
|
126
|
+
- **You want IDE integration** (inline diff in your gutter,
|
|
127
|
+
multi-cursor, ghost text, refactor previews). Try
|
|
128
|
+
[Cursor](https://cursor.com) or Claude Code's IDE mode. Reasonix
|
|
129
|
+
is terminal-first; the diff lives in `git diff`, the file tree
|
|
130
|
+
lives in `ls`, the chat lives in your shell.
|
|
131
|
+
- **You're chasing the hardest reasoning benchmarks.** Claude Opus
|
|
132
|
+
4.6 still wins some leaderboards. DeepSeek V4-pro is competitive
|
|
133
|
+
on most coding tasks but doesn't lead every benchmark. If your
|
|
134
|
+
task is "solve this PhD-level proof" rather than "fix this auth
|
|
135
|
+
bug," start with Claude.
|
|
136
|
+
- **You need fully-local / fully-free**. DeepSeek's API has free
|
|
137
|
+
credit on signup, but isn't free forever. For air-gapped or
|
|
138
|
+
always-free, look at Aider + Ollama or [Continue](https://continue.dev).
|
|
139
|
+
|
|
140
|
+
### "But DeepSeek now has an Anthropic-compatible API — can't I just point Claude Code at it?"
|
|
141
|
+
|
|
142
|
+
You can. DeepSeek ships an official Anthropic-compatible endpoint at
|
|
143
|
+
`https://api.deepseek.com/anthropic`, and Claude Code (or any Anthropic
|
|
144
|
+
SDK client) talks to it without modification. The protocol works. The
|
|
145
|
+
**caching economics** don't transfer, and that's the whole point.
|
|
146
|
+
|
|
147
|
+
Look at DeepSeek's [own compatibility table](https://api-docs.deepseek.com/guides/anthropic_api):
|
|
148
|
+
|
|
149
|
+
| Field | Status on DeepSeek's compat endpoint |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `cache_control` markers | **Ignored** |
|
|
152
|
+
| `mcp_servers` (API-level) | Ignored |
|
|
153
|
+
| `thinking.budget_tokens` | Ignored |
|
|
154
|
+
| Images / documents / citations | Not supported |
|
|
155
|
+
|
|
156
|
+
`cache_control: Ignored` is the load-bearing line. Two completely
|
|
157
|
+
different cache mechanics are colliding here:
|
|
158
|
+
|
|
159
|
+
| | Anthropic native | DeepSeek auto-cache |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| Model | **Marker-based.** You put `cache_control` on a message; Anthropic caches "everything up to this marker" as a content-addressed unit. Multiple markers = multiple independent breakpoints. | **Byte-stable prefix.** The cache fingerprints the literal byte stream from byte 0. |
|
|
162
|
+
| Claude Code's design | Built around this. Markers on system prompt + tool defs let the loop reorder, compact, or insert metadata after the markers without losing the cache. | n/a — Claude Code wasn't designed for byte-stable prefixes. |
|
|
163
|
+
| What happens when Claude Code → DeepSeek compat | Markers stripped (ignored). Claude Code's main caching strategy disappears. | Falls back to auto-cache. But Claude Code's prefix isn't byte-stable (markers were the *substitute* for byte-stability), so auto-cache misses too. |
|
|
164
|
+
|
|
165
|
+
Net effect: **Claude Code's loop, redirected at DeepSeek, gets the
|
|
166
|
+
cheap tokens and loses the cache hit it depended on.** A loop running
|
|
167
|
+
at 80%+ cache hit on Anthropic's marker cache lands somewhere in the
|
|
168
|
+
40-60% range on DeepSeek's auto-cache (matches the generic-harness
|
|
169
|
+
baseline in our benchmarks). Same model, same API, same workload —
|
|
170
|
+
the loop's invariants don't fit the cache mechanic it's now talking
|
|
171
|
+
to.
|
|
172
|
+
|
|
173
|
+
Reasonix's loop was designed around byte-stable prefix from line one.
|
|
174
|
+
No markers, no breakpoints — append-only is the invariant. That's why
|
|
175
|
+
the same τ-bench workload lands at **94.4% cache hit** on Reasonix
|
|
176
|
+
and **46.6%** on a cache-hostile baseline (committed transcripts;
|
|
177
|
+
benchmarks section below). At DeepSeek's pricing — $0.07/Mtok
|
|
178
|
+
uncached, ~$0.014/Mtok cached — the difference between 50% and 94%
|
|
179
|
+
hit is **roughly 2.5× on input cost alone**.
|
|
180
|
+
|
|
181
|
+
### "What about Aider / Cline / Continue?"
|
|
182
|
+
|
|
183
|
+
They support DeepSeek natively (no compat layer needed) and you do
|
|
184
|
+
get the cheap token price. What you don't get is the DeepSeek-
|
|
185
|
+
specific loop work — those tools' loops support every backend
|
|
186
|
+
generically (OpenAI / Anthropic / local Llama / ...) and use
|
|
187
|
+
compaction + summarization patterns that destroy byte-stability. They
|
|
188
|
+
land in the same 40-60% cache-hit range as the baseline. Plus a
|
|
189
|
+
handful of DeepSeek-specific quirks generic loops don't handle:
|
|
190
|
+
|
|
191
|
+
| Generic loops assume | DeepSeek actually does | Reasonix's fix |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| Reasoning emitted as a structured `thinking` block | R1 sometimes leaks tool-call JSON inside `<think>` tags | a `scavenge` pass that pulls escaped tool calls back out, otherwise the model thinks it called and waits for output that never comes |
|
|
194
|
+
| Tool schemas validated strictly | DeepSeek silently drops deeply-nested object/array params | auto-flatten — nested params get rewritten to single-level prefixed names so the model sees them at all |
|
|
195
|
+
| Tool-call args are well-formed JSON | DeepSeek occasionally produces `string="false"` and other malformed fragments | dedicated `ToolCallRepair` heals the common shapes before they hit dispatch |
|
|
196
|
+
| Reasoning depth tuned via system-level switches | V4 exposes a `reasoning_effort` knob (`max` / `high`) | `/effort` slash + `--effort` flag, so users can step down for cheap turns |
|
|
197
|
+
| Old tool results kept in full forever | 1M context — don't compact pre-emptively, but most agents do | call-storm breaker + result token cap, but the prefix is *never* rewritten; compaction lands as new turns at the tail |
|
|
198
|
+
|
|
199
|
+
> Cache-stability isn't a feature you turn on; it's an invariant
|
|
200
|
+
> the loop is designed around. Reasonix isn't yet-another agent
|
|
201
|
+
> CLI — it's an agent CLI built around DeepSeek's specific cache
|
|
202
|
+
> mechanic and pricing model.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
74
206
|
## `reasonix code` — pair programmer in your terminal
|
|
75
207
|
|
|
76
208
|
Scoped to the directory you launch from. The model has native
|
|
@@ -771,7 +903,7 @@ cd reasonix
|
|
|
771
903
|
npm install
|
|
772
904
|
npm run dev code # run CLI from source via tsx
|
|
773
905
|
npm run build # tsup to dist/
|
|
774
|
-
npm test # vitest (
|
|
906
|
+
npm test # vitest (1482 tests)
|
|
775
907
|
npm run lint # biome
|
|
776
908
|
npm run typecheck # tsc --noEmit
|
|
777
909
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
[](https://www.npmjs.com/package/reasonix)
|
|
19
19
|
[](./package.json)
|
|
20
20
|
|
|
21
|
-
**DeepSeek 原生的终端 AI 编程代理。**
|
|
22
|
-
|
|
21
|
+
**DeepSeek 原生的终端 AI 编程代理。** 单次任务成本约为 Claude Code 的
|
|
22
|
+
1/30,缓存优先的循环是为 DeepSeek 的定价模型量身打造的。编辑以可审查的
|
|
23
|
+
SEARCH/REPLACE 块呈现,落盘前必须确认。MIT 许可、不绑 IDE、原生 MCP。
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
@@ -68,6 +69,120 @@ Windows Terminal)。任何时候按 `Esc` 中断;`/help` 查看完整命令
|
|
|
68
69
|
|
|
69
70
|
---
|
|
70
71
|
|
|
72
|
+
## 为什么选 Reasonix?(vs Cursor / Claude Code / Cline / Aider)
|
|
73
|
+
|
|
74
|
+
三件事,别家不会同时都给你:
|
|
75
|
+
|
|
76
|
+
- **成本节省落到账单上。** DeepSeek V4 的 token 单价大约是 Claude Sonnet
|
|
77
|
+
的 1/30。光便宜还不够 —— *便宜的 token 配上 90%+ 的前缀缓存命中*才是关键。
|
|
78
|
+
Reasonix 的循环按 append-only 增长设计,缓存稳定的前缀在每次工具调用之间
|
|
79
|
+
都活着,下面的 benchmark 章节端到端验证过:实测 94.4% 缓存命中,对照组通用
|
|
80
|
+
框架只有 46.6%。`/stats` 面板每轮都跟踪 "vs Claude Sonnet 4.6" 的节省额,
|
|
81
|
+
你可以亲眼看着账单不涨。
|
|
82
|
+
|
|
83
|
+
- **它住在终端里。** 纯 CLI —— 没有 Electron,没有 VS Code 插件,没有要
|
|
84
|
+
塞进编辑器的 IDE 插件。和 git、tmux、shell 历史并排。macOS / Linux /
|
|
85
|
+
Windows(PowerShell、Git Bash、Windows Terminal 都测过)。唯一的网络
|
|
86
|
+
请求就是 DeepSeek API 本身,中间没有厂商服务器。
|
|
87
|
+
|
|
88
|
+
- **开源且彻底可改。** MIT 许可的 TypeScript。整个循环、工具注册表、
|
|
89
|
+
缓存稳定前缀、TUI、MCP 桥接 —— 全部在 `src/` 下,不到 3 万行。Fork
|
|
90
|
+
它、做私有构建、塞进 CI 都可以。没有 SaaS 层,没有企业版,没有功能闸门。
|
|
91
|
+
|
|
92
|
+
| | Reasonix | Claude Code | Cursor | Cline | Aider |
|
|
93
|
+
|---|---|---|---|---|---|
|
|
94
|
+
| 后端 | 仅 DeepSeek V4 | 仅 Anthropic | OpenAI / Anthropic | 任意(OpenRouter)| 任意(OpenRouter)|
|
|
95
|
+
| 单次任务成本 | **~$0.001–$0.005** | ~$0.05–$0.50 | $20/月 + 用量 | 视情况 | 视情况 |
|
|
96
|
+
| 运行环境 | 终端 | 终端 + IDE | IDE(Electron)| 仅 VS Code | 终端 |
|
|
97
|
+
| 开源协议 | **MIT** | 闭源 | 闭源 | Apache 2 | Apache 2 |
|
|
98
|
+
| 缓存优先前缀循环 | **工程化(94% 命中)** | 基础 | n/a | n/a | 基础 |
|
|
99
|
+
| MCP 服务器 | **原生支持** | 原生支持 | — | 测试中 | — |
|
|
100
|
+
| 计划模式(只读审计闸门)| **支持** | 支持 | — | 支持 | — |
|
|
101
|
+
| 用户编写的 skills | **支持** | 支持 | — | — | — |
|
|
102
|
+
| 编辑审阅(不自动落盘)| **支持**(`/apply`)| 支持 | 部分 | 支持 | 支持 |
|
|
103
|
+
| 工作区切换(`/cwd`、`change_workspace`)| **支持** | — | n/a(每窗一项目)| — | — |
|
|
104
|
+
| 跨会话成本面板 | **支持**(`/stats`)| — | — | — | — |
|
|
105
|
+
| 沙箱边界强制 | **严格**(拒绝 `..` 逃逸)| 支持 | 部分 | 支持 | 部分 |
|
|
106
|
+
|
|
107
|
+
### 这些情况下应该选别的
|
|
108
|
+
|
|
109
|
+
- **你想要多模型混用**(在一个工具里同时切 Claude / GPT / Gemini / 本地 Llama)。
|
|
110
|
+
试试 [Aider](https://aider.chat) 或 [Cline](https://cline.bot)。Reasonix
|
|
111
|
+
故意只绑 DeepSeek —— 每一层(缓存优先循环、R1 harvest、JSON 模式的工具
|
|
112
|
+
调用修复、reasoning_effort 上限)都是为 DeepSeek 的具体行为和经济模型
|
|
113
|
+
调出来的。绑死后端是设计选择,不是早晚要解决的限制。
|
|
114
|
+
- **你想要 IDE 集成**(编辑器侧边栏 inline diff、多光标、ghost text、重构
|
|
115
|
+
预览)。试试 [Cursor](https://cursor.com) 或 Claude Code 的 IDE 模式。
|
|
116
|
+
Reasonix 是终端优先的:diff 在 `git diff` 里、文件树在 `ls` 里、对话
|
|
117
|
+
在 shell 里。
|
|
118
|
+
- **你在追最难的推理 benchmark**。Claude Opus 4.6 还是赢一些榜单的。
|
|
119
|
+
DeepSeek V4-pro 在大多数编程任务上都很有竞争力,但不是每个 benchmark
|
|
120
|
+
都领先。如果你的任务是"证明这个 PhD 级别的数学命题"而不是"修这个
|
|
121
|
+
auth bug",从 Claude 起步更合适。
|
|
122
|
+
- **你需要完全本地 / 永远免费**。DeepSeek API 注册送额度,但不是永久
|
|
123
|
+
免费。要真正离线/永久免费,看看 Aider + Ollama 或者
|
|
124
|
+
[Continue](https://continue.dev)。
|
|
125
|
+
|
|
126
|
+
### "DeepSeek 现在有 Anthropic 兼容 API 了,我直接拿 Claude Code 接上不就行?"
|
|
127
|
+
|
|
128
|
+
可以接。DeepSeek 官方提供了 Anthropic 兼容端点
|
|
129
|
+
`https://api.deepseek.com/anthropic`,Claude Code(或任何 Anthropic SDK
|
|
130
|
+
客户端)不改一行代码就能连上去。**协议跑得通,缓存经济学跑不通** ——
|
|
131
|
+
而后者才是关键。
|
|
132
|
+
|
|
133
|
+
看 [DeepSeek 自己的兼容性表](https://api-docs.deepseek.com/guides/anthropic_api):
|
|
134
|
+
|
|
135
|
+
| 字段 | 在 DeepSeek 兼容端点上的状态 |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `cache_control` 标记 | **Ignored(被忽略)** |
|
|
138
|
+
| `mcp_servers`(API 层)| Ignored |
|
|
139
|
+
| `thinking.budget_tokens` | Ignored |
|
|
140
|
+
| 图像 / 文档 / 引用 | 不支持 |
|
|
141
|
+
|
|
142
|
+
`cache_control: Ignored` 就是杀手级的那一行。这里有**两套完全不同的缓存
|
|
143
|
+
机制在打架**:
|
|
144
|
+
|
|
145
|
+
| | Anthropic 原生 | DeepSeek 自动缓存 |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| 模型 | **Marker 驱动。** 你在某条消息上打 `cache_control`,Anthropic 把"到此 marker 为止"的内容做内容寻址缓存。多个 marker = 多个独立断点。 | **Byte-stable prefix。** 缓存对字面字节流从第 0 字节起做指纹。 |
|
|
148
|
+
| Claude Code 的设计 | 围绕这个设计的。在 system prompt + tool 定义上插 marker,让 loop 在 marker 之后做重排、压缩、插元数据都不丢缓存。 | n/a —— Claude Code 不是为 byte-stable prefix 设计的。 |
|
|
149
|
+
| Claude Code 接 DeepSeek 兼容端点之后 | Marker 被 strip(忽略)。Claude Code 的主缓存策略消失。 | Fallback 到 auto-cache。但 Claude Code 的 prefix 不是 byte-stable 的(marker 本来就是 byte-stability 的*替代*),auto-cache 也命中不了。 |
|
|
150
|
+
|
|
151
|
+
净效果:**Claude Code 的 loop 重定向到 DeepSeek 之后,便宜 token 拿到了,
|
|
152
|
+
原本依赖的缓存命中没了**。一个在 Anthropic marker cache 上 80%+ 命中的
|
|
153
|
+
loop,到 DeepSeek 的 auto-cache 上大概率掉到 40-60%(跟我们 benchmark 里
|
|
154
|
+
通用 harness 的 baseline 同区间)。同一个模型、同一个 API、同一个负载 ——
|
|
155
|
+
loop 的 invariant 跟它现在对话的缓存机制不匹配。
|
|
156
|
+
|
|
157
|
+
Reasonix 的 loop 从第一行起就是按 byte-stable prefix 的不变量设计的。没有
|
|
158
|
+
marker、没有断点 —— append-only 就是 invariant。这就是为什么同一份 τ-bench
|
|
159
|
+
负载在 Reasonix 上是 **94.4% 缓存命中**、在 cache-hostile baseline 上是
|
|
160
|
+
**46.6%**(已 commit 的 transcript,见下面 benchmark 段)。按 DeepSeek 的
|
|
161
|
+
单价 —— $0.07/Mtok 非缓存、约 $0.014/Mtok 缓存命中 —— 50% 和 94% 命中之间
|
|
162
|
+
**仅 input 这一侧就大约是 2.5× 的差距**。
|
|
163
|
+
|
|
164
|
+
### "那 Aider / Cline / Continue 呢?"
|
|
165
|
+
|
|
166
|
+
它们原生支持 DeepSeek(不需要兼容层),便宜 token 单价你确实拿到了。
|
|
167
|
+
但你拿不到 DeepSeek-specific 的循环工程 —— 这些工具的 loop 是为**通用支持**
|
|
168
|
+
所有后端(OpenAI / Anthropic / 本地 Llama / ...)设计的,用的是那种会破坏
|
|
169
|
+
byte-stability 的通用压缩 / 摘要模式。命中率落在和 baseline 同样的 40-60%
|
|
170
|
+
区间。再加上一堆 DeepSeek 怪癖通用 loop 不处理:
|
|
171
|
+
|
|
172
|
+
| 通用 loop 假定 | DeepSeek 实际行为 | Reasonix 怎么处理 |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| 推理通过结构化 `thinking` 块产出 | R1 偶尔把 tool-call JSON 漏到 `<think>` 标签里 | `scavenge` 扫描把漏出的 tool call 拣回,否则模型以为自己已经调用,等不到结果 |
|
|
175
|
+
| tool schema 严格校验 | DeepSeek 静默丢弃深嵌套 object/array 参数 | auto-flatten:嵌套参数被重写成单层 prefixed name,模型才看得见 |
|
|
176
|
+
| tool-call args 是良构 JSON | DeepSeek 偶发 `string="false"` 之类的破碎片段 | 专用 `ToolCallRepair` 在 dispatch 之前修好常见形状 |
|
|
177
|
+
| 推理深度靠系统级开关 | V4 直接暴露 `reasoning_effort` 旋钮(`max` / `high`) | `/effort` slash + `--effort` flag,简单任务可以降到 high 省钱 |
|
|
178
|
+
| 老 tool result 永久保留 | 1M context 不需要主动 compact,但通用工具都会做 | call-storm breaker + 结果 token cap,但前缀**永不重写** —— 压缩作为新 turn 追加在尾部 |
|
|
179
|
+
|
|
180
|
+
> 缓存稳定性不是一个开关,是 loop 设计之初就要建立的不变量。Reasonix
|
|
181
|
+
> 不是"又一个 agent CLI",是**围绕 DeepSeek 具体的缓存机制和定价模型
|
|
182
|
+
> 设计的 agent CLI**。
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
71
186
|
## `reasonix code` — 终端里的结对编程
|
|
72
187
|
|
|
73
188
|
作用域为启动目录。模型自带 `read_file` / `write_file` / `edit_file` /
|
|
@@ -709,7 +824,7 @@ cd reasonix
|
|
|
709
824
|
npm install
|
|
710
825
|
npm run dev code # 用 tsx 直接从源码跑 CLI
|
|
711
826
|
npm run build # tsup 打包到 dist/
|
|
712
|
-
npm test # vitest(
|
|
827
|
+
npm test # vitest(1482 个测试)
|
|
713
828
|
npm run lint # biome
|
|
714
829
|
npm run typecheck # tsc --noEmit
|
|
715
830
|
```
|
package/dist/cli/index.js
CHANGED
|
@@ -112,7 +112,7 @@ import { createParser } from "eventsource-parser";
|
|
|
112
112
|
|
|
113
113
|
// src/retry.ts
|
|
114
114
|
var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
115
|
-
async function fetchWithRetry(fetchFn, url,
|
|
115
|
+
async function fetchWithRetry(fetchFn, url, init2, opts = {}) {
|
|
116
116
|
const maxAttempts = opts.maxAttempts ?? 4;
|
|
117
117
|
const initial = opts.initialBackoffMs ?? 500;
|
|
118
118
|
const cap = opts.maxBackoffMs ?? 1e4;
|
|
@@ -121,7 +121,7 @@ async function fetchWithRetry(fetchFn, url, init, opts = {}) {
|
|
|
121
121
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
122
122
|
if (opts.signal?.aborted) throw new Error("aborted");
|
|
123
123
|
try {
|
|
124
|
-
const resp = await fetchFn(url,
|
|
124
|
+
const resp = await fetchFn(url, init2);
|
|
125
125
|
if (resp.ok || !retryable.has(resp.status)) return resp;
|
|
126
126
|
if (attempt === maxAttempts - 1) return resp;
|
|
127
127
|
await resp.text().catch(() => void 0);
|
|
@@ -7254,12 +7254,12 @@ function formatLogSize(path5 = defaultUsageLogPath()) {
|
|
|
7254
7254
|
}
|
|
7255
7255
|
|
|
7256
7256
|
// src/cli/commands/chat.tsx
|
|
7257
|
-
import { existsSync as
|
|
7257
|
+
import { existsSync as existsSync16, statSync as statSync9 } from "fs";
|
|
7258
7258
|
import { render } from "ink";
|
|
7259
7259
|
import React27, { useState as useState12 } from "react";
|
|
7260
7260
|
|
|
7261
7261
|
// src/cli/ui/App.tsx
|
|
7262
|
-
import * as
|
|
7262
|
+
import * as pathMod7 from "path";
|
|
7263
7263
|
import { Box as Box22, Static, Text as Text20, useApp, useStdout as useStdout8 } from "ink";
|
|
7264
7264
|
import React24, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
|
|
7265
7265
|
|
|
@@ -11626,6 +11626,13 @@ var SLASH_COMMANDS = [
|
|
|
11626
11626
|
},
|
|
11627
11627
|
{ cmd: "exit", summary: "quit the TUI" },
|
|
11628
11628
|
// Code-mode only
|
|
11629
|
+
{
|
|
11630
|
+
cmd: "init",
|
|
11631
|
+
argsHint: "[force]",
|
|
11632
|
+
summary: "scan the project and synthesize a baseline REASONIX.md (model writes; review with /apply). `force` overwrites an existing file.",
|
|
11633
|
+
contextual: "code",
|
|
11634
|
+
argCompleter: ["force"]
|
|
11635
|
+
},
|
|
11629
11636
|
{
|
|
11630
11637
|
cmd: "apply",
|
|
11631
11638
|
argsHint: "[N|N,M|N-M]",
|
|
@@ -12460,6 +12467,103 @@ var handlers3 = {
|
|
|
12460
12467
|
walk: walk2
|
|
12461
12468
|
};
|
|
12462
12469
|
|
|
12470
|
+
// src/cli/ui/slash/handlers/init.ts
|
|
12471
|
+
import { existsSync as existsSync15 } from "fs";
|
|
12472
|
+
import * as pathMod6 from "path";
|
|
12473
|
+
var INIT_PROMPT = [
|
|
12474
|
+
"# Task: Initialize REASONIX.md",
|
|
12475
|
+
"",
|
|
12476
|
+
"I want you to generate a REASONIX.md at the project root that captures",
|
|
12477
|
+
"the working knowledge a future Reasonix session needs to be productive",
|
|
12478
|
+
"here. This file is auto-pinned into your system prompt every launch,",
|
|
12479
|
+
"so its size and accuracy matter.",
|
|
12480
|
+
"",
|
|
12481
|
+
"## Hard constraints (do NOT relax these)",
|
|
12482
|
+
"",
|
|
12483
|
+
"- **Length cap: \u2264 80 lines / 3KB total.** Be concise. If you can't fit a",
|
|
12484
|
+
" section, drop it.",
|
|
12485
|
+
"- **Only document things you can verify by reading files.** Do NOT",
|
|
12486
|
+
" speculate about architectural intent, future roadmap, or design",
|
|
12487
|
+
" rationale. If it isn't obvious from the code, leave it out.",
|
|
12488
|
+
"- **No placeholder text.** No 'TODO: describe X', no 'Add more here'.",
|
|
12489
|
+
" Either state a fact or omit the section.",
|
|
12490
|
+
"",
|
|
12491
|
+
"## Procedure",
|
|
12492
|
+
"",
|
|
12493
|
+
"1. Read the top of any existing README* file.",
|
|
12494
|
+
"2. Read the manifest (package.json / Cargo.toml / pyproject.toml /",
|
|
12495
|
+
" go.mod / etc.) \u2014 pick whichever exists.",
|
|
12496
|
+
"3. `directory_tree` 1-2 levels deep on the project root, skipping",
|
|
12497
|
+
" common build/dependency dirs (node_modules, dist, target, .git,",
|
|
12498
|
+
" venv, __pycache__).",
|
|
12499
|
+
"4. Identify: primary language + framework, top-level layout, test",
|
|
12500
|
+
" runner, lint/format setup, build/run/test scripts, any non-obvious",
|
|
12501
|
+
" convention with visible evidence (commit message format, import",
|
|
12502
|
+
" order, naming pattern).",
|
|
12503
|
+
"5. Write REASONIX.md with the sections below, skipping any you can't",
|
|
12504
|
+
" fill from evidence.",
|
|
12505
|
+
"",
|
|
12506
|
+
"## Sections to use (skip ones with no evidence)",
|
|
12507
|
+
"",
|
|
12508
|
+
"- **Stack** \u2014 language + framework + 3-5 key deps. One line each.",
|
|
12509
|
+
"- **Layout** \u2014 top-level dirs and what lives in each. One line each.",
|
|
12510
|
+
"- **Commands** \u2014 verbatim from `scripts` block (or equivalent):",
|
|
12511
|
+
" build / test / lint / typecheck / dev / format. Whatever exists.",
|
|
12512
|
+
"- **Conventions** \u2014 only things visible in the code. Examples:",
|
|
12513
|
+
" '*.test.ts colocated with source', 'named exports only',",
|
|
12514
|
+
" 'commits use Conventional Commits prefix'. If you can't find any",
|
|
12515
|
+
" CONVENTION evidence, omit the whole section.",
|
|
12516
|
+
"- **Watch out for** \u2014 gotchas a new contributor would benefit from",
|
|
12517
|
+
" knowing BEFORE editing. Examples: 'edit_file SEARCH must match",
|
|
12518
|
+
" byte-for-byte', 'this dir is generated, don't edit by hand'.",
|
|
12519
|
+
" Omit if you find nothing concrete.",
|
|
12520
|
+
"",
|
|
12521
|
+
"## Output",
|
|
12522
|
+
"",
|
|
12523
|
+
"Write the result to `REASONIX.md` in the project root using the",
|
|
12524
|
+
"filesystem tools (edit_file with empty SEARCH if creating new,",
|
|
12525
|
+
"write_file if overwriting). After writing, STOP \u2014 do not summarize",
|
|
12526
|
+
"what you did, do not propose follow-up tasks. The user will review",
|
|
12527
|
+
"the pending edit via /apply.",
|
|
12528
|
+
"",
|
|
12529
|
+
"Start now."
|
|
12530
|
+
].join("\n");
|
|
12531
|
+
var init = (args, _loop, ctx) => {
|
|
12532
|
+
if (!ctx.codeRoot) {
|
|
12533
|
+
return {
|
|
12534
|
+
info: [
|
|
12535
|
+
"/init only works in code mode (it needs filesystem tools).",
|
|
12536
|
+
"Run `reasonix code [path]` to start a session rooted at the",
|
|
12537
|
+
"project you want to initialize, then run /init."
|
|
12538
|
+
].join("\n")
|
|
12539
|
+
};
|
|
12540
|
+
}
|
|
12541
|
+
const force = (args[0] ?? "").toLowerCase() === "force";
|
|
12542
|
+
const target = pathMod6.join(ctx.codeRoot, "REASONIX.md");
|
|
12543
|
+
if (existsSync15(target) && !force) {
|
|
12544
|
+
return {
|
|
12545
|
+
info: [
|
|
12546
|
+
`\u25B8 REASONIX.md already exists at ${target}`,
|
|
12547
|
+
"",
|
|
12548
|
+
" /init force regenerate from scratch (overwrites)",
|
|
12549
|
+
"",
|
|
12550
|
+
" Or edit it by hand \u2014 it's just markdown. The current file is",
|
|
12551
|
+
" pinned into the system prompt every launch as-is."
|
|
12552
|
+
].join("\n")
|
|
12553
|
+
};
|
|
12554
|
+
}
|
|
12555
|
+
return {
|
|
12556
|
+
info: [
|
|
12557
|
+
"\u25B8 /init \u2014 model will scan the project and synthesize REASONIX.md.",
|
|
12558
|
+
" The result lands as a pending edit; review with /apply or /walk."
|
|
12559
|
+
].join("\n"),
|
|
12560
|
+
resubmit: INIT_PROMPT
|
|
12561
|
+
};
|
|
12562
|
+
};
|
|
12563
|
+
var handlers4 = {
|
|
12564
|
+
init
|
|
12565
|
+
};
|
|
12566
|
+
|
|
12463
12567
|
// src/cli/ui/slash/handlers/jobs.ts
|
|
12464
12568
|
var jobs = (_args, _loop, ctx) => {
|
|
12465
12569
|
if (!ctx.jobs) {
|
|
@@ -12515,7 +12619,7 @@ $ ${out.command}`;
|
|
|
12515
12619
|
return { info: out.output ? `${header2}
|
|
12516
12620
|
${out.output}` : header2 };
|
|
12517
12621
|
};
|
|
12518
|
-
var
|
|
12622
|
+
var handlers5 = {
|
|
12519
12623
|
jobs,
|
|
12520
12624
|
kill,
|
|
12521
12625
|
logs
|
|
@@ -12576,7 +12680,7 @@ var mcp = (_args, loop2, ctx) => {
|
|
|
12576
12680
|
lines.push("To change this set, exit and run `reasonix setup`.");
|
|
12577
12681
|
return { info: lines.join("\n") };
|
|
12578
12682
|
};
|
|
12579
|
-
var
|
|
12683
|
+
var handlers6 = { mcp };
|
|
12580
12684
|
|
|
12581
12685
|
// src/cli/ui/slash/handlers/memory.ts
|
|
12582
12686
|
var memory = (args, _loop, ctx) => {
|
|
@@ -12711,7 +12815,7 @@ var memory = (args, _loop, ctx) => {
|
|
|
12711
12815
|
);
|
|
12712
12816
|
return { info: parts.join("\n") };
|
|
12713
12817
|
};
|
|
12714
|
-
var
|
|
12818
|
+
var handlers7 = { memory };
|
|
12715
12819
|
|
|
12716
12820
|
// src/cli/ui/slash/handlers/model.ts
|
|
12717
12821
|
var model = (args, loop2, ctx) => {
|
|
@@ -12864,7 +12968,7 @@ var pro = (args, loop2, ctx) => {
|
|
|
12864
12968
|
};
|
|
12865
12969
|
};
|
|
12866
12970
|
var ESCALATION_MODEL_ID = "deepseek-v4-pro";
|
|
12867
|
-
var
|
|
12971
|
+
var handlers8 = {
|
|
12868
12972
|
model,
|
|
12869
12973
|
models,
|
|
12870
12974
|
harvest: harvest2,
|
|
@@ -13017,7 +13121,7 @@ var compact = (args, loop2) => {
|
|
|
13017
13121
|
info: `\u25B8 compacted ${healedCount} payload(s) to ${cap.toLocaleString()} tokens each (tool results + tool-call args), saved ${tokensSaved.toLocaleString()} tokens (${charsSaved.toLocaleString()} chars). Session file rewritten.`
|
|
13018
13122
|
};
|
|
13019
13123
|
};
|
|
13020
|
-
var
|
|
13124
|
+
var handlers9 = {
|
|
13021
13125
|
think,
|
|
13022
13126
|
reasoning: think,
|
|
13023
13127
|
tool,
|
|
@@ -13105,7 +13209,7 @@ var replay = (args, loop2) => {
|
|
|
13105
13209
|
}
|
|
13106
13210
|
};
|
|
13107
13211
|
};
|
|
13108
|
-
var
|
|
13212
|
+
var handlers10 = {
|
|
13109
13213
|
plans,
|
|
13110
13214
|
replay
|
|
13111
13215
|
};
|
|
@@ -13478,7 +13582,7 @@ async function readIndexMeta(rootDir) {
|
|
|
13478
13582
|
return null;
|
|
13479
13583
|
}
|
|
13480
13584
|
}
|
|
13481
|
-
var
|
|
13585
|
+
var handlers11 = {
|
|
13482
13586
|
semantic
|
|
13483
13587
|
};
|
|
13484
13588
|
|
|
@@ -13513,7 +13617,7 @@ var forget = (_args, loop2) => {
|
|
|
13513
13617
|
info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
|
|
13514
13618
|
};
|
|
13515
13619
|
};
|
|
13516
|
-
var
|
|
13620
|
+
var handlers12 = {
|
|
13517
13621
|
sessions,
|
|
13518
13622
|
forget
|
|
13519
13623
|
};
|
|
@@ -13589,7 +13693,7 @@ ${found.body}${argsLine}`;
|
|
|
13589
13693
|
resubmit: payload
|
|
13590
13694
|
};
|
|
13591
13695
|
};
|
|
13592
|
-
var
|
|
13696
|
+
var handlers13 = {
|
|
13593
13697
|
skill,
|
|
13594
13698
|
skills: skill
|
|
13595
13699
|
};
|
|
@@ -13607,7 +13711,8 @@ var HANDLERS = {
|
|
|
13607
13711
|
...handlers9,
|
|
13608
13712
|
...handlers10,
|
|
13609
13713
|
...handlers11,
|
|
13610
|
-
...handlers12
|
|
13714
|
+
...handlers12,
|
|
13715
|
+
...handlers13
|
|
13611
13716
|
};
|
|
13612
13717
|
function handleSlash(cmd, args, loop2, ctx = {}) {
|
|
13613
13718
|
const h = HANDLERS[cmd];
|
|
@@ -15403,8 +15508,8 @@ function App({
|
|
|
15403
15508
|
const parsed = JSON.parse(ev.toolArgs);
|
|
15404
15509
|
if (typeof parsed.path === "string" && parsed.path.trim()) {
|
|
15405
15510
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
15406
|
-
const expanded = parsed.path.startsWith("~") && home ?
|
|
15407
|
-
const abs =
|
|
15511
|
+
const expanded = parsed.path.startsWith("~") && home ? pathMod7.join(home, parsed.path.slice(1)) : parsed.path;
|
|
15512
|
+
const abs = pathMod7.resolve(expanded);
|
|
15408
15513
|
setPendingWorkspace({ path: abs });
|
|
15409
15514
|
}
|
|
15410
15515
|
} catch {
|
|
@@ -16473,7 +16578,7 @@ async function chatCommand(opts) {
|
|
|
16473
16578
|
const prior = loadSessionMessages(opts.session);
|
|
16474
16579
|
if (prior.length > 0) {
|
|
16475
16580
|
const p = sessionPath(opts.session);
|
|
16476
|
-
const mtime =
|
|
16581
|
+
const mtime = existsSync16(p) ? statSync9(p).mtime : /* @__PURE__ */ new Date();
|
|
16477
16582
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
16478
16583
|
}
|
|
16479
16584
|
} else if (opts.session && opts.forceNew) {
|