adapter

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 13, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

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

Variables

This section is empty.

Functions

This section is empty.

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

func New(baseURL, apiKey, model string) Client

New returns a Client wired to the provider implied by baseURL + model. Kept for compatibility with the original constructor shape.

func NewWithConfig

func NewWithConfig(cfg Config) Client

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

type HealthOptions struct {
	Window    time.Duration
	Threshold int
}

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 Message

type Message struct {
	Role       Role       `json:"role"`
	Content    string     `json:"content,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"`
}

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 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 is skipped: it does expose /v1/models, but with a different auth header shape (`x-api-key` rather than `Authorization: Bearer`) that the OpenAI-compatible probe gets wrong. Until we wire a dedicated Anthropic probe, the static profile is the diagnostic.

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"
	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"`
	SupportsWebSearch       bool              `json:"supports_web_search"`
	SupportsXSearch         bool              `json:"supports_x_search"`
	SupportsCodeInterpreter bool              `json:"supports_code_interpreter"`
	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 Role

type Role string

Role enumerates the speakers in a conversation.

const (
	RoleSystem    Role = "system"
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
)

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.

const (
	TierUnspecified Tier = ""
	TierCheap       Tier = "cheap"
	TierBalanced    Tier = "balanced"
	TierExpensive   Tier = "expensive"
)

type Tool

type Tool struct {
	Name        string
	Description string
	Schema      map[string]any
}

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"`
}

ToolCall is a model's request to invoke a tool. ArgsJSON is the raw JSON string the model produced; validation happens at execution time.

Jump to

Keyboard shortcuts

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