Skip to main content

erase_unadopted_client_keys

Function erase_unadopted_client_keys 

Source
fn erase_unadopted_client_keys(
    body: &mut Value,
    discarded: &[String],
    issues: &[FieldIssue],
)
Expand description

Remove from the body the client keys gglib must not forward: what the trust gate binned, the budget alias gglib never emits, and one refused field.

The single place body keys leave this stage, so “what does gglib delete from a request” has one answer in one function.

§discarded — what the trust gate binned

The resolved patch is only ever inserted, and since ADR 0003 six modelled fields resolve to nothing by design, so a gated key the ladder then stays silent on rides the body to llama-server exactly like an unmodelled one. Found live, not by review: an untrusted client’s frequency_penalty: 0.9 reached /slots intact, because no layer names that field and nothing overwrote it. Before the deferral this could not happen — the floor emitted every modelled key — which is why the gate never needed this until then.

Empty when the client is trusted.

§The budget alias — always, whatever the trust setting

llama-server reads THINKING_BUDGET_TOKENS_KEY as a second spelling of reasoning_budget_tokens (ADR 0007 finding 7c). gglib reads it too, so its value is already in the resolved ladder — but gglib emits the canonical key only, and a surviving alias is a second answer to the same question sitting next to the force-inserted first. Which one wins would then be llama-server’s parse order rather than gglib’s ladder. Removing it is not a trust decision, it is a consequence of gglib having one canonical spelling.

§issues — one field, and the asymmetry is upstream’s

A refused value never becomes Some, so it never enters the resolved patch and never entered discarded either: the rejection stops at the layer, and the client’s own text rides on. For nearly every field that is exactly right — these readers reject what llama-server rejects, so the forwarded value earns a clean HTTP 400 from the system that owns the field, which tells the client more than a silent substitution would and keeps gglib no stricter than upstream (the doctrine on InferenceConfig::extract_client_sampling).

ADR 0007 finding 7c measured where that stops holding. Upstream governs the budgetreasoning_budget_tokens: -2 comes back a 400 naming the range — and does not govern effort at all. So the two reasoning controls split:

  • REASONING_EFFORT_KEY, refused → deleted. There is no downstream 400 to inherit: "banana" is accepted upstream and rendered into the user’s prompt verbatim. Left in the body, gglib’s refusal would be a record in client_fields_rejected of a value the model then read. This is the one field where gglib’s “no” has to be the only “no” there is.
  • REASONING_BUDGET_TOKENS_KEY, refused → left in place, like every other field. The client gets upstream’s honest 400. And if the ladder resolves a budget of its own, the force-insert overwrites the client’s text before it is ever sent, so the refusal costs nothing.

A FieldIssue::Normalised deletes nothing under either rule: the substitute is either force-inserted over the client’s spelling or is an absence that llama.cpp reads from the client’s own sentinel anyway (max_tokens: -1).

A body that is not a JSON object is left alone, as everywhere else here.