1use sim_codec_bridge::{
2 BridgeBook, BridgePacket, BridgePart, BridgeWeavePayload, BridgeWeaveRow, WeavePart,
3 stamp_packet_cid,
4};
5use sim_kernel::{
6 Consistency, Cx, Error, EvalFabric, EvalMode, EvalReply, EvalRequest, Expr, Result, Symbol,
7};
8use sim_lib_agent_runner_core::{
9 ModelRequest, ModelResponse, OUTPUT_GRAMMAR_DIALECT_EXTRA, OUTPUT_GRAMMAR_EXTRA,
10 OUTPUT_GRAMMAR_REQUIRED_EXTRA, RETURN_CODEC_EXTRA, RETURN_SHAPE_EXTRA, shape_to_grammar,
11 terminal_model_content,
12};
13use sim_shape::{ExprKind, ExprKindShape, FieldShape, FieldSpec};
14use sim_value::{access::field, build::entry};
15use std::sync::Arc;
16
17use crate::frontier::FrontierMenu;
18use crate::loom_validate::{next_frontier_menu, validate_weave};
19use crate::report::{BridgeObligation, BridgeReport};
20use crate::rx::{effective_caps, rx_check};
21
22pub fn validate_woven_row(
25 cx: &mut Cx,
26 packet: &BridgePacket,
27 accepted_rows: &[BridgeWeaveRow],
28 row: BridgeWeaveRow,
29) -> Result<BridgeReport> {
30 let rows = accepted_rows
31 .iter()
32 .cloned()
33 .chain(std::iter::once(row))
34 .collect();
35 let weave = BridgeWeavePayload::new(rows);
36 validate_weave(cx, &packet_with_weave(packet, &weave)?, &weave)
37}
38
39pub fn weave_row_by_row(
47 cx: &mut Cx,
48 target: &dyn EvalFabric,
49 packet: BridgePacket,
50 weave: &WeavePart,
51) -> Result<BridgePacket> {
52 if weave.rows.is_empty() {
53 return Err(Error::Eval(
54 "LOOM woven mode requires at least one row budget entry".to_owned(),
55 ));
56 }
57 require_weave_part(&packet)?;
58
59 let mut accepted_rows = Vec::new();
60 for row_index in 0..weave.rows.len() {
61 let row = request_woven_row(cx, target, &packet, &accepted_rows, row_index, &[])?;
62 let report = validate_woven_row(cx, &packet, &accepted_rows, row.clone())?;
63 if report.accepted() {
64 accepted_rows.push(row);
65 continue;
66 }
67
68 let replacement = request_woven_row(
69 cx,
70 target,
71 &packet,
72 &accepted_rows,
73 row_index,
74 &report.obligations,
75 )?;
76 let replacement_report =
77 validate_woven_row(cx, &packet, &accepted_rows, replacement.clone())?;
78 if !replacement_report.accepted() {
79 return Err(Error::Eval(format!(
80 "LOOM woven repair failed at loom/rows/{row_index}: {:?}",
81 replacement_report.obligations
82 )));
83 }
84 accepted_rows.push(replacement);
85 }
86
87 let mut completed = packet_with_weave(&packet, &BridgeWeavePayload::new(accepted_rows))?;
88 completed.header.cid = None;
89 let completed = stamp_packet_cid(&completed)?;
90 let book = BridgeBook::standard();
91 let report = rx_check(cx, &book, &completed, None)?;
92 if !report.accepted() {
93 return Err(Error::Eval(format!(
94 "LOOM woven packet failed final check: {:?}",
95 report.obligations
96 )));
97 }
98 Ok(completed)
99}
100
101fn request_woven_row(
102 cx: &mut Cx,
103 target: &dyn EvalFabric,
104 packet: &BridgePacket,
105 accepted_rows: &[BridgeWeaveRow],
106 row_index: usize,
107 obligations: &[BridgeObligation],
108) -> Result<BridgeWeaveRow> {
109 let request = woven_row_request(cx, packet, accepted_rows, row_index, obligations)?;
110 let caps = effective_caps(cx, packet)?;
111 let reply = cx.with_capabilities(caps, |cx| target.realize(cx, request))?;
112 decode_row_reply(cx, reply)
113}
114
115fn woven_row_request(
116 cx: &mut Cx,
117 packet: &BridgePacket,
118 accepted_rows: &[BridgeWeaveRow],
119 row_index: usize,
120 obligations: &[BridgeObligation],
121) -> Result<EvalRequest> {
122 let partial_weave = BridgeWeavePayload::new(accepted_rows.to_vec());
123 let partial_packet = packet_with_optional_weave(packet, Some(&partial_weave))?;
124 let menu = next_frontier_menu(cx, &partial_packet)?;
125 let role_refs = role_ref_menu(packet, accepted_rows);
126 let mut model = ModelRequest::new(
127 Expr::Map(vec![
128 entry(
129 "mode",
130 Expr::Symbol(Symbol::qualified("bridge", "loom-woven-row")),
131 ),
132 entry("row-path", row_path(row_index)),
133 entry("head-menu", menu.heads.clone()),
134 entry("role-ref-menu", string_vector(&role_refs)),
135 entry("slot-menu", slot_menu_expr(&menu)),
136 entry("obligations", obligations_expr(obligations)),
137 ]),
138 Vec::new(),
139 );
140 model.extra.push(entry(
141 "bridge-mode",
142 Expr::Symbol(Symbol::qualified("bridge", "loom-woven-row")),
143 ));
144 model
145 .extra
146 .push(entry("bridge-row-path", row_path(row_index)));
147 model
148 .extra
149 .push(entry("bridge-frontier-heads", menu.heads.clone()));
150 model
151 .extra
152 .push(entry("bridge-frontier-slots", slot_menu_expr(&menu)));
153 model
154 .extra
155 .push(entry("bridge-role-refs", string_vector(&role_refs)));
156 model.extra.push(entry(
157 "bridge-frontier-grammar",
158 Expr::String(menu.grammar.clone()),
159 ));
160 if !obligations.is_empty() {
161 model
162 .extra
163 .push(entry("bridge-obligations", obligations_expr(obligations)));
164 }
165 model.extra.push(entry(
166 RETURN_CODEC_EXTRA,
167 Expr::Symbol(Symbol::qualified("codec", "bridge")),
168 ));
169 model
170 .extra
171 .push(entry(RETURN_SHAPE_EXTRA, row_shape_expr(&menu, &role_refs)));
172 model.extra.push(entry(
173 OUTPUT_GRAMMAR_EXTRA,
174 Expr::String(row_grammar(&menu)?),
175 ));
176 model.extra.push(entry(
177 OUTPUT_GRAMMAR_DIALECT_EXTRA,
178 Expr::Symbol(Symbol::new("json-schema")),
179 ));
180 model
181 .extra
182 .push(entry(OUTPUT_GRAMMAR_REQUIRED_EXTRA, Expr::Bool(true)));
183
184 Ok(EvalRequest {
185 expr: Expr::from(model),
186 result_shape: None,
187 required_capabilities: effective_caps(cx, packet)?.iter().cloned().collect(),
188 deadline: None,
189 consistency: Consistency::default(),
190 mode: EvalMode::default(),
191 answer_limit: None,
192 stream_buffer: None,
193 stream: false,
194 trace: false,
195 })
196}
197
198fn decode_row_reply(cx: &mut Cx, reply: EvalReply) -> Result<BridgeWeaveRow> {
199 let response = ModelResponse::try_from(reply.value.object().as_expr(cx)?)?;
200 let content = terminal_model_content(&response)?;
201 BridgeWeaveRow::from_expr(field(content, "row").unwrap_or(content))
202}
203
204fn packet_with_weave(packet: &BridgePacket, weave: &BridgeWeavePayload) -> Result<BridgePacket> {
205 packet_with_optional_weave(packet, Some(weave))
206}
207
208fn packet_with_optional_weave(
209 packet: &BridgePacket,
210 weave: Option<&BridgeWeavePayload>,
211) -> Result<BridgePacket> {
212 let mut packet = packet.clone();
213 let index = require_weave_part(&packet)?;
214 if let Some(weave) = weave {
215 packet.body[index].payload = weave.to_expr();
216 }
217 packet.header.cid = None;
218 Ok(packet)
219}
220
221fn require_weave_part(packet: &BridgePacket) -> Result<usize> {
222 packet
223 .body
224 .iter()
225 .position(is_weave_part)
226 .ok_or_else(|| Error::Eval("LOOM woven mode requires a bridge/Weave part".to_owned()))
227}
228
229fn is_weave_part(part: &BridgePart) -> bool {
230 part.kind == Symbol::qualified("bridge", "Weave")
231}
232
233fn role_ref_menu(packet: &BridgePacket, accepted_rows: &[BridgeWeaveRow]) -> Vec<String> {
234 let mut refs = packet
235 .body
236 .iter()
237 .filter(|part| !is_weave_part(part))
238 .map(|part| part.id.as_qualified_str())
239 .chain(packet.header.context.iter().map(Symbol::as_qualified_str))
240 .chain(accepted_rows.iter().map(|row| row.slot.clone()))
241 .collect::<Vec<_>>();
242 refs.sort();
243 refs.dedup();
244 refs
245}
246
247fn row_shape_expr(menu: &FrontierMenu, role_refs: &[String]) -> Expr {
248 Expr::Map(vec![
249 entry(
250 "shape",
251 Expr::Symbol(Symbol::qualified("bridge", "WeaveRow")),
252 ),
253 entry("slot", Expr::Symbol(Symbol::qualified("core", "String"))),
254 entry("head", menu.heads.clone()),
255 entry("roles", string_vector(role_refs)),
256 ])
257}
258
259fn row_grammar(menu: &FrontierMenu) -> Result<String> {
260 let shape = FieldShape::anonymous(vec![
261 FieldSpec::required(
262 Symbol::new("slot"),
263 Arc::new(ExprKindShape::new(ExprKind::String)),
264 ),
265 FieldSpec::required(Symbol::new("head"), menu.head_shape.clone()),
266 FieldSpec::required(
267 Symbol::new("roles"),
268 Arc::new(ExprKindShape::new(ExprKind::Map)),
269 ),
270 ]);
271 shape_to_grammar(&shape)
272}
273
274fn slot_menu_expr(menu: &FrontierMenu) -> Expr {
275 Expr::Vector(
276 menu.slots
277 .iter()
278 .map(|(slot, shape)| {
279 Expr::Map(vec![
280 entry("slot", Expr::String(slot.clone())),
281 entry("shape", shape.clone()),
282 ])
283 })
284 .collect(),
285 )
286}
287
288fn obligations_expr(obligations: &[BridgeObligation]) -> Expr {
289 Expr::Vector(obligations.iter().map(BridgeObligation::to_expr).collect())
290}
291
292fn string_vector(items: &[String]) -> Expr {
293 Expr::Vector(items.iter().cloned().map(Expr::String).collect())
294}
295
296fn row_path(row_index: usize) -> Expr {
297 Expr::String(format!("loom/rows/{row_index}"))
298}