Skip to content

Commit c8edac3

Browse files
authored
feat: add bulk job removal endpoint (#1310)
1 parent dd96a72 commit c8edac3

4 files changed

Lines changed: 139 additions & 1 deletion

File tree

shinkai-bin/shinkai-node/src/network/handle_commands_list.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,12 @@ impl Node {
809809
let _ = Node::v2_remove_job(db_clone, bearer, job_id, res).await;
810810
});
811811
}
812+
NodeCommand::V2ApiRemoveJobs { bearer, job_ids, res } => {
813+
let db_clone = self.db.clone();
814+
tokio::spawn(async move {
815+
let _ = Node::v2_remove_jobs(db_clone, bearer, job_ids, res).await;
816+
});
817+
}
812818
NodeCommand::V2ApiVecFSRetrievePathSimplifiedJson { bearer, payload, res } => {
813819
let db_clone = Arc::clone(&self.db);
814820

shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_jobs.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,80 @@ impl Node {
16971697
Ok(())
16981698
}
16991699

1700+
pub async fn v2_remove_jobs(
1701+
db: Arc<SqliteManager>,
1702+
bearer: String,
1703+
job_ids: Vec<String>,
1704+
res: Sender<Result<serde_json::Value, APIError>>,
1705+
) -> Result<(), NodeError> {
1706+
// Validate the bearer token
1707+
if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() {
1708+
return Ok(());
1709+
}
1710+
1711+
let mut succeeded: Vec<String> = Vec::new();
1712+
let mut failed: Vec<serde_json::Value> = Vec::new();
1713+
1714+
for job_id in job_ids {
1715+
// Get the job folder name BEFORE removing from DB (get_job_folder_name queries the jobs table)
1716+
let job_chat_folder = match db.get_job_folder_name(&job_id) {
1717+
Ok(folder) => Some(folder),
1718+
Err(_) => {
1719+
// If we can't get the folder name, log but continue - the job might not have a folder yet
1720+
None
1721+
}
1722+
};
1723+
1724+
// Remove the job from database
1725+
match db.remove_job(&job_id) {
1726+
Ok(_) => {
1727+
// Remove the chat files folder (filesystem/Chat Files/{formatted_name})
1728+
if let Some(chat_folder) = job_chat_folder {
1729+
use shinkai_fs::shinkai_file_manager::ShinkaiFileManager;
1730+
if chat_folder.exists() {
1731+
if let Err(err) = ShinkaiFileManager::remove_folder(chat_folder, &db) {
1732+
// Log the error but don't fail since DB removal succeeded
1733+
eprintln!("Warning: Failed to remove job chat folder for {}: {}", job_id, err);
1734+
}
1735+
}
1736+
}
1737+
1738+
// Remove the tools_storage folder (tools_storage/{job_id}) - used for tool execution files
1739+
let node_env = fetch_node_environment();
1740+
let node_storage_path = node_env.node_storage_path.unwrap_or_default();
1741+
let tools_storage_path = PathBuf::from(&node_storage_path)
1742+
.join("tools_storage")
1743+
.join(&job_id);
1744+
1745+
if tools_storage_path.exists() {
1746+
if let Err(err) = std::fs::remove_dir_all(&tools_storage_path) {
1747+
// Log the error but don't fail since DB removal succeeded
1748+
eprintln!("Warning: Failed to remove tools_storage folder at {:?}: {}", tools_storage_path, err);
1749+
}
1750+
}
1751+
1752+
succeeded.push(job_id);
1753+
}
1754+
Err(err) => {
1755+
failed.push(serde_json::json!({
1756+
"job_id": job_id,
1757+
"error": format!("Failed to remove job: {}", err)
1758+
}));
1759+
}
1760+
}
1761+
}
1762+
1763+
let _ = res
1764+
.send(Ok(serde_json::json!({
1765+
"status": if failed.is_empty() { "success" } else { "partial" },
1766+
"message": format!("{} job(s) removed successfully, {} failed", succeeded.len(), failed.len()),
1767+
"succeeded": succeeded,
1768+
"failed": failed
1769+
})))
1770+
.await;
1771+
Ok(())
1772+
}
1773+
17001774
pub async fn v2_export_messages_from_inbox(
17011775
db: Arc<SqliteManager>,
17021776
bearer: String,

shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_jobs.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ pub fn job_routes(
175175
.and(warp::body::json())
176176
.and_then(remove_job_handler);
177177

178+
let remove_jobs_route = warp::path("remove_jobs")
179+
.and(warp::post())
180+
.and(with_sender(node_commands_sender.clone()))
181+
.and(warp::header::<String>("authorization"))
182+
.and(warp::body::json())
183+
.and_then(remove_jobs_handler);
184+
178185
let export_messages_from_inbox_route = warp::path("export_messages_from_inbox")
179186
.and(warp::post())
180187
.and(with_sender(node_commands_sender.clone()))
@@ -216,6 +223,7 @@ pub fn job_routes(
216223
.or(get_message_traces_route)
217224
.or(fork_job_messages_route)
218225
.or(remove_job_route)
226+
.or(remove_jobs_route)
219227
.or(export_messages_from_inbox_route)
220228
.or(add_messages_god_mode_route)
221229
.or(get_job_provider_route)
@@ -285,6 +293,11 @@ pub struct RemoveJobRequest {
285293
pub job_id: String,
286294
}
287295

296+
#[derive(Deserialize, ToSchema)]
297+
pub struct RemoveJobsRequest {
298+
pub job_ids: Vec<String>,
299+
}
300+
288301
#[derive(Deserialize, ToSchema)]
289302
pub struct ExportInboxMessagesRequest {
290303
pub inbox_name: String,
@@ -1249,6 +1262,45 @@ pub async fn remove_job_handler(
12491262
}
12501263
}
12511264

1265+
#[utoipa::path(
1266+
post,
1267+
path = "/v2/remove_jobs",
1268+
request_body = RemoveJobsRequest,
1269+
responses(
1270+
(status = 200, description = "Successfully removed jobs in bulk", body = Value),
1271+
(status = 400, description = "Bad request", body = APIError),
1272+
(status = 500, description = "Internal server error", body = APIError)
1273+
)
1274+
)]
1275+
pub async fn remove_jobs_handler(
1276+
node_commands_sender: Sender<NodeCommand>,
1277+
authorization: String,
1278+
payload: RemoveJobsRequest,
1279+
) -> Result<impl warp::Reply, warp::Rejection> {
1280+
let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
1281+
let (res_sender, res_receiver) = async_channel::bounded(1);
1282+
node_commands_sender
1283+
.send(NodeCommand::V2ApiRemoveJobs {
1284+
bearer,
1285+
job_ids: payload.job_ids,
1286+
res: res_sender,
1287+
})
1288+
.await
1289+
.map_err(|_| warp::reject::reject())?;
1290+
let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;
1291+
1292+
match result {
1293+
Ok(response) => {
1294+
let response = create_success_response(response);
1295+
Ok(warp::reply::with_status(warp::reply::json(&response), StatusCode::OK))
1296+
}
1297+
Err(error) => Ok(warp::reply::with_status(
1298+
warp::reply::json(&error),
1299+
StatusCode::from_u16(error.code).unwrap(),
1300+
)),
1301+
}
1302+
}
1303+
12521304
#[utoipa::path(
12531305
post,
12541306
path = "/v2/export_messages_from_inbox",
@@ -1398,14 +1450,15 @@ pub async fn get_job_provider_handler(
13981450
get_message_traces_handler,
13991451
fork_job_messages_handler,
14001452
remove_job_handler,
1453+
remove_jobs_handler,
14011454
),
14021455
components(
14031456
schemas(AddFileToFolder, V2SmartInbox, APIChangeJobAgentRequest, CreateJobRequest, JobConfig,
14041457
JobMessageRequest, GetLastMessagesRequest, V2ChatMessage, GetLastMessagesWithBranchesRequest,
14051458
UpdateJobConfigRequest, UpdateSmartInboxNameRequest, SerializedLLMProvider, JobCreationInfo,
14061459
JobMessage, NodeApiData, LLMProviderSubset, AssociatedUI, MinimalJobScope, CallbackAction, ShinkaiName,
14071460
LLMProviderInterface, RetryMessageRequest, UpdateJobScopeRequest, ExportInboxMessagesFormat, ExportInboxMessagesRequest,
1408-
ShinkaiSubidentityType, OpenAI, Ollama, Groq, Gemini, Exo, ShinkaiBackend, SendResponseBody, SendResponseBodyData, APIError, GetToolingLogsRequest, GetMessageTracesRequest, ForkJobMessagesRequest, RemoveJobRequest)
1461+
ShinkaiSubidentityType, OpenAI, Ollama, Groq, Gemini, Exo, ShinkaiBackend, SendResponseBody, SendResponseBodyData, APIError, GetToolingLogsRequest, GetMessageTracesRequest, ForkJobMessagesRequest, RemoveJobRequest, RemoveJobsRequest)
14091462
),
14101463
tags(
14111464
(name = "jobs", description = "Job API endpoints")

shinkai-libs/shinkai-http-api/src/node_commands.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ pub enum NodeCommand {
289289
job_id: String,
290290
res: Sender<Result<SendResponseBody, APIError>>,
291291
},
292+
V2ApiRemoveJobs {
293+
bearer: String,
294+
job_ids: Vec<String>,
295+
res: Sender<Result<Value, APIError>>,
296+
},
292297
V2ApiVecFSRetrievePathSimplifiedJson {
293298
bearer: String,
294299
payload: APIVecFsRetrievePathSimplifiedJson,

0 commit comments

Comments
 (0)