Core Concepts
Understand the contracts and normalization rules shared by every go-llm-router provider.
Agent contract
All providers implement the same interface:
type Agent interface {
Name() string
Send(
ctx context.Context,
messages []Message,
toolDefs []Tool,
reasoning Reasoning,
mode Mode,
) (*Output, int, error)
}
Send accepts a context, chat messages, optional tool definitions, a normalized reasoning level, and an execution mode. It returns the normalized Output, the HTTP status code, and an error, so callers can switch providers without changing their request loop.
Providers that support streaming also implement StreamAgent with identical arguments:
type StreamAgent interface {
SendStream(
ctx context.Context,
messages []Message,
toolDefs []Tool,
reasoning Reasoning,
mode Mode,
) (<-chan StreamEvent, error)
}
core/claude and core/copilot expose SendStream. core/openaiCodex and core/grokOauth consume upstream SSE internally and still return a complete core.Output from Send.
| Event type | Payload |
|---|---|
StreamEventText |
TextDelta output-text increment |
StreamEventReasoning |
ReasoningDelta reasoning-summary increment |
StreamEventToolCall |
ToolCall delta with index, ID, name, or argument fragment |
StreamEventUsage |
Normalized Usage |
StreamEventDone |
FinishReason |
StreamEventError |
Err |
Messages and multimodal content
Message contains a Role, arbitrary Content, optional ReasoningContent, model-emitted ToolCalls, and ToolCallID for tool results. Content can be a string or provider-compatible structured content. Multimodal requests use ContentPart values with text or an ImageURL containing URL and optional Detail.
messages := []core.Message{
{Role: "user", Content: "Describe this image."},
{Role: "user", Content: []core.ContentPart{
{Type: "text", Text: "What is shown here?"},
{Type: "image_url", ImageURL: &core.ImageURL{URL: imageURL}},
}},
}
Provider routing
Call router.New with a provider@model name. The optional bracket segment is ignored when matching the provider, so claude[eu]@claude-sonnet-5 still routes to claude. compat@model selects the OpenAI-compatible implementation and uses BaseURL.
The current router keys are claude, openai, gemini, grok, deepseek, nvidia, openrouter, cloudflare, compat, copilot, codex, and grok-oauth. Unknown keys return an error from router.New.
Reasoning levels
Reasoning is a typed enum that replaces the earlier string argument:
| Constant | Literal | Alias |
|---|---|---|
ReasoningNone |
none |
— |
ReasoningLow |
low |
minimal |
ReasoningMedium |
medium |
— |
ReasoningHigh |
high |
— |
ReasoningXHigh |
xhigh |
extra |
ReasoningMax |
max |
ultra |
ReasoningDefault is ReasoningMedium. ParseReasoning accepts literals and aliases; its second return value reports whether the input matched, and unmatched input yields the default.
Supported ranges differ per model. Provider adapters implement ReasoningAgent to publish their bounds:
type ReasoningAgent interface {
ReasoningLimits() (min, max Reasoning)
}
Before a request goes out, ClampReasoning(r, lo, hi, provider, model) caps the level to that range and logs a debug record through slog whenever clamping occurs. Callers pass the shared level rather than building provider-specific fields: Claude maps it to a thinking budget or output_config.effort, OpenAI to reasoning_effort or Responses reasoning.effort, and Gemini to thinkingLevel or thinkingBudget.
Execution mode and the fast tier
Mode states the requested service tier: ModeDefault and ModeFast. ParseMode parses default and fast.
ModeFast is a capability request. Adapters check core.SupportFast(provider, model) against a whitelist and only then add the provider-native field; a miss stays on the standard tier silently instead of failing. Unlisted models sometimes reject the parameter outright — Claude Opus 4.7 answers with HTTP 400 — so filtering beforehand is cheaper than handling the error afterwards.
| Route | Native control | Whitelist |
|---|---|---|
| Claude | speed: "fast" plus the fast-mode-2026-02-01 beta header |
Opus 5, Opus 4.8 |
| OpenAI | service_tier: "fast" |
gpt-5.4 and newer; codex variants from 5.3; -pro and -nano excluded |
| Grok, Grok OAuth | service_tier: "priority" |
Text inference models |
| OpenRouter | service_tier: "priority" |
openai/, google/, and x-ai/ upstreams, each filtered by that upstream's own whitelist |
| Other adapters | No tier field is sent | — |
The Gemini model list stays inside SupportFast to gate OpenRouter's google/ routes; the Gemini adapter itself currently sends no tier field.
No provider guarantees that the requested value equals the served tier, so attribution always reads the response: OpenAI, Grok, and OpenRouter report a top-level service_tier (normalized into Output.ServiceTier), while Claude reports usage.speed. When the reported value is neither fast nor priority, core.WarnFastDowngrade logs a downgrade warning through slog; an empty field means the provider reported nothing and is not treated as a downgrade.
Tool calling
A Tool has Type and a ToolFunction with a name, description, and JSON Schema parameters. Model-emitted calls arrive in Output.Choices[*].Message.ToolCalls. A typical loop sends the assistant message back, executes each requested function, and replies with a tool message carrying the matching ToolCallID.
tools := []core.Tool{{
Type: "function",
Function: core.ToolFunction{
Name: "get_weather",
Description: "Look up weather for a city",
Parameters: json.RawMessage(`{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}`),
},
}}
out, _, err := agent.Send(ctx, messages, tools, core.ReasoningMedium, core.ModeDefault)
if err != nil {
return err
}
for _, call := range out.Choices[0].Message.ToolCalls {
fmt.Println(call.Function.Name, call.Function.Arguments)
}
Gemini thinking models return a signature in ToolCall.ThoughtSignature that must be sent back unchanged on the following turn.
Usage normalization
Providers name token counters differently. Usage.UnmarshalJSON absorbs input_tokens / output_tokens, OpenAI-style prompt_tokens / completion_tokens, cache-creation tokens, and cached prompt tokens into one structure:
| Field | Meaning |
|---|---|
Input |
Non-cached input tokens |
Output |
Generated output tokens |
CacheCreate |
Tokens written to a prompt cache |
CacheRead |
Cached input tokens read by the request |
Accounting and UI code can therefore consume output.Usage without branching on the provider.
Model discovery
Provider packages expose Models(ctx, config, filter), which returns model IDs. core/copilot and core/gemini additionally expose ModelInfos, returning core.ModelInfo values that carry Thinking, Efforts, and Endpoints.
core.ModelFilter{TextOnly: true} drops image, audio, video, and embedding models through core.IsTextModel.
OAuth and extensions
core/oauth/copilot, core/oauth/codex, and core/oauth/grok provide login, load, refresh, and clear operations. Token objects are supplied through router.Config.Token; CodexToken and GrokToken expose Expired() with a 60-second safety buffer.
The core/openaiCodex Agent additionally exposes GenerateImage, returning base64 image data and a revised prompt.
Related pages
- Configuration — credentials and routing names.
- API Reference — exported types and functions.
- Architecture — request and response flow.