refactor: move olliesrv and toolsrv packages to cmd/*/internal/
Move server-only packages under their respective cmd directories: olliesrv: - agent/ -> cmd/olliesrv/internal/agent/ - backend/ -> cmd/olliesrv/internal/backend/ - bypass/ -> cmd/olliesrv/internal/bypass/ - fs/ -> cmd/olliesrv/internal/fs/ - prompts/ -> cmd/olliesrv/internal/prompts/ - session/ -> cmd/olliesrv/internal/session/ toolsrv: - Server-only code (exec9p, fs9p, server9p, spec9p, auth9p) -> cmd/toolsrv/ - sandbox/ -> cmd/toolsrv/internal/sandbox/ - Keep shared client code (client9p, spawn, registry, meta) in toolsrv/ - Add toolsrv/types.go for shared types (ToolResult, ToolResultContent) This enforces package boundaries - code in cmd/*/internal/ cannot be imported by external packages, while shared code remains importable.
This commit is contained in:
parent
85df48b0c3
commit
1fc051e3bf
|
|
@ -5,7 +5,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"ollie/bypass"
|
||||
"ollie/cmd/olliesrv/internal/bypass"
|
||||
)
|
||||
|
||||
// bypassNotifier handles desktop notifications for bypass requests
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
olog "ollie/log"
|
||||
"ollie/paths"
|
||||
"ollie/toolsrv"
|
||||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
)
|
||||
|
||||
// Prompt holds the agent prompt — a list of file paths to read and concatenate.
|
||||
|
|
@ -3,7 +3,7 @@ package agent
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
)
|
||||
|
||||
// auditTruncate trims s to 200 runes for log output.
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/paths"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/paths"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/prompts"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/cmd/olliesrv/internal/prompts"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
)
|
||||
|
||||
// parseTextToolCalls extracts tool calls from assistant text when the model
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
)
|
||||
|
||||
func mustNewOpenAI(t *testing.T, name, baseURL, apiKey string) *backend.OpenAIBackend {
|
||||
|
|
@ -3,8 +3,8 @@ package fs
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/format"
|
||||
)
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
)
|
||||
|
||||
// ModelCache caches the model list from all backends.
|
||||
|
|
@ -8,9 +8,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/paths"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
)
|
||||
|
||||
func requestAgentNew(ctx HandlerCtx, data []byte) ([]byte, error) {
|
||||
|
|
@ -8,11 +8,11 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
"ollie/bypass"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/cmd/olliesrv/internal/bypass"
|
||||
"ollie/fsedsl"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"ollie/bypass"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/bypass"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
)
|
||||
|
||||
func readSessionIdx(_ HandlerCtx) ([]byte, error) {
|
||||
|
|
@ -3,7 +3,7 @@ package fs
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
)
|
||||
|
||||
// RootState holds the root-level state for the 9P filesystem.
|
||||
|
|
@ -4,10 +4,10 @@ import (
|
|||
"context"
|
||||
"os/user"
|
||||
|
||||
"ollie/bypass"
|
||||
"ollie/cmd/olliesrv/internal/bypass"
|
||||
"ollie/fsedsl"
|
||||
olog "ollie/log"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
)
|
||||
|
||||
// Config carries all runtime dependencies for the filesystem tree.
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
"ollie/format"
|
||||
)
|
||||
|
||||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
|
|
@ -7,8 +7,8 @@ package fs
|
|||
import (
|
||||
"context"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/bypass"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/bypass"
|
||||
"ollie/fsedsl"
|
||||
olog "ollie/log"
|
||||
)
|
||||
|
|
@ -9,8 +9,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
)
|
||||
|
||||
// --- Persistence ---
|
||||
|
|
@ -17,8 +17,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/backend"
|
||||
olog "ollie/log"
|
||||
"ollie/paths"
|
||||
)
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
olog "ollie/log"
|
||||
"ollie/paths"
|
||||
"ollie/toolsrv"
|
||||
|
|
@ -4,9 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/paths"
|
||||
"ollie/prompts"
|
||||
"ollie/cmd/olliesrv/internal/prompts"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
|
|
@ -14,13 +14,13 @@ import (
|
|||
"syscall"
|
||||
|
||||
"9fans.net/go/plan9/client"
|
||||
"ollie/agent"
|
||||
"ollie/bypass"
|
||||
"ollie/cmd/olliesrv/internal/agent"
|
||||
"ollie/cmd/olliesrv/internal/bypass"
|
||||
"ollie/env"
|
||||
"ollie/fs"
|
||||
"ollie/cmd/olliesrv/internal/fs"
|
||||
olog "ollie/log"
|
||||
"ollie/paths"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"ollie/fs"
|
||||
"ollie/cmd/olliesrv/internal/fs"
|
||||
olog "ollie/log"
|
||||
"ollie/session"
|
||||
"ollie/cmd/olliesrv/internal/session"
|
||||
|
||||
"9fans.net/go/plan9"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// auth9p.go - Authentication helpers for 9P toolsrv
|
||||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// exec9p.go - Decoupled tool execution for 9P toolsrv
|
||||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -16,8 +16,9 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"ollie/cmd/toolsrv/internal/sandbox"
|
||||
"ollie/paths"
|
||||
"ollie/sandbox"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
// ExecConfig contains all context needed for tool execution.
|
||||
|
|
@ -29,20 +30,6 @@ type ExecConfig struct {
|
|||
Timeout int
|
||||
}
|
||||
|
||||
// ToolResult is the JSON structure returned by tool execution.
|
||||
type ToolResult struct {
|
||||
Content []ToolResultContent `json:"content"`
|
||||
IsError bool `json:"isError,omitempty"`
|
||||
}
|
||||
|
||||
// ToolResultContent is a single content item in a tool result.
|
||||
type ToolResultContent struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// StreamFunc returns the streaming output function from context, if any.
|
||||
func StreamFunc(ctx context.Context) func(string) {
|
||||
if fn, ok := ctx.Value(streamKey{}).(func(string)); ok {
|
||||
|
|
@ -60,9 +47,9 @@ func WithStreamFunc(ctx context.Context, fn func(string)) context.Context {
|
|||
|
||||
// ExecuteTool runs a tool script with the given args and returns the result.
|
||||
// This is the core execution function, decoupled from Server.
|
||||
func ExecuteTool(ctx context.Context, info ToolInfo, args json.RawMessage, cfg ExecConfig) (json.RawMessage, error) {
|
||||
func ExecuteTool(ctx context.Context, info toolsrv.ToolInfo, args json.RawMessage, cfg ExecConfig) (json.RawMessage, error) {
|
||||
// Resolve script path
|
||||
toolPath, err := ResolveTool(info.Name)
|
||||
toolPath, err := toolsrv.ResolveTool(info.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve tool %s: %w", info.Name, err)
|
||||
}
|
||||
|
|
@ -109,7 +96,7 @@ func ExecuteTool(ctx context.Context, info ToolInfo, args json.RawMessage, cfg E
|
|||
|
||||
// Check if tool requires sudo
|
||||
needsSudo := false
|
||||
if m, err := LoadMetaFile(info.Name); err == nil && m != nil {
|
||||
if m, err := toolsrv.LoadMetaFile(info.Name); err == nil && m != nil {
|
||||
resolved := m.Resolve()
|
||||
if resolved != nil && resolved.Sudo {
|
||||
needsSudo = true
|
||||
|
|
@ -135,13 +122,13 @@ func ExecuteTool(ctx context.Context, info ToolInfo, args json.RawMessage, cfg E
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
return json.Marshal(ToolResult{
|
||||
return json.Marshal(toolsrv.ToolResult{
|
||||
IsError: true,
|
||||
Content: []ToolResultContent{{Type: "text", Text: result + ": " + err.Error()}},
|
||||
Content: []toolsrv.ToolResultContent{{Type: "text", Text: result + ": " + err.Error()}},
|
||||
})
|
||||
}
|
||||
return json.Marshal(ToolResult{
|
||||
Content: []ToolResultContent{{Type: "text", Text: result}},
|
||||
return json.Marshal(toolsrv.ToolResult{
|
||||
Content: []toolsrv.ToolResultContent{{Type: "text", Text: result}},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
// tool=shell
|
||||
// cmd=ls -la
|
||||
// timeout=30
|
||||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -36,6 +36,8 @@ import (
|
|||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
// FS9P implements a 9P-style tool server.
|
||||
|
|
@ -45,7 +47,7 @@ type FS9P struct {
|
|||
|
||||
cwd string
|
||||
env map[string]string
|
||||
registry *Registry
|
||||
registry *toolsrv.Registry
|
||||
sessID string
|
||||
yolo bool
|
||||
|
||||
|
|
@ -88,7 +90,7 @@ func NewFS9P(cwd string) *FS9P {
|
|||
}
|
||||
|
||||
// SetRegistry configures the tool registry and session ID.
|
||||
func (fs *FS9P) SetRegistry(r *Registry, sessID string) {
|
||||
func (fs *FS9P) SetRegistry(r *toolsrv.Registry, sessID string) {
|
||||
fs.mu.Lock()
|
||||
fs.registry = r
|
||||
fs.sessID = sessID
|
||||
|
|
@ -136,7 +138,7 @@ func (fs *FS9P) GetEnv(key string) string {
|
|||
// --- Tool Registry ---
|
||||
|
||||
// ListTools returns loaded tools for the current session.
|
||||
func (fs *FS9P) ListTools() []ToolInfo {
|
||||
func (fs *FS9P) ListTools() []toolsrv.ToolInfo {
|
||||
fs.mu.RLock()
|
||||
reg := fs.registry
|
||||
sid := fs.sessID
|
||||
|
|
@ -299,7 +301,7 @@ func (fs *FS9P) NewProc(ctx context.Context, payload string, background bool) (r
|
|||
}
|
||||
|
||||
// executeTool runs a tool and returns output + exit code.
|
||||
func (fs *FS9P) executeTool(ctx context.Context, info ToolInfo, args map[string]string, cwd string, envExtra map[string]string, yolo bool) (string, int) {
|
||||
func (fs *FS9P) executeTool(ctx context.Context, info toolsrv.ToolInfo, args map[string]string, cwd string, envExtra map[string]string, yolo bool) (string, int) {
|
||||
// Convert args map to JSON for the execution path
|
||||
jsonArgs := argsToJSON(args)
|
||||
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
func TestFS9P_Basic(t *testing.T) {
|
||||
|
|
@ -76,7 +78,7 @@ func TestFS9P_ProcLimit(t *testing.T) {
|
|||
fs.procLimit = 2
|
||||
|
||||
// Create a fake registry and session ID
|
||||
reg, err := NewRegistry()
|
||||
reg, err := toolsrv.NewRegistry()
|
||||
if err != nil {
|
||||
t.Skip("no tools directory available")
|
||||
}
|
||||
|
|
@ -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 toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -14,6 +14,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
var testToolsrvBinary string
|
||||
|
|
@ -95,7 +97,7 @@ func TestIntegration_FirstConnectionSetsSecret(t *testing.T) {
|
|||
|
||||
// First connection with a secret should succeed
|
||||
secret := "test-secret-first-connection"
|
||||
conn, err := Dial(socketPath, secret)
|
||||
conn, err := toolsrv.Dial(socketPath, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("first Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -119,7 +121,7 @@ func TestIntegration_SameSecretReconnects(t *testing.T) {
|
|||
secret := "test-secret-reconnect"
|
||||
|
||||
// First connection establishes the secret
|
||||
conn1, err := Dial(socketPath, secret)
|
||||
conn1, err := toolsrv.Dial(socketPath, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("first Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -127,7 +129,7 @@ func TestIntegration_SameSecretReconnects(t *testing.T) {
|
|||
conn1.Close()
|
||||
|
||||
// Second connection with same secret should succeed
|
||||
conn2, err := Dial(socketPath, secret)
|
||||
conn2, err := toolsrv.Dial(socketPath, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("second Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -144,14 +146,14 @@ func TestIntegration_WrongSecretFails(t *testing.T) {
|
|||
defer cleanup()
|
||||
|
||||
// First connection establishes the secret
|
||||
conn1, err := Dial(socketPath, "correct-secret")
|
||||
conn1, err := toolsrv.Dial(socketPath, "correct-secret")
|
||||
if err != nil {
|
||||
t.Fatalf("first Dial failed: %v", err)
|
||||
}
|
||||
conn1.Close()
|
||||
|
||||
// Second connection with wrong secret should fail
|
||||
conn2, err := Dial(socketPath, "wrong-secret")
|
||||
conn2, err := toolsrv.Dial(socketPath, "wrong-secret")
|
||||
if err == nil {
|
||||
conn2.Close()
|
||||
t.Fatal("Dial with wrong secret should fail")
|
||||
|
|
@ -163,7 +165,7 @@ func TestIntegration_EmptySecretGeneratesOne(t *testing.T) {
|
|||
defer cleanup()
|
||||
|
||||
// Empty secret should generate a random one
|
||||
conn, err := Dial(socketPath, "")
|
||||
conn, err := toolsrv.Dial(socketPath, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Dial with empty secret failed: %v", err)
|
||||
}
|
||||
|
|
@ -184,7 +186,7 @@ func TestIntegration_BasicOperations(t *testing.T) {
|
|||
socketPath, cleanup := startTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
conn, err := Dial(socketPath, "test-secret-ops")
|
||||
conn, err := toolsrv.Dial(socketPath, "test-secret-ops")
|
||||
if err != nil {
|
||||
t.Fatalf("Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -214,14 +216,14 @@ func TestIntegration_BasicOperations(t *testing.T) {
|
|||
|
||||
func TestIntegration_ToolExecution(t *testing.T) {
|
||||
// Skip if tools directory doesn't exist (CI environment)
|
||||
if _, err := os.ReadDir(ToolsPath()); err != nil {
|
||||
if _, err := os.ReadDir(toolsrv.ToolsPath()); err != nil {
|
||||
t.Skipf("skipping: tools directory not available: %v", err)
|
||||
}
|
||||
|
||||
socketPath, cleanup := startTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
conn, err := Dial(socketPath, "test-secret-exec")
|
||||
conn, err := toolsrv.Dial(socketPath, "test-secret-exec")
|
||||
if err != nil {
|
||||
t.Fatalf("Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -257,14 +259,14 @@ func TestIntegration_ToolExecution(t *testing.T) {
|
|||
}
|
||||
|
||||
// Parse the result the same way agent/runtime.go does
|
||||
var toolResult ToolResult
|
||||
var toolResult toolsrv.ToolResult
|
||||
if err := json.Unmarshal(result, &toolResult); err != nil {
|
||||
t.Fatalf("failed to parse ToolResult: %v (raw: %s)", err, string(result))
|
||||
t.Fatalf("failed to parse toolsrv.ToolResult: %v (raw: %s)", err, string(result))
|
||||
}
|
||||
|
||||
// Verify structure
|
||||
if len(toolResult.Content) == 0 {
|
||||
t.Fatal("ToolResult.Content is empty")
|
||||
t.Fatal("toolsrv.ToolResult.Content is empty")
|
||||
}
|
||||
if toolResult.Content[0].Type != "text" {
|
||||
t.Errorf("expected type 'text', got %q", toolResult.Content[0].Type)
|
||||
|
|
@ -273,7 +275,7 @@ func TestIntegration_ToolExecution(t *testing.T) {
|
|||
t.Errorf("output doesn't contain expected text: %q", toolResult.Content[0].Text)
|
||||
}
|
||||
if toolResult.IsError {
|
||||
t.Error("ToolResult.IsError should be false")
|
||||
t.Error("toolsrv.ToolResult.IsError should be false")
|
||||
}
|
||||
|
||||
t.Logf("Tool result: %+v", toolResult)
|
||||
|
|
@ -281,14 +283,14 @@ func TestIntegration_ToolExecution(t *testing.T) {
|
|||
|
||||
func TestIntegration_MultilineContent(t *testing.T) {
|
||||
// Skip if tools directory doesn't exist (CI environment)
|
||||
if _, err := os.ReadDir(ToolsPath()); err != nil {
|
||||
if _, err := os.ReadDir(toolsrv.ToolsPath()); err != nil {
|
||||
t.Skipf("skipping: tools directory not available: %v", err)
|
||||
}
|
||||
|
||||
socketPath, cleanup := startTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
conn, err := Dial(socketPath, "test-secret-multiline")
|
||||
conn, err := toolsrv.Dial(socketPath, "test-secret-multiline")
|
||||
if err != nil {
|
||||
t.Fatalf("Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -312,9 +314,9 @@ func TestIntegration_MultilineContent(t *testing.T) {
|
|||
t.Fatalf("CallTool failed: %v", err)
|
||||
}
|
||||
|
||||
var toolResult ToolResult
|
||||
var toolResult toolsrv.ToolResult
|
||||
if err := json.Unmarshal(result, &toolResult); err != nil {
|
||||
t.Fatalf("failed to parse ToolResult: %v (raw: %s)", err, string(result))
|
||||
t.Fatalf("failed to parse toolsrv.ToolResult: %v (raw: %s)", err, string(result))
|
||||
}
|
||||
|
||||
if toolResult.IsError {
|
||||
|
|
@ -334,14 +336,14 @@ func TestIntegration_MultilineContent(t *testing.T) {
|
|||
|
||||
func TestIntegration_ToolExecutionError(t *testing.T) {
|
||||
// Skip if tools directory doesn't exist (CI environment)
|
||||
if _, err := os.ReadDir(ToolsPath()); err != nil {
|
||||
if _, err := os.ReadDir(toolsrv.ToolsPath()); err != nil {
|
||||
t.Skipf("skipping: tools directory not available: %v", err)
|
||||
}
|
||||
|
||||
socketPath, cleanup := startTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
conn, err := Dial(socketPath, "test-secret-exec-err")
|
||||
conn, err := toolsrv.Dial(socketPath, "test-secret-exec-err")
|
||||
if err != nil {
|
||||
t.Fatalf("Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -361,14 +363,14 @@ func TestIntegration_ToolExecutionError(t *testing.T) {
|
|||
}
|
||||
|
||||
// Parse the result
|
||||
var toolResult ToolResult
|
||||
var toolResult toolsrv.ToolResult
|
||||
if err := json.Unmarshal(result, &toolResult); err != nil {
|
||||
t.Fatalf("failed to parse ToolResult: %v (raw: %s)", err, string(result))
|
||||
t.Fatalf("failed to parse toolsrv.ToolResult: %v (raw: %s)", err, string(result))
|
||||
}
|
||||
|
||||
// Should have exit code in output
|
||||
if len(toolResult.Content) == 0 {
|
||||
t.Fatal("ToolResult.Content is empty")
|
||||
t.Fatal("toolsrv.ToolResult.Content is empty")
|
||||
}
|
||||
if !strings.Contains(toolResult.Content[0].Text, "exit: 42") {
|
||||
t.Errorf("output doesn't contain exit code: %q", toolResult.Content[0].Text)
|
||||
|
|
@ -384,13 +386,13 @@ func TestIntegration_ProcessKeeperReconnect(t *testing.T) {
|
|||
secret := "test-secret-keeper"
|
||||
|
||||
// Create a process manually (simulating what Spawn returns)
|
||||
proc := &Process{
|
||||
proc := &toolsrv.Process{
|
||||
SocketPath: socketPath,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
// Create keeper without respawn (we're testing reconnect, not respawn)
|
||||
keeper := NewProcessKeeper(context.Background(), proc, nil)
|
||||
keeper := toolsrv.NewProcessKeeper(context.Background(), proc, nil)
|
||||
|
||||
// First dial
|
||||
conn1, err := keeper.Dial()
|
||||
|
|
@ -419,7 +421,7 @@ func TestIntegration_ConcurrentConnections(t *testing.T) {
|
|||
secret := "test-secret-concurrent"
|
||||
|
||||
// Establish the secret
|
||||
conn0, err := Dial(socketPath, secret)
|
||||
conn0, err := toolsrv.Dial(socketPath, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("initial Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -428,11 +430,11 @@ func TestIntegration_ConcurrentConnections(t *testing.T) {
|
|||
|
||||
// Open multiple concurrent connections
|
||||
const numConns = 5
|
||||
conns := make([]*Conn, numConns)
|
||||
conns := make([]*toolsrv.Conn, numConns)
|
||||
errors := make([]error, numConns)
|
||||
|
||||
for i := 0; i < numConns; i++ {
|
||||
conns[i], errors[i] = Dial(socketPath, secret)
|
||||
conns[i], errors[i] = toolsrv.Dial(socketPath, secret)
|
||||
}
|
||||
|
||||
// Check all succeeded with same token
|
||||
|
|
@ -461,7 +463,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) {
|
|||
socketPath1, cleanup1 := startTestServer(t)
|
||||
|
||||
secret1 := "secret-for-first-server"
|
||||
conn1, err := Dial(socketPath1, secret1)
|
||||
conn1, err := toolsrv.Dial(socketPath1, secret1)
|
||||
if err != nil {
|
||||
cleanup1()
|
||||
t.Fatalf("first Dial failed: %v", err)
|
||||
|
|
@ -478,7 +480,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 := Dial(socketPath2, secret2)
|
||||
conn2, err := toolsrv.Dial(socketPath2, secret2)
|
||||
if err != nil {
|
||||
t.Fatalf("second Dial failed: %v", err)
|
||||
}
|
||||
|
|
@ -491,18 +493,18 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) {
|
|||
}
|
||||
|
||||
// Old secret should NOT work on new server
|
||||
conn3, err := Dial(socketPath2, secret1)
|
||||
conn3, err := toolsrv.Dial(socketPath2, secret1)
|
||||
if err == nil {
|
||||
conn3.Close()
|
||||
t.Error("old secret should not work on new server")
|
||||
}
|
||||
|
||||
// Verify ProcessKeeper handles this correctly
|
||||
proc := &Process{
|
||||
proc := &toolsrv.Process{
|
||||
SocketPath: socketPath2,
|
||||
Secret: secret2,
|
||||
}
|
||||
keeper := NewProcessKeeper(context.Background(), proc, nil)
|
||||
keeper := toolsrv.NewProcessKeeper(context.Background(), proc, nil)
|
||||
|
||||
conn4, err := keeper.Dial()
|
||||
if err != nil {
|
||||
|
|
@ -62,15 +62,15 @@ func main() {
|
|||
}
|
||||
|
||||
// Create 9P server (secret is established via Tauth, not env var)
|
||||
srv := toolsrv.NewServer9P()
|
||||
srv := NewServer9P()
|
||||
srv.SetYolo(*yolo)
|
||||
if toolReg != nil && sid != "" {
|
||||
srv.SetRegistry(toolReg, sid)
|
||||
}
|
||||
|
||||
// Build the filesystem tree
|
||||
ctx := toolsrv.ToolsrvCtx{Server: srv}
|
||||
tree := fsedsl.BuildTree(toolsrv.ToolsrvSpec(), ctx)
|
||||
ctx := ToolsrvCtx{Server: srv}
|
||||
tree := fsedsl.BuildTree(ToolsrvSpec(), ctx)
|
||||
if tree == nil {
|
||||
fmt.Fprintln(os.Stderr, "error: failed to build filesystem tree")
|
||||
os.Exit(1)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"ollie/fsedsl"
|
||||
"ollie/toolsrv"
|
||||
|
||||
"9fans.net/go/plan9"
|
||||
)
|
||||
|
|
@ -42,12 +41,12 @@ type connState struct {
|
|||
authFids map[uint32]*authFid
|
||||
ctx context.Context
|
||||
uname string
|
||||
srv *toolsrv.Server9P
|
||||
srv *Server9P
|
||||
noAuth bool // skip authentication (for debugging)
|
||||
}
|
||||
|
||||
// serve9P handles a single 9P connection using the fsedsl tree.
|
||||
func serve9P(ctx context.Context, conn net.Conn, tree *fsedsl.Tree, srv *toolsrv.Server9P, noAuth bool) {
|
||||
func serve9P(ctx context.Context, conn net.Conn, tree *fsedsl.Tree, srv *Server9P, noAuth bool) {
|
||||
defer conn.Close()
|
||||
|
||||
cs := &connState{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// server9p.go - State management for 9P toolsrv
|
||||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"ollie/fsedsl"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
// Server9P holds the state for the 9P tool server.
|
||||
|
|
@ -17,7 +18,7 @@ type Server9P struct {
|
|||
|
||||
secret string // set on first auth, verified on subsequent
|
||||
token string // session token returned after auth
|
||||
registry *Registry // tool registry
|
||||
registry *toolsrv.Registry // tool registry
|
||||
sessID string // session ID for registry scoping
|
||||
yolo bool // skip sandbox
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ func NewServer9P() *Server9P {
|
|||
}
|
||||
|
||||
// SetRegistry configures the tool registry and session ID.
|
||||
func (s *Server9P) SetRegistry(r *Registry, sessID string) {
|
||||
func (s *Server9P) SetRegistry(r *toolsrv.Registry, sessID string) {
|
||||
s.mu.Lock()
|
||||
s.registry = r
|
||||
s.sessID = sessID
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// spec9p.go - fsedsl-based namespace specification for toolsrv
|
||||
package toolsrv
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// types.go - Shared types for toolsrv
|
||||
package toolsrv
|
||||
|
||||
// ToolResult is the structured result from a tool execution.
|
||||
type ToolResult struct {
|
||||
Content []ToolResultContent `json:"content"`
|
||||
IsError bool `json:"isError,omitempty"`
|
||||
}
|
||||
|
||||
// ToolResultContent is a single content item in a tool result.
|
||||
type ToolResultContent struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
}
|
||||
Loading…
Reference in New Issue