codex_utils_cli/
resume_command.rs1use codex_protocol::ThreadId;
4use codex_shell_command::parse_command::shlex_join;
5
6pub fn resume_command(thread_name: Option<&str>, thread_id: Option<ThreadId>) -> Option<String> {
7 let resume_target = thread_name
8 .filter(|name| !name.is_empty())
9 .map(str::to_string)
10 .or_else(|| thread_id.map(|thread_id| thread_id.to_string()));
11 resume_target.map(|target| {
12 let needs_double_dash = target.starts_with('-');
13 let escaped = shlex_join(&[target]);
14 if needs_double_dash {
15 format!("codex resume -- {escaped}")
16 } else {
17 format!("codex resume {escaped}")
18 }
19 })
20}
21
22pub fn resume_hint(thread_name: Option<&str>, thread_id: Option<ThreadId>) -> Option<String> {
23 let thread_id = thread_id?;
24 match thread_name.filter(|name| !name.is_empty()) {
25 Some(thread_name) => Some(format!(
26 "codex resume, then select {thread_name} ({thread_id})"
27 )),
28 None => resume_command(None, Some(thread_id)),
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use pretty_assertions::assert_eq;
36
37 #[test]
38 fn prefers_name_over_id() {
39 let thread_id = ThreadId::from_string("123e4567-e89b-12d3-a456-426614174000").unwrap();
40 let command = resume_command(Some("my-thread"), Some(thread_id));
41 assert_eq!(command, Some("codex resume my-thread".to_string()));
42 }
43
44 #[test]
45 fn formats_thread_id_when_name_is_missing() {
46 let thread_id = ThreadId::from_string("123e4567-e89b-12d3-a456-426614174000").unwrap();
47 let command = resume_command(None, Some(thread_id));
48 assert_eq!(
49 command,
50 Some("codex resume 123e4567-e89b-12d3-a456-426614174000".to_string())
51 );
52 }
53
54 #[test]
55 fn returns_none_without_a_resume_target() {
56 let command = resume_command(None, None);
57 assert_eq!(command, None);
58 }
59
60 #[test]
61 fn quotes_thread_names_when_needed() {
62 let command = resume_command(Some("-starts-with-dash"), None);
63 assert_eq!(
64 command,
65 Some("codex resume -- -starts-with-dash".to_string())
66 );
67
68 let command = resume_command(Some("two words"), None);
69 assert_eq!(command, Some("codex resume 'two words'".to_string()));
70
71 let command = resume_command(Some("quote'case"), None);
72 assert_eq!(command, Some("codex resume \"quote'case\"".to_string()));
73 }
74
75 #[test]
76 fn resume_hint_names_picker_item_with_id() {
77 let thread_id = ThreadId::from_string("123e4567-e89b-12d3-a456-426614174000").unwrap();
78 let hint = resume_hint(Some("my-thread"), Some(thread_id));
79 assert_eq!(
80 hint,
81 Some(
82 "codex resume, then select my-thread (123e4567-e89b-12d3-a456-426614174000)"
83 .to_string()
84 )
85 );
86 }
87
88 #[test]
89 fn resume_hint_uses_direct_id_command_without_name() {
90 let thread_id = ThreadId::from_string("123e4567-e89b-12d3-a456-426614174000").unwrap();
91 let hint = resume_hint(None, Some(thread_id));
92 assert_eq!(
93 hint,
94 Some("codex resume 123e4567-e89b-12d3-a456-426614174000".to_string())
95 );
96 }
97
98 #[test]
99 fn resume_hint_requires_thread_id() {
100 let hint = resume_hint(Some("my-thread"), None);
101 assert_eq!(hint, None);
102 }
103}