1package agent
2
3import (
4 "context"
5 "errors"
6 "os"
7 "sort"
8 "strings"
9 "time"
10
11 "github.com/patriceckhart/zot/packages/provider"
12)
13
14// discoverableCustomProviders returns the models.json providers that opted
15// into live discovery, sorted for deterministic refresh order.
16func discoverableCustomProviders() []string {
17 var out []string
18 for name := range provider.CustomProviders() {
19 if isDiscoverableCustomProvider(name) {
20 out = append(out, name)
21 }
22 }
23 sort.Strings(out)
24 return out
25}
26
27// isDiscoverableCustomProvider reports whether name is a custom provider
28// with live discovery enabled. Its catalog entries are transient, so config
29// repair must not treat a missing model as stale.
30func isDiscoverableCustomProvider(name string) bool {
31 cfg, ok := provider.CustomProviders()[name]
32 return ok && cfg.Discover && strings.TrimSpace(cfg.BaseURL) != "" && !isBuiltinProvider(name)
33}
34
35// CustomDiscoveryConfigured reports whether any custom provider has live
36// discovery enabled. It reads only loaded configuration, never the network.
37func CustomDiscoveryConfigured() bool {
38 return len(discoverableCustomProviders()) > 0
39}
40
41// customProviderKey resolves the optional bearer key for a custom provider.
42// A missing key is not an error because local servers commonly accept
43// unauthenticated requests. skip is true when the only key is command-backed
44// and mode forbids running the command.
45func customProviderKey(ctx context.Context, name string, mode apiKeyCommandMode) (key string, skip bool, err error) {
46 if cfg, ok := provider.CustomProviders()[name]; ok && cfg.NoAuth && !isBuiltinProvider(name) {
47 return "", false, nil
48 }
49 if v := os.Getenv(normalizeCustomProviderEnvVar(name) + "_API_KEY"); v != "" {
50 return v, false, nil
51 }
52 creds, err := AuthStoreFor().Load()
53 if err != nil {
54 return "", false, err
55 }
56 stored, ok := creds.AdditionalAPIKeyCreds[name]
57 if !ok {
58 return "", false, nil
59 }
60 if stored.APIKey == "" && stored.APIKeyCommand != nil && mode == apiKeyCommandSkip {
61 return "", true, nil
62 }
63 key, _, err = resolveStoredAPIKey(ctx, name, stored, mode)
64 return key, false, err
65}
66
67// RefreshCustomProviderModels lists models from every discovery-enabled
68// custom provider and merges them into the transient managed catalog.
69// A failing provider keeps its previous snapshot; errors are joined after
70// all providers were attempted.
71func RefreshCustomProviderModels(ctx context.Context) error {
72 ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
73 defer cancel()
74 return refreshCustomProviderModels(ctx, apiKeyCommandExecute)
75}
76
77func refreshCustomProviderModels(ctx context.Context, mode apiKeyCommandMode) error {
78 var errs []error
79 for _, name := range discoverableCustomProviders() {
80 cfg := provider.CustomProviders()[name]
81 key, skip, err := customProviderKey(ctx, name, mode)
82 if err != nil {
83 errs = append(errs, err)
84 continue
85 }
86 if skip {
87 continue
88 }
89 models, err := provider.DiscoverCustomProvider(ctx, name, cfg.BaseURL, key)
90 if err != nil {
91 errs = append(errs, err)
92 continue
93 }
94 provider.SetManagedModelsForProvider(name, models)
95 }
96 return errors.Join(errs...)
97}