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 v := os.Getenv(normalizeCustomProviderEnvVar(name) + "_API_KEY"); v != "" {
47 return v, false, nil
48 }
49 creds, err := AuthStoreFor().Load()
50 if err != nil {
51 return "", false, err
52 }
53 stored, ok := creds.AdditionalAPIKeyCreds[name]
54 if !ok {
55 return "", false, nil
56 }
57 if stored.APIKey == "" && stored.APIKeyCommand != nil && mode == apiKeyCommandSkip {
58 return "", true, nil
59 }
60 key, _, err = resolveStoredAPIKey(ctx, name, stored, mode)
61 return key, false, err
62}
63
64// RefreshCustomProviderModels lists models from every discovery-enabled
65// custom provider and merges them into the transient managed catalog.
66// A failing provider keeps its previous snapshot; errors are joined after
67// all providers were attempted.
68func RefreshCustomProviderModels(ctx context.Context) error {
69 ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
70 defer cancel()
71 return refreshCustomProviderModels(ctx, apiKeyCommandExecute)
72}
73
74func refreshCustomProviderModels(ctx context.Context, mode apiKeyCommandMode) error {
75 var errs []error
76 for _, name := range discoverableCustomProviders() {
77 cfg := provider.CustomProviders()[name]
78 key, skip, err := customProviderKey(ctx, name, mode)
79 if err != nil {
80 errs = append(errs, err)
81 continue
82 }
83 if skip {
84 continue
85 }
86 models, err := provider.DiscoverCustomProvider(ctx, name, cfg.BaseURL, key)
87 if err != nil {
88 errs = append(errs, err)
89 continue
90 }
91 provider.SetManagedModelsForProvider(name, models)
92 }
93 return errors.Join(errs...)
94}