@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.
Files changed (49) hide show
  1. package/README.md +2 -1
  2. package/app/agents/ModelSection.tsx +313 -0
  3. package/app/agents/PageClient.tsx +83 -4
  4. package/app/agents/create/page.tsx +8 -21
  5. package/app/api/agents/[id]/model/route.ts +113 -0
  6. package/app/api/agents/[id]/recreate/route.ts +10 -34
  7. package/app/api/agents/[id]/route.ts +10 -29
  8. package/app/api/agents/create/route.ts +59 -57
  9. package/app/api/agents/models-summary/route.ts +163 -0
  10. package/app/api/assistant/route.ts +36 -15
  11. package/app/api/gateway/agent/route.ts +23 -6
  12. package/app/api/gateway/provider/keys.ts +13 -1
  13. package/app/api/gateway/provider/route.ts +43 -12
  14. package/app/api/gateway/sync.ts +248 -72
  15. package/app/api/models/route.ts +28 -34
  16. package/app/api/provider/auth.ts +65 -0
  17. package/app/api/provider/upstream.ts +9 -2
  18. package/app/api/provider/v1/chat/completions/route.ts +22 -16
  19. package/app/api/provider/v1/models/route.ts +26 -133
  20. package/app/components/PulseChat.tsx +25 -39
  21. package/app/components/ui/RemoveButton.tsx +46 -0
  22. package/app/components/ui/Select.tsx +3 -2
  23. package/app/components/ui/index.ts +1 -0
  24. package/app/credentials/PageClient.tsx +2 -2
  25. package/app/gateway/PageClient.tsx +257 -673
  26. package/app/globals.css +8 -0
  27. package/app/lib/models-context.tsx +43 -7
  28. package/app/wizard/useWizard.ts +6 -1
  29. package/bin/rev4a.js +73 -9
  30. package/docs/ARCHITECTURE.md +16 -4
  31. package/docs/FRONTEND-ARCHITECTURE.md +24 -2
  32. package/docs/REV4A.md +40 -17
  33. package/docs/dev/API-REFERENCE.md +170 -79
  34. package/docs/dev/GATEWAY.md +231 -89
  35. package/docs/dev/PROVIDERS.md +26 -13
  36. package/docs/rag/DATA-FRESHNESS.md +57 -28
  37. package/docs/rag/GLOSSARY.md +16 -14
  38. package/docs/rag/REV4A-OVERVIEW.md +23 -24
  39. package/docs/rag/WHAT-I-CAN-ANSWER.md +5 -7
  40. package/instrumentation.ts +9 -1
  41. package/lib/agent-readiness.ts +110 -0
  42. package/lib/channelManager.ts +64 -22
  43. package/lib/container-file.ts +27 -0
  44. package/lib/model-catalogue.ts +140 -27
  45. package/lib/rev4a-paths.ts +0 -21
  46. package/model-pricing.json +118 -110
  47. package/models.config.json +27 -12
  48. package/package.json +1 -1
  49. package/app/api/gateway/route.ts +0 -191
@@ -1,6 +1,6 @@
1
1
  # Rev4a API Reference
2
2
 
3
- > **Last updated:** 2026-09-05
3
+ > **Last updated:** 2026-09-14
4
4
 
5
5
  All routes are under `/api/`. Authentication is required on every endpoint
6
6
  unless otherwise noted.
@@ -146,76 +146,56 @@ The Gateway API reads the model catalogue from [`models.config.json`](../../mode
146
146
  (tracked in the repository). Full documentation at [GATEWAY.md](GATEWAY.md) and
147
147
  [PROVIDERS.md](PROVIDERS.md).
148
148
 
149
- ### `GET /api/gateway`
150
- Returns live gateway status from all agent containers.
151
-
152
- **Auth:** any auth method
153
-
154
- **Response:**
155
- ```json
156
- {
157
- "timestamp": "2026-08-27T22:00:00.000Z",
158
- "gateway": "Rev4a Model Gateway",
159
- "status": "online",
160
- "models": { "total": 0, "available": 0, "byProvider": [], "list": [] },
161
- "agents": {
162
- "total": 1,
163
- "list": [
164
- {
165
- "agentId": "atlas",
166
- "containerName": "openclaw-atlas",
167
- "state": "running",
168
- "defaultModel": "rev4a/deepseek/deepseek-v4-flash",
169
- "fallbacks": [],
170
- "aliases": {},
171
- "providers": []
172
- }
173
- ]
174
- },
175
- "aliases": {},
176
- "coreModel": { "defaultModel": "rev4a/deepseek/deepseek-v4-flash", "fallbacks": [] },
177
- "apiKeys": { "configured": [], "all": [] }
178
- }
179
- ```
180
-
181
- **`models`, the top-level `aliases`, and `apiKeys` are always empty here.** They
182
- exist only to keep the response shape stable for existing consumers. The model
183
- catalogue is served by `GET /api/gateway/provider`, which reads the bundled
184
- config plus user overrides from disk; provider credential state comes from the
185
- same route (`configured` per provider, out of `provider-keys.json`).
186
-
187
- Per-agent values are read from each container's `openclaw.json`, one
188
- `docker exec … cat` per agent, run concurrently. An agent whose config cannot be
189
- read is still listed, with an `error` field and empty values, rather than
190
- failing the whole response.
191
-
192
149
  ### `PUT /api/gateway/provider`
193
150
  Enable or disable a single model in the catalogue, then sync every agent.
194
151
 
195
152
  **Auth:** browser cookie or bearer token
196
153
 
197
- **Body:** `{ "modelId": "deepseek/deepseek-chat", "enabled": true }` — both
154
+ **Body:** `{ "modelId": "deepseek/deepseek-flash", "enabled": true }` — both
198
155
  required. Missing either returns `400`; an unknown `modelId` returns `404`.
156
+ If Docker cannot be reached the toggle is refused with `503` and nothing is saved,
157
+ so a whole-fleet outage never leaves the checkboxes out of step with the agents.
199
158
 
200
- **Response:**
159
+ **Response:** `status` is the toggle, which is saved before the sync runs. `sync`
160
+ reports separately whether the agents received it.
201
161
  ```json
202
162
  {
203
163
  "status": "ok",
204
- "modelId": "deepseek/deepseek-chat",
164
+ "modelId": "deepseek/deepseek-flash",
205
165
  "enabled": true,
206
166
  "provider": "deepseek",
207
- "sync": ["Active models: 2 across 1 agent(s)"]
167
+ "sync": {
168
+ "ok": true,
169
+ "activeModels": 3,
170
+ "total": 2,
171
+ "synced": ["agent_2a3c3a07", "agent_9253eee3"],
172
+ "stopped": [],
173
+ "failed": [],
174
+ "summary": "Synced 2 of 2 agent(s)."
175
+ }
208
176
  }
209
177
  ```
210
178
 
179
+ `sync.ok` is true only when every agent container received the catalogue; zero
180
+ agents counts as ok. "Every" includes agents that are not running (stopped, paused,
181
+ created): they are written through `docker cp`, listed in `stopped` as well as
182
+ `synced`, and apply the change when started or resumed. A container whose config
183
+ cannot be read is listed in `failed` and left untouched — the sync never writes a
184
+ config it did not read. When Docker cannot be reached, `total` is `0`, `dockerError`
185
+ carries the cause and `ok` is false. For this toggle that case is refused up front
186
+ with `503`, so it appears here only if Docker drops between the check and the sync;
187
+ on a provider key save (`POST`) it is reported, not refused. `configError` is set,
188
+ and nothing is attempted, when the gateway key is missing or `models.config.json`
189
+ could not be read.
190
+
211
191
  **Side effects:**
212
192
  - Writes `models.providers.rev4a` to each agent container's `openclaw.json`
213
193
  - Cleans up stale `models.json` and `auth-profiles.json` inside containers
214
- - **Does NOT touch `agents.defaults.model` or `agents.list[].model`** — model references are managed per-container via the Agents Tab (`PUT /api/gateway/agent`)
194
+ - **Does NOT touch `agents.defaults.model` or `agents.list[].model`** — model references are managed per-container from the agent detail panel (`PUT /api/gateway/agent`)
215
195
  - Does NOT restart the gateway (config is written live to the file)
216
196
 
217
197
  ### `PUT /api/gateway/agent`
218
- Update model config for a single agent container.
198
+ Update the primary model and fallbacks of a single agent container.
219
199
 
220
200
  **Auth:** any auth method
221
201
 
@@ -223,28 +203,38 @@ Update model config for a single agent container.
223
203
  ```json
224
204
  {
225
205
  "containerName": "openclaw-atlas",
226
- "model": "deepseek/deepseek-v4-flash",
227
- "fallbacks": []
206
+ "model": "deepseek/deepseek-flash",
207
+ "fallbacks": ["deepseek/deepseek-v4-pro"]
228
208
  }
229
209
  ```
230
- If `fallbacks` is omitted or empty, no fallback models are set.
210
+ `fallbacks` omitted and `fallbacks: []` are different requests. Omitted leaves the
211
+ container's existing fallbacks untouched; `[]` clears them.
231
212
 
232
- **Response:**
213
+ `containerName` is used verbatim as the `docker exec` target, not resolved from the
214
+ `AGENT_ID` label. Every container's name currently equals its label, so an agent id
215
+ works.
216
+
217
+ **Response:** reports what was written, with the `rev4a/` prefix applied.
233
218
  ```json
234
219
  {
235
220
  "status": "ok",
236
221
  "agent": "openclaw-atlas",
237
222
  "model": {
238
- "primary": "rev4a/deepseek/deepseek-v4-flash"
223
+ "primary": "rev4a/deepseek/deepseek-flash",
224
+ "fallbacks": ["rev4a/deepseek/deepseek-v4-pro"]
239
225
  },
240
226
  "verify": { "ok": true, "bytes": 2058 }
241
227
  }
242
228
  ```
243
229
 
244
230
  **Behaviour:**
245
- - Writes `agents.defaults.model.primary` and `agents.list[0].model.primary` with format `rev4a/<provider>/<model>`
246
- - No restart — writes are live via `docker exec node -e`
247
- - Does NOT set fallbacks unless explicitly provided
231
+ - Writes `agents.defaults.model`, and the entry in `agents.list` whose `id` is
232
+ `main` (skipped if there is none), both as `{ primary, fallbacks }`. The primary is
233
+ removed from its own fallback list.
234
+ - Rewrites the whole `openclaw.json` with `docker exec … cat >`. No gateway restart:
235
+ OpenClaw hot-applies the change.
236
+ - Does not check the model against the catalogue. The proxy does that when the model
237
+ is used, so a model saved here while disabled is refused at request time.
248
238
 
249
239
  ### `GET /api/gateway/provider`
250
240
  Returns current provider configuration state.
@@ -267,7 +257,7 @@ Returns current provider configuration state.
267
257
  "baseUrl": "https://api.deepseek.com",
268
258
  "docsUrl": "https://platform.deepseek.com/api_keys",
269
259
  "models": [
270
- { "id": "deepseek/deepseek-v4-flash", "name": "DeepSeek V4 Flash", "enabled": true },
260
+ { "id": "deepseek/deepseek-flash", "name": "DeepSeek Flash", "enabled": true },
271
261
  { "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro", "enabled": true }
272
262
  ]
273
263
  }
@@ -277,7 +267,8 @@ Returns current provider configuration state.
277
267
 
278
268
  ### `GET /api/provider/v1/models`
279
269
 
280
- Rev4a Provider Gateway — returns available models filtered by auth token.
270
+ Rev4a Provider Gateway — returns the models this deployment offers (enabled after
271
+ overrides, provider has a key). A missing or wrong token gets an empty list, not a 401.
281
272
 
282
273
  **Auth:** Bearer token (must match `rev4a` key in `data/provider-keys.json`)
283
274
 
@@ -287,7 +278,7 @@ Rev4a Provider Gateway — returns available models filtered by auth token.
287
278
  "object": "list",
288
279
  "total": 9,
289
280
  "data": [
290
- { "id": "deepseek/deepseek-v4-flash", "object": "model", "created": 1700000000, "owned_by": "deepseek", "permission": [], "root": "deepseek/deepseek-v4-flash" }
281
+ { "id": "deepseek/deepseek-flash", "object": "model", "created": 1700000000, "owned_by": "deepseek", "permission": [], "root": "deepseek/deepseek-flash" }
291
282
  ]
292
283
  }
293
284
  ```
@@ -299,6 +290,10 @@ Rev4a Provider Gateway — returns available models filtered by auth token.
299
290
 
300
291
  ### `POST /api/provider/v1/chat/completions`
301
292
 
293
+ A model the deployment does not offer is refused with `400` before anything reaches
294
+ upstream. Routing fields that would let the upstream choose another model — `models`,
295
+ `route`, `preset` — are removed from the body before forwarding.
296
+
302
297
  Rev4a Provider Gateway — proxy chat completions to the correct upstream.
303
298
 
304
299
  **Auth:** Bearer token (must match `rev4a` key in `data/provider-keys.json`)
@@ -306,7 +301,7 @@ Rev4a Provider Gateway — proxy chat completions to the correct upstream.
306
301
  **Body:**
307
302
  ```json
308
303
  {
309
- "model": "rev4a/deepseek-v4-flash",
304
+ "model": "rev4a/deepseek-flash",
310
305
  "messages": [{"role": "user", "content": "Hello"}]
311
306
  }
312
307
  ```
@@ -319,11 +314,35 @@ Rev4a Provider Gateway — proxy chat completions to the correct upstream.
319
314
  ---
320
315
 
321
316
  ### `GET /api/models`
322
- Models eligible to be used: the catalogue filtered down to those whose provider
323
- has a key configured.
317
+ Models eligible to be used: the catalogue filtered down to models that are enabled
318
+ (after overrides) and whose provider has a key configured, grouped by provider. Feeds the model pickers in the agent's
319
+ Model panel.
324
320
 
325
321
  **Auth:** browser cookie or bearer token
326
322
 
323
+ **Response:**
324
+ ```json
325
+ [
326
+ {
327
+ "provider": "DeepSeek",
328
+ "emoji": "🧠",
329
+ "models": [
330
+ {
331
+ "id": "deepseek/deepseek-flash",
332
+ "label": "DeepSeek Flash (V4.1)",
333
+ "input": 0.3,
334
+ "output": 1.2,
335
+ "deprecated": false
336
+ }
337
+ ]
338
+ }
339
+ ]
340
+ ```
341
+
342
+ `input` and `output` are USD per 1M tokens, `null` when unpriced. `deprecated`
343
+ marks a name upstream has retired; it stays selectable and the pickers tag it.
344
+ See [GATEWAY.md](GATEWAY.md) for the deprecation lifecycle.
345
+
327
346
  ### `GET /api/gateway/provider/balance`
328
347
  Remaining credit for one provider.
329
348
 
@@ -667,6 +686,7 @@ List running agent containers (Docker containers with `AGENT_ID` label) with ful
667
686
  "controlPort": "3033"
668
687
  }
669
688
  ]
689
+ ```
670
690
 
671
691
  **Notes:**
672
692
  - The URL is always `http://<host-ip>:<port>#token=...` (every agent gets a host port mapping)
@@ -676,6 +696,77 @@ List running agent containers (Docker containers with `AGENT_ID` label) with ful
676
696
 
677
697
  ---
678
698
 
699
+ ### `GET /api/agents/models-summary`
700
+ One row per agent container, running or not: the model it runs and whether that
701
+ model is still in good standing. Feeds the model chip on the agent cards. Running
702
+ agents are read with `docker exec`, others with `docker cp`. If Docker cannot be
703
+ reached the route answers `503` with an `error`, not an empty list.
704
+
705
+ **Auth:** any auth method
706
+
707
+ **Response:**
708
+ ```json
709
+ {
710
+ "agents": [
711
+ {
712
+ "agentId": "atlas",
713
+ "containerName": "openclaw-atlas",
714
+ "primary": "rev4a/deepseek/deepseek-flash",
715
+ "fallbackCount": 0,
716
+ "firstFallback": null,
717
+ "health": "ok"
718
+ }
719
+ ]
720
+ }
721
+ ```
722
+
723
+ `health`, ordered by the remedy each needs:
724
+
725
+ | value | meaning | remedy |
726
+ |---|---|---|
727
+ | `missing` | the primary is not in the catalogue at all | pick another model |
728
+ | `disabled` | in the catalogue but not offered — unchecked, or its provider has no key; the proxy refuses it | pick another model |
729
+ | `out-of-sync` | offered by the gateway, absent from this container's synced copy | Sync All Agents |
730
+ | `deprecated` | works, on a name upstream has retired | switch when convenient |
731
+ | `ok` | usable | — |
732
+ | `unset` | no primary configured | — |
733
+ | `unknown` | the container did not answer | — |
734
+
735
+ ### `GET /api/agents/[id]/model`
736
+ The model configuration of one agent, read from that container alone rather than
737
+ by walking the fleet. Writes still go to `PUT /api/gateway/agent`.
738
+
739
+ **Auth:** any auth method
740
+
741
+ **Response:**
742
+ ```json
743
+ {
744
+ "agentId": "atlas",
745
+ "reachable": true,
746
+ "primary": "rev4a/deepseek/deepseek-flash",
747
+ "fallbacks": [],
748
+ "resolvable": true,
749
+ "catalogueSize": 41,
750
+ "primaryStatus": "offered"
751
+ }
752
+ ```
753
+
754
+ `resolvable` is tri-state. `false` means the primary is absent from the catalogue
755
+ synced into that container, usually because the model was disabled on the Gateway
756
+ page while this agent still pointed at it. `true` means it is present. `null` means
757
+ the question does not apply: either the container did not answer, or no primary is
758
+ set.
759
+
760
+ When `reachable` is `false` the container did not answer, `primary` and `fallbacks`
761
+ come back empty rather than read, and **`catalogueSize` is omitted entirely** — it
762
+ is present only on the reachable branch.
763
+
764
+ `primaryStatus`, also reachable branch only, says why the primary would fail
765
+ regardless of this container: `offered`, `disabled` (unchecked, or its provider has
766
+ no key) or `missing` (no longer in the catalogue). `null` when no primary is set.
767
+ `resolvable` answers a different question — whether *this container's* synced copy
768
+ has it — so `offered` with `resolvable: false` means the container is out of sync.
769
+
679
770
  ### `POST /api/agents/create`
680
771
  Create a new agent container from a template.
681
772
 
@@ -687,7 +778,7 @@ Create a new agent container from a template.
687
778
  "name": "my-agent",
688
779
  "template": "prometheus",
689
780
  "portRange": "3700-3709",
690
- "model": "deepseek/deepseek-v4-flash",
781
+ "model": "deepseek/deepseek-flash",
691
782
  "fallbacks": []
692
783
  }
693
784
  ```
@@ -697,7 +788,7 @@ Create a new agent container from a template.
697
788
  | `name` | yes | Container name, also becomes `AGENT_ID` and subdomain |
698
789
  | `template` | yes | Template name (directory in `agent-templates/`) |
699
790
  | `portRange` | no | Optional port range (e.g. `3700-3709`) or single port (e.g. `3700`). Default: auto-assigned 10-port block |
700
- | `model` | no | Primary model ID from `models.config.json` |
791
+ | `model` | no | Primary model id. Must be offered by this deployment (in the catalogue, enabled, provider has a key), in any form the proxy accepts; otherwise `400`. Defaults to `rev4a/deepseek/deepseek-flash`, validated the same way |
701
792
  | `fallbacks` | no | Array of fallback model IDs |
702
793
 
703
794
  **Every agent is created with a host port mapping:**
@@ -724,16 +815,16 @@ Create a new agent container from a template.
724
815
 
725
816
  **Post-creation pipeline:**
726
817
  1. OpenClaw gateway starts with `--allow-unconfigured`, generating its own default config
727
- 2. The route waits for the gateway to be fully up (health check poll, up to 60 s)
728
- 3. Once ready, writes `gateway.controlUi.allowedOrigins` + `agents.defaults.model.primary` + fallbacks
729
- 4. Writes `models.providers.rev4a` + extraDirs + update config → single `openclaw.json` write
730
-
731
- No gateway restart is issued. OpenClaw's `gateway.reload` defaults to `hybrid`:
732
- it hot-applies changes under `agents`, `models` and `routing`, and restarts only
733
- for `gateway.*`, `discovery` and `plugins.load`. Restarting here repeated work
734
- the Gateway already does.
818
+ 2. The route waits for the gateway to finish starting (`waitForGatewayReady`, up to 60 s)
819
+ 3. Once ready, rewrites `openclaw.json` in one write — `gateway.controlUi`,
820
+ `agents.defaults.model` (primary and fallbacks, always as `rev4a/<provider>/<model>`)
821
+ and `update` — then runs `openclaw gateway restart`
822
+ 4. Applies the runtime config (hooks, shared skills) and writes `models.providers.rev4a`
823
+ with `openclaw config patch --stdin`. No restart for this step: `gateway.reload`
824
+ defaults to `hybrid`, which hot-applies changes under `agents`, `models` and
825
+ `routing`. If the catalogue cannot be read the provider patch is skipped and logged
735
826
 
736
- > **Note:** The entrypoint no longer generates `openclaw.json`. All post-creation config is written by the create route after gateway readiness, eliminating the race condition where the entrypoint would overwrite synced provider config.
827
+ Failures in steps 3 and 4 do not fail the request.
737
828
 
738
829
  **Side effects:**
739
830
  - Template files (`AGENTS.md`, `SOUL.md`, etc.) are copied into the container workspace via `docker cp`
@@ -1069,7 +1160,7 @@ Useful for inspecting the container config without SSH or terminal.
1069
1160
  "name": "my-agent",
1070
1161
  "config": {
1071
1162
  "gateway": { "controlUi": { "allowedOrigins": ["..."], "dangerouslyDisableDeviceAuth": true } },
1072
- "agents": { "defaults": { "model": { "primary": "rev4a/deepseek/deepseek-v4-flash", "fallbacks": [] } } },
1163
+ "agents": { "defaults": { "model": { "primary": "rev4a/deepseek/deepseek-flash", "fallbacks": [] } } },
1073
1164
  "models": { "providers": { "rev4a": { "baseUrl": "...", "apiKey": "***", "models": [...] } } }
1074
1165
  }
1075
1166
  }
@@ -1401,7 +1492,7 @@ Run history for a specific cron job. Queries the container's gateway via `opencl
1401
1492
  "error": "Channel is required ...",
1402
1493
  "runAtMs": 1784820025581,
1403
1494
  "durationMs": 11046,
1404
- "model": "deepseek/deepseek-v4-flash",
1495
+ "model": "deepseek/deepseek-flash",
1405
1496
  "usage": { "input_tokens": 15872, "output_tokens": 597 }
1406
1497
  }
1407
1498
  ]
@@ -1739,7 +1830,7 @@ Chat with the built-in AI assistant (PULSE).
1739
1830
  {
1740
1831
  "message": "What can I do on the Agents page?",
1741
1832
  "page": "/agents",
1742
- "model": "deepseek/deepseek-v4-flash",
1833
+ "model": "deepseek/deepseek-flash",
1743
1834
  "history": []
1744
1835
  }
1745
1836
  ```
@@ -1748,7 +1839,7 @@ Chat with the built-in AI assistant (PULSE).
1748
1839
  |-----------|-------------------------|----------|--------------------------------------------------|
1749
1840
  | `message` | string | yes | The user's question |
1750
1841
  | `page` | string | no | Current page path (used for contextual prompts) |
1751
- | `model` | string | no | AI model to use (e.g. `deepseek/deepseek-v4-flash`) |
1842
+ | `model` | string | no | AI model to use (e.g. `deepseek/deepseek-flash`) |
1752
1843
  | `history` | `{role, content}[]` | no | Recent conversation history (up to 10 messages) |
1753
1844
 
1754
1845
  **Response:** `text/event-stream` (Server-Sent Events)