Skip to content

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_sourceenv_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.