1use sim_codec_bridge::{
2 BridgeBook, BridgeCallPayload, BridgePacket, OwnedSpan, assert_roundtrip,
3 assert_total_ownership, encode_bridge_text, stamp_packet_cid, warrant_for_packet,
4};
5use sim_kernel::{Consistency, Cx, Error, EvalFabric, EvalMode, EvalRequest, Expr, Result, Symbol};
6use sim_lib_agent_runner_core::{FENCE_DATA_RULE, ModelRequest};
7use sim_value::build::entry;
8
9use crate::brief::render_brief_sentences;
10use crate::model::output_contract_for_packet;
11use crate::rx::{bridge_rx_response, effective_caps, rx_check};
12
13pub fn prepare_packet(
15 cx: &mut Cx,
16 book: &BridgeBook,
17 packet: &BridgePacket,
18) -> Result<BridgePacket> {
19 let mut packet = packet.canonicalized();
20 packet.warrant = Some(warrant_for_packet(book, &packet)?);
21 let packet = stamp_packet_cid(&packet)?;
22 let report = rx_check(cx, book, &packet, None)?;
23 if !report.accepted() {
24 return Err(Error::Eval(format!(
25 "bridge tx self-check failed: {:?}",
26 report.obligations
27 )));
28 }
29 assert_roundtrip(&packet, book)?;
30 let (face, spans) = render_model_face(book, &packet)?;
31 assert_total_ownership(&face, &spans)?;
32 Ok(packet)
33}
34
35pub fn render_model_face(
37 book: &BridgeBook,
38 packet: &BridgePacket,
39) -> Result<(String, Vec<OwnedSpan>)> {
40 let script = encode_bridge_text(packet, book)?;
41 let mut face = script.clone();
42 let mut spans = vec![OwnedSpan::Structural(script)];
43 let sentences = render_brief_sentences(book, packet)?;
44 if !sentences.is_empty() {
45 let marker = "\nFLUENT\n".to_owned();
46 face.push_str(&marker);
47 spans.push(OwnedSpan::Structural(marker));
48 for (id, sentence) in sentences {
49 let text = format!("{sentence}\n");
50 face.push_str(&text);
51 spans.push(OwnedSpan::Frame { id, text });
52 }
53 }
54 let fences = render_call_fences(packet)?;
55 if !fences.is_empty() {
56 let marker = "\nCALL-DATA\n".to_owned();
57 face.push_str(&marker);
58 spans.push(OwnedSpan::Structural(marker));
59 for (id, text) in fences {
60 face.push_str(&text);
61 spans.push(OwnedSpan::Fence { id, text });
62 }
63 }
64 Ok((face, spans))
65}
66
67pub fn bridge_tx(cx: &mut Cx, book: &BridgeBook, packet: &BridgePacket) -> Result<EvalRequest> {
69 let packet = prepare_packet(cx, book, packet)?;
70 eval_request_for_checked_packet(cx, book, &packet)
71}
72
73pub fn run_bridge(
75 cx: &mut Cx,
76 target: &dyn EvalFabric,
77 book: &BridgeBook,
78 packet: BridgePacket,
79) -> Result<(BridgePacket, crate::BridgeReport)> {
80 let packet = prepare_packet(cx, book, &packet)?;
81 let caps = effective_caps(cx, &packet)?;
82 cx.with_capabilities(caps, |cx| {
83 let request = eval_request_for_checked_packet(cx, book, &packet)?;
84 let reply = target.realize(cx, request)?;
85 let response =
86 sim_lib_agent_runner_core::ModelResponse::try_from(reply.value.object().as_expr(cx)?)?;
87 bridge_rx_response(cx, book, &response, Some(&packet))
88 })
89}
90
91pub(crate) fn eval_request_for_checked_packet(
92 cx: &mut Cx,
93 book: &BridgeBook,
94 packet: &BridgePacket,
95) -> Result<EvalRequest> {
96 let (face, _) = render_model_face(book, packet)?;
97 let mut model = ModelRequest::new(
98 Expr::String(face),
99 vec![Expr::String(FENCE_DATA_RULE.to_owned())],
100 );
101 if let Some(cid) = &packet.header.cid {
102 model.extra.push((
103 Expr::Symbol(Symbol::new("bridge-cid")),
104 Expr::String(cid.clone()),
105 ));
106 }
107 output_contract_for_packet(packet)?.into_extra_entries(&mut model.extra);
108 append_call_model_params(packet, &mut model.extra)?;
109 let required_capabilities = effective_caps(cx, packet)?.iter().cloned().collect();
110 Ok(EvalRequest {
111 expr: Expr::from(model),
112 result_shape: None,
113 required_capabilities,
114 deadline: None,
115 consistency: Consistency::default(),
116 mode: EvalMode::default(),
117 answer_limit: None,
118 stream_buffer: None,
119 stream: false,
120 trace: false,
121 })
122}
123
124fn render_call_fences(packet: &BridgePacket) -> Result<Vec<(String, String)>> {
125 let mut fences = Vec::new();
126 for part in &packet.body {
127 if part.kind != Symbol::qualified("bridge", "Call") {
128 continue;
129 }
130 let payload = BridgeCallPayload::from_expr(&part.payload)?;
131 for arg in payload.args {
132 let id = format!(
133 "{}.{}",
134 part.id.as_qualified_str(),
135 arg.name.as_qualified_str()
136 );
137 fences.push((id.clone(), format!("[{id}]\n{}\n", arg.fenced)));
138 }
139 }
140 Ok(fences)
141}
142
143fn append_call_model_params(packet: &BridgePacket, extra: &mut Vec<(Expr, Expr)>) -> Result<()> {
144 let mut calls = Vec::new();
145 for part in &packet.body {
146 if part.kind != Symbol::qualified("bridge", "Call") {
147 continue;
148 }
149 let payload = BridgeCallPayload::from_expr(&part.payload)?;
150 calls.push(Expr::Map(vec![
151 entry("part", Expr::Symbol(part.id.clone())),
152 entry("name", Expr::Symbol(payload.name)),
153 entry(
154 "model-params",
155 Expr::Map(
156 payload
157 .model_params
158 .into_iter()
159 .map(|(name, value)| (Expr::Symbol(name), value))
160 .collect(),
161 ),
162 ),
163 ]));
164 }
165 if !calls.is_empty() {
166 extra.push((
167 Expr::Symbol(Symbol::new("bridge-calls")),
168 Expr::Vector(calls),
169 ));
170 }
171 Ok(())
172}