acp-sdk 0.1.0rc5__py3-none-any.whl → 0.1.0rc7__py3-none-any.whl

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/models/models.py CHANGED
@@ -58,8 +58,8 @@ class Message(RootModel):
58
58
 
59
59
 
60
60
  AgentName = str
61
- SessionId = str
62
- RunId = str
61
+ SessionId = uuid.UUID
62
+ RunId = uuid.UUID
63
63
 
64
64
 
65
65
  class RunMode(str, Enum):
@@ -92,7 +92,7 @@ class AwaitResume(BaseModel):
92
92
 
93
93
 
94
94
  class Run(BaseModel):
95
- run_id: RunId = str(uuid.uuid4())
95
+ run_id: RunId = Field(default_factory=uuid.uuid4)
96
96
  agent_name: AgentName
97
97
  session_id: SessionId | None = None
98
98
  status: RunStatus = RunStatus.CREATED
acp_sdk/models/schemas.py CHANGED
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel, Field
1
+ from pydantic import BaseModel, ConfigDict, Field
2
2
 
3
3
  from acp_sdk.models.models import Agent, AgentName, AwaitResume, Message, Run, RunMode, SessionId
4
4
 
@@ -26,6 +26,8 @@ class RunResumeRequest(BaseModel):
26
26
  await_: AwaitResume = Field(alias="await")
27
27
  mode: RunMode
28
28
 
29
+ model_config = ConfigDict(populate_by_name=True)
30
+
29
31
 
30
32
  class RunResumeResponse(Run):
31
33
  pass
acp_sdk/server/app.py CHANGED
@@ -2,8 +2,10 @@ import asyncio
2
2
  from collections.abc import AsyncGenerator
3
3
  from concurrent.futures import ThreadPoolExecutor
4
4
  from contextlib import asynccontextmanager
5
+ from enum import Enum
5
6
 
6
7
  from fastapi import FastAPI, HTTPException, status
8
+ from fastapi.encoders import jsonable_encoder
7
9
  from fastapi.responses import JSONResponse, StreamingResponse
8
10
  from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
9
11
 
@@ -39,6 +41,10 @@ from acp_sdk.server.errors import (
39
41
  from acp_sdk.server.utils import stream_sse
40
42
 
41
43
 
44
+ class Headers(str, Enum):
45
+ RUN_ID = "Run-ID"
46
+
47
+
42
48
  def create_app(*agents: Agent) -> FastAPI:
43
49
  executor: ThreadPoolExecutor
44
50
 
@@ -102,19 +108,26 @@ def create_app(*agents: Agent) -> FastAPI:
102
108
  bundle.task = asyncio.create_task(bundle.execute(request.input, executor=executor))
103
109
  runs[bundle.run.run_id] = bundle
104
110
 
111
+ headers = {Headers.RUN_ID: str(bundle.run.run_id)}
112
+
105
113
  match request.mode:
106
114
  case RunMode.STREAM:
107
115
  return StreamingResponse(
108
116
  stream_sse(bundle),
117
+ headers=headers,
109
118
  media_type="text/event-stream",
110
119
  )
111
120
  case RunMode.SYNC:
112
121
  await bundle.join()
113
- return bundle.run
122
+ return JSONResponse(
123
+ headers=headers,
124
+ content=jsonable_encoder(bundle.run),
125
+ )
114
126
  case RunMode.ASYNC:
115
127
  return JSONResponse(
116
128
  status_code=status.HTTP_202_ACCEPTED,
117
- content=bundle.run.model_dump(),
129
+ headers=headers,
130
+ content=jsonable_encoder(bundle.run),
118
131
  )
119
132
  case _:
120
133
  raise NotImplementedError()
@@ -141,7 +154,7 @@ def create_app(*agents: Agent) -> FastAPI:
141
154
  case RunMode.ASYNC:
142
155
  return JSONResponse(
143
156
  status_code=status.HTTP_202_ACCEPTED,
144
- content=bundle.run.model_dump(),
157
+ content=jsonable_encoder(bundle.run),
145
158
  )
146
159
  case _:
147
160
  raise NotImplementedError()
@@ -156,6 +169,6 @@ def create_app(*agents: Agent) -> FastAPI:
156
169
  )
157
170
  bundle.task.cancel()
158
171
  bundle.run.status = RunStatus.CANCELLING
159
- return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
172
+ return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder(bundle.run))
160
173
 
161
174
  return app
acp_sdk/server/bundle.py CHANGED
@@ -70,7 +70,7 @@ class RunBundle:
70
70
 
71
71
  async def execute(self, input: Message, *, executor: ThreadPoolExecutor) -> None:
72
72
  with trace.get_tracer(__name__).start_as_current_span("execute"):
73
- run_logger = logging.LoggerAdapter(logger, {"run_id": self.run.run_id})
73
+ run_logger = logging.LoggerAdapter(logger, {"run_id": str(self.run.run_id)})
74
74
 
75
75
  await self.emit(CreatedEvent(run=self.run))
76
76
  try:
acp_sdk/server/server.py CHANGED
@@ -1,7 +1,12 @@
1
+ import asyncio
1
2
  import inspect
2
- from collections.abc import AsyncGenerator, Coroutine, Generator
3
+ import os
4
+ from collections.abc import AsyncGenerator, Awaitable, Coroutine, Generator
3
5
  from typing import Any, Callable
4
6
 
7
+ import uvicorn
8
+ import uvicorn.config
9
+
5
10
  from acp_sdk.models import Message
6
11
  from acp_sdk.server.agent import Agent
7
12
  from acp_sdk.server.app import create_app
@@ -13,25 +18,24 @@ from acp_sdk.server.types import RunYield, RunYieldResume
13
18
 
14
19
  class Server:
15
20
  def __init__(self) -> None:
16
- self.agents: list[Agent] = []
21
+ self._agents: list[Agent] = []
22
+ self._server: uvicorn.Server | None = None
17
23
 
18
24
  def agent(self, name: str | None = None, description: str | None = None) -> Callable:
19
25
  """Decorator to register an agent."""
20
26
 
21
27
  def decorator(fn: Callable) -> Callable:
22
- # check agent's function signature
23
28
  signature = inspect.signature(fn)
24
29
  parameters = list(signature.parameters.values())
25
30
 
26
- # validate agent's function
27
- if inspect.isasyncgenfunction(fn):
28
- if len(parameters) != 2:
29
- raise TypeError(
30
- "The agent generator function must have one 'input' argument and one 'context' argument"
31
- )
32
- else:
33
- if len(parameters) != 2:
34
- raise TypeError("The agent function must have one 'input' argument and one 'context' argument")
31
+ if len(parameters) == 0:
32
+ raise TypeError("The agent function must have at least 'input' argument")
33
+ if len(parameters) > 2:
34
+ raise TypeError("The agent function must have only 'input' and 'context' arguments")
35
+ if len(parameters) == 2 and parameters[1].name != "context":
36
+ raise TypeError("The second argument of the agent function must be 'context'")
37
+
38
+ has_context_param = len(parameters) == 2
35
39
 
36
40
  agent: Agent
37
41
  if inspect.isasyncgenfunction(fn):
@@ -47,7 +51,9 @@ class Server:
47
51
 
48
52
  async def run(self, input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
49
53
  try:
50
- gen: AsyncGenerator[RunYield, RunYieldResume] = fn(input, context)
54
+ gen: AsyncGenerator[RunYield, RunYieldResume] = (
55
+ fn(input, context) if has_context_param else fn(input)
56
+ )
51
57
  value = None
52
58
  while True:
53
59
  value = yield await gen.asend(value)
@@ -67,7 +73,7 @@ class Server:
67
73
  return description or fn.__doc__ or ""
68
74
 
69
75
  async def run(self, input: Message, context: Context) -> Coroutine[RunYield]:
70
- return await fn(input, context)
76
+ return await (fn(input, context) if has_context_param else fn(input))
71
77
 
72
78
  agent = DecoratedAgent()
73
79
  elif inspect.isgeneratorfunction(fn):
@@ -82,7 +88,7 @@ class Server:
82
88
  return description or fn.__doc__ or ""
83
89
 
84
90
  def run(self, input: Message, context: Context) -> Generator[RunYield, RunYieldResume]:
85
- yield from fn(input, context)
91
+ yield from (fn(input, context) if has_context_param else fn(input))
86
92
 
87
93
  agent = DecoratedAgent()
88
94
  else:
@@ -97,7 +103,7 @@ class Server:
97
103
  return description or fn.__doc__ or ""
98
104
 
99
105
  def run(self, input: Message, context: Context) -> RunYield:
100
- return fn(input, context)
106
+ return fn(input, context) if has_context_param else fn(input)
101
107
 
102
108
  agent = DecoratedAgent()
103
109
 
@@ -107,9 +113,67 @@ class Server:
107
113
  return decorator
108
114
 
109
115
  def register(self, *agents: Agent) -> None:
110
- self.agents.extend(agents)
116
+ self._agents.extend(agents)
117
+
118
+ def run(
119
+ self,
120
+ configure_logger: bool = True,
121
+ configure_telemetry: bool = False,
122
+ host: str = "127.0.0.1",
123
+ port: int = 8000,
124
+ uds: str | None = None,
125
+ fd: int | None = None,
126
+ loop: uvicorn.config.LoopSetupType = "auto",
127
+ http: type[asyncio.Protocol] | uvicorn.config.HTTPProtocolType = "auto",
128
+ ws: type[asyncio.Protocol] | uvicorn.config.WSProtocolType = "auto",
129
+ ws_max_size: int = 16 * 1024 * 1024,
130
+ ws_max_queue: int = 32,
131
+ ws_ping_interval: float | None = 20.0,
132
+ ws_ping_timeout: float | None = 20.0,
133
+ ws_per_message_deflate: bool = True,
134
+ lifespan: uvicorn.config.LifespanType = "auto",
135
+ env_file: str | os.PathLike[str] | None = None,
136
+ log_config: dict[str, Any]
137
+ | str
138
+ | uvicorn.config.RawConfigParser
139
+ | uvicorn.config.IO[Any]
140
+ | None = uvicorn.config.LOGGING_CONFIG,
141
+ log_level: str | int | None = None,
142
+ access_log: bool = True,
143
+ use_colors: bool | None = None,
144
+ interface: uvicorn.config.InterfaceType = "auto",
145
+ reload: bool = False,
146
+ reload_dirs: list[str] | str | None = None,
147
+ reload_delay: float = 0.25,
148
+ reload_includes: list[str] | str | None = None,
149
+ reload_excludes: list[str] | str | None = None,
150
+ workers: int | None = None,
151
+ proxy_headers: bool = True,
152
+ server_header: bool = True,
153
+ date_header: bool = True,
154
+ forwarded_allow_ips: list[str] | str | None = None,
155
+ root_path: str = "",
156
+ limit_concurrency: int | None = None,
157
+ limit_max_requests: int | None = None,
158
+ backlog: int = 2048,
159
+ timeout_keep_alive: int = 5,
160
+ timeout_notify: int = 30,
161
+ timeout_graceful_shutdown: int | None = None,
162
+ callback_notify: Callable[..., Awaitable[None]] | None = None,
163
+ ssl_keyfile: str | os.PathLike[str] | None = None,
164
+ ssl_certfile: str | os.PathLike[str] | None = None,
165
+ ssl_keyfile_password: str | None = None,
166
+ ssl_version: int = uvicorn.config.SSL_PROTOCOL_VERSION,
167
+ ssl_cert_reqs: int = uvicorn.config.ssl.CERT_NONE,
168
+ ssl_ca_certs: str | None = None,
169
+ ssl_ciphers: str = "TLSv1",
170
+ headers: list[tuple[str, str]] | None = None,
171
+ factory: bool = False,
172
+ h11_max_incomplete_event_size: int | None = None,
173
+ ) -> None:
174
+ if self._server:
175
+ raise RuntimeError("The server is already running")
111
176
 
112
- def run(self, configure_logger: bool = True, configure_telemetry: bool = False, **kwargs: dict[str, Any]) -> None:
113
177
  import uvicorn
114
178
 
115
179
  if configure_logger:
@@ -117,4 +181,63 @@ class Server:
117
181
  if configure_telemetry:
118
182
  configure_telemetry_func()
119
183
 
120
- uvicorn.run(create_app(*self.agents), **kwargs)
184
+ config = uvicorn.Config(
185
+ create_app(*self._agents),
186
+ host,
187
+ port,
188
+ uds,
189
+ fd,
190
+ loop,
191
+ http,
192
+ ws,
193
+ ws_max_size,
194
+ ws_max_queue,
195
+ ws_ping_interval,
196
+ ws_ping_timeout,
197
+ ws_per_message_deflate,
198
+ lifespan,
199
+ env_file,
200
+ log_config,
201
+ log_level,
202
+ access_log,
203
+ use_colors,
204
+ interface,
205
+ reload,
206
+ reload_dirs,
207
+ reload_delay,
208
+ reload_includes,
209
+ reload_excludes,
210
+ workers,
211
+ proxy_headers,
212
+ server_header,
213
+ date_header,
214
+ forwarded_allow_ips,
215
+ root_path,
216
+ limit_concurrency,
217
+ limit_max_requests,
218
+ backlog,
219
+ timeout_keep_alive,
220
+ timeout_notify,
221
+ timeout_graceful_shutdown,
222
+ callback_notify,
223
+ ssl_keyfile,
224
+ ssl_certfile,
225
+ ssl_keyfile_password,
226
+ ssl_version,
227
+ ssl_cert_reqs,
228
+ ssl_ca_certs,
229
+ ssl_ciphers,
230
+ headers,
231
+ factory,
232
+ h11_max_incomplete_event_size,
233
+ )
234
+ self._server = uvicorn.Server(config)
235
+ self._server.run()
236
+
237
+ @property
238
+ def should_exit(self) -> bool:
239
+ return self._server.should_exit if self._server else False
240
+
241
+ @should_exit.setter
242
+ def should_exit(self, value: bool) -> None:
243
+ self._server.should_exit = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acp-sdk
3
- Version: 0.1.0rc5
3
+ Version: 0.1.0rc7
4
4
  Summary: Agent Communication Protocol SDK
5
5
  Requires-Python: <4.0,>=3.11
6
6
  Requires-Dist: opentelemetry-api>=1.31.1
@@ -56,23 +56,19 @@ async with Client(base_url="http://localhost:8000") as client:
56
56
  The `server` submodule exposes [fastapi] application factory that makes it easy to expose any agent over ACP.
57
57
 
58
58
  ```python
59
- class EchoAgent(Agent):
60
- @property
61
- def name(self) -> str:
62
- return "echo"
59
+ server = Server()
63
60
 
64
- @property
65
- def description(self) -> str:
66
- return "Echoes everything"
61
+ @server.agent()
62
+ async def echo(input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
63
+ """Echoes everything"""
64
+ for part in input:
65
+ await asyncio.sleep(0.5)
66
+ yield {"thought": "I should echo everyting"}
67
+ await asyncio.sleep(0.5)
68
+ yield Message(part)
67
69
 
68
- async def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
69
- for part in input:
70
- await asyncio.sleep(0.5)
71
- yield {"thought": "I should echo everyting"}
72
- yield Message(part)
73
70
 
74
-
75
- serve(EchoAgent())
71
+ server.run()
76
72
  ```
77
73
 
78
74
  ➡️ Explore more in our [examples library](/python/examples).
@@ -4,19 +4,19 @@ acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
4
4
  acp_sdk/client/client.py,sha256=W6iXxH65UUODnvt3tnvgdqT_pP1meZUF2HwJpWCM0BM,5029
5
5
  acp_sdk/models/__init__.py,sha256=numSDBDT1QHx7n_Y3Deb5VOvKWcUBxbOEaMwQBSRHxc,151
6
6
  acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
7
- acp_sdk/models/models.py,sha256=ACZPQp-wm43tdlx83hrX7QmdtBRWVXvU5bsotUOidHM,3908
8
- acp_sdk/models/schemas.py,sha256=a0T29vfy93uVIce3ikbd3GSTdKBokMmfXWNVRJD3Eb8,662
7
+ acp_sdk/models/models.py,sha256=JhjI_M9GO-1lMrpZJo0IChDVrzUfnDyH5R5hFcqkXEE,3936
8
+ acp_sdk/models/schemas.py,sha256=TM_0aRsBj_9G__zzWPYDx4wAXsGs2RUH57IOTi4--Ek,728
9
9
  acp_sdk/server/__init__.py,sha256=CowMcwN_WSsnO_-ZoqWQKtNVfa21MW3X3trZ9haJjaA,329
10
10
  acp_sdk/server/agent.py,sha256=SZfJK634d4s8fRh0u1Tc56l58XCRHAh3hq5Hcabh6tI,3442
11
- acp_sdk/server/app.py,sha256=7alacNo9y_f0-BjZI0dm_rzSweW-n5YiuY0hrVtEIR8,5420
12
- acp_sdk/server/bundle.py,sha256=ZVkIOwWfYgi-VyGYSMKr9WrG_WzYbej-LD-YPUWQdeg,4982
11
+ acp_sdk/server/app.py,sha256=rf8I2qcaaXoM-36Za8vs3Re7gygcTSMs4MXbMUBJENU,5803
12
+ acp_sdk/server/bundle.py,sha256=LZzinifpbKxEBivoxOSdxUmzEcu3XNAkCiIJc2Ivveg,4987
13
13
  acp_sdk/server/context.py,sha256=MgnLV6qcDIhc_0BjW7r4Jj1tHts4ZuwpdTGIBnz2Mgo,1036
14
14
  acp_sdk/server/errors.py,sha256=fWlgVsQ5hs_AXwzc-wvy6QgoDWEMRUBlSrfJfhHHMyE,2085
15
15
  acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
16
- acp_sdk/server/server.py,sha256=xSS_TjxQ5xi_X9RJDNjFsU0JEytLlCCSqbp6sBVXXhY,4442
16
+ acp_sdk/server/server.py,sha256=B1XGptw2kTgaB52klI4aNqDAlmyT2rNpMtKDSCxyVc4,8665
17
17
  acp_sdk/server/telemetry.py,sha256=EwmtUrWMYid7XHiX76V1J6CigJPa2NrzEPOX0fBoY3o,1838
18
18
  acp_sdk/server/types.py,sha256=2yJPkfUzjVIhHmc0SegGTMqDROe2uFgycb-7CATvYVw,161
19
19
  acp_sdk/server/utils.py,sha256=EfrF9VCyVk3AM_ao-BIB9EzGbfTrh4V2Bz-VFr6f6Sg,351
20
- acp_sdk-0.1.0rc5.dist-info/METADATA,sha256=laguwvWACy9Yslwj_Wh2p25rfEciRu217HEGGXTIOKg,2147
21
- acp_sdk-0.1.0rc5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- acp_sdk-0.1.0rc5.dist-info/RECORD,,
20
+ acp_sdk-0.1.0rc7.dist-info/METADATA,sha256=IoDvdN2USySSWbm7I05iIuuRz-7vCzaBtsnilmgW5IQ,2033
21
+ acp_sdk-0.1.0rc7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ acp_sdk-0.1.0rc7.dist-info/RECORD,,