Skip to content

Commit 9c27ca8

Browse files
feat: type ResultMessage.model_usage as dict[str, ModelUsage] (#1143)
Adds a `ModelUsage` TypedDict mirroring the TypeScript SDK's `ModelUsage` shape and tightens `ResultMessage.model_usage` from `dict[str, Any]` to `dict[str, ModelUsage]`. The value is passed through verbatim from the CLI's `modelUsage` field, so this is type-only — no runtime behavior change. The two new `NotRequired` fields (`canonicalModel`, `provider`) are emitted by the CLI once anthropics/claude-cli-internal#50283 lands. They give callers a stable key for their own rate-table lookups across provider-specific model ids and aliases (e.g. Bedrock ARNs → `claude-opus-4-7`), so cost drift like "Opus 4.6/4.7 priced as 4.5" is detectable client-side. Per-token rates were intentionally left out of this pass (not authoritative); may follow separately with a source marker. **Verification:** type-annotation-only; `py_compile` clean on changed files; existing `test_parse_result_message_with_model_usage` covers the passthrough shape. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 07b46c6 commit 9c27ca8

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

src/claude_agent_sdk/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
McpToolInfo,
8686
Message,
8787
MirrorErrorMessage,
88+
ModelUsage,
8889
NotificationHookInput,
8990
NotificationHookSpecificOutput,
9091
PermissionMode,
@@ -555,6 +556,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
555556
"TERMINAL_TASK_STATUSES",
556557
"TaskUsage",
557558
"ResultMessage",
559+
"ModelUsage",
558560
"DeferredToolUse",
559561
"RateLimitEvent",
560562
"RateLimitInfo",

src/claude_agent_sdk/types.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,31 @@ class DeferredToolUse:
11971197
input: dict[str, Any]
11981198

11991199

1200+
class ModelUsage(TypedDict):
1201+
"""Per-model token usage and cost breakdown.
1202+
1203+
Keys match the TypeScript SDK's ``ModelUsage`` shape (camelCase), since
1204+
the value is passed through verbatim from the CLI's ``modelUsage`` field.
1205+
"""
1206+
1207+
inputTokens: int
1208+
outputTokens: int
1209+
cacheReadInputTokens: int
1210+
cacheCreationInputTokens: int
1211+
webSearchRequests: int
1212+
costUSD: float
1213+
contextWindow: int
1214+
maxOutputTokens: int
1215+
canonicalModel: NotRequired[str]
1216+
"""Canonical model id used for the pricing lookup (e.g.
1217+
``'claude-opus-4-7'``). May differ from the raw model string this entry
1218+
is keyed by (provider-specific ids, aliases)."""
1219+
provider: NotRequired[str]
1220+
"""API provider that served this model (``'firstParty'``, ``'bedrock'``,
1221+
``'vertex'``, ``'foundry'``, ``'anthropicAws'``, ``'anthropicGoogleCloud'``,
1222+
``'mantle'``, ``'gateway'``)."""
1223+
1224+
12001225
@dataclass
12011226
class ResultMessage:
12021227
"""Result message with cost and usage information."""
@@ -1212,7 +1237,7 @@ class ResultMessage:
12121237
usage: dict[str, Any] | None = None
12131238
result: str | None = None
12141239
structured_output: Any = None
1215-
model_usage: dict[str, Any] | None = None
1240+
model_usage: dict[str, ModelUsage] | None = None
12161241
permission_denials: list[Any] | None = None
12171242
deferred_tool_use: DeferredToolUse | None = None
12181243
errors: list[str] | None = None

0 commit comments

Comments
 (0)