Commit Graph
2 Commits
Author SHA1 Message Date
lukaszraczylo 82dcaa5309 fix(registry): metadata capture gets its own timeout + panic isolation
warmFill/refreshProvider used to reuse listModels' own context for the
subsequent captureModelMetadata call — a listModels attempt that took
real, non-trivial time (or timed out outright) guaranteed the metadata
fetch an already-spent, near-expired context, producing a spurious
deadline error and log line every warm fill or refresh for an otherwise
healthy provider. Both call sites now give captureModelMetadata its own
fresh context.WithTimeout budget, computed after listModels returns.
(review SHOULD-4)

captureModelMetadata also gains its own deferred recover, isolating a
panic inside fetchModelMetadata (or a future modelMetadataFetcher
implementation) from refreshProvider's OUTER recover — which would
otherwise overwrite the (already-nil, already-correct) err variable from
a successful listModels call and cause finishRefresh to discard a
freshly fetched, genuinely good model list over an unrelated bug in the
metadata path. "Metadata must never fail discovery" is now enforced on
the panic path, not just the plain-error-return one already handled.
(review SHOULD-5)

New regression tests: a fresh-timeout-budget check (a slow simulated
listModels must not shrink the metadata fetch's own deadline) and a
panic-in-fetchModelMetadata check (discovery must still succeed and
record both models).
2026-08-22 02:12:04 +01:00
lukaszraczylo 0c61296c73 feat(registry): model metadata capture and resolution
Config gets two new surfaces (llmgateway.go): Config.ModelMeta
(map[string]*ModelMetaConfig, keyed by exact "provider/model" or a
bare model id) for operator overrides, and ProviderConfig.MetadataPath
for openai-type providers whose discovery should also capture per-model
context (LM Studio's native /api/v0/models shape).

modelmeta.go adds the layering itself: resolveModelMeta applies config
override > discovered context > builtin table > absent for context, and
config override/free > builtin > absent for cost (discovery never
reports cost). resolveAliasModelMeta implements alias inheritance: an
alias inherits its target's fully resolved metadata unless the alias
itself has its own modelMeta entry, applied per field. validateModelMeta
rejects negative values and a free:true/non-zero-cost contradiction at
construction.

registry.go wires this into the request path: providerState gains a
discoveredContext map (stale-while-error, mirroring discovered's own
convention) with contextFor/setDiscoveredContext; a new optional
modelMetadataFetcher interface, type-asserted (interpreted-to-interpreted,
documented as yaegi-safe against the Trap-4 restriction) rather than
added to providerAdapter, lets warmFill/refreshProvider capture metadata
alongside listModels without touching anthropic/gemini adapters at all.
modelObject/listFor now attach resolved context_window/pricing to every
GET /v1/models entry, including aliases (resolveMetaFor/
resolveMetaForAlias/resolveMetaForAliasName).

provider_openai.go implements modelMetadataFetcher: fetchModelMetadata
reads ProviderConfig.MetadataPath, preferring loaded_context_length over
max_context_length when the LM Studio response reports both. providers.go
wires MetadataPath onto the adapter in buildAdapters.

tools/yaegi-check/main.go adds one cheap modelMeta config-override
assertion to the existing attempt-accounting harness, proving
resolveModelMeta's config-override layer and the context_window/pricing
extension fields both run correctly under the real Yaegi interpreter.
2026-08-22 01:35:21 +01:00