Skip to content
Open
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
18 changes: 16 additions & 2 deletions app/src/ai/blocklist/controller/response_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,11 +535,12 @@ impl ResponseStream {
RecoveryAction::Fail(reason) => {
log::warn!(
"MultiAgent request failed; not recovering: recovery={} reason={} \
attempt={}/{MAX_RECOVERY_ATTEMPTS} failed_request={} - Error: {error:?}",
attempt={}/{MAX_RECOVERY_ATTEMPTS} failed_request={} {} - Error: {error:?}",
action.log_label(),
reason.log_label(),
self.recovery.attempts_used(),
self.failed_request_label(),
self.request_context_label(),
);
self.error_event_emitted = true;
self.report_request_failure(error, is_online, self.recovery.attempts_used());
Expand All @@ -556,13 +557,26 @@ impl ResponseStream {
fn log_recovery(&self, action: RecoveryAction, wait: &str, error: &Arc<AIApiError>) {
log::warn!(
"MultiAgent request failed; recovering: recovery={} \
attempt={}/{MAX_RECOVERY_ATTEMPTS} wait={wait} failed_request={} - Error: {error:?}",
attempt={}/{MAX_RECOVERY_ATTEMPTS} wait={wait} failed_request={} {} - Error: {error:?}",
action.log_label(),
self.recovery.attempts_used() + 1,
self.failed_request_label(),
self.request_context_label(),
);
}

/// The model the failed request targeted and whether it carried user-provided credentials,
/// so a transport failure can be attributed to hosted or bring-your-own-key routing.
/// Reports presence only, never credential values.
fn request_context_label(&self) -> String {
format!(
"model={} user_api_keys={} custom_model_providers={}",
self.params.model,
self.params.api_keys.is_some(),
self.params.custom_model_providers.is_some(),
)
}

/// Sends the request for `request_id`. When the request's model is served by
/// the connected Grok subscription or may route to Gemini Enterprise, and
/// that credential is already past hard expiry, this first blocks on a
Expand Down
27 changes: 27 additions & 0 deletions app/src/ai/blocklist/controller/response_stream_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,30 @@ fn geap_refresh_does_not_add_credentials_to_a_keyless_request() {

assert!(params.api_keys.is_none());
}

#[cfg(not(target_family = "wasm"))]
#[test]
fn request_context_label_reports_a_hosted_request_without_user_credentials() {
let stream = ResponseStream::new_for_test(ResponseStreamId::new_for_test());

assert_eq!(
stream.request_context_label(),
"model=test-model user_api_keys=false custom_model_providers=false"
);
}

#[cfg(not(target_family = "wasm"))]
#[test]
fn request_context_label_reports_attached_credentials_without_leaking_them() {
let mut stream = ResponseStream::new_for_test(ResponseStreamId::new_for_test());
stream.params = params_with_geap_token("super-secret-token");
stream.params.custom_model_providers = Some(Default::default());

let label = stream.request_context_label();

assert_eq!(
label,
"model=test-model user_api_keys=true custom_model_providers=true"
);
assert!(!label.contains("super-secret-token"));
}
Loading