Both are added to the Z.AI static model list with their 1M-token context
and 128K max output. GLM-5.3-Flash is natively multimodal, so it gets the
vision and video capabilities — through an `override` rule, because the
provider's `defaults: { vision: false }` already set the flag and a fill
rule would have skipped it silently.
Neither model can stop thinking (`thinking.type` only accepts "enabled"),
so they get their own reasoning rule with low/high/max and no `disabled`,
placed before the `glm-5*` family rule that would otherwise swallow them.
This commit is contained in:
@@ -10,6 +10,12 @@
|
||||
|
||||
LLM client abstraction (OpenAI-compat, Anthropic, Ollama…). OpenAI-compatible provider *types* are runtime data, not code: `providers/declared.rs` loads `providers.yaml` at boot (see Config in [../CLAUDE.md](../CLAUDE.md)); only non-OpenAI-compatible or bespoke providers (anthropic, ollama, openai, openrouter) stay native. **Retriability** (`Model::is_retriable`, `agent-loop`) keys on the real HTTP status carried by `ModelError { status }`, **not** a substring of the message — a model id/token count containing "404"/"401" cannot mis-classify; 401/403/404/422 don't retry, 400/429/5xx/network do. **Request logging** is the `logging.rs::LoggingModel` decorator, attached by the *caller's* `ModelSelector` (`loop_adapters/selector.rs::SkaldSelector::with_log`) — never by `LlmManager`, which builds one shared client per model and cannot know whose traffic it serves. The decorator's `RequestLogTarget` carries the owner: metadata → `llm_requests` in the registry (`user_id`, the column the UI filters on), payload bodies/headers → `llm_request_payloads` in that user's own encrypted DB, keyed by `request_id`; session + frame come from the request's own `conversation`/`frame`, so kernel rounds, sub-agent frames and compaction summaries are all attributed with no extra plumbing (`ModelRequest::log` is unused here)
|
||||
|
||||
## `providers.yaml` — two traps in the model metadata
|
||||
|
||||
**`enrich` rules stop at the first glob that matches, and the default `mode: fill` skips a field that already has a value.** Both bite when adding a model to an existing family. Ordering: `glm-5.3-flash` matches `glm-5*` too, so a rule for it placed *after* the family rule never runs — the specific glob goes first, always. And `fill` means "the endpoint listing wins", which for a `static:` list is not the same as "nothing is set": `models.defaults` (today only `vision`) stamps every entry before `enrich` sees it, so a provider carrying `defaults: { vision: false }` — Z.AI does — needs **`mode: override`** to turn vision on for one model. A `fill` rule there parses, loads, logs nothing, and leaves the flag off; the only symptom is that images silently keep taking the textual `<system-extra>` path (see [Multimodal attachments](#multimodal-attachments)) on a model that can read them. `vision: true` also pushes the `vision` capability, but only when the rule actually applied.
|
||||
|
||||
**A `reasoning.modes` `values` list is the whole contract with the provider — it is not a superset to trim in the UI.** Whatever it lists is what can be sent, so a model that cannot stop thinking (`thinking.type` accepting only `"enabled"`: GLM-5.3 and GLM-5.3-Flash) simply omits `disabled` from its rule, rather than inheriting the family's `[disabled, enabled]` toggle and sending a value the API rejects. With `request: { kind: thinking }`, any value other than `disabled`/`enabled` is emitted as `{"thinking":{"type":"enabled"},"reasoning_effort":v}`, which is exactly the shape those models want.
|
||||
|
||||
## The provider API surface — the key is a boolean, never a value
|
||||
|
||||
**No provider endpoint ever returns a stored `api_key`, and the trap is that omitting it silently reads as "no key".** `LlmProviderInfo` (list) and `ProviderDetail` (`src/frontend/api/llm.rs`, the detail DTO — deliberately *not* `LlmProviderRecord`, which does carry the secret) both expose **`has_api_key: bool`** instead. That is the whole contract: the UI needs to know *whether* a key is on file, never what it is, and the browser is where a leaked key would end up in a devtools tab or a screenshot.
|
||||
|
||||
Reference in New Issue
Block a user