From 4a3251756c8b1931f6ffc5353995bbfbe111120f Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Mon, 9 Mar 2026 13:57:13 -0700 Subject: [PATCH 1/5] Fix store=False not overriding client default --- .../packages/core/agent_framework/_agents.py | 8 +- .../packages/core/tests/core/test_agents.py | 284 +++++++++++---- python/uv.lock | 332 +++++++++--------- 3 files changed, 400 insertions(+), 224 deletions(-) diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index 3aaf9f1419..b25f6c7013 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -1033,6 +1033,8 @@ async def _prepare_run_context( kwargs: dict[str, Any], ) -> _RunContext: opts = dict(options) if options else {} + # Merge default options with runtime options (runtime takes precedence) + opts = {**self.default_options, **opts} # Get tools from options or named parameter (named param takes precedence) tools_ = tools if tools is not None else opts.pop("tools", None) @@ -1046,8 +1048,10 @@ async def _prepare_run_context( and not self.context_providers and not session.service_session_id and not opts.get("conversation_id") - and not opts.get("store") - and not (getattr(self.client, "STORES_BY_DEFAULT", False) and opts.get("store") is not False) + # `store` takes precedence over client-level storage indicators. If `store` is + # explicitly set to False, we won't inject the InMemoryHistoryProvider, even if + # the client indicates that stores are used by default. + and not opts.get("store", getattr(self.client, "STORES_BY_DEFAULT", False)) ): self.context_providers.append(InMemoryHistoryProvider()) diff --git a/python/packages/core/tests/core/test_agents.py b/python/packages/core/tests/core/test_agents.py index d41b87b707..36339bef19 100644 --- a/python/packages/core/tests/core/test_agents.py +++ b/python/packages/core/tests/core/test_agents.py @@ -7,8 +7,6 @@ from uuid import uuid4 import pytest -from pytest import raises - from agent_framework import ( Agent, AgentResponse, @@ -27,6 +25,7 @@ ) from agent_framework._agents import _get_tool_name, _merge_options, _sanitize_agent_name from agent_framework._mcp import MCPTool +from pytest import raises def test_agent_session_type(agent_session: AgentSession) -> None: @@ -50,7 +49,9 @@ async def test_agent_run_with_content(agent: SupportsAgentRun) -> None: async def test_agent_run_streaming(agent: SupportsAgentRun) -> None: - async def collect_updates(updates: AsyncIterable[AgentResponseUpdate]) -> list[AgentResponseUpdate]: + async def collect_updates( + updates: AsyncIterable[AgentResponseUpdate], + ) -> list[AgentResponseUpdate]: return [u async for u in updates] updates = await collect_updates(agent.run("test", stream=True)) @@ -72,7 +73,9 @@ async def test_chat_client_agent_init(client: SupportsChatGetResponse) -> None: assert agent.description == "Test" -async def test_chat_client_agent_init_with_name(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_init_with_name( + client: SupportsChatGetResponse, +) -> None: agent_id = str(uuid4()) agent = Agent(client=client, id=agent_id, name="Test Agent", description="Test") @@ -108,7 +111,13 @@ class Greeting(BaseModel): json_text = '{"greeting": "Hello"}' client.streaming_responses.append( # type: ignore[attr-defined] - [ChatResponseUpdate(contents=[Content.from_text(json_text)], role="assistant", finish_reason="stop")] + [ + ChatResponseUpdate( + contents=[Content.from_text(json_text)], + role="assistant", + finish_reason="stop", + ) + ] ) agent = Agent(client=client, default_options={"response_format": Greeting}) @@ -134,7 +143,13 @@ class Greeting(BaseModel): json_text = '{"greeting": "Hi"}' client.streaming_responses.append( # type: ignore[attr-defined] - [ChatResponseUpdate(contents=[Content.from_text(json_text)], role="assistant", finish_reason="stop")] + [ + ChatResponseUpdate( + contents=[Content.from_text(json_text)], + role="assistant", + finish_reason="stop", + ) + ] ) agent = Agent(client=client) @@ -149,14 +164,18 @@ class Greeting(BaseModel): assert result.value.greeting == "Hi" -async def test_chat_client_agent_create_session(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_create_session( + client: SupportsChatGetResponse, +) -> None: agent = Agent(client=client) session = agent.create_session() assert isinstance(session, AgentSession) -async def test_chat_client_agent_prepare_session_and_messages(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_prepare_session_and_messages( + client: SupportsChatGetResponse, +) -> None: from agent_framework._sessions import InMemoryHistoryProvider agent = Agent(client=client, context_providers=[InMemoryHistoryProvider()]) @@ -175,7 +194,9 @@ async def test_chat_client_agent_prepare_session_and_messages(client: SupportsCh assert result_messages[1].text == "Test" -async def test_prepare_session_does_not_mutate_agent_chat_options(client: SupportsChatGetResponse) -> None: +async def test_prepare_session_does_not_mutate_agent_chat_options( + client: SupportsChatGetResponse, +) -> None: tool = {"type": "code_interpreter"} agent = Agent(client=client, tools=[tool]) @@ -195,9 +216,13 @@ async def test_prepare_session_does_not_mutate_agent_chat_options(client: Suppor assert len(agent.default_options["tools"]) == 1 -async def test_chat_client_agent_run_with_session(chat_client_base: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_run_with_session( + chat_client_base: SupportsChatGetResponse, +) -> None: mock_response = ChatResponse( - messages=[Message(role="assistant", contents=[Content.from_text("test response")])], + messages=[ + Message(role="assistant", contents=[Content.from_text("test response")]) + ], conversation_id="123", ) chat_client_base.run_responses = [mock_response] @@ -218,7 +243,9 @@ async def test_chat_client_agent_updates_existing_session_id_non_streaming( ) -> None: chat_client_base.run_responses = [ ChatResponse( - messages=[Message(role="assistant", contents=[Content.from_text("test response")])], + messages=[ + Message(role="assistant", contents=[Content.from_text("test response")]) + ], conversation_id="resp_new_123", ) ] @@ -357,7 +384,9 @@ async def test_chat_client_agent_streaming_session_id_set_without_get_final_resp assert session.service_session_id == "resp_123" -async def test_chat_client_agent_update_session_messages(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_update_session_messages( + client: SupportsChatGetResponse, +) -> None: from agent_framework._sessions import InMemoryHistoryProvider agent = Agent(client=client) @@ -368,7 +397,9 @@ async def test_chat_client_agent_update_session_messages(client: SupportsChatGet assert session.service_session_id is None - chat_messages: list[Message] = session.state.get(InMemoryHistoryProvider.DEFAULT_SOURCE_ID, {}).get("messages", []) + chat_messages: list[Message] = session.state.get( + InMemoryHistoryProvider.DEFAULT_SOURCE_ID, {} + ).get("messages", []) assert chat_messages is not None assert len(chat_messages) == 2 @@ -376,7 +407,9 @@ async def test_chat_client_agent_update_session_messages(client: SupportsChatGet assert chat_messages[1].text == "test response" -async def test_chat_client_agent_update_session_conversation_id_missing(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_update_session_conversation_id_missing( + client: SupportsChatGetResponse, +) -> None: agent = Agent(client=client) session = agent.get_session(service_session_id="123") @@ -384,7 +417,9 @@ async def test_chat_client_agent_update_session_conversation_id_missing(client: assert session.service_session_id == "123" -async def test_chat_client_agent_default_author_name(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_default_author_name( + client: SupportsChatGetResponse, +) -> None: # Name is not specified here, so default name should be used agent = Agent(client=client) @@ -393,7 +428,9 @@ async def test_chat_client_agent_default_author_name(client: SupportsChatGetResp assert result.messages[0].author_name == "UnnamedAgent" -async def test_chat_client_agent_author_name_as_agent_name(client: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_author_name_as_agent_name( + client: SupportsChatGetResponse, +) -> None: # Name is specified here, so it should be used as author name agent = Agent(client=client, name="TestAgent") @@ -402,11 +439,17 @@ async def test_chat_client_agent_author_name_as_agent_name(client: SupportsChatG assert result.messages[0].author_name == "TestAgent" -async def test_chat_client_agent_author_name_is_used_from_response(chat_client_base: SupportsChatGetResponse) -> None: +async def test_chat_client_agent_author_name_is_used_from_response( + chat_client_base: SupportsChatGetResponse, +) -> None: chat_client_base.run_responses = [ ChatResponse( messages=[ - Message(role="assistant", contents=[Content.from_text("test response")], author_name="TestAuthor") + Message( + role="assistant", + contents=[Content.from_text("test response")], + author_name="TestAuthor", + ) ] ) ] @@ -428,12 +471,16 @@ def __init__(self, messages: list[Message] | None = None) -> None: self.new_messages: list[Message] = [] self.last_service_session_id: str | None = None - async def before_run(self, *, agent: Any, session: Any, context: Any, state: Any) -> None: + async def before_run( + self, *, agent: Any, session: Any, context: Any, state: Any + ) -> None: self.before_run_called = True if self.context_messages: context.extend_messages(self, self.context_messages) - async def after_run(self, *, agent: Any, session: Any, context: Any, state: Any) -> None: + async def after_run( + self, *, agent: Any, session: Any, context: Any, state: Any + ) -> None: self.after_run_called = True if session: self.last_service_session_id = session.service_session_id @@ -442,9 +489,13 @@ async def after_run(self, *, agent: Any, session: Any, context: Any, state: Any) self.new_messages.extend(context.response.messages) -async def test_chat_agent_context_providers_model_before_run(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_context_providers_model_before_run( + client: SupportsChatGetResponse, +) -> None: """Test that context providers' before_run is called during agent run.""" - mock_provider = MockContextProvider(messages=[Message(role="system", text="Test context instructions")]) + mock_provider = MockContextProvider( + messages=[Message(role="system", text="Test context instructions")] + ) agent = Agent(client=client, context_providers=[mock_provider]) await agent.run("Hello") @@ -452,12 +503,16 @@ async def test_chat_agent_context_providers_model_before_run(client: SupportsCha assert mock_provider.before_run_called -async def test_chat_agent_context_providers_after_run(chat_client_base: SupportsChatGetResponse) -> None: +async def test_chat_agent_context_providers_after_run( + chat_client_base: SupportsChatGetResponse, +) -> None: """Test that context providers' after_run is called during agent run.""" mock_provider = MockContextProvider() chat_client_base.run_responses = [ ChatResponse( - messages=[Message(role="assistant", contents=[Content.from_text("test response")])], + messages=[ + Message(role="assistant", contents=[Content.from_text("test response")]) + ], conversation_id="test-thread-id", ) ] @@ -471,7 +526,9 @@ async def test_chat_agent_context_providers_after_run(chat_client_base: Supports assert mock_provider.last_service_session_id == "test-thread-id" -async def test_chat_agent_context_providers_messages_adding(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_context_providers_messages_adding( + client: SupportsChatGetResponse, +) -> None: """Test that context providers' after_run is called during agent run.""" mock_provider = MockContextProvider() agent = Agent(client=client, context_providers=[mock_provider]) @@ -483,10 +540,18 @@ async def test_chat_agent_context_providers_messages_adding(client: SupportsChat assert len(mock_provider.new_messages) >= 2 -async def test_chat_agent_context_instructions_in_messages(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_context_instructions_in_messages( + client: SupportsChatGetResponse, +) -> None: """Test that AI context instructions are included in messages.""" - mock_provider = MockContextProvider(messages=[Message(role="system", text="Context-specific instructions")]) - agent = Agent(client=client, instructions="Agent instructions", context_providers=[mock_provider]) + mock_provider = MockContextProvider( + messages=[Message(role="system", text="Context-specific instructions")] + ) + agent = Agent( + client=client, + instructions="Agent instructions", + context_providers=[mock_provider], + ) # We need to test the _prepare_session_and_messages method directly session_context, _ = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage] @@ -503,10 +568,16 @@ async def test_chat_agent_context_instructions_in_messages(client: SupportsChatG # instructions system message is added by a client -async def test_chat_agent_no_context_instructions(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_no_context_instructions( + client: SupportsChatGetResponse, +) -> None: """Test behavior when AI context has no instructions.""" mock_provider = MockContextProvider() - agent = Agent(client=client, instructions="Agent instructions", context_providers=[mock_provider]) + agent = Agent( + client=client, + instructions="Agent instructions", + context_providers=[mock_provider], + ) session_context, _ = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage] session=None, input_messages=[Message(role="user", text="Hello")] @@ -519,9 +590,13 @@ async def test_chat_agent_no_context_instructions(client: SupportsChatGetRespons assert messages[0].text == "Hello" -async def test_chat_agent_run_stream_context_providers(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_run_stream_context_providers( + client: SupportsChatGetResponse, +) -> None: """Test that context providers work with run method.""" - mock_provider = MockContextProvider(messages=[Message(role="system", text="Stream context instructions")]) + mock_provider = MockContextProvider( + messages=[Message(role="system", text="Stream context instructions")] + ) agent = Agent(client=client, context_providers=[mock_provider]) # Collect all stream updates and get final response @@ -537,12 +612,16 @@ async def test_chat_agent_run_stream_context_providers(client: SupportsChatGetRe assert mock_provider.after_run_called -async def test_chat_agent_context_providers_with_service_session_id(chat_client_base: SupportsChatGetResponse) -> None: +async def test_chat_agent_context_providers_with_service_session_id( + chat_client_base: SupportsChatGetResponse, +) -> None: """Test context providers with service-managed session.""" mock_provider = MockContextProvider() chat_client_base.run_responses = [ ChatResponse( - messages=[Message(role="assistant", contents=[Content.from_text("test response")])], + messages=[ + Message(role="assistant", contents=[Content.from_text("test response")]) + ], conversation_id="service-thread-123", ) ] @@ -570,7 +649,9 @@ async def test_chat_agent_as_tool_basic(client: SupportsChatGetResponse) -> None assert hasattr(tool, "input_model") -async def test_chat_agent_as_tool_custom_parameters(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_custom_parameters( + client: SupportsChatGetResponse, +) -> None: """Test as_tool with custom parameters.""" agent = Agent(client=client, name="TestAgent", description="Original description") @@ -618,7 +699,9 @@ async def test_chat_agent_as_tool_no_name(client: SupportsChatGetResponse) -> No agent.as_tool() -async def test_chat_agent_as_tool_function_execution(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_function_execution( + client: SupportsChatGetResponse, +) -> None: """Test that the generated FunctionTool can be executed.""" agent = Agent(client=client, name="TestAgent", description="Test agent") @@ -632,7 +715,9 @@ async def test_chat_agent_as_tool_function_execution(client: SupportsChatGetResp assert result == "test response" # From mock chat client -async def test_chat_agent_as_tool_with_stream_callback(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_with_stream_callback( + client: SupportsChatGetResponse, +) -> None: """Test as_tool with stream callback functionality.""" agent = Agent(client=client, name="StreamingAgent") @@ -655,7 +740,9 @@ def stream_callback(update: AgentResponseUpdate) -> None: assert result == expected_text -async def test_chat_agent_as_tool_with_custom_arg_name(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_with_custom_arg_name( + client: SupportsChatGetResponse, +) -> None: """Test as_tool with custom argument name.""" agent = Agent(client=client, name="CustomArgAgent") @@ -666,7 +753,9 @@ async def test_chat_agent_as_tool_with_custom_arg_name(client: SupportsChatGetRe assert result == "test response" -async def test_chat_agent_as_tool_with_async_stream_callback(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_with_async_stream_callback( + client: SupportsChatGetResponse, +) -> None: """Test as_tool with async stream callback functionality.""" agent = Agent(client=client, name="AsyncStreamingAgent") @@ -689,7 +778,9 @@ async def async_stream_callback(update: AgentResponseUpdate) -> None: assert result == expected_text -async def test_chat_agent_as_tool_name_sanitization(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_name_sanitization( + client: SupportsChatGetResponse, +) -> None: """Test as_tool name sanitization.""" test_cases = [ ("Invoice & Billing Agent", "Invoice_Billing_Agent"), @@ -704,10 +795,14 @@ async def test_chat_agent_as_tool_name_sanitization(client: SupportsChatGetRespo for agent_name, expected_tool_name in test_cases: agent = Agent(client=client, name=agent_name, description="Test agent") tool = agent.as_tool() - assert tool.name == expected_tool_name, f"Expected {expected_tool_name}, got {tool.name} for input {agent_name}" + assert tool.name == expected_tool_name, ( + f"Expected {expected_tool_name}, got {tool.name} for input {agent_name}" + ) -async def test_chat_agent_as_tool_propagate_session_true(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_propagate_session_true( + client: SupportsChatGetResponse, +) -> None: """Test that propagate_session=True forwards the parent's session to the sub-agent.""" agent = Agent(client=client, name="SubAgent", description="Sub agent") tool = agent.as_tool(propagate_session=True) @@ -733,7 +828,9 @@ def capturing_run(*args: Any, **kwargs: Any) -> Any: assert captured_session.state["shared_key"] == "shared_value" -async def test_chat_agent_as_tool_propagate_session_false_by_default(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_propagate_session_false_by_default( + client: SupportsChatGetResponse, +) -> None: """Test that propagate_session defaults to False and does not forward the session.""" agent = Agent(client=client, name="SubAgent", description="Sub agent") tool = agent.as_tool() # default: propagate_session=False @@ -755,7 +852,9 @@ def capturing_run(*args: Any, **kwargs: Any) -> Any: assert captured_session is None -async def test_chat_agent_as_tool_propagate_session_shares_state(client: SupportsChatGetResponse) -> None: +async def test_chat_agent_as_tool_propagate_session_shares_state( + client: SupportsChatGetResponse, +) -> None: """Test that shared session allows the sub-agent to read and write parent's state.""" agent = Agent(client=client, name="SubAgent", description="Sub agent") tool = agent.as_tool(propagate_session=True) @@ -824,13 +923,20 @@ async def test_chat_agent_with_local_mcp_tools(client: SupportsChatGetResponse) # Test agent with MCP tools in constructor with contextlib.suppress(Exception): - agent = Agent(client=client, name="TestAgent", description="Test agent", tools=[mock_mcp_tool]) + agent = Agent( + client=client, + name="TestAgent", + description="Test agent", + tools=[mock_mcp_tool], + ) # Test async context manager with MCP tools async with agent: pass -async def test_mcp_tools_not_duplicated_when_passed_as_runtime_tools(chat_client_base: Any) -> None: +async def test_mcp_tools_not_duplicated_when_passed_as_runtime_tools( + chat_client_base: Any, +) -> None: """Test that MCP tool functions from self.mcp_tools are not duplicated when already present in runtime tools.""" captured_options: list[dict[str, Any]] = [] @@ -859,7 +965,9 @@ async def capturing_inner( agent = Agent(client=chat_client_base, name="TestAgent", tools=[mock_mcp_tool]) # Simulate AG-UI turn 2: pass already-expanded MCP functions + a client tool as runtime tools - client_tool = FunctionTool(func=lambda: "client", name="client_tool", description="Client tool") + client_tool = FunctionTool( + func=lambda: "client", name="client_tool", description="Client tool" + ) runtime_tools = [mcp_func_a, mcp_func_b, client_tool] await agent.run("hello", tools=runtime_tools) @@ -882,7 +990,9 @@ async def test_agent_tool_receives_session_in_kwargs(chat_client_base: Any) -> N def echo_session_info(text: str, **kwargs: Any) -> str: # type: ignore[reportUnknownParameterType] session = kwargs.get("session") captured["has_session"] = session is not None - captured["has_state"] = session.state is not None if isinstance(session, AgentSession) else False + captured["has_state"] = ( + session.state is not None if isinstance(session, AgentSession) else False + ) return f"echo: {text}" # Make the base client emit a function call for our tool @@ -891,7 +1001,11 @@ def echo_session_info(text: str, **kwargs: Any) -> str: # type: ignore[reportUn messages=Message( role="assistant", contents=[ - Content.from_function_call(call_id="1", name="echo_session_info", arguments='{"text": "hello"}') + Content.from_function_call( + call_id="1", + name="echo_session_info", + arguments='{"text": "hello"}', + ) ], ) ), @@ -901,14 +1015,20 @@ def echo_session_info(text: str, **kwargs: Any) -> str: # type: ignore[reportUn agent = Agent(client=chat_client_base, tools=[echo_session_info]) session = agent.create_session() - result = await agent.run("hello", session=session, options={"additional_function_arguments": {"session": session}}) + result = await agent.run( + "hello", + session=session, + options={"additional_function_arguments": {"session": session}}, + ) assert result.text == "done" assert captured.get("has_session") is True assert captured.get("has_state") is True -async def test_chat_agent_tool_choice_run_level_overrides_agent_level(chat_client_base: Any, tool_tool: Any) -> None: +async def test_chat_agent_tool_choice_run_level_overrides_agent_level( + chat_client_base: Any, tool_tool: Any +) -> None: """Verify that tool_choice passed to run() overrides agent-level tool_choice.""" captured_options: list[dict[str, Any]] = [] @@ -972,7 +1092,9 @@ async def capturing_inner( assert captured_options[0]["tool_choice"] == "required" -async def test_chat_agent_tool_choice_none_at_run_preserves_agent_level(chat_client_base: Any, tool_tool: Any) -> None: +async def test_chat_agent_tool_choice_none_at_run_preserves_agent_level( + chat_client_base: Any, tool_tool: Any +) -> None: """Verify that tool_choice=None at run() uses agent-level default.""" captured_options: list[ChatOptions] = [] @@ -1262,7 +1384,9 @@ def test_sanitize_agent_name_replaces_invalid_chars(): @pytest.mark.asyncio -async def test_agent_create_session(chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool): +async def test_agent_create_session( + chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool +): """Test that create_session returns a new AgentSession.""" agent = Agent(client=chat_client_base, tools=[tool_tool]) @@ -1283,7 +1407,9 @@ def __init__(self): super().__init__(source_id="test") provider = TestContextProvider() - agent = Agent(client=chat_client_base, tools=[tool_tool], context_providers=[provider]) + agent = Agent( + client=chat_client_base, tools=[tool_tool], context_providers=[provider] + ) session = agent.create_session() @@ -1304,7 +1430,9 @@ async def test_agent_get_session_with_service_session_id( assert session.service_session_id == "test-thread-123" -def test_agent_session_from_dict(chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool): +def test_agent_session_from_dict( + chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool +): """Test AgentSession.from_dict restores a session from serialized state.""" # Create serialized session state serialized_state = { @@ -1339,11 +1467,15 @@ def test_chat_agent_calls_update_agent_name_on_client(): ) assert mock_client._update_agent_name_and_description.call_count == 1 - mock_client._update_agent_name_and_description.assert_called_with("TestAgent", "Test description") + mock_client._update_agent_name_and_description.assert_called_with( + "TestAgent", "Test description" + ) @pytest.mark.asyncio -async def test_chat_agent_context_provider_adds_tools_when_agent_has_none(chat_client_base: SupportsChatGetResponse): +async def test_chat_agent_context_provider_adds_tools_when_agent_has_none( + chat_client_base: SupportsChatGetResponse, +): """Test that context provider tools are used when agent has no default tools.""" @tool @@ -1385,7 +1517,9 @@ def __init__(self): super().__init__(source_id="instruction-context") async def before_run(self, *, agent, session, context, state): - context.extend_instructions("instruction-context", "Context-provided instructions") + context.extend_instructions( + "instruction-context", "Context-provided instructions" + ) provider = InstructionContextProvider() agent = Agent(client=chat_client_base, context_providers=[provider]) @@ -1405,7 +1539,9 @@ async def before_run(self, *, agent, session, context, state): # region STORES_BY_DEFAULT tests -async def test_stores_by_default_skips_inmemory_injection(client: SupportsChatGetResponse) -> None: +async def test_stores_by_default_skips_inmemory_injection( + client: SupportsChatGetResponse, +) -> None: """Client with STORES_BY_DEFAULT=True should not auto-inject InMemoryHistoryProvider.""" from agent_framework._sessions import InMemoryHistoryProvider @@ -1418,10 +1554,14 @@ async def test_stores_by_default_skips_inmemory_injection(client: SupportsChatGe await agent.run("Hello", session=session) # No InMemoryHistoryProvider should have been injected - assert not any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) + assert not any( + isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers + ) -async def test_stores_by_default_false_injects_inmemory(client: SupportsChatGetResponse) -> None: +async def test_stores_by_default_false_injects_inmemory( + client: SupportsChatGetResponse, +) -> None: """Client with STORES_BY_DEFAULT=False (default) should auto-inject InMemoryHistoryProvider.""" from agent_framework._sessions import InMemoryHistoryProvider @@ -1434,7 +1574,9 @@ async def test_stores_by_default_false_injects_inmemory(client: SupportsChatGetR assert any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) -async def test_stores_by_default_with_store_false_injects_inmemory(client: SupportsChatGetResponse) -> None: +async def test_stores_by_default_with_store_false_injects_inmemory( + client: SupportsChatGetResponse, +) -> None: """Client with STORES_BY_DEFAULT=True but store=False should still inject InMemoryHistoryProvider.""" from agent_framework._sessions import InMemoryHistoryProvider @@ -1449,7 +1591,21 @@ async def test_stores_by_default_with_store_false_injects_inmemory(client: Suppo assert any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) -# endregion +async def test_store_true_skips_inmemory_injection( + client: SupportsChatGetResponse, +) -> None: + """Explicitly setting store=True should not auto-inject InMemoryHistoryProvider.""" + from agent_framework._sessions import InMemoryHistoryProvider + + agent = Agent(client=client) + session = agent.create_session() + + await agent.run("Hello", session=session, options={"store": True}) + + # User explicitly enabled server storage, so InMemoryHistoryProvider should not be injected + assert not any( + isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers + ) # endregion diff --git a/python/uv.lock b/python/uv.lock index e82f8e7a3c..4842003720 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -1813,11 +1813,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.0" +version = "3.25.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8b/4c32ecde6bea6486a2a5d05340e695174351ff6b06cf651a74c005f9df00/filelock-3.25.1.tar.gz", hash = "sha256:b9a2e977f794ef94d77cdf7d27129ac648a61f585bff3ca24630c1629f701aa9", size = 40319, upload-time = "2026-03-09T19:38:47.309Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl", hash = "sha256:18972df45473c4aa2c7921b609ee9ca4925910cc3a0fb226c96b92fc224ef7bf", size = 26720, upload-time = "2026-03-09T19:38:45.718Z" }, ] [[package]] @@ -1864,51 +1864,51 @@ wheels = [ [[package]] name = "fonttools" -version = "4.61.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, - { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, - { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, - { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +version = "4.62.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/96/686339e0fda8142b7ebed39af53f4a5694602a729662f42a6209e3be91d0/fonttools-4.62.0.tar.gz", hash = "sha256:0dc477c12b8076b4eb9af2e440421b0433ffa9e1dcb39e0640a6c94665ed1098", size = 3579521, upload-time = "2026-03-09T16:50:06.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:196cafef9aeec5258425bd31a4e9a414b2ee0d1557bca184d7923d3d3bcd90f9", size = 2870816, upload-time = "2026-03-09T16:48:32.39Z" }, + { url = "https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:153afc3012ff8761b1733e8fbe5d98623409774c44ffd88fbcb780e240c11d13", size = 2416127, upload-time = "2026-03-09T16:48:34.627Z" }, + { url = "https://files.pythonhosted.org/packages/5a/71/12cfd8ae0478b7158ffa8850786781f67e73c00fd897ef9d053415c5f88b/fonttools-4.62.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13b663fb197334de84db790353d59da2a7288fd14e9be329f5debc63ec0500a5", size = 5100678, upload-time = "2026-03-09T16:48:36.454Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:591220d5333264b1df0d3285adbdfe2af4f6a45bbf9ca2b485f97c9f577c49ff", size = 5070859, upload-time = "2026-03-09T16:48:38.786Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a0/287ae04cd883a52e7bb1d92dfc4997dcffb54173761c751106845fa9e316/fonttools-4.62.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:579f35c121528a50c96bf6fcb6a393e81e7f896d4326bf40e379f1c971603db9", size = 5076689, upload-time = "2026-03-09T16:48:41.886Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4e/a2377ad26c36fcd3e671a1c316ea5ed83107de1588e2d897a98349363bc7/fonttools-4.62.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:44956b003151d5a289eba6c71fe590d63509267c37e26de1766ba15d9c589582", size = 5202053, upload-time = "2026-03-09T16:48:43.867Z" }, + { url = "https://files.pythonhosted.org/packages/44/2e/ad0472e69b02f83dc88983a9910d122178461606404be5b4838af6d1744a/fonttools-4.62.0-cp311-cp311-win32.whl", hash = "sha256:42c7848fa8836ab92c23b1617c407a905642521ff2d7897fe2bf8381530172f1", size = 2292852, upload-time = "2026-03-09T16:48:46.962Z" }, + { url = "https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl", hash = "sha256:4da779e8f342a32856075ddb193b2a024ad900bc04ecb744014c32409ae871ed", size = 2344367, upload-time = "2026-03-09T16:48:48.818Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9d/7ad1ffc080619f67d0b1e0fa6a0578f0be077404f13fd8e448d1616a94a3/fonttools-4.62.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22bde4dc12a9e09b5ced77f3b5053d96cf10c4976c6ac0dee293418ef289d221", size = 2870004, upload-time = "2026-03-09T16:48:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/4d/8b/ba59069a490f61b737e064c3129453dbd28ee38e81d56af0d04d7e6b4de4/fonttools-4.62.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7199c73b326bad892f1cb53ffdd002128bfd58a89b8f662204fbf1daf8d62e85", size = 2414662, upload-time = "2026-03-09T16:48:53.295Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8c/c52a4310de58deeac7e9ea800892aec09b00bb3eb0c53265b31ec02be115/fonttools-4.62.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d732938633681d6e2324e601b79e93f7f72395ec8681f9cdae5a8c08bc167e72", size = 5032975, upload-time = "2026-03-09T16:48:55.718Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a1/d16318232964d786907b9b3613b8409f74cf0be2da400854509d3a864e43/fonttools-4.62.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31a804c16d76038cc4e3826e07678efb0a02dc4f15396ea8e07088adbfb2578e", size = 4988544, upload-time = "2026-03-09T16:48:57.715Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8d/7e745ca3e65852adc5e52a83dc213fe1b07d61cb5b394970fcd4b1199d1e/fonttools-4.62.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:090e74ac86e68c20150e665ef8e7e0c20cb9f8b395302c9419fa2e4d332c3b51", size = 4971296, upload-time = "2026-03-09T16:48:59.678Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d4/b717a4874175146029ca1517e85474b1af80c9d9a306fc3161e71485eea5/fonttools-4.62.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8f086120e8be9e99ca1288aa5ce519833f93fe0ec6ebad2380c1dee18781f0b5", size = 5122503, upload-time = "2026-03-09T16:49:02.464Z" }, + { url = "https://files.pythonhosted.org/packages/cb/4b/92cfcba4bf8373f51c49c5ae4b512ead6fbda7d61a0e8c35a369d0db40a0/fonttools-4.62.0-cp312-cp312-win32.whl", hash = "sha256:37a73e5e38fd05c637daede6ffed5f3496096be7df6e4a3198d32af038f87527", size = 2281060, upload-time = "2026-03-09T16:49:04.385Z" }, + { url = "https://files.pythonhosted.org/packages/cd/06/cc96468781a4dc8ae2f14f16f32b32f69bde18cb9384aad27ccc7adf76f7/fonttools-4.62.0-cp312-cp312-win_amd64.whl", hash = "sha256:658ab837c878c4d2a652fcbb319547ea41693890e6434cf619e66f79387af3b8", size = 2331193, upload-time = "2026-03-09T16:49:06.598Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:274c8b8a87e439faf565d3bcd3f9f9e31bca7740755776a4a90a4bfeaa722efa", size = 2864929, upload-time = "2026-03-09T16:49:09.331Z" }, + { url = "https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93e27131a5a0ae82aaadcffe309b1bae195f6711689722af026862bede05c07c", size = 2412586, upload-time = "2026-03-09T16:49:11.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ac/8e300dbf7b4d135287c261ffd92ede02d9f48f0d2db14665fbc8b059588a/fonttools-4.62.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83c6524c5b93bad9c2939d88e619fedc62e913c19e673f25d5ab74e7a5d074e5", size = 5013708, upload-time = "2026-03-09T16:49:14.063Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:106aec9226f9498fc5345125ff7200842c01eda273ae038f5049b0916907acee", size = 4964355, upload-time = "2026-03-09T16:49:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/6dc62bcc3c3598c28a3ecb77e69018869c3e109bd83031d4973c059d318b/fonttools-4.62.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15d86b96c79013320f13bc1b15f94789edb376c0a2d22fb6088f33637e8dfcbc", size = 4953472, upload-time = "2026-03-09T16:49:18.494Z" }, + { url = "https://files.pythonhosted.org/packages/82/b3/3af7592d9b254b7b7fec018135f8776bfa0d1ad335476c2791b1334dc5e4/fonttools-4.62.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f16c07e5250d5d71d0f990a59460bc5620c3cc456121f2cfb5b60475699905f", size = 5094701, upload-time = "2026-03-09T16:49:21.67Z" }, + { url = "https://files.pythonhosted.org/packages/31/3d/976645583ab567d3ee75ff87b33aa1330fa2baeeeae5fc46210b4274dd45/fonttools-4.62.0-cp313-cp313-win32.whl", hash = "sha256:d31558890f3fa00d4f937d12708f90c7c142c803c23eaeb395a71f987a77ebe3", size = 2279710, upload-time = "2026-03-09T16:49:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl", hash = "sha256:6826a5aa53fb6def8a66bf423939745f415546c4e92478a7c531b8b6282b6c3b", size = 2330291, upload-time = "2026-03-09T16:49:26.237Z" }, + { url = "https://files.pythonhosted.org/packages/1a/64/61f69298aa6e7c363dcf00dd6371a654676900abe27d1effd1a74b43e5d0/fonttools-4.62.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4fa5a9c716e2f75ef34b5a5c2ca0ee4848d795daa7e6792bf30fd4abf8993449", size = 2864222, upload-time = "2026-03-09T16:49:28.285Z" }, + { url = "https://files.pythonhosted.org/packages/c6/57/6b08756fe4455336b1fe160ab3c11fccc90768ccb6ee03fb0b45851aace4/fonttools-4.62.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:625f5cbeb0b8f4e42343eaeb4bc2786718ddd84760a2f5e55fdd3db049047c00", size = 2410674, upload-time = "2026-03-09T16:49:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/6f/86/db65b63bb1b824b63e602e9be21b18741ddc99bcf5a7850f9181159ae107/fonttools-4.62.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6247e58b96b982709cd569a91a2ba935d406dccf17b6aa615afaed37ac3856aa", size = 4999387, upload-time = "2026-03-09T16:49:32.593Z" }, + { url = "https://files.pythonhosted.org/packages/86/c8/c6669e42d2f4efd60d38a3252cebbb28851f968890efb2b9b15f9d1092b0/fonttools-4.62.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:840632ea9c1eab7b7f01c369e408c0721c287dfd7500ab937398430689852fd1", size = 4912506, upload-time = "2026-03-09T16:49:34.927Z" }, + { url = "https://files.pythonhosted.org/packages/2e/49/0ae552aa098edd0ec548413fbf818f52ceb70535016215094a5ce9bf8f70/fonttools-4.62.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:28a9ea2a7467a816d1bec22658b0cce4443ac60abac3e293bdee78beb74588f3", size = 4951202, upload-time = "2026-03-09T16:49:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/65/ae38fc8a4cea6f162d74cf11f58e9aeef1baa7d0e3d1376dabd336c129e5/fonttools-4.62.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ae611294f768d413949fd12693a8cba0e6332fbc1e07aba60121be35eac68d0", size = 5060758, upload-time = "2026-03-09T16:49:39.464Z" }, + { url = "https://files.pythonhosted.org/packages/db/3d/bb797496f35c60544cd5af71ffa5aad62df14ef7286908d204cb5c5096fe/fonttools-4.62.0-cp314-cp314-win32.whl", hash = "sha256:273acb61f316d07570a80ed5ff0a14a23700eedbec0ad968b949abaa4d3f6bb5", size = 2283496, upload-time = "2026-03-09T16:49:42.448Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9f/91081ffe5881253177c175749cce5841f5ec6e931f5d52f4a817207b7429/fonttools-4.62.0-cp314-cp314-win_amd64.whl", hash = "sha256:a5f974006d14f735c6c878fc4b117ad031dc93638ddcc450ca69f8fd64d5e104", size = 2335426, upload-time = "2026-03-09T16:49:44.228Z" }, + { url = "https://files.pythonhosted.org/packages/f8/65/f47f9b3db1ec156a1f222f1089ba076b2cc9ee1d024a8b0a60c54258517e/fonttools-4.62.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0361a7d41d86937f1f752717c19f719d0fde064d3011038f9f19bdf5fc2f5c95", size = 2947079, upload-time = "2026-03-09T16:49:46.471Z" }, + { url = "https://files.pythonhosted.org/packages/52/73/bc62e5058a0c22cf02b1e0169ef0c3ca6c3247216d719f95bead3c05a991/fonttools-4.62.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4108c12773b3c97aa592311557c405d5b4fc03db2b969ed928fcf68e7b3c887", size = 2448802, upload-time = "2026-03-09T16:49:48.328Z" }, + { url = "https://files.pythonhosted.org/packages/2b/df/bfaa0e845884935355670e6e68f137185ab87295f8bc838db575e4a66064/fonttools-4.62.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b448075f32708e8fb377fe7687f769a5f51a027172c591ba9a58693631b077a8", size = 5137378, upload-time = "2026-03-09T16:49:50.223Z" }, + { url = "https://files.pythonhosted.org/packages/32/32/04f616979a18b48b52e634988b93d847b6346260faf85ecccaf7e2e9057f/fonttools-4.62.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5f1fa8cc9f1a56a3e33ee6b954d6d9235e6b9d11eb7a6c9dfe2c2f829dc24db", size = 4920714, upload-time = "2026-03-09T16:49:53.172Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2e/274e16689c1dfee5c68302cd7c444213cfddd23cf4620374419625037ec6/fonttools-4.62.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f8c8ea812f82db1e884b9cdb663080453e28f0f9a1f5027a5adb59c4cc8d38d1", size = 5016012, upload-time = "2026-03-09T16:49:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0c/b08117270626e7117ac2f89d732fdd4386ec37d2ab3a944462d29e6f89a1/fonttools-4.62.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:03c6068adfdc67c565d217e92386b1cdd951abd4240d65180cec62fa74ba31b2", size = 5042766, upload-time = "2026-03-09T16:49:57.726Z" }, + { url = "https://files.pythonhosted.org/packages/11/83/a48b73e54efa272ee65315a6331b30a9b3a98733310bc11402606809c50e/fonttools-4.62.0-cp314-cp314t-win32.whl", hash = "sha256:d28d5baacb0017d384df14722a63abe6e0230d8ce642b1615a27d78ffe3bc983", size = 2347785, upload-time = "2026-03-09T16:49:59.698Z" }, + { url = "https://files.pythonhosted.org/packages/f8/27/c67eab6dc3525bdc39586511b1b3d7161e972dacc0f17476dbaf932e708b/fonttools-4.62.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f9e20c4618f1e04190c802acae6dc337cb6db9fa61e492fd97cd5c5a9ff6d07", size = 2413914, upload-time = "2026-03-09T16:50:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/9c/57/c2487c281dde03abb2dec244fd67059b8d118bd30a653cbf69e94084cb23/fonttools-4.62.0-py3-none-any.whl", hash = "sha256:75064f19a10c50c74b336aa5ebe7b1f89fd0fb5255807bfd4b0c6317098f4af3", size = 1152427, upload-time = "2026-03-09T16:50:04.074Z" }, ] [[package]] @@ -2650,92 +2650,108 @@ wheels = [ [[package]] name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] [[package]] @@ -4075,7 +4091,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.9.7" +version = "7.9.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -4085,9 +4101,9 @@ dependencies = [ { name = "six", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/08/e5064ae25749367f38f6d204ce876a045ecf4fd01ed0e66477364925416c/posthog-7.9.7.tar.gz", hash = "sha256:35dcaf4acc37b386b5ebcd6037cc80821e88d359627c0f61537c667c52359483", size = 175634, upload-time = "2026-03-05T22:09:51.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/f5/490fbe0cd357bf5efaa026200d2a29aaa5e39cd8272cfe0e2d449f46f2db/posthog-7.9.8.tar.gz", hash = "sha256:52b1fa5f3d3faf2ee2fb7f5eb375332905887f7c1e386ef45103448413bd3e57", size = 176688, upload-time = "2026-03-09T14:34:07.822Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/8a/3e4dd145d7d5aaad856d522c61475c51ee80b512b6446bfb3966b2dedf66/posthog-7.9.7-py3-none-any.whl", hash = "sha256:204e47c27dcc230d0bc9b323709c36f98f86e79fa8190caea3b1fbc3c999b1a0", size = 201316, upload-time = "2026-03-05T22:09:50.18Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/8b3de1650e0c39223c7f9b7c0f4961f7d39bfa690fa800a9521565381ecb/posthog-7.9.8-py3-none-any.whl", hash = "sha256:2735bcc3232e22c88034454e820c1739f4b29e606d55f31e56b52202650e4330", size = 202361, upload-time = "2026-03-09T14:34:06.031Z" }, ] [[package]] @@ -4105,26 +4121,26 @@ wheels = [ [[package]] name = "prek" -version = "0.3.4" +version = "0.3.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/51/2324eaad93a4b144853ca1c56da76f357d3a70c7b4fd6659e972d7bb8660/prek-0.3.4.tar.gz", hash = "sha256:56a74d02d8b7dfe3c774ecfcd8c1b4e5f1e1b84369043a8003e8e3a779fce72d", size = 356633, upload-time = "2026-02-28T03:47:13.452Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/d6/277e002e56eeab3a9d48f1ca4cc067d249d6326fc1783b770d70ad5ae2be/prek-0.3.5.tar.gz", hash = "sha256:ca40b6685a4192256bc807f32237af94bf9b8799c0d708b98735738250685642", size = 374806, upload-time = "2026-03-09T10:35:18.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/20/1a964cb72582307c2f1dc7f583caab90f42810ad41551e5220592406a4c3/prek-0.3.4-py3-none-linux_armv6l.whl", hash = "sha256:c35192d6e23fe7406bd2f333d1c7dab1a4b34ab9289789f453170f33550aa74d", size = 4641915, upload-time = "2026-02-28T03:47:03.772Z" }, - { url = "https://files.pythonhosted.org/packages/c5/cb/4a21f37102bac37e415b61818344aa85de8d29a581253afa7db8c08d5a33/prek-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f784d78de72a8bbe58a5fe7bde787c364ae88f0aff5222c5c5c7287876c510a", size = 4649166, upload-time = "2026-02-28T03:47:06.164Z" }, - { url = "https://files.pythonhosted.org/packages/85/9c/a7c0d117a098d57931428bdb60fcb796e0ebc0478c59288017a2e22eca96/prek-0.3.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50a43f522625e8c968e8c9992accf9e29017abad6c782d6d176b73145ad680b7", size = 4274422, upload-time = "2026-02-28T03:46:59.356Z" }, - { url = "https://files.pythonhosted.org/packages/59/84/81d06df1724d09266df97599a02543d82fde7dfaefd192f09d9b2ccb092f/prek-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4bbb1d3912a88935f35c6ba4466b4242732e3e3a8c608623c708e83cea85de00", size = 4629873, upload-time = "2026-02-28T03:46:56.419Z" }, - { url = "https://files.pythonhosted.org/packages/09/cd/bb0aefa25cfacd8dbced75b9a9d9945707707867fa5635fb69ae1bbc2d88/prek-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca4d4134db8f6e8de3c418317becdf428957e3cab271807f475318105fd46d04", size = 4552507, upload-time = "2026-02-28T03:47:05.004Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c0/578a7af4861afb64ec81c03bfdcc1bb3341bb61f2fff8a094ecf13987a56/prek-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fb6395f6eb76133bb1e11fc718db8144522466cdc2e541d05e7813d1bbcae7d", size = 4865929, upload-time = "2026-02-28T03:47:09.231Z" }, - { url = "https://files.pythonhosted.org/packages/fc/48/f169406590028f7698ef2e1ff5bffd92ca05e017636c1163a2f5ef0f8275/prek-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae17813239ddcb4ae7b38418de4d49afff740f48f8e0556029c96f58e350412", size = 5390286, upload-time = "2026-02-28T03:47:10.796Z" }, - { url = "https://files.pythonhosted.org/packages/05/c5/98a73fec052059c3ae06ce105bef67caca42334c56d84e9ef75df72ba152/prek-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a621a690d9c127afc3d21c275030d364d1fbef3296c095068d3ae80a59546e", size = 4891028, upload-time = "2026-02-28T03:47:07.916Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b4/029966e35e59b59c142be7e1d2208ad261709ac1a66aa4a3ce33c5b9f91f/prek-0.3.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d978c31bc3b1f0b3d58895b7c6ac26f077e0ea846da54f46aeee4c7088b1b105", size = 4633986, upload-time = "2026-02-28T03:47:14.351Z" }, - { url = "https://files.pythonhosted.org/packages/1d/27/d122802555745b6940c99fcb41496001c192ddcdf56ec947ec10a0298e05/prek-0.3.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8e089a030f0a023c22a4bb2ec4ff3fcc153585d701cff67acbfca2f37e173ae", size = 4680722, upload-time = "2026-02-28T03:47:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/34/40/92318c96b3a67b4e62ed82741016ede34d97ea9579d3cc1332b167632222/prek-0.3.4-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:8060c72b764f0b88112616763da9dd3a7c293e010f8520b74079893096160a2f", size = 4535623, upload-time = "2026-02-28T03:46:52.221Z" }, - { url = "https://files.pythonhosted.org/packages/df/f5/6b383d94e722637da4926b4f609d36fe432827bb6f035ad46ee02bde66b6/prek-0.3.4-py3-none-musllinux_1_1_i686.whl", hash = "sha256:65b23268456b5a763278d4e1ec532f2df33918f13ded85869a1ddff761eb9697", size = 4729879, upload-time = "2026-02-28T03:46:57.886Z" }, - { url = "https://files.pythonhosted.org/packages/79/f8/fdc705b807d813fd713ffa4f67f96741542ed1dafbb221206078c06f3df4/prek-0.3.4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:3975c61139c7b3200e38dc3955e050b0f2615701d3deb9715696a902e850509e", size = 5001569, upload-time = "2026-02-28T03:47:00.892Z" }, - { url = "https://files.pythonhosted.org/packages/84/92/b007a41f58e8192a1e611a21b396ad870d51d7873b7af12068ebae7fc15f/prek-0.3.4-py3-none-win32.whl", hash = "sha256:37449ae82f4dc08b72e542401e3d7318f05d1163e87c31ab260a40f425d6516e", size = 4297057, upload-time = "2026-02-28T03:47:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dc/bcb02de9b11461e8e0c7d3c8fdf8cfa15ac6efe73472a4375549ba5defd2/prek-0.3.4-py3-none-win_amd64.whl", hash = "sha256:60e9aa86ca65de963510ae28c5d94b9d7a97bcbaa6e4cdb5bf5083ed4c45dc71", size = 4655174, upload-time = "2026-02-28T03:46:53.749Z" }, - { url = "https://files.pythonhosted.org/packages/0b/86/98f5598569f4cd3de7161e266fab6a8981e65555f79d4704810c1502ad0a/prek-0.3.4-py3-none-win_arm64.whl", hash = "sha256:486bdae8f4512d3b4f6eb61b83e5b7595da2adca385af4b2b7823c0ab38d1827", size = 4367817, upload-time = "2026-02-28T03:46:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a9/16dd8d3a50362ebccffe58518af1f1f571c96f0695d7fcd8bbd386585f58/prek-0.3.5-py3-none-linux_armv6l.whl", hash = "sha256:44b3e12791805804f286d103682b42a84e0f98a2687faa37045e9d3375d3d73d", size = 5105604, upload-time = "2026-03-09T10:35:00.332Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/bc6036f5bf03860cda66ab040b32737e54802b71a81ec381839deb25df9e/prek-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3cb451cc51ac068974557491beb4c7d2d41dfde29ed559c1694c8ce23bf53e8", size = 5506155, upload-time = "2026-03-09T10:35:17.64Z" }, + { url = "https://files.pythonhosted.org/packages/02/d9/a3745c2a10509c63b6a118ada766614dd705efefd08f275804d5c807aa4a/prek-0.3.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ad8f5f0d8da53dc94d00b76979af312b3dacccc9dcbc6417756c5dca3633c052", size = 5100383, upload-time = "2026-03-09T10:35:13.302Z" }, + { url = "https://files.pythonhosted.org/packages/43/8e/de965fc515d39309a332789cd3778161f7bc80cde15070bedf17f9f8cb93/prek-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4511e15d34072851ac88e4b2006868fbe13655059ad941d7a0ff9ee17138fd9f", size = 5334913, upload-time = "2026-03-09T10:35:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/44f07e8940256059cfd82520e3cbe0764ab06ddb4aa43148465db00b39ad/prek-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc0b63b8337e2046f51267facaac63ba755bc14aad53991840a5eccba3e5c28", size = 5033825, upload-time = "2026-03-09T10:35:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/94/85/3ff0f96881ff2360c212d310ff23c3cf5a15b223d34fcfa8cdcef203be69/prek-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5fc0d78c3896a674aeb8247a83bbda7efec85274dbdfbc978ceff8d37e4ed20", size = 5438586, upload-time = "2026-03-09T10:34:58.779Z" }, + { url = "https://files.pythonhosted.org/packages/79/a5/c6d08d31293400fcb5d427f8e7e6bacfc959988e868ad3a9d97b4d87c4b7/prek-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64cad21cb9072d985179495b77b312f6b81e7b45357d0c68dc1de66e0408eabc", size = 6359714, upload-time = "2026-03-09T10:34:57.454Z" }, + { url = "https://files.pythonhosted.org/packages/ba/18/321dcff9ece8065d42c8c1c7a53a23b45d2b4330aa70993be75dc5f2822f/prek-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45ee84199bb48e013bdfde0c84352c17a44cc42d5792681b86d94e9474aab6f8", size = 5717632, upload-time = "2026-03-09T10:35:08.634Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7f/1288226aa381d0cea403157f4e6b64b356e1a745f2441c31dd9d8a1d63da/prek-0.3.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f43275e5d564e18e52133129ebeb5cb071af7ce4a547766c7f025aa0955dfbb6", size = 5339040, upload-time = "2026-03-09T10:35:03.665Z" }, + { url = "https://files.pythonhosted.org/packages/22/94/cfec83df9c2b8e7ed1608087bcf9538a6a77b4c2e7365123e9e0a3162cd1/prek-0.3.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:abcee520d31522bcbad9311f21326b447694cd5edba33618c25fd023fc9865ec", size = 5162586, upload-time = "2026-03-09T10:35:11.564Z" }, + { url = "https://files.pythonhosted.org/packages/13/b7/741d62132f37a5f7cc0fad1168bd31f20dea9628f482f077f569547e0436/prek-0.3.5-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:499c56a94a155790c75a973d351a33f8065579d9094c93f6d451ada5d1e469be", size = 5002933, upload-time = "2026-03-09T10:35:16.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/83/630a5671df6550fcfa67c54955e8a8174eb9b4d97ac38fb05a362029245b/prek-0.3.5-py3-none-musllinux_1_1_i686.whl", hash = "sha256:de1065b59f194624adc9dea269d4ff6b50e98a1b5bb662374a9adaa496b3c1eb", size = 5304934, upload-time = "2026-03-09T10:35:09.975Z" }, + { url = "https://files.pythonhosted.org/packages/de/79/67a7afd0c0b6c436630b7dba6e586a42d21d5d6e5778fbd9eba7bbd3dd26/prek-0.3.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a1c4869e45ee341735d07179da3a79fa2afb5959cef8b3c8a71906eb52dc6933", size = 5829914, upload-time = "2026-03-09T10:35:05.39Z" }, + { url = "https://files.pythonhosted.org/packages/37/47/e2fe13b33e7b5fdd9dd1a312f5440208bfe1be6183e54c5c99c10f27d848/prek-0.3.5-py3-none-win32.whl", hash = "sha256:70b2152ecedc58f3f4f69adc884617b0cf44259f7414c44d6268ea6f107672eb", size = 4836910, upload-time = "2026-03-09T10:35:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ab/dc2a139fd4896d11f39631479ed385e86307af7f54059ebe9414bb0d00c6/prek-0.3.5-py3-none-win_amd64.whl", hash = "sha256:01d031b684f7e1546225393af1268d9b4451a44ef6cb9be4101c85c7862e08db", size = 5234234, upload-time = "2026-03-09T10:35:20.193Z" }, + { url = "https://files.pythonhosted.org/packages/ed/38/f7256b4b7581444f658e909c3b566f51bfabe56c03e80d107a6932d62040/prek-0.3.5-py3-none-win_arm64.whl", hash = "sha256:aa774168e3d868039ff79422bdef2df8d5a016ed804a9914607dcdd3d41da053", size = 5083330, upload-time = "2026-03-09T10:34:55.469Z" }, ] [[package]] @@ -5378,11 +5394,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.0" +version = "82.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] [[package]] From fa5496a21aa037722193f2791df490a8bc601b29 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Mon, 9 Mar 2026 15:48:20 -0700 Subject: [PATCH 2/5] Address comments --- .../packages/core/agent_framework/_agents.py | 230 +++++++++++++----- 1 file changed, 174 insertions(+), 56 deletions(-) diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index b25f6c7013..520292e2d7 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -33,7 +33,13 @@ from ._mcp import LOG_LEVEL_MAPPING, MCPTool from ._middleware import AgentMiddlewareLayer, MiddlewareTypes from ._serialization import SerializationMixin -from ._sessions import AgentSession, BaseContextProvider, BaseHistoryProvider, InMemoryHistoryProvider, SessionContext +from ._sessions import ( + AgentSession, + BaseContextProvider, + BaseHistoryProvider, + InMemoryHistoryProvider, + SessionContext, +) from ._tools import ( FunctionInvocationLayer, FunctionTool, @@ -271,7 +277,10 @@ def run( stream: bool = False, session: AgentSession | None = None, **kwargs: Any, - ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: + ) -> ( + Awaitable[AgentResponse[Any]] + | ResponseStream[AgentResponseUpdate, AgentResponse[Any]] + ): """Get a response from the agent. This method can return either a complete response or stream partial updates @@ -385,16 +394,22 @@ def __init__( self.id = id self.name = name self.description = description - self.context_providers: list[BaseContextProvider] = list(context_providers or []) + self.context_providers: list[BaseContextProvider] = list( + context_providers or [] + ) self.middleware: list[MiddlewareTypes] | None = ( cast(list[MiddlewareTypes], middleware) if middleware is not None else None ) # Merge kwargs into additional_properties - self.additional_properties: dict[str, Any] = cast(dict[str, Any], additional_properties or {}) + self.additional_properties: dict[str, Any] = cast( + dict[str, Any], additional_properties or {} + ) self.additional_properties.update(kwargs) - def create_session(self, *, session_id: str | None = None, **kwargs: Any) -> AgentSession: + def create_session( + self, *, session_id: str | None = None, **kwargs: Any + ) -> AgentSession: """Create a new lightweight session. Keyword Args: @@ -406,7 +421,9 @@ def create_session(self, *, session_id: str | None = None, **kwargs: Any) -> Age """ return AgentSession(session_id=session_id) - def get_session(self, *, service_session_id: str, session_id: str | None = None, **kwargs: Any) -> AgentSession: + def get_session( + self, *, service_session_id: str, session_id: str | None = None, **kwargs: Any + ) -> AgentSession: """Get or create a session for a service-managed session ID. Args: @@ -419,7 +436,9 @@ def get_session(self, *, service_session_id: str, session_id: str | None = None, Returns: A new AgentSession instance with service_session_id set. """ - return AgentSession(session_id=session_id, service_session_id=service_session_id) + return AgentSession( + session_id=session_id, service_session_id=service_session_id + ) async def _run_after_providers( self, @@ -439,7 +458,9 @@ async def _run_after_providers( for provider in reversed(self.context_providers): if provider_session is None: - raise RuntimeError("Provider session must be available when context providers are configured.") + raise RuntimeError( + "Provider session must be available when context providers are configured." + ) await provider.after_run( agent=self, # type: ignore[arg-type] session=provider_session, @@ -501,11 +522,15 @@ def as_tool( """ # Verify that self implements SupportsAgentRun if not isinstance(self, SupportsAgentRun): - raise TypeError(f"Agent {self.__class__.__name__} must implement SupportsAgentRun to be used as a tool") + raise TypeError( + f"Agent {self.__class__.__name__} must implement SupportsAgentRun to be used as a tool" + ) tool_name = name or _sanitize_agent_name(self.name) if tool_name is None: - raise ValueError("Agent tool name cannot be None. Either provide a name parameter or set the agent's name.") + raise ValueError( + "Agent tool name cannot be None. Either provide a name parameter or set the agent's name." + ) tool_description = description or self.description or "" argument_description = arg_description or f"Task for {tool_name}" @@ -515,7 +540,9 @@ def as_tool( input_model = create_model(model_name, **{arg_name: (str, field_info)}) # type: ignore[call-overload] # Check if callback is async once, outside the wrapper - is_async_callback = stream_callback is not None and inspect.iscoroutinefunction(stream_callback) + is_async_callback = stream_callback is not None and inspect.iscoroutinefunction( + stream_callback + ) async def agent_wrapper(**kwargs: Any) -> str: """Wrapper function that calls the agent.""" @@ -527,16 +554,27 @@ async def agent_wrapper(**kwargs: Any) -> str: # Forward runtime context kwargs, excluding framework-internal keys. forwarded_kwargs = { - k: v for k, v in kwargs.items() if k not in (arg_name, "conversation_id", "options", "session") + k: v + for k, v in kwargs.items() + if k not in (arg_name, "conversation_id", "options", "session") } if stream_callback is None: # Use non-streaming mode - return (await self.run(input_text, stream=False, session=parent_session, **forwarded_kwargs)).text + return ( + await self.run( + input_text, + stream=False, + session=parent_session, + **forwarded_kwargs, + ) + ).text # Use streaming mode - accumulate updates and create final response response_updates: list[AgentResponseUpdate] = [] - async for update in self.run(input_text, stream=True, session=parent_session, **forwarded_kwargs): + async for update in self.run( + input_text, stream=True, session=parent_session, **forwarded_kwargs + ): response_updates.append(update) if is_async_callback: await stream_callback(update) # type: ignore[misc] @@ -649,7 +687,10 @@ def __init__( id: str | None = None, name: str | None = None, description: str | None = None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None = None, default_options: OptionsCoT | None = None, context_providers: Sequence[BaseContextProvider] | None = None, **kwargs: Any, @@ -679,7 +720,9 @@ def __init__( """ opts = dict(default_options) if default_options else {} - if not isinstance(client, FunctionInvocationLayer) and isinstance(client, BaseChatClient): + if not isinstance(client, FunctionInvocationLayer) and isinstance( + client, BaseChatClient + ): logger.warning( "The provided chat client does not support function invoking, this might limit agent capabilities." ) @@ -697,17 +740,24 @@ def __init__( tools_ = tools if tools is not None else opts.pop("tools", None) # Handle instructions - named parameter takes precedence over options - instructions_ = instructions if instructions is not None else opts.pop("instructions", None) + instructions_ = ( + instructions if instructions is not None else opts.pop("instructions", None) + ) # We ignore the MCP Servers here and store them separately, # we add their functions to the tools list at runtime normalized_tools = normalize_tools(tools_) - self.mcp_tools: list[MCPTool] = [tool for tool in normalized_tools if isinstance(tool, MCPTool)] - agent_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)] + self.mcp_tools: list[MCPTool] = [ + tool for tool in normalized_tools if isinstance(tool, MCPTool) + ] + agent_tools = [ + tool for tool in normalized_tools if not isinstance(tool, MCPTool) + ] # Build chat options dict self.default_options: dict[str, Any] = { - "model_id": opts.pop("model_id", None) or (getattr(self.client, "model_id", None)), + "model_id": opts.pop("model_id", None) + or (getattr(self.client, "model_id", None)), "allow_multiple_tool_calls": opts.pop("allow_multiple_tool_calls", None), "conversation_id": opts.pop("conversation_id", None), "frequency_penalty": opts.pop("frequency_penalty", None), @@ -728,7 +778,9 @@ def __init__( **opts, # Remaining options are provider-specific } # Remove None values from chat_options - self.default_options = {k: v for k, v in self.default_options.items() if v is not None} + self.default_options = { + k: v for k, v in self.default_options.items() if v is not None + } self._async_exit_stack = AsyncExitStack() self._update_agent_name_and_description() @@ -784,7 +836,10 @@ def run( *, stream: Literal[False] = ..., session: AgentSession | None = None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None = None, options: ChatOptions[ResponseModelBoundT], **kwargs: Any, ) -> Awaitable[AgentResponse[ResponseModelBoundT]]: ... @@ -796,7 +851,10 @@ def run( *, stream: Literal[False] = ..., session: AgentSession | None = None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None = None, options: OptionsCoT | ChatOptions[None] | None = None, **kwargs: Any, ) -> Awaitable[AgentResponse[Any]]: ... @@ -808,7 +866,10 @@ def run( *, stream: Literal[True], session: AgentSession | None = None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None = None, options: OptionsCoT | ChatOptions[Any] | None = None, **kwargs: Any, ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... @@ -819,10 +880,16 @@ def run( *, stream: bool = False, session: AgentSession | None = None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None = None, options: OptionsCoT | ChatOptions[Any] | None = None, **kwargs: Any, - ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: + ) -> ( + Awaitable[AgentResponse[Any]] + | ResponseStream[AgentResponseUpdate, AgentResponse[Any]] + ): """Run the agent with the given messages and options. Note: @@ -873,7 +940,9 @@ async def _run_non_streaming() -> AgentResponse[Any]: ) if not response: - raise AgentInvalidResponseException("Chat client did not return a response.") + raise AgentInvalidResponseException( + "Chat client did not return a response." + ) await self._finalize_response( response=response, @@ -914,7 +983,9 @@ async def _post_hook(response: AgentResponse) -> None: # Update thread with conversation_id derived from streaming raw updates. # Using response_id here can break function-call continuation for APIs # where response IDs are not valid conversation handles. - conversation_id = self._extract_conversation_id_from_streaming_response(response) + conversation_id = self._extract_conversation_id_from_streaming_response( + response + ) # Ensure author names are set for all messages for message in response.messages: if message.author_name is None: @@ -933,9 +1004,13 @@ async def _post_hook(response: AgentResponse) -> None: messages=response.messages, response_id=response.response_id, ) - await self._run_after_providers(session=ctx["session"], context=session_context) + await self._run_after_providers( + session=ctx["session"], context=session_context + ) - async def _get_stream() -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: + async def _get_stream() -> ResponseStream[ + ChatResponseUpdate, ChatResponse[Any] + ]: ctx_holder["ctx"] = await self._prepare_run_context( messages=messages, session=session, @@ -951,7 +1026,9 @@ async def _get_stream() -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]] **ctx["filtered_kwargs"], ) - def _propagate_conversation_id(update: AgentResponseUpdate) -> AgentResponseUpdate: + def _propagate_conversation_id( + update: AgentResponseUpdate, + ) -> AgentResponseUpdate: """Eagerly propagate conversation_id to session as updates arrive. This ensures session.service_session_id is set even when the user @@ -961,7 +1038,11 @@ def _propagate_conversation_id(update: AgentResponseUpdate) -> AgentResponseUpda return update raw = update.raw_representation conv_id = getattr(raw, "conversation_id", None) if raw else None - if isinstance(conv_id, str) and conv_id and session.service_session_id != conv_id: + if ( + isinstance(conv_id, str) + and conv_id + and session.service_session_id != conv_id + ): session.service_session_id = conv_id return update @@ -975,8 +1056,7 @@ def _finalizer(updates: Sequence[AgentResponseUpdate]) -> AgentResponse[Any]: return self._finalize_response_updates(updates, response_format=rf) return ( - ResponseStream # type: ignore[reportUnknownMemberType] - .from_awaitable(_get_stream()) + ResponseStream.from_awaitable(_get_stream()) # type: ignore[reportUnknownMemberType] .map( transform=partial( map_chat_to_agent_update, @@ -995,14 +1075,18 @@ def _finalize_response_updates( response_format: Any | None = None, ) -> AgentResponse[Any]: """Finalize response updates into a single AgentResponse.""" - output_format_type = response_format if isinstance(response_format, type) else None + output_format_type = ( + response_format if isinstance(response_format, type) else None + ) return AgentResponse.from_updates( # pyright: ignore[reportUnknownVariableType] updates, output_format_type=output_format_type, ) @staticmethod - def _extract_conversation_id_from_streaming_response(response: AgentResponse[Any]) -> str | None: + def _extract_conversation_id_from_streaming_response( + response: AgentResponse[Any], + ) -> str | None: """Extract conversation_id from streaming raw updates, if present.""" raw = response.raw_representation if raw is None: @@ -1028,13 +1112,15 @@ async def _prepare_run_context( *, messages: AgentRunInputs | None, session: AgentSession | None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None, options: Mapping[str, Any] | None, kwargs: dict[str, Any], ) -> _RunContext: - opts = dict(options) if options else {} # Merge default options with runtime options (runtime takes precedence) - opts = {**self.default_options, **opts} + opts = _merge_options(self.default_options, dict(options) if options else {}) # Get tools from options or named parameter (named param takes precedence) tools_ = tools if tools is not None else opts.pop("tools", None) @@ -1048,9 +1134,8 @@ async def _prepare_run_context( and not self.context_providers and not session.service_session_id and not opts.get("conversation_id") - # `store` takes precedence over client-level storage indicators. If `store` is - # explicitly set to False, we won't inject the InMemoryHistoryProvider, even if - # the client indicates that stores are used by default. + # `store` takes precedence over client-level storage indicators. An explicit + # `store=False` forces local (in-memory) history injection, even if the client and not opts.get("store", getattr(self.client, "STORES_BY_DEFAULT", False)) ): self.context_providers.append(InMemoryHistoryProvider()) @@ -1079,15 +1164,21 @@ async def _prepare_run_context( else: final_tools.append(tool) # type: ignore - existing_names = {name for t in final_tools if (name := _get_tool_name(t)) is not None} + existing_names = { + name for t in final_tools if (name := _get_tool_name(t)) is not None + } for mcp_server in self.mcp_tools: if not mcp_server.is_connected: await self._async_exit_stack.enter_async_context(mcp_server) - final_tools.extend(f for f in mcp_server.functions if f.name not in existing_names) + final_tools.extend( + f for f in mcp_server.functions if f.name not in existing_names + ) # Merge runtime kwargs into additional_function_arguments so they're available # in function middleware context and tool invocation. - existing_additional_args: dict[str, Any] = opts.pop("additional_function_arguments", None) or {} + existing_additional_args: dict[str, Any] = ( + opts.pop("additional_function_arguments", None) or {} + ) additional_function_arguments = {**kwargs, **existing_additional_args} # Include session so as_tool() wrappers with propagate_session=True can access it. if active_session is not None: @@ -1122,13 +1213,17 @@ async def _prepare_run_context( co = _merge_options(chat_options, run_opts) # Build session_messages from session context: context messages + input messages - session_messages: list[Message] = session_context.get_messages(include_input=True) + session_messages: list[Message] = session_context.get_messages( + include_input=True + ) # Ensure session is forwarded in kwargs for tool invocation finalize_kwargs = dict(kwargs) finalize_kwargs["session"] = active_session # Filter chat_options from kwargs to prevent duplicate keyword argument - filtered_kwargs = {k: v for k, v in finalize_kwargs.items() if k != "chat_options"} + filtered_kwargs = { + k: v for k, v in finalize_kwargs.items() if k != "chat_options" + } return { "session": active_session, @@ -1164,7 +1259,11 @@ async def _finalize_response( # Propagate conversation_id back to session (e.g. thread ID from Assistants API). # For Responses-style APIs this can rotate every turn (response_id-based continuation), # so refresh when a newer value is returned. - if session and response.conversation_id and session.service_session_id != response.conversation_id: + if ( + session + and response.conversation_id + and session.service_session_id != response.conversation_id + ): session.service_session_id = response.conversation_id # Set the response on the context for after_run providers @@ -1215,7 +1314,9 @@ async def _prepare_session_and_messages( session_context = SessionContext( session_id=provider_session.session_id if provider_session else None, - service_session_id=provider_session.service_session_id if provider_session else None, + service_session_id=provider_session.service_session_id + if provider_session + else None, input_messages=input_messages or [], options=options or {}, ) @@ -1225,7 +1326,9 @@ async def _prepare_session_and_messages( if isinstance(provider, BaseHistoryProvider) and not provider.load_messages: continue if provider_session is None: - raise RuntimeError("Provider session must be available when context providers are configured.") + raise RuntimeError( + "Provider session must be available when context providers are configured." + ) await provider.before_run( agent=self, # type: ignore[arg-type] session=provider_session, @@ -1244,7 +1347,9 @@ async def _prepare_session_and_messages( if session_context.instructions: combined_instructions = "\n".join(session_context.instructions) if "instructions" in chat_options: - chat_options["instructions"] = f"{chat_options['instructions']}\n{combined_instructions}" + chat_options["instructions"] = ( + f"{chat_options['instructions']}\n{combined_instructions}" + ) else: chat_options["instructions"] = combined_instructions @@ -1256,7 +1361,8 @@ def as_mcp_server( server_name: str = "Agent", version: str | None = None, instructions: str | None = None, - lifespan: Callable[[Server[Any]], AbstractAsyncContextManager[Any]] | None = None, + lifespan: Callable[[Server[Any]], AbstractAsyncContextManager[Any]] + | None = None, **kwargs: Any, ) -> Server[Any]: """Create an MCP server from an agent instance. @@ -1294,7 +1400,9 @@ async def _log(level: types.LoggingLevel, data: Any) -> None: logger.log(LOG_LEVEL_MAPPING[level], data) if server and server.request_context and server.request_context.session: try: - await server.request_context.session.send_log_message(level=level, data=data) + await server.request_context.session.send_log_message( + level=level, data=data + ) except Exception as e: logger.error("Failed to send log message to server: %s", e) @@ -1315,7 +1423,12 @@ async def _list_tools() -> list[types.Tool]: # type: ignore @server.call_tool() # type: ignore async def _call_tool( # type: ignore name: str, arguments: dict[str, Any] - ) -> Sequence[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource]: + ) -> Sequence[ + types.TextContent + | types.ImageContent + | types.AudioContent + | types.EmbeddedResource + ]: """Call a tool in the agent.""" await _log(level="debug", data=f"Calling tool with args: {arguments}") @@ -1330,7 +1443,9 @@ async def _call_tool( # type: ignore # Create an instance of the input model with the arguments try: args_instance: BaseModel | dict[str, Any] = ( - agent_tool.input_model(**arguments) if agent_tool.input_model is not None else arguments + agent_tool.input_model(**arguments) + if agent_tool.input_model is not None + else arguments ) result = await agent_tool.invoke(arguments=args_instance) except Exception as e: @@ -1388,7 +1503,10 @@ def __init__( id: str | None = None, name: str | None = None, description: str | None = None, - tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, + tools: ToolTypes + | Callable[..., Any] + | Sequence[ToolTypes | Callable[..., Any]] + | None = None, default_options: OptionsCoT | None = None, context_providers: Sequence[BaseContextProvider] | None = None, middleware: Sequence[MiddlewareTypes] | None = None, From fbcc6830ab3c3157066b404e5fc2f2296b01802c Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Mon, 9 Mar 2026 16:25:49 -0700 Subject: [PATCH 3/5] Fix unit tests --- python/packages/core/agent_framework/_agents.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index 520292e2d7..03059163ee 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -1123,7 +1123,10 @@ async def _prepare_run_context( opts = _merge_options(self.default_options, dict(options) if options else {}) # Get tools from options or named parameter (named param takes precedence) - tools_ = tools if tools is not None else opts.pop("tools", None) + # Remove tools from options to prevent overwrite merged tools list later when + # we merge opts into chat_options + options_tools = opts.pop("tools", None) + tools_ = tools if tools is not None else options_tools input_messages = normalize_messages(messages) @@ -1136,6 +1139,7 @@ async def _prepare_run_context( and not opts.get("conversation_id") # `store` takes precedence over client-level storage indicators. An explicit # `store=False` forces local (in-memory) history injection, even if the client + # is configured to use service-side storage by default. and not opts.get("store", getattr(self.client, "STORES_BY_DEFAULT", False)) ): self.context_providers.append(InMemoryHistoryProvider()) From df1008b4da373ca6e309b2ff43f6fae157cc065d Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 10 Mar 2026 09:41:24 -0700 Subject: [PATCH 4/5] Fix integration tests --- .../packages/core/agent_framework/_agents.py | 216 +++++------------- 1 file changed, 56 insertions(+), 160 deletions(-) diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index 03059163ee..5f21698766 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -277,10 +277,7 @@ def run( stream: bool = False, session: AgentSession | None = None, **kwargs: Any, - ) -> ( - Awaitable[AgentResponse[Any]] - | ResponseStream[AgentResponseUpdate, AgentResponse[Any]] - ): + ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: """Get a response from the agent. This method can return either a complete response or stream partial updates @@ -394,22 +391,16 @@ def __init__( self.id = id self.name = name self.description = description - self.context_providers: list[BaseContextProvider] = list( - context_providers or [] - ) + self.context_providers: list[BaseContextProvider] = list(context_providers or []) self.middleware: list[MiddlewareTypes] | None = ( cast(list[MiddlewareTypes], middleware) if middleware is not None else None ) # Merge kwargs into additional_properties - self.additional_properties: dict[str, Any] = cast( - dict[str, Any], additional_properties or {} - ) + self.additional_properties: dict[str, Any] = cast(dict[str, Any], additional_properties or {}) self.additional_properties.update(kwargs) - def create_session( - self, *, session_id: str | None = None, **kwargs: Any - ) -> AgentSession: + def create_session(self, *, session_id: str | None = None, **kwargs: Any) -> AgentSession: """Create a new lightweight session. Keyword Args: @@ -421,9 +412,7 @@ def create_session( """ return AgentSession(session_id=session_id) - def get_session( - self, *, service_session_id: str, session_id: str | None = None, **kwargs: Any - ) -> AgentSession: + def get_session(self, *, service_session_id: str, session_id: str | None = None, **kwargs: Any) -> AgentSession: """Get or create a session for a service-managed session ID. Args: @@ -436,9 +425,7 @@ def get_session( Returns: A new AgentSession instance with service_session_id set. """ - return AgentSession( - session_id=session_id, service_session_id=service_session_id - ) + return AgentSession(session_id=session_id, service_session_id=service_session_id) async def _run_after_providers( self, @@ -458,9 +445,7 @@ async def _run_after_providers( for provider in reversed(self.context_providers): if provider_session is None: - raise RuntimeError( - "Provider session must be available when context providers are configured." - ) + raise RuntimeError("Provider session must be available when context providers are configured.") await provider.after_run( agent=self, # type: ignore[arg-type] session=provider_session, @@ -522,15 +507,11 @@ def as_tool( """ # Verify that self implements SupportsAgentRun if not isinstance(self, SupportsAgentRun): - raise TypeError( - f"Agent {self.__class__.__name__} must implement SupportsAgentRun to be used as a tool" - ) + raise TypeError(f"Agent {self.__class__.__name__} must implement SupportsAgentRun to be used as a tool") tool_name = name or _sanitize_agent_name(self.name) if tool_name is None: - raise ValueError( - "Agent tool name cannot be None. Either provide a name parameter or set the agent's name." - ) + raise ValueError("Agent tool name cannot be None. Either provide a name parameter or set the agent's name.") tool_description = description or self.description or "" argument_description = arg_description or f"Task for {tool_name}" @@ -540,9 +521,7 @@ def as_tool( input_model = create_model(model_name, **{arg_name: (str, field_info)}) # type: ignore[call-overload] # Check if callback is async once, outside the wrapper - is_async_callback = stream_callback is not None and inspect.iscoroutinefunction( - stream_callback - ) + is_async_callback = stream_callback is not None and inspect.iscoroutinefunction(stream_callback) async def agent_wrapper(**kwargs: Any) -> str: """Wrapper function that calls the agent.""" @@ -554,9 +533,7 @@ async def agent_wrapper(**kwargs: Any) -> str: # Forward runtime context kwargs, excluding framework-internal keys. forwarded_kwargs = { - k: v - for k, v in kwargs.items() - if k not in (arg_name, "conversation_id", "options", "session") + k: v for k, v in kwargs.items() if k not in (arg_name, "conversation_id", "options", "session") } if stream_callback is None: @@ -572,9 +549,7 @@ async def agent_wrapper(**kwargs: Any) -> str: # Use streaming mode - accumulate updates and create final response response_updates: list[AgentResponseUpdate] = [] - async for update in self.run( - input_text, stream=True, session=parent_session, **forwarded_kwargs - ): + async for update in self.run(input_text, stream=True, session=parent_session, **forwarded_kwargs): response_updates.append(update) if is_async_callback: await stream_callback(update) # type: ignore[misc] @@ -687,10 +662,7 @@ def __init__( id: str | None = None, name: str | None = None, description: str | None = None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None = None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, default_options: OptionsCoT | None = None, context_providers: Sequence[BaseContextProvider] | None = None, **kwargs: Any, @@ -720,9 +692,7 @@ def __init__( """ opts = dict(default_options) if default_options else {} - if not isinstance(client, FunctionInvocationLayer) and isinstance( - client, BaseChatClient - ): + if not isinstance(client, FunctionInvocationLayer) and isinstance(client, BaseChatClient): logger.warning( "The provided chat client does not support function invoking, this might limit agent capabilities." ) @@ -740,24 +710,17 @@ def __init__( tools_ = tools if tools is not None else opts.pop("tools", None) # Handle instructions - named parameter takes precedence over options - instructions_ = ( - instructions if instructions is not None else opts.pop("instructions", None) - ) + instructions_ = instructions if instructions is not None else opts.pop("instructions", None) # We ignore the MCP Servers here and store them separately, # we add their functions to the tools list at runtime normalized_tools = normalize_tools(tools_) - self.mcp_tools: list[MCPTool] = [ - tool for tool in normalized_tools if isinstance(tool, MCPTool) - ] - agent_tools = [ - tool for tool in normalized_tools if not isinstance(tool, MCPTool) - ] + self.mcp_tools: list[MCPTool] = [tool for tool in normalized_tools if isinstance(tool, MCPTool)] + agent_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)] # Build chat options dict self.default_options: dict[str, Any] = { - "model_id": opts.pop("model_id", None) - or (getattr(self.client, "model_id", None)), + "model_id": opts.pop("model_id", None) or (getattr(self.client, "model_id", None)), "allow_multiple_tool_calls": opts.pop("allow_multiple_tool_calls", None), "conversation_id": opts.pop("conversation_id", None), "frequency_penalty": opts.pop("frequency_penalty", None), @@ -778,9 +741,7 @@ def __init__( **opts, # Remaining options are provider-specific } # Remove None values from chat_options - self.default_options = { - k: v for k, v in self.default_options.items() if v is not None - } + self.default_options = {k: v for k, v in self.default_options.items() if v is not None} self._async_exit_stack = AsyncExitStack() self._update_agent_name_and_description() @@ -836,10 +797,7 @@ def run( *, stream: Literal[False] = ..., session: AgentSession | None = None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None = None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, options: ChatOptions[ResponseModelBoundT], **kwargs: Any, ) -> Awaitable[AgentResponse[ResponseModelBoundT]]: ... @@ -851,10 +809,7 @@ def run( *, stream: Literal[False] = ..., session: AgentSession | None = None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None = None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, options: OptionsCoT | ChatOptions[None] | None = None, **kwargs: Any, ) -> Awaitable[AgentResponse[Any]]: ... @@ -866,10 +821,7 @@ def run( *, stream: Literal[True], session: AgentSession | None = None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None = None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, options: OptionsCoT | ChatOptions[Any] | None = None, **kwargs: Any, ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... @@ -880,16 +832,10 @@ def run( *, stream: bool = False, session: AgentSession | None = None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None = None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, options: OptionsCoT | ChatOptions[Any] | None = None, **kwargs: Any, - ) -> ( - Awaitable[AgentResponse[Any]] - | ResponseStream[AgentResponseUpdate, AgentResponse[Any]] - ): + ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: """Run the agent with the given messages and options. Note: @@ -940,9 +886,7 @@ async def _run_non_streaming() -> AgentResponse[Any]: ) if not response: - raise AgentInvalidResponseException( - "Chat client did not return a response." - ) + raise AgentInvalidResponseException("Chat client did not return a response.") await self._finalize_response( response=response, @@ -983,9 +927,7 @@ async def _post_hook(response: AgentResponse) -> None: # Update thread with conversation_id derived from streaming raw updates. # Using response_id here can break function-call continuation for APIs # where response IDs are not valid conversation handles. - conversation_id = self._extract_conversation_id_from_streaming_response( - response - ) + conversation_id = self._extract_conversation_id_from_streaming_response(response) # Ensure author names are set for all messages for message in response.messages: if message.author_name is None: @@ -1004,13 +946,9 @@ async def _post_hook(response: AgentResponse) -> None: messages=response.messages, response_id=response.response_id, ) - await self._run_after_providers( - session=ctx["session"], context=session_context - ) + await self._run_after_providers(session=ctx["session"], context=session_context) - async def _get_stream() -> ResponseStream[ - ChatResponseUpdate, ChatResponse[Any] - ]: + async def _get_stream() -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ctx_holder["ctx"] = await self._prepare_run_context( messages=messages, session=session, @@ -1038,11 +976,7 @@ def _propagate_conversation_id( return update raw = update.raw_representation conv_id = getattr(raw, "conversation_id", None) if raw else None - if ( - isinstance(conv_id, str) - and conv_id - and session.service_session_id != conv_id - ): + if isinstance(conv_id, str) and conv_id and session.service_session_id != conv_id: session.service_session_id = conv_id return update @@ -1056,7 +990,8 @@ def _finalizer(updates: Sequence[AgentResponseUpdate]) -> AgentResponse[Any]: return self._finalize_response_updates(updates, response_format=rf) return ( - ResponseStream.from_awaitable(_get_stream()) # type: ignore[reportUnknownMemberType] + ResponseStream + .from_awaitable(_get_stream()) # type: ignore[reportUnknownMemberType] .map( transform=partial( map_chat_to_agent_update, @@ -1075,9 +1010,7 @@ def _finalize_response_updates( response_format: Any | None = None, ) -> AgentResponse[Any]: """Finalize response updates into a single AgentResponse.""" - output_format_type = ( - response_format if isinstance(response_format, type) else None - ) + output_format_type = response_format if isinstance(response_format, type) else None return AgentResponse.from_updates( # pyright: ignore[reportUnknownVariableType] updates, output_format_type=output_format_type, @@ -1112,24 +1045,23 @@ async def _prepare_run_context( *, messages: AgentRunInputs | None, session: AgentSession | None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None, options: Mapping[str, Any] | None, kwargs: dict[str, Any], ) -> _RunContext: - # Merge default options with runtime options (runtime takes precedence) - opts = _merge_options(self.default_options, dict(options) if options else {}) + opts = dict(options) if options else {} # Get tools from options or named parameter (named param takes precedence) - # Remove tools from options to prevent overwrite merged tools list later when - # we merge opts into chat_options - options_tools = opts.pop("tools", None) - tools_ = tools if tools is not None else options_tools + tools_ = tools if tools is not None else opts.pop("tools", None) input_messages = normalize_messages(messages) + # `store` in runtime or agent options takes precedence over client-level storage + # indicators. An explicit `store=False` forces local (in-memory) history injection, + # even if the client is configured to use service-side storage by default. + store_ = opts.get( + "store", getattr(self.default_options, "store", getattr(self.client, "STORES_BY_DEFAULT", False)) + ) # Auto-inject InMemoryHistoryProvider when session is provided, no context providers # registered, and no service-side storage indicators if ( @@ -1137,10 +1069,7 @@ async def _prepare_run_context( and not self.context_providers and not session.service_session_id and not opts.get("conversation_id") - # `store` takes precedence over client-level storage indicators. An explicit - # `store=False` forces local (in-memory) history injection, even if the client - # is configured to use service-side storage by default. - and not opts.get("store", getattr(self.client, "STORES_BY_DEFAULT", False)) + and not store_ ): self.context_providers.append(InMemoryHistoryProvider()) @@ -1168,21 +1097,15 @@ async def _prepare_run_context( else: final_tools.append(tool) # type: ignore - existing_names = { - name for t in final_tools if (name := _get_tool_name(t)) is not None - } + existing_names = {name for t in final_tools if (name := _get_tool_name(t)) is not None} for mcp_server in self.mcp_tools: if not mcp_server.is_connected: await self._async_exit_stack.enter_async_context(mcp_server) - final_tools.extend( - f for f in mcp_server.functions if f.name not in existing_names - ) + final_tools.extend(f for f in mcp_server.functions if f.name not in existing_names) # Merge runtime kwargs into additional_function_arguments so they're available # in function middleware context and tool invocation. - existing_additional_args: dict[str, Any] = ( - opts.pop("additional_function_arguments", None) or {} - ) + existing_additional_args: dict[str, Any] = opts.pop("additional_function_arguments", None) or {} additional_function_arguments = {**kwargs, **existing_additional_args} # Include session so as_tool() wrappers with propagate_session=True can access it. if active_session is not None: @@ -1217,17 +1140,13 @@ async def _prepare_run_context( co = _merge_options(chat_options, run_opts) # Build session_messages from session context: context messages + input messages - session_messages: list[Message] = session_context.get_messages( - include_input=True - ) + session_messages: list[Message] = session_context.get_messages(include_input=True) # Ensure session is forwarded in kwargs for tool invocation finalize_kwargs = dict(kwargs) finalize_kwargs["session"] = active_session # Filter chat_options from kwargs to prevent duplicate keyword argument - filtered_kwargs = { - k: v for k, v in finalize_kwargs.items() if k != "chat_options" - } + filtered_kwargs = {k: v for k, v in finalize_kwargs.items() if k != "chat_options"} return { "session": active_session, @@ -1263,11 +1182,7 @@ async def _finalize_response( # Propagate conversation_id back to session (e.g. thread ID from Assistants API). # For Responses-style APIs this can rotate every turn (response_id-based continuation), # so refresh when a newer value is returned. - if ( - session - and response.conversation_id - and session.service_session_id != response.conversation_id - ): + if session and response.conversation_id and session.service_session_id != response.conversation_id: session.service_session_id = response.conversation_id # Set the response on the context for after_run providers @@ -1318,9 +1233,7 @@ async def _prepare_session_and_messages( session_context = SessionContext( session_id=provider_session.session_id if provider_session else None, - service_session_id=provider_session.service_session_id - if provider_session - else None, + service_session_id=provider_session.service_session_id if provider_session else None, input_messages=input_messages or [], options=options or {}, ) @@ -1330,9 +1243,7 @@ async def _prepare_session_and_messages( if isinstance(provider, BaseHistoryProvider) and not provider.load_messages: continue if provider_session is None: - raise RuntimeError( - "Provider session must be available when context providers are configured." - ) + raise RuntimeError("Provider session must be available when context providers are configured.") await provider.before_run( agent=self, # type: ignore[arg-type] session=provider_session, @@ -1351,9 +1262,7 @@ async def _prepare_session_and_messages( if session_context.instructions: combined_instructions = "\n".join(session_context.instructions) if "instructions" in chat_options: - chat_options["instructions"] = ( - f"{chat_options['instructions']}\n{combined_instructions}" - ) + chat_options["instructions"] = f"{chat_options['instructions']}\n{combined_instructions}" else: chat_options["instructions"] = combined_instructions @@ -1365,8 +1274,7 @@ def as_mcp_server( server_name: str = "Agent", version: str | None = None, instructions: str | None = None, - lifespan: Callable[[Server[Any]], AbstractAsyncContextManager[Any]] - | None = None, + lifespan: Callable[[Server[Any]], AbstractAsyncContextManager[Any]] | None = None, **kwargs: Any, ) -> Server[Any]: """Create an MCP server from an agent instance. @@ -1404,9 +1312,7 @@ async def _log(level: types.LoggingLevel, data: Any) -> None: logger.log(LOG_LEVEL_MAPPING[level], data) if server and server.request_context and server.request_context.session: try: - await server.request_context.session.send_log_message( - level=level, data=data - ) + await server.request_context.session.send_log_message(level=level, data=data) except Exception as e: logger.error("Failed to send log message to server: %s", e) @@ -1427,12 +1333,7 @@ async def _list_tools() -> list[types.Tool]: # type: ignore @server.call_tool() # type: ignore async def _call_tool( # type: ignore name: str, arguments: dict[str, Any] - ) -> Sequence[ - types.TextContent - | types.ImageContent - | types.AudioContent - | types.EmbeddedResource - ]: + ) -> Sequence[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource]: """Call a tool in the agent.""" await _log(level="debug", data=f"Calling tool with args: {arguments}") @@ -1447,9 +1348,7 @@ async def _call_tool( # type: ignore # Create an instance of the input model with the arguments try: args_instance: BaseModel | dict[str, Any] = ( - agent_tool.input_model(**arguments) - if agent_tool.input_model is not None - else arguments + agent_tool.input_model(**arguments) if agent_tool.input_model is not None else arguments ) result = await agent_tool.invoke(arguments=args_instance) except Exception as e: @@ -1507,10 +1406,7 @@ def __init__( id: str | None = None, name: str | None = None, description: str | None = None, - tools: ToolTypes - | Callable[..., Any] - | Sequence[ToolTypes | Callable[..., Any]] - | None = None, + tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None, default_options: OptionsCoT | None = None, context_providers: Sequence[BaseContextProvider] | None = None, middleware: Sequence[MiddlewareTypes] | None = None, From 1a705483d8107065e9b60d63a6ed586446e61a45 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 10 Mar 2026 10:45:03 -0700 Subject: [PATCH 5/5] Fix tests --- .../packages/core/agent_framework/_agents.py | 4 +- .../packages/core/tests/core/test_agents.py | 118 +++++++----------- 2 files changed, 49 insertions(+), 73 deletions(-) diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index 5f21698766..5cf7ff78a2 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -1059,9 +1059,7 @@ async def _prepare_run_context( # `store` in runtime or agent options takes precedence over client-level storage # indicators. An explicit `store=False` forces local (in-memory) history injection, # even if the client is configured to use service-side storage by default. - store_ = opts.get( - "store", getattr(self.default_options, "store", getattr(self.client, "STORES_BY_DEFAULT", False)) - ) + store_ = opts.get("store", self.default_options.get("store", getattr(self.client, "STORES_BY_DEFAULT", False))) # Auto-inject InMemoryHistoryProvider when session is provided, no context providers # registered, and no service-side storage indicators if ( diff --git a/python/packages/core/tests/core/test_agents.py b/python/packages/core/tests/core/test_agents.py index cc331b09c5..a60e924387 100644 --- a/python/packages/core/tests/core/test_agents.py +++ b/python/packages/core/tests/core/test_agents.py @@ -7,6 +7,8 @@ from uuid import uuid4 import pytest +from pytest import raises + from agent_framework import ( Agent, AgentResponse, @@ -25,7 +27,6 @@ ) from agent_framework._agents import _get_tool_name, _merge_options, _sanitize_agent_name from agent_framework._mcp import MCPTool -from pytest import raises def test_agent_session_type(agent_session: AgentSession) -> None: @@ -220,9 +221,7 @@ async def test_chat_client_agent_run_with_session( chat_client_base: SupportsChatGetResponse, ) -> None: mock_response = ChatResponse( - messages=[ - Message(role="assistant", contents=[Content.from_text("test response")]) - ], + messages=[Message(role="assistant", contents=[Content.from_text("test response")])], conversation_id="123", ) chat_client_base.run_responses = [mock_response] @@ -243,9 +242,7 @@ async def test_chat_client_agent_updates_existing_session_id_non_streaming( ) -> None: chat_client_base.run_responses = [ ChatResponse( - messages=[ - Message(role="assistant", contents=[Content.from_text("test response")]) - ], + messages=[Message(role="assistant", contents=[Content.from_text("test response")])], conversation_id="resp_new_123", ) ] @@ -412,9 +409,7 @@ async def test_chat_client_agent_streaming_session_history_saved_without_get_fin async for _ in agent.run("My name is Alice", session=session, stream=True): pass - chat_messages: list[Message] = session.state.get( - InMemoryHistoryProvider.DEFAULT_SOURCE_ID, {} - ).get("messages", []) + chat_messages: list[Message] = session.state.get(InMemoryHistoryProvider.DEFAULT_SOURCE_ID, {}).get("messages", []) assert len(chat_messages) == 2 assert chat_messages[0].text == "My name is Alice" assert chat_messages[1].text == "Hello Alice!" @@ -433,9 +428,7 @@ async def test_chat_client_agent_update_session_messages( assert session.service_session_id is None - chat_messages: list[Message] = session.state.get( - InMemoryHistoryProvider.DEFAULT_SOURCE_ID, {} - ).get("messages", []) + chat_messages: list[Message] = session.state.get(InMemoryHistoryProvider.DEFAULT_SOURCE_ID, {}).get("messages", []) assert chat_messages is not None assert len(chat_messages) == 2 @@ -507,16 +500,12 @@ def __init__(self, messages: list[Message] | None = None) -> None: self.new_messages: list[Message] = [] self.last_service_session_id: str | None = None - async def before_run( - self, *, agent: Any, session: Any, context: Any, state: Any - ) -> None: + async def before_run(self, *, agent: Any, session: Any, context: Any, state: Any) -> None: self.before_run_called = True if self.context_messages: context.extend_messages(self, self.context_messages) - async def after_run( - self, *, agent: Any, session: Any, context: Any, state: Any - ) -> None: + async def after_run(self, *, agent: Any, session: Any, context: Any, state: Any) -> None: self.after_run_called = True if session: self.last_service_session_id = session.service_session_id @@ -529,9 +518,7 @@ async def test_chat_agent_context_providers_model_before_run( client: SupportsChatGetResponse, ) -> None: """Test that context providers' before_run is called during agent run.""" - mock_provider = MockContextProvider( - messages=[Message(role="system", text="Test context instructions")] - ) + mock_provider = MockContextProvider(messages=[Message(role="system", text="Test context instructions")]) agent = Agent(client=client, context_providers=[mock_provider]) await agent.run("Hello") @@ -546,9 +533,7 @@ async def test_chat_agent_context_providers_after_run( mock_provider = MockContextProvider() chat_client_base.run_responses = [ ChatResponse( - messages=[ - Message(role="assistant", contents=[Content.from_text("test response")]) - ], + messages=[Message(role="assistant", contents=[Content.from_text("test response")])], conversation_id="test-thread-id", ) ] @@ -580,9 +565,7 @@ async def test_chat_agent_context_instructions_in_messages( client: SupportsChatGetResponse, ) -> None: """Test that AI context instructions are included in messages.""" - mock_provider = MockContextProvider( - messages=[Message(role="system", text="Context-specific instructions")] - ) + mock_provider = MockContextProvider(messages=[Message(role="system", text="Context-specific instructions")]) agent = Agent( client=client, instructions="Agent instructions", @@ -630,9 +613,7 @@ async def test_chat_agent_run_stream_context_providers( client: SupportsChatGetResponse, ) -> None: """Test that context providers work with run method.""" - mock_provider = MockContextProvider( - messages=[Message(role="system", text="Stream context instructions")] - ) + mock_provider = MockContextProvider(messages=[Message(role="system", text="Stream context instructions")]) agent = Agent(client=client, context_providers=[mock_provider]) # Collect all stream updates and get final response @@ -655,9 +636,7 @@ async def test_chat_agent_context_providers_with_service_session_id( mock_provider = MockContextProvider() chat_client_base.run_responses = [ ChatResponse( - messages=[ - Message(role="assistant", contents=[Content.from_text("test response")]) - ], + messages=[Message(role="assistant", contents=[Content.from_text("test response")])], conversation_id="service-thread-123", ) ] @@ -831,9 +810,7 @@ async def test_chat_agent_as_tool_name_sanitization( for agent_name, expected_tool_name in test_cases: agent = Agent(client=client, name=agent_name, description="Test agent") tool = agent.as_tool() - assert tool.name == expected_tool_name, ( - f"Expected {expected_tool_name}, got {tool.name} for input {agent_name}" - ) + assert tool.name == expected_tool_name, f"Expected {expected_tool_name}, got {tool.name} for input {agent_name}" async def test_chat_agent_as_tool_propagate_session_true( @@ -1001,9 +978,7 @@ async def capturing_inner( agent = Agent(client=chat_client_base, name="TestAgent", tools=[mock_mcp_tool]) # Simulate AG-UI turn 2: pass already-expanded MCP functions + a client tool as runtime tools - client_tool = FunctionTool( - func=lambda: "client", name="client_tool", description="Client tool" - ) + client_tool = FunctionTool(func=lambda: "client", name="client_tool", description="Client tool") runtime_tools = [mcp_func_a, mcp_func_b, client_tool] await agent.run("hello", tools=runtime_tools) @@ -1026,9 +1001,7 @@ async def test_agent_tool_receives_session_in_kwargs(chat_client_base: Any) -> N def echo_session_info(text: str, **kwargs: Any) -> str: # type: ignore[reportUnknownParameterType] session = kwargs.get("session") captured["has_session"] = session is not None - captured["has_state"] = ( - session.state is not None if isinstance(session, AgentSession) else False - ) + captured["has_state"] = session.state is not None if isinstance(session, AgentSession) else False return f"echo: {text}" # Make the base client emit a function call for our tool @@ -1062,9 +1035,7 @@ def echo_session_info(text: str, **kwargs: Any) -> str: # type: ignore[reportUn assert captured.get("has_state") is True -async def test_chat_agent_tool_choice_run_level_overrides_agent_level( - chat_client_base: Any, tool_tool: Any -) -> None: +async def test_chat_agent_tool_choice_run_level_overrides_agent_level(chat_client_base: Any, tool_tool: Any) -> None: """Verify that tool_choice passed to run() overrides agent-level tool_choice.""" captured_options: list[dict[str, Any]] = [] @@ -1128,9 +1099,7 @@ async def capturing_inner( assert captured_options[0]["tool_choice"] == "required" -async def test_chat_agent_tool_choice_none_at_run_preserves_agent_level( - chat_client_base: Any, tool_tool: Any -) -> None: +async def test_chat_agent_tool_choice_none_at_run_preserves_agent_level(chat_client_base: Any, tool_tool: Any) -> None: """Verify that tool_choice=None at run() uses agent-level default.""" captured_options: list[ChatOptions] = [] @@ -1420,9 +1389,7 @@ def test_sanitize_agent_name_replaces_invalid_chars(): @pytest.mark.asyncio -async def test_agent_create_session( - chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool -): +async def test_agent_create_session(chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool): """Test that create_session returns a new AgentSession.""" agent = Agent(client=chat_client_base, tools=[tool_tool]) @@ -1443,9 +1410,7 @@ def __init__(self): super().__init__(source_id="test") provider = TestContextProvider() - agent = Agent( - client=chat_client_base, tools=[tool_tool], context_providers=[provider] - ) + agent = Agent(client=chat_client_base, tools=[tool_tool], context_providers=[provider]) session = agent.create_session() @@ -1466,9 +1431,7 @@ async def test_agent_get_session_with_service_session_id( assert session.service_session_id == "test-thread-123" -def test_agent_session_from_dict( - chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool -): +def test_agent_session_from_dict(chat_client_base: SupportsChatGetResponse, tool_tool: FunctionTool): """Test AgentSession.from_dict restores a session from serialized state.""" # Create serialized session state serialized_state = { @@ -1503,9 +1466,7 @@ def test_chat_agent_calls_update_agent_name_on_client(): ) assert mock_client._update_agent_name_and_description.call_count == 1 - mock_client._update_agent_name_and_description.assert_called_with( - "TestAgent", "Test description" - ) + mock_client._update_agent_name_and_description.assert_called_with("TestAgent", "Test description") @pytest.mark.asyncio @@ -1553,9 +1514,7 @@ def __init__(self): super().__init__(source_id="instruction-context") async def before_run(self, *, agent, session, context, state): - context.extend_instructions( - "instruction-context", "Context-provided instructions" - ) + context.extend_instructions("instruction-context", "Context-provided instructions") provider = InstructionContextProvider() agent = Agent(client=chat_client_base, context_providers=[provider]) @@ -1590,9 +1549,7 @@ async def test_stores_by_default_skips_inmemory_injection( await agent.run("Hello", session=session) # No InMemoryHistoryProvider should have been injected - assert not any( - isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers - ) + assert not any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) async def test_stores_by_default_false_injects_inmemory( @@ -1639,9 +1596,30 @@ async def test_store_true_skips_inmemory_injection( await agent.run("Hello", session=session, options={"store": True}) # User explicitly enabled server storage, so InMemoryHistoryProvider should not be injected - assert not any( - isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers - ) + assert not any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) + + +async def test_stores_by_default_with_store_false_in_default_options_injects_inmemory( + client: SupportsChatGetResponse, +) -> None: + """Client with STORES_BY_DEFAULT=True but store=False in default_options should inject InMemoryHistoryProvider. + + This covers the regression where store=False is set via Agent(..., default_options={"store": False}) + with no per-run override while the client has STORES_BY_DEFAULT=True. + """ + from agent_framework._sessions import InMemoryHistoryProvider + + client.STORES_BY_DEFAULT = True # type: ignore[attr-defined] + + # Set store=False at agent initialization via default_options, not at run-time + agent = Agent(client=client, default_options={"store": False}) + session = agent.create_session() + + # Run without any per-run options override + await agent.run("Hello", session=session) + + # User explicitly disabled server storage in default_options, so InMemoryHistoryProvider should be injected + assert any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) # endregion