diff --git a/python/packages/copilotstudio/README.md b/python/packages/copilotstudio/README.md index 08cad5331e5..d916a2ca2d0 100644 --- a/python/packages/copilotstudio/README.md +++ b/python/packages/copilotstudio/README.md @@ -65,7 +65,10 @@ async def main(): agent_identifier=os.environ["COPILOTSTUDIOAGENT__SCHEMANAME"], cloud=PowerPlatformCloud.PROD, copilot_agent_type=AgentType.PUBLISHED, - custom_power_platform_cloud=None + custom_power_platform_cloud=None, + # Raise aiohttp's per-line buffer above the 512 KB default so large Copilot Studio + # activities (for example, sizeable connector payloads) don't raise LineTooLong. + client_session_settings={"read_bufsize": 1024 * 1024}, ) # Create client and agent diff --git a/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py b/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py index 1cf21740b0a..8121784e17c 100644 --- a/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py +++ b/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import AsyncIterable, Awaitable, Sequence -from typing import Any, Literal, TypedDict, overload +from typing import Any, Literal, TypedDict, cast, overload from agent_framework import ( AgentMiddlewareTypes, @@ -26,6 +26,13 @@ from ._acquire_token import acquire_token from ._feature_usage import FeatureIndex +# aiohttp caps how much it buffers while reading a single response line; at the default +# read_bufsize this limit is 512 KB, which Copilot Studio can exceed for large activities +# (for example, sizeable connector payloads), raising ``aiohttp.http_exceptions.LineTooLong``. +# Copilot Studio streams each activity as one SSE ``data:`` line, so we raise the buffer to +# lift that per-line limit well above the default. See https://github.com/microsoft/agent-framework/issues/7257. +DEFAULT_READ_BUFSIZE = 1024 * 1024 + class CopilotStudioSettings(TypedDict, total=False): """Copilot Studio model settings. @@ -72,6 +79,7 @@ def __init__( cloud: PowerPlatformCloud | None = None, agent_type: AgentType | None = None, custom_power_platform_cloud: str | None = None, + client_session_settings: dict[str, Any] | None = None, username: str | None = None, token_cache: Any | None = None, scopes: list[str] | None = None, @@ -107,6 +115,15 @@ def __init__( agent_type: The type of Copilot Studio agent (Copilot, Agent, etc.). custom_power_platform_cloud: Custom Power Platform cloud URL if using a custom environment. + client_session_settings: Optional keyword arguments forwarded to the underlying + aiohttp ``ClientSession`` used by the created client. Only used when the agent + builds the client from scratch (ignored when ``client`` or ``settings`` is provided). + Whenever the agent builds the client itself (including when only ``settings`` is + provided), ``read_bufsize`` defaults to ``DEFAULT_READ_BUFSIZE`` (1 MiB) so large + Copilot Studio activities do not trigger ``aiohttp`` ``LineTooLong`` errors; set + ``read_bufsize`` (here or on the provided ``settings.client_session_settings``) to + raise or lower that limit. When you pass a fully built ``client``, configure + ``read_bufsize`` on its ``ConnectionSettings.client_session_settings`` yourself. username: Optional username for token acquisition. token_cache: Optional token cache for storing authentication tokens. scopes: Optional list of authentication scopes. Defaults to Power Platform @@ -152,13 +169,21 @@ def __init__( "or 'COPILOTSTUDIOAGENT__SCHEMANAME' environment variable." ) + resolved_session_settings: dict[str, Any] = dict(client_session_settings or {}) + resolved_session_settings.setdefault("read_bufsize", DEFAULT_READ_BUFSIZE) settings = ConnectionSettings( environment_id=resolved_environment_id, agent_identifier=resolved_agent_identifier, cloud=cloud, copilot_agent_type=agent_type, custom_power_platform_cloud=custom_power_platform_cloud, + client_session_settings=resolved_session_settings, ) + else: + # User supplied their own ConnectionSettings but no client, so the agent still builds + # the client. Ensure the read_bufsize default is present without overriding an explicit value. + supplied_session_settings = cast("dict[str, Any]", settings.client_session_settings) # pyright: ignore[reportUnknownMemberType] + supplied_session_settings.setdefault("read_bufsize", DEFAULT_READ_BUFSIZE) if not token: if not resolved_client_id: diff --git a/python/packages/copilotstudio/pyproject.toml b/python/packages/copilotstudio/pyproject.toml index 5106be98467..0fc9ada8382 100644 --- a/python/packages/copilotstudio/pyproject.toml +++ b/python/packages/copilotstudio/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "agent-framework-core>=1.13.0,<2", - "microsoft-agents-copilotstudio-client>=0.3.1,<0.3.2", + "microsoft-agents-copilotstudio-client>=1.2.0,<2", ] [tool.uv] diff --git a/python/packages/copilotstudio/tests/test_copilot_agent.py b/python/packages/copilotstudio/tests/test_copilot_agent.py index 26aba203f59..a661aeca26d 100644 --- a/python/packages/copilotstudio/tests/test_copilot_agent.py +++ b/python/packages/copilotstudio/tests/test_copilot_agent.py @@ -6,9 +6,10 @@ import pytest from agent_framework import AgentResponse, AgentResponseUpdate, AgentSession, Content, Message from agent_framework.exceptions import AgentException -from microsoft_agents.copilotstudio.client import CopilotClient +from microsoft_agents.copilotstudio.client import ConnectionSettings, CopilotClient from agent_framework_copilotstudio import CopilotStudioAgent +from agent_framework_copilotstudio._agent import DEFAULT_READ_BUFSIZE from agent_framework_copilotstudio._feature_usage import FeatureIndex @@ -99,6 +100,55 @@ def test_init_with_client(self, mock_copilot_client: MagicMock) -> None: assert agent.client == mock_copilot_client assert agent.id is not None + def test_init_applies_default_read_bufsize(self) -> None: + """The internally built client raises aiohttp's per-line limit to avoid LineTooLong (issue #7257).""" + agent = CopilotStudioAgent( + environment_id="env-id", + agent_identifier="agent-id", + token="fake-token", + ) + assert agent.client.settings.client_session_settings["read_bufsize"] == DEFAULT_READ_BUFSIZE + + def test_init_respects_custom_client_session_settings(self) -> None: + """User-provided client_session_settings are honored, and other keys are preserved.""" + agent = CopilotStudioAgent( + environment_id="env-id", + agent_identifier="agent-id", + token="fake-token", + client_session_settings={"read_bufsize": 123, "trust_env": True}, + ) + session_settings = agent.client.settings.client_session_settings + assert session_settings["read_bufsize"] == 123 + assert session_settings["trust_env"] is True + + def test_init_injects_default_read_bufsize_into_partial_settings(self) -> None: + """When client_session_settings omits read_bufsize, the default is added without dropping other keys.""" + agent = CopilotStudioAgent( + environment_id="env-id", + agent_identifier="agent-id", + token="fake-token", + client_session_settings={"trust_env": True}, + ) + session_settings = agent.client.settings.client_session_settings + assert session_settings["read_bufsize"] == DEFAULT_READ_BUFSIZE + assert session_settings["trust_env"] is True + + def test_init_applies_default_read_bufsize_to_supplied_settings(self) -> None: + """When settings (but no client) is supplied, the agent still injects the read_bufsize default.""" + settings = ConnectionSettings(environment_id="env-id", agent_identifier="agent-id") + agent = CopilotStudioAgent(settings=settings, token="fake-token") + assert agent.client.settings.client_session_settings["read_bufsize"] == DEFAULT_READ_BUFSIZE + + def test_init_supplied_settings_keep_explicit_read_bufsize(self) -> None: + """An explicit read_bufsize on supplied settings is preserved and not overridden by the default.""" + settings = ConnectionSettings( + environment_id="env-id", + agent_identifier="agent-id", + client_session_settings={"read_bufsize": 42}, + ) + agent = CopilotStudioAgent(settings=settings, token="fake-token") + assert agent.client.settings.client_session_settings["read_bufsize"] == 42 + @patch("agent_framework_copilotstudio._acquire_token.acquire_token") def test_init_empty_environment_id(self, mock_acquire_token: MagicMock) -> None: mock_acquire_token.return_value = "fake-token" diff --git a/python/samples/02-agents/providers/copilotstudio/README.md b/python/samples/02-agents/providers/copilotstudio/README.md index 43796de3784..1c3f31764d6 100644 --- a/python/samples/02-agents/providers/copilotstudio/README.md +++ b/python/samples/02-agents/providers/copilotstudio/README.md @@ -78,7 +78,10 @@ settings = ConnectionSettings( agent_identifier="your-agent-schema-name", cloud=PowerPlatformCloud.PROD, copilot_agent_type=AgentType.PUBLISHED, - custom_power_platform_cloud=None + custom_power_platform_cloud=None, + # Raise aiohttp's per-line buffer above the 512 KB default so large Copilot Studio + # activities (for example, sizeable connector payloads) don't raise LineTooLong. + client_session_settings={"read_bufsize": 1024 * 1024}, ) client = CopilotClient(settings=settings, token=token) diff --git a/python/samples/02-agents/providers/copilotstudio/copilotstudio_with_explicit_settings.py b/python/samples/02-agents/providers/copilotstudio/copilotstudio_with_explicit_settings.py index f800decc41b..9f8d9db7cb1 100644 --- a/python/samples/02-agents/providers/copilotstudio/copilotstudio_with_explicit_settings.py +++ b/python/samples/02-agents/providers/copilotstudio/copilotstudio_with_explicit_settings.py @@ -58,6 +58,9 @@ async def example_with_connection_settings() -> None: cloud=PowerPlatformCloud.PROD, # Or PowerPlatformCloud.GOV, PowerPlatformCloud.HIGH, etc. copilot_agent_type=AgentType.PUBLISHED, # Or AgentType.PREBUILT custom_power_platform_cloud=None, # Optional: for custom cloud endpoints + # Raise aiohttp's per-line buffer above the 512 KB default so large Copilot Studio + # activities (for example, sizeable connector payloads) don't raise LineTooLong. + client_session_settings={"read_bufsize": 1024 * 1024}, ) # Create CopilotClient with explicit settings diff --git a/python/uv.lock b/python/uv.lock index 3ed405101ea..fadbf8203d3 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -403,7 +403,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "agent-framework-core", editable = "packages/core" }, - { name = "microsoft-agents-copilotstudio-client", specifier = ">=0.3.1,<0.3.2" }, + { name = "microsoft-agents-copilotstudio-client", specifier = ">=1.2.0,<2" }, ] [[package]] @@ -3990,42 +3990,43 @@ wheels = [ [[package]] name = "microsoft-agents-activity" -version = "0.3.1" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/6a/dfc2fc0316b7dc4f6d24792b4a31a873b026be76792af1e0c3e65f843ef0/microsoft_agents_activity-0.3.1.tar.gz", hash = "sha256:c7567fc30f8e6f2a2d74cd65a1f7f31ade0d7ec9dd94531677d0d7b0648c77ee", size = 44886, upload-time = "2025-09-09T23:19:43.044Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/94/885013edcffdc656190527632fd3a2bf0d7a4b313c42c40230f17a877556/microsoft_agents_activity-1.2.0.tar.gz", hash = "sha256:6c6e43d92e74d148e60a02d40b31f34139ff577694f6c16ea7b35640e8e742ef", size = 65314, upload-time = "2026-07-20T19:41:37.143Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8b/50ce2243e2900e94358f37009121145bb8224a388d95d704856aa2686667/microsoft_agents_activity-0.3.1-py3-none-any.whl", hash = "sha256:d7fc2e9cf2843ec8d6d42608b808b159a12cbb61e1fc7d7b1aaf29899f20746a", size = 111904, upload-time = "2025-09-09T23:19:50.722Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/ff83466fc2cbbbf16df273b1b75e24b4a45226ee723c55561d662c97c2f6/microsoft_agents_activity-1.2.0-py3-none-any.whl", hash = "sha256:7ca92cde93ce005d93241a6c725a55c746f66d170205670d67514ec4c7d95dd0", size = 137779, upload-time = "2026-07-20T19:41:50.945Z" }, ] [[package]] name = "microsoft-agents-copilotstudio-client" -version = "0.3.1" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "microsoft-agents-hosting-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/a5/2381ffd14d6a584f9f7ab80c7b6c634f658ea651b38702eb403c930d8396/microsoft_agents_copilotstudio_client-0.3.1.tar.gz", hash = "sha256:c529209241c9d11b7a6e8696f96a3d43121c10b49e44f00e5066f9cf5256f4f3", size = 5024, upload-time = "2025-09-09T23:19:44.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/78/ef90d5789945b36bc3ebe61126a332e70c3e84e9d3bae04f34f870e41028/microsoft_agents_copilotstudio_client-1.2.0.tar.gz", hash = "sha256:85784b0a7f057cf013bdb1fbfdaa45be6ffe20436e3700f25da5bee16fabd47a", size = 27944, upload-time = "2026-07-20T19:41:40.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/35/8b4e9c691f2ce89653007f358519bbadff1fe0d495c3723c9dbbfa962a33/microsoft_agents_copilotstudio_client-0.3.1-py3-none-any.whl", hash = "sha256:cac7485405325b990202452c9c14848cbdb25d13e6cdaf7bd3eca3a5c1fb3989", size = 7420, upload-time = "2025-09-09T23:19:52.287Z" }, + { url = "https://files.pythonhosted.org/packages/14/07/f15afd04c96ae34c9e7df18abf9bfa0a85af365f872117faa849de4d32ad/microsoft_agents_copilotstudio_client-1.2.0-py3-none-any.whl", hash = "sha256:49acc6ed16e4aa615e87d4036589de3b7095660e18a11e7816413bad6dab7093", size = 23859, upload-time = "2026-07-20T19:41:54.457Z" }, ] [[package]] name = "microsoft-agents-hosting-core" -version = "0.3.1" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "azure-core" }, { name = "isodate" }, { name = "microsoft-agents-activity" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, { name = "pyjwt" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/14/a1365e0bab1486c2d16aabeb192ca90715794edf4e68be4815c245884420/microsoft_agents_hosting_core-0.3.1.tar.gz", hash = "sha256:0b76bda10e7a54ff3c86e56cbabaad5ac7a4c2a076c9833af3b2f4c86fa85e89", size = 81137, upload-time = "2025-09-09T23:19:46.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/cf/5632ed9fdf7c300e94f9be797e57cfbd2618b7940fb41b0324207d2f0e11/microsoft_agents_hosting_core-1.2.0.tar.gz", hash = "sha256:048ac0bef18c4e392563c53d2dcfc7d601333fcd5c6bca4e6ec243f34773c044", size = 131706, upload-time = "2026-07-20T19:41:42.512Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/1b/543ddaa2daf8593911a02a07a6a78366d4a6a0053ec86a557c19fa97b60e/microsoft_agents_hosting_core-0.3.1-py3-none-any.whl", hash = "sha256:a4b41556b15321b74f539c5a0a89f70955459b7ec57e9e4b24e61bba27f1cbbc", size = 94573, upload-time = "2025-09-09T23:19:53.855Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0d/8393861554180447f447a94a4fb35ebef5dedf20ce80c95c21643dcac2de/microsoft_agents_hosting_core-1.2.0-py3-none-any.whl", hash = "sha256:ca1553e9187ed0abf17e7d409d6f8372b27c74af0c00bc7a28f8c9781983ac3f", size = 194618, upload-time = "2026-07-20T19:41:56.411Z" }, ] [[package]]