Refactor IPC and DTO structures; remove unused code and streamline message handling
- Removed unused structs and methods from `response.rs`, `usage.rs`, and `client.rs`. - Simplified `Connection` handling in `conn.rs` to only support Unix sockets. - Updated `IpcServer` to exclusively use Unix sockets and removed TCP handling. - Cleaned up `editlog.rs` by removing loading and recent entry methods. - Refactored `memory.rs` to eliminate unused functions related to lesson promotion and retrospective creation. - Enhanced `search.rs` to support multiple search providers and improved error handling. - Updated chat view logic to simplify message display and improve user experience. - Removed deprecated modules and constants from various files to streamline the codebase.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
pub mod turn;
|
||||
pub mod tools;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum StreamEvent {
|
||||
Token(String),
|
||||
Reasoning(String),
|
||||
ToolCallDelta {
|
||||
index: usize,
|
||||
id: Option<String>,
|
||||
name: Option<String>,
|
||||
arguments_delta: String,
|
||||
},
|
||||
Usage {
|
||||
prompt_tokens: u64,
|
||||
completion_tokens: u64,
|
||||
total_tokens: u64,
|
||||
},
|
||||
Done,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub struct SseParser {
|
||||
buffer: String,
|
||||
event_type: Option<String>,
|
||||
data_lines: Vec<String>,
|
||||
}
|
||||
|
||||
impl SseParser {
|
||||
pub fn new() -> Self {
|
||||
SseParser {
|
||||
buffer: String::new(),
|
||||
event_type: None,
|
||||
data_lines: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, chunk: &str) -> Vec<StreamEvent> {
|
||||
self.buffer.push_str(chunk);
|
||||
let mut events = Vec::new();
|
||||
while let Some(line_end) = self.buffer.find('\n') {
|
||||
let line = self.buffer[..line_end].trim_end_matches('\r').to_string();
|
||||
self.buffer = self.buffer[line_end + 1..].to_string();
|
||||
if line.is_empty() {
|
||||
if let Some(event) = self.flush_event() {
|
||||
events.push(event);
|
||||
}
|
||||
} else if let Some(ty) = line.strip_prefix("event: ") {
|
||||
self.event_type = Some(ty.trim().to_string());
|
||||
} else if let Some(data) = line.strip_prefix("data: ") {
|
||||
self.data_lines.push(data.to_string());
|
||||
} else if line.starts_with("data:") {
|
||||
self.data_lines.push(String::new());
|
||||
}
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
fn flush_event(&mut self) -> Option<StreamEvent> {
|
||||
let data = self.data_lines.join("\n");
|
||||
self.data_lines.clear();
|
||||
let event_type = self.event_type.take().unwrap_or_default();
|
||||
if data.is_empty() || data == "[DONE]" {
|
||||
if data == "[DONE]" {
|
||||
return Some(StreamEvent::Done);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let value: Value = serde_json::from_str(&data).ok()?;
|
||||
match event_type.as_str() {
|
||||
"message.stop" => Some(StreamEvent::Done),
|
||||
"message.start" => None,
|
||||
"message.delta" | "" => {
|
||||
let delta = value.get("delta").or_else(|| value.get("choices"))?;
|
||||
if let Some(choices) = delta.as_array() {
|
||||
let choice = choices.first()?;
|
||||
let delta = choice.get("delta")?;
|
||||
if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
|
||||
return Some(StreamEvent::Token(content.to_string()));
|
||||
}
|
||||
if let Some(reasoning) = delta.get("reasoning_content").and_then(|r| r.as_str()) {
|
||||
return Some(StreamEvent::Reasoning(reasoning.to_string()));
|
||||
}
|
||||
if let Some(tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) {
|
||||
for tc in tool_calls {
|
||||
let index = tc.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as usize;
|
||||
let id = tc.get("id").and_then(|i| i.as_str()).map(|s| s.to_string());
|
||||
let name = tc.get("function")
|
||||
.and_then(|f| f.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let args_delta = tc.get("function")
|
||||
.and_then(|f| f.get("arguments"))
|
||||
.and_then(|a| a.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
return Some(StreamEvent::ToolCallDelta {
|
||||
index,
|
||||
id,
|
||||
name,
|
||||
arguments_delta: args_delta,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finish = choice.get("finish_reason");
|
||||
if let Some(reason) = finish.and_then(|r| r.as_str()) {
|
||||
if reason == "stop" || reason == "tool_calls" {
|
||||
return Some(StreamEvent::Done);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
|
||||
return Some(StreamEvent::Token(content.to_string()));
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.buffer.clear();
|
||||
self.event_type = None;
|
||||
self.data_lines.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_stream_chunk(data: &str) -> Option<StreamEvent> {
|
||||
let value: Value = serde_json::from_str(data).ok()?;
|
||||
if value == Value::Null {
|
||||
return None;
|
||||
}
|
||||
let choices = value.get("choices")?.as_array()?;
|
||||
let choice = choices.first()?;
|
||||
let delta = choice.get("delta")?;
|
||||
if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
|
||||
return Some(StreamEvent::Token(content.to_string()));
|
||||
}
|
||||
if let Some(reasoning) = delta.get("reasoning_content").and_then(|r| r.as_str()) {
|
||||
return Some(StreamEvent::Reasoning(reasoning.to_string()));
|
||||
}
|
||||
if let Some(finish) = choice.get("finish_reason").and_then(|r| r.as_str()) {
|
||||
if finish == "stop" || finish == "tool_calls" {
|
||||
return Some(StreamEvent::Done);
|
||||
}
|
||||
}
|
||||
if let Some(tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) {
|
||||
if let Some(tc) = tool_calls.first() {
|
||||
let index = tc.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as usize;
|
||||
let id = tc.get("id").and_then(|i| i.as_str()).map(|s| s.to_string());
|
||||
let name = tc.get("function")
|
||||
.and_then(|f| f.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let args = tc.get("function")
|
||||
.and_then(|f| f.get("arguments"))
|
||||
.and_then(|a| a.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
return Some(StreamEvent::ToolCallDelta {
|
||||
index,
|
||||
id,
|
||||
name,
|
||||
arguments_delta: args,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use super::turn::ParsedToolCall;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub struct ToolCallAccumulator {
|
||||
calls: Vec<ParsedToolCall>,
|
||||
}
|
||||
|
||||
impl ToolCallAccumulator {
|
||||
pub fn new() -> Self {
|
||||
ToolCallAccumulator { calls: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add_delta(
|
||||
&mut self,
|
||||
index: usize,
|
||||
id: Option<&str>,
|
||||
name: Option<&str>,
|
||||
arguments_delta: &str,
|
||||
) {
|
||||
while self.calls.len() <= index {
|
||||
self.calls.push(ParsedToolCall {
|
||||
id: String::new(),
|
||||
name: String::new(),
|
||||
arguments: String::new(),
|
||||
is_complete: false,
|
||||
});
|
||||
}
|
||||
let tc = &mut self.calls[index];
|
||||
if let Some(new_id) = id {
|
||||
if !new_id.is_empty() {
|
||||
tc.id = new_id.to_string();
|
||||
}
|
||||
}
|
||||
if let Some(new_name) = name {
|
||||
if !new_name.is_empty() {
|
||||
tc.name = new_name.to_string();
|
||||
}
|
||||
}
|
||||
tc.arguments.push_str(arguments_delta);
|
||||
}
|
||||
|
||||
pub fn calls(&self) -> &[ParsedToolCall] {
|
||||
&self.calls
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
!self.calls.is_empty() && self.calls.iter().all(|tc| !tc.name.is_empty() && !tc.arguments.is_empty())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.calls.clear();
|
||||
}
|
||||
|
||||
pub fn pending_args(&self) -> Vec<Value> {
|
||||
self.calls
|
||||
.iter()
|
||||
.filter(|tc| !tc.name.is_empty())
|
||||
.map(|tc| {
|
||||
json!({
|
||||
"tool_call_id": tc.id,
|
||||
"name": tc.name,
|
||||
"arguments": tc.arguments,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ToolCallAccumulator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
use super::StreamEvent;
|
||||
use crate::dto::chat::message::ChatMessage;
|
||||
use crate::dto::chat::tool::{ToolCall, ToolFunction};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamedTurn {
|
||||
pub messages: Vec<ChatMessage>,
|
||||
pub tool_calls: Vec<ParsedToolCall>,
|
||||
pub is_complete: bool,
|
||||
pub accumulated_content: String,
|
||||
pub accumulated_reasoning: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ParsedToolCall {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub arguments: String,
|
||||
pub is_complete: bool,
|
||||
}
|
||||
|
||||
impl ParsedToolCall {
|
||||
pub fn try_parse(&self) -> Option<Value> {
|
||||
serde_json::from_str(&self.arguments).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamedTurn {
|
||||
pub fn new() -> Self {
|
||||
StreamedTurn {
|
||||
messages: Vec::new(),
|
||||
tool_calls: Vec::new(),
|
||||
is_complete: false,
|
||||
accumulated_content: String::new(),
|
||||
accumulated_reasoning: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_event(&mut self, event: &StreamEvent) {
|
||||
match event {
|
||||
StreamEvent::Token(token) => {
|
||||
self.accumulated_content.push_str(token);
|
||||
}
|
||||
StreamEvent::Reasoning(reasoning) => {
|
||||
self.accumulated_reasoning.push_str(reasoning);
|
||||
}
|
||||
StreamEvent::ToolCallDelta {
|
||||
index,
|
||||
id,
|
||||
name,
|
||||
arguments_delta,
|
||||
} => {
|
||||
while self.tool_calls.len() <= *index {
|
||||
self.tool_calls.push(ParsedToolCall {
|
||||
id: String::new(),
|
||||
name: String::new(),
|
||||
arguments: String::new(),
|
||||
is_complete: false,
|
||||
});
|
||||
}
|
||||
let tc = &mut self.tool_calls[*index];
|
||||
if let Some(new_id) = id {
|
||||
if !new_id.is_empty() {
|
||||
tc.id = new_id.clone();
|
||||
}
|
||||
}
|
||||
if let Some(new_name) = name {
|
||||
if !new_name.is_empty() {
|
||||
tc.name = new_name.clone();
|
||||
}
|
||||
}
|
||||
tc.arguments.push_str(arguments_delta);
|
||||
}
|
||||
StreamEvent::Done => {
|
||||
self.is_complete = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_assistant_message(&self) -> ChatMessage {
|
||||
let mut msg = if self.tool_calls.is_empty() {
|
||||
ChatMessage::assistant(None)
|
||||
} else {
|
||||
let tool_dtos: Vec<ToolCall> = self.tool_calls
|
||||
.iter()
|
||||
.filter(|tc| !tc.name.is_empty())
|
||||
.map(|tc| {
|
||||
let args_value: serde_json::Value = serde_json::from_str(&tc.arguments)
|
||||
.unwrap_or(serde_json::Value::String(tc.arguments.clone()));
|
||||
ToolCall {
|
||||
id: tc.id.clone(),
|
||||
type_: "function".to_string(),
|
||||
function: ToolFunction {
|
||||
name: tc.name.clone(),
|
||||
arguments: args_value,
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut msg = ChatMessage::assistant(None);
|
||||
if !tool_dtos.is_empty() {
|
||||
msg.tool_calls = Some(tool_dtos);
|
||||
}
|
||||
msg
|
||||
};
|
||||
let content = if self.accumulated_content.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.accumulated_content.clone())
|
||||
};
|
||||
msg.content = content;
|
||||
msg
|
||||
}
|
||||
|
||||
pub fn has_tool_calls(&self) -> bool {
|
||||
self.tool_calls.iter().any(|tc| !tc.name.is_empty())
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &str {
|
||||
&self.accumulated_content
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StreamedTurn {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user