mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-13 12:47:41 +08:00
全局统一映射
This commit is contained in:
@@ -82,23 +82,6 @@ func (h *Handler) configImport(w http.ResponseWriter, r *http.Request) {
|
||||
importedAccounts++
|
||||
}
|
||||
|
||||
if len(incoming.ClaudeMapping) > 0 {
|
||||
if next.ClaudeMapping == nil {
|
||||
next.ClaudeMapping = map[string]string{}
|
||||
}
|
||||
for k, v := range incoming.ClaudeMapping {
|
||||
next.ClaudeMapping[k] = v
|
||||
}
|
||||
}
|
||||
if len(incoming.ClaudeModelMap) > 0 {
|
||||
if next.ClaudeModelMap == nil {
|
||||
next.ClaudeModelMap = map[string]string{}
|
||||
}
|
||||
for k, v := range incoming.ClaudeModelMap {
|
||||
next.ClaudeModelMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(incoming.ModelAliases) > 0 {
|
||||
if next.ModelAliases == nil {
|
||||
next.ModelAliases = map[string]string{}
|
||||
|
||||
@@ -18,12 +18,7 @@ func (h *Handler) getConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
"env_source_present": h.Store.HasEnvConfigSource(),
|
||||
"env_writeback_enabled": h.Store.IsEnvWritebackEnabled(),
|
||||
"config_path": h.Store.ConfigPath(),
|
||||
"claude_mapping": func() map[string]string {
|
||||
if len(snap.ClaudeMapping) > 0 {
|
||||
return snap.ClaudeMapping
|
||||
}
|
||||
return snap.ClaudeModelMap
|
||||
}(),
|
||||
"model_aliases": snap.ModelAliases,
|
||||
}
|
||||
accounts := make([]map[string]any, 0, len(snap.Accounts))
|
||||
for _, acc := range snap.Accounts {
|
||||
|
||||
@@ -58,12 +58,12 @@ func (h *Handler) updateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
c.Accounts = accounts
|
||||
}
|
||||
if m, ok := req["claude_mapping"].(map[string]any); ok {
|
||||
newMap := map[string]string{}
|
||||
if m, ok := req["model_aliases"].(map[string]any); ok {
|
||||
aliases := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
newMap[k] = fmt.Sprintf("%v", v)
|
||||
aliases[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
c.ClaudeMapping = newMap
|
||||
c.ModelAliases = aliases
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ func boolFrom(v any) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *config.RuntimeConfig, *config.CompatConfig, *config.ResponsesConfig, *config.EmbeddingsConfig, *config.AutoDeleteConfig, *config.HistorySplitConfig, map[string]string, map[string]string, error) {
|
||||
func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *config.RuntimeConfig, *config.CompatConfig, *config.ResponsesConfig, *config.EmbeddingsConfig, *config.AutoDeleteConfig, *config.HistorySplitConfig, map[string]string, error) {
|
||||
var (
|
||||
adminCfg *config.AdminConfig
|
||||
runtimeCfg *config.RuntimeConfig
|
||||
@@ -30,7 +30,6 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
embCfg *config.EmbeddingsConfig
|
||||
autoDeleteCfg *config.AutoDeleteConfig
|
||||
historySplitCfg *config.HistorySplitConfig
|
||||
claudeMap map[string]string
|
||||
aliasMap map[string]string
|
||||
)
|
||||
|
||||
@@ -39,7 +38,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
if v, exists := raw["jwt_expire_hours"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("admin.jwt_expire_hours", n, 1, 720, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.JWTExpireHours = n
|
||||
}
|
||||
@@ -51,33 +50,33 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
if v, exists := raw["account_max_inflight"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("runtime.account_max_inflight", n, 1, 256, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.AccountMaxInflight = n
|
||||
}
|
||||
if v, exists := raw["account_max_queue"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("runtime.account_max_queue", n, 1, 200000, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.AccountMaxQueue = n
|
||||
}
|
||||
if v, exists := raw["global_max_inflight"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("runtime.global_max_inflight", n, 1, 200000, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.GlobalMaxInflight = n
|
||||
}
|
||||
if v, exists := raw["token_refresh_interval_hours"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("runtime.token_refresh_interval_hours", n, 1, 720, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.TokenRefreshIntervalHours = n
|
||||
}
|
||||
if cfg.AccountMaxInflight > 0 && cfg.GlobalMaxInflight > 0 && cfg.GlobalMaxInflight < cfg.AccountMaxInflight {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("runtime.global_max_inflight must be >= runtime.account_max_inflight")
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("runtime.global_max_inflight must be >= runtime.account_max_inflight")
|
||||
}
|
||||
runtimeCfg = cfg
|
||||
}
|
||||
@@ -100,7 +99,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
if v, exists := raw["store_ttl_seconds"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("responses.store_ttl_seconds", n, 30, 86400, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.StoreTTLSeconds = n
|
||||
}
|
||||
@@ -112,27 +111,17 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
if v, exists := raw["provider"]; exists {
|
||||
p := strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
if err := config.ValidateTrimmedString("embeddings.provider", p, false); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.Provider = p
|
||||
}
|
||||
embCfg = cfg
|
||||
}
|
||||
|
||||
if raw, ok := req["claude_mapping"].(map[string]any); ok {
|
||||
claudeMap = map[string]string{}
|
||||
for k, v := range raw {
|
||||
key := strings.TrimSpace(k)
|
||||
val := strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
if key == "" || val == "" {
|
||||
continue
|
||||
}
|
||||
claudeMap[key] = val
|
||||
}
|
||||
}
|
||||
|
||||
if raw, ok := req["model_aliases"].(map[string]any); ok {
|
||||
aliasMap = map[string]string{}
|
||||
if aliasMap == nil {
|
||||
aliasMap = map[string]string{}
|
||||
}
|
||||
for k, v := range raw {
|
||||
key := strings.TrimSpace(k)
|
||||
val := strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
@@ -148,7 +137,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
if v, exists := raw["mode"]; exists {
|
||||
mode := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", v)))
|
||||
if err := config.ValidateAutoDeleteMode(mode); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
if mode == "" {
|
||||
mode = "none"
|
||||
@@ -170,15 +159,15 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi
|
||||
if v, exists := raw["trigger_after_turns"]; exists {
|
||||
n := intFrom(v)
|
||||
if err := config.ValidateIntRange("history_split.trigger_after_turns", n, 1, 1000, true); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
cfg.TriggerAfterTurns = &n
|
||||
}
|
||||
if err := config.ValidateHistorySplitConfig(*cfg); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
historySplitCfg = cfg
|
||||
}
|
||||
|
||||
return adminCfg, runtimeCfg, compatCfg, respCfg, embCfg, autoDeleteCfg, historySplitCfg, claudeMap, aliasMap, nil
|
||||
return adminCfg, runtimeCfg, compatCfg, respCfg, embCfg, autoDeleteCfg, historySplitCfg, aliasMap, nil
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ func (h *Handler) getSettings(w http.ResponseWriter, _ *http.Request) {
|
||||
"enabled": h.Store.HistorySplitEnabled(),
|
||||
"trigger_after_turns": h.Store.HistorySplitTriggerAfterTurns(),
|
||||
},
|
||||
"claude_mapping": settingsClaudeMapping(snap),
|
||||
"model_aliases": snap.ModelAliases,
|
||||
"env_backed": h.Store.IsEnvBacked(),
|
||||
"needs_vercel_sync": needsSync,
|
||||
|
||||
@@ -42,13 +42,3 @@ func defaultRuntimeRecommended(accountCount, maxPer int) int {
|
||||
}
|
||||
return accountCount * maxPer
|
||||
}
|
||||
|
||||
func settingsClaudeMapping(c config.Config) map[string]string {
|
||||
if len(c.ClaudeMapping) > 0 {
|
||||
return c.ClaudeMapping
|
||||
}
|
||||
if len(c.ClaudeModelMap) > 0 {
|
||||
return c.ClaudeModelMap
|
||||
}
|
||||
return map[string]string{"fast": "deepseek-v4-flash", "slow": "deepseek-v4-pro"}
|
||||
}
|
||||
|
||||
@@ -346,6 +346,34 @@ func TestUpdateConfigLegacyKeysPreserveStructuredMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateConfigReplacesModelAliases(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{
|
||||
"keys":["k1"],
|
||||
"model_aliases":{"claude-sonnet-4-6":"deepseek-v4-flash"}
|
||||
}`)
|
||||
|
||||
payload := map[string]any{
|
||||
"model_aliases": map[string]any{
|
||||
"gpt-5.5": "deepseek-v4-pro",
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(payload)
|
||||
req := httptest.NewRequest(http.MethodPost, "/admin/config", bytes.NewReader(b))
|
||||
rec := httptest.NewRecorder()
|
||||
h.updateConfig(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
snap := h.Store.Snapshot()
|
||||
if len(snap.ModelAliases) != 1 {
|
||||
t.Fatalf("expected aliases to be replaced, got %#v", snap.ModelAliases)
|
||||
}
|
||||
if snap.ModelAliases["gpt-5.5"] != "deepseek-v4-pro" {
|
||||
t.Fatalf("expected updated alias, got %#v", snap.ModelAliases)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsPasswordInvalidatesOldJWT(t *testing.T) {
|
||||
hash := authn.HashAdminPassword("old-password")
|
||||
h := newAdminTestHandler(t, `{"admin":{"password_hash":"`+hash+`"}}`)
|
||||
|
||||
@@ -17,7 +17,7 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
adminCfg, runtimeCfg, compatCfg, responsesCfg, embeddingsCfg, autoDeleteCfg, historySplitCfg, claudeMap, aliasMap, err := parseSettingsUpdateRequest(req)
|
||||
adminCfg, runtimeCfg, compatCfg, responsesCfg, embeddingsCfg, autoDeleteCfg, historySplitCfg, aliasMap, err := parseSettingsUpdateRequest(req)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()})
|
||||
return
|
||||
@@ -75,10 +75,6 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
c.HistorySplit.TriggerAfterTurns = historySplitCfg.TriggerAfterTurns
|
||||
}
|
||||
}
|
||||
if claudeMap != nil {
|
||||
c.ClaudeMapping = claudeMap
|
||||
c.ClaudeModelMap = nil
|
||||
}
|
||||
if aliasMap != nil {
|
||||
c.ModelAliases = aliasMap
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user