Skip to content
Open
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
13 changes: 4 additions & 9 deletions crates/path-cli/src/cmd_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1405,9 +1405,9 @@ fn build_cursor_session(
// Reuse the existing id when present, otherwise pre-create a
// workspaceStorage entry so Cursor adopts ours on next open.
let resolver = PathResolver::new();
if let Ok(ensured) = resolver.ensure_workspace_storage_entry(&canonical, |path| {
stable_workspace_id_for(path)
}) {
if let Ok(ensured) = resolver
.ensure_workspace_storage_entry(&canonical, |path| stable_workspace_id_for(path))
{
projector = projector.with_workspace_id(ensured.id);
if ensured.created {
eprintln!(
Expand Down Expand Up @@ -1535,13 +1535,8 @@ fn cursor_open_hints(workspace: &std::path::Path) -> Vec<String> {
}
}


#[cfg(not(target_os = "emscripten"))]
fn upsert_cursor_kv(
tx: &rusqlite::Transaction<'_>,
key: &str,
value: &str,
) -> Result<()> {
fn upsert_cursor_kv(tx: &rusqlite::Transaction<'_>, key: &str, value: &str) -> Result<()> {
tx.execute(
"INSERT OR REPLACE INTO cursorDiskKV (key, value) VALUES (?1, ?2)",
rusqlite::params![key, value],
Expand Down
9 changes: 4 additions & 5 deletions crates/path-cli/src/cmd_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1144,16 +1144,15 @@ fn derive_cursor(
Ok(toolpath_cursor::derive_path(&s, &cfg))
};

let workspace_filter = project.as_deref().map(|p| {
std::fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(p))
});
let workspace_filter = project
.as_deref()
.map(|p| std::fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(p)));
let workspace_match = |m: &toolpath_cursor::CursorSessionMetadata| -> bool {
match (&workspace_filter, &m.workspace_path) {
(None, _) => true,
(Some(_), None) => false,
(Some(want), Some(have)) => {
let canonical =
std::fs::canonicalize(have).unwrap_or_else(|_| have.clone());
let canonical = std::fs::canonicalize(have).unwrap_or_else(|_| have.clone());
&canonical == want
}
}
Expand Down
5 changes: 1 addition & 4 deletions crates/path-cli/src/cmd_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,10 +836,7 @@ fn harness_status_pi(bundle: &HarnessBundle, home: Option<&std::path::Path>) ->
}
}

fn harness_status_cursor(
bundle: &HarnessBundle,
home: Option<&std::path::Path>,
) -> HarnessStatus {
fn harness_status_cursor(bundle: &HarnessBundle, home: Option<&std::path::Path>) -> HarnessStatus {
let Some(mgr) = &bundle.cursor else {
return HarnessStatus::unresolved();
};
Expand Down
11 changes: 9 additions & 2 deletions crates/toolpath-claude/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,15 +1137,22 @@ mod tests {

// Wire: the total is stamped on every line of the split, each tagged
// with the shared message.id.
for entry in content_entries(&convo).iter().filter(|e| e.entry_type == "assistant") {
for entry in content_entries(&convo)
.iter()
.filter(|e| e.entry_type == "assistant")
{
let msg = entry.message.as_ref().unwrap();
assert_eq!(msg.id.as_deref(), Some("msg_A"));
assert_eq!(msg.usage.as_ref().unwrap().output_tokens, Some(164));
}

// Re-read: total back on the final turn only; no fabricated attribution.
let back = crate::provider::to_view(&convo);
let a: Vec<&Turn> = back.turns.iter().filter(|t| t.role == Role::Assistant).collect();
let a: Vec<&Turn> = back
.turns
.iter()
.filter(|t| t.role == Role::Assistant)
.collect();
assert!(a[0].token_usage.is_none());
assert_eq!(a[1].token_usage.as_ref().unwrap().output_tokens, Some(164));
assert!(a.iter().all(|t| t.attributed_token_usage.is_none()));
Expand Down
10 changes: 8 additions & 2 deletions crates/toolpath-claude/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,10 @@ mod tests {
canonicalize_message_usage(&mut turns);

assert!(turns[0].token_usage.is_none(), "total only on final turn");
assert_eq!(turns[1].token_usage.as_ref().unwrap().output_tokens, Some(164));
assert_eq!(
turns[1].token_usage.as_ref().unwrap().output_tokens,
Some(164)
);
assert_eq!(turns[1].token_usage.as_ref().unwrap().input_tokens, Some(6));
for t in &turns {
assert!(
Expand Down Expand Up @@ -919,7 +922,10 @@ mod tests {

assert!(turns[0].token_usage.is_none());
assert!(turns[1].token_usage.is_none());
assert_eq!(turns[2].token_usage.as_ref().unwrap().output_tokens, Some(997));
assert_eq!(
turns[2].token_usage.as_ref().unwrap().output_tokens,
Some(997)
);
for t in &turns {
assert!(t.attributed_token_usage.is_none());
}
Expand Down
3 changes: 1 addition & 2 deletions crates/toolpath-claude/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ impl ConversationReader {
}

if !entry.timestamp.is_empty()
&& let Ok(timestamp) =
entry.timestamp.parse::<chrono::DateTime<chrono::Utc>>()
&& let Ok(timestamp) = entry.timestamp.parse::<chrono::DateTime<chrono::Utc>>()
{
if started_at.is_none() || Some(timestamp) < started_at {
started_at = Some(timestamp);
Expand Down
30 changes: 21 additions & 9 deletions crates/toolpath-codex/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ fn project_view(
.find(|(_, t)| matches!(t.role, Role::Assistant))
.map(|(i, t)| group_of(i, t))
.unwrap_or_else(|| view.id.clone());
lines.push(make_turn_context_line(&first_group, &session_timestamp, &cwd, &model));
lines.push(make_turn_context_line(
&first_group,
&session_timestamp,
&cwd,
&model,
));
let mut current_group = Some(first_group);

// Running session-cumulative usage. Codex's `total_token_usage` is
Expand All @@ -180,13 +185,25 @@ fn project_view(
if matches!(turn.role, Role::Assistant) {
let group = group_of(idx, turn);
if current_group.as_deref() != Some(&group) {
lines.push(make_turn_context_line(&group, &turn.timestamp, &cwd, &model));
lines.push(make_turn_context_line(
&group,
&turn.timestamp,
&cwd,
&model,
));
current_group = Some(group);
}
}
let codex = codex_extras(turn).cloned().unwrap_or_default();
let is_final_assistant = Some(idx) == last_assistant_idx;
emit_turn_lines(turn, &codex, is_final_assistant, &cwd, &mut lines, &mut running);
emit_turn_lines(
turn,
&codex,
is_final_assistant,
&cwd,
&mut lines,
&mut running,
);
}

Ok(crate::types::Session {
Expand Down Expand Up @@ -234,12 +251,7 @@ fn make_session_meta_line(
}
}

fn make_turn_context_line(
turn_id: &str,
timestamp: &str,
cwd: &str,
model: &str,
) -> RolloutLine {
fn make_turn_context_line(turn_id: &str, timestamp: &str, cwd: &str, model: &str) -> RolloutLine {
let tc = TurnContext {
turn_id: turn_id.to_string(),
cwd: PathBuf::from(cwd),
Expand Down
74 changes: 60 additions & 14 deletions crates/toolpath-codex/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,11 @@ impl<'a> Builder<'a> {
// A step's spend that arrived before any assistant turn existed
// attaches to this, the first one.
if let Some(pending) = self.pending_attributed.take() {
add_usage(turn.attributed_token_usage.get_or_insert_with(TokenUsage::default), &pending);
add_usage(
turn.attributed_token_usage
.get_or_insert_with(TokenUsage::default),
&pending,
);
}
}

Expand Down Expand Up @@ -690,9 +694,7 @@ impl<'a> Builder<'a> {
let start = k;
let mid = self.turns[assistants[k]].group_id.clone();
if mid.is_some() {
while k + 1 < assistants.len()
&& self.turns[assistants[k + 1]].group_id == mid
{
while k + 1 < assistants.len() && self.turns[assistants[k + 1]].group_id == mid {
k += 1;
}
}
Expand Down Expand Up @@ -1158,14 +1160,38 @@ mod tests {
let (_t, mgr, id) = setup_session_fixture(&body);
let view = to_view(&mgr.read_session(&id).unwrap());

let assistants: Vec<&Turn> = view.turns.iter().filter(|t| t.role == Role::Assistant).collect();
let assistants: Vec<&Turn> = view
.turns
.iter()
.filter(|t| t.role == Role::Assistant)
.collect();
assert_eq!(assistants.len(), 2);
// Per-step attribution: 40 then 60 — NOT 80/120 (which doubling gives).
assert_eq!(assistants[0].attributed_token_usage.as_ref().unwrap().output_tokens, Some(40));
assert_eq!(assistants[1].attributed_token_usage.as_ref().unwrap().output_tokens, Some(60));
assert_eq!(
assistants[0]
.attributed_token_usage
.as_ref()
.unwrap()
.output_tokens,
Some(40)
);
assert_eq!(
assistants[1]
.attributed_token_usage
.as_ref()
.unwrap()
.output_tokens,
Some(60)
);
// Σ attributed == round total on the final turn.
assert_eq!(assistants[1].token_usage.as_ref().unwrap().output_tokens, Some(100));
let sum: u32 = assistants.iter().filter_map(|t| t.attributed_token_usage.as_ref()?.output_tokens).sum();
assert_eq!(
assistants[1].token_usage.as_ref().unwrap().output_tokens,
Some(100)
);
let sum: u32 = assistants
.iter()
.filter_map(|t| t.attributed_token_usage.as_ref()?.output_tokens)
.sum();
assert_eq!(sum, 100);
}

Expand Down Expand Up @@ -1201,11 +1227,21 @@ mod tests {
let (_t, mgr, id) = setup_session_fixture(&body);
let view = to_view(&mgr.read_session(&id).unwrap());

let assistants: Vec<&Turn> = view.turns.iter().filter(|t| t.role == Role::Assistant).collect();
let assistants: Vec<&Turn> = view
.turns
.iter()
.filter(|t| t.role == Role::Assistant)
.collect();
assert_eq!(assistants.len(), 2);
// Per-step reasoning deltas, NOT cumulative (100/260) and NOT doubled.
assert_eq!(reasoning_of(assistants[0].attributed_token_usage.as_ref()), Some(100));
assert_eq!(reasoning_of(assistants[1].attributed_token_usage.as_ref()), Some(160));
assert_eq!(
reasoning_of(assistants[0].attributed_token_usage.as_ref()),
Some(100)
);
assert_eq!(
reasoning_of(assistants[1].attributed_token_usage.as_ref()),
Some(160)
);
// Round total breakdown is the sum of attributions.
let round = assistants[1].token_usage.as_ref().unwrap();
assert_eq!(reasoning_of(Some(round)), Some(260));
Expand Down Expand Up @@ -1233,8 +1269,18 @@ mod tests {
].join("\n");
let (_t, mgr, id) = setup_session_fixture(&body);
let view = to_view(&mgr.read_session(&id).unwrap());
let a = view.turns.iter().find(|t| t.role == Role::Assistant).unwrap();
assert!(a.attributed_token_usage.as_ref().unwrap().breakdowns.is_empty());
let a = view
.turns
.iter()
.find(|t| t.role == Role::Assistant)
.unwrap();
assert!(
a.attributed_token_usage
.as_ref()
.unwrap()
.breakdowns
.is_empty()
);
assert!(a.token_usage.as_ref().unwrap().breakdowns.is_empty());
}

Expand Down
10 changes: 8 additions & 2 deletions crates/toolpath-codex/tests/fixture_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ fn reasoning_breakdown_differenced_dedup_safe_against_real_fixture() {
.iter()
.map(|t| reasoning_of(t.attributed_token_usage.as_ref()))
.sum();
assert_eq!(attributed_reasoning, 979, "Σ attributed reasoning != cumulative");
assert_eq!(
attributed_reasoning, 979,
"Σ attributed reasoning != cumulative"
);

// Per step, reasoning ⊆ output.
for t in &view.turns {
Expand Down Expand Up @@ -201,7 +204,10 @@ fn reasoning_breakdown_differenced_dedup_safe_against_real_fixture() {
r
})
.sum();
assert_eq!(round_reasoning, 979, "Σ round-total reasoning != cumulative");
assert_eq!(
round_reasoning, 979,
"Σ round-total reasoning != cumulative"
);
}

#[test]
Expand Down
Loading
Loading