mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-12 04:07:42 +08:00
fix: preserve partial-update fields for current_input_file and thinking_injection, expand DSML space-separator aliases
- Guard current_input_file.enabled / thinking_injection.{enabled,prompt} with hasNestedSettingsKey so partial updates don't overwrite omitted fields
- Expand DSML alias support to tolerate space-separated tags (e.g. <|dsml invoke>) alongside pipe-separated forms
- Sync Go sieve, Node sieve, toolcall parser, and tests for all new DSML variants
- Update API.md and toolcall-semantics.md with expanded alias coverage
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -244,6 +244,52 @@ func TestUpdateSettingsCurrentInputFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsCurrentInputFilePartialUpdatePreservesEnabled(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{"keys":["k1"],"current_input_file":{"enabled":false,"min_chars":777}}`)
|
||||
payload := map[string]any{
|
||||
"current_input_file": map[string]any{
|
||||
"min_chars": 5000,
|
||||
},
|
||||
}
|
||||
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 to remain false, got %#v", snap.CurrentInputFile.Enabled)
|
||||
}
|
||||
if snap.CurrentInputFile.MinChars != 5000 {
|
||||
t.Fatalf("expected current_input_file.min_chars=5000, got %#v", snap.CurrentInputFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsCurrentInputFilePartialUpdatePreservesMinChars(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{"keys":["k1"],"current_input_file":{"enabled":false,"min_chars":777}}`)
|
||||
payload := map[string]any{
|
||||
"current_input_file": map[string]any{
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
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.Enabled)
|
||||
}
|
||||
if snap.CurrentInputFile.MinChars != 777 {
|
||||
t.Fatalf("expected current_input_file.min_chars to remain 777, got %#v", snap.CurrentInputFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsRejectsTwoSplitModesEnabled(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{"keys":["k1"]}`)
|
||||
payload := map[string]any{
|
||||
@@ -292,6 +338,52 @@ func TestUpdateSettingsThinkingInjection(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsThinkingInjectionPartialPromptPreservesEnabled(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{"keys":["k1"],"thinking_injection":{"enabled":false,"prompt":"original prompt"}}`)
|
||||
payload := map[string]any{
|
||||
"thinking_injection": map[string]any{
|
||||
"prompt": " updated prompt ",
|
||||
},
|
||||
}
|
||||
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 to remain false, got %#v", snap.ThinkingInjection.Enabled)
|
||||
}
|
||||
if got := h.Store.ThinkingInjectionPrompt(); got != "updated prompt" {
|
||||
t.Fatalf("expected updated prompt, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsThinkingInjectionPartialEnabledPreservesPrompt(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{"keys":["k1"],"thinking_injection":{"enabled":false,"prompt":"original prompt"}}`)
|
||||
payload := map[string]any{
|
||||
"thinking_injection": map[string]any{
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
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=true, got %#v", snap.ThinkingInjection.Enabled)
|
||||
}
|
||||
if got := h.Store.ThinkingInjectionPrompt(); got != "original prompt" {
|
||||
t.Fatalf("expected original prompt to be preserved, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsAutoDeleteMode(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{"keys":["k1"],"auto_delete":{"sessions":true}}`)
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
currentInputEnabledSet := hasNestedSettingsKey(req, "current_input_file", "enabled")
|
||||
currentInputMinCharsSet := hasNestedSettingsKey(req, "current_input_file", "min_chars")
|
||||
thinkingInjectionEnabledSet := hasNestedSettingsKey(req, "thinking_injection", "enabled")
|
||||
thinkingInjectionPromptSet := hasNestedSettingsKey(req, "thinking_injection", "prompt")
|
||||
|
||||
if err := h.Store.Update(func(c *config.Config) error {
|
||||
if adminCfg != nil {
|
||||
@@ -80,16 +84,24 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if currentInputCfg != nil {
|
||||
c.CurrentInputFile.Enabled = currentInputCfg.Enabled
|
||||
if currentInputCfg.Enabled != nil && *currentInputCfg.Enabled {
|
||||
if currentInputEnabledSet {
|
||||
c.CurrentInputFile.Enabled = currentInputCfg.Enabled
|
||||
}
|
||||
if currentInputEnabledSet && currentInputCfg.Enabled != nil && *currentInputCfg.Enabled {
|
||||
disabled := false
|
||||
c.HistorySplit.Enabled = &disabled
|
||||
}
|
||||
c.CurrentInputFile.MinChars = currentInputCfg.MinChars
|
||||
if currentInputMinCharsSet {
|
||||
c.CurrentInputFile.MinChars = currentInputCfg.MinChars
|
||||
}
|
||||
}
|
||||
if thinkingInjCfg != nil {
|
||||
c.ThinkingInjection.Enabled = thinkingInjCfg.Enabled
|
||||
c.ThinkingInjection.Prompt = thinkingInjCfg.Prompt
|
||||
if thinkingInjectionEnabledSet {
|
||||
c.ThinkingInjection.Enabled = thinkingInjCfg.Enabled
|
||||
}
|
||||
if thinkingInjectionPromptSet {
|
||||
c.ThinkingInjection.Prompt = thinkingInjCfg.Prompt
|
||||
}
|
||||
}
|
||||
if aliasMap != nil {
|
||||
c.ModelAliases = aliasMap
|
||||
@@ -144,3 +156,12 @@ func (h *Handler) updateSettingsPassword(w http.ResponseWriter, r *http.Request)
|
||||
"jwt_valid_after_unix": now,
|
||||
})
|
||||
}
|
||||
|
||||
func hasNestedSettingsKey(req map[string]any, section, key string) bool {
|
||||
raw, ok := req[section].(map[string]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, exists := raw[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user