codex-mobile-bridge 0.3.11

Remote bridge and service manager for codex-mobile.
Documentation
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));
    }
}