mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-16 19:49:56 +00:00
* [agent-network] Shared proto, OpenAPI schema, and generated types * [agent-network] Management: store, manager, synthesizer, policy engine, provider catalog, HTTP/gRPC API Adds the account-scoped agent-network module: provider/policy/budget CRUD and store, the reverse-proxy service synthesizer, policy selection + limit enforcement, the provider catalog (incl. Vertex AI and AWS Bedrock entries), and the management HTTP + proxy gRPC surfaces. * [management] Fix agent-network proxy-peer fan-out on affected-peer recompute The affected-peers resolver loaded only persisted reverse-proxy services, but agent-network services are synthesized on demand and never persisted. As a result the embedded proxy peer was never folded into the affected set when a client's group changed, so the proxy received no network-map update for a newly authorised client and rejected its handshake until a full resync (restart). loadProxyServices now merges the synthesized agent-network services (injected via a registration hook to avoid an import cycle), so proxy peers learn newly authorised clients immediately. * [proxy] Reverse-proxy middleware framework, chain, and request plumbing The per-target middleware chain (slots, dispatcher, mutation gate, metadata merger), body capture, access-log terminal sink, and the proxy wiring that builds + runs chains for synthesized agent-network services. * [proxy] LLM parsers, pricing, and builtin middlewares (OpenAI, Anthropic, Vertex AI, AWS Bedrock) Request/response parsers and SSE/event-stream metering, the embedded pricing table, and the builtin middleware set: request parser, router, policy limit-check/record, cost meter, guardrail, identity inject, response parser. Includes the path-routed providers — Google Vertex AI (keyfile:: service-account OAuth minting) and AWS Bedrock (bearer auth, invoke/converse/streaming, optional /bedrock prefix) — plus the Models allowlist and unmeterable-publisher deny. * [proxy] IPv6 in-place apply and TCP accept-loop hardening on netstack listeners * [agent-network] End-to-end test suite, module docs, and deployment preset * [agent-network] Fix codespell typos and exclude false positives - labelgen word pool: vermillion -> vermilion, racoon -> raccoon. - codespell ignore list: add flate (Go compress/flate package), recordin (a test-local identifier), and unparseable (a valid alternative spelling used consistently across identifiers + a metadata-value constant). * [management] Set LastSeen on injected proxy peer in realstack test (MySQL strict-mode) The injected embedded proxy peer had a PeerStatus with a zero LastSeen, which serializes to '0000-00-00' and is rejected by MySQL in strict mode (SQLite tolerates it). Set LastSeen to a valid time so SaveAccount succeeds on both engines. * [agent-network] Remove e2e shell-script suite from this branch The end-to-end shell scripts under scripts/e2e/ are maintained in a separate testing suite and are not part of this change set. * [agent-network] Polish module docs: remove internal review scaffolding, fix links, verify diagrams Strip PR-review framing, commit references, absolute paths, and stale internal references from the agent-network module docs; fix broken relative links; verify all diagrams against the current architecture. Remove the internal AI-reviewer prompt file. * [management] Refine session expiration handling to support 3-state encoding for SSO deadlines * [agent-network] Relocate agentnetwork package to internals/modules Move management/server/agentnetwork (and its catalog/, labelgen/, types/ subpackages) to management/internals/modules/agentnetwork, alongside the reverse-proxy module, and rewrite all importers. Pure relocation: package names, the synthesizer + affectedpeers registration hook, and store access (shared store.Store) are unchanged, so no import cycle is introduced (affectedpeers still depends only on the agentnetwork/types leaf). * [agent-network] Co-locate HTTP handlers in the module (RegisterEndpoints) Move the agent-network HTTP handlers from server/http/handlers/agentnetwork into the module at internals/modules/agentnetwork/handlers (package handlers) and rename the entrypoint AddEndpoints -> RegisterEndpoints, matching the reverse-proxy module convention. Wiring in http/handler.go updated accordingly.
113 lines
4.3 KiB
Go
113 lines
4.3 KiB
Go
// Package llm provides the shared LLM request and response parsing
|
|
// library consumed by proxy middleware. It is runtime agnostic: the same
|
|
// package is used by the native built-in executor now and will be reused
|
|
// by the WASM adapter later.
|
|
package llm
|
|
|
|
// Provider identifies an LLM API provider.
|
|
type Provider int
|
|
|
|
const (
|
|
// ProviderUnknown signals that no parser matched the request.
|
|
ProviderUnknown Provider = 0
|
|
// ProviderOpenAI identifies the OpenAI API surface.
|
|
ProviderOpenAI Provider = 1
|
|
// ProviderAnthropic identifies the Anthropic Messages API surface.
|
|
ProviderAnthropic Provider = 2
|
|
// ProviderBedrock identifies the AWS Bedrock runtime surface.
|
|
ProviderBedrock Provider = 3
|
|
)
|
|
|
|
// RequestFacts captures the subset of the LLM request body that the
|
|
// middleware annotates as metadata (model, streaming flag). Additional
|
|
// fields are added as parsers grow.
|
|
type RequestFacts struct {
|
|
Model string
|
|
Stream bool
|
|
}
|
|
|
|
// Usage is the provider-agnostic token accounting emitted to metrics and
|
|
// access logs. Downstream consumers map InputTokens/OutputTokens to the
|
|
// plg.llm.* metadata allowlist entries.
|
|
//
|
|
// CachedInputTokens carries OpenAI's prompt_tokens_details.cached_tokens
|
|
// (a SUBSET of InputTokens) when the response is from OpenAI, or
|
|
// Anthropic's cache_read_input_tokens (ADDITIVE to InputTokens) when from
|
|
// Anthropic. The cost meter switches formula on KeyLLMProvider so the
|
|
// two shapes are billed correctly without double-counting.
|
|
//
|
|
// CacheCreationTokens carries Anthropic's cache_creation_input_tokens
|
|
// (ADDITIVE; not present in the OpenAI shape).
|
|
type Usage struct {
|
|
InputTokens int64
|
|
OutputTokens int64
|
|
TotalTokens int64
|
|
CachedInputTokens int64
|
|
CacheCreationTokens int64
|
|
}
|
|
|
|
// Parser is the per-provider interface implemented in this package. The
|
|
// dispatcher selects a parser by calling DetectFromURL against the incoming
|
|
// request path; ties break by registration order (see Parsers).
|
|
type Parser interface {
|
|
Provider() Provider
|
|
ProviderName() string
|
|
DetectFromURL(path string) bool
|
|
ParseRequest(body []byte) (RequestFacts, error)
|
|
ParseResponse(status int, contentType string, body []byte) (Usage, error)
|
|
// ExtractPrompt returns the user-facing prompt text from a request body.
|
|
// Different endpoint shapes (chat.completions, responses, messages) are
|
|
// handled by the per-provider implementation. Returns "" when no prompt
|
|
// can be extracted; never returns an error — extraction is best-effort
|
|
// because callers use the result for observability, not authorization.
|
|
ExtractPrompt(body []byte) string
|
|
// ExtractCompletion returns the assistant-facing completion text from a
|
|
// non-streaming response body. status and contentType match the
|
|
// ParseResponse arguments so implementations can fast-fail uniformly.
|
|
ExtractCompletion(status int, contentType string, body []byte) string
|
|
// ExtractSessionID returns a stable identifier that groups requests of
|
|
// the same conversation / coding session, read from the per-provider
|
|
// location clients populate (e.g. OpenAI Codex's client_metadata.session_id,
|
|
// Claude Code's metadata.user_id). Returns "" when the body carries no
|
|
// recognised session marker; extraction is best-effort and never errors.
|
|
ExtractSessionID(body []byte) string
|
|
}
|
|
|
|
// Parsers returns the built-in parser set in a stable order. The order is
|
|
// deterministic so that DetectFromURL ties produce consistent routing.
|
|
func Parsers() []Parser {
|
|
return []Parser{
|
|
OpenAIParser{},
|
|
AnthropicParser{},
|
|
BedrockParser{},
|
|
}
|
|
}
|
|
|
|
// DetectParser returns the first parser whose DetectFromURL matches the given
|
|
// request path. ok=false means no parser claimed the path.
|
|
func DetectParser(path string) (Parser, bool) {
|
|
for _, p := range Parsers() {
|
|
if p.DetectFromURL(path) {
|
|
return p, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// ParserByName returns the parser whose ProviderName matches id. Used by
|
|
// callers that already know which provider surface a request will hit
|
|
// (e.g. the agent-network middleware chain configured per synthesised
|
|
// service) so they can skip URL sniffing. ok=false when no parser is
|
|
// registered under that name.
|
|
func ParserByName(id string) (Parser, bool) {
|
|
if id == "" {
|
|
return nil, false
|
|
}
|
|
for _, p := range Parsers() {
|
|
if p.ProviderName() == id {
|
|
return p, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|