abstract the claude module out of the signal-gateway and put interface and ipml in their own crates

This commit is contained in:
Chris Beck
2025-12-14 11:22:28 -07:00
parent 2682ee8ee6
commit ceb988dc07
23 changed files with 736 additions and 489 deletions
+32
View File
@@ -0,0 +1,32 @@
use crate::{AssistantResponse, ChatMessage};
use std::error::Error;
use tokio_util::sync::CancellationToken;
/// Assistant trait which handles chat messages and commands.
///
/// Implementations handle the actual LLM API calls, message history management,
/// and tool execution.
#[async_trait::async_trait]
pub trait Assistant: Send {
/// Record a chat message in the assistant's history without expecting a response.
async fn record_message(&mut self, message: ChatMessage);
/// Process a chat message and generate a response.
///
/// The cancellation token can be used to interrupt long-running operations.
/// Returns `Ok(None)` if the operation was cancelled.
async fn prompt(
&mut self,
message: ChatMessage,
cancel: CancellationToken,
) -> Result<Option<AssistantResponse>, Box<dyn Error + Send + Sync>>;
/// Compact the assistant's message history (e.g., by summarizing).
///
/// This is called when the user explicitly requests compaction.
/// Automatic compaction is an internal implementation detail.
async fn compact(&mut self);
/// Log the assistant's current state for debugging.
fn debug_log(&mut self);
}
@@ -0,0 +1,44 @@
use chrono::{DateTime, Utc};
/// Indicates the "role" i.e. the manner in which a particular message was sent
#[non_exhaustive]
pub enum SentBy {
/// User message directed at the system (commands, etc.)
UserToSystem,
/// User message directed at assistant (prompts)
UserToAssistant,
/// Response from assistant
Assistant,
/// System-generated message
System,
/// Alert from alertmanager
AlertManager,
}
impl SentBy {
/// Returns the API role: "assistant" for Assistant, "user" for everything else.
pub fn api_role(&self) -> &'static str {
match self {
Self::Assistant => "assistant",
_ => "user",
}
}
/// Returns a prefix to prepend to message text for context.
pub fn prefix(&self) -> Option<&'static str> {
match self {
Self::UserToSystem => Some("[user to system]"),
Self::UserToAssistant => None, // No prefix needed for direct user messages
Self::Assistant => None,
Self::System => Some("[system]"),
Self::AlertManager => Some("[alertmanager]"),
}
}
}
/// A chat message
pub struct ChatMessage {
pub sent_by: SentBy,
pub timestamp: DateTime<Utc>,
pub text: Box<str>,
}
+14
View File
@@ -0,0 +1,14 @@
//! Assistant API used by signal-gateway.
//!
//! This crate provides abstract types for LLM assistant interactions,
//! making it easy to swap in different LLM implementations.
mod assistant;
mod chat_message;
mod response;
mod tools;
pub use assistant::Assistant;
pub use chat_message::{ChatMessage, SentBy};
pub use response::AssistantResponse;
pub use tools::{Tool, ToolExecutor, ToolResult};
+49
View File
@@ -0,0 +1,49 @@
//! Response type for assistant interactions.
use std::path::PathBuf;
/// Response from an assistant interaction.
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct AssistantResponse {
/// Text response from the assistant.
pub text: String,
/// Optional file attachments generated during the interaction.
pub attachments: Vec<PathBuf>,
}
impl AssistantResponse {
/// Create a new response with just text.
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
attachments: Vec::new(),
}
}
/// Create a response with text and attachments.
pub fn with_attachments(text: impl Into<String>, attachments: Vec<PathBuf>) -> Self {
Self {
text: text.into(),
attachments,
}
}
/// Add attachments to this response.
pub fn add_attachments(mut self, attachments: Vec<PathBuf>) -> Self {
self.attachments.extend(attachments);
self
}
}
impl From<String> for AssistantResponse {
fn from(text: String) -> Self {
Self::new(text)
}
}
impl From<&str> for AssistantResponse {
fn from(text: &str) -> Self {
Self::new(text)
}
}
+72
View File
@@ -0,0 +1,72 @@
//! Tool definitions and executor trait for the LLM API.
use async_trait::async_trait;
use serde::Serialize;
use serde_json::Value;
use std::path::PathBuf;
/// Result of executing a tool.
#[derive(Clone, Debug, Default)]
pub struct ToolResult {
/// Text result to return to LLM.
pub text: String,
/// Optional file attachments generated by the tool.
pub attachments: Vec<PathBuf>,
}
impl ToolResult {
/// Create a new tool result with just text.
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
attachments: Vec::new(),
}
}
/// Create a tool result with text and an attachment.
pub fn with_attachment(text: impl Into<String>, path: impl Into<PathBuf>) -> Self {
Self {
text: text.into(),
attachments: vec![path.into()],
}
}
}
impl From<String> for ToolResult {
fn from(text: String) -> Self {
Self::new(text)
}
}
impl From<&str> for ToolResult {
fn from(text: &str) -> Self {
Self::new(text)
}
}
/// Trait for executing tools. Implement this to provide tool capabilities.
#[async_trait]
pub trait ToolExecutor: Send + Sync {
/// Get the list of available tools as JSON tool definitions.
fn tools(&self) -> Vec<Tool>;
/// Check if this executor handles a tool with the given name.
fn has_tool(&self, name: &str) -> bool {
self.tools().iter().any(|t| t.name == name)
}
/// Execute a tool by name with the given input arguments.
/// Returns the result to be sent back to LLM, potentially with attachments.
async fn execute(&self, name: &str, input: &Value) -> Result<ToolResult, String>;
}
/// A tool definition for the LLM API.
#[derive(Clone, Debug, Serialize)]
pub struct Tool {
/// The name of the tool.
pub name: &'static str,
/// A description of what the tool does.
pub description: &'static str,
/// JSON schema for the tool's input parameters.
pub input_schema: Value,
}