acp-sdk 0.8.2__tar.gz → 0.8.4__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 (48) hide show
  1. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/PKG-INFO +1 -1
  2. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/pyproject.toml +1 -1
  3. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/models.py +16 -4
  4. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/agent.py +11 -3
  5. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/types.py +1 -1
  6. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/fixtures/server.py +7 -0
  7. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/test_suites/test_runs.py +21 -4
  8. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/models/test_models.py +62 -2
  9. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/.gitignore +0 -0
  10. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/.python-version +0 -0
  11. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/README.md +0 -0
  12. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/.gitignore +0 -0
  13. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/Makefile +0 -0
  14. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/conf.py +0 -0
  15. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/index.rst +0 -0
  16. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/make.bat +0 -0
  17. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/pytest.ini +0 -0
  18. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/__init__.py +0 -0
  19. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/__init__.py +0 -0
  20. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/client.py +0 -0
  21. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/types.py +0 -0
  22. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/utils.py +0 -0
  23. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/instrumentation.py +0 -0
  24. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/__init__.py +0 -0
  25. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/errors.py +0 -0
  26. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/schemas.py +0 -0
  27. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/py.typed +0 -0
  28. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/__init__.py +0 -0
  29. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/app.py +0 -0
  30. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/bundle.py +0 -0
  31. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/context.py +0 -0
  32. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/errors.py +0 -0
  33. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/logging.py +0 -0
  34. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/server.py +0 -0
  35. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/session.py +0 -0
  36. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/telemetry.py +0 -0
  37. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/utils.py +0 -0
  38. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/version.py +0 -0
  39. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/conftest.py +0 -0
  40. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/__init__.py +0 -0
  41. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/config.py +0 -0
  42. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/fixtures/__init__.py +0 -0
  43. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/fixtures/client.py +0 -0
  44. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/test_suites/__init__.py +0 -0
  45. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/test_suites/test_discovery.py +0 -0
  46. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/client/test_client.py +0 -0
  47. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/client/test_utils.py +0 -0
  48. {acp_sdk-0.8.2 → acp_sdk-0.8.4}/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.8.2
3
+ Version: 0.8.4
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.8.2"
3
+ version = "0.8.4"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import uuid
2
3
  from datetime import datetime, timezone
3
4
  from enum import Enum
@@ -5,7 +6,7 @@ from typing import Any, Literal, Optional, Union
5
6
 
6
7
  from pydantic import AnyUrl, BaseModel, ConfigDict, Field
7
8
 
8
- from acp_sdk.models.errors import Error
9
+ from acp_sdk.models.errors import ACPError, Error
9
10
 
10
11
 
11
12
  class AnyModel(BaseModel):
@@ -95,7 +96,7 @@ class Artifact(MessagePart):
95
96
 
96
97
  class Message(BaseModel):
97
98
  parts: list[MessagePart]
98
- created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
99
+ created_at: datetime | None = Field(default_factory=lambda: datetime.now(timezone.utc))
99
100
  completed_at: datetime | None = Field(default_factory=lambda: datetime.now(timezone.utc))
100
101
 
101
102
  def __add__(self, other: "Message") -> "Message":
@@ -103,8 +104,10 @@ class Message(BaseModel):
103
104
  raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
104
105
  return Message(
105
106
  parts=self.parts + other.parts,
106
- created_at=min(self.created_at, other.created_at),
107
- completed_at=max(self.completed_at, other.completed_at),
107
+ created_at=min(self.created_at, other.created_at) if self.created_at and other.created_at else None,
108
+ completed_at=max(self.completed_at, other.completed_at)
109
+ if self.completed_at and other.completed_at
110
+ else None,
108
111
  )
109
112
 
110
113
  def __str__(self) -> str:
@@ -194,6 +197,15 @@ class Run(BaseModel):
194
197
  created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
195
198
  finished_at: datetime | None = None
196
199
 
200
+ def raise_for_status(self) -> "Run":
201
+ match self.status:
202
+ case RunStatus.CANCELLED:
203
+ raise asyncio.CancelledError()
204
+ case RunStatus.FAILED:
205
+ raise ACPError(error=self.error)
206
+ case _:
207
+ return self
208
+
197
209
 
198
210
  class MessageCreatedEvent(BaseModel):
199
211
  type: Literal["message.created"] = "message.created"
@@ -58,13 +58,13 @@ class Agent(abc.ABC):
58
58
  run = asyncio.get_running_loop().run_in_executor(executor, self._run_func, input, context)
59
59
 
60
60
  try:
61
- while True:
61
+ while not run.done() or yield_queue.async_q.qsize() > 0:
62
62
  value = yield await yield_queue.async_q.get()
63
+ if isinstance(value, Exception):
64
+ raise value
63
65
  await yield_resume_queue.async_q.put(value)
64
66
  except janus.AsyncQueueShutDown:
65
67
  pass
66
- finally:
67
- await run # Raise exceptions
68
68
 
69
69
  async def _run_async_gen(self, input: list[Message], context: Context) -> None:
70
70
  try:
@@ -74,12 +74,16 @@ class Agent(abc.ABC):
74
74
  value = await context.yield_async(await gen.asend(value))
75
75
  except StopAsyncIteration:
76
76
  pass
77
+ except Exception as e:
78
+ await context.yield_async(e)
77
79
  finally:
78
80
  context.shutdown()
79
81
 
80
82
  async def _run_coro(self, input: list[Message], context: Context) -> None:
81
83
  try:
82
84
  await context.yield_async(await self.run(input, context))
85
+ except Exception as e:
86
+ await context.yield_async(e)
83
87
  finally:
84
88
  context.shutdown()
85
89
 
@@ -91,12 +95,16 @@ class Agent(abc.ABC):
91
95
  value = context.yield_sync(gen.send(value))
92
96
  except StopIteration:
93
97
  pass
98
+ except Exception as e:
99
+ context.yield_sync(e)
94
100
  finally:
95
101
  context.shutdown()
96
102
 
97
103
  def _run_func(self, input: list[Message], context: Context) -> None:
98
104
  try:
99
105
  context.yield_sync(self.run(input, context))
106
+ except Exception as e:
107
+ context.yield_sync(e)
100
108
  finally:
101
109
  context.shutdown()
102
110
 
@@ -5,5 +5,5 @@ from pydantic import BaseModel
5
5
  from acp_sdk.models import AwaitRequest, AwaitResume, Message
6
6
  from acp_sdk.models.models import MessagePart
7
7
 
8
- RunYield = Message | MessagePart | str | AwaitRequest | BaseModel | dict[str | Any] | None
8
+ RunYield = Message | MessagePart | str | AwaitRequest | BaseModel | dict[str | Any] | None | Exception
9
9
  RunYieldResume = AwaitResume | None
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import base64
2
3
  import time
3
4
  from collections.abc import AsyncGenerator, AsyncIterator, Generator
@@ -21,6 +22,12 @@ def server(request: pytest.FixtureRequest) -> Generator[None]:
21
22
  for message in input:
22
23
  yield message
23
24
 
25
+ @server.agent()
26
+ async def slow_echo(input: list[Message], context: Context) -> AsyncIterator[Message]:
27
+ for message in input:
28
+ await asyncio.sleep(1)
29
+ yield message
30
+
24
31
  @server.agent()
25
32
  async def awaiter(
26
33
  input: list[Message], context: Context
@@ -5,18 +5,20 @@ from datetime import timedelta
5
5
  import pytest
6
6
  from acp_sdk.client import Client
7
7
  from acp_sdk.models import (
8
+ ACPError,
9
+ AgentName,
8
10
  ArtifactEvent,
9
11
  ErrorCode,
10
12
  Message,
11
13
  MessageAwaitResume,
12
14
  MessagePart,
13
15
  MessagePartEvent,
16
+ RunCancelledEvent,
14
17
  RunCompletedEvent,
15
18
  RunCreatedEvent,
16
19
  RunInProgressEvent,
17
20
  RunStatus,
18
21
  )
19
- from acp_sdk.models.errors import ACPError
20
22
  from acp_sdk.server import Server
21
23
 
22
24
  input = [Message(parts=[MessagePart(content="Hello!")])]
@@ -78,11 +80,26 @@ async def test_failure(server: Server, client: Client) -> None:
78
80
 
79
81
 
80
82
  @pytest.mark.asyncio
81
- async def test_run_cancel(server: Server, client: Client) -> None:
82
- run = await client.run_sync(agent="awaiter", input=input)
83
- assert run.status == RunStatus.AWAITING
83
+ @pytest.mark.parametrize("agent", ["awaiter", "slow_echo"])
84
+ async def test_run_cancel(server: Server, client: Client, agent: AgentName) -> None:
85
+ run = await client.run_async(agent=agent, input=input)
84
86
  run = await client.run_cancel(run_id=run.run_id)
85
87
  assert run.status == RunStatus.CANCELLING
88
+ await asyncio.sleep(2)
89
+ run = await client.run_status(run_id=run.run_id)
90
+ assert run.status == RunStatus.CANCELLED
91
+
92
+
93
+ @pytest.mark.asyncio
94
+ @pytest.mark.parametrize("agent", ["slow_echo"])
95
+ async def test_run_cancel_stream(server: Server, client: Client, agent: AgentName) -> None:
96
+ last_event = None
97
+ async for event in client.run_stream(agent=agent, input=input):
98
+ last_event = event
99
+ if isinstance(event, RunCreatedEvent):
100
+ run = await client.run_cancel(run_id=event.run.run_id)
101
+ assert run.status == RunStatus.CANCELLING
102
+ assert isinstance(last_event, RunCancelledEvent)
86
103
 
87
104
 
88
105
  @pytest.mark.asyncio
@@ -1,5 +1,8 @@
1
+ import asyncio
2
+
1
3
  import pytest
2
- from acp_sdk.models.models import Message, MessagePart
4
+ from acp_sdk.models.errors import ACPError, Error, ErrorCode
5
+ from acp_sdk.models.models import Message, MessagePart, Run, RunStatus
3
6
 
4
7
  timestamp = "2021-09-09T22:02:47.89Z"
5
8
 
@@ -26,7 +29,27 @@ timestamp = "2021-09-09T22:02:47.89Z"
26
29
  created_at=timestamp,
27
30
  completed_at=timestamp,
28
31
  ),
29
- )
32
+ ),
33
+ (
34
+ Message(
35
+ parts=[MessagePart(content_type="text/plain", content="Foo")],
36
+ created_at=None,
37
+ completed_at=timestamp,
38
+ ),
39
+ Message(
40
+ parts=[MessagePart(content_type="text/plain", content="Bar")],
41
+ created_at=timestamp,
42
+ completed_at=None,
43
+ ),
44
+ Message(
45
+ parts=[
46
+ MessagePart(content_type="text/plain", content="Foo"),
47
+ MessagePart(content_type="text/plain", content="Bar"),
48
+ ],
49
+ created_at=None,
50
+ completed_at=None,
51
+ ),
52
+ ),
30
53
  ],
31
54
  )
32
55
  def test_message_add(first: Message, second: Message, result: Message) -> None:
@@ -76,3 +99,40 @@ def test_message_add(first: Message, second: Message, result: Message) -> None:
76
99
  )
77
100
  def test_message_compress(uncompressed: Message, compressed: Message) -> None:
78
101
  assert uncompressed.compress() == compressed
102
+
103
+
104
+ @pytest.mark.parametrize(
105
+ "run,error",
106
+ [
107
+ (
108
+ Run(agent_name="foo", status=RunStatus.CANCELLED),
109
+ asyncio.CancelledError,
110
+ ),
111
+ (
112
+ Run(
113
+ agent_name="foo",
114
+ status=RunStatus.FAILED,
115
+ error=Error(code=ErrorCode.SERVER_ERROR, message="Unspecified"),
116
+ ),
117
+ ACPError,
118
+ ),
119
+ (
120
+ Run(agent_name="foo"),
121
+ None,
122
+ ),
123
+ (
124
+ Run(agent_name="foo", status=RunStatus.IN_PROGRESS),
125
+ None,
126
+ ),
127
+ (
128
+ Run(agent_name="foo", status=RunStatus.COMPLETED),
129
+ None,
130
+ ),
131
+ ],
132
+ )
133
+ def test_run_raise_on_status_raise(run: Run, error: type[Exception] | None) -> None:
134
+ if error:
135
+ with pytest.raises(error):
136
+ run.raise_for_status()
137
+ else:
138
+ run.raise_for_status()
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
File without changes