Files
AskaEth 5c807d76a0 Initial commit: Cyrene Plugins SDK + Plugin Manager
Extracted from Cyrene main repo (backend/pkg/plugins + backend/plugin-manager).
Contains SDK interfaces (Plugin/Tool/HostAPI), 13 built-in plugins,
ToolRegistry with call log ring buffer, and Plugin Manager REST API service.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 09:49:12 +08:00

114 lines
3.0 KiB
Go

package webfetch
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
"git.yeij.top/AskaEth/Cyrene-Plugins/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")
}