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}