mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-11 22:53:00 +00:00
Rename fabro-api-types to fabro-api with progenitor client generation
Replace typify-only type generation with progenitor, which generates both Rust types (in a `types` module) and a reqwest-based HTTP client from the OpenAPI spec. Also upgrades reqwest 0.12→0.13 and rmcp 0.15→1.3 to align dependency versions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ba75e4743
commit
a02148aacd
16 changed files with 617 additions and 267 deletions
|
|
@ -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-api-types` — build.rs regenerates Rust types via typify
|
||||
2. `cargo build -p fabro-api` — build.rs regenerates Rust types and client via progenitor
|
||||
3. Write/update handler in `lib/crates/fabro-server/src/server.rs`, add route to `build_router()`
|
||||
4. `cargo nextest run -p fabro-server` — conformance test catches spec/router drift
|
||||
5. `cd lib/packages/fabro-api-client && bun run generate` — regenerates TypeScript Axios client
|
||||
|
|
@ -55,7 +55,7 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as
|
|||
- **fabro-agent** — AI coding agent with tool use (Bash, Read, Write, Edit, Glob, Grep, WebFetch). `Sandbox` trait abstracts execution environments
|
||||
- **fabro-server** — Axum HTTP server. Routes for runs, sessions, models, completions, usage. SSE event streaming. Demo mode via header
|
||||
- **fabro-llm** — Unified LLM client with providers: Anthropic, OpenAI, Gemini, OpenAI-compatible, plus retry/middleware/streaming
|
||||
- **fabro-api-types** — Auto-generated Rust types from OpenAPI spec (build.rs + typify)
|
||||
- **fabro-api** — Auto-generated Rust types and reqwest HTTP client from OpenAPI spec (build.rs + progenitor)
|
||||
- **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
|
||||
|
|
@ -72,7 +72,7 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as
|
|||
### Key design patterns
|
||||
- **Sandbox trait** — Uniform interface for local, Docker, and Daytona execution environments
|
||||
- **Graphviz graph workflows** — Stages and transitions defined as Graphviz graph attributes
|
||||
- **OpenAPI-first** — `fabro-api.yaml` drives both Rust type generation (typify) and TypeScript client generation (openapi-generator)
|
||||
- **OpenAPI-first** — `fabro-api.yaml` drives Rust type + client generation (progenitor) and TypeScript client generation (openapi-generator)
|
||||
- **Checkpoint/resume** — Workflows can be paused, checkpointed, and resumed
|
||||
|
||||
## Strategy docs
|
||||
|
|
|
|||
386
Cargo.lock
generated
386
Cargo.lock
generated
|
|
@ -650,6 +650,12 @@ dependencies = [
|
|||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
|
|
@ -766,6 +772,16 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
|
|
@ -1156,7 +1172,7 @@ name = "daytona-api-client"
|
|||
version = "0.1.0"
|
||||
source = "git+https://github.com/brynary/daytona-sdk-rust?rev=06033ca#06033caaf5d9e12918ae68396048c5c0a98822c6"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"reqwest-middleware",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -1173,7 +1189,7 @@ dependencies = [
|
|||
"daytona-api-client",
|
||||
"daytona-toolbox-client",
|
||||
"futures-util",
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"reqwest-middleware",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -1189,7 +1205,7 @@ name = "daytona-toolbox-client"
|
|||
version = "0.1.0"
|
||||
source = "git+https://github.com/brynary/daytona-sdk-rust?rev=06033ca#06033caaf5d9e12918ae68396048c5c0a98822c6"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"reqwest-middleware",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -1528,7 +1544,7 @@ dependencies = [
|
|||
"jsonschema",
|
||||
"libc",
|
||||
"paste",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shell-escape",
|
||||
|
|
@ -1541,17 +1557,19 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "fabro-api-types"
|
||||
name = "fabro-api"
|
||||
version = "0.176.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"openapiv3",
|
||||
"prettyplease",
|
||||
"schemars 0.8.22",
|
||||
"progenitor",
|
||||
"progenitor-client",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"syn 2.0.117",
|
||||
"typify",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
@ -1624,7 +1642,7 @@ dependencies = [
|
|||
"predicates",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"scopeguard",
|
||||
|
|
@ -1694,7 +1712,7 @@ version = "0.176.2"
|
|||
dependencies = [
|
||||
"fabro-util",
|
||||
"insta",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
|
@ -1713,7 +1731,7 @@ dependencies = [
|
|||
"fabro-macros",
|
||||
"fabro-test",
|
||||
"jsonwebtoken",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
|
@ -1745,7 +1763,7 @@ dependencies = [
|
|||
"fabro-util",
|
||||
"httpmock",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
|
@ -1789,7 +1807,7 @@ dependencies = [
|
|||
"indicatif",
|
||||
"insta",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
|
|
@ -1817,7 +1835,7 @@ dependencies = [
|
|||
"fabro-config",
|
||||
"fabro-types",
|
||||
"futures",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"rmcp",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -1844,7 +1862,7 @@ dependencies = [
|
|||
"httpmock",
|
||||
"open",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
@ -1924,7 +1942,7 @@ dependencies = [
|
|||
"cookie",
|
||||
"dirs",
|
||||
"fabro-agent",
|
||||
"fabro-api-types",
|
||||
"fabro-api",
|
||||
"fabro-config",
|
||||
"fabro-db",
|
||||
"fabro-github",
|
||||
|
|
@ -1950,7 +1968,7 @@ dependencies = [
|
|||
"object_store",
|
||||
"openapiv3",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"rust-embed",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
|
|
@ -1980,7 +1998,7 @@ dependencies = [
|
|||
"fabro-interview",
|
||||
"fabro-workflow",
|
||||
"futures-util",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2027,7 +2045,7 @@ dependencies = [
|
|||
"mac_address",
|
||||
"md5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"sentry",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2044,7 +2062,7 @@ dependencies = [
|
|||
"axum",
|
||||
"insta",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
|
|
@ -2059,7 +2077,7 @@ dependencies = [
|
|||
"async-trait",
|
||||
"fabro-github",
|
||||
"httpmock",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -2148,7 +2166,7 @@ dependencies = [
|
|||
"predicates",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -3285,6 +3303,50 @@ version = "1.0.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
|
||||
dependencies = [
|
||||
"jni-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||
dependencies = [
|
||||
"jni-sys-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
|
|
@ -4127,7 +4189,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"quick-xml",
|
||||
"rand 0.9.2",
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -4669,6 +4731,72 @@ dependencies = [
|
|||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d36315275b213c64c68dff684477ea7118a0f630832f737b550796a368f9962c"
|
||||
dependencies = [
|
||||
"progenitor-client",
|
||||
"progenitor-impl",
|
||||
"progenitor-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3999c302f5f2a42b7ca1cc39ad9e612c74cf2910ef6e58f869e45f3068b9659f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"percent-encoding",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de362a0477182f45accdbad4d43cd89a95a1db0a518a7c1ddf3e525e6896f0f0"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"indexmap 2.13.0",
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.117",
|
||||
"thiserror 2.0.18",
|
||||
"typify",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c98aeaaab266bf848a602c78e039e7d62c80ba36303ae4092ec65f17e7fd0eaa"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
"progenitor-impl",
|
||||
"quote",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_tokenstream",
|
||||
"serde_yaml",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.4"
|
||||
|
|
@ -4705,6 +4833,7 @@ version = "0.11.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
|
|
@ -4974,11 +5103,53 @@ dependencies = [
|
|||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"wasm-streams 0.4.2",
|
||||
"web-sys",
|
||||
"webpki-roots 1.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams 0.5.0",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-middleware"
|
||||
version = "0.4.2"
|
||||
|
|
@ -4988,7 +5159,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-trait",
|
||||
"http",
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"tower-service",
|
||||
|
|
@ -5010,9 +5181,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rmcp"
|
||||
version = "0.15.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bef41ebc9ebed2c1b1d90203e9d1756091e8a00bbc3107676151f39868ca0ee"
|
||||
checksum = "2231b2c085b371c01bc90c0e6c1cab8834711b6394533375bdbf870b0166d419"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
|
|
@ -5021,7 +5192,7 @@ dependencies = [
|
|||
"pastey",
|
||||
"pin-project-lite",
|
||||
"process-wrap",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"rmcp-macros",
|
||||
"schemars 1.2.1",
|
||||
"serde",
|
||||
|
|
@ -5036,9 +5207,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rmcp-macros"
|
||||
version = "0.15.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e88ad84b8b6237a934534a62b379a5be6388915663c0cc598ceb9b3292bbbfe"
|
||||
checksum = "36ea0e100fadf81be85d7ff70f86cd805c7572601d4ab2946207f36540854b43"
|
||||
dependencies = [
|
||||
"darling 0.23.0",
|
||||
"proc-macro2",
|
||||
|
|
@ -5201,6 +5372,33 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.9"
|
||||
|
|
@ -5249,10 +5447,12 @@ version = "0.8.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"schemars_derive 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5339,6 +5539,10 @@ name = "semver"
|
|||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry"
|
||||
|
|
@ -5347,7 +5551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "016958f51b96861dead7c1e02290f138411d05e94fad175c8636a835dee6e51e"
|
||||
dependencies = [
|
||||
"httpdate",
|
||||
"reqwest",
|
||||
"reqwest 0.12.28",
|
||||
"rustls",
|
||||
"sentry-backtrace",
|
||||
"sentry-contexts",
|
||||
|
|
@ -5530,6 +5734,18 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_tokenstream"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c49585c52c01f13c5c2ebb333f14f6885d76daa768d8a037d28017ec538c69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
|
@ -6681,7 +6897,7 @@ dependencies = [
|
|||
"base64",
|
||||
"chrono",
|
||||
"jsonwebtoken",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
|
@ -6700,7 +6916,7 @@ dependencies = [
|
|||
"axum",
|
||||
"futures-util",
|
||||
"http",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
|
@ -6731,6 +6947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b715573a376585888b742ead9be5f4826105e622169180662e2c81bed4a149c3"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6753,6 +6970,23 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd04bb1207cd4e250941cc1641f4c4815f7eaa2145f45c09dd49cb0a3691710a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"schemars 0.8.22",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.117",
|
||||
"typify-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ulid"
|
||||
version = "1.2.1"
|
||||
|
|
@ -7123,6 +7357,19 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
|
|
@ -7167,6 +7414,15 @@ dependencies = [
|
|||
"string_cache_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
|
|
@ -7418,6 +7674,15 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
|
@ -7463,6 +7728,21 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7529,6 +7809,12 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7547,6 +7833,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7565,6 +7857,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7595,6 +7893,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7613,6 +7917,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7631,6 +7941,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
|
|
@ -7649,6 +7965,12 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ thiserror = "2"
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] }
|
||||
reqwest = { version = "0.13", default-features = false, features = ["json", "stream", "rustls", "query", "form"] }
|
||||
ulid = "1"
|
||||
uuid = { version = "1", features = ["v4", "v7"] }
|
||||
rand = "0.8"
|
||||
|
|
@ -41,7 +41,7 @@ git2 = { version = "0.20", default-features = false }
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
|
||||
tracing-appender = "0.2"
|
||||
rmcp = { version = "0.15.0", default-features = false }
|
||||
rmcp = { version = "1.3", default-features = false }
|
||||
walkdir = "2"
|
||||
regex = "1"
|
||||
semver = "1"
|
||||
|
|
|
|||
|
|
@ -34,31 +34,34 @@ console.log(data);
|
|||
|
||||
The generated client includes a typed API class for each endpoint group: `RunsApi`, `WorkflowsApi`, `SessionsApi`, `VerificationsApi`, `InsightsApi`, and others.
|
||||
|
||||
## Rust (Types Only)
|
||||
## Rust (Types + 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.
|
||||
The `fabro-api` crate generates Rust structs, enums, and a `reqwest`-based HTTP client from the full OpenAPI spec at compile time using [progenitor](https://github.com/oxidecomputer/progenitor). This provides type-safe representations of all API models and a builder-style client for every endpoint.
|
||||
|
||||
### 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:
|
||||
A `build.rs` script reads `docs/api-reference/fabro-api.yaml`, patches it from OpenAPI 3.1 to 3.0 for progenitor compatibility, and generates both types and a client. The generated code is written to `OUT_DIR` and included via:
|
||||
|
||||
```rust
|
||||
// lib/crates/fabro-api-types/src/lib.rs
|
||||
include!(concat!(env!("OUT_DIR"), "/openapi_types.rs"));
|
||||
// lib/crates/fabro-api/src/lib.rs
|
||||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||
```
|
||||
|
||||
### Regenerating
|
||||
|
||||
The types are regenerated automatically on every `cargo build` when the OpenAPI spec changes:
|
||||
The types and client are regenerated automatically on every `cargo build` when the OpenAPI spec changes:
|
||||
|
||||
```bash
|
||||
cargo build -p fabro-api-types
|
||||
cargo build -p fabro-api
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```rust
|
||||
use fabro_api_types::RunListItem;
|
||||
use fabro_api::types::RunListItem;
|
||||
use fabro_api::Client;
|
||||
|
||||
let client = Client::new("http://localhost:3000");
|
||||
```
|
||||
|
||||
All generated types derive `serde::Deserialize` and `serde::Serialize`, so they work directly with any Rust HTTP client for request and response parsing.
|
||||
All generated types derive `serde::Deserialize` and `serde::Serialize`. The client uses builder-style methods for each endpoint.
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
use std::{env, fs, path::Path};
|
||||
|
||||
use schemars::schema::Schema;
|
||||
use typify::{TypeSpace, TypeSpaceSettings};
|
||||
|
||||
fn main() {
|
||||
let spec_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("docs/api-reference/fabro-api.yaml");
|
||||
|
||||
println!("cargo::rerun-if-changed={}", spec_path.display());
|
||||
|
||||
let spec_text = fs::read_to_string(&spec_path)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", spec_path.display()));
|
||||
let spec: serde_json::Value =
|
||||
serde_yaml::from_str(&spec_text).unwrap_or_else(|e| panic!("failed to parse YAML: {e}"));
|
||||
|
||||
let schemas = spec["components"]["schemas"]
|
||||
.as_object()
|
||||
.expect("no components/schemas in spec");
|
||||
|
||||
let named_schemas: Vec<(String, Schema)> = schemas
|
||||
.iter()
|
||||
.map(|(name, value)| {
|
||||
let schema: Schema = serde_json::from_value(value.clone())
|
||||
.unwrap_or_else(|e| panic!("failed to parse schema {name}: {e}"));
|
||||
(name.clone(), schema)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let settings = TypeSpaceSettings::default();
|
||||
let mut type_space = TypeSpace::new(&settings);
|
||||
type_space
|
||||
.add_ref_types(named_schemas)
|
||||
.expect("failed to add schemas to type space");
|
||||
|
||||
let token_stream = type_space.to_stream();
|
||||
let syntax_tree =
|
||||
syn::parse2::<syn::File>(token_stream).expect("failed to parse generated tokens");
|
||||
let formatted = prettyplease::unparse(&syntax_tree);
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir).join("openapi_types.rs");
|
||||
fs::write(&out_path, formatted).expect("failed to write generated types");
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#[allow(clippy::absolute_paths, clippy::derivable_impls)]
|
||||
mod generated {
|
||||
include!(concat!(env!("OUT_DIR"), "/openapi_types.rs"));
|
||||
}
|
||||
pub use generated::*;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
[package]
|
||||
name = "fabro-api-types"
|
||||
name = "fabro-api"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
publish = false
|
||||
license.workspace = true
|
||||
description = "Generated Rust types from the Fabro API OpenAPI spec"
|
||||
description = "Generated Rust types and HTTP client from the Fabro API OpenAPI spec"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
|
@ -15,13 +15,15 @@ wildcard_imports = "warn"
|
|||
|
||||
[dependencies]
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
progenitor-client = "0.13"
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
uuid = { workspace = true, features = ["serde"] }
|
||||
|
||||
[build-dependencies]
|
||||
typify = { version = "0.6", default-features = false }
|
||||
schemars = "0.8"
|
||||
openapiv3 = "2"
|
||||
progenitor = "0.13"
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
prettyplease = "0.2"
|
||||
105
lib/crates/fabro-api/build.rs
Normal file
105
lib/crates/fabro-api/build.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
use std::{env, fs, path::Path};
|
||||
|
||||
use progenitor::{GenerationSettings, Generator, InterfaceStyle};
|
||||
|
||||
/// Recursively convert OpenAPI 3.1 `type: "null"` patterns to 3.0 `nullable: true`.
|
||||
///
|
||||
/// Handles two patterns:
|
||||
/// - `oneOf: [{...}, {type: "null"}]` → the non-null schema with `nullable: true`
|
||||
/// - `type: [T1, ..., "null"]` → the remaining types with `nullable: true`
|
||||
fn patch_nullable(value: &mut serde_json::Value) {
|
||||
match value {
|
||||
serde_json::Value::Object(map) => {
|
||||
// Pattern: oneOf with a {type: "null"} variant
|
||||
if let Some(one_of) = map.get_mut("oneOf") {
|
||||
if let Some(variants) = one_of.as_array_mut() {
|
||||
let null_idx = variants.iter().position(|v| {
|
||||
v.get("type").and_then(serde_json::Value::as_str) == Some("null")
|
||||
});
|
||||
if let Some(idx) = null_idx {
|
||||
variants.remove(idx);
|
||||
if variants.len() == 1 {
|
||||
// Collapse single-variant oneOf into the schema itself
|
||||
let mut inner = variants.remove(0);
|
||||
inner
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("nullable".to_string(), serde_json::Value::Bool(true));
|
||||
patch_nullable(&mut inner);
|
||||
*value = inner;
|
||||
return;
|
||||
}
|
||||
map.insert("nullable".to_string(), serde_json::Value::Bool(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern: type array containing "null"
|
||||
let needs_nullable_from_type = map
|
||||
.get("type")
|
||||
.and_then(|v| v.as_array())
|
||||
.is_some_and(|arr| arr.iter().any(|v| v.as_str() == Some("null")));
|
||||
if needs_nullable_from_type {
|
||||
if let Some(type_val) = map.get_mut("type") {
|
||||
if let Some(arr) = type_val.as_array_mut() {
|
||||
arr.retain(|v| v.as_str() != Some("null"));
|
||||
if arr.len() == 1 {
|
||||
*type_val = arr.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
map.insert("nullable".to_string(), serde_json::Value::Bool(true));
|
||||
}
|
||||
|
||||
for v in map.values_mut() {
|
||||
patch_nullable(v);
|
||||
}
|
||||
}
|
||||
serde_json::Value::Array(arr) => {
|
||||
for v in arr {
|
||||
patch_nullable(v);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let spec_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("docs/api-reference/fabro-api.yaml");
|
||||
|
||||
println!("cargo::rerun-if-changed={}", spec_path.display());
|
||||
|
||||
let spec_text = fs::read_to_string(&spec_path)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", spec_path.display()));
|
||||
let mut spec_value: serde_json::Value =
|
||||
serde_yaml::from_str(&spec_text).unwrap_or_else(|e| panic!("failed to parse YAML: {e}"));
|
||||
|
||||
// Progenitor only supports OpenAPI 3.0.x; our spec uses 3.1.0 but doesn't
|
||||
// rely on any 3.1-only features that affect codegen.
|
||||
spec_value["openapi"] = serde_json::Value::String("3.0.3".to_string());
|
||||
patch_nullable(&mut spec_value);
|
||||
|
||||
let spec: openapiv3::OpenAPI =
|
||||
serde_json::from_value(spec_value).expect("failed to deserialize OpenAPI spec");
|
||||
|
||||
let mut settings = GenerationSettings::default();
|
||||
settings.with_interface(InterfaceStyle::Builder);
|
||||
|
||||
let mut generator = Generator::new(&settings);
|
||||
let tokens = generator
|
||||
.generate_tokens(&spec)
|
||||
.expect("failed to generate tokens from OpenAPI spec");
|
||||
let syntax_tree = syn::parse2::<syn::File>(tokens).expect("failed to parse generated tokens");
|
||||
let formatted = prettyplease::unparse(&syntax_tree);
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir).join("codegen.rs");
|
||||
fs::write(&out_path, formatted).expect("failed to write generated code");
|
||||
}
|
||||
13
lib/crates/fabro-api/src/lib.rs
Normal file
13
lib/crates/fabro-api/src/lib.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#[allow(
|
||||
clippy::absolute_paths,
|
||||
clippy::all,
|
||||
clippy::derivable_impls,
|
||||
clippy::needless_lifetimes,
|
||||
unreachable_pub,
|
||||
unused_imports
|
||||
)]
|
||||
mod generated {
|
||||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||
}
|
||||
pub use generated::Client;
|
||||
pub use generated::types;
|
||||
|
|
@ -212,12 +212,10 @@ impl McpClient {
|
|||
}
|
||||
};
|
||||
|
||||
let params = CallToolRequestParams {
|
||||
meta: None,
|
||||
name: name.to_string().into(),
|
||||
arguments: args,
|
||||
task: None,
|
||||
};
|
||||
let mut params = CallToolRequestParams::new(name.to_string());
|
||||
if let Some(arguments) = args {
|
||||
params = params.with_arguments(arguments);
|
||||
}
|
||||
|
||||
debug!(server = %self.server_name, tool = %name, "Calling MCP tool");
|
||||
|
||||
|
|
|
|||
|
|
@ -13,19 +13,11 @@ pub(crate) struct LoggingClientHandler;
|
|||
|
||||
impl ClientHandler for LoggingClientHandler {
|
||||
fn get_info(&self) -> ClientInfo {
|
||||
ClientInfo {
|
||||
protocol_version: ProtocolVersion::V_2025_03_26,
|
||||
capabilities: ClientCapabilities::default(),
|
||||
client_info: Implementation {
|
||||
name: "fabro-mcp".into(),
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
title: None,
|
||||
description: None,
|
||||
icons: None,
|
||||
website_url: None,
|
||||
},
|
||||
meta: None,
|
||||
}
|
||||
ClientInfo::new(
|
||||
ClientCapabilities::default(),
|
||||
Implementation::new("fabro-mcp", env!("CARGO_PKG_VERSION")),
|
||||
)
|
||||
.with_protocol_version(ProtocolVersion::V_2025_03_26)
|
||||
}
|
||||
|
||||
async fn on_cancelled(
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ impl Default for McpConnectionManager {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rmcp::model::{Content, RawTextContent};
|
||||
use rmcp::model::Content;
|
||||
|
||||
#[test]
|
||||
fn qualified_tool_name_basic() {
|
||||
|
|
@ -242,21 +242,14 @@ mod tests {
|
|||
}
|
||||
|
||||
fn make_text_content(text: &str) -> Content {
|
||||
Content {
|
||||
raw: RawContent::Text(RawTextContent {
|
||||
text: text.to_string(),
|
||||
meta: None,
|
||||
}),
|
||||
annotations: None,
|
||||
}
|
||||
Content::text(text)
|
||||
}
|
||||
|
||||
fn make_call_result(content: Vec<Content>, is_error: Option<bool>) -> CallToolResult {
|
||||
CallToolResult {
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error,
|
||||
meta: None,
|
||||
if is_error == Some(true) {
|
||||
CallToolResult::error(content)
|
||||
} else {
|
||||
CallToolResult::success(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -292,20 +285,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn call_result_to_string_image_placeholder() {
|
||||
use rmcp::model::RawImageContent;
|
||||
let result = CallToolResult {
|
||||
content: vec![Content {
|
||||
raw: RawContent::Image(RawImageContent {
|
||||
data: "base64data".to_string(),
|
||||
mime_type: "image/png".to_string(),
|
||||
meta: None,
|
||||
}),
|
||||
annotations: None,
|
||||
}],
|
||||
structured_content: None,
|
||||
is_error: None,
|
||||
meta: None,
|
||||
};
|
||||
let result = CallToolResult::success(vec![Content::image("base64data", "image/png")]);
|
||||
assert_eq!(
|
||||
call_result_to_string(&result),
|
||||
Ok("[image content]".to_string())
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ fabro-retro = { path = "../fabro-retro" }
|
|||
fabro-types = { path = "../fabro-types" }
|
||||
fabro-util = { path = "../fabro-util" }
|
||||
fabro-db = { path = "../fabro-db" }
|
||||
fabro-api-types = { path = "../fabro-api-types" }
|
||||
fabro-api = { path = "../fabro-api" }
|
||||
fabro-store = { path = "../fabro-store" }
|
||||
chrono.workspace = true
|
||||
futures-util.workspace = true
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use axum::Json;
|
|||
use axum::extract::{Path, Query, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use fabro_api::types::{RunStatus, RunStatusResponse, SessionTurn, SmoothnessRating};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::error::ApiError;
|
||||
|
|
@ -21,7 +22,7 @@ pub(crate) struct RetroListParams {
|
|||
#[serde(rename = "page[offset]", default)]
|
||||
offset: u32,
|
||||
workflow: Option<String>,
|
||||
smoothness: Option<fabro_api_types::SmoothnessRating>,
|
||||
smoothness: Option<SmoothnessRating>,
|
||||
}
|
||||
|
||||
fn paginated_response<T: serde::Serialize>(
|
||||
|
|
@ -141,9 +142,9 @@ pub(crate) async fn get_run_status(
|
|||
match runs::list_items().into_iter().find(|r| r.id == id) {
|
||||
Some(item) => (
|
||||
StatusCode::OK,
|
||||
Json(fabro_api_types::RunStatusResponse {
|
||||
Json(RunStatusResponse {
|
||||
id: id.clone(),
|
||||
status: fabro_api_types::RunStatus::Running,
|
||||
status: RunStatus::Running,
|
||||
error: None,
|
||||
queue_position: None,
|
||||
created_at: item.created_at,
|
||||
|
|
@ -470,13 +471,9 @@ pub(crate) async fn session_events_stub(
|
|||
|
||||
for turn in &session.turns {
|
||||
let (event_type, data) = match turn {
|
||||
fabro_api_types::SessionTurn::UserTurn(_) => continue,
|
||||
fabro_api_types::SessionTurn::AssistantTurn(t) => {
|
||||
("assistant_turn", serde_json::to_string(t).unwrap())
|
||||
}
|
||||
fabro_api_types::SessionTurn::ToolTurn(t) => {
|
||||
("tool_turn", serde_json::to_string(t).unwrap())
|
||||
}
|
||||
SessionTurn::UserTurn(_) => continue,
|
||||
SessionTurn::AssistantTurn(t) => ("assistant_turn", serde_json::to_string(t).unwrap()),
|
||||
SessionTurn::ToolTurn(t) => ("tool_turn", serde_json::to_string(t).unwrap()),
|
||||
};
|
||||
|
||||
if last_event_id.is_none() || seq > last_event_id.unwrap() {
|
||||
|
|
@ -620,7 +617,7 @@ fn ts(s: &str) -> DateTime<Utc> {
|
|||
|
||||
mod runs {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
pub(super) fn list_items() -> Vec<RunListItem> {
|
||||
vec![
|
||||
|
|
@ -1241,7 +1238,7 @@ mod runs {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn verifications() -> Vec<fabro_api_types::RunVerification> {
|
||||
pub(super) fn verifications() -> Vec<RunVerification> {
|
||||
super::verifications::run_verifications()
|
||||
}
|
||||
|
||||
|
|
@ -1340,7 +1337,7 @@ mod runs {
|
|||
}
|
||||
|
||||
mod usage {
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
pub(super) fn aggregate() -> AggregateUsage {
|
||||
AggregateUsage {
|
||||
|
|
@ -1392,7 +1389,7 @@ mod usage {
|
|||
|
||||
mod workflows {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
pub(super) fn list_items() -> Vec<WorkflowListItem> {
|
||||
vec![
|
||||
|
|
@ -1764,7 +1761,7 @@ mod workflows {
|
|||
|
||||
mod verifications {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
// ── Category definitions (name, question, controls) ─────────────────
|
||||
|
||||
|
|
@ -2823,7 +2820,7 @@ mod verifications {
|
|||
|
||||
mod signoffs {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
struct SignoffDef {
|
||||
id: &'static str,
|
||||
|
|
@ -2938,7 +2935,7 @@ mod signoffs {
|
|||
|
||||
mod retros {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn stage(
|
||||
|
|
@ -3272,7 +3269,7 @@ mod retros {
|
|||
|
||||
mod sessions {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn uid(n: u128) -> Uuid {
|
||||
|
|
@ -3414,7 +3411,7 @@ mod sessions {
|
|||
|
||||
mod insights {
|
||||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
use fabro_api::types::*;
|
||||
|
||||
pub(super) fn saved_queries() -> Vec<SavedQuery> {
|
||||
vec![
|
||||
|
|
|
|||
|
|
@ -52,10 +52,13 @@ use fabro_workflow::operations::{self, CreateRunInput, WorkflowInput};
|
|||
use fabro_workflow::pipeline::Persisted;
|
||||
use fabro_workflow::records::Checkpoint;
|
||||
|
||||
pub use fabro_api_types::{
|
||||
ApiQuestion, ApiQuestionOption, PaginatedRunList, PaginationMeta,
|
||||
QuestionType as ApiQuestionType, RunStatus, RunStatusResponse, StartRunRequest,
|
||||
SubmitAnswerRequest,
|
||||
use fabro_api::types::AggregateUsageTotals as ApiAggregateUsageTotals;
|
||||
pub use fabro_api::types::{
|
||||
AggregateUsage, ApiQuestion, ApiQuestionOption, CompletionContentPart, CompletionMessage,
|
||||
CompletionMessageRole, CompletionResponse, CompletionToolChoiceMode, CompletionUsage,
|
||||
CreateCompletionRequest, ModelReference, PaginatedRunList, PaginationMeta,
|
||||
QuestionType as ApiQuestionType, RunError, RunStatus, RunStatusResponse, StartRunRequest,
|
||||
SubmitAnswerRequest, TokenUsage, UsageByModel,
|
||||
};
|
||||
|
||||
pub fn default_page_limit() -> u32 {
|
||||
|
|
@ -377,21 +380,21 @@ async fn get_aggregate_usage(
|
|||
.aggregate_usage
|
||||
.lock()
|
||||
.expect("aggregate_usage lock poisoned");
|
||||
let by_model: Vec<fabro_api_types::UsageByModel> = agg
|
||||
let by_model: Vec<UsageByModel> = agg
|
||||
.by_model
|
||||
.iter()
|
||||
.map(|(model, totals)| fabro_api_types::UsageByModel {
|
||||
model: fabro_api_types::ModelReference { id: model.clone() },
|
||||
.map(|(model, totals)| UsageByModel {
|
||||
model: ModelReference { id: model.clone() },
|
||||
stages: totals.stages,
|
||||
usage: fabro_api_types::TokenUsage {
|
||||
usage: TokenUsage {
|
||||
input_tokens: totals.input_tokens,
|
||||
output_tokens: totals.output_tokens,
|
||||
cost: totals.cost,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
let response = fabro_api_types::AggregateUsage {
|
||||
totals: fabro_api_types::AggregateUsageTotals {
|
||||
let response = AggregateUsage {
|
||||
totals: ApiAggregateUsageTotals {
|
||||
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(),
|
||||
|
|
@ -489,12 +492,9 @@ async fn list_runs(
|
|||
.map(|(id, managed_run)| RunStatusResponse {
|
||||
id: id.to_string(),
|
||||
status: managed_run.status,
|
||||
error: managed_run
|
||||
.error
|
||||
.as_ref()
|
||||
.map(|msg| fabro_api_types::RunError {
|
||||
message: msg.clone(),
|
||||
}),
|
||||
error: managed_run.error.as_ref().map(|msg| RunError {
|
||||
message: msg.clone(),
|
||||
}),
|
||||
queue_position: queue_positions.get(id).copied(),
|
||||
created_at: managed_run.created_at,
|
||||
})
|
||||
|
|
@ -891,12 +891,9 @@ async fn get_run_status(
|
|||
Json(RunStatusResponse {
|
||||
id: id.to_string(),
|
||||
status: managed_run.status,
|
||||
error: managed_run
|
||||
.error
|
||||
.as_ref()
|
||||
.map(|msg| fabro_api_types::RunError {
|
||||
message: msg.clone(),
|
||||
}),
|
||||
error: managed_run.error.as_ref().map(|msg| RunError {
|
||||
message: msg.clone(),
|
||||
}),
|
||||
created_at: managed_run.created_at,
|
||||
queue_position,
|
||||
}),
|
||||
|
|
@ -1276,13 +1273,13 @@ fn finish_reason_to_api_stop_reason(reason: &FinishReason) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn convert_api_message(msg: &fabro_api_types::CompletionMessage) -> LlmMessage {
|
||||
fn convert_api_message(msg: &CompletionMessage) -> LlmMessage {
|
||||
let role = match msg.role {
|
||||
fabro_api_types::CompletionMessageRole::System => Role::System,
|
||||
fabro_api_types::CompletionMessageRole::User => Role::User,
|
||||
fabro_api_types::CompletionMessageRole::Assistant => Role::Assistant,
|
||||
fabro_api_types::CompletionMessageRole::Tool => Role::Tool,
|
||||
fabro_api_types::CompletionMessageRole::Developer => Role::Developer,
|
||||
CompletionMessageRole::System => Role::System,
|
||||
CompletionMessageRole::User => Role::User,
|
||||
CompletionMessageRole::Assistant => Role::Assistant,
|
||||
CompletionMessageRole::Tool => Role::Tool,
|
||||
CompletionMessageRole::Developer => Role::Developer,
|
||||
};
|
||||
let content: Vec<ContentPart> = msg
|
||||
.content
|
||||
|
|
@ -1300,15 +1297,15 @@ fn convert_api_message(msg: &fabro_api_types::CompletionMessage) -> LlmMessage {
|
|||
}
|
||||
}
|
||||
|
||||
fn convert_llm_message(msg: &LlmMessage) -> fabro_api_types::CompletionMessage {
|
||||
fn convert_llm_message(msg: &LlmMessage) -> CompletionMessage {
|
||||
let role = match msg.role {
|
||||
Role::System => fabro_api_types::CompletionMessageRole::System,
|
||||
Role::User => fabro_api_types::CompletionMessageRole::User,
|
||||
Role::Assistant => fabro_api_types::CompletionMessageRole::Assistant,
|
||||
Role::Tool => fabro_api_types::CompletionMessageRole::Tool,
|
||||
Role::Developer => fabro_api_types::CompletionMessageRole::Developer,
|
||||
Role::System => CompletionMessageRole::System,
|
||||
Role::User => CompletionMessageRole::User,
|
||||
Role::Assistant => CompletionMessageRole::Assistant,
|
||||
Role::Tool => CompletionMessageRole::Tool,
|
||||
Role::Developer => CompletionMessageRole::Developer,
|
||||
};
|
||||
let content: Vec<fabro_api_types::CompletionContentPart> = msg
|
||||
let content: Vec<CompletionContentPart> = msg
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|part| {
|
||||
|
|
@ -1316,7 +1313,7 @@ fn convert_llm_message(msg: &LlmMessage) -> fabro_api_types::CompletionMessage {
|
|||
serde_json::from_value(json).ok()
|
||||
})
|
||||
.collect();
|
||||
fabro_api_types::CompletionMessage {
|
||||
CompletionMessage {
|
||||
role,
|
||||
content,
|
||||
name: msg.name.clone(),
|
||||
|
|
@ -1327,7 +1324,7 @@ fn convert_llm_message(msg: &LlmMessage) -> fabro_api_types::CompletionMessage {
|
|||
async fn create_completion(
|
||||
_auth: AuthenticatedService,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<fabro_api_types::CreateCompletionRequest>,
|
||||
Json(req): Json<CreateCompletionRequest>,
|
||||
) -> Response {
|
||||
// Resolve model
|
||||
let model_id = req.model.unwrap_or_else(|| {
|
||||
|
|
@ -1373,12 +1370,10 @@ async fn create_completion(
|
|||
|
||||
// Convert tool_choice
|
||||
let tool_choice: Option<ToolChoice> = req.tool_choice.map(|tc| match tc.mode {
|
||||
fabro_api_types::CompletionToolChoiceMode::Auto => ToolChoice::Auto,
|
||||
fabro_api_types::CompletionToolChoiceMode::None => ToolChoice::None,
|
||||
fabro_api_types::CompletionToolChoiceMode::Required => ToolChoice::Required,
|
||||
fabro_api_types::CompletionToolChoiceMode::Named => {
|
||||
ToolChoice::named(tc.tool_name.unwrap_or_default())
|
||||
}
|
||||
CompletionToolChoiceMode::Auto => ToolChoice::Auto,
|
||||
CompletionToolChoiceMode::None => ToolChoice::None,
|
||||
CompletionToolChoiceMode::Required => ToolChoice::Required,
|
||||
CompletionToolChoiceMode::Named => ToolChoice::named(tc.tool_name.unwrap_or_default()),
|
||||
});
|
||||
|
||||
// Build the LLM request
|
||||
|
|
@ -1431,18 +1426,18 @@ async fn create_completion(
|
|||
)]);
|
||||
return Sse::new(sse_stream).into_response();
|
||||
}
|
||||
let empty_msg = fabro_api_types::CompletionMessage {
|
||||
role: fabro_api_types::CompletionMessageRole::Assistant,
|
||||
let empty_msg = CompletionMessage {
|
||||
role: CompletionMessageRole::Assistant,
|
||||
content: vec![],
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
};
|
||||
return Json(fabro_api_types::CompletionResponse {
|
||||
return Json(CompletionResponse {
|
||||
id: msg_id,
|
||||
model: model_id,
|
||||
message: empty_msg,
|
||||
stop_reason: "end_turn".to_string(),
|
||||
usage: fabro_api_types::CompletionUsage {
|
||||
usage: CompletionUsage {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
},
|
||||
|
|
@ -1528,12 +1523,12 @@ async fn create_completion(
|
|||
params = params.top_p(top_p);
|
||||
}
|
||||
match generate_object(params, schema).await {
|
||||
Ok(result) => Json(fabro_api_types::CompletionResponse {
|
||||
Ok(result) => Json(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_api_types::CompletionUsage {
|
||||
usage: CompletionUsage {
|
||||
input_tokens: result.usage.input_tokens,
|
||||
output_tokens: result.usage.output_tokens,
|
||||
},
|
||||
|
|
@ -1545,12 +1540,12 @@ async fn create_completion(
|
|||
}
|
||||
} else {
|
||||
match client.complete(&request).await {
|
||||
Ok(response) => Json(fabro_api_types::CompletionResponse {
|
||||
Ok(response) => Json(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_api_types::CompletionUsage {
|
||||
usage: CompletionUsage {
|
||||
input_tokens: response.usage.input_tokens,
|
||||
output_tokens: response.usage.output_tokens,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ use fabro_llm::types::{Message as LlmMessage, StreamEvent};
|
|||
use tokio::sync::broadcast;
|
||||
use tokio_stream::wrappers::BroadcastStream;
|
||||
|
||||
use fabro_api::types::{
|
||||
AssistantTurn, AssistantTurnKind, CreateSessionRequest, CreateSessionResponse, ModelReference,
|
||||
PaginatedSessionList, PaginationMeta, SendMessageRequest, SendMessageResponse, SessionDetail,
|
||||
SessionListItem, SessionTurn, UserTurn, UserTurnKind,
|
||||
};
|
||||
|
||||
use crate::error::ApiError;
|
||||
use crate::jwt_auth::AuthenticatedService;
|
||||
use crate::server::{AppState, PaginationParams};
|
||||
|
|
@ -30,7 +36,7 @@ pub struct SessionState {
|
|||
pub model_id: String,
|
||||
pub model_provider: Option<String>,
|
||||
pub system_prompt: Option<String>,
|
||||
pub turns: Vec<fabro_api_types::SessionTurn>,
|
||||
pub turns: Vec<SessionTurn>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub event_tx: broadcast::Sender<SessionEvent>,
|
||||
|
|
@ -77,15 +83,13 @@ fn resolve_model(model_arg: Option<String>) -> (String, Option<String>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn turns_to_messages(turns: &[fabro_api_types::SessionTurn]) -> Vec<LlmMessage> {
|
||||
fn turns_to_messages(turns: &[SessionTurn]) -> Vec<LlmMessage> {
|
||||
turns
|
||||
.iter()
|
||||
.filter_map(|turn| match turn {
|
||||
fabro_api_types::SessionTurn::UserTurn(t) => Some(LlmMessage::user(&t.content)),
|
||||
fabro_api_types::SessionTurn::AssistantTurn(t) => {
|
||||
Some(LlmMessage::assistant(&t.content))
|
||||
}
|
||||
fabro_api_types::SessionTurn::ToolTurn(_) => None,
|
||||
SessionTurn::UserTurn(t) => Some(LlmMessage::user(&t.content)),
|
||||
SessionTurn::AssistantTurn(t) => Some(LlmMessage::assistant(&t.content)),
|
||||
SessionTurn::ToolTurn(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -125,13 +129,11 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool,
|
|||
if let Some(session) = store.get_mut(&session_id) {
|
||||
session
|
||||
.turns
|
||||
.push(fabro_api_types::SessionTurn::AssistantTurn(
|
||||
fabro_api_types::AssistantTurn {
|
||||
kind: fabro_api_types::AssistantTurnKind::Assistant,
|
||||
content,
|
||||
created_at: now,
|
||||
},
|
||||
));
|
||||
.push(SessionTurn::AssistantTurn(AssistantTurn {
|
||||
kind: AssistantTurnKind::Assistant,
|
||||
content,
|
||||
created_at: now,
|
||||
}));
|
||||
session.updated_at = now;
|
||||
}
|
||||
}
|
||||
|
|
@ -193,13 +195,11 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool,
|
|||
if let Some(session) = store.get_mut(&session_id) {
|
||||
session
|
||||
.turns
|
||||
.push(fabro_api_types::SessionTurn::AssistantTurn(
|
||||
fabro_api_types::AssistantTurn {
|
||||
kind: fabro_api_types::AssistantTurnKind::Assistant,
|
||||
content: full_text,
|
||||
created_at: now,
|
||||
},
|
||||
));
|
||||
.push(SessionTurn::AssistantTurn(AssistantTurn {
|
||||
kind: AssistantTurnKind::Assistant,
|
||||
content: full_text,
|
||||
created_at: now,
|
||||
}));
|
||||
session.updated_at = now;
|
||||
}
|
||||
}
|
||||
|
|
@ -210,7 +210,7 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool,
|
|||
pub async fn create_session(
|
||||
_auth: AuthenticatedService,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<fabro_api_types::CreateSessionRequest>,
|
||||
Json(req): Json<CreateSessionRequest>,
|
||||
) -> Response {
|
||||
let (model_id, model_provider) = resolve_model(req.model);
|
||||
let now = chrono::Utc::now();
|
||||
|
|
@ -220,8 +220,8 @@ pub async fn create_session(
|
|||
let (event_tx, _) = broadcast::channel(256);
|
||||
let generation_seq = Arc::new(AtomicU64::new(1));
|
||||
|
||||
let user_turn = fabro_api_types::SessionTurn::UserTurn(fabro_api_types::UserTurn {
|
||||
kind: fabro_api_types::UserTurnKind::User,
|
||||
let user_turn = SessionTurn::UserTurn(UserTurn {
|
||||
kind: UserTurnKind::User,
|
||||
content: req.content,
|
||||
created_at: now,
|
||||
});
|
||||
|
|
@ -248,10 +248,10 @@ pub async fn create_session(
|
|||
|
||||
(
|
||||
StatusCode::CREATED,
|
||||
Json(fabro_api_types::CreateSessionResponse {
|
||||
Json(CreateSessionResponse {
|
||||
id: session_id,
|
||||
title,
|
||||
model: fabro_api_types::ModelReference { id: model_id },
|
||||
model: ModelReference { id: model_id },
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}),
|
||||
|
|
@ -268,10 +268,10 @@ pub async fn retrieve_session(
|
|||
match store.get(&id) {
|
||||
Some(session) => (
|
||||
StatusCode::OK,
|
||||
Json(fabro_api_types::SessionDetail {
|
||||
Json(SessionDetail {
|
||||
id: session.id,
|
||||
title: session.title.clone(),
|
||||
model: fabro_api_types::ModelReference {
|
||||
model: ModelReference {
|
||||
id: session.model_id.clone(),
|
||||
},
|
||||
created_at: session.created_at,
|
||||
|
|
@ -288,20 +288,18 @@ pub async fn send_message(
|
|||
_auth: AuthenticatedService,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
Json(req): Json<fabro_api_types::SendMessageRequest>,
|
||||
Json(req): Json<SendMessageRequest>,
|
||||
) -> 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_api_types::SessionTurn::UserTurn(
|
||||
fabro_api_types::UserTurn {
|
||||
kind: fabro_api_types::UserTurnKind::User,
|
||||
content: req.content,
|
||||
created_at: now,
|
||||
},
|
||||
));
|
||||
session.turns.push(SessionTurn::UserTurn(UserTurn {
|
||||
kind: UserTurnKind::User,
|
||||
content: req.content,
|
||||
created_at: now,
|
||||
}));
|
||||
session.updated_at = now;
|
||||
session.generation_seq.fetch_add(1, Ordering::Relaxed) + 1
|
||||
}
|
||||
|
|
@ -313,7 +311,7 @@ pub async fn send_message(
|
|||
|
||||
(
|
||||
StatusCode::ACCEPTED,
|
||||
Json(fabro_api_types::SendMessageResponse { accepted: true }),
|
||||
Json(SendMessageResponse { accepted: true }),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
|
@ -377,16 +375,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<fabro_api_types::SessionListItem> = store
|
||||
let mut items: Vec<SessionListItem> = store
|
||||
.values()
|
||||
.map(|session| {
|
||||
let last_message_preview = session
|
||||
.turns
|
||||
.last()
|
||||
.map(|t| match t {
|
||||
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(),
|
||||
SessionTurn::UserTurn(u) => u.content.clone(),
|
||||
SessionTurn::AssistantTurn(a) => a.content.clone(),
|
||||
SessionTurn::ToolTurn(_) => String::new(),
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let preview = if last_message_preview.len() > 100 {
|
||||
|
|
@ -394,10 +392,10 @@ pub async fn list_sessions(
|
|||
} else {
|
||||
last_message_preview
|
||||
};
|
||||
fabro_api_types::SessionListItem {
|
||||
SessionListItem {
|
||||
id: session.id,
|
||||
title: session.title.clone(),
|
||||
model: fabro_api_types::ModelReference {
|
||||
model: ModelReference {
|
||||
id: session.model_id.clone(),
|
||||
},
|
||||
last_message_preview: preview,
|
||||
|
|
@ -416,9 +414,9 @@ pub async fn list_sessions(
|
|||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(fabro_api_types::PaginatedSessionList {
|
||||
Json(PaginatedSessionList {
|
||||
data,
|
||||
meta: fabro_api_types::PaginationMeta { has_more },
|
||||
meta: PaginationMeta { has_more },
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue