glean/providers/openai_compatible
Shared implementation for all OpenAI-compatible Chat Completions APIs. OpenAI, DeepSeek, Mistral, OpenRouter, Moonshot/Kimi, Qwen and MiniMax all use this format, and so can any other compatible endpoint:
config.new(api_key: key, model: "my-model")
|> config.base_url("https://my-gateway.example.com/v1")
|> openai_compatible.create(name: "my-gateway", config: _)
Reasoning returned as reasoning_content (DeepSeek, Qwen, Kimi,
MiniMax) or reasoning (OpenRouter) is surfaced as reasoning content and
ReasoningDelta stream events.
Types
Accumulator for streaming state — threaded through a pure receive loop.
pub opaque type StreamAccumulator
Values
pub fn accumulated_content(
acc: StreamAccumulator,
) -> List(provider.GeneratedContent)
Everything accumulated, in the order a non-streaming response has it: reasoning, text, then tool calls. Public for tests.
pub fn accumulated_text(acc: StreamAccumulator) -> String
The text accumulated so far. Public for tests.
pub fn create(
name provider_name: String,
config config: config.ProviderConfig,
) -> provider.Provider
Create a Provider from an OpenAI-compatible config.
pub fn decode_response(
model_id: String,
body: String,
) -> Result(provider.GenerateResult, error.GleanError)
Decode a non-streaming Chat Completions response body. Public for tests.
pub fn encode_request(
config: config.ProviderConfig,
gen_request: provider.GenerateRequest,
stream: Bool,
) -> String
Encode a Chat Completions request body. Public for tests.
pub fn new_stream_accumulator() -> StreamAccumulator
A fresh accumulator. Public for tests.
pub fn stream_loop(
events: @internal Stream,
acc: StreamAccumulator,
on_event: fn(stream.StreamEvent) -> Nil,
provider_name: String,
model: String,
timeout_ms: Int,
) -> Result(StreamAccumulator, error.GleanError)
Receive SSE events until the stream completes.
Returns an error — never a partial success — when the stream times out, gets a non-2xx response, closes before the provider signalled completion, or carries an error payload.
Public so tests can drive it with sse.from_events.