-
Notifications
You must be signed in to change notification settings - Fork 340
Expand file tree
/
Copy pathvalidate.go
More file actions
179 lines (167 loc) · 5.36 KB
/
validate.go
File metadata and controls
179 lines (167 loc) · 5.36 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
package latest
import (
"errors"
)
func (t *Config) UnmarshalYAML(unmarshal func(any) error) error {
type alias Config
var tmp alias
if err := unmarshal(&tmp); err != nil {
return err
}
*t = Config(tmp)
return t.validate()
}
func (t *Config) validate() error {
for i := range t.Agents {
agent := &t.Agents[i]
// Validate fallback config
if err := agent.validateFallback(); err != nil {
return err
}
for j := range agent.Toolsets {
if err := agent.Toolsets[j].validate(); err != nil {
return err
}
}
if agent.Hooks != nil {
if err := agent.Hooks.validate(); err != nil {
return err
}
}
}
return nil
}
// validateFallback validates the fallback configuration for an agent
func (a *AgentConfig) validateFallback() error {
if a.Fallback == nil {
return nil
}
// -1 is allowed as a special value meaning "explicitly no retries"
if a.Fallback.Retries < -1 {
return errors.New("fallback.retries must be >= -1 (use -1 for no retries, 0 for default)")
}
if a.Fallback.Cooldown.Duration < 0 {
return errors.New("fallback.cooldown must be non-negative")
}
return nil
}
func (t *Toolset) validate() error {
// Attributes used on the wrong toolset type.
if len(t.Shell) > 0 && t.Type != "script" {
return errors.New("shell can only be used with type 'script'")
}
if t.Path != "" && t.Type != "memory" && t.Type != "tasks" {
return errors.New("path can only be used with type 'memory' or 'tasks'")
}
if len(t.PostEdit) > 0 && t.Type != "filesystem" {
return errors.New("post_edit can only be used with type 'filesystem'")
}
if t.IgnoreVCS != nil && t.Type != "filesystem" {
return errors.New("ignore_vcs can only be used with type 'filesystem'")
}
if len(t.Env) > 0 && (t.Type != "shell" && t.Type != "script" && t.Type != "mcp" && t.Type != "lsp") {
return errors.New("env can only be used with type 'shell', 'script', 'mcp' or 'lsp'")
}
if len(t.FileTypes) > 0 && t.Type != "lsp" {
return errors.New("file_types can only be used with type 'lsp'")
}
if len(t.Models) > 0 && t.Type != "model_picker" {
return errors.New("models can only be used with type 'model_picker'")
}
if t.Shared && t.Type != "todo" {
return errors.New("shared can only be used with type 'todo'")
}
if t.Version != "" && t.Type != "mcp" && t.Type != "lsp" {
return errors.New("version can only be used with type 'mcp' or 'lsp'")
}
if t.Command != "" && t.Type != "mcp" && t.Type != "lsp" {
return errors.New("command can only be used with type 'mcp' or 'lsp'")
}
if len(t.Args) > 0 && t.Type != "mcp" && t.Type != "lsp" {
return errors.New("args can only be used with type 'mcp' or 'lsp'")
}
if t.Ref != "" && t.Type != "mcp" && t.Type != "rag" {
return errors.New("ref can only be used with type 'mcp' or 'rag'")
}
if (t.Remote.URL != "" || t.Remote.TransportType != "" || t.Remote.OAuth != nil) && t.Type != "mcp" {
return errors.New("remote can only be used with type 'mcp'")
}
if (len(t.Remote.Headers) > 0) && (t.Type != "mcp" && t.Type != "a2a") {
return errors.New("remote headers can only be used with type 'mcp' or 'a2a'")
}
if len(t.Headers) > 0 && t.Type != "openapi" && t.Type != "a2a" {
return errors.New("headers can only be used with type 'openapi' or 'a2a'")
}
if t.Config != nil && t.Type != "mcp" {
return errors.New("config can only be used with type 'mcp'")
}
if t.URL != "" && t.Type != "a2a" && t.Type != "openapi" {
return errors.New("url can only be used with type 'a2a' or 'openapi'")
}
if t.Name != "" && (t.Type != "mcp" && t.Type != "a2a" && t.Type != "rag") {
return errors.New("name can only be used with type 'mcp', 'a2a', or 'rag'")
}
if t.RAGConfig != nil && t.Type != "rag" {
return errors.New("rag_config can only be used with type 'rag'")
}
switch t.Type {
case "shell":
// no additional validation needed
case "memory":
// path is optional; defaults to ~/.cagent/memory/<agent-name>/memory.db
case "tasks":
// path defaults to ./tasks.json if not set
case "mcp":
count := 0
if t.Command != "" {
count++
}
if t.Remote.URL != "" {
count++
}
if t.Ref != "" {
count++
}
if count == 0 {
return errors.New("either command, remote or ref must be set")
}
if count > 1 {
return errors.New("either command, remote or ref must be set, but only one of those")
}
if t.Remote.OAuth != nil {
if t.Remote.URL == "" {
return errors.New("oauth requires remote url to be set")
}
if t.Remote.OAuth.ClientID == "" {
return errors.New("oauth requires clientId to be set")
}
if t.Remote.OAuth.CallbackPort != 0 && (t.Remote.OAuth.CallbackPort < 1 || t.Remote.OAuth.CallbackPort > 65535) {
return errors.New("oauth callbackPort must be between 1 and 65535")
}
}
case "a2a":
if t.URL == "" {
return errors.New("a2a toolset requires a url to be set")
}
case "lsp":
if t.Command == "" {
return errors.New("lsp toolset requires a command to be set")
}
case "openapi":
if t.URL == "" {
return errors.New("openapi toolset requires a url to be set")
}
case "model_picker":
if len(t.Models) == 0 {
return errors.New("model_picker toolset requires at least one model in the 'models' list")
}
case "rag":
// rag toolset requires either a ref or inline rag_config
if t.Ref == "" && t.RAGConfig == nil {
return errors.New("rag toolset requires either ref or rag_config")
}
case "background_agents":
// no additional validation needed
}
return nil
}