Documentation
¶
Overview ¶
Package orclient is the OpenRouter streaming client: it assembles the chat-completions request body and headers, decodes the SSE response into stream parts (text, reasoning, tool calls, usage, finish), validates and repairs tool calls against the registered tool set, and registers the outcome of each call with the adaptive router.
One DoStream call is one HTTP request. Nothing here loops, retries, executes a tool or touches session state; the caller owns all four. Early teardown cancels the request context rather than merely closing the body, so an abandoned stream never holds its connection open.
Index ¶
- Constants
- Variables
- func BuildRequestBody(p RequestParams) ([]byte, error)
- func DeterministicStringify(raw json.RawMessage) ([]byte, error)
- func MapToUnified(finishReason string) string
- func MaxOutputTokens(model Model) float64
- func Message(msgs []msgmodel.ModelMessage, model Model) []msgmodel.ModelMessage
- func NormalizeMessages(msgs []msgmodel.ModelMessage, model Model) []msgmodel.ModelMessage
- func SanitizeSurrogates(content string) string
- func SetFetcherForTesting(f Fetcher) func()
- func SetNowForTesting(f func() float64) func()
- func SetRandomForTesting(f func() float64) func()
- func SetTimeoutContextFactoryForTesting(f TimeoutContextFactory) func()
- func SetTimerFactoryForTesting(f TimerFactory) func()
- func UnsupportedParts(msgs []msgmodel.ModelMessage, model Model) []msgmodel.ModelMessage
- type AbortPart
- type Annotation
- type Choice
- type Chunk
- type ChunkValue
- type Client
- type Delta
- type ErrorPart
- type Fetcher
- type FilePart
- type FinishPart
- type FinishReason
- type HeaderInputs
- type HeaderPair
- type ImageResponse
- type InvalidArgumentError
- type InvalidResponseDataError
- type InvalidToolInputError
- type JSONParseError
- type Model
- type ModelAPI
- type ModelCapabilities
- type ModelLimit
- type NoSuchToolError
- type Object
- func ConvertToOpenRouterChatMessages(prompt []msgmodel.ModelMessage) ([]*Object, error)
- func MergeOptions(target, source *Object) *Object
- func NewObject() *Object
- func Options(input OptionsInput) *Object
- func ParseObject(raw []byte) (*Object, error)
- func ProviderOptions(model Model, options *Object) *Object
- func (o *Object) Clone() *Object
- func (o *Object) Get(key string) (json.RawMessage, bool)
- func (o *Object) Has(key string) bool
- func (o *Object) Keys() []string
- func (o *Object) Len() int
- func (o *Object) MarshalJSON() ([]byte, error)
- func (o *Object) Set(key string, raw json.RawMessage) error
- func (o *Object) SetArray(key string, items []*Object)
- func (o *Object) SetBool(key string, value bool)
- func (o *Object) SetNumber(key string, value float64)
- func (o *Object) SetNumberPtr(key string, value *float64)
- func (o *Object) SetObject(key string, value *Object)
- func (o *Object) SetString(key, value string)
- func (o *Object) Without(keys ...string) *Object
- type OpenRouterMetadata
- type OptionsInput
- type ParsedToolCall
- type RawToolCall
- type ReasoningDeltaPart
- type ReasoningDetail
- type ReasoningDetailsView
- type ReasoningEndPart
- type ReasoningStartPart
- type RepairFn
- type RequestParams
- type ResponseMetadataPart
- type RouterRegistrar
- type SSEDecoder
- type SSEEvent
- type SourcePart
- type Stream
- type StreamPart
- type TextDeltaPart
- type TextEndPart
- type TextStartPart
- type TimeoutContextFactory
- type Timer
- type TimerFactory
- type Tool
- type ToolCallDelta
- type ToolCallPart
- type ToolCallRepairError
- type ToolChoice
- type ToolInputDeltaPart
- type ToolInputEndPart
- type ToolInputStartPart
- type ToolMap
- type ToolSpec
- type Translator
- type TypeValidationError
Constants ¶
const ( CompatibilityCompatible = "compatible" CompatibilityStrict = "strict" )
Compatibility modes.
const ( // DefaultTimeoutMS disables the total-request deadline. A positive // TotalTimeoutMS on Client opts back into one. DefaultTimeoutMS float64 = -1 // DefaultChunkTimeoutMS is the reader watchdog's inactivity bound. DefaultChunkTimeoutMS float64 = 120_000 )
Timeout defaults. Total request age is not a stall signal; only the progress-sensitive reader watchdog is enabled by default.
const ( PartTypeResponseMetadata = "response-metadata" PartTypeReasoningStart = "reasoning-start" PartTypeReasoningDelta = "reasoning-delta" PartTypeReasoningEnd = "reasoning-end" PartTypeTextStart = "text-start" PartTypeTextDelta = "text-delta" PartTypeTextEnd = "text-end" PartTypeSource = "source" PartTypeToolInputStart = "tool-input-start" PartTypeToolInputDelta = "tool-input-delta" PartTypeToolInputEnd = "tool-input-end" PartTypeToolCall = "tool-call" PartTypeFile = "file" PartTypeError = "error" PartTypeFinish = "finish" PartTypeAbort = "abort" )
Part type tags.
const ( FinishStop = "stop" FinishLength = "length" FinishContentFilter = "content-filter" FinishToolCalls = "tool-calls" FinishError = "error" FinishOther = "other" )
Unified finish-reason values.
const ( ReasoningDetailSummary = "reasoning.summary" ReasoningDetailEncrypted = "reasoning.encrypted" ReasoningDetailText = "reasoning.text" )
Reasoning detail type tags.
const DefaultReasoningFormat = "anthropic-claude-v1"
DefaultReasoningFormat is assumed when a text detail names no format.
const DoneSentinel = "[DONE]"
DoneSentinel is the payload the stream reader drops before parsing.
const InvalidToolName = "invalid"
InvalidToolName is the tool repair rewrites an unrepairable call to.
const Service = modelsource.DefaultID
Service is the identity of the model service whose wire this client speaks: OpenRouter's, which is the shape codeaf's model API answers in. It is the provider every model senior-dev asks for is filed under, the key a request's service options are kept under, and the namespace the service's reasoning details and finish metadata come back in.
CODEAF SPELLS THAT IDENTITY ONCE, as modelsource.DefaultID, and a law holds the whole module to it (internal/modelsource/purity_law_test.go). Every use in senior-dev reads it from here, so the word is written in one place in codeaf and in none in this program.
Variables ¶
var ( // ErrOperationTimedOut is the total-request deadline's cause. ErrOperationTimedOut = errors.New("The operation timed out.") // ErrSSEReadTimedOut is the reader watchdog's cause. ErrSSEReadTimedOut = errors.New("SSE read timed out") )
Abort cause messages. Both are matched by `adaptive.IsLikelyTimeout` and `retrysched.IsTimeoutError`, which is the whole reason they are literals.
Functions ¶
func BuildRequestBody ¶
func BuildRequestBody(p RequestParams) ([]byte, error)
BuildRequestBody returns the bytes POSTed to /chat/completions: the model and sampling parameters, the converted messages, the tool definitions, then any provider options from config (which may override a base field), then the streaming flags.
func DeterministicStringify ¶
func DeterministicStringify(raw json.RawMessage) ([]byte, error)
DeterministicStringify re-encodes a JSON document with every object's keys sorted, recursively. Assistant tool-call arguments go through it before they are sent back to the provider so the same call always serialises the same way. An empty input is reported rather than encoded, since the caller omits the field in that case.
func MapToUnified ¶
MapToUnified maps a provider finish_reason to the unified set. Everything unrecognised, including `"error"`, becomes "other": unified `error` is reserved for a parse failure, a top-level error payload or a reader error.
func MaxOutputTokens ¶
MaxOutputTokens is `min(model.limit.output, OUTPUT_TOKEN_MAX)`, falling back to OUTPUT_TOKEN_MAX when the minimum is 0 or NaN. Delegated to internal/engine/calc so the SENIOR_DEV_OUTPUT_TOKEN_MAX read lives in exactly one place.
func Message ¶
func Message(msgs []msgmodel.ModelMessage, model Model) []msgmodel.ModelMessage
Message prepares a message list for an OpenRouter model: UnsupportedParts, then NormalizeMessages. It is called immediately before BuildRequestBody.
func NormalizeMessages ¶
func NormalizeMessages(msgs []msgmodel.ModelMessage, model Model) []msgmodel.ModelMessage
NormalizeMessages sanitises surrogates in every text-bearing part and, for a DeepSeek model, appends the empty reasoning stub. It returns a new slice and leaves the input alone.
func SanitizeSurrogates ¶
SanitizeSurrogates replaces every UNPAIRED UTF-16 surrogate with U+FFFD.
This is a hand-rolled UTF-16 scan, and it MUST be UTF-16, not runes: the whole point is code units, and a surrogate pair must survive untouched while its halves individually do not.
A lone surrogate is reachable: `encoding/json` maps a `\uD800` escape to U+FFFD on decode, but a Go string is a byte string and can carry the WTF-8 encoding of a surrogate code point (ED A0 80 … ED BF BF), which is what a non-strict decoder or a byte-level splice produces. Both forms are handled.
The original string is returned BY VALUE when no replacement happens, so a CESU-8-encoded (surrogate-pair) input is not silently re-encoded to canonical UTF-8.
func SetFetcherForTesting ¶
func SetFetcherForTesting(f Fetcher) func()
SetFetcherForTesting swaps the fetch seam. Returns a restore func.
func SetNowForTesting ¶
func SetNowForTesting(f func() float64) func()
SetNowForTesting swaps the clock. Returns a restore func.
func SetRandomForTesting ¶
func SetRandomForTesting(f func() float64) func()
SetRandomForTesting swaps the randomness source. Returns a restore func.
func SetTimeoutContextFactoryForTesting ¶
func SetTimeoutContextFactoryForTesting(f TimeoutContextFactory) func()
SetTimeoutContextFactoryForTesting swaps the total-request timeout seam. Returns a restore func.
func SetTimerFactoryForTesting ¶
func SetTimerFactoryForTesting(f TimerFactory) func()
SetTimerFactoryForTesting swaps the watchdog timer. Returns a restore func.
func UnsupportedParts ¶
func UnsupportedParts(msgs []msgmodel.ModelMessage, model Model) []msgmodel.ModelMessage
UnsupportedParts runs before NormalizeMessages. It only touches ARRAY-content user messages, and only their `file` parts: a part whose modality the model does not accept becomes a text part telling the model to inform the user.
A nil capabilities.input map counts as "supports nothing", so a malformed catalog entry rewrites every file part rather than failing.
Types ¶
type AbortPart ¶
AbortPart ends a stream that was cancelled. The reason, when present, is the cancellation cause's message.
func (AbortPart) MarshalJSON ¶
type Annotation ¶
type Annotation struct {
Type string
// url_citation fields.
URL string
Title *string
StartIndex *float64
EndIndex *float64
Content *string
// Raw carries the normalized annotation object; the old-format
// `file_annotation` is validated and then IGNORED ENTIRELY.
Raw json.RawMessage
}
Annotation is one `delta.annotations[]` entry.
type Chunk ¶
type Chunk struct {
// Success reports whether the payload validated.
Success bool
// ParseError is the serialised error object emitted when Success is false.
ParseError json.RawMessage
// Value is the validated chunk.
Value *ChunkValue
}
Chunk is one parsed SSE payload.
func ParseChunk ¶
ParseChunk parses and validates one SSE payload.
type ChunkValue ¶
type ChunkValue struct {
ID *string
Model *string
Provider *string
Usage *calc.OpenRouterUsage
Choices []Choice
// ErrorField is set when the payload carries an `error` key, for BOTH
// shapes.
ErrorField json.RawMessage
}
ChunkValue is the validated payload.
type Client ¶
type Client struct {
// BaseURL is the API's OpenAI-style base URL, the one codeaf serves this
// run. It has no default: a client with none has nowhere to send a request,
// and the only road senior-dev has to a model is the one codeaf hands it.
BaseURL string
// Headers is BuildHeaders' output.
Headers []HeaderPair
// Compatibility selects whether `stream_options` is emitted; senior-dev uses
// "compatible".
Compatibility string
// TotalTimeoutMS / ChunkTimeoutMS control the optional total-request bound
// and the progress-sensitive reader watchdog. Zero means the default; a
// negative value disables the corresponding mechanism.
TotalTimeoutMS float64
ChunkTimeoutMS float64
// Fetcher overrides the package fetch seam for this client only. Embedders
// use it to retain their configured HTTP transport without mutating the
// process-wide testing seam.
Fetcher Fetcher
// Router and RouteChoice drive the exactly-once registration. Both nil
// means no routing was performed and registration is skipped entirely.
Router RouterRegistrar
RouteChoice *adaptive.RouteChoice
}
Client is one configured model API: an endpoint that answers in OpenRouter's chat-completions shape.
type Delta ¶
type Delta struct {
Content *string
Reasoning *string
ReasoningDetails []ReasoningDetail
HasReasoningDeta bool
Images []ImageResponse
ToolCalls []ToolCallDelta
HasToolCalls bool
Annotations []Annotation
HasAnnotations bool
}
Delta is `choices[i].delta`.
type ErrorPart ¶
type ErrorPart struct {
Error json.RawMessage
}
ErrorPart carries whatever the translator put in `error`. Three sources:
chunk parse failure → a validation-error object (see wire.go) `error` in the chunk → the RAW `error` object from the chunk reader error → the caught error, at flush
In the second case, when the chunk validates as the CHUNK shape (because `choices` is present) the error object is passed through unchanged; when it validates as the ERROR shape, `code`/`type`/`param` are filled with null and the keys reordered to shape order first, extras after.
func (ErrorPart) MarshalJSON ¶
type Fetcher ¶
Fetcher performs one HTTP round trip. The default is a plain http.DefaultClient; the CLI installs its own configured client per Client.
type FilePart ¶
FilePart is `{type:"file", mediaType, data}` from `delta.images[]`.
func (FilePart) MarshalJSON ¶
type FinishPart ¶
type FinishPart struct {
FinishReason FinishReason
Usage calc.LanguageModelV3Usage
Metadata OpenRouterMetadata
}
FinishPart is the single terminal part, emitted from `flush`.
func (FinishPart) MarshalJSON ¶
func (p FinishPart) MarshalJSON() ([]byte, error)
func (FinishPart) PartType ¶
func (p FinishPart) PartType() string
type FinishReason ¶
FinishReason is the `{unified, raw}` pair. `raw` is absent when no `finish_reason` was ever seen, and survives the two synthetic promotions.
func MapOpenRouterFinishReason ¶
func MapOpenRouterFinishReason(finishReason *string) FinishReason
MapOpenRouterFinishReason wraps MapToUnified, keeping the raw value.
func (FinishReason) MarshalJSON ¶
func (f FinishReason) MarshalJSON() ([]byte, error)
MarshalJSON writes `{unified, raw?}`.
type HeaderInputs ¶
type HeaderInputs struct {
// Provider is the provider-level header set before its user-agent
// suffix: Authorization, X-OpenRouter-Title, HTTP-Referer, then the
// configured provider headers (HTTP-Referer + X-Title).
Provider []HeaderPair
// ProviderUserAgentSuffix is appended to the user agent first.
ProviderUserAgentSuffix string
// Call is the per-call header set.
Call []HeaderPair
// UtilsUserAgentSuffix / RuntimeUserAgentSuffix are appended to the user
// agent last.
UtilsUserAgentSuffix string
RuntimeUserAgentSuffix string
}
HeaderInputs are the header contributors, in the order they are combined.
type HeaderPair ¶
HeaderPair is one final header, lowercase-named.
func BuildHeaders ¶
func BuildHeaders(in HeaderInputs) []HeaderPair
BuildHeaders reproduces the whole merge, which is worth doing as one function because the precedence is genuinely surprising: `X-Title` and `X-OpenRouter-Title` are BOTH on the wire, `HTTP-Referer` is set three times with the call-level value winning, and `User-Agent`/`user-agent` collide only after normalizeHeaders lowercases them.
The pipeline is:
provider = withUserAgentSuffix(providerHeaders, providerSuffix)
combined = {...provider, ...call} // case-SENSITIVE spread
withType = {"Content-Type": "application/json", ...combined}
final = withUserAgentSuffix(withType, utilsSuffix, runtimeSuffix)
withUserAgentSuffix lowercases every name, joins the non-empty user-agent parts with a space, and returns the pairs sorted by name.
type ImageResponse ¶
type ImageResponse struct{ URL string }
ImageResponse is one recognised `delta.images[]` entry.
type InvalidArgumentError ¶
InvalidArgumentError reports an unusable request parameter.
func (*InvalidArgumentError) Error ¶
func (e *InvalidArgumentError) Error() string
type InvalidResponseDataError ¶
type InvalidResponseDataError struct {
Message string
Data json.RawMessage
}
InvalidResponseDataError is returned by the tool-call accumulator for a malformed first delta. It tears the whole stream down; it is not an `error` part.
func (*InvalidResponseDataError) Error ¶
func (e *InvalidResponseDataError) Error() string
type InvalidToolInputError ¶
InvalidToolInputError reports an input that failed to parse or validate.
func (*InvalidToolInputError) Error ¶
func (e *InvalidToolInputError) Error() string
type JSONParseError ¶
JSONParseError reports an input that is not JSON. Its message, in the format
JSON parsing failed: Text: <text>.\nError message: <cause>
reaches the model verbatim through the `invalid` tool's input. The cause text is whatever encoding/json reports.
func NewJSONParseError ¶
func NewJSONParseError(text string, cause error) *JSONParseError
NewJSONParseError builds one in the format above.
func (*JSONParseError) Error ¶
func (e *JSONParseError) Error() string
type Model ¶
type Model struct {
ProviderID string `json:"providerID"`
ID string `json:"id"`
API ModelAPI `json:"api"`
Capabilities ModelCapabilities `json:"capabilities"`
Limit ModelLimit `json:"limit"`
}
Model is the catalog-model projection this package needs.
type ModelCapabilities ¶
type ModelCapabilities struct {
Temperature bool `json:"temperature"`
Reasoning bool `json:"reasoning"`
Attachment bool `json:"attachment"`
ToolCall bool `json:"toolcall"`
Input map[string]bool `json:"input"`
Output map[string]bool `json:"output"`
}
ModelCapabilities is the slice of a catalog model's capabilities request assembly reads. `temperature` gates whether a temperature is sent at all; `input` gates UnsupportedParts.
type ModelLimit ¶
type ModelLimit struct {
Context float64 `json:"context"`
Input *float64 `json:"input"`
Output float64 `json:"output"`
}
ModelLimit is a catalog model's limits.
type NoSuchToolError ¶
NoSuchToolError reports a call to an unregistered tool. The message text matters: the repair callback puts it into the `invalid` tool's input and the model reads it.
func (*NoSuchToolError) Error ¶
func (e *NoSuchToolError) Error() string
type Object ¶
type Object struct {
// contains filtered or unexported fields
}
Object is an insertion-ordered JSON object. Setting an existing key replaces its value in place.
func ConvertToOpenRouterChatMessages ¶
func ConvertToOpenRouterChatMessages(prompt []msgmodel.ModelMessage) ([]*Object, error)
ConvertToOpenRouterChatMessages converts the prompt into wire messages. The returned slice is what lands in the body's `messages` field.
func MergeOptions ¶
MergeOptions deep-merges source into target: target's keys come first in their own order, source-only keys are appended in source order, and a key whose value is an object on both sides is merged recursively in place.
func Options ¶
func Options(input OptionsInput) *Object
Options is the OpenRouter-specific request option bag, in the order the keys reach the wire after the top-level spread:
usage (npm === "@openrouter/ai-sdk-provider") prompt_cache_key (providerID === "openrouter")
No reasoning effort is set here; it is sent only when a variant or config sets it.
func ParseObject ¶
ParseObject decodes a JSON object literal into an ordered Object. Empty input yields an empty object.
func ProviderOptions ¶
ProviderOptions wraps the merged option bag under the provider's SDK key, which for OpenRouter is "openrouter": exactly the namespace BuildRequestBody unwraps and spreads over the body. Callers that go straight to BuildRequestBody can skip the round-trip; this exists so the wrapping is testable on its own.
func (*Object) Get ¶
func (o *Object) Get(key string) (json.RawMessage, bool)
Get returns the raw JSON of one key.
func (*Object) MarshalJSON ¶
MarshalJSON writes the object in insertion order.
func (*Object) Set ¶
func (o *Object) Set(key string, raw json.RawMessage) error
Set assigns raw JSON to key.
func (*Object) SetNumberPtr ¶
SetNumberPtr assigns a number, or leaves the key absent when value is nil.
type OpenRouterMetadata ¶
type OpenRouterMetadata struct {
Usage *Object
Provider *string
ReasoningDetails ReasoningDetailsView
Annotations []json.RawMessage
}
OpenRouterMetadata is `providerMetadata.openrouter` at flush: `{usage}` first, then `provider` if the stream ever carried one, then `reasoning_details` (always), then `annotations` only when non-empty.
func (OpenRouterMetadata) MarshalJSON ¶
func (m OpenRouterMetadata) MarshalJSON() ([]byte, error)
type OptionsInput ¶
OptionsInput is what Options needs.
type ParsedToolCall ¶
type ParsedToolCall struct {
Type string
ToolCallID string
ToolName string
Input json.RawMessage
Dynamic bool
Invalid bool
Error error
ProviderExecuted bool
ProviderMetadata json.RawMessage
}
ParsedToolCall is ParseToolCall's result. `Invalid` marks the synthetic stage-3 result, which is emitted as a `tool-call` part and immediately as a `tool-error` part, and is NEVER executed.
func ParseToolCall ¶
func ParseToolCall(call RawToolCall, tools *ToolMap, repair RepairFn) ParsedToolCall
ParseToolCall validates and, if needed, repairs one call. It never returns an error: every failure path collapses into the synthetic invalid call, which is the whole point of stage 3.
func (ParsedToolCall) MarshalJSON ¶
func (c ParsedToolCall) MarshalJSON() ([]byte, error)
MarshalJSON writes the call with a fixed key order.
type RawToolCall ¶
type RawToolCall struct {
ToolCallID string
ToolName string
Input string
ProviderExecuted bool
ProviderMetadata json.RawMessage
}
RawToolCall is a tool call as the stream delivered it: `input` is a raw JSON STRING, never a parsed value.
func SeniorDevRepairToolCall ¶
func SeniorDevRepairToolCall(call RawToolCall, tools *ToolMap, failure error) (*RawToolCall, error)
SeniorDevRepairToolCall is senior-dev's RepairFn.
Two steps, in order:
- if the LOWERCASED name differs from the emitted one AND a tool with the lowercase name exists → return the call with the name lowercased. Note it keeps the ORIGINAL input, so a call that failed VALIDATION (not name-lookup) and happens to be mis-cased gets re-validated against the lowercase tool's schema and can fail a second time — which then lands in stage 3 rather than the `invalid` tool.
- otherwise → rewrite to `toolName:"invalid"` with `input: {tool, error}` encoded as a raw JSON STRING, which is what RawToolCall.Input holds.
type ReasoningDeltaPart ¶
ReasoningDeltaPart is `{type:"reasoning-delta", delta, id}` — note `delta` precedes `id` here, the reverse of the tool-input parts.
func (ReasoningDeltaPart) MarshalJSON ¶
func (p ReasoningDeltaPart) MarshalJSON() ([]byte, error)
func (ReasoningDeltaPart) PartType ¶
func (p ReasoningDeltaPart) PartType() string
type ReasoningDetail ¶
type ReasoningDetail struct {
Type string
// Summary is required for the summary variant.
Summary string
// Data is required for the encrypted variant.
Data string
// Text / Signature may be null or absent on the text variant.
Text json.RawMessage
Signature json.RawMessage
// Common, all optional.
ID json.RawMessage
Format json.RawMessage
Index *float64
// Raw is the normalized provider object. Once a detail crosses the
// provider boundary it is opaque metadata; keeping these bytes avoids a
// decode-to-map/re-encode round trip changing key order or number spelling.
// It is cleared only when the provider's consecutive-text merge mutates
// the detail.
Raw json.RawMessage
}
ReasoningDetail is one parsed entry.
func ParseReasoningDetails ¶
func ParseReasoningDetails(raw json.RawMessage) []ReasoningDetail
ParseReasoningDetails parses each entry and drops the unrecognised ones.
func (ReasoningDetail) MarshalJSON ¶
func (d ReasoningDetail) MarshalJSON() ([]byte, error)
MarshalJSON writes the shape order: the variant's own keys first, then the three common ones. Absent keys are omitted; explicit nulls are written.
type ReasoningDetailsView ¶
type ReasoningDetailsView struct {
// contains filtered or unexported fields
}
ReasoningDetailsView is a LIVE reference to the provider's `accumulatedReasoningDetails` array.
This indirection is deliberate: every emitted part shares the SAME accumulator, so a `reasoning-end` emitted at chunk 2 and serialised after the stream ends shows entries that only arrived at chunk 3. Copying at emit time would lose them.
func DetailsValue ¶
func DetailsValue(details ...ReasoningDetail) ReasoningDetailsView
DetailsValue builds a detached view, for hand-constructed parts and tests.
func (ReasoningDetailsView) MarshalJSON ¶
func (v ReasoningDetailsView) MarshalJSON() ([]byte, error)
MarshalJSON always writes an array, never null: the empty case is meaningful. It signals "the provider produced no reasoning tokens this turn", which is a different statement from "no metadata".
func (ReasoningDetailsView) Slice ¶
func (v ReasoningDetailsView) Slice() []ReasoningDetail
Slice resolves the view.
type ReasoningEndPart ¶
type ReasoningEndPart struct {
ID string
Details ReasoningDetailsView
}
ReasoningEndPart carries the FULL accumulated reasoning_details array.
func (ReasoningEndPart) MarshalJSON ¶
func (p ReasoningEndPart) MarshalJSON() ([]byte, error)
func (ReasoningEndPart) PartType ¶
func (p ReasoningEndPart) PartType() string
type ReasoningStartPart ¶
type ReasoningStartPart struct{ ID string }
ReasoningStartPart is `{type:"reasoning-start", id}`.
func (ReasoningStartPart) MarshalJSON ¶
func (p ReasoningStartPart) MarshalJSON() ([]byte, error)
func (ReasoningStartPart) PartType ¶
func (p ReasoningStartPart) PartType() string
type RepairFn ¶
type RepairFn func(call RawToolCall, tools *ToolMap, failure error) (*RawToolCall, error)
RepairFn tries to fix a call that failed validation. Returning (nil, nil) keeps the ORIGINAL error.
type RequestParams ¶
type RequestParams struct {
// ModelID is the full `<vendor>/<name>` OpenRouter model id.
ModelID string
Prompt []msgmodel.ModelMessage
MaxOutputTokens *float64
// Sampling parameters. A nil field is omitted from the body, so the
// serving provider's default applies; the caller decides what to set.
Temperature *float64
TopP *float64
TopK *float64
MinP *float64
Seed *float64
FrequencyPenalty *float64
PresencePenalty *float64
RepetitionPenalty *float64
Tools []Tool
ToolChoice *ToolChoice
// OpenRouterOptions is the merged provider option bag from config (base,
// model, agent and variant options, in that order). `cacheControl` is
// split out of it before the spread.
OpenRouterOptions *Object
// Compatibility selects whether `stream_options` is emitted.
Compatibility string
}
RequestParams are the inputs to one request.
type ResponseMetadataPart ¶
type ResponseMetadataPart struct {
ID string
ModelID string
// IsModel selects which of the two emissions this is.
IsModel bool
}
ResponseMetadataPart is emitted TWICE per chunk that carries both an `id` and a `model`: once with only `id`, once with only `modelId`. They are deliberately separate parts, not one merged part.
func (ResponseMetadataPart) MarshalJSON ¶
func (p ResponseMetadataPart) MarshalJSON() ([]byte, error)
func (ResponseMetadataPart) PartType ¶
func (p ResponseMetadataPart) PartType() string
type RouterRegistrar ¶
type RouterRegistrar interface {
Register(choice adaptive.RouteChoice, elapsedSeconds, completionTokens float64, err error) adaptive.AdaptiveRouteEvent
RegisterCanceled(choice adaptive.RouteChoice)
}
RouterRegistrar is the narrow slice of *adaptive.AdaptiveModelRouter this package uses to settle a route lease.
type SSEDecoder ¶
type SSEDecoder struct {
// contains filtered or unexported fields
}
SSEDecoder splits a byte stream into events.
func NewSSEDecoder ¶
func NewSSEDecoder(r io.Reader) *SSEDecoder
NewSSEDecoder wraps a reader. The buffer is generous because a single reasoning-heavy chunk can exceed the default 4 KiB line limit by a lot.
func (*SSEDecoder) Next ¶
func (d *SSEDecoder) Next() (SSEEvent, error)
Next returns the next event, or io.EOF when the stream ends.
type SSEEvent ¶
SSEEvent is one dispatched event. Only `data` is consumed downstream; `event` and `id` are decoded because the grammar requires skipping them correctly.
type SourcePart ¶
type SourcePart struct {
URL string
Title string
Content string
StartIndex float64
EndIndex float64
}
SourcePart's `id` is THE URL ITSELF, not a generated id.
func (SourcePart) MarshalJSON ¶
func (p SourcePart) MarshalJSON() ([]byte, error)
func (SourcePart) PartType ¶
func (p SourcePart) PartType() string
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream is one in-flight response. It is NOT safe for concurrent use; the one concurrency rule that matters is that Close may be called from another goroutine, which is exactly what an early teardown needs.
func (*Stream) Close ¶
Close abandons the stream. It cancels the request context first and only then closes the body as best-effort cleanup. Safe to call from another goroutine, and safe to call twice.
func (*Stream) Next ¶
func (s *Stream) Next() (StreamPart, error)
Next returns the next stream part. It returns io.EOF exactly once, after the `finish` part.
An abort — from any of the four layers — surfaces as `{type:"abort", reason:getErrorMessage(cause)}` and the stream then ends cleanly. The cause still goes to router registration, where its exact message is what the cooldown and retry classifiers substring-match.
func (*Stream) Parts ¶
func (s *Stream) Parts() ([]StreamPart, error)
Parts drains the whole stream. Convenience for callers that do not need incremental delivery — and for tests.
type StreamPart ¶
StreamPart is one emitted part.
type TextDeltaPart ¶
TextDeltaPart is `{type:"text-delta", delta, id}`.
func (TextDeltaPart) MarshalJSON ¶
func (p TextDeltaPart) MarshalJSON() ([]byte, error)
func (TextDeltaPart) PartType ¶
func (p TextDeltaPart) PartType() string
type TextEndPart ¶
type TextEndPart struct{ ID string }
TextEndPart is `{type:"text-end", id}`.
func (TextEndPart) MarshalJSON ¶
func (p TextEndPart) MarshalJSON() ([]byte, error)
func (TextEndPart) PartType ¶
func (p TextEndPart) PartType() string
type TextStartPart ¶
type TextStartPart struct{ ID string }
TextStartPart's id is the OpenRouter response id (`gen-…`) when one has been seen, else a freshly minted 16-char id.
func (TextStartPart) MarshalJSON ¶
func (p TextStartPart) MarshalJSON() ([]byte, error)
func (TextStartPart) PartType ¶
func (p TextStartPart) PartType() string
type TimeoutContextFactory ¶
type TimeoutContextFactory func(context.Context, time.Duration, error) (context.Context, context.CancelFunc)
TimeoutContextFactory mirrors context.WithTimeoutCause for the layer-2 total-request deadline. Keeping this as a separate seam preserves the production context's real Deadline while allowing tests to fire the deadline without waiting on wall-clock time.
type Timer ¶
type Timer interface{ Stop() }
Timer / TimerFactory are the reader watchdog's timer seam, so tests can drive it on a virtual clock.
type TimerFactory ¶
type Tool ¶
type Tool struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
ProviderOptions json.RawMessage `json:"providerOptions,omitempty"`
}
Tool is one registered tool as the request sees it. InputSchema is the JSON Schema the provider receives.
type ToolCallDelta ¶
type ToolCallDelta struct {
Index *float64
ID *string
Type *string
HasFunction bool
Name *string
Arguments *string
// Raw is the original entry, carried because InvalidResponseDataError
// reports it as `data`.
Raw json.RawMessage
}
ToolCallDelta is one `delta.tool_calls[]` entry. Every field may be absent; a missing `type` on a FIRST delta is an InvalidResponseDataError.
type ToolCallPart ¶
type ToolCallPart struct {
ToolCallID string
ToolName string
Input string
HasProviderMetadata bool
Details ReasoningDetailsView
}
ToolCallPart's `input` is the RAW accumulated argument STRING, not a parsed object. `providerMetadata` is attached to the FIRST tool call only; later calls omit the key entirely.
func (ToolCallPart) MarshalJSON ¶
func (p ToolCallPart) MarshalJSON() ([]byte, error)
func (ToolCallPart) PartType ¶
func (p ToolCallPart) PartType() string
type ToolCallRepairError ¶
ToolCallRepairError wraps an error returned by the repair callback.
func (*ToolCallRepairError) Error ¶
func (e *ToolCallRepairError) Error() string
type ToolChoice ¶
ToolChoice constrains which tool the model may call. senior-dev sends `{"type":"required"}` only for a json_schema output format; otherwise nil.
type ToolInputDeltaPart ¶
ToolInputDeltaPart is `{type:"tool-input-delta", id, delta}`: `id` first, unlike the text/reasoning deltas. The processor ignores it; it exists so a consumer can render arguments as they stream.
func (ToolInputDeltaPart) MarshalJSON ¶
func (p ToolInputDeltaPart) MarshalJSON() ([]byte, error)
func (ToolInputDeltaPart) PartType ¶
func (p ToolInputDeltaPart) PartType() string
type ToolInputEndPart ¶
type ToolInputEndPart struct{ ID string }
ToolInputEndPart is `{type:"tool-input-end", id}`.
func (ToolInputEndPart) MarshalJSON ¶
func (p ToolInputEndPart) MarshalJSON() ([]byte, error)
func (ToolInputEndPart) PartType ¶
func (p ToolInputEndPart) PartType() string
type ToolInputStartPart ¶
ToolInputStartPart is `{type:"tool-input-start", id, toolName}`.
func (ToolInputStartPart) MarshalJSON ¶
func (p ToolInputStartPart) MarshalJSON() ([]byte, error)
func (ToolInputStartPart) PartType ¶
func (p ToolInputStartPart) PartType() string
type ToolMap ¶
type ToolMap struct {
// contains filtered or unexported fields
}
ToolMap is the registered tool set in a fixed order. The order reaches the request body and the `invalid` tool's availableTools list, so it is part of what keeps prompt-cache keys stable.
func NewToolMap ¶
NewToolMap builds a map in the given order.
func SortedToolMap ¶
SortedToolMap builds a ToolMap sorted by tool name.
func (*ToolMap) ActiveTools ¶
ActiveTools is Names without the invalid tool: the set the model is offered.
type ToolSpec ¶
type ToolSpec struct {
Name string
// Validate checks a parsed input against the tool's schema. Nil accepts
// any value that parsed as JSON.
Validate func(input json.RawMessage) error
}
ToolSpec is one registered tool as the parser sees it.
type Translator ¶
type Translator struct {
// contains filtered or unexported fields
}
Translator is the per-stream state the chunk translation mutates.
func NewTranslator ¶
func NewTranslator() *Translator
NewTranslator builds the initial state. finishReason starts as `other`.
func (*Translator) FinishReasonSnapshot ¶
func (t *Translator) FinishReasonSnapshot() FinishReason
FinishReasonSnapshot exposes the running finish reason, so a caller can tell before flush whether a still-unsent tool call will ever arrive.
func (*Translator) Flush ¶
func (t *Translator) Flush() []StreamPart
Flush ends the stream: it flushes unsent tool calls, closes open reasoning and text, and emits the finish part.
func (*Translator) SetStreamError ¶
func (t *Translator) SetStreamError(value json.RawMessage)
SetStreamError records a mid-stream reader error. The stream CLOSES on it, so the error only shows up in flush.
func (*Translator) Transform ¶
func (t *Translator) Transform(chunk Chunk) ([]StreamPart, error)
Transform translates one decoded chunk. A returned error (only InvalidResponseDataError from the tool-call accumulator) tears the stream down; it is not an `error` part.
type TypeValidationError ¶
type TypeValidationError struct {
Value json.RawMessage
Cause error
Message string
}
TypeValidationError is what a Validate hook's rejection is wrapped in. Its message reaches the model verbatim through the `invalid` tool's input, in the format
Type validation failed: Value: <value JSON>.\nError message: <cause>
ToolSpec.Validate implementations should return one of these.
func NewTypeValidationError ¶
func NewTypeValidationError(value json.RawMessage, cause error) *TypeValidationError
NewTypeValidationError builds one in the format above.
func (*TypeValidationError) Error ¶
func (e *TypeValidationError) Error() string