domain(pi): add managed model and capability contracts

This commit is contained in:
SaladDay
2026-07-31 17:52:59 +00:00
parent 119d795121
commit 3dc188313e
3 changed files with 888 additions and 0 deletions
+1
View File
@@ -25,6 +25,7 @@ mod model_capabilities;
mod openclaw_config;
mod opencode_config;
mod panic_hook;
mod pi_config;
mod prompt;
mod prompt_files;
mod provider;
+7
View File
@@ -0,0 +1,7 @@
//! Pi Coding Agent integration boundaries.
//!
//! This module deliberately separates the managed control-plane model from
//! Pi's shared files and from the proxy data plane. Callers must use the
//! typed model resolver rather than reimplementing provider/model inheritance.
pub(crate) mod model;
+880
View File
@@ -0,0 +1,880 @@
//! Managed Pi control-plane types.
//!
//! API identifiers are intentionally opaque here. The closed set of API
//! families that the gateway can proxy belongs to `gateway.rs`; admitting a
//! new Pi API identifier into the managed control plane must not require a
//! gateway enum update.
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::{BTreeMap, HashSet};
use thiserror::Error;
use url::Url;
pub(crate) type PiHeaderMap = BTreeMap<String, String>;
/// Pi uses JavaScript/TypeBox `Number`, not `Integer`, for model limits.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub(crate) struct PiNumber(f64);
impl PiNumber {
pub(crate) const DEFAULT_CONTEXT_WINDOW: Self = Self(128_000.0);
pub(crate) const DEFAULT_MAX_TOKENS: Self = Self(16_384.0);
pub(crate) fn new(value: f64) -> Result<Self, PiNumberError> {
if value.is_finite() {
Ok(Self(value))
} else {
Err(PiNumberError)
}
}
pub(crate) const fn get(self) -> f64 {
self.0
}
}
impl<'de> Deserialize<'de> for PiNumber {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = f64::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
impl TryFrom<f64> for PiNumber {
type Error = PiNumberError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<PiNumber> for f64 {
fn from(value: PiNumber) -> Self {
value.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[error("Pi Number must be finite")]
pub(crate) struct PiNumberError;
/// Opaque managed API identifier.
///
/// This is not the gateway support enum. Any non-empty Pi-valid identifier
/// round-trips through this type, including identifiers introduced after the
/// pinned gateway implementation.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub(crate) struct PiManagedApiId(String);
impl PiManagedApiId {
pub(crate) fn new(value: impl Into<String>) -> Result<Self, PiConfigError> {
let value = value.into();
if value.is_empty() {
return Err(PiConfigError::EmptyApiId);
}
Ok(Self(value))
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl<'de> Deserialize<'de> for PiManagedApiId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum PiModelInput {
Text,
Image,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiModelCostRates {
pub input: f64,
pub output: f64,
pub cache_read: f64,
pub cache_write: f64,
}
impl Default for PiModelCostRates {
fn default() -> Self {
Self {
input: 0.0,
output: 0.0,
cache_read: 0.0,
cache_write: 0.0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiModelCostTier {
pub input_tokens_above: f64,
pub input: f64,
pub output: f64,
pub cache_read: f64,
pub cache_write: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiModelCost {
#[serde(flatten)]
pub rates: PiModelCostRates,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tiers: Vec<PiModelCostTier>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiModelCostOverride {
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_read: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_write: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tiers: Option<Vec<PiModelCostTier>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiManagedModelOverride {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<bool>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub thinking_level_map: BTreeMap<String, Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<Vec<PiModelInput>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<PiModelCostOverride>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window: Option<PiNumber>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<PiNumber>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub headers: PiHeaderMap,
#[serde(skip_serializing_if = "Option::is_none")]
pub compat: Option<Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiManagedModel {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub api: Option<PiManagedApiId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<bool>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub thinking_level_map: BTreeMap<String, Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<Vec<PiModelInput>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<PiModelCost>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window: Option<PiNumber>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<PiNumber>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub headers: PiHeaderMap,
#[serde(skip_serializing_if = "Option::is_none")]
pub compat: Option<Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiManagedProviderConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub api: Option<PiManagedApiId>,
/// Literal or deferred transport material. The managed layer never
/// executes env/command/file/network resolution.
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub headers: PiHeaderMap,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_header: Option<bool>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub models: Vec<PiManagedModel>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub model_overrides: BTreeMap<String, PiManagedModelOverride>,
#[serde(skip_serializing_if = "Option::is_none")]
pub compat: Option<Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiEffectiveModel {
pub id: String,
pub name: String,
pub api: PiManagedApiId,
pub base_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
pub auth_header: bool,
pub reasoning: bool,
pub thinking_level_map: BTreeMap<String, Option<String>>,
pub input: Vec<PiModelInput>,
pub cost: PiModelCost,
pub context_window: PiNumber,
pub max_tokens: PiNumber,
pub headers: PiHeaderMap,
#[serde(skip_serializing_if = "Option::is_none")]
pub compat: Option<Value>,
pub provider_extra: BTreeMap<String, Value>,
pub model_extra: BTreeMap<String, Value>,
pub override_extra: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub(crate) enum PiConfigError {
#[error("Pi provider must declare at least one managed model")]
ProviderHasNoModels,
#[error("Pi API id cannot be empty")]
EmptyApiId,
#[error("Pi model id cannot be empty")]
EmptyModelId,
#[error("Pi provider contains duplicate model id '{0}'")]
DuplicateModelId(String),
#[error("Pi model '{0}' does not exist in this provider")]
ModelNotFound(String),
#[error("Pi model '{model_id}' has no effective API id")]
MissingEffectiveApi { model_id: String },
#[error("Pi model '{model_id}' has no effective endpoint")]
MissingEffectiveEndpoint { model_id: String },
#[error("Pi endpoint at '{json_pointer}' must be an absolute HTTP(S) URL: {reason}")]
InvalidEndpoint {
json_pointer: String,
reason: String,
},
#[error("Pi model override '{0}' does not match an explicit model id")]
UnknownModelOverride(String),
#[error("Pi compat at '{json_pointer}' must be an object")]
InvalidCompat { json_pointer: String },
#[error("Pi {field} cannot be empty when present")]
EmptyOptionalField { field: &'static str },
#[error("Pi model '{model_id}' {field} must be greater than zero")]
NonPositiveModelLimit {
model_id: String,
field: &'static str,
},
#[error("Pi model '{model_id}' contains an unmanaged thinking level '{level}'")]
InvalidThinkingLevel { model_id: String, level: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiNativeEntryKind {
BuiltInOverlay,
CustomCatalog,
ExtensionOverlay,
UnknownShape,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiRawNativeValidity {
Valid,
Invalid,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiManagedAssessment {
Manageable,
Unsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiCompositionStatus {
Composed,
Failed,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub(crate) enum PiManagementStatus {
Importable,
Managed {
#[serde(rename = "providerId")]
provider_id: String,
},
Unsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiGatewayStatus {
Proxyable,
DirectOnly,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiDiagnosticLayer {
RawSchema,
Managed,
Composition,
Gateway,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PiReasonCode {
RawSchemaMismatch,
RawSchemaUnsupportedOperator,
RawSchemaPinDrift,
RawSchemaAmbiguous,
CatalogRequired,
ModelOverridesOnly,
MissingExplicitModels,
ManagedTypeConversionFailed,
EmptyOptionalField,
EmptyModelId,
DuplicateModelId,
UnknownModelOverride,
MissingEffectiveApi,
MissingEffectiveEndpoint,
InvalidEndpoint,
InvalidCompat,
NonPositiveModelLimit,
InvalidThinkingLevel,
CompositionFailed,
GatewayCredentialUnavailable,
UnsupportedGatewayFamily,
InvalidHeaderName,
InvalidHeaderValue,
ProtectedHeader,
DeferredValueUnavailable,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiDiagnosticReason {
pub layer: PiDiagnosticLayer,
pub code: PiReasonCode,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_pointer: Option<String>,
}
impl PiDiagnosticReason {
pub(crate) fn new(
layer: PiDiagnosticLayer,
code: PiReasonCode,
json_pointer: Option<String>,
) -> Self {
Self {
layer,
code,
json_pointer,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PiNativeDiagnostic {
pub provider_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
pub fingerprint: String,
pub kind: PiNativeEntryKind,
pub raw_validity: PiRawNativeValidity,
pub managed_assessment: PiManagedAssessment,
pub composition_status: PiCompositionStatus,
pub management_status: PiManagementStatus,
pub gateway_status: PiGatewayStatus,
pub reasons: Vec<PiDiagnosticReason>,
}
const THINKING_LEVELS: [&str; 7] = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
pub(crate) fn validate_pi_managed_provider(
provider: &PiManagedProviderConfig,
) -> Result<(), PiConfigError> {
if provider.models.is_empty() {
return Err(PiConfigError::ProviderHasNoModels);
}
validate_optional_text(provider.name.as_deref(), "provider name")?;
validate_optional_text(provider.api_key.as_deref(), "provider apiKey")?;
validate_present_endpoint(provider.base_url.as_deref(), "/baseUrl")?;
validate_compat(provider.compat.as_ref(), "/compat")?;
let mut model_ids = HashSet::with_capacity(provider.models.len());
for (index, model) in provider.models.iter().enumerate() {
if model.id.trim().is_empty() {
return Err(PiConfigError::EmptyModelId);
}
if !model_ids.insert(model.id.as_str()) {
return Err(PiConfigError::DuplicateModelId(model.id.clone()));
}
validate_optional_text(model.name.as_deref(), "model name")?;
validate_present_endpoint(
model.base_url.as_deref(),
&format!("/models/{index}/baseUrl"),
)?;
validate_compat(model.compat.as_ref(), &format!("/models/{index}/compat"))?;
validate_model_limit(model, model.context_window, "contextWindow")?;
validate_model_limit(model, model.max_tokens, "maxTokens")?;
validate_thinking_levels(&model.id, model.thinking_level_map.keys())?;
let _ = effective_pi_model_unchecked(provider, model)?;
}
for (model_id, model_override) in &provider.model_overrides {
if !model_ids.contains(model_id.as_str()) {
return Err(PiConfigError::UnknownModelOverride(model_id.clone()));
}
validate_optional_text(model_override.name.as_deref(), "model override name")?;
validate_optional_limit(model_id, model_override.context_window, "contextWindow")?;
validate_optional_limit(model_id, model_override.max_tokens, "maxTokens")?;
validate_thinking_levels(model_id, model_override.thinking_level_map.keys())?;
validate_compat(
model_override.compat.as_ref(),
&format!("/modelOverrides/{}/compat", escape_json_pointer(model_id)),
)?;
}
Ok(())
}
pub(crate) fn effective_pi_model(
provider: &PiManagedProviderConfig,
model_id: &str,
) -> Result<PiEffectiveModel, PiConfigError> {
let model = provider
.models
.iter()
.find(|model| model.id == model_id)
.ok_or_else(|| PiConfigError::ModelNotFound(model_id.to_string()))?;
validate_pi_managed_provider(provider)?;
effective_pi_model_unchecked(provider, model)
}
fn effective_pi_model_unchecked(
provider: &PiManagedProviderConfig,
model: &PiManagedModel,
) -> Result<PiEffectiveModel, PiConfigError> {
let api = model
.api
.clone()
.or_else(|| provider.api.clone())
.ok_or_else(|| PiConfigError::MissingEffectiveApi {
model_id: model.id.clone(),
})?;
let base_url = model
.base_url
.as_ref()
.or(provider.base_url.as_ref())
.ok_or_else(|| PiConfigError::MissingEffectiveEndpoint {
model_id: model.id.clone(),
})?;
let model_override = provider.model_overrides.get(&model.id);
let mut headers = provider.headers.clone();
if let Some(model_override) = model_override {
headers.extend(model_override.headers.clone());
}
headers.extend(model.headers.clone());
let mut thinking_level_map = model.thinking_level_map.clone();
if let Some(model_override) = model_override {
thinking_level_map.extend(model_override.thinking_level_map.clone());
}
let base_cost = model.cost.clone().unwrap_or_default();
let cost = model_override
.and_then(|entry| entry.cost.as_ref())
.map(|entry| apply_cost_override(base_cost.clone(), entry))
.unwrap_or(base_cost);
let compat = merge_compat(
merge_compat(provider.compat.clone(), model.compat.clone()),
model_override.and_then(|entry| entry.compat.clone()),
);
Ok(PiEffectiveModel {
id: model.id.clone(),
name: model_override
.and_then(|entry| entry.name.clone())
.or_else(|| model.name.clone())
.unwrap_or_else(|| model.id.clone()),
api,
base_url: base_url.clone(),
api_key: provider.api_key.clone(),
auth_header: provider.auth_header.unwrap_or(false),
reasoning: model_override
.and_then(|entry| entry.reasoning)
.or(model.reasoning)
.unwrap_or(false),
thinking_level_map,
input: model_override
.and_then(|entry| entry.input.clone())
.or_else(|| model.input.clone())
.unwrap_or_else(|| vec![PiModelInput::Text]),
cost,
context_window: model_override
.and_then(|entry| entry.context_window)
.or(model.context_window)
.unwrap_or(PiNumber::DEFAULT_CONTEXT_WINDOW),
max_tokens: model_override
.and_then(|entry| entry.max_tokens)
.or(model.max_tokens)
.unwrap_or(PiNumber::DEFAULT_MAX_TOKENS),
headers,
compat,
provider_extra: provider.extra.clone(),
model_extra: model.extra.clone(),
override_extra: model_override
.map(|entry| entry.extra.clone())
.unwrap_or_default(),
})
}
fn apply_cost_override(base: PiModelCost, model_override: &PiModelCostOverride) -> PiModelCost {
PiModelCost {
rates: PiModelCostRates {
input: model_override.input.unwrap_or(base.rates.input),
output: model_override.output.unwrap_or(base.rates.output),
cache_read: model_override.cache_read.unwrap_or(base.rates.cache_read),
cache_write: model_override.cache_write.unwrap_or(base.rates.cache_write),
},
tiers: model_override
.tiers
.clone()
.unwrap_or_else(|| base.tiers.clone()),
}
}
fn merge_compat(base: Option<Value>, overlay: Option<Value>) -> Option<Value> {
match (base, overlay) {
(None, None) => None,
(value, None) | (None, value) => value,
(Some(Value::Object(mut base)), Some(Value::Object(overlay))) => {
for (key, value) in overlay {
if matches!(
key.as_str(),
"openRouterRouting" | "vercelGatewayRouting" | "chatTemplateKwargs"
) {
if let Some(Value::Object(base_nested)) = base.get_mut(&key) {
if let Value::Object(overlay_nested) = &value {
base_nested.extend(overlay_nested.clone());
continue;
}
}
}
base.insert(key, value);
}
Some(Value::Object(base))
}
(Some(_), Some(overlay)) => Some(overlay),
}
}
fn validate_optional_text(value: Option<&str>, field: &'static str) -> Result<(), PiConfigError> {
if value.is_some_and(|value| value.trim().is_empty()) {
return Err(PiConfigError::EmptyOptionalField { field });
}
Ok(())
}
fn validate_model_limit(
model: &PiManagedModel,
value: Option<PiNumber>,
field: &'static str,
) -> Result<(), PiConfigError> {
validate_optional_limit(&model.id, value, field)
}
fn validate_optional_limit(
model_id: &str,
value: Option<PiNumber>,
field: &'static str,
) -> Result<(), PiConfigError> {
if value.is_some_and(|value| value.get() <= 0.0) {
return Err(PiConfigError::NonPositiveModelLimit {
model_id: model_id.to_string(),
field,
});
}
Ok(())
}
fn validate_present_endpoint(
endpoint: Option<&str>,
json_pointer: &str,
) -> Result<(), PiConfigError> {
let Some(endpoint) = endpoint else {
return Ok(());
};
if endpoint.trim().is_empty() {
return Err(PiConfigError::InvalidEndpoint {
json_pointer: json_pointer.to_string(),
reason: "endpoint cannot be empty".to_string(),
});
}
let parsed = Url::parse(endpoint).map_err(|error| PiConfigError::InvalidEndpoint {
json_pointer: json_pointer.to_string(),
reason: error.to_string(),
})?;
if !matches!(parsed.scheme(), "http" | "https") || parsed.host().is_none() {
return Err(PiConfigError::InvalidEndpoint {
json_pointer: json_pointer.to_string(),
reason: "endpoint must be an absolute HTTP(S) URL".to_string(),
});
}
Ok(())
}
fn validate_compat(compat: Option<&Value>, json_pointer: &str) -> Result<(), PiConfigError> {
if compat.is_some_and(|value| !value.is_object()) {
return Err(PiConfigError::InvalidCompat {
json_pointer: json_pointer.to_string(),
});
}
Ok(())
}
fn escape_json_pointer(value: &str) -> String {
value.replace('~', "~0").replace('/', "~1")
}
fn validate_thinking_levels<'a>(
model_id: &str,
levels: impl Iterator<Item = &'a String>,
) -> Result<(), PiConfigError> {
for level in levels {
if !THINKING_LEVELS.contains(&level.as_str()) {
return Err(PiConfigError::InvalidThinkingLevel {
model_id: model_id.to_string(),
level: level.clone(),
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn api(value: &str) -> PiManagedApiId {
PiManagedApiId::new(value).expect("non-empty API id")
}
fn number(value: f64) -> PiNumber {
PiNumber::new(value).expect("finite Pi Number")
}
fn model(id: &str) -> PiManagedModel {
PiManagedModel {
id: id.to_string(),
name: None,
base_url: None,
api: None,
reasoning: None,
thinking_level_map: BTreeMap::new(),
input: None,
cost: None,
context_window: None,
max_tokens: None,
headers: BTreeMap::new(),
compat: None,
extra: BTreeMap::new(),
}
}
fn provider(models: Vec<PiManagedModel>) -> PiManagedProviderConfig {
PiManagedProviderConfig {
name: Some("Example".into()),
base_url: Some("https://example.com/api".into()),
api: Some(api("anthropic-messages")),
api_key: Some("$PI_KEY".into()),
headers: BTreeMap::new(),
auth_header: None,
models,
model_overrides: BTreeMap::new(),
compat: None,
extra: BTreeMap::new(),
}
}
#[test]
fn opaque_api_ids_round_trip_without_gateway_narrowing() {
let config: PiManagedProviderConfig = serde_json::from_value(json!({
"baseUrl": "https://future.example/v9",
"api": "future-wire-v9",
"models": [{"id": "future"}]
}))
.expect("opaque API id");
validate_pi_managed_provider(&config).expect("future API is manageable");
let effective = effective_pi_model(&config, "future").expect("effective future model");
assert_eq!(effective.api.as_str(), "future-wire-v9");
assert_eq!(
serde_json::to_value(&config).expect("serialize")["api"],
"future-wire-v9"
);
}
#[test]
fn provider_and_model_level_inheritance_is_exactly_two_levels() {
let inherited = effective_pi_model(&provider(vec![model("claude")]), "claude")
.expect("provider defaults");
assert_eq!(inherited.api.as_str(), "anthropic-messages");
assert_eq!(inherited.base_url, "https://example.com/api");
let mut self_contained = model("future");
self_contained.api = Some(api("future-api"));
self_contained.base_url = Some("https://model.example/v2".into());
let mut config = provider(vec![self_contained]);
config.api = None;
config.base_url = None;
let effective = effective_pi_model(&config, "future").expect("model defaults");
assert_eq!(effective.api.as_str(), "future-api");
assert_eq!(effective.base_url, "https://model.example/v2");
}
#[test]
fn override_precedence_and_nested_compat_are_stable() {
let mut base_model = model("m");
base_model.reasoning = Some(false);
base_model.headers = BTreeMap::from([
("layer".into(), "model".into()),
("model".into(), "yes".into()),
]);
base_model.compat = Some(json!({
"supportsStore": true,
"openRouterRouting": {"only": ["model"], "zdr": true}
}));
let mut config = provider(vec![base_model]);
config.headers = BTreeMap::from([
("layer".into(), "provider".into()),
("provider".into(), "yes".into()),
]);
config.compat = Some(json!({"supportsDeveloperRole": true}));
config.model_overrides.insert(
"m".into(),
PiManagedModelOverride {
reasoning: Some(true),
headers: BTreeMap::from([
("layer".into(), "override".into()),
("override".into(), "yes".into()),
]),
compat: Some(json!({
"supportsStore": false,
"openRouterRouting": {"order": ["override"]}
})),
..Default::default()
},
);
let effective = effective_pi_model(&config, "m").expect("effective");
assert!(effective.reasoning);
assert_eq!(effective.headers["layer"], "model");
assert_eq!(
effective.compat,
Some(json!({
"supportsDeveloperRole": true,
"supportsStore": false,
"openRouterRouting": {
"only": ["model"],
"zdr": true,
"order": ["override"]
}
}))
);
}
#[test]
fn fractional_pi_numbers_survive_managed_round_trip() {
let mut fractional = model("fractional");
fractional.context_window = Some(number(128000.5));
fractional.max_tokens = Some(number(16384.25));
let config = provider(vec![fractional]);
let encoded = serde_json::to_value(&config).expect("serialize");
let decoded: PiManagedProviderConfig =
serde_json::from_value(encoded).expect("deserialize");
let effective = effective_pi_model(&decoded, "fractional").expect("effective");
assert_eq!(effective.context_window.get(), 128000.5);
assert_eq!(effective.max_tokens.get(), 16384.25);
}
#[test]
fn managed_narrowing_rejects_duplicates_or_unknown_thinking_keys() {
let duplicate = provider(vec![model("same"), model("same")]);
assert_eq!(
validate_pi_managed_provider(&duplicate),
Err(PiConfigError::DuplicateModelId("same".into()))
);
let mut future_thinking = model("thinking");
future_thinking
.thinking_level_map
.insert("future".into(), Some("opaque".into()));
assert_eq!(
validate_pi_managed_provider(&provider(vec![future_thinking])),
Err(PiConfigError::InvalidThinkingLevel {
model_id: "thinking".into(),
level: "future".into()
})
);
}
#[test]
fn diagnostic_reason_serialization_is_structured() {
let reason = PiDiagnosticReason::new(
PiDiagnosticLayer::Gateway,
PiReasonCode::UnsupportedGatewayFamily,
Some("/models/0/api".into()),
);
assert_eq!(
serde_json::to_value(reason).expect("serialize"),
json!({
"layer": "gateway",
"code": "unsupported_gateway_family",
"jsonPointer": "/models/0/api"
})
);
}
}