okami.mom/albedo

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

Aly Raffaufrefactor(pages): share canonical extension declarationse3168a0

main
6.4 KiB171 linesraw
1package daemon
2
3import (
4	"context"
5	"io"
6	"net/http"
7
8	"albedo/cli/internal/daemon/protocol"
9)
10
11type CapabilityCatalog struct {
12	Workspace, Revision string
13	Extensions          map[string]bool
14	Diagnostics         []string
15	Candidates          []CatalogCandidate
16	Commands            []SessionCommand
17	Pages               []*PageDocument
18}
19type CatalogCandidate struct {
20	Description, ResolvedSource, PreferenceKey, Diagnostic, ShadowedBy *string
21	GlobalPreference, SessionOverride                                  *bool
22	ID, Kind, Title, Source                                            string
23	Valid, EffectiveEnabled, Eligible, Quarantined                     bool
24	Dependencies                                                       []string
25	Extension                                                          *protocol.ExtensionMetadata
26}
27type CatalogCapabilityRequest struct {
28	Revision, ID, Kind, Scope, ETag string
29	Enabled                         *bool
30}
31
32type CatalogDiscoveryError struct{ Code, Detail string }
33
34func (e *CatalogDiscoveryError) Error() string { return e.Detail }
35
36func GetCapabilityCatalog(ctx context.Context, conn *Connection, session string) (CapabilityCatalog, error) {
37	return readCapabilityCatalog(ctx, conn, session, true)
38}
39
40func readCapabilityCatalog(ctx context.Context, conn *Connection, session string, withDiscovery bool) (CapabilityCatalog, error) {
41	var result CapabilityCatalog
42	result.Extensions = map[string]bool{}
43	result.Candidates = []CatalogCandidate{}
44	result.Commands = []SessionCommand{}
45	params := protocol.GetCatalogParams{Limit: new(int64(200))}
46	first := true
47	seenCandidates, seenCommands, seenTokens := map[string]bool{}, map[string]bool{}, map[string]bool{}
48	pending := []string{}
49	var loadedRevision *string
50	loadedSeen := false
51	for {
52		var catalog protocol.Catalog
53		err := executeRead(ctx, conn, operation{Capability: "catalog", Name: "read session catalog", BuildRequest: func(base string, body io.Reader) (*http.Request, error) {
54			return protocol.NewGetCatalogRequest(base, session, &params)
55		}, Policy: readRecovery}, func(data []byte) error {
56			return decodeRequired(data, &catalog)
57		})
58		if err != nil {
59			return CapabilityCatalog{}, err
60		}
61		if catalog.Loaded.Commands == nil || (catalog.Discovery == nil) != (catalog.DiscoveryFailure != nil) {
62			return CapabilityCatalog{}, fieldError("catalog")
63		}
64		if catalog.DiscoveryFailure != nil {
65			if catalog.DiscoveryFailure.Code == "" || catalog.DiscoveryFailure.Detail == "" {
66				return CapabilityCatalog{}, fieldError("catalog discovery failure")
67			}
68			if withDiscovery {
69				return CapabilityCatalog{}, &CatalogDiscoveryError{Code: catalog.DiscoveryFailure.Code, Detail: catalog.DiscoveryFailure.Detail}
70			}
71		}
72		if withDiscovery {
73			if catalog.Discovery.Revision == "" || catalog.Discovery.Candidates == nil {
74				return CapabilityCatalog{}, fieldError("catalog discovery")
75			}
76			if first {
77				result.Workspace, result.Revision = catalog.Discovery.Workspace, catalog.Discovery.Revision
78				first = false
79			} else if result.Revision != catalog.Discovery.Revision {
80				return CapabilityCatalog{}, &APIError{StatusCode: 409, Code: "catalog_changed", Message: "the catalog changed during paging; refresh it"}
81			}
82		}
83		if !loadedSeen {
84			loadedRevision, loadedSeen = catalog.Loaded.Revision, true
85		} else if (loadedRevision == nil) != (catalog.Loaded.Revision == nil) || value(loadedRevision) != value(catalog.Loaded.Revision) {
86			return CapabilityCatalog{}, fieldError("loaded catalog revision")
87		}
88		if withDiscovery {
89			for _, candidate := range catalog.Discovery.Candidates {
90				if seenCandidates[candidate.ID] {
91					continue
92				}
93				seenCandidates[candidate.ID] = true
94				description := candidate.Description
95				item := CatalogCandidate{ID: candidate.ID, Kind: catalogKind(candidate.Kind), Title: candidate.Title, Source: candidate.Source, Description: &description, ResolvedSource: candidate.ResolvedSource, PreferenceKey: candidate.PreferenceKey, Diagnostic: nil, ShadowedBy: candidate.ShadowedBy, GlobalPreference: candidate.GlobalPreference, SessionOverride: candidate.SessionOverride, Valid: candidate.Valid, EffectiveEnabled: candidate.EffectiveEnabled, Eligible: candidate.Eligible, Quarantined: candidate.Quarantined, Dependencies: candidate.Dependencies, Extension: candidate.Extension}
96				if candidate.Diagnostic != nil {
97					detail := candidate.Diagnostic.Detail
98					item.Diagnostic = &detail
99				}
100				if candidate.Kind == "extension" {
101					result.Extensions[candidate.ID] = candidate.EffectiveEnabled
102				}
103				result.Candidates = append(result.Candidates, item)
104			}
105			for _, reason := range catalog.Discovery.Diagnostics {
106				result.Diagnostics = append(result.Diagnostics, reason.Detail)
107			}
108		}
109		commands := make([]SessionCommand, 0, len(catalog.Loaded.Commands)+len(catalog.NativeCommands))
110		if catalog.Loaded.Revision == nil {
111			for _, declared := range catalog.NativeCommands {
112				command, err := sessionCommandValue(declared, "")
113				if err != nil {
114					return CapabilityCatalog{}, err
115				}
116				commands = append(commands, command)
117			}
118		}
119		for _, raw := range catalog.Loaded.Commands {
120			command, err := decodeSessionCommand(raw)
121			if err != nil {
122				return CapabilityCatalog{}, err
123			}
124			commands = append(commands, command)
125		}
126		for _, command := range commands {
127			if !seenCommands[command.ID] {
128				seenCommands[command.ID] = true
129				result.Commands = append(result.Commands, command)
130			}
131		}
132		for _, page := range catalog.Pages {
133			converted, err := pageValue(page)
134			if err != nil {
135				return CapabilityCatalog{}, err
136			}
137			result.Pages = append(result.Pages, converted)
138		}
139		nextPages := []*string{catalog.Loaded.Next}
140		if withDiscovery {
141			nextPages = append(nextPages, catalog.Discovery.Next)
142		}
143		for _, next := range nextPages {
144			if next != nil && !seenTokens[*next] {
145				seenTokens[*next] = true
146				pending = append(pending, *next)
147			}
148		}
149		if len(pending) == 0 {
150			break
151		}
152		params.Next = new(pending[0])
153		pending = pending[1:]
154	}
155	return result, nil
156}
157func SetCatalogCapability(ctx context.Context, conn *Connection, session string, request CatalogCapabilityRequest) (ReloadResult, error) {
158	return setSelection(ctx, conn, session, request.Kind, request.ID, request.Revision, request.Scope, request.ETag, request.Enabled)
159}
160
161func catalogKind(kind string) string {
162	switch kind {
163	case "extension":
164		return "extensions"
165	case "skill":
166		return "skills"
167	case "instruction":
168		return "instructions"
169	}
170	return kind
171}