@zhihaohong52/sonata 0.4.0
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/LICENSE +21 -0
- package/README.md +437 -0
- package/dist/adapters/claude.js +83 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.js +382 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.js +104 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/pi.js +152 -0
- package/dist/adapters/pi.js.map +1 -0
- package/dist/adapters/reasonix.js +358 -0
- package/dist/adapters/reasonix.js.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/agent-markers.js +3 -0
- package/dist/agent-markers.js.map +1 -0
- package/dist/aipricing.js +109 -0
- package/dist/aipricing.js.map +1 -0
- package/dist/catalog.js +274 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.js +766 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/approve.js +23 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/auth.js +41 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/catalog.js +172 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/code.js +113 -0
- package/dist/commands/code.js.map +1 -0
- package/dist/commands/dispatch.js +148 -0
- package/dist/commands/dispatch.js.map +1 -0
- package/dist/commands/doctor.js +545 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gc.js +25 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/init-log.js +83 -0
- package/dist/commands/init-log.js.map +1 -0
- package/dist/commands/init.js +157 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/log.js +24 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/route.js +660 -0
- package/dist/commands/route.js.map +1 -0
- package/dist/commands/run.js +199 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/runs.js +37 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/serve.js +818 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/status.js +14 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.js +258 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/tail.js +229 -0
- package/dist/commands/tail.js.map +1 -0
- package/dist/commands/usage.js +85 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/verify.js +69 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/wait.js +38 -0
- package/dist/commands/wait.js.map +1 -0
- package/dist/config.js +588 -0
- package/dist/config.js.map +1 -0
- package/dist/detect.js +445 -0
- package/dist/detect.js.map +1 -0
- package/dist/filelock.js +83 -0
- package/dist/filelock.js.map +1 -0
- package/dist/init/apply.js +96 -0
- package/dist/init/apply.js.map +1 -0
- package/dist/init/candidates.js +115 -0
- package/dist/init/candidates.js.map +1 -0
- package/dist/init/discover.js +216 -0
- package/dist/init/discover.js.map +1 -0
- package/dist/init/helpers.js +429 -0
- package/dist/init/helpers.js.map +1 -0
- package/dist/init/interactive-state.js +128 -0
- package/dist/init/interactive-state.js.map +1 -0
- package/dist/init/plan.js +225 -0
- package/dist/init/plan.js.map +1 -0
- package/dist/init/scripted-state.js +130 -0
- package/dist/init/scripted-state.js.map +1 -0
- package/dist/init/toml.js +83 -0
- package/dist/init/toml.js.map +1 -0
- package/dist/init/validate.js +149 -0
- package/dist/init/validate.js.map +1 -0
- package/dist/ledger.js +123 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp/protocol.js +64 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.js +96 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.js +210 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mode.js +64 -0
- package/dist/mode.js.map +1 -0
- package/dist/native/claude-shim.js +228 -0
- package/dist/native/claude-shim.js.map +1 -0
- package/dist/native/codex-auth.js +177 -0
- package/dist/native/codex-auth.js.map +1 -0
- package/dist/native/copilot-auth.js +105 -0
- package/dist/native/copilot-auth.js.map +1 -0
- package/dist/native/credentials.js +83 -0
- package/dist/native/credentials.js.map +1 -0
- package/dist/native/hosts.js +60 -0
- package/dist/native/hosts.js.map +1 -0
- package/dist/native/litellm.js +103 -0
- package/dist/native/litellm.js.map +1 -0
- package/dist/native/models.js +96 -0
- package/dist/native/models.js.map +1 -0
- package/dist/native/oauth-login.js +112 -0
- package/dist/native/oauth-login.js.map +1 -0
- package/dist/native/router.js +469 -0
- package/dist/native/router.js.map +1 -0
- package/dist/native/tls.js +73 -0
- package/dist/native/tls.js.map +1 -0
- package/dist/native/usage.js +119 -0
- package/dist/native/usage.js.map +1 -0
- package/dist/normalize.js +98 -0
- package/dist/normalize.js.map +1 -0
- package/dist/pricing.js +86 -0
- package/dist/pricing.js.map +1 -0
- package/dist/roles.js +49 -0
- package/dist/roles.js.map +1 -0
- package/dist/sessions.js +62 -0
- package/dist/sessions.js.map +1 -0
- package/dist/settings.js +131 -0
- package/dist/settings.js.map +1 -0
- package/dist/store.js +70 -0
- package/dist/store.js.map +1 -0
- package/dist/tmux.js +89 -0
- package/dist/tmux.js.map +1 -0
- package/dist/tui-ink/app-state.js +340 -0
- package/dist/tui-ink/app-state.js.map +1 -0
- package/dist/tui-ink/app.js +156 -0
- package/dist/tui-ink/app.js.map +1 -0
- package/dist/tui-ink/components/byok-step.js +102 -0
- package/dist/tui-ink/components/byok-step.js.map +1 -0
- package/dist/tui-ink/components/login-screen.js +80 -0
- package/dist/tui-ink/components/login-screen.js.map +1 -0
- package/dist/tui-ink/components/models-step.js +92 -0
- package/dist/tui-ink/components/models-step.js.map +1 -0
- package/dist/tui-ink/components/multi-select-state.js +56 -0
- package/dist/tui-ink/components/multi-select-state.js.map +1 -0
- package/dist/tui-ink/components/multi-select.js +69 -0
- package/dist/tui-ink/components/multi-select.js.map +1 -0
- package/dist/tui-ink/components/providers-step.js +285 -0
- package/dist/tui-ink/components/providers-step.js.map +1 -0
- package/dist/tui-ink/components/ranked-select-state.js +41 -0
- package/dist/tui-ink/components/ranked-select-state.js.map +1 -0
- package/dist/tui-ink/components/ranked-select.js +61 -0
- package/dist/tui-ink/components/ranked-select.js.map +1 -0
- package/dist/tui-ink/components/search-select.js +47 -0
- package/dist/tui-ink/components/search-select.js.map +1 -0
- package/dist/tui-ink/components/text-input-state.js +42 -0
- package/dist/tui-ink/components/text-input-state.js.map +1 -0
- package/dist/tui-ink/components/text-input.js +48 -0
- package/dist/tui-ink/components/text-input.js.map +1 -0
- package/dist/tui-ink/run.js +38 -0
- package/dist/tui-ink/run.js.map +1 -0
- package/dist/tui-ink/test-compile.js +4 -0
- package/dist/tui-ink/test-compile.js.map +1 -0
- package/dist/tui-ink/types.js +2 -0
- package/dist/tui-ink/types.js.map +1 -0
- package/dist/tui.js +330 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/watchdog.js +85 -0
- package/dist/watchdog.js.map +1 -0
- package/hooks/capture-mode.mjs +56 -0
- package/hooks/ensure-serve.mjs +140 -0
- package/hooks/hooks.json +12 -0
- package/hooks/route-session.mjs +47 -0
- package/hooks/route-subagent.mjs +54 -0
- package/package.json +64 -0
- package/roles/code.md +13 -0
- package/roles/explore.md +9 -0
- package/roles/plan.md +10 -0
- package/roles/review.md +11 -0
- package/skills/loop/SKILL.md +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James @zhihaohong52
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# sonata
|
|
2
|
+
|
|
3
|
+
**Foreign-model subagents for Claude Code.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/zhihaohong52/sonata/actions/workflows/ci.yml)
|
|
6
|
+
[](CHANGELOG.md)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
10
|
+
Claude Code's subagents are excellent, but they are always Claude. Sonata lets
|
|
11
|
+
you dispatch a subagent backed by a different model, through the ordinary
|
|
12
|
+
Agent tool — same interface, same working directory, same report contract.
|
|
13
|
+
Different brain.
|
|
14
|
+
|
|
15
|
+
Two ways to run it:
|
|
16
|
+
|
|
17
|
+
- **Native** — the foreign model runs *inside Claude Code's own loop*: its
|
|
18
|
+
tools, its permission modes, no separate TUI. A local routing proxy
|
|
19
|
+
(`sonata serve`) makes this possible; see the [Native path guide](docs/guide/native-path.md).
|
|
20
|
+
This is the default: `sonata init` generates one tier agent per role, each
|
|
21
|
+
backed by a ranked list of models the router tries in order.
|
|
22
|
+
- **Harness** — the foreign model runs in *its own* CLI (OpenCode, Codex, Pi,
|
|
23
|
+
Reasonix), launched in a detached tmux session and driven through
|
|
24
|
+
`sonata dispatch`. No proxy required — this is the fallback lane: when
|
|
25
|
+
every native route for a tier has failed, the router says so and names the
|
|
26
|
+
`sonata dispatch --tier <role>-<tier>` command, which the agent runs itself.
|
|
27
|
+
|
|
28
|
+
Two reasons you might want that:
|
|
29
|
+
|
|
30
|
+
- **Cost.** Mechanical work — bulk refactors, test scaffolding, migrations —
|
|
31
|
+
does not need a frontier model. A cheap high-cache model is an adequate
|
|
32
|
+
substitute, and the difference over a working day is large.
|
|
33
|
+
- **Diversity of judgement.** Asking Claude to review Claude's work surfaces
|
|
34
|
+
fewer problems than asking a different model family. Review is where a second
|
|
35
|
+
opinion actually pays.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
● code-deepseek-v4-flash (running)
|
|
39
|
+
└ sonata: tail a1f3e2
|
|
40
|
+
→ read src/parser.ts
|
|
41
|
+
→ edit src/parser.ts +31 −12
|
|
42
|
+
→ bash pytest -q → 47 passed
|
|
43
|
+
done — "Refactored the parser. Tests pass."
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Status
|
|
47
|
+
|
|
48
|
+
**Working, early.** The engine and the OpenCode, Codex, Pi and Reasonix
|
|
49
|
+
harness adapters are complete and tested, each verified end to end against a
|
|
50
|
+
real model. The native path (`sonata serve`/`sonata code`/`sonata restart`
|
|
51
|
+
and the `claude` harness adapter) is also complete and verified live —
|
|
52
|
+
routing, permission modes, and the router's Codex-overload handling have all
|
|
53
|
+
been confirmed against real dispatches.
|
|
54
|
+
|
|
55
|
+
Read [Limitations](docs/guide/limitations.md) and
|
|
56
|
+
[Security](docs/guide/security.md) before depending on it.
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- **Node 22+**
|
|
61
|
+
- **tmux** — every harness runs inside a tmux session (`brew install tmux`,
|
|
62
|
+
`apt install tmux`)
|
|
63
|
+
- **LiteLLM (optional, native path only)** — install with `pip install 'litellm[proxy]'`
|
|
64
|
+
- **macOS or Linux.** Sonata launches bash scripts inside tmux and manages
|
|
65
|
+
process groups directly; Windows is not supported. WSL should work but is
|
|
66
|
+
untested.
|
|
67
|
+
- **A provider.** Either an API key for one of ~30 well-known providers (see
|
|
68
|
+
[BYOK](#byok-bring-your-own-key) — no harness needed), or at least one of the
|
|
69
|
+
harnesses below, authenticated:
|
|
70
|
+
- **[OpenCode](https://opencode.ai)** — any provider it supports
|
|
71
|
+
- **[Codex CLI](https://github.com/openai/codex)** — `codex login`
|
|
72
|
+
- **[Pi](https://github.com/earendil-works/pi-mono)** — any provider it
|
|
73
|
+
supports; model ids take the `provider/id` form
|
|
74
|
+
- **[Reasonix](https://github.com/esengine/DeepSeek-Reasonix)** — `reasonix setup`;
|
|
75
|
+
any OpenAI-compatible endpoint is a config entry, and model ids take the
|
|
76
|
+
`provider/id` form
|
|
77
|
+
- **Claude Code** — the `claude` harness adapter (native path; see the
|
|
78
|
+
[Native path guide](docs/guide/native-path.md))
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install -g @zhihaohong52/sonata
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### From source
|
|
87
|
+
|
|
88
|
+
For working on sonata itself, or to run a change that has not been released:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/zhihaohong52/sonata.git
|
|
92
|
+
cd sonata
|
|
93
|
+
npm install
|
|
94
|
+
npm run build
|
|
95
|
+
npm link # puts `sonata` on your PATH
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`sonata` on your PATH runs `dist/`, not `src/` — after changing anything under
|
|
99
|
+
`src/`, run `npm run build` or the command keeps its old behaviour.
|
|
100
|
+
|
|
101
|
+
Then, in the repository where you want to use it:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
sonata init
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`sonata init` detects your environment, reports anything broken with the command
|
|
108
|
+
that fixes it, and asks which providers, models and roles you want.
|
|
109
|
+
|
|
110
|
+
Provider setup is a menu, modeled on opencode's own `/connect`: bulk-import
|
|
111
|
+
everything you're already logged into elsewhere, or add providers one at a
|
|
112
|
+
time — including one sonata has never heard of.
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
███████╗ ██████╗ ███╗ ██╗ █████╗ ████████╗ █████╗
|
|
116
|
+
██╔════╝██╔═══██╗████╗ ██║██╔══██╗╚══██╔══╝██╔══██╗
|
|
117
|
+
███████╗██║ ██║██╔██╗ ██║███████║ ██║ ███████║
|
|
118
|
+
╚════██║██║ ██║██║╚██╗██║██╔══██║ ██║ ██╔══██║
|
|
119
|
+
███████║╚██████╔╝██║ ╚████║██║ ██║ ██║ ██║ ██║
|
|
120
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
121
|
+
|
|
122
|
+
foreign-model subagents for Claude Code
|
|
123
|
+
|
|
124
|
+
✓ tmux 3.7b
|
|
125
|
+
✓ opencode 1.18.19 · 520 models
|
|
126
|
+
✓ codex codex-cli 0.149.0 · 6 models
|
|
127
|
+
|
|
128
|
+
Set up providers
|
|
129
|
+
❯ Import from other harnesses
|
|
130
|
+
Add provider
|
|
131
|
+
|
|
132
|
+
↑↓ choose · enter confirm · ← back · esc cancel
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Import from other harnesses** bulk-selects providers your installed
|
|
136
|
+
harnesses are already authenticated for — nothing to type, nothing to
|
|
137
|
+
re-enter. It lists every provider with a detected credential (an OAuth grant
|
|
138
|
+
or a plain API key sitting in another harness's own store), whether or not
|
|
139
|
+
it is already configured, and pre-checks the ones that are — so the same
|
|
140
|
+
screen doubles as a toggle: unchecking an already-imported provider removes
|
|
141
|
+
it, along with its models and any role assignments that used them:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Import from other harnesses
|
|
145
|
+
|
|
146
|
+
filter: █
|
|
147
|
+
|
|
148
|
+
❯ ◉ codex · expires in 8d
|
|
149
|
+
○ openai · expires in 8d
|
|
150
|
+
|
|
151
|
+
2 of 2 · space toggle · type to filter · enter confirm · esc cancel
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Add provider** searches the merged catalogue of every provider a harness
|
|
155
|
+
knows about plus ~30 well-known BYOK providers — or lets you type in one
|
|
156
|
+
sonata has never seen:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
Add provider
|
|
160
|
+
|
|
161
|
+
filter: █
|
|
162
|
+
|
|
163
|
+
❯ acme · opencode
|
|
164
|
+
anthropic · opencode
|
|
165
|
+
codex · codex
|
|
166
|
+
deepseek · byok
|
|
167
|
+
⋮
|
|
168
|
+
Add a custom provider…
|
|
169
|
+
|
|
170
|
+
38 of 38 · ↑↓ choose · type to filter · enter confirm · ← back · esc cancel
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Picking a known provider offers **Run OAuth login** (for `codex` and
|
|
174
|
+
`github-copilot`, the two providers sonata can authenticate on its own — see
|
|
175
|
+
the [Native path guide](docs/guide/native-path.md)) or **Enter an API key**.
|
|
176
|
+
Picking a harness-catalogued provider then shows its models to select from:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
Models to enable
|
|
180
|
+
|
|
181
|
+
filter: gpt█
|
|
182
|
+
|
|
183
|
+
❯ ◉ openai/gpt-5.6-sol · GPT-5.6 Sol
|
|
184
|
+
○ openai/gpt-5.6-luna · GPT-5.6 Luna
|
|
185
|
+
|
|
186
|
+
2 of 13 · space toggle · type to filter · enter confirm · esc cancel
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then it asks you to rank your selected models into `simple`/`complex` tiers per
|
|
190
|
+
role — pre-sorted by a cached Artificial Analysis catalog (`sonata catalog
|
|
191
|
+
update`, with a free key from [artificialanalysis.ai](https://artificialanalysis.ai))
|
|
192
|
+
when one exists, or built-in defaults otherwise. `complex` is ordered by raw
|
|
193
|
+
capability and `simple` by capability **per task-dollar**, so demanding work
|
|
194
|
+
goes to the strongest model you picked and grunt work to the one that returns
|
|
195
|
+
the most per dollar rather than merely the cheapest. Set `avoid_gateways` in
|
|
196
|
+
`sonata.toml` to rank a particular gateway's models last without dropping them
|
|
197
|
+
as fallbacks. It then asks whether the config
|
|
198
|
+
applies to this project or the whole machine, writes `sonata.toml`, generates
|
|
199
|
+
one agent per role × tier, offers to install the permission hook, installs the
|
|
200
|
+
`sonata-loop` skill, and offers to run `sonata route auto` so the generated
|
|
201
|
+
agents have a router to reach. Project scope writes `./sonata.toml` and
|
|
202
|
+
`./.claude/agents/`; machine scope writes `~/.config/sonata/sonata.toml` and
|
|
203
|
+
`~/.claude/agents/`, where Claude Code offers the agents in every repository.
|
|
204
|
+
|
|
205
|
+
Claude Code picks up the new agents automatically; no restart is needed.
|
|
206
|
+
|
|
207
|
+
Every prompt has a flag, so it also works unattended:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
sonata init --yes --providers opencode/openai --models opencode-openai-gpt-5.6-sol --roles code,review --scope project
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### BYOK (bring your own key)
|
|
214
|
+
|
|
215
|
+
Sonata does not need a harness. **Add provider** lists ~30 well-known
|
|
216
|
+
providers alongside anything your harnesses discovered; pick one, enter its
|
|
217
|
+
API key, and choose from the models it reports. A provider not on that
|
|
218
|
+
list — an internal proxy, a self-hosted endpoint — doesn't need to be:
|
|
219
|
+
**Add a custom provider…**, at the bottom of that same search list, asks for
|
|
220
|
+
a name, a base URL, and whether it speaks OpenAI's or Anthropic's wire
|
|
221
|
+
format, then drops into the same key-entry flow.
|
|
222
|
+
|
|
223
|
+
The key is stored in sonata's own key store — never in `sonata.toml`, never in
|
|
224
|
+
an agent file, and never on the command line. It is written only after you
|
|
225
|
+
confirm the summary, so cancelling the wizard stores nothing.
|
|
226
|
+
|
|
227
|
+
Unattended, with the key stored first so it stays out of argv and shell history:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
sonata auth add deepseek
|
|
231
|
+
sonata init --yes --providers byok/deepseek --models deepseek-deepseek-v4-flash --roles code
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
BYOK models use the [native path](docs/guide/native-path.md), so they run
|
|
235
|
+
inside Claude Code's own loop, tools and permission modes.
|
|
236
|
+
|
|
237
|
+
Two things worth knowing before you pick a provider:
|
|
238
|
+
|
|
239
|
+
- **Model discovery is a convention, not a guarantee.** Sonata asks the provider
|
|
240
|
+
for `GET <base_url>/models`, which every provider in its list implements. If
|
|
241
|
+
yours does not answer — offline, or a different shape — the wizard asks you to
|
|
242
|
+
type the model ids instead, saying which of those happened. That is a
|
|
243
|
+
fallback, not an error.
|
|
244
|
+
- **A rejected key gets its own screen.** If the provider answers 401 or 403,
|
|
245
|
+
sonata says so and offers to take a different key. It also offers to keep the
|
|
246
|
+
one you gave and type ids by hand, because some providers refuse to list
|
|
247
|
+
models for a key that works perfectly well for inference.
|
|
248
|
+
- **`claude-*` models are not offered.** The router forwards that prefix to
|
|
249
|
+
Anthropic, so a `claude-` id cannot be served through a gateway. Aggregators
|
|
250
|
+
such as OpenRouter list plenty of them; sonata filters them out rather than
|
|
251
|
+
writing a config it would then refuse to load.
|
|
252
|
+
|
|
253
|
+
### Adding Codex models
|
|
254
|
+
|
|
255
|
+
**The wizard discovers OpenCode, Pi and Reasonix models** by provider then by
|
|
256
|
+
model, and **Codex through `codex app-server`'s `model/list`** (JSON-RPC over
|
|
257
|
+
stdio — codex has no provider dimension and takes a bare model id). Hand-written
|
|
258
|
+
codex entries survive `sonata init`, so a bare-id model can also be added by
|
|
259
|
+
hand:
|
|
260
|
+
|
|
261
|
+
```toml
|
|
262
|
+
[models."gpt-5-6-sol"]
|
|
263
|
+
harness = "codex"
|
|
264
|
+
id = "gpt-5.6-sol"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Hand-written entries survive `sonata init` — the wizard carries through any
|
|
268
|
+
model whose harness it does not manage.
|
|
269
|
+
|
|
270
|
+
Then run `sonata sync` to regenerate the agent files; Claude Code picks them up automatically.
|
|
271
|
+
|
|
272
|
+
## Using it
|
|
273
|
+
|
|
274
|
+
The generated agents are ordinary registry entries, so Claude selects them the
|
|
275
|
+
same way it selects any other subagent. Ask for one by name, or describe work
|
|
276
|
+
that suits it:
|
|
277
|
+
|
|
278
|
+
> "Use code-simple to convert these callbacks to async/await."
|
|
279
|
+
|
|
280
|
+
> "Get review-complex to look at the auth refactor."
|
|
281
|
+
|
|
282
|
+
Judging `simple` vs `complex` is a call you make per task — mechanical,
|
|
283
|
+
well-specified, single-file work is `simple`; cross-cutting, ambiguous, or
|
|
284
|
+
design-sensitive work is `complex`. When unsure, use `-complex`. The
|
|
285
|
+
`sonata-loop` skill `sonata init` installs (`skills/loop/SKILL.md`) drives
|
|
286
|
+
this across a whole feature: plan, route each task to a tier, gate behind
|
|
287
|
+
review, escalate a task to `complex` after two failed reviews at `simple`.
|
|
288
|
+
|
|
289
|
+
They compose with everything Claude Code already does — parallel fan-out,
|
|
290
|
+
workflows, and `isolation: "worktree"`.
|
|
291
|
+
|
|
292
|
+
## How it works
|
|
293
|
+
|
|
294
|
+
`sonata init` generates one agent per role × difficulty tier — `code-simple`,
|
|
295
|
+
`code-complex`, `review-simple`, and so on. Each agent's frontmatter names a
|
|
296
|
+
router alias (`model: sonata-code-simple`), not a specific model:
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Claude Code
|
|
300
|
+
│ Agent(subagent_type: "code-simple")
|
|
301
|
+
▼
|
|
302
|
+
sonata-code-simple (native — runs in Claude Code's own loop)
|
|
303
|
+
│ model: sonata-code-simple
|
|
304
|
+
▼
|
|
305
|
+
router (sonata serve)
|
|
306
|
+
│ resolves the alias against [tiers.code].simple, tries each
|
|
307
|
+
│ candidate in rank order, skips one in cooldown after a failure
|
|
308
|
+
▼
|
|
309
|
+
litellm → flash-1 (or the next-ranked model, on failure)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The router is the only place ranking lives — the agent just sends its alias
|
|
313
|
+
and gets an answer from whichever model actually worked. If every native
|
|
314
|
+
candidate for a tier fails, the router returns 529 naming the fallback:
|
|
315
|
+
`sonata dispatch --tier code-simple`, which the agent runs itself to reach a
|
|
316
|
+
model through its own harness (OpenCode, Codex, Pi, or Reasonix) instead:
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
sonata dispatch --tier code-simple "<task>"
|
|
320
|
+
│ resolves the tier's harness-routed candidates, in rank order
|
|
321
|
+
▼
|
|
322
|
+
sonata CLI
|
|
323
|
+
│ composes role prompt + CLAUDE.md + task
|
|
324
|
+
│ launches harness in a detached tmux session, waits for it
|
|
325
|
+
▼
|
|
326
|
+
opencode → deepseek-v4-flash (next candidate on a degraded/empty finish)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`sonata dispatch` blocks until the run finishes, needs approval, or stalls,
|
|
330
|
+
trying the next ranked candidate on a thrown launch, a degraded finish, or an
|
|
331
|
+
empty report; `sonata wait`/`sonata approve` resume or unblock a specific run
|
|
332
|
+
by id. `sonata tail` remains available as a human/debugging view of any run.
|
|
333
|
+
|
|
334
|
+
Completion is read from an exit sentinel and a report file, never scraped from
|
|
335
|
+
the terminal. If a harness dies without writing a report, sonata returns the
|
|
336
|
+
captured pane and marks the result `degraded` — so you always know when a
|
|
337
|
+
result is untrustworthy.
|
|
338
|
+
|
|
339
|
+
Progress comes from diffing the tmux pane. You can attach to any live run:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
tmux attach -t sonata-<id> # -r for read-only
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Attaching means you can correct a cheap model that is going off the rails
|
|
346
|
+
instead of paying for the rest of a wasted run.
|
|
347
|
+
|
|
348
|
+
`run_timeout_seconds` is a hard cap, enforced by a watchdog inside the launched
|
|
349
|
+
shell rather than by `sonata tail` — a runaway run with nobody watching is
|
|
350
|
+
exactly the case that needs bounding. On expiry the whole process group is
|
|
351
|
+
killed and the run is reported `DONE`, `degraded`, with a report beginning
|
|
352
|
+
`[timed out: …]`.
|
|
353
|
+
|
|
354
|
+
## Commands
|
|
355
|
+
|
|
356
|
+
| Command | Purpose |
|
|
357
|
+
|---|---|
|
|
358
|
+
| `sonata init` | Set up sonata in this project (interactive); `--prune` removes stale generated agents |
|
|
359
|
+
| `sonata doctor` | Check tmux, harnesses, auth and versions |
|
|
360
|
+
| `sonata sync` | Regenerate agent files from `sonata.toml`; `--prune` removes stale generated agents |
|
|
361
|
+
| `sonata run` | Launch a run, print its id |
|
|
362
|
+
| `sonata dispatch (--tier <role>-<tier> \| --model <key>)` | Blocking harness dispatch with ranked fallback — the fallback lane a tier agent reaches for when every native route fails |
|
|
363
|
+
| `sonata tail` | Human/debugging view of a run |
|
|
364
|
+
| `sonata approve` | Answer a pending approval |
|
|
365
|
+
| `sonata log <id>` | Print a run's whole transcript |
|
|
366
|
+
| `sonata verify <id> [--model <key>]` | Verify a completed run |
|
|
367
|
+
| `sonata auth` | Manage native-path gateway keys (`list`, `add <gateway>`, `remove <gateway>`, `login <gateway>`) |
|
|
368
|
+
| `sonata catalog [update]` | Show the cached Artificial Analysis catalog's age, or refresh it (needs a stored `artificialanalysis` key). `sonata doctor` warns when it goes stale |
|
|
369
|
+
| `sonata serve` | Run the native router and its managed LiteLLM child (`--daemon` detaches) |
|
|
370
|
+
| `sonata restart` | Kill whatever sonata router currently holds the port and start a fresh daemon |
|
|
371
|
+
| `sonata code` | Launch a Claude Code session routed through the local proxy (passes `claude` args through) |
|
|
372
|
+
| `sonata route on\|off\|status [--global]` | Route every plain `claude` session in the project (or, with `--global`, every project) through the proxy via settings.local.json/settings.json |
|
|
373
|
+
| `sonata route auto\|manual [--global]` | Route each session for its lifetime via SessionStart/SessionEnd hooks, keeping Remote Control |
|
|
374
|
+
| `sonata usage [--since 7d] [--by model\|role\|tier\|gateway\|session\|project] [--session <id>] [--json]` | Tokens and cost from the router's ledger (**native path only** — `sonata dispatch` runs never transit the router and are unobservable); unpriced volume is reported separately, never folded into the total |
|
|
375
|
+
| `sonata status [--session <id>\|--all]` | Whether the router is up and on which port, then the recent alias → candidate served → tokens → failed attempts decisions from the ledger; reachability and routing state live in `sonata route status` |
|
|
376
|
+
| `sonata runs [--json]` | List this project's dispatch runs |
|
|
377
|
+
| `sonata gc` | Kill finished tmux sessions |
|
|
378
|
+
|
|
379
|
+
## Documentation
|
|
380
|
+
|
|
381
|
+
The essentials are above. Deep-dive reference lives in
|
|
382
|
+
[`docs/guide/`](docs/guide/):
|
|
383
|
+
|
|
384
|
+
| Guide | Covers |
|
|
385
|
+
|---|---|
|
|
386
|
+
| [Native path](docs/guide/native-path.md) | Running foreign models inside Claude Code's own loop through the local routing proxy |
|
|
387
|
+
| [Codex subscription auth](docs/guide/codex-subscription.md) | Authenticating the `codex-oauth` gateway against a ChatGPT subscription |
|
|
388
|
+
| [Permission modes](docs/guide/permission-modes.md) | How each harness honours Claude Code's permission modes |
|
|
389
|
+
| [Configuration](docs/guide/configuration.md) | The `sonata.toml` schema, resolution order, and roles/tiers |
|
|
390
|
+
| [Troubleshooting](docs/guide/troubleshooting.md) | Symptom → cause table |
|
|
391
|
+
| [Security](docs/guide/security.md) | What sonata does and doesn't protect against |
|
|
392
|
+
| [Limitations](docs/guide/limitations.md) | Known gaps worth knowing before depending on this |
|
|
393
|
+
| [Adding a harness](docs/guide/adding-a-harness.md) | The adapter extension point |
|
|
394
|
+
|
|
395
|
+
Where this is headed: [Roadmap to 1.0](docs/roadmap.md).
|
|
396
|
+
|
|
397
|
+
Design history — every feature's spec and implementation plan, kept as a
|
|
398
|
+
permanent record — is indexed in [`docs/superpowers/`](docs/superpowers/).
|
|
399
|
+
Architecture reviews are in [`docs/reviews/`](docs/reviews/). What using
|
|
400
|
+
sonata to implement sonata taught about sizing and verifying dispatched work
|
|
401
|
+
is in [`docs/dispatching-work-through-sonata.md`](docs/dispatching-work-through-sonata.md).
|
|
402
|
+
|
|
403
|
+
## Development
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
npm install
|
|
407
|
+
npm test # 1177 tests; needs tmux
|
|
408
|
+
npm run typecheck
|
|
409
|
+
npm run build
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
The test suite runs against a **fake harness** — a scripted binary replaying a
|
|
413
|
+
normal run, a crash, a real captured approval prompt, a hang that the watchdog
|
|
414
|
+
must kill, a clean exit with no report, and a harness-written report — so the
|
|
415
|
+
whole engine is covered with no API spend and no harness installed.
|
|
416
|
+
|
|
417
|
+
See [Adding a harness](docs/guide/adding-a-harness.md) for the extension
|
|
418
|
+
point, and [Documentation](#documentation) above for the design-history index.
|
|
419
|
+
|
|
420
|
+
## Contributing
|
|
421
|
+
|
|
422
|
+
Issues and pull requests are welcome.
|
|
423
|
+
|
|
424
|
+
- Run `npm test` and `npm run typecheck` before opening a PR; CI runs both on
|
|
425
|
+
Linux with tmux installed.
|
|
426
|
+
- Add tests for behaviour you change. The suite needs no API keys.
|
|
427
|
+
- Prefer evidence to inference: if a change depends on how a harness behaves,
|
|
428
|
+
say how you verified it. A captured fixture beats a plausible regex.
|
|
429
|
+
- Keep harness-specific knowledge inside its adapter.
|
|
430
|
+
|
|
431
|
+
## Changelog
|
|
432
|
+
|
|
433
|
+
See [CHANGELOG.md](CHANGELOG.md) for release notes.
|
|
434
|
+
|
|
435
|
+
## License
|
|
436
|
+
|
|
437
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isReadOnlyRole, loadConfig } from '../config.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
const PROMPT_PATTERNS = [];
|
|
4
|
+
function shellQuote(value) {
|
|
5
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
6
|
+
}
|
|
7
|
+
function buildScript(input) {
|
|
8
|
+
const readOnly = isReadOnlyRole(input.role);
|
|
9
|
+
const permissionMode = readOnly ? 'plan' : input.mode;
|
|
10
|
+
const flags = [
|
|
11
|
+
'-p',
|
|
12
|
+
`--model ${shellQuote(input.modelId)}`,
|
|
13
|
+
`--permission-mode ${permissionMode}`,
|
|
14
|
+
];
|
|
15
|
+
// `--allowedTools <value>` (space form) is variadic in claude's own CLI
|
|
16
|
+
// parser: it keeps consuming every subsequent bare argument — including
|
|
17
|
+
// the prompt itself — until the next `--flag`. Probed directly: the
|
|
18
|
+
// prompt text ended up split on commas/whitespace into garbage
|
|
19
|
+
// "allowedTools" rules, and `-p` then had no prompt argument left at all.
|
|
20
|
+
// The `=` form binds exactly one value and does not swallow what follows.
|
|
21
|
+
if (readOnly)
|
|
22
|
+
flags.push('--allowedTools=Read,Grep,Glob,Bash');
|
|
23
|
+
// Resolve the actual router URL from config rather than inheriting from
|
|
24
|
+
// the parent env — the parent is typically an unproxied session where
|
|
25
|
+
// ANTHROPIC_BASE_URL is unset.
|
|
26
|
+
let routerUrl = '';
|
|
27
|
+
let contextWindow = '';
|
|
28
|
+
try {
|
|
29
|
+
const config = loadConfig(input.cwd, homedir());
|
|
30
|
+
if (config.native) {
|
|
31
|
+
routerUrl = `http://localhost:${config.native.ports.router}`;
|
|
32
|
+
const windows = Object.values(config.native.models).map(m => m.contextWindow);
|
|
33
|
+
if (windows.length > 0)
|
|
34
|
+
contextWindow = String(Math.min(...windows));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// No config or no native table — the script will run claude without
|
|
39
|
+
// proxy routing, which means the API rejects the unknown model id.
|
|
40
|
+
// That failure is legible; swallowing here keeps the adapter from
|
|
41
|
+
// crashing before it can produce it.
|
|
42
|
+
}
|
|
43
|
+
const envLines = routerUrl
|
|
44
|
+
? [
|
|
45
|
+
`export ANTHROPIC_BASE_URL=${shellQuote(routerUrl)}`,
|
|
46
|
+
...(contextWindow ? [`export CLAUDE_CODE_MAX_CONTEXT_TOKENS=${shellQuote(contextWindow)}`] : []),
|
|
47
|
+
]
|
|
48
|
+
: [];
|
|
49
|
+
const script = [
|
|
50
|
+
'#!/bin/bash',
|
|
51
|
+
'set -o pipefail',
|
|
52
|
+
...envLines,
|
|
53
|
+
`cd ${shellQuote(input.cwd)} || exit 97`,
|
|
54
|
+
// No tee — both anomalies observed on this adapter (a hang, and a run
|
|
55
|
+
// that fabricated instead of calling tools) happened with claude -p piped
|
|
56
|
+
// through tee, and neither reproduced without it. Stdout also must NOT be
|
|
57
|
+
// report.md: the instructions ask the model to Write report.md itself, and
|
|
58
|
+
// two writers to one file corrupt whichever finishes second. The final
|
|
59
|
+
// message lands in last-message.txt as the fallback report instead.
|
|
60
|
+
`claude ${flags.join(' ')} "$(cat ${shellQuote(input.instructionsPath)})" > ${shellQuote(`${input.runDir}/last-message.txt`)} 2>&1`,
|
|
61
|
+
`echo $? > ${shellQuote(`${input.runDir}/exit`)}`,
|
|
62
|
+
'',
|
|
63
|
+
].join('\n');
|
|
64
|
+
// silentUntilExit: stdout goes to last-message.txt (see the no-tee comment
|
|
65
|
+
// above), so the pane stays unchanged for the whole run and pane-silence
|
|
66
|
+
// stall detection would mark every long run STALLED.
|
|
67
|
+
return { script, interactive: false, canWriteReport: !readOnly, silentUntilExit: true };
|
|
68
|
+
}
|
|
69
|
+
export const claudeAdapter = {
|
|
70
|
+
name: 'claude',
|
|
71
|
+
versionCommand: ['claude', '--version'],
|
|
72
|
+
supportedVersions: '>=2.1.0 <3.0.0',
|
|
73
|
+
pathPrepend: [],
|
|
74
|
+
plan: buildScript,
|
|
75
|
+
canPromptForApproval: false,
|
|
76
|
+
promptPatterns: PROMPT_PATTERNS,
|
|
77
|
+
describePrompt() {
|
|
78
|
+
return null;
|
|
79
|
+
},
|
|
80
|
+
approveKeys: { yes: [], no: [] },
|
|
81
|
+
fallbackReportFile: 'last-message.txt',
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"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"}
|