Documentation
¶
Index ¶
- Constants
- func EffortInapplicableReason(cfg Config, provider Provider) string
- func IsContextOverflow(err error) bool
- func SetOpenAIAuthAccountForTest(a *OpenAIAuthAccount)
- func SetOpenAIAuthRateLimitForTest(memo *OpenAIAuthRateLimit)
- func SetRateLimitForTest(provider Provider, snap *RateLimitSnapshot)
- type BuiltinToolKind
- type Candidate
- type CheapFirst
- type Citation
- type Client
- type Config
- type FallbackChain
- type HealthOptions
- type ImageBlock
- type Message
- type MultiStreamer
- type MultiStreamerOption
- type OpenAIAuthAccount
- type OpenAIAuthRateLimit
- type Policy
- type ProbeResult
- type Provider
- type ProviderProfile
- type RateLimitSnapshot
- type Role
- type StreamEvent
- type StreamEventKind
- type Streamer
- type Tier
- type Tool
- type ToolCall
- type Usage
Constants ¶
const ( // OpenAIAuthEndpoint is the ChatGPT-subscription Responses-API // endpoint reachable via OAuth bearer auth. Distinct from // api.openai.com/v1/responses both in URL and request contract. OpenAIAuthEndpoint = "https://chatgpt.com/backend-api/codex/responses" // OpenAIAuthDefaultInstructions is sent when the caller's message // slate has no system role. The Codex backend rejects requests // with empty/missing instructions ("Instructions are required"), // so we always send something. OpenAIAuthDefaultInstructions = "You are a helpful assistant." )
const AnthropicDefaultMaxTokens int64 = 8192
AnthropicDefaultMaxTokens is the upper bound on a single completion. Anthropic requires this — there is no "no limit" sentinel. 8192 is generous for code edits and within every claude-* model's per-turn cap; bigger documents land via tool_use round-trips, not single turns.
const ChatDefaultMaxTokens int64 = 8192
ChatDefaultMaxTokens is the per-turn output cap sent on every Chat Completions request. Some OpenAI-compatible providers (NVIDIA NIM in particular) treat a missing max_tokens as zero and include that in the input+output ≤ window budget check — so a request with a full transcript and no max_tokens is rejected with 400 even when the input alone fits. Setting a real ceiling avoids that and reserves output headroom on every other provider too. 8192 matches the Anthropic adapter default — generous for code edits, within every vendor's per-turn cap, and not so large that a runaway response drains the wallet.
Exported so callers that need to plan around the reserved output (e.g. the auto-summarize input budget) can subtract the same value the adapter actually sends.
Variables ¶
This section is empty.
Functions ¶
func EffortInapplicableReason ¶ added in v0.3.0
EffortInapplicableReason explains, in one user-facing phrase, why the configured reasoning effort will NOT take effect for this provider+model — or "" when it will (or when no effort is set, which is never a no-op worth warning about). Mirrors exactly the gating each adapter applies, so the TUI can warn that a setting is silently a no-op on the active model instead of leaving the user guessing.
func IsContextOverflow ¶ added in v0.3.0
IsContextOverflow reports whether err is a provider-side rejection for exceeding the model's context window — the signal passive window-drift correction listens for. Conservative by design: an error that matches no known marker is never classified as overflow, because a false positive shrinks a window pin that then has to be re-proven upward by live traffic.
func SetOpenAIAuthAccountForTest ¶ added in v0.3.0
func SetOpenAIAuthAccountForTest(a *OpenAIAuthAccount)
SetOpenAIAuthAccountForTest seeds the cache from tests in other packages. Test-only.
func SetOpenAIAuthRateLimitForTest ¶ added in v0.3.0
func SetOpenAIAuthRateLimitForTest(memo *OpenAIAuthRateLimit)
SetOpenAIAuthRateLimitForTest seeds the package's rate-limit memo from tests in other packages (the /usage TUI renderer in particular). Test-only — never call from production paths.
func SetRateLimitForTest ¶ added in v0.3.0
func SetRateLimitForTest(provider Provider, snap *RateLimitSnapshot)
SetRateLimitForTest seeds (or clears, with nil) the snapshot for a provider so the /usage renderer can be exercised without a live API round-trip.
Types ¶
type BuiltinToolKind ¶
type BuiltinToolKind string
BuiltinToolKind is a provider-native capability exposed directly by the model provider rather than by yottacode's local tool registry.
const ( BuiltinToolWebSearch BuiltinToolKind = "web_search" BuiltinToolXSearch BuiltinToolKind = "x_search" BuiltinToolCodeInterpreter BuiltinToolKind = "code_interpreter" )
type Candidate ¶
type Candidate struct {
Streamer Streamer
Label string
Tier Tier
Profile ProviderProfile
}
Candidate is one dispatch target the router may select. Label is the human-readable identifier surfaced in EventFallback so the user can see which provider/model just failed and which one took over — e.g. "anthropic/claude-haiku-4-5" or "openai/gpt-4o". Tier is optional metadata consumed by tier-aware policies (CheapFirst); policies that do not consult it (FallbackChain) can ignore it. Profile lets the router satisfy the Client interface — the TUI's system-prompt composition and connection probe both want a ProviderProfile, and the router's representative profile is the first candidate's.
type CheapFirst ¶
type CheapFirst struct{}
CheapFirst sorts candidates by tier ascending: cheap → balanced → expensive → unspecified. Uses a stable sort, so candidates with the same tier (or no tier) preserve their declared order — this lets the user disambiguate two cheap-tier candidates by listing the preferred one first.
func (CheapFirst) Name ¶
func (CheapFirst) Name() string
func (CheapFirst) Order ¶
func (CheapFirst) Order(candidates []Candidate) []Candidate
type Citation ¶
type Citation struct {
Type string `json:"type,omitempty"`
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
FileID string `json:"file_id,omitempty"`
Filename string `json:"filename,omitempty"`
}
Citation is a source reference attached to an assistant message by a provider-native search or retrieval capability.
type Client ¶
type Client interface {
Streamer
Profile() ProviderProfile
}
Client is the richer adapter surface used by entry points. The agent loop still only depends on ChatStream; Profile is for UI, diagnostics, and future routing decisions.
func New ¶
New returns a Client wired to the provider implied by baseURL + model. Kept for compatibility with the original constructor shape.
func NewWithConfig ¶
NewWithConfig returns a Client wired to the provider implied by the config.
Routing rules:
- api.openai.com + (o1*, o3*, o4*, gpt-5*) → Responses API (the only way to surface reasoning summaries for OpenAI's reasoning models — Chat Completions hides them.)
- api.openai.com + Responses-only built-in tools enabled (`web_search`, `code_interpreter`) → Responses API
- everything else → OpenAI-compatible Chat Completions (Ollama, Llama Stack, vLLM, xAI, Together, OpenRouter, NVIDIA NIM, Groq, real OpenAI for non-reasoning models like gpt-4o / gpt-4.1.)
Auto-detection covers ~all real-world cases. There is no flag override — if you point at api.openai.com with an o-series or gpt-5 model, you get reasoning streaming for free.
type Config ¶
type Config struct {
BaseURL string
APIKey string
Model string
ProviderOverride Provider
ReasoningEffort string
// ModelMaxOutput and ModelSupportsThinking carry catalog-derived
// facts about the active model so budget-based reasoning providers
// (Anthropic, Gemini) can size a thinking budget without the adapter
// package importing catalog (which would cycle:
// adapter → catalog → auth/openai → adapter). Callers that have the
// catalog handy fill these from catalog.FindByID; both are
// zero/nil-safe — an unknown model leaves reasoning at the provider
// default. ModelSupportsThinking is a tristate: nil = unknown.
ModelMaxOutput int
ModelSupportsThinking *bool
EnableWebSearch bool
DisableWebSearch bool
EnableXSearch bool
EnableCodeInterpreter bool
SearchAllowedDomains []string
SearchExcludedDomains []string
XSearchAllowedHandles []string
XSearchExcludedHandles []string
XSearchFromDate string
XSearchToDate string
}
Config configures adapter construction and provider-native capabilities.
type FallbackChain ¶
type FallbackChain struct{}
FallbackChain tries candidates in declared order. The simplest policy and the right default when the user has hand-ordered their providers by preference (typical: "use Anthropic by default, fall through to OpenAI if Anthropic is degraded").
func (FallbackChain) Name ¶
func (FallbackChain) Name() string
func (FallbackChain) Order ¶
func (FallbackChain) Order(candidates []Candidate) []Candidate
type HealthOptions ¶
HealthOptions configures router-level health observation. Zero value means disabled (the router never marks a candidate as degraded). The router consults this tracker between policy ordering and dispatch: degraded candidates get demoted to the back of the order so the next request prefers candidates that have been working recently.
Defaults documented in DefaultsTOML and applied by cli.BuildRouter:
- Window = 60 * time.Second — sliding-window length
- Threshold = 3 — failures-in-window that mark degraded
Set Threshold = 0 to disable observation entirely (every candidate stays "healthy" regardless of error history).
type ImageBlock ¶ added in v0.3.0
type ImageBlock struct {
Data []byte `json:"data"`
MediaType string `json:"media_type"` // e.g. "image/png", "image/jpeg"
}
ImageBlock carries a single image as raw bytes plus its MIME type. Used in tool-result messages so the model can see screenshots, photos, diagrams, etc. alongside the textual output.
type Message ¶
type Message struct {
Role Role `json:"role"`
Content string `json:"content,omitempty"`
Images []ImageBlock `json:"images,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
Citations []Citation `json:"citations,omitempty"`
StopReason string `json:"stop_reason,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
// CacheHeadBytes marks how many leading bytes of Content form a
// stable, cacheable prefix — set by the composer on the system
// message to the length of the static base prompt, ahead of the
// per-turn memory tail. The Anthropic adapter splits Content there
// and puts a cache breakpoint on the head, so the big static prefix
// keeps hitting the prompt cache even when the memory tail changes
// between user turns. Other adapters ignore it (their providers
// cache the longest stable prefix automatically). 0 = no hint.
CacheHeadBytes int `json:"cache_head_bytes,omitempty"`
// Usage is the provider-reported token counts for the turn that
// produced this message. Pointer so nil ≠ "zero tokens" — adapters
// that didn't observe usage data leave it unset, and the /usage
// command can tell the difference.
Usage *Usage `json:"usage,omitempty"`
}
Message is the neutral conversation unit the agent persists and replays. Tool-role messages carry ToolCallID to bind them to the call they answer.
type MultiStreamer ¶
type MultiStreamer struct {
// contains filtered or unexported fields
}
MultiStreamer dispatches a ChatStream call across an ordered set of candidate Streamers, falling through on early failures according to the configured Policy. The whole point of yottacode being multi- provider — a single provider degradation should not interrupt the user's turn if another configured provider can serve it.
Fallback is intentionally conservative:
- A failure (EventErr) emitted before any user-visible token has streamed is "early" and triggers fallback to the next candidate.
- A failure emitted after one or more EventTokenDelta or EventReasoning events is "mid-stream" and bubbles up as a terminal error. Silently restarting on a different provider mid-reply would corrupt the conversation log and confuse the user — we'd rather surface the error and let the agent loop retry the whole turn.
- When the last candidate fails, its error bubbles up regardless of timing — there is nothing left to fall through to.
MultiStreamer emits EventFallback between the failed attempt and the next attempt, carrying enough metadata for the TUI to render a loud "fell through from X to Y because Z" line. Silent fallback is the failure mode this whole design is built to avoid.
func NewMultiStreamer ¶
func NewMultiStreamer(candidates []Candidate, policy Policy, opts ...MultiStreamerOption) (*MultiStreamer, error)
NewMultiStreamer constructs a router over the supplied candidates. Returns an error if no candidates were supplied or no policy was set — both are programmer errors, not runtime conditions, so we fail loud at construction rather than at request time. Variadic options configure health observation and (in tests) the clock.
func (*MultiStreamer) Candidates ¶
func (m *MultiStreamer) Candidates() []Candidate
Candidates returns a copy of the configured candidate list. Useful for diagnostics and `/router list`-style introspection commands.
func (*MultiStreamer) ChatStream ¶
func (m *MultiStreamer) ChatStream(ctx context.Context, messages []Message, tools []Tool) <-chan StreamEvent
ChatStream applies the policy to order the candidates, then drives them in sequence until one succeeds, all fail, or ctx is canceled. When health observation is enabled, recently-degraded candidates are stable-demoted to the back of the order so this request prefers candidates that have been working. The returned channel is closed when the request finally terminates.
func (*MultiStreamer) PolicyName ¶
func (m *MultiStreamer) PolicyName() string
PolicyName returns the name of the active policy. Mirrors what the MultiStreamer would emit on EventFallback.FallbackPolicy.
func (*MultiStreamer) Profile ¶
func (m *MultiStreamer) Profile() ProviderProfile
Profile satisfies adapter.Client by returning the first candidate's profile. The router does not synthesize a merged profile — capability gating across heterogeneous providers is a Phase 2 concern. For step 1.5 the convention is: list candidates in capability-aligned order; the first one is the representative for system-prompt composition.
type MultiStreamerOption ¶
type MultiStreamerOption func(*MultiStreamer)
MultiStreamerOption tunes router behavior at construction time. Variadic to keep the original two-arg NewMultiStreamer signature backward compatible with callers (and tests) that don't care about the new knobs.
func WithHealth ¶
func WithHealth(opts HealthOptions) MultiStreamerOption
WithHealth attaches a sliding-window health tracker. Zero values for Window or Threshold disable tracking — useful when a caller wants to pass an "unset" config struct without conditionally choosing between two constructors. Defaults are documented on HealthOptions.
type OpenAIAuthAccount ¶ added in v0.3.0
type OpenAIAuthAccount struct {
Email string
Plan string
Provider string // e.g. "google", "email" — login method
ProbedAt time.Time
}
OpenAIAuthAccount captures what the best-effort backend-api probe learned about the signed-in ChatGPT account. Every field is optional — the probe degrades gracefully when the endpoint returns less than we hoped for, and the /usage renderer prints only what's actually populated.
The endpoints we hit are NOT documented by OpenAI. They're the same surface the official Codex CLI talks to; if the shape changes upstream, this probe will silently empty out (the renderer falls back to the 429 memo and the "subscription" label).
func LastOpenAIAuthAccount ¶ added in v0.3.0
func LastOpenAIAuthAccount() *OpenAIAuthAccount
LastOpenAIAuthAccount returns the cached probe result, or nil if the cache is empty or expired. Pure accessor — never makes a network call. Callers in interactive paths (e.g. /usage) should call ProbeOpenAIAuthAccount first to refresh.
func ProbeOpenAIAuthAccount ¶ added in v0.3.0
func ProbeOpenAIAuthAccount(ctx context.Context) *OpenAIAuthAccount
ProbeOpenAIAuthAccount fires a one-shot GET against the undocumented /backend-api/me endpoint, parses whatever account info comes back, caches it, and returns the result. nil return means the probe found nothing — either the user isn't logged in, the token can't reach the endpoint, the endpoint changed shape, or any HTTP failure. Callers must treat nil as "no data," not "error" — /usage renders provider notes from the 429 memo when the probe degrades.
Re-uses any cached result younger than openAIAuthAccountTTL.
type OpenAIAuthRateLimit ¶ added in v0.3.0
OpenAIAuthRateLimit captures the last 429 the openai-auth adapter saw. Populated as a side effect of formatRateLimitHint parsing and exposed via LastOpenAIAuthRateLimit() so the /usage command can surface plan + reset metadata for the subscription-based provider (no quota query endpoint exists; the 429 is the only public signal).
func LastOpenAIAuthRateLimit ¶ added in v0.3.0
func LastOpenAIAuthRateLimit() *OpenAIAuthRateLimit
LastOpenAIAuthRateLimit returns the most recent rate-limit memo captured by this process, or nil if no 429 was observed. The returned pointer is a copy — callers can read it without holding the package lock.
type Policy ¶
type Policy interface {
// Order returns the candidates in the order they should be tried.
// Implementations must not mutate the input slice.
Order(candidates []Candidate) []Candidate
// Name is a short identifier surfaced in EventFallback for telemetry
// and TUI logging. e.g. "fallback-chain", "cheap-first".
Name() string
}
Policy decides the order in which a MultiStreamer tries its candidates for one request. It is invoked once per ChatStream call, with the full candidate slice; the returned slice is iterated in order until one succeeds (returns EventDone) or the list is exhausted.
Policy is intentionally not request-aware in this iteration. Smarter per-request routing (capability gating, request classification) is a Phase 2 concern; declarative policies first.
type ProbeResult ¶
type ProbeResult struct {
Profile ProviderProfile `json:"profile"`
BaseURL string `json:"base_url"`
Model string `json:"model"`
HTTPStatus int `json:"http_status,omitempty"`
EndpointReachable bool `json:"endpoint_reachable"`
AuthOK bool `json:"auth_ok"`
ModelVisible bool `json:"model_visible"`
AvailableModels []string `json:"available_models,omitempty"`
Issues []string `json:"issues,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
ProbeResult is the active diagnostics result for one provider config.
func Probe ¶
func Probe(ctx context.Context, cfg Config) ProbeResult
Probe runs a lightweight active diagnostics pass against the configured provider. It currently validates the /models surface because that is cheap, widely available on supported endpoints, and enough to distinguish network, auth, and model-visibility failures.
Anthropic exposes /v1/models like the OpenAI-compatible providers but authenticates with x-api-key + anthropic-version headers rather than a Bearer token, so it gets a dedicated request shape (probeAnthropic) before folding into the shared response handling.
func StaticDiagnostics ¶
func StaticDiagnostics(cfg Config) ProbeResult
StaticDiagnostics resolves provider routing and static config diagnostics without making any network requests.
type Provider ¶
type Provider string
Provider identifies the upstream model vendor or compatibility family that yottacode believes it is talking to.
const ( ProviderOpenAI Provider = "openai" ProviderOpenAIAuth Provider = "openai-auth" ProviderCopilot Provider = "copilot" ProviderXAI Provider = "xai" ProviderOllama Provider = "ollama" ProviderAnthropic Provider = "anthropic" ProviderGemini Provider = "gemini" ProviderOpenAICompatible Provider = "openai-compatible" )
type ProviderProfile ¶
type ProviderProfile struct {
Provider Provider `json:"provider"`
UsesResponsesAPI bool `json:"uses_responses_api"`
SupportsReasoning bool `json:"supports_reasoning"`
SupportsImages bool `json:"supports_images"`
SupportsWebSearch bool `json:"supports_web_search"`
SupportsXSearch bool `json:"supports_x_search"`
SupportsCodeInterpreter bool `json:"supports_code_interpreter"`
// SupportsUsageReporting indicates the adapter populates per-turn
// Usage on its returned Message. False for local/free providers
// (Ollama, NVIDIA NIM) where /usage has no meaningful surface.
SupportsUsageReporting bool `json:"supports_usage_reporting"`
EnabledBuiltinTools []BuiltinToolKind `json:"enabled_builtin_tools,omitempty"`
Issues []string `json:"issues,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
ProviderProfile is the resolved view of provider capabilities for a given adapter instance.
type RateLimitSnapshot ¶ added in v0.3.0
type RateLimitSnapshot struct {
Provider Provider
Observed time.Time
// Requests-per-window budget.
HasRequests bool
RequestsLimit int64
RequestsRemaining int64
RequestsReset time.Time
// Token budget. For OpenAI this is the single tokens bucket; for
// Anthropic it's the combined tokens bucket (the most restrictive
// limit in effect, per their docs).
HasTokens bool
TokensLimit int64
TokensRemaining int64
TokensReset time.Time
}
RateLimitSnapshot is the parsed quota state from the most recent successful API response for a provider. Pay-per-use providers (OpenAI, Anthropic, xAI) return per-minute rate-limit headers on every 200, costing nothing extra beyond the inference call we already make. /usage surfaces this as a live "how much headroom is left this minute" gauge — distinct from cost (which we estimate from token counts) and from the openai-auth 429 memo (which is only populated reactively on a rate-limit error).
The Has* flags distinguish "header absent" from "header present and zero" so the renderer can omit a family entirely rather than show a misleading 0.
func LastRateLimit ¶ added in v0.3.0
func LastRateLimit(provider Provider) *RateLimitSnapshot
LastRateLimit returns the most recent rate-limit snapshot for a provider, or nil if none has been observed this run. The returned pointer is a copy — callers can read it without holding the lock.
type StreamEvent ¶
type StreamEvent struct {
Kind StreamEventKind
Token string
ProviderToolName string
ProviderToolPhase string
ProviderToolDetail string
Final *Message
Err error
// Fallback metadata — populated only when Kind == EventFallback.
FallbackFrom string
FallbackTo string
FallbackReason string
FallbackPolicy string
}
StreamEvent is one item emitted while a completion streams.
TokenDelta carries a chunk of assistant text to render live. Done carries the fully-accumulated assistant Message (with tool_calls, if any). Err carries a terminal error; no further events will follow. Fallback carries router metadata when MultiStreamer falls through from one candidate to another; the From/To/Reason/Policy fields are populated.
type StreamEventKind ¶
type StreamEventKind int
StreamEventKind discriminates StreamEvent variants.
const ( EventTokenDelta StreamEventKind = iota // EventReasoning is emitted for "thinking" tokens produced by reasoning // models (Qwen 3, DeepSeek R1, etc.). Render these subtly so the user can // see the model is working without mistaking them for the final answer. EventReasoning EventProviderTool EventDone EventErr // EventFallback is emitted by MultiStreamer when one candidate fails // before producing any visible output and the active policy elects to // retry on a different candidate. Surfaced loudly in the TUI so a // silent provider-degradation never hides behind the abstraction. EventFallback // EventStreamProgress is a heartbeat for stream activity that has no // visible text — used by adapters to surface in-flight tool-call // argument generation so the live "tok/s" indicator keeps moving on // turns where the model produces a function call without any // reasoning summary or text output (notably gpt-5* on the Responses // API). No payload: the consumer just increments its activity counter. EventStreamProgress )
type Streamer ¶
type Streamer interface {
ChatStream(ctx context.Context, messages []Message, tools []Tool) <-chan StreamEvent
}
Streamer is the slice of behavior every concrete adapter implements. Kept mirrored with agent.Streamer so the agent loop can take any adapter the router hands it.
type Tier ¶
type Tier string
Tier is the coarse cost/capability bucket a candidate model belongs to. Mirrors the string enum already used in config.toml's [[providers.models]] entries (cheap | balanced | expensive). Empty tier is "unspecified" and sorts last under cost-aware policies.
type Tool ¶
Tool is the schema the adapter advertises to the model. Schema must be a JSON-schema object describing the function's parameters.
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
ArgsJSON string `json:"args_json"`
// ThoughtSignature is Gemini's opaque reasoning-continuity token.
// Thinking models (Gemini 3, and 2.5 with thinking enabled) attach
// it to functionCall parts and REQUIRE it to be replayed on those
// parts when the history goes back — otherwise the API rejects the
// next turn with "Function call is missing a thought_signature in
// functionCall parts." Empty for every other provider; omitempty
// keeps it out of their persisted transcripts.
ThoughtSignature string `json:"thought_signature,omitempty"`
}
ToolCall is a model's request to invoke a tool. ArgsJSON is the raw JSON string the model produced; validation happens at execution time.
type Usage ¶ added in v0.3.0
type Usage struct {
InputTokens int64 `json:"input_tokens,omitempty"`
OutputTokens int64 `json:"output_tokens,omitempty"`
CacheCreationTokens int64 `json:"cache_creation_tokens,omitempty"`
CacheReadTokens int64 `json:"cache_read_tokens,omitempty"`
ReasoningTokens int64 `json:"reasoning_tokens,omitempty"`
}
Usage is the per-turn token breakdown reported by the provider. Fields are normalized across providers: cache fields are populated only by providers that expose prompt caching (Anthropic, OpenAI's cached_input); ReasoningTokens is populated by o-series and Gemini "thoughts". All fields are int64 to match the JSON wire types and to keep arithmetic on session totals overflow-safe.
func (*Usage) Add ¶ added in v0.3.0
Add accumulates other into u. Used by the session accumulator and the daily rollup. Treats nil receivers/args as zero so callers can chain without nil-guarding.