OpenResponses Provider
The OpenResponses provider targets APIs that implement the OpenAI Responses wire shape (self-hosted servers like LMStudio, Ollama, or LocalAI, or any other /responses-compatible endpoint). Use it when a provider exposes /responses semantics instead of chat completions. It only supports language models — EmbeddingModel, ImageModel, SpeechModel, TranscriptionModel, and RerankingModel all return errors. QuiverAI is built on this same transport.
Setup
import (
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openresponses"
)
provider := openresponses.New(openresponses.Config{
BaseURL: "http://localhost:1234/v1", // e.g. LMStudio
APIKey: os.Getenv("OPENRESPONSES_API_KEY"), // optional; many local servers need none
})
model, err := provider.LanguageModel("responses-model")
if err != nil {
log.Fatal(err)
}
openresponses.Config also accepts Headers, Name (overrides the
provider identity, default "open-responses"), and
StrictResponseInput (controls how assistant history without a known item
ID is replayed).
Custom Tools
provider.Tools().CustomTool(name, options) builds a caller-executed
"custom" tool whose input the model streams as raw text (not JSON function
arguments) — useful for tools like apply_patch or free-form code/markup
generation. custom_tool_call / custom_tool_call_output items are
replayed correctly in message history, and the default custom tool ID is
"open-responses.custom" (Config.CustomToolID overrides it; QuiverAI
overrides it to "quiverai.custom").
patchTool := provider.Tools().CustomTool("apply_patch", openresponses.CustomToolOptions{
Description: "Apply a unified diff patch to the workspace.",
Format: &openresponses.CustomToolFormat{Type: "text"},
})
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Fix the off-by-one bug in main.go.",
Tools: []types.Tool{patchTool},
})
Streaming And Text Parts
Streaming emits text-start / text-end chunk boundaries around text
content, and text parts carry the Responses itemId and any annotations
in their provider metadata.
Extensions
openresponses.Config.Extensions lets a provider that embeds the Open
Responses spec plug in its own tool/item/event codecs via
openresponses.Extension, without the core language model needing to know
about them ahead of time. By default a codec's wire types must be
namespaced ("<implementor>:<type>", e.g. "lmstudio:code_execution"),
which keeps extensions portable across implementations.
For an implementation that documents a bare (non-namespaced) wire
discriminator and can't be changed, set AllowBareTypes: true and use
BareToolType/BareItemTypes/BareEventTypes instead of
ToolType/ItemTypes/EventTypes. A bare type must be non-empty, must
not contain a colon, and must not collide with a core Responses wire type
(e.g. "function", "message", "response.completed"); registering one
is an explicit, non-portable opt-in.
p := openresponses.New(openresponses.Config{
BaseURL: "https://api.example.com/v1",
Extensions: []openresponses.Extension{{
ID: "acme.legacy_tool",
AllowBareTypes: true,
BareToolType: "legacy_tool",
EncodeTool: func(name string, args map[string]interface{}) (map[string]interface{}, error) {
return args, nil
},
}},
})
Workflow Serialization
OpenResponses language models can cross a workflow boundary with
providerutils.SerializeModel / DeserializeModel, gated by
provider.SerializableModelStrict — a model is refused for serialization
when extensions are registered on it (since extension codecs can't be
reconstructed from a serialized config alone). See
Provider Serialization
for the general mechanism.
May 2026 parity updates
Reasoning summary
OpenResponses supports the reasoningSummary provider option. The provider serializes it to the Responses reasoning.summary request field for APIs that accept auto, concise, or detailed summaries.
p := openresponses.New(openresponses.Config{
APIKey: os.Getenv("OPENRESPONSES_API_KEY"),
BaseURL: "https://api.example.com/v1",
})
model, err := p.LanguageModel("responses-model")
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Summarize the reasoning at a high level.",
ProviderOptions: map[string]interface{}{
"openresponses": map[string]interface{}{
"reasoningSummary": "concise",
},
},
})
System messages
System messages are converted to the Responses instructions shape by the provider. At the core SDK layer, system-role messages inside Messages are rejected by default; use the top-level System field or opt in with AllowSystemMessages.