mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
- 删除 OpenAIProvider/OllamaProvider 死代码,LuaAdaptedProvider 独存 - DisableThinking 从 ExtraBody 移到 CompletionRequest 顶层字段 - ContextWindow 从 Provider 签名移到 BaseConfig/ModelContextWindow() 统管 - 确认 CleanText 仅做基本空白 trim,QQ 模板剥离归插件 Cleaner - Cleaner/NoMemory 仅作用于向量计算和 jieba 分词层,原文不变 - context.ContextEvent/Doc.Content 始终保存原文 - 删除 nlp/download.go 死代码 - media.go: context.Background() -> a.ctx 级联 - clawhubadapter: HTTP 超时 - cut.go: 跨平台 mod cache 路径 (GOMODCACHE->GOPATH->HomeDir) - bridge_e2e_test: 移除未用 runtime import - lua 适配器: disable_thinking 传参
263 lines
6.9 KiB
Go
263 lines
6.9 KiB
Go
//go:build windows
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"unsafe"
|
|
|
|
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
|
)
|
|
|
|
func TestBridgeE2E_WebPlugin(t *testing.T) {
|
|
exeDir, _ := os.Executable()
|
|
// Find web example build relative to the homeagent repo root
|
|
haRoot := findHomeAgentRoot(t, exeDir)
|
|
dllPath := filepath.Join(haRoot, "..", "homeagentsdk", "example", "web", "build", "plugin.dll")
|
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
|
t.Fatalf("web plugin DLL not found at %s\nRun: cd example/web && plugindev build --target windows/amd64", dllPath)
|
|
}
|
|
|
|
// Track captured tools and stages
|
|
var capturedTools []sdk.ToolDef
|
|
var capturedStages []sdk.Stage
|
|
|
|
regTool := func(name string, def sdk.ToolDef, handler sdk.ToolHandler) error {
|
|
capturedTools = append(capturedTools, def)
|
|
t.Logf(" registered tool: %s", name)
|
|
return nil
|
|
}
|
|
regStage := func(stage sdk.Stage, handler sdk.StageHandler) {
|
|
capturedStages = append(capturedStages, stage)
|
|
t.Logf(" registered stage: %s", stage)
|
|
}
|
|
regAPI := func(name string) error {
|
|
t.Logf(" registered API: %s", name)
|
|
return nil
|
|
}
|
|
|
|
sett := sdk.NewSettings("web", nil)
|
|
psdk := sdk.New("web", sdk.SDKConfig{Settings: sett, RegTool: regTool, RegStage: regStage, RegAPI: regAPI})
|
|
|
|
plg, err := newDLLPlugin(dllPath, "web", nil)
|
|
if err != nil {
|
|
t.Fatalf("newDLLPlugin failed: %v", err)
|
|
}
|
|
defer plg.Stop()
|
|
|
|
// Start — this calls NewPlugin + StartPlugin + registerTools + registerStages
|
|
if err := plg.Start(psdk); err != nil {
|
|
t.Fatalf("Start failed: %v", err)
|
|
}
|
|
|
|
// Verify tools were captured
|
|
if len(capturedTools) == 0 {
|
|
t.Fatal("no tools were registered by web plugin")
|
|
}
|
|
t.Logf("Captured %d tools:", len(capturedTools))
|
|
for _, d := range capturedTools {
|
|
t.Logf(" - %s: %s", d.Name, d.Description[:min(len(d.Description), 60)])
|
|
}
|
|
|
|
// Check specific expected tools
|
|
webSearch, webFetch := false, false
|
|
for _, d := range capturedTools {
|
|
if d.Name == "web_search" {
|
|
webSearch = true
|
|
if d.Description == "" {
|
|
t.Error("web_search has empty description")
|
|
}
|
|
params := d.Parameters
|
|
if params == nil {
|
|
t.Error("web_search has nil parameters")
|
|
} else {
|
|
if _, ok := params["properties"]; !ok {
|
|
t.Error("web_search parameters missing 'properties'")
|
|
}
|
|
}
|
|
}
|
|
if d.Name == "web_fetch" {
|
|
webFetch = true
|
|
}
|
|
}
|
|
if !webSearch {
|
|
t.Error("expected tool 'web_search' not registered")
|
|
}
|
|
if !webFetch {
|
|
t.Error("expected tool 'web_fetch' not registered")
|
|
}
|
|
|
|
// Verify bridge exports work via direct C ABI calls
|
|
t.Logf("Bridge exports: getTools=%x invokeTool=%x freeCStr=%x",
|
|
plg.getTools, plg.invokeTool, plg.freeCStr)
|
|
|
|
// GetToolDefsJSON
|
|
if plg.getTools != 0 {
|
|
toolDefsJSON := callGetToolDefsJSON(t, plg)
|
|
if len(toolDefsJSON) == 0 {
|
|
t.Error("GetToolDefsJSON returned empty array, expected tools")
|
|
}
|
|
for _, d := range toolDefsJSON {
|
|
t.Logf(" bridge tool: %s", d["name"])
|
|
}
|
|
}
|
|
|
|
// InvokeToolJSON — test with the search tool
|
|
if plg.invokeTool != 0 {
|
|
result := callInvokeToolJSON(t, plg, "web_search", map[string]interface{}{
|
|
"query": "test",
|
|
"count": 1,
|
|
})
|
|
t.Logf("InvokeToolJSON result keys: %v", keysOfMap(result))
|
|
// Should get a result map (might be error if no network, but should not crash)
|
|
if errStr, ok := result["error"]; ok {
|
|
t.Logf(" (expected — tool returned error: %v)", errStr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBridgeE2E_SanitizerStages(t *testing.T) {
|
|
exeDir, _ := os.Executable()
|
|
haRoot := findHomeAgentRoot(t, exeDir)
|
|
dllPath := filepath.Join(haRoot, "..", "homeagentsdk", "example", "sanitizer", "build", "plugin.dll")
|
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
|
t.Skip("sanitizer DLL not built")
|
|
}
|
|
|
|
var capturedStages []sdk.Stage
|
|
regStage := func(stage sdk.Stage, handler sdk.StageHandler) {
|
|
capturedStages = append(capturedStages, stage)
|
|
t.Logf(" registered stage: %s", stage)
|
|
}
|
|
|
|
sett := sdk.NewSettings("sanitizer", nil)
|
|
psdk := sdk.New("sanitizer", sdk.SDKConfig{Settings: sett,
|
|
RegTool: func(name string, def sdk.ToolDef, handler sdk.ToolHandler) error { return nil },
|
|
RegStage: regStage,
|
|
RegAPI: func(name string) error { return nil },
|
|
})
|
|
|
|
plg, err := newDLLPlugin(dllPath, "sanitizer", nil)
|
|
if err != nil {
|
|
t.Fatalf("newDLLPlugin failed: %v", err)
|
|
}
|
|
defer plg.Stop()
|
|
|
|
if err := plg.Start(psdk); err != nil {
|
|
t.Fatalf("Start failed: %v", err)
|
|
}
|
|
|
|
if len(capturedStages) == 0 {
|
|
t.Fatal("no stages registered by sanitizer")
|
|
}
|
|
found := false
|
|
for _, s := range capturedStages {
|
|
if s == sdk.StagePostAction {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected post_action stage, got %v", capturedStages)
|
|
}
|
|
|
|
// Verify bridge GetStagesJSON
|
|
if plg.getStages != 0 {
|
|
ret, _, _ := syscall.SyscallN(plg.getStages, plg.handle)
|
|
if ret != 0 {
|
|
stagesJSON := cStringPtrToString(ret)
|
|
if plg.freeCStr != 0 {
|
|
syscall.SyscallN(plg.freeCStr, ret)
|
|
}
|
|
t.Logf("GetStagesJSON: %s", stagesJSON)
|
|
if !contains(t, stagesJSON, "post_action") {
|
|
t.Error("GetStagesJSON missing post_action")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- helpers ---
|
|
|
|
func findHomeAgentRoot(t *testing.T, exeDir string) string {
|
|
t.Helper()
|
|
// Walk up from test binary directory looking for homeagent/
|
|
dir := exeDir
|
|
for i := 0; i < 10; i++ {
|
|
if _, err := os.Stat(filepath.Join(dir, "internal", "plugin")); err == nil {
|
|
return dir
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
t.Fatal("cannot find homeagent root")
|
|
return ""
|
|
}
|
|
|
|
func callGetToolDefsJSON(t *testing.T, plg *dllPlugin) []map[string]interface{} {
|
|
t.Helper()
|
|
ret, _, _ := syscall.SyscallN(plg.getTools, plg.handle)
|
|
if ret == 0 {
|
|
t.Fatal("GetToolDefsJSON returned nil")
|
|
}
|
|
jsonStr := cStringPtrToString(ret)
|
|
if plg.freeCStr != 0 {
|
|
syscall.SyscallN(plg.freeCStr, ret)
|
|
}
|
|
var defs []map[string]interface{}
|
|
if err := json.Unmarshal([]byte(jsonStr), &defs); err != nil {
|
|
t.Fatalf("GetToolDefsJSON parse error: %v", err)
|
|
}
|
|
return defs
|
|
}
|
|
|
|
func callInvokeToolJSON(t *testing.T, plg *dllPlugin, toolName string, args map[string]interface{}) map[string]interface{} {
|
|
t.Helper()
|
|
argsJSON, _ := json.Marshal(args)
|
|
cToolName := append([]byte(toolName), 0)
|
|
cArgs := append(argsJSON, 0)
|
|
|
|
ret, _, _ := syscall.SyscallN(
|
|
plg.invokeTool,
|
|
plg.handle,
|
|
uintptr(unsafe.Pointer(&cToolName[0])),
|
|
uintptr(unsafe.Pointer(&cArgs[0])),
|
|
)
|
|
if ret == 0 {
|
|
t.Fatal("InvokeToolJSON returned nil")
|
|
}
|
|
jsonStr := cStringPtrToString(ret)
|
|
if plg.freeCStr != 0 {
|
|
syscall.SyscallN(plg.freeCStr, ret)
|
|
}
|
|
var result map[string]interface{}
|
|
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
|
|
t.Fatalf("InvokeToolJSON parse error: %v (json=%s)", err, jsonStr)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func keysOfMap(m map[string]interface{}) []string {
|
|
var keys []string
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func contains(t *testing.T, s, substr string) bool {
|
|
t.Helper()
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|