package http import ( "context" "fmt" "io" "net/http" "strings" "time" "git.yeij.top/AskaEth/Cyrene-Plugins/sdk" ) type HTTPPlugin struct { sdk.BasePlugin client *http.Client } func NewHTTPPlugin() *HTTPPlugin { return &HTTPPlugin{client: &http.Client{Timeout: 10 * time.Second}} } func (p *HTTPPlugin) Metadata() sdk.PluginMetadata { return sdk.PluginMetadata{ Name: "http", DisplayName: "HTTP Client", Version: "1.0.0", Description: "Send arbitrary HTTP requests with custom methods, headers, body", Category: "network", Author: sdk.PluginAuthor{Name: "Cyrene Team"}, } } func (p *HTTPPlugin) Tools() []sdk.Tool { return []sdk.Tool{&HTTPTool{client: p.client}} } type HTTPTool struct { sdk.BaseTool client *http.Client } func (t *HTTPTool) Definition() sdk.ToolDefinition { return sdk.ToolDefinition{ ID: "http_request", Name: "http_request", DisplayName: "HTTP Client", Description: "Send arbitrary HTTP requests. Supports custom methods, headers, and body.", Category: "network", Complexity: sdk.ComplexitySimple, DangerLevel: "low", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "url": map[string]interface{}{"type": "string"}, "method": map[string]interface{}{"type": "string", "enum": []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}}, "headers": map[string]interface{}{"type": "object"}, "body": map[string]interface{}{"type": "string"}, "timeout": map[string]interface{}{"type": "number"}, }, "required": []string{"url"}, }, } } var allowedMethods = map[string]bool{ "GET": true, "POST": true, "PUT": true, "DELETE": true, "PATCH": true, "HEAD": true, "OPTIONS": true, } func (t *HTTPTool) Validate(args map[string]interface{}) error { if _, ok := args["url"]; !ok { return fmt.Errorf("missing required parameter: url") } return nil } func (t *HTTPTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) { urlStr, _ := args["url"].(string) method, _ := args["method"].(string) if method == "" { method = "GET" } if !allowedMethods[method] { return &sdk.ToolResult{ToolName: "http_request", Success: false, Error: "invalid method: " + method}, nil } if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") { return &sdk.ToolResult{ToolName: "http_request", Success: false, Error: "only http/https URLs allowed"}, nil } var bodyReader io.Reader if body, _ := args["body"].(string); body != "" { bodyReader = strings.NewReader(body) } req, err := http.NewRequest(method, urlStr, bodyReader) if err != nil { return &sdk.ToolResult{ToolName: "http_request", Success: false, Error: err.Error()}, nil } req.Header.Set("User-Agent", "CyreneBot/1.0") if headers, ok := args["headers"].(map[string]interface{}); ok { for k, v := range headers { req.Header.Set(k, fmt.Sprint(v)) } } client := t.client if timeout, _ := args["timeout"].(float64); timeout > 0 { client = &http.Client{Timeout: time.Duration(timeout) * time.Second} } resp, err := client.Do(req) if err != nil { return &sdk.ToolResult{ToolName: "http_request", Success: false, Error: err.Error()}, nil } defer resp.Body.Close() bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 50*1024)) return &sdk.ToolResult{ToolName: "http_request", Success: resp.StatusCode < 500, Output: fmt.Sprintf( "HTTP %d\n%s\n\n%s", resp.StatusCode, formatHeaders(resp.Header), string(bodyBytes))}, nil } func formatHeaders(h http.Header) string { var lines []string for k, v := range h { lines = append(lines, fmt.Sprintf("%s: %s", k, strings.Join(v, ", "))) } return strings.Join(lines, "\n") }