review: pin off-value wire contract, scope compat inheritance to the entry's api, update the superseded note
Review findings on #1977, each verified before acting: the 2026-08-03 declared-provider-catalog note is updated in place and cross-linked both ways now that reasoningEfforts/compat reopened half of its rejected alternative; resolveModelCompat inherits the catalog entry's compat only while the resolved api still is the entry's own, so a route-level api repoint no longer merges another protocol's shape as a completions base; the off-with-value promise gains a request-boundary test proving pi-ai reads thinkingLevelMap.off when the reasoning option is absent (and the catalog-level test name stops overclaiming); the cannot-stop-thinking wording narrows to what is actually enforced (no Off offered, explicit Off refused — an effortless request goes out bare); the z.const(null) comment attributes null passthrough to schemastery's nullable short-circuit; the baseten drift-gate claim names its verification source; and the layered-merge delete gap for dict keys is documented under Known Limitations with the atomic-leaf follow-up in #2003.
This commit is contained in:
@@ -83,7 +83,7 @@ A profile's `models` list *replaces* the route's installed catalog rather than e
|
||||
|
||||
`reasoningEfforts` declares a model's selectable thinking levels: each key is a level selectors offer, its value the spelling dispatch sends on the wire, so `high: high` passes the canonical name through while `max: ultra` renames it for a gateway with its own vocabulary. Keys come from pi-ai's level set (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`); a level not declared is not offered. Omitting the field keeps the installed catalog entry's capability (a hand-declared model has none and does not reason); `false` declares a non-reasoning model, which is how a profile strips reasoning from a catalog model its gateway cannot serve; an empty declaration is refused rather than guessing between those two meanings.
|
||||
|
||||
The declaration translates to pi-ai's `Model.reasoning` + `thinkingLevelMap` with every level decided explicitly — undeclared levels are pinned unsupported rather than left to pi-ai's own defaulting, which is asymmetric (an absent key means "supported" for the five base levels but "unsupported" for `xhigh`/`max`) and which a profile author should not need to know. `off` is the one three-state key: left out, the model cannot stop thinking and selectors offer no Off; declared with no value (`off:`), Off is offered and selecting it sends nothing — for the `deepseek` dialect an explicit `thinking: {type: "disabled"}` — which also covers a request naming no effort at all; declared with a value (`off: none`), that value goes on the wire as the effort parameter. There is no spelling for restoring a catalog map key to "unset": the declaration is the whole offer, so restate the catalog levels you keep.
|
||||
The declaration translates to pi-ai's `Model.reasoning` + `thinkingLevelMap` with every level decided explicitly — undeclared levels are pinned unsupported rather than left to pi-ai's own defaulting, which is asymmetric (an absent key means "supported" for the five base levels but "unsupported" for `xhigh`/`max`) and which a profile author should not need to know. `off` is the one three-state key: left out, selectors offer no Off and an explicit Off request is refused — a request naming no effort still goes out without the parameter, so what the provider then does is its own default; declared with no value (`off:`), Off is offered and selecting it sends nothing — for the `deepseek` dialect an explicit `thinking: {type: "disabled"}` — which also covers a request naming no effort at all; declared with a value (`off: none`), that value goes on the wire as the effort parameter. There is no spelling for restoring a catalog map key to "unset": the declaration is the whole offer, so restate the catalog levels you keep.
|
||||
|
||||
### Reasoning-dispatch compat switches
|
||||
|
||||
@@ -186,6 +186,7 @@ Recorded response content appends to the next request and does not invalidate it
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Settings can add or override routes, not remove composition routes** — the user layer merges over the composition `base`, so deleting a `cordis.yml`-provided provider is a composition change; `replace` on the namespace only resets the user layer.
|
||||
- **The layered merge has no delete for dict keys** — the settings seam merges the composition `base` and the user layer per key, recursively, so a `reasoningEfforts` level, `modelOverrides` entry, or `compat` field the base declares cannot be removed by the user layer, only overridden — and for `reasoningEfforts` absence *is* the meaning ("not offered"), so a base-declared level stays offered. A `models` list is an array and replaces wholesale, which is the workaround: declare the model there instead. Atomic-leaf merge semantics at the settings seam are tracked in [#2003](https://github.com/deepseek-harness/deepseek-harness/issues/2003).
|
||||
- **`headers` can carry a credential the redactor never sees** — the profile's `headers` dict is plain strings, so `Authorization` or `api-key` set there is returned verbatim by a redacted `describe()` and rendered by any configuration UI. Store credentials as `apiKeyEnv` references; making the dict write-only is deferred with the rest of the [wire-boundary work](../llm/README.md#known-limitations-and-deferred-work).
|
||||
- **A route's catalog never refreshes itself** — the catalog is whatever `settings.yaml` says, so a model list is only as current as its last edit. Nothing here queries a provider for the models it serves; a route gains a model when someone writes one.
|
||||
- **One wire protocol per route** — `api` applies to the whole route, so a mixed-protocol catalog route (an OpenAI-style catalog spanning Responses and Chat Completions) cannot host a model of the other protocol, and adding a model such a route does not describe requires naming `api` and moving every model onto it. Splitting the provider across two route keys is the workaround.
|
||||
|
||||
Reference in New Issue
Block a user