feat: Phase 3 插件与工具系统 — Plugin SDK + Plugin Manager + 13内置插件 (40文件, 3293行)
- Plugin SDK: Plugin/Tool/ComplexTool/HostAPI 标准化接口 - Plugin Manager: 插件生命周期管理 (Install/Enable/Disable/Uninstall/Reload) - Tool Registry: 聚合工具注册表 (Register/Execute/Dispatch) - 13 个内置插件: 将原有硬编码工具迁移为标准插件格式 - REST API: 11 个端点 (net/http, 零外部依赖) - ai-core 集成: PluginManagerClient 替代本地工具调用 - plugin.json 元数据: 每个插件含完整 author/version/category/permissions Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yourname/cyrene-ai/plugin-manager/internal/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")
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "http",
|
||||
"displayName": "HTTP Client",
|
||||
"version": "1.0.0",
|
||||
"minCyreneVersion": "1.0.0",
|
||||
"author": { "name": "Cyrene Team" },
|
||||
"description": "Send arbitrary HTTP requests with custom methods, headers, body",
|
||||
"license": "MIT",
|
||||
"keywords": ["http", "request", "fetch"],
|
||||
"category": "network",
|
||||
"permissions": ["network:outbound"]
|
||||
}
|
||||
Reference in New Issue
Block a user