okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffeat(api)!: migrate daemon and clients to protocol 361c43ca

main
3.5 KiB105 linesraw
1package app
2
3import (
4	"context"
5	"fmt"
6	"maps"
7	"slices"
8	"strings"
9
10	"albedo/cli/internal/config"
11	"albedo/cli/internal/daemon"
12)
13
14// providerModels is one configured provider and the model ids it offers.
15type providerModels struct {
16	provider string
17	models   []string
18}
19
20// modelChoice is a model id and the provider that serves it.
21type modelChoice struct {
22	provider string
23	model    string
24}
25
26// configuredModels lists every provider's models, the active provider first.
27// A provider lists its default model first, even when its catalog cannot be
28// read, as the daemon does when it resolves an agent's model.
29func configuredModels(ctx context.Context, conn *daemon.Connection, profiles config.Profiles) []providerModels {
30	names := slices.Sorted(maps.Keys(profiles.Providers))
31	if i := slices.Index(names, profiles.Active); i > 0 {
32		names = append([]string{profiles.Active}, slices.Delete(names, i, i+1)...)
33	}
34	listed := make([]providerModels, 0, len(names))
35	for _, name := range names {
36		settings := profiles.Providers[name]
37		models := []string{settings.Model}
38		catalog, _ := daemon.ListProfileModels(ctx, conn, settings)
39		for _, model := range catalog {
40			if !slices.Contains(models, model.ID) {
41				models = append(models, model.ID)
42			}
43		}
44		listed = append(listed, providerModels{name, slices.DeleteFunc(models, func(id string) bool { return id == "" })})
45	}
46	return listed
47}
48
49// chooseModel resolves a model the way the daemon resolves an agent's model:
50// provider/model picks the provider, and a bare id comes from the active
51// provider, else from the one provider that offers it. Only exact ids match.
52func chooseModel(listed []providerModels, active, requested string) (modelChoice, error) {
53	for _, p := range listed {
54		if model, ok := strings.CutPrefix(requested, p.provider+"/"); ok {
55			if !slices.Contains(p.models, model) {
56				return modelChoice{}, fmt.Errorf("provider %s does not offer model %s; run albedo models to see the models you can use", p.provider, model)
57			}
58			return modelChoice{p.provider, model}, nil
59		}
60	}
61	var offering []string
62	for _, p := range listed {
63		if slices.Contains(p.models, requested) {
64			offering = append(offering, p.provider)
65		}
66	}
67	switch {
68	case len(offering) == 0:
69		return modelChoice{}, fmt.Errorf("no configured provider offers model %s; run albedo models to see the models you can use", requested)
70	case slices.Contains(offering, active):
71		return modelChoice{active, requested}, nil
72	case len(offering) == 1:
73		return modelChoice{offering[0], requested}, nil
74	}
75	return modelChoice{}, fmt.Errorf("model %s is offered by %s; choose one with provider/model", requested, strings.Join(offering, ", "))
76}
77
78// switchModel moves an idle session to the chosen model without making it the
79// default for new sessions.
80func switchModel(ctx context.Context, conn *daemon.Connection, sessionID string, choice modelChoice) error {
81	configuration, err := daemon.GetSessionConfiguration(ctx, conn, sessionID)
82	if err != nil {
83		return err
84	}
85	_, err = daemon.SelectModel(ctx, conn, sessionID, daemon.ModelSelectionRequest{Model: choice.model, Provider: choice.provider, ETag: configuration.ETag})
86	return err
87}
88
89func (s *Service) Models(ctx context.Context) ([]string, error) {
90	conn, err := s.Connect(ctx)
91	if err != nil {
92		return nil, err
93	}
94	profiles, err := daemon.ProviderProfiles(ctx, conn)
95	if err != nil {
96		return nil, err
97	}
98	var models []string
99	for _, p := range configuredModels(ctx, conn, profiles) {
100		for _, model := range p.models {
101			models = append(models, p.provider+"/"+model)
102		}
103	}
104	return models, nil
105}