{"record":{"id":"7f43e6ca45dacc8b","repo":"BigPizzaV3/CodexPlusPlus","slug":"field-name-key","errorCode":null,"errorMessage":"{field_name} 的模型 {key} 值必须是字符串","messagePattern":"(.+?) 的模型 (.+?) 值必须是字符串","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":2112,"sourceCode":"\nfn parse_model_string_map(\n    value: &str,\n    field_name: &str,\n) -> anyhow::Result<HashMap<String, String>> {\n    if value.trim().is_empty() {\n        return Ok(HashMap::new());\n    }\n    let parsed: Value = serde_json::from_str(value)\n        .map_err(|error| anyhow::anyhow!(\"{field_name} JSON 解析失败：{error}\"))?;\n    let object = parsed\n        .as_object()\n        .ok_or_else(|| anyhow::anyhow!(\"{field_name} 必须是 JSON 对象\"))?;\n    object\n        .iter()\n        .map(|(key, value)| {\n            let value = value\n                .as_str()\n                .ok_or_else(|| anyhow::anyhow!(\"{field_name} 的模型 {key} 值必须是字符串\"))?;\n            Ok((key.clone(), value.to_string()))\n        })\n        .collect()\n}\n\nfn validate_model_windows(model_windows: &HashMap<String, String>) -> anyhow::Result<()> {\n    for (slug, value) in model_windows {\n        if crate::model_suffix::parse_window_token(value).is_none() {\n            anyhow::bail!(\"model_windows 的模型 {slug} 窗口值无效：{value}\");\n        }\n    }\n    Ok(())\n}\n\nfn validate_model_auto_compact(model_auto_compact: &HashMap<String, String>) -> anyhow::Result<()> {\n    for (slug, value) in model_auto_compact {\n        if crate::model_suffix::parse_compact_percent(value).is_none() {\n            anyhow::bail!(\"model_auto_compact 的模型 {slug} 百分比无效：{value}\");","sourceCodeStart":2094,"sourceCodeEnd":2130,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/relay_config.rs#L2094-L2130","documentation":"When iterating the parsed JSON object, parse_model_string_map requires every value to be a JSON string (value.as_str). If any entry's value is a number, boolean, object or array, it throws \"{field_name} 的模型 {key} 值必须是字符串\" naming the offending model key. This keeps map fields uniformly string-typed.","triggerScenarios":"model_windows written as {\"deepseek-v4-pro\": 1000000} (number) or {\"deepseek-v4-pro\": true} instead of a quoted string value.","commonSituations":"Users entering window sizes as bare integers (natural in JSON) without quotes; JSON produced by other tooling that serializes numeric windows.","solutions":["Quote the value: change {\"model\": 1000000} to {\"model\": \"1000000\"}.","Convert all numeric/boolean values in the field to strings before saving the profile.","Sanitize with code: after parsing, map every value through to_string() and re-serialize.","Prefer the `model[1M]` list syntax which avoids the JSON map entirely."],"exampleFix":"// before\nmodel_windows = \"{\\\"deepseek-v4-pro\\\": 1000000}\"\n// after\nmodel_windows = \"{\\\"deepseek-v4-pro\\\": \\\"1000000\\\"}\"","handlingStrategy":"type-guard","validationCode":"fn all_values_are_strings(json: &str) -> bool {\n    serde_json::from_str::<serde_json::Value>(json).ok()\n        .and_then(|v| v.as_object().cloned())\n        .map(|o| o.values().all(|v| v.is_string()))\n        .unwrap_or(false)\n}","typeGuard":"fn is_string_entry((k, v): (&String, &serde_json::Value)) -> bool {\n    v.is_string() || { eprintln!(\"{k} value must be a string\"); false }\n}","tryCatchPattern":"match normalize_relay_profile_for_storage(profile) {\n    Err(e) if e.to_string().contains(\"值必须是字符串\") => {\n        // re-serialize offending numeric values with to_string() and retry\n    }\n    other => other?,\n}","preventionTips":["Quote all values in map fields: \"1000000\" not 1000000","Run map values through v.to_string() when generating JSON programmatically","Add string-only schema validation in the settings UI"],"tags":["json","type-mismatch","config"],"backgroundTag":"type-mismatch","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}