Files
2026-05-08 01:59:04 +02:00

465 lines
14 KiB
Rust

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::<oc::Event>(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);
}