package models import ( "bytes" "encoding/json" "fmt" "net" "net/http" "time" "git.ourworld.tf/herocode/heroagent/pkg/openrpc" "git.ourworld.tf/herocode/heroagent/pkg/openrpc/models" ) // OpenRPCUIManager is the interface for managing OpenRPC specifications in the UI type OpenRPCUIManager interface { // ListSpecs returns a list of all loaded specification names ListSpecs() []string // GetSpec returns an OpenRPC specification by name GetSpec(name string) *models.OpenRPCSpec // ListMethods returns a list of all method names in a specification ListMethods(specName string) []string // GetMethod returns a method from a specification GetMethod(specName, methodName string) *models.Method // ExecuteRPC executes an RPC call ExecuteRPC(specName, methodName, socketPath string, params interface{}) (interface{}, error) } // OpenRPCManager implements the OpenRPCUIManager interface type OpenRPCManager struct { manager *openrpc.ORPCManager } // NewOpenRPCManager creates a new OpenRPCUIManager func NewOpenRPCManager() OpenRPCUIManager { manager := openrpc.NewORPCManager() // Try to load specs from the default directory specDirs := []string{ "./pkg/openrpc/services", "./pkg/openrpc/specs", "./specs/openrpc", } for _, dir := range specDirs { err := manager.LoadSpecs(dir) if err == nil { // Successfully loaded specs from this directory break } } return &OpenRPCManager{ manager: manager, } } // ListSpecs returns a list of all loaded specification names func (m *OpenRPCManager) ListSpecs() []string { return m.manager.ListSpecs() } // GetSpec returns an OpenRPC specification by name func (m *OpenRPCManager) GetSpec(name string) *models.OpenRPCSpec { return m.manager.GetSpec(name) } // ListMethods returns a list of all method names in a specification func (m *OpenRPCManager) ListMethods(specName string) []string { return m.manager.ListMethods(specName) } // GetMethod returns a method from a specification func (m *OpenRPCManager) GetMethod(specName, methodName string) *models.Method { return m.manager.GetMethod(specName, methodName) } // ExecuteRPC executes an RPC call func (m *OpenRPCManager) ExecuteRPC(specName, methodName, socketPath string, params interface{}) (interface{}, error) { // Create JSON-RPC request request := map[string]interface{}{ "jsonrpc": "2.0", "method": methodName, "params": params, "id": 1, } // Marshal request to JSON requestJSON, err := json.Marshal(request) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } // Check if socket path is a Unix socket or HTTP endpoint if socketPath[:1] == "/" { // Unix socket return executeUnixSocketRPC(socketPath, requestJSON) } else { // HTTP endpoint return executeHTTPRPC(socketPath, requestJSON) } } // executeUnixSocketRPC executes an RPC call over a Unix socket func executeUnixSocketRPC(socketPath string, requestJSON []byte) (interface{}, error) { // Connect to Unix socket conn, err := net.Dial("unix", socketPath) if err != nil { return nil, fmt.Errorf("failed to connect to socket %s: %w", socketPath, err) } defer conn.Close() // Set timeout deadline := time.Now().Add(10 * time.Second) if err := conn.SetDeadline(deadline); err != nil { return nil, fmt.Errorf("failed to set deadline: %w", err) } // Send request if _, err := conn.Write(requestJSON); err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } // Read response var buf bytes.Buffer if _, err := buf.ReadFrom(conn); err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } // Parse response var response map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } // Check for error if errObj, ok := response["error"]; ok { return nil, fmt.Errorf("RPC error: %v", errObj) } // Return result return response["result"], nil } // executeHTTPRPC executes an RPC call over HTTP func executeHTTPRPC(endpoint string, requestJSON []byte) (interface{}, error) { // Create HTTP request req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(requestJSON)) if err != nil { return nil, fmt.Errorf("failed to create HTTP request: %w", err) } // Set headers req.Header.Set("Content-Type", "application/json") // Create HTTP client with timeout client := &http.Client{ Timeout: 10 * time.Second, } // Send request resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to send HTTP request: %w", err) } defer resp.Body.Close() // Check status code if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP error: %s", resp.Status) } // Parse response var response map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } // Check for error if errObj, ok := response["error"]; ok { return nil, fmt.Errorf("RPC error: %v", errObj) } // Return result return response["result"], nil }