acp-sdk 0.7.2__py3-none-any.whl → 0.8.0__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/client/client.py CHANGED
@@ -23,11 +23,12 @@ from acp_sdk.models import (
23
23
  AwaitResume,
24
24
  Error,
25
25
  Event,
26
+ PingResponse,
26
27
  Run,
27
28
  RunCancelResponse,
28
- RunCreatedEvent,
29
29
  RunCreateRequest,
30
30
  RunCreateResponse,
31
+ RunEventsListResponse,
31
32
  RunId,
32
33
  RunMode,
33
34
  RunResumeRequest,
@@ -121,9 +122,10 @@ class Client:
121
122
  return Agent(**response.model_dump())
122
123
 
123
124
  async def ping(self) -> bool:
124
- response = await self._client.get("/healthcheck")
125
+ response = await self._client.get("/ping")
125
126
  self._raise_error(response)
126
- return response.json() == "OK"
127
+ PingResponse.model_validate(response.json())
128
+ return
127
129
 
128
130
  async def run_sync(self, input: Input, *, agent: AgentName) -> Run:
129
131
  response = await self._client.post(
@@ -137,7 +139,6 @@ class Client:
137
139
  )
138
140
  self._raise_error(response)
139
141
  response = RunCreateResponse.model_validate(response.json())
140
- self._set_session(response)
141
142
  return Run(**response.model_dump())
142
143
 
143
144
  async def run_async(self, input: Input, *, agent: AgentName) -> Run:
@@ -152,7 +153,6 @@ class Client:
152
153
  )
153
154
  self._raise_error(response)
154
155
  response = RunCreateResponse.model_validate(response.json())
155
- self._set_session(response)
156
156
  return Run(**response.model_dump())
157
157
 
158
158
  async def run_stream(self, input: Input, *, agent: AgentName) -> AsyncIterator[Event]:
@@ -168,8 +168,6 @@ class Client:
168
168
  ).model_dump_json(),
169
169
  ) as event_source:
170
170
  async for event in self._validate_stream(event_source):
171
- if isinstance(event, RunCreatedEvent):
172
- self._set_session(event.run)
173
171
  yield event
174
172
 
175
173
  async def run_status(self, *, run_id: RunId) -> Run:
@@ -177,6 +175,13 @@ class Client:
177
175
  self._raise_error(response)
178
176
  return Run.model_validate(response.json())
179
177
 
178
+ async def run_events(self, *, run_id: RunId) -> AsyncIterator[Event]:
179
+ response = await self._client.get(f"/runs/{run_id}/events")
180
+ self._raise_error(response)
181
+ response = RunEventsListResponse.model_validate(response.json())
182
+ for event in response.events:
183
+ yield event
184
+
180
185
  async def run_cancel(self, *, run_id: RunId) -> Run:
181
186
  response = await self._client.post(f"/runs/{run_id}/cancel")
182
187
  self._raise_error(response)
@@ -227,6 +232,3 @@ class Client:
227
232
  response.raise_for_status()
228
233
  except httpx.HTTPError:
229
234
  raise ACPError(Error.model_validate(response.json()))
230
-
231
- def _set_session(self, run: Run) -> None:
232
- self._session_id = run.session_id
acp_sdk/models/models.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import uuid
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
3
  from enum import Enum
4
4
  from typing import Any, Literal, Optional, Union
5
5
 
@@ -95,11 +95,17 @@ class Artifact(MessagePart):
95
95
 
96
96
  class Message(BaseModel):
97
97
  parts: list[MessagePart]
98
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
99
+ completed_at: datetime | None = Field(default_factory=lambda: datetime.now(timezone.utc))
98
100
 
99
101
  def __add__(self, other: "Message") -> "Message":
100
102
  if not isinstance(other, Message):
101
103
  raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
102
- return Message(parts=self.parts + other.parts)
104
+ return Message(
105
+ parts=self.parts + other.parts,
106
+ created_at=min(self.created_at, other.created_at),
107
+ completed_at=max(self.completed_at, other.completed_at),
108
+ )
103
109
 
104
110
  def __str__(self) -> str:
105
111
  return "".join(
@@ -134,7 +140,7 @@ class Message(BaseModel):
134
140
  parts[-1] = join(parts[-1], part)
135
141
  else:
136
142
  parts.append(part)
137
- return Message(parts=parts)
143
+ return Message(parts=parts, created_at=self.created_at, completed_at=self.completed_at)
138
144
 
139
145
 
140
146
  AgentName = str
@@ -185,6 +191,8 @@ class Run(BaseModel):
185
191
  await_request: AwaitRequest | None = None
186
192
  output: list[Message] = []
187
193
  error: Error | None = None
194
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
195
+ finished_at: datetime | None = None
188
196
 
189
197
 
190
198
  class MessageCreatedEvent(BaseModel):
acp_sdk/models/schemas.py CHANGED
@@ -1,6 +1,10 @@
1
1
  from pydantic import BaseModel
2
2
 
3
- from acp_sdk.models.models import Agent, AgentName, AwaitResume, Message, Run, RunMode, SessionId
3
+ from acp_sdk.models.models import Agent, AgentName, AwaitResume, Event, Message, Run, RunMode, SessionId
4
+
5
+
6
+ class PingResponse(BaseModel):
7
+ pass
4
8
 
5
9
 
6
10
  class AgentsListResponse(BaseModel):
@@ -37,3 +41,7 @@ class RunReadResponse(Run):
37
41
 
38
42
  class RunCancelResponse(Run):
39
43
  pass
44
+
45
+
46
+ class RunEventsListResponse(BaseModel):
47
+ events: list[Event]
acp_sdk/server/app.py CHANGED
@@ -21,6 +21,7 @@ from acp_sdk.models import (
21
21
  RunCancelResponse,
22
22
  RunCreateRequest,
23
23
  RunCreateResponse,
24
+ RunEventsListResponse,
24
25
  RunId,
25
26
  RunMode,
26
27
  RunReadResponse,
@@ -29,6 +30,7 @@ from acp_sdk.models import (
29
30
  SessionId,
30
31
  )
31
32
  from acp_sdk.models.errors import ACPError
33
+ from acp_sdk.models.schemas import PingResponse
32
34
  from acp_sdk.server.agent import Agent
33
35
  from acp_sdk.server.bundle import RunBundle
34
36
  from acp_sdk.server.errors import (
@@ -104,15 +106,15 @@ def create_app(
104
106
  agent = find_agent(name)
105
107
  return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
106
108
 
107
- @app.get("/healthcheck")
108
- async def healthcheck() -> str:
109
- return "OK"
109
+ @app.get("/ping")
110
+ async def ping() -> PingResponse:
111
+ return PingResponse()
110
112
 
111
113
  @app.post("/runs")
112
114
  async def create_run(request: RunCreateRequest) -> RunCreateResponse:
113
115
  agent = find_agent(request.agent_name)
114
116
 
115
- session = sessions.get(request.session_id, Session()) if request.session_id else Session()
117
+ session = sessions.get(request.session_id, Session(id=request.session_id)) if request.session_id else Session()
116
118
  nonlocal executor
117
119
  bundle = RunBundle(
118
120
  agent=agent,
@@ -155,6 +157,11 @@ def create_app(
155
157
  bundle = find_run_bundle(run_id)
156
158
  return bundle.run
157
159
 
160
+ @app.get("/runs/{run_id}/events")
161
+ async def list_run_events(run_id: RunId) -> RunEventsListResponse:
162
+ bundle = find_run_bundle(run_id)
163
+ return RunEventsListResponse(events=bundle.events)
164
+
158
165
  @app.post("/runs/{run_id}")
159
166
  async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
160
167
  bundle = find_run_bundle(run_id)
acp_sdk/server/bundle.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
  import logging
3
3
  from collections.abc import AsyncGenerator
4
4
  from concurrent.futures import ThreadPoolExecutor
5
+ from datetime import datetime, timezone
5
6
 
6
7
  from pydantic import BaseModel, ValidationError
7
8
 
@@ -43,6 +44,7 @@ class RunBundle:
43
44
  self.history = history
44
45
 
45
46
  self.stream_queue: asyncio.Queue[Event] = asyncio.Queue()
47
+ self.events: list[Event] = []
46
48
 
47
49
  self.await_queue: asyncio.Queue[AwaitResume] = asyncio.Queue(maxsize=1)
48
50
  self.await_or_terminate_event = asyncio.Event()
@@ -58,7 +60,9 @@ class RunBundle:
58
60
  self.stream_queue.task_done()
59
61
 
60
62
  async def emit(self, event: Event) -> None:
61
- await self.stream_queue.put(event)
63
+ freeze = event.model_copy(deep=True)
64
+ self.events.append(freeze)
65
+ await self.stream_queue.put(freeze)
62
66
 
63
67
  async def await_(self) -> AwaitResume:
64
68
  await self.stream_queue.put(None)
@@ -92,7 +96,9 @@ class RunBundle:
92
96
  async def flush_message() -> None:
93
97
  nonlocal in_message
94
98
  if in_message:
95
- await self.emit(MessageCompletedEvent(message=self.run.output[-1]))
99
+ message = self.run.output[-1]
100
+ message.completed_at = datetime.now(timezone.utc)
101
+ await self.emit(MessageCompletedEvent(message=message))
96
102
  in_message = False
97
103
 
98
104
  try:
@@ -114,7 +120,7 @@ class RunBundle:
114
120
  if isinstance(next, str):
115
121
  next = MessagePart(content=next)
116
122
  if not in_message:
117
- self.run.output.append(Message(parts=[]))
123
+ self.run.output.append(Message(parts=[], completed_at=None))
118
124
  in_message = True
119
125
  await self.emit(MessageCreatedEvent(message=self.run.output[-1]))
120
126
  self.run.output[-1].parts.append(next)
@@ -149,10 +155,12 @@ class RunBundle:
149
155
  except StopAsyncIteration:
150
156
  await flush_message()
151
157
  self.run.status = RunStatus.COMPLETED
158
+ self.run.finished_at = datetime.now(timezone.utc)
152
159
  await self.emit(RunCompletedEvent(run=self.run))
153
160
  run_logger.info("Run completed")
154
161
  except asyncio.CancelledError:
155
162
  self.run.status = RunStatus.CANCELLED
163
+ self.run.finished_at = datetime.now(timezone.utc)
156
164
  await self.emit(RunCancelledEvent(run=self.run))
157
165
  run_logger.info("Run cancelled")
158
166
  except Exception as e:
@@ -161,6 +169,7 @@ class RunBundle:
161
169
  else:
162
170
  self.run.error = Error(code=ErrorCode.SERVER_ERROR, message=str(e))
163
171
  self.run.status = RunStatus.FAILED
172
+ self.run.finished_at = datetime.now(timezone.utc)
164
173
  await self.emit(RunFailedEvent(run=self.run))
165
174
  run_logger.exception("Run failed")
166
175
  raise
acp_sdk/server/session.py CHANGED
@@ -7,8 +7,8 @@ from acp_sdk.server.bundle import RunBundle
7
7
 
8
8
 
9
9
  class Session:
10
- def __init__(self) -> None:
11
- self.id: SessionId = uuid.uuid4()
10
+ def __init__(self, id: SessionId | None = None) -> None:
11
+ self.id: SessionId = id or uuid.uuid4()
12
12
  self.bundles: list[RunBundle] = []
13
13
 
14
14
  def append(self, bundle: RunBundle) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acp-sdk
3
- Version: 0.7.2
3
+ Version: 0.8.0
4
4
  Summary: Agent Communication Protocol SDK
5
5
  Author: IBM Corp.
6
6
  Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
@@ -3,25 +3,25 @@ acp_sdk/instrumentation.py,sha256=JqSyvILN3sGAfOZrmckQq4-M_4_5alyPn95DK0o5lfA,16
3
3
  acp_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  acp_sdk/version.py,sha256=Niy83rgvigB4hL_rR-O4ySvI7dj6xnqkyOe_JTymi9s,73
5
5
  acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
6
- acp_sdk/client/client.py,sha256=WJNKGCml3A3zrOdSOIC8cS5s-ySfWLDWSuvAr9y25x8,8504
6
+ acp_sdk/client/client.py,sha256=GnkbhCoEghI9A0_Otmzb18V4ot1uNz0hEXNjIAPNHOE,8599
7
7
  acp_sdk/client/types.py,sha256=_H6zYt-2OHOOYRtssRnbDIiwmgsl2-KIXc9lb-mJLFA,133
8
8
  acp_sdk/client/utils.py,sha256=2jhJyrPJmVFRoDJh0q_JMqOMlC3IxCh-6HXed-PIZS8,924
9
9
  acp_sdk/models/__init__.py,sha256=numSDBDT1QHx7n_Y3Deb5VOvKWcUBxbOEaMwQBSRHxc,151
10
10
  acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
11
- acp_sdk/models/models.py,sha256=aA_ylU-Z0ZAlqPB8j-JfqJ2q516pgvTIyZ0wbfY8-UU,6654
12
- acp_sdk/models/schemas.py,sha256=OQ2bEfwFFZ1D9mhpgzfPBCcnPNvbl4qRfCWChH2wCro,644
11
+ acp_sdk/models/models.py,sha256=6OEkTMdMsl3_IbiFmFG2ChEqY_KYTf3Bi_MW5p168GQ,7183
12
+ acp_sdk/models/schemas.py,sha256=_ah7_zHsQJGxDXvnzsBvASdRsQHVphFQ7Sum6A04iRw,759
13
13
  acp_sdk/server/__init__.py,sha256=mxBBBFaZuMEUENRMLwp1XZkuLeT9QghcFmNvjnqvAAU,377
14
14
  acp_sdk/server/agent.py,sha256=wvwpi83osmW7zQWxVnzyVMXIvzOswAfhKWHscVIldhA,6245
15
- acp_sdk/server/app.py,sha256=tMWbtJgEEqIYe6gtJiVRCzt0OTTATsLLZt51SfBIqLc,6806
16
- acp_sdk/server/bundle.py,sha256=P3DfmzQOewsK8jr8yG8_RsGSrXQI4PsZCQbspVxKlus,6552
15
+ acp_sdk/server/app.py,sha256=1S1mxECioL5NeGjGo5C8u94x7Ybvj79L_Aauu2352vA,7117
16
+ acp_sdk/server/bundle.py,sha256=5Rq6E4WgmuwPQ4u-sViaHeu5loCLEUgojWav9LAghX4,7036
17
17
  acp_sdk/server/context.py,sha256=MgnLV6qcDIhc_0BjW7r4Jj1tHts4ZuwpdTGIBnz2Mgo,1036
18
18
  acp_sdk/server/errors.py,sha256=GSO8yYIqEeX8Y4Lz86ks35dMTHiQiXuOrLYYx0eXsbI,2110
19
19
  acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
20
20
  acp_sdk/server/server.py,sha256=3b_nhbDR_VUz9lneJow4Jpw-6cF2xf2k94grYmfbm1E,8144
21
- acp_sdk/server/session.py,sha256=fCRPHc2sQrz4G0n25I1S4LB7mI1wo0yYQJ1V9WRWH3g,627
21
+ acp_sdk/server/session.py,sha256=ekz1o6Sy1tQZlpaoS_VgbvFuUQh2qpiHG71mvBdvhgc,662
22
22
  acp_sdk/server/telemetry.py,sha256=1BUxNg-xL_Vqgs27PDWNc3HikrQW2lidAtT_FKlp_Qk,1833
23
23
  acp_sdk/server/types.py,sha256=teBNRWSks8XP1SCQKGEtbNWQahVD3RAOPnysTxcQPxI,292
24
24
  acp_sdk/server/utils.py,sha256=y-DDWv_QI25OJJYP5cni2FzfolKXtH3S2SYOm6OL_gc,1835
25
- acp_sdk-0.7.2.dist-info/METADATA,sha256=-g7nf-ZOtPPNVh46fhjUjYljp6Lodnu4qSwD_vSJqbI,1651
26
- acp_sdk-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- acp_sdk-0.7.2.dist-info/RECORD,,
25
+ acp_sdk-0.8.0.dist-info/METADATA,sha256=Inaa0idYypuECgh9JWdUVxIwJDRhslH7-YwAW6lupMg,1651
26
+ acp_sdk-0.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ acp_sdk-0.8.0.dist-info/RECORD,,