Name built-in providers through lithos catalog::builtin

lithos-llm now ships the built-in provider ids and constructors, so
fabro-types drops its provider_ids module and every caller uses
lithos_llm::catalog::builtin directly. The crates that name a provider
now depend on lithos-llm themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-10 09:48:37 -06:00
parent f8df58966b
commit fa3e485c95
No known key found for this signature in database
66 changed files with 317 additions and 326 deletions

6
Cargo.lock generated
View file

@ -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",

View file

@ -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<dyn std::error::Error>> {
@ -48,7 +49,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let profile: Arc<dyn AgentProfile> = Arc::from(
AgentProfileBuilder::new(
AgentProfileKind::Anthropic,
provider_ids::anthropic(),
builtin::anthropic(),
"claude-sonnet-4.5",
Arc::clone(&catalog),
)

View file

@ -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

View file

@ -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()),

View file

@ -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(),

View file

@ -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,

View file

@ -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"),

View file

@ -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<AuthMethod> {
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(),
)

View file

@ -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" }

View file

@ -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,
},
],

View file

@ -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),

View file

@ -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;

View file

@ -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",

View file

@ -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();

View file

@ -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(),

View file

@ -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("")),

View file

@ -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),

View file

@ -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" }

View file

@ -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");
}

View file

@ -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(),
)

View file

@ -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))),
));

View file

@ -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");
}

View file

@ -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");
}

View file

@ -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");
}

View file

@ -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);
}
}

View file

@ -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()),
);

View file

@ -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");
}

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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));

View file

@ -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,

View file

@ -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"]

View file

@ -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

View file

@ -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(),

View file

@ -276,7 +276,7 @@ fn canonical_eligible(catalog: &Catalog, eligible: &HashSet<ProviderId>) -> 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,

View file

@ -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

View file

@ -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 {

View file

@ -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),
)
}

View file

@ -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,

View file

@ -31,10 +31,11 @@ pub fn event_payload_from_redacted_json(line: &str, run_id: &RunId) -> Result<Ev
#[cfg(test)]
mod tests {
use ::fabro_types::{
ModelId, ModelRef, ReasoningOutput, TokenCounts as LlmTokenCounts, fixtures, provider_ids,
ModelId, ModelRef, ReasoningOutput, TokenCounts as LlmTokenCounts, fixtures,
run_event as fabro_types,
};
use fabro_agent::AgentEvent;
use lithos_llm::catalog::builtin;
use super::*;
use crate::event::{Event, to_run_event};
@ -125,7 +126,7 @@ mod tests {
visit: 1,
event: AgentEvent::AssistantMessage {
text: "done".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,

View file

@ -390,7 +390,7 @@ mod tests {
event: fabro_agent::AgentEvent::AssistantMessage {
text: String::new(),
model: ::fabro_types::ModelRef::new(
::fabro_types::provider_ids::openai(),
::lithos_llm::catalog::builtin::openai(),
ModelId::new("gpt-5.4"),
),
usage: ::fabro_types::TokenCounts::default(),

View file

@ -1901,12 +1901,13 @@ mod tests {
use fabro_types::{
ContentPart, EventEnvelope, FailureReason, Run, RunId, RunLifecycle, RunLinks, RunOrigin,
RunPairStatusResponse, RunProjection, RunStatus, RunTimestamps, SuccessReason, WorkflowRef,
provider_ids, test_support,
test_support,
};
use fabro_vault::{SecretType, Vault};
use futures::stream;
use httpmock::Method::POST;
use httpmock::MockServer;
use lithos_llm::catalog::builtin;
use tokio::sync::RwLock as AsyncRwLock;
use tokio_util::sync::CancellationToken;
@ -1932,7 +1933,7 @@ mod tests {
}
fn provider_id(&self) -> 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<_>>(),
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",

View file

@ -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,

View file

@ -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()),

View file

@ -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"),
),

View file

@ -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()
));
}

View file

@ -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,

View file

@ -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(),

View file

@ -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()),

View file

@ -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(),

View file

@ -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()),

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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 {

View file

@ -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();

View file

@ -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(),

View file

@ -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,

View file

@ -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)
}

View file

@ -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,

View file

@ -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}"

View file

@ -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),

View file

@ -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;

View file

@ -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)
}

View file

@ -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]

View file

@ -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,