move toolsrv client into olliesrv

This commit is contained in:
Ollie Agent 2026-08-16 19:07:25 +02:00
parent b321eed99f
commit 57aa906e5f
11 changed files with 56 additions and 60 deletions

View File

@ -4,7 +4,7 @@ import (
"fmt"
"sync"
client "ollie/client"
toolclient "ollie/cmd/olliesrv/internal/toolclient"
"ollie/toolsrv/protocol"
"github.com/godbus/dbus/v5"
@ -25,7 +25,7 @@ type pendingRequest struct {
}
type sessionRef struct {
dialFn func() *client.ToolsrvConn
dialFn func() *toolclient.ToolsrvConn
}
var notifier *bypassNotifier
@ -53,14 +53,14 @@ func initBypassNotifier(conn *dbus.Conn) {
// notifyBypass sends a desktop notification for a bypass request.
// When the user responds, it writes the resolution directly to toolsrvclient.
func notifyBypass(req *protocol.BypassRequest, sessionID string, dialFn func() *client.ToolsrvConn) {
func notifyBypass(req *protocol.BypassRequest, sessionID string, dialFn func() *toolclient.ToolsrvConn) {
if notifier == nil {
return
}
notifier.sendNotification(req, sessionID, dialFn)
}
func (n *bypassNotifier) sendNotification(req *protocol.BypassRequest, sessionID string, dialFn func() *client.ToolsrvConn) {
func (n *bypassNotifier) sendNotification(req *protocol.BypassRequest, sessionID string, dialFn func() *toolclient.ToolsrvConn) {
obj := n.conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
summary := "Elevation Request"

View File

@ -12,8 +12,8 @@ import (
"9fans.net/go/plan9"
p9client "9fans.net/go/plan9/client"
client "ollie/client"
"ollie/cmd/olliesrv/internal/backend"
toolclient "ollie/cmd/olliesrv/internal/toolclient"
olog "ollie/log"
"ollie/util"
)
@ -30,7 +30,7 @@ type Agent struct {
agentsDir string
systemPrompt string // system prompt for /agent reloads
envBlock string // environment block for /agent reloads
newToolServer func() *client.ToolsrvConn
newToolServer func() *toolclient.ToolsrvConn
newBackend func(string) (backend.Backend, error)
currentAction atomic.Pointer[actionHandle]
warnedContext bool
@ -333,7 +333,7 @@ func (ag *Agent) emit(ev Event) {
// SetToolServer updates the tool server connection and factory.
// Used when resuming a paused session that was restored without infra.
func (ag *Agent) SetToolServer(newToolServer func() *client.ToolsrvConn, conn *client.ToolsrvConn) {
func (ag *Agent) SetToolServer(newToolServer func() *toolclient.ToolsrvConn, conn *toolclient.ToolsrvConn) {
ag.newToolServer = newToolServer
if ag.runtime != nil {
ag.runtime.ToolServer = conn
@ -625,7 +625,7 @@ func (ag *Agent) ListModels() []string {
// ToolServer returns the tool execution server, or nil if unavailable.
// Exported for use by the 9P filesystem layer to sync tool registries.
func (ag *Agent) ToolServer() *client.ToolsrvConn {
func (ag *Agent) ToolServer() *toolclient.ToolsrvConn {
return ag.runtime.ToolServer
}
@ -713,7 +713,7 @@ type AgentParams struct {
Cwd string // working directory for tool execution
SystemPrompt string
EnvBlock string
NewToolServer func() *client.ToolsrvConn
NewToolServer func() *toolclient.ToolsrvConn
NewBackend func(string) (backend.Backend, error)
Log *olog.Logger
StartupMsgs []string

View File

@ -10,8 +10,8 @@ import (
"sync"
"time"
client "ollie/client"
"ollie/cmd/olliesrv/internal/backend"
toolclient "ollie/cmd/olliesrv/internal/toolclient"
"ollie/util"
)
@ -599,7 +599,7 @@ func (ag *Agent) execOne(ctx context.Context, call backend.ToolCall) (toolResult
ag.emit(Event{Role: "tool", Name: call.Name, Content: result, OutputFormat: toolOutputFormat(rt, call.Name)})
return toolResult{ToolCallID: call.ID, Name: call.Name, Content: result, IsError: true}, true
}
var rlErr *client.RateLimitedError
var rlErr *toolclient.RateLimitedError
if errors.As(err, &rlErr) {
result = fmt.Sprintf("error: shell is rate-limited — blocked for %v. Do not call shell() again until the block expires. Use other tools or wait.", rlErr.Remaining)
} else {

View File

@ -8,9 +8,9 @@ import (
"sort"
"strings"
client "ollie/client"
"ollie/cmd/olliesrv/internal/backend"
"ollie/cmd/olliesrv/internal/prompts"
toolclient "ollie/cmd/olliesrv/internal/toolclient"
"ollie/toolsrv/protocol"
)
@ -139,7 +139,7 @@ func RenderTools(infos []protocol.ToolInfo) string {
// agents replaces it atomically.
type Runtime struct {
Backend backend.Backend
ToolServer *client.ToolsrvConn
ToolServer *toolclient.ToolsrvConn
Preamble *Preamble
Tools []backend.Tool
ToolMeta map[string]protocol.ToolInfo
@ -161,7 +161,7 @@ func (rt *Runtime) PreambleString() string {
// BuildRuntime constructs a Runtime from a pre-configured tool server and
// optional agent config.
func BuildRuntime(cfg *AgentConfig, srv *client.ToolsrvConn, cwd string, env []string, systemPrompt, envBlock string) *Runtime {
func BuildRuntime(cfg *AgentConfig, srv *toolclient.ToolsrvConn, cwd string, env []string, systemPrompt, envBlock string) *Runtime {
var messages []string
var allToolInfos []protocol.ToolInfo

View File

@ -9,7 +9,6 @@ import (
"sync"
"time"
client "ollie/client"
"ollie/cmd/olliesrv/internal/agent"
"ollie/cmd/olliesrv/internal/backend"
"ollie/cmd/olliesrv/internal/toolclient"
@ -39,8 +38,8 @@ type Session struct {
// Mutable state protected by mu
mu sync.RWMutex
name string // friendly name
toolsConn *client.ToolsrvConn // multiplexed RPC connection
name string // friendly name
toolsConn *toolclient.ToolsrvConn // multiplexed RPC connection
paused bool
// Autosave
@ -85,7 +84,7 @@ func (s *Session) SetName(name string) {
}
// ToolsConn returns the tool server connection.
func (s *Session) ToolsConn() *client.ToolsrvConn {
func (s *Session) ToolsConn() *toolclient.ToolsrvConn {
s.mu.RLock()
defer s.mu.RUnlock()
return s.toolsConn
@ -93,7 +92,7 @@ func (s *Session) ToolsConn() *client.ToolsrvConn {
// DialToolServer dials a fresh toolsrv connection via the keeper.
// Caller is responsible for closing the returned conn.
func (s *Session) DialToolServer() *client.ToolsrvConn {
func (s *Session) DialToolServer() *toolclient.ToolsrvConn {
s.mu.RLock()
k := s.Keeper
s.mu.RUnlock()
@ -108,7 +107,7 @@ func (s *Session) DialToolServer() *client.ToolsrvConn {
}
// SetToolsConn sets the tool server connection.
func (s *Session) SetToolsConn(conn *client.ToolsrvConn) {
func (s *Session) SetToolsConn(conn *toolclient.ToolsrvConn) {
s.mu.Lock()
defer s.mu.Unlock()
s.toolsConn = conn
@ -116,7 +115,7 @@ func (s *Session) SetToolsConn(conn *client.ToolsrvConn) {
// BypassNotifyFunc is called when a bypass request arrives.
// It receives the request, session ID, and a dial function to get a connection for resolution.
type BypassNotifyFunc func(req *protocol.BypassRequest, sessionID string, dialFn func() *client.ToolsrvConn)
type BypassNotifyFunc func(req *protocol.BypassRequest, sessionID string, dialFn func() *toolclient.ToolsrvConn)
// StartBypassLoop starts a goroutine that reads bypass requests from toolsrv
// and calls notifyFn for each one. This should be called after SetToolsConn.
@ -327,7 +326,7 @@ func (s *Session) Resume() error {
if err != nil {
return fmt.Errorf("resume agent dial: %w", err)
}
ag.SetToolServer(func() *client.ToolsrvConn {
ag.SetToolServer(func() *toolclient.ToolsrvConn {
c, _ := s.Keeper.Dial()
return c
}, agConn)
@ -393,7 +392,7 @@ func (s *Session) LoadTool(name string, ag *agent.Agent) error {
}
// LoadToolOnConn loads a tool on the given connection.
func LoadToolOnConn(conn *client.ToolsrvConn, name string) error {
func LoadToolOnConn(conn *toolclient.ToolsrvConn, name string) error {
if conn == nil {
return nil
}

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
client "ollie/client"
"ollie/cmd/olliesrv/internal/agent"
"ollie/cmd/olliesrv/internal/prompts"
"ollie/cmd/olliesrv/internal/toolclient"
@ -16,8 +15,8 @@ import (
type SessionInfra struct {
Proc *toolclient.Process
Keeper *toolclient.ProcessKeeper
ToolsConn *client.ToolsrvConn
NewToolServer func() *client.ToolsrvConn
ToolsConn *toolclient.ToolsrvConn
NewToolServer func() *toolclient.ToolsrvConn
Platform string
IsGitRepo bool
}
@ -39,7 +38,7 @@ type ToolServerConfig struct {
type InfraConfig struct {
Proc *toolclient.Process
Keeper *toolclient.ProcessKeeper
ToolsConn *client.ToolsrvConn
ToolsConn *toolclient.ToolsrvConn
}
// SetupToolServer spawns the tool server process and creates the infrastructure.
@ -50,9 +49,9 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) {
// Reuse existing infrastructure if provided.
if cfg.ReuseFrom != nil {
r := cfg.ReuseFrom
var newToolServer func() *client.ToolsrvConn
var newToolServer func() *toolclient.ToolsrvConn
if r.Keeper != nil {
newToolServer = func() *client.ToolsrvConn {
newToolServer = func() *toolclient.ToolsrvConn {
conn, err := r.Keeper.Dial()
if err != nil {
return nil
@ -60,7 +59,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) {
return conn
}
} else {
newToolServer = func() *client.ToolsrvConn {
newToolServer = func() *toolclient.ToolsrvConn {
return r.ToolsConn
}
}
@ -79,7 +78,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) {
}
var proc *toolclient.Process
var keeper *toolclient.ProcessKeeper
var newToolServer func() *client.ToolsrvConn
var newToolServer func() *toolclient.ToolsrvConn
var err error
var platform string
var isGitRepo bool
@ -102,7 +101,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) {
Yolo: cfg.Yolo,
})
})
newToolServer = func() *client.ToolsrvConn {
newToolServer = func() *toolclient.ToolsrvConn {
conn, err := keeper.Dial()
if err != nil {
return nil
@ -126,7 +125,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) {
keeper = toolclient.NewProcessKeeper(cfg.Ctx, proc, func(ctx context.Context) (*toolclient.Process, error) {
return toolclient.Spawn(ctx, cfg.CWD, dialOpts...)
})
newToolServer = func() *client.ToolsrvConn {
newToolServer = func() *toolclient.ToolsrvConn {
conn, err := keeper.Dial()
if err != nil {
return nil
@ -173,7 +172,7 @@ func BuildPromptLayers(cfg *agent.AgentConfig, cwd, sessID, uname, platform stri
// LoadAutoLoadTools loads all tools from the agent config's autoLoad list.
// It first sends the session ID and uname to the tool server so that
// ollie-remote can attach its tool registry before processing tool_load RPCs.
func LoadAutoLoadTools(cfg *agent.AgentConfig, conn *client.ToolsrvConn, sessID, uname string, logError func(string, ...any)) {
func LoadAutoLoadTools(cfg *agent.AgentConfig, conn *toolclient.ToolsrvConn, sessID, uname string, logError func(string, ...any)) {
if cfg == nil || conn == nil {
return
}

View File

@ -17,7 +17,6 @@ import (
"syscall"
"time"
client "ollie/client"
"ollie/util"
)
@ -316,13 +315,13 @@ func (pk *ProcessKeeper) SetContext(ctx context.Context) {
}
// Dial connects to the managed process, respawning if necessary.
func (pk *ProcessKeeper) Dial() (*client.ToolsrvConn, error) {
func (pk *ProcessKeeper) Dial() (*ToolsrvConn, error) {
pk.mu.Lock()
defer pk.mu.Unlock()
// Try to connect to existing process
if pk.proc != nil {
conn, err := client.DialToolsrv(pk.proc.SocketPath, pk.proc.Secret)
conn, err := DialToolsrv(pk.proc.SocketPath, pk.proc.Secret)
if err == nil {
// Save the secret if this was first connection
if pk.proc.Secret == "" {
@ -343,7 +342,7 @@ func (pk *ProcessKeeper) Dial() (*client.ToolsrvConn, error) {
return nil, fmt.Errorf("respawn: %w", err)
}
pk.proc = proc
conn, err := client.DialToolsrv(proc.SocketPath, proc.Secret)
conn, err := DialToolsrv(proc.SocketPath, proc.Secret)
if err != nil {
return nil, err
}

View File

@ -1,5 +1,5 @@
// client9p.go - 9P client for toolsrvclient.
package client
package toolclient
import (
"context"

View File

@ -2,7 +2,7 @@
//
// These tests build and spawn a real toolsrv process, then connect via 9P
// to verify the authentication flow works correctly.
package main
package toolclient
import (
"context"
@ -15,7 +15,6 @@ import (
"testing"
"time"
client "ollie/client"
"ollie/toolsrv/metadata"
"ollie/toolsrv/protocol"
)
@ -142,7 +141,7 @@ func TestIntegration_FirstConnectionSetsSecret(t *testing.T) {
// First connection with a secret should succeed
secret := "test-secret-first-connection"
conn, err := client.DialToolsrv(socketPath, secret)
conn, err := DialToolsrv(socketPath, secret)
if err != nil {
t.Fatalf("first Dial failed: %v", err)
}
@ -166,7 +165,7 @@ func TestIntegration_SameSecretReconnects(t *testing.T) {
secret := "test-secret-reconnect"
// First connection establishes the secret
conn1, err := client.DialToolsrv(socketPath, secret)
conn1, err := DialToolsrv(socketPath, secret)
if err != nil {
t.Fatalf("first Dial failed: %v", err)
}
@ -174,7 +173,7 @@ func TestIntegration_SameSecretReconnects(t *testing.T) {
conn1.Close()
// Second connection with same secret should succeed
conn2, err := client.DialToolsrv(socketPath, secret)
conn2, err := DialToolsrv(socketPath, secret)
if err != nil {
t.Fatalf("second Dial failed: %v", err)
}
@ -191,14 +190,14 @@ func TestIntegration_WrongSecretFails(t *testing.T) {
defer cleanup()
// First connection establishes the secret
conn1, err := client.DialToolsrv(socketPath, "correct-secret")
conn1, err := DialToolsrv(socketPath, "correct-secret")
if err != nil {
t.Fatalf("first Dial failed: %v", err)
}
conn1.Close()
// Second connection with wrong secret should fail
conn2, err := client.DialToolsrv(socketPath, "wrong-secret")
conn2, err := DialToolsrv(socketPath, "wrong-secret")
if err == nil {
conn2.Close()
t.Fatal("Dial with wrong secret should fail")
@ -210,7 +209,7 @@ func TestIntegration_EmptySecretGeneratesOne(t *testing.T) {
defer cleanup()
// Empty secret should generate a random one
conn, err := client.DialToolsrv(socketPath, "")
conn, err := DialToolsrv(socketPath, "")
if err != nil {
t.Fatalf("Dial with empty secret failed: %v", err)
}
@ -231,7 +230,7 @@ func TestIntegration_BasicOperations(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := client.DialToolsrv(socketPath, "test-secret-ops")
conn, err := DialToolsrv(socketPath, "test-secret-ops")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
@ -269,7 +268,7 @@ func TestIntegration_ToolExecution(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := client.DialToolsrv(socketPath, "test-secret-exec")
conn, err := DialToolsrv(socketPath, "test-secret-exec")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
@ -337,7 +336,7 @@ func TestIntegration_MultilineContent(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := client.DialToolsrv(socketPath, "test-secret-multiline")
conn, err := DialToolsrv(socketPath, "test-secret-multiline")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
@ -391,7 +390,7 @@ func TestIntegration_ToolExecutionError(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := client.DialToolsrv(socketPath, "test-secret-exec-err")
conn, err := DialToolsrv(socketPath, "test-secret-exec-err")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
@ -438,7 +437,7 @@ func TestIntegration_ConcurrentConnections(t *testing.T) {
secret := "test-secret-concurrent"
// Establish the secret
conn0, err := client.DialToolsrv(socketPath, secret)
conn0, err := DialToolsrv(socketPath, secret)
if err != nil {
t.Fatalf("initial Dial failed: %v", err)
}
@ -447,11 +446,11 @@ func TestIntegration_ConcurrentConnections(t *testing.T) {
// Open multiple concurrent connections
const numConns = 5
conns := make([]*client.ToolsrvConn, numConns)
conns := make([]*ToolsrvConn, numConns)
errors := make([]error, numConns)
for i := 0; i < numConns; i++ {
conns[i], errors[i] = client.DialToolsrv(socketPath, secret)
conns[i], errors[i] = DialToolsrv(socketPath, secret)
}
// Check all succeeded with same token
@ -480,7 +479,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) {
socketPath1, cleanup1 := startTestServer(t)
secret1 := "secret-for-first-server"
conn1, err := client.DialToolsrv(socketPath1, secret1)
conn1, err := DialToolsrv(socketPath1, secret1)
if err != nil {
cleanup1()
t.Fatalf("first Dial failed: %v", err)
@ -497,7 +496,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) {
// New server should accept a new secret (any secret, since it's fresh)
secret2 := "secret-for-second-server"
conn2, err := client.DialToolsrv(socketPath2, secret2)
conn2, err := DialToolsrv(socketPath2, secret2)
if err != nil {
t.Fatalf("second Dial failed: %v", err)
}
@ -510,7 +509,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) {
}
// Old secret should NOT work on new server
conn3, err := client.DialToolsrv(socketPath2, secret1)
conn3, err := DialToolsrv(socketPath2, secret1)
if err == nil {
conn3.Close()
t.Error("old secret should not work on new server")
@ -531,7 +530,7 @@ func TestIntegration_ToolCancellation(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := client.DialToolsrv(socketPath, "test-secret-cancel")
conn, err := DialToolsrv(socketPath, "test-secret-cancel")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
@ -597,7 +596,7 @@ func TestIntegration_ToolCancellation_ContextPropagation(t *testing.T) {
socketPath, cleanup := startTestServer(t)
defer cleanup()
conn, err := client.DialToolsrv(socketPath, "test-secret-cancel-prop")
conn, err := DialToolsrv(socketPath, "test-secret-cancel-prop")
if err != nil {
t.Fatalf("Dial failed: %v", err)
}

View File

@ -275,7 +275,7 @@ test-core:
test-9p:
@echo === Testing ollie-9p ===
go test ./client/...
go test ./cmd/ollie-9p/... ./kde/lib9p/...
test-remote:
@echo === Testing toolsrv ===

Binary file not shown.