Download codex-rs/app-server-protocol/src/protocol/event_mapping.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 25.5 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/app-server-protocol/src/protocol/event_mapping.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/app-server-protocol/src/protocol/event_mapping.rs
-
curl -L -o event_mapping.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/app-server-protocol/src/protocol/event_mapping.rs
25.5 kB
| use crate::protocol::common::ServerNotification; | |
| use crate::protocol::item_builders::build_command_execution_begin_item; | |
| use crate::protocol::item_builders::build_command_execution_end_item; | |
| use crate::protocol::item_builders::convert_patch_changes; | |
| use crate::protocol::v2::AgentMessageDeltaNotification; | |
| use crate::protocol::v2::CollabAgentState; | |
| use crate::protocol::v2::CollabAgentTool; | |
| use crate::protocol::v2::CollabAgentToolCallStatus; | |
| use crate::protocol::v2::CommandExecutionOutputDeltaNotification; | |
| use crate::protocol::v2::DynamicToolCallOutputContentItem; | |
| use crate::protocol::v2::DynamicToolCallStatus; | |
| use crate::protocol::v2::FileChangePatchUpdatedNotification; | |
| use crate::protocol::v2::ItemCompletedNotification; | |
| use crate::protocol::v2::ItemStartedNotification; | |
| use crate::protocol::v2::PlanDeltaNotification; | |
| use crate::protocol::v2::ReasoningSummaryPartAddedNotification; | |
| use crate::protocol::v2::ReasoningSummaryTextDeltaNotification; | |
| use crate::protocol::v2::ReasoningTextDeltaNotification; | |
| use crate::protocol::v2::TerminalInteractionNotification; | |
| use crate::protocol::v2::ThreadItem; | |
| use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem; | |
| use codex_protocol::protocol::EventMsg; | |
| use std::collections::HashMap; | |
| /// Build the v2 app-server notification that directly corresponds to a single core event. | |
| /// | |
| /// This only covers the stateless event-to-notification projections that have a one-to-one | |
| /// mapping. Callers remain responsible for any surrounding state checks or side effects before | |
| /// invoking this helper. | |
| pub fn item_event_to_server_notification( | |
| msg: EventMsg, | |
| thread_id: &str, | |
| turn_id: &str, | |
| ) -> ServerNotification { | |
| let thread_id = thread_id.to_string(); | |
| let turn_id = turn_id.to_string(); | |
| match msg { | |
| EventMsg::DynamicToolCallResponse(response) => { | |
| let status = if response.success { | |
| DynamicToolCallStatus::Completed | |
| } else { | |
| DynamicToolCallStatus::Failed | |
| }; | |
| let duration_ms = i64::try_from(response.duration.as_millis()).ok(); | |
| let item = ThreadItem::DynamicToolCall { | |
| id: response.call_id, | |
| namespace: response.namespace, | |
| tool: response.tool, | |
| arguments: response.arguments, | |
| status, | |
| content_items: Some( | |
| response | |
| .content_items | |
| .into_iter() | |
| .map(|item| match item { | |
| CoreDynamicToolCallOutputContentItem::InputText { text } => { | |
| DynamicToolCallOutputContentItem::InputText { text } | |
| } | |
| CoreDynamicToolCallOutputContentItem::InputImage { image_url } => { | |
| DynamicToolCallOutputContentItem::InputImage { image_url } | |
| } | |
| CoreDynamicToolCallOutputContentItem::InputAudio { audio_url } => { | |
| DynamicToolCallOutputContentItem::InputAudio { audio_url } | |
| } | |
| }) | |
| .collect(), | |
| ), | |
| success: Some(response.success), | |
| duration_ms, | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id: response.turn_id, | |
| item, | |
| completed_at_ms: response.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabAgentSpawnBegin(begin_event) => { | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: begin_event.call_id, | |
| tool: CollabAgentTool::SpawnAgent, | |
| status: CollabAgentToolCallStatus::InProgress, | |
| sender_thread_id: begin_event.sender_thread_id.to_string(), | |
| receiver_thread_ids: Vec::new(), | |
| prompt: Some(begin_event.prompt), | |
| model: Some(begin_event.model), | |
| reasoning_effort: Some(begin_event.reasoning_effort), | |
| agents_states: HashMap::new(), | |
| }; | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| started_at_ms: begin_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabAgentSpawnEnd(end_event) => { | |
| let has_receiver = end_event.new_thread_id.is_some(); | |
| let status = match &end_event.status { | |
| codex_protocol::protocol::AgentStatus::Errored(_) | |
| | codex_protocol::protocol::AgentStatus::NotFound => { | |
| CollabAgentToolCallStatus::Failed | |
| } | |
| _ if has_receiver => CollabAgentToolCallStatus::Completed, | |
| _ => CollabAgentToolCallStatus::Failed, | |
| }; | |
| let (receiver_thread_ids, agents_states) = match end_event.new_thread_id { | |
| Some(id) => { | |
| let receiver_id = id.to_string(); | |
| let received_status = CollabAgentState::from(end_event.status.clone()); | |
| ( | |
| vec![receiver_id.clone()], | |
| [(receiver_id, received_status)].into_iter().collect(), | |
| ) | |
| } | |
| None => (Vec::new(), HashMap::new()), | |
| }; | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: end_event.call_id, | |
| tool: CollabAgentTool::SpawnAgent, | |
| status, | |
| sender_thread_id: end_event.sender_thread_id.to_string(), | |
| receiver_thread_ids, | |
| prompt: Some(end_event.prompt), | |
| model: Some(end_event.model), | |
| reasoning_effort: Some(end_event.reasoning_effort), | |
| agents_states, | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| completed_at_ms: end_event.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabAgentInteractionBegin(begin_event) => { | |
| let receiver_thread_ids = vec![begin_event.receiver_thread_id.to_string()]; | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: begin_event.call_id, | |
| tool: CollabAgentTool::SendInput, | |
| status: CollabAgentToolCallStatus::InProgress, | |
| sender_thread_id: begin_event.sender_thread_id.to_string(), | |
| receiver_thread_ids, | |
| prompt: Some(begin_event.prompt), | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: HashMap::new(), | |
| }; | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| started_at_ms: begin_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabAgentInteractionEnd(end_event) => { | |
| let status = match &end_event.status { | |
| codex_protocol::protocol::AgentStatus::Errored(_) | |
| | codex_protocol::protocol::AgentStatus::NotFound => { | |
| CollabAgentToolCallStatus::Failed | |
| } | |
| _ => CollabAgentToolCallStatus::Completed, | |
| }; | |
| let receiver_id = end_event.receiver_thread_id.to_string(); | |
| let received_status = CollabAgentState::from(end_event.status); | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: end_event.call_id, | |
| tool: CollabAgentTool::SendInput, | |
| status, | |
| sender_thread_id: end_event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![receiver_id.clone()], | |
| prompt: Some(end_event.prompt), | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: [(receiver_id, received_status)].into_iter().collect(), | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| completed_at_ms: end_event.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::SubAgentActivity(activity) => { | |
| let item = ThreadItem::SubAgentActivity { | |
| id: activity.event_id, | |
| kind: activity.kind.into(), | |
| agent_thread_id: activity.agent_thread_id.to_string(), | |
| agent_path: String::from(activity.agent_path), | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| completed_at_ms: activity.occurred_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabWaitingBegin(begin_event) => { | |
| let receiver_thread_ids = begin_event | |
| .receiver_thread_ids | |
| .iter() | |
| .map(ToString::to_string) | |
| .collect(); | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: begin_event.call_id, | |
| tool: CollabAgentTool::Wait, | |
| status: CollabAgentToolCallStatus::InProgress, | |
| sender_thread_id: begin_event.sender_thread_id.to_string(), | |
| receiver_thread_ids, | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: HashMap::new(), | |
| }; | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| started_at_ms: begin_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabWaitingEnd(end_event) => { | |
| let status = if end_event.statuses.values().any(|status| { | |
| matches!( | |
| status, | |
| codex_protocol::protocol::AgentStatus::Errored(_) | |
| | codex_protocol::protocol::AgentStatus::NotFound | |
| ) | |
| }) { | |
| CollabAgentToolCallStatus::Failed | |
| } else { | |
| CollabAgentToolCallStatus::Completed | |
| }; | |
| let receiver_thread_ids = end_event.statuses.keys().map(ToString::to_string).collect(); | |
| let agents_states = end_event | |
| .statuses | |
| .iter() | |
| .map(|(id, status)| (id.to_string(), CollabAgentState::from(status.clone()))) | |
| .collect(); | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: end_event.call_id, | |
| tool: CollabAgentTool::Wait, | |
| status, | |
| sender_thread_id: end_event.sender_thread_id.to_string(), | |
| receiver_thread_ids, | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states, | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| completed_at_ms: end_event.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabCloseBegin(begin_event) => { | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: begin_event.call_id, | |
| tool: CollabAgentTool::CloseAgent, | |
| status: CollabAgentToolCallStatus::InProgress, | |
| sender_thread_id: begin_event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![begin_event.receiver_thread_id.to_string()], | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: HashMap::new(), | |
| }; | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| started_at_ms: begin_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabCloseEnd(end_event) => { | |
| let status = match &end_event.status { | |
| codex_protocol::protocol::AgentStatus::Errored(_) | |
| | codex_protocol::protocol::AgentStatus::NotFound => { | |
| CollabAgentToolCallStatus::Failed | |
| } | |
| _ => CollabAgentToolCallStatus::Completed, | |
| }; | |
| let receiver_id = end_event.receiver_thread_id.to_string(); | |
| let agents_states = [( | |
| receiver_id.clone(), | |
| CollabAgentState::from(end_event.status), | |
| )] | |
| .into_iter() | |
| .collect(); | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: end_event.call_id, | |
| tool: CollabAgentTool::CloseAgent, | |
| status, | |
| sender_thread_id: end_event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![receiver_id], | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states, | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| completed_at_ms: end_event.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabResumeBegin(begin_event) => { | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: begin_event.call_id, | |
| tool: CollabAgentTool::ResumeAgent, | |
| status: CollabAgentToolCallStatus::InProgress, | |
| sender_thread_id: begin_event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![begin_event.receiver_thread_id.to_string()], | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: HashMap::new(), | |
| }; | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| started_at_ms: begin_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::CollabResumeEnd(end_event) => { | |
| let status = match &end_event.status { | |
| codex_protocol::protocol::AgentStatus::Errored(_) | |
| | codex_protocol::protocol::AgentStatus::NotFound => { | |
| CollabAgentToolCallStatus::Failed | |
| } | |
| _ => CollabAgentToolCallStatus::Completed, | |
| }; | |
| let receiver_id = end_event.receiver_thread_id.to_string(); | |
| let agents_states = [( | |
| receiver_id.clone(), | |
| CollabAgentState::from(end_event.status), | |
| )] | |
| .into_iter() | |
| .collect(); | |
| let item = ThreadItem::CollabAgentToolCall { | |
| id: end_event.call_id, | |
| tool: CollabAgentTool::ResumeAgent, | |
| status, | |
| sender_thread_id: end_event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![receiver_id], | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states, | |
| }; | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item, | |
| completed_at_ms: end_event.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::AgentMessageContentDelta(event) => { | |
| let codex_protocol::protocol::AgentMessageContentDeltaEvent { item_id, delta, .. } = | |
| event; | |
| ServerNotification::AgentMessageDelta(AgentMessageDeltaNotification { | |
| thread_id, | |
| turn_id, | |
| item_id, | |
| delta, | |
| }) | |
| } | |
| EventMsg::PlanDelta(event) => ServerNotification::PlanDelta(PlanDeltaNotification { | |
| thread_id, | |
| turn_id, | |
| item_id: event.item_id, | |
| delta: event.delta, | |
| }), | |
| EventMsg::ReasoningContentDelta(event) => { | |
| ServerNotification::ReasoningSummaryTextDelta(ReasoningSummaryTextDeltaNotification { | |
| thread_id, | |
| turn_id, | |
| item_id: event.item_id, | |
| delta: event.delta, | |
| summary_index: event.summary_index, | |
| }) | |
| } | |
| EventMsg::ReasoningRawContentDelta(event) => { | |
| ServerNotification::ReasoningTextDelta(ReasoningTextDeltaNotification { | |
| thread_id, | |
| turn_id, | |
| item_id: event.item_id, | |
| delta: event.delta, | |
| content_index: event.content_index, | |
| }) | |
| } | |
| EventMsg::AgentReasoningSectionBreak(event) => { | |
| ServerNotification::ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification { | |
| thread_id, | |
| turn_id, | |
| item_id: event.item_id, | |
| summary_index: event.summary_index, | |
| }) | |
| } | |
| EventMsg::ItemStarted(item_started_event) => { | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item: item_started_event.item.into(), | |
| started_at_ms: item_started_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::ItemCompleted(item_completed_event) => { | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item: item_completed_event.item.into(), | |
| completed_at_ms: item_completed_event.completed_at_ms, | |
| }) | |
| } | |
| EventMsg::PatchApplyUpdated(event) => { | |
| ServerNotification::FileChangePatchUpdated(FileChangePatchUpdatedNotification { | |
| thread_id, | |
| turn_id, | |
| item_id: event.call_id, | |
| changes: convert_patch_changes(&event.changes), | |
| }) | |
| } | |
| EventMsg::ExecCommandBegin(exec_command_begin_event) => { | |
| ServerNotification::ItemStarted(ItemStartedNotification { | |
| thread_id, | |
| turn_id, | |
| item: build_command_execution_begin_item(&exec_command_begin_event), | |
| started_at_ms: exec_command_begin_event.started_at_ms, | |
| }) | |
| } | |
| EventMsg::ExecCommandOutputDelta(exec_command_output_delta_event) => { | |
| let item_id = exec_command_output_delta_event.call_id; | |
| let delta = String::from_utf8_lossy(&exec_command_output_delta_event.chunk).to_string(); | |
| ServerNotification::CommandExecutionOutputDelta( | |
| CommandExecutionOutputDeltaNotification { | |
| thread_id, | |
| turn_id, | |
| item_id, | |
| delta, | |
| }, | |
| ) | |
| } | |
| EventMsg::TerminalInteraction(terminal_event) => { | |
| ServerNotification::TerminalInteraction(TerminalInteractionNotification { | |
| thread_id, | |
| turn_id, | |
| item_id: terminal_event.call_id, | |
| process_id: terminal_event.process_id, | |
| stdin: terminal_event.stdin, | |
| }) | |
| } | |
| EventMsg::ExecCommandEnd(exec_command_end_event) => { | |
| ServerNotification::ItemCompleted(ItemCompletedNotification { | |
| thread_id, | |
| turn_id, | |
| item: build_command_execution_end_item(&exec_command_end_event), | |
| completed_at_ms: exec_command_end_event.completed_at_ms, | |
| }) | |
| } | |
| _ => unreachable!("unsupported item event"), | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| use codex_protocol::ThreadId; | |
| use codex_protocol::protocol::CollabResumeBeginEvent; | |
| use codex_protocol::protocol::CollabResumeEndEvent; | |
| use codex_protocol::protocol::ExecCommandOutputDeltaEvent; | |
| use codex_protocol::protocol::ExecOutputStream; | |
| use pretty_assertions::assert_eq; | |
| fn assert_item_started_server_notification( | |
| notification: ServerNotification, | |
| expected: ItemStartedNotification, | |
| ) { | |
| match notification { | |
| ServerNotification::ItemStarted(payload) => assert_eq!(payload, expected), | |
| other => panic!("expected item started notification, got {other:?}"), | |
| } | |
| } | |
| fn assert_item_completed_server_notification( | |
| notification: ServerNotification, | |
| expected: ItemCompletedNotification, | |
| ) { | |
| match notification { | |
| ServerNotification::ItemCompleted(payload) => assert_eq!(payload, expected), | |
| other => panic!("expected item completed notification, got {other:?}"), | |
| } | |
| } | |
| fn assert_command_execution_output_delta_server_notification( | |
| notification: ServerNotification, | |
| expected: CommandExecutionOutputDeltaNotification, | |
| ) { | |
| match notification { | |
| ServerNotification::CommandExecutionOutputDelta(payload) => { | |
| assert_eq!(payload, expected) | |
| } | |
| other => panic!("expected command execution output delta, got {other:?}"), | |
| } | |
| } | |
| fn collab_resume_begin_maps_to_item_started_resume_agent() { | |
| let event = CollabResumeBeginEvent { | |
| call_id: "call-1".to_string(), | |
| started_at_ms: 123, | |
| sender_thread_id: ThreadId::new(), | |
| receiver_thread_id: ThreadId::new(), | |
| receiver_agent_nickname: None, | |
| receiver_agent_role: None, | |
| }; | |
| let notification = item_event_to_server_notification( | |
| EventMsg::CollabResumeBegin(event.clone()), | |
| "thread-1", | |
| "turn-1", | |
| ); | |
| assert_item_started_server_notification( | |
| notification, | |
| ItemStartedNotification { | |
| thread_id: "thread-1".to_string(), | |
| turn_id: "turn-1".to_string(), | |
| started_at_ms: event.started_at_ms, | |
| item: ThreadItem::CollabAgentToolCall { | |
| id: event.call_id, | |
| tool: CollabAgentTool::ResumeAgent, | |
| status: CollabAgentToolCallStatus::InProgress, | |
| sender_thread_id: event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![event.receiver_thread_id.to_string()], | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: HashMap::new(), | |
| }, | |
| }, | |
| ); | |
| } | |
| fn collab_resume_end_maps_to_item_completed_resume_agent() { | |
| let event = CollabResumeEndEvent { | |
| call_id: "call-2".to_string(), | |
| completed_at_ms: 456, | |
| sender_thread_id: ThreadId::new(), | |
| receiver_thread_id: ThreadId::new(), | |
| receiver_agent_nickname: None, | |
| receiver_agent_role: None, | |
| status: codex_protocol::protocol::AgentStatus::NotFound, | |
| }; | |
| let receiver_id = event.receiver_thread_id.to_string(); | |
| let notification = item_event_to_server_notification( | |
| EventMsg::CollabResumeEnd(event.clone()), | |
| "thread-2", | |
| "turn-2", | |
| ); | |
| assert_item_completed_server_notification( | |
| notification, | |
| ItemCompletedNotification { | |
| thread_id: "thread-2".to_string(), | |
| turn_id: "turn-2".to_string(), | |
| completed_at_ms: event.completed_at_ms, | |
| item: ThreadItem::CollabAgentToolCall { | |
| id: event.call_id, | |
| tool: CollabAgentTool::ResumeAgent, | |
| status: CollabAgentToolCallStatus::Failed, | |
| sender_thread_id: event.sender_thread_id.to_string(), | |
| receiver_thread_ids: vec![receiver_id.clone()], | |
| prompt: None, | |
| model: None, | |
| reasoning_effort: None, | |
| agents_states: [( | |
| receiver_id, | |
| CollabAgentState::from(codex_protocol::protocol::AgentStatus::NotFound), | |
| )] | |
| .into_iter() | |
| .collect(), | |
| }, | |
| }, | |
| ); | |
| } | |
| fn exec_command_output_delta_maps_to_command_execution_output_delta() { | |
| let notification = item_event_to_server_notification( | |
| EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent { | |
| call_id: "call-1".to_string(), | |
| stream: ExecOutputStream::Stdout, | |
| chunk: b"hello".to_vec(), | |
| }), | |
| "thread-1", | |
| "turn-1", | |
| ); | |
| assert_command_execution_output_delta_server_notification( | |
| notification, | |
| CommandExecutionOutputDeltaNotification { | |
| thread_id: "thread-1".to_string(), | |
| turn_id: "turn-1".to_string(), | |
| item_id: "call-1".to_string(), | |
| delta: "hello".to_string(), | |
| }, | |
| ); | |
| } | |
| } | |