acp-sdk 0.8.3__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.3 → acp_sdk-0.8.4}/PKG-INFO +1 -1
  2. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/pyproject.toml +1 -1
  3. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/models/models.py +11 -1
  4. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/agent.py +11 -3
  5. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/types.py +1 -1
  6. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/fixtures/server.py +7 -0
  7. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/test_suites/test_runs.py +21 -4
  8. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/unit/models/test_models.py +41 -1
  9. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/.gitignore +0 -0
  10. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/.python-version +0 -0
  11. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/README.md +0 -0
  12. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/docs/.gitignore +0 -0
  13. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/docs/Makefile +0 -0
  14. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/docs/conf.py +0 -0
  15. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/docs/index.rst +0 -0
  16. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/docs/make.bat +0 -0
  17. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/pytest.ini +0 -0
  18. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/__init__.py +0 -0
  19. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/client/__init__.py +0 -0
  20. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/client/client.py +0 -0
  21. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/client/types.py +0 -0
  22. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/client/utils.py +0 -0
  23. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/instrumentation.py +0 -0
  24. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/models/__init__.py +0 -0
  25. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/models/errors.py +0 -0
  26. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/models/schemas.py +0 -0
  27. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/py.typed +0 -0
  28. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/__init__.py +0 -0
  29. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/app.py +0 -0
  30. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/bundle.py +0 -0
  31. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/context.py +0 -0
  32. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/errors.py +0 -0
  33. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/logging.py +0 -0
  34. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/server.py +0 -0
  35. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/session.py +0 -0
  36. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/telemetry.py +0 -0
  37. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/server/utils.py +0 -0
  38. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/src/acp_sdk/version.py +0 -0
  39. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/conftest.py +0 -0
  40. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/__init__.py +0 -0
  41. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/config.py +0 -0
  42. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/fixtures/__init__.py +0 -0
  43. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/fixtures/client.py +0 -0
  44. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/test_suites/__init__.py +0 -0
  45. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/e2e/test_suites/test_discovery.py +0 -0
  46. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/unit/client/test_client.py +0 -0
  47. {acp_sdk-0.8.3 → acp_sdk-0.8.4}/tests/unit/client/test_utils.py +0 -0
  48. {acp_sdk-0.8.3 → 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.3
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.3"
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):
@@ -196,6 +197,15 @@ class Run(BaseModel):
196
197
  created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
197
198
  finished_at: datetime | None = None
198
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
+
199
209
 
200
210
  class MessageCreatedEvent(BaseModel):
201
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
 
@@ -96,3 +99,40 @@ def test_message_add(first: Message, second: Message, result: Message) -> None:
96
99
  )
97
100
  def test_message_compress(uncompressed: Message, compressed: Message) -> None:
98
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