ainl_runtime/adapters/
graph_patch.rs1use std::collections::HashMap;
18use std::sync::Arc;
19
20use serde_json::{json, Value};
21
22use super::PatchAdapter;
23use crate::engine::PatchDispatchContext;
24use ainl_memory::AinlNodeType;
25
26pub trait GraphPatchHostDispatch: Send + Sync {
28 fn on_patch_dispatch(&self, envelope: Value) -> Result<Value, String>;
29}
30
31pub struct GraphPatchAdapter {
34 host: Option<Arc<dyn GraphPatchHostDispatch>>,
35}
36
37impl GraphPatchAdapter {
38 pub const NAME: &'static str = "graph_patch";
39
40 pub fn new() -> Self {
41 Self { host: None }
42 }
43
44 pub fn with_host(host: Arc<dyn GraphPatchHostDispatch>) -> Self {
45 Self { host: Some(host) }
46 }
47}
48
49impl Default for GraphPatchAdapter {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55fn build_envelope(ctx: &PatchDispatchContext<'_>) -> Result<Value, String> {
56 let proc = match &ctx.node.node_type {
57 AinlNodeType::Procedural { procedural } => procedural,
58 _ => {
59 return Err("graph_patch: PatchDispatchContext.node is not procedural".to_string());
60 }
61 };
62 let utf8_preview = std::str::from_utf8(&proc.compiled_graph)
63 .ok()
64 .map(|s| s.chars().take(256).collect::<String>());
65 Ok(json!({
66 "kind": "graph_patch_dispatch",
67 "patch_label": ctx.patch_label,
68 "patch_node_id": ctx.node.id.to_string(),
69 "pattern_name": proc.pattern_name,
70 "patch_version": proc.patch_version,
71 "procedure_type": format!("{:?}", proc.procedure_type),
72 "declared_reads": proc.declared_reads,
73 "compiled_graph_byte_len": proc.compiled_graph.len(),
74 "compiled_graph_utf8_preview": utf8_preview,
75 "tool_sequence": proc.tool_sequence,
76 "trace_id": proc.trace_id,
77 "frame_keys": ctx.frame.keys().cloned().collect::<Vec<String>>(),
78 }))
79}
80
81impl PatchAdapter for GraphPatchAdapter {
82 fn name(&self) -> &str {
83 Self::NAME
84 }
85
86 fn execute(
87 &self,
88 _label: &str,
89 _frame: &HashMap<String, serde_json::Value>,
90 ) -> Result<Value, String> {
91 Err(
92 "graph_patch: dispatch uses execute_patch with PatchDispatchContext; register via \
93 AinlRuntime::register_adapter(GraphPatchAdapter::new()) or register_default_patch_adapters"
94 .to_string(),
95 )
96 }
97
98 fn execute_patch(&self, ctx: &PatchDispatchContext<'_>) -> Result<Value, String> {
99 let envelope = build_envelope(ctx)?;
100 if let Some(h) = &self.host {
101 h.on_patch_dispatch(envelope)
102 } else {
103 Ok(envelope)
104 }
105 }
106}