diff --git a/README.md b/README.md index 4542962..321ee06 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,20 @@ Triple 数据结构新增字段: - `SubjectType` — 主体类型 - `ObjectType` — 客体类型 +### ToolDef 字段说明 + +`RegisterTool` 的 `def` 参数类型为 `sdk.ToolDef`,包含以下字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `Name` | `string` | 工具名,建议插件名前缀避免冲突 | +| `Description` | `string` | 工具描述,LLM 据此选择调用 | +| `Parameters` | `map[string]interface{}` | JSON Schema 格式参数定义 | +| `NoMemory` | `bool` | 默认为 `false`;设为 `true` 时输出不参与向量/jieba/蒸馏计算(原文保留) | +| `Cleaner` | `func(string) string` | 可选,输出进入计算层前的清洗函数(如 JSON 提取 `.content`) | + +`NoMemory` 和 `Cleaner` 的详细设计意图参见核心仓 `docs/zh/PLUGIN_DEV.md`。 + ### New 构造函数 `New()` 由内核在加载插件时调用,插件开发者无需手动构造 PluginSDK: diff --git a/README_EN.md b/README_EN.md index cfcb172..b98c985 100644 --- a/README_EN.md +++ b/README_EN.md @@ -95,6 +95,20 @@ The Triple data structure includes additional fields: - `SubjectType` — subject type - `ObjectType` — object type +### ToolDef Field Reference + +The `def` parameter of `RegisterTool` is of type `sdk.ToolDef`, with the following fields: + +| Field | Type | Description | +|-------|------|-------------| +| `Name` | `string` | Tool name, use plugin name prefix to avoid conflicts | +| `Description` | `string` | Tool description, LLM uses this for tool selection | +| `Parameters` | `map[string]interface{}` | JSON Schema parameter definition | +| `NoMemory` | `bool` | Default `false`; when `true`, output skips vector/jieba/distill computation (original text preserved) | +| `Cleaner` | `func(string) string` | Optional, filters output before computation layer (e.g., extract `.content` from JSON) | + +For detailed design rationale of `NoMemory` and `Cleaner`, see `docs/en/PLUGIN_DEV.md` in the core repository. + ### New Constructor `New()` is called by the kernel when loading a plugin. Plugin developers do not need to construct PluginSDK manually: diff --git a/example/a2a/go.mod b/example/a2a/go.mod index 72eb9ad..15014f2 100644 --- a/example/a2a/go.mod +++ b/example/a2a/go.mod @@ -2,4 +2,6 @@ module a2a go 1.25.0 -require gitcode.com/JianFeeeee/homeagent-sdk v0.7.1 +require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 + +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/tools/plugindev/testplugin/main.go b/example/a2a/main.go similarity index 78% rename from tools/plugindev/testplugin/main.go rename to example/a2a/main.go index acbee2e..67c3a3c 100644 --- a/tools/plugindev/testplugin/main.go +++ b/example/a2a/main.go @@ -3,7 +3,7 @@ package main import ( - "gitcode.com/JianFeeeee/homeagent-sdk/sdk" + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" ) func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { diff --git a/example/a2a/plugin.go b/example/a2a/plugin.go index 2f00c44..4ec7d81 100644 --- a/example/a2a/plugin.go +++ b/example/a2a/plugin.go @@ -47,6 +47,13 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"agent_url", "query"}, }, + Cleaner: func(output string) string { + var r struct{ Content string } + if json.Unmarshal([]byte(output), &r) == nil && r.Content != "" { + return r.Content + } + return output + }, }, p.handleA2AQuery) s.RegisterTool(tp+"a2a_discover", sdk.ToolDef{ @@ -454,6 +461,6 @@ func (p *Plugin) handleStatus(args map[string]interface{}) (interface{}, error) addrStr, listening, map[bool]string{true: "运行中", false: "已停止"}[serverRunning]), nil } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/example/ai_image/go.mod b/example/ai_image/go.mod index 3349672..5d0c6ae 100644 --- a/example/ai_image/go.mod +++ b/example/ai_image/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/sdk-clone +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/ai_image/main.go b/example/ai_image/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/ai_image/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/ai_image/plugin.go b/example/ai_image/plugin.go index f28e1f7..61213ab 100644 --- a/example/ai_image/plugin.go +++ b/example/ai_image/plugin.go @@ -23,7 +23,7 @@ type Plugin struct { size string } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/example/bili/go.mod b/example/bili/go.mod index 1051dc9..c0cc6c0 100644 --- a/example/bili/go.mod +++ b/example/bili/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => ../.. +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/bili/main.go b/example/bili/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/bili/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/bili/plugin.go b/example/bili/plugin.go index 3f3bfa1..d764917 100644 --- a/example/bili/plugin.go +++ b/example/bili/plugin.go @@ -43,6 +43,13 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"url"}, }, + Cleaner: func(output string) string { + var r struct{ Content string } + if json.Unmarshal([]byte(output), &r) == nil && r.Content != "" { + return r.Content + } + return output + }, }, p.handleBiliVideo) return nil } @@ -229,6 +236,6 @@ func contains(slice []string, s string) bool { return false } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/example/bili/plugin.h b/example/bili/plugin.h deleted file mode 100644 index 618dde6..0000000 --- a/example/bili/plugin.h +++ /dev/null @@ -1,101 +0,0 @@ -/* Code generated by cmd/cgo; DO NOT EDIT. */ - -/* package bili */ - - -#line 1 "cgo-builtin-export-prolog" - -#include - -#ifndef GO_CGO_EXPORT_PROLOGUE_H -#define GO_CGO_EXPORT_PROLOGUE_H - -#ifndef GO_CGO_GOSTRING_TYPEDEF -typedef struct { const char *p; ptrdiff_t n; } _GoString_; -extern size_t _GoStringLen(_GoString_ s); -extern const char *_GoStringPtr(_GoString_ s); -#endif - -#endif - -/* Start of preamble from import "C" comments. */ - - -#line 3 "z_bridge_gen.go" - -#include -int ha_dispatch(int method_id, void* core_api, char* s1, char* s2, char* s3, int i1, int i2, char** result, char** error); - -#line 1 "cgo-generated-wrapper" - - -/* End of preamble from import "C" comments. */ - - -/* Start of boilerplate cgo prologue. */ -#line 1 "cgo-gcc-export-header-prolog" - -#ifndef GO_CGO_PROLOGUE_H -#define GO_CGO_PROLOGUE_H - -typedef signed char GoInt8; -typedef unsigned char GoUint8; -typedef short GoInt16; -typedef unsigned short GoUint16; -typedef int GoInt32; -typedef unsigned int GoUint32; -typedef long long GoInt64; -typedef unsigned long long GoUint64; -typedef GoInt64 GoInt; -typedef GoUint64 GoUint; -typedef size_t GoUintptr; -typedef float GoFloat32; -typedef double GoFloat64; -#ifdef _MSC_VER -#if !defined(__cplusplus) || _MSVC_LANG <= 201402L -#include -typedef _Fcomplex GoComplex64; -typedef _Dcomplex GoComplex128; -#else -#include -typedef std::complex GoComplex64; -typedef std::complex GoComplex128; -#endif -#else -typedef float _Complex GoComplex64; -typedef double _Complex GoComplex128; -#endif - -/* - static assertion to make sure the file is being used on architecture - at least with matching size of GoInt. -*/ -typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; - -#ifndef GO_CGO_GOSTRING_TYPEDEF -typedef _GoString_ GoString; -#endif -typedef void *GoMap; -typedef void *GoChan; -typedef struct { void *t; void *v; } GoInterface; -typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; - -#endif - -/* End of boilerplate cgo prologue. */ - -#ifdef __cplusplus -extern "C" { -#endif - -extern int go_init_plugin(char* name, char* configJSON, char** errorOut); -extern int go_start_plugin(void* coreAPIptr, int coreVersion, char** errorOut); -extern int go_stop_plugin(char** errorOut); -extern int go_invoke_tool(char* name, char* argsJSON, char** resultOut, char** errorOut); -extern int go_invoke_stage(char* stage, char* ctxJSON, char** errorOut); -extern int go_invoke_output(char* channel, char* msgType, char* payloadJSON, char** errorOut); -extern void go_free_string(char* ptr); - -#ifdef __cplusplus -} -#endif diff --git a/example/browser/go.mod b/example/browser/go.mod index f56bb87..d484483 100644 --- a/example/browser/go.mod +++ b/example/browser/go.mod @@ -1,21 +1,26 @@ -module browser-plugin +module browser go 1.25.0 require ( gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 github.com/chromedp/chromedp v0.9.5 + github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 + github.com/chromedp/sysutil v1.0.0 + github.com/gobwas/httphead v0.1.0 + github.com/gobwas/pool v0.2.1 + github.com/gobwas/ws v1.3.2 + github.com/josharian/intern v1.0.0 + github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 + github.com/mailru/easyjson v0.7.7 + github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde + golang.org/x/sys v0.16.0 ) -require ( - github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 // indirect - github.com/chromedp/sysutil v1.0.0 // indirect - github.com/gobwas/httphead v0.1.0 // indirect - github.com/gobwas/pool v0.2.1 // indirect - github.com/gobwas/ws v1.3.2 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - golang.org/x/sys v0.16.0 // indirect -) +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/sdk-clone +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk + +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk + +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/browser/main.go b/example/browser/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/browser/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/browser/plugin.go b/example/browser/plugin.go index c7f2169..383def9 100644 --- a/example/browser/plugin.go +++ b/example/browser/plugin.go @@ -51,7 +51,7 @@ type BrowserSession struct { currentURL string } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name, stopCh: make(chan struct{}), sessions: make(map[string]*BrowserSession)}, nil } @@ -188,6 +188,14 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { tp := p.name + "_" + cleaner := func(output string) string { + var r struct{ Content string } + if json.Unmarshal([]byte(output), &r) == nil && r.Content != "" { + return r.Content + } + return output + } + s.RegisterTool(tp+"search", sdk.ToolDef{ Name: tp + "search", Description: "使用 Bing 搜索网页。返回标题、URL 和摘要。", @@ -199,6 +207,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"query"}, }, + Cleaner: cleaner, }, p.handleSearch) s.RegisterTool(tp+"fetch", sdk.ToolDef{ @@ -213,6 +222,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"url"}, }, + Cleaner: cleaner, }, p.handleFetch) s.RegisterTool(tp+"render", sdk.ToolDef{ @@ -226,6 +236,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"url"}, }, + Cleaner: cleaner, }, p.handleRender) s.RegisterTool(tp+"start", sdk.ToolDef{ diff --git a/example/calendar/go.mod b/example/calendar/go.mod index 86f7f8a..941d1fe 100644 --- a/example/calendar/go.mod +++ b/example/calendar/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/sdk-clone +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/calendar/main.go b/example/calendar/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/calendar/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/calendar/plugin.go b/example/calendar/plugin.go index 601345b..b5187a7 100644 --- a/example/calendar/plugin.go +++ b/example/calendar/plugin.go @@ -54,7 +54,7 @@ type Plugin struct { remindTicker *time.Ticker } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name, stopCh: make(chan struct{})}, nil } diff --git a/example/editdoc/go.mod b/example/editdoc/go.mod index e0f291c..1d243f6 100644 --- a/example/editdoc/go.mod +++ b/example/editdoc/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => ../.. +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/editdoc/main.go b/example/editdoc/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/editdoc/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/editdoc/plugin.go b/example/editdoc/plugin.go index b7d9ca4..aee26f1 100644 --- a/example/editdoc/plugin.go +++ b/example/editdoc/plugin.go @@ -23,6 +23,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.RegisterTool("edit_document", sdk.ToolDef{ Name: "edit_document", Description: "编辑 Office 文档内容。支持替换文本、修改单元格等操作。编辑后原文件被覆盖。操作前建议先用 read_document 查看内容。支持 .docx / .xlsx / .pptx。", + NoMemory: true, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ @@ -124,6 +125,6 @@ func (p *Plugin) handleEditDocument(args map[string]interface{}) (interface{}, e } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/tools/plugindev/testplugin/go.mod b/example/files/go.mod similarity index 84% rename from tools/plugindev/testplugin/go.mod rename to example/files/go.mod index 4145436..c483966 100644 --- a/tools/plugindev/testplugin/go.mod +++ b/example/files/go.mod @@ -1,6 +1,6 @@ -module testplugin +module files -go 1.21 +go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 diff --git a/example/files/main.go b/example/files/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/files/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/files/plugin.go b/example/files/plugin.go index 56bd0da..e73b942 100644 --- a/example/files/plugin.go +++ b/example/files/plugin.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "log" "os" @@ -59,6 +60,14 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"path"}, }, + NoMemory: false, + Cleaner: func(output string) string { + var r struct{ Content string } + if json.Unmarshal([]byte(output), &r) == nil && r.Content != "" { + return r.Content + } + return output + }, }, p.handleRead) s.RegisterTool(tp+"write", sdk.ToolDef{ @@ -478,6 +487,6 @@ func getSetting[T any](s sdk.SettingsAPI, key string, def T) T { return val } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/example/memo/go.mod b/example/memo/go.mod index abb5c12..ddd738f 100644 --- a/example/memo/go.mod +++ b/example/memo/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => ../.. +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/memo/main.go b/example/memo/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/memo/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/memo/plugin.go b/example/memo/plugin.go index c1dd91a..4dcef98 100644 --- a/example/memo/plugin.go +++ b/example/memo/plugin.go @@ -269,6 +269,6 @@ func errorResult(msg string) map[string]interface{} { } } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/example/music/go.mod b/example/music/go.mod index 2c34cda..038962b 100644 --- a/example/music/go.mod +++ b/example/music/go.mod @@ -2,6 +2,6 @@ module music go 1.25.0 -require gitcode.com/JianFeeeee/homeagent-sdk v0.7.1 +require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/sdk-clone \ No newline at end of file +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/music/main.go b/example/music/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/music/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/music/plugin.go b/example/music/plugin.go index 8021434..6de6a34 100644 --- a/example/music/plugin.go +++ b/example/music/plugin.go @@ -56,7 +56,7 @@ type lyricData struct { Lyric string `json:"lyric"` } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } @@ -84,6 +84,13 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, "required": []string{"keyword"}, }, + Cleaner: func(output string) string { + var r struct{ Content string } + if json.Unmarshal([]byte(output), &r) == nil && r.Content != "" { + return r.Content + } + return output + }, }, p.handleSearch) s.RegisterTool(p.name+"_lyrics", sdk.ToolDef{ diff --git a/example/ocr/go.mod b/example/ocr/go.mod index 5cb1257..ebe7f3e 100644 --- a/example/ocr/go.mod +++ b/example/ocr/go.mod @@ -2,6 +2,6 @@ module ocr go 1.25.0 -require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0-20260708004841-e9bdcf9304b0 +require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => ../.. +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/ocr/main.go b/example/ocr/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/ocr/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/ocr/plugin.go b/example/ocr/plugin.go index 458d892..641ec38 100644 --- a/example/ocr/plugin.go +++ b/example/ocr/plugin.go @@ -2,6 +2,7 @@ package main import ( "encoding/base64" + "encoding/json" "fmt" "io" "log" @@ -38,6 +39,17 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { "language": map[string]interface{}{"type": "string", "description": "识别语言,默认 chi_sim+eng(中文简体+英文),可选 chi_sim / eng / chi_sim+eng"}, }, }, + Cleaner: func(output string) string { + var r struct{ Text string } + if json.Unmarshal([]byte(output), &r) == nil && r.Text != "" { + return r.Text + } + var r2 struct{ Content string } + if json.Unmarshal([]byte(output), &r2) == nil && r2.Content != "" { + return r2.Content + } + return output + }, }, p.handleOcrImage) log.Printf("[%s] plugin started", p.name) @@ -128,7 +140,6 @@ func (p *Plugin) handleOcrImage(args map[string]interface{}) (interface{}, error }, nil } - -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil -} +} \ No newline at end of file diff --git a/example/qq/go.mod b/example/qq/go.mod index 3185ccf..0fc8a14 100644 --- a/example/qq/go.mod +++ b/example/qq/go.mod @@ -2,6 +2,6 @@ module qq go 1.25.0 -require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0-20260708004841-e9bdcf9304b0 +require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => ../.. +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/qq/main.go b/example/qq/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/qq/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/qq/plugin.go b/example/qq/plugin.go index 64084b2..33c7241 100644 --- a/example/qq/plugin.go +++ b/example/qq/plugin.go @@ -167,155 +167,242 @@ meta JSON 格式: type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图片URL)/ file(文件URL)`, p.handleChannelOutput) + // 查询类工具输出清洗器:提取 JSON 中的 content/文本字段参与向量化 + cleaner := func(output string) string { + var r struct{ Content string } + if json.Unmarshal([]byte(output), &r) == nil && r.Content != "" { + return r.Content + } + return output + } + // ---- 消息 ---- - p.regTool(s, tp+"get_message", botInfo+"通过 message_id 从 NapCat 实时获取消息正文、发送者、附件等信息。message_id 从中断消息的 message_id=N 获取,或从 reply_to 的 message_id 获取。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "message_id": map[string]interface{}{"type": "integer", "description": "NapCat消息ID(从中断消息的 message_id=N 或 reply_to.message_id 获取)"}, - }, "required": []string{"message_id"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_message", Description: botInfo + "通过 message_id 从 NapCat 实时获取消息正文、发送者、附件等信息。message_id 从中断消息的 message_id=N 获取,或从 reply_to 的 message_id 获取。", + NoMemory: false, + Cleaner: cleaner, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "message_id": map[string]interface{}{"type": "integer", "description": "NapCat消息ID(从中断消息的 message_id=N 或 reply_to.message_id 获取)"}, + }, "required": []string{"message_id"}, + }, }, p.handleGetMessage) - p.regTool(s, tp+"send_file", "发送文件/图片到QQ(私聊或群聊)。文件先复制到remote目录供NapCat容器访问。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "group_id": map[string]interface{}{"type": "integer", "description": "目标群号(与user_id二选一)"}, - "user_id": map[string]interface{}{"type": "integer", "description": "目标QQ号(与group_id二选一)"}, - "file": map[string]interface{}{"type": "string", "description": "本地文件路径"}, - "name": map[string]interface{}{"type": "string", "description": "文件名(可选,默认取原文件名)"}, - "as_image": map[string]interface{}{"type": "boolean", "description": "作为图片发送(true)还是作为文件(false,默认)"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "send_file", Description: "发送文件/图片到QQ(私聊或群聊)。文件先复制到remote目录供NapCat容器访问。", + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "group_id": map[string]interface{}{"type": "integer", "description": "目标群号(与user_id二选一)"}, + "user_id": map[string]interface{}{"type": "integer", "description": "目标QQ号(与group_id二选一)"}, + "file": map[string]interface{}{"type": "string", "description": "本地文件路径"}, + "name": map[string]interface{}{"type": "string", "description": "文件名(可选,默认取原文件名)"}, + "as_image": map[string]interface{}{"type": "boolean", "description": "作为图片发送(true)还是作为文件(false,默认)"}, + }, }, + NoMemory: true, }, p.handleSendFile) - p.regTool(s, tp+"get_history", "获取QQ群聊/私聊最近历史消息。当收到引用回复消息或需要了解对话上下文时应优先调用此工具查看前后文。返回值每条格式为 [时间] 发送者: 消息内容。如果消息包含文件,会额外返回 files 字段(含 file_id 和 name),可用 qq_download_file 工具下载。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "group_id": map[string]interface{}{"type": "integer", "description": "群号(与user_id二选一)"}, - "user_id": map[string]interface{}{"type": "integer", "description": "QQ号私聊历史(与group_id二选一)"}, - "count": map[string]interface{}{"type": "integer", "description": "拉取条数,默认10"}, - }, "required": []string{}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_history", Description: "获取QQ群聊/私聊最近历史消息。当收到引用回复消息或需要了解对话上下文时应优先调用此工具查看前后文。返回值每条格式为 [时间] 发送者: 消息内容。如果消息包含文件,会额外返回 files 字段(含 file_id 和 name),可用 qq_download_file 工具下载。", + NoMemory: false, + Cleaner: cleaner, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "group_id": map[string]interface{}{"type": "integer", "description": "群号(与user_id二选一)"}, + "user_id": map[string]interface{}{"type": "integer", "description": "QQ号私聊历史(与group_id二选一)"}, + "count": map[string]interface{}{"type": "integer", "description": "拉取条数,默认10"}, + }, "required": []string{}, + }, }, p.handleGetHistory) // ---- 查询 ---- - p.regTool(s, tp+"get_groups", "获取QQ群列表,可按关键词搜索群名", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(可选)"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_groups", Description: "获取QQ群列表,可按关键词搜索群名", + NoMemory: false, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(可选)"}, + }, }, }, p.handleGetGroups) - p.regTool(s, tp+"get_friends", "获取QQ好友列表,可按昵称/备注关键词搜索", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(可选)"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_friends", Description: "获取QQ好友列表,可按昵称/备注关键词搜索", + NoMemory: false, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(可选)"}, + }, }, }, p.handleGetFriends) - p.regTool(s, tp+"get_recent_contacts", "查看最近有消息的联系人和群聊,返回最近消息概览(含消息数、最后一条消息内容)。可用于发现有谁发过消息但未处理。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "count": map[string]interface{}{"type": "integer", "description": "获取数量,默认10"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_recent_contacts", Description: "查看最近有消息的联系人和群聊,返回最近消息概览(含消息数、最后一条消息内容)。可用于发现有谁发过消息但未处理。", + NoMemory: false, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "count": map[string]interface{}{"type": "integer", "description": "获取数量,默认10"}, + }, }, }, p.handleGetRecentContacts) - p.regTool(s, tp+"resolve_name", "将QQ号或群号解析为可读的用户昵称或群名称", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "user_id": map[string]interface{}{"type": "integer", "description": "QQ号(与group_id二选一)"}, - "group_id": map[string]interface{}{"type": "integer", "description": "群号(与user_id二选一)"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "resolve_name", Description: "将QQ号或群号解析为可读的用户昵称或群名称", + NoMemory: false, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "user_id": map[string]interface{}{"type": "integer", "description": "QQ号(与group_id二选一)"}, + "group_id": map[string]interface{}{"type": "integer", "description": "群号(与user_id二选一)"}, + }, }, }, p.handleResolveName) - p.regTool(s, tp+"resolve_nickname", "按昵称/备注/群名片搜索QQ用户,返回匹配的QQ号和详细信息。支持搜索好友列表或指定群成员。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(昵称/备注/群名片)"}, - "group_id": map[string]interface{}{"type": "integer", "description": "所在群号(可选),不传则搜索好友列表"}, - }, "required": []string{"keyword"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "resolve_nickname", Description: "按昵称/备注/群名片搜索QQ用户,返回匹配的QQ号和详细信息。支持搜索好友列表或指定群成员。", + NoMemory: false, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(昵称/备注/群名片)"}, + "group_id": map[string]interface{}{"type": "integer", "description": "所在群号(可选),不传则搜索好友列表"}, + }, "required": []string{"keyword"}, + }, }, p.handleResolveNickname) - p.regTool(s, tp+"get_group_member_info", "获取QQ群成员详细信息", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "group_id": map[string]interface{}{"type": "integer", "description": "群号"}, - "user_id": map[string]interface{}{"type": "integer", "description": "QQ号"}, - }, "required": []string{"group_id", "user_id"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_group_member_info", Description: "获取QQ群成员详细信息", + NoMemory: false, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "group_id": map[string]interface{}{"type": "integer", "description": "群号"}, + "user_id": map[string]interface{}{"type": "integer", "description": "QQ号"}, + }, "required": []string{"group_id", "user_id"}, + }, }, p.handleGetGroupMemberInfo) // ---- 群管理 ---- - p.regTool(s, tp+"group_manage", "QQ群综合管理。通过command参数执行各种操作:leave退群, kick踢人, ban禁言, unban解禁, rename改名, mute-all全员禁言, set-card设名片, set-admin设管理, set-title设头衔, member-list成员列表, group-info群详情, member-info成员详情, at-all-remain@全体剩余, msg-history消息历史, recall撤回, pin-msg精华, list-files文件列表, pending-requests待处理请求, folder-create创建文件夹。注意:leave/kick/ban/unban/mute-all/set-admin等破坏性操作必须先请示管理员确认后再执行。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "command": map[string]interface{}{"type": "string", "description": "操作命令"}, - "group_id": map[string]interface{}{"type": "integer", "description": "群号"}, - "user_id": map[string]interface{}{"type": "integer", "description": "QQ号(踢人/禁言/设名片等需要)"}, - "message_id": map[string]interface{}{"type": "integer", "description": "消息ID(撤回/精华)"}, - "name": map[string]interface{}{"type": "string", "description": "群名称(rename)或文件夹名(folder-create)"}, - "card": map[string]interface{}{"type": "string", "description": "群名片(set-card)"}, - "title": map[string]interface{}{"type": "string", "description": "群头衔(set-title)"}, - "enable": map[string]interface{}{"type": "boolean", "description": "启用/禁用(set-admin/mute-all)"}, - "minutes": map[string]interface{}{"type": "integer", "description": "禁言分钟数(ban),0=解禁"}, - "count": map[string]interface{}{"type": "integer", "description": "消息条数(msg-history),默认10"}, - "folder_id": map[string]interface{}{"type": "string", "description": "文件夹ID(list-files)"}, - "reject_add": map[string]interface{}{"type": "boolean", "description": "踢出时拒绝加群(kick)"}, - "confirm": map[string]interface{}{"type": "boolean", "description": "高风险操作确认标记。执行 leave/kick/ban/unban/rename/mute-all/set-card/set-admin/set-title/recall/pin-msg/folder-create 时必须传 true"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "group_manage", Description: "QQ群综合管理。通过command参数执行各种操作:leave退群, kick踢人, ban禁言, unban解禁, rename改名, mute-all全员禁言, set-card设名片, set-admin设管理, set-title设头衔, member-list成员列表, group-info群详情, member-info成员详情, at-all-remain@全体剩余, msg-history消息历史, recall撤回, pin-msg精华, list-files文件列表, pending-requests待处理请求, folder-create创建文件夹。注意:leave/kick/ban/unban/mute-all/set-admin等破坏性操作必须先请示管理员确认后再执行。", + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "command": map[string]interface{}{"type": "string", "description": "操作命令"}, + "group_id": map[string]interface{}{"type": "integer", "description": "群号"}, + "user_id": map[string]interface{}{"type": "integer", "description": "QQ号(踢人/禁言/设名片等需要)"}, + "message_id": map[string]interface{}{"type": "integer", "description": "消息ID(撤回/精华)"}, + "name": map[string]interface{}{"type": "string", "description": "群名称(rename)或文件夹名(folder-create)"}, + "card": map[string]interface{}{"type": "string", "description": "群名片(set-card)"}, + "title": map[string]interface{}{"type": "string", "description": "群头衔(set-title)"}, + "enable": map[string]interface{}{"type": "boolean", "description": "启用/禁用(set-admin/mute-all)"}, + "minutes": map[string]interface{}{"type": "integer", "description": "禁言分钟数(ban),0=解禁"}, + "count": map[string]interface{}{"type": "integer", "description": "消息条数(msg-history),默认10"}, + "folder_id": map[string]interface{}{"type": "string", "description": "文件夹ID(list-files)"}, + "reject_add": map[string]interface{}{"type": "boolean", "description": "踢出时拒绝加群(kick)"}, + "confirm": map[string]interface{}{"type": "boolean", "description": "高风险操作确认标记。执行 leave/kick/ban/unban/rename/mute-all/set-card/set-admin/set-title/recall/pin-msg/folder-create 时必须传 true"}, + }, }, + NoMemory: true, }, p.handleGroupManage) - p.regTool(s, tp+"friend_action", "QQ好友管理:delete删除好友, block拉黑(删好友+从所有群踢出+拒绝加群), approve-friend同意好友请求, reject-friend拒绝好友请求, list-friends列出好友。注意:涉及删除/拉黑的操作必须请示管理员确认后再执行,未经授权不可操作。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "command": map[string]interface{}{"type": "string", "description": "操作: delete|block|approve-friend|reject-friend|list-friends"}, - "user_id": map[string]interface{}{"type": "integer", "description": "目标QQ号"}, - "flag": map[string]interface{}{"type": "string", "description": "好友请求flag(approve-friend/reject-friend需要)"}, - "remark": map[string]interface{}{"type": "string", "description": "好友备注(approve-friend可选)"}, - "group_id": map[string]interface{}{"type": "integer", "description": "仅从指定群踢出(block配合)"}, - "confirm": map[string]interface{}{"type": "boolean", "description": "高风险操作确认标记。执行 delete/block/approve-friend/reject-friend 时必须传 true"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "friend_action", Description: "QQ好友管理:delete删除好友, block拉黑(删好友+从所有群踢出+拒绝加群), approve-friend同意好友请求, reject-friend拒绝好友请求, list-friends列出好友。注意:涉及删除/拉黑的操作必须请示管理员确认后再执行,未经授权不可操作。", + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "command": map[string]interface{}{"type": "string", "description": "操作: delete|block|approve-friend|reject-friend|list-friends"}, + "user_id": map[string]interface{}{"type": "integer", "description": "目标QQ号"}, + "flag": map[string]interface{}{"type": "string", "description": "好友请求flag(approve-friend/reject-friend需要)"}, + "remark": map[string]interface{}{"type": "string", "description": "好友备注(approve-friend可选)"}, + "group_id": map[string]interface{}{"type": "integer", "description": "仅从指定群踢出(block配合)"}, + "confirm": map[string]interface{}{"type": "boolean", "description": "高风险操作确认标记。执行 delete/block/approve-friend/reject-friend 时必须传 true"}, + }, }, + NoMemory: true, }, p.handleFriendAction) // ---- 文件 ---- - p.regTool(s, tp+"get_group_files", "查询群文件列表、搜索文件、下载文件到本地。操作: list列出, search搜索, download下载", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "group_id": map[string]interface{}{"type": "integer", "description": "群号"}, - "command": map[string]interface{}{"type": "string", "description": "操作: list|search|download"}, - "folder_id": map[string]interface{}{"type": "string", "description": "文件夹ID(list指定文件夹)"}, - "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(search)"}, - "file_id": map[string]interface{}{"type": "string", "description": "文件ID(download)"}, - "filename": map[string]interface{}{"type": "string", "description": "保存文件名(download可选)"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_group_files", Description: "查询群文件列表、搜索文件、下载文件到本地。操作: list列出, search搜索, download下载", + NoMemory: false, + Cleaner: cleaner, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "group_id": map[string]interface{}{"type": "integer", "description": "群号"}, + "command": map[string]interface{}{"type": "string", "description": "操作: list|search|download"}, + "folder_id": map[string]interface{}{"type": "string", "description": "文件夹ID(list指定文件夹)"}, + "keyword": map[string]interface{}{"type": "string", "description": "搜索关键词(search)"}, + "file_id": map[string]interface{}{"type": "string", "description": "文件ID(download)"}, + "filename": map[string]interface{}{"type": "string", "description": "保存文件名(download可选)"}, + }, }, }, p.handleGetGroupFiles) - p.regTool(s, tp+"download_file", "从聊天记录下载文件到本地。file_id 从 qq_get_message/qq_get_history 的 files 字段获取。群文件建议提供 group_id,私聊文件建议提供 user_id 以提高成功率。异步下载,完成后推送通知。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "file_id": map[string]interface{}{"type": "string", "description": "文件 ID(从 qq_get_message 或 qq_get_history 的 files 字段获取)"}, - "url": map[string]interface{}{"type": "string", "description": "文件下载 URL(可选,qq_get_message 返回的 url 字段)"}, - "group_id": map[string]interface{}{"type": "integer", "description": "群号(可选,群文件下载)"}, - "user_id": map[string]interface{}{"type": "integer", "description": "私聊对象QQ号(可选,私聊文件下载)"}, - "filename": map[string]interface{}{"type": "string", "description": "保存文件名(可选,默认用原文件名)"}, - }, "required": []string{"file_id"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "download_file", Description: "从聊天记录下载文件到本地。file_id 从 qq_get_message/qq_get_history 的 files 字段获取。群文件建议提供 group_id,私聊文件建议提供 user_id 以提高成功率。异步下载,完成后推送通知。", + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "file_id": map[string]interface{}{"type": "string", "description": "文件 ID(从 qq_get_message 或 qq_get_history 的 files 字段获取)"}, + "url": map[string]interface{}{"type": "string", "description": "文件下载 URL(可选,qq_get_message 返回的 url 字段)"}, + "group_id": map[string]interface{}{"type": "integer", "description": "群号(可选,群文件下载)"}, + "user_id": map[string]interface{}{"type": "integer", "description": "私聊对象QQ号(可选,私聊文件下载)"}, + "filename": map[string]interface{}{"type": "string", "description": "保存文件名(可选,默认用原文件名)"}, + }, "required": []string{"file_id"}, + }, + NoMemory: true, }, p.handleDownloadFile) - p.regTool(s, tp+"get_download_tasks", "查看所有下载任务及状态(running/done/failed),包含文件名、保存路径、错误信息等", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "get_download_tasks", Description: "查看所有下载任务及状态(running/done/failed),包含文件名、保存路径、错误信息等", + NoMemory: false, + Cleaner: cleaner, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{}, + }, }, p.handleGetDownloadTasks) - p.regTool(s, tp+"upload_group_file", "上传文件到QQ群(通过base64编码发送,同时出现在群消息和群文件柜)", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "group_id": map[string]interface{}{"type": "integer", "description": "目标群号"}, - "file": map[string]interface{}{"type": "string", "description": "本地文件路径"}, - "name": map[string]interface{}{"type": "string", "description": "文件名(可选,默认取原文件名)"}, - }, "required": []string{"group_id", "file"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "upload_group_file", Description: "上传文件到QQ群(通过base64编码发送,同时出现在群消息和群文件柜)", + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "group_id": map[string]interface{}{"type": "integer", "description": "目标群号"}, + "file": map[string]interface{}{"type": "string", "description": "本地文件路径"}, + "name": map[string]interface{}{"type": "string", "description": "文件名(可选,默认取原文件名)"}, + }, "required": []string{"group_id", "file"}, + }, + NoMemory: true, }, p.handleUploadGroupFile) // ---- 文档/视频/网页工具 ---- - p.regTool(s, tp+"read_document", "读取文档内容文本。支持 PDF、DOCX、DOC、XLSX、XLS、PPTX、PPT、TXT、CSV、MD 格式。使用 libreoffice + pandoc 转换提取文本,返回前 20000 字符。适合处理用户发来的文档文件。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "path": map[string]interface{}{"type": "string", "description": "文档文件路径(已保存到本地的文件路径)"}, - }, "required": []string{"path"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "read_document", Description: "读取文档内容文本。支持 PDF、DOCX、DOC、XLSX、XLS、PPTX、PPT、TXT、CSV、MD 格式。使用 libreoffice + pandoc 转换提取文本,返回前 20000 字符。适合处理用户发来的文档文件。", + NoMemory: false, + Cleaner: cleaner, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "path": map[string]interface{}{"type": "string", "description": "文档文件路径(已保存到本地的文件路径)"}, + }, "required": []string{"path"}, + }, }, p.handleReadDocument) - p.regTool(s, tp+"video_download", "下载视频到本地。支持 B站、YouTube 等主流视频网站(通过 yt-dlp)。先调用 info_only 查看视频信息,再下载。下载后文件保存在 agentfs 目录。", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "url": map[string]interface{}{"type": "string", "description": "视频分享链接"}, - "info_only": map[string]interface{}{"type": "boolean", "description": "仅获取视频信息(标题、时长、清晰度列表),不下"}, - }, "required": []string{"url"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "video_download", Description: "下载视频到本地。支持 B站、YouTube 等主流视频网站(通过 yt-dlp)。先调用 info_only 查看视频信息,再下载。下载后文件保存在 agentfs 目录。", + NoMemory: false, + Cleaner: cleaner, + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "url": map[string]interface{}{"type": "string", "description": "视频分享链接"}, + "info_only": map[string]interface{}{"type": "boolean", "description": "仅获取视频信息(标题、时长、清晰度列表),不下"}, + }, "required": []string{"url"}, + }, }, p.handleVideoDownload) // ---- 附加 ---- - p.regTool(s, tp+"send_like", "给QQ好友点赞/戳一戳", map[string]interface{}{ - "type": "object", "properties": map[string]interface{}{ - "user_id": map[string]interface{}{"type": "integer", "description": "目标QQ号"}, - "times": map[string]interface{}{"type": "integer", "description": "点赞次数1-20,默认1"}, - }, "required": []string{"user_id"}, + p.regTool(s, sdk.ToolDef{ + Name: tp + "send_like", Description: "给QQ好友点赞/戳一戳", + Parameters: map[string]interface{}{ + "type": "object", "properties": map[string]interface{}{ + "user_id": map[string]interface{}{"type": "integer", "description": "目标QQ号"}, + "times": map[string]interface{}{"type": "integer", "description": "点赞次数1-20,默认1"}, + }, "required": []string{"user_id"}, + }, + NoMemory: true, }, p.handleSendLike) s.RegisterStage(sdk.StageBeforeToolcall, p.beforeOwnToolcall, sdk.StageScopeOwnTools) @@ -347,8 +434,8 @@ func (p *Plugin) Stop() error { return nil } -func (p *Plugin) regTool(s *sdk.PluginSDK, name, desc string, params map[string]interface{}, handler sdk.ToolHandler) { - s.RegisterTool(name, sdk.ToolDef{Name: name, Description: desc, Parameters: params}, handler) +func (p *Plugin) regTool(s *sdk.PluginSDK, def sdk.ToolDef, handler sdk.ToolHandler) { + s.RegisterTool(def.Name, def, handler) } // ======== Bot Identity ======== @@ -2078,7 +2165,7 @@ func convInt64(v interface{}) (int64, error) { return 0, fmt.Errorf("cannot convert %T to int64", v) } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{ name: name, allowFrom: make(map[int64]struct{}), diff --git a/example/qq/plugin.h b/example/qq/plugin.h deleted file mode 100644 index 6da5dae..0000000 --- a/example/qq/plugin.h +++ /dev/null @@ -1,101 +0,0 @@ -/* Code generated by cmd/cgo; DO NOT EDIT. */ - -/* package qq */ - - -#line 1 "cgo-builtin-export-prolog" - -#include - -#ifndef GO_CGO_EXPORT_PROLOGUE_H -#define GO_CGO_EXPORT_PROLOGUE_H - -#ifndef GO_CGO_GOSTRING_TYPEDEF -typedef struct { const char *p; ptrdiff_t n; } _GoString_; -extern size_t _GoStringLen(_GoString_ s); -extern const char *_GoStringPtr(_GoString_ s); -#endif - -#endif - -/* Start of preamble from import "C" comments. */ - - -#line 3 "z_bridge_gen.go" - -#include -int ha_dispatch(int method_id, void* core_api, char* s1, char* s2, char* s3, int i1, int i2, char** result, char** error); - -#line 1 "cgo-generated-wrapper" - - -/* End of preamble from import "C" comments. */ - - -/* Start of boilerplate cgo prologue. */ -#line 1 "cgo-gcc-export-header-prolog" - -#ifndef GO_CGO_PROLOGUE_H -#define GO_CGO_PROLOGUE_H - -typedef signed char GoInt8; -typedef unsigned char GoUint8; -typedef short GoInt16; -typedef unsigned short GoUint16; -typedef int GoInt32; -typedef unsigned int GoUint32; -typedef long long GoInt64; -typedef unsigned long long GoUint64; -typedef GoInt64 GoInt; -typedef GoUint64 GoUint; -typedef size_t GoUintptr; -typedef float GoFloat32; -typedef double GoFloat64; -#ifdef _MSC_VER -#if !defined(__cplusplus) || _MSVC_LANG <= 201402L -#include -typedef _Fcomplex GoComplex64; -typedef _Dcomplex GoComplex128; -#else -#include -typedef std::complex GoComplex64; -typedef std::complex GoComplex128; -#endif -#else -typedef float _Complex GoComplex64; -typedef double _Complex GoComplex128; -#endif - -/* - static assertion to make sure the file is being used on architecture - at least with matching size of GoInt. -*/ -typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; - -#ifndef GO_CGO_GOSTRING_TYPEDEF -typedef _GoString_ GoString; -#endif -typedef void *GoMap; -typedef void *GoChan; -typedef struct { void *t; void *v; } GoInterface; -typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; - -#endif - -/* End of boilerplate cgo prologue. */ - -#ifdef __cplusplus -extern "C" { -#endif - -extern int go_init_plugin(char* name, char* configJSON, char** errorOut); -extern int go_start_plugin(void* coreAPIptr, int coreVersion, char** errorOut); -extern int go_stop_plugin(char** errorOut); -extern int go_invoke_tool(char* name, char* argsJSON, char** resultOut, char** errorOut); -extern int go_invoke_stage(char* stage, char* ctxJSON, char** errorOut); -extern int go_invoke_output(char* channel, char* msgType, char* payloadJSON, char** errorOut); -extern void go_free_string(char* ptr); - -#ifdef __cplusplus -} -#endif diff --git a/example/rss/go.mod b/example/rss/go.mod index da9d63a..14abda9 100644 --- a/example/rss/go.mod +++ b/example/rss/go.mod @@ -2,13 +2,16 @@ module rss go 1.25.0 -require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 - require ( - github.com/mmcdole/gofeed v1.4.0 // indirect - github.com/mmcdole/goxpp/v2 v2.0.0 // indirect - golang.org/x/net v0.56.0 // indirect - golang.org/x/text v0.38.0 // indirect + gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 + github.com/mmcdole/gofeed v1.4.0 + github.com/mmcdole/goxpp/v2 v2.0.0 + golang.org/x/net v0.56.0 + golang.org/x/text v0.38.0 ) -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/sdk-clone +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk + +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk + +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/rss/main.go b/example/rss/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/rss/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/rss/plugin.go b/example/rss/plugin.go index f8715af..c30035a 100644 --- a/example/rss/plugin.go +++ b/example/rss/plugin.go @@ -37,7 +37,7 @@ type Plugin struct { pollTicker *time.Ticker } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } @@ -122,6 +122,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { tp := p.name + "_" s.RegisterTool(tp+"subscribe", sdk.ToolDef{ Name: tp + "subscribe", Description: "Subscribe to an RSS/Atom feed URL", + NoMemory: true, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ @@ -134,6 +135,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.RegisterTool(tp+"unsubscribe", sdk.ToolDef{ Name: tp + "unsubscribe", Description: "Unsubscribe from a feed", + NoMemory: true, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ @@ -153,6 +155,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.RegisterTool(tp+"check_now", sdk.ToolDef{ Name: tp + "check_now", Description: "Manually check all feeds for new articles now", + NoMemory: true, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{}, diff --git a/example/sanitizer/go.mod b/example/sanitizer/go.mod index 2d29add..5c20800 100644 --- a/example/sanitizer/go.mod +++ b/example/sanitizer/go.mod @@ -2,6 +2,6 @@ module sanitizer go 1.25.0 -require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0-20260708004841-e9bdcf9304b0 +require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => ../.. +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/sanitizer/main.go b/example/sanitizer/main.go new file mode 100644 index 0000000..67c3a3c --- /dev/null +++ b/example/sanitizer/main.go @@ -0,0 +1,11 @@ +//go:build !windows || !cgo + +package main + +import ( + sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk" +) + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} diff --git a/example/sanitizer/plugin.go b/example/sanitizer/plugin.go index 17c0330..de94b18 100644 --- a/example/sanitizer/plugin.go +++ b/example/sanitizer/plugin.go @@ -53,7 +53,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { func (p *Plugin) Stop() error { return nil } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{}, nil } diff --git a/example/weather/go.mod b/example/weather/go.mod index 847bb7b..e1bd210 100644 --- a/example/weather/go.mod +++ b/example/weather/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0 -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/sdk-clone +replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk diff --git a/example/weather/plugin.go b/example/weather/plugin.go index 05d428f..089876c 100644 --- a/example/weather/plugin.go +++ b/example/weather/plugin.go @@ -21,7 +21,7 @@ type Plugin struct { defaultLoc string } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } diff --git a/meta/meta.go b/meta/meta.go index 883a2bd..037ac21 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -5,7 +5,7 @@ package meta var ( // Version 是 HomeAgent SDK 版本号。 // 通过 `-ldflags="-X gitcode.com/JianFeeeee/homeagent-sdk/meta.Version=vX.Y.Z"` 注入。 - Version = "0.7.1" + Version = "0.8.0" // Commit 是构建时的 Git commit hash。 Commit = "unknown" diff --git a/sdk/plugin.go b/sdk/plugin.go index 7663ae2..14a777c 100644 --- a/sdk/plugin.go +++ b/sdk/plugin.go @@ -92,6 +92,8 @@ type ToolDef struct { Plugin string `json:"plugin,omitempty"` Description string `json:"description"` Parameters map[string]interface{} `json:"parameters"` + NoMemory bool `json:"no_memory,omitempty"` // 此工具输出不参与记忆计算,但原文保留 + Cleaner func(string) string `json:"-"` // 计算层过滤函数,不改原文;仅在向量化/jieba/蒸馏时调用 } // IOInjector provides methods for injecting input and interrupts into the agent pipeline. diff --git a/sdk/plugin_test.go b/sdk/plugin_test.go index fda9049..0ad4263 100644 --- a/sdk/plugin_test.go +++ b/sdk/plugin_test.go @@ -177,3 +177,77 @@ func TestRegisterStageOwnToolsNilRegStage(t *testing.T) { s := &PluginSDK{name: "test"} s.RegisterStage(StageBeforeToolcall, func(ctx *StageContext) error { return nil }, StageScopeOwnTools) } + +func TestToolDefCleaner(t *testing.T) { + called := false + def := ToolDef{ + Name: "test_clean", + Description: "A test tool with cleaner", + Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + Cleaner: func(output string) string { + called = true + return "cleaned:" + output + }, + } + if def.Cleaner == nil { + t.Fatal("Cleaner should not be nil") + } + result := def.Cleaner("raw output") + if !called { + t.Error("Cleaner was not called") + } + if result != "cleaned:raw output" { + t.Errorf("expected 'cleaned:raw output', got '%s'", result) + } +} + +func TestToolDefNoMemory(t *testing.T) { + def := ToolDef{ + Name: "test_nomem", + Description: "A test tool with NoMemory", + Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + NoMemory: true, + } + if !def.NoMemory { + t.Error("NoMemory should be true") + } + if def.Cleaner != nil { + t.Error("Cleaner should be nil when not set") + } +} + +func TestToolDefNoMemoryDefaultFalse(t *testing.T) { + def := ToolDef{ + Name: "test_default", + Description: "A test tool with defaults", + Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + } + if def.NoMemory { + t.Error("NoMemory should default to false") + } +} + +func TestToolDefRegisterPreservesNoMemory(t *testing.T) { + var capturedDef ToolDef + regTool := func(name string, def ToolDef, handler ToolHandler) error { + capturedDef = def + return nil + } + s := &PluginSDK{regTool: regTool, name: "test"} + def := ToolDef{ + Name: "test_tool", + Description: "test desc", + Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + NoMemory: true, + Cleaner: func(s string) string { return s }, + } + s.RegisterTool("test_tool", def, func(args map[string]interface{}) (interface{}, error) { + return nil, nil + }) + if !capturedDef.NoMemory { + t.Error("NoMemory should be preserved through RegisterTool") + } + if capturedDef.Cleaner == nil { + t.Error("Cleaner should be preserved through RegisterTool") + } +} diff --git a/tools/plugindev/cmd_build.go b/tools/plugindev/cmd_build.go index f592fa5..66dc8c2 100644 --- a/tools/plugindev/cmd_build.go +++ b/tools/plugindev/cmd_build.go @@ -14,8 +14,9 @@ import ( type BuildConfig struct { OutDir string - Targets []string // "linux/amd64", "windows/amd64", "lua" + Targets []string Bundle bool + SDKPath string } func cmdBuild(args []string) { @@ -24,48 +25,43 @@ func cmdBuild(args []string) { switch args[i] { case "--outdir": if i+1 < len(args) { - cfg.OutDir = args[i+1] - i++ + cfg.OutDir = args[i+1]; i++ } case "--target": if i+1 < len(args) { - cfg.Targets = append(cfg.Targets, args[i+1]) - i++ + cfg.Targets = append(cfg.Targets, args[i+1]); i++ } case "--bundle": cfg.Bundle = true + case "--sdk-path": + if i+1 < len(args) { + cfg.SDKPath = args[i+1]; i++ + } } } - // read plg.json plg, err := readPlgJSON("plg.json") if err != nil { - fmt.Printf("error: read plg.json: %v\n", err) - os.Exit(1) + fmt.Printf("error: read plg.json: %v\n", err); os.Exit(1) } if plg.IsLua() { - buildTarget(plg, "lua", cfg.OutDir) + buildTarget(plg, "lua", cfg.OutDir, "") return } - if cfg.Bundle { - buildBundle(plg, cfg.OutDir) + // Ensure go.mod exists with correct SDK path + ensureGoMod(plg, cfg.SDKPath) + + // Default: bundle mode (all 3 platforms in one .hmap) + if cfg.Bundle || len(cfg.Targets) == 0 { + buildBundle(plg, cfg.OutDir, cfg.SDKPath) return } - // determine targets - targets := cfg.Targets - if len(targets) == 0 { - targets = parseTargets(plg.Targets) - } - if len(targets) == 0 { - targets = []string{"native"} - } - - // build for each target - for _, t := range targets { - buildTarget(plg, t, cfg.OutDir) + // Explicit --target: build each separately + for _, t := range cfg.Targets { + buildTarget(plg, t, cfg.OutDir, cfg.SDKPath) } } @@ -80,7 +76,7 @@ var allBundleTargets = []struct { {"windows/amd64", "plugin.dll"}, } -func buildBundle(plg *PlgConfig, outDir string) { +func buildBundle(plg *PlgConfig, outDir string, sdkPath string) { os.MkdirAll(outDir, 0755) buildDir := "build" os.MkdirAll(buildDir, 0755) @@ -141,9 +137,7 @@ func buildBundle(plg *PlgConfig, outDir string) { fmt.Printf(" packaged %s\n", filepath.Base(hmapPath)) } -func (p *PlgConfig) IsLua() bool { - return p.Entry == "main.lua" -} +func (p *PlgConfig) IsLua() bool { return p.Entry == "main.lua" } func readPlgJSON(path string) (*PlgConfig, error) { data, err := os.ReadFile(path) @@ -225,7 +219,108 @@ func resolveBuild(target string) (*buildConfig, string) { } } -func buildTarget(plg *PlgConfig, target, outDir string) { +// ensureGoMod 确保插件项目的 go.mod 包含 SDK 的 replace 指令。 +// 如果 go.mod 不存在或已有正确 replace,则跳过。 +func ensureGoMod(plg *PlgConfig, sdkPath string) { + if sdkPath == "" { + // 从 plugindev 自身推断 SDK 路径 + self, err := os.Executable() + if err != nil { + return + } + cand := filepath.Dir(filepath.Dir(filepath.Dir(self))) + if _, err := os.Stat(filepath.Join(cand, "sdk", "plugin.go")); err != nil { + return + } + sdkPath = cand + } + + gomodPath := "go.mod" + data, err := os.ReadFile(gomodPath) + if err != nil { + return // no go.mod, skip + } + + lines := strings.Split(string(data), "\n") + var sdkModule string + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "require ") || strings.HasPrefix(line, "require (") { + continue + } + if strings.Contains(line, "homeagent-sdk/sdk") || strings.Contains(line, "homeagent-sdk") { + parts := strings.Fields(line) + if len(parts) >= 1 && !strings.HasPrefix(parts[0], "//") && !strings.HasPrefix(parts[0], "replace") { + sdkModule = parts[0] + } + } + } + if sdkModule == "" { + return + } + + // 检查是否已有 replace 指令 + absSDK, _ := filepath.Abs(sdkPath) + absSDK = strings.ReplaceAll(absSDK, "\\", "/") + for _, line := range lines { + if strings.Contains(line, "replace") && strings.Contains(line, sdkModule) { + parts := strings.Fields(line) + if len(parts) >= 3 && strings.ReplaceAll(parts[2], "\\", "/") == absSDK { + return // 已存在且路径正确 + } + } + } + + // 追加 replace 指令 + replaceLine := fmt.Sprintf("replace %s => %s", sdkModule, absSDK) + newData := string(data) + "\n" + replaceLine + "\n" + if err := os.WriteFile(gomodPath, []byte(newData), 0644); err != nil { + fmt.Printf(" warn: update go.mod replace: %v\n", err) + } +} + +func resolveSDKPath(sdkPath string) string { + if sdkPath != "" { + abs, _ := filepath.Abs(sdkPath) + if _, err := os.Stat(filepath.Join(abs, "sdk", "plugin.go")); err == nil { + return abs + } + fmt.Printf("error: --sdk-path %q not a valid SDK\n", sdkPath) + os.Exit(1) + } + // Detect from plugindev's own location (internal dev) + self, err := os.Executable() + if err == nil { + cand := filepath.Dir(filepath.Dir(filepath.Dir(self))) + if _, err := os.Stat(filepath.Join(cand, "sdk", "plugin.go")); err == nil { + return cand + } + } + // Active SDK via plugindev sdk use + store := os.Getenv("HOMEAGENT_SDK_DIR") + if store == "" { + home, _ := os.UserHomeDir() + if home != "" { + store = filepath.Join(home, ".homeagent", "plugindev", "sdk") + } + } + if store != "" { + if d, err := os.ReadFile(filepath.Join(store, "current")); err == nil { + ver := strings.TrimSpace(string(d)) + if ver != "" { + root := filepath.Join(store, ver) + if _, err := os.Stat(filepath.Join(root, "sdk", "plugin.go")); err == nil { + return root + } + } + } + } + fmt.Printf("error: cannot locate SDK. Use --sdk-path or 'plugindev sdk use'\n") + os.Exit(1) + return "" +} + +func buildTarget(plg *PlgConfig, target, outDir, sdkPath string) { os.MkdirAll(outDir, 0755) // Lua: no compilation, package source directly diff --git a/tools/plugindev/templates.go b/tools/plugindev/templates.go index 0308ab1..073ebdd 100644 --- a/tools/plugindev/templates.go +++ b/tools/plugindev/templates.go @@ -46,8 +46,14 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }) tp := p.name + "_" s.RegisterTool(tp+"hello", sdk.ToolDef{ - Name: tp + "hello", Description: "A hello world tool", - Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + Name: tp + "hello", + Description: "A hello world tool", + Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + NoMemory: false, // 工具输出对 LLM 注意力有信号价值时为 false,纯操作工具为 true + // Cleaner: func(output string) string { + // // 工具输出参与向量化/jieba/蒸馏前,在此过滤噪音 + // return output + // }, }, p.handleHello) fmt.Printf("[%s] started\n", p.name) return nil @@ -59,7 +65,7 @@ func (p *Plugin) handleHello(args map[string]interface{}) (interface{}, error) { return map[string]interface{}{"content": "Hello from {{.Plg.Name}} plugin!"}, nil } -func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { return &Plugin{name: name}, nil } ` @@ -157,7 +163,7 @@ func NewPlugin(name *C.char, configJSON *C.char) unsafe.Pointer { if c, ok := wrapper["config"].(map[string]interface{}); ok { config = c } } } - plg, err := NewPlugin(goName, config) + plg, err := NewPluginFactory(goName, config) if err != nil { return nil } return newHandle(plg) } @@ -173,6 +179,7 @@ func StartPlugin(handle unsafe.Pointer) C.int { }, func(stage sdk.Stage, handler sdk.StageHandler) { bs.stages[string(stage)] = handler }, func(name string) error { return nil }, + func(name string, caps int, desc string, handler sdk.ToolHandler) error { return nil }, ) if err := bs.plugin.Start(mockSDK); err != nil { return 1 } return 0 @@ -207,11 +214,11 @@ func InvokeToolJSON(handle unsafe.Pointer, toolName *C.char, argsJSON *C.char) * if bs == nil || toolName == nil { return nil } goName := C.GoString(toolName) handler, ok := bs.handlers[goName] - if !ok { r, _ := json.Marshal(map[string]interface{}{"error": "tool not found: " + goName}); return C.CString(string(r)) } + if !ok { errMsg, _ := json.Marshal(map[string]interface{}{"error": "tool not found: " + goName}); return C.CString(string(errMsg)) } var args map[string]interface{} if argsJSON != nil { json.Unmarshal([]byte(C.GoString(argsJSON)), &args) } r, err := handler(args) - if err != nil { r, _ = json.Marshal(map[string]interface{}{"error": err.Error()}); return C.CString(string(r)) } + if err != nil { errMsg, _ := json.Marshal(map[string]interface{}{"error": err.Error()}); return C.CString(string(errMsg)) } b, _ := json.Marshal(r) return C.CString(string(b)) } diff --git a/tools/plugindev/templates.go.new_header b/tools/plugindev/templates.go.new_header deleted file mode 100644 index a22823b..0000000 --- a/tools/plugindev/templates.go.new_header +++ /dev/null @@ -1 +0,0 @@ -I need to rewrite the preamble section. Let me use a python script to make this change. diff --git a/tools/plugindev/testplugin/README.md b/tools/plugindev/testplugin/README.md deleted file mode 100644 index b97bc29..0000000 --- a/tools/plugindev/testplugin/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# testplugin - -testplugin plugin - -## Build - -```bash -plugindev build -``` - -## Install - -Upload the .hmap file through the Plugin Manager API. diff --git a/tools/plugindev/testplugin/plg.json b/tools/plugindev/testplugin/plg.json deleted file mode 100644 index 6f1e716..0000000 --- a/tools/plugindev/testplugin/plg.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "testplugin", - "name_zh": "中文名", - "name_en": "Testplugin", - "version": "0.1.0", - "description": "testplugin plugin", - "author": "HomeAgent", - "entry": "plugin.so", - "tags": ["testplugin"], - "targets": "linux/amd64,windows/amd64" -} diff --git a/tools/plugindev/testplugin/plugin.go b/tools/plugindev/testplugin/plugin.go deleted file mode 100644 index ea36a54..0000000 --- a/tools/plugindev/testplugin/plugin.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - - "gitcode.com/JianFeeeee/homeagent-sdk/sdk" -) - -type Plugin struct { - name string - sdk *sdk.PluginSDK -} - -func (p *Plugin) Name() string { return p.name } - -func (p *Plugin) Start(s *sdk.PluginSDK) error { - p.sdk = s - - s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.testplugin.example", - Default: "hello", - Type: "string", - DisplayName: "示例配置", - Description: "An example configuration key", - Category: "testplugin", - }) - - tp := p.name + "_" - s.RegisterTool(tp+"hello", sdk.ToolDef{ - Name: tp + "hello", - Description: "A hello world tool", - Parameters: map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{}, - }, - }, p.handleHello) - - fmt.Printf("[%s] started\n", p.name) - return nil -} - -func (p *Plugin) Stop() error { - fmt.Printf("[%s] stopped\n", p.name) - return nil -} - -func (p *Plugin) handleHello(args map[string]interface{}) (interface{}, error) { - return map[string]interface{}{ - "content": "Hello from testplugin plugin!", - }, nil -} - -// NewPluginFactory creates a Plugin instance. Called by both Linux entry (main.go) -// and Windows bridge (z_bridge_gen.go) to avoid naming conflict with C export. -func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { - return &Plugin{name: name}, nil -}