Skip to main content

locode_engine/
session.rs

1//! The public driving API.
2
3use std::sync::Arc;
4
5use locode_protocol::{ContentBlock, Message, Report};
6use locode_provider::Provider;
7use locode_tools::Registry;
8use tokio_util::sync::CancellationToken;
9
10use crate::config::EngineConfig;
11use crate::sink::EventSink;
12
13/// One driven agent session. Owns the conversation history for the run (ephemeral).
14///
15/// Construct with [`Session::new`], then call [`Session::run`] (or
16/// [`Session::run_text`]) to drive one run to a terminal state. `run` is
17/// **infallible** — every terminal condition (including provider and `Fatal` tool
18/// errors) is captured in the returned [`Report`]'s `status`/`error`, so a caller
19/// gets a structured result every time (`locode-exec` maps status → exit code).
20pub struct Session {
21    pub(crate) provider: Arc<dyn Provider>,
22    pub(crate) registry: Registry,
23    pub(crate) preamble: Vec<Message>,
24    pub(crate) config: EngineConfig,
25    pub(crate) sink: Box<dyn EventSink>,
26    pub(crate) cancel: CancellationToken,
27}
28
29impl Session {
30    /// Assemble a session from its parts.
31    ///
32    /// `preamble` is the base `System` + `Developer` messages (the pack supplies
33    /// these); `provider`/`sink` are trait objects so the binary can select them at
34    /// runtime.
35    #[must_use]
36    pub fn new(
37        provider: Arc<dyn Provider>,
38        registry: Registry,
39        preamble: Vec<Message>,
40        config: EngineConfig,
41        sink: Box<dyn EventSink>,
42    ) -> Self {
43        Self {
44            provider,
45            registry,
46            preamble,
47            config,
48            sink,
49            cancel: CancellationToken::new(),
50        }
51    }
52
53    /// Drive the loop to a terminal state and return the run's [`Report`].
54    pub async fn run(&mut self, user: Vec<ContentBlock>) -> Report {
55        self.drive(user).await
56    }
57
58    /// Convenience: drive with a plain-text user prompt.
59    pub async fn run_text(&mut self, prompt: impl Into<String>) -> Report {
60        self.run(vec![ContentBlock::Text {
61            text: prompt.into(),
62        }])
63        .await
64    }
65}