mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 14:44:21 +01:00
cleanup
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3535,8 +3535,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
"config 0.13.4",
|
"config 0.13.4",
|
||||||
"dashmap 6.1.0",
|
"dashmap 6.1.0",
|
||||||
|
"dirs 5.0.1",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"goose",
|
"goose",
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ futures-util = "0.3"
|
|||||||
# For session IDs
|
# For session IDs
|
||||||
uuid = { version = "1", features = ["serde", "v4"] }
|
uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
dashmap = "6"
|
dashmap = "6"
|
||||||
|
# For session listing
|
||||||
|
dirs = "5"
|
||||||
|
chrono = "0.4"
|
||||||
# Add dynamic-library for extension loading
|
# Add dynamic-library for extension loading
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -53,6 +53,39 @@ pub struct EndSessionRequest {
|
|||||||
pub session_id: Uuid,
|
pub session_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct GetSessionRequest {
|
||||||
|
pub session_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SessionInfo {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub modified: String,
|
||||||
|
pub message_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ListSessionsResponse {
|
||||||
|
pub sessions: Vec<SessionInfo>,
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SessionMessage {
|
||||||
|
pub role: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct GetSessionResponse {
|
||||||
|
pub session_id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub messages: Vec<SessionMessage>,
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SummarizeSessionRequest {
|
pub struct SummarizeSessionRequest {
|
||||||
pub session_id: Uuid,
|
pub session_id: Uuid,
|
||||||
@@ -585,6 +618,158 @@ pub async fn metrics_handler() -> Result<impl warp::Reply, Rejection> {
|
|||||||
Ok(warp::reply::json(&resp))
|
Ok(warp::reply::json(&resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list_sessions_handler(
|
||||||
|
_api_key: String,
|
||||||
|
) -> Result<impl warp::Reply, Rejection> {
|
||||||
|
info!("Listing all sessions");
|
||||||
|
|
||||||
|
// Get all session files from the goose session directory
|
||||||
|
let data_dir = dirs::data_local_dir()
|
||||||
|
.ok_or_else(|| custom(AnyhowRejection(anyhow::anyhow!("Failed to get data directory"))))?;
|
||||||
|
let sessions_dir = data_dir.join("goose").join("sessions");
|
||||||
|
|
||||||
|
let mut sessions = Vec::new();
|
||||||
|
|
||||||
|
if sessions_dir.exists() {
|
||||||
|
match std::fs::read_dir(&sessions_dir) {
|
||||||
|
Ok(entries) => {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.extension().and_then(|ext| ext.to_str()) == Some("jsonl") {
|
||||||
|
// Read the session file to get metadata
|
||||||
|
match session::read_metadata(&path) {
|
||||||
|
Ok(metadata) => {
|
||||||
|
if let Ok(file_metadata) = entry.metadata() {
|
||||||
|
if let Ok(modified) = file_metadata.modified() {
|
||||||
|
let modified_str = chrono::DateTime::<chrono::Utc>::from(modified)
|
||||||
|
.format("%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let session_id = path.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
sessions.push(SessionInfo {
|
||||||
|
id: session_id,
|
||||||
|
name: metadata.description,
|
||||||
|
modified: modified_str,
|
||||||
|
message_count: metadata.message_count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to read metadata for {:?}: {}", path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to read sessions directory: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by modified date (newest first)
|
||||||
|
sessions.sort_by(|a, b| b.modified.cmp(&a.modified));
|
||||||
|
|
||||||
|
let response = ListSessionsResponse {
|
||||||
|
sessions,
|
||||||
|
status: "success".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(warp::reply::with_status(
|
||||||
|
warp::reply::json(&response),
|
||||||
|
warp::http::StatusCode::OK,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_session_handler(
|
||||||
|
req: GetSessionRequest,
|
||||||
|
_api_key: String,
|
||||||
|
) -> Result<impl warp::Reply, Rejection> {
|
||||||
|
info!("Getting session: {}", req.session_id);
|
||||||
|
|
||||||
|
let session_name = req.session_id.to_string();
|
||||||
|
let session_path = session::get_path(Identifier::Name(session_name.clone()))
|
||||||
|
.map_err(|e| custom(AnyhowRejection(anyhow::anyhow!("Failed to get session path: {}", e))))?;
|
||||||
|
|
||||||
|
// Check if session file exists
|
||||||
|
if !session_path.exists() {
|
||||||
|
let response = GetSessionResponse {
|
||||||
|
session_id: req.session_id.to_string(),
|
||||||
|
name: String::new(),
|
||||||
|
messages: Vec::new(),
|
||||||
|
status: "error".to_string(),
|
||||||
|
};
|
||||||
|
return Ok(warp::reply::with_status(
|
||||||
|
warp::reply::json(&response),
|
||||||
|
warp::http::StatusCode::NOT_FOUND,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read session metadata and messages
|
||||||
|
let metadata = session::read_metadata(&session_path)
|
||||||
|
.map_err(|e| custom(AnyhowRejection(anyhow::anyhow!("Failed to read session metadata: {}", e))))?;
|
||||||
|
|
||||||
|
let messages = session::read_messages(&session_path)
|
||||||
|
.map_err(|e| custom(AnyhowRejection(anyhow::anyhow!("Failed to read session messages: {}", e))))?;
|
||||||
|
|
||||||
|
// Convert messages to API format
|
||||||
|
let api_messages: Vec<SessionMessage> = messages.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, msg)| {
|
||||||
|
// Messages typically alternate between user and assistant
|
||||||
|
// First message is usually user, then assistant, and so on
|
||||||
|
// We can also serialize the message and check the role field
|
||||||
|
let role = if let Ok(serialized) = serde_json::to_value(msg) {
|
||||||
|
if let Some(role_value) = serialized.get("role") {
|
||||||
|
if let Some(role_str) = role_value.as_str() {
|
||||||
|
role_str.to_lowercase()
|
||||||
|
} else {
|
||||||
|
// Fallback to alternating pattern
|
||||||
|
if idx % 2 == 0 { "user" } else { "assistant" }.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to alternating pattern
|
||||||
|
if idx % 2 == 0 { "user" } else { "assistant" }.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to alternating pattern
|
||||||
|
if idx % 2 == 0 { "user" } else { "assistant" }.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract text content, filtering out thinking tags
|
||||||
|
let content = msg.content
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| match c {
|
||||||
|
MessageContent::Text(text) => Some(text.text.as_str()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
SessionMessage { role, content }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let response = GetSessionResponse {
|
||||||
|
session_id: req.session_id.to_string(),
|
||||||
|
name: metadata.description,
|
||||||
|
messages: api_messages,
|
||||||
|
status: "success".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(warp::reply::with_status(
|
||||||
|
warp::reply::json(&response),
|
||||||
|
warp::http::StatusCode::OK,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_rejection(err: Rejection) -> Result<impl warp::Reply, Rejection> {
|
pub async fn handle_rejection(err: Rejection) -> Result<impl warp::Reply, Rejection> {
|
||||||
if let Some(e) = err.find::<AnyhowRejection>() {
|
if let Some(e) = err.find::<AnyhowRejection>() {
|
||||||
let message = e.0.to_string();
|
let message = e.0.to_string();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::handlers::{
|
|||||||
end_session_handler, get_provider_config_handler, handle_rejection,
|
end_session_handler, get_provider_config_handler, handle_rejection,
|
||||||
list_extensions_handler, metrics_handler, reply_session_handler,
|
list_extensions_handler, metrics_handler, reply_session_handler,
|
||||||
start_session_handler, summarize_session_handler, with_api_key,
|
start_session_handler, summarize_session_handler, with_api_key,
|
||||||
|
list_sessions_handler, get_session_handler,
|
||||||
};
|
};
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
load_provider_config, load_configuration,
|
load_provider_config, load_configuration,
|
||||||
@@ -54,6 +55,19 @@ pub fn build_routes(api_key: String) -> impl Filter<Extract = impl warp::Reply,
|
|||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.and_then(metrics_handler);
|
.and_then(metrics_handler);
|
||||||
|
|
||||||
|
let list_sessions = warp::path("sessions")
|
||||||
|
.and(warp::path("list"))
|
||||||
|
.and(warp::get())
|
||||||
|
.and(with_api_key(api_key.clone()))
|
||||||
|
.and_then(list_sessions_handler);
|
||||||
|
|
||||||
|
let get_session = warp::path("session")
|
||||||
|
.and(warp::path("get"))
|
||||||
|
.and(warp::post())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(with_api_key(api_key.clone()))
|
||||||
|
.and_then(get_session_handler);
|
||||||
|
|
||||||
start_session
|
start_session
|
||||||
.or(reply_session)
|
.or(reply_session)
|
||||||
.or(summarize_session)
|
.or(summarize_session)
|
||||||
@@ -61,6 +75,8 @@ pub fn build_routes(api_key: String) -> impl Filter<Extract = impl warp::Reply,
|
|||||||
.or(list_extensions)
|
.or(list_extensions)
|
||||||
.or(get_provider_config)
|
.or(get_provider_config)
|
||||||
.or(metrics)
|
.or(metrics)
|
||||||
|
.or(list_sessions)
|
||||||
|
.or(get_session)
|
||||||
.recover(handle_rejection)
|
.recover(handle_rejection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1324,7 +1324,17 @@ pub async fn generate_description_with_schedule_id(
|
|||||||
anyhow::anyhow!("Failed to generate session description")
|
anyhow::anyhow!("Failed to generate session description")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let description = result.0.as_concat_text();
|
// Extract only non-thinking text content for the description
|
||||||
|
let description = result.0.content
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| match c {
|
||||||
|
crate::message::MessageContent::Text(text) => Some(text.text.as_str()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
// Validate description length for security
|
// Validate description length for security
|
||||||
let sanitized_description = if description.chars().count() > 100 {
|
let sanitized_description = if description.chars().count() > 100 {
|
||||||
|
|||||||
Reference in New Issue
Block a user