orclient

package
v0.5.1-rc.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

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

View Source
const (
	CompatibilityCompatible = "compatible"
	CompatibilityStrict     = "strict"
)

Compatibility modes.

View Source
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.

View Source
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.

View Source
const (
	FinishStop          = "stop"
	FinishLength        = "length"
	FinishContentFilter = "content-filter"
	FinishToolCalls     = "tool-calls"
	FinishError         = "error"
	FinishOther         = "other"
)

Unified finish-reason values.

View Source
const (
	ReasoningDetailSummary   = "reasoning.summary"
	ReasoningDetailEncrypted = "reasoning.encrypted"
	ReasoningDetailText      = "reasoning.text"
)

Reasoning detail type tags.

View Source
const DefaultReasoningFormat = "anthropic-claude-v1"

DefaultReasoningFormat is assumed when a text detail names no format.

View Source
const DoneSentinel = "[DONE]"

DoneSentinel is the payload the stream reader drops before parsing.

View Source
const InvalidToolName = "invalid"

InvalidToolName is the tool repair rewrites an unrepairable call to.

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

View Source
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

func MapToUnified(finishReason string) string

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

func MaxOutputTokens(model Model) float64

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

func SanitizeSurrogates(content string) string

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

type AbortPart struct {
	Reason    string
	HasReason bool
}

AbortPart ends a stream that was cancelled. The reason, when present, is the cancellation cause's message.

func (AbortPart) MarshalJSON

func (p AbortPart) MarshalJSON() ([]byte, error)

func (AbortPart) PartType

func (p AbortPart) PartType() string

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 Choice

type Choice struct {
	Delta        *Delta
	FinishReason *string
}

Choice is `value.choices[i]`.

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

func ParseChunk(text string) Chunk

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.

func (*Client) DoStream

func (c *Client) DoStream(ctx context.Context, params RequestParams) (*Stream, error)

DoStream issues the request and returns a Stream positioned before the first part, with the abort layers and the router bookkeeping installed.

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

func (p ErrorPart) MarshalJSON() ([]byte, error)

func (ErrorPart) PartType

func (p ErrorPart) PartType() string

type Fetcher

type Fetcher func(req *http.Request) (*http.Response, error)

Fetcher performs one HTTP round trip. The default is a plain http.DefaultClient; the CLI installs its own configured client per Client.

type FilePart

type FilePart struct {
	MediaType string
	Data      string
}

FilePart is `{type:"file", mediaType, data}` from `delta.images[]`.

func (FilePart) MarshalJSON

func (p FilePart) MarshalJSON() ([]byte, error)

func (FilePart) PartType

func (p FilePart) PartType() string

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

type FinishReason struct {
	Unified string
	Raw     *string
}

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

type HeaderPair struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

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

type InvalidArgumentError struct {
	Argument string
	Message  string
}

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

type InvalidToolInputError struct {
	ToolName  string
	ToolInput string
	Cause     error
	Message   string
}

InvalidToolInputError reports an input that failed to parse or validate.

func (*InvalidToolInputError) Error

func (e *InvalidToolInputError) Error() string

type JSONParseError

type JSONParseError struct {
	Text    string
	Cause   error
	Message string
}

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 ModelAPI

type ModelAPI struct {
	Npm string `json:"npm"`
	ID  string `json:"id"`
}

ModelAPI is the `api` sub-object of a catalog model.

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

type NoSuchToolError struct {
	ToolName       string
	AvailableTools []string
	Message        string
}

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

func MergeOptions(target, source *Object) *Object

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 NewObject

func NewObject() *Object

NewObject builds an empty ordered object.

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

func ParseObject(raw []byte) (*Object, error)

ParseObject decodes a JSON object literal into an ordered Object. Empty input yields an empty object.

func ProviderOptions

func ProviderOptions(model Model, options *Object) *Object

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) Clone

func (o *Object) Clone() *Object

Clone is a shallow copy (values are immutable in this model).

func (*Object) Get

func (o *Object) Get(key string) (json.RawMessage, bool)

Get returns the raw JSON of one key.

func (*Object) Has

func (o *Object) Has(key string) bool

Has reports whether key is present.

func (*Object) Keys

func (o *Object) Keys() []string

Keys returns the keys in insertion order.

func (*Object) Len

func (o *Object) Len() int

Len is the number of keys.

func (*Object) MarshalJSON

func (o *Object) MarshalJSON() ([]byte, error)

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) SetArray

func (o *Object) SetArray(key string, items []*Object)

SetArray assigns an array of already-built ordered objects.

func (*Object) SetBool

func (o *Object) SetBool(key string, value bool)

SetBool assigns a boolean value.

func (*Object) SetNumber

func (o *Object) SetNumber(key string, value float64)

SetNumber assigns a numeric value.

func (*Object) SetNumberPtr

func (o *Object) SetNumberPtr(key string, value *float64)

SetNumberPtr assigns a number, or leaves the key absent when value is nil.

func (*Object) SetObject

func (o *Object) SetObject(key string, value *Object)

SetObject assigns a nested ordered object.

func (*Object) SetString

func (o *Object) SetString(key, value string)

SetString assigns a string value.

func (*Object) Without

func (o *Object) Without(keys ...string) *Object

Without is a copy of the object with the named keys left out, in the order the rest were set.

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

type OptionsInput struct {
	Model     Model  `json:"model"`
	SessionID string `json:"sessionID"`
}

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:

  1. 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.
  2. otherwise → rewrite to `toolName:"invalid"` with `input: {tool, error}` encoded as a raw JSON STRING, which is what RawToolCall.Input holds.

type ReasoningDeltaPart

type ReasoningDeltaPart struct {
	Delta string
	ID    string
}

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

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

type SSEEvent struct {
	Event string
	Data  string
	ID    string
}

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

func (s *Stream) Close() error

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.

func (*Stream) Response

func (s *Stream) Response() *http.Response

Response exposes the HTTP response (status + headers) for the error taxonomy the caller layers on top. The body is owned by the Stream.

type StreamPart

type StreamPart interface {
	PartType() string
	json.Marshaler
}

StreamPart is one emitted part.

type TextDeltaPart

type TextDeltaPart struct {
	Delta string
	ID    string
}

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 TimerFactory func(ms float64, fn func()) Timer

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

type ToolCallRepairError struct {
	Cause         error
	OriginalError error
	Message       string
}

ToolCallRepairError wraps an error returned by the repair callback.

func (*ToolCallRepairError) Error

func (e *ToolCallRepairError) Error() string

type ToolChoice

type ToolChoice struct {
	Type     string `json:"type"`
	ToolName string `json:"toolName,omitempty"`
}

ToolChoice constrains which tool the model may call. senior-dev sends `{"type":"required"}` only for a json_schema output format; otherwise nil.

type ToolInputDeltaPart

type ToolInputDeltaPart struct {
	ID    string
	Delta string
}

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

type ToolInputStartPart struct {
	ID       string
	ToolName string
}

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

func NewToolMap(specs ...ToolSpec) *ToolMap

NewToolMap builds a map in the given order.

func SortedToolMap

func SortedToolMap(specs ...ToolSpec) *ToolMap

SortedToolMap builds a ToolMap sorted by tool name.

func (*ToolMap) ActiveTools

func (m *ToolMap) ActiveTools() []string

ActiveTools is Names without the invalid tool: the set the model is offered.

func (*ToolMap) Get

func (m *ToolMap) Get(name string) (ToolSpec, bool)

Get looks up a tool.

func (*ToolMap) Names

func (m *ToolMap) Names() []string

Names lists the tools in map order.

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL