acp-sdk 0.13.0__tar.gz → 1.0.0__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 (63) hide show
  1. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/PKG-INFO +1 -1
  2. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/pyproject.toml +1 -1
  3. acp_sdk-1.0.0/src/acp_sdk/models/common.py +5 -0
  4. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/models/errors.py +5 -1
  5. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/models/models.py +16 -9
  6. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/agent.py +18 -0
  7. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/app.py +26 -2
  8. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/server.py +18 -1
  9. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/fixtures/server.py +4 -1
  10. acp_sdk-1.0.0/tests/e2e/test_suites/test_discovery.py +42 -0
  11. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/unit/client/test_client.py +1 -1
  12. acp_sdk-0.13.0/tests/e2e/test_suites/test_discovery.py +0 -24
  13. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/.gitignore +0 -0
  14. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/.python-version +0 -0
  15. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/README.md +0 -0
  16. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/docs/.gitignore +0 -0
  17. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/docs/Makefile +0 -0
  18. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/docs/conf.py +0 -0
  19. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/docs/index.rst +0 -0
  20. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/docs/make.bat +0 -0
  21. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/pytest.ini +0 -0
  22. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/__init__.py +0 -0
  23. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/client/__init__.py +0 -0
  24. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/client/client.py +0 -0
  25. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/client/types.py +0 -0
  26. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/client/utils.py +0 -0
  27. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/instrumentation.py +0 -0
  28. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/models/__init__.py +0 -0
  29. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/models/platform.py +0 -0
  30. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/models/schemas.py +0 -0
  31. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/models/types.py +0 -0
  32. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/py.typed +0 -0
  33. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/__init__.py +0 -0
  34. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/context.py +0 -0
  35. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/errors.py +0 -0
  36. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/executor.py +0 -0
  37. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/logging.py +0 -0
  38. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/resources.py +0 -0
  39. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/store/__init__.py +0 -0
  40. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/store/memory_store.py +0 -0
  41. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/store/postgresql_store.py +0 -0
  42. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/store/redis_store.py +0 -0
  43. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/store/store.py +0 -0
  44. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/store/utils.py +0 -0
  45. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/telemetry.py +0 -0
  46. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/types.py +0 -0
  47. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/server/utils.py +0 -0
  48. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/shared/__init__.py +0 -0
  49. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/shared/resources.py +0 -0
  50. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/src/acp_sdk/version.py +0 -0
  51. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/conftest.py +0 -0
  52. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/__init__.py +0 -0
  53. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/config.py +0 -0
  54. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/fixtures/__init__.py +0 -0
  55. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/fixtures/client.py +0 -0
  56. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/test_suites/__init__.py +0 -0
  57. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/test_suites/test_runs.py +0 -0
  58. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/e2e/test_suites/test_sessions.py +0 -0
  59. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/unit/client/test_utils.py +0 -0
  60. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/unit/models/__init__.py +0 -0
  61. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/unit/models/test_models.py +0 -0
  62. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/unit/server/__init__.py +0 -0
  63. {acp_sdk-0.13.0 → acp_sdk-1.0.0}/tests/unit/server/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acp-sdk
3
- Version: 0.13.0
3
+ Version: 1.0.0
4
4
  Summary: Agent Communication Protocol SDK
5
5
  Author: IBM Corp.
6
6
  Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acp-sdk"
3
- version = "0.13.0"
3
+ version = "1.0.0"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class AnyModel(BaseModel):
5
+ model_config = ConfigDict(extra="allow")
@@ -1,7 +1,10 @@
1
1
  from enum import Enum
2
+ from typing import Optional
2
3
 
3
4
  from pydantic import BaseModel
4
5
 
6
+ from acp_sdk.models.common import AnyModel
7
+
5
8
 
6
9
  class ErrorCode(str, Enum):
7
10
  SERVER_ERROR = "server_error"
@@ -10,8 +13,9 @@ class ErrorCode(str, Enum):
10
13
 
11
14
 
12
15
  class Error(BaseModel):
13
- code: ErrorCode
16
+ code: ErrorCode | str # Allow arbitrary string for backwards compatibility
14
17
  message: str
18
+ data: Optional[AnyModel] = None
15
19
 
16
20
 
17
21
  class ACPError(Exception):
@@ -8,16 +8,13 @@ from typing import Any, Literal, Optional, Union
8
8
  from pydantic import AnyUrl, BaseModel, ConfigDict, Field
9
9
  from pydantic.json_schema import SkipJsonSchema
10
10
 
11
+ from acp_sdk.models.common import AnyModel
11
12
  from acp_sdk.models.errors import ACPError, Error
12
13
  from acp_sdk.models.platform import PlatformUIAnnotation
13
14
  from acp_sdk.models.types import AgentName, ResourceUrl, RunId, SessionId
14
15
  from acp_sdk.shared import ResourceLoader, ResourceStore
15
16
 
16
17
 
17
- class AnyModel(BaseModel):
18
- model_config = ConfigDict(extra="allow")
19
-
20
-
21
18
  class Author(BaseModel):
22
19
  name: str
23
20
  email: str | None = None
@@ -111,11 +108,11 @@ class CitationMetadata(BaseModel):
111
108
  """
112
109
 
113
110
  kind: Literal["citation"] = "citation"
114
- start_index: Optional[int]
115
- end_index: Optional[int]
116
- url: Optional[str]
117
- title: Optional[str]
118
- description: Optional[str]
111
+ start_index: Optional[int] = None
112
+ end_index: Optional[int] = None
113
+ url: Optional[str] = None
114
+ title: Optional[str] = None
115
+ description: Optional[str] = None
119
116
 
120
117
 
121
118
  class TrajectoryMetadata(BaseModel):
@@ -353,10 +350,20 @@ Event = Union[
353
350
  ]
354
351
 
355
352
 
353
+ class InputContentTypes(BaseModel):
354
+ types: list[str]
355
+
356
+
357
+ class OutputContentTypes(BaseModel):
358
+ types: list[str]
359
+
360
+
356
361
  class AgentManifest(BaseModel):
357
362
  name: str
358
363
  description: str | None = None
359
364
  metadata: Metadata = Metadata()
365
+ input_content_types: list[str] = Field(default_factory=lambda: ["*/*"])
366
+ output_content_types: list[str] = Field(default_factory=lambda: ["*/*"])
360
367
 
361
368
 
362
369
  class Session(BaseModel):
@@ -17,6 +17,14 @@ class AgentManifest(abc.ABC):
17
17
  def description(self) -> str:
18
18
  return ""
19
19
 
20
+ @property
21
+ def input_content_types(self) -> list[str]:
22
+ return []
23
+
24
+ @property
25
+ def output_content_types(self) -> list[str]:
26
+ return []
27
+
20
28
  @property
21
29
  def metadata(self) -> Metadata:
22
30
  return Metadata()
@@ -38,6 +46,8 @@ def agent(
38
46
  description: str | None = None,
39
47
  *,
40
48
  metadata: Metadata | None = None,
49
+ input_content_types: list[str] | None = None,
50
+ output_content_types: list[str] | None = None,
41
51
  ) -> Callable[[Callable], AgentManifest]:
42
52
  """Decorator to create an agent."""
43
53
 
@@ -67,6 +77,14 @@ def agent(
67
77
  def metadata(self) -> Metadata:
68
78
  return metadata or Metadata()
69
79
 
80
+ @property
81
+ def input_content_types(self) -> list[str]:
82
+ return input_content_types or ["*/*"]
83
+
84
+ @property
85
+ def output_content_types(self) -> list[str]:
86
+ return output_content_types or ["*/*"]
87
+
70
88
  agent: AgentManifest
71
89
  if inspect.isasyncgenfunction(fn):
72
90
 
@@ -13,6 +13,7 @@ from fastapi.encoders import jsonable_encoder
13
13
  from fastapi.middleware.cors import CORSMiddleware
14
14
  from fastapi.responses import JSONResponse, StreamingResponse
15
15
  from obstore.exceptions import NotFoundError
16
+ from pydantic import AnyHttpUrl, ValidationError
16
17
 
17
18
  from acp_sdk.models import (
18
19
  ACPError,
@@ -67,6 +68,7 @@ def create_app(
67
68
  resource_store: ResourceStore | None = None,
68
69
  resource_loader: ResourceLoader | None = None,
69
70
  forward_resources: bool = True,
71
+ forward_base_url: AnyHttpUrl | str | None = None,
70
72
  lifespan: Lifespan[AppType] | None = None,
71
73
  dependencies: list[Depends] | None = None,
72
74
  ) -> FastAPI:
@@ -76,6 +78,12 @@ def create_app(
76
78
  ):
77
79
  raise ValueError("Resource forwarding must be enabled when resource store does not support HTTP URLs")
78
80
 
81
+ if isinstance(forward_base_url, str):
82
+ try:
83
+ forward_base_url = AnyHttpUrl(forward_base_url)
84
+ except ValidationError:
85
+ raise ValueError("forward_base_url must be a valid http(s) url")
86
+
79
87
  executor: ThreadPoolExecutor
80
88
  client = httpx.AsyncClient()
81
89
 
@@ -141,7 +149,13 @@ def create_app(
141
149
  async def list_agents() -> AgentsListResponse:
142
150
  return AgentsListResponse(
143
151
  agents=[
144
- AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
152
+ AgentModel(
153
+ name=agent.name,
154
+ description=agent.description,
155
+ metadata=agent.metadata,
156
+ input_content_types=agent.input_content_types,
157
+ output_content_types=agent.output_content_types,
158
+ )
145
159
  for agent in agents.values()
146
160
  ]
147
161
  )
@@ -149,7 +163,13 @@ def create_app(
149
163
  @app.get("/agents/{name}")
150
164
  async def read_agent(name: AgentName) -> AgentReadResponse:
151
165
  agent = find_agent(name)
152
- return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
166
+ return AgentModel(
167
+ name=agent.name,
168
+ description=agent.description,
169
+ metadata=agent.metadata,
170
+ input_content_types=agent.input_content_types,
171
+ output_content_types=agent.output_content_types,
172
+ )
153
173
 
154
174
  @app.get("/ping")
155
175
  async def ping() -> PingResponse:
@@ -165,6 +185,10 @@ def create_app(
165
185
  def create_resource_url_forwarded(id: ResourceId) -> ResourceUrl:
166
186
  if not forward_resources:
167
187
  raise RuntimeError("Resource forwarding disabled")
188
+ if forward_base_url:
189
+ return ResourceUrl(
190
+ url=str(app.url_path_for("get_resource", resource_id=id).make_absolute_url(str(forward_base_url)))
191
+ )
168
192
  return ResourceUrl(url=str(req.url_for("get_resource", resource_id=id)))
169
193
 
170
194
  async def create_resource_url(id: ResourceId) -> ResourceUrl:
@@ -9,6 +9,7 @@ import requests
9
9
  import uvicorn
10
10
  import uvicorn.config
11
11
  from fastapi import FastAPI
12
+ from pydantic import AnyHttpUrl
12
13
 
13
14
  from acp_sdk.models import Metadata
14
15
  from acp_sdk.server.agent import AgentManifest
@@ -31,13 +32,21 @@ class Server:
31
32
  self,
32
33
  name: str | None = None,
33
34
  description: str | None = None,
35
+ input_content_types: list[str] | None = None,
36
+ output_content_types: list[str] | None = None,
34
37
  *,
35
38
  metadata: Metadata | None = None,
36
39
  ) -> Callable:
37
40
  """Decorator to register an agent."""
38
41
 
39
42
  def decorator(fn: Callable) -> Callable:
40
- agent = agent_decorator(name=name, description=description, metadata=metadata)(fn)
43
+ agent = agent_decorator(
44
+ name=name,
45
+ description=description,
46
+ metadata=metadata,
47
+ input_content_types=input_content_types,
48
+ output_content_types=output_content_types,
49
+ )(fn)
41
50
  self.register(agent)
42
51
  return fn
43
52
 
@@ -59,6 +68,8 @@ class Server:
59
68
  store: Store | None = None,
60
69
  resource_store: ResourceStore | None = None,
61
70
  resource_loader: ResourceLoader | None = None,
71
+ forward_resources: bool = True,
72
+ forward_base_url: AnyHttpUrl | str | None = None,
62
73
  host: str = "127.0.0.1",
63
74
  port: int = 8000,
64
75
  uds: str | None = None,
@@ -127,6 +138,8 @@ class Server:
127
138
  store=store,
128
139
  resource_loader=resource_loader,
129
140
  resource_store=resource_store,
141
+ forward_resources=forward_resources,
142
+ forward_base_url=forward_base_url,
130
143
  )
131
144
 
132
145
  if configure_logger:
@@ -196,6 +209,8 @@ class Server:
196
209
  store: Store | None = None,
197
210
  resource_store: ResourceStore | None = None,
198
211
  resource_loader: ResourceLoader | None = None,
212
+ forward_resources: bool = True,
213
+ forward_base_url: AnyHttpUrl | str | None = None,
199
214
  host: str = "127.0.0.1",
200
215
  port: int = 8000,
201
216
  uds: str | None = None,
@@ -256,6 +271,8 @@ class Server:
256
271
  store=store,
257
272
  resource_store=resource_store,
258
273
  resource_loader=resource_loader,
274
+ forward_resources=forward_resources,
275
+ forward_base_url=forward_base_url,
259
276
  host=host,
260
277
  port=port,
261
278
  uds=uds,
@@ -112,7 +112,10 @@ def server(request: pytest.FixtureRequest, store: Store) -> Generator[None]:
112
112
 
113
113
  yield MessagePart(content=str(context.session.id), content_type="text/plain")
114
114
 
115
- @server.agent()
115
+ @server.agent(
116
+ input_content_types=["text/plain", "application/json"],
117
+ output_content_types=["text/html", "application/json", "application/javascript", "text/css"],
118
+ )
116
119
  async def mime_types(input: list[Message], context: Context) -> AsyncIterator[Message]:
117
120
  yield MessagePart(content="<h1>HTML Content</h1>", content_type="text/html")
118
121
  yield MessagePart(content='{"key": "value"}', content_type="application/json")
@@ -0,0 +1,42 @@
1
+ import pytest
2
+ from acp_sdk.client import Client
3
+ from acp_sdk.models import AgentManifest
4
+ from acp_sdk.server import Server
5
+
6
+
7
+ @pytest.mark.asyncio
8
+ async def test_ping(server: Server, client: Client) -> None:
9
+ await client.ping()
10
+ assert True
11
+
12
+
13
+ @pytest.mark.asyncio
14
+ async def test_agents_list(server: Server, client: Client) -> None:
15
+ async for agent in client.agents():
16
+ assert isinstance(agent, AgentManifest)
17
+
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_agents_manifest(server: Server, client: Client) -> None:
21
+ agent_name = "echo"
22
+ agent = await client.agent(name=agent_name)
23
+ assert isinstance(agent, AgentManifest)
24
+ assert agent.name == agent_name
25
+
26
+
27
+ @pytest.mark.asyncio
28
+ async def test_input_content_types(server: Server, client: Client) -> None:
29
+ agent_name = "mime_types"
30
+ agent = await client.agent(name=agent_name)
31
+ assert isinstance(agent, AgentManifest)
32
+ assert agent.name == agent_name
33
+ assert agent.input_content_types == ["text/plain", "application/json"]
34
+
35
+
36
+ @pytest.mark.asyncio
37
+ async def test_output_content_types(server: Server, client: Client) -> None:
38
+ agent_name = "mime_types"
39
+ agent = await client.agent(name=agent_name)
40
+ assert isinstance(agent, AgentManifest)
41
+ assert agent.name == agent_name
42
+ assert agent.output_content_types == ["text/html", "application/json", "application/javascript", "text/css"]
@@ -19,7 +19,7 @@ from acp_sdk.models import (
19
19
  )
20
20
  from pytest_httpx import HTTPXMock
21
21
 
22
- mock_agent = AgentManifest(name="mock")
22
+ mock_agent = AgentManifest(name="mock", input_content_types=[], output_content_types=[])
23
23
  mock_agents = [mock_agent]
24
24
  mock_session = Session()
25
25
  mock_run = Run(
@@ -1,24 +0,0 @@
1
- import pytest
2
- from acp_sdk.client import Client
3
- from acp_sdk.models import AgentManifest
4
- from acp_sdk.server import Server
5
-
6
-
7
- @pytest.mark.asyncio
8
- async def test_ping(server: Server, client: Client) -> None:
9
- await client.ping()
10
- assert True
11
-
12
-
13
- @pytest.mark.asyncio
14
- async def test_agents_list(server: Server, client: Client) -> None:
15
- async for agent in client.agents():
16
- assert isinstance(agent, AgentManifest)
17
-
18
-
19
- @pytest.mark.asyncio
20
- async def test_agents_manifest(server: Server, client: Client) -> None:
21
- agent_name = "echo"
22
- agent = await client.agent(name=agent_name)
23
- assert isinstance(agent, AgentManifest)
24
- assert agent.name == agent_name
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