717ad65b05
- 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>
114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
package webfetch
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/yourname/cyrene-ai/plugin-manager/internal/sdk"
|
|
)
|
|
|
|
type WebFetchPlugin struct {
|
|
sdk.BasePlugin
|
|
client *http.Client
|
|
}
|
|
|
|
func NewWebFetchPlugin() *WebFetchPlugin {
|
|
return &WebFetchPlugin{client: &http.Client{Timeout: 15 * time.Second}}
|
|
}
|
|
|
|
func (p *WebFetchPlugin) Metadata() sdk.PluginMetadata {
|
|
return sdk.PluginMetadata{
|
|
Name: "web_fetch", DisplayName: "Web Fetch", Version: "1.0.0",
|
|
Description: "Fetch and extract text content from URLs",
|
|
Category: "network", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
|
|
}
|
|
}
|
|
|
|
func (p *WebFetchPlugin) Tools() []sdk.Tool { return []sdk.Tool{&WebFetchTool{client: p.client}} }
|
|
|
|
type WebFetchTool struct {
|
|
sdk.BaseTool
|
|
client *http.Client
|
|
}
|
|
|
|
func (t *WebFetchTool) Definition() sdk.ToolDefinition {
|
|
return sdk.ToolDefinition{
|
|
ID: "web_fetch", Name: "web_fetch", DisplayName: "Web Fetch",
|
|
Description: "Fetch content of a specified URL. Returns plain text summary (first 2000 characters). HTTP/HTTPS only.",
|
|
Category: "network", Complexity: sdk.ComplexitySimple,
|
|
Parameters: map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{"url": map[string]interface{}{"type": "string"}},
|
|
"required": []string{"url"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (t *WebFetchTool) Validate(args map[string]interface{}) error {
|
|
if _, ok := args["url"]; !ok {
|
|
return fmt.Errorf("missing required parameter: url")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *WebFetchTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
|
|
urlStr, _ := args["url"].(string)
|
|
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
|
|
return &sdk.ToolResult{ToolName: "web_fetch", Success: false, Error: "only http/https URLs allowed"}, nil
|
|
}
|
|
|
|
req, _ := http.NewRequest("GET", urlStr, nil)
|
|
req.Header.Set("User-Agent", "CyreneBot/1.0")
|
|
resp, err := t.client.Do(req)
|
|
if err != nil {
|
|
return &sdk.ToolResult{ToolName: "web_fetch", Success: false, Error: err.Error()}, nil
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 100*1024))
|
|
text := stripHTMLFull(string(bodyBytes))
|
|
text = removeBlankLines(text)
|
|
runes := []rune(text)
|
|
if len(runes) > 2000 {
|
|
text = string(runes[:2000]) + "..."
|
|
}
|
|
return &sdk.ToolResult{ToolName: "web_fetch", Success: true, Output: fmt.Sprintf(
|
|
"URL: %s\nStatus: %d\nContent-Type: %s\n\n%s",
|
|
urlStr, resp.StatusCode, resp.Header.Get("Content-Type"), text)}, nil
|
|
}
|
|
|
|
func stripHTMLFull(s string) string {
|
|
result := make([]rune, 0, len([]rune(s)))
|
|
inTag := false
|
|
for _, r := range s {
|
|
if r == '<' {
|
|
inTag = true
|
|
continue
|
|
}
|
|
if r == '>' {
|
|
inTag = false
|
|
continue
|
|
}
|
|
if !inTag {
|
|
result = append(result, r)
|
|
}
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
func removeBlankLines(s string) string {
|
|
lines := strings.Split(s, "\n")
|
|
var result []string
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed != "" {
|
|
result = append(result, trimmed)
|
|
}
|
|
}
|
|
return strings.Join(result, "\n")
|
|
}
|