Why: Uploaded images never reached the model, so a text-only chat model could be asked to read a picture it cannot see. Evidence: go test ./internal/... passed locally before this commit. Image-link rejection is covered by vision package tests. Impact: Current-turn attachments and fetched image links become image input. History keeps a text placeholder. Image turns swap to the configured vision model when the chat model is not in the capable list, and fallbacks are filtered the same way. Refs: Agent-System-Dev feature/four-model-vision Co-authored-by: Cursor <cursoragent@cursor.com>
93 lines
3.4 KiB
Go
93 lines
3.4 KiB
Go
package runtime
|
|
|
|
import (
|
|
"testing"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
func effOf(kv map[string]any) core.EffectiveSettings { return core.EffectiveSettings(kv) }
|
|
|
|
// 三层覆盖链解析:defaults → user → project 由 settings 服务合并(server 集成测试覆盖);
|
|
// 此处验证解析器对「设置层结果 + 画像层」的最后一级覆盖与回落。
|
|
func TestResolveChatModelLayers(t *testing.T) {
|
|
eff := effOf(map[string]any{"models": map[string]any{"chat": map[string]any{
|
|
"model": "model-settings", "baseUrl": "https://s.example", "apiKey": "k0",
|
|
}}})
|
|
prof := core.AgentProfile{}
|
|
|
|
// 仅设置层。
|
|
got := ResolveChatModel(prof, eff)
|
|
if got.Model != "model-settings" || got.Base != "https://s.example" || got.Key != "k0" {
|
|
t.Fatalf("settings layer: %+v", got)
|
|
}
|
|
// 画像覆盖模型(端点沿用设置层)。
|
|
prof.Model.Chat = "model-profile"
|
|
got = ResolveChatModel(prof, eff)
|
|
if got.Model != "model-profile" || got.Base != "https://s.example" {
|
|
t.Fatalf("profile layer should override model only: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveLightweightFallback(t *testing.T) {
|
|
eff := effOf(map[string]any{"models": map[string]any{
|
|
"chat": map[string]any{"model": "main-m", "baseUrl": "https://main", "apiKey": "k"},
|
|
"lightweight": map[string]any{"model": ""},
|
|
}})
|
|
got := ResolveLightweightModel(core.AgentProfile{}, eff)
|
|
if got.Model != "main-m" || got.Base != "https://main" {
|
|
t.Fatalf("lightweight should fall back to chat: %+v", got)
|
|
}
|
|
// 轻量档只配模型名:模型用轻量、端点回落主档。
|
|
eff["models"].(map[string]any)["lightweight"] = map[string]any{"model": "lite-m"}
|
|
got = ResolveLightweightModel(core.AgentProfile{Model: core.ModelRoute{Lightweight: "prof-lite"}}, eff)
|
|
if got.Model != "prof-lite" || got.Base != "https://main" {
|
|
t.Fatalf("profile lightweight + chat endpoint: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestChatFallbacksDefaultEmpty(t *testing.T) {
|
|
if got := ChatFallbacks(effOf(nil)); got != nil {
|
|
t.Fatalf("default %#v", got)
|
|
}
|
|
eff := effOf(map[string]any{"models": map[string]any{"chat": map[string]any{
|
|
"fallbacks": " backup , primary ,backup",
|
|
}}})
|
|
got := ChatFallbacks(eff)
|
|
if len(got) != 2 || got[0] != "backup" || got[1] != "primary" {
|
|
t.Fatalf("%#v", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveVisionModel(t *testing.T) {
|
|
eff := effOf(map[string]any{"models": map[string]any{"chat": map[string]any{
|
|
"model": "deepseek-v4.1-flash", "baseUrl": "https://gw.example/v1", "apiKey": "k",
|
|
"fallbacks": "plain-text,ZHIPU/GLM-5.3-FlashX",
|
|
}, "vision": map[string]any{
|
|
"model": "qwen3.8-flash",
|
|
"capable": "deepseek-v4.1-flash,qwen3.8-flash,ZHIPU/GLM-5.3-FlashX",
|
|
}}})
|
|
prof := core.AgentProfile{}
|
|
got := ResolveVisionModel(prof, eff, false)
|
|
if got.Model != "deepseek-v4.1-flash" {
|
|
t.Fatalf("no image should keep chat model: %+v", got)
|
|
}
|
|
got = ResolveVisionModel(prof, eff, true)
|
|
if got.Model != "deepseek-v4.1-flash" || got.Base != "https://gw.example/v1" || got.Key != "k" {
|
|
t.Fatalf("capable model should stay: %+v", got)
|
|
}
|
|
prof.Model.Chat = "plain-text"
|
|
got = ResolveVisionModel(prof, eff, true)
|
|
if got.Model != "qwen3.8-flash" || got.Base != "https://gw.example/v1" || got.Key != "k" {
|
|
t.Fatalf("incapable model should swap, keeping endpoint: %+v", got)
|
|
}
|
|
fb := ChatFallbacksFor(eff, true)
|
|
if len(fb) != 1 || fb[0] != "ZHIPU/GLM-5.3-FlashX" {
|
|
t.Fatalf("image fallbacks %#v", fb)
|
|
}
|
|
fb = ChatFallbacksFor(eff, false)
|
|
if len(fb) != 2 || fb[0] != "plain-text" {
|
|
t.Fatalf("text fallbacks %#v", fb)
|
|
}
|
|
}
|