acp-sdk 0.3.3__tar.gz → 0.5.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.
Files changed (54) hide show
  1. acp_sdk-0.5.0/PKG-INFO +65 -0
  2. acp_sdk-0.5.0/README.md +44 -0
  3. acp_sdk-0.5.0/docs/_sidebar.md +7 -0
  4. acp_sdk-0.5.0/docs/client.md +99 -0
  5. acp_sdk-0.5.0/docs/index.html +104 -0
  6. acp_sdk-0.5.0/docs/models.md +45 -0
  7. acp_sdk-0.5.0/docs/server.md +103 -0
  8. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/pyproject.toml +6 -2
  9. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/client/client.py +43 -15
  10. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/app.py +5 -3
  11. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/bundle.py +16 -8
  12. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/errors.py +1 -1
  13. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/server.py +4 -1
  14. acp_sdk-0.5.0/src/acp_sdk/server/types.py +9 -0
  15. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/fixtures/server.py +5 -3
  16. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/test_suites/test_runs.py +47 -15
  17. acp_sdk-0.5.0/tests/unit/client/test_client.py +36 -0
  18. acp_sdk-0.3.3/PKG-INFO +0 -99
  19. acp_sdk-0.3.3/README.md +0 -79
  20. acp_sdk-0.3.3/examples/clients/advanced.py +0 -21
  21. acp_sdk-0.3.3/examples/clients/session.py +0 -18
  22. acp_sdk-0.3.3/examples/clients/simple.py +0 -17
  23. acp_sdk-0.3.3/examples/clients/stream.py +0 -14
  24. acp_sdk-0.3.3/examples/servers/awaiting.py +0 -26
  25. acp_sdk-0.3.3/examples/servers/echo.py +0 -22
  26. acp_sdk-0.3.3/examples/servers/standalone.py +0 -25
  27. acp_sdk-0.3.3/src/acp_sdk/server/types.py +0 -6
  28. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/.gitignore +0 -0
  29. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/.python-version +0 -0
  30. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/pytest.ini +0 -0
  31. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/__init__.py +0 -0
  32. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/client/__init__.py +0 -0
  33. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/instrumentation.py +0 -0
  34. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/models/__init__.py +0 -0
  35. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/models/errors.py +0 -0
  36. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/models/models.py +0 -0
  37. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/models/schemas.py +0 -0
  38. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/py.typed +0 -0
  39. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/__init__.py +0 -0
  40. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/agent.py +0 -0
  41. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/context.py +0 -0
  42. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/logging.py +0 -0
  43. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/session.py +0 -0
  44. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/telemetry.py +0 -0
  45. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/server/utils.py +0 -0
  46. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/src/acp_sdk/version.py +0 -0
  47. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/conftest.py +0 -0
  48. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/__init__.py +0 -0
  49. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/config.py +0 -0
  50. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/fixtures/__init__.py +0 -0
  51. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/fixtures/client.py +0 -0
  52. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/e2e/test_suites/__init__.py +0 -0
  53. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/unit/models/__init__.py +0 -0
  54. {acp_sdk-0.3.3 → acp_sdk-0.5.0}/tests/unit/models/test_models.py +0 -0
acp_sdk-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: acp-sdk
3
+ Version: 0.5.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: cachetools>=5.5.2
10
+ Requires-Dist: fastapi[standard]>=0.115.8
11
+ Requires-Dist: httpx-sse>=0.4.0
12
+ Requires-Dist: httpx>=0.26.0
13
+ Requires-Dist: janus>=2.0.0
14
+ Requires-Dist: opentelemetry-api>=1.31.1
15
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.31.1
16
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1
17
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1
18
+ Requires-Dist: opentelemetry-sdk>=1.31.1
19
+ Requires-Dist: pydantic>=2.11.1
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Agent Communication Protocol SDK for Python
23
+
24
+ Agent Communication Protocol SDK for Python provides allows developers to serve and consume agents over the Agent Communication Protocol.
25
+
26
+ ## Prerequisites
27
+
28
+ ✅ Python >= 3.11
29
+
30
+ ## Installation
31
+
32
+ Install according to your Python package manager:
33
+
34
+ - `uv add acp-sdk`
35
+ - `pip install acp-sdk`
36
+ - `poetry add acp-sdk`
37
+ - ...
38
+
39
+ ## Quickstart
40
+
41
+ Register an agent and run the server:
42
+
43
+ ```py
44
+ server = Server()
45
+
46
+ @server.agent()
47
+ async def echo(inputs: list[Message]):
48
+ """Echoes everything"""
49
+ for message in inputs:
50
+ yield message
51
+
52
+ server.run(port=8000)
53
+ ```
54
+
55
+ From another process, connect to the server and run the agent:
56
+
57
+ ```py
58
+ async with Client(base_url="http://localhost:8000") as client:
59
+ run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
60
+ print(run)
61
+
62
+ ```
63
+
64
+
65
+ ➡️ Explore more in our [examples library](/examples/python).
@@ -0,0 +1,44 @@
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 according to your Python package manager:
12
+
13
+ - `uv add acp-sdk`
14
+ - `pip install acp-sdk`
15
+ - `poetry add acp-sdk`
16
+ - ...
17
+
18
+ ## Quickstart
19
+
20
+ Register an agent and run the server:
21
+
22
+ ```py
23
+ server = Server()
24
+
25
+ @server.agent()
26
+ async def echo(inputs: list[Message]):
27
+ """Echoes everything"""
28
+ for message in inputs:
29
+ yield message
30
+
31
+ server.run(port=8000)
32
+ ```
33
+
34
+ From another process, connect to the server and run the agent:
35
+
36
+ ```py
37
+ async with Client(base_url="http://localhost:8000") as client:
38
+ run = await client.run_sync(agent="echo", inputs=[Message(parts=[MessagePart(content="Howdy!")])])
39
+ print(run)
40
+
41
+ ```
42
+
43
+
44
+ ➡️ Explore more in our [examples library](/examples/python).
@@ -0,0 +1,7 @@
1
+ - [Python](/python/)
2
+
3
+ - Modules
4
+
5
+ - [Client](client.md)
6
+ - [Server](server.md)
7
+ - [Models](models.md)
@@ -0,0 +1,99 @@
1
+ # Client
2
+
3
+ <!-- TOC -->
4
+ ## Table of Contents
5
+ - [Client](#client)
6
+ - [Table of Contents](#table-of-contents)
7
+ - [Overview](#overview)
8
+ - [Usage](#usage)
9
+ - [Setting up a client](#setting-up-a-client)
10
+ - [Performing discovery](#performing-discovery)
11
+ - [Running an agent](#running-an-agent)
12
+ - [Using sessions](#using-sessions)
13
+ <!-- /TOC -->
14
+
15
+ ---
16
+
17
+ ## Overview
18
+
19
+ The client module provides a thin ACP client that can be integrated into any application that needs to communicate with ACP agents. It provides the following advantages over raw HTTP client:
20
+
21
+ - Strong typing
22
+ - Error handling
23
+ - Stream decoding
24
+ - Session management
25
+ - Instrumentation
26
+
27
+ > [!NOTE]
28
+ >
29
+ > Location within the sdk: [client](/python/src/acp_sdk/client)
30
+
31
+ ## Usage
32
+
33
+ The client is based on the [httpx.AsyncClient](https://www.python-httpx.org/async/). The usage is very similar as demonstrated below.
34
+
35
+ ### Setting up a client
36
+
37
+ To set up a simple client, simply provide an URL:
38
+
39
+ ```py
40
+ async with Client(base_url="http://localhost:8000") as client:
41
+ ...
42
+ ```
43
+
44
+ To use advanced HTTP configuration, provide `httpx` async client:
45
+
46
+ ```py
47
+ async with Client(
48
+ client=httpx.AsyncClient(
49
+ base_url="http://localhost:8000",
50
+ headers={"token": "foobar"})
51
+ ) as client:
52
+ ...
53
+ ```
54
+
55
+ ### Performing discovery
56
+
57
+ To discover available agents:
58
+
59
+ ```py
60
+ async with Client(base_url="http://localhost:8000") as client:
61
+ async for agent in client.agents():
62
+ ...
63
+ ```
64
+
65
+ ### Running an agent
66
+
67
+ Agent run can be invoked in three modes:
68
+
69
+ ```py
70
+ async with Client(base_url="http://localhost:8000") as client:
71
+ message = Message(parts=[MessagePart(content="Hello")])
72
+
73
+ # Async
74
+ run = await client.run_async(agent_name="agent", inputs=[message])
75
+ print(run.status)
76
+
77
+ # Sync - waits for completion, failure, cancellation or await
78
+ run = await client.run_sync(agent_name="agent", inputs=[message])
79
+ print(run.outputs)
80
+
81
+ # Stream - as sync but also receives events
82
+ async for event in client.run_stream(agent_name="agent", inputs=[message])
83
+ print(event)
84
+ ```
85
+
86
+ ### Using sessions
87
+
88
+ Sessions are a mechanism to have multi-turn conversations with agents.
89
+
90
+ To enter a session, create one from the client:
91
+
92
+ ```py
93
+ async with Client(base_url="http://localhost:8000" as client:
94
+ agents = [agent async for agent in client.agents()]
95
+
96
+ async with client.session() as session:
97
+ for agent in agents:
98
+ await session.run_sync(agent_name=agent.name, inputs=[Message(parts=[MessagePart(content="Hello!")])])
99
+ ```
@@ -0,0 +1,104 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>ACP Docs</title>
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
+ <meta name="description" content="Description" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
9
+ <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css" />
10
+ <link
11
+ rel="stylesheet"
12
+ title="docsify-darklight-theme"
13
+ href="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/style.min.css"
14
+ />
15
+ <style>
16
+ aside.sidebar {
17
+ padding-top: 0;
18
+ }
19
+ .app-name-link img {
20
+ filter: invert(var(--docs-logo-color-invert));
21
+ }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div id="app"></div>
26
+ <script>
27
+ function fixLinkFactory() {
28
+ var main = "https://github.com/i-am-bee/acp/tree/main";
29
+ var exampleRegex = /^[\/]{0,1}examples\/(.*)/;
30
+ var srcRegex = /^[\/]{0,1}src\/(.*)/;
31
+ var docsMatch = /^[\/]{0,1}docs\/(.*)/;
32
+
33
+ return function (url) {
34
+ if (url === "LICENSE") {
35
+ return main.concat("/LICENSE");
36
+ }
37
+ if (url.substring(0, 2) === "./") {
38
+ url = url.substring(2);
39
+ }
40
+ if (url.match(docsMatch)) {
41
+ url = url.replace(docsMatch, "$1");
42
+ }
43
+ if (url.match(exampleRegex) || url.match(srcRegex) || url === "/package.json") {
44
+ url = main.concat(url);
45
+ }
46
+ return url;
47
+ };
48
+ }
49
+
50
+ var fixLink = fixLinkFactory();
51
+ var basePath = window.location.pathname.replace(/index.html/gi, "").replace("/#/", "") || "/";
52
+
53
+ window.$docsify = {
54
+ "name": "ACP",
55
+ "basePath": basePath,
56
+ "darklightTheme": {
57
+ "defaultTheme": 'dark',
58
+ "light": {
59
+ 'docs-logo-color-invert': 0,
60
+ },
61
+ "dark": {
62
+ 'docs-logo-color-invert': 1,
63
+ }
64
+ },
65
+ "repo": "i-am-bee/acp",
66
+ "loadSidebar": true,
67
+ "subMaxLevel": 2,
68
+ "auto2top": true,
69
+ "search": {
70
+ depth: 6,
71
+ namespace: "acp",
72
+ },
73
+ "flexible-alerts": {
74
+ important: {
75
+ label: "Important",
76
+ icon: "icon-note",
77
+ className: "note",
78
+ },
79
+ },
80
+ "markdown": {
81
+ smartypants: true,
82
+ renderer: {
83
+ link: function (link, renderer, text) {
84
+ return this.origin.link.apply(this, [fixLink(link), renderer, text]);
85
+ },
86
+ html: function (html) {
87
+ var regex = /(src|href|srcset)\s*=\s*(['"])[\.]{0,1}\/docs\/(.+?)\2/gi;
88
+ html = html.replace(regex, "$1=$2" + basePath + "$3$2");
89
+ return html;
90
+ },
91
+ },
92
+ },
93
+ };
94
+ </script>
95
+ <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
96
+ <script src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js"></script>
97
+ <script src="https://unpkg.com/docsify-plugin-flexible-alerts"></script>
98
+ <script src="https://unpkg.com/docsify-copy-code@3"></script>
99
+ <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
100
+ <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js"></script>
101
+ <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-sql.min.js"></script>
102
+ <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
103
+ </body>
104
+ </html>
@@ -0,0 +1,45 @@
1
+ # Models
2
+
3
+ <!-- TOC -->
4
+ ## Table of Contents
5
+ - [Models](#models)
6
+ - [Table of Contents](#table-of-contents)
7
+ - [Overview](#overview)
8
+ - [Core](#core)
9
+ - [Schemas](#schemas)
10
+ - [Errors](#errors)
11
+ <!-- /TOC -->
12
+
13
+ ---
14
+
15
+ ## Overview
16
+
17
+ The models module provides `pydantic` models describing ACP data structures.
18
+
19
+ > [!NOTE]
20
+ >
21
+ > Location within the sdk: [models](/python/src/acp_sdk/models)
22
+
23
+ ## Core
24
+
25
+ Core models are used directly by the SDK consumers. They describe ACP structures like `Message`, `MessagePart` or `Run`.
26
+
27
+ > [!NOTE]
28
+ >
29
+ > Location within the sdk: [models.models](/python/src/acp_sdk/models/models.py)
30
+
31
+ ## Schemas
32
+
33
+ Schema models are used by client and server implementations. They describe payloads of HTTP requests and responses.
34
+
35
+ > [!NOTE]
36
+ >
37
+ > Location within the sdk: [models.schemas](/python/src/acp_sdk/models/schemas.py)
38
+
39
+ ## Errors
40
+
41
+ Error model describes errors within the system. SDK consumers can encounter errors in various places, during a stream, as part of the `Run` model or raised inside the `ACPError` exception.
42
+
43
+ > [!NOTE]
44
+ >
45
+ > Location within the sdk: [models.errors](/python/src/acp_sdk/models/errors.py)
@@ -0,0 +1,103 @@
1
+ # Server
2
+
3
+ <!-- TOC -->
4
+ ## Table of Contents
5
+ - [Server](#server)
6
+ - [Table of Contents](#table-of-contents)
7
+ - [Overview](#overview)
8
+ - [Usage](#usage)
9
+ - [Running a server](#running-a-server)
10
+ - [Standalone application](#standalone-application)
11
+ <!-- /TOC -->
12
+
13
+ ---
14
+
15
+ ## Overview
16
+
17
+ The server module aims to expose arbitrary agents over the Agent Commucation Protocol. Consumers can use this module to avoid protocol-specific technicalities and focus on the agent itself. The modules provides Agent abstractions, FastAPI application factory and Uvicorn-based server.
18
+
19
+ > [!NOTE]
20
+ >
21
+ > Location within the sdk: [server](/python/src/acp_sdk/server).
22
+
23
+ ## Usage
24
+
25
+ The SDK provides `Agent` abstract class and `agent` decorator. Arbitrary agent can be created in by either inheritance from the class or by using the decorator on a generator, coroutine or a regular function.
26
+
27
+ > [!NOTE]
28
+ >
29
+ > Location within the sdk: [server.agent](/python/src/acp_sdk/server/agent.py).
30
+
31
+ ### Running a server
32
+
33
+ The SDK provides `Server` class as a convenience for developers to easily serve agents over ACP. The server allows to easily create and register agents. It sets up logging and optionally telemetry exporters.
34
+
35
+ <!-- embedme python/examples/servers/echo.py -->
36
+
37
+ ```py
38
+ import asyncio
39
+ from collections.abc import AsyncGenerator
40
+
41
+ from acp_sdk.models import (
42
+ Message,
43
+ )
44
+ from acp_sdk.server import Context, RunYield, RunYieldResume, Server
45
+
46
+ server = Server()
47
+
48
+
49
+ @server.agent()
50
+ async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
51
+ """Echoes everything"""
52
+ for message in inputs:
53
+ await asyncio.sleep(0.5)
54
+ yield {"thought": "I should echo everything"}
55
+ await asyncio.sleep(0.5)
56
+ yield message
57
+
58
+
59
+ server.run()
60
+ ```
61
+
62
+ > [!NOTE]
63
+ >
64
+ > Location within the sdk: [server.server](/python/src/acp_sdk/server/server.py).
65
+
66
+ ### Standalone application
67
+
68
+ The SDK provides `create_app` factory that turns agents into `FastAPI` application. The application implements route handlers, error handlers, unified execution environment and more. The application can be served with arbitrary ASGI compatible server.
69
+
70
+ <!-- embedme python/examples/servers/standalone.py -->
71
+
72
+ ```py
73
+ from collections.abc import AsyncGenerator
74
+
75
+ from acp_sdk.models import (
76
+ Message,
77
+ )
78
+ from acp_sdk.server import RunYield, RunYieldResume, agent, create_app
79
+
80
+ # This example demonstrates how to serve agents with you own server
81
+
82
+
83
+ @agent()
84
+ async def echo(inputs: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
85
+ """Echoes everything"""
86
+ for message in inputs:
87
+ yield message
88
+
89
+
90
+ app = create_app(echo)
91
+
92
+ # The app can now be used with any ASGI server
93
+
94
+ # Run with
95
+ # 1. fastapi run examples/servers/standalone.py
96
+ # 2. uvicorn examples.servers.standalone:app
97
+ # ...
98
+
99
+ ```
100
+
101
+ > [!NOTE]
102
+ >
103
+ > Location within the sdk: [server.app](/python/src/acp_sdk/server/app.py).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acp-sdk"
3
- version = "0.3.3"
3
+ version = "0.5.0"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -10,7 +10,7 @@ requires-python = ">=3.11, <4.0"
10
10
  dependencies = [
11
11
  "opentelemetry-api>=1.31.1",
12
12
  "pydantic>=2.11.1",
13
- "httpx>=0.28.1",
13
+ "httpx>=0.26.0",
14
14
  "httpx-sse>=0.4.0",
15
15
  "opentelemetry-instrumentation-httpx>=0.52b1",
16
16
  "fastapi[standard]>=0.115.8",
@@ -18,8 +18,12 @@ dependencies = [
18
18
  "opentelemetry-instrumentation-fastapi>=0.52b1",
19
19
  "opentelemetry-sdk>=1.31.1",
20
20
  "janus>=2.0.0",
21
+ "cachetools>=5.5.2",
21
22
  ]
22
23
 
23
24
  [build-system]
24
25
  requires = ["hatchling"]
25
26
  build-backend = "hatchling.build"
27
+
28
+ [dependency-groups]
29
+ dev = ["pytest-httpx>=0.35.0"]
@@ -31,6 +31,9 @@ from acp_sdk.models import (
31
31
  RunResumeResponse,
32
32
  SessionId,
33
33
  )
34
+ from acp_sdk.models.models import MessagePart
35
+
36
+ Input = list[Message] | Message | list[MessagePart] | MessagePart | list[str] | str
34
37
 
35
38
 
36
39
  class Client:
@@ -79,14 +82,15 @@ class Client:
79
82
  async def agent(self, *, name: AgentName) -> Agent:
80
83
  response = await self._client.get(f"/agents/{name}")
81
84
  self._raise_error(response)
82
- return AgentReadResponse.model_validate(response.json())
85
+ response = AgentReadResponse.model_validate(response.json())
86
+ return Agent(**response.model_dump())
83
87
 
84
- async def run_sync(self, *, agent: AgentName, inputs: list[Message]) -> Run:
88
+ async def run_sync(self, input: Input, *, agent: AgentName) -> Run:
85
89
  response = await self._client.post(
86
90
  "/runs",
87
91
  content=RunCreateRequest(
88
92
  agent_name=agent,
89
- inputs=inputs,
93
+ inputs=self._unify_inputs(input),
90
94
  mode=RunMode.SYNC,
91
95
  session_id=self._session_id,
92
96
  ).model_dump_json(),
@@ -94,14 +98,14 @@ class Client:
94
98
  self._raise_error(response)
95
99
  response = RunCreateResponse.model_validate(response.json())
96
100
  self._set_session(response)
97
- return response
101
+ return Run(**response.model_dump())
98
102
 
99
- async def run_async(self, *, agent: AgentName, inputs: list[Message]) -> Run:
103
+ async def run_async(self, input: Input, *, agent: AgentName) -> Run:
100
104
  response = await self._client.post(
101
105
  "/runs",
102
106
  content=RunCreateRequest(
103
107
  agent_name=agent,
104
- inputs=inputs,
108
+ inputs=self._unify_inputs(input),
105
109
  mode=RunMode.ASYNC,
106
110
  session_id=self._session_id,
107
111
  ).model_dump_json(),
@@ -109,16 +113,16 @@ class Client:
109
113
  self._raise_error(response)
110
114
  response = RunCreateResponse.model_validate(response.json())
111
115
  self._set_session(response)
112
- return response
116
+ return Run(**response.model_dump())
113
117
 
114
- async def run_stream(self, *, agent: AgentName, inputs: list[Message]) -> AsyncIterator[Event]:
118
+ async def run_stream(self, input: Input, *, agent: AgentName) -> AsyncIterator[Event]:
115
119
  async with aconnect_sse(
116
120
  self._client,
117
121
  "POST",
118
122
  "/runs",
119
123
  content=RunCreateRequest(
120
124
  agent_name=agent,
121
- inputs=inputs,
125
+ inputs=self._unify_inputs(input),
122
126
  mode=RunMode.STREAM,
123
127
  session_id=self._session_id,
124
128
  ).model_dump_json(),
@@ -136,25 +140,28 @@ class Client:
136
140
  async def run_cancel(self, *, run_id: RunId) -> Run:
137
141
  response = await self._client.post(f"/runs/{run_id}/cancel")
138
142
  self._raise_error(response)
139
- return RunCancelResponse.model_validate(response.json())
143
+ response = RunCancelResponse.model_validate(response.json())
144
+ return Run(**response.model_dump())
140
145
 
141
- async def run_resume_sync(self, *, run_id: RunId, await_resume: AwaitResume) -> Run:
146
+ async def run_resume_sync(self, await_resume: AwaitResume, *, run_id: RunId) -> Run:
142
147
  response = await self._client.post(
143
148
  f"/runs/{run_id}",
144
149
  content=RunResumeRequest(await_resume=await_resume, mode=RunMode.SYNC).model_dump_json(),
145
150
  )
146
151
  self._raise_error(response)
147
- return RunResumeResponse.model_validate(response.json())
152
+ response = RunResumeResponse.model_validate(response.json())
153
+ return Run(**response.model_dump())
148
154
 
149
- async def run_resume_async(self, *, run_id: RunId, await_resume: AwaitResume) -> Run:
155
+ async def run_resume_async(self, await_resume: AwaitResume, *, run_id: RunId) -> Run:
150
156
  response = await self._client.post(
151
157
  f"/runs/{run_id}",
152
158
  content=RunResumeRequest(await_resume=await_resume, mode=RunMode.ASYNC).model_dump_json(),
153
159
  )
154
160
  self._raise_error(response)
155
- return RunResumeResponse.model_validate(response.json())
161
+ response = RunResumeResponse.model_validate(response.json())
162
+ return Run(**response.model_dump())
156
163
 
157
- async def run_resume_stream(self, *, run_id: RunId, await_resume: AwaitResume) -> AsyncIterator[Event]:
164
+ async def run_resume_stream(self, await_resume: AwaitResume, *, run_id: RunId) -> AsyncIterator[Event]:
158
165
  async with aconnect_sse(
159
166
  self._client,
160
167
  "POST",
@@ -183,3 +190,24 @@ class Client:
183
190
 
184
191
  def _set_session(self, run: Run) -> None:
185
192
  self._session_id = run.session_id
193
+
194
+ def _unify_inputs(self, input: Input) -> list[Message]:
195
+ if isinstance(input, list):
196
+ if len(input) == 0:
197
+ return []
198
+ if all(isinstance(item, Message) for item in input):
199
+ return input
200
+ elif all(isinstance(item, MessagePart) for item in input):
201
+ return [Message(parts=input)]
202
+ elif all(isinstance(item, str) for item in input):
203
+ return [Message(parts=[MessagePart(content=content) for content in input])]
204
+ else:
205
+ raise RuntimeError("List with mixed types is not supported")
206
+ else:
207
+ if isinstance(input, str):
208
+ input = MessagePart(content=input)
209
+ if isinstance(input, MessagePart):
210
+ input = Message(parts=[input])
211
+ if isinstance(input, Message):
212
+ input = [input]
213
+ return input
@@ -1,8 +1,10 @@
1
1
  from collections.abc import AsyncGenerator
2
2
  from concurrent.futures import ThreadPoolExecutor
3
3
  from contextlib import asynccontextmanager
4
+ from datetime import datetime, timedelta
4
5
  from enum import Enum
5
6
 
7
+ from cachetools import TTLCache
6
8
  from fastapi import FastAPI, HTTPException, status
7
9
  from fastapi.encoders import jsonable_encoder
8
10
  from fastapi.responses import JSONResponse, StreamingResponse
@@ -45,7 +47,7 @@ class Headers(str, Enum):
45
47
  RUN_ID = "Run-ID"
46
48
 
47
49
 
48
- def create_app(*agents: Agent) -> FastAPI:
50
+ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timedelta(hours=1)) -> FastAPI:
49
51
  executor: ThreadPoolExecutor
50
52
 
51
53
  @asynccontextmanager
@@ -60,8 +62,8 @@ def create_app(*agents: Agent) -> FastAPI:
60
62
  FastAPIInstrumentor.instrument_app(app)
61
63
 
62
64
  agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
63
- runs: dict[RunId, RunBundle] = {}
64
- sessions: dict[SessionId, Session] = {}
65
+ runs: TTLCache[RunId, RunBundle] = TTLCache(maxsize=run_limit, ttl=run_ttl, timer=datetime.now)
66
+ sessions: TTLCache[SessionId, Session] = TTLCache(maxsize=run_limit, ttl=run_ttl, timer=datetime.now)
65
67
 
66
68
  app.exception_handler(ACPError)(acp_error_handler)
67
69
  app.exception_handler(StarletteHTTPException)(http_exception_handler)