Files
zero/internal/sessions/exec_session.go
Vasanth T a710b0e5f5 feat: add Go headless protocol sessions (#60)
* feat: add Go headless protocol sessions

* test: harden stream-json tool list filtering

* fix: resolve stream-json review followups

* chore: enable coderabbit review verdicts
2026-06-05 10:41:38 +05:30

206 lines
4.7 KiB
Go

package sessions
import (
"encoding/json"
"fmt"
"strings"
)
type ExecMode string
const (
ModeNew ExecMode = "new"
ModeResume ExecMode = "resume"
ModeFork ExecMode = "fork"
)
type ExecError struct {
message string
}
func (err ExecError) Error() string {
return err.message
}
type PrepareExecOptions struct {
Store *Store
SessionID string
Title string
Cwd string
ModelID string
Provider string
Resume string
ResumeLatest bool
Fork string
}
type PreparedExec struct {
Mode ExecMode
Session Metadata
ContextEvents []Event
Store *Store
}
func PrepareExec(options PrepareExecOptions) (PreparedExec, error) {
resumeID := strings.TrimSpace(options.Resume)
forkID := strings.TrimSpace(options.Fork)
if (resumeID != "" || options.ResumeLatest) && forkID != "" {
return PreparedExec{}, ExecError{"Use either --resume or --fork, not both."}
}
store := options.Store
if store == nil {
store = NewStore(StoreOptions{})
}
if forkID != "" {
parent, err := store.Get(forkID)
if err != nil {
return PreparedExec{}, err
}
if parent == nil {
return PreparedExec{}, ExecError{"Zero session not found: " + forkID}
}
contextEvents, err := store.ReadEvents(parent.SessionID)
if err != nil {
return PreparedExec{}, err
}
session, err := store.Fork(parent.SessionID, ForkInput{
SessionID: options.SessionID,
Title: firstNonEmpty(options.Title, forkTitle(parent.Title)),
Cwd: options.Cwd,
ModelID: options.ModelID,
Provider: options.Provider,
})
if err != nil {
return PreparedExec{}, err
}
return PreparedExec{Mode: ModeFork, Session: session, ContextEvents: contextEvents, Store: store}, nil
}
if resumeID != "" || options.ResumeLatest {
sessionID := resumeID
if sessionID == "" && options.ResumeLatest {
latest, err := store.Latest()
if err != nil {
return PreparedExec{}, err
}
if latest == nil {
return PreparedExec{}, ExecError{"No Zero sessions available to resume."}
}
sessionID = latest.SessionID
}
session, err := store.Get(sessionID)
if err != nil {
return PreparedExec{}, err
}
if session == nil {
return PreparedExec{}, ExecError{"Zero session not found: " + sessionID}
}
contextEvents, err := store.ReadEvents(session.SessionID)
if err != nil {
return PreparedExec{}, err
}
return PreparedExec{Mode: ModeResume, Session: *session, ContextEvents: contextEvents, Store: store}, nil
}
session, err := store.Create(CreateInput{
SessionID: options.SessionID,
Title: options.Title,
Cwd: options.Cwd,
ModelID: options.ModelID,
Provider: options.Provider,
})
if err != nil {
return PreparedExec{}, err
}
return PreparedExec{Mode: ModeNew, Session: session, ContextEvents: []Event{}, Store: store}, nil
}
func FormatExecPrompt(prompt string, prepared PreparedExec) string {
if prepared.Mode == ModeNew || len(prepared.ContextEvents) == 0 {
return prompt
}
events := prepared.ContextEvents
if len(events) > 20 {
events = events[len(events)-20:]
}
lines := []string{}
for _, event := range events {
lines = append(lines, fmt.Sprintf("- #%d %s: %s", event.Sequence, event.Type, summarizePayload(event.Payload)))
}
label := "Continuing"
sessionID := prepared.Session.SessionID
if prepared.Mode == ModeFork {
label = "Forked from"
if prepared.Session.ParentSessionID != "" {
sessionID = prepared.Session.ParentSessionID
}
}
return strings.Join([]string{
fmt.Sprintf("%s Zero session %s.", label, sessionID),
"Previous session context:",
strings.Join(lines, "\n"),
"",
"Current user request:",
prompt,
}, "\n")
}
func forkTitle(title string) string {
if title == "" {
return ""
}
return title + " (fork)"
}
func summarizePayload(payload any) string {
text := extractText(payload)
text = strings.Join(strings.Fields(text), " ")
if text == "" {
data, err := json.Marshal(payload)
if err != nil {
return "{}"
}
text = string(data)
}
if len(text) > 500 {
return text[:500]
}
return text
}
func extractText(value any) string {
switch typed := value.(type) {
case string:
return typed
case json.RawMessage:
var decoded any
if err := json.Unmarshal(typed, &decoded); err == nil {
return extractText(decoded)
}
return string(typed)
case float64, bool, int:
return fmt.Sprint(typed)
case []any:
parts := []string{}
for _, item := range typed {
if text := extractText(item); text != "" {
parts = append(parts, text)
}
}
return strings.Join(parts, " ")
case map[string]any:
parts := []string{}
for _, item := range typed {
if text := extractText(item); text != "" {
parts = append(parts, text)
}
}
return strings.Join(parts, " ")
default:
return ""
}
}