Documentation
¶
Overview ¶
Package gateway provides a local HTTP compatibility layer that lets coding-harness clients speaking different model-API dialects (Anthropic Messages, OpenAI Responses, OpenAI Chat Completions, Gemini) reach any injected inference.Client/model.Model target.
This file implements Handler.ServeHTTP: the ordered request-processing pipeline. For every request it performs, in order:
- Validate method, path, content type, and header bounds.
- Authenticate the local gateway token using constant-time comparison.
- Select exactly one ingress codec.
- Apply the bounded body reader before JSON decoding.
- Decode the native request into codec.DecodedRequest.
- Resolve (ingress format, requested model) to a Target.
- Replace the neutral request's model with Target.Model. The inbound alias is never sent upstream as the target model name.
- Validate request features against Target.Model.Caps.
- Apply global concurrency admission.
- Invoke Target.Client.Invoke or Target.Client.Stream.
- Encode the result with the same dialect used for ingress.
- Close all upstream bodies/readers and release admission exactly once.
The harness-facing response reports the requested alias where its dialect expects a model field. Internal structured diagnostics may record both the alias and Target.ID.
Package gateway (this file) implements Server: a loopback-only local HTTP listener that wraps an arbitrary http.Handler (typically, but not necessarily, a *Handler built by New) with its own per-instance, cryptographically random bearer token.
Token-generation architecture (read this before touching auth here) ¶
Config.Authenticate (config.go) is required and is baked into a *Handler at gateway.New time -- it authenticates whatever token the CALLER chose when it built that Config, before any Server exists. Server cannot retroactively change what an already-built http.Handler checks, and the design doc's own worked composition example configures a Handler with Authenticate omitted entirely from its sketch of the pairing.
The design doc's security section instead describes the LOCAL SERVER -- not the Handler -- as the thing that "generates a cryptographically random bearer token per server" and "reports readiness only after the listener is bound". This file implements that literally, at the Server level: Server generates its own token independently of whatever the wrapped Handler does, and wraps ServerConfig.Handler in its own constant-time bearer-check middleware (see authMiddleware) using that self-generated token -- completely independent of, and unaware of, any authentication the inner Handler itself performs.
This makes Server self-sufficient and testable against ANY http.Handler (not just a *Handler from this package), matches "the local server generates a token" literally, and requires reopening neither the already-committed Config (whose Authenticate stays mandatory) nor auth.go. A caller that builds a real Handler with its own Authenticate gets defense-in-depth double-checking under this scheme -- harmless, not a bug, since both checks run against the same forwarded Authorization header and a request must satisfy whichever checks are actually wired in front of it.
Package gateway (this file) implements Handler.serveStreaming: the incremental pull loop that drives a streaming inference response.
serveStreaming replaces the intentionally minimal Task-6 stub declared in handler.go (see that method's remaining doc comment there for the seam contract: ServeHTTP's call site and the admission acquire/release around it are unchanged by this file). It is responsible for, in order:
- Calling target.Client.Stream (decoded.Request.Model has already been replaced with target.Model by serveInference).
- Classifying a pre-header Stream error exactly like the non-streaming Invoke failure path (a *UpstreamInvocationError via h.writeError).
- Owning the returned *stream.StreamReader[content.Chunk] for its entire lifetime once Stream succeeds (a deferred Close covers clean EOF, in-stream failure, and cancellation alike).
- Opening the native streaming response via sc.OpenStream -- the point headers commit, after which every further failure goes through the returned codec.StreamEncoder's Fail, never sc.WriteError again.
- Pulling chunks one at a time and writing each to the StreamEncoder in order, terminating with exactly one of Finish (clean EOF) or Fail (any other error).
- Arming a watcher that closes the reader when the inbound request's context is canceled, so a blocked Next() cannot outlive a client disconnect or server shutdown -- and disarming that watcher once the pull loop itself returns, so a normal, fast request never leaves a goroutine (or context bookkeeping) waiting on a longer-lived parent context.
Package gateway provides a local HTTP compatibility layer that lets coding-harness clients speaking different model-API dialects (Anthropic Messages, OpenAI Responses, OpenAI Chat Completions, Gemini) reach any injected inference.Client/model.Model target.
This file defines Target: the fully bound routing destination shared by every Resolver implementation.
Index ¶
- Constants
- type AmbiguousCodecMatchError
- type AuthenticationError
- type Authenticator
- type Binding
- type ConcurrencyLimitExceededError
- type Config
- type ConfigError
- type CountTokensUnavailableError
- type ExactResolver
- type FixedResolver
- type Handler
- type MethodNotAllowedError
- type Mux
- type NoMatchingCodecError
- type RequestTooLargeError
- type Resolver
- type ResponseEncodeError
- type RouteKey
- type RouteNotFoundError
- type Server
- type ServerConfig
- type ServerStateError
- type ShutdownTimeoutError
- type Target
- type UnknownRouteError
- type UnsupportedContentTypeError
- type UpstreamInvocationError
Constants ¶
const DefaultMaxConcurrent = 64
DefaultMaxConcurrent is the global in-flight admission bound applied when Config.MaxConcurrent is zero. 64 is a conservative default for a local, single-process sidecar gateway: generous enough not to bottleneck a single harness session, small enough to bound worst-case upstream fan-out and local memory from a misbehaving or compromised caller.
const DefaultMaxRequestBody int64 = 10 << 20 // 10 MiB
DefaultMaxRequestBody is the request body size bound applied when Config.MaxRequestBody is zero. 10 MiB comfortably covers a large multi-turn agentic conversation with several inlined images while still bounding worst-case per-request memory.
const DefaultShutdownTimeout = 5 * time.Second
DefaultShutdownTimeout is the bounded graceful-shutdown window applied when ServerConfig.ShutdownTimeout is zero. 5 seconds is a conservative, finite default consistent with the design's "bounded server shutdown" security posture: long enough to drain a typical in-flight request, short enough that Close never appears to hang.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AmbiguousCodecMatchError ¶
AmbiguousCodecMatchError reports that more than one configured ServerCodec matched the same concrete request. This indicates a caller misconfiguration (overlapping MatchRequest implementations registered together), not a client error.
func (*AmbiguousCodecMatchError) Error ¶
func (e *AmbiguousCodecMatchError) Error() string
type AuthenticationError ¶
type AuthenticationError struct{}
AuthenticationError reports that a request failed gateway-local authentication: a missing Authorization header, a non-Bearer scheme, an oversized header, or a token that does not match. Every one of those causes is reported identically -- by design, AuthenticationError carries no distinguishing detail -- so a response body or log line built from this error can never leak *why* authentication failed, only that it did.
func (*AuthenticationError) Error ¶
func (e *AuthenticationError) Error() string
type Authenticator ¶
type Authenticator interface {
// Authenticate reports whether req carries a valid credential. It must
// not consume req.Body. A non-nil error is always an
// *AuthenticationError: this interface deliberately has exactly one
// failure mode so a caller never needs to distinguish "why" auth failed.
Authenticate(req *http.Request) error
}
Authenticator authenticates one inbound HTTP request against the gateway's own local, inbound credential. This is a distinct, separate concept from inference/auth's Authenticator: that package authorizes OUTBOUND requests to a provider with provider credentials; this interface authenticates INBOUND requests reaching the gateway's own HTTP surface, and is scoped to this package.
func StaticToken ¶
func StaticToken(token string) Authenticator
StaticToken returns an Authenticator that requires "Authorization: Bearer <token>" with token compared to the supplied value using crypto/subtle.ConstantTimeCompare. token is copied; the caller's string is not retained.
StaticToken does not generate token: that is the responsibility of whatever constructs a Server (a later task) and injects the generated, per-process secret here.
type Binding ¶
Binding reports a bound Server's loopback base URL and bearer token. It is returned by value from Server.Binding -- see that method's doc comment for why Binding() itself returns a bare (string, string, bool) tuple instead of this struct.
type ConcurrencyLimitExceededError ¶
type ConcurrencyLimitExceededError struct{}
ConcurrencyLimitExceededError reports that Config.MaxConcurrent in-flight requests were already admitted when this request arrived. Admission is reject-on-full, never queued.
func (*ConcurrencyLimitExceededError) Error ¶
func (e *ConcurrencyLimitExceededError) Error() string
type Config ¶
type Config struct {
// Resolver maps (ingress format, requested model alias) to a Target.
// Required.
Resolver Resolver
// Codecs is every ingress dialect this Handler serves, keyed by the
// model.APIFormat it decodes/encodes. The key doubles as the Ingress
// value passed to Resolver.Resolve when that codec's MatchRequest wins
// route selection for a request -- codec.ServerCodec itself carries no
// APIFormat accessor, so Config is the one place that association is
// made. At least one entry is required.
Codecs map[model.APIFormat]codec.ServerCodec
// Authenticate authenticates the gateway's own local inbound token.
// Required.
Authenticate Authenticator
// ContextCounter serves the Anthropic-dialect
// POST /v1/messages/count_tokens auxiliary route (matched via
// anthropicapi.MatchCountTokensRequest, which lives outside the
// generic codec.ServerCodec surface -- see handler.go). This field is
// NOT part of the design doc's abbreviated Config sketch
// (Resolver/Codecs/Authenticate/MaxRequestBody/MaxConcurrent); it was
// added here because Task 6 requires the count_tokens route to call a
// configured contextcount.ContextCounter, and the sketch had nowhere to
// configure one. Optional: a nil ContextCounter is a valid
// configuration -- a count_tokens request then fails cleanly with a
// typed *CountTokensUnavailableError (503) instead of panicking on a
// nil interface call.
ContextCounter contextcount.ContextCounter
// MaxRequestBody bounds a request body in bytes, applied before JSON
// decoding. Zero means DefaultMaxRequestBody.
MaxRequestBody int64
// MaxConcurrent bounds global in-flight admission (Target.Client.Invoke/
// Stream and ContextCounter.CountContext calls). Admission is
// reject-on-full (a *ConcurrencyLimitExceededError), never queued. Zero
// means DefaultMaxConcurrent.
MaxConcurrent int
}
Config configures a Handler. Every field except MaxRequestBody, MaxConcurrent, and ContextCounter is required; New validates the required fields and rejects an invalid Config with a *ConfigError.
type ConfigError ¶
ConfigError reports invalid gateway routing configuration supplied to NewMux or Fixed. It covers every "invalid configuration" case this package rejects at construction time:
- a nil Client on any Target (in Routes, FormatDefaults, or Default);
- a Target.Model that fails model.Model.Validate (Err wraps the underlying *model.ValidationError, reachable via errors.As/errors.Is);
- a duplicate logical route: the same non-empty Target.ID bound, across Routes/FormatDefaults/Default, to two Targets whose Model values are not equal (see NewMux's doc comment for the full rule).
Location identifies where in the configuration the problem was found (e.g. "Routes[anthropic/primary]", "FormatDefaults[openai]", "Default"), for diagnosis; it is not part of any equality contract.
func (*ConfigError) Error ¶
func (e *ConfigError) Error() string
func (*ConfigError) Unwrap ¶
func (e *ConfigError) Unwrap() error
Unwrap exposes a wrapped validation cause (e.g. *model.ValidationError) to errors.Is/errors.As. It is nil when Reason alone is self-explanatory.
type CountTokensUnavailableError ¶
type CountTokensUnavailableError struct{}
CountTokensUnavailableError reports that the count_tokens route was invoked but the Handler was constructed with a nil Config.ContextCounter.
func (*CountTokensUnavailableError) Error ¶
func (e *CountTokensUnavailableError) Error() string
type ExactResolver ¶
type ExactResolver interface {
ResolveExact(ctx context.Context, ingress model.APIFormat, requestedModel string) (Target, error)
}
ExactResolver is the optional exact-registration contract consumed by Strict. ResolveExact must return a Target only for the exact (ingress, requestedModel) pair it registered, without applying wildcard or fallback behavior. It must return an *UnknownRouteError for an unregistered pair. Custom resolvers can implement this contract alongside Resolver to opt into strict resolution.
type FixedResolver ¶
type FixedResolver struct {
// contains filtered or unexported fields
}
FixedResolver is a Resolver that always resolves to the same Target, ignoring the requested ingress format and model alias entirely. Construct with Fixed. FixedFor additionally records an exact harness route for use by Strict; direct Resolve remains wildcard-compatible for both constructors.
func Fixed ¶
Fixed returns a Resolver that ignores the requested model alias -- and the ingress format -- entirely and always routes to one fixed target built from client and m. Routing being trivial does not skip validation: client and m are validated exactly as NewMux validates every Target, returning a *ConfigError on a nil client or a Model that fails model.Model.Validate. Because Fixed has no harness registration metadata, Strict(Fixed(...)) fails closed; use FixedFor when strict exact routing is required.
func FixedFor ¶
func FixedFor(client inference.Client, m model.Model, ingress model.APIFormat, alias string) (*FixedResolver, error)
FixedFor constructs a wildcard-compatible FixedResolver with one explicit harness registration for (ingress, alias). Strict uses that registration metadata; it does not infer the harness route from the target Model's provider-facing APIFormat or Name.
func (*FixedResolver) Resolve ¶
func (f *FixedResolver) Resolve(ctx context.Context, ingress model.APIFormat, requestedModel string) (Target, error)
Resolve implements Resolver. It ignores ingress and requestedModel and always succeeds with the target Fixed was constructed with.
func (*FixedResolver) ResolveExact ¶
func (f *FixedResolver) ResolveExact(ctx context.Context, ingress model.APIFormat, requestedModel string) (Target, error)
ResolveExact implements ExactResolver for an explicitly registered FixedFor route. A plain Fixed has no harness route metadata and therefore fails closed under Strict instead of inferring one from the upstream Model.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is an http.Handler implementing the gateway's request-processing pipeline (see package doc). It is built with New and is safe for concurrent use by multiple goroutines, as any http.Handler must be.
func New ¶
New validates config and builds a ready-to-use Handler. It rejects an invalid config with a *ConfigError (the same type NewMux/Fixed use -- there is exactly one "invalid configuration" type across this package):
- a nil Resolver;
- an empty Codecs map;
- a nil Authenticate;
- a negative MaxRequestBody or MaxConcurrent;
- two entries in Codecs whose concrete dynamic type is identical (a cheap, best-effort proxy for an obviously duplicate registration -- see the doc comment below for its limits).
Duplicate/ambiguous *route* detection cannot be fully done here: two structurally different codecs can still both return true from MatchRequest for the same concrete request, and that can only be observed once a live *http.Request exists. That full check happens at request time in Handler.ServeHTTP, which returns a distinct *AmbiguousCodecMatchError (HTTP 500) when it happens. The construction-time check here only catches the narrower, cheaper case of the literal same concrete codec type registered under two different Codecs keys by mistake.
type MethodNotAllowedError ¶
type MethodNotAllowedError struct{ Method string }
MethodNotAllowedError reports an HTTP method this gateway does not accept for any configured route. Every inference route this gateway serves is POST-only, so method validation happens once, generically, before codec selection.
func (*MethodNotAllowedError) Error ¶
func (e *MethodNotAllowedError) Error() string
type Mux ¶
type Mux struct {
Routes map[RouteKey]Target
FormatDefaults map[model.APIFormat]Target
Default *Target
}
Mux is an immutable Resolver keyed by ingress API format and the harness-requested model alias, with a per-format default and a single ultimate default target. The zero value is not usable directly as a Resolver in production; construct with NewMux, which validates and defensively copies its input.
Resolution precedence, applied by Resolve:
- an exact match in Routes for (ingress, requestedModel);
- FormatDefaults[ingress], if present;
- Default, if non-nil;
- otherwise, a *RouteNotFoundError.
There is deliberately no fuzzy, prefix, glob, or provider-name matching.
func NewMux ¶
NewMux validates and defensively copies cfg's Routes, FormatDefaults, and Default into a new, independent *Mux. After NewMux returns, mutating cfg.Routes, cfg.FormatDefaults, cfg.Default (the pointer, or the Target it points to), or any Target.Model reachable from those inputs has no effect on the returned Mux's resolution behavior. Every Target returned by the resulting Mux's Resolve is likewise independent of the Mux's internal state: mutating a returned Target (including its Model.Sampling pointer/slice fields) never corrupts a subsequent Resolve call.
NewMux rejects, returning a *ConfigError:
- any Target (in Routes, FormatDefaults, or Default) with a nil Client;
- any Target whose Model fails model.Model.Validate (the *ConfigError wraps the underlying *model.ValidationError, reachable via errors.As/errors.Is);
- a duplicate logical route. Because Routes and FormatDefaults are Go maps, a literal duplicate key cannot occur -- there is no way to construct one. The real hazard at this API shape is Target.ID, a stable diagnostic identity: if the same non-empty ID is bound, across Routes/FormatDefaults/Default, to two Targets whose Model values are not equal (compared by value, including Sampling's pointer/slice fields), the ID would no longer identify one thing and NewMux rejects the configuration. Reusing one ID for the literal same Model -- e.g. one target reached through two different route keys -- is not a conflict and is allowed. An empty ID asserts no diagnostic identity and never participates in this check: two Targets with an empty ID never collide with each other on that basis alone.
func (*Mux) Resolve ¶
func (m *Mux) Resolve(ctx context.Context, ingress model.APIFormat, requestedModel string) (Target, error)
Resolve implements Resolver, applying Mux's resolution precedence (exact route, then format default, then global default, then *RouteNotFoundError). The returned Target is independent of Mux's internal state: it is safe for the caller to mutate, including its Model.Sampling pointer/slice fields.
type NoMatchingCodecError ¶
NoMatchingCodecError reports that no configured ServerCodec recognized a request's method and path.
func (*NoMatchingCodecError) Error ¶
func (e *NoMatchingCodecError) Error() string
type RequestTooLargeError ¶
type RequestTooLargeError struct{ Limit int64 }
RequestTooLargeError reports a request body exceeding Config.MaxRequestBody.
func (*RequestTooLargeError) Error ¶
func (e *RequestTooLargeError) Error() string
type Resolver ¶
type Resolver interface {
Resolve(ctx context.Context, ingress model.APIFormat, requestedModel string) (Target, error)
}
Resolver maps an ingress API dialect and the harness-requested model alias -- untrusted, caller-supplied input -- to a fully bound Target.
Implementations must not perform unbounded work: Resolve is called on the request path. The built-in implementation is Mux, which performs a bounded set of map lookups with no fuzzy, prefix, glob, or provider-name matching. A resolver that needs that kind of matching implements Resolver directly.
type ResponseEncodeError ¶
type ResponseEncodeError struct{ Err error }
ResponseEncodeError reports that a codec's WriteResponse failed after a successful upstream invocation. By the time this can occur the codec has typically already written HTTP headers (and possibly a partial body), so there is usually nothing further this package can do over the wire; the type exists for classification completeness and for callers that inspect it via errors.As for internal diagnostics.
func (*ResponseEncodeError) Error ¶
func (e *ResponseEncodeError) Error() string
func (*ResponseEncodeError) Unwrap ¶
func (e *ResponseEncodeError) Unwrap() error
type RouteKey ¶
RouteKey identifies one exact route: the ingress API dialect and the harness-requested model alias, matched verbatim (case-sensitive, no normalization). An empty Model is an ordinary, unreserved alias like any other: it matches only a request that supplies an empty requested-model string for that ingress format. It carries no implicit "format default" meaning -- use FormatDefaults for that, and note that both may be configured for the same ingress format at once without conflict (an exact "" route and a FormatDefaults entry answer different requests: literally empty vs. anything else unmatched).
type RouteNotFoundError ¶
RouteNotFoundError reports a Resolve miss: no exact route matched Ingress and Model, no FormatDefaults entry matched Ingress, and no global Default was configured.
func (*RouteNotFoundError) Error ¶
func (e *RouteNotFoundError) Error() string
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server runs one loopback-only HTTP listener wrapping a ServerConfig.Handler behind a per-instance, cryptographically random bearer token (see the package doc above for why token generation lives here and not in Config/Authenticate). It is safe for concurrent use by multiple goroutines, including concurrent Start/Binding/Close calls.
Every Server built by NewServer owns an entirely independent listener, *http.Server, token, and lifecycle state -- constructing two Servers, even from the same ServerConfig.Handler value, never shares state between them (see server_test.go's isolation tests). Server never touches http.DefaultServeMux or any other package-level global.
func NewServer ¶
func NewServer(config ServerConfig) (*Server, error)
NewServer validates config and builds a ready-to-Start Server. It rejects an invalid config with a *ConfigError -- the same type New (config.go) uses -- for a nil Handler or a negative ShutdownTimeout.
func (*Server) Binding ¶
Binding reports this Server's loopback base URL and bearer token, and whether they are currently valid: ready is true only while the Server is in its running state -- i.e. strictly after a successful Start has bound the listener and dispatched the serving goroutine, and strictly before Close begins shutting the Server down. Binding returns ("", "", false) before the first successful Start and again once Close has been called (whether or not that Close has finished draining) -- ready flips to false at the start of shutdown, not only once it completes, so a caller can never observe a stale, about-to-be-invalid binding as ready.
Binding returns a bare (string, string, bool) tuple rather than a *Binding value on purpose: this is the exact shape the ACP-owned ModelProxy contract's Binding method expects (see acp/docs/connectors/inference-gateway.md), and Go has no structural typing for named struct return types -- matching the tuple shape is what lets *Server satisfy that interface without inference/gateway importing anything ACP-related.
func (*Server) Close ¶
Close idempotently shuts this Server down: the first call performs a bounded graceful drain (see below) and every call -- concurrent or later -- returns that same first outcome. Close on a Server that was never started is a valid no-op returning nil; it still marks the Server closed, so a subsequent Start correctly fails with *ServerStateError rather than starting a Server whose owner already gave it up.
The graceful drain combines ctx with ServerConfig.ShutdownTimeout (or DefaultShutdownTimeout, if that was zero) via context.WithTimeout, then calls http.Server.Shutdown with the combined, bounded context -- normal context composition means an earlier deadline already carried by ctx naturally wins without any special-case logic here. If Shutdown does not complete before that bounded deadline (or ctx is otherwise canceled first), the listener and any still-in-flight connections are force-closed via http.Server.Close so Close never hangs past its bound; a deadline-exceeded timeout specifically is reported as a *ShutdownTimeoutError, while any other Shutdown error is returned as-is.
func (*Server) Start ¶
Start binds a loopback (127.0.0.1), ephemeral-port TCP listener and begins serving config.Handler behind Server's own generated-token auth middleware in a background goroutine. It returns once the listener is bound and serving has been dispatched; it does not block for the Server's lifetime.
Start is idempotent in the sense that calling it more than once never binds a second listener or silently succeeds: any call after the first successful Start -- while running, closing, or already closed -- returns a *ServerStateError without attempting to bind. Concurrent Start calls race safely: exactly one wins and the rest observe the post-transition state and fail with *ServerStateError (see server_race_test.go).
type ServerConfig ¶
type ServerConfig struct {
// Handler is served behind Server's own generated-token auth
// middleware. It is never mutated and may be shared by more than one
// Server at once (see the package doc above and Server's doc comment).
Handler http.Handler
// ShutdownTimeout bounds Close's graceful-drain window. Zero means
// DefaultShutdownTimeout. Must not be negative.
ShutdownTimeout time.Duration
}
ServerConfig configures a Server. Handler is required; ShutdownTimeout is optional (zero means DefaultShutdownTimeout).
There is deliberately no address/port field: Server always binds 127.0.0.1 on an ephemeral port (see Start). This is the design's fixed security posture, not an oversight -- a Server is a local trust-boundary primitive, not a configurable network listener.
type ServerStateError ¶
ServerStateError reports an invalid Server lifecycle transition -- in practice, Start called while the Server is already running, closing, or closed. Close is never the source of a ServerStateError: it is idempotent by design (see Close's doc comment) rather than erroring on repeated calls.
func (*ServerStateError) Error ¶
func (e *ServerStateError) Error() string
type ShutdownTimeoutError ¶
ShutdownTimeoutError reports that Close's bounded graceful shutdown did not complete before its effective deadline (the caller's context combined with ServerConfig.ShutdownTimeout -- see Close), so the listener and any still-in-flight connections were force-closed instead. This is one of the design doc's listed local-server error categories ("shutdown timeout"); no earlier task owns it, so it is added here, in the one file that needs it.
func (*ShutdownTimeoutError) Error ¶
func (e *ShutdownTimeoutError) Error() string
type Target ¶
type Target struct {
ID string
Client inference.Client
Model model.Model
// AuthoritativeEffort makes Model.Sampling.Effort authoritative for every
// request served by this target. The handler will stamp it after ingress
// decode and before validation/encoding, replacing ingress effort even when
// the target effort is model.EffortNone. Other override fields remain
// untouched. False preserves current behavior.
AuthoritativeEffort bool
}
Target is a fully bound inference destination.
ID is a stable, secret-free diagnostic identity used for logs and metrics only: it is never sent upstream and is not a substitute for Model's own identity (Model.Key). An empty ID asserts no diagnostic identity and is a valid configuration.
Client is already bound to its own credentials and connection policy -- the gateway never handles secrets directly, so Client must never be nil.
Model is the secret-free model descriptor sent with each request routed to this target.
type UnknownRouteError ¶
UnknownRouteError reports a strict-resolution miss. It includes only the untrusted request key; resolved targets and their provider/endpoint details are intentionally absent.
func (*UnknownRouteError) Error ¶
func (e *UnknownRouteError) Error() string
func (*UnknownRouteError) Is ¶
func (e *UnknownRouteError) Is(target error) bool
Is lets callers match any UnknownRouteError with errors.Is while errors.As still exposes the request key fields.
type UnsupportedContentTypeError ¶
type UnsupportedContentTypeError struct{ ContentType string }
UnsupportedContentTypeError reports a request whose Content-Type is not application/json. Every currently bundled dialect is JSON-bodied, so this gateway enforces the constraint once, generically, before codec selection -- ahead of, and instead of, any per-codec Content-Type check a codec's own DecodeRequest might otherwise perform (which would classify as 400, not the 415 this boundary requires).
func (*UnsupportedContentTypeError) Error ¶
func (e *UnsupportedContentTypeError) Error() string
type UpstreamInvocationError ¶
UpstreamInvocationError reports that Target.Client.Invoke, Target.Client.Stream, or a configured contextcount.ContextCounter.CountContext returned an error, or that Target.Client.Invoke never returned before the request's context deadline. Its Error() message is deliberately generic -- it never includes the wrapped error's own message -- because that message originates from an upstream provider client the gateway does not control and may contain upstream-secret material (e.g. an echoed Authorization header from a misbehaving transport). Callers that need the underlying detail for internal diagnostics use errors.Unwrap/errors.As, never the HTTP response.
func (*UpstreamInvocationError) Error ¶
func (e *UpstreamInvocationError) Error() string
func (*UpstreamInvocationError) Unwrap ¶
func (e *UpstreamInvocationError) Unwrap() error