210 lines
5.7 KiB
Go
210 lines
5.7 KiB
Go
package session
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"ollie/agent"
|
|
"ollie/prompts"
|
|
"ollie/toolsrv"
|
|
)
|
|
|
|
// SessionInfra holds the tool server infrastructure for a session.
|
|
// Created by SetupToolServer and used by both CreateAgent and restoreSession.
|
|
type SessionInfra struct {
|
|
Proc *toolsrv.Process
|
|
Keeper *toolsrv.ProcessKeeper
|
|
ToolsConn toolsrv.Runner
|
|
NewToolServer func() toolsrv.Runner
|
|
PromptEnv []string
|
|
}
|
|
|
|
// ToolServerConfig configures tool server spawning.
|
|
type ToolServerConfig struct {
|
|
Ctx context.Context
|
|
CWD string
|
|
RemoteTarget string
|
|
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 *toolsrv.Process
|
|
Keeper *toolsrv.ProcessKeeper
|
|
ToolsConn toolsrv.Runner
|
|
}
|
|
|
|
// 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() toolsrv.Runner
|
|
if r.Keeper != nil {
|
|
newToolServer = func() toolsrv.Runner {
|
|
conn, err := r.Keeper.Dial()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return conn
|
|
}
|
|
} else {
|
|
newToolServer = func() toolsrv.Runner {
|
|
return r.ToolsConn
|
|
}
|
|
}
|
|
// Update CWD if the connection supports it.
|
|
if cwc, ok := r.ToolsConn.(interface{ SetCWD(string) }); ok {
|
|
cwc.SetCWD(cfg.CWD)
|
|
}
|
|
return &SessionInfra{
|
|
Proc: r.Proc,
|
|
Keeper: r.Keeper,
|
|
ToolsConn: newToolServer(),
|
|
NewToolServer: newToolServer,
|
|
PromptEnv: agent.PromptEnv(cfg.CWD),
|
|
}, nil
|
|
}
|
|
var proc *toolsrv.Process
|
|
var keeper *toolsrv.ProcessKeeper
|
|
var newToolServer func() toolsrv.Runner
|
|
var promptEnv []string
|
|
var err error
|
|
|
|
if cfg.RemoteTarget != "" {
|
|
proc, err = toolsrv.SpawnRemote(cfg.Ctx, toolsrv.RemoteConfig{
|
|
SSHTarget: cfg.RemoteTarget,
|
|
CWD: cfg.CWD,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("remote spawn: %w", err)
|
|
}
|
|
keeper = toolsrv.NewProcessKeeper(cfg.Ctx, proc, func(ctx context.Context) (*toolsrv.Process, error) {
|
|
return toolsrv.SpawnRemote(ctx, toolsrv.RemoteConfig{
|
|
SSHTarget: cfg.RemoteTarget,
|
|
CWD: cfg.CWD,
|
|
})
|
|
})
|
|
newToolServer = func() toolsrv.Runner {
|
|
conn, err := keeper.Dial()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return conn
|
|
}
|
|
promptEnv = []string{
|
|
"PRIME_CWD=" + cfg.CWD,
|
|
"PRIME_PLATFORM=" + proc.Info.Platform,
|
|
"PRIME_IS_GIT_REPO=" + fmt.Sprintf("%v", proc.Info.IsGitRepo),
|
|
}
|
|
} else {
|
|
var dialOpts []toolsrv.Option
|
|
if cfg.Yolo {
|
|
dialOpts = append(dialOpts, toolsrv.WithYolo())
|
|
}
|
|
proc, err = toolsrv.Spawn(cfg.Ctx, cfg.CWD, dialOpts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("local spawn: %w", err)
|
|
}
|
|
keeper = toolsrv.NewProcessKeeper(cfg.Ctx, proc, func(ctx context.Context) (*toolsrv.Process, error) {
|
|
return toolsrv.Spawn(ctx, cfg.CWD, dialOpts...)
|
|
})
|
|
newToolServer = func() toolsrv.Runner {
|
|
conn, err := keeper.Dial()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return conn
|
|
}
|
|
promptEnv = agent.PromptEnv(cfg.CWD)
|
|
}
|
|
|
|
toolsConn := newToolServer()
|
|
|
|
return &SessionInfra{
|
|
Proc: proc,
|
|
Keeper: keeper,
|
|
ToolsConn: toolsConn,
|
|
NewToolServer: newToolServer,
|
|
PromptEnv: promptEnv,
|
|
}, 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 string, promptEnv []string, systemPromptOverride string) PromptLayers {
|
|
env := []string{"OLLIE_SESSION_ID=" + sessID, "OLLIE_UNAME=" + uname}
|
|
env = append(env, promptEnv...)
|
|
|
|
spOverride := systemPromptOverride
|
|
if spOverride == "" && cfg != nil {
|
|
spOverride = cfg.SystemPrompt
|
|
}
|
|
sysPrompt := prompts.ResolveSystemPrompt(spOverride)
|
|
|
|
platform := "linux"
|
|
isGitRepo := false
|
|
for _, e := range promptEnv {
|
|
if k, v, ok := strings.Cut(e, "="); ok {
|
|
switch k {
|
|
case "PRIME_PLATFORM":
|
|
platform = v
|
|
case "PRIME_IS_GIT_REPO":
|
|
isGitRepo = v == "true"
|
|
}
|
|
}
|
|
}
|
|
envBlock := prompts.Environment(cwd, platform, isGitRepo, "")
|
|
|
|
return PromptLayers{
|
|
SystemPrompt: sysPrompt,
|
|
EnvBlock: envBlock,
|
|
}
|
|
}
|
|
|
|
// BaseLayers returns the prompt layers as a slice for agent configuration.
|
|
func (p PromptLayers) BaseLayers() []string {
|
|
return []string{p.SystemPrompt, p.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, runner toolsrv.Runner, sessID, uname string, logError func(string, ...any)) {
|
|
if cfg == nil || runner == nil {
|
|
return
|
|
}
|
|
|
|
// Send session ID to the tool server first — ollie-remote uses this
|
|
// to attach its tool registry via the OnEnvSet callback.
|
|
if es, ok := runner.(interface{ SetEnv(string, string) }); ok {
|
|
if sessID != "" {
|
|
es.SetEnv("OLLIE_SESSION_ID", sessID)
|
|
}
|
|
if uname != "" {
|
|
es.SetEnv("OLLIE_UNAME", uname)
|
|
}
|
|
}
|
|
|
|
for _, tl := range cfg.AutoLoad {
|
|
if err := LoadToolOnRunner(runner, tl); err != nil {
|
|
if logError != nil {
|
|
logError("autoLoad tool %q: %v", tl, err)
|
|
}
|
|
}
|
|
}
|
|
}
|