use anyhow::Result;
use serde_json::{Value, json};
use crate::bridge_protocol::{
AppServerHandshakeSummary, ThreadStatusInfo, ThreadTokenUsage, now_millis,
};
use crate::state::BridgeState;
use crate::state::helpers::{normalize_thread, optional_string};
pub(super) fn normalize_thread_summary(
runtime_id: &str,
thread_value: &Value,
archived: bool,
) -> Option<crate::bridge_protocol::ThreadSummary> {
normalize_thread(runtime_id, thread_value, archived).ok()
}
pub(super) fn thread_status_info(status_value: &Value) -> ThreadStatusInfo {
let kind = status_value
.get("type")
.and_then(Value::as_str)
.unwrap_or_else(|| status_value.as_str().unwrap_or("unknown"))
.to_string();
let reason = optional_string(status_value, "reason");
ThreadStatusInfo {
kind,
reason,
raw: status_value.clone(),
}
}
pub(super) fn thread_token_usage(value: &Value) -> ThreadTokenUsage {
ThreadTokenUsage {
input_tokens: first_i64(value, &["inputTokens", "input_tokens"]),
cached_input_tokens: first_i64(value, &["cachedInputTokens", "cached_input_tokens"]),
output_tokens: first_i64(value, &["outputTokens", "output_tokens"]),
reasoning_tokens: first_i64(value, &["reasoningTokens", "reasoning_tokens"]),
total_tokens: first_i64(value, &["totalTokens", "total_tokens"]),
raw: value.clone(),
updated_at_ms: now_millis(),
}
}
fn first_i64(value: &Value, keys: &[&str]) -> Option<i64> {
keys.iter()
.find_map(|key| value.get(*key).and_then(Value::as_i64))
}
pub(super) fn handshake_summary(
state: &str,
experimental_api_enabled: bool,
opt_out_notification_methods: Vec<String>,
detail: Option<String>,
) -> AppServerHandshakeSummary {
AppServerHandshakeSummary::new(
state.to_string(),
experimental_api_enabled,
opt_out_notification_methods,
detail,
)
}
pub(super) fn emit_unhandled_notification_notice(
state: &BridgeState,
runtime_id: &str,
method: &str,
params: &Value,
) -> Result<()> {
let key = format!("app_server_unhandled_notification:{runtime_id}:{method}");
if !state.should_emit_rate_limited_notice(&key) {
return Ok(());
}
state.emit_event(
"app_server_log_chunk",
Some(runtime_id),
params.get("threadId").and_then(Value::as_str),
json!({
"runtimeId": runtime_id,
"stream": "protocol",
"level": "warn",
"source": "bridge.protocol",
"message": format!("收到未处理通知: {method}"),
"detail": {
"kind": "unhandled_notification",
"method": method,
},
"occurredAtMs": now_millis(),
}),
)
}