-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathmulti_turn.rs
More file actions
156 lines (143 loc) · 5.64 KB
/
Copy pathmulti_turn.rs
File metadata and controls
156 lines (143 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use github_copilot_sdk::SessionEvent;
use github_copilot_sdk::session_events::SessionEventType;
use super::support::{
assistant_message_content, collect_until_idle, event_types, with_e2e_context,
};
#[tokio::test]
async fn should_use_tool_results_from_previous_turns() {
with_e2e_context(
"multi_turn",
"should_use_tool_results_from_previous_turns",
|ctx| {
Box::pin(async move {
ctx.set_default_copilot_user();
std::fs::write(ctx.work_dir().join("secret.txt"), "The magic number is 42.")
.expect("write secret");
let client = ctx.start_client().await;
let session = client
.create_session(ctx.approve_all_session_config())
.await
.expect("create session");
let first_events = session.subscribe();
let first = session
.send_and_wait(
"Read the file 'secret.txt' and tell me what the magic number is.",
)
.await
.expect("first send")
.expect("assistant message");
assert!(assistant_message_content(&first).contains("42"));
assert_tool_turn_ordering(
&collect_until_idle(first_events).await,
"file read turn",
);
let second = session
.send_and_wait("What is that magic number multiplied by 2?")
.await
.expect("second send")
.expect("assistant message");
assert!(assistant_message_content(&second).contains("84"));
session.disconnect().await.expect("disconnect session");
client.stop().await.expect("stop client");
})
},
)
.await;
}
#[tokio::test]
async fn should_handle_file_creation_then_reading_across_turns() {
with_e2e_context(
"multi_turn",
"should_handle_file_creation_then_reading_across_turns",
|ctx| {
Box::pin(async move {
ctx.set_default_copilot_user();
let client = ctx.start_client().await;
let session = client
.create_session(ctx.approve_all_session_config())
.await
.expect("create session");
let create_events = session.subscribe();
session
.send_and_wait(
"Create a file called 'greeting.txt' with the content \
'Hello from multi-turn test'.",
)
.await
.expect("create file turn");
assert_eq!(
std::fs::read_to_string(ctx.work_dir().join("greeting.txt"))
.expect("read greeting"),
"Hello from multi-turn test"
);
assert_tool_turn_ordering(
&collect_until_idle(create_events).await,
"file creation turn",
);
let read_events = session.subscribe();
let answer = session
.send_and_wait("Read the file 'greeting.txt' and tell me its exact contents.")
.await
.expect("read file turn")
.expect("assistant message");
assert!(assistant_message_content(&answer).contains("Hello from multi-turn test"));
assert_tool_turn_ordering(&collect_until_idle(read_events).await, "file read turn");
session.disconnect().await.expect("disconnect session");
client.stop().await.expect("stop client");
})
},
)
.await;
}
fn assert_tool_turn_ordering(events: &[SessionEvent], turn_description: &str) {
let observed_types = event_types(events).join(", ");
let user_message = index_of(events, SessionEventType::UserMessage, 0);
let tool_starts: Vec<_> = events
.iter()
.enumerate()
.filter(|(_, event)| event.parsed_type() == SessionEventType::ToolExecutionStart)
.collect();
let tool_completes: Vec<_> = events
.iter()
.enumerate()
.filter(|(_, event)| event.parsed_type() == SessionEventType::ToolExecutionComplete)
.collect();
assert!(
user_message.is_some(),
"expected user.message in {turn_description}; observed: {observed_types}"
);
assert!(
!tool_starts.is_empty(),
"expected tool.execution_start in {turn_description}; observed: {observed_types}"
);
assert!(
!tool_completes.is_empty(),
"expected tool.execution_complete in {turn_description}; observed: {observed_types}"
);
assert!(user_message.unwrap() < tool_starts[0].0);
let last_tool_complete = tool_completes
.last()
.map(|(index, _)| *index)
.expect("last tool completion");
let assistant = index_of(
events,
SessionEventType::AssistantMessage,
last_tool_complete + 1,
)
.expect("assistant.message after tools");
let idle = index_of(events, SessionEventType::SessionIdle, assistant + 1)
.expect("session.idle after assistant");
assert!(last_tool_complete < assistant);
assert!(assistant < idle);
}
fn index_of(
events: &[SessionEvent],
event_type: SessionEventType,
start_index: usize,
) -> Option<usize> {
events
.iter()
.enumerate()
.skip(start_index)
.find_map(|(index, event)| (event.parsed_type() == event_type).then_some(index))
}