rhema_agent_tools/paste.rs
1//! Paste expansion at the provider seam: the model sees plain text, the
2//! session keeps the compact chip form.
3//!
4//! Chat input may carry embedded pastes (`rhema_core::paste`'s encoding,
5//! minted by the UIs). Expanding them here — wrapped around whatever
6//! `ModelProvider` an agent is built with — means fresh and resumed
7//! histories expand alike, and the engine stays ignorant of the encoding.
8
9use std::sync::Arc;
10
11use async_trait::async_trait;
12use rhema_core::{
13 Content, EventSink, ModelOutput, ModelProvider, ModelRequest, ProviderError, Role,
14};
15
16/// Expands embedded pastes in user text before a request leaves
17/// (`rhema_core::paste`): the session carries the compact chip form a
18/// UI renders, the model sees only the plain text. Wrapped around
19/// every provider the agent is built with, so fresh and resumed
20/// histories expand alike. Public so a driver that assembles its own
21/// `Agent` — the resident handler — wraps the same seam instead of
22/// growing a second expansion.
23pub struct ExpandPastes {
24 inner: Arc<dyn ModelProvider>,
25}
26
27impl ExpandPastes {
28 /// The wrapper over `inner`.
29 pub fn new(inner: Arc<dyn ModelProvider>) -> Self {
30 Self { inner }
31 }
32}
33
34#[async_trait]
35impl ModelProvider for ExpandPastes {
36 async fn complete(
37 &self,
38 mut req: ModelRequest,
39 sink: &mut dyn EventSink,
40 ) -> Result<ModelOutput, ProviderError> {
41 for message in &mut req.messages {
42 if message.role != Role::User {
43 continue;
44 }
45 for content in &mut message.content {
46 if let Content::Text(text) = content {
47 *text = rhema_core::paste::expand(text);
48 }
49 }
50 }
51 self.inner.complete(req, sink).await
52 }
53}