@flame0510/project-aether 1.2.0 → 1.3.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 +2 -1
- package/app/agents/ModelSection.tsx +313 -0
- package/app/agents/PageClient.tsx +83 -4
- package/app/agents/create/page.tsx +8 -21
- package/app/api/agents/[id]/model/route.ts +113 -0
- package/app/api/agents/[id]/recreate/route.ts +10 -34
- package/app/api/agents/[id]/route.ts +10 -29
- package/app/api/agents/create/route.ts +59 -57
- package/app/api/agents/models-summary/route.ts +163 -0
- package/app/api/assistant/route.ts +36 -15
- package/app/api/gateway/agent/route.ts +23 -6
- package/app/api/gateway/provider/keys.ts +13 -1
- package/app/api/gateway/provider/route.ts +43 -12
- package/app/api/gateway/sync.ts +248 -72
- 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/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 +257 -673
- 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 +73 -9
- package/docs/ARCHITECTURE.md +16 -4
- package/docs/FRONTEND-ARCHITECTURE.md +24 -2
- package/docs/REV4A.md +40 -17
- package/docs/dev/API-REFERENCE.md +170 -79
- package/docs/dev/GATEWAY.md +231 -89
- package/docs/dev/PROVIDERS.md +26 -13
- package/docs/rag/DATA-FRESHNESS.md +57 -28
- package/docs/rag/GLOSSARY.md +16 -14
- package/docs/rag/REV4A-OVERVIEW.md +23 -24
- package/docs/rag/WHAT-I-CAN-ANSWER.md +5 -7
- package/instrumentation.ts +9 -1
- package/lib/agent-readiness.ts +110 -0
- package/lib/channelManager.ts +64 -22
- package/lib/container-file.ts +27 -0
- package/lib/model-catalogue.ts +140 -27
- 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/GATEWAY.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Gateway Page
|
|
2
2
|
|
|
3
|
-
> **Last updated:** 2026-
|
|
3
|
+
> **Last updated:** 2026-09-14
|
|
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,113 @@ 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
|
|
174
|
+
|
|
175
|
+
The agent list shows each agent's current model as a chip, fed by
|
|
176
|
+
`GET /api/agents/models-summary`. The red states are split by remedy, because they
|
|
177
|
+
look alike and need different actions:
|
|
178
|
+
|
|
179
|
+
- `NOT IN CATALOGUE` — the id is gone from the catalogue: pick another model;
|
|
180
|
+
- `NOT ENABLED` — it exists but is not offered, and the proxy refuses it: pick another model;
|
|
181
|
+
- `OUT OF SYNC` — the gateway offers the agent's primary, but this container's synced
|
|
182
|
+
copy does not have it: run Sync All.
|
|
183
|
+
|
|
184
|
+
Amber `DEPRECATED` marks a working model on a retired name. All of these describe the
|
|
185
|
+
primary model; a neutral `NO FALLBACK` is the one chip about the fallback list, shown
|
|
186
|
+
when it is empty. Every agent gets them, stopped ones included (read from the volume
|
|
187
|
+
with `docker cp`). The agent's Model panel repeats the red and amber distinction in
|
|
188
|
+
its warning.
|
|
157
189
|
|
|
158
190
|
---
|
|
159
191
|
|
|
160
192
|
## API Endpoints
|
|
161
193
|
|
|
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
194
|
### `PUT /api/gateway/provider`
|
|
186
195
|
|
|
187
196
|
Enables or disables a single model in the catalogue, then syncs every agent.
|
|
188
197
|
Only writes `models.providers.rev4a` — does NOT touch model references.
|
|
189
198
|
|
|
190
|
-
**Body:** `{ "modelId": "deepseek/deepseek-
|
|
199
|
+
**Body:** `{ "modelId": "deepseek/deepseek-flash", "enabled": true }` — both
|
|
191
200
|
required. Missing either returns `400`; an unknown `modelId` returns `404`.
|
|
201
|
+
If Docker cannot be reached the toggle is refused with `503` and nothing is saved.
|
|
192
202
|
|
|
193
|
-
**Response:**
|
|
203
|
+
**Response:** `status` reports the toggle, saved before the sync runs; `sync`
|
|
204
|
+
reports whether every agent received it.
|
|
194
205
|
```json
|
|
195
206
|
{
|
|
196
207
|
"status": "ok",
|
|
197
|
-
"modelId": "deepseek/deepseek-
|
|
198
|
-
"enabled":
|
|
208
|
+
"modelId": "deepseek/deepseek-flash",
|
|
209
|
+
"enabled": false,
|
|
199
210
|
"provider": "deepseek",
|
|
200
|
-
"sync":
|
|
211
|
+
"sync": { "ok": true, "activeModels": 2, "total": 2,
|
|
212
|
+
"synced": ["agent_2a3c3a07", "agent_9253eee3"], "stopped": [], "failed": [],
|
|
213
|
+
"summary": "Synced 2 of 2 agent(s)." }
|
|
201
214
|
}
|
|
202
215
|
```
|
|
203
216
|
|
|
217
|
+
Two failures, handled differently on purpose:
|
|
218
|
+
|
|
219
|
+
- **Docker unreachable** — refused before anything is written: `503`, nothing saved,
|
|
220
|
+
the checkbox does not move, so the checkboxes never disagree with every agent.
|
|
221
|
+
- **Some containers fail once Docker answers** — the toggle stands and `sync.failed`
|
|
222
|
+
lists them; the page shows it in amber, and an agent whose primary is affected shows
|
|
223
|
+
`OUT OF SYNC`. Rolling
|
|
224
|
+
the override back would not restore consistency, because the containers that did
|
|
225
|
+
receive the change would keep it.
|
|
226
|
+
|
|
227
|
+
Full shape in
|
|
228
|
+
[API-REFERENCE.md](API-REFERENCE.md).
|
|
229
|
+
|
|
204
230
|
### `PUT /api/gateway/agent`
|
|
205
231
|
|
|
206
|
-
Updates model
|
|
232
|
+
Updates the primary model and fallbacks of a single agent container.
|
|
207
233
|
|
|
208
234
|
**Body:**
|
|
209
235
|
```json
|
|
210
236
|
{
|
|
211
237
|
"containerName": "openclaw-atlas",
|
|
212
|
-
"model": "deepseek/deepseek-
|
|
238
|
+
"model": "deepseek/deepseek-flash",
|
|
239
|
+
"fallbacks": ["deepseek/deepseek-v4-pro"]
|
|
213
240
|
}
|
|
214
241
|
```
|
|
215
242
|
|
|
216
|
-
|
|
243
|
+
Omit `fallbacks` to keep the container's current list; send `[]` to clear it.
|
|
244
|
+
|
|
245
|
+
**Response:** what was written, prefixed.
|
|
217
246
|
```json
|
|
218
247
|
{
|
|
219
248
|
"status": "ok",
|
|
220
249
|
"agent": "openclaw-atlas",
|
|
221
250
|
"model": {
|
|
222
|
-
"primary": "rev4a/deepseek/deepseek-
|
|
251
|
+
"primary": "rev4a/deepseek/deepseek-flash",
|
|
252
|
+
"fallbacks": ["rev4a/deepseek/deepseek-v4-pro"]
|
|
223
253
|
},
|
|
224
254
|
"verify": { "ok": true, "bytes": 2058 }
|
|
225
255
|
}
|
|
226
256
|
```
|
|
227
257
|
|
|
258
|
+
Full semantics in [API-REFERENCE.md](API-REFERENCE.md).
|
|
259
|
+
|
|
228
260
|
### `GET /api/gateway/provider`
|
|
229
261
|
|
|
230
262
|
Returns current provider configuration state.
|
|
@@ -245,8 +277,8 @@ OpenRouter to find out.
|
|
|
245
277
|
"baseUrl": "https://api.deepseek.com",
|
|
246
278
|
"models": [
|
|
247
279
|
{
|
|
248
|
-
"id": "deepseek/deepseek-
|
|
249
|
-
"name": "DeepSeek
|
|
280
|
+
"id": "deepseek/deepseek-flash",
|
|
281
|
+
"name": "DeepSeek Flash",
|
|
250
282
|
"enabled": true,
|
|
251
283
|
"pricing": { "input": 0.05, "output": 0.10 },
|
|
252
284
|
"pricingLive": true,
|
|
@@ -337,7 +369,117 @@ Restart the Rev4a server (via systemd).
|
|
|
337
369
|
(tracked in `/root/.openclaw/.last-version`) — safe migrations only, no restart.
|
|
338
370
|
|
|
339
371
|
6. **Model ref written after gateway readiness** — When the wizard creates an agent,
|
|
340
|
-
the create route waits for gateway
|
|
372
|
+
the create route waits for the gateway to finish starting (`waitForGatewayReady`), then writes
|
|
341
373
|
`agents.defaults.model.primary` + fallbacks and `gateway.controlUi.allowedOrigins`,
|
|
342
|
-
then
|
|
374
|
+
then writes the provider block from `buildRev4aProviderConfig()` with `openclaw config patch --stdin`. The Gateway sync (`PUT /api/gateway/provider`)
|
|
343
375
|
never touches model references — only `models.providers` is synced.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Maintaining the model catalogue
|
|
380
|
+
|
|
381
|
+
Two files, both tracked in the repo, both maintained by hand:
|
|
382
|
+
|
|
383
|
+
| File | Holds |
|
|
384
|
+
|---|---|
|
|
385
|
+
| `models.config.json` | the catalogue: id, name, provider, `enabled`, `modality`, deprecation flags |
|
|
386
|
+
| `model-pricing.json` | price per 1M tokens, keyed by the same ids |
|
|
387
|
+
|
|
388
|
+
They drift, because upstream catalogues change without telling anyone. Refresh
|
|
389
|
+
them on a schedule that suits you, and always before shipping a release.
|
|
390
|
+
|
|
391
|
+
### Where the truth lives
|
|
392
|
+
|
|
393
|
+
Do not copy figures from blog posts or search results — they go stale and
|
|
394
|
+
contradict each other. Two sources answer authoritatively:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
# OpenRouter: ids, prices and input modalities for every model, no auth
|
|
398
|
+
curl -s https://openrouter.ai/api/v1/models
|
|
399
|
+
|
|
400
|
+
# DeepSeek: the definitive list of callable model names
|
|
401
|
+
curl -s https://api.deepseek.com/models -H "Authorization: Bearer $DEEPSEEK_KEY"
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The OpenRouter payload carries `pricing.prompt`, `pricing.completion`,
|
|
405
|
+
`pricing.input_cache_read` (multiply by 1e6 for per-million figures) and
|
|
406
|
+
`architecture.input_modalities`, so prices and modalities can be checked in the
|
|
407
|
+
same pass.
|
|
408
|
+
|
|
409
|
+
For direct-provider prices, use the provider's own pricing page. DeepSeek's is
|
|
410
|
+
at `api-docs.deepseek.com/quick_start/pricing`, and note it charges **peak and
|
|
411
|
+
off-peak rates** (peak: 01:00–04:00 and 06:00–10:00 UTC, Mon–Fri; off-peak is
|
|
412
|
+
half). `model-pricing.json` holds one figure per model and cannot express that,
|
|
413
|
+
nor cache-hit rates — **record the peak rate**, so the dashboard overstates
|
|
414
|
+
spend rather than understating it.
|
|
415
|
+
|
|
416
|
+
### What to check, in order
|
|
417
|
+
|
|
418
|
+
1. **Models that no longer exist upstream.** Set `enabled: false`; do not delete
|
|
419
|
+
an id an agent might still be configured with — see below.
|
|
420
|
+
2. **Prices that drifted.** Compare every `openrouter/*` entry against the live
|
|
421
|
+
API; direct providers against their pricing page.
|
|
422
|
+
3. **Modalities.** `architecture.input_modalities` is authoritative. Compare the
|
|
423
|
+
*set*, not the string: `text+image` and `image+text` are the same thing, and
|
|
424
|
+
rewriting one into the other produces a diff full of noise.
|
|
425
|
+
4. **New models worth carrying.** Add with `enabled` reflecting policy, not
|
|
426
|
+
availability — see the DeepSeek rule below.
|
|
427
|
+
5. **Retired names.** Mark them, do not remove them yet.
|
|
428
|
+
|
|
429
|
+
### The deprecation lifecycle
|
|
430
|
+
|
|
431
|
+
Upstream sometimes retires a model *name* while keeping it working, redirected to
|
|
432
|
+
a successor and billed at the successor's rates.
|
|
433
|
+
|
|
434
|
+
Handle it in two releases:
|
|
435
|
+
|
|
436
|
+
```json5
|
|
437
|
+
// release N — flag it. It still works, and it stays selectable.
|
|
438
|
+
{ "id": "provider/retired-model", "deprecated": true }
|
|
439
|
+
|
|
440
|
+
// release N+1 — remove it. Agents still on it show NOT IN CATALOGUE, in red.
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The gap between the two releases is what gives anyone using it a chance to move.
|
|
444
|
+
Removing in one step turns a warning into an outage.
|
|
445
|
+
|
|
446
|
+
`deprecated` surfaces in two places: a `DEPRECATED` chip on the agent card, and a
|
|
447
|
+
`(deprecated)` tag on the option in the primary and fallback pickers of the
|
|
448
|
+
agent's Model panel. The tag rides on the option label, so a native select keeps
|
|
449
|
+
showing it once the model is chosen.
|
|
450
|
+
|
|
451
|
+
There is no field for the successor: the redirect is the provider's, and nothing here
|
|
452
|
+
acts on it. Where the successor matters, say so in prose.
|
|
453
|
+
|
|
454
|
+
Do **not** encode deprecation in `name`. The name is the model's name; the flag
|
|
455
|
+
renders itself, and putting it in both produces `DeepSeek V4 Flash (legacy →
|
|
456
|
+
Flash) (deprecated)`.
|
|
457
|
+
|
|
458
|
+
**Never remove an id in the same release you deprecate it**, and never remove one
|
|
459
|
+
without checking who uses it: an agent whose primary model leaves the catalogue
|
|
460
|
+
fails on its next turn, and with an empty fallback list it fails hard. The agent
|
|
461
|
+
cards surface this — `NOT IN CATALOGUE` in red — but only after the fact.
|
|
462
|
+
|
|
463
|
+
### Policy vs availability
|
|
464
|
+
|
|
465
|
+
`enabled` expresses **what this deployment should offer**, not what exists. Both
|
|
466
|
+
DeepSeek direct and OpenRouter carry the DeepSeek models; this deployment uses
|
|
467
|
+
the direct provider, so the OpenRouter copies ship disabled.
|
|
468
|
+
|
|
469
|
+
User toggles live separately in `<data dir>/model-overrides.json` and win over
|
|
470
|
+
`enabled`. When a default changes to match an existing override,
|
|
471
|
+
`toggleModelOverride` drops the now-redundant key on the next interaction, so the
|
|
472
|
+
overrides file stays limited to genuine divergence.
|
|
473
|
+
|
|
474
|
+
### After editing
|
|
475
|
+
|
|
476
|
+
- both files must stay valid JSON, and every `enabled` model needs a price entry
|
|
477
|
+
- remove pricing rows for ids you removed from the catalogue — orphans accumulate
|
|
478
|
+
- read the catalogue only through `lib/model-catalogue.ts`: `loadModelsConfig()` for
|
|
479
|
+
the catalogue with overrides, `loadOfferedModels()` for what the deployment offers,
|
|
480
|
+
`isModelOffered()` to decide whether to serve a request. Nothing else in the app
|
|
481
|
+
reads `models.config.json`; `scripts/refresh-model-pricing.mjs` rewrites it offline,
|
|
482
|
+
and the backup and restore scripts copy it
|
|
483
|
+
- if the file cannot be read, the last copy this process read successfully is used;
|
|
484
|
+
with no earlier copy the sync refuses to push. Orphan overrides are pruned only
|
|
485
|
+
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
|
}
|