core

package module
v1.9.1 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 15 Imported by: 0

README

llmgw-core

Headless, embeddable LLM routing engine and resilient multi-provider proxy for Go.

Highlights

  • Headless & Zero Database: Pure Go http.Handler. Mounts anywhere (net/http, Chi, Gin, Fiber, Echo) without requiring SQLite, PostgreSQL, or any storage engine.
  • Bi-Directional Wire Translation: Full Anthropic Messages API (/v1/messages) support translated dynamically to OpenAI backends (and SSE streaming) via llm-translate.
  • Resilient Multi-Provider Routing: Route aliases, ordered failover chains, rate-limit 429 detection, and circuit breaking.
  • Pluggable Extension Hooks:
    • Authenticator: Custom token, session, or API key validation.
    • PolicyGate: RBAC, project-level, or model-level permission gates.
    • CredentialResolver: Dynamic or multi-tenant credential overrides (BYOC).
    • UsageHook: Telemetry, cost tracking, and audit logging.
  • Shared Provider Contracts: Typed connection/auth kinds, catalog and completion evidence, health classification, exact target publication policy, and a persistence-agnostic ProviderConnector interface.

Installation

go get github.com/xibodev/llmgw-core
Dependencies
  • llm-provider-auth v1.0.1. Credentials are stored through its tokenstore contract, browser sign-in runs on its browseroauth package, and Google service accounts mint tokens through its gcp package.
  • llm-translate v0.4.0. Conversions report the vendor fields inside messages that the target surface cannot carry, and the translation adapter enforces those losses; see Loss policy. The Anthropic provider has no loss report, so it keeps serving the histories it served before, and it carries the cache_control of Chat text parts into the Messages request, system prompt included.

Quick Start (Embedded in 15 Lines)

package main

import (
    "net/http"
    "github.com/xibodev/llmgw-core"
    "github.com/xibodev/llmgw-core/providers"
)

func main() {
    engine := core.NewEngine(core.Config{
        Routes: map[string]core.RouteConfig{
            "smart": {
                Targets: []core.Target{
                    {Provider: "groq", Model: "llama-3.3-70b-versatile"},
                    {Provider: "openai", Model: "gpt-4o"},
                },
            },
        },
    })

    // Register backends
    engine.RegisterProvider("openai", providers.NewOpenAIProvider("openai", "https://api.openai.com/v1", os.Getenv("OPENAI_API_KEY"), nil))
    engine.RegisterProvider("groq", providers.NewOpenAIProvider("groq", "https://api.groq.com/openai/v1", os.Getenv("GROQ_API_KEY"), nil))

    // Mount on standard HTTP server
    http.Handle("/v1/", engine)
    http.ListenAndServe(":8080", nil)
}

Provider registry

providers.DefaultRegistry() is the reviewed, validated manifest of curated provider integrations. Core owns each entry's wire and safety facts: runtime, protocol, auth methods, connection scope, risk classification, and anonymous automation. A product layers its own curation on top with an overlay, and the result is validated like a manifest:

overlay, err := providers.DecodeOverlay(overlayJSON) // strict: unknown fields fail
registry, err := providers.DefaultRegistry().WithOverlay(overlay, providers.ValidationOptions{
    RuntimeTypes: []string{"openai_compatible", "anthropic"}, // what this product can execute
})
entry, ok := registry.Lookup("gpt") // ids and aliases, case-insensitive

An overlay can:

  • Override presentation, priority, and curation fields of existing entries: label, description, categories, icon, domain, docs URL, priority, availability, common models, onboarding fields, additional aliases, and product-private extensions.
  • Add product-only entries under their own ids.
  • Remove entries the product does not offer.

An override cannot change wire or safety facts. A product that needs different wire behavior adds its own entry instead. Entries() orders by priority, then by manifest order, so an overlay without priorities keeps the manifest order. providers.RegistryProvider and its siblings read the default registry; a product with an overlay looks entries up in its own Registry, which holds the entries the overlay adds.

Besides the major clouds and labs, the manifest lists the common OpenAI-compatible hosts, each at its documented default base URL: DeepSeek, Moonshot AI (Kimi), Zhipu BigModel and Z.ai, Alibaba Cloud Model Studio (Qwen) in its international, China and US regions, Volcengine Ark, SiliconFlow, NVIDIA NIM, Venice, NEAR AI Cloud, MiniMax, LongCat, ModelScope, Novita AI, Avian, Vivgrid and ShengsuanYun, with a _cn entry where a host's China site issues its own keys, and the LiteLLM, vLLM and LM Studio local servers. A host whose provider documents no model list declares no model discovery, so a product supplies its models. Alibaba Cloud's Coding Plan is listed on both wires, for personal connections only, with a red risk notice: Alibaba Cloud allows its keys only in interactive coding tools.

Anonymous providers use one mechanism:

  • Registry.AnonymousProfiles lists the entries curated for anonymous automation.
  • AdmitAnonymousModel applies each provider's reviewed free-model rules to a raw catalog row. It fails closed: unknown providers are never admitted, and OpenCode Zen is admitted only from its verified metadata. OVH AI Endpoints serves its chat models without a key, rate limited, whatever the paid price its catalog lists, so it admits every row that states a context length and a completion limit, except the ids of classifier, moderation, embedding and reranker models.
  • SelectVerificationModel picks the model to probe.
  • DiscoverAnonymousModels combines them.

Whether to enroll an anonymous provider remains the product's policy.

Building providers from entries

providers.Build(entry, instance) makes the provider an instance of an entry serves, by the entry's runtime type, so a product that builds its providers there serves a runtime type core adds by upgrading:

Runtime type Provider
openai_compatible OpenAICompatible, with the entry's id as its RegistryID
bedrock NewBedrock, in the instance's region or the entry's default
anthropic Anthropic
ai_studio, vertex_ai Google for that deployment, in the entry's default location
azure_openai AzureOpenAI
ollama Ollama
elevenlabs, mimo ElevenLabs, MiMo

providers.Instance holds how the instance is reached: its base URL, headers, clients, and the settings of the types that need them, such as a Vertex AI project. An empty base URL is the entry's default, and an entry that requires one but has none is refused without one. Credentials are not part of it: each request carries its own, as the Runtime resolves it. SupportedRuntimeTypes lists the types above, for a product's ValidationOptions.

Build makes no provider for an entry that is not available, for OpenAI Codex and OpenCode Zen, which need more than the OpenAI-compatible wire, for the GitHub Copilot, Google Antigravity and Edge TTS runtime types, which an extension daemon serves, or for a runtime type it does not know, such as a product's own. It refuses each as a configuration error, which permits failover, whose cause is a *providers.UnsupportedEntryError naming the entry, its runtime type and the reason.

runtime.RegistryProviders(registry, describe) is a ProviderFactory built on it: describe returns an instance's RegistryInstance from the product's settings, the entry it names is resolved by id or alias, and Translate puts a translation.Adapter in front, so every instance serves Chat Completions, Messages and Responses:

Providers: coreruntime.RegistryProviders(registry, func(s Settings, instance string) (coreruntime.RegistryInstance, error) {
    p := s.Providers[instance]
    return coreruntime.RegistryInstance{
        Entry:     p.RegistryID, // such as "openai", "ai_studio" or an overlay's own
        Instance:  providers.Instance{BaseURL: p.BaseURL, Headers: p.Headers, Project: p.Project},
        Translate: true,
    }, nil
}),

Provider contract

The wire-level contract shared by products and transports:

  • Caller identifies who a request acts for. LocalCaller() is the caller of a single-user application.
  • Provider serves a Request{Surface, Model, Body, ContentType, Credential, Header} for the surfaces it lists in NativeSurfaces.
    • It returns a Response{Body, ContentType, Losses}, or a StreamIter of complete SSE frames.
    • Bodies are bytes: JSON for the chat surfaces, multipart form data for speech-to-text, and binary for speech, images and video.
    • Header optionally carries the headers the client sent, such as anthropic-beta. A provider reads only the headers it explicitly allows, after checking their values, and never a credential, so a client's Authorization or x-api-key never reaches the upstream. Translation and a replay after a credential refresh keep it. Nil means none; unlike a Credential, it prints as is.
    • A request for any other surface fails with a *SurfaceError, and a translation adapter serves it instead.
  • Errors classify themselves. ClassifyError(err).Disposition() says whether to retry, fail over, or stop. ProviderError carries a message that is safe to show and a cause that is never rendered.
  • LossPolicy decides which translation and adaptation losses a product accepts, by field-path glob, class and severity. Allowed losses are still reported, in Response.Losses and through a stream's LossReporter.

Products supply three stores, each with an in-memory reference implementation:

  • CredentialStore is the llm-provider-auth token store plus Resolve(ctx, caller, instance), which returns the key of the credential that serves a caller. Which credential applies is product policy. The Runtime keeps it fresh through tokenstore.Coordinator. APIKeyRecord stores a static key that never refreshes. Reference: NewMemoryCredentialStore.
    • A provider receives CredentialFromRecord(key, record): the access token as APIKey or Token, plus the record's AccountID, TokenType and a copy of its Metadata, such as a project ID.
    • A Credential prints and logs whether each secret is set, never its value. Metadata may hold secrets, so it is never serialized.
  • CatalogStore keeps discovered catalogs, with a new revision on every save. Save must be atomic for readers in any process. Run catalogtest.Run against every implementation. Reference: NewMemoryCatalogStore.
    • A ConditionalCatalogStore adds SaveIf(ctx, key, evidence, expected) and Delete, so a discovery fences out an invalidation in any process. Delete leaves the key a new revision, which Load returns with ErrCatalogNotFound. catalogtest.Run checks both when present.
  • EvidenceSink receives AccountEvidence: which credential served which operation, and the classified outcome. Reference: MemoryEvidenceSink.
M5 foundations

The API the remaining transports build on. All of it is additive: no existing vertical sends, returns or classifies anything differently.

// package core
type ModelInfo struct {
    // ... the fields above, unchanged ...
    DisplayName        string         `json:"display_name,omitempty"`
    Vendor             string         `json:"vendor,omitempty"`
    Free               bool           `json:"free,omitempty"`
    LegacyCapabilities map[string]any `json:"legacy_capabilities,omitempty"`
}
func AdaptModelCapabilities(legacy map[string]any, surfaces []string, discoveredAt, verifiedAt time.Time) *ModelCapabilities
func InferCapabilities(model ModelInfo, discoveredAt, verifiedAt time.Time) *ModelCapabilities

type WirePreserver interface{ PreservesWire(model string, surface ModelSurface) bool }
func PreservesWire(provider Provider, model string, surface ModelSurface) bool

type TokenCounter interface {
    CountTokens(ctx context.Context, request TokenCountRequest) (TokenCount, error)
}
type TokenCountRequest struct { Request; Header http.Header }
type TokenCount struct{ InputTokens int64 }
func CountTokens(ctx context.Context, provider Provider, request TokenCountRequest) (TokenCount, error)
var ErrTokenCountUnsupported error

const TokenTypeGCPServiceAccount = "gcp_service_account"
const TokenTypeAnthropicSetupToken = "anthropic_setup_token"
  • Rows. A gateway catalog row maps field for field: Label is DisplayName, Capabilities is LegacyCapabilities, TypedCapabilities is Capabilities, and SupportedSurfaces is SupportedAPIs.
  • Capabilities. AdaptModelCapabilities is the gateway's inference, ported verbatim: inferred, medium confidence, and no expiry. InferCapabilities applies it to a row and ignores the row's own Capabilities.
  • Preservation. PreservesWire is true when every layer serves the surface natively and the nearest WirePreserver says so. Serving Chat natively by converting it internally is not preserving it, and a response transport label reads only this declaration. A decorator exposes what it wraps through Unwrap() Provider, as translation.Adapter does.
  • Token counts. CountTokens finds the nearest TokenCounter the same way. ErrTokenCountUnsupported tells a product to estimate instead.
  • Credential kinds. A credential whose TokenType names one carries the material in Token and its extras, such as CredentialMetadataProjectID, in Metadata. The material is no bearer token, so bind it only to a provider that serves its kind.
  • Wire kit. Package providers shares the gateway's transport helpers with the verticals it holds:
    • readInvocationResponseBody reads an answer within 64 MiB, and decodeCatalogResponse a catalog within 8 MiB, checking every row.
    • sseRecordReader reads SSE records within 4 MiB with their bytes as sent, sseFrameStream relays them, and readBoundedLine bounds NDJSON.
    • httpStatusFailure reports a refused answer classified by the gateway's status set, with Retry-After. Its message never quotes the upstream; the cause holds the redacted diagnostic.
    • extractError, retryAfterDelay, transportFailure, streamFailure and catalogFailure complete it. A catalog failure's *CatalogError has a CatalogCode* code: the gateway's, without its catalog_ prefix.

Runtime

runtime.Runtime is the one value that owns a product's provider state: the registry, the provider cache, the refresh coordinators, the catalog cache and health. The core packages keep no package state and read no environment, and an architecture test enforces both.

rt, err := runtime.New(runtime.Options[Settings]{
    Settings:    settingsSource, // Snapshot() (Settings, generation)
    Providers:   buildProvider,  // func(Settings, instance) (core.Provider, error), or runtime.RegistryProviders
    Credentials: credentialStore,
    Refresh:     oauthRefresh,   // per instance; nil means credentials never refresh
    Catalogs:    catalogStore,
    Evidence:    evidenceSink,
    Registry:    registry,
})
response, err := rt.Invoke(ctx, caller, "openai", request)

The Runtime works with the product's own settings type:

  • It rebuilds providers and coordinators when the settings generation changes.
  • It resolves each caller's credential and refreshes an OAuth credential the upstream rejects with 401, once, before replaying. Static API keys are never replayed.
  • It serves stored catalogs while they are fresh, sharing them across processes through the CatalogStore.
  • Only upstream outcomes (an HTTP status or a transport failure) change an instance's health.

translation.Adapter{Provider, Policy} serves surfaces a provider lacks by translating through llm-translate:

  • Messages over Chat Completions, streaming included.
  • Chat Completions over Responses.
  • Responses over Chat Completions.
  • Chat Completions over Messages, streaming included.

When a provider serves more than one surface a route could target, the first route in that order is used, so Chat Completions goes over Responses before Messages. translation.Serves(surface, native...) reports what an Adapter serves in front of a provider with those native surfaces, and translation.ServesStream whether it streams it; they agree with Adapter.Surfaces by construction. translation.ServesChat(native...) answers the question a chat product asks of a model's surfaces. A catalog row names its surfaces as paths in SupportedAPIs, which core.ParseSurfacePath reads:

surfaces := make([]core.ModelSurface, 0, len(model.SupportedAPIs))
for _, path := range model.SupportedAPIs {
    surfaces = append(surfaces, core.ParseSurfacePath(path))
}
if translation.ServesChat(surfaces...) {
    // offer the model for chat, through translation.Adapter
}

Chat Completions over Messages converts:

  • Messages. System and developer messages become the system prompt; tool calls and tool results become tool_use and tool_result blocks.
  • Options. max_completion_tokens or max_tokens becomes max_tokens, 8192 when neither is set, since Messages requires one. temperature is clamped to Messages' range of 0 to 1, stop becomes stop_sequences, user becomes metadata.user_id, and top_p and top_k pass through.
  • Tools. Function tools, and tool_choice: auto, required as any, none, or one named function. parallel_tool_calls: false becomes disable_parallel_tool_use.
  • Losses. A field that changes what the answer is or how it is shaped is a material loss: n above 1, a response_format other than text, logprobs, logit_bias, the penalties, audio output, prediction, the legacy functions, and web_search_options. Any other dropped field, such as seed or store, is advisory. A value that asks for nothing, such as n: 1 or a zero penalty, is no loss.
  • Reasoning. reasoning_effort is dropped with an advisory loss: a Messages model that has no effort setting refuses one, and a thinking turn has to be replayed with its signed thinking block, which a Chat history cannot carry. Thinking blocks in an answer are dropped with an advisory loss, as in the other directions.
  • Answers. Text, tool calls and the finish reason. Usage counts the input read from and written to the cache in prompt_tokens, as Chat does, and the part read from the cache in cached_tokens.
  • Streams. A stream sends its usage in a last chunk when the request sets stream_options.include_usage, and reports the usage loss otherwise. A Messages error event fails the stream with the error its type documents, so an overload or a rate limit is transient; so does an upstream that ends before message_stop. A failed stream sends no finish reason and no [DONE], so it never looks complete.

translation.ChatTranscriptionAdapter is the explicit audio counterpart. It serves audio_transcriptions by sending multipart audio as an input_audio Chat Completions part and normalizes the answer to {"text": ...}. Its Enabled(model) callback is mandatory: products opt in a provider or exact model rows, and the adapter never guesses capability from a model id. An opted-in model transcribes through Chat even when the provider lists audio_transcriptions natively, as providers.OpenAICompatible does for every model; the adapter reports the surface as translated for it.

Catalogs

catalog.Service keeps catalogs as the gateway does, as instance state over a CatalogStore. A Runtime builds one from Catalogs and CatalogTTL that behaves as ListModels always has; pass Options.CatalogService to choose:

catalogs := catalog.New(catalog.Options{
    Store: store, TTL: time.Hour, KeepStale: true, SchemaVersion: 6,
    InstanceTTL: func(instance string) time.Duration { return 0 }, // 0: use TTL
})
  • Reads. Read serves a catalog younger than the TTL, or discovers it and returns what is stored afterwards. Cached and CachedLookup never discover. With KeepStale, a failed discovery keeps the stored rows, marked stale.
  • Fences. Invalidate(ctx, key, catalog.Hard) forgets a catalog and fences every discovery in flight, so a revoked credential's rows never come back. catalog.Soft forgets without fencing the operation that caused it.
  • Diagnostics report synced, not_synced, empty or error, stale and from_cache, and never quote an error.
  • Schema. CatalogEvidence.SchemaVersion stamps what a service stores; rows stamped otherwise are neither served nor kept.

The Runtime adds ReadCatalog (diagnostics), CachedCatalog, CachedModel for catalog hooks such as ZenConfig.Models, and CatalogService.

Transport planning

The gateway's transport-mode decisions, as pure helpers over PlanTransport:

  • TransportEvidence{Row, RefreshedAt}.Capabilities() gives a row the catalog's freshness, an hour by default, without upgrading its confidence.
  • TransportInterfaces offers the chat surfaces a row lists, native where PreservesWireFor says the provider preserves the wire for the credential. A CredentialWirePreserver answers per credential, so a provider may preserve a surface for one credential and nothing for another.
  • ParseSurfacePath, ParseTransportRequirement, ListingSurfaces and ResponseTransportMode port the path and header parsing, the model list's native and emulated surfaces, and the response label.
  • SurfacePath gives the path that names a surface in SupportedAPIs, such as /v1/audio/speech, which core reads back as that surface.

Runtime.Transparent sends the body as is, once, only when a transparent plan over the caller's stored catalog confirms it, and refuses a stream after that. A refusal is a *core.TransportRejectError; each product words it. Runtime.TransportMode labels a response.

Loss policy

Request losses are checked against the LossPolicy before the provider is called, and the response losses of Invoke once it answers. A stream's response losses are reported but not enforced, because its frames are already delivered. All losses, including the provider's own and those the policy allows, are returned in Response.Losses or through the stream's LossReporter.

Gemini returns a thought signature with each tool call and needs it back on the next turn. No other surface can carry one, so llm-translate reports each dropped signature as a material loss at its path, such as messages.1.tool_calls.0.extra_content.google.thought_signature. The default policy rejects unmatched material losses, so the adapter refuses:

  • a Chat request whose history carries signatures, served over Responses;
  • a Chat response that carries them, converted for a Messages or Responses client, after the provider has answered.

The refusal is a *LossPolicyError, which permits failover to a target that serves the surface natively. Under the same policy, a Messages stream over Chat Completions still serves signed tool calls and only reports the loss.

A product that serves Gemini through the adapter should consider allowing the loss, which is still reported:

policy := core.LossPolicy{Rules: []core.LossRule{
    {Path: "**.thought_signature", Class: translate.LossDropped, Action: core.LossAllow},
}}

Allowing it loses nothing on a request to a model other than Gemini: that model cannot use the signature, and the caller's history keeps it. A client that receives a translated Gemini response, though, never sees the signature, so its next turn reaches Gemini without it; providers.Google sends such a turn with the skip signature Google documents, so Gemini 3 does not refuse it.

Dropped reasoning_details, reasoning_content and cache_control, and dropped reasoning items or thinking blocks, reported at <path>.reasoning, are advisory: the default policy allows and reports them, and a product that depends on them rejects them by path.

Extra headers

Every provider config takes Headers, which the provider sends with every request it makes, its catalog's included, such as an attribution header or one a proxy in front of the upstream reads.

  • A header the provider sets itself, such as Content-Type, replaces a configured one of the same name. On an OpenAI-compatible provider a credential's own headers replace configured ones too.
  • A request authenticated with the operation's credential sends neither a configured Authorization nor a configured copy of the provider's own key header (x-api-key, x-goog-api-key, api-key, xi-api-key, Api-Key), so a configured header never stands in for the credential or competes with it. A request sent without a credential carries them as configured, for an upstream behind a proxy that authenticates by header.
  • A name that is not an HTTP token, a value with a control character, and two names that differ only in case but carry different values are refused when the provider is built, since Go's transport would refuse to send them. The error never quotes a value.

Google's service-account token exchange does not carry them.

OpenAI-compatible and Bedrock

providers.OpenAICompatible is the gateway's generic OpenAI transport: its openai_compatible, openai and litellm instances, the anonymous catalogs the registry curates, and Amazon Bedrock. Requests are shaped byte for byte as the gateway shapes them.

provider, err := providers.NewOpenAICompatible(providers.OpenAICompatibleConfig{
    BaseURL:    s.BaseURL,     // required, such as https://api.openai.com/v1
    RegistryID: s.RegistryID,  // "openai", "llm7", "pollinations", an alias, ...
    Registry:   registry,      // optional: resolves RegistryID, an overlay's entries included
    Models:     catalog.Lookup, // the product's catalog row for a model
})
// Or: providers.NewBedrock(s.Region, s.BaseURL, providers.OpenAICompatibleConfig{Models: catalog.Lookup})
if err != nil {
    return nil, err
}
return translation.Adapter{Provider: provider}, nil // Messages over Chat
  • Credential. Optional. The API key, or else the token, is the bearer, without any Bearer prefix; no key, free or none sends no Authorization. The credential's headers are sent too. A service-account or setup-token credential is refused before anything is sent.
  • Surfaces. Chat Completions for every model, and Responses for a model whose row lists it or of the openai entry. Both are declared through core.WirePreserver, so a product labels them native. The Adapter serves Responses over Chat for the other models, but does not stream it.
  • Audio. audio_transcriptions and audio_speech are native for every model, and declared through core.WirePreserver too. A transcription's multipart upload is forwarded byte for byte, with its own Content-Type and boundary, to /audio/transcriptions, so the upload's model field names the model. Speech is the request's JSON with the request's model, sent to /audio/speech. The answer comes back as sent with the upstream's content type: JSON, text, SRT or WebVTT as the transcription's response_format asks, and audio for speech, within the 64 MiB bound on any answer. Credentials, headers and refusals are the Chat surfaces', and keyless access works as it does for Chat. Neither surface streams: a stream is refused with an unsupported *core.ProviderError that permits failover.
  • OVHcloud NVR voices. An ovh_ai_endpoints instance serves its nvr-tts-<language>-<region> models on audio_speech alone, and its catalog lists them so. Speech goes to the model's own host beside oai.endpoints.kepler.ai.cloud.ovh.net, as Riva's synthesis request: the input, the language the model's name holds, 16-bit linear PCM at 16 kHz, and the voice. A voice of the model's language, such as English-US.Male-1, is sent as named; any other, such as an OpenAI voice name, or none, is the language's base voice, the swap reported as an advisory loss. The answer is WAV audio, returned as audio/wav; another response_format is refused, and speed and instructions are dropped as advisory losses. A keyless instance sends no key, within OVHcloud's anonymous rate limit.
  • Requests. Chat carries the fields the gateway's transport forwards, and drops the others as advisory losses unless ForwardAllFields is set. Responses is forwarded as sent, with the request's model and stream flag. ForceAPISupport, or a request's force_api_support, serves Chat over Responses for a model whose row lists only Responses, retrying a 400 that names temperature or top_p once without it, and sends max_tokens to a reasoning model as max_completion_tokens.
  • Catalog. /models rows keep their vendor, display name and endpoints; a capabilities block is distilled into LegacyCapabilities, and Capabilities is what core.InferCapabilities derives. Without a key, kilo_code, llm7, ovh_ai_endpoints and pollinations list only the models AdmitAnonymousModel admits, marked Free. Pollinations takes Chat at /v1/chat/completions and lists a bare JSON array.
  • Bedrock. NewBedrock(region, baseURL, config) sends a Bedrock API key as a bearer to the base URL, or to the region's bedrock-runtime endpoint, us-east-1 by default. Nothing is signed, and a malformed region is refused. Bedrock has no audio API, so it serves neither audio surface.
  • Errors are *core.ProviderError, classified by the gateway's status set with Retry-After. A native Responses 404 or 405 is a *core.SurfaceError, and a catalog failure's *providers.CatalogError has a CatalogCode* code.

ElevenLabs and Xiaomi MiMo audio

providers.ElevenLabs serves audio_transcriptions through ElevenLabs Scribe and audio_speech through synchronous text-to-speech. It maps the OpenAI multipart upload to /v1/speech-to-text, uses xi-api-key, takes the TTS voice from the request, and returns the upstream audio content type. Its catalog rows declare exactly one audio surface each.

providers.MiMo serves audio_speech for the explicitly configured MiMo TTS models. It maps an OpenAI speech request to MiMo's Chat Completions audio contract, authenticates with Api-Key, decodes the returned base64 audio, and returns it as binary audio. Both providers accept model lists in their config; nil selects the reviewed defaults and an empty list disables the surface.

Anthropic

providers.Anthropic is the Anthropic Messages vertical: Messages passed through as the gateway passes them, token counts, and the catalog and what its rows mean.

provider, err := providers.NewAnthropic(providers.AnthropicConfig{
    BaseURL:  s.AnthropicBaseURL, // optional; https://api.anthropic.com
    Preamble: gatewayPreamble,    // optional: func(ctx) string
})
  • Credential. An API key is sent as x-api-key; a credential of any other kind is refused before anything is sent. Without a credential nothing authenticates, for an Anthropic-compatible endpoint that needs none.
  • Surfaces. Messages only, and preserved: the body passes through with the request's model and the operation's stream flag, the answer comes back as Anthropic sent it, and core.PreservesWire reports it, so a product labels it native. A stream is relayed byte for byte and fails if it ends before message_stop. translation.Adapter serves Chat Completions over it, streaming included; see Runtime.
  • Preamble. The hook's text goes before the system prompt, as the gateway puts its preamble there. A body's _llmgw_preamble, which llm-translate also reads, supplies it when the hook returns none, and never reaches Anthropic.
  • Client headers. Messages requests, streamed or not, forward the client's anthropic-version from Request.Header in place of the default, and add each of its anthropic-beta values, so a client can turn on beta features such as extended context or interleaved thinking. A value that is blank or holds a control character is dropped, and no other header is read.
  • Token counts. CountTokens implements core.TokenCounter over /v1/messages/count_tokens, forwarding the client headers as Messages requests do, from TokenCountRequest.Header, or from the request's own when that is nil.
  • Catalog. ListModels reads the one page of /v1/models the gateway reads. Every row serves Messages, with the registry's static capabilities, fresh for an hour.
  • Errors are *core.ProviderError; a refusal keeps its status and Retry-After.

Azure OpenAI

providers.AzureOpenAI serves one Azure OpenAI resource as the gateway serves it: Chat Completions, and the resource's own deployments as its catalog.

provider, err := providers.NewAzureOpenAI(providers.AzureOpenAIConfig{
    BaseURL: s.AzureEndpoint, // the portal's endpoint; /openai/v1 is added
})
if err != nil {
    return nil, err // not a resource endpoint
}
return translation.Adapter{Provider: provider}, nil // Messages and Responses over Chat
  • Credential. An API key, sent as api-key and never as a bearer. A request without one is refused before anything is sent.
  • Base URL. The portal's origin, /openai or /openai/v1. Anything else, a proxy mount included, is refused when the provider is built.
  • Surfaces. Chat Completions, sent to /openai/v1/chat/completions without an api-version. The body is rebuilt from the fields Azure's v1 Chat Completions operation defines, plus service_tier, which Azure documents for Chat; every other field, thinking and web_search_options among them, is reported as an advisory loss. AzureChatField names the fields that are sent, for a product that weighs a target before sending. It is not the client's body, so Azure OpenAI declares no wire preservation and its answers are labelled translated, as the gateway labels them. The answer comes back as Azure sent it.
  • Catalog. The resource's succeeded, chat-callable deployments, listed at the pinned api-version 2023-03-15-preview and never from /models. A page is continued by cursor only on the resource's origin, and no redirect off the origin is followed, since it would carry the api-key.
  • Errors are *core.ProviderError. A catalog failure's code is the gateway's: authentication_failed for a rejected key and not_discoverable for any other.

Google

providers.Google is the Gemini vertical for both of Google's deployments, AI Studio and Vertex AI, over their native generateContent and streamGenerateContent API: Chat, embeddings, image generation, the catalog and what its rows mean, and the credential each deployment takes. Text requests are shaped byte for byte as the gateway shapes them.

Providers: func(s Settings, instance string) (core.Provider, error) {
    google, err := providers.NewGoogle(providers.GoogleConfig{
        Deployment:  providers.GoogleVertexAI, // or providers.GoogleAIStudio
        Project:     s.VertexProject,          // optional: a key names its own
        Location:    s.VertexLocation,         // optional: "global"
        RequestType: s.VertexRequestType,      // optional: "paygo" or "dedicated"
    })
    if err != nil {
        return nil, err
    }
    return translation.Adapter{Provider: google}, nil // Messages and Responses over Chat
},
  • Credential. An API key travels in x-goog-api-key, never in a URL, and a token is the bearer. On Vertex AI a credential of kind core.TokenTypeGCPServiceAccount holds a service-account key in Token. Each instance exchanges it for cloud-platform tokens and caches them until shortly before they expire, sharing them with no other instance. The key's project fills an unset Project and must match a set one; a project the credential's metadata names under core.CredentialMetadataProjectID bills calls to it instead. Vertex AI without a credential is a configuration error and sends nothing, while AI Studio sends what it has, as the gateway does. Other credential kinds are refused.
  • Surfaces. Chat Completions and embeddings, for every model. Chat is converted to Gemini contents with the gateway's mapping: each message's string content, system and developer text as systemInstruction, max_tokens and temperature. Every other field is dropped and reported as a loss, a material one for content that is not text and for fields that change the answer's structure. Embeddings embed each input with one call, and refuse dimensions and encodings other than float, as the gateway does. GenerateImages implements core.ImageGenerator. Video generation is not served yet.
  • Tools. Function tools become Gemini function declarations, their parameters sent whole as parametersJsonSchema; strict is dropped as an advisory loss, since Google does not enforce the schema. tool_choice becomes the function calling config: none is NONE, required is ANY, and a named function is ANY limited to it. An assistant's tool calls become functionCall parts, their arguments a JSON object, and the tool results that follow them functionResponse parts in one user turn, naming the function of the call each answers: a result that holds a JSON object as that object, and any other as {"result": text}. A result that answers no earlier call is refused as an invalid request. Gemini's calls come back as tool_calls, each with Gemini's ID or one unique to the answer, and the finish reason is tool_calls.
  • Thought signatures. A call Gemini makes carries its thought signature at extra_content.google.thought_signature, where Google's own OpenAI-compatible API puts it, and a call returned on the next turn is sent with the signature it carries there, on its function, or on the call itself. Gemini 3 refuses a turn's function calls without one, so a turn whose calls carry none is sent with the skip signature Google documents for such history, which Google warns costs some answer quality.
  • Finish reasons follow Gemini's: MAX_TOKENS is length, a safety or policy stop is content_filter, and every other reason is stop.
  • Streaming. A Chat stream is sent to streamGenerateContent with alt=sse, mapped as Invoke maps it. Each Gemini event becomes one Chat chunk, a whole tool call included; the stream then ends with the finish reason, a usage chunk when stream_options.include_usage asks for one, and [DONE]. A stream that ends without a finish reason fails, an error event is classified by its status, and a stream with nothing to show fails before any frame, as Invoke fails on an empty answer. translation.Adapter streams Messages over it. Embeddings do not stream.
  • Catalog. AI Studio's catalog is paged, and a model's supportedGenerationMethods say what it serves. Vertex AI lists the managed models of the instance's location on its v1beta1 publisher route, which only an OAuth principal may read: with an API key alone the catalog fails with CatalogCodeNotDiscoverable, and without a project with CatalogCodeConfigurationIncomplete. A walk is bounded at 100 pages and 20,000 rows. Rows carry the gateway's legacy capabilities and the typed capabilities it infers from them.
  • Errors are *core.ProviderError, classified by the gateway's status set. A refusal names the cause Google's error envelope gives, such as exhausted billing or a model the project cannot use, and its cause's diagnostic is the gateway's own message, redacted. A refusal keeps the wait it asks for: a Retry-After header, or else the retryDelay of the envelope's google.rpc.RetryInfo detail, which the Gemini API sends with its quota refusals. An empty answer names its reason, such as reasoning that spent all of max_tokens.

Ollama

providers.Ollama is the Ollama vertical: a keyless daemon over its native /api/chat, and its /api/tags catalog.

Providers: func(s Settings, instance string) (core.Provider, error) {
    ollama, err := providers.NewOllama(providers.OllamaConfig{
        BaseURL: s.OllamaBaseURL, // the daemon root; empty is http://127.0.0.1:11434
    })
    if err != nil {
        return nil, err // a base OllamaBaseURLIssue objects to, such as .../v1
    }
    return translation.Adapter{Provider: ollama}, nil // Messages and Responses over Chat
},
  • Base URL. OllamaBaseURLIssue is the gateway's diagnostic: the base is the daemon's native root, not its OpenAI-compatible /v1 URL. NewOllama refuses a base it objects to.
  • Requests are converted to /api/chat byte for byte as the gateway converts them: the messages, temperature, top_p, tools, and max_tokens as num_predict. Other fields are dropped and reported as losses. The gateway's quirks stay: a developer message is sent as a system one, an empty message is dropped, and content that is not a string is sent as Go prints it, so an image never reaches the model as an image. Such content is reported as a material loss at its path, as is each part of it that is not text, such as an image.
  • Answers. A reply becomes a Chat completion with usage from Ollama's evaluation counts, and a stream becomes Chat chunks ending in [DONE], each as the gateway renders it. A stream reports its losses through core.LossReporter.
  • Catalog. ListModels names each row by its name, or else its model, with its details.family as the vendor. Ollama reports no surfaces or capabilities.
  • Errors are *core.ProviderError, classified by the gateway's status set with Retry-After. A stream that breaks off may be repeated, as in the gateway.

Extensions

Providers core does not carry are served by an extension daemon: a separate process a product runs beside it. The extension package is the client of the daemon's HTTP protocol. extension.Client lists what the daemon serves, runs operations, refreshes credentials and signs in, and extension.Provider adapts one daemon provider to core.Provider, so a Runtime routes to it like any other provider.

client, err := extension.NewClient(extension.Config{
    BaseURL: "http://127.0.0.1:18888",
    Secret:  secret, // the daemon's shared secret
})
info, err := client.Info(ctx)
for _, served := range info.Providers {
    provider := extension.NewProvider(client, served)
    // served.CredentialKind() is oauth, token or none.
}
coordinator, err := tokenstore.NewCoordinator(store, client.RefreshFunc("example"))
  • Credentials stay with the product. The daemon keeps none: every operation carries its credential in headers, a refresh returns the renewed record for the product to store, and the product serializes refreshes under its own lease, as tokenstore.Coordinator does.
  • Sign-in. Client.OAuthDriver is a device and code driver, so oauthflow.Service runs a daemon provider's sign-in like any other.
  • Catalog. A daemon may report what each model serves in its SupportedAPIs and capabilities, as a core catalog row does. A model it says nothing about serves every surface of its provider: Provider.ListModels lists it with their paths, as core.SurfacePath gives them, so core infers those surfaces for it.
  • Errors are *core.ProviderError, classified from the daemon's status as core classifies any upstream failure. A refused refresh is an *extension.RefreshError, terminal when the provider rejected the grant.
  • Timeouts. Without a context deadline, an invoke is bounded by DefaultTimeout and info, models, refresh and sign-in steps by DefaultControlTimeout, which stays inside tokenstore.Coordinator's default lease wait. A stream has no deadline but the caller's. An operation made with extension.WithTimeout instead carries its provider's timeout to the daemon, which applies it to its request to the provider: the answer may take that long to begin, and then pause that long between two reads. The client then bounds the operation by nothing but its context, so an answer that keeps arriving is never cut.
  • Security. Anyone who can reach a daemon can use its keyless providers. Bind it to the loopback interface and start it with a secret.

The package documentation specifies the protocol for anyone writing a daemon.

Execution

execution holds the primitives failover is built from. Endpoints, policy gates, translation-loss routing and affinity stay product code: a product orders its candidates and hands the list over.

  • HealthTracker keeps each key's circuit and cooldown behind a mutex, with an injectable clock. Consecutive circuit failures open the circuit at FailureThreshold, for OpenDuration or what a Backoff returns. After that it is half-open: the next failure reopens it and a success closes it. A Retry-After keeps the key unavailable until then. Policies can differ per key. Observe, the default reading of an error, uses core.ClassifyError and names each failure's class, so a Backoff can hold a key that failed for billing back longer than one that was rate limited; a product can replace it.
  • Execute tries candidates in order. It skips unavailable ones, stops at a terminal disposition, and moves on after a failover or retryable one, repeating the candidate first only when Retry asks. It records outcomes and returns the result with a trace of every candidate reached. A canceled context stops it and records nothing.
    • CredentialFailover moves on past a rejected credential, a failure of class auth or forbidden, for candidates that hold different credentials, without holding the rejection against the instance.
    • An Admission, such as a client-side limit on requests a minute per instance, is asked about each candidate Health admits, before its try; one it refuses is skipped as an unavailable one is, its trace entry marked Unadmitted.
  • ExecuteStream commits to a candidate once a frame carries output, content or reasoning, as the product's predicate decides. Earlier frames are held back, so a failure before output moves to the next candidate unseen. After output nothing fails over: a failure reaches the caller as the stream's *AfterOutputError.

Providers name the refusals a product routes on with their own classes, so nothing parses error text: billing (HTTP 402, OpenAI's insufficient_quota, an Anthropic credit balance too low, Google's depleted prepayment), which fails over and counts against the instance, and context_overflow, tools_unsupported and media_unsupported (a request longer than the context window, or tools or images the model does not take), which fail over and count against nothing, so a product can also compact the history or retry without the tools or media.

tracker := execution.NewHealthTracker(execution.HealthOptions{})
executor := execution.Executor[core.Target]{
    Health: tracker,
    Key:    func(target core.Target) string { return target.Provider },
}
result, err := execution.ExecuteStream(ctx, executor, targets, open, carriesOutput)
if err != nil {
    return err // result.Attempts still holds the trace
}
defer result.Value.Close()
Per-instance resilience

execution.Resilient(provider, key, policy) guards one instance, as the gateway's resilience wrapper did:

  • While policy.Health reports the key unavailable, every operation fails with a *CircuitOpenError, a 503 that permits failover, and nothing is sent.
  • A try whose failure is retryable repeats up to Retry.Attempts, waiting ExponentialBackoff or a longer Retry-After. A stream repeats only its opening. RepeatableRequest never repeats a stateful Responses request, or one that may pay for a second result, such as an image.
  • The outcome of each operation's last try moves the circuit. ListModels passes through, and the wrapper unwraps, so wire preservation and token counts read the provider's declarations.
  • TransientClassification is the gateway's reading, which a policy opts into: only 408, 429, 500, 502, 503 and 504 repeat, and every repeat counts against the circuit. Core's default reading is unchanged.

runtime.Options.Policy returns an instance's runtime.Policy, and the Runtime wraps the provider it binds. Its circuits live in one tracker it owns, keyed by instance, so they outlive settings changes:

Policy: func(s Settings, instance string) runtime.Policy {
    p := s.PolicyFor(instance) // the gateway's retry and circuit settings
    return runtime.Policy{
        Retry: execution.Retry{Attempts: p.RetryMaxAttempts,
            Delay: execution.ExponentialBackoff(p.InitialBackoff, p.Multiplier, p.MaxBackoff)},
        Circuit: execution.HealthPolicy{FailureThreshold: p.CircuitFailureThreshold,
            OpenDuration: p.CircuitCooldown, IgnoreRetryAfter: true},
        Classify: execution.TransientClassification,
    }
},

Anonymous providers

anonymous.Orchestrator runs the automation that connects the reviewed anonymous providers. For each profile it enrolls the provider, claims its daily check, discovers the admitted catalog, probes every model with Reply with: ok, and publishes only the targets that answered.

orchestrator, err := anonymous.New(anonymous.Options{
    Catalog: catalog, // Discover(ctx, caller, instance): the admitted models, read now
    Invoker: rt,      // a runtime.Runtime: probes run under each instance's policy
    Hooks:   hooks,   // Enabled, Enroll, Claim, Generation, Record
})
stop := orchestrator.Start(ctx, time.Hour, wake) // at once, hourly, and on wake
defer stop()
  • Hooks keep policy and persistence in the product: the gate, enrolling a provider in its configuration, the durable claim, the evidence generation, and recording each Result.
  • Probes cover every discovered model, or with ProbeVerificationModel the reviewed default. A probe keeps its status and Retry-After, so a 401 reads as a rejection and a 429 as retryable.
  • Results carry the gateway's report: status, authentication state, catalog and completion evidence, counts, failure code, retryability and the targets. ConnectAll is the manual run, which consults neither the gate nor the claim.

providers.AutoConnectAnonymousProviders and providers.NewAnonymousOpenAICompatibleAdapter are deprecated in its favour.

OAuth flows

oauthflow.Service runs the server-side sign-in flows both products expose: browser authorization codes with PKCE, device authorization, and codes the user pastes back. Products keep their HTTP routes and map each onto one call:

svc, err := oauthflow.New(oauthflow.Options{
    // Or a shared store. The memory store can cap each caller's pending flows.
    Store:         oauthflow.NewMemoryFlowStore(oauthflow.MemoryFlowStoreOptions{MaxFlowsPerCaller: 5}),
    Credentials:   credentialStore, // core.CredentialStore
    Drivers:       driverFor,       // (instance, method) -> Driver
    CredentialKey: keyFor,          // product policy
})
view, err := svc.Start(ctx, caller, "example", oauthflow.MethodBrowser,
    oauthflow.WithRedirectURI(callbackURL))
view, err = svc.Callback(ctx, oauthflow.CompleteInput{Code: code, State: state})
  • A FlowStore binds each flow to the whole Caller that started it. Another caller gets ErrFlowNotFound, as if the flow did not exist.
  • Consume is atomic and single-use, and an expired flow is never consumed. Run oauthflowtest.Run against every implementation.
  • Poll never asks the provider more often than the flow's interval, even across processes, and honours slow_down.
  • Complete and Callback consume the flow before the code exchange, so a code is used at most once even when the exchange fails. A pasted redirect URL whose state does not match spends nothing, so the user can paste again.
  • Verifiers, OAuth states, device codes and tokens stay on the server. The returned View never holds them.
  • Credentials are saved through CredentialStore.Save under a key the product chooses.
  • BrowserPKCE is a ready driver over llm-provider-auth's browseroauth.

The package documentation maps each gateway and Facet Studio route onto the Service.

License

MIT

Documentation

Index

Constants

View Source
const (
	// TokenTypeGCPServiceAccount marks a Google Cloud service-account key.
	// Token is the key's JSON document as Google issues it, which a
	// provider exchanges for short-lived access tokens and never sends.
	// Metadata may name, under CredentialMetadataProjectID, the project
	// calls are billed to when it is not the key's own. The spelling is
	// llm-provider-auth's gcp.CredentialKind.
	TokenTypeGCPServiceAccount = "gcp_service_account"
	// TokenTypeAnthropicSetupToken marks an Anthropic setup token, the
	// long-lived OAuth token of a Claude subscription. Token is the setup
	// token, which Anthropic takes as a bearer with its OAuth beta marker
	// rather than as an x-api-key; llm-provider-auth's anthropic package
	// validates it and applies those headers.
	TokenTypeAnthropicSetupToken = "anthropic_setup_token"
)

Credential kinds whose material is neither a static API key nor an OAuth access token.

A Credential of such a kind names it in TokenType, carries the kind's material in Token, never in APIKey, and carries its extras in Metadata under the keys the kind documents. A token-store record of the same TokenType, holding the material as its access token without an expiry, becomes such a credential through CredentialFromRecord.

The material is not a bearer token, and only a provider that serves the kind reads it. A product therefore binds such a credential only to an instance whose provider serves its kind.

View Source
const (
	// CredentialMetadataOAuthProfile names how the owner signed in, such as
	// a device or a browser login. The provider defines the values.
	CredentialMetadataOAuthProfile = "oauth_profile"
	// CredentialMetadataOAuthClientID is the OAuth client the grant belongs to.
	CredentialMetadataOAuthClientID = "oauth_client_id"
	// CredentialMetadataOAuthClientMode is how that client authenticates,
	// such as public or confidential.
	CredentialMetadataOAuthClientMode = "oauth_client_mode"
	// CredentialMetadataOAuthRedirectURI is the redirect URI of a browser
	// login.
	CredentialMetadataOAuthRedirectURI = "oauth_redirect_uri"
	// CredentialMetadataOAuthClientSecret is a confidential client's secret.
	CredentialMetadataOAuthClientSecret = "oauth_client_secret"
	// CredentialMetadataAccountLabel is a label for the account, such as an
	// email address, for display.
	CredentialMetadataAccountLabel = "account_label"
	// CredentialMetadataProjectID is the upstream project the credential's
	// calls are billed to, such as the Code Assist project of an Antigravity
	// login. A provider that discovers it lets the product store it here.
	CredentialMetadataProjectID = "project_id"
)

Metadata keys under which a record carries what its OAuth grant and its provider need later, the way the gateway's credential store keeps them. A refresh reads them so each credential refreshes with the client it was granted to, not whichever client is configured now.

View Source
const (
	EvidenceOperationInvoke     = "invoke"
	EvidenceOperationStream     = "stream"
	EvidenceOperationListModels = "list_models"
)

Operations named in AccountEvidence.

View Source
const (
	ContentTypeJSON        = "application/json"
	ContentTypeEventStream = "text/event-stream"
)

Content types of JSON bodies and SSE streams.

View Source
const (
	ListingChatCompletions = "/v1/chat/completions"
	ListingResponses       = "/v1/responses"
	ListingMessages        = "/v1/messages"
)

The chat surfaces a model list classifies, labelled as the gateway's /v1/models labels them.

View Source
const (
	TransportModeNative      = "native"
	TransportModeTranslated  = "translated"
	TransportModeTransparent = "transparent"
)

The transport labels of a response.

View Source
const DefaultTransportFreshness = time.Hour

DefaultTransportFreshness is how long after its catalog was refreshed a row counts as fresh evidence for transport planning: the gateway's hour, whatever TTL the catalog itself has.

View Source
const ModelCapabilitiesSchemaVersion = 1
View Source
const TokenTypeAPIKey = "api_key"

TokenTypeAPIKey marks a token-store record that holds a static API key rather than an OAuth access token.

Variables

View Source
var ErrCatalogConflict = errors.New("core: catalog revision conflict")

ErrCatalogConflict reports a conditional save whose expected revision is no longer the key's revision.

View Source
var ErrCatalogNotFound = errors.New("core: catalog not found")

ErrCatalogNotFound reports that no catalog is stored for a key.

View Source
var ErrInvalidTransportRequirement = errors.New("core: a requested transport must be transparent when present")

ErrInvalidTransportRequirement reports a requested transport other than transparent.

View Source
var ErrNoCredential = errors.New("core: no credential resolves for this caller and instance")

ErrNoCredential reports that no credential resolves for a caller and a provider instance.

View Source
var ErrTokenCountUnsupported = errors.New("core: the provider cannot count tokens natively")

ErrTokenCountUnsupported reports a provider that cannot count a request's tokens natively. A product may estimate the count instead, as the gateway does for a target that cannot count.

Functions

func APIKeyRecord added in v0.9.0

func APIKeyRecord(apiKey string) tokenstore.Record

APIKeyRecord returns the token-store record of a static API key. It has no expiry, so tokenstore.Coordinator never refreshes it.

func KnownModelSurface added in v0.9.0

func KnownModelSurface(surface ModelSurface) bool

KnownModelSurface reports whether a surface is defined by this version.

func ListingSurfaces added in v0.14.0

func ListingSurfaces(provider Provider, credential *Credential, evidence TransportEvidence, evaluatedAt time.Time) (native, emulated, unknown []string)

ListingSurfaces classifies the chat surfaces of a model list's row, as the gateway's native_surfaces, emulated_surfaces and unknown_surfaces do: a surface is native when a plan under TransportRequirementAny carries it natively, emulated when the plan adapts it, and unknown when it refuses. A row that neither claims chat in LegacyCapabilities nor lists a chat surface classifies nothing. The row is as the list presents it, so a product that infers capabilities or surfaces for presentation passes them in its LegacyCapabilities and SupportedAPIs. Credential is the one the caller's requests carry.

func NewProviderOperationError added in v0.3.0

func NewProviderOperationError(op string, statusCode int, retryAfter string, err error) error

NewProviderOperationError creates a classifiable provider error. Callers should not put response bodies, credentials, or private endpoints in op.

func PreservesWire added in v0.14.0

func PreservesWire(provider Provider, model string, surface ModelSurface) bool

PreservesWire reports whether provider forwards surface for model in its own wire protocol: each layer serves the surface natively, and the nearest WirePreserver says so.

A decorator that hands the requests of its native surfaces to the provider it wraps unchanged, as translation.Adapter does, exposes that provider through an Unwrap() Provider method, and PreservesWire looks through it. A decorator that changes those requests declares for itself instead. A provider that neither declares nor unwraps preserves nothing.

func PreservesWireFor added in v0.14.0

func PreservesWireFor(provider Provider, credential *Credential, model string, surface ModelSurface) bool

PreservesWireFor is PreservesWire for a request that carries credential: the nearest layer that declares either interface decides, and a CredentialWirePreserver answers for the credential.

func ResponseTransportMode added in v0.14.0

func ResponseTransportMode(provider Provider, credential *Credential, model string, surface ModelSurface, evidence *TransportEvidence, evaluatedAt time.Time) string

ResponseTransportMode labels the transport of a response an exact target served a request that carried credential, as the gateway's X-LLMGW-Transport-Mode does. With evidence from the target's stored catalog row, it is native when a plan under TransportRequirementAny carries the surface natively. Without it, it reports the path the provider takes: native when the provider preserves the surface's wire. Anything else is translated.

func ServesNatively added in v0.9.0

func ServesNatively(provider Provider, model string, surface ModelSurface) bool

ServesNatively reports whether provider serves surface for model without translation.

func SurfacePath added in v1.3.0

func SurfacePath(surface ModelSurface) string

SurfacePath returns the endpoint path that names surface in a catalog row's SupportedAPIs, with the /v1 prefix: "/v1/chat/completions" for Chat Completions or "/v1/audio/speech" for speech. Core reads each path back as its surface: ParseSurfacePath and AdaptModelCapabilities recognize a chat surface's path, and AdaptModelCapabilities infers the operation of any other. A surface this version does not define has no path: SurfacePath returns the empty string.

Types

type AccountEvidence added in v0.9.0

type AccountEvidence struct {
	Caller   Caller
	Instance string
	// CredentialKey and CredentialRevision identify the token-store record
	// that served the operation; AccountID is the account it is bound to.
	CredentialKey      string
	CredentialRevision string
	AccountID          string
	Operation          string
	Surface            ModelSurface
	Model              string
	// Outcome is the classified result, including when it was observed.
	Outcome ProviderHealthEvidence
}

AccountEvidence attributes one provider operation to the credential that served it, so a product can audit usage and detect a stale or replaced credential. It carries no secrets.

type AllowAllPolicy

type AllowAllPolicy struct{}

AllowAllPolicy is a PolicyGate that permits all requests.

func (AllowAllPolicy) Allows

func (AllowAllPolicy) Allows(ctx context.Context, principal *Principal, target Target) (bool, string)

type Authenticator

type Authenticator interface {
	Authenticate(r *http.Request) (*Principal, error)
}

Authenticator authenticates an incoming HTTP request. If not configured, requests proceed as anonymous.

type AuthenticatorFunc

type AuthenticatorFunc func(r *http.Request) (*Principal, error)

AuthenticatorFunc is a functional adapter for Authenticator.

func (AuthenticatorFunc) Authenticate

func (f AuthenticatorFunc) Authenticate(r *http.Request) (*Principal, error)

type Caller added in v0.9.0

type Caller struct {
	ID        string     `json:"id"`
	Kind      CallerKind `json:"kind"`
	ProjectID string     `json:"project_id,omitempty"`
}

Caller identifies who a request acts for. Products map their own identity types onto it; core never needs more than these fields. ProjectID is optional.

func LocalCaller added in v0.9.0

func LocalCaller() Caller

LocalCaller is the caller of a single-user application. It is a complete, valid Caller.

func (Caller) Validate added in v0.9.0

func (c Caller) Validate() error

Validate reports a caller with no identity or an unknown kind. An anonymous caller may have an empty ID.

type CallerKind added in v0.9.0

type CallerKind string

CallerKind classifies who is making a request.

const (
	// CallerHuman is a signed-in person.
	CallerHuman CallerKind = "human"
	// CallerService is an automation identity, such as an API key.
	CallerService CallerKind = "service"
	// CallerAnonymous is an unauthenticated request.
	CallerAnonymous CallerKind = "anonymous"
	// CallerLocal is the single user of a local, single-user application.
	CallerLocal CallerKind = "local"
)

type CatalogEvidence added in v0.3.0

type CatalogEvidence struct {
	Status        CatalogEvidenceStatus `json:"status"`
	Models        []ModelInfo           `json:"models,omitempty"`
	ObservedAt    time.Time             `json:"observed_at,omitempty"`
	SchemaVersion int                   `json:"schema_version,omitempty"`
}

CatalogEvidence records what a provider advertised. Discovery alone does not prove that any model can successfully perform inference.

SchemaVersion stamps the rules that wrote the evidence, so a product that changes what a stored row means can refuse rows older rules wrote.

type CatalogEvidenceStatus added in v0.3.0

type CatalogEvidenceStatus string
const (
	CatalogNotProbed  CatalogEvidenceStatus = "not_probed"
	CatalogDiscovered CatalogEvidenceStatus = "discovered"
	CatalogEmpty      CatalogEvidenceStatus = "empty"
	CatalogFailed     CatalogEvidenceStatus = "failed"
)

type CatalogKey added in v0.9.0

type CatalogKey struct {
	Instance      string
	CredentialKey string
}

CatalogKey identifies one stored catalog: a provider instance and the key of the credential that discovered it. A catalog depends on the credential, not on the caller: callers sharing a system credential share its catalog, while a personal subscription gets its own. An empty CredentialKey is the catalog of a provider discovered without a credential.

type CatalogRecord added in v0.9.0

type CatalogRecord struct {
	Evidence CatalogEvidence
	Revision string
}

CatalogRecord is a stored catalog and its revision. The revision is opaque and changes on every save.

type CatalogStore added in v0.9.0

type CatalogStore interface {
	// Load returns the stored record, or ErrCatalogNotFound.
	Load(ctx context.Context, key CatalogKey) (CatalogRecord, error)
	// Save stores evidence and returns its new revision.
	Save(ctx context.Context, key CatalogKey, evidence CatalogEvidence) (revision string, err error)
}

CatalogStore keeps discovered catalogs where every process that shares the store can read them.

Save must be atomic with respect to Load in any process: a reader sees the previous record or the new one, never a mix. Because the revision changes on every save, a Runtime that cached a catalog can tell another process's write from its own. The catalogtest package verifies these guarantees.

type CompletionProbeEvidence added in v0.3.0

type CompletionProbeEvidence struct {
	Target      Target                `json:"target"`
	Status      CompletionProbeStatus `json:"status"`
	ObservedAt  time.Time             `json:"observed_at,omitempty"`
	Latency     time.Duration         `json:"latency,omitempty"`
	FailureCode string                `json:"failure_code,omitempty"`
}

CompletionProbeEvidence records inference evidence for one exact target.

func (CompletionProbeEvidence) InferenceVerified added in v0.3.0

func (e CompletionProbeEvidence) InferenceVerified() bool

type CompletionProbeStatus added in v0.3.0

type CompletionProbeStatus string
const (
	CompletionNotProbed CompletionProbeStatus = "not_probed"
	CompletionVerified  CompletionProbeStatus = "verified"
	CompletionFailed    CompletionProbeStatus = "failed"
)

type ConditionalCatalogStore added in v0.14.0

type ConditionalCatalogStore interface {
	CatalogStore
	// SaveIf stores evidence and returns its new revision only while the
	// key's revision is expected, atomically. Otherwise it changes nothing
	// and returns ErrCatalogConflict.
	SaveIf(ctx context.Context, key CatalogKey, evidence CatalogEvidence, expected string) (revision string, err error)
	// Delete forgets the key's record and gives the key a new revision, so
	// a SaveIf that expects any earlier state conflicts. Deleting a key
	// that holds no record succeeds.
	Delete(ctx context.Context, key CatalogKey) error
}

ConditionalCatalogStore is a CatalogStore that can fence writes and forget catalogs. A catalog.Service uses it so that a discovery which ran across an invalidation, in this process or another, cannot store what the invalidation removed.

A key's revision is its record's, or the revision Delete left, or empty for a key never saved. Load of a deleted key returns ErrCatalogNotFound together with a record that carries only that revision, so a caller can expect it. The catalogtest package verifies these guarantees.

type Config

type Config struct {
	Providers    map[string]ProviderConfig `json:"providers"`
	Routes       map[string]RouteConfig    `json:"routes"`
	DefaultRoute string                    `json:"default_route,omitempty"`

	// Pluggable extension hooks
	Authenticator      Authenticator
	PolicyGate         PolicyGate
	CredentialResolver CredentialResolver
	UsageHook          UsageHook
}

Config configures the core LLM Gateway engine.

type Credential

type Credential struct {
	APIKey             string            `json:"api_key,omitempty"`
	Token              string            `json:"token,omitempty"`
	ConnectionID       string            `json:"connection_id,omitempty"`
	CredentialRevision int64             `json:"credential_revision,omitempty"`
	Headers            map[string]string `json:"headers,omitempty"`
	// AccountID identifies the upstream account the credential belongs to,
	// such as the ChatGPT account a Codex token acts for. Empty when unknown.
	AccountID string `json:"account_id,omitempty"`
	// TokenType names the kind of credential: TokenTypeAPIKey for a static
	// key, usually "Bearer" for an OAuth token, or a kind the provider
	// defines, such as a service account.
	TokenType string `json:"token_type,omitempty"`
	// Metadata carries provider-specific values, such as a project ID.
	// Providers read it and never write it, because a credential may serve
	// concurrent requests. It may hold secrets, such as a client secret, so
	// it never serializes and its values never print.
	Metadata map[string]string `json:"-"`
}

Credential contains authentication secrets for an upstream provider.

fmt and slog print whether each secret is set, never its value. JSON encoding carries every field but Metadata, so slog's JSON handler reveals the secrets of a credential nested in another logged value.

func CredentialFromRecord added in v0.9.0

func CredentialFromRecord(key string, record tokenstore.Record) *Credential

CredentialFromRecord converts a stored record into the credential a provider receives. The access token becomes APIKey for an API-key record and Token otherwise, and the key becomes ConnectionID. AccountID, TokenType and Metadata carry over, so a provider can address the account, tell what kind of credential it holds and read its provider-specific values. Metadata is copied, so the credential and the record never share a map. The refresh token stays behind, because only tokenstore.Coordinator refreshes. Record revisions are opaque strings, so CredentialRevision stays zero and evidence carries the revision instead.

func (Credential) Format added in v0.10.0

func (c Credential) Format(state fmt.State, verb rune)

Format keeps every verb from printing secrets. Without it, fmt uses String and GoString only for %v, %s, %q, %x and %X, and prints the fields for any other verb, secrets included. Only %p of a value and %w escape it: fmt reports those as bad verbs and prints the fields without calling a method.

func (Credential) GoString added in v0.10.0

func (c Credential) GoString() string

GoString keeps %#v from printing secrets.

func (Credential) LogValue added in v0.10.0

func (c Credential) LogValue() slog.Value

LogValue keeps structured logging from printing secrets. Metadata keys are sorted, so equal credentials log alike.

func (Credential) String added in v0.10.0

func (c Credential) String() string

String describes the credential without its secrets. The API key, the token and the headers print only whether they are set, and each Metadata key prints with only whether its value is set.

type CredentialResolver

type CredentialResolver interface {
	Resolve(ctx context.Context, principal *Principal, providerID string) (*Credential, error)
}

CredentialResolver resolves upstream credentials for a principal and provider.

type CredentialStore added in v0.9.0

type CredentialStore interface {
	tokenstore.Store
	// Resolve returns the key of the credential that serves caller on
	// instance, or ErrNoCredential.
	Resolve(ctx context.Context, caller Caller, instance string) (key string, err error)
}

CredentialStore holds the credentials a Runtime uses.

It is the llm-provider-auth token store plus Resolve. The store's Lease must exclude every process that shares it: a file store holds an OS advisory lock on a separate lock file, and a database store holds a lease row with a holder and an expiry, never a transaction held across a refresh.

Resolve names the credential for a caller and a provider instance. Which credential applies is product policy. Keeping it fresh is not: the Runtime refreshes OAuth credentials through tokenstore.Coordinator, which never lets a refresh change the account.

type CredentialWirePreserver added in v0.14.0

type CredentialWirePreserver interface {
	// PreservesWireFor reports whether the provider forwards surface for
	// model in its own wire protocol when the request carries credential,
	// which may be nil. It is consulted only for a surface the provider
	// serves natively.
	PreservesWireFor(credential *Credential, model string, surface ModelSurface) bool
}

CredentialWirePreserver is an optional Provider interface for a provider whose wire preservation depends on the credential a request carries. A provider that reshapes every anonymous request, as OpenCode Zen's admission does, preserves nothing for a request without a key, whatever it preserves for one with a key.

type Disposition added in v0.9.0

type Disposition string

Disposition is what a failure permits a router to do next.

const (
	// DispositionRetryable permits retrying the same target, and failing over.
	DispositionRetryable Disposition = "retryable"
	// DispositionFailover permits trying the next target but not repeating
	// this one.
	DispositionFailover Disposition = "failover"
	// DispositionTerminal ends the request: no target would serve it.
	DispositionTerminal Disposition = "terminal"
)

type Engine

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

Engine is the core LLM Gateway routing and proxy engine. It implements http.Handler and can be mounted directly on any router.

func NewEngine

func NewEngine(cfg Config) *Engine

func (*Engine) RegisterProvider

func (e *Engine) RegisterProvider(id string, provider any)

RegisterProvider registers an active provider backend.

func (*Engine) ServeHTTP

func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

type EvidenceSink added in v0.9.0

type EvidenceSink interface {
	Record(ctx context.Context, evidence AccountEvidence)
}

EvidenceSink receives account evidence. Record must neither fail nor delay the operation it describes: a sink that persists evidence buffers or drops.

type EvidenceSinkFunc added in v0.9.0

type EvidenceSinkFunc func(ctx context.Context, evidence AccountEvidence)

EvidenceSinkFunc adapts a function to EvidenceSink.

func (EvidenceSinkFunc) Record added in v0.9.0

func (f EvidenceSinkFunc) Record(ctx context.Context, evidence AccountEvidence)

Record implements EvidenceSink.

type FreshnessStatus added in v0.3.0

type FreshnessStatus string
const (
	FreshnessUnknown FreshnessStatus = "unknown"
	FreshnessFresh   FreshnessStatus = "fresh"
	FreshnessExpired FreshnessStatus = "expired"
)

type GenerateImagesRequest added in v0.6.0

type GenerateImagesRequest struct {
	Model  string `json:"model"`
	Prompt string `json:"prompt"`
	Count  int    `json:"count,omitempty"`
}

GenerateImagesRequest is the storage-neutral input for image synthesis. Count is a caller-side result bound; providers may return fewer images.

type GenerateImagesResult added in v0.6.0

type GenerateImagesResult struct {
	Images []GeneratedImage `json:"images"`
	Usage  map[string]any   `json:"usage,omitempty"`
}

GenerateImagesResult contains generated bytes and provider-native usage.

type GeneratedImage added in v0.6.0

type GeneratedImage struct {
	Data     []byte `json:"-"`
	MimeType string `json:"mime_type"`
}

GeneratedImage contains one decoded inline image returned by a provider.

type ImageGenerator added in v0.6.0

type ImageGenerator interface {
	GenerateImages(context.Context, GenerateImagesRequest, *Credential) (GenerateImagesResult, error)
}

ImageGenerator is implemented by providers with a typed image-generation operation. It makes no assumptions about credential or image storage.

type Loss added in v0.9.0

type Loss = translate.Loss

Loss is one compatibility finding from translating between surfaces or from adapting a request to a provider. It is llm-translate's type, so both kinds of finding combine in one report.

func StreamLosses added in v0.9.0

func StreamLosses(stream StreamIter) []Loss

StreamLosses returns the losses a stream reports, or nil for a stream that does not translate.

type LossAction added in v0.9.0

type LossAction string

LossAction is what a loss policy does with one loss.

const (
	LossAllow  LossAction = "allow"
	LossReject LossAction = "reject"
)

type LossPolicy added in v0.9.0

type LossPolicy struct {
	Rules []LossRule
}

LossPolicy decides which translation and adaptation losses a product accepts.

Rules apply in order and the first match decides. A loss that no rule matches is rejected unless it is advisory, so an unknown severity counts as material. Allowed losses are still reported to the caller.

func (LossPolicy) Check added in v0.9.0

func (p LossPolicy) Check(losses []Loss) error

Check returns a *LossPolicyError when the policy rejects any loss.

func (LossPolicy) Decide added in v0.9.0

func (p LossPolicy) Decide(loss Loss) LossAction

Decide returns the action for one loss.

func (LossPolicy) Rejected added in v0.9.0

func (p LossPolicy) Rejected(losses []Loss) []Loss

Rejected returns the losses the policy rejects, in report order.

func (LossPolicy) Validate added in v0.9.0

func (p LossPolicy) Validate() error

Validate reports a malformed rule.

type LossPolicyError added in v0.9.0

type LossPolicyError struct {
	Losses []Loss
}

LossPolicyError reports losses a policy rejected. Another target may serve the surface natively, so it permits failover.

func (*LossPolicyError) Error added in v0.9.0

func (e *LossPolicyError) Error() string

func (*LossPolicyError) ProviderErrorClassification added in v0.9.0

func (e *LossPolicyError) ProviderErrorClassification() ProviderErrorClassification

ProviderErrorClassification permits failover without marking the provider unhealthy.

type LossReporter added in v0.9.0

type LossReporter interface {
	Losses() []Loss
}

LossReporter is implemented by streams that translate. Losses returns the request-side losses before the first frame and adds response-side losses as frames are translated; the report is complete once Next returns io.EOF.

type LossRule added in v0.9.0

type LossRule struct {
	// Path is a glob over the dot-separated loss path. "*" matches exactly one
	// segment, "**" matches any number of segments including none, and any
	// other segment matches literally. An empty Path matches every loss.
	Path string
	// Class and Severity narrow the rule. Empty matches any value.
	Class    translate.LossClass
	Severity translate.LossSeverity
	Action   LossAction
}

LossRule matches losses and decides them.

type MemoryCatalogStore added in v0.9.0

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

MemoryCatalogStore is the in-memory reference CatalogStore. It is a ConditionalCatalogStore too.

func NewMemoryCatalogStore added in v0.9.0

func NewMemoryCatalogStore() *MemoryCatalogStore

NewMemoryCatalogStore returns an empty store.

func (*MemoryCatalogStore) Delete added in v0.14.0

func (s *MemoryCatalogStore) Delete(ctx context.Context, key CatalogKey) error

Delete implements ConditionalCatalogStore.

func (*MemoryCatalogStore) Load added in v0.9.0

Load implements CatalogStore.

func (*MemoryCatalogStore) Save added in v0.9.0

func (s *MemoryCatalogStore) Save(ctx context.Context, key CatalogKey, evidence CatalogEvidence) (string, error)

Save implements CatalogStore.

func (*MemoryCatalogStore) SaveIf added in v0.14.0

func (s *MemoryCatalogStore) SaveIf(ctx context.Context, key CatalogKey, evidence CatalogEvidence, expected string) (string, error)

SaveIf implements ConditionalCatalogStore.

type MemoryCredentialStore added in v0.9.0

type MemoryCredentialStore struct {
	*tokenstore.Memory
	// contains filtered or unexported fields
}

MemoryCredentialStore is the in-memory reference CredentialStore: tokenstore.Memory plus a resolution table. Resolve prefers a binding for the exact caller and falls back to the instance's shared binding, the way a product prefers a user's own credential over a system one.

func NewMemoryCredentialStore added in v0.9.0

func NewMemoryCredentialStore() *MemoryCredentialStore

NewMemoryCredentialStore returns an empty store.

func (*MemoryCredentialStore) Bind added in v0.9.0

func (s *MemoryCredentialStore) Bind(caller Caller, instance, key string)

Bind makes key the credential of caller on instance.

func (*MemoryCredentialStore) BindShared added in v0.9.0

func (s *MemoryCredentialStore) BindShared(instance, key string)

BindShared makes key the credential of every caller on instance that has no binding of its own.

func (*MemoryCredentialStore) Resolve added in v0.9.0

func (s *MemoryCredentialStore) Resolve(ctx context.Context, caller Caller, instance string) (string, error)

Resolve implements CredentialStore.

type MemoryEvidenceSink added in v0.9.0

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

MemoryEvidenceSink is the in-memory reference EvidenceSink.

func (*MemoryEvidenceSink) Record added in v0.9.0

func (s *MemoryEvidenceSink) Record(_ context.Context, evidence AccountEvidence)

Record implements EvidenceSink.

func (*MemoryEvidenceSink) Records added in v0.9.0

func (s *MemoryEvidenceSink) Records() []AccountEvidence

Records returns a copy of everything recorded, oldest first.

type ModelCapabilities added in v0.3.0

type ModelCapabilities struct {
	SchemaVersion     int                        `json:"schema_version"`
	Operations        ModelOperationCapabilities `json:"operations"`
	Surfaces          ModelSurfaceCapabilities   `json:"surfaces"`
	Inputs            ModelInputCapabilities     `json:"inputs"`
	Tools             Support                    `json:"tools"`
	Reasoning         Support                    `json:"reasoning"`
	StructuredOutput  Support                    `json:"structured_output"`
	Streaming         Support                    `json:"streaming"`
	StatefulResponses Support                    `json:"stateful_responses"`
	Limits            ModelCapabilityLimits      `json:"limits"`
	Provenance        ModelCapabilityProvenance  `json:"provenance"`
	Freshness         ModelCapabilityFreshness   `json:"freshness"`
}

ModelCapabilities is the bounded version 1 shared model capability contract.

func AdaptModelCapabilities added in v0.14.0

func AdaptModelCapabilities(
	legacy map[string]any, surfaces []string, discoveredAt, verifiedAt time.Time,
) *ModelCapabilities

AdaptModelCapabilities converts the gateway's compatibility metadata into the bounded capability contract: legacy is a catalog row's untyped capability map, and surfaces are the endpoints it lists. Missing keys remain unknown; only explicit false values become unsupported.

It is the gateway's function, ported verbatim, so a product and the gateway infer the same capabilities from the same row. The result is inferred with medium confidence. A listed chat surface supports the chat operation, and chat implies text input unless the map says otherwise. Freshness records the times given, in UTC, and never an expiry.

func InferCapabilities added in v0.14.0

func InferCapabilities(model ModelInfo, discoveredAt, verifiedAt time.Time) *ModelCapabilities

InferCapabilities infers a row's capabilities from what the row lists, its LegacyCapabilities and its SupportedAPIs, through AdaptModelCapabilities. It ignores Capabilities, so the caller decides whether capabilities a catalog reported take precedence over inferred ones, as the gateway's catalog lets them when it stores a row.

func (ModelCapabilities) OperationCompatibility added in v0.3.0

func (c ModelCapabilities) OperationCompatibility(operation ModelOperation) Support

OperationCompatibility returns unknown for both unreported capabilities and operation values outside this schema version.

func (ModelCapabilities) SurfaceCompatibility added in v0.3.0

func (c ModelCapabilities) SurfaceCompatibility(surface ModelSurface) Support

SurfaceCompatibility returns unknown for both unreported capabilities and surface values outside this schema version.

type ModelCapabilityConfidence added in v0.3.0

type ModelCapabilityConfidence string
const (
	ModelCapabilityConfidenceLow    ModelCapabilityConfidence = "low"
	ModelCapabilityConfidenceMedium ModelCapabilityConfidence = "medium"
	ModelCapabilityConfidenceHigh   ModelCapabilityConfidence = "high"
)

type ModelCapabilityFreshness added in v0.3.0

type ModelCapabilityFreshness struct {
	DiscoveredAt *time.Time `json:"discovered_at,omitempty"`
	VerifiedAt   *time.Time `json:"verified_at,omitempty"`
	ExpiresAt    *time.Time `json:"expires_at,omitempty"`
}

func (ModelCapabilityFreshness) IsFresh added in v0.3.0

func (f ModelCapabilityFreshness) IsFresh(at time.Time) bool

func (ModelCapabilityFreshness) Status added in v0.3.0

Status reports unknown when no expiry was supplied. Expiry is inclusive: a capability is expired at its expires_at instant.

type ModelCapabilityLimits added in v0.3.0

type ModelCapabilityLimits struct {
	ContextTokens   *int64 `json:"context_tokens,omitempty"`
	MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"`
}

ModelCapabilityLimits uses pointers so an unknown limit is distinct from a provider-reported numeric value.

type ModelCapabilityProvenance added in v0.3.0

type ModelCapabilityProvenance struct {
	Source     ModelCapabilitySource     `json:"source"`
	Confidence ModelCapabilityConfidence `json:"confidence"`
}

type ModelCapabilitySource added in v0.3.0

type ModelCapabilitySource string
const (
	ModelCapabilitySourceUpstreamReported ModelCapabilitySource = "upstream_reported"
	ModelCapabilitySourceRegistryStatic   ModelCapabilitySource = "registry_static"
	ModelCapabilitySourceModelsDev        ModelCapabilitySource = "models_dev"
	ModelCapabilitySourceInferred         ModelCapabilitySource = "inferred"
	ModelCapabilitySourceOperatorOverride ModelCapabilitySource = "operator_override"
)

type ModelInfo

type ModelInfo struct {
	ID            string             `json:"id"`
	Object        string             `json:"object"`
	Created       int64              `json:"created"`
	OwnedBy       string             `json:"owned_by"`
	Description   string             `json:"description,omitempty"`
	Tags          []string           `json:"tags,omitempty"`
	APIEligible   *bool              `json:"api_eligible,omitempty"`
	APIVisibility string             `json:"api_visibility,omitempty"`
	SupportedAPIs []string           `json:"supported_apis,omitempty"`
	Capabilities  *ModelCapabilities `json:"capabilities,omitempty"`
	// DisplayName is the name to show for the model, such as a catalog
	// row's display_name.
	DisplayName string `json:"display_name,omitempty"`
	// Vendor is who makes the model, as the upstream catalog names it.
	Vendor string `json:"vendor,omitempty"`
	// Free marks a model the upstream serves at no cost, such as a free
	// model an anonymous catalog admits. Catalogs that predate the field
	// tag such a row "free" instead.
	Free bool `json:"free,omitempty"`
	// LegacyCapabilities is the gateway's untyped capability map, such as
	// {"chat": true, "vision": true, "context_window": 128000}, kept as the
	// catalog reported it. InferCapabilities reads it. Copies of a row share
	// the map, so copy it before changing it.
	LegacyCapabilities map[string]any `json:"legacy_capabilities,omitempty"`
}

ModelInfo describes an available upstream model or alias.

DisplayName, Vendor, Free and LegacyCapabilities carry what the gateway's catalogs record about a row beyond the fields before them. Each is left out of JSON when empty, so a row that sets none of them encodes exactly as it did before they existed.

type ModelInputCapabilities added in v0.3.0

type ModelInputCapabilities struct {
	Text  Support `json:"text"`
	Image Support `json:"image"`
}

type ModelOperation added in v0.3.0

type ModelOperation string
const (
	ModelOperationChat       ModelOperation = "chat"
	ModelOperationEmbeddings ModelOperation = "embeddings"
	ModelOperationImage      ModelOperation = "image"
	ModelOperationAudioIn    ModelOperation = "audio_in"
	ModelOperationAudioOut   ModelOperation = "audio_out"
	ModelOperationVideo      ModelOperation = "video"
	ModelOperationTokenCount ModelOperation = "token_count"
)

type ModelOperationCapabilities added in v0.3.0

type ModelOperationCapabilities struct {
	Chat       Support `json:"chat"`
	Embeddings Support `json:"embeddings"`
	Image      Support `json:"image"`
	AudioIn    Support `json:"audio_in"`
	AudioOut   Support `json:"audio_out"`
	Video      Support `json:"video"`
	TokenCount Support `json:"token_count"`
}

type ModelSurface added in v0.3.0

type ModelSurface string
const (
	ModelSurfaceChatCompletions ModelSurface = "chat_completions"
	ModelSurfaceResponses       ModelSurface = "responses"
	ModelSurfaceMessages        ModelSurface = "messages"

	// Non-chat surfaces. Their request and response bodies are not always
	// JSON: speech-to-text uploads multipart form data, and speech, image and
	// video responses are binary. The capabilities schema reports no
	// compatibility for them yet.
	ModelSurfaceEmbeddings          ModelSurface = "embeddings"
	ModelSurfaceAudioTranscriptions ModelSurface = "audio_transcriptions"
	ModelSurfaceAudioSpeech         ModelSurface = "audio_speech"
	ModelSurfaceImages              ModelSurface = "images"
	ModelSurfaceVideos              ModelSurface = "videos"
)

func ParseSurfacePath added in v0.14.0

func ParseSurfacePath(path string) ModelSurface

ParseSurfacePath returns the chat surface a request path or a catalog endpoint names, with or without the /v1 prefix: "/v1/responses" and "/responses" are Responses. Anything else, a non-chat surface included, is the empty surface, as in the gateway's transport planning.

type ModelSurfaceCapabilities added in v0.3.0

type ModelSurfaceCapabilities struct {
	ChatCompletions Support `json:"chat_completions"`
	Responses       Support `json:"responses"`
	Messages        Support `json:"messages"`
}

type PolicyGate

type PolicyGate interface {
	Allows(ctx context.Context, principal *Principal, target Target) (bool, string)
}

PolicyGate controls whether a principal is authorized to access a given provider/model target.

type Principal deprecated

type Principal struct {
	ID        string         `json:"id"`
	Type      string         `json:"type"` // "user", "api_key", "anonymous", "service"
	Name      string         `json:"name,omitempty"`
	ProjectID string         `json:"project_id,omitempty"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

Principal represents the authenticated caller making a request.

Deprecated: new APIs take Caller, and Principal.Caller converts. Principal and the Engine hooks that use it move to Caller in the next minor release.

func (*Principal) Caller deprecated added in v0.9.0

func (p *Principal) Caller() Caller

Caller converts a Principal to its Caller.

Deprecated: Principal is replaced by Caller and will be removed in the next minor release. Its Type maps onto a CallerKind: "user" is human, "api_key" and "service" are service, and an empty or "anonymous" type is anonymous.

type Provider added in v0.9.0

type Provider interface {
	// NativeSurfaces lists the surfaces model serves without translation.
	NativeSurfaces(model string) []ModelSurface
	// Invoke performs one non-streaming operation.
	Invoke(ctx context.Context, request Request) (Response, error)
	// Stream performs one streaming operation. Each frame is one complete
	// SSE record of the surface's stream.
	Stream(ctx context.Context, request Request) (StreamIter, error)
	// ListModels returns the provider's catalog for the credential.
	ListModels(ctx context.Context, credential *Credential) ([]ModelInfo, error)
}

Provider performs wire-level operations on the surfaces it serves natively.

A request for any other surface fails with a *SurfaceError; a translation adapter in front of the provider serves other surfaces. Implementations honour ctx cancellation and take credentials only from the request.

type ProviderAdapter added in v0.3.0

type ProviderAdapter struct {
	ValidateAuthentication ProviderAuthenticationValidator
	DiscoverModels         ProviderModelDiscoverer
	SelectProbeTargets     ProviderProbeSelector
	Complete               ProviderCompletionRuntime
}

ProviderAdapter contains the small provider-specific operations used by the shared connector. Complete is both the probe and reusable runtime path.

type ProviderAuthKind added in v0.3.0

type ProviderAuthKind string

ProviderAuthKind describes how a provider connection authenticates.

const (
	ProviderAuthAPIKey           ProviderAuthKind = "api_key"
	ProviderAuthOAuthDevice      ProviderAuthKind = "oauth_device"
	ProviderAuthOAuthBrowser     ProviderAuthKind = "oauth_browser"
	ProviderAuthServiceAccount   ProviderAuthKind = "service_account"
	ProviderAuthWorkloadIdentity ProviderAuthKind = "workload_identity"
	ProviderAuthOfficialClient   ProviderAuthKind = "official_client"
	ProviderAuthAnonymous        ProviderAuthKind = "anonymous"
)

type ProviderAuthenticationValidator added in v0.3.0

type ProviderAuthenticationValidator func(context.Context, ProviderConnection) error

type ProviderCompletionRuntime added in v0.3.0

type ProviderCompletionRuntime func(context.Context, ProviderConnection, Target, map[string]any) (map[string]any, error)

type ProviderConfig

type ProviderConfig struct {
	Type          string            `json:"type"`                     // "openai_compatible", "anthropic", "googleai", "ollama", "elevenlabs", "mimo", "github_copilot", "codex", "edgetts"
	BaseURL       string            `json:"base_url,omitempty"`       // Upstream base endpoint
	APIKey        string            `json:"api_key,omitempty"`        // Static API key or token
	Timeout       time.Duration     `json:"timeout,omitempty"`        // Request timeout
	Headers       map[string]string `json:"headers,omitempty"`        // Extra HTTP headers
	AllowedModels []string          `json:"allowed_models,omitempty"` // Whitelist of models
	Disabled      bool              `json:"disabled,omitempty"`       // Disable provider
}

ProviderConfig configures a single upstream provider backend.

type ProviderConnectRequest added in v0.3.0

type ProviderConnectRequest struct {
	Connection        ProviderConnection      `json:"connection"`
	PublicationPolicy TargetPublicationPolicy `json:"publication_policy"`
	// AuthenticationValidated permits callers that established authentication
	// before Connect to omit an adapter authentication validator.
	AuthenticationValidated bool `json:"authentication_validated,omitempty"`
}

type ProviderConnectResult added in v0.3.0

type ProviderConnectResult struct {
	Connection ProviderConnection        `json:"connection"`
	Catalog    CatalogEvidence           `json:"catalog"`
	Probes     []CompletionProbeEvidence `json:"probes,omitempty"`
	Health     ProviderHealthEvidence    `json:"health"`
	Targets    []Target                  `json:"targets,omitempty"`
}

ProviderConnectResult is the complete, persistence-agnostic outcome of provider onboarding.

type ProviderConnection added in v0.3.0

type ProviderConnection struct {
	ID         string                 `json:"id,omitempty"`
	ProviderID string                 `json:"provider_id"`
	Kind       ProviderConnectionKind `json:"kind"`
	AuthKind   ProviderAuthKind       `json:"auth_kind"`
	OwnerID    string                 `json:"owner_id,omitempty"`
	Credential *Credential            `json:"-"`
}

ProviderConnection is the persistence-agnostic description of a configured provider connection. Credential is available to connectors but never JSON encoded.

func (ProviderConnection) Validate added in v0.3.0

func (c ProviderConnection) Validate() error

Validate rejects contradictory connection descriptions. Codex is always a human-owned subscription connection established through device OAuth.

type ProviderConnectionKind added in v0.3.0

type ProviderConnectionKind string

ProviderConnectionKind describes who owns and may use a provider connection.

const (
	ProviderConnectionSystem               ProviderConnectionKind = "system"
	ProviderConnectionPersonal             ProviderConnectionKind = "personal"
	ProviderConnectionPersonalSubscription ProviderConnectionKind = "personal_subscription"
	ProviderConnectionAnonymous            ProviderConnectionKind = "anonymous"
)

type ProviderConnector added in v0.3.0

type ProviderConnector interface {
	Connect(ctx context.Context, request ProviderConnectRequest) (ProviderConnectResult, error)
}

ProviderConnector orchestrates provider-specific authentication, discovery, probing, classification, and exact target publication.

type ProviderError added in v0.9.0

type ProviderError struct {
	Message        string
	Class          ProviderErrorClass
	Classification ProviderErrorClassification
	Cause          error
}

ProviderError is the canonical provider failure.

Message is safe to show a client: it never contains credentials, private endpoints or upstream response bodies. Cause keeps the underlying error for errors.Is and errors.As and is never rendered.

func NewConfigurationError added in v0.9.0

func NewConfigurationError(message string, cause error) *ProviderError

NewConfigurationError reports a provider that cannot run as configured. It permits failover, because another target may be configured correctly, but never marks the provider unhealthy.

func (*ProviderError) Error added in v0.9.0

func (e *ProviderError) Error() string

func (*ProviderError) ProviderErrorClassification added in v0.9.0

func (e *ProviderError) ProviderErrorClassification() ProviderErrorClassification

ProviderErrorClassification returns the routing metadata.

func (*ProviderError) Unwrap added in v0.9.0

func (e *ProviderError) Unwrap() error

type ProviderErrorClass added in v0.3.0

type ProviderErrorClass string
const (
	ProviderErrorNone        ProviderErrorClass = "none"
	ProviderErrorAuth        ProviderErrorClass = "auth"
	ProviderErrorForbidden   ProviderErrorClass = "forbidden"
	ProviderErrorRateLimited ProviderErrorClass = "rate_limited"
	ProviderErrorTransport   ProviderErrorClass = "transport"
	ProviderErrorUpstream    ProviderErrorClass = "upstream"
	// ProviderErrorBilling is a refusal for want of credit or paid quota,
	// such as HTTP 402, which waiting out a rate limit does not cure.
	ProviderErrorBilling ProviderErrorClass = "billing"
	// ProviderErrorContextOverflow, ProviderErrorToolsUnsupported and
	// ProviderErrorMediaUnsupported refuse a request's shape: one longer
	// than the model's context window, one that sends tools to a model
	// without tool calling, and one that sends an image or other media to a
	// model that cannot read it. Compacting the history, or leaving out the
	// tools or the media, may serve it, and so may another model.
	ProviderErrorContextOverflow  ProviderErrorClass = "context_overflow"
	ProviderErrorToolsUnsupported ProviderErrorClass = "tools_unsupported"
	ProviderErrorMediaUnsupported ProviderErrorClass = "media_unsupported"
)
const (
	// ProviderErrorConfiguration is a provider that cannot run as configured.
	ProviderErrorConfiguration ProviderErrorClass = "configuration"
	// ProviderErrorInvalidRequest is a request the provider rejects as
	// malformed; other targets would reject it too.
	ProviderErrorInvalidRequest ProviderErrorClass = "invalid_request"
	// ProviderErrorUnsupported is a request this target cannot serve, such as
	// a surface it lacks or a translation the loss policy rejects.
	ProviderErrorUnsupported ProviderErrorClass = "unsupported"
)

Error classes beyond the upstream causes that health classification reports.

type ProviderErrorClassification added in v0.4.0

type ProviderErrorClassification struct {
	StatusCode       int
	Retryable        bool
	FailoverEligible bool
	CircuitFailure   bool
	RetryAfter       time.Duration
}

ProviderErrorClassification is the provider-independent routing metadata for an operation failure. StatusCode is zero when no HTTP response was received, and RetryAfter is zero when the provider gave no delay. Disposition summarizes what the failure permits.

func ClassifyError added in v0.9.0

func ClassifyError(err error) ProviderErrorClassification

ClassifyError returns the routing metadata of any error. An error that does not classify itself, including a canceled context, is terminal.

func (ProviderErrorClassification) Disposition added in v0.9.0

func (c ProviderErrorClassification) Disposition() Disposition

Disposition summarizes the classification.

type ProviderErrorClassifier added in v0.4.0

type ProviderErrorClassifier interface {
	ProviderErrorClassification() ProviderErrorClassification
}

ProviderErrorClassifier exposes routing metadata without requiring callers to depend on a provider package's concrete error type.

type ProviderFailure added in v0.3.0

type ProviderFailure struct {
	StatusCode int       `json:"status_code,omitempty"`
	RetryAfter string    `json:"retry_after,omitempty"`
	ObservedAt time.Time `json:"observed_at,omitempty"`
	Err        error     `json:"-"`
}

ProviderFailure contains only values needed for classification. Err is never serialized because transport errors may include credentials or private hosts.

type ProviderHealthEvidence added in v0.3.0

type ProviderHealthEvidence struct {
	Status     ProviderHealthStatus `json:"status"`
	ErrorClass ProviderErrorClass   `json:"error_class"`
	Permanent  bool                 `json:"permanent,omitempty"`
	Retryable  bool                 `json:"retryable,omitempty"`
	RetryAfter time.Duration        `json:"retry_after,omitempty"`
	ObservedAt time.Time            `json:"observed_at,omitempty"`
}

func ClassifyProviderFailure added in v0.3.0

func ClassifyProviderFailure(f ProviderFailure) ProviderHealthEvidence

ClassifyProviderFailure maps HTTP and transport outcomes into stable health semantics.

type ProviderHealthStatus added in v0.3.0

type ProviderHealthStatus string
const (
	ProviderHealthUnknown   ProviderHealthStatus = "unknown"
	ProviderHealthHealthy   ProviderHealthStatus = "healthy"
	ProviderHealthDegraded  ProviderHealthStatus = "degraded"
	ProviderHealthUnhealthy ProviderHealthStatus = "unhealthy"
)

type ProviderModelDiscoverer added in v0.3.0

type ProviderModelDiscoverer func(context.Context, ProviderConnection) ([]ModelInfo, error)

type ProviderOperationError added in v0.3.0

type ProviderOperationError struct {
	Failure ProviderFailure
	Op      string
}

ProviderOperationError preserves classification inputs without exposing an upstream response body or transport error through serialized evidence.

func (*ProviderOperationError) Error added in v0.3.0

func (e *ProviderOperationError) Error() string

func (*ProviderOperationError) ProviderErrorClassification added in v0.4.0

func (e *ProviderOperationError) ProviderErrorClassification() ProviderErrorClassification

func (*ProviderOperationError) Unwrap added in v0.3.0

func (e *ProviderOperationError) Unwrap() error

type ProviderOrchestrator added in v0.3.0

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

ProviderOrchestrator resolves only explicitly registered adapters. This is particularly important for Codex: its personal subscription OAuth flow must never fall through to anonymous OpenAI-compatible behavior.

func NewProviderOrchestrator added in v0.3.0

func NewProviderOrchestrator() *ProviderOrchestrator

func (*ProviderOrchestrator) Connect added in v0.3.0

func (*ProviderOrchestrator) Register added in v0.3.0

func (o *ProviderOrchestrator) Register(providerID string, adapter ProviderAdapter) error

type ProviderProbeSelector added in v0.3.0

type ProviderProbeSelector func(ProviderConnection, []ModelInfo) []Target

type Request added in v0.9.0

type Request struct {
	Surface     ModelSurface
	Model       string
	Body        []byte
	ContentType string
	Credential  *Credential
	// Header holds headers the client sent, such as anthropic-version and
	// anthropic-beta, that select how the upstream serves the request. A
	// provider reads only the headers it explicitly allows, after checking
	// their values, and never a credential: only Credential authenticates,
	// so a client's Authorization or x-api-key never reaches the upstream.
	// Copies of a request, such as its replay after a credential refresh,
	// share the map, so providers never write it. Nil means none. Unlike
	// Credential, it prints and logs as is.
	Header http.Header
}

Request is one wire-level provider operation.

Body is exactly the surface's request body: JSON for the chat surfaces and embeddings, multipart form data for speech-to-text. ContentType names its encoding. The credential is explicit; a provider never looks one up.

func (Request) Validate added in v0.9.0

func (r Request) Validate() error

Validate reports a request that no provider could serve.

type Resolution added in v0.1.1

type Resolution struct {
	Targets  []Target `json:"targets"`
	Category string   `json:"category,omitempty"`
}

Resolution records both the resolved targets and the canonical category name, when the request addressed a category rather than a direct model.

type Response added in v0.9.0

type Response struct {
	Body        []byte
	ContentType string
	Losses      []Loss
}

Response is the result of a non-streaming operation.

Body is the surface's response body, JSON or binary as ContentType says. Losses lists every compatibility loss the operation incurred, including losses the loss policy allowed, so a product can log or label them.

type RouteConfig

type RouteConfig struct {
	Targets     []Target `json:"targets"`               // Ordered targets to try
	Fallback    bool     `json:"fallback,omitempty"`    // Fall back to subsequent targets on error
	Description string   `json:"description,omitempty"` // User-facing description
}

RouteConfig configures an alias (failover chain) across multiple provider targets.

type StaticCredentialResolver

type StaticCredentialResolver struct {
	Credentials map[string]Credential
}

StaticCredentialResolver returns pre-configured credentials from a map.

func (StaticCredentialResolver) Resolve

func (s StaticCredentialResolver) Resolve(ctx context.Context, principal *Principal, providerID string) (*Credential, error)

type StreamIter added in v0.4.0

type StreamIter interface {
	Next() ([]byte, error)
	Close() error
}

StreamIter yields complete SSE frames from an active provider stream.

type Support added in v0.3.0

type Support uint8

Support is an explicitly tri-state capability value. Its zero value is SupportUnknown and marshals as "unknown" rather than false.

const (
	SupportUnknown Support = iota
	SupportSupported
	SupportUnsupported
)

func (Support) IsKnown added in v0.3.0

func (s Support) IsKnown() bool

func (Support) IsSupported added in v0.3.0

func (s Support) IsSupported() bool

func (Support) MarshalJSON added in v0.3.0

func (s Support) MarshalJSON() ([]byte, error)

func (Support) String added in v0.3.0

func (s Support) String() string

func (*Support) UnmarshalJSON added in v0.3.0

func (s *Support) UnmarshalJSON(data []byte) error

type SurfaceError added in v0.9.0

type SurfaceError struct {
	Surface ModelSurface
	Model   string
}

SurfaceError reports a request for a surface the provider does not serve natively. Another target may serve it, so it permits failover.

func (*SurfaceError) Error added in v0.9.0

func (e *SurfaceError) Error() string

func (*SurfaceError) ProviderErrorClassification added in v0.9.0

func (e *SurfaceError) ProviderErrorClassification() ProviderErrorClassification

ProviderErrorClassification permits failover without marking the provider unhealthy.

type Target

type Target struct {
	Provider string `json:"provider"`
	Model    string `json:"model"`
}

Target represents a resolved provider and model.

func PublishExactTargets added in v0.3.0

func PublishExactTargets(providerID string, catalog CatalogEvidence, probes []CompletionProbeEvidence, policy TargetPublicationPolicy) ([]Target, error)

PublishExactTargets converts catalog evidence to exact provider/model targets. Under verification policy, only targets with successful inference evidence publish.

type TargetPublicationPolicy added in v0.3.0

type TargetPublicationPolicy string
const (
	PublishDiscoveredTargets TargetPublicationPolicy = "catalog_discovered"
	PublishVerifiedTargets   TargetPublicationPolicy = "inference_verified"
)

type TokenCount added in v0.14.0

type TokenCount struct {
	InputTokens int64
}

TokenCount is an upstream's count of a request's input tokens.

func CountTokens added in v0.14.0

func CountTokens(ctx context.Context, provider Provider, request TokenCountRequest) (TokenCount, error)

CountTokens counts request's input tokens through the nearest TokenCounter, looking through decorators that unwrap as PreservesWire does, and fails with ErrTokenCountUnsupported when there is none. A decorator that applies its own policy to counts, such as retries, implements TokenCounter itself and forwards through CountTokens.

type TokenCountRequest added in v0.14.0

type TokenCountRequest struct {
	Request
	Header http.Header
}

TokenCountRequest asks how many input tokens a request would use.

Request is the operation as it would be invoked, credential included; Anthropic counts a Messages body. Header holds the protocol headers the client sent that select how the upstream counts, such as anthropic-version and anthropic-beta. A provider forwards only the headers its protocol defines, after checking their values, and never authenticates with one. Header shadows Request.Header: Anthropic counts with Header, or with Request.Header when Header is nil, so a request counts with the headers it would be invoked with.

type TokenCounter added in v0.14.0

type TokenCounter interface {
	CountTokens(ctx context.Context, request TokenCountRequest) (TokenCount, error)
}

TokenCounter is an optional Provider interface for the token_count operation: counting a request's input tokens without running it, as Anthropic's /v1/messages/count_tokens does. A request it cannot count, such as one for another surface, fails with ErrTokenCountUnsupported.

type TransportConfidence added in v0.4.0

type TransportConfidence string
const (
	TransportConfidenceLow    TransportConfidence = "low"
	TransportConfidenceMedium TransportConfidence = "medium"
	TransportConfidenceHigh   TransportConfidence = "high"
)

type TransportDisposition added in v0.4.0

type TransportDisposition string
const (
	TransportNative  TransportDisposition = "native"
	TransportAdapted TransportDisposition = "adapted"
	TransportReject  TransportDisposition = "reject"
)

type TransportEvidence added in v0.14.0

type TransportEvidence struct {
	// Row is the catalog row.
	Row ModelInfo
	// RefreshedAt is when the catalog holding Row was refreshed, its
	// CatalogEvidence.ObservedAt. Zero means never.
	RefreshedAt time.Time
	// FreshFor is how long after RefreshedAt the evidence stays fresh. Zero
	// or less uses DefaultTransportFreshness.
	FreshFor time.Duration
}

TransportEvidence is what a stored catalog says about one of its rows for transport planning.

func (TransportEvidence) Capabilities added in v0.14.0

func (e TransportEvidence) Capabilities() ModelCapabilities

Capabilities returns the capabilities to plan with, as the gateway plans: the row's reported Capabilities, or those InferCapabilities infers from what it lists, with their freshness replaced by the catalog's. They were discovered at RefreshedAt and expire FreshFor later, or carry no freshness at all when the catalog was never refreshed, so evidence is never fresher than the catalog that holds it. Provenance and confidence stay the row's: freshness never upgrades evidence.

type TransportInterface added in v0.4.0

type TransportInterface struct {
	Surface ModelSurface `json:"surface"`
	Native  Support      `json:"native"`
}

TransportInterface is an available provider interface. Native records whether using this interface for its named surface preserves native provider semantics; SupportUnsupported therefore means available through adaptation, not unavailable.

func TransportInterfaces added in v0.14.0

func TransportInterfaces(provider Provider, credential *Credential, model string, surfaces []string) []TransportInterface

TransportInterfaces returns the interfaces a catalog row offers for transport planning: one for each chat surface in surfaces, the row's SupportedAPIs, that ParseSurfacePath recognizes, in their order. An interface is native when provider preserves the surface's wire for model and a request that carries credential (see PreservesWireFor), and adapted otherwise. A surface the row does not list gets no interface, so a request for it is adapted or refused. A nil provider, one that could not be built, offers none.

type TransportPlan added in v0.4.0

type TransportPlan struct {
	Disposition TransportDisposition  `json:"disposition"`
	Surface     ModelSurface          `json:"surface"`
	Confidence  TransportConfidence   `json:"confidence"`
	Losses      []translate.Loss      `json:"losses,omitempty"`
	Reason      TransportRejectReason `json:"reason,omitempty"`
}

TransportPlan is a deterministic decision over the supplied evidence.

func PlanTransport added in v0.4.0

func PlanTransport(request TransportPlanRequest) TransportPlan

PlanTransport selects a native interface when one is confirmed, preserves unknown evidence as eligible at low confidence, and otherwise considers an explicitly reported adaptation. It performs no I/O and applies no gateway or provider policy.

type TransportPlanRequest added in v0.4.0

type TransportPlanRequest struct {
	Operation            ModelOperation       `json:"operation"`
	Surface              ModelSurface         `json:"surface"`
	EvaluatedAt          time.Time            `json:"evaluated_at"`
	ExactTarget          bool                 `json:"exact_target"`
	Capabilities         ModelCapabilities    `json:"capabilities"`
	Interfaces           []TransportInterface `json:"interfaces"`
	Requirement          TransportRequirement `json:"requirement"`
	TranslationEvaluated bool                 `json:"translation_evaluated"`
	Translation          translate.Report     `json:"translation"`
}

TransportPlanRequest contains only evidence needed to choose a transport. Translation describes the conversion that would be used by an adapted plan.

type TransportRejectError added in v0.14.0

type TransportRejectError struct {
	Reason  TransportRejectReason
	Surface ModelSurface
	Model   string
	// NativeInterface reports that the stored catalog lists Surface for
	// Model and the provider preserves its wire.
	NativeInterface bool
}

TransportRejectError reports a request refused for the transport it requires, before anything reached the provider. A product words the refusal from Reason, as the gateway does, and NativeInterface tells a surface the provider does not preserve from evidence that is stale or untrusted.

func (*TransportRejectError) Error added in v0.14.0

func (e *TransportRejectError) Error() string

type TransportRejectReason added in v0.4.0

type TransportRejectReason string
const (
	// TransportRejectNotCataloged is a model its stored catalog does not
	// list, so no evidence can confirm a native surface.
	TransportRejectNotCataloged TransportRejectReason = "not_cataloged"
	// TransportRejectStreaming is a transparent request that streams,
	// which transparency does not support yet.
	TransportRejectStreaming TransportRejectReason = "streaming_unsupported"
)

Reasons a transparent request is refused besides a plan's.

const (
	TransportRejectInvalidRequirement     TransportRejectReason = "invalid_requirement"
	TransportRejectOperationUnsupported   TransportRejectReason = "operation_unsupported"
	TransportRejectNoInterface            TransportRejectReason = "no_interface"
	TransportRejectNativeRequired         TransportRejectReason = "native_required"
	TransportRejectExactTargetRequired    TransportRejectReason = "exact_target_required"
	TransportRejectNativeUnconfirmed      TransportRejectReason = "native_unconfirmed"
	TransportRejectTranslationUnevaluated TransportRejectReason = "translation_not_evaluated"
	TransportRejectTranslationLoss        TransportRejectReason = "translation_loss"
)

type TransportRequirement added in v0.4.0

type TransportRequirement string

TransportRequirement constrains whether a request may use adaptation.

const (
	TransportRequirementAny         TransportRequirement = "any"
	TransportRequirementNative      TransportRequirement = "native"
	TransportRequirementTransparent TransportRequirement = "transparent"
)

func ParseTransportRequirement added in v0.14.0

func ParseTransportRequirement(value string) (TransportRequirement, error)

ParseTransportRequirement reads the transport a client requests, such as the gateway's X-LLMGW-Transport-Mode header: blank is TransportRequirementAny, and transparent, in any case, is TransportRequirementTransparent. Anything else, native included, is ErrInvalidTransportRequirement, because a client may ask only for transparency.

type UsageHook

type UsageHook interface {
	RecordUsage(ctx context.Context, record UsageRecord)
}

UsageHook receives telemetry records after each completed request.

type UsageHookFunc

type UsageHookFunc func(ctx context.Context, record UsageRecord)

UsageHookFunc is a functional adapter for UsageHook.

func (UsageHookFunc) RecordUsage

func (f UsageHookFunc) RecordUsage(ctx context.Context, record UsageRecord)

type UsageRecord

type UsageRecord struct {
	RequestID    string        `json:"request_id"`
	PrincipalID  string        `json:"principal_id,omitempty"`
	ProjectID    string        `json:"project_id,omitempty"`
	Provider     string        `json:"provider"`
	Model        string        `json:"model"`
	InputTokens  int           `json:"input_tokens"`
	OutputTokens int           `json:"output_tokens"`
	Duration     time.Duration `json:"duration"`
	Stream       bool          `json:"stream"`
	StatusCode   int           `json:"status_code"`
	Error        string        `json:"error,omitempty"`
}

UsageRecord holds metrics and token counts from a completed LLM call.

type WirePreserver added in v0.14.0

type WirePreserver interface {
	// PreservesWire reports whether the provider forwards surface for
	// model in its own wire protocol. It is consulted only for a surface
	// the provider serves natively.
	PreservesWire(model string, surface ModelSurface) bool
}

WirePreserver is an optional Provider interface. A provider implements it to declare the surfaces it forwards in their own wire protocol: the request reaches the upstream as that surface's request, and the answer comes back as that surface's response or frames, with no conversion through another surface inside the provider. Setting the model or the stream flag, or encoding the JSON again, keeps a surface preserved; translating it does not.

Preserving a surface is a stronger claim than serving it natively. Codex and Antigravity list Chat Completions among their native surfaces but convert it internally, so they serve it without preserving it, and the gateway labels such an answer translated. A product's response transport label reads PreservesWire, never NativeSurfaces, and preservation is never inferred from any other method.

Directories

Path Synopsis
Package anonymous runs the automation that connects the reviewed anonymous providers, as the gateway's does: it enrolls each profile's provider, discovers what its catalog admits, probes the models with a completion and publishes the exact targets that answered.
Package anonymous runs the automation that connects the reviewed anonymous providers, as the gateway's does: it enrolls each profile's provider, discovers what its catalog admits, probes the models with a completion and publishes the exact targets that answered.
Package catalog keeps discovered provider catalogs in a core.CatalogStore the way the gateway keeps its own: reads that serve a stored catalog while it is younger than a TTL and otherwise discover it, reads that never discover, and discovery fenced against invalidation, so a discovery that began before a catalog was invalidated never stores over what came after.
Package catalog keeps discovered provider catalogs in a core.CatalogStore the way the gateway keeps its own: reads that serve a stored catalog while it is younger than a TTL and otherwise discover it, reads that never discover, and discovery fenced against invalidation, so a discovery that began before a catalog was invalidated never stores over what came after.
Package catalogtest is the conformance suite for core.CatalogStore implementations.
Package catalogtest is the conformance suite for core.CatalogStore implementations.
Package execution provides the primitives failover is built from: a HealthTracker that breaks circuits and cools down instances, and executors that try candidates in order and never fail over once output has reached the caller.
Package execution provides the primitives failover is built from: a HealthTracker that breaks circuits and cools down instances, and executors that try candidates in order and never fail over once output has reached the caller.
Package extension is the client side of the extension protocol: the HTTP protocol between a product and an extension daemon, a separate process that serves providers the product does not carry itself.
Package extension is the client side of the extension protocol: the HTTP protocol between a product and an extension daemon, a separate process that serves providers the product does not carry itself.
Package oauthflow runs server-side OAuth flows for products that sign users in to LLM providers: browser authorization codes with PKCE, device authorization, and codes the owner pastes back.
Package oauthflow runs server-side OAuth flows for products that sign users in to LLM providers: browser authorization codes with PKCE, device authorization, and codes the owner pastes back.
oauthflowtest
Package oauthflowtest is the conformance suite for oauthflow.FlowStore implementations.
Package oauthflowtest is the conformance suite for oauthflow.FlowStore implementations.
Package runtime provides Runtime, the one value that owns an embedding product's provider state: the provider registry, the provider cache, the credential refresh coordinators, the catalog cache and provider health.
Package runtime provides Runtime, the one value that owns an embedding product's provider state: the provider registry, the provider cache, the credential refresh coordinators, the catalog cache and provider health.
Package translation serves surfaces a provider lacks by translating through llm-translate.
Package translation serves surfaces a provider lacks by translating through llm-translate.

Jump to

Keyboard shortcuts

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