diff --git a/Cargo.lock b/Cargo.lock index 9b9bada2b..fbbd434d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,6 +2292,7 @@ dependencies = [ "insta", "jsonschema", "libc", + "lithos-llm", "paste", "serde", "serde_json", @@ -2318,6 +2319,7 @@ dependencies = [ "fabro-config", "fabro-environment", "fabro-types", + "lithos-llm", "openapiv3", "prettyplease", "progenitor", @@ -2463,6 +2465,7 @@ dependencies = [ "insta", "jsonwebtoken", "libc", + "lithos-llm", "miette", "nix 0.30.1", "object_store", @@ -2692,6 +2695,7 @@ dependencies = [ "fabro-types", "fabro-util", "httpmock", + "lithos-llm", "regex", "serde", "serde_json", @@ -3034,6 +3038,7 @@ dependencies = [ "http-body-util", "httpmock", "jsonwebtoken", + "lithos-llm", "mime_guess", "multer", "object_store", @@ -3372,6 +3377,7 @@ dependencies = [ "hex", "httpmock", "jsonschema", + "lithos-llm", "md5", "miette", "mime_guess", diff --git a/docs/public/reference/sdk.mdx b/docs/public/reference/sdk.mdx index 63be41cec..d45a75bba 100644 --- a/docs/public/reference/sdk.mdx +++ b/docs/public/reference/sdk.mdx @@ -32,7 +32,8 @@ use std::sync::Arc; use fabro_agent::{AgentProfile, AgentProfileBuilder, LocalSandbox, Session, SessionOptions}; use fabro_auth::VaultCredentialSource; use fabro_llm::ClientOptions; -use fabro_types::{AgentProfileKind, provider_ids}; +use fabro_types::AgentProfileKind; +use lithos_llm::catalog::builtin; #[tokio::main] async fn main() -> Result<(), Box> { @@ -48,7 +49,7 @@ async fn main() -> Result<(), Box> { let profile: Arc = Arc::from( AgentProfileBuilder::new( AgentProfileKind::Anthropic, - provider_ids::anthropic(), + builtin::anthropic(), "claude-sonnet-4.5", Arc::clone(&catalog), ) diff --git a/lib/apps/fabro-cli/Cargo.toml b/lib/apps/fabro-cli/Cargo.toml index 2b9c78d48..8b844e79d 100644 --- a/lib/apps/fabro-cli/Cargo.toml +++ b/lib/apps/fabro-cli/Cargo.toml @@ -45,6 +45,7 @@ fabro-telemetry = { path = "../../foundation/fabro-telemetry" } fabro-store = { path = "../../components/fabro-store" } fabro-vault = { path = "../../foundation/fabro-vault" } fabro-types = { path = "../../foundation/fabro-types", features = ["clap"] } +lithos-llm = { workspace = true, features = ["runtime"] } fabro-redact.workspace = true fabro-util = { path = "../../foundation/fabro-util" } fabro-http.workspace = true diff --git a/lib/apps/fabro-cli/src/commands/install.rs b/lib/apps/fabro-cli/src/commands/install.rs index 11d88f15d..f888fc24b 100644 --- a/lib/apps/fabro-cli/src/commands/install.rs +++ b/lib/apps/fabro-cli/src/commands/install.rs @@ -39,13 +39,14 @@ use fabro_server::serve; use fabro_store::ArtifactStore; use fabro_types::settings::server::ServerAuthMethod; use fabro_types::settings::validate_public_url_with_label; -use fabro_types::{ProviderId, ServerSettings, provider_ids}; +use fabro_types::{ProviderId, ServerSettings}; use fabro_util::printer::Printer; use fabro_util::terminal::Styles; use fabro_util::version::FABRO_VERSION; use fabro_util::{browser, dev_token, path, session_secret}; use fabro_vault::SecretType as VaultSecretType; use futures::future::BoxFuture; +use lithos_llm::catalog::builtin; use rand::Rng; use tokio::net::TcpListener; use tokio::process::Command as TokioCommand; @@ -429,14 +430,14 @@ impl InstallInputSource for InteractiveInstallInputSource { if use_device_auth { let credential = authenticate_provider_with_method( - provider_ids::openai(), + builtin::openai(), AuthMethod::CodexDevice(codex_oauth_config()), s, printer, ) .await?; credentials.push(credential); - configured_providers.push(provider_ids::openai()); + configured_providers.push(builtin::openai()); openai_configured = true; } } @@ -2698,7 +2699,7 @@ client_id = "client-id" description: None, }, credential_secret_request(&LoginResult::ApiKey { - provider: fabro_types::provider_ids::anthropic(), + provider: lithos_llm::catalog::builtin::anthropic(), key: "anthropic-key".to_string(), }) .unwrap(), @@ -3504,9 +3505,9 @@ root = "{}" fn install_llm_providers_come_from_catalog_api_key_providers() { let ids = install_llm_provider_ids(&INSTALL_CATALOG); - assert!(ids.contains(&fabro_types::provider_ids::anthropic())); - assert!(ids.contains(&fabro_types::provider_ids::openai())); - assert!(ids.contains(&fabro_types::provider_ids::gemini())); + assert!(ids.contains(&lithos_llm::catalog::builtin::anthropic())); + assert!(ids.contains(&lithos_llm::catalog::builtin::openai())); + assert!(ids.contains(&lithos_llm::catalog::builtin::gemini())); assert!(ids.contains(&ProviderId::new("moonshot"))); assert!(ids.contains(&ProviderId::new("zai"))); assert!(ids.contains(&ProviderId::new("minimax"))); @@ -3531,7 +3532,7 @@ root = "{}" #[test] fn non_interactive_source_rejects_hidden_args_without_switch() { let args = install_args(false, InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), ..InstallNonInteractiveArgs::default() }); let err = NonInteractiveInstallInputSource::new(&args).unwrap_err(); @@ -3544,7 +3545,7 @@ root = "{}" #[test] fn non_interactive_source_rejects_conflicting_api_key_inputs() { let args = install_args(true, InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_stdin: true, llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::Token), @@ -3640,7 +3641,7 @@ root = "{}" fn non_interactive_source_rejects_missing_github_strategy() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_username: Some("brynary".to_string()), ..InstallNonInteractiveArgs::default() @@ -3658,7 +3659,7 @@ root = "{}" fn non_interactive_source_rejects_missing_github_username_for_new_config() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::Token), ..InstallNonInteractiveArgs::default() @@ -3675,7 +3676,7 @@ root = "{}" fn non_interactive_source_allows_keep_existing_settings_without_username() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::Token), keep_existing_settings: true, @@ -3690,7 +3691,7 @@ root = "{}" fn non_interactive_source_rejects_missing_github_owner_for_app() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::App), ..InstallNonInteractiveArgs::default() @@ -3709,7 +3710,7 @@ root = "{}" fn non_interactive_source_rejects_github_owner_for_token() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::Token), github_owner: Some("personal".to_string()), @@ -3729,7 +3730,7 @@ root = "{}" fn non_interactive_source_rejects_github_username_for_app() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::App), github_owner: Some("personal".to_string()), @@ -3749,7 +3750,7 @@ root = "{}" fn non_interactive_source_allows_github_app_setup() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::App), github_owner: Some("personal".to_string()), @@ -3764,7 +3765,7 @@ root = "{}" async fn non_interactive_source_requires_config_choice_when_settings_exist() { let source = NonInteractiveInstallInputSource { args: InstallNonInteractiveArgs { - llm_provider: Some(fabro_types::provider_ids::anthropic()), + llm_provider: Some(lithos_llm::catalog::builtin::anthropic()), llm_api_key_env: Some("ANTHROPIC_API_KEY".to_string()), github_strategy: Some(InstallGitHubStrategyArg::Token), github_username: Some("brynary".to_string()), diff --git a/lib/apps/fabro-cli/src/commands/model.rs b/lib/apps/fabro-cli/src/commands/model.rs index 6a1eae9f5..caaf0f4e4 100644 --- a/lib/apps/fabro-cli/src/commands/model.rs +++ b/lib/apps/fabro-cli/src/commands/model.rs @@ -513,9 +513,8 @@ impl Default for ModelsCommand { #[cfg(test)] mod tests { - use fabro_types::{ - ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffort, provider_ids, - }; + use fabro_types::{ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffort}; + use lithos_llm::catalog::builtin; use super::*; @@ -902,7 +901,7 @@ mod tests { .header("Content-Type", "application/json") .body( serde_json::json!({ - "data": [test_model_json("test-model", provider_ids::anthropic())], + "data": [test_model_json("test-model", builtin::anthropic())], "meta": { "has_more": false } }) .to_string(), @@ -916,7 +915,7 @@ mod tests { mock.assert_async().await; assert_eq!(models.len(), 1); assert_eq!(models[0].id.as_str(), "test-model"); - assert_eq!(models[0].provider, provider_ids::anthropic()); + assert_eq!(models[0].provider, builtin::anthropic()); } #[tokio::test] @@ -933,7 +932,7 @@ mod tests { .header("Content-Type", "application/json") .body( serde_json::json!({ - "data": [test_model_json("model-a", provider_ids::anthropic())], + "data": [test_model_json("model-a", builtin::anthropic())], "meta": { "has_more": false } }) .to_string(), @@ -961,12 +960,12 @@ mod tests { then.status(200) .header("Content-Type", "application/json") .body( - serde_json::json!({ - "data": [test_model_json("claude-sonnet-4-5", provider_ids::anthropic())], - "meta": { "has_more": false } - }) - .to_string(), - ); + serde_json::json!({ + "data": [test_model_json("claude-sonnet-4-5", builtin::anthropic())], + "meta": { "has_more": false } + }) + .to_string(), + ); }) .await; @@ -991,7 +990,7 @@ mod tests { .header("Content-Type", "application/json") .body( serde_json::json!({ - "data": [test_model_json("model-a", provider_ids::anthropic())], + "data": [test_model_json("model-a", builtin::anthropic())], "meta": { "has_more": true } }) .to_string(), @@ -1008,7 +1007,7 @@ mod tests { .header("Content-Type", "application/json") .body( serde_json::json!({ - "data": [test_model_json("model-b", provider_ids::openai())], + "data": [test_model_json("model-b", builtin::openai())], "meta": { "has_more": false } }) .to_string(), diff --git a/lib/apps/fabro-cli/src/commands/run/run_progress/mod.rs b/lib/apps/fabro-cli/src/commands/run/run_progress/mod.rs index a04f7b943..1f851de06 100644 --- a/lib/apps/fabro-cli/src/commands/run/run_progress/mod.rs +++ b/lib/apps/fabro-cli/src/commands/run/run_progress/mod.rs @@ -461,10 +461,11 @@ mod tests { use fabro_types::run_event::CliEnsureCompletedProps; use fabro_types::{ MetadataSnapshotFailureKind, MetadataSnapshotPhase, ModelId, ModelRef, ParallelBranchId, - SandboxProviderKind, StageId, TokenCounts, fixtures, provider_ids, + SandboxProviderKind, StageId, TokenCounts, fixtures, }; use fabro_workflow::event::{Event, RunNoticeLevel, to_run_event, to_run_event_at}; use fabro_workflow::outcome::billed_model_usage_from_llm; + use lithos_llm::catalog::builtin; use super::*; use crate::commands::run::run_progress::stage_display::ToolCallStatus; @@ -570,7 +571,7 @@ mod tests { fn assistant_event(model: &str, text: &str) -> AgentEvent { AgentEvent::AssistantMessage { text: text.into(), - model: ModelRef::new(provider_ids::openai(), ModelId::new(model)), + model: ModelRef::new(builtin::openai(), ModelId::new(model)), usage: TokenCounts::default(), cost: None, tool_call_count: 0, @@ -589,7 +590,7 @@ mod tests { fn llm_request_started(stage: &str, model: &str) -> Event { agent_event(stage, AgentEvent::LlmRequestStarted { - requested_model: ModelRef::new(provider_ids::anthropic(), ModelId::new(model)), + requested_model: ModelRef::new(builtin::anthropic(), ModelId::new(model)), }) } @@ -605,7 +606,7 @@ mod tests { billing: Some( billed_model_usage_from_llm( &fabro_llm::test_support::test_catalog(), - &ModelRef::new(provider_ids::openai(), ModelId::new("gpt-5.4")), + &ModelRef::new(builtin::openai(), ModelId::new("gpt-5.4")), TokenCounts { input: 1200, output: 300, diff --git a/lib/apps/fabro-cli/src/main.rs b/lib/apps/fabro-cli/src/main.rs index 8374eaafb..8d4edd598 100644 --- a/lib/apps/fabro-cli/src/main.rs +++ b/lib/apps/fabro-cli/src/main.rs @@ -586,7 +586,7 @@ mod tests { ProviderCommand, ProviderNamespace, }; use clap::error::ErrorKind; - use fabro_types::provider_ids; + use lithos_llm::catalog::builtin; use temp_env::with_var; use tokio::runtime::Runtime; @@ -658,7 +658,7 @@ destination = "{destination}" Commands::Provider(ProviderNamespace { command: ProviderCommand::Login(args), }) => { - assert_eq!(args.provider, provider_ids::openai()); + assert_eq!(args.provider, builtin::openai()); } _ => panic!("unexpected command variant"), } @@ -672,7 +672,7 @@ destination = "{destination}" Commands::Provider(ProviderNamespace { command: ProviderCommand::Login(args), }) => { - assert_eq!(args.provider, provider_ids::anthropic()); + assert_eq!(args.provider, builtin::anthropic()); } _ => panic!("unexpected command variant"), } @@ -693,7 +693,7 @@ destination = "{destination}" Commands::Provider(ProviderNamespace { command: ProviderCommand::Login(args), }) => { - assert_eq!(args.provider, provider_ids::anthropic()); + assert_eq!(args.provider, builtin::anthropic()); assert!(args.api_key_stdin); } _ => panic!("unexpected command variant"), diff --git a/lib/apps/fabro-cli/src/shared/provider_auth.rs b/lib/apps/fabro-cli/src/shared/provider_auth.rs index f9a2872b3..badd55c5d 100644 --- a/lib/apps/fabro-cli/src/shared/provider_auth.rs +++ b/lib/apps/fabro-cli/src/shared/provider_auth.rs @@ -20,9 +20,10 @@ use fabro_auth::{ }; use fabro_llm::lithos_catalog::{Catalog, CatalogProvider}; use fabro_llm::probe::{self, ApiKeyProbeError, ModelTestStatus}; -use fabro_types::{ProviderId, provider_ids}; +use fabro_types::ProviderId; use fabro_util::printer::Printer; use fabro_util::terminal::Styles; +use lithos_llm::catalog::builtin; use tokio::task::spawn_blocking; // --------------------------------------------------------------------------- @@ -184,7 +185,7 @@ async fn read_and_validate_api_key( } pub(crate) async fn pick_auth_method(provider: &ProviderId) -> Result { - if provider != &provider_ids::openai() { + if provider != &builtin::openai() { return Ok(AuthMethod::ApiKey); } @@ -374,9 +375,9 @@ mod tests { fn builtin_api_key_providers_have_key_urls() { let catalog = fabro_llm::default_catalog(); for provider in [ - provider_ids::anthropic(), - provider_ids::openai(), - provider_ids::gemini(), + builtin::anthropic(), + builtin::openai(), + builtin::gemini(), ProviderId::new("moonshot"), ProviderId::new("zai"), ProviderId::new("minimax"), @@ -408,7 +409,7 @@ mod tests { #[fabro_macros::e2e_test(live("ANTHROPIC_API_KEY"))] async fn validate_api_key_rejects_invalid_key() { let result = validate_api_key( - &provider_ids::anthropic(), + &builtin::anthropic(), "sk-invalid-key-12345", default_catalog_for_provider_auth(), ) diff --git a/lib/apps/fabro-server/Cargo.toml b/lib/apps/fabro-server/Cargo.toml index 97bf82e42..3efb0947f 100644 --- a/lib/apps/fabro-server/Cargo.toml +++ b/lib/apps/fabro-server/Cargo.toml @@ -44,6 +44,7 @@ fabro-proc = { path = "../../foundation/fabro-proc" } fabro-template = { path = "../../foundation/fabro-template" } fabro-tool = { path = "../../components/fabro-tool" } fabro-types = { path = "../../foundation/fabro-types" } +lithos-llm = { workspace = true, features = ["runtime"] } fabro-util = { path = "../../foundation/fabro-util" } fabro-api = { path = "../../foundation/fabro-api" } fabro-client = { path = "../../foundation/fabro-client" } diff --git a/lib/apps/fabro-server/src/demo/mod.rs b/lib/apps/fabro-server/src/demo/mod.rs index f3001e045..7820a9431 100644 --- a/lib/apps/fabro-server/src/demo/mod.rs +++ b/lib/apps/fabro-server/src/demo/mod.rs @@ -1495,7 +1495,7 @@ mod runs { EventBody::AgentMessage(AgentMessageProps { text: "I'll start by loading the environment configurations for both production and staging to compare them.".into(), model: fabro_types::ModelRef::new( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), fabro_types::ModelId::new("claude-opus-4.6"), ), billing: BilledTokenCounts::default(), @@ -1571,7 +1571,7 @@ mod runs { EventBody::AgentMessage(AgentMessageProps { text: "I've detected drift in 3 resources between production and staging:\n\n1. **redis.max_connections** — production has 200, staging has 100\n2. **redis.tls** — enabled in production, disabled in staging\n3. **iam.session_duration** — production uses 3600s, staging uses 1800s".into(), model: fabro_types::ModelRef::new( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), fabro_types::ModelId::new("claude-opus-4.6"), ), billing: BilledTokenCounts::default(), @@ -1595,7 +1595,7 @@ mod runs { name: "Detect Drift".into(), }, model: Some(billing_model( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-opus-4-6", )), billing: BilledTokenCounts { @@ -1617,7 +1617,7 @@ mod runs { name: "Propose Changes".into(), }, model: Some(billing_model( - fabro_types::provider_ids::gemini(), + lithos_llm::catalog::builtin::gemini(), "gemini-3.1-pro-preview", )), billing: BilledTokenCounts { @@ -1639,7 +1639,7 @@ mod runs { name: "Review Changes".into(), }, model: Some(billing_model( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), "gpt-5.3-codex", )), billing: BilledTokenCounts { @@ -1661,7 +1661,7 @@ mod runs { name: "Apply Changes".into(), }, model: Some(billing_model( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-opus-4-6", )), billing: BilledTokenCounts { @@ -1700,7 +1700,7 @@ mod runs { total_usd_micros: Some(1_350_000), }, model: billing_model( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-opus-4-6", ), stages: 2, @@ -1716,7 +1716,7 @@ mod runs { total_usd_micros: Some(720_000), }, model: billing_model( - fabro_types::provider_ids::gemini(), + lithos_llm::catalog::builtin::gemini(), "gemini-3.1-pro-preview", ), stages: 1, @@ -1731,7 +1731,7 @@ mod runs { total_tokens: 11760, total_usd_micros: Some(190_000), }, - model: billing_model(fabro_types::provider_ids::openai(), "gpt-5.3-codex"), + model: billing_model(lithos_llm::catalog::builtin::openai(), "gpt-5.3-codex"), stages: 1, }, ], @@ -2108,7 +2108,7 @@ mod billing { total_usd_micros: Some(12_150_000), }, model: billing_model( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-opus-4-6", ), stages: 18, @@ -2124,7 +2124,7 @@ mod billing { total_usd_micros: Some(6_480_000), }, model: billing_model( - fabro_types::provider_ids::gemini(), + lithos_llm::catalog::builtin::gemini(), "gemini-3.1-pro-preview", ), stages: 9, @@ -2139,7 +2139,7 @@ mod billing { total_tokens: 105_840, total_usd_micros: Some(1_710_000), }, - model: billing_model(fabro_types::provider_ids::openai(), "gpt-5.3-codex"), + model: billing_model(lithos_llm::catalog::builtin::openai(), "gpt-5.3-codex"), stages: 9, }, ], diff --git a/lib/apps/fabro-server/src/install.rs b/lib/apps/fabro-server/src/install.rs index f91a28463..d04ebcb1e 100644 --- a/lib/apps/fabro-server/src/install.rs +++ b/lib/apps/fabro-server/src/install.rs @@ -2567,7 +2567,7 @@ mod tests { #[test] fn install_provider_base_url_falls_back_to_catalog_base_url() { let state = InstallAppState::for_test("expected"); - let provider = install_catalog_provider(&fabro_types::provider_ids::openai()).unwrap(); + let provider = install_catalog_provider(&lithos_llm::catalog::builtin::openai()).unwrap(); assert_eq!( provider_base_url_override(&state, provider), @@ -2578,10 +2578,10 @@ mod tests { #[test] fn install_provider_base_url_prefers_state_override() { let state = InstallAppState::for_test("expected").with_provider_base_url( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), "https://proxy.example.com/v1", ); - let provider = install_catalog_provider(&fabro_types::provider_ids::openai()).unwrap(); + let provider = install_catalog_provider(&lithos_llm::catalog::builtin::openai()).unwrap(); assert_eq!( provider_base_url_override(&state, provider), diff --git a/lib/apps/fabro-server/src/run_manifest.rs b/lib/apps/fabro-server/src/run_manifest.rs index fcc9c42cc..cf3984951 100644 --- a/lib/apps/fabro-server/src/run_manifest.rs +++ b/lib/apps/fabro-server/src/run_manifest.rs @@ -2017,7 +2017,7 @@ enabled = {clone_enabled} prepared.settings.clone(), validated.graph(), test_catalog().as_ref(), - &[fabro_types::provider_ids::anthropic()], + &[lithos_llm::catalog::builtin::anthropic()], ) .unwrap() .run; diff --git a/lib/apps/fabro-server/src/run_title_generation.rs b/lib/apps/fabro-server/src/run_title_generation.rs index f54be4e17..963fecc00 100644 --- a/lib/apps/fabro-server/src/run_title_generation.rs +++ b/lib/apps/fabro-server/src/run_title_generation.rs @@ -195,7 +195,8 @@ mod tests { use fabro_llm::adapter::{ProviderAdapter, ResolvedCall}; use fabro_llm::lithos_catalog::AdapterId; use fabro_llm::{Error as LlmError, Response, ResponseStream}; - use fabro_types::{RunId, provider_ids}; + use fabro_types::RunId; + use lithos_llm::catalog::builtin; use toml::Value as TomlValue; use super::*; @@ -358,7 +359,7 @@ mod tests { let title = generate_title_or_current(GenerateTitleInput { client, model_id: "gpt-5.4".to_string(), - provider_id: provider_ids::openai(), + provider_id: builtin::openai(), prompt: TitlePromptInput { run_id: &run_id, current_title: "Current", diff --git a/lib/apps/fabro-server/src/server/handler/sessions.rs b/lib/apps/fabro-server/src/server/handler/sessions.rs index bce2e83fe..ccd526c0f 100644 --- a/lib/apps/fabro-server/src/server/handler/sessions.rs +++ b/lib/apps/fabro-server/src/server/handler/sessions.rs @@ -1574,7 +1574,7 @@ enabled = true #[test] fn canonical_session_model_uses_readiness_priority_and_explicit_pins() { let catalog = portable_session_catalog(); - let openai = fabro_types::provider_ids::openai(); + let openai = lithos_llm::catalog::builtin::openai(); let openrouter = ProviderId::new("openrouter"); assert_eq!( @@ -1621,7 +1621,7 @@ enabled = true #[test] fn canonical_session_model_preserves_unknown_passthrough_on_selected_provider() { let catalog = portable_session_catalog(); - let openai = fabro_types::provider_ids::openai(); + let openai = lithos_llm::catalog::builtin::openai(); let openrouter = ProviderId::new("openrouter"); let both = std::collections::HashSet::from([openai.clone(), openrouter.clone()]); @@ -1641,7 +1641,7 @@ enabled = true #[test] fn canonical_session_model_passes_through_colon_bearing_model_ids() { let catalog = portable_session_catalog(); - let openai = fabro_types::provider_ids::openai(); + let openai = lithos_llm::catalog::builtin::openai(); let openrouter = ProviderId::new("openrouter"); let both = std::collections::HashSet::from([openai.clone(), openrouter.clone()]); @@ -1666,7 +1666,7 @@ enabled = true let catalog = portable_session_catalog(); let error = canonical_session_model( &catalog, - &std::collections::HashSet::from([fabro_types::provider_ids::openai()]), + &std::collections::HashSet::from([lithos_llm::catalog::builtin::openai()]), Some("gpt-56-sol"), Some(&ProviderId::new("openrouter")), ) @@ -1678,7 +1678,7 @@ enabled = true #[test] fn canonical_session_model_normalizes_legacy_builtin_selector_before_qualification() { let catalog = portable_session_catalog(); - let openai = fabro_types::provider_ids::openai(); + let openai = lithos_llm::catalog::builtin::openai(); let openrouter = ProviderId::new("openrouter"); let both = std::collections::HashSet::from([openai.clone(), openrouter.clone()]); @@ -1732,7 +1732,7 @@ enabled = true &catalog, &catalog.enabled_provider_ids().into_iter().collect(), Some("openrouter:gpt-56-sol"), - Some(&fabro_types::provider_ids::openai()), + Some(&lithos_llm::catalog::builtin::openai()), ) .unwrap_err(); diff --git a/lib/apps/fabro-server/src/server/tests.rs b/lib/apps/fabro-server/src/server/tests.rs index 317451efa..0a00c7fc0 100644 --- a/lib/apps/fabro-server/src/server/tests.rs +++ b/lib/apps/fabro-server/src/server/tests.rs @@ -1787,7 +1787,7 @@ async fn resolve_llm_client_reads_openai_token_from_vault() { let llm_result = state.resolve_llm_client().await.unwrap(); assert_eq!(llm_result.provider_ids(), vec![ - fabro_types::provider_ids::openai() + lithos_llm::catalog::builtin::openai() ]); assert!(llm_result.auth_issues.is_empty()); } @@ -1861,7 +1861,7 @@ async fn llm_source_configured_providers_reads_openai_token_from_vault() { .unwrap(); assert_eq!(state.configured_llm_provider_ids().await, vec![ - fabro_types::provider_ids::openai() + lithos_llm::catalog::builtin::openai() ]); } @@ -6121,7 +6121,7 @@ fn context_window_event( event: fabro_agent::AgentEvent::AssistantMessage { text: "assistant response".to_string(), model: ModelRef::new( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), ModelId::new("gpt-5.4"), ), usage: TokenCounts::default(), @@ -7201,7 +7201,10 @@ fn test_billed_usage( output_tokens: u64, ) -> fabro_types::BilledModelUsage { let mut usage = fabro_types::BilledModelUsage::new( - ModelRef::new(fabro_types::provider_ids::openai(), ModelId::new(model_id)), + ModelRef::new( + lithos_llm::catalog::builtin::openai(), + ModelId::new(model_id), + ), TokenCounts { input: input_tokens, output: output_tokens, @@ -16768,7 +16771,7 @@ async fn get_aggregate_billing_returns_provider_model_speed_identity() { agg.total_runs = 1; agg.by_model.insert( ModelRef::new( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), ModelId::new("claude-opus-4-6"), ), ModelBillingTotals { @@ -16786,7 +16789,7 @@ async fn get_aggregate_billing_returns_provider_model_speed_identity() { ); agg.by_model.insert( ModelRef::new( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), ModelId::new("claude-opus-4-6"), ) .with_speed(Some(Speed::Fast)), @@ -16846,7 +16849,10 @@ async fn get_aggregate_billing_saturates_total_cost_across_models() { .expect("aggregate billing lock"); for (model_id, total_usd_micros) in [("maximum", i64::MAX), ("one", 1)] { agg.by_model.insert( - ModelRef::new(fabro_types::provider_ids::openai(), ModelId::new(model_id)), + ModelRef::new( + lithos_llm::catalog::builtin::openai(), + ModelId::new(model_id), + ), ModelBillingTotals { stages: 1, billing: BilledTokenCounts { @@ -16891,7 +16897,7 @@ fn aggregate_billing_counts_projection_rollup_usage_visits() { by_model: vec![ fabro_workflow::ProjectionBillingByModel { model: ModelRef::new( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), ModelId::new("gpt-5.4"), ), stages: 1, @@ -16907,7 +16913,7 @@ fn aggregate_billing_counts_projection_rollup_usage_visits() { }, fabro_workflow::ProjectionBillingByModel { model: ModelRef::new( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), ModelId::new("gpt-5.4"), ) .with_speed(Some(Speed::Fast)), @@ -16933,21 +16939,25 @@ fn aggregate_billing_counts_projection_rollup_usage_visits() { assert_eq!(accumulator.total_timing.wall_time_ms, 2000); assert_eq!(accumulator.by_model.len(), 2); assert_eq!( - accumulator.by_model - [&ModelRef::new(fabro_types::provider_ids::openai(), ModelId::new("gpt-5.4"))] + accumulator.by_model[&ModelRef::new( + lithos_llm::catalog::builtin::openai(), + ModelId::new("gpt-5.4") + )] .stages, 1 ); assert_eq!( - accumulator.by_model - [&ModelRef::new(fabro_types::provider_ids::openai(), ModelId::new("gpt-5.4"))] + accumulator.by_model[&ModelRef::new( + lithos_llm::catalog::builtin::openai(), + ModelId::new("gpt-5.4") + )] .billing .input_tokens, 100 ); assert_eq!( accumulator.by_model[&ModelRef::new( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), ModelId::new("gpt-5.4") ) .with_speed(Some(Speed::Fast))] @@ -16956,7 +16966,7 @@ fn aggregate_billing_counts_projection_rollup_usage_visits() { ); assert_eq!( accumulator.by_model[&ModelRef::new( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), ModelId::new("gpt-5.4") ) .with_speed(Some(Speed::Fast))] @@ -18300,7 +18310,7 @@ async fn attach_stream_replays_agent_message_reasoning() { event: fabro_agent::AgentEvent::AssistantMessage { text: String::new(), model: ModelRef::new( - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), ModelId::new("gpt-5.4"), ), usage: TokenCounts::default(), diff --git a/lib/apps/fabro-server/tests/it/api/install.rs b/lib/apps/fabro-server/tests/it/api/install.rs index 75fa128be..109e459a2 100644 --- a/lib/apps/fabro-server/tests/it/api/install.rs +++ b/lib/apps/fabro-server/tests/it/api/install.rs @@ -1416,7 +1416,7 @@ async fn install_validation_endpoints_validate_credentials_and_github_token() { let app = build_install_router( InstallAppState::for_test("test-install-token") .with_provider_base_url( - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), format!("{}/v1", llm_mock.url("")), ) .with_github_api_base_url(github_mock.url("")), diff --git a/lib/apps/fabro-server/tests/it/scenario/run_completion.rs b/lib/apps/fabro-server/tests/it/scenario/run_completion.rs index f32702a30..6ceccb1ea 100644 --- a/lib/apps/fabro-server/tests/it/scenario/run_completion.rs +++ b/lib/apps/fabro-server/tests/it/scenario/run_completion.rs @@ -57,7 +57,7 @@ fn test_app_with_openai_agent_backend(openai_base_url: String, api_key: String) Some(Box::new( fabro_workflow::handler::llm::AgentApiBackend::new_with_catalog( OPENAI_AGENT_MODEL.to_string(), - fabro_types::provider_ids::openai(), + lithos_llm::catalog::builtin::openai(), fabro_workflow::model_fallback::ModelFallbackPolicy::default(), Arc::clone(&llm_source), Arc::clone(&steering_hub), diff --git a/lib/components/fabro-agent/Cargo.toml b/lib/components/fabro-agent/Cargo.toml index 6ecc8ca81..a61a700d5 100644 --- a/lib/components/fabro-agent/Cargo.toml +++ b/lib/components/fabro-agent/Cargo.toml @@ -27,6 +27,7 @@ anyhow.workspace = true fabro-auth = { path = "../../foundation/fabro-auth" } fabro-config = { path = "../../foundation/fabro-config", features = ["clap"] } fabro-types = { path = "../../foundation/fabro-types", features = ["clap"] } +lithos-llm = { workspace = true, features = ["runtime"] } fabro-llm = { path = "../fabro-llm" } fabro-mcp = { path = "../fabro-mcp" } fabro-sandbox = { path = "../fabro-sandbox" } diff --git a/lib/components/fabro-agent/src/agent_profile.rs b/lib/components/fabro-agent/src/agent_profile.rs index 52a9ae2e2..47712159e 100644 --- a/lib/components/fabro-agent/src/agent_profile.rs +++ b/lib/components/fabro-agent/src/agent_profile.rs @@ -91,7 +91,8 @@ pub trait AgentProfile: Send + Sync { #[cfg(test)] mod tests { - use fabro_types::{AgentProfileKind, provider_ids}; + use fabro_types::AgentProfileKind; + use lithos_llm::catalog::builtin; use super::*; use crate::test_support::{MockSandbox, TestProfile}; @@ -100,7 +101,7 @@ mod tests { fn profile_provider_and_model() { let profile = TestProfile::new(); assert_eq!(profile.profile_kind(), AgentProfileKind::Anthropic); - assert_eq!(profile.provider_id(), provider_ids::anthropic()); + assert_eq!(profile.provider_id(), builtin::anthropic()); assert_eq!(profile.model(), "mock-model"); } diff --git a/lib/components/fabro-agent/src/cli.rs b/lib/components/fabro-agent/src/cli.rs index 51aa41db7..3a6afc7dc 100644 --- a/lib/components/fabro-agent/src/cli.rs +++ b/lib/components/fabro-agent/src/cli.rs @@ -822,7 +822,7 @@ mod tests { use fabro_llm::test_support::{ client_with_adapters, test_catalog as fabro_test_catalog, test_catalog_with_overlay, }; - use fabro_types::provider_ids; + use lithos_llm::catalog::builtin; use serde_json::json; use super::*; @@ -1027,7 +1027,7 @@ profile = "openai" #[test] fn ensure_provider_registered_reports_missing_credentials() { let client = client_with_adapters(Vec::new(), ClientOptions::default()); - let error = ensure_provider_registered(&client, &provider_ids::anthropic()).unwrap_err(); + let error = ensure_provider_registered(&client, &builtin::anthropic()).unwrap_err(); assert_eq!( error.to_string(), "LLM credentials not configured for provider 'anthropic'" @@ -1098,9 +1098,9 @@ profile = "openai" #[test] fn summarizer_model_id_prefers_the_provider_small_default() { let catalog = test_catalog(); - let model_id = summarizer_model_id(&provider_ids::openai(), &catalog, "gpt-5.4"); + let model_id = summarizer_model_id(&builtin::openai(), &catalog, "gpt-5.4"); - assert_eq!(model_id.provider(), &provider_ids::openai()); + assert_eq!(model_id.provider(), &builtin::openai()); assert_eq!(model_id.model().as_str(), "gpt-5.4-mini"); } @@ -1110,7 +1110,7 @@ profile = "openai" fn build_profile_can_register_subagent_tools() { let mut profile = AgentProfileBuilder::new( AgentProfileKind::Anthropic, - provider_ids::anthropic(), + builtin::anthropic(), "model", test_catalog(), ) diff --git a/lib/components/fabro-agent/src/error.rs b/lib/components/fabro-agent/src/error.rs index 21dca27f2..fc25ca015 100644 --- a/lib/components/fabro-agent/src/error.rs +++ b/lib/components/fabro-agent/src/error.rs @@ -86,8 +86,8 @@ mod tests { use std::time::Duration; use fabro_llm::{ErrorKind, RetryClassification}; - use fabro_types::provider_ids; use fabro_util::error; + use lithos_llm::catalog::builtin; use super::*; @@ -180,7 +180,7 @@ mod tests { fn serde_roundtrip_llm_provider() { let err = Error::from(ErrorData::from( fabro_llm::Error::new(ErrorKind::RateLimit, "too fast") - .with_provider(provider_ids::openai()) + .with_provider(builtin::openai()) .with_status(429) .with_retry(RetryClassification::after(Duration::from_secs(2))), )); diff --git a/lib/components/fabro-agent/src/profiles/anthropic.rs b/lib/components/fabro-agent/src/profiles/anthropic.rs index dfcd73340..70813c72a 100644 --- a/lib/components/fabro-agent/src/profiles/anthropic.rs +++ b/lib/components/fabro-agent/src/profiles/anthropic.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use fabro_llm::lithos_catalog::Catalog; -use fabro_types::{AgentProfileKind, ProviderId, provider_ids}; +use fabro_types::{AgentProfileKind, ProviderId}; +use lithos_llm::catalog::builtin; use super::EnvContext; use crate::agent_profile::AgentProfile; @@ -48,7 +49,7 @@ impl AnthropicProfile { Self { base: BaseProfile { profile_kind: AgentProfileKind::Anthropic, - provider_id: provider_ids::anthropic(), + provider_id: builtin::anthropic(), model: model.into(), catalog: None, registry, @@ -116,7 +117,7 @@ mod tests { fn anthropic_profile_identity() { let profile = AnthropicProfile::new("claude-sonnet-4-20250514"); assert_eq!(profile.profile_kind(), AgentProfileKind::Anthropic); - assert_eq!(profile.provider_id(), provider_ids::anthropic()); + assert_eq!(profile.provider_id(), builtin::anthropic()); assert_eq!(profile.model(), "claude-sonnet-4-20250514"); } diff --git a/lib/components/fabro-agent/src/profiles/claude5.rs b/lib/components/fabro-agent/src/profiles/claude5.rs index 66ad7a841..ec5ffd583 100644 --- a/lib/components/fabro-agent/src/profiles/claude5.rs +++ b/lib/components/fabro-agent/src/profiles/claude5.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use fabro_llm::lithos_catalog::Catalog; -use fabro_types::{AgentProfileKind, ProviderId, provider_ids}; +use fabro_types::{AgentProfileKind, ProviderId}; +use lithos_llm::catalog::builtin; use super::EnvContext; use crate::agent_profile::AgentProfile; @@ -65,7 +66,7 @@ impl Claude5Profile { Self { base: BaseProfile { profile_kind: AgentProfileKind::Claude5, - provider_id: provider_ids::anthropic(), + provider_id: builtin::anthropic(), model: model.into(), catalog: None, registry, @@ -166,7 +167,7 @@ mod tests { fn profile_identity() { let profile = Claude5Profile::new("claude-fable-5"); assert_eq!(profile.profile_kind(), AgentProfileKind::Claude5); - assert_eq!(profile.provider_id(), provider_ids::anthropic()); + assert_eq!(profile.provider_id(), builtin::anthropic()); assert_eq!(profile.model(), "claude-fable-5"); } diff --git a/lib/components/fabro-agent/src/profiles/gemini.rs b/lib/components/fabro-agent/src/profiles/gemini.rs index d4e812b78..9af27f5ba 100644 --- a/lib/components/fabro-agent/src/profiles/gemini.rs +++ b/lib/components/fabro-agent/src/profiles/gemini.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use fabro_llm::lithos_catalog::Catalog; -use fabro_types::{AgentProfileKind, ProviderId, provider_ids}; +use fabro_types::{AgentProfileKind, ProviderId}; +use lithos_llm::catalog::builtin; use super::EnvContext; use crate::agent_profile::AgentProfile; @@ -42,7 +43,7 @@ impl GeminiProfile { Self { base: BaseProfile { profile_kind: AgentProfileKind::Gemini, - provider_id: provider_ids::gemini(), + provider_id: builtin::gemini(), model: model.into(), catalog: None, registry, @@ -108,7 +109,7 @@ mod tests { fn gemini_profile_identity() { let profile = GeminiProfile::new("gemini-2.0-flash"); assert_eq!(profile.profile_kind(), AgentProfileKind::Gemini); - assert_eq!(profile.provider_id(), provider_ids::gemini()); + assert_eq!(profile.provider_id(), builtin::gemini()); assert_eq!(profile.model(), "gemini-2.0-flash"); } diff --git a/lib/components/fabro-agent/src/profiles/gpt56.rs b/lib/components/fabro-agent/src/profiles/gpt56.rs index 2bb2dd1c4..c06fac909 100644 --- a/lib/components/fabro-agent/src/profiles/gpt56.rs +++ b/lib/components/fabro-agent/src/profiles/gpt56.rs @@ -17,7 +17,8 @@ use std::sync::Arc; use fabro_llm::lithos_catalog::Catalog; -use fabro_types::{AgentProfileKind, ProviderId, ToolDefinition, provider_ids}; +use fabro_types::{AgentProfileKind, ProviderId, ToolDefinition}; +use lithos_llm::catalog::builtin; use serde_json::Value; use super::EnvContext; @@ -69,7 +70,7 @@ impl Gpt56Profile { Self { base: BaseProfile { profile_kind: AgentProfileKind::Gpt56, - provider_id: provider_ids::openai(), + provider_id: builtin::openai(), model: model.into(), catalog: None, registry, @@ -252,7 +253,7 @@ enabled = true fn gpt56_profile_identity() { let profile = Gpt56Profile::new("gpt-5.6-sol"); assert_eq!(profile.profile_kind(), AgentProfileKind::Gpt56); - assert_eq!(profile.provider_id(), provider_ids::openai()); + assert_eq!(profile.provider_id(), builtin::openai()); assert_eq!(profile.model(), "gpt-5.6-sol"); } @@ -327,8 +328,7 @@ enabled = true /// it points 5.6 at a tool it was never given. #[test] fn shell_description_names_the_editor_actually_registered() { - let direct = - Gpt56Profile::new("gpt-5.6-sol").with_route(provider_ids::openai(), test_catalog()); + let direct = Gpt56Profile::new("gpt-5.6-sol").with_route(builtin::openai(), test_catalog()); let shell = direct.tool_registry().get("shell_command").unwrap(); assert!(shell.definition.description.contains("`apply_patch`")); assert!(!shell.definition.description.contains("`edit_file`")); @@ -349,8 +349,7 @@ enabled = true assert!(!rendered.contains("apply_patch")); assert!(!rendered.contains("*** Begin Patch")); - let direct = - Gpt56Profile::new("gpt-5.6-sol").with_route(provider_ids::openai(), test_catalog()); + let direct = Gpt56Profile::new("gpt-5.6-sol").with_route(builtin::openai(), test_catalog()); let rendered = prompt(&direct); assert!(rendered.contains("Use `apply_patch` for local file edits")); assert!(rendered.contains("*** Begin Patch")); @@ -427,8 +426,7 @@ enabled = true #[test] fn provider_prompt_uses_catalog_display_name() { - let direct = - Gpt56Profile::new("gpt-5.6-sol").with_route(provider_ids::openai(), test_catalog()); + let direct = Gpt56Profile::new("gpt-5.6-sol").with_route(builtin::openai(), test_catalog()); assert!(prompt(&direct).contains("powered by OpenAI")); let gateway = Gpt56Profile::new("gpt-5.6-sol") @@ -465,7 +463,7 @@ enabled = true #[test] fn catalog_reports_the_5_6_context_window() { let profile = - Gpt56Profile::new("gpt-5.6-sol").with_route(provider_ids::openai(), test_catalog()); + Gpt56Profile::new("gpt-5.6-sol").with_route(builtin::openai(), test_catalog()); assert_eq!(profile.context_window_size(), 1_050_000); } } diff --git a/lib/components/fabro-agent/src/profiles/mod.rs b/lib/components/fabro-agent/src/profiles/mod.rs index 27905fa86..374b265fb 100644 --- a/lib/components/fabro-agent/src/profiles/mod.rs +++ b/lib/components/fabro-agent/src/profiles/mod.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use std::sync::Arc; use fabro_llm::lithos_catalog::Catalog; -#[cfg(test)] -use fabro_types::provider_ids; use fabro_types::{AgentProfileKind, ProviderId}; +#[cfg(test)] +use lithos_llm::catalog::builtin; pub mod anthropic; pub mod claude5; @@ -701,31 +701,23 @@ mod tests { let catalog = Arc::new(test_catalog()); let env = MockSandbox::linux(); let cases = [ - ( - AgentProfileKind::OpenAi, - provider_ids::openai(), - "gpt-5.4-mini", - ), + (AgentProfileKind::OpenAi, builtin::openai(), "gpt-5.4-mini"), ( AgentProfileKind::Anthropic, - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4-5", ), ( AgentProfileKind::Gemini, - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", ), ( AgentProfileKind::Claude5, - provider_ids::anthropic(), + builtin::anthropic(), "claude-sonnet-5", ), - ( - AgentProfileKind::Gpt56, - provider_ids::openai(), - "gpt-5.6-sol", - ), + (AgentProfileKind::Gpt56, builtin::openai(), "gpt-5.6-sol"), ]; for (profile_kind, provider_id, model) in cases { @@ -782,7 +774,7 @@ mod tests { ) { let builder = AgentProfileBuilder::new( profile_kind, - provider_ids::anthropic(), + builtin::anthropic(), model, Arc::new(test_catalog()), ); diff --git a/lib/components/fabro-agent/src/profiles/openai.rs b/lib/components/fabro-agent/src/profiles/openai.rs index 29018cfb5..92085a920 100644 --- a/lib/components/fabro-agent/src/profiles/openai.rs +++ b/lib/components/fabro-agent/src/profiles/openai.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use fabro_llm::lithos_catalog::Catalog; -use fabro_types::{AgentProfileKind, ProviderId, provider_ids}; +use fabro_types::{AgentProfileKind, ProviderId}; +use lithos_llm::catalog::builtin; use super::EnvContext; use crate::agent_profile::AgentProfile; @@ -43,7 +44,7 @@ impl OpenAiProfile { Self { base: BaseProfile { profile_kind: AgentProfileKind::OpenAi, - provider_id: provider_ids::openai(), + provider_id: builtin::openai(), model: model.into(), catalog: None, registry, @@ -117,7 +118,7 @@ mod tests { fn openai_profile_identity() { let profile = OpenAiProfile::new("o3-mini"); assert_eq!(profile.profile_kind(), AgentProfileKind::OpenAi); - assert_eq!(profile.provider_id(), provider_ids::openai()); + assert_eq!(profile.provider_id(), builtin::openai()); assert_eq!(profile.model(), "o3-mini"); } diff --git a/lib/components/fabro-agent/src/session.rs b/lib/components/fabro-agent/src/session.rs index 15500c1fd..484adbdd2 100644 --- a/lib/components/fabro-agent/src/session.rs +++ b/lib/components/fabro-agent/src/session.rs @@ -2250,9 +2250,10 @@ mod tests { use fabro_llm::{ErrorKind, ResponseStream, RetryPolicy}; use fabro_types::{ ContentPart, Cost, CostSource, ReasoningOutput, StageContextWindowCountMethod, - ToolDefinition, provider_ids, text_of, tool_result_to_json, + ToolDefinition, text_of, tool_result_to_json, }; use futures::stream; + use lithos_llm::catalog::builtin; use tokio::time::{sleep, timeout}; use super::*; @@ -2386,7 +2387,7 @@ mod tests { impl ScriptedError { fn build(&self) -> fabro_llm::Error { fabro_llm::Error::new(self.kind.clone(), self.message.clone()) - .with_provider(provider_ids::anthropic()) + .with_provider(builtin::anthropic()) .with_retry(self.retry) } } diff --git a/lib/components/fabro-agent/src/test_support.rs b/lib/components/fabro-agent/src/test_support.rs index bb3820aab..a25200467 100644 --- a/lib/components/fabro-agent/src/test_support.rs +++ b/lib/components/fabro-agent/src/test_support.rs @@ -10,9 +10,8 @@ use fabro_llm::{ Client, ClientOptions, Error as LlmError, FinishReason, Request, Response, ResponseStream, }; pub use fabro_sandbox::test_support::{MockSandbox, MutableMockSandbox}; -use fabro_types::{ - AgentProfileKind, ContentPart, ModelId, ProviderId, TokenCounts, ToolCall, provider_ids, -}; +use fabro_types::{AgentProfileKind, ContentPart, ModelId, ProviderId, TokenCounts, ToolCall}; +use lithos_llm::catalog::builtin; use crate::agent_profile::AgentProfile; use crate::config::SessionOptions; @@ -24,7 +23,7 @@ use crate::skills::{Skill, format_skills_prompt_section}; use crate::tool_registry::{RegisteredTool, ToolRegistry, ToolSource}; /// The provider every test profile routes to. -pub const TEST_PROVIDER: &str = provider_ids::ANTHROPIC; +pub const TEST_PROVIDER: &str = builtin::ids::ANTHROPIC; /// The model every test profile requests. It is not in the catalog, so the /// provider's passthrough route serves it. pub const TEST_MODEL: &str = "mock-model"; @@ -65,7 +64,7 @@ impl AgentProfile for TestProfile { } fn provider_id(&self) -> ProviderId { - provider_ids::anthropic() + builtin::anthropic() } fn model(&self) -> &'static str { diff --git a/lib/components/fabro-agent/src/tools.rs b/lib/components/fabro-agent/src/tools.rs index 7e284954a..fcc746de7 100644 --- a/lib/components/fabro-agent/src/tools.rs +++ b/lib/components/fabro-agent/src/tools.rs @@ -732,7 +732,8 @@ mod tests { use std::collections::HashMap; use fabro_llm::adapter::ProviderAdapter; - use fabro_types::{CommandTermination, ModelId, provider_ids}; + use fabro_types::{CommandTermination, ModelId}; + use lithos_llm::catalog::builtin; use tokio::sync::broadcast; use tokio_util::sync::CancellationToken; @@ -1978,7 +1979,7 @@ mod tests { let client = make_client(provider).await; let summarizer = WebFetchSummarizer { client, - model_id: ModelHandle::new(provider_ids::anthropic(), ModelId::new("mock-model")), + model_id: ModelHandle::new(builtin::anthropic(), ModelId::new("mock-model")), }; let tool = make_web_fetch_tool(Some(summarizer)); @@ -2075,7 +2076,7 @@ mod tests { let summarizer = WebFetchSummarizer { client, - model_id: ModelHandle::new(provider_ids::anthropic(), ModelId::new("target-model")), + model_id: ModelHandle::new(builtin::anthropic(), ModelId::new("target-model")), }; let tool = make_web_fetch_tool(Some(summarizer)); diff --git a/lib/components/fabro-agent/src/types.rs b/lib/components/fabro-agent/src/types.rs index b31458592..1fd5eb458 100644 --- a/lib/components/fabro-agent/src/types.rs +++ b/lib/components/fabro-agent/src/types.rs @@ -817,7 +817,8 @@ pub struct SessionEvent { #[cfg(test)] mod tests { use fabro_llm::{ErrorKind, RetryClassification}; - use fabro_types::{CostSource, ModelId, ProviderId, provider_ids}; + use fabro_types::{CostSource, ModelId, ProviderId}; + use lithos_llm::catalog::builtin; use super::*; @@ -1102,7 +1103,7 @@ mod tests { }; let event = AgentEvent::AssistantMessage { text: "Hello".into(), - model: ModelRef::new(provider_ids::openai(), ModelId::new("test-model")), + model: ModelRef::new(builtin::openai(), ModelId::new("test-model")), usage, cost: Some(Cost { usd_micros: 125_000, diff --git a/lib/components/fabro-agent/tests/it/parity_matrix.rs b/lib/components/fabro-agent/tests/it/parity_matrix.rs index f85b0b5f2..53888a64a 100644 --- a/lib/components/fabro-agent/tests/it/parity_matrix.rs +++ b/lib/components/fabro-agent/tests/it/parity_matrix.rs @@ -18,7 +18,8 @@ use fabro_llm::lithos_catalog::Catalog; use fabro_llm::test_support::client_from_env; use fabro_llm::{Client, ClientOptions, catalog}; use fabro_test::{EnvVars, TwinScenario, TwinScenarios, TwinToolCall, twin_openai}; -use fabro_types::{ModelHandle, ModelId, ProviderId, provider_ids}; +use fabro_types::{ModelHandle, ModelId, ProviderId}; +use lithos_llm::catalog::builtin; type Provider = ProviderId; @@ -30,11 +31,11 @@ struct OpenAiTwinOptions { fn summarizer_model_id(provider: &Provider) -> ModelHandle { let (provider, model) = match provider.as_str() { - provider_ids::OPENAI | "moonshot" | "zai" | "minimax" | "inception" => { - (provider_ids::openai(), "gpt-5.4-mini") + builtin::ids::OPENAI | "moonshot" | "zai" | "minimax" | "inception" => { + (builtin::openai(), "gpt-5.4-mini") } - provider_ids::GEMINI => (provider_ids::gemini(), "gemini-3-flash-preview"), - provider_ids::ANTHROPIC => (provider_ids::anthropic(), "claude-haiku-4.5"), + builtin::ids::GEMINI => (builtin::gemini(), "gemini-3-flash-preview"), + builtin::ids::ANTHROPIC => (builtin::anthropic(), "claude-haiku-4.5"), other => panic!("unexpected provider {other}"), }; ModelHandle::new(provider, ModelId::new(model)) @@ -142,7 +143,7 @@ fn twin_catalog(base_url: &str, overlay: &str) -> Catalog { } async fn make_client(provider: &Provider, twin: Option<&OpenAiTwinOptions>) -> Client { - if provider == &provider_ids::openai() && fabro_test::TestMode::from_env().is_twin() { + if provider == &builtin::openai() && fabro_test::TestMode::from_env().is_twin() { return make_twin_client(twin.expect("openai twin config should be provided")).await; } @@ -261,7 +262,7 @@ macro_rules! openai_twin_provider_test { .await; } let mut session = make_session( - provider_ids::openai(), + builtin::openai(), "gpt-5.4-mini", tmp.path(), ToolSecrets::default(), @@ -278,14 +279,14 @@ macro_rules! provider_tests { ($scenario:ident) => { provider_test!( $scenario, - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4.5", anthropic, keys = ["ANTHROPIC_API_KEY"] ); provider_test!( $scenario, - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", gemini, keys = ["GEMINI_API_KEY"] @@ -406,21 +407,21 @@ provider_tests!(subagent_spawn); provider_test!( web_fetch, - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4-5", anthropic, keys = ["ANTHROPIC_API_KEY"] ); provider_test!( web_fetch, - provider_ids::openai(), + builtin::openai(), "gpt-5.4-mini", openai, keys = ["OPENAI_API_KEY"] ); provider_test!( web_fetch, - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", gemini, keys = ["GEMINI_API_KEY"] @@ -457,19 +458,19 @@ provider_test!( ); web_search_provider_test!( - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4-5", anthropic, keys = ["ANTHROPIC_API_KEY", "BRAVE_SEARCH_API_KEY"] ); web_search_provider_test!( - provider_ids::openai(), + builtin::openai(), "gpt-5.4-mini", openai, keys = ["OPENAI_API_KEY", "BRAVE_SEARCH_API_KEY"] ); web_search_provider_test!( - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", gemini, keys = ["GEMINI_API_KEY", "BRAVE_SEARCH_API_KEY"] @@ -518,14 +519,14 @@ macro_rules! non_openai_provider_tests { ($scenario:ident) => { provider_test!( $scenario, - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4.5", anthropic, keys = ["ANTHROPIC_API_KEY"] ); provider_test!( $scenario, - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", gemini, keys = ["GEMINI_API_KEY"] @@ -800,7 +801,7 @@ macro_rules! reasoning_effort_tests { } reasoning_effort_tests!( - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4.5", anthropic_reasoning_effort, keys = ["ANTHROPIC_API_KEY"] @@ -808,7 +809,7 @@ reasoning_effort_tests!( // gpt-5-mini does not support the reasoning.effort parameter, so no OpenAI // test. reasoning_effort_tests!( - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", gemini_reasoning_effort, keys = ["GEMINI_API_KEY"] @@ -878,19 +879,19 @@ macro_rules! loop_detection_tests { } loop_detection_tests!( - provider_ids::anthropic(), + builtin::anthropic(), "claude-haiku-4-5", anthropic_loop_detection, keys = ["ANTHROPIC_API_KEY"] ); loop_detection_tests!( - provider_ids::openai(), + builtin::openai(), "gpt-5.4-mini", openai_loop_detection, keys = ["OPENAI_API_KEY"] ); loop_detection_tests!( - provider_ids::gemini(), + builtin::gemini(), "gemini-3-flash-preview", gemini_loop_detection, keys = ["GEMINI_API_KEY"] diff --git a/lib/components/fabro-hooks/Cargo.toml b/lib/components/fabro-hooks/Cargo.toml index fe9659c76..183c5a970 100644 --- a/lib/components/fabro-hooks/Cargo.toml +++ b/lib/components/fabro-hooks/Cargo.toml @@ -18,6 +18,7 @@ fabro-auth = { path = "../../foundation/fabro-auth" } fabro-llm = { path = "../fabro-llm" } fabro-redact.workspace = true fabro-types = { path = "../../foundation/fabro-types" } +lithos-llm = { workspace = true, features = ["runtime"] } fabro-util = { path = "../../foundation/fabro-util" } fabro-http.workspace = true serde.workspace = true diff --git a/lib/components/fabro-llm/src/api.rs b/lib/components/fabro-llm/src/api.rs index 965b3046f..d99e86907 100644 --- a/lib/components/fabro-llm/src/api.rs +++ b/lib/components/fabro-llm/src/api.rs @@ -117,7 +117,7 @@ fn saturating_i64(value: u64) -> i64 { #[cfg(test)] mod tests { - use fabro_types::provider_ids; + use lithos_llm::catalog::builtin; use super::*; use crate::test_support::test_catalog; @@ -125,17 +125,17 @@ mod tests { #[test] fn models_are_stamped_with_configured_providers() { let catalog = test_catalog(); - let configured = HashSet::from([provider_ids::openai()]); + let configured = HashSet::from([builtin::openai()]); let models = models(&catalog, &configured); let openai = models .iter() - .find(|model| model.provider == provider_ids::openai()) + .find(|model| model.provider == builtin::openai()) .expect("openai models listed"); assert!(openai.configured); assert!(openai.limits.context_window > 0); let anthropic = models .iter() - .find(|model| model.provider == provider_ids::anthropic()) + .find(|model| model.provider == builtin::anthropic()) .expect("anthropic models listed"); assert!(!anthropic.configured); assert!(models.iter().any(|model| model.default)); @@ -145,12 +145,12 @@ mod tests { fn providers_skip_stand_ins_and_disabled_entries() { let catalog = test_catalog(); let providers = providers(&catalog, &HashSet::new()); - assert!(providers.iter().any(|p| p.id == provider_ids::openai())); + assert!(providers.iter().any(|p| p.id == builtin::openai())); assert!(providers.iter().all(|p| p.id.as_str() != "openai-codex")); assert!(providers.iter().all(|p| p.id.as_str() != "ollama")); let openai = providers .iter() - .find(|p| p.id == provider_ids::openai()) + .find(|p| p.id == builtin::openai()) .unwrap(); assert_eq!( openai.expected_secret_name.as_deref(), diff --git a/lib/components/fabro-llm/src/selection.rs b/lib/components/fabro-llm/src/selection.rs index 65f26e9a0..85e1434c8 100644 --- a/lib/components/fabro-llm/src/selection.rs +++ b/lib/components/fabro-llm/src/selection.rs @@ -276,7 +276,7 @@ fn canonical_eligible(catalog: &Catalog, eligible: &HashSet) -> Hash #[cfg(test)] mod tests { - use fabro_types::provider_ids; + use lithos_llm::catalog::builtin; use super::*; use crate::test_support::{test_catalog, test_catalog_with_overlay}; @@ -291,7 +291,7 @@ mod tests { let selected = resolve_selection(&catalog, Some("sonnet"), None, &eligible(&["anthropic"])).unwrap(); assert_eq!(selected, SelectedModel { - provider: provider_ids::anthropic(), + provider: builtin::anthropic(), model: "claude-sonnet-5".to_string(), }); } @@ -306,7 +306,7 @@ mod tests { &eligible(&["openai", "anthropic"]), ) .unwrap(); - assert_eq!(selected.provider, provider_ids::anthropic()); + assert_eq!(selected.provider, builtin::anthropic()); assert_eq!(selected.model, "totally-new-model"); } @@ -321,7 +321,7 @@ mod tests { ) .unwrap(); assert_eq!(selected, SelectedModel { - provider: provider_ids::openai(), + provider: builtin::openai(), model: "gpt-5.6-sol".to_string(), }); @@ -333,7 +333,7 @@ mod tests { ) .unwrap(); assert_eq!(unknown, SelectedModel { - provider: provider_ids::openai(), + provider: builtin::openai(), model: "brand-new-model".to_string(), }); @@ -346,7 +346,7 @@ mod tests { assert_eq!( unavailable, Err(ModelSelectionError::ProviderUnavailable { - provider: provider_ids::openai(), + provider: builtin::openai(), }) ); } @@ -373,13 +373,13 @@ mod tests { let error = resolve_selection( &catalog, Some("gpt-5.4"), - Some(&provider_ids::openai()), + Some(&builtin::openai()), &eligible(&["anthropic"]), ) .unwrap_err(); assert!(matches!( error, - ModelSelectionError::ProviderUnavailable { provider } if provider == provider_ids::openai() + ModelSelectionError::ProviderUnavailable { provider } if provider == builtin::openai() )); } @@ -389,11 +389,11 @@ mod tests { let selected = resolve_selection_with_catalog_fallback( &catalog, Some("gpt-5.4"), - Some(&provider_ids::openai()), + Some(&builtin::openai()), &eligible(&["anthropic"]), ) .unwrap(); - assert_eq!(selected.provider, provider_ids::openai()); + assert_eq!(selected.provider, builtin::openai()); let error = resolve_selection_with_catalog_fallback( &catalog, None, diff --git a/lib/components/fabro-workflow/Cargo.toml b/lib/components/fabro-workflow/Cargo.toml index 388078b5b..7d31fa61b 100644 --- a/lib/components/fabro-workflow/Cargo.toml +++ b/lib/components/fabro-workflow/Cargo.toml @@ -43,6 +43,7 @@ fabro-core = { path = "../../foundation/fabro-core" } fabro-store = { path = "../fabro-store" } fabro-static.workspace = true fabro-types = { path = "../../foundation/fabro-types" } +lithos-llm = { workspace = true, features = ["runtime"] } fabro-http.workspace = true thiserror.workspace = true strum.workspace = true diff --git a/lib/components/fabro-workflow/src/billing_rollup.rs b/lib/components/fabro-workflow/src/billing_rollup.rs index 9414a96a3..9d166ed08 100644 --- a/lib/components/fabro-workflow/src/billing_rollup.rs +++ b/lib/components/fabro-workflow/src/billing_rollup.rs @@ -7,8 +7,9 @@ pub use fabro_types::billing_rollup::{ mod tests { use fabro_types::{ AttrValue, BilledTokenCounts, Graph, ModelId, ModelRef, Node, RunProjection, RunSpec, - StageCompletion, StageOutcome, first_event_seq, provider_ids, test_support, + StageCompletion, StageOutcome, first_event_seq, test_support, }; + use lithos_llm::catalog::builtin; use super::billing_rollup_from_projection; use crate::test_support::test_usage; @@ -134,7 +135,7 @@ mod tests { #[test] fn rollup_keeps_in_flight_stage_usage_unpriced() { let mut projection = test_projection(); - let model = ModelRef::new(provider_ids::openai(), ModelId::new("gpt-5.4")); + let model = ModelRef::new(builtin::openai(), ModelId::new("gpt-5.4")); let stage = projection.stage_entry("agent", 1, first_event_seq(1)); stage.started_at = Some(chrono::Utc::now()); stage.usage = BilledTokenCounts { diff --git a/lib/components/fabro-workflow/src/error.rs b/lib/components/fabro-workflow/src/error.rs index 552c5da33..319987070 100644 --- a/lib/components/fabro-workflow/src/error.rs +++ b/lib/components/fabro-workflow/src/error.rs @@ -751,7 +751,8 @@ mod tests { /// A stored LLM error of `kind` from the `openai` provider. fn sdk_error(kind: ErrorKind, message: &str) -> ErrorData { ErrorData::from( - fabro_llm::Error::new(kind, message).with_provider(fabro_types::provider_ids::openai()), + fabro_llm::Error::new(kind, message) + .with_provider(lithos_llm::catalog::builtin::openai()), ) } @@ -759,7 +760,7 @@ mod tests { fn transient_error(kind: ErrorKind, message: &str) -> ErrorData { ErrorData::from( fabro_llm::Error::new(kind, message) - .with_provider(fabro_types::provider_ids::openai()) + .with_provider(lithos_llm::catalog::builtin::openai()) .with_retry(RetryClassification::Safe), ) } diff --git a/lib/components/fabro-workflow/src/event/convert.rs b/lib/components/fabro-workflow/src/event/convert.rs index da235b8ac..906d3fbb5 100644 --- a/lib/components/fabro-workflow/src/event/convert.rs +++ b/lib/components/fabro-workflow/src/event/convert.rs @@ -1463,14 +1463,14 @@ mod tests { use ::fabro_types::{ AutomationRef, EventBody, FailureReason, ModelId, ModelRef, ParallelBranchId, Principal, ProviderId, RunNoticeCode, RunNoticeLevel, RunProvenance, StageId, SystemActorKind, - TokenCounts as LlmTokenCounts, fixtures, provider_ids, run_event as fabro_types, - test_support, + TokenCounts as LlmTokenCounts, fixtures, run_event as fabro_types, test_support, }; use chrono::Utc; use fabro_agent::{ AgentEvent, McpToolSummary, MemoryFileSummary, SandboxEvent, SkillActivationSource, SkillSummary, }; + use lithos_llm::catalog::builtin; use super::*; use crate::error::Error; @@ -2522,10 +2522,7 @@ mod tests { visit: 1, event: AgentEvent::AssistantMessage { text: "ok".to_string(), - model: ModelRef::new( - provider_ids::anthropic(), - ModelId::new("claude-sonnet"), - ), + model: ModelRef::new(builtin::anthropic(), ModelId::new("claude-sonnet")), usage: LlmTokenCounts::default(), cost: None, tool_call_count: 0, @@ -2644,7 +2641,7 @@ mod tests { visit: 1, event: AgentEvent::AssistantMessage { text: "ok".to_string(), - model: ModelRef::new(provider_ids::openai(), ModelId::new("gpt-5.4")), + model: ModelRef::new(builtin::openai(), ModelId::new("gpt-5.4")), usage: LlmTokenCounts::default(), cost: None, tool_call_count: 0, @@ -2674,7 +2671,7 @@ mod tests { visit: 1, event: AgentEvent::AssistantMessage { text: String::new(), - model: ModelRef::new(provider_ids::openai(), ModelId::new("gpt-5.4")), + model: ModelRef::new(builtin::openai(), ModelId::new("gpt-5.4")), usage: LlmTokenCounts::default(), cost: None, tool_call_count: 1, diff --git a/lib/components/fabro-workflow/src/event/redaction.rs b/lib/components/fabro-workflow/src/event/redaction.rs index 9cfe29ee7..689f7868d 100644 --- a/lib/components/fabro-workflow/src/event/redaction.rs +++ b/lib/components/fabro-workflow/src/event/redaction.rs @@ -31,10 +31,11 @@ pub fn event_payload_from_redacted_json(line: &str, run_id: &RunId) -> Result ProviderId { - provider_ids::openai() + builtin::openai() } fn model(&self) -> &str { @@ -2250,20 +2251,20 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn agent_backend_stores_config() { let backend = AgentApiBackend::new( "claude-opus-4-6".to_string(), - provider_ids::openai(), + builtin::openai(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), ); assert_eq!(backend.model, "claude-opus-4-6"); - assert_eq!(backend.provider_id, provider_ids::openai()); + assert_eq!(backend.provider_id, builtin::openai()); } #[test] fn agent_backend_initializes_empty_sessions() { let backend = AgentApiBackend::new( "claude-opus-4-6".to_string(), - provider_ids::anthropic(), + builtin::anthropic(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -2952,7 +2953,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn build_profile_can_register_subagent_tools() { let mut profile = AgentProfileBuilder::new( AgentProfileKind::Anthropic, - provider_ids::anthropic(), + builtin::anthropic(), "claude-opus-4-6", Arc::new(test_catalog()), ) @@ -3130,7 +3131,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = .resolve_provider_context("gpt-5.4", Some("openai")) .unwrap(); - assert_eq!(provider.provider_id, provider_ids::openai()); + assert_eq!(provider.provider_id, builtin::openai()); } #[test] @@ -3175,7 +3176,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn api_backend_selects_claude5_profile_for_sonnet5() { let backend = AgentApiBackend::new_with_catalog( "claude-sonnet-5".to_string(), - provider_ids::anthropic(), + builtin::anthropic(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3186,7 +3187,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = .resolve_provider_context("claude-sonnet-5", None) .unwrap(); - assert_eq!(provider.provider_id, provider_ids::anthropic()); + assert_eq!(provider.provider_id, builtin::anthropic()); assert_eq!(provider.profile_kind, AgentProfileKind::Claude5); } @@ -3214,7 +3215,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn run_model_controls_apply_when_node_omits_controls() { let backend = AgentApiBackend::new( "gpt-5.4".to_string(), - provider_ids::openai(), + builtin::openai(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3235,7 +3236,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn node_controls_override_run_model_controls() { let backend = AgentApiBackend::new( "gpt-5.4".to_string(), - provider_ids::openai(), + builtin::openai(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3264,7 +3265,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn omitted_reasoning_effort_stays_unset() { let backend = AgentApiBackend::new( "gpt-5.4".to_string(), - provider_ids::openai(), + builtin::openai(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3332,7 +3333,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = ])); let backend = AgentApiBackend::new_with_catalog( "claude-fable-5".to_string(), - provider_ids::anthropic(), + builtin::anthropic(), policy, auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3340,7 +3341,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = ); let (mut plan, notices) = backend.fallback_plan( "claude-fable-5", - &provider_ids::anthropic(), + &builtin::anthropic(), EffectiveRequestControls::default(), ); @@ -3375,7 +3376,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = .unwrap(); let backend = AgentApiBackend::new( "claude-opus-4-6".to_string(), - provider_ids::anthropic(), + builtin::anthropic(), ModelFallbackPolicy::default(), Arc::new(VaultCredentialSource::with_env_lookup( Arc::new(AsyncRwLock::new(vault)), @@ -3390,7 +3391,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = assert_eq!( client.available_providers().iter().collect::>(), - vec![&provider_ids::anthropic()] + vec![&builtin::anthropic()] ); } @@ -3403,7 +3404,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = )])); let backend = AgentApiBackend::new( "claude-fable-5".to_string(), - provider_ids::anthropic(), + builtin::anthropic(), fallback_policy, auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3440,7 +3441,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = .unwrap(); let (mut fallback_plan, notices) = backend.fallback_plan( "claude-fable-5", - &provider_ids::anthropic(), + &builtin::anthropic(), EffectiveRequestControls::default(), ); assert!(notices.is_empty()); @@ -3458,7 +3459,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = .unwrap(); assert_eq!(completion.response.text(), "fallback ok"); - assert_eq!(completion.model.provider, provider_ids::openai()); + assert_eq!(completion.model.provider, builtin::openai()); assert_eq!(completion.model.model_id.as_str(), "gpt-5.5"); let failover = emitted_failover .lock() @@ -3907,7 +3908,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = async fn api_backend_shutdown_closes_cached_sessions_once() { let backend = AgentApiBackend::new( "gpt-5.4".to_string(), - provider_ids::openai(), + builtin::openai(), ModelFallbackPolicy::default(), auth_test_support::vault_only_credential_source(), SteeringHub::for_tests(), @@ -3940,7 +3941,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = ); let (fallback_plan, notices) = backend.fallback_plan( "gpt-5.4", - &provider_ids::openai(), + &builtin::openai(), EffectiveRequestControls::default(), ); assert!(notices.is_empty()); @@ -4032,7 +4033,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn failover_eligible_llm_error() -> ErrorData { ErrorData::from( fabro_llm::Error::new(ErrorKind::Network, "boom") - .with_provider(provider_ids::openai()) + .with_provider(builtin::openai()) .with_retry(RetryClassification::Safe), ) } @@ -4040,7 +4041,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = fn non_failover_llm_error() -> ErrorData { ErrorData::from( fabro_llm::Error::new(ErrorKind::InvalidRequest, "bad key") - .with_provider(provider_ids::openai()) + .with_provider(builtin::openai()) .with_status(401), ) } @@ -4050,7 +4051,7 @@ capabilities = {{ text = true, tools = true, response_format = {{ json_object = ErrorKind::ContentFilter, "claude-fable-5 refused the request", ) - .with_provider(provider_ids::anthropic()) + .with_provider(builtin::anthropic()) .with_provider_code("refusal") .with_raw_data(serde_json::json!({ "stop_reason": "refusal", diff --git a/lib/components/fabro-workflow/src/handler/llm/preamble.rs b/lib/components/fabro-workflow/src/handler/llm/preamble.rs index 5f812e533..9042f7a4a 100644 --- a/lib/components/fabro-workflow/src/handler/llm/preamble.rs +++ b/lib/components/fabro-workflow/src/handler/llm/preamble.rs @@ -589,7 +589,8 @@ fn build_summary_preamble( #[cfg(test)] mod tests { use fabro_graphviz::graph::AttrValue; - use fabro_types::{ModelId, ModelRef, TokenCounts, provider_ids}; + use fabro_types::{ModelId, ModelRef, TokenCounts}; + use lithos_llm::catalog::builtin; use super::*; use crate::outcome::{BilledModelUsage, billed_model_usage_from_llm}; @@ -597,7 +598,7 @@ mod tests { fn stage_usage(model: &str, input: u64, output: u64) -> BilledModelUsage { billed_model_usage_from_llm( &fabro_llm::test_support::test_catalog(), - &ModelRef::new(provider_ids::anthropic(), ModelId::new(model)), + &ModelRef::new(builtin::anthropic(), ModelId::new(model)), TokenCounts { input, output, diff --git a/lib/components/fabro-workflow/src/lifecycle/git.rs b/lib/components/fabro-workflow/src/lifecycle/git.rs index 509f1a397..6c05fb46f 100644 --- a/lib/components/fabro-workflow/src/lifecycle/git.rs +++ b/lib/components/fabro-workflow/src/lifecycle/git.rs @@ -1306,7 +1306,7 @@ mod tests { None, finalize_locations, tokio_util::sync::CancellationToken::new(), - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-sonnet-4-6".to_string(), auth_test_support::vault_only_credential_source(), Arc::new(fabro_llm::test_support::test_catalog()), diff --git a/lib/components/fabro-workflow/src/operations/create.rs b/lib/components/fabro-workflow/src/operations/create.rs index 196100033..8a7a6d383 100644 --- a/lib/components/fabro-workflow/src/operations/create.rs +++ b/lib/components/fabro-workflow/src/operations/create.rs @@ -688,9 +688,10 @@ mod tests { use fabro_store::Database; use fabro_types::settings::InterpString; use fabro_types::settings::run::RunMode; - use fabro_types::{EventBody, WorkflowSettings, fixtures, provider_ids, test_support}; + use fabro_types::{EventBody, WorkflowSettings, fixtures, test_support}; use fabro_util::error::collect_chain; use fabro_validate::Severity; + use lithos_llm::catalog::builtin; use object_store::local::LocalFileSystem; use object_store::memory::InMemory; @@ -2028,19 +2029,19 @@ mod tests { }"#; let catalog = portable_model_catalog(); let cases = [ - (vec![provider_ids::openai()], None, provider_ids::openai()), + (vec![builtin::openai()], None, builtin::openai()), ( vec![ProviderId::new("openrouter")], None, ProviderId::new("openrouter"), ), ( - vec![provider_ids::openai(), ProviderId::new("openrouter")], + vec![builtin::openai(), ProviderId::new("openrouter")], None, - provider_ids::openai(), + builtin::openai(), ), ( - vec![provider_ids::openai(), ProviderId::new("openrouter")], + vec![builtin::openai(), ProviderId::new("openrouter")], Some("openrouter"), ProviderId::new("openrouter"), ), diff --git a/lib/components/fabro-workflow/src/operations/start.rs b/lib/components/fabro-workflow/src/operations/start.rs index 78f7691b6..7f03e6ffe 100644 --- a/lib/components/fabro-workflow/src/operations/start.rs +++ b/lib/components/fabro-workflow/src/operations/start.rs @@ -1310,9 +1310,10 @@ mod tests { }; use fabro_types::{ BilledModelUsage, GitContext, ManifestPath, RunTarget, StageTiming, WorkflowSettings, - fixtures, provider_ids, test_support, + fixtures, test_support, }; use fabro_vault::SecretType; + use lithos_llm::catalog::builtin; use object_store::memory::InMemory; use super::*; @@ -1475,7 +1476,7 @@ mod tests { error, Error::ModelSelection(fabro_llm::ModelSelectionError::ProviderUnavailable { provider - }) if provider == provider_ids::openai() + }) if provider == builtin::openai() )); } diff --git a/lib/components/fabro-workflow/src/outcome.rs b/lib/components/fabro-workflow/src/outcome.rs index 2025bab23..c3f9135d5 100644 --- a/lib/components/fabro-workflow/src/outcome.rs +++ b/lib/components/fabro-workflow/src/outcome.rs @@ -125,7 +125,8 @@ pub fn format_cost(cost: f64) -> String { mod tests { use fabro_llm::lithos_catalog::Catalog; use fabro_llm::test_support::{test_catalog, test_catalog_with_overlay}; - use fabro_types::{ModelId, ModelRef, ProviderId, Speed, TokenCounts, UsdMicros, provider_ids}; + use fabro_types::{ModelId, ModelRef, ProviderId, Speed, TokenCounts, UsdMicros}; + use lithos_llm::catalog::builtin; use super::{OutcomeExt, billed_model_usage_from_llm}; @@ -149,7 +150,7 @@ mod tests { }; let billed = billed_model_usage_from_llm( &catalog(), - &model_ref(provider_ids::openai(), "gpt-5.4", None), + &model_ref(builtin::openai(), "gpt-5.4", None), usage, ) .unwrap(); @@ -169,7 +170,7 @@ mod tests { }; let billed = billed_model_usage_from_llm( &catalog(), - &model_ref(provider_ids::openai(), "gpt-5.4", None), + &model_ref(builtin::openai(), "gpt-5.4", None), usage, ) .unwrap() @@ -199,11 +200,7 @@ mod tests { }; let billed = billed_model_usage_from_llm( &catalog(), - &model_ref( - provider_ids::anthropic(), - "claude-opus-5", - Some(Speed::Fast), - ), + &model_ref(builtin::anthropic(), "claude-opus-5", Some(Speed::Fast)), usage, ) .unwrap(); @@ -253,7 +250,7 @@ pricing = { input_usd_micros_per_million = 1000000, output_usd_micros_per_millio fn passthrough_model_on_known_provider_has_no_cost() { let billed = billed_model_usage_from_llm( &catalog(), - &model_ref(provider_ids::openai(), "brand-new-model", None), + &model_ref(builtin::openai(), "brand-new-model", None), TokenCounts { input: 10, output: 5, diff --git a/lib/components/fabro-workflow/src/pipeline/execute/tests.rs b/lib/components/fabro-workflow/src/pipeline/execute/tests.rs index ec254b3d2..426cf15a4 100644 --- a/lib/components/fabro-workflow/src/pipeline/execute/tests.rs +++ b/lib/components/fabro-workflow/src/pipeline/execute/tests.rs @@ -265,7 +265,7 @@ async fn execute_test_run_with_options( }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -325,7 +325,7 @@ async fn execute_runs_start_to_exit_and_returns_final_context() { }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -466,7 +466,7 @@ async fn resumed_in_flight_node_starts_a_new_stage_execution() { }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -580,7 +580,7 @@ async fn run_with_lifecycle( }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), diff --git a/lib/components/fabro-workflow/src/pipeline/finalize.rs b/lib/components/fabro-workflow/src/pipeline/finalize.rs index bc1ec1860..9d40f7520 100644 --- a/lib/components/fabro-workflow/src/pipeline/finalize.rs +++ b/lib/components/fabro-workflow/src/pipeline/finalize.rs @@ -996,7 +996,7 @@ mod tests { None, locations, tokio_util::sync::CancellationToken::new(), - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-sonnet-4-6".to_string(), auth_test_support::vault_only_credential_source(), Arc::new(fabro_llm::test_support::test_catalog()), @@ -1029,7 +1029,7 @@ mod tests { None, locations, tokio_util::sync::CancellationToken::new(), - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-sonnet-4-6".to_string(), auth_test_support::vault_only_credential_source(), Arc::new(fabro_llm::test_support::test_catalog()), diff --git a/lib/components/fabro-workflow/src/pipeline/initialize.rs b/lib/components/fabro-workflow/src/pipeline/initialize.rs index d4effcf2e..79bde9850 100644 --- a/lib/components/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/components/fabro-workflow/src/pipeline/initialize.rs @@ -901,7 +901,7 @@ mod tests { sandbox: SandboxSpec::Local { working_directory }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -1091,7 +1091,7 @@ mod tests { assert_eq!(initialized.model, "test-model"); assert_eq!( initialized.engine.run.provider_id, - fabro_types::provider_ids::anthropic() + lithos_llm::catalog::builtin::anthropic() ); assert!( readiness( @@ -1217,7 +1217,7 @@ mod tests { let (_registry, effective_dry_run) = build_registry( &LlmSpec { model: "claude-opus-4-6".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -1344,7 +1344,7 @@ mod tests { }, llm: LlmSpec { model: "fake-acp".to_string(), - provider_id: fabro_types::provider_ids::openai(), + provider_id: lithos_llm::catalog::builtin::openai(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -1447,7 +1447,7 @@ mod tests { }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), @@ -1589,7 +1589,7 @@ mod tests { }, llm: LlmSpec { model: "test-model".to_string(), - provider_id: fabro_types::provider_ids::anthropic(), + provider_id: lithos_llm::catalog::builtin::anthropic(), fallbacks: ModelFallbackPolicy::default(), mcp_servers: Vec::new(), model_controls: RunModelControls::default(), diff --git a/lib/components/fabro-workflow/src/services.rs b/lib/components/fabro-workflow/src/services.rs index 2456b56fb..3bdf9c504 100644 --- a/lib/components/fabro-workflow/src/services.rs +++ b/lib/components/fabro-workflow/src/services.rs @@ -320,7 +320,7 @@ impl EngineServices { None, locations, CancellationToken::new(), - fabro_types::provider_ids::anthropic(), + lithos_llm::catalog::builtin::anthropic(), "claude-sonnet-4.6".to_string(), Arc::new(StubCredentialSource), Arc::new(fabro_llm::default_catalog()), diff --git a/lib/components/fabro-workflow/src/test_support.rs b/lib/components/fabro-workflow/src/test_support.rs index 47410b8db..680ad5e50 100644 --- a/lib/components/fabro-workflow/src/test_support.rs +++ b/lib/components/fabro-workflow/src/test_support.rs @@ -14,7 +14,8 @@ use fabro_llm::test_support::test_catalog; use fabro_store::{ArtifactStore, RunProjection, test_support as store_test_support}; #[cfg(feature = "test-support")] use fabro_types::ProviderId; -use fabro_types::{ModelId, ModelRef, provider_ids}; +use fabro_types::{ModelId, ModelRef}; +use lithos_llm::catalog::builtin; use object_store::local::LocalFileSystem; use crate::artifact_upload::ArtifactSink; @@ -89,7 +90,7 @@ pub fn test_usage( output_tokens: u64, ) -> fabro_types::BilledModelUsage { let mut usage = fabro_types::BilledModelUsage::new( - ModelRef::new(provider_ids::openai(), ModelId::new(model_id)), + ModelRef::new(builtin::openai(), ModelId::new(model_id)), fabro_types::TokenCounts { input: input_tokens, output: output_tokens, @@ -269,7 +270,7 @@ async fn initialized( options.hook_runner, locations, run_options.cancel_token.clone(), - provider_ids::anthropic(), + builtin::anthropic(), "claude-sonnet-4-6".to_string(), options .llm_source diff --git a/lib/components/fabro-workflow/tests/materialize_run.rs b/lib/components/fabro-workflow/tests/materialize_run.rs index be8a28522..04939cb0a 100644 --- a/lib/components/fabro-workflow/tests/materialize_run.rs +++ b/lib/components/fabro-workflow/tests/materialize_run.rs @@ -1,10 +1,11 @@ use fabro_graphviz::graph::Graph; use fabro_graphviz::parser; use fabro_llm::test_support::test_catalog; +use fabro_types::WorkflowSettings; use fabro_types::settings::InterpString; use fabro_types::settings::run::{PullRequestSettings, RunGoal, RunModelSettings, RunNamespace}; -use fabro_types::{WorkflowSettings, provider_ids}; use fabro_workflow::run_materialization::materialize_run; +use lithos_llm::catalog::builtin; fn graph(source: &str) -> Graph { parser::parse(source).expect("graph should parse") @@ -35,7 +36,7 @@ fn materialize_run_applies_graph_and_catalog_defaults() { }; let materialized = materialize_run(settings, &graph(source), &test_catalog(), &[ - provider_ids::anthropic(), + builtin::anthropic(), ]) .unwrap(); let resolved = &materialized.run; @@ -62,7 +63,7 @@ fn materialize_run_uses_configured_provider_defaults() { WorkflowSettings::default(), &graph(source), &test_catalog(), - &[provider_ids::openai()], + &[builtin::openai()], ) .unwrap(); let resolved = &materialized.run; diff --git a/lib/foundation/fabro-api/Cargo.toml b/lib/foundation/fabro-api/Cargo.toml index 1930ef92f..02f85c3df 100644 --- a/lib/foundation/fabro-api/Cargo.toml +++ b/lib/foundation/fabro-api/Cargo.toml @@ -19,6 +19,7 @@ fabro-automation = { path = "../../components/fabro-automation" } fabro-config = { path = "../fabro-config" } fabro-environment.workspace = true fabro-types = { path = "../fabro-types" } +lithos-llm = { workspace = true, features = ["runtime"] } progenitor-client = "0.13" regress = "0.10" reqwest.workspace = true diff --git a/lib/foundation/fabro-api/tests/model_round_trip.rs b/lib/foundation/fabro-api/tests/model_round_trip.rs index 0a18443f1..edde1b7db 100644 --- a/lib/foundation/fabro-api/tests/model_round_trip.rs +++ b/lib/foundation/fabro-api/tests/model_round_trip.rs @@ -1,9 +1,8 @@ use std::any::{TypeId, type_name}; use fabro_api::types::{Model as ApiModel, ModelControls as ApiModelControls}; -use fabro_types::{ - Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffort, provider_ids, -}; +use fabro_types::{Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffort}; +use lithos_llm::catalog::builtin; #[test] fn model_reuses_canonical_type() { @@ -15,7 +14,7 @@ fn model_reuses_canonical_type() { fn model_json_matches_openapi_shape() { let model = Model { id: "claude-opus-4.7".into(), - provider: provider_ids::anthropic(), + provider: builtin::anthropic(), family: "claude-4".to_string(), display_name: "Claude Opus 4.7".to_string(), limits: ModelLimits { diff --git a/lib/foundation/fabro-api/tests/provider_id_round_trip.rs b/lib/foundation/fabro-api/tests/provider_id_round_trip.rs index 877cc39ef..8b03868eb 100644 --- a/lib/foundation/fabro-api/tests/provider_id_round_trip.rs +++ b/lib/foundation/fabro-api/tests/provider_id_round_trip.rs @@ -1,7 +1,8 @@ use std::any::{TypeId, type_name}; use fabro_api::types::{ModelHandle as ApiModelHandle, ProviderId as ApiProviderId}; -use fabro_types::{ModelHandle, ModelId, ProviderId, provider_ids}; +use fabro_types::{ModelHandle, ModelId, ProviderId}; +use lithos_llm::catalog::builtin; use serde_json::json; #[test] @@ -13,7 +14,7 @@ fn provider_id_and_model_handle_reuse_lithos_types() { #[test] fn provider_id_json_is_a_bare_string() { assert_eq!( - serde_json::to_value(provider_ids::anthropic()).unwrap(), + serde_json::to_value(builtin::anthropic()).unwrap(), json!("anthropic") ); assert_eq!( @@ -24,7 +25,7 @@ fn provider_id_json_is_a_bare_string() { #[test] fn model_handle_json_matches_openapi_shape() { - let handle = ModelHandle::new(provider_ids::openai(), ModelId::new("gpt-5.4")); + let handle = ModelHandle::new(builtin::openai(), ModelId::new("gpt-5.4")); let json = serde_json::to_value(&handle).unwrap(); assert_eq!(json, json!({"provider": "openai", "model": "gpt-5.4"})); let round_trip: ApiModelHandle = serde_json::from_value(json).unwrap(); diff --git a/lib/foundation/fabro-api/tests/provider_round_trip.rs b/lib/foundation/fabro-api/tests/provider_round_trip.rs index e15492123..330b50278 100644 --- a/lib/foundation/fabro-api/tests/provider_round_trip.rs +++ b/lib/foundation/fabro-api/tests/provider_round_trip.rs @@ -1,7 +1,8 @@ use std::any::{TypeId, type_name}; use fabro_api::types::Provider as ApiProvider; -use fabro_types::{Provider, ProviderId, provider_ids}; +use fabro_types::{Provider, ProviderId}; +use lithos_llm::catalog::builtin; #[test] fn provider_reuses_canonical_type() { @@ -11,7 +12,7 @@ fn provider_reuses_canonical_type() { #[test] fn provider_json_matches_openapi_shape() { let provider = Provider { - id: provider_ids::anthropic(), + id: builtin::anthropic(), display_name: "Anthropic".to_string(), adapter: "anthropic".to_string(), base_url: "https://api.anthropic.test".to_string(), diff --git a/lib/foundation/fabro-api/tests/session_contract_round_trip.rs b/lib/foundation/fabro-api/tests/session_contract_round_trip.rs index 0afb3c683..044e8a0b6 100644 --- a/lib/foundation/fabro-api/tests/session_contract_round_trip.rs +++ b/lib/foundation/fabro-api/tests/session_contract_round_trip.rs @@ -33,7 +33,7 @@ fn session_detail_round_trips_messages_active_turn_and_last_seq() { title: Some("Ask Fabro".to_string()), status: SessionStatus::Running, model: Some("gpt-5.4".to_string()), - provider: Some(fabro_types::provider_ids::openai()), + provider: Some(lithos_llm::catalog::builtin::openai()), active_turn: Some(SessionTurn { id: turn_id, started_at: turn_started_at, diff --git a/lib/foundation/fabro-auth/src/secrets.rs b/lib/foundation/fabro-auth/src/secrets.rs index 3661f660f..09d0a7d17 100644 --- a/lib/foundation/fabro-auth/src/secrets.rs +++ b/lib/foundation/fabro-auth/src/secrets.rs @@ -6,8 +6,7 @@ //! vault under those same names, so the vault entry an operator creates and //! the environment variable a shell exports are spelled alike. -use fabro_types::provider_ids; -use lithos_llm::catalog::{AuthScheme, CatalogProvider, ProviderId}; +use lithos_llm::catalog::{AuthScheme, CatalogProvider, ProviderId, builtin}; use lithos_llm::credentials::ConventionalCredentials; use crate::OPENAI_CODEX_VAULT_SECRET_NAME; @@ -43,5 +42,5 @@ pub fn accepts_api_key(provider: &CatalogProvider) -> bool { /// The vault entry holding `provider`'s OAuth credential, for the providers /// Fabro can log into with a browser flow. pub(crate) fn oauth_secret_name(provider: &ProviderId) -> Option<&'static str> { - (provider.as_str() == provider_ids::OPENAI_CODEX).then_some(OPENAI_CODEX_VAULT_SECRET_NAME) + (provider.as_str() == builtin::ids::OPENAI_CODEX).then_some(OPENAI_CODEX_VAULT_SECRET_NAME) } diff --git a/lib/foundation/fabro-auth/src/strategies/codex_device.rs b/lib/foundation/fabro-auth/src/strategies/codex_device.rs index 47681b2e1..005a1b8a3 100644 --- a/lib/foundation/fabro-auth/src/strategies/codex_device.rs +++ b/lib/foundation/fabro-auth/src/strategies/codex_device.rs @@ -5,7 +5,7 @@ use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use chrono::{DateTime, Utc}; use fabro_http::HttpClient; -use fabro_types::provider_ids; +use lithos_llm::catalog::builtin; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::time::sleep; @@ -299,7 +299,7 @@ impl AuthStrategy for CodexDeviceStrategy { .map_err(anyhow::Error::msg)?; Ok(LoginResult::OAuth { - provider: provider_ids::openai(), + provider: builtin::openai(), credential: OAuthCredential { tokens: OAuthTokens { access_token: token_response.access_token, diff --git a/lib/foundation/fabro-auth/src/strategy.rs b/lib/foundation/fabro-auth/src/strategy.rs index 4c91b2486..8db2fbf22 100644 --- a/lib/foundation/fabro-auth/src/strategy.rs +++ b/lib/foundation/fabro-auth/src/strategy.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; -use fabro_types::provider_ids; -use lithos_llm::catalog::{Catalog, ProviderId}; +use lithos_llm::catalog::{Catalog, ProviderId, builtin}; use crate::context::{AuthContextRequest, AuthContextResponse}; use crate::credential::{OAuthConfig, OAuthCredential}; @@ -74,7 +73,7 @@ pub fn strategy_for( // forgets the constraint. assert_eq!( provider_id.as_str(), - provider_ids::OPENAI, + builtin::ids::OPENAI, "CodexDevice auth is only constructed by CLI code for the \ OpenAI provider; all existing call sites enforce this pairing: \ got provider_id={provider_id}" diff --git a/lib/foundation/fabro-auth/src/vault_source.rs b/lib/foundation/fabro-auth/src/vault_source.rs index c1bef4db0..7f211d7d3 100644 --- a/lib/foundation/fabro-auth/src/vault_source.rs +++ b/lib/foundation/fabro-auth/src/vault_source.rs @@ -19,10 +19,9 @@ use std::sync::Arc; use async_trait::async_trait; use fabro_static::EnvVars; -use fabro_types::provider_ids; use fabro_types::settings::{InterpString, ResolveCtx}; use fabro_vault::{SecretType, Vault}; -use lithos_llm::catalog::{CatalogProvider, ProviderId}; +use lithos_llm::catalog::{CatalogProvider, ProviderId, builtin}; use lithos_llm::credentials::{ ConventionalCredentials, CredentialError, CredentialHeader, CredentialProvider, Credentials, HttpAuthentication, HttpCredentials, SecretValue, @@ -184,7 +183,7 @@ impl VaultCredentialSource { ) -> Credentials { if let Credentials::Http(http) = &mut credentials { http.extra_headers.extend(interpolated); - if provider.id().as_str() == provider_ids::OPENAI { + if provider.id().as_str() == builtin::ids::OPENAI { for (variable, header) in [ (EnvVars::OPENAI_ORG_ID, OPENAI_ORGANIZATION_HEADER), (EnvVars::OPENAI_PROJECT_ID, OPENAI_PROJECT_HEADER), diff --git a/lib/foundation/fabro-types/src/lib.rs b/lib/foundation/fabro-types/src/lib.rs index ce95b8e18..5f42b0798 100644 --- a/lib/foundation/fabro-types/src/lib.rs +++ b/lib/foundation/fabro-types/src/lib.rs @@ -27,7 +27,6 @@ pub mod outcome; pub mod pair; pub mod parallel; pub mod principal; -pub mod provider_ids; pub mod pull_request; pub mod repository; pub mod run; diff --git a/lib/foundation/fabro-types/src/provider_ids.rs b/lib/foundation/fabro-types/src/provider_ids.rs deleted file mode 100644 index e3ab92142..000000000 --- a/lib/foundation/fabro-types/src/provider_ids.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Well-known provider identifiers. -//! -//! Provider identity is open-ended catalog data, so [`ProviderId`] is a plain -//! string newtype. The first-party providers are named here because code -//! paths such as Codex login and the install flow refer to them directly. - -use lithos_llm::catalog::ProviderId; - -pub const ANTHROPIC: &str = "anthropic"; -pub const OPENAI: &str = "openai"; -/// The ChatGPT-subscription deployment that stands in for [`OPENAI`] when a -/// Codex OAuth credential is present. -pub const OPENAI_CODEX: &str = "openai-codex"; -pub const GEMINI: &str = "gemini"; - -#[must_use] -pub fn anthropic() -> ProviderId { - ProviderId::new(ANTHROPIC) -} - -#[must_use] -pub fn openai() -> ProviderId { - ProviderId::new(OPENAI) -} - -#[must_use] -pub fn openai_codex() -> ProviderId { - ProviderId::new(OPENAI_CODEX) -} - -#[must_use] -pub fn gemini() -> ProviderId { - ProviderId::new(GEMINI) -} diff --git a/lib/foundation/fabro-types/src/run_event/agent.rs b/lib/foundation/fabro-types/src/run_event/agent.rs index b06f21e08..639774573 100644 --- a/lib/foundation/fabro-types/src/run_event/agent.rs +++ b/lib/foundation/fabro-types/src/run_event/agent.rs @@ -523,16 +523,16 @@ pub struct AgentSkillActivatedProps { #[cfg(test)] mod tests { + use lithos_llm::catalog::builtin; use serde_json::json; use super::*; - use crate::provider_ids; use crate::transcript::{ ContentPart, MessageKind, MessageSource, TranscriptMessage, tool_result_from_json, }; fn sample_model_ref() -> ModelRef { - ModelRef::new(provider_ids::openai(), "gpt-5".into()) + ModelRef::new(builtin::openai(), "gpt-5".into()) } #[test] diff --git a/lib/foundation/fabro-types/src/run_event/mod.rs b/lib/foundation/fabro-types/src/run_event/mod.rs index fc2e6c9c5..bef0919f9 100644 --- a/lib/foundation/fabro-types/src/run_event/mod.rs +++ b/lib/foundation/fabro-types/src/run_event/mod.rs @@ -1036,12 +1036,13 @@ impl<'de> Deserialize<'de> for RunEvent { #[cfg(test)] mod tests { + use lithos_llm::catalog::builtin; use serde_json::json; use super::*; use crate::{ AuthMethod, BlobHash, CommandTermination, Edge, Graph, IdpIdentity, ModelRef, Node, - PendingReason, WorkflowSettings, fixtures, provider_ids, test_support, + PendingReason, WorkflowSettings, fixtures, test_support, }; fn user_principal(login: &str) -> Principal { @@ -2364,7 +2365,7 @@ mod tests { fn agent_message_omits_context_window_when_absent() { let body = EventBody::AgentMessage(AgentMessageProps { text: "ok".to_string(), - model: ModelRef::new(provider_ids::openai(), "gpt-5.4".into()), + model: ModelRef::new(builtin::openai(), "gpt-5.4".into()), billing: BilledTokenCounts::default(), cost_source: None, tool_call_count: 0, @@ -2391,7 +2392,7 @@ mod tests { fn agent_message_omits_reasoning_when_absent() { let body = EventBody::AgentMessage(AgentMessageProps { text: "ok".to_string(), - model: ModelRef::new(provider_ids::openai(), "gpt-5.4".into()), + model: ModelRef::new(builtin::openai(), "gpt-5.4".into()), billing: BilledTokenCounts::default(), cost_source: None, tool_call_count: 0, @@ -2415,7 +2416,7 @@ mod tests { fn agent_message_carries_reasoning_through_canonical_json() { let body = EventBody::AgentMessage(AgentMessageProps { text: String::new(), - model: ModelRef::new(provider_ids::openai(), "gpt-5.4".into()), + model: ModelRef::new(builtin::openai(), "gpt-5.4".into()), billing: BilledTokenCounts::default(), cost_source: None, tool_call_count: 1, @@ -2468,7 +2469,7 @@ mod tests { }; let body = EventBody::AgentMessage(AgentMessageProps { text: "ok".to_string(), - model: ModelRef::new(provider_ids::openai(), "gpt-5.4".into()), + model: ModelRef::new(builtin::openai(), "gpt-5.4".into()), billing: BilledTokenCounts::default(), cost_source: None, tool_call_count: 0,