Synced from monorepo
Changes: - Gate session-lifecycle heap steady state with a dhat soak - Unbreak merge lifecycle e2e after default model → grok-4.5 - Scan home-scope rules dirs at <root>/rules - Complete text-input paste and terminal parity - Gate project roles and personas - Use canonical editing in dialogs - Use canonical editing in search bars - Reject ambiguous MCP tool IDs - Harden Git operands for plugins - Simplify queue drain API - Pass RFC 9207 iss through MCP OAuth token exchange - Show leader roster when local agents map is empty - Use canonical editing in Persona views - Remove marketplace default-skills auto-install and purge old installs - Use canonical editing in extension forms - Add canonical dashboard text editing - Use canonical editing in settings - Add /summarize as a /recap alias - Restore previous agent when exiting dashboard - Use tool_choice auto for compaction - Settings toggle for snap-prompt-to-top on send - Update default models to grok-4.5 - Source login shell once for local bash (env + alias/function snapshot) - Template hardcoded param names in server-native tool descriptions - Fix System-Reminder XML tag injection in CLAUDE.md via agents_md - Fix remote workspace-server hardcoding LSP trust (repo code execution risk) - Clear orphaned tool-call updates at turn end - Suppress task wake after cancel - Send x-grok-client-identifier on direct API tool calls - Harden dashboard peek lease transitions - Host /btw side panel in live region (minimal mode) - Bound scroll presentation latency - Highlight multi-line constructs correctly in diffs and the file viewer - Block web_fetch non-public IPs; local opt-in is explicit-host only - Seed coding_data_retention_opt_out=false for OAuth e2es in pty-harness - Follow up clipboard delivery feedback - Use canonical editing in pickers - Route TextArea through canonical editor - Persistent "watching" status row; quieter turn markers - Gate sensitive edit targets - Expose agent registry counts and gate session churn on them - Default coding data sharing to opt-out until server preference applies - Wire chat attachment ids through gateway prompts - On auth refresh failure, issue retry - Forward preview provenance and computer lifecycle state - Document independent privacy controls and scope /privacy output - Strip SamplingError Display prefix on rate-limit UI copy - Stop dumping Cloudflare HTML into Retry failed - Disable in-place prompt edit (scroll jank on enter) - Strip forced ANSI color from gh pr view JSON - Plumb bash tool description onto ToolUsageCard wire
This commit is contained in:
parent
98c3b2438a
commit
7cfcb20d2b
292 changed files with 23315 additions and 9209 deletions
120
crates/codegen/xai-grok-sampler/tests/cf_edge_error_message.rs
Normal file
120
crates/codegen/xai-grok-sampler/tests/cf_edge_error_message.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
//! Wire e2e + pure tests for user-facing API error sanitization.
|
||||
//!
|
||||
//! Edge proxies return non-JSON bodies (HTML). Those must never reach TUI
|
||||
//! scrollback; only structured JSON error envelopes and status-based copy.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use xai_grok_sampler::{SamplerConfig, SamplingClient};
|
||||
use xai_grok_sampling_types::{
|
||||
ContentPart, ConversationItem, ConversationRequest, UserItem, status_user_message,
|
||||
user_facing_api_error_message,
|
||||
};
|
||||
use xai_grok_test_support::{MockInferenceServer, ScriptedResponse};
|
||||
|
||||
const CF_524_HTML: &str = r#"<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head><title>grok.com | 524: A timeout occurred</title></head>
|
||||
<body>
|
||||
<h1>A timeout occurred <span>Error code 524</span></h1>
|
||||
<div>Visit cloudflare.com for more information.</div>
|
||||
</body>
|
||||
</html>"#;
|
||||
|
||||
fn test_config(base_url: &str, api_key: &str) -> SamplerConfig {
|
||||
SamplerConfig {
|
||||
api_key: Some(api_key.to_string()),
|
||||
base_url: base_url.to_string(),
|
||||
model: "test-model".to_string(),
|
||||
..SamplerConfig::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn user_request(text: &str) -> ConversationRequest {
|
||||
ConversationRequest {
|
||||
items: vec![ConversationItem::User(UserItem {
|
||||
content: vec![ContentPart::Text {
|
||||
text: Arc::<str>::from(text),
|
||||
}],
|
||||
..Default::default()
|
||||
})],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_err(status: u16, body: &str) -> xai_grok_sampling_types::SamplingError {
|
||||
let server = MockInferenceServer::start().await.expect("start mock");
|
||||
server.enqueue_response("/v1/chat/completions", ScriptedResponse::text(status, body));
|
||||
let mut cfg = test_config(&server.url(), "test-key");
|
||||
cfg.max_retries = Some(0);
|
||||
let client = SamplingClient::new(cfg).expect("client");
|
||||
match client.conversation_stream(user_request("hi")).await {
|
||||
Ok(_) => panic!("expected API error"),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn stream_524_html_uses_status_copy() {
|
||||
let err = stream_err(524, CF_524_HTML).await;
|
||||
let s = err.to_string();
|
||||
assert!(!s.contains("<!DOCTYPE") && !s.contains("<html"));
|
||||
assert!(s.contains(&status_user_message(
|
||||
reqwest::StatusCode::from_u16(524).unwrap()
|
||||
)));
|
||||
assert!(s.contains("524"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn stream_503_html_uses_unavailable_copy() {
|
||||
let err = stream_err(503, "<html><body>Service Unavailable</body></html>").await;
|
||||
let s = err.to_string();
|
||||
assert!(!s.contains("<html"));
|
||||
assert!(s.contains("temporarily unavailable"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn stream_json_error_envelope_is_preserved() {
|
||||
let body = r#"{"error":{"message":"rate limit exceeded","type":"rate_limit_error"}}"#;
|
||||
let err = stream_err(429, body).await;
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("rate limit exceeded"));
|
||||
assert!(!s.contains("temporarily unavailable"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_user_message_matrix() {
|
||||
let cases: &[(u16, &str)] = &[
|
||||
(502, "temporarily unavailable"),
|
||||
(503, "temporarily unavailable"),
|
||||
(504, "temporarily unavailable"),
|
||||
(520, "timed out"),
|
||||
(524, "timed out"),
|
||||
(500, "Something went wrong on the server"),
|
||||
(400, "Request failed"),
|
||||
];
|
||||
for &(code, needle) in cases {
|
||||
let msg = status_user_message(reqwest::StatusCode::from_u16(code).unwrap());
|
||||
assert!(
|
||||
msg.contains(needle),
|
||||
"status {code}: expected {needle:?} in {msg:?}"
|
||||
);
|
||||
assert!(
|
||||
msg.contains(&format!("HTTP {code}")),
|
||||
"status {code}: expected HTTP code in {msg:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_json_empty_body_falls_back_to_status() {
|
||||
let msg = user_facing_api_error_message(reqwest::StatusCode::BAD_GATEWAY, b"");
|
||||
assert_eq!(msg, status_user_message(reqwest::StatusCode::BAD_GATEWAY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structured_json_is_not_replaced_by_status_copy() {
|
||||
let bytes = br#"{"error":{"message":"credits exhausted","type":"server_error"}}"#;
|
||||
let msg = user_facing_api_error_message(reqwest::StatusCode::PAYMENT_REQUIRED, bytes);
|
||||
assert_eq!(msg, "credits exhausted");
|
||||
}
|
||||
Loading…
Reference in a new issue