"""Agent result handling for SDK. This module defines the AgentResult class which encapsulates the complete response from an agent's processing cycle. """ from collections.abc import Sequence from dataclasses import dataclass from typing import Any, cast from pydantic import BaseModel from ..experimental.checkpoint import Checkpoint from ..interrupt import Interrupt from ..telemetry.metrics import EventLoopMetrics from ..types.content import Message from ..types.session import decode_bytes_values, encode_bytes_values from ..types.streaming import StopReason @dataclass class AgentResult: """Represents the last result of invoking an agent with a prompt. Attributes: stop_reason: The reason why the agent's processing stopped. message: The last message generated by the agent. metrics: Performance metrics collected during processing. state: Additional state information from the event loop. interrupts: List of interrupts if raised by user. structured_output: Parsed structured output when structured_output_model was specified. checkpoint: Checkpoint captured when the agent paused for durable execution. Populated only when stop_reason == "checkpoint". See strands.experimental.checkpoint for usage. """ stop_reason: StopReason message: Message metrics: EventLoopMetrics state: Any interrupts: Sequence[Interrupt] | None = None structured_output: BaseModel | None = None checkpoint: Checkpoint | None = None @property def context_size(self) -> int | None: """Most recent context size in tokens from the last LLM call. Returns: The total prompt the model processed on the most recent cycle, including cached tokens, or None if no data is available. """ return self.metrics.latest_context_size @property def projected_context_size(self) -> int | None: """Projected context size for the next model call. Returns: The projected token count (total prompt including cached tokens plus generated output), or None if no data is available. """ return self.metrics.projected_context_size def __str__(self) -> str: """Return a string representation of the agent result. Priority order: 1. Interrupts (if present) → stringified list of interrupt dicts 2. Structured output (if present) → JSON string 3. Text content from message → concatenated text blocks Returns: String representation based on the priority order above. """ if self.interrupts: return str([interrupt.to_dict() for interrupt in self.interrupts]) if self.structured_output: return self.structured_output.model_dump_json() content_array = self.message.get("content", []) result = "" for item in content_array: if isinstance(item, dict): if "text" in item: result += item.get("text", "") + "\n" elif "citationsContent" in item: citations_block = item["citationsContent"] if "content" in citations_block: for content in citations_block["content"]: if isinstance(content, dict) and "text" in content: result += content.get("text", "") + "\n" return result @classmethod def from_dict(cls, data: dict[str, Any]) -> "AgentResult": """Rehydrate an AgentResult from persisted JSON. Args: data: Dictionary containing the serialized AgentResult data Returns: AgentResult instance Raises: TypeError: If the data format is invalid """ if data.get("type") != "agent_result": raise TypeError(f"AgentResult.from_dict: unexpected type {data.get('type')!r}") message = cast(Message, decode_bytes_values(data.get("message"))) stop_reason = cast(StopReason, data.get("stop_reason")) checkpoint_data = data.get("checkpoint") checkpoint = Checkpoint.from_dict(checkpoint_data) if checkpoint_data else None return cls( message=message, stop_reason=stop_reason, metrics=EventLoopMetrics(), state={}, checkpoint=checkpoint, ) def to_dict(self) -> dict[str, Any]: """Convert this AgentResult to JSON-serializable dictionary. Binary values in ``message`` (for example Converse ``redactedContent`` blobs) are base64-encoded with the same helpers used by session persistence. Returns: Dictionary containing serialized AgentResult data """ return { "type": "agent_result", "message": encode_bytes_values(self.message), "stop_reason": self.stop_reason, "checkpoint": self.checkpoint.to_dict() if self.checkpoint else None, }