provider

package
v0.31.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package provider defines the (model, embedding, edit-apply) boundaries the engine talks to, plus the default Claude model tiers. v1 wires NO implementations here — these are the seams the model-backed phases (understand, learn, agent) plug into. Defaults are recorded now so the tiered strategy is explicit and configurable.

Index

Constants

View Source
const (
	// EnvEndpointURL is the FULL compat base including any path prefix
	// (Groq-style), e.g. http://localhost:11434/v1 — {base}/chat/completions is
	// the turn endpoint. Setting it (with no memcode token) puts the CLI in
	// endpoint mode.
	EnvEndpointURL = "MEMCODE_ENDPOINT_URL"
	// EnvEndpointKey is the endpoint's optional bearer credential; unset sends
	// no Authorization header (a keyless local endpoint).
	EnvEndpointKey = "MEMCODE_ENDPOINT_KEY"
	// EnvEndpointModel is the endpoint's INITIAL model id — used until a /model
	// choice is remembered for the endpoint (config wins once one exists).
	EnvEndpointModel = "MEMCODE_ENDPOINT_MODEL"
)
View Source
const (
	EnvAPIURL   = "MEMCODE_API_URL"
	EnvAPIToken = "MEMCODE_API_TOKEN"

	// TokenPrefix marks org-scoped gateway keys minted by /login — the ONLY
	// kind of credential that exists. Its presence in the stored token IS the
	// local logged-in signal (zero network at boot).
	TokenPrefix = "memcode_"

	// DefaultAPIURL is the production memcode gateway.
	DefaultAPIURL = "https://code.memcode.ai"
)

--- gateway connection ---

The CLI has exactly ONE backend: the memcode gateway (cli → api → llms). Hosted provider keys, BYOK storage, and metering live SERVER-side in the api module; the wire adapters and routing policy ship in this binary (the CLI is the agent — shared providers/*, llm selection). The endpoint defaults to production; MEMCODE_API_URL is a DEV OVERRIDE for pointing at a locally running gateway (`go run ./api`), never a requirement.

View Source
const EnvCredentialSource = "MEMCODE_CREDENTIAL_SOURCE"

EnvCredentialSource names the explicitly-selected credential source. Empty = own exported keys only (the ambient path). Set to a subscription source ("copilot", …) — by the first-run wizard or by hand — to activate a login that lives in another tool's files/keychain, which never auto-activates.

View Source
const EnvCredentials = "MEMCODE_CREDENTIALS"

EnvCredentials is the ordered CONSENT LIST of attached credential sources ("claude,codex"). It replaces the single-value MEMCODE_CREDENTIAL_SOURCE: with subscriptions as family lanes there is no "the" source anymore — every listed source the user has consented to attaches as a lane. The old var still reads as a one-item list and is migrated (rewritten to this var, old one deleted) the next time `memcode auth` writes; consent semantics are unchanged — a subscription NEVER activates from ambient state alone.

Variables

View Source
var ErrGatewayOnly = errors.New("not available on a custom endpoint — this needs the memcode gateway (run /login)")

ErrGatewayOnly is returned by the side-channel capabilities (websearch / webfetch / advisor) in endpoint mode: they are memcode gateway services with no compat equivalent. Callers already degrade on error (webfetch falls back to the local fetch; the web_search tool def isn't advertised off-gateway).

View Source
var ErrNoEndpointModel = errors.New("no model selected for this endpoint — pick one with /model, or set MEMCODE_ENDPOINT_MODEL")

ErrNoEndpointModel is returned when a turn reaches an endpoint transport with no model anywhere (no session pin, no endpoint default).

View Source
var ErrNotLoggedIn = fmt.Errorf("not signed in — run /login to connect to memcode.ai")

ErrNotLoggedIn is returned by the lazy provider for any model-backed call made before /login. The TUI shows its own gate before dispatch; this is the backstop for any path that slips through.

Functions

func APITokenSource

func APITokenSource() string

APITokenSource reports where the gateway token resolves from, for diagnostics (e.g. `memcode doctor`) — "environment" or the global config path — or "" if none is found. Call it BEFORE LoadDotEnv so an already-loaded file isn't misreported as the process environment.

func APIURL

func APIURL() string

APIURL resolves the gateway endpoint: the MEMCODE_API_URL override if set, otherwise production.

func AttachedSources added in v0.26.0

func AttachedSources() []string

AttachedSources returns the ordered, canonicalized, deduplicated consent list. MEMCODE_CREDENTIALS wins; the legacy single-value var reads as a one-item list so pre-migration installs keep working. Unknown tokens are dropped (the boot warning surfaces them via SelectedSourcesUnresolved's resolution pass, not here).

func BackendServingLabel added in v0.26.0

func BackendServingLabel(backend string) string

BackendServingLabel renders the per-turn "via X" for a lane-stamped backend, "" for gateway/exclusive stamps (callers fall back to "memcode" or the exclusive endpoint's name).

func ByokDelete

func ByokDelete(ctx context.Context, providerID string) error

ByokDelete removes the user's key for a provider.

func ByokList

func ByokList(ctx context.Context) (cloudclient.ByokKeys, error)

ByokList fetches the provider roster + the user's masked key rows.

func ByokPut

func ByokPut(ctx context.Context, providerID, key string) (cloudclient.ByokPutResult, error)

ByokPut stores/replaces the user's key for a provider (gateway live-probes it first). The caller is responsible for redacting the key from any UI/log surfaces BEFORE calling.

func ByokValidate

func ByokValidate(ctx context.Context, providerID string) (bool, string, error)

ByokValidate live-probes the stored key.

func CatalogKnows

func CatalogKnows(id string) bool

CatalogKnows reports whether the embedded catalog has a real entry for a model id — the cost-display gate: uncataloged (local) models show token counts, not a $ figure priced off the defaults card.

func CatalogWindow

func CatalogWindow(id string) int

CatalogWindow returns the embedded catalog's context window for a model id it KNOWS (exact id or label) — 0 otherwise. Endpoint mode keys on it for the /model picker's window column and the pin's meter sizing: known ids get real numbers, unknown local models get blank, never a made-up default.

func ConventionalKey

func ConventionalKey(base string) string

ConventionalKey returns the standard env var's value for a known provider host, "" for local/unknown endpoints (a keyless Ollama stays keyless).

func DefaultModel

func DefaultModel(t Tier) string

DefaultModel returns the default model id for a tier.

planner, reviewer        -> Sol    (hard reasoning — the frontier tier)
coder, synthesizer       -> Terra  (the everyday default)
classifier               -> Luna   (the reducer's cheap, frequent router)

func EffectiveModel

func EffectiveModel(s string) string

EffectiveModel resolves a configured tier value (alias or model id) to the model REQUESTED of the gateway. Model policy is the gateway's call — it may re-target a request at the self-hosted pool — but the requested id still matters: it names the tier intent and prices the counterfactual.

func EndpointModels

func EndpointModels(ctx context.Context, ep Endpoint) []string

EndpointModels lists the endpoint's model ids via GET {base}/models (part of the compat standard — OpenAI, Groq, Ollama, LM Studio, vLLM all serve it). When the base was configured WITHOUT its /v1 path prefix, {base}/models 404s on most local runtimes — one retry at {base}/v1/models covers that without the CLI ever rewriting the configured base for turns. Errors (endpoint lacks the route entirely) return nil — the /model picker then falls back to the config list / free-text entry.

func EndpointName

func EndpointName(base string) string

EndpointName derives a short display name from a base URL — the host (with port) for a valid URL, the raw string otherwise. Config-listed endpoints carry their own names; this covers the env-defined ones.

func ExplicitCredentialSource added in v0.25.0

func ExplicitCredentialSource() bool

ExplicitCredentialSource reports the user has actively selected a credential source (memcode auth …) — the consent signal that lets a subscription login outrank other backends.

func GlobalEnvPath

func GlobalEnvPath() string

GlobalEnvPath returns the user-level secrets file: $XDG_CONFIG_HOME/memcode/.env if XDG_CONFIG_HOME is set, otherwise ~/.config/memcode/.env. Returns "" if no home directory can be determined.

func LaneBackendVendor added in v0.26.0

func LaneBackendVendor(backend string) (vendor, kind string, ok bool)

LaneBackendVendor maps a per-turn Backend stamp to its lane identity: ("anthropic","sub",true) for "claude-sub", ("openai","ownkey",true) for "ownkey:openai", ok=false for gateway/exclusive stamps.

func LoadDotEnv

func LoadDotEnv()

LoadDotEnv loads KEY=VALUE pairs into the process environment WITHOUT overriding variables already set, from the user-global file only (GlobalEnvPath, e.g. ~/.config/memcode/.env).

The working repo's .env is DELIBERATELY not read. That file belongs to the project, not the agent, and honoring it meant a cloned repo could silently hijack the agent: its MEMCODE_API_URL/MEMCODE_ENDPOINT_URL would redirect the whole conversation to an arbitrary endpoint, and an app's OPENAI_API_KEY meant for the app would get picked up and billed by the agent. Credentials come from the exported environment or the memcode-owned global file, never from the repo being worked on.

Precedence: real exported env > global file. Best-effort: a missing file is not an error. Secrets live in the gitignored global env file, never in .memcode config or the database.

func OwnKeyVendor added in v0.10.0

func OwnKeyVendor(baseURL string) (string, bool)

OwnKeyVendor reports the vendor name when a base URL is a direct-provider host memcode dials with an exported key ("anthropic", "openai"), ok=false for a generic/local endpoint. Lets diagnostics name an own-key backend for what it is instead of calling it a nameless "custom endpoint".

func ResolveAlias

func ResolveAlias(s string) string

ResolveAlias maps the short aliases (opus|sonnet|haiku|sol|terra|luna) to model ids. The Claude aliases (opus|sonnet|haiku) are kept for backward compat with existing configs; the GPT-5.6 aliases (sol|terra|luna) are the current tiers. Any other value is treated as a literal model id and returned unchanged.

func SelectedSourceUnresolved added in v0.25.0

func SelectedSourceUnresolved() (string, bool)

SelectedSourceUnresolved reports a credential source the user explicitly selected that did NOT resolve to a live login (expired token, signed out of the host tool). The TUI surfaces this at boot: serving silently falling back to another credential is exactly the failure users cannot see.

func SelectedSourcesUnresolved added in v0.26.0

func SelectedSourcesUnresolved() []string

SelectedSourcesUnresolved lists attached sources whose login did NOT resolve (expired token, signed out of the host tool). Boot warns per source: serving silently missing a consented lane is the failure users cannot see.

func ServingLabel added in v0.25.0

func ServingLabel(name string) string

ServingLabel maps an endpoint's internal name to the word a user chose in the auth wizard — the "via X" in the TUI's served-by line. Empty for endpoints that aren't a subscription source (their Name already reads fine).

func SetRetryNotify

func SetRetryNotify(prov ModelProvider, fn func(attempt int, err error, delay time.Duration))

SetRetryNotify wires a retry-notify callback into the gateway transport (if the provider is the SDK client, which it is in production). No-op for any provider that doesn't support it (test fakes, a future local backend) — those just get silent retry. This is the seam the runtime uses to surface "⊙ retrying…" in the TUI without coupling itself to the SDK's concrete client type.

func ShortModel

func ShortModel(id string) string

ShortModel maps a model id back to its short alias for display (the inverse of ResolveAlias). Unknown ids are returned unchanged.

func SourceVendor added in v0.26.0

func SourceVendor(source string) string

SourceVendor returns the catalog vendor a canonical source serves, "" for unknown sources.

func SubscriptionEndpointName added in v0.25.0

func SubscriptionEndpointName(name string) bool

SubscriptionEndpointName reports whether an endpoint name is one of the wizard-selected subscription sources (vs a custom endpoint or own key).

Types

type Advisor

type Advisor interface {
	Advise(ctx context.Context, question, effort string) (string, error)
}

Advisor is an optional capability: a provider that can ask a second-opinion model (a different vendor) to advise the best path forward. Type-assert for it.

type Applier

type Applier interface {
	Apply(ctx context.Context, path string, edit Edit) (ApplyResult, error)
}

Applier merges an edit into a file. v1 = anchored search/replace; a fast-apply model can be swapped in later without touching callers.

type ApplyResult

type ApplyResult struct {
	Diff    string
	Applied bool
}

ApplyResult reports the outcome of applying an Edit.

type Connector

type Connector interface {
	Connected() bool
	SetCredentials(url, token string)
}

Connector is the credential-swap capability the TUI needs from a provider: present on *Lazy, absent on test fakes (which count as connected). This is the seam runtime.Session.Connected forwards through — and since Phase C, Connected means hosted-OR-endpoint (any usable backend); Endpointer (above) is the sibling seam that says WHICH.

type Edit

type Edit struct {
	OldString  string
	NewString  string
	ReplaceAll bool
}

Edit is an anchored search/replace edit (see the Agent Runtime Contract).

type Endpoint

type Endpoint struct {
	Name    string   // short display name ("ollama", or the host:port for env endpoints)
	BaseURL string   // full compat base incl. any path prefix (http://localhost:11434/v1)
	Key     string   // optional bearer; "" = no Authorization header
	Model   string   // session model id ("" = resolve via GET {base}/models or /model)
	Models  []string // optional curated picker list / allowlist from config

	// Headers are extra request headers a subscription backend requires to
	// accept the turn (a Copilot endpoint's Editor-Version / integration id).
	// Set only by the credential sources; empty for a normal endpoint. Carried
	// through to the compat transport.
	Headers map[string]string
}

Endpoint describes one arbitrary OpenAI-compatible endpoint. Resolved from the environment (EndpointFromEnv) or from the project config's named endpoint list (config.ResolveEndpoint merges the two).

func EndpointFromEnv

func EndpointFromEnv() (Endpoint, bool)

EndpointFromEnv resolves the env-configured endpoint (the dotenv chain loads MEMCODE_ENDPOINT_* like every other knob). ok=false when no URL is set.

type Endpointer

type Endpointer interface {
	Endpoint() (Endpoint, bool)
}

Endpointer is the endpoint-mode introspection capability, sibling to Connector: present on *Lazy (and the raw conn), absent on test fakes. The runtime forwards it (Session.Endpoint) so the TUI can drive the /model picker, cost display, and capability gating off the ACTIVE backend rather than env sniffing.

type ErrLaneExhausted added in v0.26.0

type ErrLaneExhausted struct {
	Lane        LaneInfo
	Status      int
	ResetAt     time.Time // zero when unknown
	CanFallback bool      // a gateway base exists for a consented reissue
	Err         error
}

ErrLaneExhausted: a lane's credential hit its quota/rate window (HTTP 429/402 after the adapter's own retries). The provider NEVER reroutes on its own — the runtime raises the fallback-choice card and, on consent, reissues via CompleteOnGateway/StreamOnGateway.

func (*ErrLaneExhausted) Error added in v0.26.0

func (e *ErrLaneExhausted) Error() string

func (*ErrLaneExhausted) Unwrap added in v0.26.0

func (e *ErrLaneExhausted) Unwrap() error

type ErrNoLane added in v0.26.0

type ErrNoLane struct {
	Model    string
	Vendor   string
	Attached []LaneInfo
}

ErrNoLane: the turn's model belongs to a vendor with no serving path — no gateway login and no lane for that family. Terminal for the fallback walk; the message carries the remedies.

func (*ErrNoLane) Error added in v0.26.0

func (e *ErrNoLane) Error() string

type LaneInfo added in v0.26.0

type LaneInfo struct {
	Vendor string // "anthropic" | "openai" | "grok"
	Name   string // endpoint name: "claude-sub", "codex", "ownkey:anthropic", …
	Kind   string // "sub" | "ownkey"
	Model  string // the lane's default model id
}

LaneInfo is the UI/ledger surface of an attached lane.

type Laner added in v0.26.0

type Laner interface {
	Lanes() []LaneInfo
	GatewayPresent() bool
}

Laner is the policy-side seam over the lane set: selection consults it to prefer $0 subscription vendors and to clamp signed-out resolution to attached families.

type Lazy

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

Lazy is a ModelProvider whose backend connection may be ABSENT at construction: the TUI always opens (mandatory-login boot), and /login swaps real credentials in without a restart. All capability methods forward to the inner connection (the compat turn transport + the gateway side-channel client, or the compat transport alone in endpoint mode — see wire.go), or fail with ErrNotLoggedIn while signed out. Safe for concurrent use (atomic pointer swap).

func NewFromEnvLazy

func NewFromEnvLazy(endpoints ...Endpoint) *Lazy

NewFromEnvLazy constructs the lazy provider. Backend selection (one-wire Phase C): a real login (a memcode_-prefixed org key — the local logged-in signal) → the hosted gateway; else a configured endpoint (the caller's resolved config endpoint, or MEMCODE_ENDPOINT_URL) → that endpoint on the compat transport; else family lanes from attached subscriptions / own keys; else signed out — the TUI opens on the sign-in card. Unlike NewFromEnv it never fails — signed-out is a valid state for the TUI.

func (*Lazy) Advise

func (l *Lazy) Advise(ctx context.Context, question, effort string) (string, error)

func (*Lazy) ClearCredentials

func (l *Lazy) ClearCredentials()

ClearCredentials drops the gateway client (the /logout path). With a custom endpoint configured the connection falls BACK to it — the same backend selection boot applies — so signing out of memcode returns to endpoint mode, not dead air; otherwise subsequent model calls fail with ErrNotLoggedIn until the next SetCredentials.

func (*Lazy) Complete

func (l *Lazy) Complete(ctx context.Context, r wire.Request) (wire.Response, error)

func (*Lazy) CompleteOnGateway added in v0.26.0

func (l *Lazy) CompleteOnGateway(ctx context.Context, r wire.Request) (wire.Response, error)

CompleteOnGateway forces a turn onto the gateway base, bypassing lane dispatch — the consented-fallback path after an ErrLaneExhausted card.

func (*Lazy) Connected

func (l *Lazy) Connected() bool

Connected reports whether a usable backend is present — hosted gateway credentials, a configured custom endpoint, or at least one family lane.

func (*Lazy) Endpoint

func (l *Lazy) Endpoint() (Endpoint, bool)

Endpoint reports the ACTIVE custom endpoint, ok=false when hosted or signed out. This is the one backend-mode signal the runtime/TUI key on (via Session.Endpoint) — capability gating, the /model picker, cost display.

func (*Lazy) GatewayPresent added in v0.26.0

func (l *Lazy) GatewayPresent() bool

GatewayPresent reports a live hosted-gateway base (side channels exist). False in exclusive-endpoint mode and when signed out.

func (*Lazy) Lanes added in v0.26.0

func (l *Lazy) Lanes() []LaneInfo

Lanes reports the attached family lanes for UI/policy consumers (the provider.Laner seam). Empty in exclusive-endpoint mode and plain hosted.

func (*Lazy) SetCredentials

func (l *Lazy) SetCredentials(url, token string)

SetCredentials swaps in a fresh gateway connection (the /login success path). A retry-notify callback registered before login is re-applied.

func (*Lazy) SetRetryNotify

func (l *Lazy) SetRetryNotify(fn func(attempt int, err error, delay time.Duration))

SetRetryNotify satisfies the retryNotifier seam: applied to the current client if present, and remembered for the client /login constructs later.

func (*Lazy) Stream

func (l *Lazy) Stream(ctx context.Context, r wire.Request, h wire.StreamHandler) (wire.Response, error)

func (*Lazy) StreamOnGateway added in v0.26.0

func (l *Lazy) StreamOnGateway(ctx context.Context, r wire.Request, h wire.StreamHandler) (wire.Response, error)

StreamOnGateway is CompleteOnGateway's streaming twin.

func (*Lazy) WebFetch

func (l *Lazy) WebFetch(ctx context.Context, url string) (string, error)

func (*Lazy) WebSearch

func (l *Lazy) WebSearch(ctx context.Context, query string) (string, error)

type ModelFact

type ModelFact = memcode.ModelFact

type ModelProvider

type ModelProvider interface {
	Complete(ctx context.Context, r wire.Request) (wire.Response, error)
}

ModelProvider performs reasoning/generation calls (Claude in v1).

func NewFromEnv

func NewFromEnv(endpoints ...Endpoint) (ModelProvider, error)

NewFromEnv constructs the backend connection from the environment — the ONE place the connection story lives. Call it at the cmd boundary after LoadDotEnv. Backend selection: a memcode token → the hosted gateway at {api}/v1; else a configured endpoint (MEMCODE_ENDPOINT_URL, or a resolved config endpoint passed by the caller) → the same compat transport pointed at it; else the signed-out error. ONE turn transport either way (wire.go). The variadic endpoint lets callers that load project config pass its resolved endpoint without this package importing config.

type ModelsInfo

type ModelsInfo = memcode.ModelsInfo

func FetchModels

func FetchModels(ctx context.Context) (ModelsInfo, error)

FetchModels asks the gateway for the routing control plane, resolving the endpoint + credential from the environment the way every CLI surface does.

type PinnableModel

type PinnableModel = memcode.PinnableModel

func AvailablePins

func AvailablePins(ctx context.Context) []PinnableModel

AvailablePins asks the gateway which concrete models the /model picker may offer (the pinnable subset of the servable list). On gateway outage or a lane-only session, fall back to the embedded catalog so /model remains a real picker instead of collapsing to Automatic/free-text.

type Streamer

type Streamer interface {
	Stream(ctx context.Context, r wire.Request, h wire.StreamHandler) (wire.Response, error)
}

Streamer is an optional capability: a provider that can stream a completion, emitting text/usage as they arrive while still returning the fully assembled Response. Callers type-assert for it and fall back to Complete otherwise.

(Capability interfaces live at their consuming boundary — here, the CLI — not in the shared protocol package; the wire types they reference are common's.)

type Tier

type Tier string

Tier names the role of a model call. Each maps to a sensible default model but is overridable via config.

const (
	TierPlanner     Tier = "planner"
	TierCoder       Tier = "coder"
	TierReviewer    Tier = "reviewer"
	TierSynthesizer Tier = "synthesizer"
	TierClassifier  Tier = "classifier"
)

type WebFetcher

type WebFetcher interface {
	WebFetch(ctx context.Context, url string) (string, error)
}

WebFetcher is an optional capability: a provider that can fetch a specific URL server-side (text/PDF; not JS-rendered pages) and return its readable content.

type WebSearcher

type WebSearcher interface {
	WebSearch(ctx context.Context, query string) (string, error)
}

WebSearcher is an optional capability: a provider that can answer a query using server-side web search, returning a synthesized text answer. Type-assert for it.

Jump to

Keyboard shortcuts

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