acp-sdk 0.6.0__tar.gz → 0.7.1__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 (48) hide show
  1. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/PKG-INFO +4 -4
  2. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/README.md +3 -3
  3. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/docs/client.md +5 -5
  4. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/docs/server.md +4 -4
  5. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/pyproject.toml +1 -1
  6. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/client/client.py +5 -28
  7. acp_sdk-0.7.1/src/acp_sdk/client/types.py +3 -0
  8. acp_sdk-0.7.1/src/acp_sdk/client/utils.py +24 -0
  9. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/models/models.py +1 -1
  10. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/models/schemas.py +1 -1
  11. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/agent.py +14 -14
  12. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/app.py +1 -1
  13. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/bundle.py +10 -10
  14. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/session.py +2 -2
  15. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/fixtures/server.py +8 -8
  16. acp_sdk-0.7.1/tests/e2e/test_suites/test_discovery.py +18 -0
  17. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/test_suites/test_runs.py +35 -35
  18. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/unit/client/test_client.py +1 -1
  19. acp_sdk-0.7.1/tests/unit/client/test_utils.py +30 -0
  20. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/.gitignore +0 -0
  21. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/.python-version +0 -0
  22. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/docs/_sidebar.md +0 -0
  23. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/docs/index.html +0 -0
  24. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/docs/models.md +0 -0
  25. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/pytest.ini +0 -0
  26. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/__init__.py +0 -0
  27. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/client/__init__.py +0 -0
  28. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/instrumentation.py +0 -0
  29. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/models/__init__.py +0 -0
  30. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/models/errors.py +0 -0
  31. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/py.typed +0 -0
  32. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/__init__.py +0 -0
  33. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/context.py +0 -0
  34. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/errors.py +0 -0
  35. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/logging.py +0 -0
  36. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/server.py +0 -0
  37. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/telemetry.py +0 -0
  38. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/types.py +0 -0
  39. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/server/utils.py +0 -0
  40. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/src/acp_sdk/version.py +0 -0
  41. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/conftest.py +0 -0
  42. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/__init__.py +0 -0
  43. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/config.py +0 -0
  44. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/fixtures/__init__.py +0 -0
  45. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/fixtures/client.py +0 -0
  46. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/e2e/test_suites/__init__.py +0 -0
  47. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/unit/models/__init__.py +0 -0
  48. {acp_sdk-0.6.0 → acp_sdk-0.7.1}/tests/unit/models/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acp-sdk
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: Agent Communication Protocol SDK
5
5
  Author: IBM Corp.
6
6
  Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
@@ -44,9 +44,9 @@ Register an agent and run the server:
44
44
  server = Server()
45
45
 
46
46
  @server.agent()
47
- async def echo(inputs: list[Message]):
47
+ async def echo(input: list[Message]):
48
48
  """Echoes everything"""
49
- for message in inputs:
49
+ for message in input:
50
50
  yield message
51
51
 
52
52
  server.run(port=8000)
@@ -56,7 +56,7 @@ From another process, connect to the server and run the agent:
56
56
 
57
57
  ```py
58
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!")])])
59
+ run = await client.run_sync(agent="echo", input=[Message(parts=[MessagePart(content="Howdy!")])])
60
60
  print(run)
61
61
 
62
62
  ```
@@ -23,9 +23,9 @@ Register an agent and run the server:
23
23
  server = Server()
24
24
 
25
25
  @server.agent()
26
- async def echo(inputs: list[Message]):
26
+ async def echo(input: list[Message]):
27
27
  """Echoes everything"""
28
- for message in inputs:
28
+ for message in input:
29
29
  yield message
30
30
 
31
31
  server.run(port=8000)
@@ -35,7 +35,7 @@ From another process, connect to the server and run the agent:
35
35
 
36
36
  ```py
37
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!")])])
38
+ run = await client.run_sync(agent="echo", input=[Message(parts=[MessagePart(content="Howdy!")])])
39
39
  print(run)
40
40
 
41
41
  ```
@@ -71,15 +71,15 @@ async with Client(base_url="http://localhost:8000") as client:
71
71
  message = Message(parts=[MessagePart(content="Hello")])
72
72
 
73
73
  # Async
74
- run = await client.run_async(agent_name="agent", inputs=[message])
74
+ run = await client.run_async(agent_name="agent", input=[message])
75
75
  print(run.status)
76
76
 
77
77
  # Sync - waits for completion, failure, cancellation or await
78
- run = await client.run_sync(agent_name="agent", inputs=[message])
79
- print(run.outputs)
78
+ run = await client.run_sync(agent_name="agent", input=[message])
79
+ print(run.output)
80
80
 
81
81
  # Stream - as sync but also receives events
82
- async for event in client.run_stream(agent_name="agent", inputs=[message])
82
+ async for event in client.run_stream(agent_name="agent", input=[message])
83
83
  print(event)
84
84
  ```
85
85
 
@@ -95,5 +95,5 @@ async with Client(base_url="http://localhost:8000" as client:
95
95
 
96
96
  async with client.session() as session:
97
97
  for agent in agents:
98
- await session.run_sync(agent_name=agent.name, inputs=[Message(parts=[MessagePart(content="Hello!")])])
98
+ await session.run_sync(agent_name=agent.name, input=[Message(parts=[MessagePart(content="Hello!")])])
99
99
  ```
@@ -47,9 +47,9 @@ server = Server()
47
47
 
48
48
 
49
49
  @server.agent()
50
- async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
50
+ async def echo(input: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
51
51
  """Echoes everything"""
52
- for message in inputs:
52
+ for message in input:
53
53
  await asyncio.sleep(0.5)
54
54
  yield {"thought": "I should echo everything"}
55
55
  await asyncio.sleep(0.5)
@@ -81,9 +81,9 @@ from acp_sdk.server import RunYield, RunYieldResume, agent, create_app
81
81
 
82
82
 
83
83
  @agent()
84
- async def echo(inputs: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
84
+ async def echo(input: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
85
85
  """Echoes everything"""
86
- for message in inputs:
86
+ for message in input:
87
87
  yield message
88
88
 
89
89
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acp-sdk"
3
- version = "0.6.0"
3
+ version = "0.7.1"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -11,6 +11,8 @@ from httpx_sse import EventSource, aconnect_sse
11
11
  from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
12
12
  from pydantic import TypeAdapter
13
13
 
14
+ from acp_sdk.client.types import Input
15
+ from acp_sdk.client.utils import input_to_messages
14
16
  from acp_sdk.instrumentation import get_tracer
15
17
  from acp_sdk.models import (
16
18
  ACPError,
@@ -21,7 +23,6 @@ from acp_sdk.models import (
21
23
  AwaitResume,
22
24
  Error,
23
25
  Event,
24
- Message,
25
26
  Run,
26
27
  RunCancelResponse,
27
28
  RunCreatedEvent,
@@ -33,9 +34,6 @@ from acp_sdk.models import (
33
34
  RunResumeResponse,
34
35
  SessionId,
35
36
  )
36
- from acp_sdk.models.models import MessagePart
37
-
38
- Input = list[Message] | Message | list[MessagePart] | MessagePart | list[str] | str
39
37
 
40
38
 
41
39
  class Client:
@@ -127,7 +125,7 @@ class Client:
127
125
  "/runs",
128
126
  content=RunCreateRequest(
129
127
  agent_name=agent,
130
- inputs=self._unify_inputs(input),
128
+ input=input_to_messages(input),
131
129
  mode=RunMode.SYNC,
132
130
  session_id=self._session_id,
133
131
  ).model_dump_json(),
@@ -142,7 +140,7 @@ class Client:
142
140
  "/runs",
143
141
  content=RunCreateRequest(
144
142
  agent_name=agent,
145
- inputs=self._unify_inputs(input),
143
+ input=input_to_messages(input),
146
144
  mode=RunMode.ASYNC,
147
145
  session_id=self._session_id,
148
146
  ).model_dump_json(),
@@ -159,7 +157,7 @@ class Client:
159
157
  "/runs",
160
158
  content=RunCreateRequest(
161
159
  agent_name=agent,
162
- inputs=self._unify_inputs(input),
160
+ input=input_to_messages(input),
163
161
  mode=RunMode.STREAM,
164
162
  session_id=self._session_id,
165
163
  ).model_dump_json(),
@@ -227,24 +225,3 @@ class Client:
227
225
 
228
226
  def _set_session(self, run: Run) -> None:
229
227
  self._session_id = run.session_id
230
-
231
- def _unify_inputs(self, input: Input) -> list[Message]:
232
- if isinstance(input, list):
233
- if len(input) == 0:
234
- return []
235
- if all(isinstance(item, Message) for item in input):
236
- return input
237
- elif all(isinstance(item, MessagePart) for item in input):
238
- return [Message(parts=input)]
239
- elif all(isinstance(item, str) for item in input):
240
- return [Message(parts=[MessagePart(content=content) for content in input])]
241
- else:
242
- raise RuntimeError("List with mixed types is not supported")
243
- else:
244
- if isinstance(input, str):
245
- input = MessagePart(content=input)
246
- if isinstance(input, MessagePart):
247
- input = Message(parts=[input])
248
- if isinstance(input, Message):
249
- input = [input]
250
- return input
@@ -0,0 +1,3 @@
1
+ from acp_sdk.models import Message, MessagePart
2
+
3
+ Input = list[Message] | Message | list[MessagePart] | MessagePart | list[str] | str
@@ -0,0 +1,24 @@
1
+ from acp_sdk.client.types import Input
2
+ from acp_sdk.models.models import Message, MessagePart
3
+
4
+
5
+ def input_to_messages(input: Input) -> list[Message]:
6
+ if isinstance(input, list):
7
+ if len(input) == 0:
8
+ return []
9
+ if all(isinstance(item, Message) for item in input):
10
+ return input
11
+ elif all(isinstance(item, MessagePart) for item in input):
12
+ return [Message(parts=input)]
13
+ elif all(isinstance(item, str) for item in input):
14
+ return [Message(parts=[MessagePart(content=content) for content in input])]
15
+ else:
16
+ raise TypeError("List with mixed types is not supported")
17
+ else:
18
+ if isinstance(input, str):
19
+ input = MessagePart(content=input)
20
+ if isinstance(input, MessagePart):
21
+ input = Message(parts=[input])
22
+ if isinstance(input, Message):
23
+ input = [input]
24
+ return input
@@ -177,7 +177,7 @@ class Run(BaseModel):
177
177
  session_id: SessionId | None = None
178
178
  status: RunStatus = RunStatus.CREATED
179
179
  await_request: AwaitRequest | None = None
180
- outputs: list[Message] = []
180
+ output: list[Message] = []
181
181
  error: Error | None = None
182
182
 
183
183
 
@@ -14,7 +14,7 @@ class AgentReadResponse(Agent):
14
14
  class RunCreateRequest(BaseModel):
15
15
  agent_name: AgentName
16
16
  session_id: SessionId | None = None
17
- inputs: list[Message]
17
+ input: list[Message]
18
18
  mode: RunMode = RunMode.SYNC
19
19
 
20
20
 
@@ -32,14 +32,14 @@ class Agent(abc.ABC):
32
32
 
33
33
  @abc.abstractmethod
34
34
  def run(
35
- self, inputs: list[Message], context: Context
35
+ self, input: list[Message], context: Context
36
36
  ) -> (
37
37
  AsyncGenerator[RunYield, RunYieldResume] | Generator[RunYield, RunYieldResume] | Coroutine[RunYield] | RunYield
38
38
  ):
39
39
  pass
40
40
 
41
41
  async def execute(
42
- self, inputs: list[Message], session_id: SessionId | None, executor: ThreadPoolExecutor
42
+ self, input: list[Message], session_id: SessionId | None, executor: ThreadPoolExecutor
43
43
  ) -> AsyncGenerator[RunYield, RunYieldResume]:
44
44
  yield_queue: janus.Queue[RunYield] = janus.Queue()
45
45
  yield_resume_queue: janus.Queue[RunYieldResume] = janus.Queue()
@@ -49,13 +49,13 @@ class Agent(abc.ABC):
49
49
  )
50
50
 
51
51
  if inspect.isasyncgenfunction(self.run):
52
- run = asyncio.create_task(self._run_async_gen(inputs, context))
52
+ run = asyncio.create_task(self._run_async_gen(input, context))
53
53
  elif inspect.iscoroutinefunction(self.run):
54
- run = asyncio.create_task(self._run_coro(inputs, context))
54
+ run = asyncio.create_task(self._run_coro(input, context))
55
55
  elif inspect.isgeneratorfunction(self.run):
56
- run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen, inputs, context)
56
+ run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen, input, context)
57
57
  else:
58
- run = asyncio.get_running_loop().run_in_executor(executor, self._run_func, inputs, context)
58
+ run = asyncio.get_running_loop().run_in_executor(executor, self._run_func, input, context)
59
59
 
60
60
  try:
61
61
  while True:
@@ -66,7 +66,7 @@ class Agent(abc.ABC):
66
66
  finally:
67
67
  await run # Raise exceptions
68
68
 
69
- async def _run_async_gen(self, input: Message, context: Context) -> None:
69
+ async def _run_async_gen(self, input: list[Message], context: Context) -> None:
70
70
  try:
71
71
  gen: AsyncGenerator[RunYield, RunYieldResume] = self.run(input, context)
72
72
  value = None
@@ -77,13 +77,13 @@ class Agent(abc.ABC):
77
77
  finally:
78
78
  context.shutdown()
79
79
 
80
- async def _run_coro(self, input: Message, context: Context) -> None:
80
+ async def _run_coro(self, input: list[Message], context: Context) -> None:
81
81
  try:
82
82
  await context.yield_async(await self.run(input, context))
83
83
  finally:
84
84
  context.shutdown()
85
85
 
86
- def _run_gen(self, input: Message, context: Context) -> None:
86
+ def _run_gen(self, input: list[Message], context: Context) -> None:
87
87
  try:
88
88
  gen: Generator[RunYield, RunYieldResume] = self.run(input, context)
89
89
  value = None
@@ -94,7 +94,7 @@ class Agent(abc.ABC):
94
94
  finally:
95
95
  context.shutdown()
96
96
 
97
- def _run_func(self, input: Message, context: Context) -> None:
97
+ def _run_func(self, input: list[Message], context: Context) -> None:
98
98
  try:
99
99
  context.yield_sync(self.run(input, context))
100
100
  finally:
@@ -139,7 +139,7 @@ def agent(
139
139
  if inspect.isasyncgenfunction(fn):
140
140
 
141
141
  class AsyncGenDecoratorAgent(DecoratorAgentBase):
142
- async def run(self, input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
142
+ async def run(self, input: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
143
143
  try:
144
144
  gen: AsyncGenerator[RunYield, RunYieldResume] = (
145
145
  fn(input, context) if has_context_param else fn(input)
@@ -154,21 +154,21 @@ def agent(
154
154
  elif inspect.iscoroutinefunction(fn):
155
155
 
156
156
  class CoroDecoratorAgent(DecoratorAgentBase):
157
- async def run(self, input: Message, context: Context) -> Coroutine[RunYield]:
157
+ async def run(self, input: list[Message], context: Context) -> Coroutine[RunYield]:
158
158
  return await (fn(input, context) if has_context_param else fn(input))
159
159
 
160
160
  agent = CoroDecoratorAgent()
161
161
  elif inspect.isgeneratorfunction(fn):
162
162
 
163
163
  class GenDecoratorAgent(DecoratorAgentBase):
164
- def run(self, input: Message, context: Context) -> Generator[RunYield, RunYieldResume]:
164
+ def run(self, input: list[Message], context: Context) -> Generator[RunYield, RunYieldResume]:
165
165
  yield from (fn(input, context) if has_context_param else fn(input))
166
166
 
167
167
  agent = GenDecoratorAgent()
168
168
  else:
169
169
 
170
170
  class FuncDecoratorAgent(DecoratorAgentBase):
171
- def run(self, input: Message, context: Context) -> RunYield:
171
+ def run(self, input: list[Message], context: Context) -> RunYield:
172
172
  return fn(input, context) if has_context_param else fn(input)
173
173
 
174
174
  agent = FuncDecoratorAgent()
@@ -105,7 +105,7 @@ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timed
105
105
  bundle = RunBundle(
106
106
  agent=agent,
107
107
  run=Run(agent_name=agent.name, session_id=session.id),
108
- inputs=request.inputs,
108
+ input=request.input,
109
109
  history=list(session.history()),
110
110
  executor=executor,
111
111
  )
@@ -35,11 +35,11 @@ from acp_sdk.server.logging import logger
35
35
 
36
36
  class RunBundle:
37
37
  def __init__(
38
- self, *, agent: Agent, run: Run, inputs: list[Message], history: list[Message], executor: ThreadPoolExecutor
38
+ self, *, agent: Agent, run: Run, input: list[Message], history: list[Message], executor: ThreadPoolExecutor
39
39
  ) -> None:
40
40
  self.agent = agent
41
41
  self.run = run
42
- self.inputs = inputs
42
+ self.input = input
43
43
  self.history = history
44
44
 
45
45
  self.stream_queue: asyncio.Queue[Event] = asyncio.Queue()
@@ -47,7 +47,7 @@ class RunBundle:
47
47
  self.await_queue: asyncio.Queue[AwaitResume] = asyncio.Queue(maxsize=1)
48
48
  self.await_or_terminate_event = asyncio.Event()
49
49
 
50
- self.task = asyncio.create_task(self._execute(inputs, executor=executor))
50
+ self.task = asyncio.create_task(self._execute(input, executor=executor))
51
51
 
52
52
  async def stream(self) -> AsyncGenerator[Event]:
53
53
  while True:
@@ -83,7 +83,7 @@ class RunBundle:
83
83
  async def join(self) -> None:
84
84
  await self.await_or_terminate_event.wait()
85
85
 
86
- async def _execute(self, inputs: list[Message], *, executor: ThreadPoolExecutor) -> None:
86
+ async def _execute(self, input: list[Message], *, executor: ThreadPoolExecutor) -> None:
87
87
  with get_tracer().start_as_current_span("run"):
88
88
  run_logger = logging.LoggerAdapter(logger, {"run_id": str(self.run.run_id)})
89
89
 
@@ -92,14 +92,14 @@ class RunBundle:
92
92
  async def flush_message() -> None:
93
93
  nonlocal in_message
94
94
  if in_message:
95
- await self.emit(MessageCompletedEvent(message=self.run.outputs[-1]))
95
+ await self.emit(MessageCompletedEvent(message=self.run.output[-1]))
96
96
  in_message = False
97
97
 
98
98
  try:
99
99
  await self.emit(RunCreatedEvent(run=self.run))
100
100
 
101
101
  generator = self.agent.execute(
102
- inputs=self.history + inputs, session_id=self.run.session_id, executor=executor
102
+ input=self.history + input, session_id=self.run.session_id, executor=executor
103
103
  )
104
104
  run_logger.info("Run started")
105
105
 
@@ -114,14 +114,14 @@ class RunBundle:
114
114
  if isinstance(next, str):
115
115
  next = MessagePart(content=next)
116
116
  if not in_message:
117
- self.run.outputs.append(Message(parts=[]))
117
+ self.run.output.append(Message(parts=[]))
118
118
  in_message = True
119
- await self.emit(MessageCreatedEvent(message=self.run.outputs[-1]))
120
- self.run.outputs[-1].parts.append(next)
119
+ await self.emit(MessageCreatedEvent(message=self.run.output[-1]))
120
+ self.run.output[-1].parts.append(next)
121
121
  await self.emit(MessagePartEvent(part=next))
122
122
  elif isinstance(next, Message):
123
123
  await flush_message()
124
- self.run.outputs.append(next)
124
+ self.run.output.append(next)
125
125
  await self.emit(MessageCreatedEvent(message=next))
126
126
  for part in next.parts:
127
127
  await self.emit(MessagePartEvent(part=part))
@@ -17,5 +17,5 @@ class Session:
17
17
  def history(self) -> Iterator[Message]:
18
18
  for bundle in self.bundles:
19
19
  if bundle.run.status == RunStatus.COMPLETED:
20
- yield from bundle.inputs
21
- yield from bundle.run.outputs
20
+ yield from bundle.input
21
+ yield from bundle.run.output
@@ -17,36 +17,36 @@ def server(request: pytest.FixtureRequest) -> Generator[None]:
17
17
  server = Server()
18
18
 
19
19
  @server.agent()
20
- async def echo(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
21
- for message in inputs:
20
+ async def echo(input: list[Message], context: Context) -> AsyncIterator[Message]:
21
+ for message in input:
22
22
  yield message
23
23
 
24
24
  @server.agent()
25
25
  async def awaiter(
26
- inputs: list[Message], context: Context
26
+ input: list[Message], context: Context
27
27
  ) -> AsyncGenerator[Message | MessageAwaitRequest, AwaitResume]:
28
28
  yield MessageAwaitRequest(message=Message(parts=[]))
29
29
  yield MessagePart(content="empty", content_type="text/plain")
30
30
 
31
31
  @server.agent()
32
- async def failer(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
32
+ async def failer(input: list[Message], context: Context) -> AsyncIterator[Message]:
33
33
  yield Error(code=ErrorCode.INVALID_INPUT, message="Wrong question buddy!")
34
34
 
35
35
  @server.agent()
36
- async def sessioner(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
36
+ async def sessioner(input: list[Message], context: Context) -> AsyncIterator[Message]:
37
37
  assert context.session_id is not None
38
38
 
39
39
  yield MessagePart(content=str(context.session_id), content_type="text/plain")
40
40
 
41
41
  @server.agent()
42
- async def mime_types(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
42
+ async def mime_types(input: list[Message], context: Context) -> AsyncIterator[Message]:
43
43
  yield MessagePart(content="<h1>HTML Content</h1>", content_type="text/html")
44
44
  yield MessagePart(content='{"key": "value"}', content_type="application/json")
45
45
  yield MessagePart(content="console.log('Hello');", content_type="application/javascript")
46
46
  yield MessagePart(content="body { color: red; }", content_type="text/css")
47
47
 
48
48
  @server.agent()
49
- async def base64_encoding(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
49
+ async def base64_encoding(input: list[Message], context: Context) -> AsyncIterator[Message]:
50
50
  yield Message(
51
51
  parts=[
52
52
  MessagePart(
@@ -61,7 +61,7 @@ def server(request: pytest.FixtureRequest) -> Generator[None]:
61
61
  )
62
62
 
63
63
  @server.agent()
64
- async def artifact_producer(inputs: list[Message], context: Context) -> AsyncGenerator[Message | Artifact, None]:
64
+ async def artifact_producer(input: list[Message], context: Context) -> AsyncGenerator[Message | Artifact, None]:
65
65
  yield MessagePart(content="Processing with artifacts", content_type="text/plain")
66
66
  yield Artifact(name="text-result.txt", content_type="text/plain", content="This is a text artifact result")
67
67
  yield Artifact(
@@ -0,0 +1,18 @@
1
+ import pytest
2
+ from acp_sdk.client import Client
3
+ from acp_sdk.models import Agent
4
+ from acp_sdk.server import Server
5
+
6
+
7
+ @pytest.mark.asyncio
8
+ async def test_agents_list(server: Server, client: Client) -> None:
9
+ async for agent in client.agents():
10
+ assert isinstance(agent, Agent)
11
+
12
+
13
+ @pytest.mark.asyncio
14
+ async def test_agents_details(server: Server, client: Client) -> None:
15
+ agent_name = "echo"
16
+ agent = await client.agent(name=agent_name)
17
+ assert isinstance(agent, Agent)
18
+ assert agent.name == agent_name
@@ -19,33 +19,33 @@ from acp_sdk.models import (
19
19
  from acp_sdk.models.errors import ACPError
20
20
  from acp_sdk.server import Server
21
21
 
22
- inputs = [Message(parts=[MessagePart(content="Hello!")])]
22
+ input = [Message(parts=[MessagePart(content="Hello!")])]
23
23
  await_resume = MessageAwaitResume(message=Message(parts=[]))
24
24
 
25
25
 
26
26
  @pytest.mark.asyncio
27
27
  async def test_run_sync(server: Server, client: Client) -> None:
28
- run = await client.run_sync(agent="echo", input=inputs)
28
+ run = await client.run_sync(agent="echo", input=input)
29
29
  assert run.status == RunStatus.COMPLETED
30
- assert run.outputs == inputs
30
+ assert run.output == input
31
31
 
32
32
 
33
33
  @pytest.mark.asyncio
34
34
  async def test_run_async(server: Server, client: Client) -> None:
35
- run = await client.run_async(agent="echo", input=inputs)
35
+ run = await client.run_async(agent="echo", input=input)
36
36
  assert run.status == RunStatus.CREATED
37
37
 
38
38
 
39
39
  @pytest.mark.asyncio
40
40
  async def test_run_stream(server: Server, client: Client) -> None:
41
- event_stream = [event async for event in client.run_stream(agent="echo", input=inputs)]
41
+ event_stream = [event async for event in client.run_stream(agent="echo", input=input)]
42
42
  assert isinstance(event_stream[0], RunCreatedEvent)
43
43
  assert isinstance(event_stream[-1], RunCompletedEvent)
44
44
 
45
45
 
46
46
  @pytest.mark.asyncio
47
47
  async def test_run_status(server: Server, client: Client) -> None:
48
- run = await client.run_async(agent="echo", input=inputs)
48
+ run = await client.run_async(agent="echo", input=input)
49
49
  while run.status in (RunStatus.CREATED, RunStatus.IN_PROGRESS):
50
50
  run = await client.run_status(run_id=run.run_id)
51
51
  assert run.status == RunStatus.COMPLETED
@@ -53,7 +53,7 @@ async def test_run_status(server: Server, client: Client) -> None:
53
53
 
54
54
  @pytest.mark.asyncio
55
55
  async def test_failure(server: Server, client: Client) -> None:
56
- run = await client.run_sync(agent="failer", input=inputs)
56
+ run = await client.run_sync(agent="failer", input=input)
57
57
  assert run.status == RunStatus.FAILED
58
58
  assert run.error is not None
59
59
  assert run.error.code == ErrorCode.INVALID_INPUT
@@ -61,7 +61,7 @@ async def test_failure(server: Server, client: Client) -> None:
61
61
 
62
62
  @pytest.mark.asyncio
63
63
  async def test_run_cancel(server: Server, client: Client) -> None:
64
- run = await client.run_sync(agent="awaiter", input=inputs)
64
+ run = await client.run_sync(agent="awaiter", input=input)
65
65
  assert run.status == RunStatus.AWAITING
66
66
  run = await client.run_cancel(run_id=run.run_id)
67
67
  assert run.status == RunStatus.CANCELLING
@@ -69,7 +69,7 @@ async def test_run_cancel(server: Server, client: Client) -> None:
69
69
 
70
70
  @pytest.mark.asyncio
71
71
  async def test_run_resume_sync(server: Server, client: Client) -> None:
72
- run = await client.run_sync(agent="awaiter", input=inputs)
72
+ run = await client.run_sync(agent="awaiter", input=input)
73
73
  assert run.status == RunStatus.AWAITING
74
74
  assert run.await_request is not None
75
75
 
@@ -79,7 +79,7 @@ async def test_run_resume_sync(server: Server, client: Client) -> None:
79
79
 
80
80
  @pytest.mark.asyncio
81
81
  async def test_run_resume_async(server: Server, client: Client) -> None:
82
- run = await client.run_sync(agent="awaiter", input=inputs)
82
+ run = await client.run_sync(agent="awaiter", input=input)
83
83
  assert run.status == RunStatus.AWAITING
84
84
  assert run.await_request is not None
85
85
 
@@ -89,7 +89,7 @@ async def test_run_resume_async(server: Server, client: Client) -> None:
89
89
 
90
90
  @pytest.mark.asyncio
91
91
  async def test_run_resume_stream(server: Server, client: Client) -> None:
92
- run = await client.run_sync(agent="awaiter", input=inputs)
92
+ run = await client.run_sync(agent="awaiter", input=input)
93
93
  assert run.status == RunStatus.AWAITING
94
94
  assert run.await_request is not None
95
95
 
@@ -101,19 +101,19 @@ async def test_run_resume_stream(server: Server, client: Client) -> None:
101
101
  @pytest.mark.asyncio
102
102
  async def test_run_session(server: Server, client: Client) -> None:
103
103
  async with client.session() as session:
104
- run = await session.run_sync(agent="echo", input=inputs)
105
- assert run.outputs == inputs
106
- run = await session.run_sync(agent="echo", input=inputs)
107
- assert run.outputs == inputs + inputs + inputs
104
+ run = await session.run_sync(agent="echo", input=input)
105
+ assert run.output == input
106
+ run = await session.run_sync(agent="echo", input=input)
107
+ assert run.output == input + input + input
108
108
 
109
109
 
110
110
  @pytest.mark.asyncio
111
111
  async def test_mime_types(server: Server, client: Client) -> None:
112
- run = await client.run_sync(agent="mime_types", input=inputs)
112
+ run = await client.run_sync(agent="mime_types", input=input)
113
113
  assert run.status == RunStatus.COMPLETED
114
- assert len(run.outputs) == 1
114
+ assert len(run.output) == 1
115
115
 
116
- message_parts = run.outputs[0].parts
116
+ message_parts = run.output[0].parts
117
117
  content_types = [part.content_type for part in message_parts]
118
118
 
119
119
  assert "text/html" in content_types
@@ -130,11 +130,11 @@ async def test_mime_types(server: Server, client: Client) -> None:
130
130
 
131
131
  @pytest.mark.asyncio
132
132
  async def test_base64_encoding(server: Server, client: Client) -> None:
133
- run = await client.run_sync(agent="base64_encoding", input=inputs)
133
+ run = await client.run_sync(agent="base64_encoding", input=input)
134
134
  assert run.status == RunStatus.COMPLETED
135
- assert len(run.outputs) == 1
135
+ assert len(run.output) == 1
136
136
 
137
- message_parts = run.outputs[0].parts
137
+ message_parts = run.output[0].parts
138
138
  assert len(message_parts) == 2
139
139
 
140
140
  base64_part = next((part for part in message_parts if part.content_encoding == "base64"), None)
@@ -150,17 +150,17 @@ async def test_base64_encoding(server: Server, client: Client) -> None:
150
150
 
151
151
  @pytest.mark.asyncio
152
152
  async def test_artifacts(server: Server, client: Client) -> None:
153
- run = await client.run_sync(agent="artifact_producer", input=inputs)
153
+ run = await client.run_sync(agent="artifact_producer", input=input)
154
154
  assert run.status == RunStatus.COMPLETED
155
155
 
156
- assert len(run.outputs) == 1
157
- assert run.outputs[0].parts[0].content == "Processing with artifacts"
156
+ assert len(run.output) == 1
157
+ assert run.output[0].parts[0].content == "Processing with artifacts"
158
158
 
159
- assert len(run.outputs[0].parts) == 4
159
+ assert len(run.output[0].parts) == 4
160
160
 
161
- text_artifact = next((a for a in run.outputs[0].parts if a.name == "text-result.txt"), None)
162
- json_artifact = next((a for a in run.outputs[0].parts if a.name == "data.json"), None)
163
- image_artifact = next((a for a in run.outputs[0].parts if a.name == "image.png"), None)
161
+ text_artifact = next((a for a in run.output[0].parts if a.name == "text-result.txt"), None)
162
+ json_artifact = next((a for a in run.output[0].parts if a.name == "data.json"), None)
163
+ image_artifact = next((a for a in run.output[0].parts if a.name == "image.png"), None)
164
164
 
165
165
  assert text_artifact is not None
166
166
  assert text_artifact.content_type == "text/plain"
@@ -179,7 +179,7 @@ async def test_artifacts(server: Server, client: Client) -> None:
179
179
 
180
180
  @pytest.mark.asyncio
181
181
  async def test_artifact_streaming(server: Server, client: Client) -> None:
182
- events = [event async for event in client.run_stream(agent="artifact_producer", input=inputs)]
182
+ events = [event async for event in client.run_stream(agent="artifact_producer", input=input)]
183
183
 
184
184
  assert isinstance(events[0], RunCreatedEvent)
185
185
  assert isinstance(events[-1], RunCompletedEvent)
@@ -201,7 +201,7 @@ async def test_artifact_streaming(server: Server, client: Client) -> None:
201
201
  @pytest.mark.asyncio
202
202
  @pytest.mark.parametrize("server", [timedelta(seconds=5)], indirect=True)
203
203
  async def test_run_ttl(server: Server, client: Client) -> None:
204
- run = await client.run_async(agent="echo", input=inputs)
204
+ run = await client.run_async(agent="echo", input=input)
205
205
  run = await client.run_status(run_id=run.run_id)
206
206
  await asyncio.sleep(6)
207
207
  try:
@@ -218,10 +218,10 @@ async def test_run_ttl(server: Server, client: Client) -> None:
218
218
  @pytest.mark.parametrize("server", [timedelta(seconds=5)], indirect=True)
219
219
  async def test_session_ttl(server: Server, client: Client) -> None:
220
220
  async with client.session() as session:
221
- run = await session.run_sync(agent="echo", input=inputs)
221
+ run = await session.run_sync(agent="echo", input=input)
222
222
  await asyncio.sleep(3)
223
- run = await session.run_sync(agent="echo", input=inputs)
224
- assert len(run.outputs) == 3
223
+ run = await session.run_sync(agent="echo", input=input)
224
+ assert len(run.output) == 3
225
225
  await asyncio.sleep(3)
226
- run = await session.run_sync(agent="echo", input=inputs)
227
- assert len(run.outputs) == 7 # First run shall be forgotten
226
+ run = await session.run_sync(agent="echo", input=input)
227
+ assert len(run.output) == 7 # First run shall be forgotten
@@ -3,7 +3,7 @@ from acp_sdk.client import Client
3
3
  from acp_sdk.models import Message, MessagePart, Run, RunCompletedEvent
4
4
  from pytest_httpx import HTTPXMock
5
5
 
6
- mock_run = Run(agent_name="mock", outputs=[Message(parts=[MessagePart(content="Hello!")])])
6
+ mock_run = Run(agent_name="mock", output=[Message(parts=[MessagePart(content="Hello!")])])
7
7
 
8
8
 
9
9
  @pytest.mark.asyncio
@@ -0,0 +1,30 @@
1
+ import pytest
2
+ from acp_sdk.client.types import Input
3
+ from acp_sdk.client.utils import input_to_messages
4
+ from acp_sdk.models import Message, MessagePart
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "input,messages",
9
+ [
10
+ ([], []),
11
+ ("Hello", [Message(parts=[MessagePart(content="Hello")])]),
12
+ (["Hello"], [Message(parts=[MessagePart(content="Hello")])]),
13
+ (MessagePart(content="Hello"), [Message(parts=[MessagePart(content="Hello")])]),
14
+ ([MessagePart(content="Hello")], [Message(parts=[MessagePart(content="Hello")])]),
15
+ (Message(parts=[MessagePart(content="Hello")]), [Message(parts=[MessagePart(content="Hello")])]),
16
+ ([Message(parts=[MessagePart(content="Hello")])], [Message(parts=[MessagePart(content="Hello")])]),
17
+ ],
18
+ )
19
+ def test_input_to_messages(input: Input, messages: list[Message]) -> None:
20
+ result = input_to_messages(input)
21
+ assert result == messages
22
+
23
+
24
+ @pytest.mark.parametrize(
25
+ "input",
26
+ [["foo", Message(parts=[])], ["foo", MessagePart(content="foo")], [Message(parts=[]), MessagePart(content="foo")]],
27
+ )
28
+ def test_input_to_messages_mixed_input(input: Input) -> None:
29
+ with pytest.raises(TypeError):
30
+ input_to_messages(["foo", Message(parts=[])])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes