///|
/// Provider-owned model catalog.
///
/// A provider extension builds this value after resolving its own endpoint,
/// protocol, credentials, model ids, and capability rules. The router only
/// validates composition and aggregates the resulting slots; it never parses
/// provider configuration.
pub(all) struct ProviderModelCatalog {
provider_id : String
slots : Array[ModelSlot]
capabilities : Array[String]
}
///|
/// Construct and validate a provider catalog atomically.
pub fn ProviderModelCatalog::ProviderModelCatalog(
provider_id~ : String,
slots~ : Array[ModelSlot],
capabilities? : Array[String] = [],
) -> ProviderModelCatalog raise @posoco.CompositionError {
if provider_id.length() == 0 {
raise @posoco.CompositionError::ManifestSchemaError(
manifest_id="posoco_ext_llm",
detail="provider catalog id must not be empty",
)
}
if slots.is_empty() {
raise @posoco.CompositionError::EmptyPort(
"posoco_ext_llm.catalog." + provider_id,
)
}
let seen : Map[String, Bool] = Map::from_array([])
for slot in slots {
if slot.id.length() == 0 {
raise @posoco.CompositionError::ManifestSchemaError(
manifest_id="posoco_ext_llm",
detail="provider catalog slot id must not be empty",
)
}
if slot.provider_id != provider_id {
raise @posoco.CompositionError::ManifestSchemaError(
manifest_id="posoco_ext_llm",
detail="slot provider id does not match catalog provider: " + slot.id,
)
}
if seen.contains(slot.id) {
raise @posoco.CompositionError::ManifestSchemaError(
manifest_id="posoco_ext_llm",
detail="duplicate provider catalog slot id: " + slot.id,
)
}
seen[slot.id] = true
}
{ provider_id, slots, capabilities, }
}
///|
/// Provider id for diagnostics and UI labels.
pub fn ProviderModelCatalog::provider_id(self : ProviderModelCatalog) -> String {
self.provider_id
}
///|
/// Provider-owned model slots in declaration order.
pub fn ProviderModelCatalog::slots(
self : ProviderModelCatalog,
) -> Array[ModelSlot] {
self.slots.copy()
}
///|
/// Provider capabilities such as `chat`, `streaming`, or provider-native
/// operations. Cetas treats these as opaque capability identifiers.
pub fn ProviderModelCatalog::capabilities(
self : ProviderModelCatalog,
) -> Array[String] {
self.capabilities.copy()
}
///|
/// Authentication methods advertised by the provider's slots. This is
/// derived from extension-owned factories so a host receives a generic
/// capability list without inspecting provider configuration.
pub fn ProviderModelCatalog::auth_methods(
self : ProviderModelCatalog,
) -> Array[AuthMethod] {
let result : Array[AuthMethod] = []
for slot in self.slots {
match slot.api_key {
Some(_) =>
if !result.contains(AuthMethod::ApiKey) {
result.push(AuthMethod::ApiKey)
}
None => ()
}
match slot.oauth {
Some(_) =>
if !result.contains(AuthMethod::OAuth) {
result.push(AuthMethod::OAuth)
}
None => ()
}
}
result
}