diff --git a/signal-gateway/src/claude/mod.rs b/signal-gateway/src/claude/mod.rs index 1bf5bf1..0dcf34e 100644 --- a/signal-gateway/src/claude/mod.rs +++ b/signal-gateway/src/claude/mod.rs @@ -6,8 +6,6 @@ mod worker; pub use tools::{Tool, ToolExecutor}; use conf::Conf; -use serde::{Deserialize, Serialize}; -use serde_json::Value; use std::path::PathBuf; use std::sync::Weak; use tokio::sync::{mpsc, oneshot}; @@ -89,91 +87,6 @@ pub struct ClaudeApi { worker_handle: tokio::task::JoinHandle<()>, } -/// Request body for the Claude Messages API. -#[derive(Serialize)] -struct MessagesRequest<'a> { - model: &'a str, - max_tokens: u32, - system: &'a str, - messages: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - tools: Vec, -} - -/// A message in the conversation (can have multiple content blocks). -#[derive(Clone, Debug, Serialize, Deserialize)] -struct MessageContent { - role: String, - content: Vec, -} - -impl MessageContent { - fn user(text: &str) -> Self { - Self { - role: "user".to_owned(), - content: vec![ContentBlock::Text { - text: text.to_owned(), - }], - } - } - - fn assistant(blocks: Vec) -> Self { - Self { - role: "assistant".to_owned(), - content: blocks, - } - } - - fn tool_result(tool_use_id: String, content: String, is_error: bool) -> Self { - Self { - role: "user".to_owned(), - content: vec![ContentBlock::ToolResult { - tool_use_id, - content, - is_error: if is_error { Some(true) } else { None }, - }], - } - } -} - -/// A content block in the request/response. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum ContentBlock { - Text { - text: String, - }, - ToolUse { - id: String, - name: String, - input: Value, - }, - ToolResult { - tool_use_id: String, - content: String, - #[serde(skip_serializing_if = "Option::is_none")] - is_error: Option, - }, -} - -/// Response from the Claude Messages API. -#[derive(Debug, Deserialize)] -struct MessagesResponse { - content: Vec, - stop_reason: String, -} - -/// Error response from the Claude API. -#[derive(Deserialize)] -struct ErrorResponse { - error: ApiErrorDetail, -} - -#[derive(Deserialize)] -struct ApiErrorDetail { - message: String, -} - impl ClaudeApi { /// Create a new Claude API client from configuration. /// diff --git a/signal-gateway/src/claude/worker.rs b/signal-gateway/src/claude/worker.rs index 2e4c461..3c3a5a9 100644 --- a/signal-gateway/src/claude/worker.rs +++ b/signal-gateway/src/claude/worker.rs @@ -1,9 +1,8 @@ //! Background worker that processes Claude API requests serially. -use super::{ - ClaudeConfig, ClaudeError, ContentBlock, ErrorResponse, MessageContent, MessagesRequest, - MessagesResponse, ToolExecutor, ANTHROPIC_API_VERSION, -}; +use super::{ANTHROPIC_API_VERSION, ClaudeConfig, ClaudeError, Tool, ToolExecutor}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::sync::Weak; use tokio::sync::{mpsc, oneshot}; use tracing::info; @@ -199,3 +198,88 @@ impl ClaudeWorker { Err(ClaudeError::TooManyIterations(max_iterations)) } } + +/// Request body for the Claude Messages API. +#[derive(Serialize)] +struct MessagesRequest<'a> { + model: &'a str, + max_tokens: u32, + system: &'a str, + messages: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + tools: Vec, +} + +/// A message in the conversation (can have multiple content blocks). +#[derive(Clone, Debug, Serialize, Deserialize)] +struct MessageContent { + role: String, + content: Vec, +} + +impl MessageContent { + fn user(text: &str) -> Self { + Self { + role: "user".to_owned(), + content: vec![ContentBlock::Text { + text: text.to_owned(), + }], + } + } + + fn assistant(blocks: Vec) -> Self { + Self { + role: "assistant".to_owned(), + content: blocks, + } + } + + fn tool_result(tool_use_id: String, content: String, is_error: bool) -> Self { + Self { + role: "user".to_owned(), + content: vec![ContentBlock::ToolResult { + tool_use_id, + content, + is_error: if is_error { Some(true) } else { None }, + }], + } + } +} + +/// A content block in the request/response. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ContentBlock { + Text { + text: String, + }, + ToolUse { + id: String, + name: String, + input: Value, + }, + ToolResult { + tool_use_id: String, + content: String, + #[serde(skip_serializing_if = "Option::is_none")] + is_error: Option, + }, +} + +/// Response from the Claude Messages API. +#[derive(Debug, Deserialize)] +struct MessagesResponse { + content: Vec, + stop_reason: String, +} + +/// Error response from the Claude API. +#[derive(Deserialize)] +struct ErrorResponse { + error: ApiErrorDetail, +} + +#[derive(Deserialize)] +struct ApiErrorDetail { + message: String, +}