use super::*;
use crate::providers::openai::Route;
use crate::providers::openai::responses_api::SystemInstructionsPlacement;
use crate::test_utils::{RecordingHttpClient, json_body};
fn transport() -> crate::http_client::DynHttpClient {
crate::http_client::DynHttpClient::new(RecordingHttpClient::new("{}"))
}
#[test]
fn every_registered_identity_round_trips_through_its_qualified_spelling() {
let mut unique = std::collections::HashSet::new();
let mut seen = 0;
for id in ProviderId::all() {
assert!(unique.insert(id), "duplicate selection: {id}");
assert_eq!(ProviderId::new(id.vendor(), id.format().unwrap()), Some(id));
let qualified = id.to_string();
assert_eq!(
ProviderId::resolve(&qualified),
Ok(id),
"`{qualified}` must resolve to itself"
);
seen += 1;
}
assert!(seen > 20, "the registry is not empty: {seen}");
}
#[test]
fn copilot_hosts_follow_the_token_and_stay_when_credentials_change() {
let token = "tid=1;proxy-ep=proxy.individual.githubcopilot.com;exp=2";
let id = ProviderId::resolve("copilot").unwrap();
for (initial_key, expected_host) in [
("", "https://api.githubcopilot.com"),
(token, "https://api.individual.githubcopilot.com"),
] {
let config = id
.config(initial_key)
.unwrap()
.with_credential("host-secret");
let ProviderConfig::OpenAi(config) = config else {
panic!("Copilot is an OpenAI-family preset")
};
assert_eq!(config.base_url, expected_host);
assert_eq!(config.api_key.expose(), "host-secret");
}
}
#[test]
fn configured_identity_never_mints_an_unregistered_or_custom_preset() {
let custom =
openai::wire::Dialect::gateway("private", "https://private.invalid", "PRIVATE_KEY");
let config = ProviderConfig::OpenAi(openai::wire::OpenAIConfig::with_key(&custom, ""));
assert_eq!(config.id(), None);
assert!(
serde_json::to_value(&config).is_err(),
"an unreloadable dialect must not enter persisted data"
);
let modified = openai::wire::Dialect {
base_url: "https://modified.invalid",
..openai::wire::OPENAI
};
let config = ProviderConfig::OpenAi(openai::wire::OpenAIConfig::with_key(&modified, ""));
assert!(
serde_json::to_value(&config).is_err(),
"custom dialect payload must not silently disappear"
);
let id = config.id().unwrap();
assert_eq!(id, ProviderId::resolve("openai").unwrap());
let ProviderConfig::OpenAi(preset) = id.config("").unwrap() else {
panic!("OpenAI preset")
};
assert_eq!(preset.base_url, openai::wire::OPENAI.base_url);
}
#[test]
fn anthropic_custom_dialects_are_executable_but_not_lossily_persisted() {
for dialect in [
anthropic::wire::compatible("private", "https://private.invalid", "PRIVATE_KEY", None),
anthropic::wire::Dialect {
base_url: "https://modified.invalid",
..anthropic::wire::ANTHROPIC
},
] {
let config =
ProviderConfig::Anthropic(anthropic::wire::AnthropicConfig::with_key(&dialect, ""));
assert!(serde_json::to_value(&config).is_err());
let _handler =
config.completion_handler("custom", "model", transport(), ModelFacts::default());
}
}
#[test]
fn a_second_format_for_a_vendor_leaves_qualified_references_unambiguous() {
let dual: Vec<&str> = ["zai", "minimax", "moonshot", "xiaomimimo"].into();
for vendor in dual {
let selections: Vec<ProviderId> = ProviderId::vendor_selections(vendor).collect();
assert_eq!(
selections.len(),
2,
"`{vendor}` is registered for two families"
);
assert!(matches!(
ProviderId::resolve(vendor),
Err(SelectionError::Ambiguous { .. })
));
for id in selections {
assert_eq!(ProviderId::resolve(&id.to_string()), Ok(id));
}
}
assert_eq!(
ProviderId::resolve("deepseek"),
ProviderId::new("deepseek", Format::OpenAi).ok_or(SelectionError::Unknown {
vendor: "deepseek".to_owned()
})
);
}
#[test]
fn ambiguous_shorthand_offers_resolvable_alternatives() {
let error = ProviderId::resolve("zai").expect_err("`zai` is ambiguous");
let SelectionError::Ambiguous { alternatives, .. } = &error else {
panic!("expected an ambiguity: {error}");
};
assert_eq!(alternatives, &["zai/openai", "zai/anthropic"]);
for alternative in alternatives {
let id = ProviderId::resolve(alternative).expect("an alternative resolves");
assert_eq!(&id.to_string(), alternative, "and is already canonical");
}
assert!(
error.to_string().contains("zai/openai"),
"the message names them: {error}"
);
}
#[test]
fn malformed_input_refuses() {
use SelectionError::*;
assert!(matches!(ProviderId::resolve(""), Err(Malformed { .. })));
assert!(matches!(
ProviderId::resolve("/openai"),
Err(Malformed { .. })
));
assert!(matches!(
ProviderId::resolve("openai/openai/extra"),
Err(Malformed { .. })
));
assert!(matches!(
ProviderId::resolve("openai/"),
Err(UnknownFormat { .. })
));
assert!(matches!(
ProviderId::resolve("openai/messages"),
Err(UnknownFormat { .. })
));
assert!(matches!(
ProviderId::resolve("nosuchvendor"),
Err(Unknown { .. })
));
let unregistered =
ProviderId::resolve("openai/anthropic").expect_err("openai speaks no Messages endpoint");
assert!(matches!(unregistered, Unregistered { .. }));
assert!(
unregistered.to_string().contains("openai/openai"),
"and says what it does speak: {unregistered}"
);
}
#[test]
fn every_dual_format_vendor_declares_a_registered_primary() {
let mut vendors: Vec<&str> = ProviderId::all().map(|id| id.vendor()).collect();
vendors.dedup();
for vendor in vendors {
let selections: Vec<ProviderId> = ProviderId::vendor_selections(vendor).collect();
let declared = PRIMARY.iter().find(|(name, _)| *name == vendor);
assert_eq!(
declared.is_some(),
selections.len() > 1,
"`{vendor}` declares a primary exactly when it has two families"
);
if let Some((_, format)) = declared {
let primary = ProviderId::new(vendor, *format).expect("a registered family");
assert_eq!(ProviderId::catalog(vendor), Some(primary));
}
}
for spec in crate::catalog::Catalog::builtin().iter() {
if spec.provider.is_registered() {
assert_eq!(
Some(spec.provider),
ProviderId::catalog(spec.provider.vendor()),
"{}/{}: no built-in row names another family",
spec.provider.vendor(),
spec.id
);
}
}
}
#[test]
fn configurations_round_trip_and_stay_distinct() {
let openai = ProviderId::resolve("openai/openai").unwrap();
let plain = openai.config("sk-secret").unwrap();
let elsewhere = match plain.clone() {
ProviderConfig::OpenAi(provider) => {
ProviderConfig::OpenAi(provider.with_base_url("https://gateway.invalid/v1"))
}
other => panic!("openai/openai is an OpenAI configuration: {other:?}"),
};
let on_chat = match plain.clone() {
ProviderConfig::OpenAi(provider) => {
ProviderConfig::OpenAi(provider.with_route(Route::Chat))
}
other => panic!("{other:?}"),
};
let as_messages = match plain.clone() {
ProviderConfig::OpenAi(provider) => {
ProviderConfig::OpenAi(provider.with_system_instructions_as_messages())
}
other => panic!("{other:?}"),
};
let mut written = Vec::new();
for config in [&plain, &elsewhere, &on_chat, &as_messages] {
let json = serde_json::to_string(config).expect("a configuration serializes");
assert!(
json.starts_with(r#"{"openai":"#),
"as an object, family first: {json}"
);
assert!(
!json.contains("sk-secret"),
"and never with the credential: {json}"
);
let read: ProviderConfig = serde_json::from_str(&json).expect("and reads back");
assert_eq!(read, config.clone().with_credential(""));
written.push(json);
}
for (index, json) in written.iter().enumerate() {
for (other, sibling) in written.iter().enumerate() {
assert_eq!(
index == other,
json == sibling,
"configurations differing in host, route or placement must not collapse:\n{json}\n{sibling}"
);
}
}
}
#[test]
fn bad_configuration_data_reports_what_was_wrong() {
let bad_family = serde_json::from_str::<ProviderConfig>(
r#"{"messages":{"api_key":"x","base_url":"b","version":"v","betas":[],"dialect":"anthropic"}}"#,
)
.expect_err("`messages` is not a family");
assert!(bad_family.to_string().contains("messages"), "{bad_family}");
let bad_dialect = serde_json::from_str::<ProviderConfig>(
r#"{"openai":{"api_key":"x","base_url":"b","dialect":"nosuch","auth":"Bearer"}}"#,
)
.expect_err("`nosuch` is no dialect");
assert!(bad_dialect.to_string().contains("nosuch"), "{bad_dialect}");
let misspelled = serde_json::from_str::<ProviderConfig>(
r#"{"openai":{"api_key":"x","base_url":"b","dialect":"openai","auth":"Bearer","rout":"chat"}}"#,
)
.expect_err("a misspelled option is not silently dropped");
assert!(misspelled.to_string().contains("rout"), "{misspelled}");
}
#[test]
fn optional_auth_selections_are_not_described_as_requiring_a_credential() {
let local = ProviderId::resolve("llamacpp/openai").unwrap();
assert_eq!(local.api_key_envs(), ["LLAMACPP_API_KEY"]);
assert!(
!local.requires_credential(),
"a local llama-server authenticates optionally"
);
for qualified in ["openai/openai", "anthropic/anthropic", "gcp.gemini/gemini"] {
let id = ProviderId::resolve(qualified).unwrap();
assert!(id.requires_credential(), "{qualified} needs a credential");
assert!(!id.api_key_envs().is_empty());
assert!(id.api_key_envs().iter().all(|name| !name.is_empty()));
}
}
#[test]
fn a_gateway_absent_from_the_old_vocabulary_is_materializable() {
let json = r#"{"openai":{"api_key":"[redacted]","base_url":"https://api.venice.ai/api/v1","dialect":"venice","auth":"Bearer"}}"#;
let config: ProviderConfig = serde_json::from_str(json).expect("Venice reads from data");
assert_eq!(config.vendor(), "venice");
let config = config.with_credential("vk-test");
assert!(!config.is_unauthenticated(), "the host rehydrated it");
let handler = config.completion_handler(
"default",
"venice-uncensored",
transport(),
ModelFacts::default(),
);
let descriptor = handler.descriptor();
assert!(
format!("{descriptor:?}").contains("default"),
"the label reaches the descriptor: {descriptor:?}"
);
}
#[test]
fn the_configured_instruction_placement_reaches_the_request_body() {
use crate::completion::CompletionRequest;
use crate::wire::{Mode, Wire};
let request = || CompletionRequest::new("hello").preamble("be brief");
let encode = |config: ProviderConfig| {
let ProviderConfig::OpenAi(provider) = config else {
panic!("an OpenAI configuration");
};
let wire = provider.responses("gpt-4.1-mini");
let encoded = wire
.encode(request(), Mode::Unary)
.expect("the request encodes");
json_body(&encoded.request)
};
let openai = ProviderId::resolve("openai/openai").unwrap();
let default = encode(openai.config("k").unwrap());
assert_eq!(
default.get("instructions").and_then(|value| value.as_str()),
Some("be brief"),
"{default}"
);
let json = r#"{"openai":{"api_key":"[redacted]","base_url":"https://api.openai.com/v1","dialect":"openai","auth":"Bearer","system_instructions":"input_system_messages"}}"#;
let read: ProviderConfig = serde_json::from_str(json).expect("the override reads from data");
let rehydrated = read.with_credential("k");
let ProviderConfig::OpenAi(config) = &rehydrated else {
panic!("an OpenAI configuration");
};
assert_eq!(
config.system_instructions,
Some(SystemInstructionsPlacement::InputSystemMessages)
);
let overridden = encode(rehydrated.clone());
assert!(
overridden.get("instructions").is_none(),
"no top-level instructions: {overridden}"
);
let input = overridden
.get("input")
.and_then(|value| value.as_array())
.expect("an input array");
assert!(
input.iter().any(|item| {
item.get("role").and_then(|role| role.as_str()) == Some("system")
&& format!("{item}").contains("be brief")
}),
"the preamble is a system message instead: {overridden}"
);
}
#[test]
fn credentials_never_enter_a_serialized_configuration() {
const SENTINEL: &str = "sk-do-not-leak";
for id in ProviderId::all() {
let json = serde_json::to_string(&id.config(SENTINEL).unwrap()).expect("serializes");
assert!(!json.contains(SENTINEL), "{json}");
let read: ProviderConfig = serde_json::from_str(&json).expect("reads back");
assert!(read.is_unauthenticated(), "{json}");
}
}
#[test]
fn the_family_name_is_the_configuration_tag() {
for id in ProviderId::all() {
let config = id.config("").unwrap();
let json = serde_json::to_value(&config).expect("a configuration serializes");
let tag = json
.as_object()
.expect("externally tagged")
.keys()
.next()
.expect("one tag")
.clone();
assert_eq!(
tag,
id.format().unwrap().as_str(),
"{id}: the family name and the configuration tag agree"
);
assert_eq!(
serde_json::to_string(&id.format().unwrap()).unwrap(),
format!("\"{tag}\"")
);
assert_eq!(
serde_json::from_str::<Format>(&format!("\"{tag}\"")).unwrap(),
id.format().unwrap()
);
}
}
#[test]
fn a_configured_base_url_round_trips_through_serialization() {
const HOST: &str = "https://gateway.invalid/v2";
for selection in ["openai/openai", "anthropic/anthropic", "gcp.gemini/gemini"] {
let config = ProviderId::resolve(selection)
.unwrap()
.config("sk-secret")
.unwrap()
.with_base_url(HOST);
let json = serde_json::to_string(&config).expect("a configuration serializes");
assert!(json.contains(HOST), "{selection}: {json}");
let read: ProviderConfig = serde_json::from_str(&json).expect("and reads back");
assert_eq!(read.base_url(), HOST, "{selection}");
assert_eq!(read, config.with_credential(""), "{selection}");
}
}
#[test]
fn both_doors_of_a_dual_format_vendor_reach_their_own_endpoint() {
let chat = ProviderId::resolve("zai/openai").unwrap();
let messages = ProviderId::resolve("zai/anthropic").unwrap();
assert_eq!(chat.vendor(), messages.vendor());
assert_ne!(chat, messages);
match chat.config("").unwrap() {
ProviderConfig::OpenAi(provider) => {
assert_eq!(provider.base_url, "https://api.z.ai/api/paas/v4");
assert_eq!(provider.dialect.name, "zai");
}
other => panic!("zai/openai is an OpenAI configuration: {other:?}"),
}
match messages.config("").unwrap() {
ProviderConfig::Anthropic(provider) => {
assert_eq!(provider.base_url, "https://api.z.ai/api/anthropic");
assert_eq!(provider.dialect.name, "zai");
}
other => panic!("zai/anthropic is a Messages configuration: {other:?}"),
}
assert_eq!(chat.api_key_envs(), messages.api_key_envs());
}
#[test]
fn ids_of_different_vendors_differ_in_one_format() {
let openai = ProviderId::resolve("openai/openai").unwrap();
let venice = ProviderId::resolve("venice/openai").unwrap();
assert_eq!(openai.format(), venice.format());
assert_ne!(openai, venice);
}
#[test]
fn a_catalog_only_id_describes_itself_and_round_trips() {
for (vendor, credential) in [("aws_bedrock", true), ("candle", false)] {
let id = ProviderId::catalog(vendor).expect("known");
assert!(!id.is_registered(), "{vendor}");
assert_eq!(id.format(), None, "{vendor}");
assert!(id.api_key_envs().is_empty(), "{vendor}");
assert!(id.config("sk-test").is_none(), "{vendor}");
assert_eq!(id.requires_credential(), credential, "{vendor}");
let json = serde_json::to_string(&id).expect("serializes");
assert_eq!(json, format!("\"{vendor}\""));
assert_eq!(serde_json::from_str::<ProviderId>(&json).unwrap(), id);
}
let openai = ProviderId::catalog("openai").expect("registered");
assert!(openai.is_registered());
assert_eq!(serde_json::to_string(&openai).unwrap(), "\"openai/openai\"");
assert!(ProviderId::catalog("nosuchvendor").is_none());
assert!(serde_json::from_str::<ProviderId>("\"nosuchvendor\"").is_err());
}
fn offline() -> (RecordingHttpClient, ConnectOptions) {
let http = RecordingHttpClient::new("{}");
let options = ConnectOptions::new().api_key("sk-test").http(http.clone());
(http, options)
}
async fn sent_to(model: DynModel<Completion>, http: &RecordingHttpClient) -> String {
let _ = model.call("hi").await;
http.requests()
.last()
.map(|request| request.uri.clone())
.unwrap_or_default()
}
#[tokio::test]
async fn connect_reaches_the_family_asked_for() {
let catalog = crate::catalog::Catalog::builtin();
for (reference, format, sent) in [
(
"zai/glm-5",
None,
"https://api.z.ai/api/paas/v4/chat/completions",
),
(
"zai/anthropic:glm-5",
None,
"https://api.z.ai/api/anthropic/v1/messages",
),
(
"zai/glm-5",
Some(Format::Anthropic),
"https://api.z.ai/api/anthropic/v1/messages",
),
(
"minimax/MiniMax-M2.7",
None,
"https://api.minimax.io/anthropic/v1/messages",
),
(
"minimax/MiniMax-M2.7",
Some(Format::OpenAi),
"https://api.minimax.io/v1/chat/completions",
),
] {
let (http, options) = offline();
let options = match format {
Some(format) => options.format(format),
None => options,
};
let model = catalog.connect_with(reference, options).expect("connects");
assert_eq!(sent_to(model, &http).await, sent, "{reference} {format:?}");
}
let (http, options) = offline();
let spec = catalog.resolve("zai/glm-5").expect("listed").spec;
let model = catalog
.connect_with(spec, options.format(Format::Anthropic))
.expect("connects");
assert_eq!(
sent_to(model, &http).await,
"https://api.z.ai/api/anthropic/v1/messages"
);
let (http, options) = offline();
let model = catalog
.connect_with(
"deepseek/deepseek-chat",
options.base_url("https://proxy.invalid/v1"),
)
.expect("connects");
assert_eq!(
sent_to(model, &http).await,
"https://proxy.invalid/v1/chat/completions"
);
}
#[test]
fn connect_refuses_what_it_cannot_build() {
let catalog = crate::catalog::Catalog::builtin();
let connect = |reference: &str, options: ConnectOptions| {
catalog.connect_with(reference, options.api_key("sk-test"))
};
for malformed in ["no-model", "deepseek:deepseek-chat", "/gpt-5.5", "openai/"] {
assert!(
matches!(
connect(malformed, ConnectOptions::new()),
Err(ConnectError::Malformed { .. })
),
"{malformed}"
);
}
let error =
connect("antropic/claude-opus-5-5", ConnectOptions::new()).expect_err("no such vendor");
let ConnectError::NotFound(not_found) = &error else {
panic!("a miss with suggestions: {error}");
};
assert_eq!(
not_found.suggestions.first().map(String::as_str),
Some("anthropic/claude-opus-5-5")
);
assert!(error.to_string().contains("did you mean"), "{error}");
let error = connect("candle/qwen3", ConnectOptions::new()).expect_err("catalog-only");
assert!(
matches!(
&error,
ConnectError::CatalogOnly { vendor, served_by: "the `rig-candle` crate" } if vendor == "candle"
),
"{error}"
);
let bedrock = catalog
.resolve("aws_bedrock/us.anthropic.claude-sonnet-5")
.expect("listed")
.spec;
let error = catalog
.connect_with(bedrock, ConnectOptions::new().api_key("sk-test"))
.expect_err("catalog-only");
assert!(error.to_string().contains("rig-bedrock"), "{error}");
for (reference, options) in [
("openai/anthropic:gpt-5.5", ConnectOptions::new()),
(
"openai/gpt-5.5",
ConnectOptions::new().format(Format::Anthropic),
),
] {
let error = connect(reference, options).expect_err("no such family");
assert!(
matches!(
error,
ConnectError::Selection(SelectionError::Unregistered { .. })
),
"{reference}: {error}"
);
}
}
#[test]
fn connect_takes_every_selector_spelling() {
let catalog = crate::catalog::Catalog::builtin();
let spec = catalog.resolve("openai/gpt-5.5").expect("listed").spec;
let owned = "openai/gpt-5.5".to_owned();
for selector in [
ModelSelector::from(spec),
ModelSelector::from("openai/gpt-5.5"),
ModelSelector::from(&owned),
] {
let model = catalog
.connect_with(selector, offline().1)
.expect("connects");
assert_eq!(model.id(), Some("gpt-5.5"));
assert_eq!(model.spec(), Some(spec));
}
let snapshot = catalog
.connect_with("anthropic/claude-sonnet-4-6-20990101", offline().1)
.expect("connects");
assert_eq!(snapshot.id(), Some("claude-sonnet-4-6-20990101"));
assert_eq!(
snapshot.spec().map(|spec| spec.id.as_str()),
Some("claude-sonnet-4-6")
);
let unlisted = catalog
.connect_with("ollama/some-local-pull:4b", offline().1)
.expect("an unlisted model of a known vendor connects");
assert_eq!(unlisted.id(), Some("some-local-pull:4b"));
assert_eq!(unlisted.spec(), None);
}
#[test]
fn a_model_connected_from_a_spec_or_a_catalog_carries_its_facts() {
for reference in [
"openai/gpt-5.5",
"anthropic/claude-sonnet-4-6",
"gcp.gemini/gemini-2.5-flash",
] {
let builtin = crate::catalog::Catalog::builtin()
.resolve(reference)
.expect("listed")
.spec;
let spec = builtin.clone().with_context_window(1_234);
let catalog = crate::catalog::Catalog::builtin();
let model = catalog.connect_with(&spec, offline().1).expect("connects");
assert_eq!(model.spec(), Some(&spec), "{reference}");
let model = catalog
.connect_with(reference, offline().1)
.expect("connects");
assert_eq!(model.spec(), Some(builtin), "{reference}");
let mut catalog = crate::catalog::Catalog::builtin().clone();
catalog.insert(spec.clone());
let model = catalog
.connect_with(reference, offline().1)
.expect("connects");
assert_eq!(model.spec(), Some(&spec), "{reference}");
}
}
#[test]
fn a_missing_key_names_every_variable_tried() {
const UNSET: [&str; 2] = [
"RIG_REGISTRY_TEST_UNSET_KEY",
"RIG_REGISTRY_TEST_UNSET_TOKEN",
];
let error = key_from_env("acme", UNSET.to_vec(), true).expect_err("nothing is set");
assert!(
matches!(&error, ConnectError::MissingKey { vendor, tried } if vendor == "acme" && tried == &UNSET),
"{error}"
);
let message = error.to_string();
assert!(
message.contains("`RIG_REGISTRY_TEST_UNSET_KEY` or `RIG_REGISTRY_TEST_UNSET_TOKEN`"),
"{message}"
);
assert_eq!(
key_from_env("acme", UNSET.to_vec(), false).expect("optional"),
(String::new(), None)
);
let azure = ProviderId::resolve("azure.openai").unwrap();
assert_eq!(azure.api_key_envs(), ["AZURE_API_KEY", "AZURE_TOKEN"]);
let openai::wire::Dialect {
alternate_auth: Some(alternative),
..
} = openai::wire::AZURE
else {
panic!("Azure takes a token too");
};
assert_eq!(
openai_auth(&openai::wire::AZURE, Some("AZURE_TOKEN")),
alternative.auth
);
assert_eq!(
openai_auth(&openai::wire::AZURE, Some("AZURE_API_KEY")),
openai::wire::AZURE.quirks.auth
);
}
#[test]
fn connect_options_never_show_the_key() {
let options = ConnectOptions::new()
.api_key("sk-do-not-leak")
.format(Format::OpenAi);
let shown = format!("{options:?}");
assert!(!shown.contains("sk-do-not-leak"), "{shown}");
assert!(shown.contains("OpenAi"), "{shown}");
}
#[test]
fn a_declared_vendor_is_filed_and_read_back_but_never_configured() {
let corp = ProviderId::declare("registry-test-corp").expect("a valid name");
assert_eq!(ProviderId::declare("registry-test-corp"), Ok(corp));
assert_eq!(ProviderId::catalog("registry-test-corp"), Some(corp));
assert_eq!(
ProviderId::declare("openai").ok(),
ProviderId::catalog("openai")
);
for malformed in ["", "a/b", "a b"] {
assert!(matches!(
ProviderId::declare(malformed),
Err(SelectionError::Malformed { .. })
));
}
assert_eq!(corp.to_string(), "registry-test-corp");
assert_eq!(
(
corp.format(),
corp.is_registered(),
corp.is_declared(),
corp.requires_credential()
),
(None, false, true, false)
);
for known in ["openai", "candle"] {
assert!(
!ProviderId::catalog(known).is_some_and(|id| id.is_declared()),
"{known}"
);
}
assert!(corp.config("key").is_none() && corp.api_key_envs().is_empty());
assert!(ProviderId::catalog_vendors().any(|vendor| vendor == "registry-test-corp"));
let spec = ModelSpec::new(corp, "big");
let json = serde_json::to_value(&spec).expect("a spec serializes");
assert_eq!(
serde_json::from_value::<ModelSpec>(json).ok(),
Some(spec.clone())
);
assert!(matches!(
Catalog::builtin().connect_with(&spec, ConnectOptions::new().api_key("key")),
Err(ConnectError::CatalogOnly { .. })
));
}
#[tokio::test]
async fn a_model_built_with_facts_prices_its_replies() {
let corp = ProviderId::declare("registry-priced-corp").expect("a valid name");
let spec = ModelSpec::new(corp, "big").with_pricing(crate::catalog::Pricing::new(1.0, 2.0));
let dialect =
openai::wire::Dialect::gateway(corp.vendor(), "https://corp.invalid/v1", "CORP_KEY");
let reply = serde_json::json!({
"id": "reply", "object": "chat.completion", "created": 0, "model": "big",
"choices": [{"index": 0, "finish_reason": "stop",
"message": {"role": "assistant", "content": "hi"}}],
"usage": {"prompt_tokens": 1_000_000, "completion_tokens": 1_000_000,
"total_tokens": 2_000_000}
});
let http = RecordingHttpClient::new(reply.to_string());
let model = ProviderConfig::OpenAi(openai::wire::OpenAIConfig::with_key(&dialect, "key"))
.completion_model(
"big",
crate::http_client::DynHttpClient::new(http.clone()),
ModelFacts::new(spec),
);
let response = model
.call(crate::completion::CompletionRequest::new("hi"))
.await
.expect("the reply decodes");
assert_eq!(response.usage.cost.map(|cost| cost.total), Some(3.0));
assert!(
http.requests()
.first()
.is_some_and(|request| request.uri.starts_with("https://corp.invalid/v1"))
);
}