@flame0510/project-aether 1.2.0 → 1.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/README.md +3 -1
- package/agent-templates/README.md +42 -22
- package/agent-templates/base-image/Dockerfile +42 -33
- package/agent-templates/base-image/entrypoint.sh +67 -12
- package/app/agents/BrowserAccessSection.tsx +510 -0
- package/app/agents/ChannelManager.tsx +19 -11
- package/app/agents/ImageDownloadBanner.tsx +53 -19
- package/app/agents/ModelSection.tsx +316 -0
- package/app/agents/PageClient.tsx +708 -167
- package/app/agents/UpdateSection.tsx +300 -0
- package/app/agents/create/PageClient.tsx +11 -49
- package/app/agents/create/page.tsx +8 -21
- package/app/api/agents/[id]/backup/route.ts +26 -69
- package/app/api/agents/[id]/channels/pairing/route.ts +3 -3
- package/app/api/agents/[id]/channels/telegram/route.ts +2 -2
- package/app/api/agents/[id]/cold-backup/route.ts +56 -0
- package/app/api/agents/[id]/devices/route.ts +126 -0
- package/app/api/agents/[id]/invite-link/route.ts +53 -0
- package/app/api/agents/[id]/lifecycle/route.ts +3 -0
- package/app/api/agents/[id]/model/route.ts +113 -0
- package/app/api/agents/[id]/open-control-ui/route.ts +58 -0
- package/app/api/agents/[id]/recreate/route.ts +33 -187
- package/app/api/agents/[id]/restart/route.ts +5 -0
- package/app/api/agents/[id]/restore/route.ts +40 -70
- package/app/api/agents/[id]/route.ts +38 -169
- package/app/api/agents/[id]/update/rollback/route.ts +30 -0
- package/app/api/agents/[id]/update/route.ts +50 -0
- package/app/api/agents/activity-summary/route.ts +67 -0
- package/app/api/agents/create/route.ts +91 -145
- package/app/api/agents/devices-summary/route.ts +37 -0
- package/app/api/agents/download-image/route.ts +16 -9
- package/app/api/agents/image-status/route.ts +31 -111
- package/app/api/agents/models-summary/route.ts +163 -0
- package/app/api/agents/route.ts +25 -49
- package/app/api/agents/token/route.ts +33 -10
- package/app/api/assistant/route.ts +37 -16
- package/app/api/gateway/agent/route.ts +37 -6
- package/app/api/gateway/provider/balance/route.ts +5 -2
- package/app/api/gateway/provider/keys.ts +13 -1
- package/app/api/gateway/provider/route.ts +43 -12
- package/app/api/gateway/sync.ts +335 -76
- package/app/api/models/route.ts +28 -34
- package/app/api/provider/auth.ts +65 -0
- package/app/api/provider/upstream.ts +9 -2
- package/app/api/provider/v1/chat/completions/route.ts +22 -16
- package/app/api/provider/v1/models/route.ts +26 -133
- package/app/api/setup/agent-image/route.ts +14 -42
- package/app/components/DashboardToolbar.tsx +1 -1
- package/app/components/PulseChat.tsx +25 -39
- package/app/components/ui/RemoveButton.tsx +46 -0
- package/app/components/ui/Select.tsx +3 -2
- package/app/components/ui/index.ts +1 -0
- package/app/credentials/PageClient.tsx +2 -2
- package/app/gateway/PageClient.tsx +253 -674
- package/app/globals.css +8 -0
- package/app/lib/models-context.tsx +43 -7
- package/app/wizard/useWizard.ts +6 -1
- package/bin/rev4a.js +116 -50
- package/daemon.js +6 -6
- package/docs/ARCHITECTURE.md +110 -12
- package/docs/FRONTEND-ARCHITECTURE.md +31 -2
- package/docs/REV4A.md +93 -33
- package/docs/dev/API-REFERENCE.md +723 -178
- package/docs/dev/DATABASE.md +96 -0
- package/docs/dev/GATEWAY.md +250 -93
- package/docs/dev/PROVIDERS.md +26 -13
- package/docs/rag/DATA-FRESHNESS.md +59 -28
- package/docs/rag/GLOSSARY.md +27 -16
- package/docs/rag/REV4A-OVERVIEW.md +37 -25
- package/docs/rag/WHAT-I-CAN-ANSWER.md +10 -8
- package/instrumentation.ts +52 -1
- package/lib/agent-busy.ts +21 -0
- package/lib/agent-devices.ts +361 -0
- package/lib/agent-edit-state.ts +108 -0
- package/lib/agent-edit.ts +157 -0
- package/lib/agent-images.ts +375 -0
- package/lib/agent-ports-server.ts +27 -0
- package/lib/agent-ports.ts +68 -0
- package/lib/agent-readiness.ts +110 -0
- package/lib/agent-recreate-state.ts +108 -0
- package/lib/agent-recreate.ts +305 -0
- package/lib/agent-restore-state.ts +107 -0
- package/lib/agent-restore.ts +135 -0
- package/lib/agent-setup.ts +66 -17
- package/lib/agent-update-state.ts +122 -0
- package/lib/agent-update.ts +448 -0
- package/lib/agent-versions.json +14 -0
- package/lib/agent-versions.ts +80 -0
- package/lib/buildAgentImage.ts +88 -290
- package/lib/channelManager.ts +153 -64
- package/lib/cold-backup.ts +354 -0
- package/lib/container-file.ts +27 -0
- package/lib/credentials/delivery.ts +3 -3
- package/lib/db-bootstrap.mjs +76 -0
- package/lib/docker-utils.ts +3 -3
- package/lib/model-catalogue.ts +140 -27
- package/lib/provider-balance.ts +33 -12
- package/lib/rev4a-paths.ts +0 -21
- package/model-pricing.json +118 -110
- package/models.config.json +27 -12
- package/package.json +1 -1
- package/app/api/gateway/route.ts +0 -191
package/docs/dev/DATABASE.md
CHANGED
|
@@ -107,6 +107,102 @@ CREATE INDEX idx_metrics_ts ON system_metrics(ts);
|
|
|
107
107
|
|
|
108
108
|
Rows older than 24 h are pruned automatically by the daemon.
|
|
109
109
|
|
|
110
|
+
### `agent_upgrades` — agent updates and rollbacks
|
|
111
|
+
|
|
112
|
+
```sql
|
|
113
|
+
CREATE TABLE agent_upgrades (
|
|
114
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
115
|
+
agent_id TEXT NOT NULL,
|
|
116
|
+
status TEXT NOT NULL, -- pending, backing_up, migrating, verifying, done, failed,
|
|
117
|
+
-- interrupted, rolling_back, rolled_back, rollback_failed
|
|
118
|
+
from_version TEXT,
|
|
119
|
+
to_version TEXT NOT NULL,
|
|
120
|
+
backup_file TEXT, -- pre-update cold backup in the rev4a-backups volume
|
|
121
|
+
baseline_json TEXT, -- transcript events per session and cron job names before
|
|
122
|
+
verify_json TEXT, -- the comparison after the update
|
|
123
|
+
error TEXT,
|
|
124
|
+
started_at INTEGER NOT NULL,
|
|
125
|
+
updated_at INTEGER NOT NULL,
|
|
126
|
+
finished_at INTEGER
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE INDEX idx_agent_upgrades_agent ON agent_upgrades(agent_id, started_at);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
One row per update attempt (`lib/agent-update.ts`); a rollback moves the same row on to
|
|
133
|
+
`rolling_back`. Created by `lib/db-bootstrap.mjs`. At startup, rows still in an active
|
|
134
|
+
status are marked `interrupted`. Rows are never pruned.
|
|
135
|
+
|
|
136
|
+
### `agent_recreates` — agent recreates
|
|
137
|
+
|
|
138
|
+
```sql
|
|
139
|
+
CREATE TABLE agent_recreates (
|
|
140
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
141
|
+
agent_id TEXT NOT NULL,
|
|
142
|
+
status TEXT NOT NULL, -- backing_up, recreating, done, failed, interrupted
|
|
143
|
+
image TEXT, -- the image the container is rebuilt on
|
|
144
|
+
backup_file TEXT, -- pre-recreate cold backup in the rev4a-backups volume
|
|
145
|
+
error TEXT,
|
|
146
|
+
started_at INTEGER NOT NULL,
|
|
147
|
+
updated_at INTEGER NOT NULL,
|
|
148
|
+
finished_at INTEGER
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
CREATE INDEX idx_agent_recreates_agent ON agent_recreates(agent_id, started_at);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
One row per recreate attempt (`lib/agent-recreate.ts`), the same-version counterpart of
|
|
155
|
+
an update: a cold backup, the container rebuilt, the gateway waited for. It is written
|
|
156
|
+
so a page reload or a Rev4a restart mid-way is visible instead of lost
|
|
157
|
+
(`GET /api/agents/[id]/recreate`). At startup, rows still in an active status become
|
|
158
|
+
`interrupted` and the agent is started again once its backup helper has finished. Rows
|
|
159
|
+
are never pruned.
|
|
160
|
+
|
|
161
|
+
### `agent_restores` — agent restores
|
|
162
|
+
|
|
163
|
+
```sql
|
|
164
|
+
CREATE TABLE agent_restores (
|
|
165
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
166
|
+
agent_id TEXT NOT NULL,
|
|
167
|
+
status TEXT NOT NULL, -- restoring, done, failed, interrupted
|
|
168
|
+
file TEXT, -- the archive being applied, in the rev4a-backups volume
|
|
169
|
+
error TEXT,
|
|
170
|
+
started_at INTEGER NOT NULL,
|
|
171
|
+
updated_at INTEGER NOT NULL,
|
|
172
|
+
finished_at INTEGER
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
CREATE INDEX idx_agent_restores_agent ON agent_restores(agent_id, started_at);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
One row per restore attempt (`lib/agent-restore.ts`): the volume is replaced from the
|
|
179
|
+
archive. Persisted for the same reason as a recreate (`GET /api/agents/[id]/restore`),
|
|
180
|
+
and at startup an active row becomes `interrupted` with the container started again.
|
|
181
|
+
Rows are never pruned.
|
|
182
|
+
|
|
183
|
+
### `agent_edits` — agent renames and port changes
|
|
184
|
+
|
|
185
|
+
```sql
|
|
186
|
+
CREATE TABLE agent_edits (
|
|
187
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
188
|
+
agent_id TEXT NOT NULL,
|
|
189
|
+
status TEXT NOT NULL, -- rebuilding, done, failed, interrupted
|
|
190
|
+
display_name TEXT, -- the name being applied, when it was a rename
|
|
191
|
+
port_range TEXT, -- the range being applied, when it was a port change
|
|
192
|
+
error TEXT,
|
|
193
|
+
started_at INTEGER NOT NULL,
|
|
194
|
+
updated_at INTEGER NOT NULL,
|
|
195
|
+
finished_at INTEGER
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
CREATE INDEX idx_agent_edits_agent ON agent_edits(agent_id, started_at);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
One row per edit attempt (`lib/agent-edit.ts`): the container is rebuilt with the new
|
|
202
|
+
name/ports, no backup involved. Persisted for the same reason as the others
|
|
203
|
+
(`GET /api/agents/[id]`); at startup an active row becomes `interrupted` with the
|
|
204
|
+
container started again. Rows are never pruned.
|
|
205
|
+
|
|
110
206
|
## Useful Queries
|
|
111
207
|
|
|
112
208
|
```sql
|
package/docs/dev/GATEWAY.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Gateway Page
|
|
2
2
|
|
|
3
|
-
> **Last updated:** 2026-
|
|
3
|
+
> **Last updated:** 2026-09-15
|
|
4
4
|
|
|
5
|
-
The Gateway page (`/gateway`) is the
|
|
6
|
-
|
|
5
|
+
The Gateway page (`/gateway`) is the control panel for provider configuration:
|
|
6
|
+
API keys and the model catalogue offered to every agent container. It does not
|
|
7
|
+
assign models to agents.
|
|
7
8
|
|
|
8
9
|
The Gateway aggregates multiple upstream AI providers (DeepSeek, OpenAI, Anthropic,
|
|
9
10
|
Kimi, GLM, Qwen, and others) behind a single Rev4a provider. Agent containers see
|
|
@@ -15,17 +16,21 @@ at `/api/provider/v1/`.
|
|
|
15
16
|
## Overview
|
|
16
17
|
|
|
17
18
|
```
|
|
18
|
-
Gateway Page
|
|
19
|
-
|
|
20
|
-
└── Agents Tab → Change primary model per agent container
|
|
19
|
+
Gateway Page → Manage provider API keys, enable/disable models, push sync
|
|
20
|
+
Agent detail → Change the primary model and fallbacks of one agent
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
The Gateway page is system configuration: which providers are wired up and which
|
|
24
|
+
models the fleet is offered. Assigning a model to one agent is that agent's
|
|
25
|
+
configuration, so it lives in the agent's detail panel (`app/agents/ModelSection.tsx`)
|
|
26
|
+
and not here.
|
|
27
|
+
|
|
23
28
|
The Gateway talks to each agent container directly via `docker exec`, reading and
|
|
24
29
|
writing to `/root/.openclaw/openclaw.json` inside the container.
|
|
25
30
|
|
|
26
31
|
---
|
|
27
32
|
|
|
28
|
-
##
|
|
33
|
+
## Providers
|
|
29
34
|
|
|
30
35
|
Lists all AI providers from `models.config.json` in the project root. Each provider
|
|
31
36
|
row shows:
|
|
@@ -37,13 +42,25 @@ row shows:
|
|
|
37
42
|
|
|
38
43
|
### Provider Key Storage
|
|
39
44
|
|
|
45
|
+
> **Where these files actually live.** Both sit under the Rev4a data directory,
|
|
46
|
+
> `REV4A_DATA`, which defaults to `~/.config/rev4a/` and moves with
|
|
47
|
+
> `REV4A_DATA_DIR`. Neither is in the repository. They are nested differently and
|
|
48
|
+
> this catches people out, including while writing this document:
|
|
49
|
+
>
|
|
50
|
+
> | file | full path |
|
|
51
|
+
> |---|---|
|
|
52
|
+
> | provider keys | `REV4A_DATA/data/provider-keys.json` |
|
|
53
|
+
> | model overrides | `REV4A_DATA/model-overrides.json` |
|
|
54
|
+
>
|
|
55
|
+
> Paths written below as `data/provider-keys.json` mean the first row.
|
|
56
|
+
|
|
40
57
|
Provider API keys are stored in `data/provider-keys.json`:
|
|
41
58
|
|
|
42
59
|
```json
|
|
43
60
|
{
|
|
44
61
|
"deepseek": "sk-...",
|
|
45
62
|
"openrouter": "sk-...",
|
|
46
|
-
"rev4a": "
|
|
63
|
+
"rev4a": "<gateway key>"
|
|
47
64
|
}
|
|
48
65
|
```
|
|
49
66
|
|
|
@@ -52,19 +69,28 @@ against the Rev4a provider proxy using this token. See [`PROVIDERS.md`](PROVIDER
|
|
|
52
69
|
|
|
53
70
|
### Sync Flow
|
|
54
71
|
|
|
55
|
-
When a provider key is saved
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
When a provider key is saved, a model toggle is changed, Sync All is pressed, or
|
|
73
|
+
Rev4a starts, the Gateway:
|
|
74
|
+
|
|
75
|
+
1. **Reads** `models.config.json` and the overrides, and keeps the models that are
|
|
76
|
+
enabled and whose provider has a key. If the file cannot be read and no earlier
|
|
77
|
+
read succeeded, the sync stops here and reports `configError`
|
|
78
|
+
2. **Builds** a `models.providers.rev4a` config block (without `rev4a/` prefix on
|
|
79
|
+
model IDs)
|
|
80
|
+
3. **Lists** every container with the `AGENT_ID` Docker label, **running or
|
|
81
|
+
not**. Stopped agents are included so that starting one later does not bring
|
|
82
|
+
back an old catalogue
|
|
83
|
+
4. **Reads** each container's `/root/.openclaw/openclaw.json` — through
|
|
84
|
+
`docker exec` when running, through `docker cp` otherwise (stopped, paused, created), since `exec`
|
|
85
|
+
cannot reach them. A read that fails, or returns an empty object, stops
|
|
86
|
+
there: **nothing is written** and the container is reported in `sync.failed`
|
|
87
|
+
5. **Cleans up** stale `models.json` and `auth-profiles.json` — running containers
|
|
88
|
+
only, since it needs `exec`
|
|
89
|
+
6. **Writes** the file back with only `models.providers.rev4a` replaced — **does NOT
|
|
90
|
+
touch** `agents.defaults.model` or `agents.list[].model`. Running containers get
|
|
91
|
+
it through `docker exec` and hot-apply it; the others through `docker cp`, with
|
|
92
|
+
the file's original mode, and apply it when started or resumed
|
|
93
|
+
7. **Does NOT restart** the gateway
|
|
68
94
|
|
|
69
95
|
### Model ID Convention
|
|
70
96
|
|
|
@@ -80,7 +106,7 @@ Inside `models.providers.rev4a.models`, model IDs are stored **without** the
|
|
|
80
106
|
"api": "openai-completions",
|
|
81
107
|
"apiKey": "***",
|
|
82
108
|
"models": [
|
|
83
|
-
{ "id": "deepseek/deepseek-
|
|
109
|
+
{ "id": "deepseek/deepseek-flash", "name": "DeepSeek Flash" },
|
|
84
110
|
{ "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro" }
|
|
85
111
|
]
|
|
86
112
|
}
|
|
@@ -90,7 +116,7 @@ Inside `models.providers.rev4a.models`, model IDs are stored **without** the
|
|
|
90
116
|
```
|
|
91
117
|
|
|
92
118
|
OpenClaw automatically prefixes model IDs with the provider name at runtime,
|
|
93
|
-
producing `rev4a/deepseek/deepseek-
|
|
119
|
+
producing `rev4a/deepseek/deepseek-flash`. The `mode` field is **not** set —
|
|
94
120
|
OpenClaw defaults to merging config models with auto-discovered models.
|
|
95
121
|
|
|
96
122
|
### Model Catalogue
|
|
@@ -124,107 +150,119 @@ containers.
|
|
|
124
150
|
|
|
125
151
|
---
|
|
126
152
|
|
|
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
|
-
|
|
153
|
+
## Per-agent model assignment
|
|
154
|
+
|
|
155
|
+
Lives in the agent detail panel, not on this page. `app/agents/ModelSection.tsx`
|
|
156
|
+
reads `GET /api/agents/[id]/model` for the current primary and fallbacks, and the
|
|
157
|
+
enabled catalogue from `GET /api/models`, which returns only models whose provider
|
|
158
|
+
has a configured API key in `<data dir>/provider-keys.json`. Note this page does
|
|
159
|
+
**not** apply that filter — `GET /api/gateway/provider` returns the whole catalogue
|
|
160
|
+
so you can add a key and enable its models in one pass. The filter is applied by
|
|
161
|
+
`/api/models` and by the sync.
|
|
162
|
+
|
|
163
|
+
Saving calls `PUT /api/gateway/agent`, which writes the container's
|
|
164
|
+
`openclaw.json`:
|
|
165
|
+
|
|
166
|
+
- `agents.defaults.model`, and the entry in `agents.list` whose `id` is `main` —
|
|
167
|
+
found by id, not by position, and skipped entirely if no such entry exists. The
|
|
168
|
+
primary is stored as `rev4a/<provider>/<model>`
|
|
169
|
+
- `fallbacks` omitted and `fallbacks: []` are different requests: omitted leaves the
|
|
170
|
+
container's existing list untouched, `[]` clears it. The Model panel is the only
|
|
171
|
+
UI that sets them; the create wizard does not
|
|
172
|
+
- **no restart** — `agents` is in OpenClaw's hot-reload column, so the write
|
|
173
|
+
applies on the agent's next turn. Exception on OpenClaw 2026.7.1-2: a Telegram
|
|
174
|
+
channel keeps the configuration it had when it started, so Telegram replies stay on
|
|
175
|
+
the old model until the container is restarted. The Control UI picks the change up
|
|
176
|
+
at once. OpenClaw 9.x reads the configuration per message
|
|
177
|
+
|
|
178
|
+
The agent list shows each agent's current model as a chip, fed by
|
|
179
|
+
`GET /api/agents/models-summary`. The red states are split by remedy, because they
|
|
180
|
+
look alike and need different actions:
|
|
181
|
+
|
|
182
|
+
- `NOT IN CATALOGUE` — the id is gone from the catalogue: pick another model;
|
|
183
|
+
- `NOT ENABLED` — it exists but is not offered, and the proxy refuses it: pick another model;
|
|
184
|
+
- `OUT OF SYNC` — the gateway offers the agent's primary, but this container's synced
|
|
185
|
+
copy does not have it: run Sync All.
|
|
186
|
+
|
|
187
|
+
Amber `DEPRECATED` marks a working model on a retired name. All of these describe the
|
|
188
|
+
primary model; a neutral `NO FALLBACK` is the one chip about the fallback list, shown
|
|
189
|
+
when it is empty. Every agent gets them, stopped ones included (read from the volume
|
|
190
|
+
with `docker cp`). The agent's Model panel repeats the red and amber distinction in
|
|
191
|
+
its warning.
|
|
157
192
|
|
|
158
193
|
---
|
|
159
194
|
|
|
160
195
|
## API Endpoints
|
|
161
196
|
|
|
162
|
-
### `GET /api/gateway`
|
|
163
|
-
|
|
164
|
-
Returns live Gateway status from all agent containers. Reads agent model configs
|
|
165
|
-
via `openclaw models status --json` inside each container.
|
|
166
|
-
|
|
167
|
-
**Response:**
|
|
168
|
-
```json
|
|
169
|
-
{
|
|
170
|
-
"agents": {
|
|
171
|
-
"total": 1,
|
|
172
|
-
"list": [
|
|
173
|
-
{
|
|
174
|
-
"containerName": "openclaw-atlas",
|
|
175
|
-
"agentId": "atlas",
|
|
176
|
-
"agentName": "Atlas",
|
|
177
|
-
"defaultModel": "rev4a/deepseek/deepseek-v4-flash",
|
|
178
|
-
"configured": true
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
197
|
### `PUT /api/gateway/provider`
|
|
186
198
|
|
|
187
199
|
Enables or disables a single model in the catalogue, then syncs every agent.
|
|
188
200
|
Only writes `models.providers.rev4a` — does NOT touch model references.
|
|
189
201
|
|
|
190
|
-
**Body:** `{ "modelId": "deepseek/deepseek-
|
|
202
|
+
**Body:** `{ "modelId": "deepseek/deepseek-flash", "enabled": true }` — both
|
|
191
203
|
required. Missing either returns `400`; an unknown `modelId` returns `404`.
|
|
204
|
+
If Docker cannot be reached the toggle is refused with `503` and nothing is saved.
|
|
192
205
|
|
|
193
|
-
**Response:**
|
|
206
|
+
**Response:** `status` reports the toggle, saved before the sync runs; `sync`
|
|
207
|
+
reports whether every agent received it.
|
|
194
208
|
```json
|
|
195
209
|
{
|
|
196
210
|
"status": "ok",
|
|
197
|
-
"modelId": "deepseek/deepseek-
|
|
198
|
-
"enabled":
|
|
211
|
+
"modelId": "deepseek/deepseek-flash",
|
|
212
|
+
"enabled": false,
|
|
199
213
|
"provider": "deepseek",
|
|
200
|
-
"sync":
|
|
214
|
+
"sync": { "ok": true, "activeModels": 2, "total": 2,
|
|
215
|
+
"synced": ["agent_2a3c3a07", "agent_9253eee3"], "stopped": [], "failed": [],
|
|
216
|
+
"summary": "Synced 2 of 2 agent(s)." }
|
|
201
217
|
}
|
|
202
218
|
```
|
|
203
219
|
|
|
220
|
+
Two failures, handled differently on purpose:
|
|
221
|
+
|
|
222
|
+
- **Docker unreachable** — refused before anything is written: `503`, nothing saved,
|
|
223
|
+
the checkbox does not move, so the checkboxes never disagree with every agent.
|
|
224
|
+
- **An agent under a cold backup** is skipped: writing its config while tar reads the
|
|
225
|
+
volume would put a half-changed file in the archive. It is listed in `sync.failed`
|
|
226
|
+
with the reason; the next sync catches it up.
|
|
227
|
+
- **Some containers fail once Docker answers** — the toggle stands and `sync.failed`
|
|
228
|
+
lists them; the page shows it in amber, and an agent whose primary is affected shows
|
|
229
|
+
`OUT OF SYNC`. Rolling
|
|
230
|
+
the override back would not restore consistency, because the containers that did
|
|
231
|
+
receive the change would keep it.
|
|
232
|
+
|
|
233
|
+
Full shape in
|
|
234
|
+
[API-REFERENCE.md](API-REFERENCE.md).
|
|
235
|
+
|
|
204
236
|
### `PUT /api/gateway/agent`
|
|
205
237
|
|
|
206
|
-
Updates model
|
|
238
|
+
Updates the primary model and fallbacks of a single agent container.
|
|
207
239
|
|
|
208
240
|
**Body:**
|
|
209
241
|
```json
|
|
210
242
|
{
|
|
211
243
|
"containerName": "openclaw-atlas",
|
|
212
|
-
"model": "deepseek/deepseek-
|
|
244
|
+
"model": "deepseek/deepseek-flash",
|
|
245
|
+
"fallbacks": ["deepseek/deepseek-v4-pro"]
|
|
213
246
|
}
|
|
214
247
|
```
|
|
215
248
|
|
|
216
|
-
|
|
249
|
+
Omit `fallbacks` to keep the container's current list; send `[]` to clear it.
|
|
250
|
+
|
|
251
|
+
**Response:** what was written, prefixed.
|
|
217
252
|
```json
|
|
218
253
|
{
|
|
219
254
|
"status": "ok",
|
|
220
255
|
"agent": "openclaw-atlas",
|
|
221
256
|
"model": {
|
|
222
|
-
"primary": "rev4a/deepseek/deepseek-
|
|
257
|
+
"primary": "rev4a/deepseek/deepseek-flash",
|
|
258
|
+
"fallbacks": ["rev4a/deepseek/deepseek-v4-pro"]
|
|
223
259
|
},
|
|
224
260
|
"verify": { "ok": true, "bytes": 2058 }
|
|
225
261
|
}
|
|
226
262
|
```
|
|
227
263
|
|
|
264
|
+
Full semantics in [API-REFERENCE.md](API-REFERENCE.md).
|
|
265
|
+
|
|
228
266
|
### `GET /api/gateway/provider`
|
|
229
267
|
|
|
230
268
|
Returns current provider configuration state.
|
|
@@ -245,8 +283,8 @@ OpenRouter to find out.
|
|
|
245
283
|
"baseUrl": "https://api.deepseek.com",
|
|
246
284
|
"models": [
|
|
247
285
|
{
|
|
248
|
-
"id": "deepseek/deepseek-
|
|
249
|
-
"name": "DeepSeek
|
|
286
|
+
"id": "deepseek/deepseek-flash",
|
|
287
|
+
"name": "DeepSeek Flash",
|
|
250
288
|
"enabled": true,
|
|
251
289
|
"pricing": { "input": 0.05, "output": 0.10 },
|
|
252
290
|
"pricingLive": true,
|
|
@@ -327,17 +365,136 @@ Restart the Rev4a server (via systemd).
|
|
|
327
365
|
4. **No gateway restart after model sync** — The sync module writes `models.providers`
|
|
328
366
|
to the file directly without restarting the gateway.
|
|
329
367
|
|
|
368
|
+
The model `input` list follows each agent's OpenClaw version
|
|
369
|
+
(`lib/agent-versions.json`, resolved by `containerVersionSync()`): the newest release
|
|
370
|
+
accepts `text | image | audio | video`, while `2026.7.1-2` accepts only `text | image`,
|
|
371
|
+
and one unknown value makes it discard the whole generated catalogue
|
|
372
|
+
(`model catalog load issue`). That is why the previous version stays in the table
|
|
373
|
+
while agents still run it.
|
|
374
|
+
|
|
330
375
|
5. **Entrypoint does not generate openclaw.json** — The entrypoint resolves the
|
|
331
376
|
auth token and starts the gateway with `--allow-unconfigured`; OpenClaw
|
|
332
377
|
generates its own default config on first boot. All post-creation config
|
|
333
378
|
(controlUi, model refs, providers) is written by the create route
|
|
334
379
|
(`POST /api/agents/create`) after the gateway is fully ready.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
380
|
+
Before the gateway starts, the entrypoint runs `openclaw doctor --fix --non-interactive`
|
|
381
|
+
when the OpenClaw version changed since the last successful run (tracked in
|
|
382
|
+
`/root/.openclaw/.last-version`), and fills in the image defaults that are missing:
|
|
383
|
+
plugin load path, web search provider, heartbeat off. See
|
|
384
|
+
[REV4A.md](../REV4A.md#agent-templates).
|
|
338
385
|
|
|
339
386
|
6. **Model ref written after gateway readiness** — When the wizard creates an agent,
|
|
340
|
-
the create route waits for gateway
|
|
341
|
-
`agents.defaults.model.primary` + fallbacks and `gateway.controlUi
|
|
342
|
-
then
|
|
387
|
+
the create route waits for the gateway to finish starting (`waitForGatewayReady`), then writes
|
|
388
|
+
`agents.defaults.model.primary` + fallbacks and the Control UI origin policy (`gateway.controlUi`),
|
|
389
|
+
then writes the provider block from `buildRev4aProviderConfig()` with `openclaw config patch --stdin`. The Gateway sync (`PUT /api/gateway/provider`)
|
|
343
390
|
never touches model references — only `models.providers` is synced.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Maintaining the model catalogue
|
|
395
|
+
|
|
396
|
+
Two files, both tracked in the repo, both maintained by hand:
|
|
397
|
+
|
|
398
|
+
| File | Holds |
|
|
399
|
+
|---|---|
|
|
400
|
+
| `models.config.json` | the catalogue: id, name, provider, `enabled`, `modality`, deprecation flags |
|
|
401
|
+
| `model-pricing.json` | price per 1M tokens, keyed by the same ids |
|
|
402
|
+
|
|
403
|
+
They drift, because upstream catalogues change without telling anyone. Refresh
|
|
404
|
+
them on a schedule that suits you, and always before shipping a release.
|
|
405
|
+
|
|
406
|
+
### Where the truth lives
|
|
407
|
+
|
|
408
|
+
Do not copy figures from blog posts or search results — they go stale and
|
|
409
|
+
contradict each other. Two sources answer authoritatively:
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# OpenRouter: ids, prices and input modalities for every model, no auth
|
|
413
|
+
curl -s https://openrouter.ai/api/v1/models
|
|
414
|
+
|
|
415
|
+
# DeepSeek: the definitive list of callable model names
|
|
416
|
+
curl -s https://api.deepseek.com/models -H "Authorization: Bearer $DEEPSEEK_KEY"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The OpenRouter payload carries `pricing.prompt`, `pricing.completion`,
|
|
420
|
+
`pricing.input_cache_read` (multiply by 1e6 for per-million figures) and
|
|
421
|
+
`architecture.input_modalities`, so prices and modalities can be checked in the
|
|
422
|
+
same pass.
|
|
423
|
+
|
|
424
|
+
For direct-provider prices, use the provider's own pricing page. DeepSeek's is
|
|
425
|
+
at `api-docs.deepseek.com/quick_start/pricing`, and note it charges **peak and
|
|
426
|
+
off-peak rates** (peak: 01:00–04:00 and 06:00–10:00 UTC, Mon–Fri; off-peak is
|
|
427
|
+
half). `model-pricing.json` holds one figure per model and cannot express that,
|
|
428
|
+
nor cache-hit rates — **record the peak rate**, so the dashboard overstates
|
|
429
|
+
spend rather than understating it.
|
|
430
|
+
|
|
431
|
+
### What to check, in order
|
|
432
|
+
|
|
433
|
+
1. **Models that no longer exist upstream.** Set `enabled: false`; do not delete
|
|
434
|
+
an id an agent might still be configured with — see below.
|
|
435
|
+
2. **Prices that drifted.** Compare every `openrouter/*` entry against the live
|
|
436
|
+
API; direct providers against their pricing page.
|
|
437
|
+
3. **Modalities.** `architecture.input_modalities` is authoritative. Compare the
|
|
438
|
+
*set*, not the string: `text+image` and `image+text` are the same thing, and
|
|
439
|
+
rewriting one into the other produces a diff full of noise.
|
|
440
|
+
4. **New models worth carrying.** Add with `enabled` reflecting policy, not
|
|
441
|
+
availability — see the DeepSeek rule below.
|
|
442
|
+
5. **Retired names.** Mark them, do not remove them yet.
|
|
443
|
+
|
|
444
|
+
### The deprecation lifecycle
|
|
445
|
+
|
|
446
|
+
Upstream sometimes retires a model *name* while keeping it working, redirected to
|
|
447
|
+
a successor and billed at the successor's rates.
|
|
448
|
+
|
|
449
|
+
Handle it in two releases:
|
|
450
|
+
|
|
451
|
+
```json5
|
|
452
|
+
// release N — flag it. It still works, and it stays selectable.
|
|
453
|
+
{ "id": "provider/retired-model", "deprecated": true }
|
|
454
|
+
|
|
455
|
+
// release N+1 — remove it. Agents still on it show NOT IN CATALOGUE, in red.
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The gap between the two releases is what gives anyone using it a chance to move.
|
|
459
|
+
Removing in one step turns a warning into an outage.
|
|
460
|
+
|
|
461
|
+
`deprecated` surfaces in two places: a `DEPRECATED` chip on the agent card, and a
|
|
462
|
+
`(deprecated)` tag on the option in the primary and fallback pickers of the
|
|
463
|
+
agent's Model panel. The tag rides on the option label, so a native select keeps
|
|
464
|
+
showing it once the model is chosen.
|
|
465
|
+
|
|
466
|
+
There is no field for the successor: the redirect is the provider's, and nothing here
|
|
467
|
+
acts on it. Where the successor matters, say so in prose.
|
|
468
|
+
|
|
469
|
+
Do **not** encode deprecation in `name`. The name is the model's name; the flag
|
|
470
|
+
renders itself, and putting it in both produces `DeepSeek V4 Flash (legacy →
|
|
471
|
+
Flash) (deprecated)`.
|
|
472
|
+
|
|
473
|
+
**Never remove an id in the same release you deprecate it**, and never remove one
|
|
474
|
+
without checking who uses it: an agent whose primary model leaves the catalogue
|
|
475
|
+
fails on its next turn, and with an empty fallback list it fails hard. The agent
|
|
476
|
+
cards surface this — `NOT IN CATALOGUE` in red — but only after the fact.
|
|
477
|
+
|
|
478
|
+
### Policy vs availability
|
|
479
|
+
|
|
480
|
+
`enabled` expresses **what this deployment should offer**, not what exists. Both
|
|
481
|
+
DeepSeek direct and OpenRouter carry the DeepSeek models; this deployment uses
|
|
482
|
+
the direct provider, so the OpenRouter copies ship disabled.
|
|
483
|
+
|
|
484
|
+
User toggles live separately in `<data dir>/model-overrides.json` and win over
|
|
485
|
+
`enabled`. When a default changes to match an existing override,
|
|
486
|
+
`toggleModelOverride` drops the now-redundant key on the next interaction, so the
|
|
487
|
+
overrides file stays limited to genuine divergence.
|
|
488
|
+
|
|
489
|
+
### After editing
|
|
490
|
+
|
|
491
|
+
- both files must stay valid JSON, and every `enabled` model needs a price entry
|
|
492
|
+
- remove pricing rows for ids you removed from the catalogue — orphans accumulate
|
|
493
|
+
- read the catalogue only through `lib/model-catalogue.ts`: `loadModelsConfig()` for
|
|
494
|
+
the catalogue with overrides, `loadOfferedModels()` for what the deployment offers,
|
|
495
|
+
`isModelOffered()` to decide whether to serve a request. Nothing else in the app
|
|
496
|
+
reads `models.config.json`; `scripts/refresh-model-pricing.mjs` rewrites it offline,
|
|
497
|
+
and the backup and restore scripts copy it
|
|
498
|
+
- if the file cannot be read, the last copy this process read successfully is used;
|
|
499
|
+
with no earlier copy the sync refuses to push. Orphan overrides are pruned only
|
|
500
|
+
when a toggle is saved against a freshly read catalogue
|
package/docs/dev/PROVIDERS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Provider Gateway & Key Management
|
|
2
2
|
|
|
3
|
-
> **Last updated:** 2026-
|
|
3
|
+
> **Last updated:** 2026-09-14
|
|
4
4
|
|
|
5
5
|
This document covers the Rev4a Provider Gateway (proxy), the provider key
|
|
6
6
|
endpoints, and how agent containers authenticate against the Rev4a proxy.
|
|
@@ -19,7 +19,7 @@ Agent Container Rev4a Gateway Upstream API
|
|
|
19
19
|
│ │ │
|
|
20
20
|
│ Authorization: Bearer <rev4a-key> │
|
|
21
21
|
│ POST /api/provider/v1/chat/completions │
|
|
22
|
-
│ model: rev4a/deepseek
|
|
22
|
+
│ model: rev4a/deepseek/deepseek-flash │
|
|
23
23
|
│────────────────────────>│ │
|
|
24
24
|
│ │ POST https://api.deepseek.com/v1/chat/completions
|
|
25
25
|
│ │ Authorization: Bearer <deepseek-key>
|
|
@@ -67,7 +67,7 @@ not by auto-discovery.
|
|
|
67
67
|
"total": 9,
|
|
68
68
|
"data": [
|
|
69
69
|
{
|
|
70
|
-
"id": "deepseek/deepseek-
|
|
70
|
+
"id": "deepseek/deepseek-flash",
|
|
71
71
|
"object": "model",
|
|
72
72
|
"created": 1700000000,
|
|
73
73
|
"owned_by": "deepseek"
|
|
@@ -95,11 +95,15 @@ name is parsed to extract the provider and model ID.
|
|
|
95
95
|
|
|
96
96
|
| Model alias | Upstream provider | Upstream model |
|
|
97
97
|
|---|---|---|
|
|
98
|
-
| `rev4a/deepseek-
|
|
98
|
+
| `rev4a/deepseek-flash` | `deepseek` | `deepseek-flash` |
|
|
99
99
|
| `rev4a/deepseek-v4-pro` | `deepseek` | `deepseek-v4-pro` |
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
|
|
101
|
+
This map is a short list of two-segment shortcuts, not the general mechanism.
|
|
102
|
+
Every other model resolves through step 3 below, from its three-segment id:
|
|
103
|
+
`rev4a/kimi/kimi-k3` resolves to provider `kimi`, model `kimi-k3`. A two-segment
|
|
104
|
+
`rev4a/kimi-k3` is **not** an alias and does not resolve — `rev4a` is not a
|
|
105
|
+
provider key, so it falls through to the `deepseek` default with an unusable
|
|
106
|
+
model string.
|
|
103
107
|
|
|
104
108
|
**Model parsing fallback:**
|
|
105
109
|
1. Check `request.provider` field
|
|
@@ -114,7 +118,7 @@ The upstream module (`app/api/provider/upstream.ts`) handles this via a per-prov
|
|
|
114
118
|
**Request body** (OpenAI-compatible):
|
|
115
119
|
```json
|
|
116
120
|
{
|
|
117
|
-
"model": "rev4a/deepseek-
|
|
121
|
+
"model": "rev4a/deepseek-flash",
|
|
118
122
|
"messages": [{"role": "user", "content": "Hello"}]
|
|
119
123
|
}
|
|
120
124
|
```
|
|
@@ -145,8 +149,13 @@ Store a provider API key, or re-run the sync without changing keys.
|
|
|
145
149
|
**Body:** `{ "provider": "deepseek", "apiKey": "sk-..." }`, or `{ "_syncOnly": true }`
|
|
146
150
|
to push the current configuration to every agent container without touching any key.
|
|
147
151
|
|
|
148
|
-
**Response:** `{ "status": "ok", ...
|
|
149
|
-
`
|
|
152
|
+
**Response:** `{ "status": "ok", "provider": "...", "configured": true, "sync": { ... } }`.
|
|
153
|
+
The key is saved before the sync runs, so `status` reports the save and `sync`
|
|
154
|
+
reports whether every agent received it; its shape is documented under
|
|
155
|
+
`PUT /api/gateway/provider` in [API-REFERENCE.md](API-REFERENCE.md). With
|
|
156
|
+
`_syncOnly` the sync *is* the operation: a failed or partial sync returns `502` with
|
|
157
|
+
`status: "error"` and `error` set to the summary. Unknown provider names are
|
|
158
|
+
rejected with `400` and the list of known providers.
|
|
150
159
|
|
|
151
160
|
**Auth:** browser cookie or bearer token
|
|
152
161
|
|
|
@@ -154,9 +163,13 @@ to push the current configuration to every agent container without touching any
|
|
|
154
163
|
|
|
155
164
|
Enable or disable a single model in the catalogue.
|
|
156
165
|
|
|
157
|
-
**Body:** `{ "modelId": "deepseek/deepseek-
|
|
166
|
+
**Body:** `{ "modelId": "deepseek/deepseek-flash", "enabled": true }` — both fields
|
|
158
167
|
required. Unknown `modelId` returns `404`.
|
|
159
168
|
|
|
169
|
+
**Response:** as for the key save — `status` for the toggle, `sync` for whether
|
|
170
|
+
the agents received it. If Docker cannot be reached the toggle is refused with `503` and
|
|
171
|
+
nothing is saved; a key save in the same situation is saved and the failure reported.
|
|
172
|
+
|
|
160
173
|
**Auth:** browser cookie or bearer token
|
|
161
174
|
|
|
162
175
|
There is no `DELETE`: a provider key is cleared by storing an empty one.
|
|
@@ -177,7 +190,7 @@ Inside each agent container, the `openclaw.json` file has this structure for the
|
|
|
177
190
|
"api": "openai-completions",
|
|
178
191
|
"apiKey": "<gateway-token>",
|
|
179
192
|
"models": [
|
|
180
|
-
{ "id": "deepseek/deepseek-
|
|
193
|
+
{ "id": "deepseek/deepseek-flash", "name": "DeepSeek Flash" },
|
|
181
194
|
{ "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro" }
|
|
182
195
|
]
|
|
183
196
|
}
|
|
@@ -186,7 +199,7 @@ Inside each agent container, the `openclaw.json` file has this structure for the
|
|
|
186
199
|
"agents": {
|
|
187
200
|
"defaults": {
|
|
188
201
|
"model": {
|
|
189
|
-
"primary": "rev4a/deepseek/deepseek-
|
|
202
|
+
"primary": "rev4a/deepseek/deepseek-flash"
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
205
|
}
|