-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathskill.go
More file actions
212 lines (184 loc) · 6.55 KB
/
Copy pathskill.go
File metadata and controls
212 lines (184 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package skill
import (
"context"
"io/fs"
"path"
"sort"
"strings"
"github.com/agent-dance/agent-adaptor/driver"
)
// Skill, Ref, and Source are the values accepted by adaptor.WithSkills and by
// the host extension contracts in this package.
type (
// Skill is the full description of one skill: identity (Key),
// origin (Source), Required marker, human-readable Reason, and
// optional Metadata. Skill values act as [Ref], so constructor
// results can be passed straight to adaptor.WithSkills.
Skill = driver.Skill
// Ref is what adaptor.WithSkills accepts: either a
// provider catalogue key (built with [Key]) or a fully-defined
// [Skill] value (built with [Dir], [FS], [Inline], or [Archive]).
Ref = driver.SkillRef
// Source is the open marker interface for a Skill's origin.
// Hosts may define custom Source types as long as a matching
// [Materializer] is installed to handle them.
Source = driver.SkillSource
)
// PathSource sources a skill from a local directory containing SKILL.md.
type PathSource struct {
// Path is the skill directory.
Path string
}
// SkillSource implements [Source].
func (PathSource) SkillSource() {}
// SkillPath returns the directory consumed by a compatible materializer.
func (s PathSource) SkillPath() string { return s.Path }
// FSSource sources a skill from an io/fs.FS tree rooted at Root.
type FSSource struct {
// FS contains the skill tree.
FS fs.FS
// Root locates the skill directory within FS. Empty and "." mean the FS root.
Root string
}
// SkillSource implements [Source].
func (FSSource) SkillSource() {}
// SkillFS returns the filesystem and root consumed by a compatible materializer.
func (s FSSource) SkillFS() (fs.FS, string) { return s.FS, s.Root }
// InlineSource carries a single SKILL.md document.
type InlineSource struct {
// SkillMD is the complete SKILL.md content.
SkillMD string
}
// SkillSource implements [Source].
func (InlineSource) SkillSource() {}
// InlineSkillMD returns the SKILL.md content consumed by a compatible materializer.
func (s InlineSource) InlineSkillMD() string { return s.SkillMD }
// Provider resolves catalogue keys into concrete skills for a run. Providers
// may additionally return Required skills that were not explicitly requested.
type Provider interface {
// GetSkills resolves the requested catalogue keys. Implementations may also
// include skills marked Required.
GetSkills(ctx context.Context, keys []string) (map[string]Skill, error)
}
// Catalog extends [Provider] with deterministic catalogue enumeration.
type Catalog interface {
Provider
// Catalogue returns the skills available from the provider.
Catalogue(ctx context.Context) ([]Skill, error)
}
// Set is a static map-backed [Catalog].
type Set map[string]Skill
// GetSkills implements [Provider]. Required entries are always returned.
func (s Set) GetSkills(_ context.Context, keys []string) (map[string]Skill, error) {
out := make(map[string]Skill, len(keys))
for _, key := range keys {
key = strings.TrimSpace(key)
if key == "" {
continue
}
if value, ok := s.lookup(key); ok {
out[value.Key] = value
}
}
for mapKey, value := range s {
if !value.Required {
continue
}
if strings.TrimSpace(value.Key) == "" {
value.Key = mapKey
}
if _, exists := out[value.Key]; !exists {
out[value.Key] = value
}
}
return out, nil
}
// Catalogue implements [Catalog] and returns entries ordered by key.
func (s Set) Catalogue(_ context.Context) ([]Skill, error) {
out := make([]Skill, 0, len(s))
for mapKey, value := range s {
if strings.TrimSpace(value.Key) == "" {
value.Key = mapKey
}
out = append(out, value)
}
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
return out, nil
}
func (s Set) lookup(key string) (Skill, bool) {
value, ok := s[key]
if !ok {
return Skill{}, false
}
if strings.TrimSpace(value.Key) == "" {
value.Key = key
}
return value, true
}
// Materializer writes a skill source to a directory containing SKILL.md.
type Materializer interface {
// Materialize makes s available in a directory containing SKILL.md and
// returns that directory.
Materialize(ctx context.Context, s Skill) (sourcePath string, err error)
}
// Reserved Metadata keys interpreted by the SDK and drivers. Setting
// MetadataRuntimeName on a Skill overrides the directory name the
// materializer writes and drivers mount. MetadataDisplayName provides a
// human-readable label for inspection and user interfaces.
const (
// MetadataRuntimeName is the metadata key for the provider-visible directory name.
MetadataRuntimeName = driver.SkillMetadataRuntimeName
// MetadataDisplayName is the metadata key for a human-readable skill name.
MetadataDisplayName = driver.SkillMetadataDisplayName
)
// Dir builds a Skill sourced from a local directory that contains
// SKILL.md (and optional reference files). The skill key defaults to
// the directory basename; callers may override it by assigning to the
// returned Skill's Key field.
func Dir(path string) Skill {
return Skill{Key: keyFromPath(path), Source: PathSource{Path: path}}
}
// FS builds a Skill sourced from an io/fs.FS tree rooted at root. The
// root entry must contain SKILL.md; "" or "." mean the FS root. The
// skill key defaults to the basename of root ("skill" when root is
// empty); callers may override it by assigning to the returned Skill's
// Key field.
func FS(fsys fs.FS, root string) Skill {
key := keyFromPath(root)
if key == "" {
key = "skill"
}
return Skill{Key: key, Source: FSSource{FS: fsys, Root: root}}
}
// Inline builds a Skill whose entire content is the given SKILL.md
// string. Key is required. Skills that need auxiliary reference files
// should use [FS] or [Archive] instead.
func Inline(key, skillMD string) Skill {
return Skill{Key: key, Source: InlineSource{SkillMD: skillMD}}
}
// Key returns a Ref that references a provider-side skill by its
// catalogue key. The key is resolved by the [Provider] installed on
// the agent; unknown keys fail the run before the driver is invoked.
func Key(k string) Ref {
return driver.SkillKey(k)
}
// Require returns a copy of s marked Required=true with the given
// human-readable reason. Required skills join the selected set of
// every run that sees them, regardless of what the caller passed to
// WithSkills.
func Require(s Skill, reason string) Skill {
s.Required = true
s.Reason = reason
return s
}
func keyFromPath(value string) string {
value = strings.TrimRight(strings.ReplaceAll(strings.TrimSpace(value), "\\", "/"), "/")
if value == "" {
return ""
}
base := path.Base(value)
if base == "." || base == "/" {
return ""
}
return base
}