Skip to main content

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.