Skip to content

Commit 58186f5

Browse files
authored
Fix unknown server.connected SSE (#3)
1 parent 46bb3a3 commit 58186f5

File tree

7 files changed

+330
-75
lines changed

7 files changed

+330
-75
lines changed

src-tauri/src/backend/event_translator.rs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ pub(crate) fn translate_sse_event(
394394
"session.updated" => translate_session_created_or_updated(properties, state, true),
395395
"session.status" => translate_session_status(properties, state),
396396
"session.idle" => translate_session_idle(properties, state),
397+
"session.error" => translate_session_error(properties, state),
397398
"permission.asked" => translate_sse_permission(properties, state),
398399
"question.asked" => translate_question_asked(properties, state),
399400
"question.replied" => translate_question_completed(properties),
@@ -1447,13 +1448,16 @@ fn translate_session_status(properties: &Value, state: &mut SessionTranslationSt
14471448
vec![build_turn_started(&thread_id, &synthetic_turn_id)]
14481449
}
14491450
"idle" => {
1450-
if turn_id.is_empty() {
1451+
if thread_id.is_empty() {
14511452
return vec![];
14521453
}
14531454
let mut events = Vec::new();
14541455
if let Some(msg_completed) = build_agent_message_completed(state, &thread_id) {
14551456
events.push(msg_completed);
14561457
}
1458+
// Background helper prompts can complete without ever emitting an
1459+
// "active" status, so still emit turn/completed with an empty turn
1460+
// id to unblock shared background collectors.
14571461
events.push(build_turn_completed(&thread_id, &turn_id));
14581462
state.finish_turn(&thread_id);
14591463
events
@@ -1518,6 +1522,52 @@ fn translate_session_idle(properties: &Value, state: &mut SessionTranslationStat
15181522
events
15191523
}
15201524

1525+
fn translate_session_error(properties: &Value, state: &mut SessionTranslationState) -> Vec<Value> {
1526+
let session_id = properties
1527+
.get("sessionID")
1528+
.or_else(|| properties.get("session_id"))
1529+
.or_else(|| properties.get("id"))
1530+
.and_then(|v| v.as_str())
1531+
.unwrap_or_default();
1532+
1533+
if !session_id.is_empty() {
1534+
state.session_id = session_id.to_string();
1535+
}
1536+
1537+
let thread_id = state.session_id.clone();
1538+
if thread_id.is_empty() {
1539+
return vec![];
1540+
}
1541+
1542+
let turn_id = state
1543+
.get_turn_state(&thread_id)
1544+
.map(|ts| ts.turn_id.clone())
1545+
.unwrap_or_default();
1546+
1547+
let error = properties.get("error").unwrap_or(&Value::Null);
1548+
let error_msg = error
1549+
.get("data")
1550+
.and_then(|data| data.get("message"))
1551+
.or_else(|| error.get("message"))
1552+
.or_else(|| error.get("error"))
1553+
.or_else(|| error.get("name"))
1554+
.and_then(|v| v.as_str())
1555+
.unwrap_or("unknown error");
1556+
1557+
state.finish_turn(&thread_id);
1558+
vec![json!({
1559+
"method": "error",
1560+
"params": {
1561+
"threadId": thread_id,
1562+
"turnId": turn_id,
1563+
"willRetry": false,
1564+
"error": {
1565+
"message": error_msg
1566+
}
1567+
}
1568+
})]
1569+
}
1570+
15211571
// ---------------------------------------------------------------------------
15221572
// permission.updated — permission requests from the agent
15231573
// ---------------------------------------------------------------------------
@@ -2441,6 +2491,48 @@ mod tests {
24412491
assert!(events.iter().any(|e| e["method"] == "turn/completed"));
24422492
}
24432493

2494+
#[test]
2495+
fn session_status_idle_without_active_turn_still_completes() {
2496+
let mut state = SessionTranslationState::new(String::new());
2497+
let event = json!({
2498+
"type": "session.status",
2499+
"properties": {
2500+
"sessionID": "ses_background_1",
2501+
"status": { "type": "idle" }
2502+
}
2503+
});
2504+
2505+
let events = translate_sse_event(&event, &mut state);
2506+
assert_eq!(events.len(), 1);
2507+
assert_eq!(events[0]["method"], "turn/completed");
2508+
assert_eq!(events[0]["params"]["threadId"], "ses_background_1");
2509+
assert_eq!(events[0]["params"]["turn"]["id"], "");
2510+
}
2511+
2512+
#[test]
2513+
fn session_error_produces_error_event_without_active_turn() {
2514+
let mut state = SessionTranslationState::new(String::new());
2515+
let event = json!({
2516+
"type": "session.error",
2517+
"properties": {
2518+
"sessionID": "ses_background_2",
2519+
"error": {
2520+
"name": "BadRequestError",
2521+
"data": {
2522+
"message": "model not available"
2523+
}
2524+
}
2525+
}
2526+
});
2527+
2528+
let events = translate_sse_event(&event, &mut state);
2529+
assert_eq!(events.len(), 1);
2530+
assert_eq!(events[0]["method"], "error");
2531+
assert_eq!(events[0]["params"]["threadId"], "ses_background_2");
2532+
assert_eq!(events[0]["params"]["turnId"], "");
2533+
assert_eq!(events[0]["params"]["error"]["message"], "model not available");
2534+
}
2535+
24442536
#[test]
24452537
fn session_status_active_produces_turn_started_when_missing() {
24462538
let mut state = SessionTranslationState::new(String::new());

0 commit comments

Comments
 (0)