Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6307,6 +6307,15 @@
"model": "claude-3-5-sonnet-20241022",
"max-turns": 15
},
{
"id": "claude",
"model": "claude-3-5-sonnet-20241022",
"max-tokens": 4096
},
{
"id": "custom",
"max-iterations": 3
},
{
"id": "copilot",
"version": "beta"
Expand Down Expand Up @@ -6354,7 +6363,33 @@
"description": "Maximum number of chat iterations per run as a string value"
}
],
"description": "Maximum number of chat iterations per run. Helps prevent runaway loops and control costs. Has sensible defaults and can typically be omitted. Note: Only supported by the claude engine."
"description": "Maximum number of chat iterations per run. Helps prevent runaway loops and control costs. Has sensible defaults and can typically be omitted. Note: Only supported by the claude and custom engines."
},
"max-tokens": {
"oneOf": [
{
"type": "integer",
"description": "Maximum number of tokens (input + output) per request as an integer value"
},
{
"type": "string",
"description": "Maximum number of tokens (input + output) per request as a string value"
}
],
"description": "Maximum number of tokens to use per request. Controls the context window size and response length. Has sensible defaults and can typically be omitted. Note: Only supported by the claude and custom engines."
},
"max-iterations": {
"oneOf": [
{
"type": "integer",
"description": "Maximum number of iterations per run as an integer value"
},
{
"type": "string",
"description": "Maximum number of iterations per run as a string value"
}
],
"description": "Maximum number of iterations per run. Similar to max-turns but used by some engines as an alias. Helps prevent runaway loops and control costs. Has sensible defaults and can typically be omitted. Note: Only supported by the custom engine."
},
"concurrency": {
"oneOf": [
Expand Down
68 changes: 67 additions & 1 deletion pkg/workflow/agent_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,13 @@ func (c *Compiler) validateMaxTurnsSupport(frontmatter map[string]any, engine Co

// max-turns is specified, check if the engine supports it
if !engine.SupportsMaxTurns() {
return fmt.Errorf("max-turns not supported: engine '%s' does not support the max-turns feature. Use engine: copilot or remove max-turns from your configuration. Example:\nengine:\n id: copilot\n max-turns: 5", engine.GetID())
errorMsg := fmt.Sprintf("max-turns not supported: engine '%s' does not support the max-turns feature. Supported engines: claude, custom. Example:\nengine:\n id: claude\n max-turns: 5", engine.GetID())
if c.strictMode {
return fmt.Errorf("max-turns not supported: engine '%s' does not support the max-turns feature. Supported engines: claude, custom. Example:\nengine:\n id: claude\n max-turns: 5", engine.GetID())
}
// In non-strict mode, issue a warning
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(errorMsg))
c.IncrementWarningCount()
}

// Engine supports max-turns - additional validation could be added here if needed
Expand All @@ -142,6 +148,66 @@ func (c *Compiler) validateMaxTurnsSupport(frontmatter map[string]any, engine Co
return nil
}

// validateMaxTokensSupport validates that max-tokens is only used with engines that support this feature
func (c *Compiler) validateMaxTokensSupport(frontmatter map[string]any, engine CodingAgentEngine) error {
// Check if max-tokens is specified in the engine config
engineSetting, engineConfig := c.ExtractEngineConfig(frontmatter)
_ = engineSetting // Suppress unused variable warning

hasMaxTokens := engineConfig != nil && engineConfig.MaxTokens != ""

if !hasMaxTokens {
// No max-tokens specified, no validation needed
return nil
}

// max-tokens is specified, check if the engine supports it
if !engine.SupportsMaxTokens() {
errorMsg := fmt.Sprintf("max-tokens not supported: engine '%s' does not support the max-tokens feature. Supported engines: claude, custom. Example:\nengine:\n id: claude\n max-tokens: 4096", engine.GetID())
if c.strictMode {
return fmt.Errorf("max-tokens not supported: engine '%s' does not support the max-tokens feature. Supported engines: claude, custom. Example:\nengine:\n id: claude\n max-tokens: 4096", engine.GetID())
}
// In non-strict mode, issue a warning
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(errorMsg))
c.IncrementWarningCount()
}

// Engine supports max-tokens - additional validation could be added here if needed
// For now, we rely on JSON schema validation for format checking

return nil
}

// validateMaxIterationsSupport validates that max-iterations is only used with engines that support this feature
func (c *Compiler) validateMaxIterationsSupport(frontmatter map[string]any, engine CodingAgentEngine) error {
// Check if max-iterations is specified in the engine config
engineSetting, engineConfig := c.ExtractEngineConfig(frontmatter)
_ = engineSetting // Suppress unused variable warning

hasMaxIterations := engineConfig != nil && engineConfig.MaxIterations != ""

if !hasMaxIterations {
// No max-iterations specified, no validation needed
return nil
}

// max-iterations is specified, check if the engine supports it
if !engine.SupportsMaxIterations() {
errorMsg := fmt.Sprintf("max-iterations not supported: engine '%s' does not support the max-iterations feature. Supported engines: custom. Example:\nengine:\n id: custom\n max-iterations: 3", engine.GetID())
if c.strictMode {
return fmt.Errorf("max-iterations not supported: engine '%s' does not support the max-iterations feature. Supported engines: custom. Example:\nengine:\n id: custom\n max-iterations: 3", engine.GetID())
}
// In non-strict mode, issue a warning
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(errorMsg))
c.IncrementWarningCount()
}

// Engine supports max-iterations - additional validation could be added here if needed
// For now, we rely on JSON schema validation for format checking

return nil
}

// validateWebSearchSupport validates that web-search tool is only used with engines that support this feature
func (c *Compiler) validateWebSearchSupport(tools map[string]any, engine CodingAgentEngine) {
// Check if web-search tool is requested
Expand Down
16 changes: 16 additions & 0 deletions pkg/workflow/agentic_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ type CapabilityProvider interface {
// SupportsMaxTurns returns true if this engine supports the max-turns feature
SupportsMaxTurns() bool

// SupportsMaxTokens returns true if this engine supports the max-tokens feature
SupportsMaxTokens() bool

// SupportsMaxIterations returns true if this engine supports the max-iterations feature
SupportsMaxIterations() bool

// SupportsWebFetch returns true if this engine has built-in support for the web-fetch tool
SupportsWebFetch() bool

Expand Down Expand Up @@ -196,6 +202,8 @@ type BaseEngine struct {
supportsToolsAllowlist bool
supportsHTTPTransport bool
supportsMaxTurns bool
supportsMaxTokens bool
supportsMaxIterations bool
supportsWebFetch bool
supportsWebSearch bool
supportsFirewall bool
Expand Down Expand Up @@ -229,6 +237,14 @@ func (e *BaseEngine) SupportsMaxTurns() bool {
return e.supportsMaxTurns
}

func (e *BaseEngine) SupportsMaxTokens() bool {
return e.supportsMaxTokens
}

func (e *BaseEngine) SupportsMaxIterations() bool {
return e.supportsMaxIterations
}

func (e *BaseEngine) SupportsWebFetch() bool {
return e.supportsWebFetch
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/workflow/claude_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ func NewClaudeEngine() *ClaudeEngine {
description: "Uses Claude Code with full MCP tool support and allow-listing",
experimental: false,
supportsToolsAllowlist: true,
supportsHTTPTransport: true, // Claude supports both stdio and HTTP transport
supportsMaxTurns: true, // Claude supports max-turns feature
supportsWebFetch: true, // Claude has built-in WebFetch support
supportsWebSearch: true, // Claude has built-in WebSearch support
supportsFirewall: true, // Claude supports network firewalling via AWF
supportsHTTPTransport: true, // Claude supports both stdio and HTTP transport
supportsMaxTurns: true, // Claude supports max-turns feature
supportsMaxTokens: true, // Claude supports max-tokens feature
supportsMaxIterations: false, // Claude uses max-turns instead
supportsWebFetch: true, // Claude has built-in WebFetch support
supportsWebSearch: true, // Claude has built-in WebSearch support
supportsFirewall: true, // Claude supports network firewalling via AWF
},
}
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/codex_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func NewCodexEngine() *CodexEngine {
supportsToolsAllowlist: true,
supportsHTTPTransport: true, // Codex now supports HTTP transport for remote MCP servers
supportsMaxTurns: false, // Codex does not support max-turns feature
supportsMaxTokens: false, // Codex max-tokens needs verification
supportsMaxIterations: false, // Codex max-iterations needs verification
supportsWebFetch: false, // Codex does not have built-in web-fetch support
supportsWebSearch: true, // Codex has built-in web-search support
supportsFirewall: true, // Codex supports network firewalling via AWF
Expand Down
10 changes: 10 additions & 0 deletions pkg/workflow/compiler_orchestrator_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle
return nil, err
}

// Validate max-tokens support for the current engine
if err := c.validateMaxTokensSupport(result.Frontmatter, agenticEngine); err != nil {
return nil, err
}

// Validate max-iterations support for the current engine
if err := c.validateMaxIterationsSupport(result.Frontmatter, agenticEngine); err != nil {
return nil, err
}

// Validate web-search support for the current engine (warning only)
c.validateWebSearchSupport(tools, agenticEngine)

Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/copilot_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func NewCopilotEngine() *CopilotEngine {
supportsToolsAllowlist: true,
supportsHTTPTransport: true, // Copilot CLI supports HTTP transport via MCP
supportsMaxTurns: false, // Copilot CLI does not support max-turns feature yet
supportsMaxTokens: false, // Copilot CLI max-tokens needs verification
supportsMaxIterations: false, // Copilot CLI max-iterations needs verification
supportsWebFetch: true, // Copilot CLI has built-in web-fetch support
supportsWebSearch: false, // Copilot CLI does not have built-in web-search support
supportsFirewall: true, // Copilot supports network firewalling via AWF
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/custom_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func NewCustomEngine() *CustomEngine {
supportsToolsAllowlist: false,
supportsHTTPTransport: false,
supportsMaxTurns: true, // Custom engine supports max-turns for consistency
supportsMaxTokens: true, // Custom engine supports max-tokens for consistency
supportsMaxIterations: true, // Custom engine supports max-iterations for consistency
supportsWebFetch: false, // Custom engine does not have built-in web-fetch support
supportsWebSearch: false, // Custom engine does not have built-in web-search support
},
Expand Down
50 changes: 37 additions & 13 deletions pkg/workflow/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ var engineLog = logger.New("workflow:engine")

// EngineConfig represents the parsed engine configuration
type EngineConfig struct {
ID string
Version string
Model string
MaxTurns string
Concurrency string // Agent job-level concurrency configuration (YAML format)
UserAgent string
Command string // Custom executable path (when set, skip installation steps)
Env map[string]string
Steps []map[string]any
Config string
Args []string
Firewall *FirewallConfig // AWF firewall configuration
Agent string // Agent identifier for copilot --agent flag (copilot engine only)
ID string
Version string
Model string
MaxTurns string
MaxTokens string // Maximum number of tokens (input + output) per request
MaxIterations string // Maximum number of iterations (alias for MaxTurns for some engines)
Concurrency string // Agent job-level concurrency configuration (YAML format)
UserAgent string
Command string // Custom executable path (when set, skip installation steps)
Env map[string]string
Steps []map[string]any
Config string
Args []string
Firewall *FirewallConfig // AWF firewall configuration
Agent string // Agent identifier for copilot --agent flag (copilot engine only)
}

// NetworkPermissions represents network access permissions for workflow execution
Expand Down Expand Up @@ -116,6 +118,28 @@ func (c *Compiler) ExtractEngineConfig(frontmatter map[string]any) (string, *Eng
}
}

// Extract optional 'max-tokens' field
if maxTokens, hasMaxTokens := engineObj["max-tokens"]; hasMaxTokens {
if maxTokensInt, ok := maxTokens.(int); ok {
config.MaxTokens = fmt.Sprintf("%d", maxTokensInt)
} else if maxTokensUint64, ok := maxTokens.(uint64); ok {
config.MaxTokens = fmt.Sprintf("%d", maxTokensUint64)
} else if maxTokensStr, ok := maxTokens.(string); ok {
config.MaxTokens = maxTokensStr
}
}

// Extract optional 'max-iterations' field
if maxIterations, hasMaxIterations := engineObj["max-iterations"]; hasMaxIterations {
if maxIterationsInt, ok := maxIterations.(int); ok {
config.MaxIterations = fmt.Sprintf("%d", maxIterationsInt)
} else if maxIterationsUint64, ok := maxIterations.(uint64); ok {
config.MaxIterations = fmt.Sprintf("%d", maxIterationsUint64)
} else if maxIterationsStr, ok := maxIterations.(string); ok {
config.MaxIterations = maxIterationsStr
}
}

// Extract optional 'concurrency' field (string or object format)
if concurrency, hasConcurrency := engineObj["concurrency"]; hasConcurrency {
if concurrencyStr, ok := concurrency.(string); ok {
Expand Down
Loading
Loading