Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/openhuman/inference/provider/compatible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,13 @@ impl OpenAiCompatibleProvider {
Some(model),
status,
);
} else if super::is_provider_config_rejection_http(status, self.name.as_str(), &error) {
super::log_provider_config_rejection(
"responses_api",
self.name.as_str(),
Some(model),
status,
);
} else if super::should_report_provider_http_failure(status) {
crate::core::observability::report_error(
message.as_str(),
Expand Down Expand Up @@ -856,6 +863,13 @@ impl OpenAiCompatibleProvider {
Some(native_request.model.as_str()),
status,
);
} else if super::is_provider_config_rejection_http(status, self.name.as_str(), &body) {
Comment thread
M3gA-Mind marked this conversation as resolved.
super::log_provider_config_rejection(
"streaming_chat",
self.name.as_str(),
Some(native_request.model.as_str()),
status,
);
} else if super::should_report_provider_http_failure(status) {
crate::core::observability::report_error(
message.as_str(),
Expand Down Expand Up @@ -1348,6 +1362,13 @@ impl Provider for OpenAiCompatibleProvider {
Some(model),
status,
);
} else if super::is_provider_config_rejection_http(status, self.name.as_str(), &error) {
super::log_provider_config_rejection(
"chat_completions",
self.name.as_str(),
Some(model),
status,
);
} else if super::should_report_provider_http_failure(status) {
crate::core::observability::report_error(
message.as_str(),
Expand Down Expand Up @@ -1797,6 +1818,13 @@ impl Provider for OpenAiCompatibleProvider {
Some(model),
status,
);
} else if super::is_provider_config_rejection_http(status, self.name.as_str(), &error) {
super::log_provider_config_rejection(
"native_chat",
self.name.as_str(),
Some(model),
status,
);
} else if super::should_report_provider_http_failure(status) {
crate::core::observability::report_error(
message.as_str(),
Expand Down Expand Up @@ -1952,6 +1980,17 @@ impl Provider for OpenAiCompatibleProvider {
Some(model_owned.as_str()),
status,
);
} else if super::is_provider_config_rejection_http(
status,
provider_name.as_str(),
&raw_error,
) {
super::log_provider_config_rejection(
"stream_chat",
provider_name.as_str(),
Some(model_owned.as_str()),
status,
);
} else if super::should_report_provider_http_failure(status) {
crate::core::observability::report_error(
message.as_str(),
Expand Down
67 changes: 67 additions & 0 deletions src/openhuman/inference/provider/compatible_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use super::*;
use sentry::test::TestTransport;
use std::sync::Arc;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

fn make_provider(name: &str, url: &str, key: Option<&str>) -> OpenAiCompatibleProvider {
OpenAiCompatibleProvider::new(name, url, key, AuthStyle::Bearer)
Expand Down Expand Up @@ -374,6 +378,69 @@ async fn chat_via_responses_requires_non_system_message() {
.contains("requires at least one non-system message"));
}

#[tokio::test]
async fn streaming_chat_config_rejection_propagates_error_without_sentry_report() {
// Representative guardrail for the new provider-config-rejection
// suppression branches in compatible.rs: streaming_chat should still
// return an error, but it must not call report_error/Sentry for this
// deterministic user-config state.
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/chat/completions"))
.respond_with(
ResponseTemplate::new(400)
.set_body_string("invalid temperature: only 1 is allowed for this model"),
)
.mount(&mock_server)
.await;

let transport = TestTransport::new();
let sentry_options = sentry::ClientOptions {
dsn: Some("https://public@sentry.invalid/1".parse().unwrap()),
transport: Some(Arc::new(transport.clone())),
..Default::default()
};
let sentry_hub = Arc::new(sentry::Hub::new(
Some(Arc::new(sentry_options.into())),
Arc::new(Default::default()),
));
let _sentry_guard = sentry::HubSwitchGuard::new(sentry_hub);

let provider =
OpenAiCompatibleProvider::new("custom_openai", &mock_server.uri(), None, AuthStyle::None);
let request = NativeChatRequest {
model: "kimi-k2".to_string(),
messages: vec![NativeMessage {
role: "user".to_string(),
content: Some("hello".to_string()),
tool_call_id: None,
tool_calls: None,
}],
temperature: Some(0.7),
stream: Some(true),
tools: None,
tool_choice: None,
thread_id: None,
stream_options: Some(super::compatible_types::OpenAiStreamOptions {
include_usage: true,
}),
};
let (delta_tx, _delta_rx) = tokio::sync::mpsc::channel(8);

let err = provider
.stream_native_chat(None, &request, &delta_tx, 0)
.await
.expect_err("400 provider config-rejection must still propagate as Err");
assert!(
err.to_string().contains("streaming API error"),
"err: {err}"
);
assert!(
transport.fetch_and_clear_events().is_empty(),
"provider config-rejection must not be reported to Sentry"
);
}

// ----------------------------------------------------------
// Custom endpoint path tests (Issue #114)
// ----------------------------------------------------------
Expand Down
Loading