BigPizzaV3/CodexPlusPlus · error

model_metadata 必须是 JSON 对象

Error message

model_metadata 必须是 JSON 对象

What it means

After parsing model_metadata, the function requires the top-level JSON value to be an object (as_object). Arrays, strings, numbers or null produce "model_metadata 必须是 JSON 对象". Each object value must itself be an object (checked on the next lines) since it holds per-model metadata.

Solutions

  1. Wrap entries in a top-level object keyed by model slug: {"slug": {...}}.
  2. If you have an array, convert it to an object keyed by each record's slug field.
  3. Validate the parsed value is an object before assigning to the profile.
  4. Clear the field if metadata overrides are not needed.

Example fix

// before
model_metadata = "[{\"slug\": \"deepseek-v4-pro\", \"context_window\": 1000000}]"
// after
model_metadata = "{\"deepseek-v4-pro\": {\"context_window\": 1000000}}"
Defensive patterns

Strategy: type-guard

Validate before calling

fn metadata_is_object(json: &str) -> bool {
    serde_json::from_str::<serde_json::Value>(json)
        .map(|v| v.is_object())
        .unwrap_or(false)
}

Type guard

fn is_object(v: &serde_json::Value) -> bool { v.is_object() }

Try / catch

match apply_model_catalog_to_config(...) {
    Err(e) if e.to_string().contains("model_metadata 必须是 JSON 对象") => {
        // wrap array entries into a slug-keyed object and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: model_metadata set to a JSON array like [{...}], a bare string, or a number — valid JSON but not an object at top level.

Common situations: Users pasting an array of metadata records, or a single model's metadata object without the outer slug→object wrapper.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/633d78c4139f5aeb. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/relay_config.rs:2144

fn validate_model_auto_compact(model_auto_compact: &HashMap<String, String>) -> anyhow::Result<()> {
    for (slug, value) in model_auto_compact {
        if crate::model_suffix::parse_compact_percent(value).is_none() {
            anyhow::bail!("model_auto_compact 的模型 {slug} 百分比无效:{value}");
        }
    }
    Ok(())
}

fn parse_model_metadata_map(metadata_json: &str) -> anyhow::Result<serde_json::Map<String, Value>> {
    if metadata_json.trim().is_empty() {
        return Ok(serde_json::Map::new());
    }
    let value: Value = serde_json::from_str(metadata_json)
        .map_err(|error| anyhow::anyhow!("model_metadata JSON 解析失败:{error}"))?;
    let map = value
        .as_object()
        .ok_or_else(|| anyhow::anyhow!("model_metadata 必须是 JSON 对象"))?;
    for (slug, metadata) in map {
        if !metadata.is_object() {
            anyhow::bail!("model_metadata 的模型 {slug} 值必须是对象");
        }
    }
    Ok(map.clone())
}

fn model_metadata_has_entries(
    metadata: &serde_json::Map<String, Value>,
    entry_slugs: &HashSet<String>,
) -> bool {
    metadata.keys().any(|slug| entry_slugs.contains(slug))
}

fn apply_model_metadata_overrides(
    catalog_json: &str,
    override_map: &serde_json::Map<String, Value>,

View on GitHub (pinned to b1ed92e5e4)