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:
Raphael Westphal
2026-08-20 12:43:55 +02:00
parent 5292a69d66
commit 2d0839357d
11 changed files with 553 additions and 48 deletions
+134
View File
@@ -114,3 +114,137 @@ func TestAutoTitleAppliesRename(t *testing.T) {
}
t.Fatal("session row missing")
}
func TestRequestTitleFailures(t *testing.T) {
t.Run("http-500", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
if _, err := requestTitle("x"); err == nil {
t.Fatal("500 must error")
}
})
t.Run("bad-json", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("not json"))
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
if _, err := requestTitle("x"); err == nil {
t.Fatal("bad json must error")
}
})
t.Run("empty-choices", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"choices":[]}`))
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
if _, err := requestTitle("x"); err == nil {
t.Fatal("empty choices must error")
}
})
t.Run("whitespace-only-title", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":" *** "}}]}`))
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
if _, err := requestTitle("x"); err == nil {
t.Fatal("whitespace-only title must error")
}
})
t.Run("long-title-clamped", func(t *testing.T) {
long := make([]byte, 200)
for i := range long {
long[i] = 'a'
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"` + string(long) + `"}}]}`))
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
got, err := requestTitle("x")
if err != nil {
t.Fatal(err)
}
if len(got) != 60 {
t.Fatalf("clamp = %d", len(got))
}
})
t.Run("multiline-title-first-line", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"First line\nsecond"}}]}`))
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
got, err := requestTitle("x")
if err != nil {
t.Fatal(err)
}
if got != "First line" {
t.Fatalf("got %q", got)
}
})
}
func TestAutoTitleRequestShape(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req struct {
Model string `json:"model"`
MaxTok int `json:"max_tokens"`
Thinking map[string]any `json:"thinking"`
Messages []struct {
Content string `json:"content"`
} `json:"messages"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
if req.Model == "" || req.MaxTok == 0 || req.Thinking == nil {
t.Errorf("request shape wrong: %+v", req)
}
if len(req.Messages) != 1 || !contains(req.Messages[0].Content, autoTitlePromptTail[:20]) {
t.Errorf("messages wrong: %+v", req.Messages)
}
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"ok title"}}]}`))
}))
t.Cleanup(srv.Close)
t.Setenv("LVMH_AUTOTITLE_URL", srv.URL)
if _, err := requestTitle("the thing"); err != nil {
t.Fatal(err)
}
}
func contains(s, sub string) bool {
return len(s) >= len(sub) && (s == sub || len(sub) == 0 || indexOf(s, sub) >= 0)
}
func indexOf(s, sub string) int {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return i
}
}
return -1
}
func TestAutoTitleClosedStoreFails(t *testing.T) {
_, store, hub := newTestServerHub(t)
if err := hub.store.UpsertSession(SessionInfo{ID: "s-x"}); err != nil {
t.Fatal(err)
}
_ = store.Close()
t.Setenv("LVMH_AUTOTITLE_URL", dummyTitleServer(t))
// must not panic; logs the failure
hub.autoTitle("s-x", "text")
}
func TestAutoTitleNoKey(t *testing.T) {
// no ZAI key in env -> requestTitle errors before any HTTP call
t.Setenv(envProviderAPIKey, "")
t.Setenv("LVMH_AUTOTITLE_URL", "http://127.0.0.1:1")
if _, err := requestTitle("x"); err == nil {
t.Fatal("missing key must error")
}
}