ollie/cmd/olliesrv/internal/agent/commands.go

99 lines
2.4 KiB
Go

// commands.go — Slash command interception.
//
// Intercepts /commands in user prompts before they reach the LLM. Each
// slash command maps to a ctl write: /compact → "compact", /clear → "clear".
// Returns true if the input was consumed as a command.
package agent
import (
"context"
"io"
"os"
"strings"
"9fans.net/go/plan9"
p9client "9fans.net/go/plan9/client"
)
// HandleCommand intercepts /commands before they reach the LLM.
// Each slash command is a rdwr to the agent's ctl file: /compact → write "compact" to ctl.
// Returns true if the input was consumed as a command.
func (ag *Agent) HandleCommand(_ context.Context, input string) bool {
if !strings.HasPrefix(input, "/") {
return false
}
rest := strings.TrimSpace(input[1:])
if rest == "" {
return false
}
if strings.HasPrefix(rest, "compactionmodel ") {
value := strings.TrimSpace(strings.TrimPrefix(rest, "compactionmodel"))
rest = "compactionmodel=" + value
}
ns := os.Getenv("NAMESPACE")
if ns == "" {
ns = p9client.Namespace()
}
user := os.Getenv("USER")
if user == "" {
user = "none"
}
aname := os.Getenv("OLLIE_UNAME")
if aname == "" {
aname = user
}
conn, err := p9client.Dial("unix", ns+"/ollie")
if err != nil {
ag.emit(infoEvent("error: " + err.Error()))
return true
}
fsys, err := conn.Attach(nil, user, aname)
if err != nil {
conn.Close()
ag.emit(infoEvent("error: " + err.Error()))
return true
}
defer fsys.Close()
ctlPath := "session/" + ag.sessionID + "/agent/" + ag.id + "/ctl"
fid, err := fsys.Open(ctlPath, plan9.ORDWR)
if err == nil {
_, err = fid.Write([]byte(rest))
}
var out []byte
if err == nil {
_, err = fid.Seek(0, 0)
}
if err == nil {
out, err = io.ReadAll(fid)
}
if fid != nil {
fid.Close()
}
if err != nil {
ag.emit(infoEvent("error: " + err.Error()))
return true
}
if len(out) > 0 {
ag.emit(infoEvent(strings.TrimRight(string(out), "\n")))
}
return true
}
// Inject sets a pending inject prompt. If one is already pending, queues it.
func (ag *Agent) Inject(prompt string) {
if !ag.pendingInject.CompareAndSwap(nil, &prompt) {
ag.fifo.Push(prompt)
return
}
ag.emit(Event{Role: "info", Content: "\n"})
ag.emit(Event{Role: "user", Content: prompt})
}
// InjectRewrite replaces any pending inject with the given prompt.
func (ag *Agent) InjectRewrite(prompt string) {
ag.pendingInject.Store(&prompt)
ag.emit(Event{Role: "info", Content: "\n"})
ag.emit(Event{Role: "user", Content: prompt})
}