feat: 新增 thinking 注入配置支持,扩展设置管理与前端交互

新增 promptcompat 和 OpenAI shared 层的 thinking 注入逻辑,
完善配置系统的编解码与校验,更新设置管理 API 与前端 UI。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
CJACK
2026-04-26 13:35:20 +08:00
parent 3627c7366d
commit c09a4b51a5
34 changed files with 1038 additions and 94 deletions

View File

@@ -58,12 +58,23 @@ func TestGetSettingsIncludesHistorySplitDefaults(t *testing.T) {
var body map[string]any
_ = json.Unmarshal(rec.Body.Bytes(), &body)
historySplit, _ := body["history_split"].(map[string]any)
if got := boolFrom(historySplit["enabled"]); !got {
t.Fatalf("expected history_split.enabled=true, body=%v", body)
if got := boolFrom(historySplit["enabled"]); got {
t.Fatalf("expected history_split.enabled=false, body=%v", body)
}
if got := intFrom(historySplit["trigger_after_turns"]); got != 1 {
t.Fatalf("expected history_split.trigger_after_turns=1, got %d body=%v", got, body)
}
currentInputFile, _ := body["current_input_file"].(map[string]any)
if got := boolFrom(currentInputFile["enabled"]); !got {
t.Fatalf("expected current_input_file.enabled=true, body=%v", body)
}
if got := intFrom(currentInputFile["min_chars"]); got != 0 {
t.Fatalf("expected current_input_file.min_chars=0, got %d body=%v", got, body)
}
thinkingInjection, _ := body["thinking_injection"].(map[string]any)
if got := boolFrom(thinkingInjection["enabled"]); !got {
t.Fatalf("expected thinking_injection.enabled=true, body=%v", body)
}
}
func TestUpdateSettingsValidation(t *testing.T) {
@@ -177,7 +188,7 @@ func TestUpdateSettingsHistorySplit(t *testing.T) {
h := newAdminTestHandler(t, `{"keys":["k1"]}`)
payload := map[string]any{
"history_split": map[string]any{
"enabled": false,
"enabled": true,
"trigger_after_turns": 3,
},
}
@@ -190,11 +201,85 @@ func TestUpdateSettingsHistorySplit(t *testing.T) {
}
snap := h.Store.Snapshot()
if snap.HistorySplit.Enabled == nil || !*snap.HistorySplit.Enabled {
t.Fatalf("expected history_split.enabled to be forced true, got %#v", snap.HistorySplit.Enabled)
t.Fatalf("expected history_split.enabled=true, got %#v", snap.HistorySplit.Enabled)
}
if snap.HistorySplit.TriggerAfterTurns == nil || *snap.HistorySplit.TriggerAfterTurns != 3 {
t.Fatalf("expected history_split.trigger_after_turns=3, got %#v", snap.HistorySplit.TriggerAfterTurns)
}
if snap.CurrentInputFile.Enabled == nil || *snap.CurrentInputFile.Enabled {
t.Fatalf("expected history split to disable current_input_file, got %#v", snap.CurrentInputFile.Enabled)
}
}
func TestUpdateSettingsCurrentInputFile(t *testing.T) {
h := newAdminTestHandler(t, `{"keys":["k1"],"history_split":{"enabled":true,"trigger_after_turns":2}}`)
payload := map[string]any{
"current_input_file": map[string]any{
"enabled": true,
"min_chars": 12345,
},
}
b, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPut, "/admin/settings", bytes.NewReader(b))
rec := httptest.NewRecorder()
h.updateSettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
snap := h.Store.Snapshot()
if snap.CurrentInputFile.Enabled == nil || !*snap.CurrentInputFile.Enabled {
t.Fatalf("expected current_input_file.enabled=true, got %#v", snap.CurrentInputFile)
}
if snap.CurrentInputFile.MinChars != 12345 {
t.Fatalf("expected current_input_file.min_chars=12345, got %#v", snap.CurrentInputFile)
}
if snap.HistorySplit.Enabled == nil || *snap.HistorySplit.Enabled {
t.Fatalf("expected current input file to disable history_split, got %#v", snap.HistorySplit.Enabled)
}
}
func TestUpdateSettingsRejectsTwoSplitModesEnabled(t *testing.T) {
h := newAdminTestHandler(t, `{"keys":["k1"]}`)
payload := map[string]any{
"history_split": map[string]any{
"enabled": true,
"trigger_after_turns": 3,
},
"current_input_file": map[string]any{
"enabled": true,
"min_chars": 0,
},
}
b, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPut, "/admin/settings", bytes.NewReader(b))
rec := httptest.NewRecorder()
h.updateSettings(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String())
}
}
func TestUpdateSettingsThinkingInjection(t *testing.T) {
h := newAdminTestHandler(t, `{"keys":["k1"]}`)
payload := map[string]any{
"thinking_injection": map[string]any{
"enabled": false,
},
}
b, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPut, "/admin/settings", bytes.NewReader(b))
rec := httptest.NewRecorder()
h.updateSettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
snap := h.Store.Snapshot()
if snap.ThinkingInjection.Enabled == nil || *snap.ThinkingInjection.Enabled {
t.Fatalf("expected thinking_injection.enabled=false, got %#v", snap.ThinkingInjection.Enabled)
}
if h.Store.ThinkingInjectionEnabled() {
t.Fatal("expected thinking injection accessor to reflect disabled config")
}
}
func TestUpdateSettingsAutoDeleteMode(t *testing.T) {