From 0314303f6ab3558ce771ef969f625c124e2658af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:45:57 +0000 Subject: [PATCH 1/4] Initial plan From b0534ab626439eb9d66ad9d349e7b4965ae0d844 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:57:08 +0000 Subject: [PATCH 2/4] Add orchestration ID to durable agent entity state for Python Co-authored-by: larohra <41490930+larohra@users.noreply.github.com> --- .../_durable_agent_state.py | 20 ++-- .../agent_framework_azurefunctions/_models.py | 7 ++ .../_orchestration.py | 2 + .../azurefunctions/tests/test_entities.py | 95 ++++++++++++++++++- .../azurefunctions/tests/test_models.py | 65 +++++++++++++ .../tests/test_orchestration.py | 23 +++++ 6 files changed, 205 insertions(+), 7 deletions(-) diff --git a/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py b/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py index 73695e61f2..6e2c3fbf9a 100644 --- a/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py +++ b/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py @@ -227,7 +227,7 @@ class DurableAgentState: in Azure Durable Entities. It maintains the conversation history as a sequence of request and response entries, each with their messages, timestamps, and metadata. - The state follows a versioned schema (currently 1.0.0) that defines the structure for: + The state follows a versioned schema (currently 1.1.0) that defines the structure for: - Request entries: User/system messages with optional response format specifications - Response entries: Assistant messages with token usage information - Messages: Individual chat messages with role, content items, and timestamps @@ -235,7 +235,7 @@ class DurableAgentState: State is serialized to JSON with this structure: { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "data": { "conversationHistory": [ {"$type": "request", "correlationId": "...", "createdAt": "...", "messages": [...]}, @@ -246,17 +246,17 @@ class DurableAgentState: Attributes: data: Container for conversation history and optional extension data - schema_version: Schema version string (defaults to "1.0.0") + schema_version: Schema version string (defaults to "1.1.0") """ data: DurableAgentStateData - schema_version: str = "1.0.0" + schema_version: str = "1.1.0" - def __init__(self, schema_version: str = "1.0.0"): + def __init__(self, schema_version: str = "1.1.0"): """Initialize a new durable agent state. Args: - schema_version: Schema version to use (defaults to "1.0.0") + schema_version: Schema version to use (defaults to "1.1.0") """ self.data = DurableAgentStateData() self.schema_version = schema_version @@ -430,6 +430,7 @@ class DurableAgentStateRequest(DurableAgentStateEntry): Attributes: response_type: Expected response type ("text" or "json") response_schema: JSON schema for structured responses (when response_type is "json") + orchestration_id: ID of the orchestration that initiated this request (if any) correlationId: Unique identifier linking this request to its response created_at: Timestamp when the request was created messages: List of messages included in this request @@ -438,6 +439,7 @@ class DurableAgentStateRequest(DurableAgentStateEntry): response_type: str | None = None response_schema: dict[str, Any] | None = None + orchestration_id: str | None = None def __init__( self, @@ -447,6 +449,7 @@ def __init__( extension_data: dict[str, Any] | None = None, response_type: str | None = None, response_schema: dict[str, Any] | None = None, + orchestration_id: str | None = None, ) -> None: super().__init__( json_type=DurableAgentStateEntryJsonType.REQUEST, @@ -457,9 +460,12 @@ def __init__( ) self.response_type = response_type self.response_schema = response_schema + self.orchestration_id = orchestration_id def to_dict(self) -> dict[str, Any]: data = super().to_dict() + if self.orchestration_id is not None: + data["orchestrationId"] = self.orchestration_id if self.response_type is not None: data["responseType"] = self.response_type if self.response_schema is not None: @@ -484,6 +490,7 @@ def from_dict(cls, data: dict[str, Any]) -> DurableAgentStateRequest: extension_data=data.get("extensionData"), response_type=data.get("responseType"), response_schema=data.get("responseSchema"), + orchestration_id=data.get("orchestrationId"), ) @staticmethod @@ -495,6 +502,7 @@ def from_run_request(request: RunRequest) -> DurableAgentStateRequest: created_at=datetime.now(tz=timezone.utc), response_type=request.request_response_format, response_schema=_serialize_response_format(request.response_format), + orchestration_id=request.orchestration_id, ) diff --git a/python/packages/azurefunctions/agent_framework_azurefunctions/_models.py b/python/packages/azurefunctions/agent_framework_azurefunctions/_models.py index 19f175a485..c2bb2e6fd6 100644 --- a/python/packages/azurefunctions/agent_framework_azurefunctions/_models.py +++ b/python/packages/azurefunctions/agent_framework_azurefunctions/_models.py @@ -287,6 +287,7 @@ class RunRequest: thread_id: Optional thread ID for tracking correlation_id: Optional correlation ID for tracking the response to this specific request created_at: Optional timestamp when the request was created + orchestration_id: Optional ID of the orchestration that initiated this request """ message: str @@ -297,6 +298,7 @@ class RunRequest: thread_id: str | None = None correlation_id: str | None = None created_at: str | None = None + orchestration_id: str | None = None def __init__( self, @@ -308,6 +310,7 @@ def __init__( thread_id: str | None = None, correlation_id: str | None = None, created_at: str | None = None, + orchestration_id: str | None = None, ) -> None: self.message = message self.role = self.coerce_role(role) @@ -317,6 +320,7 @@ def __init__( self.thread_id = thread_id self.correlation_id = correlation_id self.created_at = created_at + self.orchestration_id = orchestration_id @staticmethod def coerce_role(value: Role | str | None) -> Role: @@ -346,6 +350,8 @@ def to_dict(self) -> dict[str, Any]: result["correlationId"] = self.correlation_id if self.created_at: result["created_at"] = self.created_at + if self.orchestration_id: + result["orchestrationId"] = self.orchestration_id return result @@ -361,6 +367,7 @@ def from_dict(cls, data: dict[str, Any]) -> RunRequest: thread_id=data.get("thread_id"), correlation_id=data.get("correlationId"), created_at=data.get("created_at"), + orchestration_id=data.get("orchestrationId"), ) diff --git a/python/packages/azurefunctions/agent_framework_azurefunctions/_orchestration.py b/python/packages/azurefunctions/agent_framework_azurefunctions/_orchestration.py index 2fd4522964..efe39966f1 100644 --- a/python/packages/azurefunctions/agent_framework_azurefunctions/_orchestration.py +++ b/python/packages/azurefunctions/agent_framework_azurefunctions/_orchestration.py @@ -132,12 +132,14 @@ def my_orchestration(context): correlation_id = str(self.context.new_uuid()) # Prepare the request using RunRequest model + # Include the orchestration's instance_id so it can be stored in the agent's entity state run_request = RunRequest( message=message_str, enable_tool_calls=enable_tool_calls, correlation_id=correlation_id, thread_id=session_id.key, response_format=response_format, + orchestration_id=self.context.instance_id, ) logger.debug(f"[DurableAIAgent] Calling entity {entity_id} with message: {message_str[:100]}...") diff --git a/python/packages/azurefunctions/tests/test_entities.py b/python/packages/azurefunctions/tests/test_entities.py index 2f73f1daa8..ff5adbe746 100644 --- a/python/packages/azurefunctions/tests/test_entities.py +++ b/python/packages/azurefunctions/tests/test_entities.py @@ -79,7 +79,7 @@ def test_init_creates_entity(self) -> None: assert entity.agent == mock_agent assert len(entity.state.data.conversation_history) == 0 assert entity.state.data.extension_data is None - assert entity.state.schema_version == "1.0.0" + assert entity.state.schema_version == "1.1.0" def test_init_stores_agent_reference(self) -> None: """Test that the agent reference is stored correctly.""" @@ -929,5 +929,98 @@ async def test_entity_function_with_run_request_dict(self) -> None: assert result["message"] == "Test message" +class TestDurableAgentStateRequestOrchestrationId: + """Test suite for DurableAgentStateRequest orchestration_id field.""" + + def test_request_with_orchestration_id(self) -> None: + """Test creating a request with an orchestration_id.""" + request = DurableAgentStateRequest( + correlation_id="corr-123", + created_at=datetime.now(), + messages=[ + DurableAgentStateMessage( + role="user", + contents=[DurableAgentStateTextContent(text="test")], + ) + ], + orchestration_id="orch-456", + ) + + assert request.orchestration_id == "orch-456" + + def test_request_to_dict_includes_orchestration_id(self) -> None: + """Test that to_dict includes orchestrationId when set.""" + request = DurableAgentStateRequest( + correlation_id="corr-123", + created_at=datetime.now(), + messages=[ + DurableAgentStateMessage( + role="user", + contents=[DurableAgentStateTextContent(text="test")], + ) + ], + orchestration_id="orch-789", + ) + + data = request.to_dict() + + assert "orchestrationId" in data + assert data["orchestrationId"] == "orch-789" + + def test_request_to_dict_excludes_orchestration_id_when_none(self) -> None: + """Test that to_dict excludes orchestrationId when not set.""" + request = DurableAgentStateRequest( + correlation_id="corr-123", + created_at=datetime.now(), + messages=[ + DurableAgentStateMessage( + role="user", + contents=[DurableAgentStateTextContent(text="test")], + ) + ], + ) + + data = request.to_dict() + + assert "orchestrationId" not in data + + def test_request_from_dict_with_orchestration_id(self) -> None: + """Test from_dict correctly parses orchestrationId.""" + data = { + "$type": "request", + "correlationId": "corr-123", + "createdAt": "2024-01-01T00:00:00Z", + "messages": [{"role": "user", "contents": [{"$type": "text", "text": "test"}]}], + "orchestrationId": "orch-from-dict", + } + + request = DurableAgentStateRequest.from_dict(data) + + assert request.orchestration_id == "orch-from-dict" + + def test_request_from_run_request_with_orchestration_id(self) -> None: + """Test from_run_request correctly transfers orchestration_id.""" + run_request = RunRequest( + message="test message", + correlation_id="corr-run", + orchestration_id="orch-from-run-request", + ) + + durable_request = DurableAgentStateRequest.from_run_request(run_request) + + assert durable_request.orchestration_id == "orch-from-run-request" + + def test_request_from_run_request_without_orchestration_id(self) -> None: + """Test from_run_request correctly handles missing orchestration_id.""" + run_request = RunRequest( + message="test message", + correlation_id="corr-run", + ) + + durable_request = DurableAgentStateRequest.from_run_request(run_request) + + assert durable_request.orchestration_id is None + + if __name__ == "__main__": pytest.main([__file__, "-v", "--tb=short"]) diff --git a/python/packages/azurefunctions/tests/test_models.py b/python/packages/azurefunctions/tests/test_models.py index 5b803ead13..92a3353fc5 100644 --- a/python/packages/azurefunctions/tests/test_models.py +++ b/python/packages/azurefunctions/tests/test_models.py @@ -336,6 +336,71 @@ def test_round_trip_with_correlationId(self) -> None: assert restored.correlation_id == original.correlation_id assert restored.thread_id == original.thread_id + def test_init_with_orchestration_id(self) -> None: + """Test RunRequest initialization with orchestration_id.""" + request = RunRequest( + message="Test message", + thread_id="thread-orch-init", + orchestration_id="orch-123", + ) + + assert request.message == "Test message" + assert request.orchestration_id == "orch-123" + + def test_to_dict_with_orchestration_id(self) -> None: + """Test to_dict includes orchestrationId.""" + request = RunRequest( + message="Test", + thread_id="thread-orch-to-dict", + orchestration_id="orch-456", + ) + data = request.to_dict() + + assert data["message"] == "Test" + assert data["orchestrationId"] == "orch-456" + + def test_to_dict_excludes_orchestration_id_when_none(self) -> None: + """Test to_dict excludes orchestrationId when not set.""" + request = RunRequest( + message="Test", + thread_id="thread-orch-none", + ) + data = request.to_dict() + + assert "orchestrationId" not in data + + def test_from_dict_with_orchestration_id(self) -> None: + """Test from_dict with orchestrationId.""" + data = { + "message": "Test", + "orchestrationId": "orch-789", + "thread_id": "thread-orch-from-dict", + } + request = RunRequest.from_dict(data) + + assert request.message == "Test" + assert request.orchestration_id == "orch-789" + assert request.thread_id == "thread-orch-from-dict" + + def test_round_trip_with_orchestration_id(self) -> None: + """Test round-trip to_dict and from_dict with orchestration_id.""" + original = RunRequest( + message="Test message", + thread_id="thread-123", + role=Role.SYSTEM, + correlation_id="corr-123", + orchestration_id="orch-123", + ) + + data = original.to_dict() + restored = RunRequest.from_dict(data) + + assert restored.message == original.message + assert restored.role == original.role + assert restored.correlation_id == original.correlation_id + assert restored.orchestration_id == original.orchestration_id + assert restored.thread_id == original.thread_id + class TestAgentResponse: """Test suite for AgentResponse.""" diff --git a/python/packages/azurefunctions/tests/test_orchestration.py b/python/packages/azurefunctions/tests/test_orchestration.py index 93201a64e9..6bc96b1918 100644 --- a/python/packages/azurefunctions/tests/test_orchestration.py +++ b/python/packages/azurefunctions/tests/test_orchestration.py @@ -140,6 +140,29 @@ def test_run_creates_entity_call(self) -> None: assert request["correlationId"] == "correlation-guid" assert "thread_id" in request assert request["thread_id"] == "thread-guid" + # Verify orchestration ID is set from context.instance_id + assert "orchestrationId" in request + assert request["orchestrationId"] == "test-instance-001" + + def test_run_sets_orchestration_id(self) -> None: + """Test that run() sets the orchestration_id from context.instance_id.""" + mock_context = Mock() + mock_context.instance_id = "my-orchestration-123" + mock_context.new_uuid = Mock(side_effect=["thread-guid", "correlation-guid"]) + + mock_task = Mock() + mock_task._is_scheduled = False + mock_context.call_entity = Mock(return_value=mock_task) + + agent = DurableAIAgent(mock_context, "TestAgent") + thread = agent.get_new_thread() + + agent.run(messages="Test", thread=thread) + + call_args = mock_context.call_entity.call_args + request = call_args[0][2] + + assert request["orchestrationId"] == "my-orchestration-123" def test_run_without_thread(self) -> None: """Test that run() works without explicit thread (creates unique session key).""" From ed842631ee1fd23b5db08261bc512665b47dd20d Mon Sep 17 00:00:00 2001 From: Laveesh Rohra Date: Wed, 26 Nov 2025 12:10:38 -0800 Subject: [PATCH 3/4] Fix type safety checks --- .../agent_framework_azurefunctions/_app.py | 16 +- .../_durable_agent_state.py | 262 ++++++++++-------- .../azurefunctions/tests/test_entities.py | 5 +- python/uv.lock | 64 ++--- 4 files changed, 187 insertions(+), 160 deletions(-) diff --git a/python/packages/azurefunctions/agent_framework_azurefunctions/_app.py b/python/packages/azurefunctions/agent_framework_azurefunctions/_app.py index e83a9244a7..7d8ebe0264 100644 --- a/python/packages/azurefunctions/agent_framework_azurefunctions/_app.py +++ b/python/packages/azurefunctions/agent_framework_azurefunctions/_app.py @@ -562,6 +562,7 @@ async def mcp_tool_handler(context: str, client: df.DurableOrchestrationClient) logger.debug("[MCP Tool Trigger] Received invocation for agent: %s", agent_name) return await self._handle_mcp_tool_invocation(agent_name=agent_name, context=context, client=client) + _ = mcp_tool_handler logger.debug("[AgentFunctionApp] Registered MCP tool trigger for agent: %s", agent_name) async def _handle_mcp_tool_invocation( @@ -587,15 +588,17 @@ async def _handle_mcp_tool_invocation( # Parse JSON context string try: - parsed_context = json.loads(context) + parsed_context: Any = json.loads(context) except json.JSONDecodeError as e: raise ValueError(f"Invalid MCP context format: {e}") from e + parsed_context = cast(Mapping[str, Any], parsed_context) if isinstance(parsed_context, dict) else {} + # Extract arguments from MCP context - arguments = parsed_context.get("arguments", {}) if isinstance(parsed_context, dict) else {} + arguments: dict[str, Any] = parsed_context.get("arguments", {}) # Validate required 'query' argument - query = arguments.get("query") + query: Any = arguments.get("query") if not query or not isinstance(query, str): raise ValueError("MCP Tool invocation is missing required 'query' argument of type string.") @@ -951,10 +954,9 @@ def _extract_normalized_headers(self, req: func.HttpRequest) -> dict[str, str]: """Create a lowercase header mapping from the incoming request.""" headers: dict[str, str] = {} raw_headers = req.headers - if isinstance(raw_headers, Mapping): - for key, value in raw_headers.items(): - if value is not None: - headers[str(key).lower()] = str(value) + for key, value in cast(Mapping[str, str], raw_headers).items(): + headers[key.lower()] = value + return headers @staticmethod diff --git a/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py b/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py index 6e2c3fbf9a..f870a135e1 100644 --- a/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py +++ b/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py @@ -32,7 +32,7 @@ import json from datetime import datetime, timezone from enum import Enum -from typing import Any +from typing import Any, cast from agent_framework import ( AgentRunResponse, @@ -74,6 +74,130 @@ def _parse_created_at(value: Any) -> datetime: return datetime.now(tz=timezone.utc) +def _parse_messages(data: dict[str, Any]) -> list[DurableAgentStateMessage]: + """Parse messages from a dictionary, converting dicts to DurableAgentStateMessage objects. + + Args: + data: Dictionary containing a 'messages' key with a list of message data + + Returns: + List of DurableAgentStateMessage objects + """ + messages: list[DurableAgentStateMessage] = [] + raw_messages: list[Any] = data.get("messages", []) + for raw_msg in raw_messages: + if isinstance(raw_msg, dict): + messages.append(DurableAgentStateMessage.from_dict(cast(dict[str, Any], raw_msg))) + elif isinstance(raw_msg, DurableAgentStateMessage): + messages.append(raw_msg) + return messages + + +def _parse_history_entries(data_dict: dict[str, Any]) -> list[DurableAgentStateEntry]: + """Parse conversation history entries from a dictionary. + + Args: + data_dict: Dictionary containing a 'conversationHistory' key with a list of entry data + + Returns: + List of DurableAgentStateEntry objects (requests and responses) + """ + history_data: list[Any] = data_dict.get("conversationHistory", []) + deserialized_history: list[DurableAgentStateEntry] = [] + for raw_entry in history_data: + if isinstance(raw_entry, dict): + entry_dict = cast(dict[str, Any], raw_entry) + entry_type = entry_dict.get("$type") or entry_dict.get("json_type") + if entry_type == DurableAgentStateEntryJsonType.RESPONSE: + deserialized_history.append(DurableAgentStateResponse.from_dict(entry_dict)) + elif entry_type == DurableAgentStateEntryJsonType.REQUEST: + deserialized_history.append(DurableAgentStateRequest.from_dict(entry_dict)) + else: + deserialized_history.append(DurableAgentStateEntry.from_dict(entry_dict)) + elif isinstance(raw_entry, DurableAgentStateEntry): + deserialized_history.append(raw_entry) + return deserialized_history + + +def _parse_contents(data: dict[str, Any]) -> list[DurableAgentStateContent]: + """Parse content items from a dictionary. + + Args: + data: Dictionary containing a 'contents' key with a list of content data + + Returns: + List of DurableAgentStateContent objects + """ + contents: list[DurableAgentStateContent] = [] + raw_contents: list[Any] = data.get("contents", []) + for raw_content in raw_contents: + if isinstance(raw_content, dict): + content_dict = cast(dict[str, Any], raw_content) + content_type: str | None = content_dict.get("$type") + if content_type == DurableAgentStateTextContent.type: + contents.append(DurableAgentStateTextContent(text=content_dict.get("text"))) + elif content_type == DurableAgentStateDataContent.type: + contents.append( + DurableAgentStateDataContent( + uri=str(content_dict.get("uri", "")), + media_type=content_dict.get("mediaType"), + ) + ) + elif content_type == DurableAgentStateErrorContent.type: + contents.append( + DurableAgentStateErrorContent( + message=content_dict.get("message"), + error_code=content_dict.get("errorCode"), + details=content_dict.get("details"), + ) + ) + elif content_type == DurableAgentStateFunctionCallContent.type: + contents.append( + DurableAgentStateFunctionCallContent( + call_id=str(content_dict.get("callId", "")), + name=str(content_dict.get("name", "")), + arguments=content_dict.get("arguments", {}), + ) + ) + elif content_type == DurableAgentStateFunctionResultContent.type: + contents.append( + DurableAgentStateFunctionResultContent( + call_id=str(content_dict.get("callId", "")), + result=content_dict.get("result"), + ) + ) + elif content_type == DurableAgentStateHostedFileContent.type: + contents.append(DurableAgentStateHostedFileContent(file_id=str(content_dict.get("fileId", "")))) + elif content_type == DurableAgentStateHostedVectorStoreContent.type: + contents.append( + DurableAgentStateHostedVectorStoreContent( + vector_store_id=str(content_dict.get("vectorStoreId", "")) + ) + ) + elif content_type == DurableAgentStateTextReasoningContent.type: + contents.append(DurableAgentStateTextReasoningContent(text=content_dict.get("text"))) + elif content_type == DurableAgentStateUriContent.type: + contents.append( + DurableAgentStateUriContent( + uri=str(content_dict.get("uri", "")), + media_type=str(content_dict.get("mediaType", "")), + ) + ) + elif content_type == DurableAgentStateUsageContent.type: + usage_data = content_dict.get("usage") + if usage_data and isinstance(usage_data, dict): + contents.append( + DurableAgentStateUsageContent( + usage=DurableAgentStateUsage.from_dict(cast(dict[str, Any], usage_data)) + ) + ) + elif content_type == DurableAgentStateUnknownContent.type: + contents.append(DurableAgentStateUnknownContent(content=content_dict.get("content", {}))) + elif isinstance(raw_content, DurableAgentStateContent): + contents.append(raw_content) + return contents + + class DurableAgentStateContent: """Base class for all content types in durable agent state messages. @@ -197,25 +321,8 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls, data_dict: dict[str, Any]) -> DurableAgentStateData: - # Restore the conversation history - deserialize entries from dicts to objects - history_data = data_dict.get("conversationHistory", []) - deserialized_history: list[DurableAgentStateEntry] = [] - for entry_dict in history_data: - if isinstance(entry_dict, dict): - # Deserialize based on $type discriminator - entry_type = entry_dict.get("$type") or entry_dict.get("json_type") - if entry_type == DurableAgentStateEntryJsonType.RESPONSE: - deserialized_history.append(DurableAgentStateResponse.from_dict(entry_dict)) - elif entry_type == DurableAgentStateEntryJsonType.REQUEST: - deserialized_history.append(DurableAgentStateRequest.from_dict(entry_dict)) - else: - deserialized_history.append(DurableAgentStateEntry.from_dict(entry_dict)) - else: - # Already an object - deserialized_history.append(entry_dict) - return cls( - conversation_history=deserialized_history, + conversation_history=_parse_history_entries(data_dict), extension_data=data_dict.get("extensionData"), ) @@ -227,7 +334,7 @@ class DurableAgentState: in Azure Durable Entities. It maintains the conversation history as a sequence of request and response entries, each with their messages, timestamps, and metadata. - The state follows a versioned schema (currently 1.1.0) that defines the structure for: + The state follows a versioned schema (see SCHEMA_VERSION class constant) that defines the structure for: - Request entries: User/system messages with optional response format specifications - Response entries: Assistant messages with token usage information - Messages: Individual chat messages with role, content items, and timestamps @@ -235,7 +342,7 @@ class DurableAgentState: State is serialized to JSON with this structure: { - "schemaVersion": "1.1.0", + "schemaVersion": "", "data": { "conversationHistory": [ {"$type": "request", "correlationId": "...", "createdAt": "...", "messages": [...]}, @@ -246,17 +353,20 @@ class DurableAgentState: Attributes: data: Container for conversation history and optional extension data - schema_version: Schema version string (defaults to "1.1.0") + schema_version: Schema version string (defaults to SCHEMA_VERSION) """ + # Durable Agent Schema version + SCHEMA_VERSION: str = "1.1.0" + data: DurableAgentStateData - schema_version: str = "1.1.0" + schema_version: str = SCHEMA_VERSION - def __init__(self, schema_version: str = "1.1.0"): + def __init__(self, schema_version: str = SCHEMA_VERSION): """Initialize a new durable agent state. Args: - schema_version: Schema version to use (defaults to "1.1.0") + schema_version: Schema version to use (defaults to SCHEMA_VERSION) """ self.data = DurableAgentStateData() self.schema_version = schema_version @@ -325,7 +435,7 @@ def try_get_agent_response(self, correlation_id: str) -> dict[str, Any] | None: if entry.correlation_id == correlation_id and isinstance(entry, DurableAgentStateResponse): # Found the entry, extract response data # Get the text content from assistant messages only - content = "\n".join(message.text for message in entry.messages if message.text is not None) + content = "\n".join(message.text for message in entry.messages if message.text) return {"content": content, "message_count": self.message_count, "correlationId": correlation_id} return None @@ -388,28 +498,17 @@ def __init__( self.extension_data = extension_data def to_dict(self) -> dict[str, Any]: - # Ensure createdAt is never null - created_at_value = self.created_at - if created_at_value is None: - created_at_value = datetime.now(tz=timezone.utc) - return { "$type": self.json_type, "correlationId": self.correlation_id, - "createdAt": created_at_value.isoformat() if isinstance(created_at_value, datetime) else created_at_value, + "createdAt": self.created_at.isoformat(), "messages": [m.to_dict() for m in self.messages], } @classmethod def from_dict(cls, data: dict[str, Any]) -> DurableAgentStateEntry: created_at = _parse_created_at(data.get("created_at")) - - messages = [] - for msg_dict in data.get("messages", []): - if isinstance(msg_dict, dict): - messages.append(DurableAgentStateMessage.from_dict(msg_dict)) - else: - messages.append(msg_dict) + messages = _parse_messages(data) return cls( json_type=DurableAgentStateEntryJsonType(data.get("$type", "entry")), @@ -475,13 +574,7 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls, data: dict[str, Any]) -> DurableAgentStateRequest: created_at = _parse_created_at(data.get("created_at")) - - messages = [] - for msg_dict in data.get("messages", []): - if isinstance(msg_dict, dict): - messages.append(DurableAgentStateMessage.from_dict(msg_dict)) - else: - messages.append(msg_dict) + messages = _parse_messages(data) return cls( correlation_id=data.get("correlationId", ""), @@ -553,20 +646,12 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls, data: dict[str, Any]) -> DurableAgentStateResponse: created_at = _parse_created_at(data.get("created_at")) - - messages = [] - for msg_dict in data.get("messages", []): - if isinstance(msg_dict, dict): - messages.append(DurableAgentStateMessage.from_dict(msg_dict)) - else: - messages.append(msg_dict) + messages = _parse_messages(data) usage_dict = data.get("usage") - usage = None + usage: DurableAgentStateUsage | None = None if usage_dict and isinstance(usage_dict, dict): - usage = DurableAgentStateUsage.from_dict(usage_dict) - elif usage_dict: - usage = usage_dict + usage = DurableAgentStateUsage.from_dict(cast(dict[str, Any], usage_dict)) return cls( correlation_id=data.get("correlationId", ""), @@ -647,68 +732,9 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls, data: dict[str, Any]) -> DurableAgentStateMessage: - contents: list[DurableAgentStateContent] = [] - for content_dict in data.get("contents", []): - if isinstance(content_dict, dict): - content_type = content_dict.get("$type") - if content_type == DurableAgentStateTextContent.type: - contents.append(DurableAgentStateTextContent(text=content_dict.get("text"))) - elif content_type == DurableAgentStateDataContent.type: - contents.append( - DurableAgentStateDataContent( - uri=content_dict.get("uri", ""), media_type=content_dict.get("mediaType") - ) - ) - elif content_type == DurableAgentStateErrorContent.type: - contents.append( - DurableAgentStateErrorContent( - message=content_dict.get("message"), - error_code=content_dict.get("errorCode"), - details=content_dict.get("details"), - ) - ) - elif content_type == DurableAgentStateFunctionCallContent.type: - contents.append( - DurableAgentStateFunctionCallContent( - call_id=content_dict.get("callId", ""), - name=content_dict.get("name", ""), - arguments=content_dict.get("arguments", {}), - ) - ) - elif content_type == DurableAgentStateFunctionResultContent.type: - contents.append( - DurableAgentStateFunctionResultContent( - call_id=content_dict.get("callId", ""), result=content_dict.get("result") - ) - ) - elif content_type == DurableAgentStateHostedFileContent.type: - contents.append(DurableAgentStateHostedFileContent(file_id=content_dict.get("fileId", ""))) - elif content_type == DurableAgentStateHostedVectorStoreContent.type: - contents.append( - DurableAgentStateHostedVectorStoreContent(vector_store_id=content_dict.get("vectorStoreId", "")) - ) - elif content_type == DurableAgentStateTextReasoningContent.type: - contents.append(DurableAgentStateTextReasoningContent(text=content_dict.get("text"))) - elif content_type == DurableAgentStateUriContent.type: - contents.append( - DurableAgentStateUriContent( - uri=content_dict.get("uri", ""), media_type=content_dict.get("mediaType", "") - ) - ) - elif content_type == DurableAgentStateUsageContent.type: - usage_data = content_dict.get("usage") - if usage_data and isinstance(usage_data, dict): - contents.append( - DurableAgentStateUsageContent(usage=DurableAgentStateUsage.from_dict(usage_data)) - ) - elif content_type == DurableAgentStateUnknownContent.type: - contents.append(DurableAgentStateUnknownContent(content=content_dict.get("content", {}))) - else: - contents.append(content_dict) # type: ignore - return cls( role=data.get("role", ""), - contents=contents, + contents=_parse_contents(data), author_name=data.get("authorName"), created_at=_parse_created_at(data.get("createdAt")), extension_data=data.get("extensionData"), @@ -717,7 +743,7 @@ def from_dict(cls, data: dict[str, Any]) -> DurableAgentStateMessage: @property def text(self) -> str: """Extract text from the contents list.""" - text_parts = [] + text_parts: list[str] = [] for content in self.contents: if isinstance(content, DurableAgentStateTextContent): text_parts.append(content.text or "") diff --git a/python/packages/azurefunctions/tests/test_entities.py b/python/packages/azurefunctions/tests/test_entities.py index ff5adbe746..07eef65e54 100644 --- a/python/packages/azurefunctions/tests/test_entities.py +++ b/python/packages/azurefunctions/tests/test_entities.py @@ -79,7 +79,7 @@ def test_init_creates_entity(self) -> None: assert entity.agent == mock_agent assert len(entity.state.data.conversation_history) == 0 assert entity.state.data.extension_data is None - assert entity.state.schema_version == "1.1.0" + assert entity.state.schema_version == DurableAgentState.SCHEMA_VERSION def test_init_stores_agent_reference(self) -> None: """Test that the agent reference is stored correctly.""" @@ -124,8 +124,7 @@ async def test_run_agent_executes_agent(self) -> None: # Verify agent.run was called mock_agent.run.assert_called_once() _, kwargs = mock_agent.run.call_args - sent_messages = kwargs.get("messages") - assert isinstance(sent_messages, list) + sent_messages: list[Any] = kwargs.get("messages") assert len(sent_messages) == 1 sent_message = sent_messages[0] assert isinstance(sent_message, ChatMessage) diff --git a/python/uv.lock b/python/uv.lock index 87081e2bc5..eba6d09b0d 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -47,7 +47,7 @@ overrides = [ [[package]] name = "a2a-sdk" -version = "0.3.17" +version = "0.3.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -56,9 +56,9 @@ dependencies = [ { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/c1/695e724ca9fa88933625f694dd371257375d25b9567919d86eedf8530a05/a2a_sdk-0.3.17.tar.gz", hash = "sha256:c39bb5731d7386c323efe6b01d15fd82fb0e65d512d1b0caaa46ce180d4cd4df", size = 229014, upload-time = "2025-11-24T12:37:41.261Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/74/db61ee9d2663b291a7eec03bbc7685bec72b1ceb113001350766c03f20de/a2a_sdk-0.3.19.tar.gz", hash = "sha256:ecf526d1d7781228d8680292f913bad1099ba3335a7f0ea6811543c2bd3e601d", size = 229184, upload-time = "2025-11-25T13:48:05.185Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/75/d7254295a2073747d34aa4dba20fe6791f761781d718b88539bb3d4524c4/a2a_sdk-0.3.17-py3-none-any.whl", hash = "sha256:d3e04db524d6cfd087af0b1aede9cb790155ca8770b1d651a30df514dd2c056e", size = 141527, upload-time = "2025-11-24T12:37:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/cd/cd/14c1242d171b9739770be35223f1cbc1fb0244ebea2c704f8ae0d9e6abf7/a2a_sdk-0.3.19-py3-none-any.whl", hash = "sha256:314123f84524259313ec0cd9826a34bae5de769dea44b8eb9a0eca79b8935772", size = 141519, upload-time = "2025-11-25T13:48:02.622Z" }, ] [[package]] @@ -1799,7 +1799,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" }, + { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -4583,7 +4583,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.4" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -4591,9 +4591,9 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [package.optional-dependencies] @@ -5107,7 +5107,7 @@ wheels = [ [[package]] name = "redisvl" -version = "0.12.0" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpath-ng", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -5120,9 +5120,9 @@ dependencies = [ { name = "redis", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "tenacity", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/6b/10fe769e1102d99cd9e47633bacd01ab71fb416958e77469cc55f032f471/redisvl-0.12.0.tar.gz", hash = "sha256:205db9eb9639b78a9e479b012f6db64a12aa47129fdfaf3ad59623b5736e00d2", size = 683456, upload-time = "2025-11-21T23:20:57.218Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/ac/7c527765011d07652ff9d97fd16f563d625bd1887ad09bafe2626f77f225/redisvl-0.12.1.tar.gz", hash = "sha256:c4df3f7dd2d92c71a98e54ba32bcfb4f7bd526c749e4721de0fd1f08e0ecddec", size = 689730, upload-time = "2025-11-25T19:24:04.562Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/24/417f7c171caa460e45b688ee94e67788bd63544a90c3fdc411f248fce795/redisvl-0.12.0-py3-none-any.whl", hash = "sha256:406695793681c1f46f61b6a1141a6b6f86261bf690caf0de00595c511700012d", size = 175071, upload-time = "2025-11-21T23:20:55.605Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6a/f8c9f915a1d18fff2499684caff929d0c6e004ac5f6e5f9ecec88314cd2a/redisvl-0.12.1-py3-none-any.whl", hash = "sha256:c7aaea242508624b78a448362b7a33e3b411049271ce8bdc7ef95208b1095e6e", size = 176692, upload-time = "2025-11-25T19:24:03.013Z" }, ] [[package]] @@ -6303,28 +6303,28 @@ wheels = [ [[package]] name = "uv" -version = "0.9.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/08/3bf76403ea7c22feef634849137fab10b28ab5ba5bbf08a53390763d5448/uv-0.9.11.tar.gz", hash = "sha256:605a7a57f508aabd029fc0c5ef5c60a556f8c50d32e194f1a300a9f4e87f18d4", size = 3744387, upload-time = "2025-11-20T23:20:00.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/26/8f917e9faddd9cb49abcbc8c7dac5343b0f61d04c6ac36873d2a324fee1a/uv-0.9.11-py3-none-linux_armv6l.whl", hash = "sha256:803f85cf25ab7f1fca10fe2e40a1b9f5b1d48efc25efd6651ba3c9668db6a19e", size = 20787588, upload-time = "2025-11-20T23:18:53.738Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1f/eafd39c719ddee19fc25884f68c1a7e736c0fca63c1cbef925caf8ebd739/uv-0.9.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6a31b0bd4eaec59bf97816aefbcd75cae4fcc8875c4b19ef1846b7bff3d67c70", size = 19922144, upload-time = "2025-11-20T23:18:57.569Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f3/6b9fac39e5b65fa47dba872dcf171f1470490cd645343e8334f20f73885b/uv-0.9.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48548a23fb5a103b8955dfafff7d79d21112b8e25ce5ff25e3468dc541b20e83", size = 18380643, upload-time = "2025-11-20T23:19:01.02Z" }, - { url = "https://files.pythonhosted.org/packages/d6/9a/d4080e95950a4fc6fdf20d67b9a43ffb8e3d6d6b7c8dda460ae73ddbecd9/uv-0.9.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:cb680948e678590b5960744af2ecea6f2c0307dbb74ac44daf5c00e84ad8c09f", size = 20310262, upload-time = "2025-11-20T23:19:04.914Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b4/86d9c881bd6accf2b766f7193b50e9d5815f2b34806191d90ea24967965e/uv-0.9.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ef1982295e5aaf909a9668d6fb6abfc5089666c699f585a36f3a67f1a22916a", size = 20392988, upload-time = "2025-11-20T23:19:08.258Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1d/6a227b7ca1829442c1419ba1db856d176b6e0861f9bf9355a8790a5d02b5/uv-0.9.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92ff773aa4193148019533c55382c2f9c661824bbf0c2e03f12aeefc800ede57", size = 21394892, upload-time = "2025-11-20T23:19:12.626Z" }, - { url = "https://files.pythonhosted.org/packages/5a/8f/df45b8409923121de8c4081c9d6d8ba3273eaa450645e1e542d83179c7b5/uv-0.9.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70137a46675bbecf3a8b43d292a61767f1b944156af3d0f8d5986292bd86f6cf", size = 22987735, upload-time = "2025-11-20T23:19:16.27Z" }, - { url = "https://files.pythonhosted.org/packages/89/51/bbf3248a619c9f502d310a11362da5ed72c312d354fb8f9667c5aa3be9dd/uv-0.9.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5af9117bab6c4b3a1cacb0cddfb3cd540d0adfb13c7b8a9a318873cf2d07e52", size = 22617321, upload-time = "2025-11-20T23:19:20.1Z" }, - { url = "https://files.pythonhosted.org/packages/3f/cd/a158ec989c5433dc86ebd9fea800f2aed24255b84ab65b6d7407251e5e31/uv-0.9.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cc86940d9b3a425575f25dc45247be2fb31f7fed7bf3394ae9daadd466e5b80", size = 21615712, upload-time = "2025-11-20T23:19:23.71Z" }, - { url = "https://files.pythonhosted.org/packages/73/da/2597becbc0fcbb59608d38fda5db79969e76dedf5b072f0e8564c8f0628b/uv-0.9.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e97906ca1b90dac91c23af20e282e2e37c8eb80c3721898733928a295f2defda", size = 21661022, upload-time = "2025-11-20T23:19:27.385Z" }, - { url = "https://files.pythonhosted.org/packages/52/66/9b8f3b3529b23c2a6f5b9612da70ea53117935ec999757b4f1d640f63d63/uv-0.9.11-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d901269e1db72abc974ba61d37be6e56532e104922329e0b553d9df07ba224be", size = 20440548, upload-time = "2025-11-20T23:19:31.051Z" }, - { url = "https://files.pythonhosted.org/packages/72/b2/683afdb83e96dd966eb7cf3688af56a1b826c8bc1e8182fb10ec35b3e391/uv-0.9.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8abfb7d4b136de3e92dd239ea9a51d4b7bbb970dc1b33bec84d08facf82b9a6e", size = 21493758, upload-time = "2025-11-20T23:19:34.688Z" }, - { url = "https://files.pythonhosted.org/packages/f4/00/99848bc9834aab104fa74aa1a60b1ca478dee824d2e4aacb15af85673572/uv-0.9.11-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:1f8afc13b3b94bce1e72514c598d41623387b2b61b68d7dbce9a01a0d8874860", size = 20332324, upload-time = "2025-11-20T23:19:38.376Z" }, - { url = "https://files.pythonhosted.org/packages/6c/94/8cfd1bb1cc5d768cb334f976ba2686c6327e4ac91c16b8469b284956d4d9/uv-0.9.11-py3-none-musllinux_1_1_i686.whl", hash = "sha256:7d414cfa410f1850a244d87255f98d06ca61cc13d82f6413c4f03e9e0c9effc7", size = 20845062, upload-time = "2025-11-20T23:19:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/a0/42/43f66bfc621464dabe9cfe3cbf69cddc36464da56ab786c94fc9ccf99cc7/uv-0.9.11-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:edc14143d0ba086a7da4b737a77746bb36bc00e3d26466f180ea99e3bf795171", size = 21857559, upload-time = "2025-11-20T23:19:46.026Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4d/bfd41bf087522601c724d712c3727aeb62f51b1f67c4ab86a078c3947525/uv-0.9.11-py3-none-win32.whl", hash = "sha256:af5fd91eecaa04b4799f553c726307200f45da844d5c7c5880d64db4debdd5dc", size = 19639246, upload-time = "2025-11-20T23:19:50.254Z" }, - { url = "https://files.pythonhosted.org/packages/2c/2f/d51c02627de68a7ca5b82f0a5d61d753beee3fe696366d1a1c5d5e40cd58/uv-0.9.11-py3-none-win_amd64.whl", hash = "sha256:c65a024ad98547e32168f3a52360fe73ff39cd609a8fb9dd2509aac91483cfc8", size = 21626822, upload-time = "2025-11-20T23:19:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/af/d8/e07e866ee328d3c9f27a6d57a018d8330f47be95ef4654a178779c968a66/uv-0.9.11-py3-none-win_arm64.whl", hash = "sha256:4907a696c745703542ed2559bdf5380b92c8b1d4bf290ebfed45bf9a2a2c6690", size = 20046856, upload-time = "2025-11-20T23:19:58.517Z" }, +version = "0.9.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/10/ad3dc22d0cabe7c335a1d7fc079ceda73236c0984da8d8446de3d2d30c9b/uv-0.9.13.tar.gz", hash = "sha256:105a6f4ff91480425d1b61917e89ac5635b8e58a79267e2be103338ab448ccd6", size = 3761269, upload-time = "2025-11-26T16:17:30.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/ae/94ec7111b006bc7212bf727907a35510a37928c15302ecc3757cfd7d6d7f/uv-0.9.13-py3-none-linux_armv6l.whl", hash = "sha256:7be41bdeb82c246f8ef1421cf4d1dd6ab3e5f46e4235eb22c8f5bf095debc069", size = 20830010, upload-time = "2025-11-26T16:17:13.147Z" }, + { url = "https://files.pythonhosted.org/packages/8a/53/5eb0eb0ca7ed41c10447d6c859b4d81efc5b76de14d01fd900af7d7bd1be/uv-0.9.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1d4c624bb2b81f885b7182d99ebdd5c2842219d2ac355626a4a2b6c1e3e6f8c1", size = 19961915, upload-time = "2025-11-26T16:17:15.587Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d1/0f0c8dc2125709a8e072b73e5e89da9f016d492ca88b909b23b3006c2b51/uv-0.9.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:318d0b9a39fa26f95a428a551d44cbefdfd58178954a831669248a42f39d3c75", size = 18426731, upload-time = "2025-11-26T16:17:31.855Z" }, + { url = "https://files.pythonhosted.org/packages/36/ee/f9db8cb69d584b8326b3e0e60e5a639469cdebac76e7f4ff5ba7c2c6fe6c/uv-0.9.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:6a641ed7bcc8d317d22a7cb1ad0dfa41078c8e392f6f9248b11451abff0ccf50", size = 20315156, upload-time = "2025-11-26T16:17:08.125Z" }, + { url = "https://files.pythonhosted.org/packages/8a/49/045bbfe264fc1add3e238e0e11dec62725c931946dbcda3780d15ca3591b/uv-0.9.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e797ae9d283ee129f33157d84742607547939ca243d7a8c17710dc857a7808bd", size = 20430487, upload-time = "2025-11-26T16:17:28.143Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/18a14dbaedfd2492de5cca50b46a238d5199e9f0291f027f63a03f2ebdd4/uv-0.9.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48fa9cf568c481c150f957a2f9285d1c3ad2c1d50c904b03bcebd5c9669c5668", size = 21378284, upload-time = "2025-11-26T16:16:48.696Z" }, + { url = "https://files.pythonhosted.org/packages/08/04/d0fc5fb25e3f90740913b1c028e1556515e4e1fea91e1f58e7c18c1712a3/uv-0.9.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a66817a416c1c79303fd5e40c319ed9c8e59b46fb04cf3eac4447e95b9ec8763", size = 23016232, upload-time = "2025-11-26T16:16:46.149Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bc/cef461a47cddeb99c2a3b31f3946d38cbca7923b0f2fb6666756ba63a84a/uv-0.9.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05eb7e941c54666e8c52519a79ff46d15b5206967645652d3dfb2901fd982493", size = 22657140, upload-time = "2025-11-26T16:17:03.026Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/5c9de65279480b1922c51aae409bbfa1d90ff108f8b81688022499f2c3e2/uv-0.9.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fe5ac5b0a98a876da8f4c08e03217589a89ea96883cfdc9c4b397bf381ef7b9", size = 21644453, upload-time = "2025-11-26T16:16:43.228Z" }, + { url = "https://files.pythonhosted.org/packages/da/e5/148ab5edb339f5833d04f0bcb8380a53e8b19bd5f091ae67222ed188b393/uv-0.9.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6627d0abbaf58f9ff6e07e3f8522d65421230969aa2d7b10421339f0cb30dec4", size = 21655007, upload-time = "2025-11-26T16:16:51.36Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/a77587e4608af6efc5a72d3a937573eb5d08052550a3f248821b50898626/uv-0.9.13-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6cca7671efacf6e2950eb86273ecce4a9a3f8bfa6ac04e8a17be9499bb3bb882", size = 20448163, upload-time = "2025-11-26T16:16:53.768Z" }, + { url = "https://files.pythonhosted.org/packages/81/ad/e3bb28d175f22edf1779a81b76910e842dcde012859556b28e9f4b630f26/uv-0.9.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2e00a4f8404000e86074d7d2fe5734078126a65aefed1e9a39f10c390c4c15dc", size = 21477072, upload-time = "2025-11-26T16:16:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/32/b6/9231365ab2495107a9e23aa36bb5400a4b697baaa0e5367f009072e88752/uv-0.9.13-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:3a4c16906e78f148c295a2e4f2414b843326a0f48ae68f7742149fd2d5dafbf7", size = 20421263, upload-time = "2025-11-26T16:17:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/8c/83/d83eeee9cea21b9a9e053d4a2ec752a3b872e22116851317da04681cc27e/uv-0.9.13-py3-none-musllinux_1_1_i686.whl", hash = "sha256:f254cb60576a3ae17f8824381f0554120b46e2d31a1c06fc61432c55d976892d", size = 20855418, upload-time = "2025-11-26T16:17:05.552Z" }, + { url = "https://files.pythonhosted.org/packages/6e/88/70102f374cfbbb284c6fe385e35978bff25a70b8e6afa871886af8963595/uv-0.9.13-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d50cea327b994786866b2d12c073097b9c8d883d42f0c0408b2d968492f571a4", size = 21871073, upload-time = "2025-11-26T16:17:00.213Z" }, + { url = "https://files.pythonhosted.org/packages/01/05/00c90367db0c81379c9d2b1fb458a09a0704ecd89821c071cb0d8a917752/uv-0.9.13-py3-none-win32.whl", hash = "sha256:a80296b1feb61bac36aee23ea79be33cd9aa545236d0780fbffaac113a17a090", size = 19607949, upload-time = "2025-11-26T16:17:23.337Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e0/718b433acf811388e309936524be5786b8e0cc8ff23128f9cc29a34c075b/uv-0.9.13-py3-none-win_amd64.whl", hash = "sha256:5732cd0fe09365fa5ad2c0a2d0c007bb152a2aa3c48e79f570eec13fc235d59d", size = 21722341, upload-time = "2025-11-26T16:17:20.764Z" }, + { url = "https://files.pythonhosted.org/packages/9f/31/142457b7c9d5edcdd8d4853c740c397ec83e3688b69d0ef55da60f7ab5b5/uv-0.9.13-py3-none-win_arm64.whl", hash = "sha256:edfc3d53b6adefae766a67672e533d7282431f0deb2570186d1c3dd0d0e3c0a3", size = 20042030, upload-time = "2025-11-26T16:17:18.058Z" }, ] [[package]] From aeada4056366c4e4ef466820eef25c32750aa7b6 Mon Sep 17 00:00:00 2001 From: Laveesh Rohra Date: Wed, 26 Nov 2025 14:21:18 -0800 Subject: [PATCH 4/4] Fix tests --- .../agent_framework_azurefunctions/_durable_agent_state.py | 2 +- python/packages/azurefunctions/tests/test_orchestration.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py b/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py index e0287b128a..ffb71d2367 100644 --- a/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py +++ b/python/packages/azurefunctions/agent_framework_azurefunctions/_durable_agent_state.py @@ -393,7 +393,7 @@ def from_dict(cls, state: dict[str, Any]) -> DurableAgentState: logger.warning("Resetting state as it is incompatible with the current schema, all history will be lost") return cls() - instance = cls(schema_version=state.get("schemaVersion", "1.0.0")) + instance = cls(schema_version=state.get("schemaVersion", DurableAgentState.SCHEMA_VERSION)) instance.data = DurableAgentStateData.from_dict(state.get("data", {})) return instance diff --git a/python/packages/azurefunctions/tests/test_orchestration.py b/python/packages/azurefunctions/tests/test_orchestration.py index c763f19757..0f845d4105 100644 --- a/python/packages/azurefunctions/tests/test_orchestration.py +++ b/python/packages/azurefunctions/tests/test_orchestration.py @@ -312,9 +312,8 @@ def test_run_sets_orchestration_id(self) -> None: mock_context.instance_id = "my-orchestration-123" mock_context.new_uuid = Mock(side_effect=["thread-guid", "correlation-guid"]) - mock_task = Mock() - mock_task._is_scheduled = False - mock_context.call_entity = Mock(return_value=mock_task) + entity_task = _create_entity_task() + mock_context.call_entity = Mock(return_value=entity_task) agent = DurableAIAgent(mock_context, "TestAgent") thread = agent.get_new_thread()