llm request tracking, user context cleanup, minor fixes

This commit is contained in:
2026-07-11 00:23:14 +01:00
parent 5936cf0b2e
commit 587958ffe0
24 changed files with 402 additions and 347 deletions
+5 -4
View File
@@ -6,6 +6,7 @@ use serde::Deserialize;
use serde_json::{Value, json};
use skald_core::approval::NewApprovalRule;
use skald_core::db::approval_rules;
use skald_core::tool_catalog::{AllTools, McpServerMeta, ToolInfo};
use std::collections::HashSet;
use std::sync::Arc;
@@ -18,7 +19,7 @@ use super::{ApiError, guard::AuthUser, require_context};
pub async fn list_rules(
State(skald): State<Arc<Skald>>,
) -> Result<Json<Value>, ApiError> {
let rules = skald.approval().list_rules().await?;
let rules = approval_rules::list(skald.db()).await?;
Ok(Json(json!(rules)))
}
@@ -28,7 +29,7 @@ pub async fn create_rule(
State(skald): State<Arc<Skald>>,
Json(body): Json<NewApprovalRule>,
) -> Result<Json<Value>, ApiError> {
let id = skald.approval().add_rule(body).await?;
let id = approval_rules::insert(skald.db(), body).await?;
Ok(Json(json!({ "id": id })))
}
@@ -42,7 +43,7 @@ pub async fn update_rule(
Path(p): Path<RulePath>,
Json(body): Json<NewApprovalRule>,
) -> Result<Json<Value>, ApiError> {
skald.approval().update_rule(p.id, body).await?;
approval_rules::update(skald.db(), p.id, body).await?;
Ok(Json(json!({ "ok": true })))
}
@@ -52,7 +53,7 @@ pub async fn delete_rule(
State(skald): State<Arc<Skald>>,
Path(p): Path<RulePath>,
) -> Result<Json<Value>, ApiError> {
skald.approval().delete_rule(p.id).await?;
approval_rules::delete(skald.db(), p.id).await?;
Ok(Json(json!({ "ok": true })))
}
+32 -15
View File
@@ -1,5 +1,5 @@
use axum::{
Json,
Json, Extension,
extract::{Path, State},
http::StatusCode,
};
@@ -8,7 +8,7 @@ use serde::Deserialize;
use skald_core::db::{scheduled_jobs, job_runs};
use std::sync::Arc;
use skald_core::skald::Skald;
use super::ApiError;
use super::{ApiError, guard::AuthUser, require_context};
#[derive(serde::Serialize)]
pub struct JobResponse {
@@ -29,8 +29,12 @@ pub struct JobResponse {
pub running_since: Option<String>,
}
pub async fn list(State(skald): State<Arc<Skald>>) -> Result<Json<Vec<JobResponse>>, ApiError> {
let jobs = scheduled_jobs::list(skald.db()).await?;
pub async fn list(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
) -> Result<Json<Vec<JobResponse>>, ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let jobs = scheduled_jobs::list(&ctx.pool).await?;
Ok(Json(jobs.into_iter().map(|j| JobResponse {
id: j.id,
title: j.title,
@@ -51,22 +55,26 @@ pub async fn list(State(skald): State<Arc<Skald>>) -> Result<Json<Vec<JobRespons
}
pub async fn delete_job(
Path(id): Path<i64>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(id): Path<i64>,
) -> Result<(), ApiError> {
let found = scheduled_jobs::delete(skald.db(), id).await?;
let ctx = require_context(&skald, &auth.user_id).await?;
let found = scheduled_jobs::delete(&ctx.pool, id).await?;
if found { Ok(()) } else { Err(ApiError::not_found(format!("job {id} not found"))) }
}
pub async fn toggle(
Path(id): Path<i64>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(id): Path<i64>,
Json(body): Json<serde_json::Value>,
) -> Result<(), ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let enabled = body["enabled"]
.as_bool()
.ok_or_else(|| ApiError::bad_request("'enabled' boolean required"))?;
let found = scheduled_jobs::set_enabled(skald.db(), id, enabled).await?;
let found = scheduled_jobs::set_enabled(&ctx.pool, id, enabled).await?;
if found { Ok(()) } else { Err(ApiError::not_found(format!("job {id} not found"))) }
}
@@ -76,15 +84,17 @@ pub struct SetRunContextBody {
}
pub async fn set_run_context(
Path(id): Path<i64>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(id): Path<i64>,
Json(body): Json<SetRunContextBody>,
) -> Result<(), ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
use skald_core::run_context::RunContext;
let json = body.security_group.as_ref().map(|sg| {
RunContext::with_security_group(Some(sg.clone())).to_db()
});
let found = scheduled_jobs::set_run_context(skald.db(), id, json.as_deref()).await?;
let found = scheduled_jobs::set_run_context(&ctx.pool, id, json.as_deref()).await?;
if found { Ok(()) } else { Err(ApiError::not_found(format!("job {id} not found"))) }
}
@@ -106,19 +116,26 @@ pub struct JobRunResponse {
}
pub async fn kill_job(
Path(id): Path<i64>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(id): Path<i64>,
) -> Result<StatusCode, ApiError> {
let job = scheduled_jobs::get_by_id(skald.db(), id).await?
let ctx = require_context(&skald, &auth.user_id).await?;
let job = scheduled_jobs::get_by_id(&ctx.pool, id).await?
.ok_or_else(|| ApiError::not_found(format!("job {id} not found")))?;
let session_id = job.running_session_id
.ok_or_else(|| ApiError::bad_request("job is not currently running"))?;
skald.system_bus().send(core_api::system_bus::SystemEvent::SessionCancelled { session_id });
// Direct cancel on the user's own session manager — no system bus fan-out.
ctx.sessions.cancel_session(session_id).await;
Ok(StatusCode::ACCEPTED)
}
pub async fn list_runs(State(skald): State<Arc<Skald>>) -> Result<Json<Vec<JobRunResponse>>, ApiError> {
let runs = job_runs::list_all(skald.db(), 200).await?;
pub async fn list_runs(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
) -> Result<Json<Vec<JobRunResponse>>, ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let runs = job_runs::list_all(&ctx.pool, 200).await?;
Ok(Json(runs.into_iter().map(|r| JobRunResponse {
id: r.id,
job_id: r.job_id,
+71 -66
View File
@@ -1,13 +1,13 @@
use axum::{
extract::{Path, Query, State},
response::IntoResponse,
Json,
Json, Extension,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use skald_core::skald::Skald;
use super::ApiError;
use super::{ApiError, guard::AuthUser, require_context};
const KEY: &str = "DEBUG_MODE";
@@ -44,18 +44,14 @@ const PAGE_SIZE: i64 = 20;
#[derive(Deserialize)]
pub struct LlmRequestsQuery {
pub agent_id: Option<String>,
pub source: Option<String>,
pub from: Option<String>,
pub to: Option<String>,
pub page: Option<i64>,
pub from: Option<String>,
pub to: Option<String>,
pub page: Option<i64>,
}
#[derive(Serialize)]
pub struct LlmRequestItem {
pub id: i64,
pub agent_id: Option<String>,
pub source: Option<String>,
pub model_name: String,
pub created_at: String,
pub input_tokens: Option<i64>,
@@ -76,37 +72,34 @@ pub struct LlmRequestsResponse {
pub async fn list_llm_requests(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Query(params): Query<LlmRequestsQuery>,
) -> Result<impl IntoResponse, ApiError> {
let page = params.page.unwrap_or(1).max(1);
let offset = (page - 1) * PAGE_SIZE;
// Bind optional filters twice each: once for the IS NULL check, once for the
// equality check. SQLite evaluates `? IS NULL` against the bound value itself.
let items = sqlx::query_as::<_, (i64, Option<String>, Option<String>, String, String, Option<i64>, Option<i64>, Option<i64>, Option<i64>, i64, Option<String>)>(
// Metadata-only query on system.db. No JOIN with chat_sessions (that table
// lives in the per-user owner bucket). Filters by user_id so each user sees
// only their own requests.
let items = sqlx::query_as::<_, (i64, String, String, Option<i64>, Option<i64>, Option<i64>, Option<i64>, i64, Option<String>)>(
"SELECT
r.id,
s.agent_id,
s.source,
r.model_name,
r.created_at,
r.input_tokens,
r.output_tokens,
r.cache_read_tokens,
r.cache_creation_tokens,
r.duration_ms,
r.error_text
FROM llm_requests r
LEFT JOIN chat_sessions s ON s.id = r.session_id
WHERE (? IS NULL OR s.agent_id = ?)
AND (? IS NULL OR s.source = ?)
AND (? IS NULL OR r.created_at >= ?)
AND (? IS NULL OR r.created_at <= ?)
ORDER BY r.created_at DESC
id,
model_name,
created_at,
input_tokens,
output_tokens,
cache_read_tokens,
cache_creation_tokens,
duration_ms,
error_text
FROM llm_requests
WHERE user_id = ?
AND (? IS NULL OR created_at >= ?)
AND (? IS NULL OR created_at <= ?)
ORDER BY created_at DESC
LIMIT ? OFFSET ?",
)
.bind(&params.agent_id).bind(&params.agent_id)
.bind(&params.source).bind(&params.source)
.bind(&auth.user_id)
.bind(&params.from).bind(&params.from)
.bind(&params.to).bind(&params.to)
.bind(PAGE_SIZE)
@@ -114,22 +107,19 @@ pub async fn list_llm_requests(
.fetch_all(&**skald.db())
.await?
.into_iter()
.map(|(id, agent_id, source, model_name, created_at, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, error_text)| {
LlmRequestItem { id, agent_id, source, model_name, created_at, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, error_text }
.map(|(id, model_name, created_at, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, error_text)| {
LlmRequestItem { id, model_name, created_at, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, error_text }
})
.collect::<Vec<_>>();
let total = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*)
FROM llm_requests r
LEFT JOIN chat_sessions s ON s.id = r.session_id
WHERE (? IS NULL OR s.agent_id = ?)
AND (? IS NULL OR s.source = ?)
AND (? IS NULL OR r.created_at >= ?)
AND (? IS NULL OR r.created_at <= ?)",
FROM llm_requests
WHERE user_id = ?
AND (? IS NULL OR created_at >= ?)
AND (? IS NULL OR created_at <= ?)",
)
.bind(&params.agent_id).bind(&params.agent_id)
.bind(&params.source).bind(&params.source)
.bind(&auth.user_id)
.bind(&params.from).bind(&params.from)
.bind(&params.to).bind(&params.to)
.fetch_one(&**skald.db())
@@ -143,8 +133,7 @@ pub async fn list_llm_requests(
#[derive(Serialize)]
pub struct LlmRequestDetail {
pub id: i64,
pub agent_id: Option<String>,
pub source: Option<String>,
pub request_id: Option<String>,
pub stack_id: Option<i64>,
pub model_name: String,
pub created_at: String,
@@ -154,6 +143,8 @@ pub struct LlmRequestDetail {
pub cache_creation_tokens: Option<i64>,
pub duration_ms: i64,
pub error_text: Option<String>,
// Payload fields — now live in llm_request_payloads in the user's own database.
// Left as None for now; a future cross-pool lookup via request_id can fill them.
pub request_json: Option<String>,
pub request_headers: Option<String>,
pub response_json: Option<String>,
@@ -162,40 +153,54 @@ pub struct LlmRequestDetail {
pub async fn get_llm_request(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(id): Path<i64>,
) -> Result<impl IntoResponse, ApiError> {
let row = sqlx::query_as::<_, (i64, Option<String>, Option<String>, Option<i64>, String, String, Option<i64>, Option<i64>, Option<i64>, Option<i64>, i64, Option<String>, Option<String>, Option<String>, Option<String>, Option<String>)>(
let row = sqlx::query_as::<_, (i64, Option<String>, Option<i64>, String, String, Option<i64>, Option<i64>, Option<i64>, Option<i64>, i64, Option<String>)>(
"SELECT
r.id,
s.agent_id,
s.source,
r.stack_id,
r.model_name,
r.created_at,
r.input_tokens,
r.output_tokens,
r.cache_read_tokens,
r.cache_creation_tokens,
r.duration_ms,
r.error_text,
NULLIF(r.request_json, '') AS request_json,
r.request_headers,
r.response_json,
r.response_headers
FROM llm_requests r
LEFT JOIN chat_sessions s ON s.id = r.session_id
WHERE r.id = ?",
id,
request_id,
stack_id,
model_name,
created_at,
input_tokens,
output_tokens,
cache_read_tokens,
cache_creation_tokens,
duration_ms,
error_text
FROM llm_requests
WHERE id = ? AND user_id = ?",
)
.bind(id)
.bind(&auth.user_id)
.fetch_optional(&**skald.db())
.await?;
let Some((id, agent_id, source, stack_id, model_name, created_at, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, error_text, request_json, request_headers, response_json, response_headers)) = row else {
let Some((id, request_id, stack_id, model_name, created_at, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, error_text)) = row else {
return Err(ApiError::not_found(format!("llm_request {id} not found")));
};
// Try to fetch the payload from the user's own database via request_id.
let payload = if let Some(ref rid) = request_id {
let ctx = require_context(&skald, &auth.user_id).await.ok();
if let Some(ctx) = ctx {
sqlx::query_as::<_, (Option<String>, Option<String>, Option<String>, Option<String>)>(
"SELECT request_json, request_headers, response_json, response_headers
FROM llm_request_payloads WHERE request_id = ?",
)
.bind(rid)
.fetch_optional(&*ctx.pool)
.await
.ok()
.flatten()
} else { None }
} else { None };
let (request_json, request_headers, response_json, response_headers) = payload.unwrap_or((None, None, None, None));
Ok(Json(LlmRequestDetail {
id, agent_id, source, stack_id, model_name, created_at,
id, request_id, stack_id, model_name, created_at,
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens,
duration_ms, error_text,
request_json, request_headers, response_json, response_headers,
-9
View File
@@ -139,8 +139,6 @@ pub fn router() -> Router<Arc<Skald>> {
// Config properties
.route("/config", get(config::list_properties))
.route("/config/{key}", put(config::set_property))
// TIC
.route("/tic/trigger", post(tic_trigger))
// Plugins
.route("/plugins", get(plugins::list))
.route("/plugins/{id}", put(plugins::update))
@@ -164,13 +162,6 @@ pub fn router() -> Router<Arc<Skald>> {
.route("/file", delete(files::delete_file))
}
async fn tic_trigger(State(skald): State<Arc<Skald>>) -> impl IntoResponse {
tokio::spawn(async move {
Arc::clone(skald.tic_manager()).tick_now().await;
});
StatusCode::ACCEPTED
}
pub struct ApiError {
status: StatusCode,
message: String,
+88 -37
View File
@@ -6,9 +6,11 @@ use axum::{
http::StatusCode,
};
use serde::{Deserialize, Serialize};
use sqlx::SqlitePool;
use skald_core::db::project_tickets::ProjectTicket;
use skald_core::db::projects::Project;
use skald_core::db::{project_tickets, projects};
use skald_core::run_context::RunContext;
use skald_core::skald::Skald;
use super::{ApiError, guard::AuthUser, require_context};
@@ -130,62 +132,70 @@ impl<'de> Deserialize<'de> for TicketPath {
pub async fn list(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
) -> Result<Json<Vec<ProjectResponse>>, ApiError> {
let projects = skald.projects().list().await?;
Ok(Json(projects.into_iter().map(Into::into).collect()))
let ctx = require_context(&skald, &auth.user_id).await?;
let items = projects::list(&ctx.pool).await?;
Ok(Json(items.into_iter().map(Into::into).collect()))
}
pub async fn create(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Json(body): Json<ProjectBody>,
) -> Result<(StatusCode, Json<ProjectResponse>), ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let rc_json = body.rc_json();
let rc = rc_json.as_deref().and_then(RunContext::from_db);
let project = skald.projects().create(
let project = projects::create(
&ctx.pool,
&body.name,
&body.path,
body.description.as_deref().unwrap_or(""),
rc.as_ref(),
rc_json.as_deref(),
).await?;
Ok((StatusCode::CREATED, Json(project.into())))
}
pub async fn get_project(
Path(p): Path<ProjectPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(p): Path<ProjectPath>,
) -> Result<Json<ProjectResponse>, ApiError> {
let project = skald.projects().get(p.id).await?
let ctx = require_context(&skald, &auth.user_id).await?;
let project = projects::get(&ctx.pool, p.id).await?
.ok_or_else(|| ApiError::not_found(format!("project {} not found", p.id)))?;
Ok(Json(project.into()))
}
pub async fn update(
Path(p): Path<ProjectPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(p): Path<ProjectPath>,
Json(body): Json<ProjectBody>,
) -> Result<Json<ProjectResponse>, ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let rc_json = body.rc_json();
let rc = rc_json.as_deref().and_then(RunContext::from_db);
let found = skald.projects().update(
p.id,
&body.name,
&body.path,
let found = projects::update(
&ctx.pool, p.id,
&body.name, &body.path,
body.description.as_deref().unwrap_or(""),
rc.as_ref(),
rc_json.as_deref(),
).await?;
if !found {
return Err(ApiError::not_found(format!("project {} not found", p.id)));
}
let project = skald.projects().get(p.id).await?
let project = projects::get(&ctx.pool, p.id).await?
.ok_or_else(|| ApiError::not_found(format!("project {} not found", p.id)))?;
Ok(Json(project.into()))
}
pub async fn delete(
Path(p): Path<ProjectPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(p): Path<ProjectPath>,
) -> Result<StatusCode, ApiError> {
let found = skald.projects().delete(p.id).await?;
let ctx = require_context(&skald, &auth.user_id).await?;
let found = projects::delete(&ctx.pool, p.id).await?;
if found { Ok(StatusCode::NO_CONTENT) }
else { Err(ApiError::not_found(format!("project {} not found", p.id))) }
}
@@ -193,56 +203,97 @@ pub async fn delete(
// ── Ticket handlers ───────────────────────────────────────────────────────────
pub async fn list_tickets(
Path(p): Path<ProjectPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(p): Path<ProjectPath>,
) -> Result<Json<Vec<TicketResponse>>, ApiError> {
let tickets = skald.ticket_manager().list(p.id).await?;
let ctx = require_context(&skald, &auth.user_id).await?;
let tickets = project_tickets::list_for_project(&ctx.pool, p.id).await?;
Ok(Json(tickets.into_iter().map(Into::into).collect()))
}
pub async fn create_ticket(
Path(p): Path<ProjectPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(p): Path<ProjectPath>,
Json(body): Json<TicketBody>,
) -> Result<(StatusCode, Json<TicketResponse>), ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let rc_json = body.rc_json();
let rc = rc_json.as_deref().and_then(RunContext::from_db);
// Tickets run a task sub-agent — no default. The agent's `type == task` is enforced
// when the ticket starts, via TaskManager::spawn_async_job (require_task_agent).
let agent_id = body.agent_id.as_deref().map(str::trim).filter(|s| !s.is_empty())
.ok_or_else(|| ApiError::bad_request("agent_id is required — pick a task agent for this ticket"))?;
let ticket = skald.ticket_manager().create(
p.id,
let ticket = project_tickets::create(
&ctx.pool, p.id,
&body.title,
body.description.as_deref().unwrap_or(""),
agent_id,
rc.as_ref(),
rc_json.as_deref(),
).await?;
projects::touch(&ctx.pool, p.id).await?;
Ok((StatusCode::CREATED, Json(ticket.into())))
}
pub async fn delete_ticket(
Path(tp): Path<TicketPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(tp): Path<TicketPath>,
) -> Result<StatusCode, ApiError> {
let found = skald.ticket_manager().delete(tp.tid).await?;
if found { Ok(StatusCode::NO_CONTENT) }
else { Err(ApiError::not_found(format!("ticket {} not found", tp.tid))) }
let ctx = require_context(&skald, &auth.user_id).await?;
let ticket = project_tickets::get(&ctx.pool, tp.tid).await?;
let found = project_tickets::delete(&ctx.pool, tp.tid).await?;
if found {
if let Some(t) = ticket {
projects::touch(&ctx.pool, t.project_id).await?;
}
Ok(StatusCode::NO_CONTENT)
} else {
Err(ApiError::not_found(format!("ticket {} not found", tp.tid)))
}
}
pub async fn start_ticket(
Path(tp): Path<TicketPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(tp): Path<TicketPath>,
) -> Result<StatusCode, ApiError> {
skald.ticket_manager().start(tp.tid).await?;
let ctx = require_context(&skald, &auth.user_id).await?;
let ticket = project_tickets::get(&ctx.pool, tp.tid).await?
.ok_or_else(|| ApiError::not_found(format!("ticket {} not found", tp.tid)))?;
let project = projects::get(&ctx.pool, ticket.project_id).await?
.ok_or_else(|| ApiError::not_found(format!("project {} not found", ticket.project_id)))?;
let base: Option<RunContext> =
ticket.run_context.as_deref().and_then(RunContext::from_db)
.or_else(|| project.run_context.as_deref().and_then(RunContext::from_db));
let rc = skald_core::projects::build_runtime_run_context(&project, base);
let origin_ref = format!("PROJECT_TASK:{}", tp.tid);
let rc_json = rc.to_db();
let job = ctx.cron.spawn_async_job(
&ticket.title,
&ticket.description,
&ticket.description,
&ticket.agent_id,
Some(&rc_json),
&origin_ref,
)?;
project_tickets::start(&ctx.pool, tp.tid, job.id).await?;
projects::touch(&ctx.pool, ticket.project_id).await?;
Ok(StatusCode::ACCEPTED)
}
pub async fn reset_ticket(
Path(tp): Path<TicketPath>,
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
Path(tp): Path<TicketPath>,
) -> Result<StatusCode, ApiError> {
skald.ticket_manager().reset(tp.tid).await?;
let ctx = require_context(&skald, &auth.user_id).await?;
let project_id = project_tickets::get(&ctx.pool, tp.tid).await?.map(|t| t.project_id);
project_tickets::reset(&ctx.pool, tp.tid).await?;
if let Some(pid) = project_id {
projects::touch(&ctx.pool, pid).await?;
}
Ok(StatusCode::NO_CONTENT)
}
@@ -261,7 +312,7 @@ pub struct SessionResponse {
/// provisioning config, shared by session-open and session-reset so the two never
/// diverge.
pub async fn provisioning_for_source(
skald: &Skald,
pool: &SqlitePool,
source: &str,
) -> Result<(String, Option<RunContext>), ApiError> {
let Some(id) = source
@@ -271,7 +322,7 @@ pub async fn provisioning_for_source(
return Ok(("main".to_string(), None));
};
let project = skald.projects().get(id).await?
let project = projects::get(pool, id).await?
.ok_or_else(|| ApiError::not_found(format!("project {id} not found")))?;
let base = project.run_context.as_deref().and_then(RunContext::from_db);
let rc = skald_core::projects::build_runtime_run_context(&project, base);
@@ -288,7 +339,7 @@ pub async fn open_session(
) -> Result<Json<SessionResponse>, ApiError> {
let ctx = require_context(&skald, &auth.user_id).await?;
let source = format!("{PROJECT_SOURCE_PREFIX}{}", p.id);
let (agent, rc) = provisioning_for_source(&skald, &source).await?;
let (agent, rc) = provisioning_for_source(&ctx.pool, &source).await?;
let session_id = ctx.chat_hub
.provision_session(&source, &agent, rc.as_ref(), false)
.await?;
+1 -1
View File
@@ -38,7 +38,7 @@ pub async fn create(
let ctx = require_context(&skald, &auth.user_id).await?;
// Resolve agent + RunContext from the source so project chats reset with the
// coordinator agent (not the default `main`), then provision a fresh session.
let (agent, rc) = super::projects::provisioning_for_source(&skald, &q.source).await?;
let (agent, rc) = super::projects::provisioning_for_source(&ctx.pool, &q.source).await?;
ctx.chat_hub.provision_session(&q.source, &agent, rc.as_ref(), true).await?;
Ok(Json(json!({})))
}