Skip to main content

zagens_runtime/repl/
sandbox.rs

1//! REPL fence-extraction utilities.
2//!
3//! The agent's main loop scans assistant text for ` ```repl ` fenced blocks
4//! and feeds them to a [`crate::repl::runtime::PythonRuntime`]. Capturing
5//! `FINAL(...)` and routing sub-LLM RPCs are handled inside the runtime via
6//! a stdin/stdout protocol — no scraping required here.
7
8/// Check if a string contains a `` ```repl `` fenced code block.
9pub fn has_repl_block(text: &str) -> bool {
10    text.contains("```repl")
11}
12
13/// Extract every `` ```repl `` block from `text` with byte offsets.
14pub fn extract_repl_blocks(text: &str) -> Vec<ReplBlock> {
15    let mut blocks = Vec::new();
16    let mut rest = text;
17
18    while let Some(start_idx) = rest.find("```repl") {
19        let after_fence = &rest[start_idx..];
20        let code_start = after_fence.find('\n').unwrap_or(after_fence.len());
21        let code_region = &after_fence[code_start..];
22        let Some(end_offset) = code_region.find("\n```") else {
23            break;
24        };
25        let code = code_region[..end_offset].to_string();
26        let global_start = text.len() - rest.len() + start_idx;
27        let global_end = global_start + code_start + end_offset + 3;
28        blocks.push(ReplBlock {
29            code,
30            start_offset: global_start,
31            end_offset: global_end,
32        });
33        rest = &after_fence[code_start + end_offset + 4..];
34    }
35
36    blocks
37}
38
39/// A `` ```repl `` code block with byte-offset position info.
40#[derive(Debug, Clone)]
41pub struct ReplBlock {
42    pub code: String,
43    pub start_offset: usize,
44    pub end_offset: usize,
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn has_repl_block_detects_fence() {
53        assert!(has_repl_block("some text ```repl\ncode\n``` more"));
54        assert!(!has_repl_block("no repl here ```python\ncode\n```"));
55        assert!(!has_repl_block("just text"));
56    }
57
58    #[test]
59    fn extract_repl_blocks_single() {
60        let text = "before\n```repl\nprint('hello')\n```\nafter";
61        let blocks = extract_repl_blocks(text);
62        assert_eq!(blocks.len(), 1);
63        assert_eq!(blocks[0].code.trim(), "print('hello')");
64    }
65
66    #[test]
67    fn extract_repl_blocks_multiple() {
68        let text = "```repl\ncode1\n```\nmid\n```repl\ncode2\n```\nend";
69        let blocks = extract_repl_blocks(text);
70        assert_eq!(blocks.len(), 2);
71        assert_eq!(blocks[0].code.trim(), "code1");
72        assert_eq!(blocks[1].code.trim(), "code2");
73    }
74
75    #[test]
76    fn extract_repl_blocks_empty_when_none() {
77        let blocks = extract_repl_blocks("no blocks here");
78        assert!(blocks.is_empty());
79    }
80}