Files
livekit/cmd/agent-endpoint-client/main.go
T
Théo Monnom 8c7011cb7a agent: HTTP endpoints data plane
Serve worker-declared FastAPI routes at /agents/{deployment}/{path} without
any worker-side listener: workers dial a fixed pool of wires speaking
AgentHttp.Frame, the server opens multiplexed streams carrying one opaque
HTTP/1.1 exchange each (two-level credit flow control, prioritized write
scheduler, attach epoch fencing). The front does starlette-exact manifest
matching with per-endpoint public access, typed 401/404/405, a retry table,
and SSE/WebSocket passthrough; a pluggable fallback hook lets multi-node
deployments resolve misses elsewhere. Includes a conformance client, a
manual sidecar, and the acceptance suite.

The auth middleware now reads access_token from the query string only:
FormValue consumed the bodies of proxied url-encoded POSTs.
2026-08-19 18:25:35 -07:00

112 lines
3.4 KiB
Go

// Copyright 2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// agent-endpoint-client is the reference sidecar for the agent HTTP endpoints
// data plane: it registers a manifest against a livekit-server and bridges
// tunnel streams to any local HTTP server, standing in for the SDK's tunnel
// client until it lands.
//
// Example, against a dev server:
//
// livekit-server --dev &
// python app.py # any local HTTP server on :8080
// agent-endpoint-client -url ws://localhost:7880/agent -api-key devkey \
// -api-secret secret -deployment production -target 127.0.0.1:8080 \
// -route "GET /json" -route "public GET /sse"
// curl http://localhost:7880/agents/production/json
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/livekit-server/pkg/agent/endpoint/client"
)
type routeFlags []string
func (r *routeFlags) String() string { return strings.Join(*r, ",") }
func (r *routeFlags) Set(v string) error { *r = append(*r, v); return nil }
func main() {
var (
url = flag.String("url", "ws://localhost:7880/agent", "livekit-server /agent URL")
apiKey = flag.String("api-key", "devkey", "API key")
apiSecret = flag.String("api-secret", "secret", "API secret")
agentName = flag.String("agent-name", "endpoint-sidecar", "agent name")
deployment = flag.String("deployment", "", "deployment name (empty = default)")
target = flag.String("target", "127.0.0.1:8080", "local HTTP server to bridge into")
routes routeFlags
)
flag.Var(&routes, "route", `route to expose, e.g. "GET /json", "public POST /sms", repeatable`)
flag.Parse()
if len(routes) == 0 {
fmt.Fprintln(os.Stderr, "at least one -route is required")
os.Exit(1)
}
var endpoints []*livekit.AgentHttp_AgentEndpoint
for _, r := range routes {
parts := strings.Fields(r)
public := false
if len(parts) > 0 && parts[0] == "public" {
public = true
parts = parts[1:]
}
if len(parts) != 2 {
fmt.Fprintf(os.Stderr, "invalid -route %q, want \"[public] METHOD /path\"\n", r)
os.Exit(1)
}
endpoints = append(endpoints, &livekit.AgentHttp_AgentEndpoint{
Path: parts[1],
Methods: []string{strings.ToUpper(parts[0])},
Public: public,
})
}
logger.InitFromConfig(&logger.Config{Level: "info"}, "agent-endpoint-client")
w := client.New(client.Config{
ServerURL: *url,
APIKey: *apiKey,
APISecret: *apiSecret,
AgentName: *agentName,
Deployment: *deployment,
Endpoints: endpoints,
TargetAddr: *target,
Logger: logger.GetLogger(),
})
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
if err := w.Start(ctx); err != nil {
fmt.Fprintln(os.Stderr, "start failed:", err)
os.Exit(1)
}
logger.Infow("endpoint sidecar attached", "workerID", w.WorkerID(), "routes", len(endpoints))
<-ctx.Done()
w.Close()
}