cc-transcript 14.20.1__tar.gz → 14.21.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cc-transcript might be problematic. Click here for more details.

Files changed (162) hide show
  1. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/PKG-INFO +1 -1
  2. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/render.py +6 -3
  3. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/pyproject.toml +1 -1
  4. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/render.rs +84 -19
  5. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/Cargo.lock +0 -0
  6. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/Cargo.toml +0 -0
  7. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/LICENSE +0 -0
  8. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/README.md +0 -0
  9. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/__init__.py +0 -0
  10. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/__init__.pyi +0 -0
  11. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/_native.pyi +0 -0
  12. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/activity.py +0 -0
  13. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/activity_probe.py +0 -0
  14. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/builders.py +0 -0
  15. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/codex.py +0 -0
  16. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/command.py +0 -0
  17. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/context.py +0 -0
  18. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/corrections.py +0 -0
  19. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/cost.py +0 -0
  20. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/decision_store.py +0 -0
  21. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/decisions.py +0 -0
  22. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/discovery.py +0 -0
  23. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/disktruth.py +0 -0
  24. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/evidence.py +0 -0
  25. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/extract/__init__.py +0 -0
  26. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/extract/correct.py +0 -0
  27. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/facts.py +0 -0
  28. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/filterspec.py +0 -0
  29. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/heartbeats.py +0 -0
  30. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/ids.py +0 -0
  31. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/judge/__init__.py +0 -0
  32. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/judge/llm.py +0 -0
  33. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/judge/similar.py +0 -0
  34. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/judge/verdicts.py +0 -0
  35. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/ledger.py +0 -0
  36. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/literals.py +0 -0
  37. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/__init__.py +0 -0
  38. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/candidates.py +0 -0
  39. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/confidence.py +0 -0
  40. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/engine.py +0 -0
  41. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/formats.py +0 -0
  42. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/sampling.py +0 -0
  43. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/signals.py +0 -0
  44. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/sourcekind.py +0 -0
  45. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/spec.py +0 -0
  46. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/mining/store.py +0 -0
  47. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/models.py +0 -0
  48. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/nlp.py +0 -0
  49. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/notifications.py +0 -0
  50. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/parser.py +0 -0
  51. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/py.typed +0 -0
  52. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/query.py +0 -0
  53. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/__init__.py +0 -0
  54. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/buckets.py +0 -0
  55. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  56. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  57. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  58. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/engine.py +0 -0
  59. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/lexicon.py +0 -0
  60. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/sentiment/scorespec.py +0 -0
  61. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/synthetic.py +0 -0
  62. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/tools.py +0 -0
  63. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/cc_transcript/watch.py +0 -0
  64. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/Cargo.toml +0 -0
  65. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/blame.rs +0 -0
  66. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
  67. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/digest.rs +0 -0
  68. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/facts.rs +0 -0
  69. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/grep.rs +0 -0
  70. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/list.rs +0 -0
  71. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/mod.rs +0 -0
  72. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  73. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/show.rs +0 -0
  74. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/slice.rs +0 -0
  75. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/stats.rs +0 -0
  76. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/commands/watch.rs +0 -0
  77. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/lib.rs +0 -0
  78. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/main.rs +0 -0
  79. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/output.rs +0 -0
  80. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/target.rs +0 -0
  81. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/cli/src/timearg.rs +0 -0
  82. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/Cargo.toml +0 -0
  83. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/benches/parse.rs +0 -0
  84. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/benches/render.rs +0 -0
  85. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/benches/toolcall.rs +0 -0
  86. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/activity.rs +0 -0
  87. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/actor.rs +0 -0
  88. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/blame.rs +0 -0
  89. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/buckets.rs +0 -0
  90. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/discovery.rs +0 -0
  91. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/lower.rs +0 -0
  92. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/mod.rs +0 -0
  93. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/parse.rs +0 -0
  94. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/probe.rs +0 -0
  95. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/protocol.rs +0 -0
  96. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/codex/types.rs +0 -0
  97. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/command.rs +0 -0
  98. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/context.rs +0 -0
  99. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/corrections.rs +0 -0
  100. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/cost.rs +0 -0
  101. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/discovery.rs +0 -0
  102. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/facts.rs +0 -0
  103. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/feedback.rs +0 -0
  104. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/filter.rs +0 -0
  105. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/gateway.rs +0 -0
  106. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/ids.rs +0 -0
  107. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/judge.rs +0 -0
  108. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/lib.rs +0 -0
  109. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/literals/command.rs +0 -0
  110. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/literals/corrections.rs +0 -0
  111. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/literals/feedback.rs +0 -0
  112. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/literals/mining.rs +0 -0
  113. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/literals/mod.rs +0 -0
  114. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/literals/protocol.rs +0 -0
  115. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/notifications.rs +0 -0
  116. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/parse.rs +0 -0
  117. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/protocol.rs +0 -0
  118. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/pystr.rs +0 -0
  119. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/query.rs +0 -0
  120. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/rng.rs +0 -0
  121. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/schema.rs +0 -0
  122. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/sqlite.rs +0 -0
  123. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/toolcall.rs +0 -0
  124. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/types.rs +0 -0
  125. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/value.rs +0 -0
  126. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/core/src/watch.rs +0 -0
  127. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/Cargo.toml +0 -0
  128. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/build.rs +0 -0
  129. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  130. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/actor_bridge.rs +0 -0
  131. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  132. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/codex.rs +0 -0
  133. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/command.rs +0 -0
  134. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/context.rs +0 -0
  135. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/corrections.rs +0 -0
  136. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/discovery.rs +0 -0
  137. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/feedback.rs +0 -0
  138. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/judge.rs +0 -0
  139. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/lexicon.rs +0 -0
  140. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/lib.rs +0 -0
  141. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/mining.rs +0 -0
  142. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/nlp.rs +0 -0
  143. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/python.rs +0 -0
  144. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/render.rs +0 -0
  145. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/score.rs +0 -0
  146. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/sqlite.rs +0 -0
  147. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/toolcall.rs +0 -0
  148. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/attachment.rs +0 -0
  149. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/blocks.rs +0 -0
  150. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/command.rs +0 -0
  151. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/convert.rs +0 -0
  152. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/dunder.rs +0 -0
  153. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/events.rs +0 -0
  154. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/meta.rs +0 -0
  155. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/mod.rs +0 -0
  156. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/print.rs +0 -0
  157. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/store.rs +0 -0
  158. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/system.rs +0 -0
  159. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/toolcall.rs +0 -0
  160. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/toolresult.rs +0 -0
  161. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/views/transcript.rs +0 -0
  162. {cc_transcript-14.20.1 → cc_transcript-14.21.0}/rust/crates/py/src/watch.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-transcript
3
- Version: 14.20.1
3
+ Version: 14.21.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -62,10 +62,13 @@ def prefixed(prefix: str, text: str) -> tuple[str, ...]:
62
62
 
63
63
 
64
64
  def render_turn(turn: Turn, *, budget: Budget) -> str:
65
- """Render one turn: the prompt, assistant prose, and every tool call, in order.
65
+ """Render one turn: the prompt, the user's mid-turn messages, assistant prose, and every tool call with its result, in order.
66
66
 
67
- Prose chunks clip to ``budget.turn_chars``; each tool call renders via
68
- :func:`render_tool_call` under ``budget.tool_chars``.
67
+ Prose chunks and tool results clip to ``budget.turn_chars``; each tool call
68
+ renders via :func:`render_tool_call` under ``budget.tool_chars``. A result
69
+ renders as ``result:``, or ``failed:`` when the call errored; an
70
+ AskUserQuestion answer renders as ``user answered:`` with the chosen option's
71
+ description, the selected preview and any notes.
69
72
  """
70
73
  return _native.render_turn_from_events(
71
74
  turn.prompt,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cc-transcript"
3
- version = "14.20.1" # informational — releases stamp the version from the git tag
3
+ version = "14.21.0" # informational — releases stamp the version from the git tag
4
4
  description = "Grep every Claude Code session you've ever run."
5
5
  readme = "README.md"
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -1,7 +1,7 @@
1
1
  //! The one renderer, ported from `cc_transcript/render.py` — every cut happens here,
2
2
  //! under a [`Budget`]. Parity notes on the individual helpers.
3
3
 
4
- use std::collections::{HashMap, HashSet};
4
+ use std::collections::HashMap;
5
5
 
6
6
  use chrono::{DateTime, FixedOffset};
7
7
  use sonic_rs::{JsonContainerTrait, JsonType, JsonValueTrait, Value};
@@ -14,13 +14,16 @@ use crate::facts::{McpServerSummary, ToolFact};
14
14
  use crate::filter::{entry_text, event_kind};
15
15
  use crate::ids::encode_string;
16
16
  use crate::pystr;
17
- use crate::toolcall::{parse_tool_call, ToolCall};
17
+ use crate::toolcall::{
18
+ parse_tool_call, parse_tool_result, AskUserQuestionResult, ToolCall, ToolResult,
19
+ };
18
20
  use crate::types::{
19
21
  joined_text, ApiError, AssistantEntry, AttachmentDetail, Attribution, CacheCreation,
20
22
  CompactBoundary, ContentBlock, Entry, EntryMeta, HookInfo, ModelRefusalFallback,
21
23
  PreservedMessages, PreservedSegment, ServerToolUse, StopHookSummary, SystemDetail,
22
24
  ToolResultBlock, TurnDuration, Usage, UserEntry,
23
25
  };
26
+ use crate::value::{field, field_str};
24
27
 
25
28
  const SIZE_UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
26
29
  const BLANK_TIME: &str = " ";
@@ -434,15 +437,15 @@ pub fn render_tool_call(call: &ToolCall, budget: &Budget) -> String {
434
437
  }
435
438
 
436
439
  /// Render one turn — the prompt, the user's mid-turn messages, assistant prose,
437
- /// every tool call, and the user's AskUserQuestion answers, in order (render.py
438
- /// render_turn). Prose clips to `budget.turn_chars`; each tool call and answer
439
- /// renders under `budget.tool_chars`.
440
+ /// every tool call with its result, and the user's AskUserQuestion answers, in order
441
+ /// (render.py render_turn). Prose and tool results clip to `budget.turn_chars`; each
442
+ /// tool call and answer preview renders under `budget.tool_chars`.
440
443
  pub fn render_turn(turn: &Turn, budget: &Budget) -> String {
441
444
  let mut parts: Vec<String> = Vec::new();
442
445
  if !turn.prompt.is_empty() {
443
446
  parts.push(format!("user: {}", clip(&turn.prompt, budget.turn_chars)));
444
447
  }
445
- let mut questions: HashSet<&str> = HashSet::new();
448
+ let mut calls: HashMap<&str, &str> = HashMap::new();
446
449
  for &event in &turn.events {
447
450
  match event {
448
451
  Entry::Assistant(assistant) => {
@@ -452,9 +455,7 @@ pub fn render_turn(turn: &Turn, budget: &Budget) -> String {
452
455
  parts.push(format!("assistant: {}", clip(text, budget.turn_chars)));
453
456
  }
454
457
  ContentBlock::ToolUse(tool_use) => {
455
- if tool_use.name == ASK_USER_QUESTION {
456
- questions.insert(&tool_use.id);
457
- }
458
+ calls.insert(&tool_use.id, &tool_use.name);
458
459
  parts.push(render_tool_call(
459
460
  &parse_tool_call(&tool_use.name, &tool_use.input),
460
461
  budget,
@@ -465,17 +466,13 @@ pub fn render_turn(turn: &Turn, budget: &Budget) -> String {
465
466
  }
466
467
  }
467
468
  Entry::User(user) => {
468
- parts.extend(user.blocks().iter().filter_map(|block| match block {
469
- ContentBlock::ToolResult(result)
470
- if !result.is_error && questions.contains(result.tool_use_id.as_str()) =>
471
- {
472
- Some(format!(
473
- "user answered: {}",
474
- clip(&result.content, budget.tool_chars)
475
- ))
469
+ for block in user.blocks() {
470
+ if let ContentBlock::ToolResult(result) = block {
471
+ if let Some(name) = calls.get(result.tool_use_id.as_str()) {
472
+ parts.extend(result_lines(name, result, budget));
473
+ }
476
474
  }
477
- _ => None,
478
- }));
475
+ }
479
476
  }
480
477
  Entry::Attachment(attachment) => {
481
478
  if let Some(prompt) = human_queued_prompt(&attachment.detail) {
@@ -488,6 +485,74 @@ pub fn render_turn(turn: &Turn, budget: &Budget) -> String {
488
485
  parts.join("\n")
489
486
  }
490
487
 
488
+ fn result_lines(name: &str, result: &ToolResultBlock, budget: &Budget) -> Vec<String> {
489
+ if result.is_error {
490
+ return vec![format!(
491
+ "failed: {}",
492
+ clip(&result.content, budget.turn_chars)
493
+ )];
494
+ }
495
+ if name == ASK_USER_QUESTION {
496
+ if let Some(ToolResult::AskUserQuestion(answer)) = result
497
+ .tool_use_result
498
+ .as_ref()
499
+ .map(|payload| parse_tool_result(name, payload))
500
+ {
501
+ return answer_lines(&answer, budget);
502
+ }
503
+ }
504
+ if pystr::strip(&result.content).is_empty() {
505
+ return Vec::new();
506
+ }
507
+ vec![format!(
508
+ "result: {}",
509
+ clip(&result.content, budget.turn_chars)
510
+ )]
511
+ }
512
+
513
+ fn answer_lines(answer: &AskUserQuestionResult, budget: &Budget) -> Vec<String> {
514
+ let mut lines = Vec::new();
515
+ for (question, label) in &answer.answers {
516
+ let option = chosen_option(&answer.raw, question, label);
517
+ let annotation = answer
518
+ .annotations
519
+ .iter()
520
+ .find_map(|(asked, annotation)| (asked == question).then_some(annotation));
521
+ lines.push(format!(
522
+ "user answered: {} -> {}",
523
+ clip(question, budget.turn_chars),
524
+ clip(label, budget.turn_chars)
525
+ ));
526
+ if let Some(description) = option.and_then(|option| field_str(option, "description")) {
527
+ lines.push(format!(
528
+ " option: {}",
529
+ clip(description, budget.turn_chars)
530
+ ));
531
+ }
532
+ let preview = annotation
533
+ .and_then(|annotation| annotation.preview.as_deref())
534
+ .or_else(|| option.and_then(|option| field_str(option, "preview")));
535
+ if let Some(preview) = preview {
536
+ lines.push(format!(" preview: {}", clip(preview, budget.tool_chars)));
537
+ }
538
+ if let Some(notes) = annotation.and_then(|annotation| annotation.notes.as_deref()) {
539
+ lines.push(format!(" notes: {}", clip(notes, budget.turn_chars)));
540
+ }
541
+ }
542
+ lines
543
+ }
544
+
545
+ fn chosen_option<'a>(payload: &'a Value, question: &str, label: &str) -> Option<&'a Value> {
546
+ field(payload, "questions")?
547
+ .as_array()?
548
+ .iter()
549
+ .find(|asked| field_str(asked, "question") == Some(question))
550
+ .and_then(|asked| field(asked, "options"))
551
+ .and_then(JsonContainerTrait::as_array)?
552
+ .iter()
553
+ .find(|option| field_str(option, "label") == Some(label))
554
+ }
555
+
491
556
  pub(crate) fn human_queued_prompt(detail: &AttachmentDetail) -> Option<&str> {
492
557
  match detail {
493
558
  AttachmentDetail::QueuedCommand(queued)
File without changes