Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions packages/webui/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,93 @@ proxy).
the protocol default). The plaintext key never leaves the
server in any response path.

### `GET /api/providers/presets`

Built-in preset provider gallery (ticket 02). The response lists every
curated template (currently 10 — 智谱 / Kimi / 百炼 / 火山 / mimo /
minimax / opencode go / OpenRouter / Claude Code / Codex) with the
metadata each one would write into the user-level file on enable.
The `enabled` flag and `enabledIds` array mark templates whose id
already appears in the configured catalogue, so the UI can render
"Enabled" / "Enable" buttons without a second round-trip.

Templates never carry key material: `apiKey` / `apiKeyMasked` / `hasKey`
are intentionally absent from the gallery payload. Users supply the
credential after enabling a preset.

A preset's `auth.type` (`byok` or `coding-plan`) is currently
COSMETIC at this layer: no code path branches on it, and an enabled
preset with empty key is consumed identically to a byok record by
the engine. The label is preserved on the persisted record so a
future subscription-auth behaviour (per-provider key flow,
auto-refresh, scoped quotas) has a stable placeholder to attach to;
it does NOT change behaviour today.

**Response 200**
```json
{
"ok": true,
"version": 2,
"presets": [
{
"id": "zhipu",
"label": "智谱 (Zhipu / GLM)",
"protocol": "openai",
"auth": { "type": "byok", "baseURL": "https://open.bigmodel.cn/api/paas/v4/" },
"models": [
{ "id": "glm-4-plus", "label": "GLM-4 Plus", "contextLimit": 128000, "modalities": ["text"] }
],
"enabled": false
}
],
"enabledIds": ["zhipu"]
}
```

### `POST /api/providers/preset/:id/enable`

One-click materialisation of a preset into the user-level catalogue.
The handler resolves the template, merges it into the existing
catalogue, writes the file via the same `writeProvidersConfig`
pipeline that PUT uses (atomic rename, full v2 validation gate), and
broadcasts the standard `providers.updated` SSE event so every
connected client refreshes its catalogue. The next `/api/models`
read picks up the new entries without a restart (the user-level file
is re-read on every call).

Idempotent: a second call for the same id returns `200` with
`alreadyEnabled: true` and the existing masked record rather than
clobbering the user's later edits to `apiKey` / `baseURL`. Custom
providers that share an id with a preset are NOT overwritten — the
handler surfaces the existing record under the same idempotent
contract.

The persisted record starts with an empty `apiKey`; the user fills
it through the same form the custom-providers UI uses.

**Response 200** (newly enabled)
```json
{
"ok": true,
"alreadyEnabled": false,
"provider": { /* masked view, same shape as GET */ },
"path": "/home/you/.mcode-webui/providers.json"
}
```

**Response 200** (idempotent — preset already configured)
```json
{
"ok": true,
"alreadyEnabled": true,
"provider": { /* the existing masked record */ }
}
```

- `400 UNKNOWN_PRESET` — `:id` does not name a known template.
- `500 WRITE_FAILED` — disk I/O failure (the in-memory state did
not change; the operator should retry).

---

## Usage
Expand Down
2 changes: 1 addition & 1 deletion packages/webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
"settings": "GET|POST /api/settings",
"upload": "POST /api/upload",
"model": "GET /api/models, POST /api/set-model|permissions|answer",
"providers": "GET|PUT /api/providers, POST /api/providers/test",
"providers": "GET|PUT /api/providers, POST /api/providers/test, GET /api/providers/presets, POST /api/providers/preset/:id/enable",
"usage": "GET|POST /api/usage[-real|-trigger|/refresh]",
"protocol": "GET|POST /api/protocol/* (acp shim)",
"debug": "GET|POST /api/debug/* (DEBUG_INJECT gated)"
Expand Down
15 changes: 15 additions & 0 deletions packages/webui/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ export const OWNED_ROUTES = new Set([
"GET /api/providers",
"PUT /api/providers",
"POST /api/providers/test",
// Preset providers (ticket 02): gallery + one-click enable.
"GET /api/providers/presets",
"POST /api/providers/preset/:id/enable",
// Debug injection (gated by DEBUG_INJECT=1).
"POST /api/debug/inject",
"GET /api/debug/state",
Expand Down Expand Up @@ -513,6 +516,18 @@ export function createHonoApp() {
app.post("/api/providers/test", (c) =>
invokeHandler(c, c.get(CAPTURE_KEY), providersRoute.handleTestProvider),
);
// ----- Preset providers (ticket 02) -----
app.get("/api/providers/presets", (c) =>
invokeHandler(c, c.get(CAPTURE_KEY), providersRoute.handleGetPresets),
);
app.post("/api/providers/preset/:id/enable", (c) =>
invokeHandler(
c,
c.get(CAPTURE_KEY),
(req, res, ctx) =>
providersRoute.handleEnablePreset(req, res, ctx, { id: c.req.param("id") }),
),
);

// ----- Debug injection (gated by DEBUG_INJECT=1) -----
app.post("/api/debug/inject", (c) =>
Expand Down
Loading
Loading