Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/jp_cli/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub(crate) mod plugin;
mod query;
pub(crate) mod target;
pub(crate) mod time;
pub(crate) mod turn_range;

use std::{fmt, num::NonZeroU8};

Expand All @@ -22,7 +23,6 @@ use crate::{Ctx, ctx::IntoPartialAppConfig};

#[derive(Debug, clap::Subcommand)]
#[command(disable_help_subcommand = true, allow_external_subcommands = true)]
#[expect(clippy::large_enum_variant)]
pub(crate) enum Commands {
/// Initialize a new workspace.
Init(init::Init),
Expand Down
28 changes: 17 additions & 11 deletions crates/jp_cli/src/cmd/compact_flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ impl clap::Args for CompactFlag {
`r` / `reasoning`: strip reasoning blocks\n- `s` / `summarize`: generate an \
LLM summary\n- `t` / `tools` (or `t=MODE`): strip tool calls; bare strips \
both, or MODE is one of `strip`/`s`, `strip-requests`/`sreq`, \
`strip-responses`/`sres`, `omit`/`o`\n\nRange: FROM..TO, single number, or \
.. for all\n\nExamples: s:..-3, r+t, t=sreq:5..-3, r:-20",
`strip-responses`/`sres`, `omit`/`o`\n\nRange: FROM..TO (1-based, inclusive \
on both ends, so 1..5 is turns 1-5), single number, or .. for \
all\n\nExamples: s:..-3, r+t, t=sreq:5..-3, r:-20",
)
.action(ArgAction::Append)
.num_args(0..=1)
Expand Down Expand Up @@ -165,10 +166,10 @@ pub(crate) struct CompactSpec {
pub range: Option<DslRange>,
}

/// A parsed DSL range, Python-slice style.
/// A parsed DSL range (`FROM..TO`), inclusive on both ends.
///
/// Each bound is an absolute turn index (positive in the DSL) or a from-end
/// offset (negative).
/// Each bound is a 1-based absolute turn index (positive in the DSL) or a
/// from-end offset (negative).
/// `None` means that end is open (the start or the end of the conversation).
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DslRange {
Expand All @@ -193,9 +194,10 @@ impl CompactSpec {
}

if let Some(range) = &self.range {
// Open ends map to start / end: `Absolute(0)` is turn 0, `FromEnd(0)`
// Open ends map to the conversation start / end: `Turns(0)` keeps
// nothing at the front (compact from the first turn), `FromEnd(0)`
// is the last turn.
rule.keep_first = Some(range.from.clone().unwrap_or(RuleBound::Absolute(0)));
rule.keep_first = Some(range.from.clone().unwrap_or(RuleBound::Turns(0)));
rule.keep_last = Some(range.to.clone().unwrap_or(RuleBound::FromEnd(0)));
}

Expand Down Expand Up @@ -263,23 +265,27 @@ impl FromStr for CompactSpec {
}
}

/// Parse one DSL range bound: a positive integer is an absolute turn index, a
/// negative integer is an offset from the end.
/// Parse one DSL range bound: a positive integer is a 1-based absolute turn
/// index, a negative integer is an offset from the end.
fn parse_dsl_bound(s: &str) -> Result<RuleBound, String> {
if let Some(rest) = s.strip_prefix('-') {
let n = rest
.parse()
.map_err(|_| format!("invalid bound '-{rest}'"))?;
Ok(RuleBound::FromEnd(n))
} else {
let n = s.parse().map_err(|_| format!("invalid bound '{s}'"))?;
let n: usize = s.parse().map_err(|_| format!("invalid bound '{s}'"))?;
if n == 0 {
return Err("turn indices are 1-based; `0` is not a valid turn".to_owned());
}
Ok(RuleBound::Absolute(n))
}
}

fn parse_dsl_range(s: &str) -> Result<DslRange, String> {
// Explicit range: FROM..TO (either side may be empty). Both ends are
// Python-slice style: positive = absolute turn, negative = from the end.
// inclusive: a positive bound is a 1-based absolute turn, a negative bound
// counts from the end.
if let Some((left, right)) = s.split_once("..") {
let from = if left.is_empty() {
None
Expand Down
8 changes: 6 additions & 2 deletions crates/jp_cli/src/cmd/compact_flag_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ fn parse_errors() {
assert!("s:abc".parse::<CompactSpec>().is_err());
// Non-numeric bound
assert!("s:5..x".parse::<CompactSpec>().is_err());
// Absolute bounds are 1-based; `0` is invalid.
assert!("s:0..".parse::<CompactSpec>().is_err());
assert!("s:0..5".parse::<CompactSpec>().is_err());
// Unknown tool mode
assert!("t=nope".parse::<CompactSpec>().is_err());
// Boolean policies reject values
Expand All @@ -170,8 +173,9 @@ fn to_partial_rule_with_range() {
assert_eq!(rule.reasoning, Some(ReasoningMode::Strip));
assert_eq!(rule.tool_calls, Some(ToolCallsMode::Strip));
assert!(rule.summary.is_none());
// Open start maps to absolute turn 0; `-3` keeps the last 3.
assert_eq!(rule.keep_first, Some(RuleBound::Absolute(0)));
// Open start maps to keep-first 0 (compact from the first turn); `-3` keeps
// the last 3.
assert_eq!(rule.keep_first, Some(RuleBound::Turns(0)));
assert_eq!(rule.keep_last, Some(RuleBound::FromEnd(3)));
}

Expand Down
Loading
Loading