-
Notifications
You must be signed in to change notification settings - Fork 75
Add SupportsPlugins() capability detection and validation for agentic engines #14516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,10 @@ func TestCopilotEngine(t *testing.T) { | |
| t.Error("Expected copilot engine to not support max-turns yet") | ||
| } | ||
|
|
||
| if !engine.SupportsPlugins() { | ||
| t.Error("Expected copilot engine to support plugins") | ||
| } | ||
|
|
||
| // Test declared output files (session files are copied to logs folder) | ||
| outputFiles := engine.GetDeclaredOutputFiles() | ||
| if len(outputFiles) != 1 { | ||
|
|
@@ -84,6 +88,21 @@ func TestOtherEnginesNoDefaultDetectionModel(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| func TestOtherEnginesNoPluginSupport(t *testing.T) { | ||
| // Test that only Copilot engine supports plugins | ||
| engines := []CodingAgentEngine{ | ||
| NewClaudeEngine(), | ||
| NewCodexEngine(), | ||
| NewCustomEngine(), | ||
| } | ||
|
|
||
| for _, engine := range engines { | ||
| if engine.SupportsPlugins() { | ||
| t.Errorf("Expected engine '%s' to not support plugins, but it does", engine.GetID()) | ||
| } | ||
| } | ||
|
Comment on lines
+91
to
+103
|
||
| } | ||
|
|
||
| func TestCopilotEngineInstallationSteps(t *testing.T) { | ||
| engine := NewCopilotEngine() | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -146,3 +146,27 @@ func (c *Compiler) validateSingleEngineSpecification(mainEngineSetting string, i | |||||
|
|
||||||
| return "", fmt.Errorf("invalid engine configuration in included file, missing or invalid 'id' field. Expected string or object with 'id' field.\n\nExample (string):\nengine: copilot\n\nExample (object):\nengine:\n id: copilot\n model: gpt-4\n\nSee: %s", constants.DocsEnginesURL) | ||||||
| } | ||||||
|
|
||||||
| // validatePluginSupport validates that plugins are only used with engines that support them | ||||||
| func (c *Compiler) validatePluginSupport(pluginInfo *PluginInfo, agenticEngine CodingAgentEngine) error { | ||||||
| // No plugins specified, validation passes | ||||||
| if pluginInfo == nil || len(pluginInfo.Plugins) == 0 { | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| engineValidationLog.Printf("Validating plugin support for engine: %s", agenticEngine.GetID()) | ||||||
|
|
||||||
| // Check if the engine supports plugins | ||||||
| if !agenticEngine.SupportsPlugins() { | ||||||
| // Build error message listing the plugins that were specified | ||||||
| pluginsList := strings.Join(pluginInfo.Plugins, ", ") | ||||||
|
|
||||||
| return fmt.Errorf("engine '%s' does not support plugins. The following plugins cannot be installed: %s\n\nOnly the 'copilot' engine currently supports plugin installation.\n\nTo fix this, either:\n1. Remove the 'plugins' field from your workflow\n2. Change to an engine that supports plugins (e.g., engine: copilot)\n\nSee: %s", | ||||||
|
||||||
| return fmt.Errorf("engine '%s' does not support plugins. The following plugins cannot be installed: %s\n\nOnly the 'copilot' engine currently supports plugin installation.\n\nTo fix this, either:\n1. Remove the 'plugins' field from your workflow\n2. Change to an engine that supports plugins (e.g., engine: copilot)\n\nSee: %s", | |
| return fmt.Errorf("engine '%s' does not support plugins. The following plugins cannot be installed: %s\n\nTo fix this, either:\n1. Remove the 'plugins' field from your workflow\n2. Change to an engine that supports plugins (see the documentation for supported engines)\n\nSee: %s", |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -394,3 +394,124 @@ func TestValidateEngineDidYouMean(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
|
||
| // TestValidatePluginSupport tests the validatePluginSupport function | ||
| func TestValidatePluginSupport(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| pluginInfo *PluginInfo | ||
| engineID string | ||
| expectError bool | ||
| errorMsg string | ||
| }{ | ||
| { | ||
| name: "no plugins with copilot engine", | ||
| pluginInfo: nil, | ||
| engineID: "copilot", | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "plugins with copilot engine (supported)", | ||
| pluginInfo: &PluginInfo{ | ||
| Plugins: []string{"org/plugin1", "org/plugin2"}, | ||
| }, | ||
| engineID: "copilot", | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "plugins with claude engine (not supported)", | ||
| pluginInfo: &PluginInfo{ | ||
| Plugins: []string{"org/plugin1"}, | ||
| }, | ||
| engineID: "claude", | ||
| expectError: true, | ||
| errorMsg: "does not support plugins", | ||
| }, | ||
| { | ||
| name: "plugins with codex engine (not supported)", | ||
| pluginInfo: &PluginInfo{ | ||
| Plugins: []string{"org/plugin1", "org/plugin2"}, | ||
| }, | ||
| engineID: "codex", | ||
| expectError: true, | ||
| errorMsg: "does not support plugins", | ||
| }, | ||
| { | ||
| name: "plugins with custom engine (not supported)", | ||
| pluginInfo: &PluginInfo{ | ||
| Plugins: []string{"org/plugin1"}, | ||
| }, | ||
| engineID: "custom", | ||
| expectError: true, | ||
| errorMsg: "does not support plugins", | ||
| }, | ||
|
Comment on lines
+421
to
+447
|
||
| { | ||
| name: "empty plugin list", | ||
| pluginInfo: &PluginInfo{Plugins: []string{}}, | ||
| engineID: "claude", | ||
| expectError: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| compiler := NewCompiler() | ||
| engine, err := compiler.engineRegistry.GetEngine(tt.engineID) | ||
| if err != nil { | ||
| t.Fatalf("Failed to get engine: %v", err) | ||
| } | ||
|
|
||
| err = compiler.validatePluginSupport(tt.pluginInfo, engine) | ||
|
|
||
| if tt.expectError && err == nil { | ||
| t.Error("Expected validation to fail but it succeeded") | ||
| } else if !tt.expectError && err != nil { | ||
| t.Errorf("Expected validation to succeed but it failed: %v", err) | ||
| } else if tt.expectError && err != nil && tt.errorMsg != "" { | ||
| if !strings.Contains(err.Error(), tt.errorMsg) { | ||
| t.Errorf("Expected error containing '%s', got '%s'", tt.errorMsg, err.Error()) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // TestValidatePluginSupportErrorMessage verifies the plugin validation error message quality | ||
| func TestValidatePluginSupportErrorMessage(t *testing.T) { | ||
| compiler := NewCompiler() | ||
| claudeEngine, err := compiler.engineRegistry.GetEngine("claude") | ||
| if err != nil { | ||
| t.Fatalf("Failed to get claude engine: %v", err) | ||
| } | ||
|
|
||
| pluginInfo := &PluginInfo{ | ||
| Plugins: []string{"org/plugin1", "org/plugin2"}, | ||
| } | ||
|
|
||
| err = compiler.validatePluginSupport(pluginInfo, claudeEngine) | ||
| if err == nil { | ||
| t.Fatal("Expected validation to fail for plugins with claude engine") | ||
| } | ||
|
|
||
| errorMsg := err.Error() | ||
|
|
||
| // Error should mention the engine name | ||
| if !strings.Contains(errorMsg, "claude") { | ||
| t.Errorf("Error message should mention the engine name, got: %s", errorMsg) | ||
| } | ||
|
|
||
| // Error should list the plugins that can't be installed | ||
| if !strings.Contains(errorMsg, "org/plugin1") || !strings.Contains(errorMsg, "org/plugin2") { | ||
| t.Errorf("Error message should list the plugins, got: %s", errorMsg) | ||
| } | ||
|
|
||
| // Error should mention copilot as the supported engine | ||
| if !strings.Contains(errorMsg, "copilot") { | ||
| t.Errorf("Error message should mention copilot as supported engine, got: %s", errorMsg) | ||
| } | ||
|
|
||
| // Error should provide actionable fixes | ||
| if !strings.Contains(errorMsg, "To fix this") { | ||
| t.Errorf("Error message should provide actionable fixes, got: %s", errorMsg) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interface hierarchy comment block above lists CapabilityProvider methods but doesn't mention SupportsPlugins, even though it is now part of the interface. Please update that documentation list to keep the file-level architecture docs accurate.