Skip to main content

conversation_api/execution/
facade.rs

1//! App Facade contract used by the canonical Agent implementation.
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::execution::{ActionId, ExternalError, InvocationContext, OperationId};
8
9/// Canonical App Facade request selected by Agent-owned tool code.
10#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct FacadeRequest {
13    /// Stable idempotency identifier for this tool operation.
14    pub operation_id: OperationId,
15    /// Canonical `/app/...` target. The Agent owns every tool-to-target mapping.
16    pub target: String,
17    /// Structured input validated and normalized by Agent-owned tool code.
18    pub input: Value,
19    /// Output projection, separate from the immutable business input.
20    pub response_detail: crate::execution::ResponseDetail,
21}
22
23pub use crate::{InteractionCard, InteractionKind, InteractionTone};
24
25/// Prepared operation awaiting an explicit commit or rejection.
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct PreparedAction {
29    /// Stable action identifier used for idempotent commit and rejection.
30    pub action_id: ActionId,
31    /// Idempotency identifier supplied by Runtime.
32    pub operation_id: OperationId,
33    /// App Facade-generated preview suitable for user confirmation.
34    pub card: InteractionCard,
35    /// Opaque execution plan. Runtime persists it but never exposes it to the model or UI.
36    #[serde(default)]
37    pub payload: Value,
38    /// App Facade revision that must still be current when committing.
39    pub revision: u64,
40}
41
42/// Structured result returned by an App Facade operation.
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct FacadeResult {
46    /// In-process presentation provenance, assigned by the adapter from its request.
47    /// Never inferred from the value type or sent on the wire. RPC adapters restore it.
48    #[serde(skip)]
49    pub response_detail: super::ResponseDetail,
50    /// Canonical serialized `FacadeResponse`; never a bare business DTO.
51    #[serde(deserialize_with = "deserialize_response")]
52    pub value: Value,
53    /// New prompt-context generation when this operation changed Agent-visible facts.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub context_invalidation: Option<crate::execution::ContextInvalidation>,
56}
57
58/// Environment-specific implementation of the App Facade boundary.
59///
60/// Implementations may execute in-process (`MeowCore`) or through RPC (agent-cloud/Lion), but they
61/// cannot supply prompts, tools, workflows, or model-facing schemas.
62#[async_trait]
63pub trait AppFacade: Send + Sync {
64    /// Executes a direct App Facade request.
65    async fn invoke(
66        &self,
67        context: &InvocationContext,
68        request: FacadeRequest,
69    ) -> Result<FacadeResult, ExternalError>;
70
71    /// Prepares a mutating operation without applying it.
72    async fn prepare(
73        &self,
74        context: &InvocationContext,
75        request: FacadeRequest,
76    ) -> Result<PreparedAction, ExternalError>;
77
78    /// Commits a prepared operation idempotently.
79    async fn commit(
80        &self,
81        context: &InvocationContext,
82        action: PreparedAction,
83    ) -> Result<FacadeResult, ExternalError>;
84
85    /// Rejects a prepared operation idempotently.
86    async fn reject(
87        &self,
88        context: &InvocationContext,
89        action: PreparedAction,
90    ) -> Result<(), ExternalError>;
91}
92
93fn deserialize_response<'de, D: serde::Deserializer<'de>>(
94    deserializer: D,
95) -> Result<Value, D::Error> {
96    use serde::de::Error;
97    let response = crate::execution::FacadeResponse::deserialize(deserializer)?;
98    serde_json::to_value(response).map_err(D::Error::custom)
99}