use dirigent_protocol::adapters::OpenCodeAdapter; use dirigent_protocol::{Event, Message, MessagePart, MessageRole, MessageStatus, Session}; use opencode_client::types as oc; #[test] fn test_parse_opencode_events() { // Load sample events from fixture let fixture = include_str!("fixtures/sample_events.jsonl"); for (idx, line) in fixture.lines().enumerate() { let result = serde_json::from_str::(line); assert!( result.is_ok(), "Failed to parse OpenCode event at line {}: {:?}", idx + 1, result.err() ); } } #[test] fn test_translate_server_connected() { let adapter = OpenCodeAdapter::new(); let oc_event = oc::Event::ServerConnected { properties: serde_json::json!({}), }; let result = adapter.translate_event(oc_event); assert!(result.is_ok()); assert!(matches!(result.unwrap(), Event::Connected)); } #[test] fn test_translate_session_created() { let adapter = OpenCodeAdapter::new(); let oc_session = oc::Session { id: "ses_test123".to_string(), project_id: "test_project".to_string(), directory: "/test/path".to_string(), parent_id: None, summary: None, share: None, title: "Test Session".to_string(), version: "0.15.31".to_string(), time: oc::SessionTime { created: 1700000000000, updated: 1700000000000, compacting: None, }, revert: None, }; let oc_event = oc::Event::SessionCreated { properties: oc::SessionEventInfo { info: oc_session.clone(), }, }; let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::SessionCreated { connector_id: _, session, } => { assert_eq!(session.id, "ses_test123"); assert_eq!(session.title, "Test Session"); assert_eq!(session.metadata.project_path, "/test/path"); } _ => panic!("Expected SessionCreated event"), } } #[test] fn test_translate_user_message() { let adapter = OpenCodeAdapter::new(); let oc_message = oc::Message::User(oc::UserMessage { id: "msg_user1".to_string(), session_id: "ses_test123".to_string(), time: oc::MessageTime { created: 1700000001000, }, summary: None, }); let oc_event = oc::Event::MessageUpdated { properties: oc::MessageEventInfo { info: oc_message }, }; let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::MessageCompleted { connector_id: _, message, } => { assert_eq!(message.id, "msg_user1"); assert_eq!(message.session_id, "ses_test123"); assert!(matches!(message.role, MessageRole::User)); assert!(matches!(message.status, MessageStatus::Completed)); } _ => panic!("Expected MessageCompleted event for user message"), } } #[test] fn test_translate_assistant_message_streaming() { let oc_message = oc::Message::Assistant(oc::AssistantMessage { id: "msg_asst1".to_string(), session_id: "ses_test123".to_string(), time: oc::AssistantMessageTime { created: 1700000002000, completed: None, // Still streaming }, error: None, system: vec![], parent_id: Some("msg_user1".to_string()), model_id: Some("gpt-4".to_string()), provider_id: Some("openai".to_string()), mode: None, path: None, summary: None, cost: 0.05, tokens: Default::default(), }); let oc_event = oc::Event::MessageUpdated { properties: oc::MessageEventInfo { info: oc_message }, }; let adapter = OpenCodeAdapter::new(); let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::MessageStarted { connector_id: _, message, } => { assert_eq!(message.id, "msg_asst1"); assert!(matches!(message.role, MessageRole::Assistant)); assert!(matches!(message.status, MessageStatus::Streaming)); } _ => panic!("Expected MessageStarted event for streaming message"), } } #[test] fn test_translate_assistant_message_completed() { let oc_message = oc::Message::Assistant(oc::AssistantMessage { id: "msg_asst1".to_string(), session_id: "ses_test123".to_string(), time: oc::AssistantMessageTime { created: 1700000002000, completed: Some(1700000005000), // Completed }, error: None, system: vec![], parent_id: Some("msg_user1".to_string()), model_id: Some("gpt-4".to_string()), provider_id: Some("openai".to_string()), mode: None, path: None, summary: None, cost: 0.05, tokens: Default::default(), }); let oc_event = oc::Event::MessageUpdated { properties: oc::MessageEventInfo { info: oc_message }, }; let adapter = OpenCodeAdapter::new(); let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::MessageCompleted { connector_id: _, message, } => { assert_eq!(message.id, "msg_asst1"); assert!(matches!(message.role, MessageRole::Assistant)); assert!(matches!(message.status, MessageStatus::Completed)); } _ => panic!("Expected MessageCompleted event"), } } #[test] fn test_translate_text_part() { let oc_part = oc::Part::Text(oc::TextPart { id: "prt_text1".to_string(), session_id: "ses_test123".to_string(), message_id: "msg_user1".to_string(), text: "Hello, can you help me?".to_string(), synthetic: Some(false), time: None, }); let oc_event = oc::Event::MessagePartUpdated { properties: oc::MessagePartEventInfo { part: oc_part, delta: None, }, }; let adapter = OpenCodeAdapter::new(); let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::SessionUpdate { connector_id: _, session_id, update, } => { assert_eq!(session_id, "ses_test123"); match update { dirigent_protocol::SessionUpdate::AgentMessageChunk { message_id, content, .. } => { assert_eq!(message_id, "msg_user1"); match content { dirigent_protocol::ContentBlock::Text { text } => { assert_eq!(text, "Hello, can you help me?"); } _ => panic!("Expected Text content block"), } } _ => panic!("Expected AgentMessageChunk"), } } _ => panic!("Expected SessionUpdate event"), } } #[test] fn test_translate_reasoning_part() { let oc_part = oc::Part::Reasoning(oc::ReasoningPart { id: "prt_reasoning1".to_string(), session_id: "ses_test123".to_string(), message_id: "msg_asst1".to_string(), text: "Let me think about this...".to_string(), time: None, }); let oc_event = oc::Event::MessagePartUpdated { properties: oc::MessagePartEventInfo { part: oc_part, delta: Some(" more".to_string()), }, }; let adapter = OpenCodeAdapter::new(); let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::SessionUpdate { connector_id: _, session_id, update, } => { assert_eq!(session_id, "ses_test123"); match update { dirigent_protocol::SessionUpdate::AgentThoughtChunk { message_id, content, .. } => { assert_eq!(message_id, "msg_asst1"); match content { dirigent_protocol::ContentBlock::Text { text } => { // Should use delta, not full text assert_eq!(text, " more"); } _ => panic!("Expected Text content block"), } } _ => panic!("Expected AgentThoughtChunk"), } } _ => panic!("Expected SessionUpdate event"), } } #[test] fn test_translate_tool_part_completed() { let oc_part = oc::Part::Tool(oc::ToolPart { id: "prt_tool1".to_string(), session_id: "ses_test123".to_string(), message_id: "msg_asst1".to_string(), call_id: "call_123".to_string(), tool: "Read".to_string(), state: oc::ToolState::Completed { input: serde_json::json!({"file_path": "/test/file.txt"}), output: "File contents here".to_string(), title: "Reading file".to_string(), metadata: serde_json::json!({}), time: oc::PartTime { start: 1700000003000, end: Some(1700000004000), }, attachments: None, }, metadata: None, }); let oc_event = oc::Event::MessagePartUpdated { properties: oc::MessagePartEventInfo { part: oc_part, delta: None, }, }; let adapter = OpenCodeAdapter::new(); let result = adapter.translate_event(oc_event); assert!(result.is_ok()); match result.unwrap() { Event::SessionUpdate { connector_id: _, session_id, update, } => { assert_eq!(session_id, "ses_test123"); match update { dirigent_protocol::SessionUpdate::ToolCall { message_id, tool_call, .. } => { assert_eq!(message_id, "msg_asst1"); assert_eq!(tool_call.tool_name, "Read"); assert_eq!( tool_call.status, dirigent_protocol::ToolCallStatus::Completed ); assert!(tool_call.raw_input.is_some()); assert_eq!( tool_call.raw_input.unwrap().get("file_path").unwrap(), "/test/file.txt" ); assert!(tool_call.raw_output.is_some()); assert_eq!( tool_call.raw_output.unwrap().as_str().unwrap(), "File contents here" ); } _ => panic!("Expected ToolCall"), } } _ => panic!("Expected SessionUpdate event"), } } #[test] fn test_full_event_stream() { let fixture = include_str!("fixtures/sample_events.jsonl"); let adapter = OpenCodeAdapter::new(); // One adapter for entire stream let mut session_created = false; let mut message_count = 0; let mut part_count = 0; for line in fixture.lines() { let oc_event: oc::Event = serde_json::from_str(line).expect("Failed to parse OpenCode event"); let result = adapter.translate_event(oc_event); match result { Ok(Event::SessionCreated { .. }) => session_created = true, Ok(Event::MessageStarted { .. }) | Ok(Event::MessageCompleted { .. }) => { message_count += 1 } Ok(Event::SessionUpdate { .. }) => part_count += 1, Err(_) => {} // Some events might not translate (e.g., unknown types) or duplicates _ => {} } } assert!(session_created, "Session should be created"); assert!(message_count > 0, "Should have messages"); assert!(part_count > 0, "Should have message parts"); } #[test] fn test_dirigent_protocol_serialization() { // Test that Dirigent protocol types can be serialized and deserialized let session = Session { id: "ses_test".to_string(), title: "Test".to_string(), created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), metadata: dirigent_protocol::SessionMetadata { project_path: "/test".to_string(), model: Some("gpt-4".to_string()), total_messages: 5, system_message: None, current_mode_id: None, _meta: None, project_id: None, }, cwd: None, models: None, modes: None, config_options: None, acp_client_id: None, }; let json = serde_json::to_string(&session).expect("Failed to serialize"); let deserialized: Session = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(session, deserialized); } #[test] fn test_message_protocol_serialization() { let message = Message { id: "msg_test".to_string(), session_id: "ses_test".to_string(), role: MessageRole::Assistant, created_at: chrono::Utc::now(), content: vec![ MessagePart::Text { text: "Hello".to_string(), }, MessagePart::Tool { tool: "Read".to_string(), tool_call_id: None, input: serde_json::json!({"file": "test.txt"}), output: Some(serde_json::json!("content")), }, ], status: MessageStatus::Completed, metadata: None, }; let json = serde_json::to_string(&message).expect("Failed to serialize"); let deserialized: Message = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(message, deserialized); }