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
35 changes: 35 additions & 0 deletions backend/internal/adapters/agent/kimi/kimi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment thread
codebanditssss marked this conversation as resolved.
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)
Expand All @@ -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
}

Expand Down Expand Up @@ -133,6 +150,9 @@ func (p *Plugin) PromptReadinessHints(ctx context.Context, _ ports.LaunchConfig)
// approval settings of the original session -- so cfg.Permissions is
// intentionally ignored here.
func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) (cmd []string, ok bool, err error) {
// Deliberately does not forward cfg.Config.Model: --model + --session
// composition is unverified against a real kimi install (see #2890 and
// appendModelFlag). Pinned by TestGetRestoreCommandDoesNotForwardModelOverride.
if err := ctx.Err(); err != nil {
return nil, false, err
}
Expand Down Expand Up @@ -170,6 +190,21 @@ func appendApprovalFlags(cmd *[]string, permissions ports.PermissionMode) {
}
}

// appendModelFlag appends `--model <model>` when a role-specific model
// override is configured. A blank/whitespace-only value means no override was
// configured, so the flag is omitted and Kimi's own default model applies.
//
// Restore-path note: this is intentionally NOT called from
// GetRestoreCommand. Kimi's docs do not confirm --model composes with
// --session the way it does on the launch path (see issue #2890); wiring it
// blind risks silently breaking session resume. Left as a fast-follow pending
// verification against a real kimi install.
func appendModelFlag(cmd *[]string, cfg ports.AgentConfig) {
Comment thread
codebanditssss marked this conversation as resolved.
if model := strings.TrimSpace(cfg.Model); model != "" {
*cmd = append(*cmd, "--model", model)
}
}

func normalizePermissionMode(mode ports.PermissionMode) ports.PermissionMode {
switch mode {
case ports.PermissionModeDefault,
Expand Down
77 changes: 74 additions & 3 deletions backend/internal/adapters/agent/kimi/kimi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,84 @@ func TestManifest(t *testing.T) {
}
}

func TestGetConfigSpecEmpty(t *testing.T) {
func TestGetConfigSpecAdvertisesModelField(t *testing.T) {
spec, err := (&Plugin{}).GetConfigSpec(context.Background())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(spec.Fields) != 0 {
t.Fatalf("expected no fields, got %d", len(spec.Fields))
if len(spec.Fields) != 1 {
t.Fatalf("expected 1 field, got %d", len(spec.Fields))
}
if spec.Fields[0].Key != "model" || spec.Fields[0].Type != ports.ConfigFieldString {
t.Fatalf("field = %#v, want model/string", spec.Fields[0])
}
}

func TestGetLaunchCommandAppendsModelFlagWhenConfigured(t *testing.T) {
p := &Plugin{resolvedBinary: "kimi"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
Config: ports.AgentConfig{Model: "k2.5"},
})
if err != nil {
t.Fatal(err)
}
want := []string{"kimi", "--model", "k2.5"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
}

func TestGetLaunchCommandOmitsModelFlagWhenBlank(t *testing.T) {
p := &Plugin{resolvedBinary: "kimi"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
Config: ports.AgentConfig{Model: " "},
})
if err != nil {
t.Fatal(err)
}
want := []string{"kimi"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
}

func TestGetLaunchCommandModelFlagComposesWithApprovalFlags(t *testing.T) {
p := &Plugin{resolvedBinary: "kimi"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
Permissions: ports.PermissionModeBypassPermissions,
Config: ports.AgentConfig{Model: "k2.5"},
})
if err != nil {
t.Fatal(err)
}
want := []string{"kimi", "-y", "--model", "k2.5"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
}

// Mirrors the issue #2890 harness note: --model + --session composition is
// unverified against a real kimi install, so appendModelFlag is intentionally
// not wired into GetRestoreCommand. This test locks that decision in so a
// future "just mirror Copilot/Codex" edit doesn't silently forward the model
// override on restore without re-verifying first.
func TestGetRestoreCommandDoesNotForwardModelOverride(t *testing.T) {
p := &Plugin{resolvedBinary: "kimi"}
cmd, ok, err := p.GetRestoreCommand(context.Background(), ports.RestoreConfig{
Session: ports.SessionRef{
Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "01HZABC"},
},
Config: ports.AgentConfig{Model: "k2.5"},
})
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("ok=false, want true")
}
want := []string{"kimi", "--session", "01HZABC"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v (model override must not be forwarded on restore)", cmd, want)
}
}

Expand Down
Loading