Add server-side workflows with YAML files, task freeze and lead tools
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
)
|
||||
|
||||
var ErrInvalid = errors.New("invalid workflow")
|
||||
|
||||
const maxNameRunes = 100
|
||||
|
||||
func VendorOf(model string) api.AgentSessionVendor {
|
||||
if strings.HasPrefix(model, "gpt-") {
|
||||
return api.Codex
|
||||
}
|
||||
return api.Claude
|
||||
}
|
||||
|
||||
func CleanName(name string) (string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
problem := nameProblem(name)
|
||||
if problem != "" {
|
||||
return "", fmt.Errorf("%w: workflow name %s", ErrInvalid, problem)
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func nameProblem(name string) string {
|
||||
switch {
|
||||
case strings.TrimSpace(name) == "":
|
||||
return "is empty"
|
||||
case strings.TrimSpace(name) != name:
|
||||
return fmt.Sprintf("%q has leading or trailing spaces", name)
|
||||
case utf8.RuneCountInString(name) > maxNameRunes:
|
||||
return fmt.Sprintf("%q is longer than %d characters", name, maxNameRunes)
|
||||
case strings.ContainsFunc(name, unicode.IsControl):
|
||||
return fmt.Sprintf("%q isn't a single line", name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Validate(def api.WorkflowDefinition) error {
|
||||
problems := []string{}
|
||||
if len(def.Stages) == 0 {
|
||||
problems = append(problems, "it has no stages")
|
||||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
for i, s := range def.Stages {
|
||||
where := fmt.Sprintf("stage %d (%q)", i+1, s.Name)
|
||||
p := nameProblem(s.Name)
|
||||
if p != "" {
|
||||
problems = append(problems, fmt.Sprintf("%s: name %s", where, p))
|
||||
}
|
||||
if seen[s.Name] {
|
||||
problems = append(problems, fmt.Sprintf("%s: another stage has the same name", where))
|
||||
}
|
||||
seen[s.Name] = true
|
||||
|
||||
switch {
|
||||
case !s.Vendor.Valid():
|
||||
problems = append(problems, fmt.Sprintf("%s: vendor must be claude or codex, not %q", where, s.Vendor))
|
||||
case strings.TrimSpace(s.Model) == "":
|
||||
problems = append(problems, fmt.Sprintf("%s: model is required", where))
|
||||
case string(VendorOf(s.Model)) != string(s.Vendor):
|
||||
problems = append(problems, fmt.Sprintf("%s: %s is a %s model, not %s", where, s.Model, VendorOf(s.Model), s.Vendor))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(s.Prompt) == "" {
|
||||
problems = append(problems, fmt.Sprintf("%s: prompt is empty", where))
|
||||
}
|
||||
}
|
||||
|
||||
if len(problems) > 0 {
|
||||
return fmt.Errorf("%w: %s", ErrInvalid, strings.Join(problems, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user