extproc

package
v0.39.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: Apache-2.0, MIT Imports: 27 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DropUpstreamStatus  = DropReason(capture.DropUpstreamStatus)
	DropNonTurnRequest  = DropReason(capture.DropNonTurnRequest)
	DropRequestDecode   = DropReason(capture.DropRequestDecode)
	DropEmptyResponse   = DropReason(capture.DropEmptyResponse)
	DropUnknownProvider = DropReason(capture.DropUnknownProvider)
	DropResponseDecode  = DropReason(capture.DropResponseDecode)
	DropReducerError    = DropReason(capture.DropReducerError)
)

The capture-policy half. These say what makes a turn capturable at all, so they belong to every implementation of tapes capture and not to this one: two capture paths that disagree about any of them record different sessions from identical traffic.

The strings are therefore NOT declared here. They are conversions of the specified vocabulary in pkg/capture, so this adapter reads the contract instead of restating it — restating it is how the last capture contract drifted while both copies stayed green. Behaviour and metric labels are unchanged: these are the same strings they have always been, sourced from the place that now owns them.

View Source
const DefaultLargeTurnThreshold = 4 * 1024 * 1024

Metrics is the Prometheus surface for tapes-extproc. Each Processor owns its own registry so tests can scrape in isolation; the cmd-level wiring mounts /metrics on the existing HTTP mux. DefaultLargeTurnThreshold is the response-body size at which a turn is counted as "large". Chosen to sit above the expected p99 for normal agentic traffic (~1 MB) but below pathological tool-use turns. The threshold is observability, not a cap — turns above it are still captured and dispatched as usual.

It is deliberately NOT capture policy, and that was a decision rather than an omission — see fixtures/drop-reason/README.md, which records it alongside the drop-reason taxonomy because it is the same kind of question. It gates a counter and nothing else: two builds that disagree about it capture, dispatch and store byte-identical turns, and differ only in one deployment's sizing dashboard. That is also why it is settable per deployment. A threshold that dropped or truncated a turn would have to be contract; this one is an observability knob wearing a policy's shape.

Variables

This section is empty.

Functions

func GRPCServerOptions added in v0.35.0

func GRPCServerOptions(cfg Config) []grpc.ServerOption

GRPCServerOptions builds the server options main wires into grpc.NewServer; the recv limit default is justified at defaultGRPCMaxRecvBytes.

func RegisterServer

func RegisterServer(s *grpc.Server, p *Processor)

RegisterServer installs p on the given gRPC server.

Types

type Config

type Config struct {
	// IngestURL is the base URL for the tapes ingest server.
	IngestURL string
	// ListenAddr is the gRPC TCP listen address.
	ListenAddr string
	// MetricsAddr is the HTTP metrics/health listen address.
	MetricsAddr string
	// ProviderMapFile is the path to the provider mapping YAML file.
	ProviderMapFile string
	// MaxInflight is the dispatch semaphore capacity.
	MaxInflight int
	// GRPCMaxRecvBytes is the gRPC server's maximum receive message size.
	GRPCMaxRecvBytes int
	// DispatchByteBudget bounds the total marshalled payload bytes in flight to ingest.
	DispatchByteBudget int64
	// RawResponseMode selects whether the dispatch envelope carries the
	// verbatim upstream response bytes, the adapter's reduction, or both.
	// Zero value is RawResponseOff, so a Config built by a test or an
	// older caller keeps the historical wire shape.
	RawResponseMode RawResponseMode
}

Config holds configuration for the tapes-extproc adapter.

func ConfigFromEnv

func ConfigFromEnv() Config

ConfigFromEnv reads configuration from environment variables.

type ContextObserver

type ContextObserver interface {
	OnAcceptedContext(provider string, requestID string, ctx OutcomeContext)
	OnDropContext(provider string, reason DropReason, requestID string, ctx OutcomeContext)
}

ContextObserver is an optional extension for observers that need safe per-turn context. The base Observer stays small for existing tests; metrics implements this richer interface to emit low-cardinality terminal outcomes.

type DispatchObserver

type DispatchObserver interface {
	OnDispatchLatency(provider string, requestID string, seconds float64)
	OnInflight(n int)
}

DispatchObserver is an optional extension for dispatch-stage metrics.

type DispatchedSessionEnvelope

type DispatchedSessionEnvelope struct {
	OrgID                  string         `json:"org_id"`
	AuthSubject            string         `json:"auth_subject"`
	HarnessID              string         `json:"harness_id"`
	HarnessSessionID       string         `json:"harness_session_id,omitempty"`
	HarnessVersion         string         `json:"harness_version,omitempty"`
	Cwd                    string         `json:"cwd,omitempty"`
	Name                   string         `json:"name,omitempty"`
	ParentHarnessSessionID string         `json:"parent_harness_session_id,omitempty"`
	HarnessMetadata        map[string]any `json:"harness_metadata,omitempty"`
}

DispatchedSessionEnvelope is the session-tracking block posted to tapes-ingest. The JSON tags are the wire shape tapes-ingest parses against; renaming a field is a breaking change to ingest, not just to this struct.

Naming: this type is the wire-shaped sibling of headers.SessionEnvelope (the parsed view of the inbound request). The two are intentionally separate — headers.SessionEnvelope tracks parser-side state (Present, HarnessMetadataMalformed) that has no place on the dispatched JSON, and DispatchedSessionEnvelope carries the auth-derived fields (OrgID, AuthSubject) the headers package can't see. buildSessionEnvelope is the one site that maps between them.

HarnessMetadata is the decoded JSON object (already base64url- decoded by the processor). nil means no metadata header was attached — distinct from "{}" which the caller may attach explicitly.

type Dispatcher

type Dispatcher struct {
	// contains filtered or unexported fields
}

Dispatcher owns the "turn → tapes-ingest" path. Kept separate from the processor state machine so retry/backoff/marshal-error handling is one reviewable diff and the silent "_" on json.Marshal that kept the 4-week bug invisible cannot be reintroduced without also breaking its test.

func NewDispatcher

func NewDispatcher(ingestURL string, maxInflight int, byteBudget int64, client *http.Client) *Dispatcher

NewDispatcher returns a Dispatcher with the given ingest URL, in-flight count cap, and in-flight payload byte budget. Non-positive values fall back to the config defaults.

func (*Dispatcher) Dispatch

func (d *Dispatcher) Dispatch(ctx context.Context, env TurnEnvelope)

Dispatch enqueues a turn for POST to tapes-ingest. Returns immediately; the actual HTTP call runs on a goroutine bounded by the semaphore.

func (*Dispatcher) RecordDrop

func (d *Dispatcher) RecordDrop(provider string, reason DropReason, requestID string)

RecordDrop is used by the processor to record drops that happened before the dispatch stage (client disconnect, unknown provider, …) so all drop reasons pass through one observation point.

func (*Dispatcher) RecordDropContext

func (d *Dispatcher) RecordDropContext(provider string, reason DropReason, requestID string, ctx OutcomeContext)

RecordDropContext is the rich variant used when the processor has already parsed request metadata. It keeps metric labels bounded while logs retain enough context to answer model/endpoint/size/status questions.

func (*Dispatcher) SetObserver

func (d *Dispatcher) SetObserver(o Observer)

SetObserver installs a metrics/spy observer. Thread-unsafe once Dispatch is running; call during setup only.

type DropReason

type DropReason string

DropReason is a closed enum of why a turn failed to land in tapes. Keeping it a named type (not a string literal) forces new call sites to add a constant and — by extension — a metric label row. Dashboards and alerts then stay stable against typos.

The enum has two halves, and which half a reason is in is a decision rather than a grouping — see fixtures/drop-reason/, where both halves are specified and the line between them is argued.

const (
	DropMarshalError     DropReason = "marshal_error"
	DropIngestReject     DropReason = "ingest_reject"
	DropIngestTimeout    DropReason = "ingest_timeout"
	DropSemFull          DropReason = "sem_full"
	DropClientDisconnect DropReason = "client_disconnect"
	// DropUpstreamNoResponse: the stream was torn down after the request
	// completed and before any response byte arrived. Distinct from
	// DropEmptyResponse, which is a response phase that completed normally
	// carrying nothing: that is a property of the exchange, this is a
	// property of the connection.
	DropUpstreamNoResponse DropReason = "upstream_no_response"
	// DropMissingStatus: the response phase ended without Envoy ever
	// sending :status, which violates the ext_proc message contract. It is
	// a transport reason despite looking like a policy one — no
	// implementation that reads a status directly off a response can
	// reach it.
	DropMissingStatus DropReason = "missing_status"
	// DropRequestOverBudget: request accumulation stopped at
	// requestCaptureBudget — a request that large can never land at ingest,
	// so the turn is shed before any marshal or POST. Forwarding is untouched.
	DropRequestOverBudget DropReason = "request_over_budget"
)

The transport and runtime half. These are correctly this adapter's own: each one names a way THIS deployment can fail to move bytes, and an implementation without a dispatch queue, a downstream client connection or a remote ingest endpoint cannot produce them. Promoting them to the shared vocabulary would specify one deployment's plumbing as everyone's contract.

They stay declared here for exactly that reason, and the corpus specifies them as non-contract so that "not shared" is recorded rather than assumed.

func AllDropReasons

func AllDropReasons() []DropReason

AllDropReasons enumerates every constant above so metric wiring can preallocate label rows and the metric-enumeration test can assert completeness.

type Metrics

type Metrics struct {
	// contains filtered or unexported fields
}

func NewMetrics

func NewMetrics() *Metrics

NewMetrics constructs a fresh registry populated with the full extproc set. AllDropReasons label rows are pre-created so /metrics renders every possible reason even before the first drop of that kind.

func (*Metrics) AsObserver

func (m *Metrics) AsObserver() Observer

AsObserver adapts Metrics to the Dispatcher.Observer interface so the extproc dispatcher can emit terminal outcomes without importing promhttp.

func (*Metrics) Handler

func (m *Metrics) Handler() http.Handler

Handler returns an http.Handler serving Prometheus text exposition.

func (*Metrics) LargeTurnThreshold

func (m *Metrics) LargeTurnThreshold() int

LargeTurnThreshold returns the byte threshold above which a turn is counted in tapes_extproc_turns_large_total. Configurable via SetLargeTurnThreshold; defaults to DefaultLargeTurnThreshold.

func (*Metrics) ObserveAccepted

func (m *Metrics) ObserveAccepted(provider string)

ObserveAccepted increments captured_total for a provider.

func (*Metrics) ObserveBodyBytes

func (m *Metrics) ObserveBodyBytes(provider, side string, bytes int)

ObserveBodyBytes records accumulated body size by side ("request" | "response").

func (*Metrics) ObserveBodyBytesByOutcome

func (m *Metrics) ObserveBodyBytesByOutcome(provider, side, outcome, reason string, bytes int)

ObserveBodyBytesByOutcome records body-size distributions for both accepted and dropped turns. This complements tapes_extproc_body_bytes, which historically measured only successful captured turns.

func (*Metrics) ObserveDispatchLatency

func (m *Metrics) ObserveDispatchLatency(provider string, seconds float64)

ObserveDispatchLatency records the HTTP POST roundtrip to ingest.

func (*Metrics) ObserveDrop

func (m *Metrics) ObserveDrop(provider string, reason DropReason)

ObserveDrop increments dropped_total for a provider / reason.

func (*Metrics) ObserveRawResponseAttached

func (m *Metrics) ObserveRawResponseAttached(provider, shape string)

ObserveRawResponseAttached counts a turn dispatched with verbatim bytes.

func (*Metrics) ObserveRawResponseFallback

func (m *Metrics) ObserveRawResponseFallback(provider, reason string)

ObserveRawResponseFallback counts a raw-only turn that kept its reduction.

func (*Metrics) ObserveRawResponseSkipped

func (m *Metrics) ObserveRawResponseSkipped(provider, reason string)

ObserveRawResponseSkipped counts a turn whose verbatim bytes were withheld.

func (*Metrics) ObserveReducerEmpty

func (m *Metrics) ObserveReducerEmpty(provider, contentType string, upstreamStatus int)

ObserveReducerEmpty increments the counter that tracks reducer outputs that would fail tapes ingest's validator. Labels surface the upstream shape (content_type, HTTP status) so an operator can tell whether the empty cases concentrate on, e.g., text/event-stream@200 (real reducer bug) vs. application/json@5xx (upstream error envelope captured as if it were a turn). The content-type label is normalized to its bare MIME type to keep cardinality bounded.

func (*Metrics) ObserveRequestContentLength added in v0.35.0

func (m *Metrics) ObserveRequestContentLength(provider string, bytes int64)

ObserveRequestContentLength records a parsed request Content-Length at the request-headers phase. Callers must only pass successfully parsed values; absent/unparseable headers go through ObserveRequestContentLengthUnknown instead so the histogram's low buckets are never corrupted by zero stand-ins.

func (*Metrics) ObserveRequestContentLengthUnknown added in v0.35.0

func (m *Metrics) ObserveRequestContentLengthUnknown(provider string)

ObserveRequestContentLengthUnknown counts a request whose Content-Length was absent or unparseable at the request-headers phase. Increments only the unknown counter — never the content-length histogram.

func (*Metrics) ObserveResponseDecodeSalvaged

func (m *Metrics) ObserveResponseDecodeSalvaged(provider, encoding string, messageStopSeen bool)

ObserveResponseDecodeSalvaged records a successful decode from a truncated compressed response body. It is separate from response_decoded_total so operators can tell normal gzip@ok traffic from Option-A salvage recoveries.

func (*Metrics) ObserveResponseDecoded

func (m *Metrics) ObserveResponseDecoded(encoding, result string)

ObserveResponseDecoded records one decode attempt on the upstream response body. encoding is the raw Content-Encoding header value (normalized to lowercase, empty/labelIdentity mapped to labelIdentity). result is "ok" or "error". The metric is purely observational — success doesn't mean the reducer was happy with the decoded bytes, just that decode itself succeeded. Pair with reducer_empty_total when investigating which path is dominant.

func (*Metrics) ObserveSSEChunks

func (m *Metrics) ObserveSSEChunks(provider string, n int)

ObserveSSEChunks records how many SSE frames a streamed turn produced.

func (*Metrics) ObserveTerminal

func (m *Metrics) ObserveTerminal(provider, outcome, reason string, ctx OutcomeContext)

ObserveTerminal records the bounded terminal outcome dimensions that let operators correlate drops with endpoint, stream mode, model family, and upstream status class without promoting raw paths/models/request IDs to labels.

func (*Metrics) ObserveTerminalDuration

func (m *Metrics) ObserveTerminalDuration(provider, outcome, reason string, seconds float64)

ObserveTerminalDuration records header-to-terminal latency for accepted and dropped turns. Zero or negative durations are ignored so partially-populated contexts don't create misleading near-zero rows.

func (*Metrics) ObserveTurnDuration

func (m *Metrics) ObserveTurnDuration(provider string, seconds float64)

ObserveTurnDuration records the header-to-dispatch wall time.

func (*Metrics) ObserveTurnSize

func (m *Metrics) ObserveTurnSize(provider string, respBytes int)

ObserveTurnSize increments tapes_extproc_turns_large_total when the response body exceeded the threshold. Called unconditionally on every dispatched turn; the threshold check lives here so the call site stays one line.

func (*Metrics) Registry

func (m *Metrics) Registry() *prometheus.Registry

Registry exposes the *prometheus.Registry so callers can mount the scrape handler or assert on metric state.

func (*Metrics) SetBuildInfo added in v0.35.0

func (m *Metrics) SetBuildInfo(version, commit string)

SetBuildInfo publishes the build metadata series. The value is always exactly 1 (Prometheus build_info convention — enables group_left joins).

func (*Metrics) SetInflight

func (m *Metrics) SetInflight(n int)

SetInflight updates the gauge of currently-dispatching turns.

func (*Metrics) SetLargeTurnThreshold

func (m *Metrics) SetLargeTurnThreshold(bytes int)

SetLargeTurnThreshold overrides the "large turn" byte threshold. Useful for tests and for per-environment tuning once real p99 data is in.

type Observer

type Observer interface {
	OnAccepted(provider string, requestID string)
	OnDrop(provider string, reason DropReason, requestID string)
}

Observer hooks every terminal outcome. nil-safe.

type OutcomeContext

type OutcomeContext struct {
	Method string
	Path   string
	// ThreadID is the harness sub-thread identifier (e.g. a Claude Code
	// subagent agent-id), "" for main-thread calls. It is high-cardinality
	// and surfaced ONLY in structured logs via logAttrs — never folded into
	// a metric label set. Metrics read the bounded fields below directly
	// through normalizers (see Metrics.ObserveTerminal), so this field is
	// invisible to the metrics path.
	ThreadID            string
	Endpoint            string
	Model               string
	ModelFamily         string
	Stream              string
	ContentType         string
	ContentEncoding     string
	UpstreamStatus      int
	UpstreamStatusClass string
	RequestBytes        int
	ResponseBytes       int
	ElapsedSeconds      float64
}

OutcomeContext carries safe request/response metadata for logs and metrics. Raw high-cardinality values are used only in structured logs; metrics fold them through bounded label normalizers in Metrics.

type Processor

type Processor struct {
	extprocv3.UnimplementedExternalProcessorServer
	// contains filtered or unexported fields
}

Processor implements the ext_proc ExternalProcessor service. It is the sidecar that observes traffic through a tenant's Envoy AI Gateway and forwards completed turns to tapes-ingest.

The processor is a per-stream state machine: accumulate request and response bodies with append (never overwrite), gate dispatch on EndOfStream so partial frames don't trigger a premature POST, and issue ModeOverride to FULL_DUPLEX_STREAMED only when stream:true is seen in the request body so non-streaming turns stay on the default BUFFERED path. Body accumulators are plain bytes.Buffer — per-turn memory scales with the upstream response, ceiling'd in practice by the caller's max_tokens. tapes_extproc_body_bytes (histogram) and tapes_extproc_turns_large_total (counter above LargeTurnThreshold) track the distribution so cluster sizing can be adjusted from real data.

func NewProcessor

func NewProcessor(cfg Config) (*Processor, error)

NewProcessor builds a Processor from Config.

func (*Processor) Dispatcher

func (p *Processor) Dispatcher() *Dispatcher

Dispatcher returns the dispatch path, exposed for tests and metric wiring.

func (*Processor) Metrics

func (p *Processor) Metrics() *Metrics

Metrics exposes the Prometheus registry so the cmd wiring can mount /metrics on its existing HTTP mux.

func (*Processor) Process

Process implements the bidirectional ext_proc RPC. The state machine is linear: RequestHeaders → RequestBody* → ResponseHeaders → ResponseBody* with dispatch firing once on response-body EOS (or on client disconnect for a drop metric).

func (*Processor) SetProviderMap

func (p *Processor) SetProviderMap(m map[string]string)

SetProviderMap replaces the backend-to-provider mapping used by resolveProvider. Tests use this to exercise the unknown-provider path without creating a real ProviderMapFile on disk.

type RawLaneObserver

type RawLaneObserver interface {
	// OnRawResponseAttached fires when verbatim bytes go on the wire.
	// shape is "dual" or "raw_only".
	OnRawResponseAttached(provider, shape string)
	// OnRawResponseSkipped fires when the mode wanted bytes but they were
	// withheld. The row lands fidelity:reduced.
	OnRawResponseSkipped(provider, reason string)
	// OnRawResponseFallback fires when a raw-only turn kept its reduction
	// because ingest could not have produced one.
	OnRawResponseFallback(provider, reason string)
}

RawLaneObserver is an optional extension covering the verbatim-bytes lane. Every outcome is non-fatal to the turn, so none of them belong in DropReason — but each one silently changes the fidelity a row lands with, which makes them exactly the things an operator needs counted rather than inferred.

type RawResponseMode

type RawResponseMode string

RawResponseMode selects what the dispatch envelope carries for the response half of a turn. It exists to make the migration from "extproc reduces" to "tapes reduces" a config change with a proving step in the middle, rather than a flag day.

off  — the historical shape: the adapter's reduction only. No verbatim
       bytes leave the process. Stored rows land fidelity:reduced.
dual — reduction AND verbatim bytes. Ingest keeps the adapter's reduction
       (it consumed the live stream and may have seen framing the stored
       bytes no longer show) and stores the bytes alongside it, so rows
       land fidelity:raw while the rendered response is unchanged.
raw  — verbatim bytes only. Ingest reduces server-side with the shared
       pkg/capture reducers. This is the end state: one reducer for every
       capture path means two paths cannot reduce differently.

Rollout is deliberately ratcheted: off → dual (prove equivalence on real traffic, since dual changes nothing an operator sees while making the bytes available for comparison) → raw (delete the second reducer). See README.

const (
	// RawResponseOff is the default. Changing the default is a separate,
	// deliberate decision from making the mode available.
	RawResponseOff RawResponseMode = "off"
	// RawResponseDual sends both halves.
	RawResponseDual RawResponseMode = "dual"
	// RawResponseRaw sends verbatim bytes and no reduction.
	RawResponseRaw RawResponseMode = "raw"
)

func ParseRawResponseMode

func ParseRawResponseMode(s string) (RawResponseMode, error)

ParseRawResponseMode maps a config string onto the enum. An unrecognized value is an error rather than a silent fallback: a typo'd mode that quietly disables the raw lane would look exactly like a working deployment right up until someone asks why nothing is fidelity:raw.

type TurnEnvelope

type TurnEnvelope struct {
	Provider  string            `json:"provider"`
	AgentName string            `json:"agent_name,omitempty"`
	Request   json.RawMessage   `json:"request"`
	Response  *llm.ChatResponse `json:"response"`

	// RawResponse is the upstream response body exactly as it arrived on
	// the wire — still under whatever Content-Encoding the upstream used.
	// encoding/json renders it as standard padded base64.
	//
	// It is NOT the decoded bytes: tapes stores the column byte-faithfully
	// and decodes only to reduce, so decompressing here would make the
	// stored bytes something the upstream never sent.
	RawResponse []byte `json:"raw_response,omitempty"`

	// RawResponseEncoding is the Content-Encoding describing RawResponse.
	// Empty means identity. Ingest needs it to decode before reducing;
	// without it the bytes are unreducible archive.
	RawResponseEncoding string `json:"raw_response_encoding,omitempty"`

	// RawResponseWithheld says this adapter captured verbatim bytes for the
	// turn and chose not to send them — always because including them would
	// have pushed the envelope past ingest's body limit, and a turn without
	// its bytes beats no turn at all.
	//
	// Only the producer can know this. An envelope with no raw_response is
	// otherwise the same envelope whether the bytes never existed or were
	// dropped on the way out, and those are opposite operational facts: the
	// first says which adapters are deployed, the second says a limit bit and
	// wants tuning. Ingest folds this into raw_response_dropped; the field is
	// spelled differently on purpose, because that column is the union of "the
	// producer withheld" and "ingest capped" and this is only the first.
	//
	// It is set at exactly the two places bytes are withheld after being
	// captured — the pre-dispatch transport-budget decision, and
	// enforceBodyLimit's post-marshal strip — and nowhere else. In particular
	// it is NOT set when the mode never asked for bytes: mode=off captured
	// nothing to withhold, and marking those turns would report the whole
	// fleet as losing bytes it never had.
	//
	// Invariant, relied on by ingest, which honors the marker only when no
	// bytes arrived: withheld implies raw_response absent. Both writers below
	// clear the bytes in the same breath as setting this.
	RawResponseWithheld bool `json:"raw_response_withheld,omitempty"`

	// No omitempty: it has no effect on a non-pointer struct, and meta is
	// always sent.
	Meta TurnMeta `json:"meta"`
	// Session is the optional session-tracking block. Present
	// (non-nil) when the inbound request carried any X-Tapes-*
	// header; nil otherwise. omitempty keeps the wire shape stable
	// for tapes-ingest endpoints that don't know about session
	// blocks yet.
	Session *DispatchedSessionEnvelope `json:"session,omitempty"`
	// contains filtered or unexported fields
}

TurnEnvelope is the JSON body posted to tapes-ingest. Request stays raw provider JSON while Response is the already-reduced canonical LLM response.

type TurnMeta

type TurnMeta struct {
	RequestID   string `json:"request_id,omitempty"`
	ContentType string `json:"content_type,omitempty"`

	// ThreadID is the harness's sub-thread identifier for this call
	// (e.g. Claude Code's subagent agent-id), "" for main-thread
	// calls. Resolved harness-neutrally by headers.ThreadID.
	ThreadID string `json:"thread_id,omitempty"`

	Method              string  `json:"method,omitempty"`
	Path                string  `json:"path,omitempty"`
	Endpoint            string  `json:"endpoint,omitempty"`
	Model               string  `json:"model,omitempty"`
	ModelFamily         string  `json:"model_family,omitempty"`
	Stream              string  `json:"stream,omitempty"`
	ContentEncoding     string  `json:"content_encoding,omitempty"`
	UpstreamStatus      int     `json:"upstream_status,omitempty"`
	UpstreamStatusClass string  `json:"upstream_status_class,omitempty"`
	RequestBytes        int     `json:"request_bytes,omitempty"`
	ResponseBytes       int     `json:"response_bytes,omitempty"`
	ElapsedSeconds      float64 `json:"elapsed_seconds,omitempty"`
}

TurnMeta carries capture-side metadata. Kept in its own struct so it can grow without disturbing the Provider/Request/Response contract ingest already accepts.

Every field serializes onto the wire envelope: tapes-ingest persists the whole meta block verbatim into its immutable raw-turn store, so anything captured here is recoverable downstream without re-capture. Ingest deployments that predate the raw store simply ignore the extra keys.

Directories

Path Synopsis
Package headers names the HTTP / ext_proc header values that tapes-extproc reads from the upstream traffic Envoy hands it.
Package headers names the HTTP / ext_proc header values that tapes-extproc reads from the upstream traffic Envoy hands it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL