okami.mom/albedo

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

dawntui: replace the generic /quota page with a screen of latest limits, bars and per-limit trails8e3e173

main
5.9 KiB176 linesraw
1package daemon
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"io"
8	"net/http"
9	"slices"
10	"strings"
11	"time"
12
13	"albedo/cli/internal/daemon/protocol"
14)
15
16type CachePolicyPage = protocol.CachePolicyPage
17type CachePolicyQuery struct {
18	Extension, Host, Model, Next string
19	Limit                        int
20}
21type RequestRecordPage = protocol.RequestRecordPage
22type QuotaPage = protocol.QuotaPage
23type QuotaObservation = protocol.QuotaObservation
24
25func GetCachePolicy(ctx context.Context, conn *Connection, query CachePolicyQuery) (CachePolicyPage, error) {
26	params := protocol.ListModelsParams{View: new("cache-policy"), Limit: new(int64(min(200, max(1, query.Limit))))}
27	if query.Extension != "" {
28		params.Extension = &query.Extension
29	}
30	if query.Host != "" {
31		params.Host = &query.Host
32	}
33	if query.Model != "" {
34		params.Model = &query.Model
35	}
36	if query.Next != "" {
37		params.Next = &query.Next
38	}
39	var result protocol.CachePolicyPage
40	err := executeRead(ctx, conn, operation{Name: "inspect cache policy", BuildRequest: func(base string, body io.Reader) (*http.Request, error) {
41		return protocol.NewListModelsRequest(base, &params)
42	}, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &result) })
43	return result, err
44}
45func GetRequestRecords(ctx context.Context, conn *Connection, session, next string, limit int) (RequestRecordPage, error) {
46	params := protocol.GetContextParams{View: new("requests"), Limit: new(int64(min(200, max(1, limit))))}
47	if next != "" {
48		params.Next = &next
49	}
50	var result protocol.RequestRecordPage
51	err := executeRead(ctx, conn, operation{Capability: "context", Name: "inspect provider requests", BuildRequest: func(base string, body io.Reader) (*http.Request, error) {
52		return protocol.NewGetContextRequest(base, session, &params)
53	}, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &result) })
54	return result, err
55}
56
57// GetLatestQuota is the newest reading of every limit of every account.
58func GetLatestQuota(ctx context.Context, conn *Connection) ([]QuotaObservation, error) {
59	var result protocol.Server
60	err := executeRead(ctx, conn, operation{Name: "read latest quota", BuildRequest: func(base string, body io.Reader) (*http.Request, error) {
61		return protocol.NewGetServerRequest(base, nil)
62	}, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &result) })
63	return result.Quota, err
64}
65
66// QuotaTrailQuery names one limit of one account, and optionally how far back to read.
67type QuotaTrailQuery struct {
68	Provider string
69	Account  string
70	Limit    string
71	Since    time.Time
72}
73
74// GetQuotaTrail is a limit's readings, oldest first. It reads at most five
75// pages of 200, newest first, so a limit with more than that since the bound
76// comes back without its oldest readings: a trail is for drawing, not for audit.
77func GetQuotaTrail(ctx context.Context, conn *Connection, q QuotaTrailQuery) ([]QuotaObservation, error) {
78	observations := []QuotaObservation{}
79	next := ""
80	seen := map[string]bool{}
81	for page := 0; page < 5; page++ {
82		params := protocol.GetServerParams{
83			Include:       new("quota_history"),
84			Limit:         new(int64(200)),
85			QuotaProvider: &q.Provider,
86			QuotaAccount:  &q.Account,
87			QuotaLimit:    &q.Limit,
88		}
89		if !q.Since.IsZero() {
90			formatted := q.Since.UTC().Format(time.RFC3339Nano)
91			params.QuotaSince = &formatted
92		}
93		if next != "" {
94			params.Next = &next
95		}
96		var result protocol.Server
97		err := executeRead(ctx, conn, operation{Name: "inspect quota trail", BuildRequest: func(base string, body io.Reader) (*http.Request, error) {
98			return protocol.NewGetServerRequest(base, &params)
99		}, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &result, "quota_history") })
100		if err != nil {
101			return nil, err
102		}
103		if result.QuotaHistory == nil {
104			return nil, fieldError("quota history")
105		}
106		observations = append(observations, result.QuotaHistory.Items...)
107		next = value(result.QuotaHistory.Next)
108		if next == "" {
109			break
110		}
111		if seen[next] {
112			return nil, fieldError("quota trail page cursor")
113		}
114		seen[next] = true
115	}
116	slices.Reverse(observations)
117	return observations, nil
118}
119
120// Platform inspectors are rendered locally from typed facts. Their domain APIs
121// remain usable by clients that do not display terminal pages.
122func loadPlatformInspector(ctx context.Context, conn *Connection, snapshot Session, command string) (*PageDocument, error) {
123	doc := &PageDocument{Title: strings.TrimPrefix(command, "/"), Rows: []PageRow{}, Actions: []PageAction{}, Session: &snapshot}
124	next := ""
125	seen := map[string]bool{}
126	appendRow := func(id, text, badge string, details any) {
127		encoded, _ := json.MarshalIndent(details, "", "  ")
128		doc.Rows = append(doc.Rows, PageRow{ID: id, Text: text, Badge: badge, Detail: string(encoded), Tone: TonePlain})
129	}
130	for {
131		switch command {
132		case "/ttl":
133			page, err := GetCachePolicy(ctx, conn, CachePolicyQuery{Limit: 200, Next: next})
134			if err != nil {
135				return nil, err
136			}
137			if next == "" {
138				for _, layer := range page.Layers {
139					state := "loaded"
140					if !layer.Loaded {
141						state = "not loaded"
142					}
143					if layer.Error != nil {
144						state = layer.Error.Detail
145					}
146					doc.Summary += fmt.Sprintf("%s: %s · %s\n", layer.Name, state, layer.Path)
147				}
148			}
149			for _, entry := range page.Entries {
150				appendRow(entry.ID, entry.ID, entry.Policy, entry)
151			}
152			next = value(page.Next)
153		case "/requests":
154			page, err := GetRequestRecords(ctx, conn, snapshot.ID, next, 200)
155			if err != nil {
156				return nil, err
157			}
158			for _, entry := range page.Items {
159				appendRow(fmt.Sprint(entry.Sequence), entry.ProviderProfile+" · "+entry.Model, entry.Outcome, entry)
160			}
161			next = value(page.Next)
162		default:
163			return nil, fmt.Errorf("unknown platform inspector %s", command)
164		}
165		if next == "" {
166			break
167		}
168		if seen[next] {
169			return nil, fieldError("inspector page cursor")
170		}
171		seen[next] = true
172	}
173	doc.Summary = strings.TrimSpace(doc.Summary)
174	doc.Empty = "No observations yet."
175	return doc, nil
176}