465 lines
14 KiB
Rust
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);
|
|
}
|