Skip to main content

opi_coding_agent/
picker.rs

1//! Picker integration: bridges provider registry and session listing to
2//! SelectItem for the SelectList widget (task 3.11).
3
4use std::path::Path;
5
6use opi_agent::session_branch::{BranchInfo, SessionTree};
7use opi_tui::select_list::SelectItem;
8
9/// Collect SelectItem entries from all registered providers' model lists.
10///
11/// Each entry's `id` is the fully-qualified `provider:model` spec, `display`
12/// is the model's display name, and `metadata` is the provider id.
13pub fn model_picker_items(registry: &opi_ai::registry::ProviderRegistry) -> Vec<SelectItem> {
14    registry
15        .all_models()
16        .into_iter()
17        .map(|(provider_id, model)| SelectItem {
18            id: format!("{provider_id}:{}", model.id),
19            display: model.display_name.clone(),
20            metadata: provider_id.to_string(),
21        })
22        .collect()
23}
24
25/// Collect SelectItem entries from one provider's advertised model list.
26pub fn model_picker_items_from_provider(
27    provider: &dyn opi_ai::provider::Provider,
28) -> Vec<SelectItem> {
29    let provider_id = provider.id();
30    provider
31        .models()
32        .iter()
33        .map(|model| SelectItem {
34            id: format!("{provider_id}:{}", model.id),
35            display: model.display_name.clone(),
36            metadata: provider_id.to_string(),
37        })
38        .collect()
39}
40
41/// Collect SelectItem entries from a reconstructed session branch tree.
42pub fn branch_picker_items(tree: &SessionTree) -> Vec<SelectItem> {
43    let active_index = tree.active_branch_index();
44    tree.branches()
45        .iter()
46        .enumerate()
47        .map(|(index, branch)| branch_picker_item(branch, index, active_index == Some(index)))
48        .collect()
49}
50
51fn branch_picker_item(branch: &BranchInfo, index: usize, is_active: bool) -> SelectItem {
52    let name = if index == 0 && branch.fork_point.is_none() {
53        "Trunk".to_owned()
54    } else {
55        format!("Branch {}", index + 1)
56    };
57    let display = match branch.summary.as_deref() {
58        Some(summary) if !summary.is_empty() => format!("{name}: {summary}"),
59        _ => name,
60    };
61    let mut metadata = format!(
62        "{} entries, depth {}, tip {}",
63        branch.entry_count, branch.depth, branch.tip_id
64    );
65    if is_active {
66        metadata.push_str(", active");
67    }
68    SelectItem {
69        id: branch.tip_id.clone(),
70        display,
71        metadata,
72    }
73}
74
75/// Collect SelectItem entries from session listing in the given directory.
76///
77/// Each entry's `id` is the session id, `display` is the cwd (truncated if
78/// needed), and `metadata` is the timestamp.
79pub fn session_picker_items(dir: &Path) -> Result<Vec<SelectItem>, std::io::Error> {
80    let sessions = crate::session_cli::list_sessions(dir).unwrap_or_default();
81    Ok(sessions
82        .into_iter()
83        .map(|s| {
84            let cwd_short = if s.cwd.len() > 40 {
85                let start = s.cwd.floor_char_boundary(s.cwd.len() - 37);
86                format!("...{}", &s.cwd[start..])
87            } else {
88                s.cwd
89            };
90            SelectItem {
91                id: s.id,
92                display: cwd_short,
93                metadata: s.timestamp,
94            }
95        })
96        .collect())
97}