Documentation
¶
Index ¶
- Constants
- Variables
- func ContextWindowFor(cfg AppConfig, providerID, model string) int
- func DiscoverModels(cfg AppConfig) map[string][]string
- func EstimateCostUSD(model string, inputTokens, outputTokens int) float64
- func EstimateCostUSDWithCache(model string, inputTokens, cacheHitTokens, outputTokens int) float64
- func ExtractEmbeddedReasoning(content string) (string, string)
- func FetchOpenAIModels(baseURL, apiKey string) ([]string, error)
- func FetchOpenAIModelsDetailed(baseURL, apiKey string) ([]string, map[string]int, error)
- func FormatTokens(n int) string
- func FriendlyName(id string) string
- func GetActiveContext7Key() string
- func GetActiveSearchKey() (key string, providerName string)
- func GlobalConfigPath() string
- func GlobalJSONCConfigPath() string
- func IdleWatchdog(ctx context.Context, cancel context.CancelFunc, idleTimeout time.Duration) (mark func(), stop func(), idleFired func() bool)
- func IsRetryable(err error) bool
- func LiveModelsVersion() int64
- func NewStreamingHTTPClient() *http.Client
- func OpenCodeAuthPath() string
- func OpenCodeConfigPath() string
- func OpenCodeImportEnabled() bool
- func ParseModelJSON(input string) ([]string, map[string]CustomModel, error)
- func ProjectConfigPath() string
- func ProjectJSONCConfigPath() string
- func RedactAPIError(err error) error
- func RegisterCacheHitRatio(model string, ratio float64)
- func RegisterModelPrice(model string, inputPrice, outputPrice float64)
- func ResolveModelID(models []string, model string) string
- func SaveContext7Key(key string) error
- func SaveGlobalConfig(cfg AppConfig) error
- func SaveSearchKey(key string) error
- func SaveSearchProviderKey(providerName, key string) error
- func StreamTruncated() error
- type APIError
- type AnthropicAdapter
- type AppConfig
- type AskQuestion
- type AskResult
- type AskUserHandler
- type CompletionRequest
- type CompletionResponse
- type CustomModel
- type CustomProviderConfig
- type DetectedProvider
- type Message
- type ModelEntry
- type ModelInfo
- type ModelLimits
- type OpenAIAdapter
- type OpenCodeAdapter
- func (a *OpenCodeAdapter) Complete(ctx context.Context, req CompletionRequest) (*CompletionResponse, error)
- func (a *OpenCodeAdapter) CompleteWithProgress(ctx context.Context, req CompletionRequest, onProgress func(string)) (*CompletionResponse, error)
- func (a *OpenCodeAdapter) StreamComplete(ctx context.Context, req CompletionRequest, onDelta func(string)) (*CompletionResponse, error)
- type ProgressingAdapter
- type ProviderAdapter
- type ProviderInfo
- type SearchProviderStatus
- type StreamingAdapter
- type ToolCall
- type ToolDefinition
- type Usage
Constants ¶
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 ¶
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.
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.
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 ¶
ContextWindowFor returns the real context window (in tokens) for a model, resolved in priority order:
- The user-declared per-model "limit" in their config (the opencode.jsonc style block the /connect wizard accepts) — highest priority.
- 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.
- The builtin research-backed table for builtin providers' default models (e.g. opencode free models per-model windows, gemini = 1M, ...).
- 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 ¶
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 ¶
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
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
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 ¶
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
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 ¶
FormatTokens renders a token count compactly and readably for the UI: 512 → "512", 123443 → "123.4k", 1048576 → "1.0M".
func FriendlyName ¶
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
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 ¶
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 ¶
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 ¶
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
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 ¶
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 ¶
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
SaveContext7Key saves or clears the Context7 API key in ~/.config/brocode/config.json.
func SaveGlobalConfig ¶
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
SaveSearchKey saves the search API key to global ~/.config/brocode/config.json with auto provider detection.
func SaveSearchProviderKey ¶ added in v0.1.37
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 ¶
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.
type AnthropicAdapter ¶
AnthropicAdapter implements ProviderAdapter for Anthropic API.
func NewAnthropicAdapter ¶
func NewAnthropicAdapter(baseURL, apiKey string) *AnthropicAdapter
NewAnthropicAdapter creates a new Anthropic provider adapter.
func (*AnthropicAdapter) Complete ¶
func (a *AnthropicAdapter) Complete(ctx context.Context, req CompletionRequest) (*CompletionResponse, error)
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 ¶
ModelEntry maps provider ID and model ID for picker UI.
type ModelInfo ¶ added in v0.1.1
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 (a *OpenAIAdapter) Complete(ctx context.Context, req CompletionRequest) (*CompletionResponse, error)
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 (a *OpenCodeAdapter) Complete(ctx context.Context, req CompletionRequest) (*CompletionResponse, error)
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
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.