[management,proxy] Agent network: per-account LLM gateway (policy, metering, multi-provider) (#6555)

* [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.
This commit is contained in:
Maycon Santos
2026-06-27 13:41:00 +02:00
committed by GitHub
parent 615631567a
commit b416063bcc
187 changed files with 36835 additions and 660 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,45 @@ func (e AccessRestrictionsCrowdsecMode) Valid() bool {
}
}
// Defines values for AgentNetworkCatalogProviderKind.
const (
AgentNetworkCatalogProviderKindCustom AgentNetworkCatalogProviderKind = "custom"
AgentNetworkCatalogProviderKindGateway AgentNetworkCatalogProviderKind = "gateway"
AgentNetworkCatalogProviderKindProvider AgentNetworkCatalogProviderKind = "provider"
)
// Valid indicates whether the value is a known member of the AgentNetworkCatalogProviderKind enum.
func (e AgentNetworkCatalogProviderKind) Valid() bool {
switch e {
case AgentNetworkCatalogProviderKindCustom:
return true
case AgentNetworkCatalogProviderKindGateway:
return true
case AgentNetworkCatalogProviderKindProvider:
return true
default:
return false
}
}
// Defines values for AgentNetworkConsumptionDimensionKind.
const (
AgentNetworkConsumptionDimensionKindGroup AgentNetworkConsumptionDimensionKind = "group"
AgentNetworkConsumptionDimensionKindUser AgentNetworkConsumptionDimensionKind = "user"
)
// Valid indicates whether the value is a known member of the AgentNetworkConsumptionDimensionKind enum.
func (e AgentNetworkConsumptionDimensionKind) Valid() bool {
switch e {
case AgentNetworkConsumptionDimensionKindGroup:
return true
case AgentNetworkConsumptionDimensionKindUser:
return true
default:
return false
}
}
// Defines values for CreateAzureIntegrationRequestHost.
const (
CreateAzureIntegrationRequestHostMicrosoftCom CreateAzureIntegrationRequestHost = "microsoft.com"
@@ -1163,6 +1202,84 @@ func (e WorkloadType) Valid() bool {
}
}
// Defines values for GetApiAgentNetworkAccessLogsParamsSortBy.
const (
GetApiAgentNetworkAccessLogsParamsSortByCostUsd GetApiAgentNetworkAccessLogsParamsSortBy = "cost_usd"
GetApiAgentNetworkAccessLogsParamsSortByDecision GetApiAgentNetworkAccessLogsParamsSortBy = "decision"
GetApiAgentNetworkAccessLogsParamsSortByDuration GetApiAgentNetworkAccessLogsParamsSortBy = "duration"
GetApiAgentNetworkAccessLogsParamsSortByModel GetApiAgentNetworkAccessLogsParamsSortBy = "model"
GetApiAgentNetworkAccessLogsParamsSortByProvider GetApiAgentNetworkAccessLogsParamsSortBy = "provider"
GetApiAgentNetworkAccessLogsParamsSortByStatusCode GetApiAgentNetworkAccessLogsParamsSortBy = "status_code"
GetApiAgentNetworkAccessLogsParamsSortByTimestamp GetApiAgentNetworkAccessLogsParamsSortBy = "timestamp"
GetApiAgentNetworkAccessLogsParamsSortByTotalTokens GetApiAgentNetworkAccessLogsParamsSortBy = "total_tokens"
GetApiAgentNetworkAccessLogsParamsSortByUserId GetApiAgentNetworkAccessLogsParamsSortBy = "user_id"
)
// Valid indicates whether the value is a known member of the GetApiAgentNetworkAccessLogsParamsSortBy enum.
func (e GetApiAgentNetworkAccessLogsParamsSortBy) Valid() bool {
switch e {
case GetApiAgentNetworkAccessLogsParamsSortByCostUsd:
return true
case GetApiAgentNetworkAccessLogsParamsSortByDecision:
return true
case GetApiAgentNetworkAccessLogsParamsSortByDuration:
return true
case GetApiAgentNetworkAccessLogsParamsSortByModel:
return true
case GetApiAgentNetworkAccessLogsParamsSortByProvider:
return true
case GetApiAgentNetworkAccessLogsParamsSortByStatusCode:
return true
case GetApiAgentNetworkAccessLogsParamsSortByTimestamp:
return true
case GetApiAgentNetworkAccessLogsParamsSortByTotalTokens:
return true
case GetApiAgentNetworkAccessLogsParamsSortByUserId:
return true
default:
return false
}
}
// Defines values for GetApiAgentNetworkAccessLogsParamsSortOrder.
const (
GetApiAgentNetworkAccessLogsParamsSortOrderAsc GetApiAgentNetworkAccessLogsParamsSortOrder = "asc"
GetApiAgentNetworkAccessLogsParamsSortOrderDesc GetApiAgentNetworkAccessLogsParamsSortOrder = "desc"
)
// Valid indicates whether the value is a known member of the GetApiAgentNetworkAccessLogsParamsSortOrder enum.
func (e GetApiAgentNetworkAccessLogsParamsSortOrder) Valid() bool {
switch e {
case GetApiAgentNetworkAccessLogsParamsSortOrderAsc:
return true
case GetApiAgentNetworkAccessLogsParamsSortOrderDesc:
return true
default:
return false
}
}
// Defines values for GetApiAgentNetworkUsageOverviewParamsGranularity.
const (
GetApiAgentNetworkUsageOverviewParamsGranularityDay GetApiAgentNetworkUsageOverviewParamsGranularity = "day"
GetApiAgentNetworkUsageOverviewParamsGranularityMonth GetApiAgentNetworkUsageOverviewParamsGranularity = "month"
GetApiAgentNetworkUsageOverviewParamsGranularityWeek GetApiAgentNetworkUsageOverviewParamsGranularity = "week"
)
// Valid indicates whether the value is a known member of the GetApiAgentNetworkUsageOverviewParamsGranularity enum.
func (e GetApiAgentNetworkUsageOverviewParamsGranularity) Valid() bool {
switch e {
case GetApiAgentNetworkUsageOverviewParamsGranularityDay:
return true
case GetApiAgentNetworkUsageOverviewParamsGranularityMonth:
return true
case GetApiAgentNetworkUsageOverviewParamsGranularityWeek:
return true
default:
return false
}
}
// Defines values for GetApiEventsNetworkTrafficParamsType.
const (
GetApiEventsNetworkTrafficParamsTypeTYPEDROP GetApiEventsNetworkTrafficParamsType = "TYPE_DROP"
@@ -1541,6 +1658,564 @@ type AccountSettings struct {
RoutingPeerDnsResolutionEnabled *bool `json:"routing_peer_dns_resolution_enabled,omitempty"`
}
// AgentNetworkAccessLog One per-request agent-network (LLM) access log entry with flattened, queryable LLM dimensions.
type AgentNetworkAccessLog struct {
// CostUsd Estimated USD cost of the request.
CostUsd float64 `json:"cost_usd"`
// Decision Policy decision for the request (e.g. allow, deny).
Decision *string `json:"decision,omitempty"`
// DenyReason Raw deny reason code when the request was blocked (e.g. llm_policy.token_cap_exceeded).
DenyReason *string `json:"deny_reason,omitempty"`
// DurationMs Duration of the request in milliseconds.
DurationMs int `json:"duration_ms"`
// GroupIds NetBird group ids that authorised the request (the caller's groups intersected with the policy's source groups).
GroupIds *[]string `json:"group_ids,omitempty"`
// Host Upstream host the request was routed to. Empty when log collection is disabled.
Host *string `json:"host,omitempty"`
// Id Unique identifier for the access log entry.
Id string `json:"id"`
// InputTokens Input (prompt) tokens consumed.
InputTokens int64 `json:"input_tokens"`
// Method HTTP method of the request.
Method *string `json:"method,omitempty"`
// Model Requested LLM model.
Model *string `json:"model,omitempty"`
// OutputTokens Output (completion) tokens produced.
OutputTokens int64 `json:"output_tokens"`
// Path Request path. Empty when log collection is disabled.
Path *string `json:"path,omitempty"`
// Provider LLM provider vendor (e.g. openai, anthropic).
Provider *string `json:"provider,omitempty"`
// RequestPrompt Captured request prompt. Present only when prompt collection is enabled.
RequestPrompt *string `json:"request_prompt,omitempty"`
// ResolvedProviderId NetBird agent-network provider id that served the request.
ResolvedProviderId *string `json:"resolved_provider_id,omitempty"`
// ResponseCompletion Captured response completion. Present only when prompt collection is enabled.
ResponseCompletion *string `json:"response_completion,omitempty"`
// SelectedPolicyId Agent-network policy id that authorised (or denied) the request.
SelectedPolicyId *string `json:"selected_policy_id,omitempty"`
// ServiceId ID of the synthesised agent-network service that handled the request.
ServiceId string `json:"service_id"`
// SessionId Conversation / coding-session identifier that groups related requests. Sourced from the client's session marker (e.g. OpenAI Codex client_metadata.session_id, Claude Code metadata.user_id). Empty for clients that send none.
SessionId *string `json:"session_id,omitempty"`
// SourceIp Source IP of the request. Empty when log collection is disabled.
SourceIp *string `json:"source_ip,omitempty"`
// StatusCode HTTP status code returned upstream.
StatusCode int `json:"status_code"`
// Stream Whether the request was a streaming completion.
Stream *bool `json:"stream,omitempty"`
// Timestamp Timestamp when the request was made.
Timestamp time.Time `json:"timestamp"`
// TotalTokens Total tokens consumed.
TotalTokens int64 `json:"total_tokens"`
// UserId NetBird user id of the authenticated caller, if applicable.
UserId *string `json:"user_id,omitempty"`
}
// AgentNetworkAccessLogsResponse defines model for AgentNetworkAccessLogsResponse.
type AgentNetworkAccessLogsResponse struct {
// Data List of agent-network access log entries.
Data []AgentNetworkAccessLog `json:"data"`
// Page Current page number.
Page int `json:"page"`
// PageSize Number of items per page.
PageSize int `json:"page_size"`
// TotalPages Total number of pages available.
TotalPages int `json:"total_pages"`
// TotalRecords Total number of log records matching the filter.
TotalRecords int `json:"total_records"`
}
// AgentNetworkBudgetRule Account-level budget rule. A limit-only rule bound to groups and/or users that applies across all policies as a min-wins ceiling. Empty targets means it applies to every caller.
type AgentNetworkBudgetRule struct {
CreatedAt *time.Time `json:"created_at,omitempty"`
// Enabled Whether the rule is enforced.
Enabled bool `json:"enabled"`
// Id Budget rule ID.
Id string `json:"id"`
// Limits Token and budget caps attached directly to the policy. These compose with any guardrail-level checks.
Limits AgentNetworkPolicyLimits `json:"limits"`
// Name Display name for the budget rule.
Name string `json:"name"`
// TargetGroups NetBird group ids the rule binds. Empty plus empty target_users means account-wide.
TargetGroups []string `json:"target_groups"`
// TargetUsers NetBird user ids the rule binds directly.
TargetUsers []string `json:"target_users"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// AgentNetworkBudgetRuleRequest defines model for AgentNetworkBudgetRuleRequest.
type AgentNetworkBudgetRuleRequest struct {
// Enabled Whether the rule is enforced. Defaults to true on create.
Enabled *bool `json:"enabled,omitempty"`
// Limits Token and budget caps attached directly to the policy. These compose with any guardrail-level checks.
Limits AgentNetworkPolicyLimits `json:"limits"`
// Name Display name for the budget rule.
Name string `json:"name"`
// TargetGroups NetBird group ids the rule binds. Empty plus empty target_users means account-wide.
TargetGroups *[]string `json:"target_groups,omitempty"`
// TargetUsers NetBird user ids the rule binds directly.
TargetUsers *[]string `json:"target_users,omitempty"`
}
// AgentNetworkCatalogExtraHeader One optional per-provider routing/config header surfaced on the dashboard. Operator-typed value lives on the provider record's `extra_values` map keyed by `name`. UI copy (input label, helper line, tooltip) is owned by the dashboard, keyed by `name`.
type AgentNetworkCatalogExtraHeader struct {
// Name Wire header name the proxy stamps with the operator-typed value.
Name string `json:"name"`
}
// AgentNetworkCatalogHeaderPairInjection HeaderPair identity-injection shape — separate per-dimension headers (LiteLLM-style, Bifrost).
type AgentNetworkCatalogHeaderPairInjection struct {
// Customizable When true, the wire header names are operator-overridable per provider record (Bifrost). When false, the catalog values are authoritative (LiteLLM and similar gateways with a fixed wire protocol).
Customizable bool `json:"customizable"`
// EndUserIdHeader Wire header name for the caller's display identity. Default placeholder when `customizable` is true.
EndUserIdHeader string `json:"end_user_id_header"`
// TagsHeader Wire header name for the caller's groups CSV. Default placeholder when `customizable` is true.
TagsHeader string `json:"tags_header"`
}
// AgentNetworkCatalogIdentityInjection Catalog-declared identity-injection shape. Present when this provider supports stamping the caller's NetBird identity onto upstream requests. Exactly one of `header_pair` or `json_metadata` is set per provider entry. The dashboard reads the `customizable` flag on whichever shape is present to decide whether to surface the labels as editable inputs (true → editable with the catalog values shown as placeholders; false → fixed and read-only).
type AgentNetworkCatalogIdentityInjection struct {
// HeaderPair HeaderPair identity-injection shape — separate per-dimension headers (LiteLLM-style, Bifrost).
HeaderPair *AgentNetworkCatalogHeaderPairInjection `json:"header_pair,omitempty"`
// JsonMetadata JSONMetadata identity-injection shape — one wire header carrying a JSON object whose keys label each dimension (Portkey-style, Cloudflare AI Gateway).
JsonMetadata *AgentNetworkCatalogJSONMetadataInjection `json:"json_metadata,omitempty"`
}
// AgentNetworkCatalogJSONMetadataInjection JSONMetadata identity-injection shape — one wire header carrying a JSON object whose keys label each dimension (Portkey-style, Cloudflare AI Gateway).
type AgentNetworkCatalogJSONMetadataInjection struct {
// Customizable When true, the JSON keys are operator-overridable per provider record (Cloudflare). The wire header itself stays catalog-owned. When false, the catalog values are authoritative (Portkey and similar gateways with a fixed JSON schema).
Customizable bool `json:"customizable"`
// GroupsKey JSON key for the caller's groups CSV. Default placeholder when `customizable` is true.
GroupsKey string `json:"groups_key"`
// Header Wire header name carrying the JSON metadata payload. Catalog-owned (not customizable per provider record).
Header string `json:"header"`
// UserKey JSON key for the caller's display identity. Default placeholder when `customizable` is true.
UserKey string `json:"user_key"`
}
// AgentNetworkCatalogModel defines model for AgentNetworkCatalogModel.
type AgentNetworkCatalogModel struct {
// ContextWindow Maximum context window in tokens.
ContextWindow int `json:"context_window"`
// Id Catalog model identifier as exposed by the upstream provider.
Id string `json:"id"`
// InputPer1k Input token price per 1k tokens, in USD.
InputPer1k float64 `json:"input_per_1k"`
// Label Human-friendly model name for the dashboard.
Label string `json:"label"`
// OutputPer1k Output token price per 1k tokens, in USD.
OutputPer1k float64 `json:"output_per_1k"`
}
// AgentNetworkCatalogProvider defines model for AgentNetworkCatalogProvider.
type AgentNetworkCatalogProvider struct {
// AuthHeaderTemplate Template the proxy uses to inject the API key (the literal string ${API_KEY} is replaced at request time).
AuthHeaderTemplate string `json:"auth_header_template"`
// BrandColor Hex brand color used to render the provider badge in the dashboard.
BrandColor string `json:"brand_color"`
// DefaultContentType Default Content-Type for upstream requests.
DefaultContentType string `json:"default_content_type"`
// DefaultHost Default upstream host suggested when adding a provider of this type.
DefaultHost string `json:"default_host"`
// Description Short description shown in the provider picker.
Description string `json:"description"`
// ExtraHeaders Catalog-declared list of optional per-provider routing/config headers the proxy stamps on every upstream request. Each entry surfaces an input on the dashboard's provider modal (one per item, labeled with `label`). Operators fill any subset; values land on the provider record's `extra_values` map keyed by `name`. Used by gateways like Portkey for `x-portkey-config: pc-...` (saved-config id resolving upstream provider + virtual key).
ExtraHeaders *[]AgentNetworkCatalogExtraHeader `json:"extra_headers,omitempty"`
// Id Catalog provider identifier (referenced by AgentNetworkProvider.provider_id).
Id string `json:"id"`
// IdentityInjection Catalog-declared identity-injection shape. Present when this provider supports stamping the caller's NetBird identity onto upstream requests. Exactly one of `header_pair` or `json_metadata` is set per provider entry. The dashboard reads the `customizable` flag on whichever shape is present to decide whether to surface the labels as editable inputs (true → editable with the catalog values shown as placeholders; false → fixed and read-only).
IdentityInjection *AgentNetworkCatalogIdentityInjection `json:"identity_injection,omitempty"`
// Kind Presentation grouping for the provider Select on the dashboard.
// "provider" — first-party vendor API (OpenAI, Anthropic, …); the upstream is the model itself.
// "gateway" — routing/aggregation layer in front of multiple providers (LiteLLM, Portkey, …); typically pairs with NetBird identity stamping.
// "custom" — generic OpenAI-compatible self-hosted endpoint catch-all.
Kind AgentNetworkCatalogProviderKind `json:"kind"`
// Models Catalog models available for this provider.
Models []AgentNetworkCatalogModel `json:"models"`
// Name Display name for the provider.
Name string `json:"name"`
}
// AgentNetworkCatalogProviderKind Presentation grouping for the provider Select on the dashboard.
// "provider" — first-party vendor API (OpenAI, Anthropic, …); the upstream is the model itself.
// "gateway" — routing/aggregation layer in front of multiple providers (LiteLLM, Portkey, …); typically pairs with NetBird identity stamping.
// "custom" — generic OpenAI-compatible self-hosted endpoint catch-all.
type AgentNetworkCatalogProviderKind string
// AgentNetworkConsumption One per-(dimension, window) consumption counter row. The proxy ticks one row per dimension on every served LLM request; the dashboard reads this listing to surface live counter growth.
type AgentNetworkConsumption struct {
// CostUsd Total USD spend booked against this dimension for the window.
CostUsd float64 `json:"cost_usd"`
// DimensionId NetBird user id (when `dimension_kind=user`) or NetBird group id (when `dimension_kind=group`).
DimensionId string `json:"dimension_id"`
// DimensionKind Whether this row counts a single end user or a single source group across every member.
DimensionKind AgentNetworkConsumptionDimensionKind `json:"dimension_kind"`
// TokensInput Total input tokens consumed within the window.
TokensInput int64 `json:"tokens_input"`
// TokensOutput Total output tokens consumed within the window.
TokensOutput int64 `json:"tokens_output"`
// UpdatedAt Timestamp of the last increment recorded for this row.
UpdatedAt *time.Time `json:"updated_at,omitempty"`
// WindowSeconds Length of the aligned window this counter covers, in seconds. Distinct window lengths produce independent counters even on the same dimension.
WindowSeconds int64 `json:"window_seconds"`
// WindowStartUtc UTC start of the aligned window this counter covers. Aligned to the unix epoch so every node computes the same boundary.
WindowStartUtc time.Time `json:"window_start_utc"`
}
// AgentNetworkConsumptionDimensionKind Whether this row counts a single end user or a single source group across every member.
type AgentNetworkConsumptionDimensionKind string
// AgentNetworkGuardrail defines model for AgentNetworkGuardrail.
type AgentNetworkGuardrail struct {
// Checks Guardrail check parameters. Each entry has an `enabled` flag plus per-check configuration; disabled entries are inert.
Checks AgentNetworkGuardrailChecks `json:"checks"`
// CreatedAt Timestamp when the guardrail was created.
CreatedAt *time.Time `json:"created_at,omitempty"`
// Description Optional human-readable description.
Description string `json:"description"`
// Id Guardrail ID
Id string `json:"id"`
// Name Display name for the guardrail.
Name string `json:"name"`
// UpdatedAt Timestamp when the guardrail was last updated.
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// AgentNetworkGuardrailChecks Guardrail check parameters. Each entry has an `enabled` flag plus per-check configuration; disabled entries are inert.
type AgentNetworkGuardrailChecks struct {
ModelAllowlist struct {
Enabled bool `json:"enabled"`
// Models Allowed catalog model ids. Requests for any other model are denied.
Models []string `json:"models"`
} `json:"model_allowlist"`
PromptCapture struct {
Enabled bool `json:"enabled"`
RedactPii bool `json:"redact_pii"`
} `json:"prompt_capture"`
}
// AgentNetworkGuardrailRequest defines model for AgentNetworkGuardrailRequest.
type AgentNetworkGuardrailRequest struct {
// Checks Guardrail check parameters. Each entry has an `enabled` flag plus per-check configuration; disabled entries are inert.
Checks AgentNetworkGuardrailChecks `json:"checks"`
// Description Optional human-readable description.
Description *string `json:"description,omitempty"`
// Name Display name for the guardrail.
Name string `json:"name"`
}
// AgentNetworkPolicy defines model for AgentNetworkPolicy.
type AgentNetworkPolicy struct {
// CreatedAt Timestamp when the policy was created.
CreatedAt *time.Time `json:"created_at,omitempty"`
// Description Optional human-readable description.
Description string `json:"description"`
// DestinationProviderIds Agent Network provider ids (returned by the providers API) the source groups can reach.
DestinationProviderIds []string `json:"destination_provider_ids"`
// Enabled Whether the policy is enabled.
Enabled bool `json:"enabled"`
// GuardrailIds Agent Network guardrail ids attached to this policy.
GuardrailIds []string `json:"guardrail_ids"`
// Id Policy ID
Id string `json:"id"`
// Limits Token and budget caps attached directly to the policy. These compose with any guardrail-level checks.
Limits AgentNetworkPolicyLimits `json:"limits"`
// Name Display name for the policy.
Name string `json:"name"`
// SourceGroups NetBird group ids whose members are allowed to call the destination providers.
SourceGroups []string `json:"source_groups"`
// UpdatedAt Timestamp when the policy was last updated.
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// AgentNetworkPolicyBudgetLimit Per-policy USD spend cap. `group_cap_usd` is applied to each source group independently — every group in the policy's `source_groups` gets its own bucket of this size. `user_cap_usd` is applied independently to each individual user. Caps reset to zero at the start of each window.
type AgentNetworkPolicyBudgetLimit struct {
Enabled bool `json:"enabled"`
// GroupCapUsd USD allowed per source group within the window (each group has its own bucket of this size). 0 means uncapped.
GroupCapUsd float64 `json:"group_cap_usd"`
// UserCapUsd USD allowed per individual user within the window. 0 means uncapped.
UserCapUsd float64 `json:"user_cap_usd"`
// WindowSeconds Reset frequency in seconds. Caps reset at the start of each window. Minimum 60 (one minute) when the limit is enabled.
WindowSeconds int64 `json:"window_seconds"`
}
// AgentNetworkPolicyLimits Token and budget caps attached directly to the policy. These compose with any guardrail-level checks.
type AgentNetworkPolicyLimits struct {
// BudgetLimit Per-policy USD spend cap. `group_cap_usd` is applied to each source group independently — every group in the policy's `source_groups` gets its own bucket of this size. `user_cap_usd` is applied independently to each individual user. Caps reset to zero at the start of each window.
BudgetLimit AgentNetworkPolicyBudgetLimit `json:"budget_limit"`
// TokenLimit Per-policy token cap. `group_cap` is applied to each source group independently — every group in the policy's `source_groups` gets its own bucket of this size. `user_cap` is applied independently to each individual user. Caps reset to zero at the start of each window.
TokenLimit AgentNetworkPolicyTokenLimit `json:"token_limit"`
}
// AgentNetworkPolicyRequest defines model for AgentNetworkPolicyRequest.
type AgentNetworkPolicyRequest struct {
// Description Optional human-readable description.
Description *string `json:"description,omitempty"`
// DestinationProviderIds Agent Network provider ids the source groups can reach.
DestinationProviderIds []string `json:"destination_provider_ids"`
// Enabled Whether the policy is enabled. Defaults to true on create.
Enabled *bool `json:"enabled,omitempty"`
// GuardrailIds Agent Network guardrail ids to attach to this policy.
GuardrailIds *[]string `json:"guardrail_ids,omitempty"`
// Limits Token and budget caps attached directly to the policy. These compose with any guardrail-level checks.
Limits *AgentNetworkPolicyLimits `json:"limits,omitempty"`
// Name Display name for the policy.
Name string `json:"name"`
// SourceGroups NetBird group ids whose members are allowed to call the destination providers.
SourceGroups []string `json:"source_groups"`
}
// AgentNetworkPolicyTokenLimit Per-policy token cap. `group_cap` is applied to each source group independently — every group in the policy's `source_groups` gets its own bucket of this size. `user_cap` is applied independently to each individual user. Caps reset to zero at the start of each window.
type AgentNetworkPolicyTokenLimit struct {
Enabled bool `json:"enabled"`
// GroupCap Tokens allowed per source group within the window (each group has its own bucket of this size). 0 means uncapped.
GroupCap int64 `json:"group_cap"`
// UserCap Tokens allowed per individual user within the window. 0 means uncapped.
UserCap int64 `json:"user_cap"`
// WindowSeconds Reset frequency in seconds. The cap counter resets to zero at the start of each window. Minimum 60 (one minute) when the limit is enabled.
WindowSeconds int64 `json:"window_seconds"`
}
// AgentNetworkProvider defines model for AgentNetworkProvider.
type AgentNetworkProvider struct {
// CreatedAt Timestamp when the provider was created.
CreatedAt *time.Time `json:"created_at,omitempty"`
// Enabled Whether the provider is enabled.
Enabled bool `json:"enabled"`
// ExtraValues Operator-typed values for catalog-declared extra headers. Keys are wire header names (e.g. `x-portkey-config`); values are the strings the proxy stamps on every upstream request to this provider. Catalog (AgentNetworkCatalogProvider.extra_headers) declares which keys are accepted; values not declared by the catalog are ignored at synth time. Empty / missing values mean no header stamped.
ExtraValues *map[string]string `json:"extra_values,omitempty"`
// Id Provider ID
Id string `json:"id"`
// IdentityHeaderGroups Wire header name the proxy stamps with the caller's NetBird groups as a comma-separated list (sorted) when the catalog entry's HeaderPair is `customizable`. Empty disables stamping for this dimension. Same per-catalog semantics as `identity_header_user_id`.
IdentityHeaderGroups *string `json:"identity_header_groups,omitempty"`
// IdentityHeaderUserId Wire header name the proxy stamps with the caller's display identity (user email or peer name) when the catalog entry's HeaderPair is `customizable`. Empty disables stamping for this dimension. Ignored when the catalog entry has a fixed HeaderPair (e.g. LiteLLM, Portkey). Used today by Bifrost: typical values are `x-bf-lh-netbird_user_id` (always-on log metadata) or `x-bf-dim-netbird_user_id` (Prometheus / OTEL — requires the label to be pre-declared in the gateway's `client.prometheus_labels` config).
IdentityHeaderUserId *string `json:"identity_header_user_id,omitempty"`
// Models Models exposed through this endpoint, with the operator's per-1k input/output prices. Empty means all catalog models are allowed at catalog prices.
Models []AgentNetworkProviderModel `json:"models"`
// Name Display name shown in the dashboard.
Name string `json:"name"`
// ProviderId Catalog identifier for the upstream AI provider (e.g. openai_api, anthropic_api, azure_openai_api, bedrock_api, vertex_ai_api, mistral_api, custom).
ProviderId string `json:"provider_id"`
// UpdatedAt Timestamp when the provider was last updated.
UpdatedAt *time.Time `json:"updated_at,omitempty"`
// UpstreamUrl Full upstream URL (with scheme) that NetBird forwards traffic to.
UpstreamUrl string `json:"upstream_url"`
}
// AgentNetworkProviderModel A model exposed by the provider, with the operator's per-1k input/output prices in USD.
type AgentNetworkProviderModel struct {
// Id Model identifier (e.g. "gpt-4o-mini").
Id string `json:"id"`
// InputPer1k Cost per 1k input tokens, in USD.
InputPer1k float64 `json:"input_per_1k"`
// OutputPer1k Cost per 1k output tokens, in USD.
OutputPer1k float64 `json:"output_per_1k"`
}
// AgentNetworkProviderRequest defines model for AgentNetworkProviderRequest.
type AgentNetworkProviderRequest struct {
// ApiKey Upstream provider API key. Sealed at rest on the management server and never returned in responses. Required on create; optional on update (omit to keep the existing key).
ApiKey *string `json:"api_key,omitempty"`
// BootstrapCluster Proxy cluster used to bootstrap the per-account agent-network endpoint when the first provider is created. Ignored on subsequent creates and on updates because the cluster is pinned on the account-level Settings row.
BootstrapCluster *string `json:"bootstrap_cluster,omitempty"`
// Enabled Whether the provider is enabled. Defaults to true on create.
Enabled *bool `json:"enabled,omitempty"`
// ExtraValues Operator-typed values for catalog-declared extra headers (see AgentNetworkProvider.extra_values). When present on a request, the whole map replaces the stored values. Empty strings drop the corresponding key.
ExtraValues *map[string]string `json:"extra_values,omitempty"`
// IdentityHeaderGroups Wire header name for the caller's groups CSV. See AgentNetworkProvider.identity_header_groups. Same omit / empty semantics as `identity_header_user_id`.
IdentityHeaderGroups *string `json:"identity_header_groups,omitempty"`
// IdentityHeaderUserId Wire header name for the caller's display identity. See AgentNetworkProvider.identity_header_user_id. When omitted on a request, the stored value is left unchanged; pass an empty string explicitly to clear it (which disables stamping for this dimension).
IdentityHeaderUserId *string `json:"identity_header_user_id,omitempty"`
// Models Models exposed through this endpoint, with the operator's per-1k input/output prices. Empty means all catalog models are allowed at catalog prices.
Models *[]AgentNetworkProviderModel `json:"models,omitempty"`
// Name Display name for the provider.
Name string `json:"name"`
// ProviderId Catalog identifier for the upstream AI provider (e.g. openai_api, anthropic_api, azure_openai_api, bedrock_api, vertex_ai_api, mistral_api, custom).
ProviderId string `json:"provider_id"`
// UpstreamUrl Full upstream URL (with scheme) that NetBird forwards traffic to.
UpstreamUrl string `json:"upstream_url"`
}
// AgentNetworkSettings Per-account Agent Network gateway settings. One row per account; cluster and subdomain are auto-assigned on first provider create and immutable thereafter.
type AgentNetworkSettings struct {
// AccessLogRetentionDays Days to retain full access-log rows; older rows are swept. 0 or less means keep indefinitely. Usage records are retained independently.
AccessLogRetentionDays *int `json:"access_log_retention_days,omitempty"`
// Cluster Address of the NetBird proxy cluster fronting this account's agent-network endpoint.
Cluster string `json:"cluster"`
// CreatedAt Timestamp when the settings row was created.
CreatedAt *time.Time `json:"created_at,omitempty"`
// EnableLogCollection Whether per-request access-log entries are collected for this account's agent-network traffic.
EnableLogCollection bool `json:"enable_log_collection"`
// EnablePromptCollection Master switch for request/response prompt capture. Capture runs only when this is on AND a policy guardrail also enables it.
EnablePromptCollection bool `json:"enable_prompt_collection"`
// Endpoint Bare hostname agents call for this account, computed as `<subdomain>.<cluster>`.
Endpoint string `json:"endpoint"`
// RedactPii Whether captured prompts have PII redacted. Effective redaction is the OR of this and any policy guardrail's redact setting.
RedactPii bool `json:"redact_pii"`
// Subdomain Auto-generated DNS-safe label that prefixes the cluster to form the agent-network endpoint.
Subdomain string `json:"subdomain"`
// UpdatedAt Timestamp when the settings row was last updated.
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// AgentNetworkSettingsRequest Mutable account-level Agent Network settings. Cluster and subdomain are immutable and not accepted here.
type AgentNetworkSettingsRequest struct {
// AccessLogRetentionDays Days to retain full access-log rows; older rows are swept. 0 or less means keep indefinitely.
AccessLogRetentionDays *int `json:"access_log_retention_days,omitempty"`
// EnableLogCollection Whether per-request access-log entries are collected for this account's agent-network traffic.
EnableLogCollection bool `json:"enable_log_collection"`
// EnablePromptCollection Master switch for request/response prompt capture.
EnablePromptCollection bool `json:"enable_prompt_collection"`
// RedactPii Whether captured prompts have PII redacted.
RedactPii bool `json:"redact_pii"`
}
// AgentNetworkUsageBucket One aggregated agent-network usage time bucket (UTC). The bucket width is set by the request's granularity.
type AgentNetworkUsageBucket struct {
// CostUsd Total estimated USD spend in the bucket.
CostUsd float64 `json:"cost_usd"`
// InputTokens Total input (prompt) tokens in the bucket.
InputTokens int64 `json:"input_tokens"`
// OutputTokens Total output (completion) tokens in the bucket.
OutputTokens int64 `json:"output_tokens"`
// PeriodStart Start of the bucket in YYYY-MM-DD (UTC) — the day, the week start (Monday), or the month start, depending on granularity.
PeriodStart string `json:"period_start"`
// TotalTokens Total tokens in the bucket.
TotalTokens int64 `json:"total_tokens"`
}
// AvailablePorts defines model for AvailablePorts.
type AvailablePorts struct {
// Tcp Number of available TCP ports left on the ingress peer
@@ -4892,6 +5567,87 @@ type bearerAuthContextKey string
// tokenAuthContextKey is the context key for TokenAuth security scheme
type tokenAuthContextKey string
// GetApiAgentNetworkAccessLogsParams defines parameters for GetApiAgentNetworkAccessLogs.
type GetApiAgentNetworkAccessLogsParams struct {
// Page Page number for pagination (1-indexed).
Page *int `form:"page,omitempty" json:"page,omitempty"`
// PageSize Number of items per page (max 100).
PageSize *int `form:"page_size,omitempty" json:"page_size,omitempty"`
// SortBy Field to sort by.
SortBy *GetApiAgentNetworkAccessLogsParamsSortBy `form:"sort_by,omitempty" json:"sort_by,omitempty"`
// SortOrder Sort order (ascending or descending).
SortOrder *GetApiAgentNetworkAccessLogsParamsSortOrder `form:"sort_order,omitempty" json:"sort_order,omitempty"`
// Search General search across log ID, host, path, model, and user email/name.
Search *string `form:"search,omitempty" json:"search,omitempty"`
// UserId Filter by authenticated user ID.
UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"`
// SessionId Filter to a single conversation / coding session id (groups all requests of one session).
SessionId *string `form:"session_id,omitempty" json:"session_id,omitempty"`
// GroupId Filter by authorising group id. Repeat for multiple (matches any).
GroupId *[]string `form:"group_id,omitempty" json:"group_id,omitempty"`
// ProviderId Filter by resolved provider id. Repeat for multiple (matches any).
ProviderId *[]string `form:"provider_id,omitempty" json:"provider_id,omitempty"`
// Model Filter by model. Repeat for multiple (matches any).
Model *[]string `form:"model,omitempty" json:"model,omitempty"`
// Decision Filter by policy decision (e.g. allow, deny).
Decision *string `form:"decision,omitempty" json:"decision,omitempty"`
// Path Filter by request path prefix (matches entries whose path starts with this value).
Path *string `form:"path,omitempty" json:"path,omitempty"`
// StartDate Filter by timestamp >= start_date (RFC3339 format).
StartDate *time.Time `form:"start_date,omitempty" json:"start_date,omitempty"`
// EndDate Filter by timestamp <= end_date (RFC3339 format).
EndDate *time.Time `form:"end_date,omitempty" json:"end_date,omitempty"`
}
// GetApiAgentNetworkAccessLogsParamsSortBy defines parameters for GetApiAgentNetworkAccessLogs.
type GetApiAgentNetworkAccessLogsParamsSortBy string
// GetApiAgentNetworkAccessLogsParamsSortOrder defines parameters for GetApiAgentNetworkAccessLogs.
type GetApiAgentNetworkAccessLogsParamsSortOrder string
// GetApiAgentNetworkUsageOverviewParams defines parameters for GetApiAgentNetworkUsageOverview.
type GetApiAgentNetworkUsageOverviewParams struct {
// Granularity Time bucket width. Defaults to day.
Granularity *GetApiAgentNetworkUsageOverviewParamsGranularity `form:"granularity,omitempty" json:"granularity,omitempty"`
// StartDate Filter by timestamp >= start_date (RFC3339 format).
StartDate *time.Time `form:"start_date,omitempty" json:"start_date,omitempty"`
// EndDate Filter by timestamp <= end_date (RFC3339 format).
EndDate *time.Time `form:"end_date,omitempty" json:"end_date,omitempty"`
// UserId Filter by user ID.
UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"`
// SessionId Filter to a single conversation / coding session id.
SessionId *string `form:"session_id,omitempty" json:"session_id,omitempty"`
// GroupId Filter by authorising group id. Repeat for multiple (matches any).
GroupId *[]string `form:"group_id,omitempty" json:"group_id,omitempty"`
// ProviderId Filter by resolved provider id. Repeat for multiple (matches any).
ProviderId *[]string `form:"provider_id,omitempty" json:"provider_id,omitempty"`
// Model Filter by model. Repeat for multiple (matches any).
Model *[]string `form:"model,omitempty" json:"model,omitempty"`
}
// GetApiAgentNetworkUsageOverviewParamsGranularity defines parameters for GetApiAgentNetworkUsageOverview.
type GetApiAgentNetworkUsageOverviewParamsGranularity string
// GetApiEventsNetworkTrafficParams defines parameters for GetApiEventsNetworkTraffic.
type GetApiEventsNetworkTrafficParams struct {
// Page Page number
@@ -5090,6 +5846,33 @@ type GetApiUsersParams struct {
// PutApiAccountsAccountIdJSONRequestBody defines body for PutApiAccountsAccountId for application/json ContentType.
type PutApiAccountsAccountIdJSONRequestBody = AccountRequest
// PostApiAgentNetworkBudgetRulesJSONRequestBody defines body for PostApiAgentNetworkBudgetRules for application/json ContentType.
type PostApiAgentNetworkBudgetRulesJSONRequestBody = AgentNetworkBudgetRuleRequest
// PutApiAgentNetworkBudgetRulesRuleIdJSONRequestBody defines body for PutApiAgentNetworkBudgetRulesRuleId for application/json ContentType.
type PutApiAgentNetworkBudgetRulesRuleIdJSONRequestBody = AgentNetworkBudgetRuleRequest
// PostApiAgentNetworkGuardrailsJSONRequestBody defines body for PostApiAgentNetworkGuardrails for application/json ContentType.
type PostApiAgentNetworkGuardrailsJSONRequestBody = AgentNetworkGuardrailRequest
// PutApiAgentNetworkGuardrailsGuardrailIdJSONRequestBody defines body for PutApiAgentNetworkGuardrailsGuardrailId for application/json ContentType.
type PutApiAgentNetworkGuardrailsGuardrailIdJSONRequestBody = AgentNetworkGuardrailRequest
// PostApiAgentNetworkPoliciesJSONRequestBody defines body for PostApiAgentNetworkPolicies for application/json ContentType.
type PostApiAgentNetworkPoliciesJSONRequestBody = AgentNetworkPolicyRequest
// PutApiAgentNetworkPoliciesPolicyIdJSONRequestBody defines body for PutApiAgentNetworkPoliciesPolicyId for application/json ContentType.
type PutApiAgentNetworkPoliciesPolicyIdJSONRequestBody = AgentNetworkPolicyRequest
// PostApiAgentNetworkProvidersJSONRequestBody defines body for PostApiAgentNetworkProviders for application/json ContentType.
type PostApiAgentNetworkProvidersJSONRequestBody = AgentNetworkProviderRequest
// PutApiAgentNetworkProvidersProviderIdJSONRequestBody defines body for PutApiAgentNetworkProvidersProviderId for application/json ContentType.
type PutApiAgentNetworkProvidersProviderIdJSONRequestBody = AgentNetworkProviderRequest
// PutApiAgentNetworkSettingsJSONRequestBody defines body for PutApiAgentNetworkSettings for application/json ContentType.
type PutApiAgentNetworkSettingsJSONRequestBody = AgentNetworkSettingsRequest
// PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType.
type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest

View File

@@ -843,10 +843,14 @@ type SyncResponse struct {
NetworkMap *NetworkMap `protobuf:"bytes,5,opt,name=NetworkMap,proto3" json:"NetworkMap,omitempty"`
// Posture checks to be evaluated by client
Checks []*Checks `protobuf:"bytes,6,rep,name=Checks,proto3" json:"Checks,omitempty"`
// Absolute UTC instant at which the peer's SSO session expires.
// Unset when the peer is not SSO-registered or login expiration is disabled.
// Carried on every Sync snapshot so admin-side changes propagate live without
// a client reconnect.
// 3-state session deadline. Carried on every Sync snapshot so admin-side
// changes propagate live without a client reconnect.
//
// field unset (nil) → snapshot carries no info; client keeps the
// deadline it already had
// set, seconds=0 nanos=0 → explicit "expiry disabled" or peer is not
// SSO-registered; client clears its anchor
// set, valid timestamp → new absolute UTC deadline
SessionExpiresAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=sessionExpiresAt,proto3" json:"sessionExpiresAt,omitempty"`
}
@@ -1608,8 +1612,11 @@ type LoginResponse struct {
PeerConfig *PeerConfig `protobuf:"bytes,2,opt,name=peerConfig,proto3" json:"peerConfig,omitempty"`
// Posture checks to be evaluated by client
Checks []*Checks `protobuf:"bytes,3,rep,name=Checks,proto3" json:"Checks,omitempty"`
// Absolute UTC instant at which the peer's SSO session expires.
// Unset when the peer is not SSO-registered or login expiration is disabled.
// 3-state session deadline; same encoding as SyncResponse.sessionExpiresAt.
//
// field unset (nil) → no info; client keeps any deadline it had
// set, seconds=0 nanos=0 → explicit "expiry disabled" / non-SSO peer
// set, valid timestamp → new absolute UTC deadline
SessionExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=sessionExpiresAt,proto3" json:"sessionExpiresAt,omitempty"`
}
@@ -1739,7 +1746,10 @@ type ExtendAuthSessionResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Absolute UTC instant at which the peer's SSO session now expires.
// 3-state session deadline; same encoding as SyncResponse.sessionExpiresAt.
// In practice ExtendAuthSession only succeeds for SSO peers with expiry
// enabled, so this carries a valid timestamp on the success path. The
// 3-state encoding is documented here for symmetry with Login/Sync.
SessionExpiresAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=sessionExpiresAt,proto3" json:"sessionExpiresAt,omitempty"`
}

File diff suppressed because it is too large Load Diff

View File

@@ -43,6 +43,18 @@ service ProxyService {
// issue a session cookie without redirecting through the OIDC flow.
// Mirrors ValidateSession's response shape.
rpc ValidateTunnelPeer(ValidateTunnelPeerRequest) returns (ValidateTunnelPeerResponse);
// CheckLLMPolicyLimits is the pre-flight RPC the proxy calls before each
// LLM request. Management runs the per-policy headroom selection across
// every policy authorising the caller's user / groups for the resolved
// provider and returns the chosen attribution policy + group, or a deny
// when no applicable policy has headroom > 0.
rpc CheckLLMPolicyLimits(CheckLLMPolicyLimitsRequest) returns (CheckLLMPolicyLimitsResponse);
// RecordLLMUsage is the post-flight RPC the proxy calls after the upstream
// returns. Increments the per-(dimension, window) counters for the
// attribution policy chosen by CheckLLMPolicyLimits.
rpc RecordLLMUsage(RecordLLMUsageRequest) returns (RecordLLMUsageResponse);
}
// ProxyCapabilities describes what a proxy can handle.
@@ -107,6 +119,59 @@ message PathTargetOptions {
// reachable without WireGuard (public APIs, LAN services, localhost
// sidecars). Defaults to false — embedded client is the standard path.
bool direct_upstream = 7;
// Proxy clamps to [0, proxy-wide max (1 MiB)] at apply time. Agent-network
// synthesized targets only; private services leave these zero.
int64 capture_max_request_bytes = 8;
// Proxy clamps to [0, proxy-wide max (1 MiB)] at apply time.
int64 capture_max_response_bytes = 9;
// Content types eligible for body capture (e.g. "application/json").
repeated string capture_content_types = 10;
// Per-target middleware configurations populated by the agent-network
// synthesizer. Validated and clamped by the proxy at apply time.
repeated MiddlewareConfig middlewares = 11;
// When true, the proxy stamps agent_network=true on access-log entries
// for this target so management routes them to the agent-network log
// surface.
bool agent_network = 12;
// When true, the proxy suppresses the per-request access-log emission for
// this target. Defaults false to preserve existing access-log behavior for
// every non-agent-network target. The agent-network synth target sets this
// true only when the account's EnableLogCollection toggle is off.
bool disable_access_log = 13;
}
// MiddlewareSlot identifies where in the request lifecycle a middleware
// runs. Mirrors proxy/internal/middleware.Slot.
enum MiddlewareSlot {
MIDDLEWARE_SLOT_UNSPECIFIED = 0;
MIDDLEWARE_SLOT_ON_REQUEST = 1;
MIDDLEWARE_SLOT_ON_RESPONSE = 2;
MIDDLEWARE_SLOT_TERMINAL = 3;
}
// MiddlewareConfig is the per-target configuration for a single middleware.
// The proxy validates every incoming MiddlewareConfig at apply time:
// unknown ids are rejected, timeout is clamped to [10ms, 5s], and the
// declared slot must match the registered middleware's slot.
message MiddlewareConfig {
// Middleware id; must match the proxy-local compiled-in registry.
string id = 1;
bool enabled = 2;
MiddlewareSlot slot = 3;
// Free-form JSON unmarshalled by the middleware factory into its own typed
// config struct. Empty / null / {} are valid (zero-value config).
bytes config_json = 4;
enum FailMode {
FAIL_OPEN = 0;
FAIL_CLOSED = 1;
}
FailMode fail_mode = 5;
// Clamped to [10ms, 5s] at apply time; zero → 500ms default.
google.protobuf.Duration timeout = 6;
// When true, the middleware may mutate request headers or body (subject to
// policy). Honoured only when the implementation also declares
// MutationsSupported.
bool can_mutate = 7;
}
message PathMapping {
@@ -190,6 +255,10 @@ message AccessLog {
string protocol = 16;
// Extra key-value metadata for the access log entry (e.g. crowdsec_verdict, scenario).
map<string, string> metadata = 17;
// When true, the entry was emitted by an agent-network synth service.
// Management routes these to the agent-network access-log surface instead
// of the standard service log.
bool agent_network = 18;
}
message AuthenticateRequest {
@@ -376,3 +445,59 @@ message SyncMappingsResponse {
bool initial_sync_complete = 2;
}
// CheckLLMPolicyLimitsRequest carries the resolved caller identity and the
// upstream provider already chosen by llm_router. Management computes which
// policies authorise the request, picks the one with the most remaining
// headroom, and returns the attribution decision.
message CheckLLMPolicyLimitsRequest {
// account_id is the netbird account the request belongs to.
string account_id = 1;
// user_id is the netbird user id of the caller. May be empty when the
// principal is a tunnel-peer that isn't bound to a user; group membership
// still gates the request in that case.
string user_id = 2;
// group_ids is the caller's full group membership at request time.
repeated string group_ids = 3;
// provider_id is the agent-network provider record id chosen by llm_router.
string provider_id = 4;
// model is the upstream model identifier extracted from the request body.
string model = 5;
}
// CheckLLMPolicyLimitsResponse is management's allow-or-deny decision for a
// pre-flight check.
message CheckLLMPolicyLimitsResponse {
// decision is "allow" or "deny".
string decision = 1;
// selected_policy_id names the policy that paid for this request.
string selected_policy_id = 2;
// attribution_group_id is the source group the request booked against.
string attribution_group_id = 3;
// window_seconds is the cap window length the selected policy uses.
int64 window_seconds = 4;
// deny_code is set on decision="deny" with a stable label.
string deny_code = 5;
// deny_reason is a short human-readable explanation paired with deny_code.
string deny_reason = 6;
}
// RecordLLMUsageRequest is the post-flight increment the proxy posts after
// the upstream call. Counters are keyed on (account, dimension, window).
message RecordLLMUsageRequest {
string account_id = 1;
string user_id = 2;
// group_id is the selected policy's attribution group, recorded against the
// policy window (window_seconds).
string group_id = 3;
int64 window_seconds = 4;
int64 tokens_input = 5;
int64 tokens_output = 6;
double cost_usd = 7;
// group_ids is the caller's full group membership, used to fan the same
// usage out to every applicable account-level budget rule's own window.
repeated string group_ids = 8;
}
message RecordLLMUsageResponse {
}

View File

@@ -43,6 +43,16 @@ type ProxyServiceClient interface {
// issue a session cookie without redirecting through the OIDC flow.
// Mirrors ValidateSession's response shape.
ValidateTunnelPeer(ctx context.Context, in *ValidateTunnelPeerRequest, opts ...grpc.CallOption) (*ValidateTunnelPeerResponse, error)
// CheckLLMPolicyLimits is the pre-flight RPC the proxy calls before each
// LLM request. Management runs the per-policy headroom selection across
// every policy authorising the caller's user / groups for the resolved
// provider and returns the chosen attribution policy + group, or a deny
// when no applicable policy has headroom > 0.
CheckLLMPolicyLimits(ctx context.Context, in *CheckLLMPolicyLimitsRequest, opts ...grpc.CallOption) (*CheckLLMPolicyLimitsResponse, error)
// RecordLLMUsage is the post-flight RPC the proxy calls after the upstream
// returns. Increments the per-(dimension, window) counters for the
// attribution policy chosen by CheckLLMPolicyLimits.
RecordLLMUsage(ctx context.Context, in *RecordLLMUsageRequest, opts ...grpc.CallOption) (*RecordLLMUsageResponse, error)
}
type proxyServiceClient struct {
@@ -179,6 +189,24 @@ func (c *proxyServiceClient) ValidateTunnelPeer(ctx context.Context, in *Validat
return out, nil
}
func (c *proxyServiceClient) CheckLLMPolicyLimits(ctx context.Context, in *CheckLLMPolicyLimitsRequest, opts ...grpc.CallOption) (*CheckLLMPolicyLimitsResponse, error) {
out := new(CheckLLMPolicyLimitsResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/CheckLLMPolicyLimits", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *proxyServiceClient) RecordLLMUsage(ctx context.Context, in *RecordLLMUsageRequest, opts ...grpc.CallOption) (*RecordLLMUsageResponse, error) {
out := new(RecordLLMUsageResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/RecordLLMUsage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ProxyServiceServer is the server API for ProxyService service.
// All implementations must embed UnimplementedProxyServiceServer
// for forward compatibility
@@ -208,6 +236,16 @@ type ProxyServiceServer interface {
// issue a session cookie without redirecting through the OIDC flow.
// Mirrors ValidateSession's response shape.
ValidateTunnelPeer(context.Context, *ValidateTunnelPeerRequest) (*ValidateTunnelPeerResponse, error)
// CheckLLMPolicyLimits is the pre-flight RPC the proxy calls before each
// LLM request. Management runs the per-policy headroom selection across
// every policy authorising the caller's user / groups for the resolved
// provider and returns the chosen attribution policy + group, or a deny
// when no applicable policy has headroom > 0.
CheckLLMPolicyLimits(context.Context, *CheckLLMPolicyLimitsRequest) (*CheckLLMPolicyLimitsResponse, error)
// RecordLLMUsage is the post-flight RPC the proxy calls after the upstream
// returns. Increments the per-(dimension, window) counters for the
// attribution policy chosen by CheckLLMPolicyLimits.
RecordLLMUsage(context.Context, *RecordLLMUsageRequest) (*RecordLLMUsageResponse, error)
mustEmbedUnimplementedProxyServiceServer()
}
@@ -242,6 +280,12 @@ func (UnimplementedProxyServiceServer) ValidateSession(context.Context, *Validat
func (UnimplementedProxyServiceServer) ValidateTunnelPeer(context.Context, *ValidateTunnelPeerRequest) (*ValidateTunnelPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ValidateTunnelPeer not implemented")
}
func (UnimplementedProxyServiceServer) CheckLLMPolicyLimits(context.Context, *CheckLLMPolicyLimitsRequest) (*CheckLLMPolicyLimitsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckLLMPolicyLimits not implemented")
}
func (UnimplementedProxyServiceServer) RecordLLMUsage(context.Context, *RecordLLMUsageRequest) (*RecordLLMUsageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordLLMUsage not implemented")
}
func (UnimplementedProxyServiceServer) mustEmbedUnimplementedProxyServiceServer() {}
// UnsafeProxyServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -428,6 +472,42 @@ func _ProxyService_ValidateTunnelPeer_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _ProxyService_CheckLLMPolicyLimits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckLLMPolicyLimitsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).CheckLLMPolicyLimits(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/CheckLLMPolicyLimits",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).CheckLLMPolicyLimits(ctx, req.(*CheckLLMPolicyLimitsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ProxyService_RecordLLMUsage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RecordLLMUsageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProxyServiceServer).RecordLLMUsage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/management.ProxyService/RecordLLMUsage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).RecordLLMUsage(ctx, req.(*RecordLLMUsageRequest))
}
return interceptor(ctx, in, info, handler)
}
// ProxyService_ServiceDesc is the grpc.ServiceDesc for ProxyService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -463,6 +543,14 @@ var ProxyService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ValidateTunnelPeer",
Handler: _ProxyService_ValidateTunnelPeer_Handler,
},
{
MethodName: "CheckLLMPolicyLimits",
Handler: _ProxyService_CheckLLMPolicyLimits_Handler,
},
{
MethodName: "RecordLLMUsage",
Handler: _ProxyService_RecordLLMUsage_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@@ -219,6 +219,26 @@ func NewNetworkResourceNotFoundError(resourceID string) error {
return Errorf(NotFound, "network resource: %s not found", resourceID)
}
// NewAgentNetworkProviderNotFoundError creates a new Error with NotFound type for a missing Agent Network provider.
func NewAgentNetworkProviderNotFoundError(providerID string) error {
return Errorf(NotFound, "agent network provider: %s not found", providerID)
}
// NewAgentNetworkPolicyNotFoundError creates a new Error with NotFound type for a missing Agent Network policy.
func NewAgentNetworkPolicyNotFoundError(policyID string) error {
return Errorf(NotFound, "agent network policy: %s not found", policyID)
}
// NewAgentNetworkGuardrailNotFoundError creates a new Error with NotFound type for a missing Agent Network guardrail.
func NewAgentNetworkGuardrailNotFoundError(guardrailID string) error {
return Errorf(NotFound, "agent network guardrail: %s not found", guardrailID)
}
// NewAgentNetworkBudgetRuleNotFoundError creates a new Error with NotFound type for a missing Agent Network budget rule.
func NewAgentNetworkBudgetRuleNotFoundError(ruleID string) error {
return Errorf(NotFound, "agent network budget rule: %s not found", ruleID)
}
// NewPermissionDeniedError creates a new Error with PermissionDenied type for a permission denied error.
func NewPermissionDeniedError() error {
return Errorf(PermissionDenied, "permission denied")