ollie/cmd/olliesrv/internal/session/setup.go

198 lines
5.5 KiB
Go

package session
import (
"context"
"fmt"
"ollie/cmd/olliesrv/internal/agent"
"ollie/cmd/olliesrv/internal/prompts"
"ollie/cmd/olliesrv/internal/toolclient"
toolsrvclient "ollie/toolsrv/client"
"ollie/util"
)
// SessionInfra holds the tool server infrastructure for a session.
// Created by SetupToolServer and used by both CreateAgent and restoreSession.
type SessionInfra struct {
Proc *toolclient.Process
Keeper *toolclient.ProcessKeeper
ToolsConn *toolsrvclient.Conn
NewToolServer func() *toolsrvclient.Conn
Platform string
IsGitRepo bool
}
// ToolServerConfig configures tool server spawning.
type ToolServerConfig struct {
Ctx context.Context
CWD string
RemoteTarget string
SessionID string // session ID for tool registry
Yolo bool
// ReuseFrom, if non-nil, reuses existing infrastructure instead of spawning.
// Used when a session already has a tool server from Create().
ReuseFrom *InfraConfig
}
// InfraConfig describes existing infrastructure to reuse.
type InfraConfig struct {
Proc *toolclient.Process
Keeper *toolclient.ProcessKeeper
ToolsConn *toolsrvclient.Conn
}
// SetupToolServer spawns the tool server process and creates the infrastructure.
// For remote targets, it spawns via SSH. For local, it spawns a local process.
// If ReuseInfra is set, reuses existing infrastructure instead of spawning.
// Returns the infrastructure or an error.
func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) {
// Reuse existing infrastructure if provided.
if cfg.ReuseFrom != nil {
r := cfg.ReuseFrom
var newToolServer func() *toolsrvclient.Conn
if r.Keeper != nil {
newToolServer = func() *toolsrvclient.Conn {
conn, err := r.Keeper.Dial()
if err != nil {
return nil
}
return conn
}
} else {
newToolServer = func() *toolsrvclient.Conn {
return r.ToolsConn
}
}
// Update CWD on the reused connection.
if r.ToolsConn != nil {
r.ToolsConn.SetCWD(cfg.CWD)
}
return &SessionInfra{
Proc: r.Proc,
Keeper: r.Keeper,
ToolsConn: newToolServer(),
NewToolServer: newToolServer,
Platform: "linux",
IsGitRepo: util.IsGitRepo(cfg.CWD),
}, nil
}
var proc *toolclient.Process
var keeper *toolclient.ProcessKeeper
var newToolServer func() *toolsrvclient.Conn
var err error
var platform string
var isGitRepo bool
if cfg.RemoteTarget != "" {
proc, err = toolclient.SpawnRemote(cfg.Ctx, toolclient.RemoteConfig{
SSHTarget: cfg.RemoteTarget,
CWD: cfg.CWD,
SessionID: cfg.SessionID,
Yolo: cfg.Yolo,
})
if err != nil {
return nil, fmt.Errorf("remote spawn: %w", err)
}
keeper = toolclient.NewProcessKeeper(cfg.Ctx, proc, func(ctx context.Context) (*toolclient.Process, error) {
return toolclient.SpawnRemote(ctx, toolclient.RemoteConfig{
SSHTarget: cfg.RemoteTarget,
CWD: cfg.CWD,
SessionID: cfg.SessionID,
Yolo: cfg.Yolo,
})
})
newToolServer = func() *toolsrvclient.Conn {
conn, err := keeper.Dial()
if err != nil {
return nil
}
return conn
}
platform = proc.Info.Platform
isGitRepo = proc.Info.IsGitRepo
} else {
var dialOpts []toolclient.Option
if cfg.Yolo {
dialOpts = append(dialOpts, toolclient.WithYolo())
}
if cfg.SessionID != "" {
dialOpts = append(dialOpts, toolclient.WithSessionID(cfg.SessionID))
}
proc, err = toolclient.Spawn(cfg.Ctx, cfg.CWD, dialOpts...)
if err != nil {
return nil, fmt.Errorf("local spawn: %w", err)
}
keeper = toolclient.NewProcessKeeper(cfg.Ctx, proc, func(ctx context.Context) (*toolclient.Process, error) {
return toolclient.Spawn(ctx, cfg.CWD, dialOpts...)
})
newToolServer = func() *toolsrvclient.Conn {
conn, err := keeper.Dial()
if err != nil {
return nil
}
return conn
}
platform = "linux"
isGitRepo = util.IsGitRepo(cfg.CWD)
}
toolsConn := newToolServer()
return &SessionInfra{
Proc: proc,
Keeper: keeper,
ToolsConn: toolsConn,
NewToolServer: newToolServer,
Platform: platform,
IsGitRepo: isGitRepo,
}, nil
}
// PromptLayers holds the resolved prompt components for an agent.
type PromptLayers struct {
SystemPrompt string
EnvBlock string
}
// BuildPromptLayers resolves system prompt, operational model, and environment block.
func BuildPromptLayers(cfg *agent.AgentConfig, cwd, sessID, uname, platform string, isGitRepo bool, systemPromptOverride string) PromptLayers {
spOverride := systemPromptOverride
if spOverride == "" && cfg != nil {
spOverride = cfg.SystemPrompt
}
sysPrompt := prompts.ResolveSystemPrompt(spOverride)
envBlock := agent.EnvironmentBlock(cwd, platform, isGitRepo, "")
return PromptLayers{
SystemPrompt: sysPrompt,
EnvBlock: envBlock,
}
}
// 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 *toolsrvclient.Conn, sessID, uname string, logError func(string, ...any)) {
if cfg == nil || conn == nil {
return
}
// Send session ID to the tool server first — ollie-remote uses this
// to attach its tool registry via the OnEnvSet callback.
if sessID != "" {
conn.SetEnv("OLLIE_SESSION_ID", sessID)
}
if uname != "" {
conn.SetAgentID(uname)
}
for _, tl := range cfg.AutoLoad {
if err := LoadToolOnConn(conn, tl); err != nil {
if logError != nil {
logError("autoLoad tool %q: %v", tl, err)
}
}
}
}