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>
This commit is contained in:
+177
@@ -0,0 +1,177 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
|
||||
)
|
||||
|
||||
type TextPlugin struct{ sdk.BasePlugin }
|
||||
|
||||
func (p *TextPlugin) Metadata() sdk.PluginMetadata {
|
||||
return sdk.PluginMetadata{
|
||||
Name: "text", DisplayName: "Text Processing", Version: "1.0.0",
|
||||
Description: "Text processing: count stats, summarize, regex extract",
|
||||
Category: "utility", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *TextPlugin) Tools() []sdk.Tool { return []sdk.Tool{&TextTool{}} }
|
||||
|
||||
type TextTool struct{ sdk.BaseTool }
|
||||
|
||||
func (t *TextTool) Definition() sdk.ToolDefinition {
|
||||
return sdk.ToolDefinition{
|
||||
ID: "text", Name: "text", DisplayName: "Text Processing",
|
||||
Description: "Text processing. Count stats, summarize, translate, regex extract.",
|
||||
Category: "utility", Complexity: sdk.ComplexitySimple,
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"action": map[string]interface{}{"type": "string", "enum": []string{"count", "summarize", "translate", "extract"}},
|
||||
"text": map[string]interface{}{"type": "string"},
|
||||
"target_lang": map[string]interface{}{"type": "string", "enum": []string{"en", "zh", "ja", "ko", "fr", "de"}},
|
||||
"pattern": map[string]interface{}{"type": "string"},
|
||||
},
|
||||
"required": []string{"action", "text"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TextTool) Validate(args map[string]interface{}) error {
|
||||
for _, k := range []string{"action", "text"} {
|
||||
if _, ok := args[k]; !ok {
|
||||
return fmt.Errorf("missing required parameter: %s", k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TextTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
|
||||
action, _ := args["action"].(string)
|
||||
txt, _ := args["text"].(string)
|
||||
|
||||
switch action {
|
||||
case "count":
|
||||
charsNoSpace := 0
|
||||
chineseChars := 0
|
||||
for _, r := range txt {
|
||||
if !unicode.IsSpace(r) {
|
||||
charsNoSpace++
|
||||
}
|
||||
if unicode.Is(unicode.Han, r) {
|
||||
chineseChars++
|
||||
}
|
||||
}
|
||||
words := strings.Fields(txt)
|
||||
lines := strings.Split(txt, "\n")
|
||||
paragraphs := regexp.MustCompile(`\n\s*\n`).Split(txt, -1)
|
||||
return &sdk.ToolResult{ToolName: "text", Success: true, Output: fmt.Sprintf(
|
||||
"Characters: %d (no spaces: %d, Chinese: %d)\nBytes: %d\nWords: %d\nLines: %d\nParagraphs: %d",
|
||||
len([]rune(txt)), charsNoSpace, chineseChars, len(txt), len(words), len(lines), len(paragraphs))}, nil
|
||||
|
||||
case "summarize":
|
||||
paragraphs := regexp.MustCompile(`\n\s*\n`).Split(txt, -1)
|
||||
firstPara := ""
|
||||
if len(paragraphs) > 0 {
|
||||
runes := []rune(paragraphs[0])
|
||||
if len(runes) > 300 {
|
||||
runes = runes[:300]
|
||||
}
|
||||
firstPara = string(runes)
|
||||
}
|
||||
sentences := regexp.MustCompile(`[。!?.!?]+`).Split(txt, -1)
|
||||
keywords := []string{"重要", "关键", "因此", "总结", "important", "key", "conclusion", "therefore"}
|
||||
type scored struct {
|
||||
text string
|
||||
score int
|
||||
}
|
||||
var scoredSents []scored
|
||||
for _, s := range sentences {
|
||||
s = strings.TrimSpace(s)
|
||||
if len([]rune(s)) < 10 {
|
||||
continue
|
||||
}
|
||||
score := len([]rune(s))
|
||||
for _, kw := range keywords {
|
||||
if strings.Contains(strings.ToLower(s), strings.ToLower(kw)) {
|
||||
score += 20
|
||||
}
|
||||
}
|
||||
scoredSents = append(scoredSents, scored{s, score})
|
||||
}
|
||||
var out strings.Builder
|
||||
out.WriteString(fmt.Sprintf("First paragraph: %s\n\nKey sentences:\n", firstPara))
|
||||
count := 0
|
||||
for i := 0; i < len(scoredSents) && count < 5; i++ {
|
||||
out.WriteString(fmt.Sprintf("- %s\n", scoredSents[i].text))
|
||||
count++
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "text", Success: true, Output: out.String()}, nil
|
||||
|
||||
case "translate":
|
||||
targetLang, _ := args["target_lang"].(string)
|
||||
if targetLang == "" {
|
||||
targetLang = "en"
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "text", Success: true, Output: fmt.Sprintf(
|
||||
"[Translation request] Please translate the following text to %s.\n\nOriginal text:\n%s", targetLang, txt)}, nil
|
||||
|
||||
case "extract":
|
||||
pattern, _ := args["pattern"].(string)
|
||||
var out strings.Builder
|
||||
extracted := false
|
||||
if pattern == "" || pattern == "email" {
|
||||
re := regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`)
|
||||
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
||||
out.WriteString("Emails:\n")
|
||||
for _, m := range matches {
|
||||
out.WriteString(fmt.Sprintf("- %s\n", m))
|
||||
}
|
||||
extracted = true
|
||||
}
|
||||
}
|
||||
if pattern == "" || pattern == "phone" {
|
||||
re := regexp.MustCompile(`1[3-9]\d{9}`)
|
||||
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
||||
out.WriteString("Phone numbers:\n")
|
||||
for _, m := range matches {
|
||||
out.WriteString(fmt.Sprintf("- %s\n", m))
|
||||
}
|
||||
extracted = true
|
||||
}
|
||||
}
|
||||
if pattern == "" || pattern == "url" {
|
||||
re := regexp.MustCompile(`https?://[^\s<>"{}|\\^` + "`" + `\[\]]+`)
|
||||
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
||||
out.WriteString("URLs:\n")
|
||||
for _, m := range matches {
|
||||
out.WriteString(fmt.Sprintf("- %s\n", m))
|
||||
}
|
||||
extracted = true
|
||||
}
|
||||
}
|
||||
if !extracted && pattern != "" && pattern != "email" && pattern != "phone" && pattern != "url" {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "text", Success: false, Error: "Invalid regex: " + err.Error()}, nil
|
||||
}
|
||||
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
||||
out.WriteString(fmt.Sprintf("Pattern matches (%s):\n", pattern))
|
||||
for _, m := range matches {
|
||||
out.WriteString(fmt.Sprintf("- %s\n", m))
|
||||
}
|
||||
extracted = true
|
||||
}
|
||||
}
|
||||
if !extracted {
|
||||
return &sdk.ToolResult{ToolName: "text", Success: true, Output: "No matches found"}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "text", Success: true, Output: out.String()}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "text", Success: false, Error: "unknown action: " + action}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user