Skip to main content

monoloop_testkit/
pipeline.rs

1//! Helpers to feed interpreters and collect/render events.
2
3use monoloop_contracts::{
4    ConnectionId, DialectBinding, DialectDescriptor, ExternalSessionId, InterpretationId,
5    InterpretationLimits, InterpreterOutputEvent,
6};
7use monoloop_interpreter::{DefaultInterpreterFactory, InterpreterFactory, StartInterpretation};
8use std::sync::Arc;
9
10use crate::console::{ConsoleRenderer, ConsoleRendererConfig, ConsoleSink, SyncMemorySink};
11
12/// Cursor ACP dialect binding (stdio NDJSON profile).
13pub fn cursor_acp_binding() -> DialectBinding {
14    DialectBinding::negotiated(DialectDescriptor::cursor_acp("1"))
15}
16
17/// Antigravity / agy ACP dialect binding (stdio NDJSON profile).
18pub fn agy_acp_binding() -> DialectBinding {
19    DialectBinding::negotiated(DialectDescriptor::agy_acp("1"))
20}
21
22/// OpenAI Codex ACP dialect binding (stdio NDJSON profile).
23pub fn codex_acp_binding() -> DialectBinding {
24    DialectBinding::negotiated(DialectDescriptor::codex_acp("1"))
25}
26
27/// Z.ai CLI headless dialect binding (OpenAI-chat NDJSON profile).
28pub fn zai_cli_binding() -> DialectBinding {
29    DialectBinding::negotiated(DialectDescriptor::zai_cli("1"))
30}
31
32/// Claude Code headless dialect binding (stream-json NDJSON profile).
33pub fn claude_code_binding() -> DialectBinding {
34    DialectBinding::negotiated(DialectDescriptor::claude_code("1"))
35}
36
37/// Interpret complete (or pre-concatenated) raw bytes under a dialect binding.
38pub async fn interpret_bytes(
39    dialect: DialectBinding,
40    bytes: &[u8],
41    external_session_id: Option<ExternalSessionId>,
42) -> Vec<InterpreterOutputEvent> {
43    feed_chunks(
44        dialect,
45        &[bytes::Bytes::copy_from_slice(bytes)],
46        external_session_id,
47    )
48    .await
49}
50
51/// Feed arbitrary fragmentation of the same logical stream.
52pub async fn feed_chunks(
53    dialect: DialectBinding,
54    chunks: &[bytes::Bytes],
55    external_session_id: Option<ExternalSessionId>,
56) -> Vec<InterpreterOutputEvent> {
57    let factory = DefaultInterpreterFactory::new();
58    let interp = factory
59        .start(StartInterpretation {
60            interpretation_id: InterpretationId::generate(),
61            connection_id: ConnectionId::new("test-conn"),
62            external_session_id,
63            dialect,
64            limits: InterpretationLimits::default(),
65        })
66        .expect("start");
67
68    for chunk in chunks {
69        interp.input.push_bytes(chunk.clone()).await.expect("push");
70    }
71    interp.input.finish_clean().await.expect("finish");
72    collect_interpretation(&interp).await
73}
74
75/// Drain events until Ended.
76pub async fn collect_interpretation(
77    interp: &monoloop_interpreter::Interpretation,
78) -> Vec<InterpreterOutputEvent> {
79    let mut out = Vec::new();
80    loop {
81        match interp.events.recv().await {
82            Some(ev) => {
83                let done = matches!(ev, InterpreterOutputEvent::Ended(_));
84                out.push(ev);
85                if done {
86                    break;
87                }
88            }
89            None => break,
90        }
91    }
92    out
93}
94
95/// Run interpretation and render to a memory sink; return (events, console text).
96pub async fn interpret_and_render(
97    dialect: DialectBinding,
98    chunks: &[bytes::Bytes],
99) -> (Vec<InterpreterOutputEvent>, String) {
100    let events = feed_chunks(dialect, chunks, None).await;
101    let sink = Arc::new(SyncMemorySink::new());
102    let renderer = ConsoleRenderer::new(ConsoleRendererConfig::default(), sink.clone());
103    for ev in &events {
104        renderer.render(ev);
105    }
106    (events, sink.join())
107}
108
109/// ACP dialect binding helper.
110pub fn acp_binding() -> DialectBinding {
111    DialectBinding::negotiated(DialectDescriptor::acp_json_rpc("1"))
112}
113
114/// Test raw text dialect binding.
115pub fn test_text_binding() -> DialectBinding {
116    DialectBinding::fixed(DialectDescriptor::test_raw())
117}
118
119/// Render events with a custom sink.
120pub fn render_all(events: &[InterpreterOutputEvent], sink: Arc<dyn ConsoleSink>) {
121    let renderer = ConsoleRenderer::new(ConsoleRendererConfig::default(), sink);
122    for ev in events {
123        renderer.render(ev);
124    }
125}