acp-sdk 0.1.0rc7__tar.gz → 0.2.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.
- acp_sdk-0.2.0/PKG-INFO +113 -0
- acp_sdk-0.2.0/README.md +91 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/examples/clients/advanced.py +3 -3
- acp_sdk-0.2.0/examples/clients/session.py +18 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/examples/clients/simple.py +5 -3
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/examples/clients/stream.py +4 -2
- acp_sdk-0.2.0/examples/servers/awaiting.py +25 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/examples/servers/echo.py +4 -4
- acp_sdk-0.2.0/examples/servers/standalone.py +25 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/pyproject.toml +4 -2
- acp_sdk-0.2.0/pytest.ini +5 -0
- acp_sdk-0.2.0/src/acp_sdk/__init__.py +2 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/client/client.py +53 -14
- acp_sdk-0.2.0/src/acp_sdk/models/models.py +170 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/models/schemas.py +1 -1
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/__init__.py +1 -0
- acp_sdk-0.2.0/src/acp_sdk/server/agent.py +178 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/app.py +17 -15
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/bundle.py +62 -33
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/server.py +11 -88
- acp_sdk-0.2.0/src/acp_sdk/server/session.py +21 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/telemetry.py +7 -2
- acp_sdk-0.2.0/src/acp_sdk/server/types.py +6 -0
- acp_sdk-0.2.0/src/acp_sdk/version.py +3 -0
- acp_sdk-0.2.0/tests/conftest.py +4 -0
- acp_sdk-0.2.0/tests/e2e/config.py +2 -0
- acp_sdk-0.2.0/tests/e2e/fixtures/__init__.py +0 -0
- acp_sdk-0.2.0/tests/e2e/fixtures/client.py +12 -0
- acp_sdk-0.2.0/tests/e2e/fixtures/server.py +83 -0
- acp_sdk-0.2.0/tests/e2e/test_suites/__init__.py +0 -0
- acp_sdk-0.2.0/tests/e2e/test_suites/test_runs.py +191 -0
- acp_sdk-0.1.0rc7/PKG-INFO +0 -74
- acp_sdk-0.1.0rc7/README.md +0 -55
- acp_sdk-0.1.0rc7/examples/servers/awaiting.py +0 -23
- acp_sdk-0.1.0rc7/examples/servers/multi-echo.py +0 -57
- acp_sdk-0.1.0rc7/src/acp_sdk/__init__.py +0 -1
- acp_sdk-0.1.0rc7/src/acp_sdk/models/models.py +0 -181
- acp_sdk-0.1.0rc7/src/acp_sdk/server/agent.py +0 -105
- acp_sdk-0.1.0rc7/src/acp_sdk/server/types.py +0 -6
- acp_sdk-0.1.0rc7/tests/test_e2e.py +0 -113
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/.gitignore +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/.python-version +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/client/__init__.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/models/__init__.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/models/errors.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/py.typed +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/context.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/errors.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/logging.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.2.0}/src/acp_sdk/server/utils.py +0 -0
- {acp_sdk-0.1.0rc7/tests → acp_sdk-0.2.0/tests/e2e}/__init__.py +0 -0
acp_sdk-0.2.0/PKG-INFO
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: acp-sdk
|
3
|
+
Version: 0.2.0
|
4
|
+
Summary: Agent Communication Protocol SDK
|
5
|
+
Author: IBM Corp.
|
6
|
+
Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
|
7
|
+
License-Expression: Apache-2.0
|
8
|
+
Requires-Python: <4.0,>=3.11
|
9
|
+
Requires-Dist: opentelemetry-api>=1.31.1
|
10
|
+
Requires-Dist: pydantic>=2.11.1
|
11
|
+
Provides-Extra: client
|
12
|
+
Requires-Dist: httpx-sse>=0.4.0; extra == 'client'
|
13
|
+
Requires-Dist: httpx>=0.28.1; extra == 'client'
|
14
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == 'client'
|
15
|
+
Provides-Extra: server
|
16
|
+
Requires-Dist: fastapi[standard]>=0.115.8; extra == 'server'
|
17
|
+
Requires-Dist: janus>=2.0.0; extra == 'server'
|
18
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.31.1; extra == 'server'
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == 'server'
|
20
|
+
Requires-Dist: opentelemetry-sdk>=1.31.1; extra == 'server'
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
|
23
|
+
# Agent Communication Protocol SDK for Python
|
24
|
+
|
25
|
+
Agent Communication Protocol SDK for Python provides allows developers to serve and consume agents over the Agent Communication Protocol.
|
26
|
+
|
27
|
+
## Prerequisites
|
28
|
+
|
29
|
+
✅ Python >= 3.11
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
Install to use client:
|
34
|
+
|
35
|
+
```shell
|
36
|
+
pip install acp-sdk[client]
|
37
|
+
```
|
38
|
+
|
39
|
+
Install to use server:
|
40
|
+
|
41
|
+
```shell
|
42
|
+
pip install acp-sdk[server]
|
43
|
+
```
|
44
|
+
|
45
|
+
Install to use models only:
|
46
|
+
|
47
|
+
```shell
|
48
|
+
pip install acp-sdk
|
49
|
+
```
|
50
|
+
|
51
|
+
## Overview
|
52
|
+
|
53
|
+
### Core
|
54
|
+
|
55
|
+
The core of the SDK exposes [pydantic](https://docs.pydantic.dev/) data models corresponding to REST API requests, responses, resources, events and errors.
|
56
|
+
|
57
|
+
|
58
|
+
### Client
|
59
|
+
|
60
|
+
The `client` submodule exposes [httpx](https://www.python-httpx.org/) based client with simple methods for communication over ACP.
|
61
|
+
|
62
|
+
```python
|
63
|
+
async with Client(base_url="http://localhost:8000") as client:
|
64
|
+
run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
|
65
|
+
print(run)
|
66
|
+
|
67
|
+
```
|
68
|
+
|
69
|
+
### Server
|
70
|
+
|
71
|
+
The `server` submodule exposes `Agent` class and `agent` decorator together with [fastapi](https://fastapi.tiangolo.com/) application factory, making it easy to expose agents over ACP. Additionaly, it exposes [uvicorn](https://www.uvicorn.org/) based server to serve agents with set up logging, [opentelemetry](https://opentelemetry.io/) and more.
|
72
|
+
|
73
|
+
```python
|
74
|
+
server = Server()
|
75
|
+
|
76
|
+
@server.agent()
|
77
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
78
|
+
"""Echoes everything"""
|
79
|
+
for message in inputs:
|
80
|
+
yield {"thought": "I should echo everyting"}
|
81
|
+
await asyncio.sleep(0.5)
|
82
|
+
yield message
|
83
|
+
|
84
|
+
|
85
|
+
server.run()
|
86
|
+
```
|
87
|
+
|
88
|
+
➡️ Explore more in our [examples library](/python/examples).
|
89
|
+
|
90
|
+
## Architecture
|
91
|
+
|
92
|
+
The architecture of the SDK is outlined in the following segment. It focuses on central parts of the SDK without going into much detail.
|
93
|
+
|
94
|
+
### Models
|
95
|
+
|
96
|
+
The core of the SDK contains pydantic models for requests, responses, resources, events and errors. Users of the SDK are meant to use these models directly or indirectly.
|
97
|
+
|
98
|
+
### Server
|
99
|
+
|
100
|
+
The server module consists of 3 parts:
|
101
|
+
|
102
|
+
1. Agent interface
|
103
|
+
2. FastAPI application factory
|
104
|
+
3. Uvicorn based server
|
105
|
+
|
106
|
+
Each part builds on top of the previous one. Not all parts need to be used, e.g. users are advised to bring their own ASGI server for production deployments.
|
107
|
+
|
108
|
+
### Client
|
109
|
+
|
110
|
+
The client module consists of httpx based client with session support. The client is meant to be thin and mimic the REST API. Exception is session management which has been abstracted into a context manager.
|
111
|
+
|
112
|
+
|
113
|
+
|
acp_sdk-0.2.0/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Agent Communication Protocol SDK for Python
|
2
|
+
|
3
|
+
Agent Communication Protocol SDK for Python provides allows developers to serve and consume agents over the Agent Communication Protocol.
|
4
|
+
|
5
|
+
## Prerequisites
|
6
|
+
|
7
|
+
✅ Python >= 3.11
|
8
|
+
|
9
|
+
## Installation
|
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:
|
24
|
+
|
25
|
+
```shell
|
26
|
+
pip install acp-sdk
|
27
|
+
```
|
28
|
+
|
29
|
+
## Overview
|
30
|
+
|
31
|
+
### Core
|
32
|
+
|
33
|
+
The core of the SDK exposes [pydantic](https://docs.pydantic.dev/) data models corresponding to REST API requests, responses, resources, events and errors.
|
34
|
+
|
35
|
+
|
36
|
+
### Client
|
37
|
+
|
38
|
+
The `client` submodule exposes [httpx](https://www.python-httpx.org/) based client with simple methods for communication over ACP.
|
39
|
+
|
40
|
+
```python
|
41
|
+
async with Client(base_url="http://localhost:8000") as client:
|
42
|
+
run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
|
43
|
+
print(run)
|
44
|
+
|
45
|
+
```
|
46
|
+
|
47
|
+
### Server
|
48
|
+
|
49
|
+
The `server` submodule exposes `Agent` class and `agent` decorator together with [fastapi](https://fastapi.tiangolo.com/) application factory, making it easy to expose agents over ACP. Additionaly, it exposes [uvicorn](https://www.uvicorn.org/) based server to serve agents with set up logging, [opentelemetry](https://opentelemetry.io/) and more.
|
50
|
+
|
51
|
+
```python
|
52
|
+
server = Server()
|
53
|
+
|
54
|
+
@server.agent()
|
55
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
56
|
+
"""Echoes everything"""
|
57
|
+
for message in inputs:
|
58
|
+
yield {"thought": "I should echo everyting"}
|
59
|
+
await asyncio.sleep(0.5)
|
60
|
+
yield message
|
61
|
+
|
62
|
+
|
63
|
+
server.run()
|
64
|
+
```
|
65
|
+
|
66
|
+
➡️ Explore more in our [examples library](/python/examples).
|
67
|
+
|
68
|
+
## Architecture
|
69
|
+
|
70
|
+
The architecture of the SDK is outlined in the following segment. It focuses on central parts of the SDK without going into much detail.
|
71
|
+
|
72
|
+
### Models
|
73
|
+
|
74
|
+
The core of the SDK contains pydantic models for requests, responses, resources, events and errors. Users of the SDK are meant to use these models directly or indirectly.
|
75
|
+
|
76
|
+
### Server
|
77
|
+
|
78
|
+
The server module consists of 3 parts:
|
79
|
+
|
80
|
+
1. Agent interface
|
81
|
+
2. FastAPI application factory
|
82
|
+
3. Uvicorn based server
|
83
|
+
|
84
|
+
Each part builds on top of the previous one. Not all parts need to be used, e.g. users are advised to bring their own ASGI server for production deployments.
|
85
|
+
|
86
|
+
### Client
|
87
|
+
|
88
|
+
The client module consists of httpx based client with session support. The client is meant to be thin and mimic the REST API. Exception is session management which has been abstracted into a context manager.
|
89
|
+
|
90
|
+
|
91
|
+
|
@@ -2,7 +2,7 @@ import asyncio
|
|
2
2
|
|
3
3
|
import httpx
|
4
4
|
from acp_sdk.client.client import Client
|
5
|
-
from acp_sdk.models import Message,
|
5
|
+
from acp_sdk.models import Message, MessagePart
|
6
6
|
|
7
7
|
|
8
8
|
async def example() -> None:
|
@@ -13,8 +13,8 @@ async def example() -> None:
|
|
13
13
|
# Additional client configuration
|
14
14
|
)
|
15
15
|
) as client:
|
16
|
-
run = await client.run_sync(agent="echo",
|
17
|
-
print(run
|
16
|
+
run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
|
17
|
+
print(run)
|
18
18
|
|
19
19
|
|
20
20
|
if __name__ == "__main__":
|
@@ -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())
|
@@ -3,14 +3,16 @@ import asyncio
|
|
3
3
|
from acp_sdk.client import Client
|
4
4
|
from acp_sdk.models import (
|
5
5
|
Message,
|
6
|
-
|
6
|
+
MessagePart,
|
7
7
|
)
|
8
8
|
|
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
|
-
|
12
|
+
run = await client.run_sync(
|
13
|
+
agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!", content_type="text/plain")])]
|
14
|
+
)
|
15
|
+
print(run.outputs)
|
14
16
|
|
15
17
|
|
16
18
|
if __name__ == "__main__":
|
@@ -1,12 +1,14 @@
|
|
1
1
|
import asyncio
|
2
2
|
|
3
3
|
from acp_sdk.client import Client
|
4
|
-
from acp_sdk.models import Message,
|
4
|
+
from acp_sdk.models import Message, MessagePart
|
5
5
|
|
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(
|
9
|
+
async for event in client.run_stream(
|
10
|
+
agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!", content_type="text/plain")])]
|
11
|
+
):
|
10
12
|
print(event)
|
11
13
|
|
12
14
|
|
@@ -0,0 +1,25 @@
|
|
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()
|
@@ -10,13 +10,13 @@ server = Server()
|
|
10
10
|
|
11
11
|
|
12
12
|
@server.agent()
|
13
|
-
async def echo(
|
13
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
14
14
|
"""Echoes everything"""
|
15
|
-
for
|
15
|
+
for message in inputs:
|
16
16
|
await asyncio.sleep(0.5)
|
17
17
|
yield {"thought": "I should echo everyting"}
|
18
18
|
await asyncio.sleep(0.5)
|
19
|
-
yield
|
19
|
+
yield message
|
20
20
|
|
21
21
|
|
22
|
-
server()
|
22
|
+
server.run()
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from collections.abc import AsyncGenerator
|
2
|
+
|
3
|
+
from acp_sdk.models import (
|
4
|
+
Message,
|
5
|
+
)
|
6
|
+
from acp_sdk.server import RunYield, RunYieldResume, agent, create_app
|
7
|
+
|
8
|
+
# This example demonstrates how to serve agents with you own server
|
9
|
+
|
10
|
+
|
11
|
+
@agent()
|
12
|
+
async def echo(inputs: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
|
13
|
+
"""Echoes everything"""
|
14
|
+
for message in inputs:
|
15
|
+
yield message
|
16
|
+
|
17
|
+
|
18
|
+
app = create_app(echo)
|
19
|
+
|
20
|
+
# The app can now be used with any ASGI server
|
21
|
+
|
22
|
+
# Run with
|
23
|
+
# 1. fastapi run examples/servers/standalone.py
|
24
|
+
# 2. uvicorn examples.servers.standalone:app
|
25
|
+
# ...
|
@@ -1,9 +1,11 @@
|
|
1
1
|
[project]
|
2
2
|
name = "acp-sdk"
|
3
|
-
version = "0.
|
3
|
+
version = "0.2.0"
|
4
4
|
description = "Agent Communication Protocol SDK"
|
5
|
+
license = "Apache-2.0"
|
5
6
|
readme = "README.md"
|
6
|
-
authors = []
|
7
|
+
authors = [{ name = "IBM Corp." }]
|
8
|
+
maintainers = [{ name = "Tomas Pilar", email = "thomas7pilar@gmail.com" }]
|
7
9
|
requires-python = ">=3.11, <4.0"
|
8
10
|
dependencies = ["opentelemetry-api>=1.31.1", "pydantic>=2.11.1"]
|
9
11
|
|
acp_sdk-0.2.0/pytest.ini
ADDED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
import uuid
|
2
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
3
|
+
from contextlib import asynccontextmanager
|
2
4
|
from types import TracebackType
|
3
5
|
from typing import Self
|
4
6
|
|
@@ -15,22 +17,31 @@ from acp_sdk.models import (
|
|
15
17
|
AgentsListResponse,
|
16
18
|
AwaitResume,
|
17
19
|
Error,
|
20
|
+
Event,
|
18
21
|
Message,
|
19
22
|
Run,
|
20
23
|
RunCancelResponse,
|
24
|
+
RunCreatedEvent,
|
21
25
|
RunCreateRequest,
|
22
26
|
RunCreateResponse,
|
23
|
-
RunEvent,
|
24
27
|
RunId,
|
25
28
|
RunMode,
|
26
29
|
RunResumeRequest,
|
27
30
|
RunResumeResponse,
|
31
|
+
SessionId,
|
28
32
|
)
|
29
33
|
|
30
34
|
|
31
35
|
class Client:
|
32
|
-
def __init__(
|
36
|
+
def __init__(
|
37
|
+
self,
|
38
|
+
*,
|
39
|
+
base_url: httpx.URL | str = "",
|
40
|
+
session_id: SessionId | None = None,
|
41
|
+
client: httpx.AsyncClient | None = None,
|
42
|
+
) -> None:
|
33
43
|
self.base_url = base_url
|
44
|
+
self.session_id = session_id
|
34
45
|
|
35
46
|
self._client = self._init_client(client)
|
36
47
|
|
@@ -51,6 +62,10 @@ class Client:
|
|
51
62
|
) -> None:
|
52
63
|
await self._client.__aexit__(exc_type, exc_value, traceback)
|
53
64
|
|
65
|
+
@asynccontextmanager
|
66
|
+
async def session(self, session_id: SessionId | None = None) -> AsyncGenerator[Self]:
|
67
|
+
yield Client(client=self._client, session_id=session_id or uuid.uuid4())
|
68
|
+
|
54
69
|
async def agents(self) -> AsyncIterator[Agent]:
|
55
70
|
response = await self._client.get("/agents")
|
56
71
|
self._raise_error(response)
|
@@ -62,30 +77,51 @@ class Client:
|
|
62
77
|
self._raise_error(response)
|
63
78
|
return AgentReadResponse.model_validate(response.json())
|
64
79
|
|
65
|
-
async def run_sync(self, *, agent: AgentName,
|
80
|
+
async def run_sync(self, *, agent: AgentName, inputs: list[Message]) -> Run:
|
66
81
|
response = await self._client.post(
|
67
82
|
"/runs",
|
68
|
-
|
83
|
+
content=RunCreateRequest(
|
84
|
+
agent_name=agent,
|
85
|
+
inputs=inputs,
|
86
|
+
mode=RunMode.SYNC,
|
87
|
+
session_id=self.session_id,
|
88
|
+
).model_dump_json(),
|
69
89
|
)
|
70
90
|
self._raise_error(response)
|
71
|
-
|
91
|
+
response = RunCreateResponse.model_validate(response.json())
|
92
|
+
self._set_session(response)
|
93
|
+
return response
|
72
94
|
|
73
|
-
async def run_async(self, *, agent: AgentName,
|
95
|
+
async def run_async(self, *, agent: AgentName, inputs: list[Message]) -> Run:
|
74
96
|
response = await self._client.post(
|
75
97
|
"/runs",
|
76
|
-
|
98
|
+
content=RunCreateRequest(
|
99
|
+
agent_name=agent,
|
100
|
+
inputs=inputs,
|
101
|
+
mode=RunMode.ASYNC,
|
102
|
+
session_id=self.session_id,
|
103
|
+
).model_dump_json(),
|
77
104
|
)
|
78
105
|
self._raise_error(response)
|
79
|
-
|
106
|
+
response = RunCreateResponse.model_validate(response.json())
|
107
|
+
self._set_session(response)
|
108
|
+
return response
|
80
109
|
|
81
|
-
async def run_stream(self, *, agent: AgentName,
|
110
|
+
async def run_stream(self, *, agent: AgentName, inputs: list[Message]) -> AsyncIterator[Event]:
|
82
111
|
async with aconnect_sse(
|
83
112
|
self._client,
|
84
113
|
"POST",
|
85
114
|
"/runs",
|
86
|
-
|
115
|
+
content=RunCreateRequest(
|
116
|
+
agent_name=agent,
|
117
|
+
inputs=inputs,
|
118
|
+
mode=RunMode.STREAM,
|
119
|
+
session_id=self.session_id,
|
120
|
+
).model_dump_json(),
|
87
121
|
) as event_source:
|
88
122
|
async for event in self._validate_stream(event_source):
|
123
|
+
if isinstance(event, RunCreatedEvent):
|
124
|
+
self._set_session(event.run)
|
89
125
|
yield event
|
90
126
|
|
91
127
|
async def run_status(self, *, run_id: RunId) -> Run:
|
@@ -114,7 +150,7 @@ class Client:
|
|
114
150
|
self._raise_error(response)
|
115
151
|
return RunResumeResponse.model_validate(response.json())
|
116
152
|
|
117
|
-
async def run_resume_stream(self, *, run_id: RunId, await_: AwaitResume) -> AsyncIterator[
|
153
|
+
async def run_resume_stream(self, *, run_id: RunId, await_: AwaitResume) -> AsyncIterator[Event]:
|
118
154
|
async with aconnect_sse(
|
119
155
|
self._client,
|
120
156
|
"POST",
|
@@ -127,9 +163,9 @@ class Client:
|
|
127
163
|
async def _validate_stream(
|
128
164
|
self,
|
129
165
|
event_source: EventSource,
|
130
|
-
) -> AsyncIterator[
|
166
|
+
) -> AsyncIterator[Event]:
|
131
167
|
async for event in event_source.aiter_sse():
|
132
|
-
event = TypeAdapter(
|
168
|
+
event = TypeAdapter(Event).validate_json(event.data)
|
133
169
|
yield event
|
134
170
|
|
135
171
|
def _raise_error(self, response: httpx.Response) -> None:
|
@@ -137,3 +173,6 @@ class Client:
|
|
137
173
|
response.raise_for_status()
|
138
174
|
except httpx.HTTPError:
|
139
175
|
raise ACPError(Error.model_validate(response.json()))
|
176
|
+
|
177
|
+
def _set_session(self, run: Run) -> None:
|
178
|
+
self.session_id = run.session_id
|
@@ -0,0 +1,170 @@
|
|
1
|
+
import uuid
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Any, Literal, Optional, Union
|
4
|
+
|
5
|
+
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
|
6
|
+
|
7
|
+
from acp_sdk.models.errors import Error
|
8
|
+
|
9
|
+
|
10
|
+
class Metadata(BaseModel):
|
11
|
+
model_config = ConfigDict(extra="allow")
|
12
|
+
|
13
|
+
|
14
|
+
class AnyModel(BaseModel):
|
15
|
+
model_config = ConfigDict(extra="allow")
|
16
|
+
|
17
|
+
|
18
|
+
class MessagePart(BaseModel):
|
19
|
+
name: Optional[str] = None
|
20
|
+
content_type: str
|
21
|
+
content: Optional[str] = None
|
22
|
+
content_encoding: Optional[Literal["plain", "base64"]] = "plain"
|
23
|
+
content_url: Optional[AnyUrl] = None
|
24
|
+
|
25
|
+
model_config = ConfigDict(extra="forbid")
|
26
|
+
|
27
|
+
def model_post_init(self, __context: Any) -> None:
|
28
|
+
if self.content is None and self.content_url is None:
|
29
|
+
raise ValueError("Either content or content_url must be provided")
|
30
|
+
if self.content is not None and self.content_url is not None:
|
31
|
+
raise ValueError("Only one of content or content_url can be provided")
|
32
|
+
|
33
|
+
|
34
|
+
class Artifact(MessagePart):
|
35
|
+
name: str
|
36
|
+
|
37
|
+
|
38
|
+
class Message(BaseModel):
|
39
|
+
parts: list[MessagePart]
|
40
|
+
|
41
|
+
def __add__(self, other: "Message") -> "Message":
|
42
|
+
if not isinstance(other, Message):
|
43
|
+
raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
|
44
|
+
return Message(*(self.parts + other.parts))
|
45
|
+
|
46
|
+
def __str__(self) -> str:
|
47
|
+
return "".join(
|
48
|
+
part.content for part in self.parts if part.content is not None and part.content_type == "text/plain"
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
AgentName = str
|
53
|
+
SessionId = uuid.UUID
|
54
|
+
RunId = uuid.UUID
|
55
|
+
|
56
|
+
|
57
|
+
class RunMode(str, Enum):
|
58
|
+
SYNC = "sync"
|
59
|
+
ASYNC = "async"
|
60
|
+
STREAM = "stream"
|
61
|
+
|
62
|
+
|
63
|
+
class RunStatus(str, Enum):
|
64
|
+
CREATED = "created"
|
65
|
+
IN_PROGRESS = "in-progress"
|
66
|
+
AWAITING = "awaiting"
|
67
|
+
CANCELLING = "cancelling"
|
68
|
+
CANCELLED = "cancelled"
|
69
|
+
COMPLETED = "completed"
|
70
|
+
FAILED = "failed"
|
71
|
+
|
72
|
+
@property
|
73
|
+
def is_terminal(self) -> bool:
|
74
|
+
terminal_states = {RunStatus.COMPLETED, RunStatus.FAILED, RunStatus.CANCELLED}
|
75
|
+
return self in terminal_states
|
76
|
+
|
77
|
+
|
78
|
+
class AwaitRequest(BaseModel):
|
79
|
+
type: Literal["placeholder"] = "placeholder"
|
80
|
+
|
81
|
+
|
82
|
+
class AwaitResume(BaseModel):
|
83
|
+
pass
|
84
|
+
|
85
|
+
|
86
|
+
class Run(BaseModel):
|
87
|
+
run_id: RunId = Field(default_factory=uuid.uuid4)
|
88
|
+
agent_name: AgentName
|
89
|
+
session_id: SessionId | None = None
|
90
|
+
status: RunStatus = RunStatus.CREATED
|
91
|
+
await_request: AwaitRequest | None = None
|
92
|
+
outputs: list[Message] = []
|
93
|
+
error: Error | None = None
|
94
|
+
|
95
|
+
|
96
|
+
class MessageCreatedEvent(BaseModel):
|
97
|
+
type: Literal["message.created"] = "message.created"
|
98
|
+
message: Message
|
99
|
+
|
100
|
+
|
101
|
+
class MessagePartEvent(BaseModel):
|
102
|
+
type: Literal["message.part"] = "message.part"
|
103
|
+
part: MessagePart
|
104
|
+
|
105
|
+
|
106
|
+
class ArtifactEvent(BaseModel):
|
107
|
+
type: Literal["message.part"] = "message.part"
|
108
|
+
part: Artifact
|
109
|
+
|
110
|
+
|
111
|
+
class MessageCompletedEvent(BaseModel):
|
112
|
+
type: Literal["message.completed"] = "message.completed"
|
113
|
+
message: Message
|
114
|
+
|
115
|
+
|
116
|
+
class AwaitEvent(BaseModel):
|
117
|
+
type: Literal["await"] = "await"
|
118
|
+
await_request: AwaitRequest | None = None
|
119
|
+
|
120
|
+
|
121
|
+
class GenericEvent(BaseModel):
|
122
|
+
type: Literal["generic"] = "generic"
|
123
|
+
generic: AnyModel
|
124
|
+
|
125
|
+
|
126
|
+
class RunCreatedEvent(BaseModel):
|
127
|
+
type: Literal["run.created"] = "run.created"
|
128
|
+
run: Run
|
129
|
+
|
130
|
+
|
131
|
+
class RunInProgressEvent(BaseModel):
|
132
|
+
type: Literal["run.in-progress"] = "run.in-progress"
|
133
|
+
run: Run
|
134
|
+
|
135
|
+
|
136
|
+
class RunFailedEvent(BaseModel):
|
137
|
+
type: Literal["run.failed"] = "run.failed"
|
138
|
+
run: Run
|
139
|
+
|
140
|
+
|
141
|
+
class RunCancelledEvent(BaseModel):
|
142
|
+
type: Literal["run.cancelled"] = "run.cancelled"
|
143
|
+
run: Run
|
144
|
+
|
145
|
+
|
146
|
+
class RunCompletedEvent(BaseModel):
|
147
|
+
type: Literal["run.completed"] = "run.completed"
|
148
|
+
run: Run
|
149
|
+
|
150
|
+
|
151
|
+
Event = Union[
|
152
|
+
RunCreatedEvent,
|
153
|
+
RunInProgressEvent,
|
154
|
+
MessageCreatedEvent,
|
155
|
+
ArtifactEvent,
|
156
|
+
MessagePartEvent,
|
157
|
+
MessageCompletedEvent,
|
158
|
+
AwaitEvent,
|
159
|
+
GenericEvent,
|
160
|
+
RunCancelledEvent,
|
161
|
+
RunFailedEvent,
|
162
|
+
RunCompletedEvent,
|
163
|
+
MessagePartEvent,
|
164
|
+
]
|
165
|
+
|
166
|
+
|
167
|
+
class Agent(BaseModel):
|
168
|
+
name: str
|
169
|
+
description: str | None = None
|
170
|
+
metadata: Metadata = Metadata()
|