fix(sdk): declare only callable tool handlers

This commit is contained in:
xiexian committed 2026-10-02 23:45:31 +08:00
1 parent 77990ed9eb
commit 9aa53444c7
3 files changed
+124 -2

No files matched your search

@@ -0,0 +1,101 @@
package sidecar
import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"reasonix/internal/base/testenv"
"reasonix/internal/ext/extension/protocol"
"reasonix/internal/ext/pluginpkg"
)
func TestSDKNilToolsDoNotExceedManifest(t *testing.T) {
sdk, err := filepath.Abs(filepath.Join("..", "..", "..", "..", "sdk", "go"))
if err != nil {
t.Fatal(err)
}
source := testenv.TempDir(t)
main := filepath.Join(source, "main.go")
if err := os.WriteFile(main, []byte(`package main
import (
"context"
"encoding/json"
"os"
extension "github.com/esengine/DeepSeek-Reasonix/sdk/go"
)
type handler struct{}
func (handler) Initialize(context.Context, extension.InitializeParams) (*extension.InitializeResult, error) {
return &extension.InitializeResult{}, nil
}
func main() {
err := extension.Serve(context.Background(), handler{}, extension.Options{
Name: "nil-tools", Version: "0.1.0",
Tools: map[string]extension.ToolFunc{
"lookup": func(_ context.Context, args json.RawMessage) (string, error) { return string(args), nil },
"disabled": nil,
},
})
if err != nil { os.Exit(1) }
}
`), 0o644); err != nil {
t.Fatal(err)
}
binary := filepath.Join(source, "nil-tools.exe")
ctx, cancel := context.WithTimeout(t.Context(), time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, "go", "build", "-o", binary, main)
cmd.Dir = sdk
cmd.Env = append(os.Environ(), "GOWORK=off", "GOTOOLCHAIN=local", "GOPROXY=off")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("build SDK tool fixture: %v\n%s", err, out)
}
if err := os.WriteFile(filepath.Join(source, pluginpkg.NativeManifest), []byte(`{
"apiVersion": "reasonix.io/plugin/v2",
"name": "nil-tools",
"version": "0.1.0",
"contributes": {},
"runtime": {
"command": "${REASONIX_PLUGIN_ROOT}/nil-tools.exe",
"capabilities": ["tools"],
"tools": [{"name": "lookup", "description": "Echo arguments", "inputSchema": {"type": "object"}}]
}
}`), 0o644); err != nil {
t.Fatal(err)
}
pkg, _, err := pluginpkg.ParseDir(source)
if err != nil {
t.Fatal(err)
}
installed := pluginpkg.InstalledPlugin{Name: "nil-tools", Version: "0.1.0", Enabled: true, Root: source}
client, err := StartClient(t.Context(), ClientOptions{Package: pkg, Installed: installed, Session: testSessionContext()})
if err != nil {
t.Fatalf("nil callback prevented the SDK's served tool from starting: %v", err)
}
defer func() {
if err := client.Close(); err != nil {
t.Error(err)
}
waitFor(t, "SDK tool fixture process exit", 10*time.Second, client.Exited)
}()
if tools := client.Handshake().Tools; len(tools) != 1 || tools[0] != "lookup" || client.ServesTool("disabled") {
t.Fatalf("served tools = %v, want only lookup", tools)
}
result, err := client.CallTool(t.Context(), "lookup", json.RawMessage(`{"q":"hello"}`), 5*time.Second)
if err != nil || result.IsError || result.Content != `{"q":"hello"}` {
t.Fatalf("served SDK tool = %+v, %v", result, err)
}
_, err = client.CallTool(t.Context(), "disabled", nil, 5*time.Second)
if err == nil || protocolReason(t, err) != protocol.ErrUnknownMethod {
t.Fatalf("nil SDK tool callback = %v, want unknown_method", err)
}
}
+4 -2
View File
@@ -19,8 +19,10 @@ func (s *server) declareTools(result *InitializeResult) {
if len(result.Tools) > 0 || len(s.opts.Tools) == 0 {
return
}
for name := range s.opts.Tools {
result.Tools = append(result.Tools, name)
for name, run := range s.opts.Tools {
if run != nil {
result.Tools = append(result.Tools, name)
}
}
slices.Sort(result.Tools)
}
+19
View File
@@ -18,6 +18,7 @@ func TestToolsAreDeclaredAndServed(t *testing.T) {
"broken": func(context.Context, json.RawMessage) (string, error) {
return "", errors.New("the index is not built yet")
},
"disabled": nil,
}
host, _ := startFakeHost(t, basicHandler(), Options{Tools: tools})
init := host.handshake(t)
@@ -38,4 +39,22 @@ func TestToolsAreDeclaredAndServed(t *testing.T) {
if resp.Err == nil || resp.Err.Code != CodeMethodNotFound {
t.Fatalf("an unserved tool = %+v, want unknown_method", resp.Err)
}
resp = host.request(MethodExtensionToolCall, ToolCallParams{Name: "disabled", Arguments: json.RawMessage(`{}`)})
if resp.Err == nil || resp.Err.Code != CodeMethodNotFound {
t.Fatalf("a nil tool handler = %+v, want unknown_method", resp.Err)
}
}
func TestNilToolHandlersDoNotDeclareTools(t *testing.T) {
for _, tools := range []map[string]ToolFunc{nil, {}, {"disabled": nil}} {
host, _ := startFakeHost(t, basicHandler(), Options{Tools: tools})
init := host.handshake(t)
if len(init.Tools) != 0 {
t.Fatalf("nil or empty handlers declared tools: %v", init.Tools)
}
resp := host.request(MethodExtensionToolCall, ToolCallParams{Name: "disabled", Arguments: json.RawMessage(`{}`)})
if resp.Err == nil || resp.Err.Code != CodeMethodNotFound {
t.Fatalf("a nil tool handler = %+v, want unknown_method", resp.Err)
}
}
}