use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use meerkat_core::skills::{SkillKey, SkillRef};
use meerkat_core::{
BudgetLimits, Config, ContentInput, HookRunOverrides, OutputSchema, PeerCorrelationId,
PeerMeta, Provider, PublicTurnToolOverlay,
comms::{PeerId, PeerName},
config::SystemPromptOverride,
connection::{BindingId, ProfileId, RealmId},
lifecycle::run_primitive::CoreRenderable,
};
use super::mob::{WireForkContext, WireMobBackendKind, WireMobRuntimeMode};
use super::runtime::PeerResponseTerminalStatusWire;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestCreateSessionRequest {
pub prompt: ContentInput,
#[serde(default, skip_serializing_if = "SystemPromptOverride::is_inherit")]
pub system_prompt: SystemPromptOverride,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<Provider>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_schema: Option<OutputSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub structured_output_retries: Option<u32>,
#[serde(default)]
pub verbose: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub keep_alive: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub comms_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub peer_meta: Option<PeerMeta>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hooks_override: Option<HookRunOverrides>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_builtins: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_shell: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_memory: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_mob: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_schedule: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_web_search: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_workgraph: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget_limits: Option<BudgetLimits>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_params: Option<crate::wire::runtime::WireProviderParamsOverride>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preload_skills: Option<Vec<SkillKey>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill_refs: Option<Vec<SkillRef>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub labels: Option<BTreeMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub additional_instructions: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub app_context: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shell_env: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestContinueSessionRequest {
pub session_id: String,
pub prompt: ContentInput,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_schema: Option<OutputSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub structured_output_retries: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub keep_alive: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub comms_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub peer_meta: Option<PeerMeta>,
#[serde(default)]
pub verbose: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<Provider>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hooks_override: Option<HookRunOverrides>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_web_search: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill_refs: Option<Vec<SkillRef>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flow_tool_overlay: Option<PublicTurnToolOverlay>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub additional_instructions: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestAppendSystemContextRequest {
pub content: CoreRenderable,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub idempotency_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RestPeerResponseTerminalRequest {
pub peer_id: PeerId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<PeerName>,
pub request_id: PeerCorrelationId,
pub status: PeerResponseTerminalStatusWire,
pub result: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum RestSetConfigRequest {
Wrapped {
#[cfg_attr(feature = "schema", schemars(with = "Value"))]
config: Config,
#[serde(default, skip_serializing_if = "Option::is_none")]
expected_generation: Option<u64>,
},
Direct(#[cfg_attr(feature = "schema", schemars(with = "Value"))] Config),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum RestPatchConfigRequest {
Wrapped {
patch: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
expected_generation: Option<u64>,
},
Direct(Value),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestAuthProfileCreateRequest {
pub realm_id: RealmId,
pub binding_id: BindingId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<ProfileId>,
pub provider: String,
pub auth_method: String,
pub secret: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestAuthBindingTestRequest {
pub realm_id: RealmId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<ProfileId>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestMobHelperRequest {
pub prompt: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_identity: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runtime_mode: Option<WireMobRuntimeMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backend: Option<WireMobBackendKind>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestMobForkHelperRequest {
pub source_member_id: String,
pub prompt: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_identity: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fork_context: Option<WireForkContext>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runtime_mode: Option<WireMobRuntimeMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backend: Option<WireMobBackendKind>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestMobWaitRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub member_ids: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestMobWireMembersBatchRequest {
pub edges: Vec<super::mob::MobWireMembersBatchEdge>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RestSessionDetailsResponse {
pub session_id: String,
pub session_ref: String,
pub created_at: String,
pub updated_at: String,
pub message_count: usize,
pub total_tokens: u64,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub labels: BTreeMap<String, String>,
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn create_session_request_carries_enable_web_search() {
let parsed: RestCreateSessionRequest = serde_json::from_value(serde_json::json!({
"prompt": "hello",
"enable_web_search": true,
}))
.expect("create-session body parses");
assert_eq!(parsed.enable_web_search, Some(true));
}
#[test]
fn create_session_request_system_prompt_tri_state() {
let parsed: RestCreateSessionRequest = serde_json::from_value(serde_json::json!({
"prompt": "hello",
"system_prompt": "You are terse.",
}))
.expect("string system_prompt parses");
assert_eq!(
parsed.system_prompt,
SystemPromptOverride::Set("You are terse.".to_string())
);
let parsed: RestCreateSessionRequest = serde_json::from_value(serde_json::json!({
"prompt": "hello",
}))
.expect("omitted system_prompt parses");
assert!(parsed.system_prompt.is_inherit());
let parsed: RestCreateSessionRequest = serde_json::from_value(serde_json::json!({
"prompt": "hello",
"system_prompt": {"action": "disable"},
}))
.expect("disable system_prompt parses");
assert_eq!(parsed.system_prompt, SystemPromptOverride::Disable);
}
#[test]
fn peer_response_terminal_rejects_unknown_fields() {
let err = serde_json::from_value::<RestPeerResponseTerminalRequest>(serde_json::json!({
"peer_id": "00000000-0000-0000-0000-000000000000",
"request_id": "00000000-0000-0000-0000-000000000000",
"status": "completed",
"result": {},
"unexpected": true,
}))
.unwrap_err();
assert!(err.to_string().contains("unexpected"));
}
}