Documentation
¶
Overview ¶
Package catalog owns OpenRouter model discovery across every modality. Callers ask narrow capability questions; fetching, TTLs, and offline fallbacks stay behind this seam so chat, graph tools, and future voice input do not grow separate model caches.
Index ¶
- Constants
- func CacheKey(source, base string) string
- func ReasoningWord(model Model, alwaysOn bool) string
- func Remember(options Options, models []Model) error
- type Catalog
- func (c *Catalog) BlockingReads() int64
- func (c *Catalog) Close()
- func (c *Catalog) Concrete(modelID string, resolves Resolves) string
- func (c *Catalog) ContextLength(modelID string) int
- func (c *Catalog) ContextLengthNow(modelID string) int
- func (c *Catalog) FetchedAt() time.Time
- func (c *Catalog) FetchedAtNow() time.Time
- func (c *Catalog) Identity(modelID string) string
- func (c *Catalog) Model(modelID string) (Model, bool)
- func (c *Catalog) ModelsNow() []Model
- func (c *Catalog) ModelsWithInput(modality string) []Model
- func (c *Catalog) ModelsWithOutput(modality string) []Model
- func (c *Catalog) NearestModels(modelID string, limit int) []string
- func (c *Catalog) PriceNow(modelID string) (prompt, completion float64, known bool)
- func (c *Catalog) ReasoningProfile(modelID string) (ReasoningProfile, bool)
- func (c *Catalog) Servable(modelID string) string
- func (c *Catalog) SnapshotNow() *Catalog
- func (c *Catalog) Supports(modelID, direction, modality string) bool
- func (c *Catalog) SupportsParameter(modelID, parameter string) (bool, bool)
- func (c *Catalog) Warmed(ctx context.Context) bool
- type Model
- type Options
- type ReasoningProfile
- type Resolves
Constants ¶
const ( TTL = 24 * time.Hour // DefaultBaseURL lives beside the compiled-in fallback rows because those // rows are this service's ids, so the package that serves them has to be // able to recognise its own base. DefaultBaseURL = "https://openrouter.ai/api/v1" )
Variables ¶
This section is empty.
Functions ¶
func CacheKey ¶
CacheKey is the shared service-and-base ownership key. Empty is the legacy default-service/default-base case; every other pair gets sixteen hex digits.
func ReasoningWord ¶
ReasoningWord is the short phrase a surface shows beside a model for what it does with reasoning, and the empty string when there is nothing to say.
Three published states are worth telling apart while choosing a model. Most of the catalog takes no reasoning knob at all and stays silent here. A model that takes one but publishes no level can be asked to think, but not how hard. A model that publishes `reasoning_effort` is the only kind whose thinking this harness can dial, which is what the planning economy does.
alwaysOn is the fourth state and the one nobody publishes: an endpoint that has refused to have its reasoning turned off (provider.ReasoningMandatory). It is passed in rather than looked up because it is learned from rejected calls, and a published catalog is not where learned facts live.
The phrase lives here, once, because three surfaces show it — the picker, the v2 palette, and `codeaf models` — and three spellings of one fact is how a product ends up meaning three different things by the same word.
func Remember ¶
Remember writes rows already learned from a service's successful /models response into that service-and-base compartment. It performs no network request. A later Refresh may replace these minimal rows with richer catalog facts, but a second read is never allowed to erase a listing the connection probe just proved exists.
Types ¶
type Catalog ¶
type Catalog struct {
// contains filtered or unexported fields
}
Catalog is immutable once resolved and therefore safe to share among the head, executor leaves, and the terminal lens. A lazily loaded catalog holds the fetch as a future instead: the value is handed out immediately and the first capability question waits, if anything still has to wait at all.
func Load ¶
Load fetches at most once. A fresh cache avoids I/O; a failed fetch degrades to a stale cache, then on the default base to a very small set of known modality defaults.
func LoadLazy ¶
LoadLazy starts the same discovery immediately but never makes the caller wait for it. On a cold cache the fetch is a network round-trip with a fifteen-second ceiling, and a launch path that awaits it holds the first frame behind a dead terminal. Nothing a catalog answers can be asked before the surface is up, so the goroutine warms the value while the caller carries on, and only a question that genuinely arrives first ever blocks.
A CACHE PAST ITS TTL IS ANSWERED AT ONCE AND REFRESHED BEHIND THE ANSWER. The questions that do arrive first are not rare: opening a conversation asks several (the agent's own tool belt cannot be built without knowing which media models exist), and they all wait on this one value. So a catalog that fetched in front of a day-old cache made the first launch of every day pay a GET /models before anything was drawn — about ten seconds on an ordinary connection, most of a minute on one whose DNS was failing (v0.5.0, 2026-10-01). Yesterday's rows are the truest answer anyone has in that moment, exactly as [load] already serves them when the fetch fails, and they carry their own date (Catalog.FetchedAt). The fetch still happens, on this catalog's own warming goroutine, and its rows reach the disk for the next reader; this catalog keeps answering what it answered first, because a listing that changed under somebody mid-conversation would be a second source of truth. Only a machine with no cache at all still waits, once.
func Recall ¶
Recall reads only rows already remembered for one service and base. It never reaches the network and never substitutes the default service's fallbacks, so a launch can put a just-connected service on its picker without turning the first frame into a catalog refresh.
func Refresh ¶
Refresh is Load with Options.Refresh set, for the one caller that has to SAY what happened: a person who pressed a key asking for today's list.
The catalog it hands back is exactly the one Load would have — a failed fetch still degrades to the cache and then to the built-ins, because asking for fresher facts must never leave a surface with fewer. The error beside it is why the fetch did not land, and nil when it did. Load drops that error on purpose, since a launch nobody asked for has nobody to tell; a refresh somebody asked for owes them a sentence.
func (*Catalog) BlockingReads ¶
BlockingReads is how many questions this catalog has been asked through the door that can wait ([Catalog.rows]), as against the ones asked through Catalog.ModelsNow and its neighbours, which never can.
IT EXISTS SO THAT A LAUNCH PATH CAN BE HELD TO A NUMBER. "Nothing before the first frame resolves the catalog" is a law about the shape of the code, and the only honest way to test it is to count the blocking questions a launch asks and pin the total: a wall-clock assertion would pass or fail on whether the warming goroutine happened to land first, which is a fact about the network and not about the change under review. cmd/codeaf's launch pins read this; nothing inside this package does.
func (*Catalog) Close ¶ added in v0.4.0
func (c *Catalog) Close()
Close cancels and joins a lazy catalog warm. It is safe to call more than once. Eager and zero catalogs have no background work and return immediately.
func (*Catalog) Concrete ¶
Concrete is the model id in the spelling a foreign catalog can actually find, and it VERIFIES before it substitutes.
Nothing inside codeaf needs it: an alias is a model id OpenRouter accepts, and every call codeaf makes with one is answered. It matters at exactly one boundary — a subprocess that looks a model up in a *different* catalog, one keyed by that catalog's own spellings and with no idea what floats. Handed a name that catalog does not carry, the process dies before it has spent a cent.
The candidates are tried in the order that a wrong answer costs least:
- the alias target, because a floating id resolves to nothing anywhere but here (see Model.AliasTarget);
- the id as written, because it is what the person and the panel actually chose, and foreign catalogs key on undated names far more often than the comment on CanonicalSlug assumed;
- the canonical slug, the dated spelling, which is a real id in some catalogs and in others is a name nobody has ever published.
The third is why this takes a resolver at all. Substituting the canonical slug unasked is what broke a leaf on the default model of every install: models.dev carries deepseek/deepseek-v4-flash and deepseek/deepseek-v4-flash-0731 and has never heard of deepseek/deepseek-v4-flash-20260423, so a translation meant to help handed the far side a name that could not exist.
When nothing resolves, the id as written is forwarded — never a substitution the target catalog is KNOWN not to have — so the far side's own error names the model the operator chose. When resolves is nil nothing can be asked, and only the alias target is applied: it is a fact about this catalog rather than a guess about another's spelling. The leading "~" — OpenRouter's own alias marker, and part of no model's name — is dropped throughout, exactly as Model and Supports already drop it.
func (*Catalog) ContextLength ¶
ContextLength is how many tokens the named model accepts, or zero when this catalog cannot say — an unknown slug, a catalog that never loaded, a row cached before the field was kept. Zero is the honest answer and never a small model: a caller sizing anything from this must have its own default for the case where the provider was silent, because being wrong downward here means forgetting material the model could have held.
func (*Catalog) ContextLengthNow ¶ added in v0.3.0
ContextLengthNow is Catalog.ContextLength for a caller that must not wait: it reads through the rows already in hand and answers zero while a lazy catalog is still warming, exactly as Catalog.ModelsNow answers nil, and it matches ids the same way Catalog.ContextLength does — normalizing the reasoning-effort suffix and a leading ~ ([normalizeID]) — so a conversation started on `model:high` answers its window and not zero.
It exists for the model warm (cmd/codeaf's warmV3Models), which gates on Catalog.Warmed before it reads anything — so the rows are always there when this answers — and must not ask a blocking question of a catalog it is about to stop waiting on.
func (*Catalog) FetchedAt ¶
FetchedAt is when this catalog's rows left the provider, or the zero time when they never did — an unloaded catalog, or the built-in fallbacks. A surface that shows a model list may date it from here; nothing inside this package reads it, because a decision made on the age of a catalog would be a second TTL living somewhere the first one cannot see.
func (*Catalog) FetchedAtNow ¶ added in v0.3.0
FetchedAtNow is Catalog.FetchedAt for a caller that must not wait: it reads the rows already in hand and answers the zero time while a lazy catalog is still warming, exactly as Catalog.ModelsNow answers nil.
It exists for the model warm (cmd/codeaf's warmV3Models), which gates on Catalog.Warmed before it reads anything — so the rows are always there when this answers — and must not ask a blocking question of a catalog it is about to stop waiting on.
func (*Catalog) Identity ¶
Identity is the one model behind a spelling of it, and it is what an accumulated history has to be keyed by.
Concrete answers a different question — which spelling a FOREIGN catalog will accept — and it is deliberately conservative about substituting, because a name that catalog does not carry kills a subprocess. Nothing is being handed to anybody here. This is the local question: two spellings the operator used on two days, and whether the measurements taken under them describe one model. The alias target and the canonical slug both say they do, so both are applied and the dated spelling wins, because it is the one name that cannot float.
It never waits. Identity is asked on the launch path, before anything has been planned, and a still-warming catalog blocking there would put a fetch in front of the first frame of every run. A catalog that has not resolved yet answers the id as written, which is what every caller did before this existed — and the records written under it are merged into the resolved identity by the first process that can see one (see profile.Load).
func (*Catalog) Model ¶
Model returns one catalog row by slug. The returned slices do not alias the immutable catalog, so callers may safely retain or amend the result.
func (*Catalog) ModelsNow ¶
ModelsNow is the whole model list for a caller that MUST NOT WAIT, and nil while a lazily loaded catalog is still warming.
Every other listing here resolves through [Catalog.rows], which on a cold cache means a fifteen-second fetch — fine for `codeaf models`, wrong for a picker a person just opened. Nil is the honest answer for "nobody has the facts yet": a surface that gets it falls back to whatever list it can read off disk, and the next time the picker opens the warm catalog answers.
The rows are cloned for the same reason Catalog.Model clones: the catalog is immutable and shared, and a caller that sorted the returned slice's models in place would be sorting everyone's.
func (*Catalog) ModelsWithInput ¶
ModelsWithInput returns a stable copy of models advertising modality.
func (*Catalog) ModelsWithOutput ¶
ModelsWithOutput returns a stable copy of models advertising modality.
func (*Catalog) NearestModels ¶
NearestModels names the models most like modelID, closest first, for a caller that has to move off it and would rather not ask a person which way to go.
SAME CLASS MEANS SERVES THE SAME CONVERSATION, and it is four published facts rather than a judgement: the row answers in text only, it accepts tool calls, its window is not dramatically smaller, and it is not the model we are leaving. A chat turn that moved to a model with no tools or a quarter of the window would be a fallback that fails differently rather than one that works.
The ordering is by the same vendor first — the endpoints of one vendor's line are the likeliest to accept the same request shape — and then by published intelligence, nearest first, with an unpublished score ranking last. Elo breaks the remaining ties, so two rows that published nothing but a name still come back in a stable order rather than in map order.
IT NEVER WAITS, exactly as Catalog.SupportsParameter never does: this is asked on the request path, by an adapter that has just been refused, with a person watching. A catalog that has not resolved, or a model it has never heard of, answers nil — nobody knows, which is a fine answer and better than a fifteen-second fetch in front of an error.
func (*Catalog) PriceNow ¶
PriceNow is what modelID's own published tariff is, per token in US dollars, and whether anybody actually published one.
The third value carries the whole distinction the price fields cannot: a zero price is a real figure — eighteen rows really are free — and "the provider said nothing" is not. A caller that read the two the same way would either invent a free model or throw away a real one. `PriceUnknown` is the row's own word for the second case, and a row the catalog has never seen is the same answer arrived at differently.
It never waits, for Catalog.SupportsParameter's reason: the caller is the model adapter shaping a body it is about to send, and a still-warming catalog blocking there would put a fetch in front of the first call of every run. A catalog that has not resolved is one more way of not knowing.
func (*Catalog) ReasoningProfile ¶
func (c *Catalog) ReasoningProfile(modelID string) (ReasoningProfile, bool)
ReasoningProfile answers what the provider published about modelID's thinking pass, and whether it published anything.
The second bool matters for the same reason it does on SupportsParameter: a row nobody has seen, a catalog still warming, and a row cached before the block was kept all say "no idea", and the adapter falls back to the one other way it can learn the fact — being told no by the endpoint. It never waits, for SupportsParameter's reason.
func (*Catalog) Servable ¶
Servable is the id the router will actually serve this spelling under, and it is the only fold a per-model LEDGER may use.
Catalog.Identity answers a neighbouring question and answers it wrongly for this one. Identity is asked whether two spellings describe one model, and it prefers the dated canonical slug because that is the name which cannot float. For a floating alias the router publishes canonical_slug as the ALIAS itself and, one hop on, as a dated id it lists nowhere and serves through no endpoints page: on 2026-09-01 `~deepseek/deepseek-v4-flash-latest` resolved through Identity to `deepseek/deepseek-v4-flash-20260731`, which is not a model anybody can send to. A ledger keyed on that would hold beliefs about a name no request will ever wear — the same split this fixes, one spelling further out. The alias target, `deepseek/deepseek-v4-flash-0731`, is the real servable id and the endpoints page is published under it.
The bare undated id is not the answer either, and it is worth saying because it looks like one: `deepseek/deepseek-v4-flash` is the 0423 snapshot, a DIFFERENT MODEL with its own machines and its own speeds.
So: one hop, alias target only, and never the canonical slug. A row without an alias target is already servable and comes back as written.
It never waits, for Catalog.Identity's reason and one more: this is read on the send path as well as at launch, and a fold that could block would put a fetch in front of a request.
A CATALOG WITH NO ROWS YET ANSWERS NOTHING, and the empty string is that answer rather than a fold to nothing. Saying "the id as written" would be a lie a reader cannot tell from a fact, and the reader that matters memoises: [lane.LedgerModel] remembers the first answer for the life of the process, so a process that asked while the catalog was still in flight would key its whole run on the alias — the split this fold exists to end, made permanent by a guess. Nothing is the one answer a caller can act on correctly, by using the name it already has and asking again.
func (*Catalog) SnapshotNow ¶
SnapshotNow returns a catalog whose capability questions never start or join a fetch. Fresh rows win; while warming, only this service's cached rows or its permitted built-in fallback are used. An unknown custom service stays empty rather than inheriting another provider's capabilities.
func (*Catalog) Supports ¶
Supports answers whether modelID advertises modality in direction. Unknown models and directions calmly return false.
func (*Catalog) SupportsParameter ¶
SupportsParameter answers whether modelID accepts a request field, and whether anyone actually knows.
The second bool is the whole point, and it is why this cannot be a plain predicate. A model the catalog has never heard of, a catalog that never loaded, and a row cached before parameters were kept all say "no idea" — and a caller that read that as "does not support it" would silently drop a knob the operator asked for, while one that read it as "supports it" would send a field that 400s. Only the caller knows which way to fail, so the fact and its confidence travel together.
It never waits. This is the one catalog question asked on the request path, where the caller is the model adapter shaping a body it is about to send, and a still-warming catalog blocking there would put a fifteen-second fetch in front of the first call of every run. A catalog that has not resolved yet is simply one more way of not knowing.
func (*Catalog) Warmed ¶ added in v0.3.0
Warmed answers whether this catalog's rows have landed, waiting within the bound ctx carries for a lazily loaded one — from the disk cache or from the fetch, whichever wins — and answering false when the bound ends first. A catalog that resolved eagerly (Load, Refresh) answers true at once, and a nil catalog answers false.
It exists for a caller whose next step reads the rows through a seam that must not wait (Catalog.ModelsNow, and the binaries that set config's AutoModels from it): a bounded wait turns "not yet" into "the rows" when the rows are a disk read away, without ever turning the caller into a fetch. The caller owns the bound; this only honors it.
type Model ¶
type Model struct {
ID string `json:"id"`
// CanonicalSlug is the concrete model behind a floating alias. OpenRouter
// publishes ids like `deepseek/deepseek-v4-flash-latest` that resolve, at
// request time and on its side, to whatever is current — which is why every
// call codeaf makes with the alias simply works. A second catalog that does
// not float, keyed by concrete id, has never heard of the alias, and this is
// the field that translates between them. Empty for the great majority of
// rows, and empty for every row in a cache written before it was read.
CanonicalSlug string `json:"canonical_slug,omitempty"`
// AliasTarget is where a floating id actually points, and it is the field
// that makes [Catalog.Concrete] true.
//
// The comment above CanonicalSlug describes what that field was believed to
// do. The live catalog on 2026-08-11 disagrees: for the eleven alias rows
// it publishes, `canonical_slug` repeats the ALIAS ("~x-ai/grok-latest"),
// and the concrete model sits in `alias_target.slug` ("x-ai/grok-4.5").
// Resolving through canonical_slug alone therefore hands a floating id
// straight back, which is precisely the failure Concrete exists to prevent.
// Empty for every row that does not float.
AliasTarget string `json:"alias_target,omitempty"`
Name string `json:"name,omitempty"`
// ContextLength is how many tokens the model will actually accept, and it
// was being thrown away by the row that already fetched it. Nothing priced
// it, so nothing kept it — and downstream the loop that has to decide how
// much transcript to carry was left sizing its memory from a spend ceiling
// instead, which is how a leaf ended up with a 25KB window in front of a
// 200k-token model. Zero means the provider did not say, or the row was
// cached before this field existed; every reader must have an answer for
// that case rather than treating zero as a tiny model.
ContextLength int `json:"context_length,omitempty"`
PromptPrice float64 `json:"prompt_price,omitempty"`
CompletionPrice float64 `json:"completion_price,omitempty"`
RequestPrice float64 `json:"request_price,omitempty"`
// PriceUnknown says the provider published no number, which is a different
// fact from a number that is zero and must not be shown as one.
//
// OpenRouter spells "it depends" as "-1": its own routers
// (openrouter/auto and friends) charge whatever the model they pick
// charges, and there were five such rows in the live catalog on
// 2026-08-11 beside eighteen genuinely free ones priced "0". Collapsing
// both to 0.0 — which is what this package did until this field existed —
// tells a reader that a router is free. It is not; nobody yet knows what
// it costs. A surface reads this before it reads the two prices, and
// renders absence rather than "$0.00" (design-law-v2 §16 EMPTINESS).
PriceUnknown bool `json:"price_unknown,omitempty"`
// CacheReadPrice is what a token served off the provider's warm prefix
// costs, per token — OpenRouter's `pricing.input_cache_read`. 246 of 413
// rows published one on 2026-08-15; it is typically a tenth of PromptPrice,
// and the difference between the two is the whole of what a prompt cache is
// worth to a session that re-sends its transcript every step.
//
// Zero is "the provider did not say", exactly as with the other prices, and
// a surface must render absence rather than a saving of the full prompt
// price — a cache read is never free.
CacheReadPrice float64 `json:"cache_read_price,omitempty"`
// ArenaElo is the best Elo the row publishes across Design Arena's boards —
// OpenRouter's `benchmarks.design_arena`, a LIST of
// {arena, category, elo, win_rate, rank} objects, 155 of 413 rows non-empty
// on 2026-08-15.
//
// The list is reduced to its MAXIMUM rather than averaged, and the choice is
// about what the number is for: a row shows one figure, the boards are
// different tasks rather than repeated measurements of one, and a model that
// tops the webapps board and sits mid-table on 3d has a real strength an
// average would report as mediocrity. Zero means nobody published one.
ArenaElo float64 `json:"arena_elo,omitempty"`
// The three Artificial Analysis scores the catalog keeps, carried verbatim
// and never computed here.
//
// OpenRouter's rows may carry a `benchmarks` block, and inside it an
// `artificial_analysis` object with `intelligence_index`, `coding_index`
// and `agentic_index` — Artificial Analysis's numbers, republished. 155 of
// 528 rows had one on 2026-08-11. Zero means NOBODY published a score, and
// never a model that scored zero: a surface showing these must render the
// zero as absence the way it renders an absent price.
//
// All three are kept because they are read seat by seat: the agentic and
// coding indexes describe a long tool loop, the intelligence index a single
// reasoning call, and the seat asking decides which one it needs.
IntelligenceIndex float64 `json:"intelligence_index,omitempty"`
CodingIndex float64 `json:"coding_index,omitempty"`
AgenticIndex float64 `json:"agentic_index,omitempty"`
// Created is when the model was listed, in Unix seconds — the release date
// the crew router reads a model's age from. Zero means the row did not say
// or was cached before this field was kept; a reader then falls back to a
// date in the canonical slug, or to none.
Created int64 `json:"created,omitempty"`
// OpenWeights says the row's weights are published — OpenRouter's
// `hugging_face_id`, kept as the one-word answer to whether the weights are
// public. A row cached before this field existed reads false, which every
// reader must take as unknown rather than closed.
OpenWeights bool `json:"open_weights,omitempty"`
InputModalities []string `json:"input_modalities,omitempty"`
OutputModalities []string `json:"output_modalities,omitempty"`
// Parameters is which request fields the provider says this model accepts —
// OpenRouter's `supported_parameters`, lowercased and deduped.
//
// It is the only published answer to "may this call carry a reasoning knob",
// and until it was kept, nothing could ask: the adapter's gate for that
// question was wired to nil in production, so a harness economy went to
// every model blind and 400ed the ones that do not take it. An empty list
// means the provider said nothing or the row predates this field, which is
// unknown rather than "supports nothing" — see [Catalog.SupportsParameter].
Parameters []string `json:"parameters,omitempty"`
// Reasoning is what the provider publishes about this model's thinking
// pass — OpenRouter's `reasoning` block on the row. It is the second
// published answer the adapter reads on the request path, beside
// Parameters: whether the pass can be turned off at all, and which effort
// words the model takes. Absent (Known false) on a row cached before it was
// kept and on a row the provider published nothing for — see
// [Catalog.ReasoningProfile].
Reasoning ReasoningProfile `json:"reasoning,omitempty"`
}
Model is the small, durable part of one OpenRouter catalog row. Pricing is display-ready economics for the existing picker; architecture is retained verbatim for modality queries.
func (Model) ReasoningLevels ¶
ReasoningLevels says the effort can be dialled — `reasoning_effort` — rather than only switched on. It is the difference between a model whose thinking this harness can economize and one whose thinking it can only accept: MiniMax M2.7 takes `reasoning` and no level, and refuses to have it turned off at all.
func (Model) Reasons ¶
Reasons says the provider accepts a reasoning knob on this model — the published fact, not an inference about how the model thinks. A row that carries no parameter list answers false, which is the same answer it gives for a model that genuinely takes no knob; a surface that needs to tell those apart should ask Catalog.SupportsParameter, which reports its confidence.
type Options ¶
type Options struct {
// Source is the stable service identity. AN EMPTY SOURCE IS THE DEFAULT
// SERVICE, whose ids are the only ones the compiled fallbacks describe.
Source string
BaseURL string
APIKey string
Dir string
// HTTPClient is the service-owned request road. Most OpenAI-compatible
// catalogs leave it nil; services whose listing needs rotating credentials
// or a wire translation supply the same client their model calls use.
HTTPClient *http.Client
Now func() time.Time
// Refresh spends the network even when the cache is inside [TTL]. It is
// the ONLY way a fetch happens off the daily clock, and it exists so a
// person who just watched a provider ship a model can ask for it by hand
// rather than being told to wait a day or delete a file.
//
// A refresh that fails still degrades to the cache it was trying to
// replace: asking for fresher facts must never leave a surface with fewer
// facts than it had.
Refresh bool
// contains filtered or unexported fields
}
Options describes the one catalog fetch. Dir is the codeaf configuration directory (CODEAF_PROFILE_DIR when configured, ~/.codeaf otherwise).
type ReasoningProfile ¶
type ReasoningProfile struct {
Known bool `json:"known,omitempty"`
Mandatory bool `json:"mandatory,omitempty"`
Efforts []string `json:"efforts,omitempty"`
DefaultEffort string `json:"default_effort,omitempty"`
}
ReasoningProfile is the provider's own account of a model's thinking pass.
Mandatory says the pass cannot be disabled: OpenRouter's rule for such a row is "hide disable controls and do not send effort: none — the model rejects it", and 83 of the rows carried it on 2026-08-28 (GLM 5.3, Gemini 3.7 Flash, Grok 4.6 among them). Efforts is the ladder of words the model accepts, lowercased and in the provider's order; DefaultEffort is where the model sits when nobody sends a word — for GLM 5.3 that is "max", which is why a request that sent nothing spent ten thousand tokens thinking.
type Resolves ¶
Resolves is the target catalog's own answer to "do you have this id?", asked by Catalog.Concrete before it hands a subprocess a spelling other than the one it was given. It is a function rather than an import because the only catalog that matters here lives behind another package's internal/ wall, and because the question — not the table — is what this package needs.
A nil Resolves means nobody can be asked, which is a different answer from "no": see Concrete.