lean_ctx/core/a2a/
transfer.rs1use serde_json::Value;
9
10use crate::core::handoff_transfer_bundle::HandoffTransferBundleV1;
11
12pub const BUNDLE_MIME_V1: &str = "application/vnd.leanctx.handoff-bundle.v1+json";
14
15pub fn wrap_bundle_as_a2a_task(bundle: &HandoffTransferBundleV1) -> Result<Value, String> {
20 let bundle_json =
21 serde_json::to_value(bundle).map_err(|e| format!("bundle serialization failed: {e}"))?;
22
23 let session_id = &bundle.ledger.session.id;
24 let task_id = format!(
25 "handoff-{session_id}-{}",
26 &bundle.ledger.content_md5[..bundle.ledger.content_md5.len().min(8)]
27 );
28
29 Ok(serde_json::json!({
30 "id": task_id,
31 "status": {
32 "state": "completed",
33 "timestamp": bundle.exported_at.to_rfc3339(),
34 },
35 "messages": [],
36 "artifacts": [{
37 "type": "data",
38 "mimeType": BUNDLE_MIME_V1,
39 "data": bundle_json,
40 }],
41 "history": [],
42 "metadata": {
43 "producer": "lean-ctx",
44 "bundleSchemaVersion": bundle.schema_version,
45 "privacy": bundle.privacy,
46 },
47 }))
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::core::handoff_ledger::HandoffLedgerV1;
54 use crate::core::handoff_transfer_bundle::{ArtifactsExcerptV1, ProjectIdentityV1};
55
56 fn sample_bundle() -> HandoffTransferBundleV1 {
57 let mut ledger = HandoffLedgerV1::default();
58 ledger.session.id = "sess-42".to_string();
59 ledger.content_md5 = "abcdef0123456789".to_string();
60 HandoffTransferBundleV1 {
61 schema_version: 1,
62 exported_at: chrono::DateTime::parse_from_rfc3339("2026-06-01T12:00:00Z")
63 .unwrap()
64 .with_timezone(&chrono::Utc),
65 privacy: "redacted".to_string(),
66 project: ProjectIdentityV1 {
67 project_root_hash: None,
68 project_identity_hash: None,
69 },
70 ledger,
71 artifacts: ArtifactsExcerptV1::default(),
72 signature: None,
73 signer_public_key: None,
74 signer_agent_id: None,
75 }
76 }
77
78 #[test]
79 fn wraps_bundle_with_required_a2a_task_fields() {
80 let task = wrap_bundle_as_a2a_task(&sample_bundle()).unwrap();
81 assert_eq!(task["id"], "handoff-sess-42-abcdef01");
82 assert_eq!(task["status"]["state"], "completed");
83 assert_eq!(task["artifacts"][0]["type"], "data");
84 assert_eq!(task["artifacts"][0]["mimeType"], BUNDLE_MIME_V1);
85 assert_eq!(task["artifacts"][0]["data"]["schema_version"], 1);
86 assert_eq!(task["metadata"]["producer"], "lean-ctx");
87 }
88
89 #[test]
90 fn wrap_is_deterministic() {
91 let a = wrap_bundle_as_a2a_task(&sample_bundle()).unwrap();
92 let b = wrap_bundle_as_a2a_task(&sample_bundle()).unwrap();
93 assert_eq!(a, b);
94 }
95}