mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
refactor: adopt strum for enum string conversions
Replace hand-written Display/FromStr/as_str boilerplate with strum derives on Provider, RunStatus, StatusReason, Speed, ReasoningEffort, SandboxProvider, Fidelity, ModelTestMode, ModelTestStatus. Update a few downstream callers whose FromStr::Err = String assumption no longer holds. Net -172 lines, zero wire-format change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3e2e1d3dbe
commit
18cfd1b92d
19 changed files with 147 additions and 305 deletions
14
AGENTS.md
14
AGENTS.md
|
|
@ -111,6 +111,20 @@ When interpolating values into shell command strings (in `fabro-workflow`), alwa
|
|||
- **Functions**: import the parent module, call as `module::function()` — `use fabro_workflow::operations; operations::create(...)`
|
||||
- **No glob imports** in production code (`use foo::*`). Globs are acceptable in test modules and preludes. Enforced by clippy `wildcard_imports` lint.
|
||||
|
||||
## Enum string/int conversions (strum)
|
||||
|
||||
For any enum where a variant maps to a fixed string or integer, derive it with `strum` instead of hand-writing `impl Display`, `impl FromStr`, `as_str()`, `fn all()`, or `const ALL: &[Self]`. Hand-written variant→string maps drift across the three impls on every rename.
|
||||
|
||||
- `strum::Display` replaces hand-written `impl fmt::Display` whose body is a match over string literals.
|
||||
- `strum::EnumString` replaces hand-written `impl FromStr`. The `Err` type becomes `strum::ParseError` — adjust callers that assumed `Err = String`.
|
||||
- `strum::IntoStaticStr` replaces `impl From<E> for &'static str`. When an existing `as_str(self) -> &'static str` is on the public API, keep it as a one-line wrapper: `pub fn as_str(self) -> &'static str { self.into() }`.
|
||||
- `strum::EnumIter`, `strum::VariantArray`, `strum::VariantNames` replace hand-written `fn all()` / `const ALL` / `&[&'static str]` arrays. Do NOT use these if the hand-written list intentionally excludes variants (e.g. `Provider::ALL` skips `OpenAiCompatible`).
|
||||
- `strum::FromRepr` replaces `fn from_u8`/`from_i32`. Note: it returns `Option<Self>`, so don't adopt it when the existing conversion has a `_ => default` fallback — that's a behavior change, not a cleanup.
|
||||
|
||||
Align strum with serde. When the enum also derives `Serialize`/`Deserialize` with `#[serde(rename_all = "...")]`, add the matching `#[strum(serialize_all = "...")]`. For variant aliases, use `#[strum(to_string = "canonical", serialize = "alias")]` — strum picks the last `serialize` for `Display`/`IntoStaticStr` otherwise, so `to_string` is needed to pin the canonical form.
|
||||
|
||||
Skip strum when parsing is fuzzy (URL/path detection, structured IDs, multi-token formats), when a variant carries a `String` catch-all, or when `Display` does dynamic formatting.
|
||||
|
||||
## Snapshot tests (insta)
|
||||
|
||||
Many CLI tests use `insta` inline snapshots. When a snapshot needs updating:
|
||||
|
|
|
|||
26
Cargo.lock
generated
26
Cargo.lock
generated
|
|
@ -1790,6 +1790,7 @@ dependencies = [
|
|||
"nom",
|
||||
"regex",
|
||||
"serde",
|
||||
"strum",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
|
|
@ -1874,6 +1875,7 @@ dependencies = [
|
|||
"rand 0.9.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
|
|
@ -1914,6 +1916,7 @@ dependencies = [
|
|||
"insta",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1984,6 +1987,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"strum",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
|
|
@ -2207,6 +2211,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.8.23",
|
||||
"ulid",
|
||||
|
|
@ -6290,6 +6295,27 @@ version = "0.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ percent-encoding = "2"
|
|||
minijinja = "2"
|
||||
fabro-http = { path = "lib/crates/fabro-http" }
|
||||
graphviz-sys = { git = "https://github.com/fabro-sh/graphviz-sys" }
|
||||
strum = { version = "0.28", features = ["derive"] }
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
|
|
|||
|
|
@ -420,12 +420,10 @@ pub async fn run_with_args_and_client(
|
|||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
|
||||
// Parse provider string to enum early for compile-time safety
|
||||
let provider: Provider = args
|
||||
.provider
|
||||
.as_deref()
|
||||
.unwrap_or("anthropic")
|
||||
let provider_str = args.provider.as_deref().unwrap_or("anthropic");
|
||||
let provider: Provider = provider_str
|
||||
.parse()
|
||||
.map_err(|e: String| anyhow::anyhow!("{e}"))?;
|
||||
.map_err(|_| anyhow::anyhow!("unknown provider: {provider_str}"))?;
|
||||
|
||||
// Build LLM client — use provided client or create from env
|
||||
let mut client = if let Some(c) = llm_client {
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ fabro-types = { path = "../fabro-types" }
|
|||
nom = "7"
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
strum.workspace = true
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
/// Fidelity mode controlling how much prior context is provided to LLM
|
||||
/// sessions.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Display, EnumString)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum Fidelity {
|
||||
/// Complete context, no summarization — sessions share a thread.
|
||||
Full,
|
||||
|
|
@ -13,10 +13,13 @@ pub enum Fidelity {
|
|||
#[default]
|
||||
Compact,
|
||||
/// Brief textual summary (~600 token target).
|
||||
#[strum(serialize = "summary:low")]
|
||||
SummaryLow,
|
||||
/// Moderate textual summary (~1500 token target).
|
||||
#[strum(serialize = "summary:medium")]
|
||||
SummaryMedium,
|
||||
/// Detailed per-stage Markdown report.
|
||||
#[strum(serialize = "summary:high")]
|
||||
SummaryHigh,
|
||||
}
|
||||
|
||||
|
|
@ -31,36 +34,6 @@ impl Fidelity {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Fidelity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
Self::Full => "full",
|
||||
Self::Truncate => "truncate",
|
||||
Self::Compact => "compact",
|
||||
Self::SummaryLow => "summary:low",
|
||||
Self::SummaryMedium => "summary:medium",
|
||||
Self::SummaryHigh => "summary:high",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Fidelity {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"full" => Ok(Self::Full),
|
||||
"truncate" => Ok(Self::Truncate),
|
||||
"compact" => Ok(Self::Compact),
|
||||
"summary:low" => Ok(Self::SummaryLow),
|
||||
"summary:medium" => Ok(Self::SummaryMedium),
|
||||
"summary:high" => Ok(Self::SummaryHigh),
|
||||
other => Err(format!("unknown fidelity mode: {other}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ anyhow.workspace = true
|
|||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
tokio.workspace = true
|
||||
uuid.workspace = true
|
||||
rand.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use fabro_model::Model;
|
||||
use strum::{EnumString, IntoStaticStr};
|
||||
use tokio::time;
|
||||
|
||||
use crate::client::Client;
|
||||
|
|
@ -10,7 +10,8 @@ use crate::generate::{self, GenerateParams};
|
|||
use crate::tools::Tool;
|
||||
use crate::types::{GenerateResult, ReasoningEffort};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumString, IntoStaticStr)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ModelTestMode {
|
||||
#[default]
|
||||
Basic,
|
||||
|
|
@ -19,11 +20,8 @@ pub enum ModelTestMode {
|
|||
|
||||
impl ModelTestMode {
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Basic => "basic",
|
||||
Self::Deep => "deep",
|
||||
}
|
||||
pub fn as_str(self) -> &'static str {
|
||||
self.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -35,19 +33,8 @@ impl ModelTestMode {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for ModelTestMode {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value {
|
||||
"basic" => Ok(Self::Basic),
|
||||
"deep" => Ok(Self::Deep),
|
||||
other => Err(format!("invalid model test mode: {other}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ModelTestStatus {
|
||||
Ok,
|
||||
Error,
|
||||
|
|
@ -55,11 +42,8 @@ pub enum ModelTestStatus {
|
|||
|
||||
impl ModelTestStatus {
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Ok => "ok",
|
||||
Self::Error => "error",
|
||||
}
|
||||
pub fn as_str(self) -> &'static str {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -411,8 +411,21 @@ pub struct RateLimitInfo {
|
|||
|
||||
// --- 3.8 ReasoningEffort ---
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
strum::IntoStaticStr,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ReasoningEffort {
|
||||
Low,
|
||||
Medium,
|
||||
|
|
@ -423,35 +436,7 @@ pub enum ReasoningEffort {
|
|||
|
||||
impl ReasoningEffort {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Low => "low",
|
||||
Self::Medium => "medium",
|
||||
Self::High => "high",
|
||||
Self::XHigh => "xhigh",
|
||||
Self::Max => "max",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ReasoningEffort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ReasoningEffort {
|
||||
type Err = String;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"low" => Ok(Self::Low),
|
||||
"medium" => Ok(Self::Medium),
|
||||
"high" => Ok(Self::High),
|
||||
"xhigh" => Ok(Self::XHigh),
|
||||
"max" => Ok(Self::Max),
|
||||
other => Err(format!(
|
||||
"invalid reasoning_effort: {other:?} (expected low, medium, high, xhigh, or max)"
|
||||
)),
|
||||
}
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1318,12 +1303,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_from_str_rejects_unknown_with_updated_error() {
|
||||
fn reasoning_effort_from_str_rejects_unknown() {
|
||||
use std::str::FromStr;
|
||||
let err = ReasoningEffort::from_str("bogus").expect_err("should reject");
|
||||
assert!(
|
||||
err.contains("low, medium, high, xhigh, or max"),
|
||||
"error should list all accepted levels, got: {err}"
|
||||
);
|
||||
assert!(ReasoningEffort::from_str("bogus").is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ workspace = true
|
|||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
insta.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString, IntoStaticStr};
|
||||
|
||||
use crate::{Model, Provider};
|
||||
|
||||
|
|
@ -96,8 +95,21 @@ impl PricePerMTok {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
IntoStaticStr,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Speed {
|
||||
Standard,
|
||||
Fast,
|
||||
|
|
@ -106,28 +118,7 @@ pub enum Speed {
|
|||
impl Speed {
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Standard => "standard",
|
||||
Self::Fast => "fast",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Speed {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value {
|
||||
"standard" => Ok(Self::Standard),
|
||||
"fast" => Ok(Self::Fast),
|
||||
other => Err(format!("unknown speed: {other}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Speed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,39 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString, IntoStaticStr};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider enum — compile-time safe provider identity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Known LLM provider variants.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
IntoStaticStr,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Provider {
|
||||
Anthropic,
|
||||
#[serde(rename = "openai", alias = "open_ai")]
|
||||
#[strum(to_string = "openai", serialize = "open_ai")]
|
||||
OpenAi,
|
||||
Gemini,
|
||||
Kimi,
|
||||
Zai,
|
||||
Minimax,
|
||||
#[strum(to_string = "inception", serialize = "inception_labs")]
|
||||
Inception,
|
||||
#[serde(rename = "openai_compatible", alias = "open_ai_compatible")]
|
||||
#[strum(to_string = "openai_compatible", serialize = "open_ai_compatible")]
|
||||
OpenAiCompatible,
|
||||
}
|
||||
|
||||
|
|
@ -106,40 +120,7 @@ impl Provider {
|
|||
/// adapter names, and other serialization boundaries.
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Anthropic => "anthropic",
|
||||
Self::OpenAi => "openai",
|
||||
Self::Gemini => "gemini",
|
||||
Self::Kimi => "kimi",
|
||||
Self::Zai => "zai",
|
||||
Self::Minimax => "minimax",
|
||||
Self::Inception => "inception",
|
||||
Self::OpenAiCompatible => "openai_compatible",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Provider {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Provider {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"anthropic" => Ok(Self::Anthropic),
|
||||
"openai" | "open_ai" => Ok(Self::OpenAi),
|
||||
"gemini" => Ok(Self::Gemini),
|
||||
"kimi" => Ok(Self::Kimi),
|
||||
"zai" => Ok(Self::Zai),
|
||||
"minimax" => Ok(Self::Minimax),
|
||||
"inception" | "inception_labs" => Ok(Self::Inception),
|
||||
"openai_compatible" => Ok(Self::OpenAiCompatible),
|
||||
other => Err(format!("unknown provider: {other}")),
|
||||
}
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ tokio.workspace = true
|
|||
tokio-util.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
tracing.workspace = true
|
||||
base64.workspace = true
|
||||
fabro-proc = { path = "../fabro-proc" }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
/// Sandbox provider for agent tool operations.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Display, EnumString)]
|
||||
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
|
||||
pub enum SandboxProvider {
|
||||
/// Run tools on the local host (default)
|
||||
#[default]
|
||||
|
|
@ -22,29 +22,6 @@ impl SandboxProvider {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SandboxProvider {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Local => write!(f, "local"),
|
||||
Self::Docker => write!(f, "docker"),
|
||||
Self::Daytona => write!(f, "daytona"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SandboxProvider {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"local" => Ok(Self::Local),
|
||||
"docker" => Ok(Self::Docker),
|
||||
"daytona" => Ok(Self::Daytona),
|
||||
other => Err(format!("unknown sandbox provider: {other}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SandboxProvider;
|
||||
|
|
|
|||
|
|
@ -6751,7 +6751,13 @@ async fn list_models(
|
|||
let provider = match params.provider.as_deref() {
|
||||
Some(value) => match fabro_model::Provider::from_str(value) {
|
||||
Ok(provider) => Some(provider),
|
||||
Err(err) => return ApiError::new(StatusCode::BAD_REQUEST, err).into_response(),
|
||||
Err(_) => {
|
||||
return ApiError::new(
|
||||
StatusCode::BAD_REQUEST,
|
||||
format!("unknown provider: {value}"),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
|
@ -6799,7 +6805,13 @@ async fn test_model(
|
|||
let mode = match params.mode.as_deref() {
|
||||
Some(value) => match ModelTestMode::from_str(value) {
|
||||
Ok(mode) => mode,
|
||||
Err(err) => return ApiError::new(StatusCode::BAD_REQUEST, err).into_response(),
|
||||
Err(_) => {
|
||||
return ApiError::new(
|
||||
StatusCode::BAD_REQUEST,
|
||||
format!("invalid model test mode: {value}"),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
},
|
||||
None => ModelTestMode::Basic,
|
||||
};
|
||||
|
|
@ -11203,14 +11215,14 @@ timeout = "30s"
|
|||
.send(test_event_envelope(
|
||||
1,
|
||||
run_two,
|
||||
EventBody::RunQueued(Default::default()),
|
||||
EventBody::RunQueued(fabro_types::run_event::RunStatusEffectProps::default()),
|
||||
))
|
||||
.unwrap();
|
||||
event_tx
|
||||
.send(test_event_envelope(
|
||||
2,
|
||||
run_one,
|
||||
EventBody::RunQueued(Default::default()),
|
||||
EventBody::RunQueued(fabro_types::run_event::RunStatusEffectProps::default()),
|
||||
))
|
||||
.unwrap();
|
||||
drop(event_tx);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ ipnet = { version = "2.11.0", features = ["serde"] }
|
|||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
strum.workspace = true
|
||||
toml.workspace = true
|
||||
ulid.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,5 @@ pub use sandbox_record::SandboxRecord;
|
|||
pub use stage_id::{ParallelBranchId, StageId};
|
||||
pub use start::StartRecord;
|
||||
pub use status::{
|
||||
BlockedReason, InvalidTransition, ParseRunStatusError, RunControlAction, RunStatus,
|
||||
RunStatusRecord, StatusReason,
|
||||
BlockedReason, InvalidTransition, RunControlAction, RunStatus, RunStatusRecord, StatusReason,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, EnumString)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum RunStatus {
|
||||
Submitted,
|
||||
Queued,
|
||||
|
|
@ -91,57 +92,6 @@ impl RunStatus {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RunStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
Self::Submitted => "submitted",
|
||||
Self::Queued => "queued",
|
||||
Self::Starting => "starting",
|
||||
Self::Running => "running",
|
||||
Self::Blocked => "blocked",
|
||||
Self::Paused => "paused",
|
||||
Self::Removing => "removing",
|
||||
Self::Succeeded => "succeeded",
|
||||
Self::Failed => "failed",
|
||||
Self::Dead => "dead",
|
||||
Self::Archived => "archived",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RunStatus {
|
||||
type Err = ParseRunStatusError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"submitted" => Ok(Self::Submitted),
|
||||
"queued" => Ok(Self::Queued),
|
||||
"starting" => Ok(Self::Starting),
|
||||
"running" => Ok(Self::Running),
|
||||
"blocked" => Ok(Self::Blocked),
|
||||
"paused" => Ok(Self::Paused),
|
||||
"removing" => Ok(Self::Removing),
|
||||
"succeeded" => Ok(Self::Succeeded),
|
||||
"failed" => Ok(Self::Failed),
|
||||
"dead" => Ok(Self::Dead),
|
||||
"archived" => Ok(Self::Archived),
|
||||
_ => Err(ParseRunStatusError(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseRunStatusError(String);
|
||||
|
||||
impl fmt::Display for ParseRunStatusError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "invalid run status: {:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseRunStatusError {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct InvalidTransition {
|
||||
pub from: RunStatus,
|
||||
|
|
@ -156,8 +106,9 @@ impl fmt::Display for InvalidTransition {
|
|||
|
||||
impl std::error::Error for InvalidTransition {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, EnumString)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum StatusReason {
|
||||
Completed,
|
||||
PartialSuccess,
|
||||
|
|
@ -172,57 +123,6 @@ pub enum StatusReason {
|
|||
SandboxInitializing,
|
||||
}
|
||||
|
||||
impl fmt::Display for StatusReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
Self::Completed => "completed",
|
||||
Self::PartialSuccess => "partial_success",
|
||||
Self::WorkflowError => "workflow_error",
|
||||
Self::Cancelled => "cancelled",
|
||||
Self::Terminated => "terminated",
|
||||
Self::TransientInfra => "transient_infra",
|
||||
Self::BudgetExhausted => "budget_exhausted",
|
||||
Self::LaunchFailed => "launch_failed",
|
||||
Self::BootstrapFailed => "bootstrap_failed",
|
||||
Self::SandboxInitFailed => "sandbox_init_failed",
|
||||
Self::SandboxInitializing => "sandbox_initializing",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StatusReason {
|
||||
type Err = ParseStatusReasonError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"completed" => Ok(Self::Completed),
|
||||
"partial_success" => Ok(Self::PartialSuccess),
|
||||
"workflow_error" => Ok(Self::WorkflowError),
|
||||
"cancelled" => Ok(Self::Cancelled),
|
||||
"terminated" => Ok(Self::Terminated),
|
||||
"transient_infra" => Ok(Self::TransientInfra),
|
||||
"budget_exhausted" => Ok(Self::BudgetExhausted),
|
||||
"launch_failed" => Ok(Self::LaunchFailed),
|
||||
"bootstrap_failed" => Ok(Self::BootstrapFailed),
|
||||
"sandbox_init_failed" => Ok(Self::SandboxInitFailed),
|
||||
"sandbox_initializing" => Ok(Self::SandboxInitializing),
|
||||
_ => Err(ParseStatusReasonError(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseStatusReasonError(String);
|
||||
|
||||
impl fmt::Display for ParseStatusReasonError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "invalid status reason: {:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseStatusReasonError {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BlockedReason {
|
||||
|
|
|
|||
|
|
@ -335,12 +335,12 @@ impl RunSession {
|
|||
.map(InterpString::as_source)
|
||||
.filter(|value| !value.is_empty());
|
||||
|
||||
let provider_enum: Provider = provider
|
||||
.as_deref()
|
||||
.map(str::parse::<Provider>)
|
||||
.transpose()
|
||||
.map_err(|err| Error::Precondition(err.clone()))?
|
||||
.unwrap_or_else(|| Provider::default_for_configured(&configured));
|
||||
let provider_enum: Provider = match provider.as_deref() {
|
||||
Some(value) => value
|
||||
.parse::<Provider>()
|
||||
.map_err(|_| Error::Precondition(format!("unknown provider: {value}")))?,
|
||||
None => Provider::default_for_configured(&configured),
|
||||
};
|
||||
|
||||
let fallback_chain = resolve_fallback_chain(provider_enum, &model, &resolved.model);
|
||||
let mcp_servers = resolved
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue