diff --git a/AGENTS.md b/AGENTS.md index 0938ced32..66f4bfabb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co The OpenAPI spec at `docs/api-reference/fabro-api.yaml` is the source of truth for the fabro-api HTTP interface. 1. Edit `docs/api-reference/fabro-api.yaml` -2. `cargo build -p fabro-types` — build.rs regenerates Rust types via typify +2. `cargo build -p fabro-api-types` — build.rs regenerates Rust types via typify 3. Write/update handler in `lib/crates/fabro-api/src/server.rs`, add route to `build_router()` 4. `cargo nextest run -p fabro-api` — conformance test catches spec/router drift 5. `cd lib/packages/fabro-api-client && bun run generate` — regenerates TypeScript Axios client @@ -57,7 +57,7 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as - **fabro-exe** — SSH-based sandbox implementation (`ExeSandbox`) - **fabro-sprites** — Sprites VM sandbox implementation via `sprite` CLI - **fabro-llm** — Unified LLM client with providers: Anthropic, OpenAI, Gemini, OpenAI-compatible, plus retry/middleware/streaming -- **fabro-types** — Auto-generated Rust types from OpenAPI spec (build.rs + typify) +- **fabro-api-types** — Auto-generated Rust types from OpenAPI spec (build.rs + typify) - **fabro-github** — GitHub App auth (JWT signing, installation tokens, PR creation) - **fabro-db** — SQLite with WAL mode, schema migrations - **fabro-mcp** — Model Context Protocol client/server diff --git a/Cargo.lock b/Cargo.lock index c102e5b54..1a5cc7d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1265,6 +1265,7 @@ dependencies = [ "clap", "dirs", "fabro-agent", + "fabro-api-types", "fabro-config", "fabro-db", "fabro-github", @@ -1275,7 +1276,6 @@ dependencies = [ "fabro-model", "fabro-retro", "fabro-sandbox", - "fabro-types", "fabro-util", "fabro-workflows", "futures-util", @@ -1308,6 +1308,21 @@ dependencies = [ "x509-parser", ] +[[package]] +name = "fabro-api-types" +version = "0.176.2" +dependencies = [ + "chrono", + "prettyplease", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_yaml", + "syn", + "typify", + "uuid", +] + [[package]] name = "fabro-cli" version = "0.176.2" @@ -1693,21 +1708,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "fabro-types" -version = "0.176.2" -dependencies = [ - "chrono", - "prettyplease", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_yaml", - "syn", - "typify", - "uuid", -] - [[package]] name = "fabro-util" version = "0.176.2" diff --git a/docs/api-reference/client-sdks.mdx b/docs/api-reference/client-sdks.mdx index c2a755c12..3e3ba3975 100644 --- a/docs/api-reference/client-sdks.mdx +++ b/docs/api-reference/client-sdks.mdx @@ -36,14 +36,14 @@ The generated client includes a typed API class for each endpoint group: `RunsAp ## Rust (Types Only) -The `fabro-types` crate generates Rust structs and enums from the OpenAPI component schemas at compile time using [typify](https://github.com/oxidecomputer/typify). This provides type-safe representations of all API models but does not include an HTTP client. +The `fabro-api-types` crate generates Rust structs and enums from the OpenAPI component schemas at compile time using [typify](https://github.com/oxidecomputer/typify). This provides type-safe representations of all API models but does not include an HTTP client. ### How It Works A `build.rs` script reads `docs/api-reference/fabro-api.yaml`, extracts `components/schemas`, and feeds them to typify. The generated code is written to `OUT_DIR` and included via: ```rust -// lib/crates/fabro-types/src/lib.rs +// lib/crates/fabro-api-types/src/lib.rs include!(concat!(env!("OUT_DIR"), "/openapi_types.rs")); ``` @@ -52,13 +52,13 @@ include!(concat!(env!("OUT_DIR"), "/openapi_types.rs")); The types are regenerated automatically on every `cargo build` when the OpenAPI spec changes: ```bash -cargo build -p fabro-types +cargo build -p fabro-api-types ``` ### Usage ```rust -use fabro_types::RunListItem; +use fabro_api_types::RunListItem; ``` All generated types derive `serde::Deserialize` and `serde::Serialize`, so they work directly with any Rust HTTP client for request and response parsing. diff --git a/lib/crates/fabro-types/Cargo.toml b/lib/crates/fabro-api-types/Cargo.toml similarity index 95% rename from lib/crates/fabro-types/Cargo.toml rename to lib/crates/fabro-api-types/Cargo.toml index 81e2a7a4e..6217715fb 100644 --- a/lib/crates/fabro-types/Cargo.toml +++ b/lib/crates/fabro-api-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fabro-types" +name = "fabro-api-types" edition.workspace = true version.workspace = true license.workspace = true diff --git a/lib/crates/fabro-types/build.rs b/lib/crates/fabro-api-types/build.rs similarity index 100% rename from lib/crates/fabro-types/build.rs rename to lib/crates/fabro-api-types/build.rs diff --git a/lib/crates/fabro-types/src/lib.rs b/lib/crates/fabro-api-types/src/lib.rs similarity index 100% rename from lib/crates/fabro-types/src/lib.rs rename to lib/crates/fabro-api-types/src/lib.rs diff --git a/lib/crates/fabro-api/Cargo.toml b/lib/crates/fabro-api/Cargo.toml index c83d51587..55ede29ab 100644 --- a/lib/crates/fabro-api/Cargo.toml +++ b/lib/crates/fabro-api/Cargo.toml @@ -22,7 +22,7 @@ fabro-model = { path = "../fabro-model" } fabro-retro = { path = "../fabro-retro" } fabro-util = { path = "../fabro-util" } fabro-db = { path = "../fabro-db" } -fabro-types = { path = "../fabro-types" } +fabro-api-types = { path = "../fabro-api-types" } chrono.workspace = true futures-util.workspace = true axum = "0.8" diff --git a/lib/crates/fabro-api/src/demo/mod.rs b/lib/crates/fabro-api/src/demo/mod.rs index ad1b5d86a..4c33fef21 100644 --- a/lib/crates/fabro-api/src/demo/mod.rs +++ b/lib/crates/fabro-api/src/demo/mod.rs @@ -20,7 +20,7 @@ pub struct RetroListParams { #[serde(rename = "page[offset]", default)] offset: u32, workflow: Option, - smoothness: Option, + smoothness: Option, } fn paginated_response( @@ -140,9 +140,9 @@ pub async fn get_run_status( match runs::list_items().into_iter().find(|r| r.id == id) { Some(item) => ( StatusCode::OK, - Json(fabro_types::RunStatusResponse { + Json(fabro_api_types::RunStatusResponse { id: id.clone(), - status: fabro_types::RunStatus::Running, + status: fabro_api_types::RunStatus::Running, error: None, queue_position: None, created_at: item.created_at, @@ -464,11 +464,11 @@ pub async fn session_events_stub( for turn in &session.turns { let (event_type, data) = match turn { - fabro_types::SessionTurn::UserTurn(_) => continue, - fabro_types::SessionTurn::AssistantTurn(t) => { + fabro_api_types::SessionTurn::UserTurn(_) => continue, + fabro_api_types::SessionTurn::AssistantTurn(t) => { ("assistant_turn", serde_json::to_string(t).unwrap()) } - fabro_types::SessionTurn::ToolTurn(t) => { + fabro_api_types::SessionTurn::ToolTurn(t) => { ("tool_turn", serde_json::to_string(t).unwrap()) } }; @@ -614,7 +614,7 @@ fn ts(s: &str) -> DateTime { mod runs { use super::ts; - use fabro_types::*; + use fabro_api_types::*; pub fn list_items() -> Vec { vec![ @@ -1235,7 +1235,7 @@ mod runs { } } - pub fn verifications() -> Vec { + pub fn verifications() -> Vec { super::verifications::run_verifications() } @@ -1336,7 +1336,7 @@ mod runs { } mod usage { - use fabro_types::*; + use fabro_api_types::*; pub fn aggregate() -> AggregateUsage { AggregateUsage { @@ -1388,7 +1388,7 @@ mod usage { mod workflows { use super::ts; - use fabro_types::*; + use fabro_api_types::*; pub fn list_items() -> Vec { vec![ @@ -1768,7 +1768,7 @@ mod workflows { mod verifications { use super::ts; - use fabro_types::*; + use fabro_api_types::*; // ── Category definitions (name, question, controls) ───────────────── @@ -2611,7 +2611,7 @@ mod verifications { mod signoffs { use super::ts; - use fabro_types::*; + use fabro_api_types::*; struct SignoffDef { id: &'static str, @@ -2726,7 +2726,7 @@ mod signoffs { mod retros { use super::ts; - use fabro_types::*; + use fabro_api_types::*; #[allow(clippy::too_many_arguments)] fn stage( @@ -3060,7 +3060,7 @@ mod retros { mod sessions { use super::ts; - use fabro_types::*; + use fabro_api_types::*; use uuid::Uuid; fn uid(n: u128) -> Uuid { @@ -3202,7 +3202,7 @@ mod sessions { mod insights { use super::ts; - use fabro_types::*; + use fabro_api_types::*; pub fn saved_queries() -> Vec { vec![ diff --git a/lib/crates/fabro-api/src/server.rs b/lib/crates/fabro-api/src/server.rs index 34c6f4ab6..ef982236b 100644 --- a/lib/crates/fabro-api/src/server.rs +++ b/lib/crates/fabro-api/src/server.rs @@ -29,7 +29,7 @@ use fabro_workflows::records::Checkpoint; use fabro_workflows::run_options::LifecycleOptions; use fabro_workflows::run_options::RunOptions; -pub use fabro_types::{ +pub use fabro_api_types::{ ApiQuestion, ApiQuestionOption, PaginatedRunList, PaginationMeta, QuestionType as ApiQuestionType, RunStatus, RunStatusResponse, StartRunRequest, SubmitAnswerRequest, @@ -356,21 +356,21 @@ async fn get_aggregate_usage( .aggregate_usage .lock() .expect("aggregate_usage lock poisoned"); - let by_model: Vec = agg + let by_model: Vec = agg .by_model .iter() - .map(|(model, totals)| fabro_types::UsageByModel { - model: fabro_types::ModelReference { id: model.clone() }, + .map(|(model, totals)| fabro_api_types::UsageByModel { + model: fabro_api_types::ModelReference { id: model.clone() }, stages: totals.stages, - usage: fabro_types::TokenUsage { + usage: fabro_api_types::TokenUsage { input_tokens: totals.input_tokens, output_tokens: totals.output_tokens, cost: totals.cost, }, }) .collect(); - let response = fabro_types::AggregateUsage { - totals: fabro_types::AggregateUsageTotals { + let response = fabro_api_types::AggregateUsage { + totals: fabro_api_types::AggregateUsageTotals { runs: agg.total_runs, input_tokens: by_model.iter().map(|m| m.usage.input_tokens).sum(), output_tokens: by_model.iter().map(|m| m.usage.output_tokens).sum(), @@ -476,9 +476,12 @@ async fn list_runs( .map(|(id, managed_run)| RunStatusResponse { id: id.clone(), status: managed_run.status, - error: managed_run.error.as_ref().map(|msg| fabro_types::RunError { - message: msg.clone(), - }), + error: managed_run + .error + .as_ref() + .map(|msg| fabro_api_types::RunError { + message: msg.clone(), + }), queue_position: queue_positions.get(id).copied(), created_at: managed_run.created_at, }) @@ -891,9 +894,12 @@ async fn get_run_status( Json(RunStatusResponse { id: id.clone(), status: managed_run.status, - error: managed_run.error.as_ref().map(|msg| fabro_types::RunError { - message: msg.clone(), - }), + error: managed_run + .error + .as_ref() + .map(|msg| fabro_api_types::RunError { + message: msg.clone(), + }), created_at: managed_run.created_at, queue_position, }), @@ -1232,13 +1238,13 @@ fn finish_reason_to_api_stop_reason(reason: &fabro_llm::types::FinishReason) -> } } -fn convert_api_message(msg: &fabro_types::CompletionMessage) -> fabro_llm::types::Message { +fn convert_api_message(msg: &fabro_api_types::CompletionMessage) -> fabro_llm::types::Message { let role = match msg.role { - fabro_types::CompletionMessageRole::System => fabro_llm::types::Role::System, - fabro_types::CompletionMessageRole::User => fabro_llm::types::Role::User, - fabro_types::CompletionMessageRole::Assistant => fabro_llm::types::Role::Assistant, - fabro_types::CompletionMessageRole::Tool => fabro_llm::types::Role::Tool, - fabro_types::CompletionMessageRole::Developer => fabro_llm::types::Role::Developer, + fabro_api_types::CompletionMessageRole::System => fabro_llm::types::Role::System, + fabro_api_types::CompletionMessageRole::User => fabro_llm::types::Role::User, + fabro_api_types::CompletionMessageRole::Assistant => fabro_llm::types::Role::Assistant, + fabro_api_types::CompletionMessageRole::Tool => fabro_llm::types::Role::Tool, + fabro_api_types::CompletionMessageRole::Developer => fabro_llm::types::Role::Developer, }; let content: Vec = msg .content @@ -1256,15 +1262,15 @@ fn convert_api_message(msg: &fabro_types::CompletionMessage) -> fabro_llm::types } } -fn convert_llm_message(msg: &fabro_llm::types::Message) -> fabro_types::CompletionMessage { +fn convert_llm_message(msg: &fabro_llm::types::Message) -> fabro_api_types::CompletionMessage { let role = match msg.role { - fabro_llm::types::Role::System => fabro_types::CompletionMessageRole::System, - fabro_llm::types::Role::User => fabro_types::CompletionMessageRole::User, - fabro_llm::types::Role::Assistant => fabro_types::CompletionMessageRole::Assistant, - fabro_llm::types::Role::Tool => fabro_types::CompletionMessageRole::Tool, - fabro_llm::types::Role::Developer => fabro_types::CompletionMessageRole::Developer, + fabro_llm::types::Role::System => fabro_api_types::CompletionMessageRole::System, + fabro_llm::types::Role::User => fabro_api_types::CompletionMessageRole::User, + fabro_llm::types::Role::Assistant => fabro_api_types::CompletionMessageRole::Assistant, + fabro_llm::types::Role::Tool => fabro_api_types::CompletionMessageRole::Tool, + fabro_llm::types::Role::Developer => fabro_api_types::CompletionMessageRole::Developer, }; - let content: Vec = msg + let content: Vec = msg .content .iter() .filter_map(|part| { @@ -1272,7 +1278,7 @@ fn convert_llm_message(msg: &fabro_llm::types::Message) -> fabro_types::Completi serde_json::from_value(json).ok() }) .collect(); - fabro_types::CompletionMessage { + fabro_api_types::CompletionMessage { role, content, name: msg.name.clone(), @@ -1283,7 +1289,7 @@ fn convert_llm_message(msg: &fabro_llm::types::Message) -> fabro_types::Completi async fn create_completion( _auth: AuthenticatedService, State(state): State>, - Json(req): Json, + Json(req): Json, ) -> Response { // Resolve model let model_id = req.model.unwrap_or_else(|| { @@ -1330,12 +1336,12 @@ async fn create_completion( // Convert tool_choice let tool_choice: Option = req.tool_choice.map(|tc| match tc.mode { - fabro_types::CompletionToolChoiceMode::Auto => fabro_llm::types::ToolChoice::Auto, - fabro_types::CompletionToolChoiceMode::None => fabro_llm::types::ToolChoice::None, - fabro_types::CompletionToolChoiceMode::Required => { + fabro_api_types::CompletionToolChoiceMode::Auto => fabro_llm::types::ToolChoice::Auto, + fabro_api_types::CompletionToolChoiceMode::None => fabro_llm::types::ToolChoice::None, + fabro_api_types::CompletionToolChoiceMode::Required => { fabro_llm::types::ToolChoice::Required } - fabro_types::CompletionToolChoiceMode::Named => { + fabro_api_types::CompletionToolChoiceMode::Named => { fabro_llm::types::ToolChoice::named(tc.tool_name.unwrap_or_default()) } }); @@ -1390,18 +1396,18 @@ async fn create_completion( )]); return Sse::new(sse_stream).into_response(); } - let empty_msg = fabro_types::CompletionMessage { - role: fabro_types::CompletionMessageRole::Assistant, + let empty_msg = fabro_api_types::CompletionMessage { + role: fabro_api_types::CompletionMessageRole::Assistant, content: vec![], name: None, tool_call_id: None, }; - return Json(fabro_types::CompletionResponse { + return Json(fabro_api_types::CompletionResponse { id: msg_id, model: model_id, message: empty_msg, stop_reason: "end_turn".to_string(), - usage: fabro_types::CompletionUsage { + usage: fabro_api_types::CompletionUsage { input_tokens: 0, output_tokens: 0, }, @@ -1493,12 +1499,12 @@ async fn create_completion( params = params.top_p(top_p); } match fabro_llm::generate::generate_object(params, schema).await { - Ok(result) => Json(fabro_types::CompletionResponse { + Ok(result) => Json(fabro_api_types::CompletionResponse { id: msg_id, model: model_id, message: convert_llm_message(&result.response.message), stop_reason: finish_reason_to_api_stop_reason(&result.finish_reason), - usage: fabro_types::CompletionUsage { + usage: fabro_api_types::CompletionUsage { input_tokens: result.usage.input_tokens, output_tokens: result.usage.output_tokens, }, @@ -1510,12 +1516,12 @@ async fn create_completion( } } else { match client.complete(&request).await { - Ok(response) => Json(fabro_types::CompletionResponse { + Ok(response) => Json(fabro_api_types::CompletionResponse { id: response.id, model: response.model, message: convert_llm_message(&response.message), stop_reason: finish_reason_to_api_stop_reason(&response.finish_reason), - usage: fabro_types::CompletionUsage { + usage: fabro_api_types::CompletionUsage { input_tokens: response.usage.input_tokens, output_tokens: response.usage.output_tokens, }, diff --git a/lib/crates/fabro-api/src/sessions.rs b/lib/crates/fabro-api/src/sessions.rs index 9bec2cad3..cf97c388b 100644 --- a/lib/crates/fabro-api/src/sessions.rs +++ b/lib/crates/fabro-api/src/sessions.rs @@ -25,7 +25,7 @@ pub struct SessionState { pub model_id: String, pub model_provider: Option, pub system_prompt: Option, - pub turns: Vec, + pub turns: Vec, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub event_tx: broadcast::Sender, @@ -72,17 +72,17 @@ fn resolve_model(model_arg: Option) -> (String, Option) { } } -fn turns_to_messages(turns: &[fabro_types::SessionTurn]) -> Vec { +fn turns_to_messages(turns: &[fabro_api_types::SessionTurn]) -> Vec { turns .iter() .filter_map(|turn| match turn { - fabro_types::SessionTurn::UserTurn(t) => { + fabro_api_types::SessionTurn::UserTurn(t) => { Some(fabro_llm::types::Message::user(&t.content)) } - fabro_types::SessionTurn::AssistantTurn(t) => { + fabro_api_types::SessionTurn::AssistantTurn(t) => { Some(fabro_llm::types::Message::assistant(&t.content)) } - fabro_types::SessionTurn::ToolTurn(_) => None, + fabro_api_types::SessionTurn::ToolTurn(_) => None, }) .collect() } @@ -120,13 +120,15 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, { let mut store = store.write().expect("session store lock poisoned"); if let Some(session) = store.get_mut(&session_id) { - session.turns.push(fabro_types::SessionTurn::AssistantTurn( - fabro_types::AssistantTurn { - kind: fabro_types::AssistantTurnKind::Assistant, - content, - created_at: now, - }, - )); + session + .turns + .push(fabro_api_types::SessionTurn::AssistantTurn( + fabro_api_types::AssistantTurn { + kind: fabro_api_types::AssistantTurnKind::Assistant, + content, + created_at: now, + }, + )); session.updated_at = now; } } @@ -187,13 +189,15 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, { let mut store = store.write().expect("session store lock poisoned"); if let Some(session) = store.get_mut(&session_id) { - session.turns.push(fabro_types::SessionTurn::AssistantTurn( - fabro_types::AssistantTurn { - kind: fabro_types::AssistantTurnKind::Assistant, - content: full_text, - created_at: now, - }, - )); + session + .turns + .push(fabro_api_types::SessionTurn::AssistantTurn( + fabro_api_types::AssistantTurn { + kind: fabro_api_types::AssistantTurnKind::Assistant, + content: full_text, + created_at: now, + }, + )); session.updated_at = now; } } @@ -204,7 +208,7 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, pub async fn create_session( _auth: AuthenticatedService, State(state): State>, - Json(req): Json, + Json(req): Json, ) -> Response { let (model_id, model_provider) = resolve_model(req.model); let now = chrono::Utc::now(); @@ -214,8 +218,8 @@ pub async fn create_session( let (event_tx, _) = broadcast::channel(256); let generation_seq = Arc::new(AtomicU64::new(1)); - let user_turn = fabro_types::SessionTurn::UserTurn(fabro_types::UserTurn { - kind: fabro_types::UserTurnKind::User, + let user_turn = fabro_api_types::SessionTurn::UserTurn(fabro_api_types::UserTurn { + kind: fabro_api_types::UserTurnKind::User, content: req.content, created_at: now, }); @@ -242,10 +246,10 @@ pub async fn create_session( ( StatusCode::CREATED, - Json(fabro_types::CreateSessionResponse { + Json(fabro_api_types::CreateSessionResponse { id: session_id, title, - model: fabro_types::ModelReference { id: model_id }, + model: fabro_api_types::ModelReference { id: model_id }, created_at: now, updated_at: now, }), @@ -262,10 +266,10 @@ pub async fn retrieve_session( match store.get(&id) { Some(session) => ( StatusCode::OK, - Json(fabro_types::SessionDetail { + Json(fabro_api_types::SessionDetail { id: session.id, title: session.title.clone(), - model: fabro_types::ModelReference { + model: fabro_api_types::ModelReference { id: session.model_id.clone(), }, created_at: session.created_at, @@ -282,20 +286,20 @@ pub async fn send_message( _auth: AuthenticatedService, State(state): State>, Path(id): Path, - Json(req): Json, + Json(req): Json, ) -> Response { let seq = { let mut store = state.sessions.write().expect("session store lock poisoned"); match store.get_mut(&id) { Some(session) => { let now = chrono::Utc::now(); - session - .turns - .push(fabro_types::SessionTurn::UserTurn(fabro_types::UserTurn { - kind: fabro_types::UserTurnKind::User, + session.turns.push(fabro_api_types::SessionTurn::UserTurn( + fabro_api_types::UserTurn { + kind: fabro_api_types::UserTurnKind::User, content: req.content, created_at: now, - })); + }, + )); session.updated_at = now; session.generation_seq.fetch_add(1, Ordering::Relaxed) + 1 } @@ -307,7 +311,7 @@ pub async fn send_message( ( StatusCode::ACCEPTED, - Json(fabro_types::SendMessageResponse { accepted: true }), + Json(fabro_api_types::SendMessageResponse { accepted: true }), ) .into_response() } @@ -373,16 +377,16 @@ pub async fn list_sessions( let limit = pagination.limit.clamp(1, 100) as usize; let offset = pagination.offset as usize; - let mut items: Vec = store + let mut items: Vec = store .values() .map(|session| { let last_message_preview = session .turns .last() .map(|t| match t { - fabro_types::SessionTurn::UserTurn(u) => u.content.clone(), - fabro_types::SessionTurn::AssistantTurn(a) => a.content.clone(), - fabro_types::SessionTurn::ToolTurn(_) => String::new(), + fabro_api_types::SessionTurn::UserTurn(u) => u.content.clone(), + fabro_api_types::SessionTurn::AssistantTurn(a) => a.content.clone(), + fabro_api_types::SessionTurn::ToolTurn(_) => String::new(), }) .unwrap_or_default(); let preview = if last_message_preview.len() > 100 { @@ -390,10 +394,10 @@ pub async fn list_sessions( } else { last_message_preview }; - fabro_types::SessionListItem { + fabro_api_types::SessionListItem { id: session.id, title: session.title.clone(), - model: fabro_types::ModelReference { + model: fabro_api_types::ModelReference { id: session.model_id.clone(), }, last_message_preview: preview, @@ -412,9 +416,9 @@ pub async fn list_sessions( ( StatusCode::OK, - Json(fabro_types::PaginatedSessionList { + Json(fabro_api_types::PaginatedSessionList { data, - meta: fabro_types::PaginationMeta { has_more }, + meta: fabro_api_types::PaginationMeta { has_more }, }), ) .into_response()