Files
netbird/client/server/agentnetwork.go
mlsmaycon e193e59c6a [client] Route agent-network through the daemon and shape env per provider
Two field-test findings drive this change: the direct-dial path needed
sudo (the profile's WireGuard key is root-owned), and a single flat
ANTHROPIC_* export set is wrong for providers that speak other API
shapes.

Relay the setup request through the daemon instead: a new
GetAgentNetworkSetup daemon RPC forwards to management over the
engine's existing peer connection, so unprivileged callers get the
caller-scoped answer the same way 'netbird status' works — no sudo,
and the key never leaves the daemon. The daemon's JSON gateway exposes
the RPC for the desktop UI for free.

Teach 'agent-network env' the per-provider environment contracts,
mirroring Claude Code's LLM-gateway configuration:
- anthropic flavor: ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN /
  ANTHROPIC_MODEL
- bedrock_api: CLAUDE_CODE_USE_BEDROCK, ANTHROPIC_BEDROCK_BASE_URL,
  CLAUDE_CODE_SKIP_BEDROCK_AUTH (the proxy injects AWS credentials)
- vertex_ai_api: CLAUDE_CODE_USE_VERTEX, ANTHROPIC_VERTEX_BASE_URL,
  CLAUDE_CODE_SKIP_VERTEX_AUTH, plus comments for the admin-supplied
  ANTHROPIC_VERTEX_PROJECT_ID and CLOUD_ML_REGION (the proxy forwards
  the URL path, so those values must be the operator's real ones)
- openai flavor: OPENAI_BASE_URL / OPENAI_API_KEY
- anything else: comment lines only — no guessed variables

Selection stays explicit: --provider picks by operator label or
catalog id and is required when several providers are authorized;
--model is validated against the provider's allowed set and required
when several models are allowed. Ambiguity renders as shell comments,
never as exports.

Linear: NET-1399
2026-08-04 09:10:01 +00:00

60 lines
1.9 KiB
Go

package server
import (
"context"
"time"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/proto"
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
)
// GetAgentNetworkSetup relays the peer's Agent Network setup request to the
// management server over the engine's existing connection. Running through
// the daemon keeps the WireGuard key inside the daemon — unprivileged CLI
// callers get a caller-scoped answer without reading the profile config.
func (s *Server) GetAgentNetworkSetup(ctx context.Context, _ *proto.GetAgentNetworkSetupRequest) (*proto.GetAgentNetworkSetupResponse, error) {
s.mutex.Lock()
clientRunning := s.clientRunning
connectClient := s.connectClient
s.mutex.Unlock()
if !clientRunning || connectClient == nil {
return nil, gstatus.Errorf(codes.FailedPrecondition, "client is not running, run 'netbird up' first")
}
engine := connectClient.Engine()
if engine == nil {
return nil, gstatus.Errorf(codes.FailedPrecondition, "engine not initialized")
}
setupCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
setup, err := engine.GetAgentNetworkSetup(setupCtx)
if err != nil {
return nil, gstatus.Errorf(codes.Internal, "get agent network setup: %v", err)
}
return toDaemonAgentNetworkSetup(setup), nil
}
func toDaemonAgentNetworkSetup(setup *mgmProto.AgentNetworkSetupResponse) *proto.GetAgentNetworkSetupResponse {
resp := &proto.GetAgentNetworkSetupResponse{
Configured: setup.Configured,
Endpoint: setup.Endpoint,
Providers: make([]*proto.AgentNetworkProvider, 0, len(setup.Providers)),
}
for _, p := range setup.Providers {
resp.Providers = append(resp.Providers, &proto.AgentNetworkProvider{
Name: p.Name,
CatalogId: p.CatalogId,
ApiFlavor: p.ApiFlavor,
AllModelsAllowed: p.AllModelsAllowed,
Models: p.Models,
})
}
return resp
}