acp-sdk 0.7.1__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/PKG-INFO +1 -1
  2. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/docs/client.md +4 -4
  3. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/pyproject.toml +1 -1
  4. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/client/client.py +5 -0
  5. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/models/models.py +8 -2
  6. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/app.py +15 -3
  7. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/server.py +54 -1
  8. acp_sdk-0.7.2/src/acp_sdk/server/utils.py +49 -0
  9. acp_sdk-0.7.2/tests/unit/client/test_client.py +145 -0
  10. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/unit/models/test_models.py +19 -0
  11. acp_sdk-0.7.1/src/acp_sdk/server/utils.py +0 -14
  12. acp_sdk-0.7.1/tests/unit/client/test_client.py +0 -36
  13. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/.gitignore +0 -0
  14. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/.python-version +0 -0
  15. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/README.md +0 -0
  16. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/docs/_sidebar.md +0 -0
  17. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/docs/index.html +0 -0
  18. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/docs/models.md +0 -0
  19. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/docs/server.md +0 -0
  20. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/pytest.ini +0 -0
  21. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/__init__.py +0 -0
  22. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/client/__init__.py +0 -0
  23. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/client/types.py +0 -0
  24. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/client/utils.py +0 -0
  25. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/instrumentation.py +0 -0
  26. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/models/__init__.py +0 -0
  27. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/models/errors.py +0 -0
  28. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/models/schemas.py +0 -0
  29. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/py.typed +0 -0
  30. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/__init__.py +0 -0
  31. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/agent.py +0 -0
  32. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/bundle.py +0 -0
  33. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/context.py +0 -0
  34. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/errors.py +0 -0
  35. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/logging.py +0 -0
  36. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/session.py +0 -0
  37. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/telemetry.py +0 -0
  38. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/server/types.py +0 -0
  39. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/src/acp_sdk/version.py +0 -0
  40. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/conftest.py +0 -0
  41. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/__init__.py +0 -0
  42. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/config.py +0 -0
  43. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/fixtures/__init__.py +0 -0
  44. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/fixtures/client.py +0 -0
  45. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/fixtures/server.py +0 -0
  46. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/test_suites/__init__.py +0 -0
  47. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/test_suites/test_discovery.py +0 -0
  48. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/e2e/test_suites/test_runs.py +0 -0
  49. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/unit/client/test_utils.py +0 -0
  50. {acp_sdk-0.7.1 → acp_sdk-0.7.2}/tests/unit/models/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acp-sdk
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: Agent Communication Protocol SDK
5
5
  Author: IBM Corp.
6
6
  Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
@@ -71,15 +71,15 @@ async with Client(base_url="http://localhost:8000") as client:
71
71
  message = Message(parts=[MessagePart(content="Hello")])
72
72
 
73
73
  # Async
74
- run = await client.run_async(agent_name="agent", input=[message])
74
+ run = await client.run_async(agent="agent", input=[message])
75
75
  print(run.status)
76
76
 
77
77
  # Sync - waits for completion, failure, cancellation or await
78
- run = await client.run_sync(agent_name="agent", input=[message])
78
+ run = await client.run_sync(agent="agent", input=[message])
79
79
  print(run.output)
80
80
 
81
81
  # Stream - as sync but also receives events
82
- async for event in client.run_stream(agent_name="agent", input=[message])
82
+ async for event in client.run_stream(agent="agent", input=[message])
83
83
  print(event)
84
84
  ```
85
85
 
@@ -95,5 +95,5 @@ async with Client(base_url="http://localhost:8000" as client:
95
95
 
96
96
  async with client.session() as session:
97
97
  for agent in agents:
98
- await session.run_sync(agent_name=agent.name, input=[Message(parts=[MessagePart(content="Hello!")])])
98
+ await session.run_sync(agent=agent.name, input=[Message(parts=[MessagePart(content="Hello!")])])
99
99
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acp-sdk"
3
- version = "0.7.1"
3
+ version = "0.7.2"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -120,6 +120,11 @@ class Client:
120
120
  response = AgentReadResponse.model_validate(response.json())
121
121
  return Agent(**response.model_dump())
122
122
 
123
+ async def ping(self) -> bool:
124
+ response = await self._client.get("/healthcheck")
125
+ self._raise_error(response)
126
+ return response.json() == "OK"
127
+
123
128
  async def run_sync(self, input: Input, *, agent: AgentName) -> Run:
124
129
  response = await self._client.post(
125
130
  "/runs",
@@ -47,6 +47,11 @@ class Dependency(BaseModel):
47
47
  name: str
48
48
 
49
49
 
50
+ class Capability(BaseModel):
51
+ name: str
52
+ description: str
53
+
54
+
50
55
  class Metadata(BaseModel):
51
56
  annotations: AnyModel | None = None
52
57
  documentation: str | None = None
@@ -54,7 +59,8 @@ class Metadata(BaseModel):
54
59
  programming_language: str | None = None
55
60
  natural_languages: list[str] | None = None
56
61
  framework: str | None = None
57
- use_cases: list[str] | None = None
62
+ capabilities: list[Capability] | None = None
63
+ domains: list[str] | None = None
58
64
  tags: list[str] | None = None
59
65
  created_at: datetime | None = None
60
66
  updated_at: datetime | None = None
@@ -93,7 +99,7 @@ class Message(BaseModel):
93
99
  def __add__(self, other: "Message") -> "Message":
94
100
  if not isinstance(other, Message):
95
101
  raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
96
- return Message(*(self.parts + other.parts))
102
+ return Message(parts=self.parts + other.parts)
97
103
 
98
104
  def __str__(self) -> str:
99
105
  return "".join(
@@ -5,7 +5,7 @@ from datetime import datetime, timedelta
5
5
  from enum import Enum
6
6
 
7
7
  from cachetools import TTLCache
8
- from fastapi import FastAPI, HTTPException, status
8
+ from fastapi import Depends, FastAPI, HTTPException, status
9
9
  from fastapi.encoders import jsonable_encoder
10
10
  from fastapi.responses import JSONResponse, StreamingResponse
11
11
  from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
@@ -47,7 +47,12 @@ class Headers(str, Enum):
47
47
  RUN_ID = "Run-ID"
48
48
 
49
49
 
50
- def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timedelta(hours=1)) -> FastAPI:
50
+ def create_app(
51
+ *agents: Agent,
52
+ run_limit: int = 1000,
53
+ run_ttl: timedelta = timedelta(hours=1),
54
+ dependencies: list[Depends] | None = None,
55
+ ) -> FastAPI:
51
56
  executor: ThreadPoolExecutor
52
57
 
53
58
  @asynccontextmanager
@@ -57,7 +62,10 @@ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timed
57
62
  executor = exec
58
63
  yield
59
64
 
60
- app = FastAPI(lifespan=lifespan)
65
+ app = FastAPI(
66
+ lifespan=lifespan,
67
+ dependencies=dependencies,
68
+ )
61
69
 
62
70
  FastAPIInstrumentor.instrument_app(app)
63
71
 
@@ -96,6 +104,10 @@ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timed
96
104
  agent = find_agent(name)
97
105
  return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
98
106
 
107
+ @app.get("/healthcheck")
108
+ async def healthcheck() -> str:
109
+ return "OK"
110
+
99
111
  @app.post("/runs")
100
112
  async def create_run(request: RunCreateRequest) -> RunCreateResponse:
101
113
  agent = find_agent(request.agent_name)
@@ -4,6 +4,7 @@ from collections.abc import Awaitable
4
4
  from datetime import timedelta
5
5
  from typing import Any, Callable
6
6
 
7
+ import requests
7
8
  import uvicorn
8
9
  import uvicorn.config
9
10
 
@@ -12,7 +13,9 @@ from acp_sdk.server.agent import Agent
12
13
  from acp_sdk.server.agent import agent as agent_decorator
13
14
  from acp_sdk.server.app import create_app
14
15
  from acp_sdk.server.logging import configure_logger as configure_logger_func
16
+ from acp_sdk.server.logging import logger
15
17
  from acp_sdk.server.telemetry import configure_telemetry as configure_telemetry_func
18
+ from acp_sdk.server.utils import async_request_with_retry
16
19
 
17
20
 
18
21
  class Server:
@@ -43,6 +46,7 @@ class Server:
43
46
  self,
44
47
  configure_logger: bool = True,
45
48
  configure_telemetry: bool = False,
49
+ self_registration: bool = True,
46
50
  run_limit: int = 1000,
47
51
  run_ttl: timedelta = timedelta(hours=1),
48
52
  host: str = "127.0.0.1",
@@ -158,7 +162,14 @@ class Server:
158
162
  h11_max_incomplete_event_size,
159
163
  )
160
164
  self._server = uvicorn.Server(config)
161
- self._server.run()
165
+
166
+ asyncio.run(self._serve(self_registration=self_registration))
167
+
168
+ async def _serve(self, self_registration: bool = True) -> None:
169
+ registration_task = asyncio.create_task(self._register_agent()) if self_registration else None
170
+ await self._server.serve()
171
+ if registration_task:
172
+ registration_task.cancel()
162
173
 
163
174
  @property
164
175
  def should_exit(self) -> bool:
@@ -167,3 +178,45 @@ class Server:
167
178
  @should_exit.setter
168
179
  def should_exit(self, value: bool) -> None:
169
180
  self._server.should_exit = value
181
+
182
+ async def _register_agent(self) -> None:
183
+ """If not in PRODUCTION mode, register agent to the beeai platform and provide missing env variables"""
184
+ if os.getenv("PRODUCTION_MODE", False):
185
+ logger.debug("Agent is not automatically registered in the production mode.")
186
+ return
187
+
188
+ url = os.getenv("PLATFORM_URL", "http://127.0.0.1:8333")
189
+ for agent in self._agents:
190
+ request_data = {
191
+ "location": f"http://{self._server.config.host}:{self._server.config.port}",
192
+ "id": agent.name,
193
+ }
194
+ try:
195
+ await async_request_with_retry(
196
+ lambda client, data=request_data: client.post(
197
+ f"{url}/api/v1/provider/register/unmanaged", json=data
198
+ )
199
+ )
200
+ logger.info("Agent registered to the beeai server.")
201
+
202
+ # check missing env keyes
203
+ envs_request = await async_request_with_retry(lambda client: client.get(f"{url}/api/v1/env"))
204
+ envs = envs_request.get("env")
205
+
206
+ # register all available envs
207
+ missing_keyes = []
208
+ for env in agent.metadata.model_dump().get("env", []):
209
+ server_env = envs.get(env.get("name"))
210
+ if server_env:
211
+ logger.debug(f"Env variable {env['name']} = '{server_env}' added dynamically")
212
+ os.environ[env["name"]] = server_env
213
+ elif env.get("required"):
214
+ missing_keyes.append(env)
215
+ if len(missing_keyes):
216
+ logger.error(f"Can not run agent, missing required env variables: {missing_keyes}")
217
+ raise Exception("Missing env variables")
218
+
219
+ except requests.exceptions.ConnectionError as e:
220
+ logger.warning(f"Can not reach server, check if running on {url} : {e}")
221
+ except (requests.exceptions.HTTPError, Exception) as e:
222
+ logger.warning(f"Agent can not be registered to beeai server: {e}")
@@ -0,0 +1,49 @@
1
+ import asyncio
2
+ from collections.abc import AsyncGenerator, Coroutine
3
+ from typing import Any, Callable
4
+
5
+ import httpx
6
+ import requests
7
+ from pydantic import BaseModel
8
+
9
+ from acp_sdk.server.bundle import RunBundle
10
+ from acp_sdk.server.logging import logger
11
+
12
+
13
+ def encode_sse(model: BaseModel) -> str:
14
+ return f"data: {model.model_dump_json()}\n\n"
15
+
16
+
17
+ async def stream_sse(bundle: RunBundle) -> AsyncGenerator[str]:
18
+ async for event in bundle.stream():
19
+ yield encode_sse(event)
20
+
21
+
22
+ async def async_request_with_retry(
23
+ request_func: Callable[[httpx.AsyncClient], Coroutine[Any, Any, httpx.Response]],
24
+ max_retries: int = 5,
25
+ backoff_factor: float = 1,
26
+ ) -> dict[str, Any]:
27
+ async with httpx.AsyncClient() as client:
28
+ retries = 0
29
+ while retries < max_retries:
30
+ try:
31
+ response = await request_func(client)
32
+ response.raise_for_status()
33
+ return response.json()
34
+ except httpx.HTTPStatusError as e:
35
+ if e.response.status_code in [429, 500, 502, 503, 504, 509]:
36
+ retries += 1
37
+ backoff = backoff_factor * (2 ** (retries - 1))
38
+ logger.warning(f"Request retry (try {retries}/{max_retries}), waiting {backoff} seconds...")
39
+ await asyncio.sleep(backoff)
40
+ else:
41
+ logger.debug("A non-retryable error was encountered.")
42
+ raise
43
+ except httpx.RequestError:
44
+ retries += 1
45
+ backoff = backoff_factor * (2 ** (retries - 1))
46
+ logger.warning(f"Request retry (try {retries}/{max_retries}), waiting {backoff} seconds...")
47
+ await asyncio.sleep(backoff)
48
+
49
+ raise requests.exceptions.ConnectionError(f"Request failed after {max_retries} retries.")
@@ -0,0 +1,145 @@
1
+ import json
2
+ import uuid
3
+
4
+ import pytest
5
+ from acp_sdk.client import Client
6
+ from acp_sdk.models import Agent, AgentsListResponse, Message, MessagePart, Run, RunCompletedEvent
7
+ from acp_sdk.models.models import MessageAwaitResume
8
+ from pytest_httpx import HTTPXMock
9
+
10
+ mock_agent = Agent(name="mock")
11
+ mock_agents = [mock_agent]
12
+ mock_run = Run(
13
+ agent_name=mock_agent.name, session_id=uuid.uuid4(), output=[Message(parts=[MessagePart(content="Hello!")])]
14
+ )
15
+
16
+
17
+ @pytest.mark.asyncio
18
+ async def test_agents(httpx_mock: HTTPXMock) -> None:
19
+ httpx_mock.add_response(
20
+ url="http://test/agents", method="GET", content=AgentsListResponse(agents=mock_agents).model_dump_json()
21
+ )
22
+
23
+ async with Client(base_url="http://test") as client:
24
+ agents = [agent async for agent in client.agents()]
25
+ assert agents == mock_agents
26
+
27
+
28
+ @pytest.mark.asyncio
29
+ async def test_agent(httpx_mock: HTTPXMock) -> None:
30
+ httpx_mock.add_response(
31
+ url=f"http://test/agents/{mock_agent.name}", method="GET", content=mock_agent.model_dump_json()
32
+ )
33
+
34
+ async with Client(base_url="http://test") as client:
35
+ agent = await client.agent(name=mock_agent.name)
36
+ assert agent == mock_agent
37
+
38
+
39
+ @pytest.mark.asyncio
40
+ async def test_run_sync(httpx_mock: HTTPXMock) -> None:
41
+ httpx_mock.add_response(url="http://test/runs", method="POST", content=mock_run.model_dump_json())
42
+
43
+ async with Client(base_url="http://test") as client:
44
+ run = await client.run_sync("Howdy!", agent=mock_run.agent_name)
45
+ assert run == mock_run
46
+
47
+
48
+ @pytest.mark.asyncio
49
+ async def test_run_async(httpx_mock: HTTPXMock) -> None:
50
+ httpx_mock.add_response(url="http://test/runs", method="POST", content=mock_run.model_dump_json())
51
+
52
+ async with Client(base_url="http://test") as client:
53
+ run = await client.run_async("Howdy!", agent=mock_run.agent_name)
54
+ assert run == mock_run
55
+
56
+
57
+ @pytest.mark.asyncio
58
+ async def test_run_stream(httpx_mock: HTTPXMock) -> None:
59
+ mock_event = RunCompletedEvent(run=mock_run)
60
+ httpx_mock.add_response(
61
+ url="http://test/runs",
62
+ method="POST",
63
+ headers={"content-type": "text/event-stream"},
64
+ content=f"data: {mock_event.model_dump_json()}\n\n",
65
+ )
66
+
67
+ async with Client(base_url="http://test") as client:
68
+ async for event in client.run_stream("Howdy!", agent=mock_run.agent_name):
69
+ assert event == mock_event
70
+
71
+
72
+ @pytest.mark.asyncio
73
+ async def test_run_status(httpx_mock: HTTPXMock) -> None:
74
+ httpx_mock.add_response(url=f"http://test/runs/{mock_run.run_id}", method="GET", content=mock_run.model_dump_json())
75
+
76
+ async with Client(base_url="http://test") as client:
77
+ run = await client.run_status(run_id=mock_run.run_id)
78
+ assert run == mock_run
79
+
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_run_cancel(httpx_mock: HTTPXMock) -> None:
83
+ httpx_mock.add_response(
84
+ url=f"http://test/runs/{mock_run.run_id}/cancel", method="POST", content=mock_run.model_dump_json()
85
+ )
86
+
87
+ async with Client(base_url="http://test") as client:
88
+ run = await client.run_cancel(run_id=mock_run.run_id)
89
+ assert run == mock_run
90
+
91
+
92
+ @pytest.mark.asyncio
93
+ async def test_run_resume_sync(httpx_mock: HTTPXMock) -> None:
94
+ httpx_mock.add_response(
95
+ url=f"http://test/runs/{mock_run.run_id}", method="POST", content=mock_run.model_dump_json()
96
+ )
97
+
98
+ async with Client(base_url="http://test") as client:
99
+ run = await client.run_resume_sync(MessageAwaitResume(message=Message(parts=[])), run_id=mock_run.run_id)
100
+ assert run == mock_run
101
+
102
+
103
+ @pytest.mark.asyncio
104
+ async def test_run_resume_async(httpx_mock: HTTPXMock) -> None:
105
+ httpx_mock.add_response(
106
+ url=f"http://test/runs/{mock_run.run_id}", method="POST", content=mock_run.model_dump_json()
107
+ )
108
+
109
+ async with Client(base_url="http://test") as client:
110
+ run = await client.run_resume_async(MessageAwaitResume(message=Message(parts=[])), run_id=mock_run.run_id)
111
+ assert run == mock_run
112
+
113
+
114
+ @pytest.mark.asyncio
115
+ async def test_run_resume_stream(httpx_mock: HTTPXMock) -> None:
116
+ mock_event = RunCompletedEvent(run=mock_run)
117
+ httpx_mock.add_response(
118
+ url=f"http://test/runs/{mock_run.run_id}",
119
+ method="POST",
120
+ headers={"content-type": "text/event-stream"},
121
+ content=f"data: {mock_event.model_dump_json()}\n\n",
122
+ )
123
+
124
+ async with Client(base_url="http://test") as client:
125
+ async for event in client.run_resume_stream(
126
+ MessageAwaitResume(message=Message(parts=[])), run_id=mock_run.run_id
127
+ ):
128
+ assert event == mock_event
129
+
130
+
131
+ @pytest.mark.asyncio
132
+ async def test_session(httpx_mock: HTTPXMock) -> None:
133
+ httpx_mock.add_response(url="http://test/runs", method="POST", content=mock_run.model_dump_json(), is_reusable=True)
134
+
135
+ async with Client(base_url="http://test") as client, client.session(mock_run.session_id) as session:
136
+ assert session._session_id == mock_run.session_id
137
+ await session.run_sync("Howdy!", agent=mock_run.agent_name)
138
+ await client.run_sync("Howdy!", agent=mock_run.agent_name)
139
+
140
+ requests = httpx_mock.get_requests()
141
+ body = json.loads(requests[0].content)
142
+ assert body["session_id"] == str(mock_run.session_id)
143
+
144
+ body = json.loads(requests[1].content)
145
+ assert body["session_id"] is None
@@ -2,6 +2,25 @@ import pytest
2
2
  from acp_sdk.models.models import Message, MessagePart
3
3
 
4
4
 
5
+ @pytest.mark.parametrize(
6
+ "first,second,result",
7
+ [
8
+ (
9
+ Message(parts=[MessagePart(content_type="text/plain", content="Foo")]),
10
+ Message(parts=[MessagePart(content_type="text/plain", content="Bar")]),
11
+ Message(
12
+ parts=[
13
+ MessagePart(content_type="text/plain", content="Foo"),
14
+ MessagePart(content_type="text/plain", content="Bar"),
15
+ ]
16
+ ),
17
+ )
18
+ ],
19
+ )
20
+ def test_message_add(first: Message, second: Message, result: Message) -> None:
21
+ assert first + second == result
22
+
23
+
5
24
  @pytest.mark.parametrize(
6
25
  "uncompressed,compressed",
7
26
  [
@@ -1,14 +0,0 @@
1
- from collections.abc import AsyncGenerator
2
-
3
- from pydantic import BaseModel
4
-
5
- from acp_sdk.server.bundle import RunBundle
6
-
7
-
8
- def encode_sse(model: BaseModel) -> str:
9
- return f"data: {model.model_dump_json()}\n\n"
10
-
11
-
12
- async def stream_sse(bundle: RunBundle) -> AsyncGenerator[str]:
13
- async for event in bundle.stream():
14
- yield encode_sse(event)
@@ -1,36 +0,0 @@
1
- import pytest
2
- from acp_sdk.client import Client
3
- from acp_sdk.models import Message, MessagePart, Run, RunCompletedEvent
4
- from pytest_httpx import HTTPXMock
5
-
6
- mock_run = Run(agent_name="mock", output=[Message(parts=[MessagePart(content="Hello!")])])
7
-
8
-
9
- @pytest.mark.asyncio
10
- async def test_run_sync(httpx_mock: HTTPXMock) -> None:
11
- httpx_mock.add_response(content=mock_run.model_dump_json())
12
-
13
- async with Client(base_url="http://localhost:8000") as client:
14
- run = await client.run_sync("Howdy!", agent="mock")
15
- assert run == mock_run
16
-
17
-
18
- @pytest.mark.asyncio
19
- async def test_run_async(httpx_mock: HTTPXMock) -> None:
20
- httpx_mock.add_response(content=mock_run.model_dump_json())
21
-
22
- async with Client(base_url="http://localhost:8000") as client:
23
- run = await client.run_async("Howdy!", agent="mock")
24
- assert run == mock_run
25
-
26
-
27
- @pytest.mark.asyncio
28
- async def test_run_stream(httpx_mock: HTTPXMock) -> None:
29
- mock_event = RunCompletedEvent(run=mock_run)
30
- httpx_mock.add_response(
31
- headers={"content-type": "text/event-stream"}, content=f"data: {mock_event.model_dump_json()}\n\n"
32
- )
33
-
34
- async with Client(base_url="http://localhost:8000") as client:
35
- async for event in client.run_stream("Howdy!", agent="mock"):
36
- assert event == mock_event
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes