zot/internal/agent/modelsync.go
2026-05-05 08:40:37 +02:00

93 lines
2.7 KiB
Go

package agent
import (
"context"
"path/filepath"
"time"
"github.com/patriceckhart/zot/internal/provider"
)
// ModelCachePath returns the on-disk location of the merged model cache.
func ModelCachePath() string {
return filepath.Join(ZotHome(), "models-cache.json")
}
// UserModelsPath returns the path to the user's models.json override.
func UserModelsPath() string {
return filepath.Join(ZotHome(), "models.json")
}
// LoadCachedModels loads the cache file and applies it to the provider
// package so FindModel / ModelsForProvider see live ids immediately.
// Safe to call before any credentials are known.
func LoadCachedModels() {
c, err := provider.LoadCache(ModelCachePath())
if err != nil {
return
}
if len(c.Models) > 0 {
provider.SetLiveModels(c.Models)
}
}
// LoadUserModels reads $ZOT_HOME/models.json and merges any user-defined
// models into the active catalog. User models take highest precedence.
func LoadUserModels() {
models := provider.LoadUserModels(UserModelsPath())
provider.SetUserModels(models)
}
// RefreshModelsAsync kicks a background discovery for every provider
// we have credentials for. Refreshed results are merged into the
// active catalog and persisted to the on-disk cache.
//
// Silent on error: discovery is a nice-to-have. Callers can still use
// the baked-in catalog if this fails.
func RefreshModelsAsync() {
go refreshModels()
}
func refreshModels() {
cached, _ := provider.LoadCache(ModelCachePath())
if cached.IsFresh() {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var all []provider.Model
if cred, method, err := ResolveCredential("anthropic", ""); err == nil && method == "apikey" {
// /v1/models on Anthropic is API-key only; OAuth tokens can
// also list models via the bearer header, but we skip OAuth
// here to avoid surprise rate-limit hits on subscription keys.
if live, err := provider.DiscoverAnthropic(ctx, cred, ""); err == nil {
all = append(all, live...)
}
}
if cred, method, err := ResolveCredential("openai", ""); err == nil && method == "apikey" {
if live, err := provider.DiscoverOpenAI(ctx, cred, ""); err == nil {
all = append(all, live...)
}
}
if cred, method, err := ResolveCredential("kimi", ""); err == nil && method == "apikey" {
if live, err := provider.DiscoverOpenAI(ctx, cred, "https://api.kimi.com/coding/v1"); err == nil {
for i := range live {
live[i].Provider = "kimi"
live[i].Source = "live"
}
all = append(all, live...)
}
}
if len(all) == 0 {
return
}
provider.SetLiveModels(all)
_ = provider.SaveCache(ModelCachePath(), provider.ModelCache{
FetchedAt: time.Now().UTC(),
Models: all,
})
}