codex-mobile-bridge 0.3.7

Remote bridge and service manager for codex-mobile.
Documentation
use anyhow::Result;
use serde_json::{Value, json};

use super::super::helpers::{normalize_thread_summary, thread_status_info, thread_token_usage};
use crate::bridge_protocol::ThreadStatusInfo;
use crate::state::BridgeState;
use crate::state::helpers::{normalize_name, optional_string, required_string};

pub(super) async fn handle_thread_started(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_summary = params
        .get("thread")
        .and_then(|thread| normalize_thread_summary(runtime_id, thread, false));
    if let Some(thread) = thread_summary.as_ref() {
        state.storage.upsert_thread_index(thread)?;
        let _ = state
            .storage
            .record_directory_usage(std::path::Path::new(&thread.cwd));
        let _ = state.emit_directory_state();
    }
    state.emit_event(
        "thread/started",
        Some(runtime_id),
        params
            .get("thread")
            .and_then(|thread| thread.get("id"))
            .and_then(Value::as_str),
        json!({
            "runtimeId": runtime_id,
            "thread": params.get("thread"),
            "threadSummary": thread_summary,
        }),
    )?;
    Ok(())
}

pub(super) async fn handle_thread_status_changed(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_id = required_string(&params, "threadId")?;
    let status_value = params.get("status").cloned().unwrap_or(Value::Null);
    let status_info = thread_status_info(&status_value);
    if let Some(mut thread) = state.storage.get_thread_index(thread_id)? {
        thread.is_loaded = status_info.kind != "notLoaded";
        thread.is_active = status_info.kind == "active";
        thread.status = status_info.kind.clone();
        thread.status_info = status_info.clone();
        state.storage.upsert_thread_index(&thread)?;
    }
    state.emit_event(
        "thread/status/changed",
        Some(runtime_id),
        Some(thread_id),
        json!({
            "runtimeId": runtime_id,
            "threadId": thread_id,
            "status": status_value,
            "statusInfo": status_info,
        }),
    )?;
    Ok(())
}

pub(super) async fn handle_thread_name_updated(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_id = required_string(&params, "threadId")?;
    let thread_name = normalize_name(optional_string(&params, "threadName"));
    if let Some(mut thread) = state.storage.get_thread_index(thread_id)? {
        thread.name = thread_name.clone();
        state.storage.upsert_thread_index(&thread)?;
    }
    state.emit_event(
        "thread/name/updated",
        Some(runtime_id),
        Some(thread_id),
        json!({
            "runtimeId": runtime_id,
            "threadId": thread_id,
            "threadName": thread_name,
        }),
    )?;
    Ok(())
}

pub(super) async fn handle_thread_archived(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_id = required_string(&params, "threadId")?;
    state.storage.set_thread_archived(thread_id, true)?;
    state.emit_event(
        "thread/archived",
        Some(runtime_id),
        Some(thread_id),
        json!({
            "runtimeId": runtime_id,
            "threadId": thread_id,
        }),
    )?;
    Ok(())
}

pub(super) async fn handle_thread_unarchived(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_id = required_string(&params, "threadId")?;
    state.storage.set_thread_archived(thread_id, false)?;
    state.emit_event(
        "thread/unarchived",
        Some(runtime_id),
        Some(thread_id),
        json!({
            "runtimeId": runtime_id,
            "threadId": thread_id,
        }),
    )?;
    Ok(())
}

pub(super) async fn handle_thread_closed(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_id = required_string(&params, "threadId")?;
    if let Some(mut thread) = state.storage.get_thread_index(thread_id)? {
        thread.is_loaded = false;
        thread.is_active = false;
        thread.status = "closed".to_string();
        thread.status_info = ThreadStatusInfo {
            kind: "closed".to_string(),
            reason: optional_string(&params, "reason"),
            raw: params
                .get("status")
                .cloned()
                .unwrap_or_else(|| json!({ "type": "closed" })),
        };
        state.storage.upsert_thread_index(&thread)?;
    }
    state.emit_event(
        "thread/closed",
        Some(runtime_id),
        Some(thread_id),
        json!({
            "runtimeId": runtime_id,
            "threadId": thread_id,
            "reason": params.get("reason"),
        }),
    )?;
    Ok(())
}

pub(super) async fn handle_thread_token_usage_updated(
    state: &BridgeState,
    runtime_id: &str,
    params: Value,
) -> Result<()> {
    let thread_id = required_string(&params, "threadId")?;
    let token_usage = thread_token_usage(
        params
            .get("tokenUsage")
            .or_else(|| params.get("usage"))
            .unwrap_or(&Value::Null),
    );
    if let Some(mut thread) = state.storage.get_thread_index(thread_id)? {
        thread.token_usage = Some(token_usage.clone());
        state.storage.upsert_thread_index(&thread)?;
    }
    state.emit_event(
        "thread/tokenUsage/updated",
        Some(runtime_id),
        Some(thread_id),
        json!({
            "runtimeId": runtime_id,
            "threadId": thread_id,
            "tokenUsage": token_usage,
        }),
    )?;
    Ok(())
}