Documentation
¶
Overview ¶
This file implements the Anthropic Messages API streaming decoder (US-008). It is a stateful Decoder (see transport.go) that translates Anthropic SSE event payloads into the provider-agnostic AssistantMessageEvent set, accumulating a partial AssistantMessage as deltas arrive.
Anthropic streams a fixed event sequence:
message_start → seeds id/model and initial usage (input tokens)
content_block_start → opens a text / thinking / tool_use block at an index
content_block_delta → text_delta / thinking_delta / signature_delta /
input_json_delta append to the open block
content_block_stop → closes the block (tool_use JSON is parsed here)
message_delta → carries the final stop_reason and output-token usage
message_stop → terminal; the accumulated message is emitted as done
error → a runtime error payload → terminal error event
Per the dual failure model (FR-13) the decoder never panics: malformed payloads and Anthropic `error` events are surfaced as a returned error (which the transport turns into a terminal StreamErrorEvent) rather than crashing.
This file implements credential resolution (US-012): API key lookup from environment variables and a config file (per provider), plus an OAuth token source that refreshes short-lived tokens on expiry. The resolver satisfies the LoopConfig.GetAPIKey shape (func(ctx, provider) string) so the agent loop can obtain a fresh key per request.
Security (FR: secret values are not written to logs): secret values are never logged or embedded in error messages. Errors and String()/redaction helpers reference credentials by key name / provider only.
This file holds the shared disk-cache plumbing for the two model catalogs golder keeps under the golder home directory:
- the models.dev reasoning ladder catalog (reasoning_catalog.go)
- the provider model list (modelcatalog.go)
Both are JSON files written opportunistically and read best-effort: a missing, unreadable, or corrupt file never surfaces an error, it just means "no cache". Both share one freshness window (catalogTTL) so a long-lived install refreshes its provider metadata about once a day in total.
This file implements the credential-hygiene primitives from issue #568 (ported from dsh's credential handling):
- ScrubEnv: derive a child-process environment with credential-shaped variables removed, so a spawned process (sub-agent, plugin) does not inherit secrets it does not need. Mirrors dsh's stdio bridge, which "deliberately removes ambient variables whose names usually identify credentials and all DSH_* variables"; golder scrubs credential-shaped names and all GOLDER_* internals. Explicit allow entries are re-injected last, so a parent can still hand a child exactly the one key it needs.
- CredentialFile: a named-credential store at $GOLDER_HOME/.credentials.yaml (mode 0600). Config files reference a credential by NAME; the literal secret lives only here, mirroring dsh's write-only keys + "settings retain only its credential reference".
This file persists the provider model lists fetched by /model (issue #566), so the list is requested about once per catalogTTL (24h) per provider and endpoint instead of once per session. It shares the file plumbing and the TTL with the reasoning ladder catalog (catalogcache.go), keeping the two caches one mechanism.
A cache key is the provider name plus the effective base URL, so a --base-url override (or a self-hosted endpoint) never serves another endpoint's list. Entries are independent: each carries its own checked_at.
This file implements online model discovery (issue #566, ported from dsh's "Fetch available models"): asking an endpoint which models it serves, so the /model catalog and switching can follow a provider's real lineup.
Both wire formats golder speaks expose a plain GET for it:
- OpenAI-compatible (and Responses) gateways: GET {base}/models
- Anthropic Messages: GET {base}/v1/models
and both answer {"data":[{"id": ...}]}, so one parser serves both. Auth follows each protocol's request convention (Bearer for OpenAI-compatible, x-api-key + anthropic-version for Anthropic). A local endpoint that needs no key is fine: the key is sent only when non-empty.
This file implements the OpenAI-compatible streaming decoder (US-009): a stateful Decoder (see transport.go) for the OpenAI Chat Completions SSE stream, which is also the wire format spoken by most third-party gateways (OpenRouter, Groq, together, local servers, …). Selecting the base URL is a transport concern (NewRequest builds the *http.Request), so this decoder is base-URL agnostic and reused across every OpenAI-compatible provider.
OpenAI streams a sequence of chat.completion.chunk objects:
{"choices":[{"delta":{"role":"assistant"}}]} → first chunk
{"choices":[{"delta":{"content":"Hel"}}]} → text delta
{"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_1",
"function":{"name":"f","arguments":"{\"a\":"}}}]}]} → tool-call delta
{"choices":[{"finish_reason":"tool_calls"}]} → stop reason
{"usage":{"prompt_tokens":10,"completion_tokens":5}} → final usage
[DONE] → transport-level
Per the dual failure model (FR-13) the decoder never panics: malformed payloads surface as a returned error which the transport turns into a terminal StreamErrorEvent.
This file defines the provider streaming abstraction (US-003/US-007 base): the StreamFn contract, the per-delta AssistantMessageEvent set, and the AssistantMessageEventStream (a specialization of EventStream) that a provider pushes deltas onto while yielding a final AssistantMessage.
Contract (FR-13): a StreamFn never expresses a request failure by returning an error. Runtime failures are encoded as an error event plus a terminal assistant message (stopReason=error/aborted + errorMessage). The returned error is reserved for the earliest "could not even build the stream" case.
This file defines the unified Provider interface and its dual failure model (US-007). A Provider turns a CompletionRequest into a stream of AssistantMessageEvents. Failures follow the same contract as StreamFn (FR-13):
- "cannot even build the stream" (bad config, missing model) → returned error.
- any runtime failure once streaming has begun → a terminal StreamErrorEvent carrying an assistant message with stopReason=error/aborted, after which the stream is closed. It is never a returned error.
Model carries provider-agnostic capability metadata so the loop and UI can reason about a model without knowing the concrete provider.
This file implements the first concrete Providers (US-013): Bedrock, OpenRouter, and Ollama. Each satisfies the Provider interface by building an *http.Request and delegating to the shared transport (StreamRequest) with a provider-appropriate Decoder — no bespoke HTTP/SSE handling per provider.
Two backing driver shapes cover all three:
- openAICompatDriver — POSTs to {baseURL}/chat/completions in the OpenAI Chat Completions wire format, decoded by OpenAIDecoder. OpenRouter and Ollama are instances of it; it is the generic OpenAI-compatible layer, reusable for any gateway (Groq, together, local servers, …).
- anthropicCompatDriver — POSTs the Anthropic Messages wire format, decoded by AnthropicDecoder. Bedrock rides this: Anthropic-on-Bedrock speaks the Messages API, so the decoder is reused wholesale.
Failures follow the dual failure model (FR-13): only the earliest "cannot build the stream" case (missing key, bad request construction) is a returned error; every runtime failure rides the stream as a terminal error event, which StreamRequest already guarantees.
Security (US-012 / US-026): API keys are referenced by provider name in any error; secret values are never logged or embedded in error text.
This file owns everything about reasoning effort: the mapping from golder's unified ThinkingLevel onto each gateway's wire effort value, and the models.dev catalog that says which levels a model actually advertises.
The mapping half is pure: no IO, no global state. The catalog half fetches and caches models.dev's metadata, because the gateways golder talks to do not expose it themselves (opencode-go's /models returns only id/object/created/owned_by). They live together because they are one decision: WireReasoningEffort reads the catalog and falls back to the hand-maintained ladders in this same file.
golder's unified ladder (off < minimal < low < medium < high < xhigh < max) is wider than any single gateway's. Every wire driver — Chat Completions (`reasoning_effort`) and Responses (`reasoning.effort`) — calls WireReasoningEffort, so a model is mapped identically no matter which protocol carries its request.
A model's accepted ladder resolves in layers:
- the models.dev catalog entry for (provider, model), when it has one,
- the provider's own ReasoningLadders (registry.go), for gateway quirks,
- sharedReasoningLadders, for model families that behave the same on every gateway,
- defaultReasoningLadder, the conservative common denominator.
A requested level above the ladder's top clamps down to its top rung (a model with a real max keeps it); a level below its bottom clamps up to the bottom rung. off/empty omits the field so the gateway keeps its own default, and a catalog model marked as non-reasoning gets no effort field at all.
The catalog is one JSON document keyed by provider. golder extracts only the providers it can route to (the built-in registry, using each spec's ModelsDevID) and keeps just each model's reasoning flag plus its effort-type values, so a full fetch is a few tens of KB on disk. It is cached at $GOLDER_HOME/reasoning-catalog.json (or ~/.golder/reasoning-catalog.json) with the shared catalogTTL: a session never blocks on the network for it, and the fetch happens at most once per TTL instead of once per session. Refreshes are conditional (If-None-Match), so an unchanged upstream catalog answers 304 and costs no body. Missing, stale, or corrupt caches degrade silently to the hand-maintained ladders.
This file defines the central provider registry (US-001): a single source of truth for every built-in provider — its name, the environment variables that carry its API key (in precedence order), its default base URL, wire protocol, auth scheme, extra headers, provider-specific base-URL override env vars, and its model-facing behavior (which model-id prefixes infer it, which models.dev entry describes it, and any reasoning-effort ladder quirks its gateway has).
Adding or supporting a provider should be ONE entry in providerRegistry: transport metadata first, then the optional behavior fields. Anything a provider does not declare falls back to the shared defaults — ResolveProvider builds the driver from the spec, InferProviderFromModel reads ModelPrefixes, the reasoning catalog reads ModelsDevID, and WireReasoningEffort consults ReasoningLadders before the model-family table in reasoning.go. The only per-provider code left is special-auth composition (special_auth.go), which needs request signing rather than metadata.
The registry is deliberately additive and self-contained: later nodes wire auth resolution (auth.go), the --provider flag (main.go), base_url overrides, and per-provider construction (providers.go) to READ from it. This node only introduces the data + a lookup, so it does not change existing behavior.
Data source: the PRD "Technical Considerations" table (tasks/prd-provider-env-parity.md), derived from pi's env-api-keys.ts and the per-provider *.models.ts files.
Security: the registry holds only env var NAMES, never secret values. Keys are resolved from the environment at request time (see auth.go) and never logged.
This file implements the OpenAI Responses API driver (US-003, #539), the backing for --protocol openai/resp_api. Unlike openAICompatDriver (which hand-rolls the Chat Completions wire format), this driver speaks the Responses API (POST {base_url}/responses) via the official github.com/openai/openai-go SDK.
This milestone covers streaming text: a plain prompt in, assistant text out, consumed from the Responses SSE stream and mapped into golder's AssistantMessage the same way OpenAIDecoder does (API/Provider tags, Usage, ResponseID/ResponseModel, StopReason=end_turn). Tools (#541) and images/reasoning (#542) layer on later.
Failure model (FR-13): only the earliest "cannot build the stream" case (missing API key) is a returned error. Every runtime failure — including a non-2xx from the endpoint — rides the returned stream as a terminal StreamErrorEvent, matching the chat driver's observable behavior.
Base URL + auth: the SDK client is pointed at the resolved base_url and given the resolved key via option.WithBaseURL / option.WithAPIKey rather than reading the environment, so ResolveBaseURL precedence is preserved. A custom *http.Client may be injected (option.WithHTTPClient) for tests.
This file implements parameter validation and endpoint construction for the "special auth" providers (US-007 / FR-12): Azure OpenAI, Amazon Bedrock, Google Vertex, and Cloudflare (Workers AI + AI Gateway). Unlike the generic bearer/x-api-key providers, each of these composes its endpoint from several environment variables and/or needs a non-standard credential path, so a dedicated resolver validates the required parameters and builds the concrete base URL before handing off to the shared OpenAI-/Anthropic-compatible driver.
Scope note (PRD Non-Goals): AWS SigV4 request signing is NOT implemented. Bedrock supports only the AWS_BEARER_TOKEN_BEDROCK bearer path; other AWS credential sources (AWS_PROFILE, AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY) are only *detected* so that a clear, actionable error is returned instead of an opaque auth failure.
Security: this file reads env var NAMES and composes URLs from non-secret parameters (region, resource name, account id, …). Secret values (API keys, bearer tokens) are never logged or embedded in error text — errors name the absent env var, never a value.
This file implements the shared transport driver (US-007 support): a provider-agnostic layer that turns an HTTP request into a stream of StreamEvents. Each provider degenerates to a stateful Decoder; the transport owns HTTP + SSE line parsing + retry + dual watchdogs + the dual failure model.
The design mirrors pi's providerio: the transport never returns a runtime failure as a Go error once streaming has begun — it rides the stream as a terminal StreamErrorEvent. Only the earliest "cannot build the stream" case (bad request construction) is a returned error.
Index ¶
- Constants
- Variables
- func APIKeyEnvHint(name string) string
- func BaseURLEnvVars(spec ProviderSpec) []string
- func CachedModelCatalog(key string) (ids []string, fresh bool)
- func CanonicalizeModel(model string) string
- func ContextWindowFor(providerName, model string) int
- func CredentialFilePath() string
- func CredentialFilePermissionsWarn(path string) bool
- func DisplayURL(raw string) string
- func EffectiveProtocol(providerName, model, protocol string) string
- func EnsureReasoningCatalog(ctx context.Context) error
- func EnvCredentialVar(provider string) (string, bool)
- func FetchProviderModels(ctx context.Context, providerName, baseURL, protocol, apiKey string) ([]string, error)
- func FetchRemoteModels(ctx context.Context, baseURL, protocol, apiKey string) ([]string, error)
- func InferProviderFromModel(model string) (string, bool)
- func IsProviderName(id string) bool
- func IsSpecialAuthProvider(spec ProviderSpec) bool
- func KnownReasoningLevels(providerName, model string) []agentcore.ThinkingLevel
- func LoadCredentialFile(path string) (map[string]string, error)
- func ModelCatalogCacheKey(providerName, baseURL string) string
- func NeedsProxy(name, rawURL string) bool
- func NewOpenAIResponsesProvider(name, baseURL string, models []Model) *responsesDriver
- func NormalizeProtocol(raw string) (string, error)
- func PreferResponses(modelID string) bool
- func ProtocolLabel(raw string) string
- func ProviderNames() []string
- func ProxySettings() (config.ProxyConfig, error)
- func ProxyStatus(name, baseURL string) string
- func ProxyURL() string
- func ReasoningLevels(providerName, model string) []agentcore.ThinkingLevel
- func ResolveBaseURL(spec ProviderSpec, flagBaseURL string, env func(string) string) string
- func ResolveCredentialReference(path, name string) (string, error)
- func ScrubEnv(parent []string, allow map[string]string) []string
- func ScrubsCredentialVar(name string) bool
- func SessionHeaderValue(providerName string, extra map[string]any) string
- func StartBackgroundReasoningCatalogRefresh()
- func StoreModelCatalog(key string, ids []string)
- func ValidateProxyURL(raw string) error
- func WireReasoningEffort(providerName, model string, level agentcore.ThinkingLevel) string
- func WithSessionExtra(extra map[string]any, sid string) map[string]any
- type APIKeyConfig
- type AnthropicDecoder
- type AssistantMessageEvent
- type AssistantMessageEventStream
- type CompletionRequest
- type CredentialStatus
- type CredentialStore
- func (c *CredentialStore) GetAPIKey(ctx context.Context, provider string) string
- func (c *CredentialStore) HasCredential(ctx context.Context, provider string) bool
- func (c *CredentialStore) RegisterOAuth(provider string, src *TokenSource)
- func (c *CredentialStore) SetOverride(provider, key string)
- func (c *CredentialStore) Status(provider string) CredentialStatus
- type Decoder
- type LlmContext
- type Model
- type OAuthToken
- type OpenAIDecoder
- type Provider
- func NewAnthropicProtocolProvider(name, baseURL, authScheme string, models []Model) Provider
- func NewAnthropicProvider(baseURL string, models []Model) Provider
- func NewBedrockProvider(baseURL string, models []Model) Provider
- func NewNvidiaProvider(baseURL string, models []Model) Provider
- func NewOllamaProvider(baseURL string, models []Model) Provider
- func NewOpenAICompatibleProvider(baseURL string, models []Model) Provider
- func NewOpenAICompatibleProviderAs(name, baseURL string, models []Model) Provider
- func NewOpenRouterProvider(baseURL string, models []Model) Provider
- func ResolveNamedProvider(name, model, baseURL, protocol string, env func(string) string) (Provider, string, error)
- func ResolveProvider(model, baseURL, protocol, providerName string, env func(string) string) (Provider, string, error)
- func ResolveSpecialProvider(spec ProviderSpec, model, flagBaseURL string, env func(string) string) (Provider, error)
- type ProviderSpec
- type ReasoningLadder
- type StreamConfig
- type StreamDoneEvent
- type StreamErrorEvent
- type StreamEvent
- type StreamFn
- type StreamStartEvent
- type StreamTextEvent
- type StreamThinkingEvent
- type StreamToolCallEvent
- type TokenSource
- type TransportConfig
Constants ¶
const ( StreamEventStart = "start" StreamEventText = "text" StreamEventThinking = "thinking" StreamEventToolCall = "toolcall" StreamEventDone = "done" StreamEventError = "error" )
AssistantMessageEvent kinds.
const ( ProtocolOpenAI = "openai" ProtocolAnthropic = "anthropic" )
Protocol values.
const ( AuthBearer = "bearer" AuthXAPIKey = "x-api-key" AuthAWS = "aws" AuthAzure = "azure" AuthSpecial = "special" )
AuthScheme values.
const ExtraSessionID = "session_id"
ExtraSessionID is the StreamConfig.Extra key carrying the golder session id (see RunConfig.SessionID) from the loop to the provider drivers. It is opaque to every other layer.
const OpencodeSessionHeader = "x-opencode-session"
OpencodeSessionHeader carries the client session id to opencode's Zen endpoints. The Go endpoint (modelList "lite") rejects requests without it (400 MissingSessionID: "cannot be routed efficiently"), using the value for sticky upstream routing. The full Zen endpoint tolerates its absence.
const ProtocolOpenAIResponses = "openai/resp_api"
ProtocolOpenAIResponses is the canonical selector for the OpenAI Responses API wire format (POST {base_url}/responses). It is distinct from ProtocolOpenAI (Chat Completions) so ResolveProvider can route to the SDK-based Responses driver.
Variables ¶
var ProcessSessionID = sync.OnceValue(func() string { return fmt.Sprintf("golder-%d-%d", os.Getpid(), time.Now().Unix()) })
ProcessSessionID returns a stable per-process session identity for callers that need the opencode sticky-routing header but have no real session id (e.g. the risk reviewer, or a process-isolated sub-agent). Sharing one affinity bucket with the process's runs is the desired routing behavior there anyway.
Functions ¶
func APIKeyEnvHint ¶
APIKeyEnvHint returns a human-readable hint naming the environment variable(s) that satisfy the provider's credential check, for embedding in missing-key errors (issue #564: "openrouter: missing API key" alone leaves the user guessing what to set). Unknown providers fall back to the generic <PROVIDER>_API_KEY convention.
func BaseURLEnvVars ¶
func BaseURLEnvVars(spec ProviderSpec) []string
BaseURLEnvVars lists endpoint override variables in resolution order.
func CachedModelCatalog ¶
CachedModelCatalog returns the cached list for key and whether it is still fresh (younger than catalogTTL). A nil list with fresh=false means "no usable cache": either nothing was ever stored, or the stored entry is stale (callers may still use the list as a fallback when a refetch fails).
func CanonicalizeModel ¶
CanonicalizeModel maps a bare built-in provider name (e.g. "zai", "DEEPSEEK") to that provider's DefaultModel (e.g. "glm-4.7"), so a user who types the provider alone gets its default model instead of the literal id being sent to OpenRouter (issue #564). Any other id — a real model id, an unknown name, a "provider/model" routed id — is returned unchanged.
The mapping is exact-match on the trimmed, lowercased id against the built-in provider registry; a model id that happens to equal a provider name is not a meaningful OpenRouter id, so the ambiguity is harmless.
func ContextWindowFor ¶ added in v1.2.13
ContextWindowFor returns the context-token budget models.dev lists for (providerName, model), or 0 when it has no value. Lookups read the reasoning catalog's in-memory/disk copy, so they never block on the network; callers fall back to cli.DefaultContextWindow on 0 (unknown provider, custom base-URL model, or a catalog that has not been fetched yet). A case-only mismatch between the gateway's model id and models.dev's brand casing still matches.
func CredentialFilePath ¶
func CredentialFilePath() string
CredentialFilePath returns the named-credential file location: $GOLDER_HOME/.credentials.yaml when GOLDER_HOME is set, else ~/.golder/.credentials.yaml. An unresolvable home returns "".
func CredentialFilePermissionsWarn ¶
CredentialFilePermissionsWarn reports whether the credential file's mode is more permissive than 0600 (group/other access), so the caller can warn without failing.
func DisplayURL ¶
DisplayURL omits credentials, query strings and fragments from UI output.
func EffectiveProtocol ¶
EffectiveProtocol reports the wire protocol a run will actually speak for the given provider selection, model id, and explicit --protocol flag. It mirrors ResolveNamedProvider's effective-protocol computation without its driver construction or errors, so the startup banner can display the real wire format (a multi-protocol gateway such as opencode-go shows openai/resp_api for a deepseek model and openai/chat for anything else). An explicit protocol wins when it is valid and compatible with the provider; otherwise the provider's registry default applies, upgraded to Responses for Responses-family models on OpenAI-wired providers. Unknown providers, unknown protocols, and incompatible pairs yield "" so the caller falls back to its placeholder instead of displaying a misleading value.
func EnsureReasoningCatalog ¶
EnsureReasoningCatalog refreshes the catalog when the in-memory or on-disk copy is missing or older than the TTL, and loads it into memory. Concurrent callers serialize; the loser of the race re-checks freshness and returns without a second fetch. Network errors leave the existing cache untouched.
func EnvCredentialVar ¶
EnvCredentialVar reports the name of the environment variable holding a credential for a provider, without returning its value. Candidates come from the provider registry (EnvVars, in precedence order), then the generic <PROVIDER>_API_KEY fallback. A variable that is set but empty does not count. The name is safe to display; the value never leaves this call.
func FetchProviderModels ¶
func FetchProviderModels(ctx context.Context, providerName, baseURL, protocol, apiKey string) ([]string, error)
FetchProviderModels uses the same provider identity for routing as chat requests, even when baseURL points at a user-configured relay.
func FetchRemoteModels ¶
FetchRemoteModels queries baseURL (with protocol selecting the URL shape and auth header convention) for its model catalog and returns the ids sorted and deduplicated. protocol may be empty, a canonical value ("openai", "openai/resp_api", "anthropic"), or a raw alias — it is normalized first, and the Responses protocol shares the Chat Completions /models path. An empty apiKey sends no auth header (local gateways like Ollama need none).
func InferProviderFromModel ¶
InferProviderFromModel guesses the built-in provider name that serves a given model id, based on the id's well-known name prefix (e.g. "claude-*" → anthropic, "deepseek-*" → deepseek). This is what lets `golder -m claude-opus-4-8` reach the Anthropic API without the user spelling out the provider or its wire protocol (ResolveProvider step 3).
The mapping is deliberately conservative: only names that unambiguously identify a single built-in provider are inferred. Ambiguous families served by many gateways (notably "llama-*", "qwq-*", "gemma-*", "mixtral-*") are NOT inferred — they return ok=false so the caller falls through to its existing default (OpenRouter). The prefixes live in the registry (ProviderSpec.ModelPrefixes), in registry order; a test pins that no two providers claim overlapping prefixes, so the order only matters for determinism. Matching is case-insensitive.
func IsProviderName ¶
IsProviderName reports whether id is exactly a built-in provider name (case-insensitive, surrounding whitespace tolerated).
func IsSpecialAuthProvider ¶
func IsSpecialAuthProvider(spec ProviderSpec) bool
IsSpecialAuthProvider reports whether a provider spec needs the bespoke endpoint-construction / credential-validation handled by ResolveSpecialProvider, rather than the generic driver wiring. It matches the multi-parameter auth schemes (azure/aws/special) and the two Cloudflare providers (which keep a standard auth scheme but still compose their endpoint from env vars).
func KnownReasoningLevels ¶
func KnownReasoningLevels(providerName, model string) []agentcore.ThinkingLevel
KnownReasoningLevels returns the catalog's explicit effort list for a model, or nil when the catalog has nothing definitive (unknown model, no effort option, or no reasoning). Unlike ReasoningLevels it never falls back to the family table, so callers can tell "models.dev says so" apart from "unknown" when annotating UI.
func LoadCredentialFile ¶
LoadCredentialFile reads a named-credential file (YAML mapping of name → secret). The file is expected to be mode 0600; callers can use CredentialFilePermissionsWarn to check.
func ModelCatalogCacheKey ¶
ModelCatalogCacheKey identifies a model catalog by provider and effective endpoint. The base URL is normalized (trimmed, trailing slash dropped) so the same endpoint always maps to one entry.
func NeedsProxy ¶
func NewOpenAIResponsesProvider ¶
NewOpenAIResponsesProvider builds a Responses API provider targeting baseURL. baseURL must be the fully resolved endpoint (e.g. https://api.openai.com/v1); the SDK appends the /responses path.
func NormalizeProtocol ¶
NormalizeProtocol maps a raw --protocol / protocol value to a canonical internal selector. Input is trimmed and lower-cased before matching. An empty value stays empty (unset → model-id heuristics). Recognized values normalize to ProtocolOpenAI, ProtocolOpenAIResponses, or ProtocolAnthropic. Any other value is an error naming the accepted set, so a typo surfaces to the caller for exit-code mapping instead of silently falling through.
func PreferResponses ¶
PreferResponses reports whether modelID belongs to a Responses-preferred family. It is a pure naming heuristic (mirroring opencode's own shouldUseResponsesApi): an explicit --protocol always wins over it, and it never applies to Anthropic-wired providers (ResolveNamedProvider guards that), so a Claude id can never be routed to the Responses driver here.
func ProtocolLabel ¶
ProtocolLabel maps a raw --protocol value to the human-facing label shown in the startup banner's Protocol row, so the displayed wire format matches what golder actually speaks. It differs from NormalizeProtocol in one deliberate way: the bare "openai" input is surfaced as "openai/chat", making the Chat Completions variant explicit rather than ambiguous. "openai/resp_api" and "anthropic" pass through as themselves.
An empty input returns empty (the banner then falls back to "—" or the provider name, so an unset protocol on a named/inferred provider is not mislabeled). An unrecognized value returns the trimmed input verbatim — the label is presentation-only and must never fail; a real typo is already rejected upstream by NormalizeProtocol during resolution.
func ProviderNames ¶
func ProviderNames() []string
ProviderNames returns all built-in provider names in registry order.
func ProxySettings ¶
func ProxySettings() (config.ProxyConfig, error)
ProxySettings returns the user's provider selection, preserving the old OpenCode defaults until the first explicit selection is saved.
func ProxyStatus ¶
func ProxyURL ¶
func ProxyURL() string
ProxyURL is an address only; setting it never enables every provider.
func ReasoningLevels ¶
func ReasoningLevels(providerName, model string) []agentcore.ThinkingLevel
ReasoningLevels returns the selectable reasoning levels for a model: the models.dev catalog's effort list when it has one, else the family ladder. An empty (nil) return means the catalog says the model does not reason, so there is nothing to select and the wire sends no effort field. The result is never mutated; callers must not modify it.
func ResolveBaseURL ¶
func ResolveBaseURL(spec ProviderSpec, flagBaseURL string, env func(string) string) string
ResolveBaseURL determines the effective base URL for a selected provider, applying the base_url override precedence (US-004 / FR-8, FR-9). The first non-empty source wins, in this order:
- flagBaseURL — the explicit --base-url/-u flag (highest).
- provider-specific base-url env var(s) from spec.BaseURLEnvVars, in the order the registry declares them (e.g. AZURE_OPENAI_BASE_URL).
- the generic <PROVIDER>_BASE_URL env var, where <PROVIDER> is the provider name uppercased with '-' rewritten to '_' (e.g. zai-coding-cn → ZAI_CODING_CN_BASE_URL).
- spec.DefaultBaseURL — the registry default (lowest).
Values are trimmed of surrounding whitespace before the non-empty check, so a whitespace-only env var does not shadow a lower-precedence source. Environment lookups go through the injected env func so callers control the environment.
func ResolveCredentialReference ¶
ResolveCredentialReference loads the named-credential file and returns the secret stored under name. Config files should carry the name only; the literal secret exists here alone.
func ScrubEnv ¶
ScrubEnv filters a parent environment for a child process: every credential-shaped variable and every GOLDER_* internal is dropped, then the allow entries are appended (so a parent can hand a child exactly the one credential it needs, under a name that would otherwise be scrubbed). Malformed parent entries (no "=") are dropped. The result is safe to assign to os/exec's cmd.Env.
func ScrubsCredentialVar ¶
ScrubsCredentialVar reports whether an environment variable name is credential-shaped and should be stripped from child-process environments. All GOLDER_* internals are scrubbed too (they are golder's own state, not a child's business); PATH/HOME and other ordinary names never match.
func SessionHeaderValue ¶
SessionHeaderValue returns the x-opencode-session value for a request, or "" when no header should be sent. Only providers whose name starts with "opencode" ever send it (mirroring opencode's own client, which gates on model.providerID.startsWith("opencode")); every other provider ignores the session id entirely.
func StartBackgroundReasoningCatalogRefresh ¶
func StartBackgroundReasoningCatalogRefresh()
StartBackgroundReasoningCatalogRefresh refreshes a stale or missing catalog in a goroutine so startup never blocks on models.dev. It returns immediately and swallows errors: a failed refresh leaves the previous cache in place.
func StoreModelCatalog ¶
StoreModelCatalog records a freshly fetched list for key.
func ValidateProxyURL ¶
func WireReasoningEffort ¶
func WireReasoningEffort(providerName, model string, level agentcore.ThinkingLevel) string
WireReasoningEffort maps the unified level onto the effort value to send for model. Both the Chat Completions and the Responses driver call it, so they agree by construction. providerName keys the models.dev catalog; an empty providerName simply skips the catalog lookup. An empty return means "omit the field": off/unset (or an unknown level) leaves the gateway's default behavior untouched, and a catalog model marked non-reasoning is never sent an effort at all.
func WithSessionExtra ¶
WithSessionExtra returns a copy of extra carrying the session id under ExtraSessionID. It is a no-op (returns extra unchanged) when sid is empty or an id is already present, so an explicit value always wins and shared maps are never mutated.
Types ¶
type APIKeyConfig ¶
type APIKeyConfig struct {
// Keys maps provider name → API key.
Keys map[string]string `json:"keys"`
}
APIKeyConfig is the on-disk config-file shape: a map of provider name to API key. It is parsed from JSON and holds only static keys (OAuth lives in TokenSource). Values are secrets and must not be logged.
func LoadAPIKeyConfig ¶
func LoadAPIKeyConfig(data []byte) (*APIKeyConfig, error)
LoadAPIKeyConfig parses an APIKeyConfig from JSON bytes (e.g. a config file).
func LoadAPIKeyConfigFile ¶
func LoadAPIKeyConfigFile(path string) (*APIKeyConfig, error)
LoadAPIKeyConfigFile reads and parses an APIKeyConfig from a file path. A missing file is not an error — it returns an empty config so env/OAuth can still resolve keys.
type AnthropicDecoder ¶
type AnthropicDecoder struct {
// contains filtered or unexported fields
}
AnthropicDecoder is the stateful SSE decoder for the Anthropic Messages API. It implements the transport Decoder interface. It is not safe for concurrent use — the transport drives it from a single goroutine.
func NewAnthropicDecoder ¶
func NewAnthropicDecoder() *AnthropicDecoder
NewAnthropicDecoder builds a fresh decoder for one streamed response.
func (*AnthropicDecoder) Decode ¶
func (d *AnthropicDecoder) Decode(payload []byte) ([]StreamEvent, error)
Decode turns one Anthropic SSE data payload into zero or more StreamEvents.
func (*AnthropicDecoder) Finish ¶
func (d *AnthropicDecoder) Finish() ([]StreamEvent, error)
Finish flushes a terminal done event if the stream ended without an explicit message_stop (e.g. a clean EOF mid-stream), so a partial response is still delivered rather than lost.
type AssistantMessageEvent ¶
type AssistantMessageEvent interface {
// EventKind returns the delta discriminant.
EventKind() string
// contains filtered or unexported methods
}
AssistantMessageEvent is the sealed interface for provider stream deltas. The loop dispatches on EventKind; the raw event is also surfaced to consumers via MessageUpdateEvent.AssistantMessageEvent.
type AssistantMessageEventStream ¶
type AssistantMessageEventStream = agentcore.EventStream[AssistantMessageEvent, agentcore.AssistantMessage]
AssistantMessageEventStream is the provider-level stream: deltas of type AssistantMessageEvent with a final AssistantMessage result. isComplete fires on done/error; extractResult takes the terminal event's message.
func NewAssistantMessageEventStream ¶
func NewAssistantMessageEventStream(buffer int) *AssistantMessageEventStream
NewAssistantMessageEventStream builds a provider stream wired with the done/error completion callbacks.
func StreamRequest ¶
func StreamRequest(ctx context.Context, cfg TransportConfig) (*AssistantMessageEventStream, error)
StreamRequest runs cfg as a transport stream. Per the dual failure model it returns an error only when the very first request cannot be built or the initial connection can never be established; every runtime failure once streaming begins rides the returned stream as a terminal StreamErrorEvent.
type CompletionRequest ¶
type CompletionRequest struct {
// Model is the provider-specific model id to complete against.
Model string
// Context is the shaped request (system prompt, LLM-bound messages, tools).
Context LlmContext
// Config carries per-request options (API key, thinking level, extras).
Config StreamConfig
}
CompletionRequest is the provider-agnostic input to StreamCompletion: the model id, the shaped LLM context, and per-request options.
type CredentialStatus ¶
type CredentialStatus struct {
// OAuth reports a registered OAuth token source (auto-refreshing).
OAuth bool
// Override reports an explicit key (--api-key).
Override bool
// EnvVar names the environment variable holding a value, or "" for none.
EnvVar string
// Config reports a key in the config-file layer.
Config bool
}
CredentialStatus reports which credential sources hold a value for a provider, in resolution priority order. It is a presence probe: no secret value is retrieved or returned, only which layer can supply one.
func (CredentialStatus) Available ¶
func (s CredentialStatus) Available() bool
Available reports whether any layer can supply a credential.
func (CredentialStatus) Source ¶
func (s CredentialStatus) Source() string
Source returns a short, value-free label for the highest-priority layer that holds a credential, or "" when none does. Env sources return the variable name (safe to display), never the value.
type CredentialStore ¶
type CredentialStore struct {
// contains filtered or unexported fields
}
CredentialStore resolves API keys per provider from three layers, in order: OAuth token source (if registered), environment variable, config file. It implements the LoopConfig.GetAPIKey shape via GetAPIKey.
It is safe for concurrent use.
func NewCredentialStore ¶
func NewCredentialStore(config *APIKeyConfig) *CredentialStore
NewCredentialStore builds a store over an optional config file. A nil config is treated as empty.
func (*CredentialStore) GetAPIKey ¶
func (c *CredentialStore) GetAPIKey(ctx context.Context, provider string) string
GetAPIKey resolves the API key for a provider. Resolution order: OAuth token (refreshed on expiry) → explicit override (--api-key) → environment variable → config file. Returns "" when no credential is available. This matches LoopConfig.GetAPIKey so it can be assigned directly.
On OAuth refresh failure it falls back to override/env/config rather than returning a secret-bearing error; the empty return lets the caller fall back to a static key. It never logs secret values.
func (*CredentialStore) HasCredential ¶
func (c *CredentialStore) HasCredential(ctx context.Context, provider string) bool
HasCredential reports whether any credential (OAuth/env/config) is available for a provider, without exposing the value.
func (*CredentialStore) RegisterOAuth ¶
func (c *CredentialStore) RegisterOAuth(provider string, src *TokenSource)
RegisterOAuth registers an OAuth TokenSource for a provider. Once registered, GetAPIKey prefers the (auto-refreshing) OAuth token over static keys.
func (*CredentialStore) SetOverride ¶
func (c *CredentialStore) SetOverride(provider, key string)
SetOverride records an explicit API key for a provider that wins over the environment variable and config file (but not a live OAuth token, which is auto-refreshed). It is the seam for a CLI --api-key flag: an empty key is ignored so a bare flag does not clobber env/config resolution.
func (*CredentialStore) Status ¶
func (c *CredentialStore) Status(provider string) CredentialStatus
Status probes the credential layers for a provider without reading any secret into a caller-visible value. A nil store (or empty config) reports no config/override/OAuth source; the env layer is always probed.
type Decoder ¶
type Decoder interface {
// Decode turns one SSE data payload into zero or more StreamEvents. A
// returned error is treated as a runtime stream failure (terminal error
// event), never a panic.
Decode(payload []byte) ([]StreamEvent, error)
// Finish flushes any trailing state, returning a final batch of events.
Finish() ([]StreamEvent, error)
}
Decoder is the per-provider stateful SSE payload decoder. The transport calls Decode for every complete SSE data payload (one event's worth of bytes) and Finish once the stream ends so the decoder can flush any buffered terminal event.
type LlmContext ¶
type LlmContext struct {
SystemPrompt string
Messages agentcore.MessageList
Tools []agentcore.AgentTool
}
LlmContext is the shaped request handed to a StreamFn: the system prompt, the LLM-bound messages (UI-only messages already filtered), and the tools.
type Model ¶
type Model struct {
// Provider is the provider name (e.g. "anthropic", "openai").
Provider string `json:"provider"`
// ID is the provider-specific model id (e.g. "claude-opus-4-8").
ID string `json:"id"`
// DisplayName is a human-friendly label; falls back to ID when empty.
DisplayName string `json:"displayName,omitempty"`
// ContextWindow is the maximum input+output token window, 0 if unknown.
ContextWindow int `json:"contextWindow,omitempty"`
// MaxOutputTokens is the max tokens the model may emit per response, 0 if
// unknown.
MaxOutputTokens int `json:"maxOutputTokens,omitempty"`
// SupportsThinking reports whether the model exposes a reasoning/thinking
// channel.
SupportsThinking bool `json:"supportsThinking,omitempty"`
// SupportsTools reports whether the model can call tools.
SupportsTools bool `json:"supportsTools,omitempty"`
// SupportsImages reports whether the model accepts image (multimodal) input.
// When false, an image block in the request is reported as a hard error
// rather than silently dropped, so the user learns the model cannot see it.
SupportsImages bool `json:"supportsImages,omitempty"`
// ThinkingLevels maps unified thinking levels to this model's wire values.
// nil when the model does not support thinking (decision #10).
ThinkingLevels agentcore.ThinkingLevelMap `json:"-"`
}
Model is provider-agnostic metadata describing a single model's identity and capabilities. Providers construct these; the loop/UI consume them.
type OAuthToken ¶
OAuthToken is the result of an OAuth exchange/refresh. Values are secrets.
type OpenAIDecoder ¶
type OpenAIDecoder struct {
// contains filtered or unexported fields
}
OpenAIDecoder is the stateful SSE decoder for the OpenAI Chat Completions API and compatible gateways. It implements the transport Decoder interface and is not safe for concurrent use — the transport drives it from one goroutine.
func NewOpenAIDecoder ¶
func NewOpenAIDecoder() *OpenAIDecoder
NewOpenAIDecoder builds a fresh decoder for one streamed response.
func (*OpenAIDecoder) Decode ¶
func (d *OpenAIDecoder) Decode(payload []byte) ([]StreamEvent, error)
Decode turns one OpenAI SSE data payload into zero or more StreamEvents.
func (*OpenAIDecoder) Finish ¶
func (d *OpenAIDecoder) Finish() ([]StreamEvent, error)
Finish flushes a terminal done event if the stream ended without an explicit terminator, so a partial response is still delivered rather than lost.
type Provider ¶
type Provider interface {
// Name returns the provider's identifier (matches Model.Provider).
Name() string
// Models lists the models this provider can serve.
Models() []Model
// StreamCompletion streams a completion for req. Per the dual failure model
// it returns an error only for the earliest "cannot build the stream" case;
// all runtime failures ride the returned stream as a terminal error event.
StreamCompletion(ctx context.Context, req CompletionRequest) (*AssistantMessageEventStream, error)
}
Provider is the unified streaming interface implemented by every backend. It hides per-vendor differences behind a single AssistantMessageEvent stream.
func NewAnthropicProtocolProvider ¶
NewAnthropicProtocolProvider builds a named Anthropic-Messages provider whose auth header follows the given registry AuthScheme. It is the target of an explicit --provider selection for any anthropic-protocol built-in (anthropic, minimax, minimax-cn, and — routed generically for now — bedrock/ cloudflare-ai-gateway): the driver identity is the provider's own name (so errors reference it), baseURL is the already-resolved endpoint (spec default or override), and authScheme selects the header shape (see anthropicAuthHeaderFor). Secret values are never logged.
func NewAnthropicProvider ¶
NewAnthropicProvider builds a provider that speaks the Anthropic Messages wire format directly (POST {baseURL}/messages), the target of an explicit --protocol=anthropic selection. baseURL defaults to the public Anthropic API when empty. Auth uses the Anthropic conventions: an x-api-key header plus the required anthropic-version header. The API key resolves by the "anthropic" provider name (ANTHROPIC_API_KEY / CLAUDE_API_KEY, or the --api-key override); secret values are never logged.
func NewBedrockProvider ¶
NewBedrockProvider builds the Bedrock provider, reusing the Anthropic Messages decoder (Anthropic-on-Bedrock speaks the Messages wire format). baseURL defaults to a us-east-1 runtime endpoint when empty; real deployments pass the region-specific URL. Auth is a Bearer token (Bedrock API keys); SigV4 signing, when required, is layered by the caller's HTTP client.
func NewNvidiaProvider ¶
NewNvidiaProvider builds the NVIDIA provider (hosted NIM, OpenAI-compatible, Bearer auth). baseURL defaults to the public integrate endpoint when empty. The API key is resolved by the "nvidia" provider name (NVIDIA_API_KEY); secret values are never logged.
func NewOllamaProvider ¶
NewOllamaProvider builds the Ollama provider (local, OpenAI-compatible, no auth). baseURL defaults to the local daemon when empty.
func NewOpenAICompatibleProvider ¶
NewOpenAICompatibleProvider builds a generic OpenAI-compatible provider for an arbitrary gateway reached by baseURL (Bearer auth). It is the target of an explicit --protocol=openai selection: unlike the preset constructors it has no default endpoint (baseURL must be supplied) and carries the neutral provider name "openai", so an API key resolves from OPENAI_API_KEY (or the --api-key override bound to that name). Secret values are never logged.
func NewOpenAICompatibleProviderAs ¶
NewOpenAICompatibleProviderAs builds the same generic driver carrying the given provider name, so errors and per-provider request decoration (e.g. the x-opencode-session header) reference the selected provider instead of the neutral "openai". ResolveNamedProvider uses it for every OpenAI-wired registry entry; API-key resolution still keys on the returned provider name upstream and is unaffected.
func NewOpenRouterProvider ¶
NewOpenRouterProvider builds the OpenRouter provider — the reference OpenAI-compatible gateway. baseURL defaults to the public endpoint when empty.
func ResolveNamedProvider ¶
func ResolveNamedProvider(name, model, baseURL, protocol string, env func(string) string) (Provider, string, error)
ResolveNamedProvider builds the driver for an explicit --provider selection. It looks the name up in the built-in registry and constructs the wire driver for the effective protocol: "openai" → an OpenAI-compatible (Bearer) driver, "anthropic" → an Anthropic-Messages driver, "openai/resp_api" → the Responses driver. An explicit --protocol wins; otherwise a Responses-family model id upgrades an OpenAI-wired provider (see PreferResponses). The base URL follows the override precedence in ResolveBaseURL (--base-url > provider-specific env > generic <PROVIDER>_BASE_URL > spec default). The returned provider-name string is the spec name, so downstream API-key resolution reads the provider's own env var (spec.EnvVars).
Special providers with bespoke auth (azure/bedrock/vertex/cloudflare — AuthScheme aws/azure/special, or the cloudflare-* names) are routed to ResolveSpecialProvider, which validates their required env vars and composes the concrete endpoint (node #188).
func ResolveProvider ¶
func ResolveProvider(model, baseURL, protocol, providerName string, env func(string) string) (Provider, string, error)
ResolveProvider maps a model id to a built-in provider. An explicit --provider name wins over every other rule: it selects a built-in provider from the registry and constructs the matching wire driver (see ResolveNamedProvider). When provider is empty, protocol and model-id heuristics apply as before.
When protocol is a non-empty explicit selection ("openai" or "anthropic") it wins over the model-id heuristics: the provider is built directly for that wire format against baseURL, which is how a user points golder at a self-hosted or third-party endpoint and says which protocol it speaks. An "anthropic" selection with no baseURL targets the public Anthropic API.
When protocol is empty, resolution falls back to model-id heuristics:
- A bare provider name (e.g. "zai") is canonicalized to that provider's DefaultModel (see CanonicalizeModel, issue #564).
- An "ollama/" prefix (or a base URL on the Ollama port) → local Ollama.
- An "nvidia/" prefix → NVIDIA NIM (strips the prefix for the wire id).
- Model-name inference: with no --base-url, a well-known model-name prefix (e.g. "claude-*", "deepseek-*") selects its first-party built-in provider via ResolveNamedProvider (see InferProviderFromModel).
- A bare provider name with no DefaultModel is an error naming the mismatch, rather than a silent OpenRouter fallback that fails later with a misleading missing-API-key error.
- Everything else → OpenRouter, the reference OpenAI-compatible gateway.
An unknown protocol value is an error, surfaced to the caller for exit-code mapping rather than silently falling back.
func ResolveSpecialProvider ¶
func ResolveSpecialProvider(spec ProviderSpec, model, flagBaseURL string, env func(string) string) (Provider, error)
ResolveSpecialProvider validates the required parameters for a special-auth provider and constructs the matching wire driver against the composed base URL. flagBaseURL is the explicit --base-url override (highest precedence, wins over any composed default); env resolves environment variables (os.Getenv in production, a fake in tests). A missing required parameter yields an error naming exactly which env var is absent; no network request is made here.
type ProviderSpec ¶
type ProviderSpec struct {
// Name is the canonical provider name (e.g. "deepseek", "zai-coding-cn").
Name string
// EnvVars lists the environment variables checked (in precedence order) for
// this provider's API key. The first non-empty value wins.
EnvVars []string
// DefaultBaseURL is the provider's default API endpoint. It may be a template
// (containing placeholders like {region}) for providers whose endpoint is
// composed from additional parameters (Bedrock, Vertex, Cloudflare, Azure).
DefaultBaseURL string
// Protocol is the wire protocol the provider speaks: "openai" (OpenAI Chat
// Completions) or "anthropic" (Anthropic Messages).
Protocol string
// AuthScheme names how credentials are attached: "bearer", "x-api-key",
// "aws", "azure", or "special".
AuthScheme string
// ExtraHeaders are provider-specific headers attached to every request (may
// be nil).
ExtraHeaders map[string]string
// BaseURLEnvVars lists provider-specific base-URL override environment
// variables (e.g. AZURE_OPENAI_BASE_URL), in precedence order. May be empty;
// the generic <PROVIDER>_BASE_URL convention is handled by callers.
BaseURLEnvVars []string
// ForceProxy is the legacy default selection before the user saves /proxy
// settings. An explicit provider list replaces these defaults completely.
ForceProxy bool
// ModelsDevID is this provider's key in models.dev's api.json (used by the
// reasoning ladder catalog). Empty means Name.
ModelsDevID string
// ModelPrefixes lists the lowercase model-id prefixes that identify this
// provider when only --model is given (see InferProviderFromModel). Keep
// entries precise: a prefix that another provider's ids also carry would
// shadow that provider, and registry order decides ties.
ModelPrefixes []string
// ReasoningLadders overrides the shared model-family ladder table
// (reasoning.go) for this provider's models: the first entry whose Match is
// a case-insensitive substring of the model id wins, before the family
// table is consulted. Declare a ladder here only when the gateway diverges
// from the model family's usual ladder (e.g. a gateway that rejects a level
// the upstream API accepts). Empty means "no quirks".
ReasoningLadders []ReasoningLadder
// DefaultModel is the model id a bare provider name resolves to (e.g.
// "zai" → "glm-4.7", see CanonicalizeModel), so /model <provider> and
// --model <provider> select a real wire id instead of sending the literal
// provider name. Empty means the provider has no default; a bare name is
// then reported as a provider/model mismatch instead of guessing.
DefaultModel string
}
ProviderSpec is the metadata describing one built-in provider. It is the single source of truth consumed by auth resolution, the --provider flag, base_url override handling, and per-provider wiring.
func LookupProviderSpec ¶
func LookupProviderSpec(name string) (ProviderSpec, bool)
LookupProviderSpec returns the ProviderSpec for a provider name and whether it is a known built-in provider. The returned spec is a copy; mutating its slice or map fields is discouraged as they are shared with the registry.
func ProviderSpecs ¶
func ProviderSpecs() []ProviderSpec
ProviderSpecs returns all built-in provider specs in registry (display) order. Callers must not mutate the returned specs' slice/map fields.
type ReasoningLadder ¶
type ReasoningLadder struct {
Match string
Ladder []agentcore.ThinkingLevel
}
ReasoningLadder is one model-family reasoning-effort rule: Match is a case-insensitive substring of the model id, Ladder the effort levels the gateway accepts for it, lowest to highest.
type StreamConfig ¶
type StreamConfig struct {
APIKey string
ThinkingLevel agentcore.ThinkingLevel
// Extra holds provider-specific options; opaque to the loop.
Extra map[string]any
}
StreamConfig carries per-request settings for a StreamFn.
type StreamDoneEvent ¶
type StreamDoneEvent struct{ Message agentcore.AssistantMessage }
StreamDoneEvent is the terminal success event; Message is the final response.
func (StreamDoneEvent) EventKind ¶
func (StreamDoneEvent) EventKind() string
type StreamErrorEvent ¶
type StreamErrorEvent struct {
Message agentcore.AssistantMessage
Err error
}
StreamErrorEvent is the terminal failure event; Message carries the terminal assistant message (stopReason=error/aborted + errorMessage).
func (StreamErrorEvent) EventKind ¶
func (StreamErrorEvent) EventKind() string
type StreamEvent ¶
type StreamEvent = AssistantMessageEvent
StreamEvent is the transport-level alias for AssistantMessageEvent. Decoders produce these; the transport forwards them onto the stream. (Decision #25: reuse AssistantMessageEvent rather than a parallel event type.)
type StreamFn ¶
type StreamFn func(ctx context.Context, model string, llm LlmContext, cfg StreamConfig) (*AssistantMessageEventStream, error)
StreamFn produces a provider stream for a model + shaped context. Per the contract it returns an error only for early "cannot build the stream" failures; all runtime failures ride the returned stream as error events.
func StreamFnFromProvider ¶
StreamFnFromProvider adapts a Provider to the loop's StreamFn contract so a Provider can drive streamAssistantResponse directly. The two failure models are identical, so the adaptation is a straight delegation.
type StreamStartEvent ¶
type StreamStartEvent struct{ Partial agentcore.AssistantMessage }
StreamStartEvent carries the initial (usually empty) partial message.
func (StreamStartEvent) EventKind ¶
func (StreamStartEvent) EventKind() string
type StreamTextEvent ¶
type StreamTextEvent struct{ Partial agentcore.AssistantMessage }
StreamTextEvent carries the partial message after a text delta.
func (StreamTextEvent) EventKind ¶
func (StreamTextEvent) EventKind() string
type StreamThinkingEvent ¶
type StreamThinkingEvent struct{ Partial agentcore.AssistantMessage }
StreamThinkingEvent carries the partial after a thinking delta.
func (StreamThinkingEvent) EventKind ¶
func (StreamThinkingEvent) EventKind() string
type StreamToolCallEvent ¶
type StreamToolCallEvent struct{ Partial agentcore.AssistantMessage }
StreamToolCallEvent carries the partial after a tool-call delta.
func (StreamToolCallEvent) EventKind ¶
func (StreamToolCallEvent) EventKind() string
type TokenSource ¶
type TokenSource struct {
// Refresh exchanges the current refresh token for a new access token. It
// returns the new access token, its expiry, and (optionally) a rotated
// refresh token. Required for a TokenSource to refresh; nil means the token
// is static and never refreshed.
Refresh func(ctx context.Context, refreshToken string) (OAuthToken, error)
// Now is injectable for testing; defaults to time.Now.
Now func() time.Time
// Leeway refreshes the token this long before its actual expiry to avoid
// racing the boundary. Defaults to 30s.
Leeway time.Duration
// contains filtered or unexported fields
}
TokenSource yields an access token, refreshing it when expired. It models an OAuth credential whose access token is short-lived (FR-15: getApiKey refreshes on expiry). It is safe for concurrent use.
func NewTokenSource ¶
func NewTokenSource(initial OAuthToken, refresh func(ctx context.Context, refreshToken string) (OAuthToken, error)) *TokenSource
NewTokenSource builds a TokenSource seeded with an initial token and a refresh function. refresh may be nil for a static (never-expiring) token.
type TransportConfig ¶
type TransportConfig struct {
// Client is the HTTP client; defaults to http.DefaultClient when nil.
Client *http.Client
// NewRequest builds a fresh *http.Request for each connection attempt. It is
// called once per connect (initial + reconnects) so retries never replay a
// consumed body — the caller owns idempotent request construction.
NewRequest func(ctx context.Context) (*http.Request, error)
// Decoder converts SSE payloads to StreamEvents (required).
Decoder Decoder
// MaxConnectRetries bounds connect-only retries (default 2).
MaxConnectRetries int
}
TransportConfig configures a single StreamRequest run.