provider

package
v0.1.47 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultResponseHeaderTimeout is how long to wait for the first response
	// byte before declaring the provider unreachable.
	DefaultResponseHeaderTimeout = 90 * time.Second

	// DefaultStreamIdleTimeout is how long the stream may go without a single
	// chunk before it is treated as stalled (provider died / connection hung).
	DefaultStreamIdleTimeout = 60 * time.Second

	// TotalTimeout bounds NON-streaming completions, which have no idle signal
	// to measure — a total wall-clock deadline is the only safe bound there.
	TotalTimeout = 120 * time.Second
)

Streaming-friendly HTTP settings. A plain http.Client{Timeout: T} applies T to the ENTIRE request including the body read, so a long generation (reasoning models stream for minutes) is misclassified as failed the moment it exceeds T — even while tokens keep flowing. The correct split is:

  • ResponseHeaderTimeout bounds only how long we wait for the first byte (a provider that never answers).
  • An idle watchdog (see IdleWatchdog) aborts the stream only when NO data arrives for a sustained gap (a provider that stalled mid-generation).

A stream that sends a trickle of chunks indefinitely is healthy and is never cut off by a wall-clock deadline.

Variables

View Source
var BuiltinProviders = []ProviderInfo{
	{
		ID:             "opencode",
		Name:           "BroCode Free Gateway",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "",
		DefaultBaseURL: "https://opencode.ai/zen/v1",
		DefaultModels:  OpenCodeFreeModels,
		ContextLimits:  builtinContextLimits["opencode"],
	},
	{
		ID:             "deepseek",
		Name:           "DeepSeek API",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "DEEPSEEK_API_KEY",
		DefaultBaseURL: "https://api.deepseek.com",
		DefaultModels: []string{
			"deepseek-chat",
			"deepseek-v4-flash",
			"deepseek-v4-pro",
		},
		ContextLimits: builtinContextLimits["deepseek"],
	},
	{
		ID:             "poolside",
		Name:           "Poolside / Laguna",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "POOLSIDE_API_KEY",
		DefaultBaseURL: "https://inference.poolside.ai/v1",
		DefaultModels: []string{
			"poolside/laguna-s-2.1",
			"poolside/laguna-xs-2.1",
		},
		ContextLimits: builtinContextLimits["poolside"],
	},
	{
		ID:             "anthropic",
		Name:           "Anthropic Claude",
		Protocol:       "anthropic",
		APIKeyEnvVar:   "ANTHROPIC_API_KEY",
		DefaultBaseURL: "https://api.anthropic.com",
		DefaultModels: []string{
			"claude-sonnet-5",
			"claude-opus-5",
			"claude-haiku-4-5",
			"claude-3-7-sonnet-20250219",
			"claude-3-5-sonnet-20241022",
			"claude-3-5-haiku-20241022",
		},
		ContextLimits: builtinContextLimits["anthropic"],
	},
	{
		ID:             "openai",
		Name:           "OpenAI",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "OPENAI_API_KEY",
		DefaultBaseURL: "https://api.openai.com/v1",
		DefaultModels: []string{
			"gpt-5",
			"gpt-5-mini",
			"gpt-5-nano",
			"gpt-4o",
			"gpt-4o-mini",
			"o3-mini",
		},
		ContextLimits: builtinContextLimits["openai"],
	},
	{
		ID:             "openrouter",
		Name:           "OpenRouter",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "OPENROUTER_API_KEY",
		DefaultBaseURL: "https://openrouter.ai/api/v1",
		DefaultModels: []string{
			"meta-llama/llama-3.3-70b-instruct:free",
			"deepseek/deepseek-r1:free",
			"google/gemini-2.5-flash:free",
			"qwen/qwen-2.5-coder-32b-instruct:free",
			"anthropic/claude-sonnet-5",
			"anthropic/claude-3.7-sonnet",
			"openai/gpt-4o",
			"meta-llama/llama-3.3-70b-instruct",
			"deepseek/deepseek-r1",
		},
		ContextLimits: builtinContextLimits["openrouter"],
	},
	{
		ID:             "groq",
		Name:           "Groq",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "GROQ_API_KEY",
		DefaultBaseURL: "https://api.groq.com/openai/v1",
		DefaultModels: []string{
			"llama-3.3-70b-versatile",
			"deepseek-r1-distill-llama-70b",
			"qwen-2.5-coder-32b",
			"llama-3.1-8b-instant",
		},
		ContextLimits: builtinContextLimits["groq"],
	},
	{
		ID:             "google",
		Name:           "Google Gemini",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "GEMINI_API_KEY",
		DefaultBaseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
		DefaultModels: []string{
			"gemini-2.5-flash",
			"gemini-2.5-pro",
			"gemini-2.5-flash-lite",
			"gemini-2.0-flash",
			"gemini-2.0-flash-thinking-exp",
			"gemini-1.5-pro",
		},
		ContextLimits: builtinContextLimits["google"],
	},
	{
		ID:             "ollama",
		Name:           "Ollama (Local)",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "",
		DefaultBaseURL: "http://localhost:11434/v1",
		DefaultModels: []string{
			"qwen2.5-coder",
			"deepseek-r1",
			"llama3.2",
		},
	},
	{
		ID:             "cerebras",
		Name:           "Cerebras (Ultra-Fast)",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "CEREBRAS_API_KEY",
		DefaultBaseURL: "https://api.cerebras.ai/v1",
		DefaultModels: []string{
			"llama-3.3-70b",
			"llama3.1-8b",
			"deepseek-r1-distill-llama-70b",
		},
		ContextLimits: builtinContextLimits["cerebras"],
	},
	{
		ID:             "mistral",
		Name:           "Mistral AI",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "MISTRAL_API_KEY",
		DefaultBaseURL: "https://api.mistral.ai/v1",
		DefaultModels: []string{
			"codestral-latest",
			"mistral-large-latest",
			"mistral-small-latest",
			"pixtral-large-latest",
			"ministral-8b-latest",
		},
		ContextLimits: builtinContextLimits["mistral"],
	},
	{
		ID:             "sambanova",
		Name:           "SambaNova Fast",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "SAMBANOVA_API_KEY",
		DefaultBaseURL: "https://api.sambanova.ai/v1",
		DefaultModels: []string{
			"Meta-Llama-3.3-70B-Instruct",
			"Qwen2.5-Coder-32B-Instruct",
			"DeepSeek-R1-Distill-Llama-70B",
		},
		ContextLimits: builtinContextLimits["sambanova"],
	},
	{
		ID:             "github",
		Name:           "GitHub Models",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "GITHUB_TOKEN",
		DefaultBaseURL: "https://models.inference.ai.azure.com",
		DefaultModels: []string{
			"gpt-4o",
			"gpt-4o-mini",
			"o3-mini",
			"DeepSeek-R1",
			"meta-llama-3.3-70b-instruct",
		},
		ContextLimits: builtinContextLimits["github"],
	},
	{
		ID:             "cohere",
		Name:           "Cohere",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "COHERE_API_KEY",
		DefaultBaseURL: "https://api.cohere.com/v2",
		DefaultModels: []string{
			"command-r-plus-08-2024",
			"command-r-08-2024",
		},
		ContextLimits: builtinContextLimits["cohere"],
	},
	{
		ID:             "cloudflare",
		Name:           "Cloudflare Workers AI",
		Protocol:       "openai-compatible",
		APIKeyEnvVar:   "CLOUDFLARE_API_TOKEN",
		DefaultBaseURL: "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1",
		DefaultModels: []string{
			"@cf/meta/llama-3.3-70b-instruct",
			"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
			"@cf/qwen/qwen2.5-coder-32b-instruct",
		},
		ContextLimits: builtinContextLimits["cloudflare"],
	},
}

BuiltinProviders maps all pre-registered LLM providers. Context limits come from builtinContextLimits (research-backed) unless overridden by the user's own config model_map.

View Source
var ErrStreamIdle = errors.New("provider stream idle timeout")

ErrStreamIdle is returned when a provider stream stalls (no chunk within the idle window). It is retryable — the provider may recover on a new call.

View Source
var OpenCodeFreeModels = []string{
	"deepseek-v4-flash-free",
	"hy3-free",
	"mimo-v2.5-free",
	"laguna-s-2.1-free",
	"ling-3.0-tiny-free",
	"longcat-2.0-free",
	"nemotron-3-ultra-free",
	"nemotron-3.5-lightning-free",
	"big-pickle",
}

OpenCodeFreeModels is the static list of OpenCode free-tier models BroCode surfaces through model discovery. BroCode is fully standalone: it talks to the OpenCode OpenAI-compatible gateway over HTTP and never spawns the opencode CLI binary — model availability comes from this list plus the provider registry, not by shelling out to any external process.

Functions

func ContextWindowFor

func ContextWindowFor(cfg AppConfig, providerID, model string) int

ContextWindowFor returns the real context window (in tokens) for a model, resolved in priority order:

  1. The user-declared per-model "limit" in their config (the opencode.jsonc style block the /connect wizard accepts) — highest priority.
  2. The live context_length reported by the gateway's /models endpoint (cached during DiscoverModels). It represents the real per-deployment cap — e.g. poolside reports 262144 although the model natively supports 1M — but is capped at the researched builtin window for the same model, so the research-backed table stays the ceiling.
  3. The builtin research-backed table for builtin providers' default models (e.g. opencode free models per-model windows, gemini = 1M, ...).
  4. A model-family fallback (claude-sonnet-*, gpt-5*, deepseek-*, gemini-*, ...) so dated or unlisted model IDs still resolve to their generation's window instead of collapsing to the 128k default.

Returns 0 when nothing is known — callers fall back to their default window.

func DiscoverModels

func DiscoverModels(cfg AppConfig) map[string][]string

DiscoverModels returns all models from detected providers using configured/builtin defaults and cached live models for instant (<1ms) non-blocking startup. It never blocks on synchronous network requests during startup.

func EstimateCostUSD

func EstimateCostUSD(model string, inputTokens, outputTokens int) float64

EstimateCostUSD returns an estimated USD cost for a completion using the model's list price with no cache discount. Returns 0 for free/unknown models (unlimited budget for local and free-gateway providers).

func EstimateCostUSDWithCache added in v0.1.1

func EstimateCostUSDWithCache(model string, inputTokens, cacheHitTokens, outputTokens int) float64

EstimateCostUSDWithCache prices a completion accounting for prompt-cache hits: cacheHitTokens of the input are billed at the family's cache-hit rate (cacheHitRatioFor) and the remainder at the full input rate. It falls back to the full-input pricing when cacheHitTokens is invalid or no cache ratio applies. Returns 0 for free/unknown models.

func ExtractEmbeddedReasoning added in v0.1.1

func ExtractEmbeddedReasoning(content string) (string, string)

ExtractEmbeddedReasoning pulls <think>...</think> chain-of-thought blocks out of the main message content so internal model deliberation is captured in Reasoning rather than leaking into visible user-facing output.

func FetchOpenAIModels

func FetchOpenAIModels(baseURL, apiKey string) ([]string, error)

FetchOpenAIModels lists the models a gateway exposes via its OpenAI-compatible GET /models endpoint. Used to populate a custom provider's model list when the user didn't declare one.

func FetchOpenAIModelsDetailed added in v0.1.1

func FetchOpenAIModelsDetailed(baseURL, apiKey string) ([]string, map[string]int, error)

FetchOpenAIModelsDetailed lists the models a gateway exposes via its OpenAI-compatible GET /models endpoint, keeping each model's context_length. Returns the deduplicated sorted IDs plus a map of model ID → context_length for the entries that report one.

func FormatTokens

func FormatTokens(n int) string

FormatTokens renders a token count compactly and readably for the UI: 512 → "512", 123443 → "123.4k", 1048576 → "1.0M".

func FriendlyName

func FriendlyName(id string) string

FriendlyName returns the display name for a provider ID, falling back to the ID itself for custom providers. The UI uses this everywhere so BroCode brands itself as its own product — the underlying gateway tool is never shown in the terminal.

func GetActiveContext7Key added in v0.1.37

func GetActiveContext7Key() string

GetActiveContext7Key retrieves the active Context7 API key. Priority: 1. Environment variable CONTEXT7_API_KEY 2. Saved key in AppConfig (~/.config/brocode/config.json)

func GetActiveSearchKey added in v0.1.37

func GetActiveSearchKey() (key string, providerName string)

GetActiveSearchKey retrieves the active web search API key and provider ("tavily" or "exa"). Priority: 1. Environment variable TAVILY_API_KEY / EXA_API_KEY 2. Saved key in AppConfig (~/.config/brocode/config.json)

func GlobalConfigPath

func GlobalConfigPath() string

GlobalConfigPath returns the user's global config file path (machine-written by the wizard).

func GlobalJSONCConfigPath

func GlobalJSONCConfigPath() string

GlobalJSONCConfigPath returns the hand-editable global config path (BroCode's own format, JSONC — comments allowed). Overrides config.json when both exist.

func IdleWatchdog

func IdleWatchdog(ctx context.Context, cancel context.CancelFunc, idleTimeout time.Duration) (mark func(), stop func(), idleFired func() bool)

IdleWatchdog aborts a stream that stops delivering data. It is the correct "timeout" for SSE: mark() must be called after every successfully read chunk, and if idleTimeout elapses with no activity the watchdog calls cancel(). The returned stop() releases the watchdog goroutine early (it also exits on its own when ctx is done). idleFired is set atomically before cancel() so the caller can distinguish an idle abort from a user cancel.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reports whether a failed completion is worth retrying on the same provider before routing to a fallback. Permanent user-caused failures (cancel, invalid model, auth) are never retried; transient network stalls and provider overload are.

func LiveModelsVersion added in v0.1.43

func LiveModelsVersion() int64

LiveModelsVersion returns the current cache generation counter. Callers can compare snapshots to detect when live models have arrived.

func NewStreamingHTTPClient

func NewStreamingHTTPClient() *http.Client

NewStreamingHTTPClient returns an http.Client suitable for SSE streaming: no total body deadline (which would kill long generations), but a bounded wait for the response headers so a dead provider fails fast instead of hanging the turn.

func OpenCodeAuthPath added in v0.1.3

func OpenCodeAuthPath() string

OpenCodeAuthPath returns opencode's credential store path (~/.local/share/opencode/auth.json on Unix, %LOCALAPPDATA%/opencode/auth.json on Windows), keyed by provider ID.

func OpenCodeConfigPath

func OpenCodeConfigPath() string

OpenCodeConfigPath returns the local OpenCode config file path (~/.config/opencode/opencode.jsonc)

func OpenCodeImportEnabled

func OpenCodeImportEnabled() bool

OpenCodeImportEnabled reports whether BroCode may borrow opencode's config (provider blocks + MCP servers) and auto-detect the opencode provider. BroCode has its own standalone configs (.brocode/, ~/.config/brocode/); the opencode import is only a convenience bridge. Set BROCODE_NO_OPENCODE=1 to run fully standalone: BroCode configs only, no opencode.jsonc import and no opencode provider auto-detection.

func ParseModelJSON

func ParseModelJSON(input string) ([]string, map[string]CustomModel, error)

ParseModelJSON parses the models block entered in the custom-provider wizard. It accepts the opencode.jsonc shape (object keyed by model ID with name/limit) OR a plain JSON array of model ID strings, and returns the ordered model IDs plus the per-model detail map.

It is also tolerant of a bare object body pasted without the wrapping braces (a very common copy-paste slip when taking the "models" block out of opencode.jsonc) — the body is wrapped in { } automatically.

func ProjectConfigPath

func ProjectConfigPath() string

ProjectConfigPath returns the current working directory's config file path (machine-written by the wizard).

func ProjectJSONCConfigPath

func ProjectJSONCConfigPath() string

ProjectJSONCConfigPath returns the hand-editable project config path (BroCode's own format, JSONC — comments allowed). Overrides config.json when both exist.

func RedactAPIError

func RedactAPIError(err error) error

RedactAPIError converts a generic error into its display-safe form, stripping any embedded credential material. Currently identity for typed errors; guards against leaking request bodies on the wire.

func RegisterCacheHitRatio added in v0.1.1

func RegisterCacheHitRatio(model string, ratio float64)

RegisterCacheHitRatio overrides the cache-hit price ratio for a model (fraction of the input price billed for cached tokens). A ratio ≤ 0 removes the override and falls back to the built-in table.

func RegisterModelPrice

func RegisterModelPrice(model string, inputPrice, outputPrice float64)

RegisterModelPrice overrides the price for a model (USD per million input and output tokens). Values ≤0 fall back to the built-in table.

func ResolveModelID

func ResolveModelID(models []string, model string) string

ResolveModelID maps a possibly-stale saved model ID onto a real model in the provider's list. Exact match wins; otherwise a listed model that shares the last path segment ("laguna-s-2.1" → "poolside/laguna-s-2.1") is chosen so configs saved before an API added its vendor prefix keep working; otherwise the input is returned unchanged (unknown custom IDs still go through as-is).

func SaveContext7Key added in v0.1.37

func SaveContext7Key(key string) error

SaveContext7Key saves or clears the Context7 API key in ~/.config/brocode/config.json.

func SaveGlobalConfig

func SaveGlobalConfig(cfg AppConfig) error

SaveGlobalConfig saves config to global path (~/.config/brocode/config.json) safely with field preservation: search/context7 keys on disk are never clobbered by partial in-memory configs.

func SaveSearchKey added in v0.1.37

func SaveSearchKey(key string) error

SaveSearchKey saves the search API key to global ~/.config/brocode/config.json with auto provider detection.

func SaveSearchProviderKey added in v0.1.37

func SaveSearchProviderKey(providerName, key string) error

SaveSearchProviderKey saves a specific search provider's key (e.g. "tavily" or "exa") to ~/.config/brocode/config.json.

func StreamTruncated

func StreamTruncated() error

StreamTruncated reports the canonical truncated-stream error so adapters can return it consistently and routing can classify it as retryable.

Types

type APIError

type APIError struct {
	StatusCode int
	Body       string
}

APIError is a typed non-2xx response from a provider. The status code lets the routing layer decide whether a retry can possibly help: 429/5xx are transient (retry/fallback), 4xx auth/model errors are permanent.

func (*APIError) Error

func (e *APIError) Error() string

type AnthropicAdapter

type AnthropicAdapter struct {
	BaseURL string
	APIKey  string
	Client  *http.Client
}

AnthropicAdapter implements ProviderAdapter for Anthropic API.

func NewAnthropicAdapter

func NewAnthropicAdapter(baseURL, apiKey string) *AnthropicAdapter

NewAnthropicAdapter creates a new Anthropic provider adapter.

func (*AnthropicAdapter) Complete

type AppConfig

type AppConfig struct {
	DefaultProvider string                          `json:"default_provider,omitempty"`
	DefaultModel    string                          `json:"default_model,omitempty"`
	Providers       map[string]CustomProviderConfig `json:"providers,omitempty"`
	// FallbackPolicy controls automatic model routing when the primary fails:
	// "auto" (default), "confirm" (ask before cross-vendor fallback), or
	// "primary_only" (never fall back).
	FallbackPolicy string `json:"fallback_policy,omitempty"`
	SearchKey      string `json:"search_key,omitempty"`      // Tavily or Exa API key
	SearchProvider string `json:"search_provider,omitempty"` // "tavily" or "exa"
	Context7Key    string `json:"context7_key,omitempty"`    // Context7 Documentation API key
}

AppConfig represents global and local settings for BroCode.

func LoadConfig

func LoadConfig() AppConfig

LoadConfig loads configuration from BroCode and OpenCode locations with merging. Precedence (highest wins): project BroCode → global BroCode → opencode.jsonc. BroCode's own config is authoritative; opencode.jsonc only fills gaps — a provider is imported only when BroCode configures NO provider with the same ID or the same base URL (so a duplicate like lalarasa vs a BroCode-configured gateway never shows up twice).

type AskQuestion

type AskQuestion struct {
	Question string   `json:"question"`
	Options  []string `json:"options"`
	Multi    bool     `json:"multi"`
}

AskQuestion is a single interactive multiple-choice question presented to the user. It lives in provider (not tool) so the OpenCode CLI adapter can present clarification questions through the same interactive modal as the ask_user tool — the tool package aliases these types to keep one source of truth without an import cycle.

func ParseAskBlocks

func ParseAskBlocks(text string) ([]AskQuestion, string)

ParseAskBlocks extracts structured question blocks ([Q]...[/Q] with [O] options and an optional [M] multi flag) from model output. It returns the parsed questions plus the text with all marker blocks removed (the model's own analysis around the questions is preserved). When no question blocks are present it returns nil and the original text unchanged. ANSI escapes are stripped first so colored CLI output still parses.

type AskResult

type AskResult struct {
	Question string   `json:"question"`
	Answers  []string `json:"answers"`
	Custom   string   `json:"custom,omitempty"`
}

AskResult is the user's answer to one question.

type AskUserHandler

type AskUserHandler func(ctx context.Context, questions []AskQuestion) ([]AskResult, error)

AskUserHandler presents interactive questions to the user and blocks until they answer. Wired by the TUI; nil means headless (no modal possible).

type CompletionRequest

type CompletionRequest struct {
	Model       string           `json:"model"`
	Messages    []Message        `json:"messages"`
	Tools       []ToolDefinition `json:"tools,omitempty"`
	Temperature float64          `json:"temperature,omitempty"`
}

CompletionRequest is the generic payload sent to any provider adapter.

type CompletionResponse

type CompletionResponse struct {
	Content      string     `json:"content"`
	Reasoning    string     `json:"reasoning"`
	ToolCalls    []ToolCall `json:"tool_calls"`
	Usage        Usage      `json:"usage"`
	FinishReason string     `json:"finish_reason"`
}

CompletionResponse is the generic output returned by a provider adapter.

type CustomModel

type CustomModel struct {
	Name   string      `json:"name,omitempty"`
	Limits ModelLimits `json:"limit"`
}

CustomModel describes a declared model with optional display name and limits.

type CustomProviderConfig

type CustomProviderConfig struct {
	Protocol  string                 `json:"protocol"`            // "openai-compatible" or "anthropic"
	BaseURL   string                 `json:"base_url"`            // API endpoint base URL
	APIKeyEnv string                 `json:"api_key_env"`         // Environment variable name for key
	APIKey    string                 `json:"api_key,omitempty"`   // Stored API key (0600 mode file only)
	Models    []string               `json:"models,omitempty"`    // Pre-declared model IDs
	ModelMap  map[string]CustomModel `json:"model_map,omitempty"` // Model ID → name/limits details
}

CustomProviderConfig represents custom user provider overrides.

type DetectedProvider

type DetectedProvider struct {
	Info   ProviderInfo
	APIKey string
}

DetectedProvider contains provider metadata and resolved API key.

func AutoDetect

func AutoDetect(cfg AppConfig) []DetectedProvider

AutoDetect scans environment variables and configuration to find usable providers.

type Message

type Message struct {
	Role       string     `json:"role"`
	Content    string     `json:"content,omitempty"`
	Reasoning  string     `json:"reasoning,omitempty"`
	ToolCalls  []ToolCall `json:"tool_calls,omitempty"`
	ToolCallID string     `json:"tool_call_id,omitempty"`
	// Mode and Model are BroCode-internal metadata stamped when the turn was
	// produced. They are never forwarded to providers (adapters map the known
	// fields into their own wire format) — they exist so a persisted session
	// can restore each answer with its original mode badge and model label.
	Mode  string `json:"mode,omitempty"`
	Model string `json:"model,omitempty"`
}

Message represents a chat message in the harness.

type ModelEntry

type ModelEntry struct {
	Provider string `json:"provider"`
	Model    string `json:"model"`
}

ModelEntry maps provider ID and model ID for picker UI.

type ModelInfo added in v0.1.1

type ModelInfo struct {
	ID            string `json:"id"`
	ContextLength int    `json:"context_length"`
}

ModelInfo is one entry from a gateway's /models endpoint: the wire model ID plus its context_length when the gateway reports one (OpenAI-compatible gateways expose context_length per model; poolside uses it to report the real per-key deployment cap, which differs from the native model window).

type ModelLimits

type ModelLimits struct {
	Context int `json:"context,omitempty"` // context window size in tokens
	Output  int `json:"output,omitempty"`  // max output tokens
	// Optional list prices in USD per million tokens. Override the built-in
	// price table when a custom provider/model bills differently.
	InputPrice  float64 `json:"input_price,omitempty"`  // USD per M input tokens
	OutputPrice float64 `json:"output_price,omitempty"` // USD per M output tokens
}

ModelLimits mirrors opencode.jsonc's per-model "limit" block.

type OpenAIAdapter

type OpenAIAdapter struct {
	BaseURL string
	APIKey  string
	Client  *http.Client

	// StreamIdleTimeout bounds a gap with no SSE chunk before the stream is
	// treated as stalled. Overridable in tests; defaults to
	// DefaultStreamIdleTimeout.
	StreamIdleTimeout time.Duration
}

OpenAIAdapter implements ProviderAdapter for OpenAI-compatible HTTP APIs.

func NewOpenAIAdapter

func NewOpenAIAdapter(baseURL, apiKey string) *OpenAIAdapter

NewOpenAIAdapter creates a new adapter for OpenAI-compatible APIs.

func (*OpenAIAdapter) Complete

func (*OpenAIAdapter) StreamComplete

func (a *OpenAIAdapter) StreamComplete(ctx context.Context, req CompletionRequest, onDelta func(string)) (*CompletionResponse, error)

StreamComplete implements StreamingAdapter: content deltas are forwarded via onDelta while tool-call fragments accumulate across SSE chunks. The stream is bounded by the idle watchdog, NOT a total deadline — long generations that keep emitting chunks are never cut off.

type OpenCodeAdapter

type OpenCodeAdapter struct {
	// contains filtered or unexported fields
}

OpenCodeAdapter routes completion requests to the OpenCode free-model gateway (an OpenAI-compatible HTTP endpoint). It is intentionally free of any dependency on the opencode CLI binary: no subprocess is spawned and no output is scraped. BroCode's own engine controls the agent loop, system prompt and tools, so the gateway model receives the same native context as any other provider — there is no separate "gateway loop" to compensate for.

func NewOpenCodeAdapter

func NewOpenCodeAdapter() *OpenCodeAdapter

NewOpenCodeAdapter creates an OpenCode provider adapter wired to the official free-model gateway. The endpoint is BroCode-controlled (never a personal/third-party URL), so no opencode installation is required.

func (*OpenCodeAdapter) Complete

func (*OpenCodeAdapter) CompleteWithProgress

func (a *OpenCodeAdapter) CompleteWithProgress(ctx context.Context, req CompletionRequest, onProgress func(string)) (*CompletionResponse, error)

CompleteWithProgress satisfies the ProgressingAdapter interface for compatibility.

func (*OpenCodeAdapter) StreamComplete added in v0.1.11

func (a *OpenCodeAdapter) StreamComplete(ctx context.Context, req CompletionRequest, onDelta func(string)) (*CompletionResponse, error)

StreamComplete forwards the request to the HTTP gateway, streaming content deltas to onDelta (the chat streaming handler) token by token.

type ProgressingAdapter

type ProgressingAdapter interface {
	ProviderAdapter
	CompleteWithProgress(ctx context.Context, req CompletionRequest, onProgress func(string)) (*CompletionResponse, error)
}

ProgressingAdapter is an optional capability: providers whose execution produces realtime status/progress lines (e.g. a local CLI running tools). onProgress receives each status line as it appears; the returned response still holds the final accumulated result.

type ProviderAdapter

type ProviderAdapter interface {
	Complete(ctx context.Context, req CompletionRequest) (*CompletionResponse, error)
}

ProviderAdapter defines the unified contract for LLM communication.

type ProviderInfo

type ProviderInfo struct {
	ID             string   `json:"id"`
	Name           string   `json:"name"`
	Protocol       string   `json:"protocol"` // "openai-compatible" or "anthropic"
	APIKeyEnvVar   string   `json:"api_key_env_var"`
	DefaultBaseURL string   `json:"default_base_url"`
	DefaultModels  []string `json:"default_models"`
	// ContextLimits maps model ID → context window in tokens. Values are
	// research-backed (vendor docs, 2026): used as the fallback when the user
	// hasn't declared a per-model limit in their config. 0 = unknown → 128k.
	ContextLimits map[string]int `json:"context_limits,omitempty"`
	// ModelsPublic marks an OpenAI-compatible provider whose /models endpoint
	// needs no API key (e.g. open local models or public proxy).
	// The live list is fetched unconditionally and is AUTHORITATIVE — models
	// the proxy does not serve are never offered in the picker.
	ModelsPublic bool `json:"models_public,omitempty"`
}

ProviderInfo describes a provider capability & configuration metadata.

type SearchProviderStatus added in v0.1.37

type SearchProviderStatus struct {
	PrimaryProvider   string // "tavily", "exa", or "free"
	PrimaryKey        string
	SecondaryProvider string
	SecondaryKey      string
	Badge             string // " · 🌐:Free", " · 🌐:Tavily", " · 🌐:Exa", " · 🌐:Tavily+Exa"
}

SearchProviderStatus describes configured search providers and multi-tier fallback order.

func GetSearchProviderStatus added in v0.1.37

func GetSearchProviderStatus() SearchProviderStatus

GetSearchProviderStatus computes the active and fallback search configuration.

type StreamingAdapter

type StreamingAdapter interface {
	ProviderAdapter
	StreamComplete(ctx context.Context, req CompletionRequest, onDelta func(string)) (*CompletionResponse, error)
}

StreamingAdapter is an optional capability: providers that can emit content deltas token-by-token. onDelta receives each content fragment as it arrives; the returned response still holds the fully accumulated result.

type ToolCall

type ToolCall struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Arguments string `json:"arguments"`
}

ToolCall represents a function call invoked by the model.

func ExtractEmbeddedToolCalls added in v0.1.1

func ExtractEmbeddedToolCalls(content string) ([]ToolCall, string)

ExtractEmbeddedToolCalls inspects message content for pseudo-XML tool calls emitted by models like Poolside laguna-s-2.1, Qwen, or custom fine-tunes. It parses the tool calls into standard ToolCall structs and returns the cleaned remaining text content.

type ToolDefinition

type ToolDefinition struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Parameters  map[string]any `json:"parameters"`
}

ToolDefinition defines a tool schema for native function calling.

type Usage

type Usage struct {
	PromptTokens     int `json:"prompt_tokens"`
	CompletionTokens int `json:"completion_tokens"`
	TotalTokens      int `json:"total_tokens"`
	// PromptCacheHitTokens is the portion of the input served from the prompt
	// cache, billed at the model's cache-hit rate instead of the full input
	// price. Mapped from DeepSeek prompt_cache_hit_tokens, OpenAI
	// prompt_tokens_details.cached_tokens, and Claude cache_read_input_tokens.
	PromptCacheHitTokens int `json:"prompt_cache_hit_tokens"`
}

Usage tracks token consumption.

Jump to

Keyboard shortcuts

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