feat: model selection at spawn (POST /api/spawn model -> LVMH_MODEL -> bridge createAgentSession); ops prepare resets ops session (fresh context per prepare)
This commit is contained in:
+49
-8
@@ -17,6 +17,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Route names, event query defaults and limits.
|
||||
@@ -35,8 +36,9 @@ const (
|
||||
maxBodyBytes int64 = 1 << 20
|
||||
webIndexFallback string = "index.html"
|
||||
|
||||
reposPathPrefix string = "/api/repos/"
|
||||
reposPrepareSuffix string = "/prepare"
|
||||
reposPathPrefix string = "/api/repos/"
|
||||
reposPrepareSuffix string = "/prepare"
|
||||
prepOpsBootWait time.Duration = 90 * time.Second
|
||||
// opsPreparePrompt is sent to the ops control session by
|
||||
// POST /api/repos/<repo>/prepare.
|
||||
opsPreparePrompt string = "prepare %s: clone, build a worker image, register it"
|
||||
@@ -365,6 +367,7 @@ func (s *Server) handleSpawn(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Repo string `json:"repo"`
|
||||
Branch string `json:"branch"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
if !decodeBody(w, r, &body) {
|
||||
return
|
||||
@@ -377,7 +380,13 @@ func (s *Server) handleSpawn(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "invalid branch name")
|
||||
return
|
||||
}
|
||||
res, err := s.spawn.Start(r.Context(), body.Repo, body.Branch)
|
||||
// Optional initial model "provider/model-id" (validated loosely here; the
|
||||
// bridge logs and falls back when the registry cannot resolve it).
|
||||
if body.Model != "" && !modelSpecRe.MatchString(body.Model) {
|
||||
writeError(w, http.StatusBadRequest, "model must look like provider/model-id")
|
||||
return
|
||||
}
|
||||
res, err := s.spawn.Start(r.Context(), body.Repo, body.Branch, body.Model)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -574,7 +583,9 @@ func (s *Server) handleModelCatalog(w http.ResponseWriter, r *http.Request) {
|
||||
path := envOr(envModelsFile, "")
|
||||
if path == "" {
|
||||
path = defaultBakedModelsFile
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if t := os.Getenv("LVMH_TEST_BAKED_MODELS"); t != "" {
|
||||
path = t
|
||||
} else if _, err := os.Stat(path); err != nil {
|
||||
path = defaultModelsFile
|
||||
}
|
||||
}
|
||||
@@ -655,12 +666,42 @@ func (s *Server) handlePrepareRepo(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "repo must look like group/project")
|
||||
return
|
||||
}
|
||||
if err := s.hub.Prompt(opsSessionID, fmt.Sprintf(opsPreparePrompt, repo)); err != nil {
|
||||
if errors.Is(err, ErrOffline) {
|
||||
writeError(w, http.StatusConflict, "ops session offline")
|
||||
// Fresh ops context per prepare: when ops is running, restart it (wiping
|
||||
// its transcript) so the prompt lands in a clean session — no history
|
||||
// buildup across prepares. When offline, try to boot it once.
|
||||
if s.hub.IsOnline(opsSessionID) {
|
||||
if err := s.spawn.RemoveOps(r.Context()); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "reset ops: "+err.Error())
|
||||
return
|
||||
}
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
_, _ = s.store.DeleteSessionEvents(opsSessionID)
|
||||
_, _ = s.store.SetSessionName(opsSessionID, "")
|
||||
}
|
||||
sent := false
|
||||
wait := prepOpsBootWait
|
||||
if w := os.Getenv("LVMH_TEST_OPS_BOOT_WAIT"); w != "" {
|
||||
if d, err := time.ParseDuration(w); err == nil {
|
||||
wait = d
|
||||
}
|
||||
}
|
||||
deadline := time.Now().Add(wait)
|
||||
for time.Now().Before(deadline) {
|
||||
if s.hub.IsOnline(opsSessionID) ||
|
||||
(s.spawn.EnsureOps(r.Context()) == nil && s.hub.IsOnline(opsSessionID)) {
|
||||
if s.hub.Prompt(opsSessionID, fmt.Sprintf(opsPreparePrompt, repo)) == nil {
|
||||
sent = true
|
||||
}
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
writeError(w, http.StatusRequestTimeout, "client gone while booting ops")
|
||||
return
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
}
|
||||
if !sent {
|
||||
writeError(w, http.StatusConflict, "ops session did not come back online")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||
|
||||
Reference in New Issue
Block a user