Documentation
¶
Overview ¶
Package openai implements port.LLMProvider over the OpenAI Responses API (POST /v1/responses) using github.com/openai/openai-go/v3. It names the PROTOCOL, not a single vendor: this one adapter serves the composition registry's "openai", "openrouter", AND (issue #262) the intent-driven ToolHive LLM gateway registry entries — each is the same Responses-API wire protocol with a different base URL + credential, so registering one more OpenAI-compatible endpoint here never touches the OpenAI/Anthropic SDK boundary.
The harness owns its own conversation state (the brief's "strategy B"): every request is stateless (Store:false, no previous_response_id) and resends the full input item slice, with reasoning items carried forward verbatim and reasoning.encrypted_content requested via Include so reasoning survives across turns. Tools are sent as function tools with their JSON schemas. The two-layer system prompt is rendered into Instructions.
The streaming SSE events are translated into provider-neutral port.Chunk values by the pure translate function, which is exercised directly from recorded fixtures in tests; no network is required to test the translation.
Index ¶
- type CacheDialect
- type OpenRouterProviderPreferences
- type Option
- func WithAPIKey(key string) Option
- func WithBaseURL(url string) Option
- func WithCacheDialect(d CacheDialect) Option
- func WithCacheKeySalt(salt string) Option
- func WithHTTPClient(c *http.Client) Option
- func WithMaxRetries(retries int) Option
- func WithOpenRouterMetadata(enabled bool) Option
- func WithOpenRouterProviderPreferences(resolve func(model string) *OpenRouterProviderPreferences) Option
- func WithPromptCacheBreakpoints(on bool) Option
- func WithProviderCapabilities(caps port.ProviderCapabilities) Option
- func WithReasoningEffort(effort string) Option
- func WithRequestOption(opts ...option.RequestOption) Option
- type Provider
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CacheDialect ¶
type CacheDialect string
CacheDialect selects which provider-side prompt-cache wire dialect buildParams (method) emits. The OpenAI Responses API already caches implicitly (a breakpoint on the most recent user/tool message, matched against the 50 most recent breakpoints, longest match wins) — this Option only controls the vendor-shaped hints layered on top: prompt_cache_key (routing observability) and prompt_cache_retention (an OpenAI-only, model-gated retention extension).
It does NOT control the ASK for a cache. The request-root cache_control field this type once carried for OpenRouter is retired in favour of the protocol-native prompt_cache_breakpoint, which rides the separate WithPromptCacheBreakpoints Option precisely so an endpoint whose identity the dialect cannot classify still gets the ask.
const ( // CacheDialectNone emits no cache hints at all — the SAFE DEFAULT, and the // byte-identical wire without cache hints. The zero value. CacheDialectNone CacheDialect = "" // CacheDialectOpenAI emits prompt_cache_key, and — on an allow-listed // model (retentionFor) — prompt_cache_retention. CacheDialectOpenAI CacheDialect = "openai" // CacheDialectOpenRouter emits prompt_cache_key and nothing else. It once // also emitted an OpenRouter-private request-root cache_control field; // that is retired in favour of the protocol-native breakpoint, so the // two OpenAI-shaped dialects now differ ONLY in retention. It must still // NEVER emit prompt_cache_retention — an OpenAI-only field a non-OpenAI // upstream would reject. CacheDialectOpenRouter CacheDialect = "openrouter" )
type OpenRouterProviderPreferences ¶
OpenRouterProviderPreferences is the OpenRouter DOWNSTREAM-provider routing object (issue #480) stamped onto the request body's `provider` key. It is the adapter-local, provider-private mirror of OpenRouter's ProviderPreferences schema — v1 carries only Order + AllowFallbacks. It is NOT a port.LLMRequest field: the request stays provider-neutral and the knob is minted per model at adapter construction (the remint discipline).
Order lists downstream provider slugs (lowercase-kebab, e.g. "anthropic", "google-vertex", "deepinfra/turbo") tried in order; setting it disables OpenRouter's default price load-balancing. AllowFallbacks is a POINTER so "absent" (OpenRouter default true) is distinguishable from an explicit false (pin hard to Order, no fallback). Base-slug matching applies: "google-vertex" matches all its regions/variants (service tiers excepted).
type Option ¶
type Option func(*config)
Option configures a Provider.
func WithAPIKey ¶
WithAPIKey sets the API key used to authenticate requests.
func WithBaseURL ¶
WithBaseURL overrides the API host so OpenAI-compatible endpoints (vLLM, LiteLLM, a local proxy, ...) can be targeted. The SDK appends "/responses".
func WithCacheDialect ¶
func WithCacheDialect(d CacheDialect) Option
WithCacheDialect selects the cache-hint dialect buildParams (method) emits. Composition gates this on (providerID, resolvedBaseURL) — NEVER providerID alone: an operator can point the "openai" id at a non-canonical compatible endpoint (vLLM/LiteLLM via --openai-base-url) that would 400 on prompt_cache_retention or an unrecognised field, so the dialect for a non-canonical base URL must degrade to CacheDialectNone in composition, not here. An unrecognised token (this adapter has no validation at Option- application time) degrades to CacheDialectNone in buildParams — fail-soft, mirroring reasoningEffortFor's omit-on-unknown arm.
func WithCacheKeySalt ¶ added in v0.2.0
WithCacheKeySalt folds a per-process random value into the prompt_cache_key prefix hash. Composition mints one value per app.Build and passes it to every openai-adapter entry.
Why: the unsalted derivation has no installation-specific input, so two unrelated installations sharing harness version, soul, agent def and tool inventory emit an IDENTICAL key. That lets a recipient correlate sessions across principals and credentials, and confirm guessed configuration — and routing metadata frequently outlives prompt bodies in a provider's retention tiers, so the key outlives the content it fingerprints.
"" (the Option unset) reproduces the unsalted derivation byte-for-byte, so a consumer passing no Option is unaffected. The salt is never sent as itself, never logged, and never persisted: a fresh value per process costs one sticky-routing lane change per restart, which the derivation already treats as fail-soft for an anchor change.
func WithHTTPClient ¶
WithHTTPClient sets the final *http.Client the SDK issues requests through (e.g. a redirect-refusing or policy-guarded client). It is deliberately applied after arbitrary WithRequestOption values, so a late generic SDK option cannot replace and bypass a security transport. nil is ignored (SDK default). NOTE for callers: do NOT set Client.Timeout here — a streaming turn runs for minutes; establishment/idle bounds live in llmresilience, not the transport's blanket deadline.
func WithMaxRetries ¶
WithMaxRetries configures the openai-go retry loop. Composition uses zero when llmresilience is the sole retry owner or when a dynamic credential source must not be called multiple times inside one outer attempt.
func WithOpenRouterMetadata ¶
WithOpenRouterMetadata arms the `X-OpenRouter-Metadata: enabled` request header, which makes OpenRouter return the openrouter_metadata block (naming the routed downstream provider) on the terminal streaming event. It is a SEPARATE Option from WithOpenRouterProviderPreferences so the routing echo works even with no order configured. Gated to the openrouter registry entry in composition. The adapter never logs; the value reaches the loop as ChunkProviderRoute.
func WithOpenRouterProviderPreferences ¶
func WithOpenRouterProviderPreferences(resolve func(model string) *OpenRouterProviderPreferences) Option
WithOpenRouterProviderPreferences sets a resolve-at-request closure keyed on the request's model id, returning the downstream-provider routing object for that model (nil = send nothing). The per-model closure shape exists because the registry entry is shared across models while the config is per-model. It is gated to the openrouter registry entry in COMPOSITION — every other entry passes no Option, so the `provider` key can never leak to a non-OpenRouter endpoint. The body key is injected via option.WithJSONSet at Stream time, so buildParams and the byte-stable prompt-cache prefix are untouched.
func WithPromptCacheBreakpoints ¶ added in v0.2.0
WithPromptCacheBreakpoints arms the protocol-native explicit prompt-cache breakpoint. Composition passes !cfg.PromptCacheDisabled.
It is a SEPARATE Option from WithCacheDialect on purpose. The dialect gates vendor-shaped hints on endpoint identity; the breakpoint is part of the Responses protocol and is exactly what an unrecognised endpoint needs, so tying it to the dialect would leave CacheDialectNone endpoints — the ToolHive gateway among them — with no way to ask for a cache, which is the bug this breakpoint exists to fix.
Default false, so a consumer passing no Option keeps the breakpoint-free wire.
func WithProviderCapabilities ¶
func WithProviderCapabilities(caps port.ProviderCapabilities) Option
WithProviderCapabilities sets the per-SESSION input-capability intersection (the catalog ∩ adapter value composition computes via modelCapability) the request builder consults when projecting a tool result's typed Parts. It is an adapter-CONSTRUCTION Option, not a port.LLMRequest field — the per- session engine factory re-mints the adapter (alongside reasoning effort) when the session's resolved (provider, model) carries a DIFFERENT intersection than the operator-default model the shared provider was built with; the default path (same model) reuses the shared provider byte-for-byte. When unset, the builder degrades to the adapter's own static Capabilities(), and a tool result with no Parts always takes the legacy single-string function_call_output regardless. A deliberately text-only (zero-value) caps is distinct from unset (nil).
func WithReasoningEffort ¶
WithReasoningEffort sets the reasoning-effort token stamped on every request's reasoning.effort field. The value is a NEUTRAL composition token, ALREADY CLAMPED for OpenAI (xhigh/max are clamped to high in composition, with a diagnostic, because this adapter has no port.Diagnostics). Empty (and "auto") OMITS the field — the provider default applies. It is an adapter-CONSTRUCTION Option, not a port.LLMRequest field, so the provider stays neutral; the per-session engine factory re-mints the adapter when the session's effort differs from the operator default (the same factory discipline as the per-call model override).
func WithRequestOption ¶
func WithRequestOption(opts ...option.RequestOption) Option
WithRequestOption threads an arbitrary openai-go request option through to the client (e.g. option.WithHeader, option.WithMaxRetries). Multiple are applied in order, after the API key and base URL.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is a port.LLMProvider backed by the OpenAI Responses API. Construct it with New.
func New ¶
New constructs a Provider. At minimum supply WithAPIKey; add WithBaseURL for compatible endpoints.
func (*Provider) Capabilities ¶
func (*Provider) Capabilities() port.ProviderCapabilities
Capabilities reports the provider's multimodal input support — the ADAPTER TRANSMIT authority composition's modelCapability intersects with the catalog (catalog ∩ adapter). It is STATIC (the OpenAI Responses input-message content union supports text + image + file but has NO audio member, openai-go v3.37.0, so Audio is false; Image is true; EmbeddedContext is true because inline text flattens into the input_text content part). It does NOT reflect the per- session intersection — that lives on p.caps (set via WithProviderCapabilities) and is consulted ONLY by the request builder's tool-result projection; the port method stays the static transmit authority so modelCapability's AND stays honest.
func (*Provider) Stream ¶
func (p *Provider) Stream(ctx context.Context, req port.LLMRequest) (iter.Seq2[port.Chunk, error], error)
Stream issues a streaming Responses request and yields provider-neutral chunks. The returned iterator translates each SSE event via translate; it stops (abandoning the underlying stream) when ctx is cancelled, and surfaces a terminal transport error as the iterator's error. The outer error is reserved for a failure to construct the request parameters.
A rejection of the replayed encrypted reasoning is REPAIRED once, before any chunk has gone out: see withoutEncryptedReasoning. Everything else — an ordinary 4xx, a post-commit failure, a cancellation — stays terminal.