use anyhow::Result;
use serde_json::{Value, json};
use tracing::warn;
use super::super::runtime::ManagedRuntime;
use super::*;
const THREAD_NOT_MATERIALIZED_MARKERS: [&str; 2] = [
"not materialized yet",
"includeturns is unavailable before first user message",
];
impl BridgeState {
pub(super) async fn read_thread_result_with_turn_fallback(
&self,
runtime: &ManagedRuntime,
thread_id: &str,
) -> Result<Value> {
match runtime
.app_server
.request(
"thread/read",
json!({
"threadId": thread_id,
"includeTurns": true,
}),
)
.await
{
Ok(result) => Ok(result),
Err(error) if should_retry_thread_read_without_turns(&error) => {
warn!(
"thread/read 在空线程上无法返回 turns,回退为只读线程元数据: thread_id={thread_id}"
);
runtime
.app_server
.request(
"thread/read",
json!({
"threadId": thread_id,
"includeTurns": false,
}),
)
.await
}
Err(error) => Err(error),
}
}
}
fn should_retry_thread_read_without_turns(error: &anyhow::Error) -> bool {
let message = error.to_string().to_ascii_lowercase();
THREAD_NOT_MATERIALIZED_MARKERS
.iter()
.any(|marker| message.contains(marker))
}
#[cfg(test)]
mod tests {
use anyhow::anyhow;
use super::should_retry_thread_read_without_turns;
#[test]
fn materialized_thread_error_triggers_turn_fallback() {
let error = anyhow!(
"[-32600] thread 019d8740 is not materialized yet; includeTurns is unavailable before first user message"
);
assert!(should_retry_thread_read_without_turns(&error));
}
#[test]
fn unrelated_thread_read_error_does_not_trigger_fallback() {
let error = anyhow!("[-32000] thread/read failed because runtime is offline");
assert!(!should_retry_thread_read_without_turns(&error));
}
}