okami.mom/albedo

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

Aly Raffaufrefactor(cli): remove unused state fields5924d14

main
6.2 KiB159 linesraw
1package daemon
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"net/http"
8	"strings"
9
10	"albedo/cli/internal/config"
11	"albedo/cli/internal/daemon/protocol"
12)
13
14type Model struct {
15	ID, Label, CapKey           string
16	Efforts, Input              []string
17	Context, MaxContext, Output int
18	Raised                      bool
19}
20type EffortResult struct {
21	Effort, Message, ETag string
22	Available             []string
23}
24type ModelSelection struct{ Provider, Model, Protocol, Effort, ETag, DefaultETag, ModelsETag string }
25type ModelSelectionRequest struct{ Model, Provider, Effort, ETag string }
26type ModelChangeRequest struct {
27	Model, Provider, Effort, ETag, DefaultETag string
28	MakeDefault                                bool
29}
30type ModelContextCapRequest struct {
31	CapKey, ETag string
32	Enabled      bool
33}
34
35func ListProfileModels(ctx context.Context, conn *Connection, profile config.Settings) ([]Model, error) {
36	if profile.ProfileName != "" {
37		return listModels(ctx, conn, protocol.ListModelsParams{ProviderProfile: &profile.ProfileName})
38	}
39	return ListModels(ctx, conn, profile.Extension, profile.BaseURL)
40}
41func ListModels(ctx context.Context, conn *Connection, provider, endpoint string) ([]Model, error) {
42	params := protocol.ListModelsParams{Provider: &provider}
43	if endpoint != "" {
44		params.Endpoint = &endpoint
45	}
46	return listModels(ctx, conn, params)
47}
48func listModels(ctx context.Context, conn *Connection, params protocol.ListModelsParams) ([]Model, error) {
49	result := []Model{}
50	params.Limit = new(int64(200))
51	err := walkPages(func(next *string) (protocol.ModelPage, *string, error) {
52		params.Next = next
53		var page protocol.ModelPage
54		pageErr := executeRead(ctx, conn, operation{Name: "list models", BuildRequest: func(base string, body io.Reader) (*http.Request, error) {
55			return protocol.NewListModelsRequest(base, &params)
56		}, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &page) })
57		return page, page.Next, pageErr
58	}, func(page protocol.ModelPage) error {
59		if page.Items == nil {
60			return fieldError("models")
61		}
62		for _, row := range page.Items {
63			if row.ID == "" || row.CapKey == "" || row.Efforts == nil {
64				return fieldError("model")
65			}
66			model := Model{ID: row.ID, Label: row.Label, CapKey: row.CapKey, Input: row.InputModalities, Context: int(value(row.DefaultContextTokens)), MaxContext: int(value(row.MaxContextTokens)), Output: int(value(row.MaxOutputTokens)), Raised: row.Raised}
67			for _, effort := range row.Efforts {
68				model.Efforts = append(model.Efforts, effort.ID)
69			}
70			result = append(result, model)
71		}
72		return nil
73	}, "model page cursor")
74	if err != nil {
75		return nil, err
76	}
77	return result, nil
78}
79
80func SelectModel(ctx context.Context, conn *Connection, id string, request ModelSelectionRequest) (ModelSelection, error) {
81	body := map[string]any{"model": request.Model}
82	if request.Provider != "" {
83		body["provider_profile"] = request.Provider
84	}
85	if request.Effort != "" {
86		body["effort"] = request.Effort
87	}
88	session, err := patchSession(ctx, conn, id, request.ETag, body)
89	return ModelSelection{Provider: session.Provider, Model: session.Model, Protocol: session.Protocol, Effort: session.Effort, ETag: session.ETag}, err
90}
91func ChangeModel(ctx context.Context, conn *Connection, id string, request ModelChangeRequest) (ModelSelection, error) {
92	if request.MakeDefault && request.DefaultETag == "" {
93		return ModelSelection{}, fieldError("provider settings validator")
94	}
95	selection, err := SelectModel(ctx, conn, id, ModelSelectionRequest{Model: request.Model, Provider: request.Provider, Effort: request.Effort, ETag: request.ETag})
96	if err != nil {
97		return selection, err
98	}
99	if request.MakeDefault {
100		var change protocol.ProviderSettingsChange
101		saveErr := patchSettingsGroup(ctx, conn, "providers", request.DefaultETag, map[string]any{"default_profile": selection.Provider, "profiles": map[string]any{selection.Provider: map[string]any{"model": selection.Model, "effort": optionalText(selection.Effort)}}}, &change)
102		selection.DefaultETag = change.Resource.ETag
103		if saveErr != nil {
104			return selection, fmt.Errorf("switched session; saving the default model: %w", saveErr)
105		}
106	}
107	return selection, nil
108}
109func SetModelContextCap(ctx context.Context, conn *Connection, request ModelContextCapRequest) (string, error) {
110	if request.CapKey == "" {
111		return "", fieldError("model cap key")
112	}
113	var change protocol.ModelSettingsChange
114	err := patchSettingsGroup(ctx, conn, "models", request.ETag, map[string]any{"raised_caps": map[string]bool{request.CapKey: request.Enabled}}, &change)
115	return change.Resource.ETag, err
116}
117func ReadEffort(ctx context.Context, conn *Connection, session Session) (EffortResult, error) {
118	params := protocol.ListModelsParams{Model: &session.Model}
119	if session.Provider != "" {
120		params.ProviderProfile = &session.Provider
121	}
122	models, err := listModels(ctx, conn, params)
123	if err != nil {
124		return EffortResult{}, err
125	}
126	for _, model := range models {
127		if model.ID == session.Model {
128			return EffortResult{Effort: session.Effort, Available: model.Efforts}, nil
129		}
130	}
131	return EffortResult{}, fmt.Errorf("model %q has no metadata", session.Model)
132}
133
134// SelectEffort clears the preference when effort is empty.
135func SelectEffort(ctx context.Context, conn *Connection, session Session, effort string) (EffortResult, error) {
136	result, err := patchSession(ctx, conn, session.ID, session.ETag, map[string]any{"effort": optionalText(effort)})
137	return EffortResult{Effort: result.Effort, Message: "Effort saved.", ETag: result.ETag}, err
138}
139
140// ResolveSessionModel keeps a manual ID in the observed session profile, or
141// selects an explicitly named configured profile. The daemon validates the ID
142// and computes the available reasoning efforts.
143func ResolveSessionModel(profiles config.Profiles, requested string) (string, string, error) {
144	if strings.TrimSpace(requested) == "" {
145		return "", "", fmt.Errorf("enter a model ID")
146	}
147	for name := range profiles.Providers {
148		if model, explicit := strings.CutPrefix(requested, name+"/"); explicit {
149			if model == "" {
150				return "", "", fmt.Errorf("enter a model ID after %s/", name)
151			}
152			return name, model, nil
153		}
154	}
155	if _, configured := profiles.Providers[profiles.Active]; !configured {
156		return "", "", fmt.Errorf("the session's provider is not configured; run /login")
157	}
158	return profiles.Active, requested, nil
159}