1use async_trait::async_trait;
22use std::path::Path;
23
24use crate::ffi_manifest::FfiManifest;
25use crate::memory::{MemMessage, MemoryProvider, SessionContext};
26use crate::metrics::TurnMetrics;
27
28#[derive(Default)]
30pub struct FfiSurfaceProvider {
31 block: Option<String>,
34}
35
36impl FfiSurfaceProvider {
37 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42}
43
44#[async_trait]
45impl MemoryProvider for FfiSurfaceProvider {
46 fn name(&self) -> &str {
47 "knowledge_base"
48 }
49
50 async fn initialize(&mut self, ctx: &SessionContext) -> anyhow::Result<()> {
51 let manifest = FfiManifest::from_workspace(Path::new(&ctx.workspace)).unwrap_or_default();
52 let n = manifest.len();
53 self.block = if manifest.is_empty() {
54 None
55 } else {
56 Some(manifest.render_block())
57 };
58 if self.block.is_some() {
59 tracing::info!(crates = n, "knowledge_base: FFI import surface injected");
60 }
61 Ok(())
62 }
63
64 fn system_prompt_block(&self) -> Option<String> {
65 self.block.clone()
66 }
67
68 fn build_messages(&self, _system_prompt: &str, _new_task: &str) -> Vec<MemMessage> {
69 Vec::new()
71 }
72
73 async fn sync_turn(&mut self, _user: &str, _assistant: &str, _metrics: &TurnMetrics) {}
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 fn ctx(workspace: &str) -> SessionContext {
81 SessionContext {
82 workspace: workspace.to_string(),
83 session_id: "s".into(),
84 }
85 }
86
87 fn write_binding(root: &Path, krate: &str, submodule: &str) {
88 let dir = root.join(krate).join("src");
89 std::fs::create_dir_all(&dir).unwrap();
90 std::fs::write(
91 dir.join("pyo3_module.rs"),
92 format!("#[pyclass(name = \"X\", module = \"newt_agent._newt_agent.{submodule}\")] struct X;"),
93 )
94 .unwrap();
95 }
96
97 #[tokio::test]
98 async fn injects_surface_for_a_pyo3_workspace() {
99 let dir = tempfile::tempdir().unwrap();
100 write_binding(dir.path(), "newt-core", "core");
101 let mut p = FfiSurfaceProvider::new();
102 p.initialize(&ctx(dir.path().to_str().unwrap()))
103 .await
104 .unwrap();
105 let block = p.system_prompt_block().expect("a surface block");
106 assert!(block.contains("newt_agent._newt_agent.core"));
107 assert!(block.contains("authoritative"));
108 }
109
110 #[tokio::test]
111 async fn no_op_on_non_pyo3_workspace() {
112 let dir = tempfile::tempdir().unwrap();
113 std::fs::write(dir.path().join("README.md"), "no bindings here").unwrap();
114 let mut p = FfiSurfaceProvider::new();
115 p.initialize(&ctx(dir.path().to_str().unwrap()))
116 .await
117 .unwrap();
118 assert!(p.system_prompt_block().is_none(), "should inject nothing");
119 }
120}