mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-21 01:18:02 +00:00
feat: sdk NoMemory/Cleaner + example build fixes
- sdk/plugin.go: ToolDef adds NoMemory/Cleaner fields - sdk/plugin_test.go: unit tests for NoMemory/Cleaner - all example plugins: NoMemory/Cleaner annotated for each tool - plugindev/templates.go: template shows NoMemory/Cleaner pattern - plugindev/cmd_build.go: fix ensureGoMod, buildBundle/buildTarget sdkPath param, NewPluginFactory - example/go.mod: add external dependency declarations (chromedp, gofeed) - add main.go stubs for all example plugins
This commit is contained in:
14
README.md
14
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:
|
||||
|
||||
14
README_EN.md
14
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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/ai_image/main.go
Normal file
11
example/ai_image/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/bili/main.go
Normal file
11
example/bili/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package bili */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#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 <stdlib.h>
|
||||
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 <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
#include <complex>
|
||||
typedef std::complex<float> GoComplex64;
|
||||
typedef std::complex<double> 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
|
||||
@ -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
|
||||
|
||||
11
example/browser/main.go
Normal file
11
example/browser/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/calendar/main.go
Normal file
11
example/calendar/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/editdoc/main.go
Normal file
11
example/editdoc/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
module testplugin
|
||||
module files
|
||||
|
||||
go 1.21
|
||||
go 1.25.0
|
||||
|
||||
require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0
|
||||
|
||||
11
example/files/main.go
Normal file
11
example/files/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/memo/main.go
Normal file
11
example/memo/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
replace gitcode.com/JianFeeeee/homeagent-sdk => E:/program/homeagent/homeagentsdk
|
||||
|
||||
11
example/music/main.go
Normal file
11
example/music/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/ocr/main.go
Normal file
11
example/ocr/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
11
example/qq/main.go
Normal file
11
example/qq/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -167,14 +167,30 @@ 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{}{
|
||||
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{}{
|
||||
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二选一)"},
|
||||
@ -182,58 +198,91 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
"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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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": "群号"},
|
||||
@ -249,9 +298,13 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
"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{}{
|
||||
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号"},
|
||||
@ -260,10 +313,16 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
"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{}{
|
||||
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"},
|
||||
@ -272,9 +331,12 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
"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{}{
|
||||
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 字段)"},
|
||||
@ -282,40 +344,65 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
"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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}{
|
||||
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{}),
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package qq */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#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 <stdlib.h>
|
||||
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 <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
#include <complex>
|
||||
typedef std::complex<float> GoComplex64;
|
||||
typedef std::complex<double> 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
|
||||
@ -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
|
||||
|
||||
11
example/rss/main.go
Normal file
11
example/rss/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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{}{},
|
||||
|
||||
@ -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
|
||||
|
||||
11
example/sanitizer/main.go
Normal file
11
example/sanitizer/main.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
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))
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
I need to rewrite the preamble section. Let me use a python script to make this change.
|
||||
@ -1,13 +0,0 @@
|
||||
# testplugin
|
||||
|
||||
testplugin plugin
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
plugindev build
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Upload the .hmap file through the Plugin Manager API.
|
||||
@ -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"
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user