acp-sdk 1.0.0rc3__py3-none-any.whl → 1.0.0rc4__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.
@@ -1,2 +1,3 @@
1
1
  from acp_sdk.models.errors import * # noqa: F403
2
2
  from acp_sdk.models.models import * # noqa: F403
3
+ from acp_sdk.models.schemas import * # noqa: F403
acp_sdk/models/models.py CHANGED
@@ -175,43 +175,7 @@ RunEvent = Union[
175
175
  ]
176
176
 
177
177
 
178
- class RunCreateRequest(BaseModel):
179
- agent_name: AgentName
180
- session_id: SessionId | None = None
181
- input: Message
182
- mode: RunMode = RunMode.SYNC
183
-
184
-
185
- class RunCreateResponse(Run):
186
- pass
187
-
188
-
189
- class RunResumeRequest(BaseModel):
190
- await_: AwaitResume = Field(alias="await")
191
- mode: RunMode
192
-
193
-
194
- class RunResumeResponse(Run):
195
- pass
196
-
197
-
198
- class RunReadResponse(Run):
199
- pass
200
-
201
-
202
- class RunCancelResponse(Run):
203
- pass
204
-
205
-
206
178
  class Agent(BaseModel):
207
179
  name: str
208
180
  description: str | None = None
209
181
  metadata: Metadata = Metadata()
210
-
211
-
212
- class AgentsListResponse(BaseModel):
213
- agents: list[Agent]
214
-
215
-
216
- class AgentReadResponse(Agent):
217
- pass
@@ -0,0 +1,39 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ from acp_sdk.models.models import Agent, AgentName, AwaitResume, Message, Run, RunMode, SessionId
4
+
5
+
6
+ class AgentsListResponse(BaseModel):
7
+ agents: list[Agent]
8
+
9
+
10
+ class AgentReadResponse(Agent):
11
+ pass
12
+
13
+
14
+ class RunCreateRequest(BaseModel):
15
+ agent_name: AgentName
16
+ session_id: SessionId | None = None
17
+ input: Message
18
+ mode: RunMode = RunMode.SYNC
19
+
20
+
21
+ class RunCreateResponse(Run):
22
+ pass
23
+
24
+
25
+ class RunResumeRequest(BaseModel):
26
+ await_: AwaitResume = Field(alias="await")
27
+ mode: RunMode
28
+
29
+
30
+ class RunResumeResponse(Run):
31
+ pass
32
+
33
+
34
+ class RunReadResponse(Run):
35
+ pass
36
+
37
+
38
+ class RunCancelResponse(Run):
39
+ pass
@@ -1,3 +1,8 @@
1
1
  from acp_sdk.server.agent import Agent as Agent
2
- from acp_sdk.server.server import create_app as create_app
3
- from acp_sdk.server.server import serve as serve
2
+ from acp_sdk.server.agent import SyncAgent as SyncAgent
3
+ from acp_sdk.server.app import create_app as create_app
4
+ from acp_sdk.server.context import Context as Context
5
+ from acp_sdk.server.context import SyncContext as SyncContext
6
+ from acp_sdk.server.server import Server as Server
7
+ from acp_sdk.server.types import RunYield as RunYield
8
+ from acp_sdk.server.types import RunYieldResume as RunYieldResume
acp_sdk/server/agent.py CHANGED
@@ -1,15 +1,18 @@
1
1
  import abc
2
+ import asyncio
2
3
  from collections.abc import AsyncGenerator
4
+ from concurrent.futures import ThreadPoolExecutor
5
+
6
+ import janus
3
7
 
4
8
  from acp_sdk.models import (
5
9
  AgentName,
6
- Await,
7
- AwaitResume,
8
10
  Message,
9
11
  SessionId,
10
12
  )
11
13
  from acp_sdk.models.models import Metadata
12
- from acp_sdk.server.context import Context
14
+ from acp_sdk.server.context import Context, SyncContext
15
+ from acp_sdk.server.types import RunYield, RunYieldResume
13
16
 
14
17
 
15
18
  class Agent(abc.ABC):
@@ -26,10 +29,46 @@ class Agent(abc.ABC):
26
29
  return Metadata()
27
30
 
28
31
  @abc.abstractmethod
29
- def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
32
+ def run(
33
+ self, input: Message, context: Context, executor: ThreadPoolExecutor
34
+ ) -> AsyncGenerator[RunYield, RunYieldResume]:
30
35
  pass
31
36
 
32
37
  async def session(self, session_id: SessionId | None) -> SessionId | None:
33
38
  if session_id:
34
39
  raise NotImplementedError()
35
40
  return None
41
+
42
+
43
+ class SyncAgent(Agent):
44
+ @abc.abstractmethod
45
+ def run_sync(self, input: Message, context: SyncContext, executor: ThreadPoolExecutor) -> RunYield | None:
46
+ pass
47
+
48
+ async def run(
49
+ self, input: Message, context: Context, executor: ThreadPoolExecutor
50
+ ) -> AsyncGenerator[RunYield, RunYieldResume]:
51
+ yield_queue: janus.Queue[RunYield] = janus.Queue()
52
+ yield_resume_queue: janus.Queue[RunYieldResume] = janus.Queue()
53
+
54
+ run_future = asyncio.get_running_loop().run_in_executor(
55
+ executor,
56
+ self.run_sync,
57
+ input,
58
+ SyncContext(
59
+ session_id=context.session_id,
60
+ yield_queue=yield_queue.sync_q,
61
+ yield_resume_queue=yield_resume_queue.sync_q,
62
+ ),
63
+ executor,
64
+ )
65
+
66
+ while True:
67
+ yield_task = asyncio.create_task(yield_queue.async_q.get())
68
+ done, _ = await asyncio.wait([yield_task, run_future], return_when=asyncio.FIRST_COMPLETED)
69
+ if yield_task in done:
70
+ resume = yield await yield_task
71
+ await yield_resume_queue.async_q.put(resume)
72
+ if run_future in done:
73
+ yield await run_future
74
+ break
acp_sdk/server/app.py ADDED
@@ -0,0 +1,161 @@
1
+ import asyncio
2
+ from collections.abc import AsyncGenerator
3
+ from concurrent.futures import ThreadPoolExecutor
4
+ from contextlib import asynccontextmanager
5
+
6
+ from fastapi import FastAPI, HTTPException, status
7
+ from fastapi.responses import JSONResponse, StreamingResponse
8
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
9
+
10
+ from acp_sdk.models import (
11
+ Agent as AgentModel,
12
+ )
13
+ from acp_sdk.models import (
14
+ AgentName,
15
+ AgentReadResponse,
16
+ AgentsListResponse,
17
+ Run,
18
+ RunCancelResponse,
19
+ RunCreateRequest,
20
+ RunCreateResponse,
21
+ RunId,
22
+ RunMode,
23
+ RunReadResponse,
24
+ RunResumeRequest,
25
+ RunResumeResponse,
26
+ RunStatus,
27
+ )
28
+ from acp_sdk.models.errors import ACPError
29
+ from acp_sdk.server.agent import Agent
30
+ from acp_sdk.server.bundle import RunBundle
31
+ from acp_sdk.server.errors import (
32
+ RequestValidationError,
33
+ StarletteHTTPException,
34
+ acp_error_handler,
35
+ catch_all_exception_handler,
36
+ http_exception_handler,
37
+ validation_exception_handler,
38
+ )
39
+ from acp_sdk.server.utils import stream_sse
40
+
41
+
42
+ def create_app(*agents: Agent) -> FastAPI:
43
+ executor: ThreadPoolExecutor
44
+
45
+ @asynccontextmanager
46
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
47
+ nonlocal executor
48
+ with ThreadPoolExecutor(max_workers=5) as exec:
49
+ executor = exec
50
+ yield
51
+
52
+ app = FastAPI(lifespan=lifespan)
53
+
54
+ FastAPIInstrumentor.instrument_app(app)
55
+
56
+ agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
57
+ runs: dict[RunId, RunBundle] = {}
58
+
59
+ app.exception_handler(ACPError)(acp_error_handler)
60
+ app.exception_handler(StarletteHTTPException)(http_exception_handler)
61
+ app.exception_handler(RequestValidationError)(validation_exception_handler)
62
+ app.exception_handler(Exception)(catch_all_exception_handler)
63
+
64
+ def find_run_bundle(run_id: RunId) -> RunBundle:
65
+ bundle = runs.get(run_id)
66
+ if not bundle:
67
+ raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
68
+ return bundle
69
+
70
+ def find_agent(agent_name: AgentName) -> Agent:
71
+ agent = agents.get(agent_name, None)
72
+ if not agent:
73
+ raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")
74
+ return agent
75
+
76
+ @app.get("/agents")
77
+ async def list_agents() -> AgentsListResponse:
78
+ return AgentsListResponse(
79
+ agents=[
80
+ AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
81
+ for agent in agents.values()
82
+ ]
83
+ )
84
+
85
+ @app.get("/agents/{name}")
86
+ async def read_agent(name: AgentName) -> AgentReadResponse:
87
+ agent = find_agent(name)
88
+ return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
89
+
90
+ @app.post("/runs")
91
+ async def create_run(request: RunCreateRequest) -> RunCreateResponse:
92
+ agent = find_agent(request.agent_name)
93
+ bundle = RunBundle(
94
+ agent=agent,
95
+ run=Run(
96
+ agent_name=agent.name,
97
+ session_id=request.session_id,
98
+ ),
99
+ )
100
+
101
+ nonlocal executor
102
+ bundle.task = asyncio.create_task(bundle.execute(request.input, executor=executor))
103
+ runs[bundle.run.run_id] = bundle
104
+
105
+ match request.mode:
106
+ case RunMode.STREAM:
107
+ return StreamingResponse(
108
+ stream_sse(bundle),
109
+ media_type="text/event-stream",
110
+ )
111
+ case RunMode.SYNC:
112
+ await bundle.join()
113
+ return bundle.run
114
+ case RunMode.ASYNC:
115
+ return JSONResponse(
116
+ status_code=status.HTTP_202_ACCEPTED,
117
+ content=bundle.run.model_dump(),
118
+ )
119
+ case _:
120
+ raise NotImplementedError()
121
+
122
+ @app.get("/runs/{run_id}")
123
+ async def read_run(run_id: RunId) -> RunReadResponse:
124
+ bundle = find_run_bundle(run_id)
125
+ return bundle.run
126
+
127
+ @app.post("/runs/{run_id}")
128
+ async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
129
+ bundle = find_run_bundle(run_id)
130
+ bundle.stream_queue = asyncio.Queue() # TODO improve
131
+ await bundle.await_queue.put(request.await_)
132
+ match request.mode:
133
+ case RunMode.STREAM:
134
+ return StreamingResponse(
135
+ stream_sse(bundle),
136
+ media_type="text/event-stream",
137
+ )
138
+ case RunMode.SYNC:
139
+ await bundle.join()
140
+ return bundle.run
141
+ case RunMode.ASYNC:
142
+ return JSONResponse(
143
+ status_code=status.HTTP_202_ACCEPTED,
144
+ content=bundle.run.model_dump(),
145
+ )
146
+ case _:
147
+ raise NotImplementedError()
148
+
149
+ @app.post("/runs/{run_id}/cancel")
150
+ async def cancel_run(run_id: RunId) -> RunCancelResponse:
151
+ bundle = find_run_bundle(run_id)
152
+ if bundle.run.status.is_terminal:
153
+ raise HTTPException(
154
+ status_code=403,
155
+ detail=f"Run with terminal status {bundle.run.status} can't be cancelled",
156
+ )
157
+ bundle.task.cancel()
158
+ bundle.run.status = RunStatus.CANCELLING
159
+ return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
160
+
161
+ return app
acp_sdk/server/bundle.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from collections.abc import AsyncGenerator
4
+ from concurrent.futures import ThreadPoolExecutor
4
5
 
5
6
  from opentelemetry import trace
6
7
  from pydantic import ValidationError
@@ -68,7 +69,7 @@ class RunBundle:
68
69
  async def join(self) -> None:
69
70
  await self.await_or_terminate_event.wait()
70
71
 
71
- async def execute(self, input: Message) -> None:
72
+ async def execute(self, input: Message, *, executor: ThreadPoolExecutor) -> None:
72
73
  with trace.get_tracer(__name__).start_as_current_span("execute"):
73
74
  run_logger = logging.LoggerAdapter(logger, {"run_id": self.run.run_id})
74
75
 
@@ -77,7 +78,9 @@ class RunBundle:
77
78
  self.run.session_id = await self.agent.session(self.run.session_id)
78
79
  run_logger.info("Session loaded")
79
80
 
80
- generator = self.agent.run(input=input, context=Context(session_id=self.run.session_id))
81
+ generator = self.agent.run(
82
+ input=input, context=Context(session_id=self.run.session_id), executor=executor
83
+ )
81
84
  run_logger.info("Run started")
82
85
 
83
86
  self.run.status = RunStatus.IN_PROGRESS
acp_sdk/server/context.py CHANGED
@@ -1,6 +1,26 @@
1
+ import janus
2
+
1
3
  from acp_sdk.models import SessionId
4
+ from acp_sdk.server.types import RunYield, RunYieldResume
2
5
 
3
6
 
4
7
  class Context:
5
8
  def __init__(self, *, session_id: SessionId | None = None) -> None:
6
9
  self.session_id = session_id
10
+
11
+
12
+ class SyncContext(Context):
13
+ def __init__(
14
+ self,
15
+ *,
16
+ session_id: SessionId | None = None,
17
+ yield_queue: janus.SyncQueue[RunYield],
18
+ yield_resume_queue: janus.SyncQueue[RunYieldResume],
19
+ ) -> None:
20
+ super().__init__(session_id=session_id)
21
+ self._yield_queue = yield_queue
22
+ self._yield_resume_queue = yield_resume_queue
23
+
24
+ def yield_(self, data: RunYield) -> RunYieldResume:
25
+ self._yield_queue.put(data)
26
+ return self._yield_resume_queue.get()
acp_sdk/server/server.py CHANGED
@@ -1,164 +1,110 @@
1
- import asyncio
2
- from typing import Any
3
-
4
- from fastapi import FastAPI, HTTPException, status
5
- from fastapi.responses import JSONResponse, StreamingResponse
6
- from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
7
-
8
- from acp_sdk.models import (
9
- Agent as AgentModel,
10
- )
11
- from acp_sdk.models import (
12
- AgentName,
13
- AgentReadResponse,
14
- AgentsListResponse,
15
- Run,
16
- RunCancelResponse,
17
- RunCreateRequest,
18
- RunCreateResponse,
19
- RunId,
20
- RunMode,
21
- RunReadResponse,
22
- RunResumeRequest,
23
- RunResumeResponse,
24
- RunStatus,
25
- )
26
- from acp_sdk.models.errors import ACPError
27
- from acp_sdk.server.agent import Agent
28
- from acp_sdk.server.bundle import RunBundle
29
- from acp_sdk.server.errors import (
30
- RequestValidationError,
31
- StarletteHTTPException,
32
- acp_error_handler,
33
- catch_all_exception_handler,
34
- http_exception_handler,
35
- validation_exception_handler,
36
- )
1
+ import inspect
2
+ from collections.abc import AsyncGenerator
3
+ from concurrent.futures import ThreadPoolExecutor
4
+ from typing import Any, Callable
5
+
6
+ from acp_sdk.models import Message
7
+ from acp_sdk.server.agent import Agent, SyncAgent
8
+ from acp_sdk.server.app import create_app
9
+ from acp_sdk.server.context import Context
37
10
  from acp_sdk.server.logging import configure_logger as configure_logger_func
38
11
  from acp_sdk.server.telemetry import configure_telemetry as configure_telemetry_func
39
- from acp_sdk.server.utils import stream_sse
40
-
41
-
42
- def create_app(*agents: Agent) -> FastAPI:
43
- app = FastAPI(title="acp-agents")
44
-
45
- FastAPIInstrumentor.instrument_app(app)
46
-
47
- agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
48
- runs: dict[RunId, RunBundle] = {}
49
-
50
- app.exception_handler(ACPError)(acp_error_handler)
51
- app.exception_handler(StarletteHTTPException)(http_exception_handler)
52
- app.exception_handler(RequestValidationError)(validation_exception_handler)
53
- app.exception_handler(Exception)(catch_all_exception_handler)
54
-
55
- def find_run_bundle(run_id: RunId) -> RunBundle:
56
- bundle = runs.get(run_id)
57
- if not bundle:
58
- raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
59
- return bundle
60
-
61
- def find_agent(agent_name: AgentName) -> Agent:
62
- agent = agents.get(agent_name, None)
63
- if not agent:
64
- raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")
65
- return agent
66
-
67
- @app.get("/agents")
68
- async def list_agents() -> AgentsListResponse:
69
- return AgentsListResponse(
70
- agents=[
71
- AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
72
- for agent in agents.values()
73
- ]
74
- )
75
-
76
- @app.get("/agents/{name}")
77
- async def read_agent(name: AgentName) -> AgentReadResponse:
78
- agent = find_agent(name)
79
- return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
80
-
81
- @app.post("/runs")
82
- async def create_run(request: RunCreateRequest) -> RunCreateResponse:
83
- agent = find_agent(request.agent_name)
84
- bundle = RunBundle(
85
- agent=agent,
86
- run=Run(
87
- agent_name=agent.name,
88
- session_id=request.session_id,
89
- ),
90
- )
91
-
92
- bundle.task = asyncio.create_task(bundle.execute(request.input))
93
- runs[bundle.run.run_id] = bundle
94
-
95
- match request.mode:
96
- case RunMode.STREAM:
97
- return StreamingResponse(
98
- stream_sse(bundle),
99
- media_type="text/event-stream",
100
- )
101
- case RunMode.SYNC:
102
- await bundle.join()
103
- return bundle.run
104
- case RunMode.ASYNC:
105
- return JSONResponse(
106
- status_code=status.HTTP_202_ACCEPTED,
107
- content=bundle.run.model_dump(),
108
- )
109
- case _:
110
- raise NotImplementedError()
111
-
112
- @app.get("/runs/{run_id}")
113
- async def read_run(run_id: RunId) -> RunReadResponse:
114
- bundle = find_run_bundle(run_id)
115
- return bundle.run
116
-
117
- @app.post("/runs/{run_id}")
118
- async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
119
- bundle = find_run_bundle(run_id)
120
- bundle.stream_queue = asyncio.Queue() # TODO improve
121
- await bundle.await_queue.put(request.await_)
122
- match request.mode:
123
- case RunMode.STREAM:
124
- return StreamingResponse(
125
- stream_sse(bundle),
126
- media_type="text/event-stream",
127
- )
128
- case RunMode.SYNC:
129
- await bundle.join()
130
- return bundle.run
131
- case RunMode.ASYNC:
132
- return JSONResponse(
133
- status_code=status.HTTP_202_ACCEPTED,
134
- content=bundle.run.model_dump(),
135
- )
136
- case _:
137
- raise NotImplementedError()
138
-
139
- @app.post("/runs/{run_id}/cancel")
140
- async def cancel_run(run_id: RunId) -> RunCancelResponse:
141
- bundle = find_run_bundle(run_id)
142
- if bundle.run.status.is_terminal:
143
- raise HTTPException(
144
- status_code=403,
145
- detail=f"Run with terminal status {bundle.run.status} can't be cancelled",
146
- )
147
- bundle.task.cancel()
148
- bundle.run.status = RunStatus.CANCELLING
149
- return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
150
-
151
- return app
152
-
153
-
154
- def serve(
155
- *agents: Agent, configure_logger: bool = True, configure_telemetry: bool = False, **kwargs: dict[str, Any]
156
- ) -> None:
157
- import uvicorn
158
-
159
- if configure_logger:
160
- configure_logger_func()
161
- if configure_telemetry:
162
- configure_telemetry_func()
163
-
164
- uvicorn.run(create_app(*agents), **kwargs)
12
+ from acp_sdk.server.types import RunYield, RunYieldResume
13
+
14
+
15
+ class Server:
16
+ def __init__(self) -> None:
17
+ self.agents: list[Agent] = []
18
+
19
+ def agent(self, name: str | None = None, description: str | None = None) -> Callable:
20
+ """Decorator to register an agent."""
21
+
22
+ def decorator(fn: Callable) -> Callable:
23
+ # check agent's function signature
24
+ signature = inspect.signature(fn)
25
+ parameters = list(signature.parameters.values())
26
+
27
+ # validate agent's function
28
+ if inspect.isasyncgenfunction(fn):
29
+ if len(parameters) != 2:
30
+ raise TypeError(
31
+ "The agent generator function must have one 'input' argument and one 'context' argument"
32
+ )
33
+ else:
34
+ if len(parameters) != 2:
35
+ raise TypeError("The agent function must have one 'input' argument and one 'context' argument")
36
+
37
+ agent: Agent
38
+ if inspect.isasyncgenfunction(fn):
39
+
40
+ class DecoratedAgent(Agent):
41
+ @property
42
+ def name(self) -> str:
43
+ return name or fn.__name__
44
+
45
+ @property
46
+ def description(self) -> str:
47
+ return description or fn.__doc__ or ""
48
+
49
+ async def run(
50
+ self, input: Message, context: Context, executor: ThreadPoolExecutor
51
+ ) -> AsyncGenerator[RunYield, RunYieldResume]:
52
+ gen: AsyncGenerator[RunYield, RunYieldResume] = fn(input, context)
53
+ value = None
54
+ while True:
55
+ try:
56
+ value = yield await gen.asend(value)
57
+ except StopAsyncIteration:
58
+ break
59
+
60
+ agent = DecoratedAgent()
61
+ elif inspect.iscoroutinefunction(fn):
62
+
63
+ class DecoratedAgent(Agent):
64
+ @property
65
+ def name(self) -> str:
66
+ return name or fn.__name__
67
+
68
+ @property
69
+ def description(self) -> str:
70
+ return description or fn.__doc__ or ""
71
+
72
+ async def run(
73
+ self, input: Message, context: Context, executor: ThreadPoolExecutor
74
+ ) -> AsyncGenerator[RunYield, RunYieldResume]:
75
+ yield await fn(input, context)
76
+
77
+ agent = DecoratedAgent()
78
+ else:
79
+
80
+ class DecoratedAgent(SyncAgent):
81
+ @property
82
+ def name(self) -> str:
83
+ return name or fn.__name__
84
+
85
+ @property
86
+ def description(self) -> str:
87
+ return description or fn.__doc__ or ""
88
+
89
+ def run_sync(self, input: Message, context: Context, executor: ThreadPoolExecutor) -> None:
90
+ return fn(input, context)
91
+
92
+ agent = DecoratedAgent()
93
+
94
+ self.register(agent)
95
+ return fn
96
+
97
+ return decorator
98
+
99
+ def register(self, *agents: Agent) -> None:
100
+ self.agents.extend(agents)
101
+
102
+ def run(self, configure_logger: bool = True, configure_telemetry: bool = False, **kwargs: dict[str, Any]) -> None:
103
+ import uvicorn
104
+
105
+ if configure_logger:
106
+ configure_logger_func()
107
+ if configure_telemetry:
108
+ configure_telemetry_func()
109
+
110
+ uvicorn.run(create_app(*self.agents), **kwargs)
@@ -0,0 +1,6 @@
1
+ from typing import Any
2
+
3
+ from acp_sdk.models import Await, AwaitResume, Message
4
+
5
+ RunYield = Message | Await | dict[str | Any]
6
+ RunYieldResume = AwaitResume | None
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: acp-sdk
3
+ Version: 1.0.0rc4
4
+ Summary: Agent Communication Protocol SDK
5
+ Requires-Python: <4.0,>=3.11
6
+ Requires-Dist: opentelemetry-api>=1.31.1
7
+ Requires-Dist: pydantic>=2.11.1
8
+ Provides-Extra: client
9
+ Requires-Dist: httpx-sse>=0.4.0; extra == 'client'
10
+ Requires-Dist: httpx>=0.28.1; extra == 'client'
11
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == 'client'
12
+ Provides-Extra: server
13
+ Requires-Dist: fastapi[standard]>=0.115.8; extra == 'server'
14
+ Requires-Dist: janus>=2.0.0; extra == 'server'
15
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.31.1; extra == 'server'
16
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == 'server'
17
+ Requires-Dist: opentelemetry-sdk>=1.31.1; extra == 'server'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Agent Communication Protocol SDK for Python
21
+
22
+ Agent Communication Protocol SDK for Python provides allows developers to serve and consume agents over the Agent Communication Protocol.
23
+
24
+ ## Prerequisites
25
+
26
+ ✅ Python >= 3.11
27
+
28
+ ## Installation
29
+
30
+ Install to use client:
31
+
32
+ ```shell
33
+ pip install acp-sdk[client]
34
+ ```
35
+
36
+ Install to use server:
37
+
38
+ ```shell
39
+ pip install acp-sdk[server]
40
+ ```
41
+
42
+ ## Overview
43
+
44
+ ### Client
45
+
46
+ The `client` submodule exposes [httpx]() based client with simple methods for communication over ACP.
47
+
48
+ ```python
49
+ async with Client(base_url="http://localhost:8000") as client:
50
+ run = await client.run_sync(agent="echo", input=Message(TextMessagePart(content="Howdy!")))
51
+ print(run.output)
52
+ ```
53
+
54
+ ### Server
55
+
56
+ The `server` submodule exposes [fastapi] application factory that makes it easy to expose any agent over ACP.
57
+
58
+ ```python
59
+ class EchoAgent(Agent):
60
+ @property
61
+ def name(self) -> str:
62
+ return "echo"
63
+
64
+ @property
65
+ def description(self) -> str:
66
+ return "Echoes everything"
67
+
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
+
74
+
75
+ serve(EchoAgent())
76
+ ```
77
+
78
+ ➡️ Explore more in our [examples library](/python/examples).
@@ -0,0 +1,22 @@
1
+ acp_sdk/__init__.py,sha256=XlcAbmvJfvJK5tmOFdxXP19aDwxxdG_jygsTCvXtjTk,43
2
+ acp_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
4
+ acp_sdk/client/client.py,sha256=W6iXxH65UUODnvt3tnvgdqT_pP1meZUF2HwJpWCM0BM,5029
5
+ acp_sdk/models/__init__.py,sha256=numSDBDT1QHx7n_Y3Deb5VOvKWcUBxbOEaMwQBSRHxc,151
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
9
+ acp_sdk/server/__init__.py,sha256=Qd0uTLaStTGxRaHvtXQpTWoruGvRGu8L17d8IMoPgu8,447
10
+ acp_sdk/server/agent.py,sha256=pAJkUR0OJn7qzsW7JPk3YIjRoPrSFljcWXcH4kBuRCM,2214
11
+ acp_sdk/server/app.py,sha256=7alacNo9y_f0-BjZI0dm_rzSweW-n5YiuY0hrVtEIR8,5420
12
+ acp_sdk/server/bundle.py,sha256=J-1prwhTHeK29ONjFERbv7gNY3YVSCeVI0p5687y-WQ,5076
13
+ acp_sdk/server/context.py,sha256=DIs4T61nIkangT2Mdk5oEHmK__XzfssqtodQ266CvoQ,758
14
+ acp_sdk/server/errors.py,sha256=fWlgVsQ5hs_AXwzc-wvy6QgoDWEMRUBlSrfJfhHHMyE,2085
15
+ acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
16
+ acp_sdk/server/server.py,sha256=hhju0fXo0vmkD5QYgT_zbZUDZXnCCFQzF3AZCMSgWko,4120
17
+ acp_sdk/server/telemetry.py,sha256=EwmtUrWMYid7XHiX76V1J6CigJPa2NrzEPOX0fBoY3o,1838
18
+ acp_sdk/server/types.py,sha256=2yJPkfUzjVIhHmc0SegGTMqDROe2uFgycb-7CATvYVw,161
19
+ acp_sdk/server/utils.py,sha256=EfrF9VCyVk3AM_ao-BIB9EzGbfTrh4V2Bz-VFr6f6Sg,351
20
+ acp_sdk-1.0.0rc4.dist-info/METADATA,sha256=qHSrUOETNJflMDnlTh8dWQh1sHNdO0OZOStlOJgzjdk,2147
21
+ acp_sdk-1.0.0rc4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ acp_sdk-1.0.0rc4.dist-info/RECORD,,
@@ -1,51 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: acp-sdk
3
- Version: 1.0.0rc3
4
- Summary: Agent Communication Protocol SDK
5
- Requires-Python: <4.0,>=3.11
6
- Requires-Dist: opentelemetry-api>=1.31.1
7
- Requires-Dist: pydantic>=2.11.1
8
- Provides-Extra: client
9
- Requires-Dist: httpx-sse>=0.4.0; extra == 'client'
10
- Requires-Dist: httpx>=0.28.1; extra == 'client'
11
- Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == 'client'
12
- Provides-Extra: server
13
- Requires-Dist: fastapi[standard]>=0.115.8; extra == 'server'
14
- Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.31.1; extra == 'server'
15
- Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == 'server'
16
- Requires-Dist: opentelemetry-sdk>=1.31.1; extra == 'server'
17
- Description-Content-Type: text/markdown
18
-
19
- # Agent Communication Protocol SDK for Python
20
-
21
- ## Prerequisites
22
-
23
- ✅ Python >= 3.13
24
-
25
- ## Installation
26
-
27
- Install using pip:
28
-
29
- ```shell
30
- pip install acp-sdk
31
- ```
32
-
33
- ## Examples
34
-
35
- The SDK can be used to implement both clients and servers.
36
-
37
- ### Server
38
-
39
- ```shell
40
- python examples/servers/echo.py
41
- ```
42
-
43
- ### Client
44
-
45
- To run an example client:
46
-
47
- ```shell
48
- python examples/clients/simple.py
49
- ```
50
-
51
- ➡️ Explore more in our [examples library](/python/examples).
@@ -1,19 +0,0 @@
1
- acp_sdk/__init__.py,sha256=XlcAbmvJfvJK5tmOFdxXP19aDwxxdG_jygsTCvXtjTk,43
2
- acp_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
4
- acp_sdk/client/client.py,sha256=W6iXxH65UUODnvt3tnvgdqT_pP1meZUF2HwJpWCM0BM,5029
5
- acp_sdk/models/__init__.py,sha256=klWKtCE_R8LIQEin0nqgiEyzGZbcvxntalk6qWqr7Js,100
6
- acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
7
- acp_sdk/models/models.py,sha256=HnDBIuANsiM-Kl7T1aElGgnOib14OxV28RyrxDQyiig,4433
8
- acp_sdk/server/__init__.py,sha256=aVVbBzZdFdX3vnIgX0r4WfTHbAq1tVh9tt-oKJ7BxRg,156
9
- acp_sdk/server/agent.py,sha256=hMYrbS6b-4nYBB2NV9Zw5RPbmD3oUMl5y-OcnjvNoo4,802
10
- acp_sdk/server/bundle.py,sha256=kMLyHQZxM-hL-B79Qcxu4JdvA_M86Tj_kk1C2bSQ9Fw,4936
11
- acp_sdk/server/context.py,sha256=oILWXHNTXq3mLX0RM7jiIKMC19dEiw7AYy6X0L6OUXg,163
12
- acp_sdk/server/errors.py,sha256=fWlgVsQ5hs_AXwzc-wvy6QgoDWEMRUBlSrfJfhHHMyE,2085
13
- acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
14
- acp_sdk/server/server.py,sha256=yN_QzPBCNTlfkXsae6UpDliEn_PONliH4OglvuMD-Sc,5498
15
- acp_sdk/server/telemetry.py,sha256=EwmtUrWMYid7XHiX76V1J6CigJPa2NrzEPOX0fBoY3o,1838
16
- acp_sdk/server/utils.py,sha256=EfrF9VCyVk3AM_ao-BIB9EzGbfTrh4V2Bz-VFr6f6Sg,351
17
- acp_sdk-1.0.0rc3.dist-info/METADATA,sha256=dGD1WNqlpUAE3rYQ-axzeWsmcS8RRxN0UFh_LLx_g9U,1183
18
- acp_sdk-1.0.0rc3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- acp_sdk-1.0.0rc3.dist-info/RECORD,,