cache.go RawHistoryFolder
1package provider
2
3import (
4 "encoding/json"
5 "errors"
6 "os"
7 "path/filepath"
8 "time"
9)
10
11// ModelCache is the on-disk shape for discovered models.
12type ModelCache struct {
13 Version int `json:"version,omitempty"`
14 FetchedAt time.Time `json:"fetched_at"`
15 Models []Model `json:"models"`
16 AuthoritativeProviders []string `json:"authoritative_providers,omitempty"`
17 ProviderScopes map[string]string `json:"provider_scopes,omitempty"`
18}
19
20// ModelCacheVersion invalidates caches created before OpenCode Go protocol
21// routing was based on models.dev adapters. Older caches must not be loaded as
22// routing metadata because they can silently select the wrong wire protocol.
23const ModelCacheVersion = 4
24
25// CacheTTL is how long a discovered list is considered fresh. Model metadata
26// and availability change less often than a typical process starts, so keep
27// the synchronized catalog for one day before querying its sources again.
28const CacheTTL = 24 * time.Hour
29
30// LoadCache reads the model cache from path. Returns an empty ModelCache
31// (no error) if the file does not exist.
32func LoadCache(path string) (ModelCache, error) {
33 var c ModelCache
34 b, err := os.ReadFile(path)
35 if errors.Is(err, os.ErrNotExist) {
36 return c, nil
37 }
38 if err != nil {
39 return c, err
40 }
41 if err := json.Unmarshal(b, &c); err != nil {
42 return c, err
43 }
44 for i := range c.Models {
45 if c.Models[i].Source == "" {
46 c.Models[i].Source = "cache"
47 }
48 }
49 return c, nil
50}
51
52// SaveCache writes the cache atomically.
53func SaveCache(path string, c ModelCache) error {
54 if c.Version == 0 {
55 c.Version = ModelCacheVersion
56 }
57 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
58 return err
59 }
60 b, err := json.MarshalIndent(c, "", " ")
61 if err != nil {
62 return err
63 }
64 tmp := path + ".tmp"
65 if err := os.WriteFile(tmp, b, 0o600); err != nil {
66 return err
67 }
68 return os.Rename(tmp, path)
69}
70
71// IsFresh reports whether the current-format cache was fetched within CacheTTL.
72func (c ModelCache) IsFresh() bool {
73 if c.Version != ModelCacheVersion || c.FetchedAt.IsZero() {
74 return false
75 }
76 return time.Since(c.FetchedAt) < CacheTTL
77}