mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-09-07 10:17:46 +02:00
439 lines
11 KiB
Go
439 lines
11 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type StepTimeout string
|
|
|
|
func (t *StepTimeout) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var i int
|
|
if err := unmarshal(&i); err == nil {
|
|
if i < 0 {
|
|
i = 0
|
|
}
|
|
*t = StepTimeout(strconv.Itoa(i))
|
|
return nil
|
|
}
|
|
|
|
var s string
|
|
if err := unmarshal(&s); err == nil {
|
|
*t = StepTimeout(strings.TrimSpace(s))
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("invalid timeout")
|
|
}
|
|
|
|
func (t StepTimeout) MarshalYAML() (interface{}, error) {
|
|
s := strings.TrimSpace(string(t))
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
if isDigits(s) {
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return s, nil
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (t StepTimeout) Duration() (time.Duration, error) {
|
|
s := strings.TrimSpace(string(t))
|
|
if s == "" {
|
|
return 0, nil
|
|
}
|
|
|
|
if isDigits(s) {
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid timeout: %w", err)
|
|
}
|
|
if i <= 0 {
|
|
return 0, nil
|
|
}
|
|
return time.Duration(i) * time.Second, nil
|
|
}
|
|
|
|
if strings.HasSuffix(s, "d") {
|
|
daysStr := strings.TrimSuffix(s, "d")
|
|
if !isDigits(daysStr) {
|
|
return 0, fmt.Errorf("invalid timeout: %s", s)
|
|
}
|
|
days, err := strconv.Atoi(daysStr)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid timeout: %w", err)
|
|
}
|
|
if days <= 0 {
|
|
return 0, nil
|
|
}
|
|
return time.Duration(days) * 24 * time.Hour, nil
|
|
}
|
|
|
|
d, err := time.ParseDuration(s)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid timeout: %w", err)
|
|
}
|
|
if d <= 0 {
|
|
return 0, nil
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func isDigits(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if c < '0' || c > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
type StepThreads string
|
|
|
|
func (t *StepThreads) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var i int
|
|
if err := unmarshal(&i); err == nil {
|
|
if i < 0 {
|
|
i = 0
|
|
}
|
|
*t = StepThreads(strconv.Itoa(i))
|
|
return nil
|
|
}
|
|
|
|
var s string
|
|
if err := unmarshal(&s); err == nil {
|
|
*t = StepThreads(strings.TrimSpace(s))
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("invalid threads")
|
|
}
|
|
|
|
func (t StepThreads) MarshalYAML() (interface{}, error) {
|
|
s := strings.TrimSpace(string(t))
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
if isDigits(s) {
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return s, nil
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (t StepThreads) Int() (int, error) {
|
|
s := strings.TrimSpace(string(t))
|
|
if s == "" {
|
|
return 0, nil
|
|
}
|
|
|
|
i, err := strconv.Atoi(s)
|
|
if err == nil {
|
|
if i <= 0 {
|
|
return 0, nil
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
f, ferr := strconv.ParseFloat(s, 64)
|
|
if ferr != nil {
|
|
return 0, fmt.Errorf("invalid threads: %w", err)
|
|
}
|
|
|
|
if f <= 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
if f != float64(int(f)) {
|
|
return 0, fmt.Errorf("invalid threads: %s", s)
|
|
}
|
|
|
|
return int(f), nil
|
|
}
|
|
|
|
// StepRunnerConfig holds per-step runner configuration for remote-bash steps
|
|
// The runner type is specified separately in Step.StepRunner
|
|
type StepRunnerConfig struct {
|
|
*RunnerConfig `yaml:",inline"` // Embed all RunnerConfig fields (image, host, etc.)
|
|
}
|
|
|
|
// Step represents a single execution step in a module
|
|
type Step struct {
|
|
Name string `yaml:"name"`
|
|
Type StepType `yaml:"type"`
|
|
StepRunner RunnerType `yaml:"step_runner"` // Runner for this step: local (default), docker, ssh
|
|
PreCondition string `yaml:"pre_condition"`
|
|
Log string `yaml:"log"`
|
|
Timeout StepTimeout `yaml:"timeout,omitempty"`
|
|
|
|
// Bash step fields
|
|
Command string `yaml:"command"`
|
|
Commands []string `yaml:"commands"`
|
|
ParallelCommands []string `yaml:"parallel_commands"`
|
|
StdFile string `yaml:"std_file"` // File path to save stdout/stderr output
|
|
|
|
// Structured argument fields (for bash/remote-bash steps)
|
|
// These are templated and joined with Command in order: command + speed + config + input + output
|
|
SpeedArgs string `yaml:"speed_args"`
|
|
ConfigArgs string `yaml:"config_args"`
|
|
InputArgs string `yaml:"input_args"`
|
|
OutputArgs string `yaml:"output_args"`
|
|
|
|
// Function step fields
|
|
Function string `yaml:"function"`
|
|
Functions []string `yaml:"functions"`
|
|
ParallelFunctions []string `yaml:"parallel_functions"`
|
|
|
|
// Parallel step fields
|
|
ParallelSteps []Step `yaml:"parallel_steps"`
|
|
|
|
// Foreach step fields
|
|
Input string `yaml:"input"`
|
|
Variable string `yaml:"variable"`
|
|
Threads StepThreads `yaml:"threads,omitempty"`
|
|
Step *Step `yaml:"step"`
|
|
|
|
// Remote-bash step fields
|
|
StepRunnerConfig *StepRunnerConfig `yaml:"step_runner_config"`
|
|
StepRemoteFile string `yaml:"step_remote_file"` // File path on remote (Docker/SSH) to copy after execution
|
|
HostOutputFile string `yaml:"host_output_file"` // Local path to copy the remote file to
|
|
|
|
// HTTP step fields
|
|
URL string `yaml:"url"`
|
|
Method string `yaml:"method"`
|
|
Headers map[string]string `yaml:"headers"`
|
|
RequestBody string `yaml:"request_body"`
|
|
|
|
// LLM step fields
|
|
Messages []LLMMessage `yaml:"messages"`
|
|
Tools []LLMTool `yaml:"tools,omitempty"`
|
|
ToolChoice interface{} `yaml:"tool_choice,omitempty"`
|
|
LLMConfig *LLMStepConfig `yaml:"llm_config,omitempty"`
|
|
IsEmbedding bool `yaml:"is_embedding,omitempty"`
|
|
EmbeddingInput []string `yaml:"embedding_input,omitempty"`
|
|
ExtraLLMParams map[string]interface{} `yaml:"extra_llm_parameters,omitempty"`
|
|
|
|
// Common fields
|
|
Exports map[string]string `yaml:"exports"`
|
|
OnSuccess []Action `yaml:"on_success"`
|
|
OnError []Action `yaml:"on_error"`
|
|
Decision *DecisionConfig `yaml:"decision,omitempty"`
|
|
}
|
|
|
|
// DecisionCase represents a single case in switch-style decision
|
|
type DecisionCase struct {
|
|
Goto string `yaml:"goto"`
|
|
}
|
|
|
|
// DecisionConfig supports switch/case routing for conditional workflow branching.
|
|
//
|
|
// Switch/case syntax:
|
|
//
|
|
// decision:
|
|
// switch: "{{variable}}"
|
|
// cases:
|
|
// "value1": { goto: step-a }
|
|
// "value2": { goto: step-b }
|
|
// default:
|
|
// goto: fallback-step
|
|
type DecisionConfig struct {
|
|
Switch string `yaml:"switch,omitempty"`
|
|
Cases map[string]DecisionCase `yaml:"cases,omitempty"`
|
|
Default *DecisionCase `yaml:"default,omitempty"`
|
|
}
|
|
|
|
// Action represents on_success/on_error handler
|
|
type Action struct {
|
|
Action ActionType `yaml:"action"`
|
|
Message string `yaml:"message"`
|
|
Condition string `yaml:"condition"`
|
|
Name string `yaml:"name"` // for export action
|
|
Value interface{} `yaml:"value"` // for export action
|
|
Type StepType `yaml:"type"` // for run action
|
|
Command string `yaml:"command"` // for run bash action
|
|
Functions []string `yaml:"functions"` // for run function action
|
|
Export map[string]string `yaml:"export"` // for run function action
|
|
Notify string `yaml:"notify"` // notification message
|
|
}
|
|
|
|
// IsBashStep returns true if this is a bash step
|
|
func (s *Step) IsBashStep() bool {
|
|
return s.Type == StepTypeBash
|
|
}
|
|
|
|
// IsFunctionStep returns true if this is a function step
|
|
func (s *Step) IsFunctionStep() bool {
|
|
return s.Type == StepTypeFunction
|
|
}
|
|
|
|
// IsParallelStep returns true if this is a parallel step
|
|
func (s *Step) IsParallelStep() bool {
|
|
return s.Type == StepTypeParallel
|
|
}
|
|
|
|
// IsForeachStep returns true if this is a foreach step
|
|
func (s *Step) IsForeachStep() bool {
|
|
return s.Type == StepTypeForeach
|
|
}
|
|
|
|
// IsRemoteBashStep returns true if this is a remote-bash step
|
|
func (s *Step) IsRemoteBashStep() bool {
|
|
return s.Type == StepTypeRemoteBash
|
|
}
|
|
|
|
// IsHTTPStep returns true if this is an HTTP step
|
|
func (s *Step) IsHTTPStep() bool {
|
|
return s.Type == StepTypeHTTP
|
|
}
|
|
|
|
// IsLLMStep returns true if this is an LLM step
|
|
func (s *Step) IsLLMStep() bool {
|
|
return s.Type == StepTypeLLM
|
|
}
|
|
|
|
// GetStepRunner returns the step runner type, defaulting to host/local
|
|
func (s *Step) GetStepRunner() RunnerType {
|
|
if s.StepRunner == "" {
|
|
return RunnerTypeHost // default to local
|
|
}
|
|
return s.StepRunner
|
|
}
|
|
|
|
// HasParallelCommands returns true if step has parallel commands
|
|
func (s *Step) HasParallelCommands() bool {
|
|
return len(s.ParallelCommands) > 0
|
|
}
|
|
|
|
// HasParallelFunctions returns true if step has parallel functions
|
|
func (s *Step) HasParallelFunctions() bool {
|
|
return len(s.ParallelFunctions) > 0
|
|
}
|
|
|
|
// HasDecision returns true if step has decision routing
|
|
func (s *Step) HasDecision() bool {
|
|
if s.Decision == nil {
|
|
return false
|
|
}
|
|
return s.Decision.Switch != "" || len(s.Decision.Cases) > 0
|
|
}
|
|
|
|
// HasExports returns true if step exports variables
|
|
func (s *Step) HasExports() bool {
|
|
return len(s.Exports) > 0
|
|
}
|
|
|
|
// GetCommands returns the list of commands to execute
|
|
// Returns single command as slice if Commands is empty
|
|
func (s *Step) GetCommands() []string {
|
|
if len(s.Commands) > 0 {
|
|
return s.Commands
|
|
}
|
|
if s.Command != "" {
|
|
return []string{s.Command}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetFunctions returns the list of functions to execute
|
|
// Returns single function as slice if Functions is empty
|
|
func (s *Step) GetFunctions() []string {
|
|
if len(s.Functions) > 0 {
|
|
return s.Functions
|
|
}
|
|
if s.Function != "" {
|
|
return []string{s.Function}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Clone creates a shallow copy of the step with new slices for Commands
|
|
func (s *Step) Clone() *Step {
|
|
cloned := *s
|
|
|
|
// Deep copy slices to avoid modifying originals
|
|
if len(s.Commands) > 0 {
|
|
cloned.Commands = make([]string, len(s.Commands))
|
|
copy(cloned.Commands, s.Commands)
|
|
}
|
|
if len(s.ParallelCommands) > 0 {
|
|
cloned.ParallelCommands = make([]string, len(s.ParallelCommands))
|
|
copy(cloned.ParallelCommands, s.ParallelCommands)
|
|
}
|
|
if len(s.Functions) > 0 {
|
|
cloned.Functions = make([]string, len(s.Functions))
|
|
copy(cloned.Functions, s.Functions)
|
|
}
|
|
if len(s.ParallelFunctions) > 0 {
|
|
cloned.ParallelFunctions = make([]string, len(s.ParallelFunctions))
|
|
copy(cloned.ParallelFunctions, s.ParallelFunctions)
|
|
}
|
|
|
|
// Deep copy StepRunnerConfig
|
|
if s.StepRunnerConfig != nil {
|
|
clonedConfig := &StepRunnerConfig{}
|
|
if s.StepRunnerConfig.RunnerConfig != nil {
|
|
cfg := *s.StepRunnerConfig.RunnerConfig
|
|
// Deep copy slices in RunnerConfig
|
|
if len(cfg.Volumes) > 0 {
|
|
cfg.Volumes = make([]string, len(s.StepRunnerConfig.Volumes))
|
|
copy(cfg.Volumes, s.StepRunnerConfig.Volumes)
|
|
}
|
|
if len(cfg.Env) > 0 {
|
|
cfg.Env = make(map[string]string, len(s.StepRunnerConfig.Env))
|
|
for k, v := range s.StepRunnerConfig.Env {
|
|
cfg.Env[k] = v
|
|
}
|
|
}
|
|
clonedConfig.RunnerConfig = &cfg
|
|
}
|
|
cloned.StepRunnerConfig = clonedConfig
|
|
}
|
|
|
|
// Deep copy HTTP Headers map
|
|
if len(s.Headers) > 0 {
|
|
cloned.Headers = make(map[string]string, len(s.Headers))
|
|
for k, v := range s.Headers {
|
|
cloned.Headers[k] = v
|
|
}
|
|
}
|
|
|
|
// Deep copy LLM fields
|
|
if len(s.Messages) > 0 {
|
|
cloned.Messages = make([]LLMMessage, len(s.Messages))
|
|
copy(cloned.Messages, s.Messages)
|
|
}
|
|
if len(s.Tools) > 0 {
|
|
cloned.Tools = make([]LLMTool, len(s.Tools))
|
|
copy(cloned.Tools, s.Tools)
|
|
}
|
|
if len(s.EmbeddingInput) > 0 {
|
|
cloned.EmbeddingInput = make([]string, len(s.EmbeddingInput))
|
|
copy(cloned.EmbeddingInput, s.EmbeddingInput)
|
|
}
|
|
if len(s.ExtraLLMParams) > 0 {
|
|
cloned.ExtraLLMParams = make(map[string]interface{}, len(s.ExtraLLMParams))
|
|
for k, v := range s.ExtraLLMParams {
|
|
cloned.ExtraLLMParams[k] = v
|
|
}
|
|
}
|
|
|
|
return &cloned
|
|
}
|