acp-sdk 0.3.0__tar.gz → 0.3.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 (47) hide show
  1. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/PKG-INFO +2 -14
  2. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/README.md +1 -13
  3. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/examples/clients/advanced.py +1 -3
  4. acp_sdk-0.3.2/examples/clients/session.py +18 -0
  5. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/examples/clients/simple.py +1 -3
  6. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/examples/clients/stream.py +1 -3
  7. acp_sdk-0.3.2/examples/servers/awaiting.py +26 -0
  8. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/pyproject.toml +1 -1
  9. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/models/models.py +42 -6
  10. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/app.py +10 -0
  11. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/bundle.py +2 -0
  12. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/types.py +1 -1
  13. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/fixtures/server.py +5 -3
  14. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/test_suites/test_runs.py +6 -5
  15. acp_sdk-0.3.2/tests/unit/models/__init__.py +0 -0
  16. acp_sdk-0.3.2/tests/unit/models/test_models.py +37 -0
  17. acp_sdk-0.3.0/examples/clients/session.py +0 -22
  18. acp_sdk-0.3.0/examples/servers/awaiting.py +0 -25
  19. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/.gitignore +0 -0
  20. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/.python-version +0 -0
  21. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/examples/servers/echo.py +0 -0
  22. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/examples/servers/standalone.py +0 -0
  23. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/pytest.ini +0 -0
  24. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/__init__.py +0 -0
  25. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/client/__init__.py +0 -0
  26. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/client/client.py +0 -0
  27. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/instrumentation.py +0 -0
  28. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/models/__init__.py +0 -0
  29. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/models/errors.py +0 -0
  30. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/models/schemas.py +0 -0
  31. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/py.typed +0 -0
  32. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/__init__.py +0 -0
  33. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/agent.py +0 -0
  34. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/context.py +0 -0
  35. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/errors.py +0 -0
  36. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/logging.py +0 -0
  37. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/server.py +0 -0
  38. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/session.py +0 -0
  39. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/telemetry.py +0 -0
  40. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/server/utils.py +0 -0
  41. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/src/acp_sdk/version.py +0 -0
  42. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/conftest.py +0 -0
  43. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/__init__.py +0 -0
  44. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/config.py +0 -0
  45. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/fixtures/__init__.py +0 -0
  46. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/fixtures/client.py +0 -0
  47. {acp_sdk-0.3.0 → acp_sdk-0.3.2}/tests/e2e/test_suites/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acp-sdk
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Agent Communication Protocol SDK
5
5
  Author: IBM Corp.
6
6
  Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
@@ -28,19 +28,7 @@ Agent Communication Protocol SDK for Python provides allows developers to serve
28
28
 
29
29
  ## Installation
30
30
 
31
- Install to use client:
32
-
33
- ```shell
34
- pip install acp-sdk[client]
35
- ```
36
-
37
- Install to use server:
38
-
39
- ```shell
40
- pip install acp-sdk[server]
41
- ```
42
-
43
- Install to use models only:
31
+ Install with:
44
32
 
45
33
  ```shell
46
34
  pip install acp-sdk
@@ -8,19 +8,7 @@ Agent Communication Protocol SDK for Python provides allows developers to serve
8
8
 
9
9
  ## Installation
10
10
 
11
- Install to use client:
12
-
13
- ```shell
14
- pip install acp-sdk[client]
15
- ```
16
-
17
- Install to use server:
18
-
19
- ```shell
20
- pip install acp-sdk[server]
21
- ```
22
-
23
- Install to use models only:
11
+ Install with:
24
12
 
25
13
  ```shell
26
14
  pip install acp-sdk
@@ -13,9 +13,7 @@ async def example() -> None:
13
13
  # Additional client configuration
14
14
  )
15
15
  ) as client:
16
- run = await client.run_sync(
17
- agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!", content_type="text/plain")])]
18
- )
16
+ run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
19
17
  print(run)
20
18
 
21
19
 
@@ -0,0 +1,18 @@
1
+ import asyncio
2
+
3
+ from acp_sdk.client import Client
4
+ from acp_sdk.models import (
5
+ Message,
6
+ MessagePart,
7
+ )
8
+
9
+
10
+ async def example() -> None:
11
+ async with Client(base_url="http://localhost:8000") as client, client.session() as session:
12
+ run = await session.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
13
+ run = await session.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy again!")])])
14
+ print(run)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ asyncio.run(example())
@@ -9,9 +9,7 @@ from acp_sdk.models import (
9
9
 
10
10
  async def example() -> None:
11
11
  async with Client(base_url="http://localhost:8000") as client:
12
- run = await client.run_sync(
13
- agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!", content_type="text/plain")])]
14
- )
12
+ run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
15
13
  print(run)
16
14
 
17
15
 
@@ -6,9 +6,7 @@ from acp_sdk.models import Message, MessagePart
6
6
 
7
7
  async def example() -> None:
8
8
  async with Client(base_url="http://localhost:8000") as client:
9
- async for event in client.run_stream(
10
- agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!", content_type="text/plain")])]
11
- ):
9
+ async for event in client.run_stream(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])]):
12
10
  print(event)
13
11
 
14
12
 
@@ -0,0 +1,26 @@
1
+ from collections.abc import AsyncGenerator
2
+
3
+ from acp_sdk.models import (
4
+ Message,
5
+ MessageAwaitRequest,
6
+ MessageAwaitResume,
7
+ MessagePart,
8
+ )
9
+ from acp_sdk.server import Context, Server
10
+ from acp_sdk.server.types import RunYield, RunYieldResume
11
+
12
+ server = Server()
13
+
14
+
15
+ @server.agent()
16
+ async def awaiting(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
17
+ """Greets and awaits for more data"""
18
+ yield MessagePart(content="Hello!")
19
+ resume = yield MessageAwaitRequest(
20
+ message=Message(parts=[MessagePart(content="Can you provide me with additional configuration?")])
21
+ )
22
+ assert isinstance(resume, MessageAwaitResume)
23
+ yield MessagePart(content=f"Thanks for config: {resume.message}")
24
+
25
+
26
+ server.run()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acp-sdk"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -17,12 +17,12 @@ class AnyModel(BaseModel):
17
17
 
18
18
  class MessagePart(BaseModel):
19
19
  name: Optional[str] = None
20
- content_type: str
20
+ content_type: Optional[str] = "text/plain"
21
21
  content: Optional[str] = None
22
22
  content_encoding: Optional[Literal["plain", "base64"]] = "plain"
23
23
  content_url: Optional[AnyUrl] = None
24
24
 
25
- model_config = ConfigDict(extra="forbid")
25
+ model_config = ConfigDict(extra="allow")
26
26
 
27
27
  def model_post_init(self, __context: Any) -> None:
28
28
  if self.content is None and self.content_url is None:
@@ -48,6 +48,36 @@ class Message(BaseModel):
48
48
  part.content for part in self.parts if part.content is not None and part.content_type == "text/plain"
49
49
  )
50
50
 
51
+ def compress(self) -> "Message":
52
+ def can_be_joined(first: MessagePart, second: MessagePart) -> bool:
53
+ return (
54
+ first.name is None
55
+ and second.name is None
56
+ and first.content_type == "text/plain"
57
+ and second.content_type == "text/plain"
58
+ and first.content_encoding == "plain"
59
+ and second.content_encoding == "plain"
60
+ and first.content_url is None
61
+ and second.content_url is None
62
+ )
63
+
64
+ def join(first: MessagePart, second: MessagePart) -> MessagePart:
65
+ return MessagePart(
66
+ name=None,
67
+ content_type="text/plain",
68
+ content=first.content + second.content,
69
+ content_encoding="plain",
70
+ content_url=None,
71
+ )
72
+
73
+ parts: list[MessagePart] = []
74
+ for part in self.parts:
75
+ if len(parts) > 0 and can_be_joined(parts[-1], part):
76
+ parts[-1] = join(parts[-1], part)
77
+ else:
78
+ parts.append(part)
79
+ return Message(parts=parts)
80
+
51
81
 
52
82
  AgentName = str
53
83
  SessionId = uuid.UUID
@@ -75,12 +105,18 @@ class RunStatus(str, Enum):
75
105
  return self in terminal_states
76
106
 
77
107
 
78
- class AwaitRequest(BaseModel):
79
- type: Literal["placeholder"] = "placeholder"
108
+ class MessageAwaitRequest(BaseModel):
109
+ type: Literal["message"] = "message"
110
+ message: Message
111
+
112
+
113
+ class MessageAwaitResume(BaseModel):
114
+ type: Literal["message"] = "message"
115
+ message: Message
80
116
 
81
117
 
82
- class AwaitResume(BaseModel):
83
- pass
118
+ AwaitRequest = Union[MessageAwaitRequest]
119
+ AwaitResume = Union[MessageAwaitResume]
84
120
 
85
121
 
86
122
  class Run(BaseModel):
@@ -144,6 +144,16 @@ def create_app(*agents: Agent) -> FastAPI:
144
144
  @app.post("/runs/{run_id}")
145
145
  async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
146
146
  bundle = find_run_bundle(run_id)
147
+
148
+ if bundle.run.await_request is None:
149
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"Run {run_id} has no await request")
150
+
151
+ if bundle.run.await_request.type != request.await_resume.type:
152
+ raise HTTPException(
153
+ status_code=status.HTTP_403_FORBIDDEN,
154
+ detail=f"Run {run_id} is expecting resume of type {bundle.run.await_request.type}",
155
+ )
156
+
147
157
  await bundle.resume(request.await_resume)
148
158
  match request.mode:
149
159
  case RunMode.STREAM:
@@ -129,6 +129,8 @@ class RunBundle:
129
129
  run_logger.info("Run resumed")
130
130
  elif isinstance(next, Error):
131
131
  raise ACPError(error=next)
132
+ elif next is None:
133
+ pass # Do nothing
132
134
  else:
133
135
  try:
134
136
  generic = AnyModel.model_validate(next)
@@ -2,5 +2,5 @@ from typing import Any
2
2
 
3
3
  from acp_sdk.models import AwaitRequest, AwaitResume, Message
4
4
 
5
- RunYield = Message | AwaitRequest | dict[str | Any]
5
+ RunYield = Message | AwaitRequest | dict[str | Any] | None
6
6
  RunYieldResume = AwaitResume | None
@@ -4,7 +4,7 @@ from collections.abc import AsyncGenerator, AsyncIterator, Generator
4
4
  from threading import Thread
5
5
 
6
6
  import pytest
7
- from acp_sdk.models import Artifact, AwaitRequest, AwaitResume, Error, ErrorCode, Message, MessagePart
7
+ from acp_sdk.models import Artifact, AwaitResume, Error, ErrorCode, Message, MessageAwaitRequest, MessagePart
8
8
  from acp_sdk.server import Context, Server
9
9
 
10
10
  from e2e.config import Config
@@ -20,8 +20,10 @@ def server() -> Generator[None]:
20
20
  yield message
21
21
 
22
22
  @server.agent()
23
- async def awaiter(inputs: list[Message], context: Context) -> AsyncGenerator[Message | AwaitRequest, AwaitResume]:
24
- yield AwaitRequest()
23
+ async def awaiter(
24
+ inputs: list[Message], context: Context
25
+ ) -> AsyncGenerator[Message | MessageAwaitRequest, AwaitResume]:
26
+ yield MessageAwaitRequest(message=Message(parts=[]))
25
27
  yield MessagePart(content="empty", content_type="text/plain")
26
28
 
27
29
  @server.agent()
@@ -4,9 +4,9 @@ import pytest
4
4
  from acp_sdk.client import Client
5
5
  from acp_sdk.models import (
6
6
  ArtifactEvent,
7
- AwaitResume,
8
7
  ErrorCode,
9
8
  Message,
9
+ MessageAwaitResume,
10
10
  MessageCreatedEvent,
11
11
  MessagePart,
12
12
  RunCompletedEvent,
@@ -16,7 +16,8 @@ from acp_sdk.models import (
16
16
  )
17
17
  from acp_sdk.server import Server
18
18
 
19
- inputs = [Message(parts=[MessagePart(content="Hello!", content_type="text/plain")])]
19
+ inputs = [Message(parts=[MessagePart(content="Hello!")])]
20
+ await_resume = MessageAwaitResume(message=Message(parts=[]))
20
21
 
21
22
 
22
23
  @pytest.mark.asyncio
@@ -69,7 +70,7 @@ async def test_run_resume_sync(server: Server, client: Client) -> None:
69
70
  assert run.status == RunStatus.AWAITING
70
71
  assert run.await_request is not None
71
72
 
72
- run = await client.run_resume_sync(run_id=run.run_id, await_resume=AwaitResume())
73
+ run = await client.run_resume_sync(run_id=run.run_id, await_resume=await_resume)
73
74
  assert run.status == RunStatus.COMPLETED
74
75
 
75
76
 
@@ -79,7 +80,7 @@ async def test_run_resume_async(server: Server, client: Client) -> None:
79
80
  assert run.status == RunStatus.AWAITING
80
81
  assert run.await_request is not None
81
82
 
82
- run = await client.run_resume_async(run_id=run.run_id, await_resume=AwaitResume())
83
+ run = await client.run_resume_async(run_id=run.run_id, await_resume=await_resume)
83
84
  assert run.status == RunStatus.IN_PROGRESS
84
85
 
85
86
 
@@ -89,7 +90,7 @@ async def test_run_resume_stream(server: Server, client: Client) -> None:
89
90
  assert run.status == RunStatus.AWAITING
90
91
  assert run.await_request is not None
91
92
 
92
- event_stream = [event async for event in client.run_resume_stream(run_id=run.run_id, await_resume=AwaitResume())]
93
+ event_stream = [event async for event in client.run_resume_stream(run_id=run.run_id, await_resume=await_resume)]
93
94
  assert isinstance(event_stream[0], RunInProgressEvent)
94
95
  assert isinstance(event_stream[-1], RunCompletedEvent)
95
96
 
File without changes
@@ -0,0 +1,37 @@
1
+ import pytest
2
+ from acp_sdk.models.models import Message, MessagePart
3
+
4
+
5
+ @pytest.mark.parametrize(
6
+ "uncompressed,compressed",
7
+ [
8
+ (
9
+ Message(
10
+ parts=[
11
+ MessagePart(content_type="text/plain", content="Foo"),
12
+ MessagePart(content_type="text/plain", content="Bar"),
13
+ ]
14
+ ),
15
+ Message(parts=[MessagePart(content_type="text/plain", content="FooBar")]),
16
+ ),
17
+ (
18
+ Message(
19
+ parts=[
20
+ MessagePart(content_type="text/plain", content="Foo"),
21
+ MessagePart(content_type="text/html", content="<head>"),
22
+ MessagePart(content_type="text/plain", content="Foo"),
23
+ MessagePart(content_type="text/plain", content="Bar"),
24
+ ]
25
+ ),
26
+ Message(
27
+ parts=[
28
+ MessagePart(content_type="text/plain", content="Foo"),
29
+ MessagePart(content_type="text/html", content="<head>"),
30
+ MessagePart(content_type="text/plain", content="FooBar"),
31
+ ]
32
+ ),
33
+ ),
34
+ ],
35
+ )
36
+ def test_message_compress(uncompressed: Message, compressed: Message) -> None:
37
+ assert uncompressed.compress() == compressed
@@ -1,22 +0,0 @@
1
- import asyncio
2
-
3
- from acp_sdk.client import Client
4
- from acp_sdk.models import (
5
- Message,
6
- MessagePart,
7
- )
8
-
9
-
10
- async def example() -> None:
11
- async with Client(base_url="http://localhost:8000") as client, client.session() as session:
12
- run = await session.run_sync(
13
- agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!", content_type="text/plain")])]
14
- )
15
- run = await session.run_sync(
16
- agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy again!", content_type="text/plain")])]
17
- )
18
- print(run)
19
-
20
-
21
- if __name__ == "__main__":
22
- asyncio.run(example())
@@ -1,25 +0,0 @@
1
- from collections.abc import AsyncGenerator
2
- from typing import Any
3
-
4
- from acp_sdk.models import (
5
- AwaitRequest,
6
- AwaitResume,
7
- Message,
8
- MessagePart,
9
- )
10
- from acp_sdk.server import Context, Server
11
-
12
- server = Server()
13
-
14
-
15
- @server.agent()
16
- async def awaiting(
17
- inputs: list[Message], context: Context
18
- ) -> AsyncGenerator[Message | AwaitRequest | Any, AwaitResume]:
19
- """Greets and awaits for more data"""
20
- yield MessagePart(content="Hello!", content_type="text/plain")
21
- data = yield AwaitRequest()
22
- yield MessagePart(content=f"Thanks for {data}", content_type="text/plain")
23
-
24
-
25
- server.run()
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