toolsrv: fix ReadAt offset for request-response files

After Write(), the file offset is at the end of the written data.
For request-response files (like proc/new), we need to read the
result from offset 0, not from the current offset.

Changed CallTool() to use ReadAt(buf, 0) instead of io.ReadAll()
which uses Read() and inherits the wrong offset.

Same fix was already applied to token reading in Dial().

Added TestIntegration_ToolExecution to verify the full tool
execution path works end-to-end.
This commit is contained in:
Levi Neely 2026-08-10 19:14:09 +02:00
parent 11c67a7fb1
commit c9789de2bd
2 changed files with 65 additions and 3 deletions

View File

@ -148,9 +148,22 @@ func (c *Conn) CallTool(ctx context.Context, name string, args json.RawMessage)
return nil, fmt.Errorf("write: %w", err)
}
result, err := io.ReadAll(fid)
if err != nil {
return nil, fmt.Errorf("read: %w", err)
// Read result from offset 0 (Write advances the file offset, but
// for request-response files we need to read the result from the start)
var result []byte
buf := make([]byte, 8192)
for offset := int64(0); ; {
n, err := fid.ReadAt(buf, offset)
if n > 0 {
result = append(result, buf[:n]...)
offset += int64(n)
}
if err == io.EOF || n == 0 {
break
}
if err != nil {
return nil, fmt.Errorf("read: %w", err)
}
}
return json.RawMessage(result), nil

View File

@ -6,10 +6,12 @@ package toolsrv
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
@ -208,6 +210,53 @@ func TestIntegration_BasicOperations(t *testing.T) {
t.Logf("ListTools returned %d tools", len(tools))
}
func TestIntegration_ToolExecution(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := Dial(socketPath, "test-secret-exec")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
defer conn.Close()
// Load the shell tool
if err := conn.LoadTool("shell"); err != nil {
t.Fatalf("LoadTool failed: %v", err)
}
// Verify it's loaded
tools, err := conn.ListTools()
if err != nil {
t.Fatalf("ListTools failed: %v", err)
}
found := false
for _, tool := range tools {
if tool.Name == "shell" {
found = true
break
}
}
if !found {
t.Fatal("shell tool not found after loading")
}
// Execute a tool
ctx := context.Background()
args := json.RawMessage(`{"cmd": "echo hello from test"}`)
result, err := conn.CallTool(ctx, "shell", args)
if err != nil {
t.Fatalf("CallTool failed: %v", err)
}
// Result should be JSON containing the output
resultStr := string(result)
if !strings.Contains(resultStr, "hello from test") {
t.Errorf("unexpected result: %s", resultStr)
}
t.Logf("Tool result: %s", resultStr)
}
func TestIntegration_ProcessKeeperReconnect(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()