From b92f0270c20d5c1bcd5e1f9e10712dc50a8c013b Mon Sep 17 00:00:00 2001 From: CleverThis Date: Thu, 9 Apr 2026 23:38:25 +0000 Subject: [PATCH 1/2] fix(cli): fix session show/list JSON output to not redact input messages (#6436) ISSUES CLOSED: #6436 --- features/session_cli.feature | 10 +++++++ features/steps/session_cli_steps.py | 38 ++++++++++++++++++++++++ src/cleveragents/cli/commands/session.py | 5 ++++ src/cleveragents/shared/redaction.py | 2 ++ 4 files changed, 55 insertions(+) diff --git a/features/session_cli.feature b/features/session_cli.feature index 29effefae..35b26e637 100644 --- a/features/session_cli.feature +++ b/features/session_cli.feature @@ -39,6 +39,11 @@ Feature: Session CLI commands Then the session CLI output should be valid JSON And the session CLI JSON should contain "sessions" + Scenario: List sessions JSON includes token usage counts + Given there are mocked existing sessions + When I run session CLI list with --format json + Then the session CLI JSON list token usage should include counts + # Show command tests Scenario: Show session with valid ID Given there is a mocked session with messages @@ -59,6 +64,11 @@ Feature: Session CLI commands When I run session CLI show with --format json Then the session CLI output should be valid JSON + Scenario: Show session JSON includes token usage counts + Given there is a mocked session with messages + When I run session CLI show with --format json + Then the session CLI JSON token usage should include counts + Scenario: Show session with invalid ID When I run session CLI show with an invalid session ID Then the session CLI should exit with error diff --git a/features/steps/session_cli_steps.py b/features/steps/session_cli_steps.py index 045fae897..a74c8a4f6 100644 --- a/features/steps/session_cli_steps.py +++ b/features/steps/session_cli_steps.py @@ -514,6 +514,44 @@ def step_json_contains_key(context: Context, key: str) -> None: assert key in data, f"Key '{key}' not found in JSON: {data}" +@then("the session CLI JSON token usage should include counts") +def step_json_show_token_usage_counts(context: Context) -> None: + parsed = json.loads(context.result.output) + data = _unwrap_envelope(parsed) + token_usage = data.get("token_usage") + assert isinstance(token_usage, dict), ( + f"token_usage missing or not an object: {token_usage}" + ) + _assert_token_usage_counts_are_ints(token_usage) + + +@then("the session CLI JSON list token usage should include counts") +def step_json_list_token_usage_counts(context: Context) -> None: + parsed = json.loads(context.result.output) + data = _unwrap_envelope(parsed) + sessions = data.get("sessions") + assert isinstance(sessions, list), f"sessions missing or not a list: {sessions}" + for index, session in enumerate(sessions): + token_usage = session.get("token_usage") + assert isinstance(token_usage, dict), ( + f"token_usage missing for session {index}: {token_usage}" + ) + _assert_token_usage_counts_are_ints(token_usage) + + +def _assert_token_usage_counts_are_ints(token_usage: dict[str, Any]) -> None: + input_tokens = token_usage.get("input_tokens") + output_tokens = token_usage.get("output_tokens") + assert isinstance(input_tokens, int), ( + f"input_tokens should be int, got {input_tokens!r}" + ) + assert isinstance(output_tokens, int), ( + f"output_tokens should be int, got {output_tokens!r}" + ) + assert input_tokens != "***REDACTED***", "input_tokens should not be redacted" + assert output_tokens != "***REDACTED***", "output_tokens should not be redacted" + + @then("the session CLI should exit with error") def step_exit_with_error(context: Context) -> None: assert context.result.exit_code != 0, ( diff --git a/src/cleveragents/cli/commands/session.py b/src/cleveragents/cli/commands/session.py index 25229f840..a6690eb80 100644 --- a/src/cleveragents/cli/commands/session.py +++ b/src/cleveragents/cli/commands/session.py @@ -133,6 +133,11 @@ def _session_list_dict(sessions: list[Session]) -> dict[str, Any]: "actor": s.actor_name or "(none)", "messages": s.message_count, "updated": s.updated_at.isoformat(), + "token_usage": { + "input_tokens": s.token_usage.input_tokens, + "output_tokens": s.token_usage.output_tokens, + "estimated_cost": f"${s.token_usage.estimated_cost:.4f}", + }, } ) diff --git a/src/cleveragents/shared/redaction.py b/src/cleveragents/shared/redaction.py index d2b76d3f2..25486dfe6 100644 --- a/src/cleveragents/shared/redaction.py +++ b/src/cleveragents/shared/redaction.py @@ -44,6 +44,8 @@ _FALSE_POSITIVE_KEYS: set[str] = { "token_count", "token_limit", "token_usage", + "input_tokens", + "output_tokens", "max_tokens", "total_tokens", "prompt_tokens", -- 2.52.0 From f2b60cc3d6c10aa2dbdcf857fc0b96bb630705ff Mon Sep 17 00:00:00 2001 From: CleverThis Date: Fri, 10 Apr 2026 19:32:58 +0000 Subject: [PATCH 2/2] fix(cli): restore session list json contract Ensure the session list helper only emits the documented fields so downstream consumers stay compliant, and tighten the Behave coverage to enforce the contract.\n\nISSUES CLOSED: #6436 --- features/session_cli.feature | 4 ++-- features/steps/session_cli_steps.py | 20 ++++++++++++++------ src/cleveragents/cli/commands/session.py | 5 ----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/features/session_cli.feature b/features/session_cli.feature index 35b26e637..b363e03ea 100644 --- a/features/session_cli.feature +++ b/features/session_cli.feature @@ -39,10 +39,10 @@ Feature: Session CLI commands Then the session CLI output should be valid JSON And the session CLI JSON should contain "sessions" - Scenario: List sessions JSON includes token usage counts + Scenario: List sessions JSON matches the documented contract Given there are mocked existing sessions When I run session CLI list with --format json - Then the session CLI JSON list token usage should include counts + Then the session CLI JSON list entries should match the documented contract # Show command tests Scenario: Show session with valid ID diff --git a/features/steps/session_cli_steps.py b/features/steps/session_cli_steps.py index a74c8a4f6..779643ffb 100644 --- a/features/steps/session_cli_steps.py +++ b/features/steps/session_cli_steps.py @@ -525,18 +525,26 @@ def step_json_show_token_usage_counts(context: Context) -> None: _assert_token_usage_counts_are_ints(token_usage) -@then("the session CLI JSON list token usage should include counts") -def step_json_list_token_usage_counts(context: Context) -> None: +@then("the session CLI JSON list entries should match the documented contract") +def step_json_list_contract(context: Context) -> None: parsed = json.loads(context.result.output) data = _unwrap_envelope(parsed) sessions = data.get("sessions") assert isinstance(sessions, list), f"sessions missing or not a list: {sessions}" for index, session in enumerate(sessions): - token_usage = session.get("token_usage") - assert isinstance(token_usage, dict), ( - f"token_usage missing for session {index}: {token_usage}" + assert isinstance(session, dict), ( + f"session entry at index {index} is not an object: {session!r}" + ) + expected_keys = {"id", "name", "actor", "messages", "updated"} + actual_keys = set(session.keys()) + missing = expected_keys - actual_keys + extra = actual_keys - expected_keys + assert not missing, ( + f"session entry {index} missing keys {sorted(missing)}: {session!r}" + ) + assert not extra, ( + f"session entry {index} has undocumented keys {sorted(extra)}: {session!r}" ) - _assert_token_usage_counts_are_ints(token_usage) def _assert_token_usage_counts_are_ints(token_usage: dict[str, Any]) -> None: diff --git a/src/cleveragents/cli/commands/session.py b/src/cleveragents/cli/commands/session.py index a6690eb80..25229f840 100644 --- a/src/cleveragents/cli/commands/session.py +++ b/src/cleveragents/cli/commands/session.py @@ -133,11 +133,6 @@ def _session_list_dict(sessions: list[Session]) -> dict[str, Any]: "actor": s.actor_name or "(none)", "messages": s.message_count, "updated": s.updated_at.isoformat(), - "token_usage": { - "input_tokens": s.token_usage.input_tokens, - "output_tokens": s.token_usage.output_tokens, - "estimated_cost": f"${s.token_usage.estimated_cost:.4f}", - }, } ) -- 2.52.0