use serde::{Deserialize, Serialize};
use serde_json::Value;
#[cfg(feature = "schema")]
#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(default)]
#[allow(dead_code)]
pub struct ConfigContractSchema {
#[serde(default, skip_serializing_if = "Value::is_null")]
pub agent: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub storage: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub budget: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub retry: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub tools: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub models: Value,
#[serde(default, skip_serializing_if = "is_zero_u32")]
#[schemars(range(min = 1))]
pub max_tokens: u32,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub shell: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub store: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub comms: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub compaction: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub limits: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub rest: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub hooks: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub skills: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub self_hosted: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub provider_tools: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub model_fallback: Value,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub presentation: Value,
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub realm: std::collections::BTreeMap<String, Value>,
}
#[cfg(feature = "schema")]
fn is_zero_u32(value: &u32) -> bool {
*value == 0
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WireLiveHotSwapSkipReason {
NoOpOrOverride,
IdentityLookupFailed { error: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WireLiveHotSwapSkip {
pub session_id: String,
pub reason: WireLiveHotSwapSkipReason,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WireLiveSwapFailure {
pub session_id: String,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WireLiveChannelRefreshFailure {
OpenConfigBuildFailed { error: String },
SnapshotVersionFailed { error: String },
EnqueueFailed { error: String },
QueueAcceptanceRejected { error: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WireLiveRefreshFailure {
pub session_id: String,
pub failure: WireLiveChannelRefreshFailure,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WireLiveChannelCloseFailure {
SignalFailed { error: String },
CloseAuthorityRejected { error: String },
CommitHandoffMissing,
HostCommitFailed { error: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WireLiveCloseFailure {
pub session_id: String,
pub failure: WireLiveChannelCloseFailure,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WireLiveConfigPropagationReport {
pub clean: bool,
pub swapped: Vec<String>,
pub skipped: Vec<WireLiveHotSwapSkip>,
pub swap_failed: Vec<WireLiveSwapFailure>,
pub refreshed: Vec<String>,
pub closed: Vec<String>,
pub refresh_failed: Vec<WireLiveRefreshFailure>,
pub close_failed: Vec<WireLiveCloseFailure>,
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ConfigWriteResult {
#[serde(flatten)]
pub envelope: meerkat_core::ConfigEnvelope,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub live_propagation: Option<WireLiveConfigPropagationReport>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum ConfigSetParams {
Wrapped {
#[cfg_attr(feature = "schema", schemars(with = "ConfigContractSchema"))]
config: meerkat_core::Config,
#[serde(default, skip_serializing_if = "Option::is_none")]
expected_generation: Option<u64>,
},
Direct(
#[cfg_attr(feature = "schema", schemars(with = "ConfigContractSchema"))]
meerkat_core::Config,
),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ConfigPatchParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub patch: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_generation: Option<u64>,
}
#[cfg(all(test, feature = "schema"))]
mod tests {
use super::*;
#[test]
fn config_contract_schema_matches_max_tokens_validation_floor()
-> Result<(), Box<dyn std::error::Error>> {
let schema = schemars::schema_for!(ConfigContractSchema);
let schema_json = serde_json::to_value(schema)?;
assert_eq!(
schema_json
.pointer("/properties/max_tokens/minimum")
.and_then(serde_json::Value::as_u64),
Some(1),
"Config::validate rejects max_tokens == 0, so the public config-set schema must not advertise zero as valid"
);
Ok(())
}
}