BigPizzaV3/CodexPlusPlus · error
上游没有返回可用模型
Error message
上游没有返回可用模型
What it means
fetch_relay_profile_model_ids (crates/codex-plus-core/src/model_catalog.rs:588-593) fetches the /models endpoint via fetch_models_from_source; when zero model ids are parsed out of the response it bails, using the status JSON's `message` when present (e.g. 'HTTP 401' or a reqwest error) and defaulting to '上游没有返回可用模型' when the fetch technically succeeded but parse_model_payload found nothing. So this error is the aggregate of 'fetch failed' and 'fetch succeeded but list is empty'.
Source
Thrown at crates/codex-plus-core/src/model_catalog.rs:593
base_url: if profile.upstream_base_url.trim().is_empty() {
profile.base_url.trim().to_string()
} else {
profile.upstream_base_url.trim().to_string()
},
api_key: profile.api_key.trim().to_string(),
};
if source.base_url.is_empty() {
anyhow::bail!("Base URL 不能为空");
}
let endpoint = models_endpoint(&source.base_url);
let client = crate::http_client::proxied_client(&profile.user_agent)?;
let (models, status) = fetch_models_from_source(&client, &source).await;
if models.is_empty() {
let message = status
.get("message")
.and_then(Value::as_str)
.unwrap_or("上游没有返回可用模型");
anyhow::bail!("{message}");
}
Ok((models, endpoint))
}
fn preferred_responses_api_status(sources: &[Value]) -> Value {
let statuses = sources
.iter()
.filter_map(|source| source.get("responses_api"))
.collect::<Vec<_>>();
for wanted in ["unsupported", "supported", "failed"] {
if let Some(status) = statuses
.iter()
.find(|status| status.get("status").and_then(Value::as_str) == Some(wanted))
{
return (*status).clone();
}
}
responses_api_status("unknown", "", "")View on GitHub (pinned to 1f431ae49b)
Solutions
- curl the endpoint yourself: `curl -H 'Authorization: Bearer <key>' <base_url>/models` and check status plus JSON shape — fix the URL (add /v1) or key accordingly
- Verify api_key is set on the profile: a blank key sends no Authorization header and many relays answer 401 (surfaced as message 'HTTP 401')
- Confirm the upstream implements the OpenAI-compatible models list (data[].id entries) — parse_model_payload only recognizes that shape; if not, populate the model list manually in the profile
- Check proxy environment variables (HTTP_PROXY/HTTPS_PROXY) that proxied_client honors and that may block loopback/upstream reachability
- Retry after upstream recovery — transient 5xx or empty lists surface identically
Example fix
// before
let profile = RelayProfile {
base_url: "https://relay.example.com".into(), // website root, not API root
..Default::default()
};
fetch_relay_profile_model_ids(&profile).await?; // bail: 上游没有返回可用模型
// after
let profile = RelayProfile {
base_url: "https://relay.example.com/v1".into(),
api_key: load_key(),
..Default::default()
};
fetch_relay_profile_model_ids(&profile).await?; Defensive patterns
Strategy: retry
Validate before calling
// Probe the models endpoint before wiring the profile into the catalog
async fn models_endpoint_ok(client: &reqwest::Client, base: &str, key: &str) -> bool {
let url = format!("{}/models", base.trim_end_matches('/'));
let mut req = client.get(&url).header("accept", "application/json");
if !key.is_empty() { req = req.bearer_auth(key); }
matches!(req.send().await, Ok(resp) if resp.status().is_success())
} Type guard
fn profile_fetchable(profile: &RelayProfile) -> bool {
relay_profile_has_base_url(profile) // non-empty URL is the precondition; key may be optional
} Try / catch
match fetch_relay_profile_model_ids(&profile).await {
Err(e) => {
let msg = e.to_string();
if msg.contains("HTTP 401") || msg.contains("HTTP 403") {
surface_to_user("relay auth rejected; check api_key");
} else if msg.contains("上游没有返回可用模型") || msg.contains("HTTP 4") {
surface_to_user("endpoint returned no models; check base_url includes /v1 and supports /models");
} else {
retry_later(profile.id); // network-class failures: transient
}
}
Ok((models, endpoint)) => { /* cache models */ }
} Prevention
- Store the API root (usually ending in /v1) as base_url and verify with curl before saving
- Keep api_key current; blank keys omit Authorization and many relays return 401
- Cache last-good model lists so transient upstream failures don't wipe the catalog
- Check HTTP(S)_PROXY env vars the proxied client honors when requests fail
When it happens
Trigger: Refreshing a relay profile's model list when: the endpoint returns non-2xx (message becomes 'HTTP 401/404/...'), the connection fails (message becomes the reqwest error), or the response is 2xx but contains no recognizable model ids (unexpected JSON schema, empty data[] array, HTML login page parsed as JSON failure).
Common situations: Wrong or incomplete base URL (missing /v1, pointing at a website root); expired or missing api_key causing 401; relay providers that don't implement OpenAI-compatible GET /models; upstream temporarily empty or filtered; a proxy env var (proxied_client honors it) black-holing the request.
Related errors
- Base URL 不能为空
- openai/plugins marketplace download is too large: {} bytes
- provider sync requires launcher hooks with codex-plus-data i
- Remote Control session recovery requires launcher hooks with
- macOS open command is empty
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/4a3a46aa0cbc9198.
Report an issue: GitHub.