Skip to content
Merged
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
112 changes: 77 additions & 35 deletions pkg/server/ai_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,85 @@ limitations under the License.

package server

// AIRequest represents a standard request to an AI plugin
// Following the simplicity principle: model name + prompt + optional config
type AIRequest struct {
Model string `json:"model"` // Model identifier (e.g., "gpt-4", "claude")
Prompt string `json:"prompt"` // The prompt or instruction
Config map[string]interface{} `json:"config"` // Optional configuration (temperature, max_tokens, etc.)
}

// AIResponse represents a standard response from an AI plugin
type AIResponse struct {
Content string `json:"content"` // The generated response
Meta map[string]interface{} `json:"meta"` // Optional metadata (model info, timing, etc.)
}

// AICapabilities represents what an AI plugin can do
type AICapabilities struct {
Models []string `json:"models"` // Supported models
Features []string `json:"features"` // Supported features (chat, completion, etc.)
Limits map[string]int `json:"limits"` // Limits (max_tokens, rate_limit, etc.)
Description string `json:"description"` // Plugin description
Version string `json:"version"` // Plugin version
}
// AI Plugin Communication Interface Standards
// AI plugins use the existing testing.Loader.Query(map[string]string) interface

// Standard AI plugin communication methods
const (
AIMethodGenerate = "ai.generate" // Generate content from prompt
AIMethodCapabilities = "ai.capabilities" // Get plugin capabilities
AIMethodGenerate = "ai.generate" // Generate content from prompt
AIMethodCapabilities = "ai.capabilities" // Get plugin capabilities
)

// Standard plugin communication message format
type PluginRequest struct {
Method string `json:"method"` // Method name
Payload interface{} `json:"payload"` // Request payload
}

type PluginResponse struct {
Success bool `json:"success"` // Whether request succeeded
Data interface{} `json:"data"` // Response data
Error string `json:"error"` // Error message if failed
}
// AI Plugin Query Parameter Standards
// AI plugins are called using loader.Query(query map[string]string) with these parameters:

// For ai.generate:
// - "method": "ai.generate"
// - "model": model identifier (e.g., "gpt-4", "claude")
// - "prompt": the prompt or instruction
// - "config": optional JSON configuration string (e.g., `{"temperature": 0.7, "max_tokens": 1000}`)

// For ai.capabilities:
// - "method": "ai.capabilities"

// AI Plugin Response Standards
// AI plugins return response through testing.DataResult.Pairs with these keys:

// For successful ai.generate:
// - "content": the generated content
// - "meta": optional JSON metadata string (model info, timing, etc.)
// - "success": "true"

// For successful ai.capabilities:
// - "capabilities": JSON string containing plugin capabilities
// - "models": JSON array of supported models (fallback if capabilities not available)
// - "features": JSON array of supported features (fallback)
// - "description": plugin description (fallback)
// - "version": plugin version (fallback)
// - "success": "true"

// For errors:
// - "error": error message
// - "success": "false"

// Plugin Discovery
// AI plugins are identified by having "ai" in their categories field:
// categories: ["ai"]

// Usage Examples:
//
// Get AI plugins:
// stores, err := server.GetStores(ctx, &SimpleQuery{Kind: "ai"})
//
// Call AI plugin:
// loader, err := server.getLoaderByStoreName("my-ai-plugin")
// result, err := loader.Query(map[string]string{
// "method": "ai.generate",
// "model": "gpt-4",
// "prompt": "Hello world",
// "config": `{"temperature": 0.7}`,
// })
// content := result.Pairs["content"]

// Documentation structures (for reference only, actual types are generated from proto)
// See server.proto for the actual message definitions:
//
// AIRequest fields:
// - plugin_name: AI plugin name
// - model: Model identifier (e.g., "gpt-4", "claude")
// - prompt: The prompt or instruction
// - config: JSON configuration string (optional)
//
// AIResponse fields:
// - content: Generated content
// - meta: JSON metadata string (optional)
// - success: Whether the call succeeded
// - error: Error message if failed
//
// AICapabilitiesResponse fields:
// - models: Supported models
// - features: Supported features
// - description: Plugin description
// - version: Plugin version
// - success: Whether the call succeeded
// - error: Error message if failed
193 changes: 138 additions & 55 deletions pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1766,63 +1766,146 @@ func (s *UniqueSlice[T]) GetAll() []T {

var errNoTestSuiteFound = errors.New("no test suite found")

// AI Plugin Communication Methods
// These methods provide communication interface for AI plugins using the existing ExtManager
// CallAI calls an AI plugin to generate content
func (s *server) CallAI(ctx context.Context, req *AIRequest) (*AIResponse, error) {
if req.GetPluginName() == "" {
return &AIResponse{
Success: false,
Error: "plugin_name is required",
}, nil
}

// GetAIPlugins returns all plugins with "ai" category
func (s *server) GetAIPlugins(ctx context.Context, req *Empty) (*StoreKinds, error) {
stores, err := s.GetStoreKinds(ctx, req)
// Get the loader for the AI plugin
loader, err := s.getLoaderByStoreName(req.GetPluginName())
if err != nil {
return nil, err
}

var aiPlugins []*StoreKind
for _, store := range stores.Data {
for _, category := range store.Categories {
if category == "ai" {
aiPlugins = append(aiPlugins, store)
break
}
return &AIResponse{
Success: false,
Error: fmt.Sprintf("failed to get AI plugin '%s': %v", req.GetPluginName(), err),
}, nil
}
defer loader.Close()

// Prepare query parameters
query := map[string]string{
"method": AIMethodGenerate,
"model": req.GetModel(),
"prompt": req.GetPrompt(),
}

// Add config (always include, even if empty)
query["config"] = req.GetConfig()

// Call the plugin using the Query interface
result, err := loader.Query(query)
if err != nil {
return &AIResponse{
Success: false,
Error: fmt.Sprintf("AI plugin call failed: %v", err),
}, nil
}

// Extract response from result
response := &AIResponse{
Success: true,
}

// Get content from result
if content, ok := result.Pairs["content"]; ok {
response.Content = content
}

// Get metadata if available
if meta, ok := result.Pairs["meta"]; ok {
response.Meta = meta
}

// Check for errors from plugin
if errorMsg, ok := result.Pairs["error"]; ok && errorMsg != "" {
response.Success = false
response.Error = errorMsg
}

// Check success flag from plugin
if success, ok := result.Pairs["success"]; ok && success == "false" {
response.Success = false
if response.Error == "" {
response.Error = "AI plugin returned failure status"
}
}

return &StoreKinds{Data: aiPlugins}, nil
}

// SendAIRequest sends a request to an AI plugin using the standard plugin communication protocol
func (s *server) SendAIRequest(ctx context.Context, pluginName string, req *AIRequest) (*AIResponse, error) {
// This would communicate with the AI plugin via unix socket using PluginRequest/PluginResponse
// Implementation would be similar to other plugin communications

// TODO: Send pluginReq to plugin via storeExtMgr communication channel
// pluginReq := &PluginRequest{
// Method: AIMethodGenerate,
// Payload: req,
// }

remoteServerLogger.Info("Sending AI request", "plugin", pluginName, "model", req.Model)

return &AIResponse{
Content: "AI response placeholder - implementation needed",
Meta: map[string]interface{}{"plugin": pluginName, "model": req.Model},
}, nil
}

// GetAICapabilities gets capabilities from an AI plugin
func (s *server) GetAICapabilities(ctx context.Context, pluginName string) (*AICapabilities, error) {
// TODO: Send pluginReq to plugin via storeExtMgr communication channel
// pluginReq := &PluginRequest{
// Method: AIMethodCapabilities,
// Payload: &Empty{},
// }

remoteServerLogger.Info("Getting AI capabilities", "plugin", pluginName)

return &AICapabilities{
Models: []string{"placeholder-model"},
Features: []string{"generate", "capabilities"},
Limits: map[string]int{"max_tokens": 4096},
Description: "AI plugin capabilities placeholder",
Version: "1.0.0",
}, nil

remoteServerLogger.Info("AI plugin called",
"plugin", req.GetPluginName(),
"model", req.GetModel(),
"success", response.GetSuccess())

return response, nil
}

// GetAICapabilities gets the capabilities of an AI plugin
func (s *server) GetAICapabilities(ctx context.Context, req *AICapabilitiesRequest) (*AICapabilitiesResponse, error) {
if req.GetPluginName() == "" {
return &AICapabilitiesResponse{
Success: false,
Error: "plugin_name is required",
}, nil
}

// Get the loader for the AI plugin
loader, err := s.getLoaderByStoreName(req.GetPluginName())
if err != nil {
return &AICapabilitiesResponse{
Success: false,
Error: fmt.Sprintf("failed to get AI plugin '%s': %v", req.GetPluginName(), err),
}, nil
}
defer loader.Close()

// Query for capabilities
query := map[string]string{
"method": AIMethodCapabilities,
}

result, err := loader.Query(query)
if err != nil {
return &AICapabilitiesResponse{
Success: false,
Error: fmt.Sprintf("failed to get capabilities: %v", err),
}, nil
}

// Build response from result
response := &AICapabilitiesResponse{
Success: true,
}

// Parse capabilities from result
if models, ok := result.Pairs["models"]; ok {
// Try to parse as JSON array first
response.Models = strings.Split(models, ",")
}

if features, ok := result.Pairs["features"]; ok {
response.Features = strings.Split(features, ",")
}

if description, ok := result.Pairs["description"]; ok {
response.Description = description
}

if version, ok := result.Pairs["version"]; ok {
response.Version = version
}

// Check for errors
if errorMsg, ok := result.Pairs["error"]; ok && errorMsg != "" {
response.Success = false
response.Error = errorMsg
}

remoteServerLogger.Info("AI plugin capabilities retrieved",
"plugin", req.GetPluginName(),
"models", len(response.GetModels()),
"features", len(response.GetFeatures()))

return response, nil
}
Loading
Loading