mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-05 13:09:07 +02:00
[proxy,management] Conform the Agent Network endpoint to the LLM gateway protocol (#7154)
[proxy,management] Conform the Agent Network endpoint to the LLM gateway protocol Reviewed the proxy against Claude Code's published gateway contract. The transport layer already held up; fourteen gaps sat one layer up, in the model catalog and in the non-inference endpoints clients call. Two of them cost money. The catalog carried no claude-opus-5 or claude-sonnet-5, so an operator could not authorise the models coding agents default to — those requests denied as not-routable, or priced at zero where a catch-all carried them. And gateway records pin ParserID "openai" while the same record serves /v1/messages, so Anthropic responses were read with the OpenAI parser, which never looks at message_start where input tokens live: input metered as roughly zero on every stream and cost was skipped entirely. The rest fix requests refused for structural rather than policy reasons: model discovery denied for every account with a model allowlist, token counting denied on Bedrock and mis-parsed on Vertex, startup probes refused and written into the access log at every session start, and denials rendered in a shape no LLM client parses. Two changes are additive by design — the deny body keeps every field it had and adds the vendor's error object alongside, and body-level identity injection is now gated on the request's dialect so it stops sending OpenAI-shape fields into Anthropic bodies that reject them. The end-to-end work turned up one more: the discovery filter treated any slash in a model id as a gateway prefix, which would have dropped every self-hosted "Qwen/..." model from the picker.
This commit is contained in:
@@ -22,6 +22,15 @@ const (
|
||||
// body. Empty for clients that don't send one.
|
||||
KeyLLMSessionID = "llm.session_id"
|
||||
|
||||
// Sub-agent attribution (emitted by llm_request_parser from the
|
||||
// client's request headers). A coding agent that spawns helpers
|
||||
// stamps the spawned agent's id, and the spawning agent's id when
|
||||
// the helper is itself nested, so cost within one session can be
|
||||
// split across the agents that ran in parallel. These identify an
|
||||
// agent, not a person or a device: never treat them as a user id.
|
||||
KeyLLMAgentID = "llm.agent_id"
|
||||
KeyLLMParentAgentID = "llm.parent_agent_id"
|
||||
|
||||
// LLM response-side metadata (emitted by llm_response_parser).
|
||||
//nolint:gosec // metadata key name, not a credential
|
||||
KeyLLMInputTokens = "llm.input_tokens"
|
||||
@@ -66,6 +75,14 @@ const (
|
||||
// downstream gateways' spend logs.
|
||||
KeyLLMAuthorisingGroups = "llm.authorising_groups"
|
||||
|
||||
// LLM non-inference marker (emitted by llm_router on the allow path
|
||||
// for endpoints that legitimately carry no model, such as model
|
||||
// listing). The router still authorises these against the caller's
|
||||
// groups; the marker only tells the limits gate that a per-model
|
||||
// allowlist has nothing to evaluate, so an empty model must not be
|
||||
// read as an undetermined one. Never derived from client input.
|
||||
KeyLLMNonInference = "llm.non_inference"
|
||||
|
||||
// LLM policy attribution (emitted by llm_limit_check on the allow
|
||||
// path). Names the policy that paid for this request and the
|
||||
// dimension counters the post-flight llm_limit_record middleware
|
||||
|
||||
Reference in New Issue
Block a user