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.
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/PKG-INFO +1 -1
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/pyproject.toml +1 -1
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/models.py +16 -4
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/agent.py +11 -3
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/types.py +1 -1
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/fixtures/server.py +7 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/test_suites/test_runs.py +21 -4
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/models/test_models.py +62 -2
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/.gitignore +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/.python-version +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/README.md +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/.gitignore +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/Makefile +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/conf.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/index.rst +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/docs/make.bat +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/pytest.ini +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/client.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/types.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/client/utils.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/instrumentation.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/errors.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/models/schemas.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/py.typed +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/app.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/bundle.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/context.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/errors.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/logging.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/server.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/session.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/telemetry.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/server/utils.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/src/acp_sdk/version.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/conftest.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/config.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/fixtures/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/fixtures/client.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/test_suites/__init__.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/e2e/test_suites/test_discovery.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/client/test_client.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/client/test_utils.py +0 -0
- {acp_sdk-0.8.2 → acp_sdk-0.8.4}/tests/unit/models/__init__.py +0 -0
@@ -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
|
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
|
-
|
82
|
-
|
83
|
-
|
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.
|
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
|
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
|
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
|