Coverage for src / harness_utils / models / parts.py: 90%
72 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-31 13:47 -0600
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-31 13:47 -0600
1"""Part types for message decomposition.
3Parts are the granular units that make up messages. This enables
4selective compaction where tool outputs can be cleared while
5preserving text and metadata.
6"""
8from dataclasses import dataclass, field
9from typing import Any, Literal
12@dataclass
13class TimeInfo:
14 """Timing information for parts."""
16 start: int # Unix timestamp in milliseconds
17 end: int | None = None
18 compacted: int | None = None # When output was compacted
21@dataclass
22class ToolState:
23 """State information for tool execution."""
25 status: Literal["pending", "running", "completed", "error"]
26 input: dict[str, Any] = field(default_factory=dict)
27 output: str = ""
28 title: str = ""
29 metadata: dict[str, Any] = field(default_factory=dict)
30 error: str | None = None
31 time: TimeInfo | None = None
32 attachments: list[dict[str, Any]] = field(default_factory=list)
35@dataclass
36class Part:
37 """Base class for message parts."""
39 type: str = field(init=False)
40 time: TimeInfo | None = None
41 metadata: dict[str, Any] = field(default_factory=dict)
44@dataclass
45class TextPart(Part):
46 """Text content part."""
48 text: str = ""
49 ignored: bool = False # If true, skip when converting to model format
51 def __post_init__(self) -> None:
52 self.type = "text"
55@dataclass
56class ReasoningPart(Part):
57 """Extended thinking/reasoning content part."""
59 text: str = ""
61 def __post_init__(self) -> None:
62 self.type = "reasoning"
65@dataclass
66class ToolPart(Part):
67 """Tool execution part."""
69 tool: str = ""
70 call_id: str = ""
71 state: ToolState = field(default_factory=lambda: ToolState(status="pending"))
73 def __post_init__(self) -> None:
74 self.type = "tool"
77@dataclass
78class StepStartPart(Part):
79 """Step boundary marker - start of LLM turn."""
81 snapshot: str = "" # State snapshot ID
83 def __post_init__(self) -> None:
84 self.type = "step-start"
87@dataclass
88class StepFinishPart(Part):
89 """Step boundary marker - end of LLM turn."""
91 reason: Literal["stop", "tool-calls", "length"] = "stop"
92 snapshot: str = "" # State snapshot ID
93 tokens: dict[str, Any] = field(default_factory=dict)
94 cost: float = 0.0
96 def __post_init__(self) -> None:
97 self.type = "step-finish"
100@dataclass
101class CompactionPart(Part):
102 """Marker for compaction request."""
104 auto: bool = False # Was this auto-triggered?
106 def __post_init__(self) -> None:
107 self.type = "compaction"
110@dataclass
111class PatchPart(Part):
112 """Code change marker."""
114 hash: str = "" # Diff hash
115 files: list[str] = field(default_factory=list)
117 def __post_init__(self) -> None:
118 self.type = "patch"
121@dataclass
122class SubtaskPart(Part):
123 """Subtask invocation marker."""
125 prompt: str = ""
126 description: str = ""
127 agent: str = ""
128 model: dict[str, str] = field(default_factory=dict)
130 def __post_init__(self) -> None:
131 self.type = "subtask"