Documentation
¶
Overview ¶
Package wsproxy demonstrates the embedded WebSocket proxy pattern using up.Register + up.WithGroup, with a complete implementation for the OpenAI Responses API WebSocket mode at /v1/responses.
Protocol: OpenAI Responses API WebSocket mode (/v1/responses) ¶
The client sends one message to start a response:
{"type":"response.create","model":"gpt-4.1","input":[...],"max_output_tokens":N}
The server streams events back:
{"type":"response.created", ...}
{"type":"response.output_item.delta", "delta":"Hi"} // text chunks
{"type":"response.output_item.done", ...}
{"type":"response.completed", "response":{"usage":{"input_tokens":N,"output_tokens":M}}}
The SessionTap taps every frame:
- Client → Upstream: extracts model name from response.create
- Upstream → Client: extracts token usage from response.completed
Architecture ¶
Direct-dial (default):
Client ──WS──► Envoy :10000
│ ──► ws-proxy-local (127.0.0.1:10001)
│ │
│ WSProxy.ServeHTTP
│ │ ──► wss://api.openai.com/v1/responses
│ │ Authorization: Bearer $OPENAI_API_KEY
│
Normal HTTP ──► upstream cluster (TLS + ws-auth upstream filter)
Egress-via-Envoy (WSPROXY_EGRESS_URL set):
Client ──WS──► Envoy :10000
│ ──► ws-proxy-local (127.0.0.1:10001)
│ │
│ WSProxy.ServeHTTP
│ │ ──► ws://127.0.0.1:10002 (egress listener)
│ │
│ Envoy :10002
│ │ ws-auth injects Bearer token
│ │ ──► wss://api.openai.com/v1/responses
Index ¶
Constants ¶
const AuthExtensionName = "ws-auth"
const ExtensionName = "ws-proxy"
Variables ¶
This section is empty.
Functions ¶
func InitSessionLog ¶
func InitSessionLog(path string)
InitSessionLog enables JSON-line session logging to path. Called from Register() when WSPROXY_SESSION_LOG is set.
func Register ¶
func Register()
Register wires the ws-proxy filter into Envoy via up.Register + up.WithSidecar. The filter itself is a no-op for regular HTTP — it only starts the embedded WebSocket proxy server via the Sidecar. ws-auth (upstream filter) is registered separately in auth.go's init().
func RegisterDirect ¶
RegisterDirect registers a second named filter instance in direct-dial mode.
func ResolveEnv ¶
ResolveEnv expands ${ENV_VAR} references in v using os.Expand. Returns v unchanged if the variable is unset.
Types ¶
type AuthConfig ¶
type AuthConfig struct {
// StripHeaders are request headers removed before forwarding upstream.
StripHeaders []string
// InjectHeaders are headers added to the upstream request.
// Values support ${ENV_VAR} expansion.
InjectHeaders map[string]string
}
AuthConfig holds credentials for the upstream cluster. Loaded once at filter config time from environment variables.
func DefaultAuthConfig ¶
func DefaultAuthConfig() AuthConfig
DefaultAuthConfig returns the default config reading from well-known env vars.
type Config ¶
type Config struct {
// UpstreamURL is the upstream wss:// base URL.
// Default: "wss://api.openai.com"
UpstreamURL string `json:"upstream_url"`
// AuthHeader is the HTTP header name injected when dialing upstream.
// Default: "authorization"
AuthHeader string `json:"auth_header"`
// AuthValue is the header value. Supports ${ENV_VAR} expansion.
// Default: "Bearer ${OPENAI_API_KEY}"
AuthValue string `json:"auth_value"`
// ShutdownTimeout for graceful shutdown. Default: "5s".
ShutdownTimeout string `json:"shutdown_timeout"`
// ListenAddress is the loopback address for the embedded proxy server.
// Default: "127.0.0.1:10001". Must match the ws-proxy-local cluster in envoy.yaml.
ListenAddress string `json:"listen_addr"`
// EgressURL, when set, makes the embedded server dial this local Envoy listener
// instead of UpstreamURL. Envoy handles TLS origination and credential injection
// (ws-auth upstream filter) on the egress cluster.
// Example: "ws://127.0.0.1:10002". Default: "" (direct dial).
EgressURL string `json:"egress_url"`
// OTELEndpoint enables actor-side OTLP/gRPC metrics export when set.
// Requires github.com/dio/logging + github.com/tetratelabs/telemetry.
// Example: "127.0.0.1:4317".
OTELEndpoint string `json:"otel_endpoint"`
// OTELExportInterval controls actor-side metric export cadence.
// Default: "1s".
OTELExportInterval string `json:"otel_export_interval"`
}
Config is the JSON config for the ws-proxy filter.
type SessionRecord ¶
type SessionRecord struct {
Path string `json:"path"`
Model string `json:"model"`
InputTokens uint32 `json:"input_tokens"`
OutputTokens uint32 `json:"output_tokens"`
DurationMS int64 `json:"duration_ms"`
Result string `json:"result"`
Reason string `json:"reason"`
}
SessionRecord is the JSON shape written to sessionLogPath per session. Exported so e2e can unmarshal and assert without string matching.
type SessionTap ¶
type SessionTap struct {
// contains filtered or unexported fields
}
SessionTap extracts the model name and token usage from OpenAI Responses frames. Created fresh for each WebSocket session. Exported for unit testing.
func (*SessionTap) FeedClient ¶
func (t *SessionTap) FeedClient(data []byte)
FeedClient taps the response.create frame (first client message) to extract the model name. All subsequent client frames are ignored once model is known.
func (*SessionTap) FeedUpstream ¶
func (t *SessionTap) FeedUpstream(data []byte)
FeedUpstream taps the response.completed frame (final server event) to extract token usage. All other upstream frames are ignored.
func (*SessionTap) Model ¶
func (t *SessionTap) Model() string
Model returns the model name extracted from response.create, or "" if not yet seen.
func (*SessionTap) Usage ¶
func (t *SessionTap) Usage() TokenUsage
Usage returns the token usage extracted from response.completed.
type TokenUsage ¶
TokenUsage holds the token counts from the completed response.
type WSProxy ¶
type WSProxy struct {
// OnClientFrame is called for each text frame the client sends.
// Runs in the pump goroutine — must be fast and non-blocking.
OnClientFrame func(websocket.MessageType, []byte)
// OnUpstreamFrame is called for each text frame received from upstream.
// Runs in the pump goroutine — must be fast and non-blocking.
OnUpstreamFrame func(websocket.MessageType, []byte)
// contains filtered or unexported fields
}
WSProxy is an http.Handler that proxies WebSocket connections to an upstream provider, tapping frames for model extraction and token counting.