Skip to main content

codetether_rlm/
traits.rs

1//! Trait abstractions for host-crate dependencies.
2//!
3//! `codetether-rlm` is independent and cannot depend on
4//! `codetether-agent`. These traits define the narrow interface
5//! the RLM code needs. The main crate provides implementations.
6
7use anyhow::Result;
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10
11/// Schema-driven tool definition passed to the model.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ToolDefinition {
14    pub name: String,
15    pub description: String,
16    pub parameters: serde_json::Value,
17}
18
19/// A tool call returned by the model.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ToolCall {
22    pub id: String,
23    pub name: String,
24    pub arguments: serde_json::Value,
25}
26
27/// Completion response from a provider.
28#[derive(Debug, Clone)]
29pub struct LlmResponse {
30    pub text: String,
31    pub tool_calls: Vec<ToolCall>,
32    pub finish_reason: Option<String>,
33    pub input_tokens: usize,
34    pub output_tokens: usize,
35}
36
37/// A message in the conversation.
38#[derive(Debug, Clone)]
39pub struct LlmMessage {
40    pub role: String,
41    pub text: String,
42    pub tool_calls: Vec<ToolCall>,
43    pub tool_call_id: Option<String>,
44}
45
46impl LlmMessage {
47    /// Create a user message.
48    pub fn user(text: String) -> Self {
49        Self {
50            role: "user".into(),
51            text,
52            tool_calls: vec![],
53            tool_call_id: None,
54        }
55    }
56    /// Create an assistant message (text only).
57    pub fn assistant(text: String) -> Self {
58        Self {
59            role: "assistant".into(),
60            text,
61            tool_calls: vec![],
62            tool_call_id: None,
63        }
64    }
65    /// Create an assistant message from a response.
66    pub fn assistant_from(resp: &LlmResponse) -> Self {
67        Self {
68            role: "assistant".into(),
69            text: resp.text.clone(),
70            tool_calls: resp.tool_calls.clone(),
71            tool_call_id: None,
72        }
73    }
74    /// Create a tool-result message.
75    pub fn tool_result(call_id: &str, content: &str) -> Self {
76        Self {
77            role: "tool".into(),
78            text: content.into(),
79            tool_calls: vec![],
80            tool_call_id: Some(call_id.into()),
81        }
82    }
83}
84
85/// Narrow provider trait for LLM completions.
86#[async_trait]
87pub trait LlmProvider: Send + Sync {
88    /// Generate a single completion.
89    async fn complete(
90        &self,
91        messages: Vec<LlmMessage>,
92        tools: Vec<ToolDefinition>,
93        model: &str,
94        temperature: Option<f32>,
95    ) -> Result<LlmResponse>;
96}
97
98/// Trait for FunctionGemma-powered tool call reformatting.
99#[async_trait]
100pub trait ToolCallRewriter: Send + Sync {
101    /// Extract structured tool calls from a text-only response.
102    async fn maybe_reformat(
103        &self,
104        response: LlmResponse,
105        tools: &[ToolDefinition],
106        strict: bool,
107    ) -> Result<LlmResponse>;
108}
109
110/// Trait for emitting RLM events (progress + completion).
111pub trait RlmEventBus: Send + Sync {
112    /// Emit a progress tick.
113    fn emit_progress(&self, event: crate::RlmProgressEvent);
114    /// Emit the terminal completion record.
115    fn emit_completion(&self, event: crate::RlmCompletion);
116}