Models, providers & personas¶
The model picker, provider list, reasoning-effort control, and personality selector. Verified against api/routes.py, api/config.py, and api/providers.py.
Models¶
GET /api/models — the picker catalog¶
Query: freshness (optional; only session_visit accepted — a short freshness horizon).
Response
{
"active_provider": "openrouter",
"default_model": "anthropic/claude-opus-4.7",
"groups": [
{ "provider": "OpenAI", "provider_id": "openai",
"models": [ { "id": "gpt-5.5", "label": "GPT-5.5" } ],
"extra_models": [ { "id": "…", "label": "…" } ] }
]
}
models is the picker-visible subset per provider; extra_models is the overflow catalog for search/show-all. Duplicate cross-provider IDs get an @provider: prefix. The build is profile-scoped and TTL-cached (a cold call may live-probe providers, ~8s).
GET /api/models/live — one provider, live-fetched¶
Query: provider (optional; defaults to the active profile's provider). Response { "provider", "models": [ { "id", "label" } ], "count" }. Falls back to the static catalog on error ({ "error", "models": [] }). The live list is authoritative for that provider — a client merges it over the cached group.
POST /api/default-model — set the main model¶
Body { "model": "<id>", "provider": "<slug>|auto"?, "advanced": {}? } → { "ok": true, "model", "provider" }. Writes model.default / model.provider to the active profile's config.yaml, reloads, and invalidates the models cache. 400 if model missing.
POST /api/model/set — main or auxiliary model¶
Body { "scope": "main"|"auxiliary", "task"?, "provider"?, "model", "advanced"? }. For auxiliary, task is one of the aux slots (vision, web_extract, compression, session_search, title_generation, curator, …). main behaves like /api/default-model. 400 on unknown scope.
Reasoning effort¶
GET /api/reasoning¶
Query: model/provider/base_url (optional — resolve supported efforts; default to the active model).
{ "show_reasoning": true, "reasoning_effort": "medium",
"supported_efforts": ["low","medium","high"], "supports_reasoning_effort": true }
POST /api/reasoning¶
Body — one of { "display": "show|hide|on|off|true|false|1|0" } or { "effort": "none|minimal|low|medium|high|xhigh", "model"?, "provider"? }. Returns the same shape as the GET. 400 if neither field is supplied, or on an invalid value.
Providers¶
GET /api/providers¶
Response
{
"active_provider": "openrouter",
"providers": [
{ "id": "openai", "display_name": "OpenAI", "has_key": true,
"configurable": true, "is_self_hosted": false, "base_url": null,
"is_oauth": false, "key_source": "env_var", "auth_error": null,
"models": [ { "id": "…", "label": "…" } ], "models_total": 12 }
]
}
has_key = a credential is present; configurable = the key can be set from the client; key_source ∈ env_file | env_var | config_yaml | oauth | token | none. Sorted active-first, then custom, then keyed.
Setting a provider key (
POST /api/providers,/api/providers/delete,/api/providers/self-hosted) is a settings-management concern beyond core parity, but exists.
Personalities¶
GET /api/personalities¶
Response { "personalities": [ { "name": "mentor", "description": "…" } ] } — read from agent.personalities in config.yaml. A client's slash-autocomplete conventionally prepends "none".
POST /api/personality/set¶
Body { "session_id", "name" } ("" clears) → { "ok": true, "personality": "<name>|null", "prompt": "<resolved system prompt>" }. 404 if the personality isn't in config; 400 on a subagent (view-only) session.