Skip to main content

sim_lib_bridge/
tx.rs

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
13/// Canonicalizes, stamps, and locally validates a packet before transmission.
14pub 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
35/// Renders the model-facing BRIDGE line face and ownership spans.
36pub 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
67/// Builds an eval request for a packet after running the TX self-check gate.
68pub 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
73/// Runs one checked BRIDGE exchange over a target eval fabric.
74pub 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}