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::{
8    ActionId, ApprovalRequirement, ExternalError, InvocationContext, OperationId,
9};
10
11/// Canonical App Facade request selected by Agent-owned tool code.
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13pub struct FacadeRequest {
14    /// Stable idempotency identifier for this tool operation.
15    pub operation_id: OperationId,
16    /// Canonical `/app/...` target. The Agent owns every tool-to-target mapping.
17    pub target: String,
18    /// Structured input validated and normalized by Agent-owned tool code.
19    pub input: Value,
20}
21
22pub use crate::{
23    ClientTask, InteractionKind, InteractionPreview, InteractionTone, PreviewDetail, PreviewValue,
24};
25
26/// Prepared operation awaiting an explicit commit or rejection.
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
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 preview: InteractionPreview,
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    /// Approval requirement computed from the canonical prepared operation.
41    pub approval: ApprovalRequirement,
42    /// Presentation/continuation kind for clients.
43    pub interaction_kind: InteractionKind,
44}
45
46/// Structured result returned by an App Facade operation.
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
48pub struct FacadeResult {
49    /// App Facade result payload.
50    pub value: Value,
51    /// A new prepared continuation when committing a user action still requires user work.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub continuation: Option<PreparedAction>,
54    /// New prompt-context generation when this operation changed Agent-visible facts.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub context_invalidation: Option<crate::execution::ContextInvalidation>,
57}
58
59/// Environment-specific implementation of the App Facade boundary.
60///
61/// Implementations may execute in-process (`MeowCore`) or through RPC (agent-cloud/Lion), but they
62/// cannot supply prompts, tools, workflows, or model-facing schemas.
63#[async_trait]
64pub trait AppFacade: Send + Sync {
65    /// Executes a direct App Facade request.
66    async fn invoke(
67        &self,
68        context: &InvocationContext,
69        request: FacadeRequest,
70    ) -> Result<FacadeResult, ExternalError>;
71
72    /// Prepares a mutating operation without applying it.
73    async fn prepare(
74        &self,
75        context: &InvocationContext,
76        request: FacadeRequest,
77    ) -> Result<PreparedAction, ExternalError>;
78
79    /// Commits a prepared operation idempotently.
80    async fn commit(
81        &self,
82        context: &InvocationContext,
83        action: PreparedAction,
84    ) -> Result<FacadeResult, ExternalError>;
85
86    /// Rejects a prepared operation idempotently.
87    async fn reject(
88        &self,
89        context: &InvocationContext,
90        action: PreparedAction,
91    ) -> Result<(), ExternalError>;
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn typed_preview_rejects_incomplete_client_work() {
100        let mut preview = InteractionPreview::confirmation(
101            "integration.install",
102            "Integration required",
103            "Install it, then continue.",
104        );
105        preview.client_task = Some(ClientTask::InstallIntegrations {
106            integration_ids: Vec::new(),
107        });
108        assert!(!preview.is_valid());
109
110        preview.client_task = Some(ClientTask::InstallIntegrations {
111            integration_ids: vec!["matter".to_owned()],
112        });
113        assert!(preview.is_valid());
114        let encoded = serde_json::to_value(preview).expect("serializes");
115        assert_eq!(encoded["clientTask"]["type"], "install_integrations");
116        assert_eq!(encoded["clientTask"]["integrationIds"][0], "matter");
117    }
118}