diff --git a/pkg/workflow/safe_outputs_config_generation.go b/pkg/workflow/safe_outputs_config_generation.go new file mode 100644 index 00000000000..309ce97ccc2 --- /dev/null +++ b/pkg/workflow/safe_outputs_config_generation.go @@ -0,0 +1,96 @@ +package workflow + +import ( + "fmt" + "sort" +) + +// generateCustomJobToolDefinition creates an MCP tool definition for a custom safe-output job +// Returns a map representing the tool definition in MCP format with name, description, and inputSchema +func generateCustomJobToolDefinition(jobName string, jobConfig *SafeJobConfig) map[string]any { + safeOutputsConfigLog.Printf("Generating tool definition for custom job: %s", jobName) + + // Build the tool definition + tool := map[string]any{ + "name": jobName, + } + + // Add description if present + if jobConfig.Description != "" { + tool["description"] = jobConfig.Description + } else { + // Provide a default description if none is specified + tool["description"] = fmt.Sprintf("Execute the %s custom job", jobName) + } + + // Build the input schema + inputSchema := map[string]any{ + "type": "object", + "properties": make(map[string]any), + } + + // Track required fields + var requiredFields []string + + // Add each input to the schema + if len(jobConfig.Inputs) > 0 { + properties := inputSchema["properties"].(map[string]any) + + for inputName, inputDef := range jobConfig.Inputs { + property := map[string]any{} + + // Add description + if inputDef.Description != "" { + property["description"] = inputDef.Description + } + + // Convert type to JSON Schema type + switch inputDef.Type { + case "choice": + // Choice inputs are strings with enum constraints + property["type"] = "string" + if len(inputDef.Options) > 0 { + property["enum"] = inputDef.Options + } + case "boolean": + property["type"] = "boolean" + case "number": + property["type"] = "number" + case "string", "": + // Default to string if type is not specified + property["type"] = "string" + default: + // For any unknown type, default to string + property["type"] = "string" + } + + // Add default value if present + if inputDef.Default != nil { + property["default"] = inputDef.Default + } + + // Track required fields + if inputDef.Required { + requiredFields = append(requiredFields, inputName) + } + + properties[inputName] = property + } + } + + // Add required fields array if any inputs are required + if len(requiredFields) > 0 { + sort.Strings(requiredFields) + inputSchema["required"] = requiredFields + } + + // Prevent additional properties to maintain schema strictness + inputSchema["additionalProperties"] = false + + tool["inputSchema"] = inputSchema + + safeOutputsConfigLog.Printf("Generated tool definition for %s with %d inputs, %d required", + jobName, len(jobConfig.Inputs), len(requiredFields)) + + return tool +} diff --git a/pkg/workflow/safe_outputs_generation.go b/pkg/workflow/safe_outputs_generation.go index 5cf7bfaac8a..fd326d2700f 100644 --- a/pkg/workflow/safe_outputs_generation.go +++ b/pkg/workflow/safe_outputs_generation.go @@ -14,100 +14,6 @@ import ( "github.com/github/gh-aw/pkg/stringutil" ) -// ======================================== -// Safe Output Configuration Generation -// ======================================== - -// generateCustomJobToolDefinition creates an MCP tool definition for a custom safe-output job -// Returns a map representing the tool definition in MCP format with name, description, and inputSchema -func generateCustomJobToolDefinition(jobName string, jobConfig *SafeJobConfig) map[string]any { - safeOutputsConfigLog.Printf("Generating tool definition for custom job: %s", jobName) - - // Build the tool definition - tool := map[string]any{ - "name": jobName, - } - - // Add description if present - if jobConfig.Description != "" { - tool["description"] = jobConfig.Description - } else { - // Provide a default description if none is specified - tool["description"] = fmt.Sprintf("Execute the %s custom job", jobName) - } - - // Build the input schema - inputSchema := map[string]any{ - "type": "object", - "properties": make(map[string]any), - } - - // Track required fields - var requiredFields []string - - // Add each input to the schema - if len(jobConfig.Inputs) > 0 { - properties := inputSchema["properties"].(map[string]any) - - for inputName, inputDef := range jobConfig.Inputs { - property := map[string]any{} - - // Add description - if inputDef.Description != "" { - property["description"] = inputDef.Description - } - - // Convert type to JSON Schema type - switch inputDef.Type { - case "choice": - // Choice inputs are strings with enum constraints - property["type"] = "string" - if len(inputDef.Options) > 0 { - property["enum"] = inputDef.Options - } - case "boolean": - property["type"] = "boolean" - case "number": - property["type"] = "number" - case "string", "": - // Default to string if type is not specified - property["type"] = "string" - default: - // For any unknown type, default to string - property["type"] = "string" - } - - // Add default value if present - if inputDef.Default != nil { - property["default"] = inputDef.Default - } - - // Track required fields - if inputDef.Required { - requiredFields = append(requiredFields, inputName) - } - - properties[inputName] = property - } - } - - // Add required fields array if any inputs are required - if len(requiredFields) > 0 { - sort.Strings(requiredFields) - inputSchema["required"] = requiredFields - } - - // Prevent additional properties to maintain schema strictness - inputSchema["additionalProperties"] = false - - tool["inputSchema"] = inputSchema - - safeOutputsConfigLog.Printf("Generated tool definition for %s with %d inputs, %d required", - jobName, len(jobConfig.Inputs), len(requiredFields)) - - return tool -} - func populateDispatchWorkflowFiles(data *WorkflowData, markdownPath string) { if data.SafeOutputs == nil || data.SafeOutputs.DispatchWorkflow == nil { return