From 01d698a49e5f6feb15118efcacafccf9a140fc8c Mon Sep 17 00:00:00 2001 From: "cyrus@tinyhumans.ai" Date: Fri, 29 May 2026 17:21:46 +0530 Subject: [PATCH 1/2] fix(inference): return empty model list on 404 from /models endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many OpenAI-compatible providers (DeepSeek, Kimi, Moonshot, custom proxies) do not implement the /models discovery endpoint and return 404. The previous code treated this as an error, which propagated up through `inference_list_models` and fired a Sentry event for each call. Fix: when the /models endpoint returns 404, return an empty model list with `unsupported: true` instead of erroring. Providers without model listing are expected — not all providers expose /models. Closes #2938, resolves Sentry TAURI-RUST-1Z (819 events). --- src/openhuman/inference/provider/ops.rs | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/openhuman/inference/provider/ops.rs b/src/openhuman/inference/provider/ops.rs index 945e0a8eec..020e99b66a 100644 --- a/src/openhuman/inference/provider/ops.rs +++ b/src/openhuman/inference/provider/ops.rs @@ -112,6 +112,22 @@ async fn list_configured_models_from_config( let status = response.status(); if !status.is_success() { + // A 404 from the /models endpoint means the provider does not support model + // listing — this is expected for many OpenAI-compatible providers (e.g. DeepSeek, + // Moonshot, Kimi, custom proxies). Return an empty model list so the caller can + // proceed normally instead of surfacing a spurious error / Sentry event. + // (Sentry issue TAURI-RUST-1Z — 819 events from this path alone.) + if status == reqwest::StatusCode::NOT_FOUND { + log::debug!( + "[providers][list_models] slug={} returned 404 — provider does not support /models listing; returning empty list", + entry.slug + ); + return Ok(crate::rpc::RpcOutcome::new( + serde_json::json!({ "models": serde_json::Value::Array(vec![]), "unsupported": true }), + vec!["provider does not support model listing (404)".to_string()], + )); + } + let body = response.text().await.unwrap_or_default(); let sanitized = sanitize_api_error(&body); let truncated = crate::openhuman::util::truncate_with_ellipsis(&sanitized, 300); @@ -1207,6 +1223,68 @@ mod tests { ); } + /// Spawn a minimal axum server that always returns 404 for the /models endpoint. + /// Used to verify that providers without model listing return an empty list, + /// not an error (Sentry issue TAURI-RUST-1Z). + async fn spawn_models_404_server() -> String { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("bind"); + let addr = listener.local_addr().expect("local_addr"); + let app = axum::Router::new().route( + "/models", + axum::routing::get(|| async { + (axum::http::StatusCode::NOT_FOUND, "Not Found").into_response() + }), + ); + tokio::spawn(async move { + axum::serve(listener, app).await.expect("serve"); + }); + format!("http://{addr}") + } + + #[tokio::test] + async fn models_404_returns_empty_list_not_error() { + // Providers that return 404 on /models (e.g. DeepSeek, Kimi, custom proxies) + // must yield an empty model list, not an Err. Returning an Err was firing a + // Sentry error for every `inference_list_models` call (TAURI-RUST-1Z, 819 events). + let tmp = tempfile::tempdir().expect("tempdir"); + let endpoint = spawn_models_404_server().await; + + let mut config = Config { + config_path: tmp.path().join("config.toml"), + workspace_dir: tmp.path().join("workspace"), + ..Config::default() + }; + config.secrets.encrypt = false; + config.cloud_providers.push(CloudProviderCreds { + id: "p_custom_test".to_string(), + slug: "custom-no-models".to_string(), + label: "Custom (no /models)".to_string(), + endpoint, + auth_style: AuthStyle::Bearer, + legacy_type: None, + default_model: None, + }); + + let outcome = list_configured_models_from_config("custom-no-models", &config) + .await + .expect("404 from /models must succeed with an empty list"); + + let models = outcome.value["models"] + .as_array() + .expect("response must have a `models` array"); + assert!( + models.is_empty(), + "expected empty model list for a 404 /models endpoint, got: {models:?}" + ); + assert_eq!( + outcome.value["unsupported"], + serde_json::Value::Bool(true), + "unsupported flag must be set to true for 404 providers" + ); + } + #[test] fn factory_backend() { assert!(create_backend_inference_provider( From 53fcc9d1188af0719e932f6822e1b869edd0c95b Mon Sep 17 00:00:00 2001 From: "cyrus@tinyhumans.ai" Date: Fri, 29 May 2026 19:08:50 +0530 Subject: [PATCH 2/2] =?UTF-8?q?ci:=20re-trigger=20Rust=20Core=20Tests=20(p?= =?UTF-8?q?rev=20run=20failed=20=E2=80=94=20no=20space=20left=20on=20devic?= =?UTF-8?q?e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit