Skip to content

Commit a8beb20

Browse files
committed
fix(sdk): modernize typing and fix ToolResult missing is_error
CI mypy --strict flagged two ToolResult() call sites in tools.py for missing the `is_error` named argument, and ruff flagged 13 lint issues in the new sessions.py / test_sessions.py module added in the previous commit. - Pass `is_error=` explicitly at both chat_with_tools entry points, using `bool(tool_name) and tool_name not in tool_map` so unknown tool names are flagged as errors. - Replace deprecated `typing.Dict` / `typing.List` with `dict` /`list` (PEP 585; SDK already targets Python 3.9+ where these work natively). - Drop unused `Optional` import and `typing.Dict` import. - Strip quotes from forward-reference `"Phase"` annotation (UP037). - Replace en-dash in `phase_share` docstring with hyphen-minus (RUF002). - Organize and prune unused imports in tests/test_sessions.py (I001, F401).
1 parent 9bc263b commit a8beb20

3 files changed

Lines changed: 31 additions & 22 deletions

File tree

src/hawk/sessions.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import time
1414
from dataclasses import dataclass, field
1515
from enum import Enum
16-
from typing import Dict, List, Optional
1716

1817

1918
class Phase(str, Enum):
@@ -27,7 +26,7 @@ class Phase(str, Enum):
2726
UNKNOWN = ""
2827

2928
@classmethod
30-
def parse(cls, s: str) -> "Phase":
29+
def parse(cls, s: str) -> Phase:
3130
"""Return the Phase matching s, or Phase.UNKNOWN if unrecognised."""
3231
for member in cls:
3332
if member.value == s:
@@ -54,7 +53,7 @@ class ContextSnapshot:
5453
token_count: int
5554
compressed_at: float = field(default_factory=time.time) # Unix timestamp
5655
summary: str = ""
57-
anchors: List[str] = field(default_factory=list)
56+
anchors: list[str] = field(default_factory=list)
5857

5958

6059
@dataclass
@@ -84,7 +83,7 @@ class CostAccumulator:
8483
"""
8584

8685
session_id: str
87-
by_phase: Dict[Phase, PhaseUsage] = field(default_factory=dict)
86+
by_phase: dict[Phase, PhaseUsage] = field(default_factory=dict)
8887
total_tokens: int = 0
8988
total_cost_usd: float = 0.0
9089

@@ -107,7 +106,7 @@ def add(
107106
self.total_cost_usd += cost_usd
108107

109108
def phase_share(self, phase: Phase) -> float:
110-
"""Return the fraction of total tokens consumed by phase (0.01.0)."""
109+
"""Return the fraction of total tokens consumed by phase (0.0-1.0)."""
111110
if self.total_tokens == 0:
112111
return 0.0
113112
usage = self.by_phase.get(phase)
@@ -116,10 +115,16 @@ def phase_share(self, phase: Phase) -> float:
116115
return usage.total_tokens / self.total_tokens
117116

118117
def __str__(self) -> str:
119-
parts = [f"session={self.session_id} total={self.total_tokens}tok ${self.total_cost_usd:.4f}"]
120-
for phase, pu in sorted(self.by_phase.items(), key=lambda kv: kv[1].total_tokens, reverse=True):
118+
parts = [
119+
f"session={self.session_id} total={self.total_tokens}tok ${self.total_cost_usd:.4f}"
120+
]
121+
for phase, pu in sorted(
122+
self.by_phase.items(), key=lambda kv: kv[1].total_tokens, reverse=True
123+
):
121124
share = self.phase_share(phase)
122-
parts.append(f" {phase.value or 'unknown'}: {pu.total_tokens}tok ({share:.1%}) ${pu.cost_usd:.4f}")
125+
parts.append(
126+
f" {phase.value or 'unknown'}: {pu.total_tokens}tok ({share:.1%}) ${pu.cost_usd:.4f}"
127+
)
123128
return "\n".join(parts)
124129

125130

@@ -161,9 +166,9 @@ class ResolutionResult:
161166
"""Complete output of a multi-phase resolution run."""
162167

163168
session_id: str = ""
164-
candidates: List[PatchCandidate] = field(default_factory=list)
169+
candidates: list[PatchCandidate] = field(default_factory=list)
165170
validation_passed: bool = False
166-
validation_failures: List[str] = field(default_factory=list)
167-
phase_metrics: List[PhaseMetrics] = field(default_factory=list)
171+
validation_failures: list[str] = field(default_factory=list)
172+
phase_metrics: list[PhaseMetrics] = field(default_factory=list)
168173
total_input_tokens: int = 0
169174
total_output_tokens: int = 0

src/hawk/tools.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,13 @@ def chat_with_tools(
147147
result = _execute_tool(tool_map[tool_name], arguments)
148148
else:
149149
result = json.dumps({"error": f"Unknown tool: {tool_name}"})
150-
tool_results.append(ToolResult(
151-
tool_use_id=tc.get("id", "") if isinstance(tc, dict) else getattr(tc, "id", ""),
152-
content=result,
153-
))
150+
tool_results.append(
151+
ToolResult(
152+
tool_use_id=tc.get("id", "") if isinstance(tc, dict) else getattr(tc, "id", ""),
153+
content=result,
154+
is_error=bool(tool_name) and tool_name not in tool_map,
155+
)
156+
)
154157

155158
# Send tool results back to continue the conversation.
156159
response = client.chat(
@@ -215,10 +218,13 @@ async def chat_with_tools_async(
215218
result = await _execute_tool_async(tool_map[tool_name], arguments)
216219
else:
217220
result = json.dumps({"error": f"Unknown tool: {tool_name}"})
218-
tool_results.append(ToolResult(
219-
tool_use_id=tc.get("id", "") if isinstance(tc, dict) else getattr(tc, "id", ""),
220-
content=result,
221-
))
221+
tool_results.append(
222+
ToolResult(
223+
tool_use_id=tc.get("id", "") if isinstance(tc, dict) else getattr(tc, "id", ""),
224+
content=result,
225+
is_error=bool(tool_name) and tool_name not in tool_map,
226+
)
227+
)
222228

223229
response = await client.chat(
224230
prompt,

tests/test_sessions.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
"""Tests for hawk.sessions phase-attributed session state."""
22

3-
import pytest
4-
5-
from hawk.sessions import CostAccumulator, ContextSnapshot, Phase, PhaseUsage, ToolCallRecord
3+
from hawk.sessions import ContextSnapshot, CostAccumulator, Phase, ToolCallRecord
64

75

86
def test_phase_parse_known():

0 commit comments

Comments
 (0)