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