use crate::error::AgentError;
use crate::message::ContextMessage;
#[derive(Debug, Clone)]
pub struct ToolCallInfo {
pub id: String,
pub name: String,
pub arguments: String,
}
pub trait ContextBackendResponse {
fn response_type(&self) -> ResponseType;
fn reasoning_content(&self) -> Option<String>;
fn content(&self) -> Option<String>;
fn tool_calls(&self) -> Vec<ToolCallInfo>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseType {
Empty,
Reasoning,
Content,
ReasoningAndContent,
}
#[derive(Debug, Clone)]
pub enum StreamEvent<R> {
Thinking(R),
ContentFirst(R),
Content(R),
ToolCalls(R),
}
pub trait ScratchOpts {
fn scratch(&self) -> Option<&str>;
}
pub trait ContextBackend: Send + Sync + Clone + 'static {
type Message: ContextMessage;
type Opts: ScratchOpts + Clone + Default + Send + Sync;
type Response: Clone + Send + Sync + ContextBackendResponse;
fn user_message(&self, content: impl Into<String> + Send) -> Self::Message;
fn system_message(&self, content: impl Into<String> + Send) -> Self::Message;
fn tool_message(
&self,
tool_call_id: impl Into<String> + Send,
content: impl Into<String> + Send,
) -> Self::Message;
fn to_system_message(&self, msg: Self::Message) -> Self::Message {
msg.with_role(crate::Role::System)
}
fn to_request_messages(
&self,
messages: Vec<Self::Message>,
) -> Result<Vec<Self::Message>, AgentError> {
Ok(messages
.into_iter()
.map(|m| {
if m.preserve_reasoning() {
m
} else {
m.without_reasoning()
}
})
.collect())
}
fn merge_chunks(&self, responses: &[Self::Response]) -> Option<Self::Message>;
fn extract_messages_from_backend_response(
&self,
responses: &[Self::Response],
) -> Result<Vec<Self::Message>, AgentError>;
fn estimate_tokens(
&self,
messages: &[Self::Message],
) -> impl std::future::Future<Output = Result<usize, AgentError>> + Send;
fn context_window(&self) -> usize;
fn send(
&self,
messages: &[Self::Message],
opts: &Self::Opts,
) -> impl std::future::Future<Output = Result<Self::Response, AgentError>> + Send;
fn send_stream(
&self,
messages: Vec<Self::Message>,
opts: Self::Opts,
) -> impl futures_core::Stream<Item = Result<Self::Response, AgentError>> + Send + 'static;
fn classify_chunk(
&self,
response: &Self::Response,
saw_thinking: &mut bool,
) -> Vec<StreamEvent<Self::Response>> {
let mut events = Vec::new();
if !response.tool_calls().is_empty() {
events.push(StreamEvent::ToolCalls(response.clone()));
}
match response.response_type() {
ResponseType::Empty => return events,
ResponseType::Reasoning => {
events.push(StreamEvent::Thinking(response.clone()));
*saw_thinking = true;
}
ResponseType::Content => {
if *saw_thinking {
events.push(StreamEvent::ContentFirst(response.clone()));
*saw_thinking = false;
} else {
events.push(StreamEvent::Content(response.clone()));
}
}
ResponseType::ReasoningAndContent => {
events.push(StreamEvent::Thinking(response.clone()));
*saw_thinking = true;
events.push(StreamEvent::ContentFirst(response.clone()));
*saw_thinking = false;
}
}
events
}
fn message_to_jsonl(&self, msg: &Self::Message) -> Result<String, AgentError> {
serde_json::to_string(msg).map_err(|e| AgentError::Context(e.to_string()))
}
fn message_from_jsonl(&self, line: &str) -> Result<Self::Message, AgentError> {
serde_json::from_str(line).map_err(|e| AgentError::Context(format!("JSONL 解析失败: {e}")))
}
}