cellstate/entity/
turn.rs

1//! `cellstate turn` — Manage turns (individual messages within a scope).
2//!
3//! Subcommands: create, get.
4
5use anyhow::{anyhow, Result};
6use clap::{Arg, ArgMatches, Command};
7
8use cellstate_core::api_types::{CreateTurnRequest, TurnResponse};
9
10use super::client::ApiClient;
11use super::output::OutputConfig;
12use super::require_arg;
13
14const BASE: &str = "/api/v1/turns";
15
16pub fn build_command() -> Command {
17    Command::new("turn")
18        .about("Manage turns (individual messages within a scope)")
19        .subcommand_required(true)
20        .subcommand(
21            Command::new("create")
22                .about("Create a new turn")
23                .arg(
24                    Arg::new("scope-id")
25                        .long("scope-id")
26                        .help("Scope ID this turn belongs to (defaults to session scope)"),
27                )
28                .arg(
29                    Arg::new("role")
30                        .long("role")
31                        .required(true)
32                        .help("Role of the turn (user, assistant, system, tool)"),
33                )
34                .arg(
35                    Arg::new("content")
36                        .long("content")
37                        .required(true)
38                        .help("Content of the turn"),
39                )
40                .arg(
41                    Arg::new("sequence")
42                        .long("sequence")
43                        .default_value("0")
44                        .help("Sequence number within the scope"),
45                )
46                .arg(
47                    Arg::new("token-count")
48                        .long("token-count")
49                        .default_value("0")
50                        .help("Token count"),
51                )
52                .arg(
53                    Arg::new("tool-calls")
54                        .long("tool-calls")
55                        .help("Tool calls as JSON"),
56                )
57                .arg(
58                    Arg::new("tool-results")
59                        .long("tool-results")
60                        .help("Tool results as JSON"),
61                )
62                .arg(
63                    Arg::new("metadata")
64                        .long("metadata")
65                        .help("Additional metadata as JSON"),
66                ),
67        )
68        .subcommand(
69            Command::new("get")
70                .about("Get turn details")
71                .arg(Arg::new("id").required(true).help("Turn ID")),
72        )
73}
74
75pub async fn dispatch(
76    matches: &ArgMatches,
77    client: &ApiClient,
78    output: &OutputConfig,
79    session: &crate::session::CliSession,
80) -> Result<()> {
81    match matches.subcommand() {
82        Some(("create", sub)) => {
83            let scope_id =
84                require_arg(sub, "scope-id", session.scope_id.as_deref(), "scope")?.parse()?;
85            let role = sub
86                .get_one::<String>("role")
87                .unwrap()
88                .parse()
89                .map_err(|e: String| anyhow!(e))?;
90            let content = sub.get_one::<String>("content").unwrap().clone();
91            let sequence: i32 = sub.get_one::<String>("sequence").unwrap().parse()?;
92            let token_count: i32 = sub.get_one::<String>("token-count").unwrap().parse()?;
93            let tool_calls = sub
94                .get_one::<String>("tool-calls")
95                .map(|s| serde_json::from_str(s))
96                .transpose()?;
97            let tool_results = sub
98                .get_one::<String>("tool-results")
99                .map(|s| serde_json::from_str(s))
100                .transpose()?;
101            let metadata = sub
102                .get_one::<String>("metadata")
103                .map(|s| serde_json::from_str(s))
104                .transpose()?;
105            let req = CreateTurnRequest {
106                scope_id,
107                sequence,
108                role,
109                content,
110                token_count,
111                tool_calls,
112                tool_results,
113                metadata,
114            };
115            let resp: TurnResponse = client.post(BASE, &req).await?;
116            output.print(&resp);
117        }
118        Some(("get", sub)) => {
119            let id = sub.get_one::<String>("id").unwrap();
120            let resp: TurnResponse = client.get(&format!("{BASE}/{id}")).await?;
121            output.print(&resp);
122        }
123        _ => unreachable!("subcommand_required(true) prevents this"),
124    }
125    Ok(())
126}