From 51eb95f133898b984f538ccd9dea65eb807e72a7 Mon Sep 17 00:00:00 2001 From: ashutoshhota Date: Wed, 22 Jul 2026 20:57:38 +0530 Subject: [PATCH] fix(kimi):forward role-specific model config on launch and restore --- backend/internal/adapters/agent/kimi/kimi.go | 27 ++++- .../internal/adapters/agent/kimi/kimi_test.go | 104 +++++++++++++++++- 2 files changed, 125 insertions(+), 6 deletions(-) diff --git a/backend/internal/adapters/agent/kimi/kimi.go b/backend/internal/adapters/agent/kimi/kimi.go index 63d6210994..14b4186661 100644 --- a/backend/internal/adapters/agent/kimi/kimi.go +++ b/backend/internal/adapters/agent/kimi/kimi.go @@ -77,6 +77,22 @@ func (p *Plugin) Manifest() adapters.Manifest { } } +// GetConfigSpec reports the per-project agent config keys Kimi understands. +func (p *Plugin) GetConfigSpec(ctx context.Context) (ports.ConfigSpec, error) { + if err := ctx.Err(); err != nil { + return ports.ConfigSpec{}, err + } + return ports.ConfigSpec{ + Fields: []ports.ConfigField{ + { + Key: "model", + Type: ports.ConfigFieldString, + Description: "Model override passed to `kimi --model`.", + }, + }, + }, nil +} + // GetLaunchCommand builds the argv to start a new Kimi session: // // kimi [--auto|-y] (interactive) @@ -94,6 +110,7 @@ func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) ( cmd = []string{binary} appendApprovalFlags(&cmd, cfg.Permissions) + appendModelFlag(&cmd, cfg.Config) return cmd, nil } @@ -145,7 +162,9 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) if err != nil { return nil, false, err } - cmd = []string{binary, "--session", agentSessionID} + cmd = []string{binary} + appendModelFlag(&cmd, cfg.Config) + cmd = append(cmd, "--session", agentSessionID) return cmd, true, nil } @@ -170,6 +189,12 @@ func appendApprovalFlags(cmd *[]string, permissions ports.PermissionMode) { } } +func appendModelFlag(cmd *[]string, cfg ports.AgentConfig) { + if model := strings.TrimSpace(cfg.Model); model != "" { + *cmd = append(*cmd, "--model", model) + } +} + func normalizePermissionMode(mode ports.PermissionMode) ports.PermissionMode { switch mode { case ports.PermissionModeDefault, diff --git a/backend/internal/adapters/agent/kimi/kimi_test.go b/backend/internal/adapters/agent/kimi/kimi_test.go index a012305c2a..86fa028e1e 100644 --- a/backend/internal/adapters/agent/kimi/kimi_test.go +++ b/backend/internal/adapters/agent/kimi/kimi_test.go @@ -32,13 +32,22 @@ func TestManifest(t *testing.T) { } } -func TestGetConfigSpecEmpty(t *testing.T) { - spec, err := (&Plugin{}).GetConfigSpec(context.Background()) +func TestGetConfigSpecReportsModelField(t *testing.T) { + plugin := &Plugin{} + + spec, err := plugin.GetConfigSpec(context.Background()) if err != nil { - t.Fatalf("err: %v", err) + t.Fatal(err) + } + want := []ports.ConfigField{ + { + Key: "model", + Type: ports.ConfigFieldString, + Description: "Model override passed to `kimi --model`.", + }, } - if len(spec.Fields) != 0 { - t.Fatalf("expected no fields, got %d", len(spec.Fields)) + if !reflect.DeepEqual(spec.Fields, want) { + t.Fatalf("config fields\nwant: %#v\n got: %#v", want, spec.Fields) } } @@ -122,6 +131,38 @@ func TestGetLaunchCommandIgnoresSystemPrompt(t *testing.T) { } } +func TestGetLaunchCommandAppendsConfiguredModel(t *testing.T) { + plugin := &Plugin{resolvedBinary: "kimi"} + + cmd, err := plugin.GetLaunchCommand(context.Background(), ports.LaunchConfig{ + Config: ports.AgentConfig{Model: " claude-sonnet-5 "}, + Prompt: "fix this", + }) + if err != nil { + t.Fatal(err) + } + if !containsSubsequence(cmd, []string{"--model", "claude-sonnet-5"}) { + t.Fatalf("command %#v missing trimmed --model flag", cmd) + } + if containsSubsequence(cmd, []string{"--model", " claude-sonnet-5 "}) { + t.Fatalf("command %#v used untrimmed model", cmd) + } +} + +func TestGetLaunchCommandOmitsBlankConfiguredModel(t *testing.T) { + plugin := &Plugin{resolvedBinary: "kimi"} + + cmd, err := plugin.GetLaunchCommand(context.Background(), ports.LaunchConfig{ + Config: ports.AgentConfig{Model: " \t "}, + }) + if err != nil { + t.Fatal(err) + } + if contains(cmd, "--model") { + t.Fatalf("command %#v contains --model for blank model", cmd) + } +} + // Kimi docs: `--yolo` and `--auto` cannot be used together with `--continue` // or `--session` — resumed sessions inherit the approval settings of the // original session — so the restore path must not emit approval flags @@ -192,6 +233,26 @@ func TestGetRestoreCommandNoID(t *testing.T) { } } +func TestGetRestoreCommandAppendsConfiguredModel(t *testing.T) { + plugin := &Plugin{resolvedBinary: "kimi"} + + cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ + Config: ports.AgentConfig{Model: " claude-sonnet-5 "}, + Session: ports.SessionRef{ + Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "01HZABC"}, + }, + }) + if err != nil { + t.Fatalf("err = %v, want nil", err) + } + if !ok { + t.Fatal("ok = false, want true") + } + if !containsSubsequence(cmd, []string{"--model", "claude-sonnet-5"}) { + t.Fatalf("restore command %#v missing trimmed --model flag", cmd) + } +} + func TestGetAgentHooksInstallsSystemPromptInstructions(t *testing.T) { workspace := t.TempDir() kimiHome := t.TempDir() @@ -589,3 +650,36 @@ func TestContextCancellation(t *testing.T) { t.Fatalf("ResolveKimiBinary err = %v, want context.Canceled", err) } } + +func contains(values []string, needle string) bool { + for _, value := range values { + if value == needle { + return true + } + } + return false +} + +func containsSubsequence(values []string, needle []string) bool { + if len(needle) == 0 { + return true + } + + for start := range values { + if start+len(needle) > len(values) { + return false + } + ok := true + for offset, want := range needle { + if values[start+offset] != want { + ok = false + break + } + } + if ok { + return true + } + } + + return false +}