Documentation
¶
Overview ¶
Package openaicompat is a stdlib-only LEAF adapter that fetches a LIVE model catalog from any endpoint speaking the OpenAI-shaped GET /v1/models protocol — it names the PROTOCOL, not a specific vendor. It carries no behaviour beyond the HTTP GET + JSON→neutral mapping; nothing here knows about the agent, providers, ports, the composition layer, ToolHive, or any other adapter.
Provenance and wire shape ¶
The endpoint is:
GET <baseURL>/models
where baseURL already ends in "/v1" (e.g. "http://127.0.0.1:14000/v1"). An optional bearer token is sent as `Authorization: Bearer <token>` when non-empty (many OpenAI-compatible gateways — including the ToolHive LLM gateway proxy — accept a placeholder credential rather than none at all). The response envelope is {"object":"list","data":[{"id","object","created","owned_by","display_name","context_window"}]} — decode only `id`, `display_name`, and `context_window`; everything else is ignored by encoding/json. Wire shape pinned to stacklok-enterprise-platform#2270 ("New data-plane GET /v1/models intercept... returns an OpenAI-shaped response (`{object:"list", data:[{id, object, created, owned_by, display_name}]}`)"), and extended by stacklok-enterprise-platform#3288; the fixture in testdata/ mirrors that shape.
Layering ¶
LEAF adapter: net/http + encoding/json + stdlib ONLY. It imports NO domain (session/prompt/tool/governance), NO port, NO internal/app, NO providercatalog, and NO other adapter — and, critically, NO ToolHive Go package: this lister only ever speaks the generic OpenAI-shaped protocol. Its Model type is package-own and must never leak into the domain or port — only the composition layer (internal/app) reads it and maps it to its own neutral modelEntry type (so there is no import cycle: the adapter does not import internal/app).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RefuseRedirects ¶
RefuseRedirects is the shared CheckRedirect policy (CWE-918): a loopback gateway endpoint must never be allowed to bounce a request off-loopback via a redirect response. It is exported so the INFERENCE path (the openai adapter's WithHTTPClient, wired for the ToolHive gateway registry entry only — see internal/app/registry.go's newGatewayEntry) and this LISTING path share the exact same policy and cannot drift on wording/behaviour.
Types ¶
type Lister ¶
type Lister struct {
// contains filtered or unexported fields
}
Lister fetches a live OpenAI-shaped model catalog over an INJECTED *http.Client (tests pass a mock transport; production gets a default client with a sane timeout).
func NewLister ¶
NewLister constructs a Lister against baseURL (already ending in "/v1") with an optional bearerToken (sent as `Authorization: Bearer <token>` when non-empty). NOTE the argument order — (baseURL, bearerToken, client) — deliberately differs from the sibling provider/anthropic.NewLister's (key, baseURL, client): don't copy-paste call sites between the two without checking. A nil client yields a default client with defaultTimeout AND RefuseRedirects (CWE-918): baseURL is a loopback address the CALLER already validated (composition never lets an operator point this at a remote host in v1), so a hostile/misconfigured listener on that port answering with a redirect must never be allowed to bounce the request off-loopback — production wiring may pass nil and tests inject a mock transport (which bypasses CheckRedirect entirely, so tests exercising the redirect gate use a real httptest server).
func (*Lister) ListModels ¶
ListModels GETs the live catalog and maps it to []Model. It is read-only and fail-safe to the caller: any transport, status, size, or parse error returns a non-nil error (the composition layer then classifies it and falls back to the embedded catalog / last-known-good). It NEVER panics.
type Model ¶
Model is the adapter's OWN neutral result type. The composition layer maps it to its composition-local modelEntry; it never leaves this package's caller as-is and carries nothing provider-private (no key, no URL).
type StatusError ¶
type StatusError struct {
Code int
}
StatusError is returned when the endpoint answers with a non-2xx status. The composition layer classifies it via errors.As (401/403 ⇒ "unauthorized"; every other status, or a non-StatusError failure, ⇒ "unreachable").
func (*StatusError) Error ¶
func (e *StatusError) Error() string
func (*StatusError) StatusCode ¶
func (e *StatusError) StatusCode() int
StatusCode exposes the response code for provider-neutral classification.