Skip to content
Closed
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
14 changes: 14 additions & 0 deletions tui/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ type meta struct {
// ShowThinking 记忆是否把模型思考(reasoning_content)暗显进对话流(/thinking 切换)。默认关。
ShowThinking bool `json:"show_thinking,omitempty"`

// DefaultModel 默认起手模型("auto"/"flash"/"pro"),语义同 /model:auto=关键词路由,
// flash/pro=定死该模型。仅当会话从未用 /model 锁定过时生效;空 = auto(现有行为)。
DefaultModel string `json:"default_model,omitempty"`

// MousePassthrough 记忆鼠标穿透模式(F2 切换):开启后关掉鼠标捕获,由终端原生处理
// 文字选择与右键粘贴。需要它的用户(WSL2 / 习惯终端原生选择)是一直需要,故重启保持。
MousePassthrough bool `json:"mouse_passthrough,omitempty"`
Expand Down Expand Up @@ -57,6 +61,16 @@ func webHost() string {

func webPort() int { return metaGet().WebPort }

// defaultModel 返回 meta.json 配置的默认起手模型(auto/flash/pro),空或非法回退 "auto"。
// 语义与 /model 一致;只影响「会话从未锁定过模型」的初始起手,不覆盖用户的 /model 锁定。
func defaultModel() string {
switch m := metaGet().DefaultModel; m {
case "flash", "pro":
return m
}
return "auto"
}

// modelCaps 是单个模型探测出的能力位,按维度独立存。
type modelCaps struct {
Vision bool `json:"vision"`
Expand Down
86 changes: 86 additions & 0 deletions tui/meta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package tui

import (
"testing"

"deepx/agent"
"deepx/tools"
)

// TestDefaultModel 验证 meta.json 的 default_model 归一化:空/非法回退 auto,
// flash/pro 原样返回。语义与 /model 一致。
func TestDefaultModel(t *testing.T) {
t.Setenv("HOME", t.TempDir()) // 写临时 ~/.deepx/meta.json,不碰真实配置
cases := []struct {
in string
want string
}{
{"", "auto"},
{"auto", "auto"},
{"flash", "flash"},
{"pro", "pro"},
{"bogus", "auto"},
{"PRO", "auto"}, // 大小写敏感,非法回退 auto
}
for _, c := range cases {
metaUpdate(func(m *meta) { m.DefaultModel = c.in })
if got := defaultModel(); got != c.want {
t.Errorf("defaultModel(%q) = %q, want %q", c.in, got, c.want)
}
}
}

// TestRestoreModelPinDefaultMeta 验证 restoreModelPin 对空 pin 的回退逻辑:
// 用 meta.json 的 default_model 兜底起手模型,但显式保存的 /model 锁定优先。
func TestRestoreModelPinDefaultMeta(t *testing.T) {
t.Setenv("HOME", t.TempDir())
cfg := agent.ModelConfig{
Flash: agent.ModelEntry{Model: "flash-x"},
Pro: agent.ModelEntry{Model: "pro-x"},
}
newModel := func() *model { return &model{models: cfg} }

// 空 pin + default_model=flash → 起手 flash
metaUpdate(func(m *meta) { m.DefaultModel = "flash" })
m := newModel()
m.restoreModelPin("")
if m.modelPin != tools.RoleFlash {
t.Errorf("空 pin + default flash: modelPin = %q, want %q", m.modelPin, tools.RoleFlash)
}

// 空 pin + default_model=pro → 起手 pro
metaUpdate(func(m *meta) { m.DefaultModel = "pro" })
m = newModel()
m.restoreModelPin("")
if m.modelPin != tools.RolePro {
t.Errorf("空 pin + default pro: modelPin = %q, want %q", m.modelPin, tools.RolePro)
}

// 空 pin + default_model 为空 → 回退 auto(现有行为)
metaUpdate(func(m *meta) { m.DefaultModel = "" })
m = newModel()
m.restoreModelPin("")
if m.modelPin != "auto" {
t.Errorf("空 pin + 无默认: modelPin = %q, want auto", m.modelPin)
}

// 显式 /model 锁定优先于 default_model
metaUpdate(func(m *meta) { m.DefaultModel = "pro" })
m = newModel()
m.restoreModelPin("auto")
if m.modelPin != "auto" {
t.Errorf("显式 auto: modelPin = %q, want auto", m.modelPin)
}
m.restoreModelPin(tools.RoleFlash)
if m.modelPin != tools.RoleFlash {
t.Errorf("显式 flash: modelPin = %q, want %q", m.modelPin, tools.RoleFlash)
}

// default_model=flash 但 flash 未配置 → 回退 auto(与现有锁定逻辑一致)
metaUpdate(func(m *meta) { m.DefaultModel = "flash" })
m2 := &model{models: agent.ModelConfig{Pro: agent.ModelEntry{Model: "pro-x"}}}
m2.restoreModelPin("")
if m2.modelPin != "auto" {
t.Errorf("default flash 未配置: modelPin = %q, want auto", m2.modelPin)
}
}
7 changes: 6 additions & 1 deletion tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ func initialModel(models agent.ModelConfig, needsSetup bool, version string, hub
visionByModel: visionByModel,
activeModelRole: role,
activeModelID: activeID,
modelPin: "auto",
modelPin: defaultModel(),
version: version,
mode: agent.AgentMode_Auto,
workingMode: agent.WorkingModeDefault, // 默认 kp;下方从 session 恢复
Expand Down Expand Up @@ -3848,6 +3848,11 @@ func (m *model) applyModelPin(arg string) {
// flash/pro 且对应模型已配置 → 锁定并即时切右栏显示;否则回退 auto(右栏起手 flash,
// flash 未配置则 pro)。供 initialModel 启动恢复与 loadCurrentConversation 切会话恢复共用。
func (m *model) restoreModelPin(pin string) {
// 会话从未锁定过(空)→ 回退 meta.json 的 default_model 作为默认起手模型;
// 显式存过 auto/flash/pro 则按原样恢复 —— 用户的 /model 选择优先于全局默认。
if pin == "" {
pin = defaultModel()
}
switch {
case pin == tools.RoleFlash && m.models.Flash.Model != "":
m.modelPin = tools.RoleFlash
Expand Down