Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions agentteams-controller/cmd/agt/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,12 @@ Create or update each Worker separately to configure its runtime fields.`,

func updateManagerCmd() *cobra.Command {
var (
name string
model string
runtime string
image string
soul string
name string
model string
modelProvider string
runtime string
image string
soul string
)

cmd := &cobra.Command{
Expand All @@ -197,6 +198,7 @@ func updateManagerCmd() *cobra.Command {
Long: `Update an existing Manager resource. Only specified fields are changed.

agt update manager --name default --model claude-sonnet-4-6
agt update manager --name default --model qwen3.6-plus --model-provider=
agt update manager --name default --image agentteams/agentteams-manager:v1.2.0
To update CPU/memory resources, use a YAML manifest and pass it with 'agt apply -f manager.yaml'.`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -206,6 +208,9 @@ func updateManagerCmd() *cobra.Command {

req := map[string]interface{}{}
setIfNotEmpty(req, "model", model)
if cmd.Flags().Changed("model-provider") {
req["modelProvider"] = modelProvider
}
setIfNotEmpty(req, "runtime", runtime)
setIfNotEmpty(req, "image", image)
setIfNotEmpty(req, "soul", soul)
Expand All @@ -226,6 +231,7 @@ func updateManagerCmd() *cobra.Command {

cmd.Flags().StringVar(&name, "name", "", "Manager name (required)")
cmd.Flags().StringVar(&model, "model", "", "LLM model ID")
cmd.Flags().StringVar(&modelProvider, "model-provider", "", "LLM provider name (empty clears the binding)")
cmd.Flags().StringVar(&runtime, "runtime", "", "Agent runtime (openclaw|copaw|hermes|openhuman)")
cmd.Flags().StringVar(&image, "image", "", "Container image override")
cmd.Flags().StringVar(&soul, "soul", "", "Manager SOUL.md content")
Expand Down
45 changes: 45 additions & 0 deletions agentteams-controller/cmd/agt/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)

func TestUpdateManagerCanClearModelProvider(t *testing.T) {
var body map[string]interface{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut || r.URL.Path != "/api/v1/managers/default" {
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Errorf("decode request body: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{}`))
}))
defer server.Close()
t.Setenv("AGENTTEAMS_CONTROLLER_URL", server.URL)

cmd := updateManagerCmd()
cmd.SetArgs([]string{"--name", "default", "--model", "known-good-model", "--model-provider="})
if err := cmd.Execute(); err != nil {
t.Fatalf("execute update manager: %v", err)
}

if got := body["model"]; got != "known-good-model" {
t.Fatalf("model=%v, want known-good-model", got)
}
provider, ok := body["modelProvider"]
if !ok {
t.Fatal("modelProvider is missing from request")
}
if provider != "" {
t.Fatalf("modelProvider=%v, want empty", provider)
}
}
4 changes: 2 additions & 2 deletions agentteams-controller/internal/server/resource_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,8 @@ func (h *ResourceHandler) UpdateManager(w http.ResponseWriter, r *http.Request)
if req.Model != "" {
mgr.Spec.Model = req.Model
}
if req.ModelProvider != "" {
mgr.Spec.ModelProvider = req.ModelProvider
if req.ModelProvider != nil {
mgr.Spec.ModelProvider = *req.ModelProvider
}
if req.Runtime != "" {
mgr.Spec.Runtime = req.Runtime
Expand Down
35 changes: 35 additions & 0 deletions agentteams-controller/internal/server/resource_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,41 @@ func TestCreateAndUpdateManagerPersistsModelProvider(t *testing.T) {
}
}

func TestUpdateManagerClearsModelProviderWhenExplicitlyEmpty(t *testing.T) {
scheme := newServerTestScheme(t)
manager := &v1beta1.Manager{
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
Spec: v1beta1.ManagerSpec{
Model: "custom-model",
ModelProvider: "deleted-provider",
},
}
k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(manager).Build()
handler := NewResourceHandler(k8sClient, "default", nil, "")

updateReq := httptest.NewRequest(http.MethodPut, "/api/v1/managers/default", bytes.NewReader([]byte(`{
"model":"known-good-model",
"modelProvider":""
}`)))
updateReq.SetPathValue("name", "default")
updateRec := httptest.NewRecorder()
handler.UpdateManager(updateRec, updateReq)
if updateRec.Code != http.StatusOK {
t.Fatalf("expected update status %d, got %d: %s", http.StatusOK, updateRec.Code, updateRec.Body.String())
}

var updated v1beta1.Manager
if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "default", Namespace: "default"}, &updated); err != nil {
t.Fatalf("get updated manager: %v", err)
}
if updated.Spec.Model != "known-good-model" {
t.Fatalf("updated manager model=%q, want known-good-model", updated.Spec.Model)
}
if updated.Spec.ModelProvider != "" {
t.Fatalf("updated manager modelProvider=%q, want empty", updated.Spec.ModelProvider)
}
}

func TestCreateTeamRequiresExactlyOneLeaderReference(t *testing.T) {
scheme := newServerTestScheme(t)
leadOne := &v1beta1.Worker{ObjectMeta: metav1.ObjectMeta{Name: "lead-one", Namespace: "default"}}
Expand Down
2 changes: 1 addition & 1 deletion agentteams-controller/internal/server/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ type CreateManagerRequest struct {

type UpdateManagerRequest struct {
Model string `json:"model,omitempty"`
ModelProvider string `json:"modelProvider,omitempty"`
ModelProvider *string `json:"modelProvider,omitempty"`
Runtime string `json:"runtime,omitempty"`
Image string `json:"image,omitempty"`
Soul string `json:"soul,omitempty"`
Expand Down
Loading