mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-19 07:04:21 +01:00
feat(api): persist sessions to disk
This commit is contained in:
@@ -232,6 +232,13 @@ By default, the server runs on `127.0.0.1:8080`. You can modify this using confi
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Session Management
|
||||||
|
|
||||||
|
Sessions created via the API are stored in the same location as the CLI
|
||||||
|
(`~/.local/share/goose/sessions` on most platforms). Each session is saved to a
|
||||||
|
`<session_id>.jsonl` file. You can resume or inspect these sessions with the CLI
|
||||||
|
by providing the session ID returned from the API.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Using cURL
|
### Using cURL
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ use goose::agents::{
|
|||||||
ExtensionConfig,
|
ExtensionConfig,
|
||||||
};
|
};
|
||||||
use mcp_core::tool::Tool;
|
use mcp_core::tool::Tool;
|
||||||
use std::collections::HashMap;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use goose::session::{self, Identifier};
|
||||||
|
use goose::agents::SessionConfig;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use goose::providers::{create, providers};
|
use goose::providers::{create, providers};
|
||||||
use goose::model::ModelConfig;
|
use goose::model::ModelConfig;
|
||||||
@@ -29,10 +32,6 @@ static AGENT: LazyLock<tokio::sync::Mutex<Agent>> = LazyLock::new(|| {
|
|||||||
tokio::sync::Mutex::new(Agent::new())
|
tokio::sync::Mutex::new(Agent::new())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global store for session histories
|
|
||||||
static SESSION_HISTORY: LazyLock<tokio::sync::Mutex<HashMap<Uuid, Vec<Message>>>> = LazyLock::new(|| {
|
|
||||||
tokio::sync::Mutex::new(HashMap::new())
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct SessionRequest {
|
struct SessionRequest {
|
||||||
@@ -132,8 +131,20 @@ async fn start_session_handler(
|
|||||||
|
|
||||||
// Generate a new session ID and process the messages
|
// Generate a new session ID and process the messages
|
||||||
let session_id = Uuid::new_v4();
|
let session_id = Uuid::new_v4();
|
||||||
|
let session_name = session_id.to_string();
|
||||||
|
let session_path = session::get_path(Identifier::Name(session_name.clone()));
|
||||||
|
|
||||||
let result = agent.reply(&messages, None).await;
|
let provider = agent.provider().await.ok();
|
||||||
|
|
||||||
|
let result = agent
|
||||||
|
.reply(
|
||||||
|
&messages,
|
||||||
|
Some(SessionConfig {
|
||||||
|
id: Identifier::Name(session_name.clone()),
|
||||||
|
working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(mut stream) => {
|
Ok(mut stream) => {
|
||||||
@@ -141,8 +152,9 @@ async fn start_session_handler(
|
|||||||
if let Ok(Some(response)) = stream.try_next().await {
|
if let Ok(Some(response)) = stream.try_next().await {
|
||||||
let response_text = response.as_concat_text();
|
let response_text = response.as_concat_text();
|
||||||
messages.push(response);
|
messages.push(response);
|
||||||
let mut history = SESSION_HISTORY.lock().await;
|
if let Err(e) = session::persist_messages(&session_path, &messages, provider.clone()).await {
|
||||||
history.insert(session_id, messages);
|
warn!("Failed to persist session {}: {}", session_name, e);
|
||||||
|
}
|
||||||
|
|
||||||
let api_response = StartSessionResponse {
|
let api_response = StartSessionResponse {
|
||||||
message: response_text,
|
message: response_text,
|
||||||
@@ -154,8 +166,9 @@ async fn start_session_handler(
|
|||||||
warp::http::StatusCode::OK,
|
warp::http::StatusCode::OK,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let mut history = SESSION_HISTORY.lock().await;
|
if let Err(e) = session::persist_messages(&session_path, &messages, provider.clone()).await {
|
||||||
history.insert(session_id, messages);
|
warn!("Failed to persist session {}: {}", session_name, e);
|
||||||
|
}
|
||||||
|
|
||||||
let api_response = StartSessionResponse {
|
let api_response = StartSessionResponse {
|
||||||
message: "Session started but no response generated".to_string(),
|
message: "Session started but no response generated".to_string(),
|
||||||
@@ -190,11 +203,13 @@ async fn reply_session_handler(
|
|||||||
|
|
||||||
let mut agent = AGENT.lock().await;
|
let mut agent = AGENT.lock().await;
|
||||||
|
|
||||||
// Retrieve existing session history
|
let session_name = req.session_id.to_string();
|
||||||
let mut history = SESSION_HISTORY.lock().await;
|
let session_path = session::get_path(Identifier::Name(session_name.clone()));
|
||||||
let entry = match history.get_mut(&req.session_id) {
|
|
||||||
Some(messages) => messages,
|
// Retrieve existing session history from disk
|
||||||
None => {
|
let mut messages = match session::read_messages(&session_path) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(_) => {
|
||||||
let response = ApiResponse {
|
let response = ApiResponse {
|
||||||
message: "Session not found".to_string(),
|
message: "Session not found".to_string(),
|
||||||
status: "error".to_string(),
|
status: "error".to_string(),
|
||||||
@@ -207,19 +222,30 @@ async fn reply_session_handler(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Append the new user message
|
// Append the new user message
|
||||||
entry.push(Message::user().with_text(&req.prompt));
|
messages.push(Message::user().with_text(&req.prompt));
|
||||||
let messages = entry.clone();
|
|
||||||
|
let provider = agent.provider().await.ok();
|
||||||
|
|
||||||
// Process the messages through the agent
|
// Process the messages through the agent
|
||||||
let result = agent.reply(&messages, None).await;
|
let result = agent
|
||||||
|
.reply(
|
||||||
|
&messages,
|
||||||
|
Some(SessionConfig {
|
||||||
|
id: Identifier::Name(session_name.clone()),
|
||||||
|
working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(mut stream) => {
|
Ok(mut stream) => {
|
||||||
// Process the stream to get the first response
|
// Process the stream to get the first response
|
||||||
if let Ok(Some(response)) = stream.try_next().await {
|
if let Ok(Some(response)) = stream.try_next().await {
|
||||||
let response_text = response.as_concat_text();
|
let response_text = response.as_concat_text();
|
||||||
// store assistant response in history
|
messages.push(response);
|
||||||
entry.push(response);
|
if let Err(e) = session::persist_messages(&session_path, &messages, provider.clone()).await {
|
||||||
|
warn!("Failed to persist session {}: {}", session_name, e);
|
||||||
|
}
|
||||||
let api_response = ApiResponse {
|
let api_response = ApiResponse {
|
||||||
message: format!("Reply: {}", response_text),
|
message: format!("Reply: {}", response_text),
|
||||||
status: "success".to_string(),
|
status: "success".to_string(),
|
||||||
@@ -229,6 +255,9 @@ async fn reply_session_handler(
|
|||||||
warp::http::StatusCode::OK,
|
warp::http::StatusCode::OK,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
|
if let Err(e) = session::persist_messages(&session_path, &messages, provider.clone()).await {
|
||||||
|
warn!("Failed to persist session {}: {}", session_name, e);
|
||||||
|
}
|
||||||
let api_response = ApiResponse {
|
let api_response = ApiResponse {
|
||||||
message: "Reply processed but no response generated".to_string(),
|
message: "Reply processed but no response generated".to_string(),
|
||||||
status: "warning".to_string(),
|
status: "warning".to_string(),
|
||||||
@@ -257,8 +286,10 @@ async fn end_session_handler(
|
|||||||
req: EndSessionRequest,
|
req: EndSessionRequest,
|
||||||
_api_key: String,
|
_api_key: String,
|
||||||
) -> Result<impl warp::Reply, Rejection> {
|
) -> Result<impl warp::Reply, Rejection> {
|
||||||
let mut history = SESSION_HISTORY.lock().await;
|
let session_name = req.session_id.to_string();
|
||||||
if history.remove(&req.session_id).is_some() {
|
let session_path = session::get_path(Identifier::Name(session_name.clone()));
|
||||||
|
|
||||||
|
if std::fs::remove_file(&session_path).is_ok() {
|
||||||
let response = ApiResponse {
|
let response = ApiResponse {
|
||||||
message: "Session ended".to_string(),
|
message: "Session ended".to_string(),
|
||||||
status: "success".to_string(),
|
status: "success".to_string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user