agentapprovalprotocol 0.3.0__tar.gz → 0.3.2__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 (26) hide show
  1. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/PKG-INFO +1 -1
  2. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/pyproject.toml +1 -1
  3. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/_gate.py +1 -1
  4. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/langgraph.py +33 -1
  5. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_adk.py +3 -3
  6. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_claude_agent_sdk.py +1 -1
  7. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_crewai.py +5 -5
  8. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_gate.py +1 -1
  9. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_langgraph.py +70 -2
  10. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_openai_agents.py +4 -4
  11. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_pydantic_ai.py +1 -1
  12. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/uv.lock +1 -1
  13. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/.gitignore +0 -0
  14. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/README.md +0 -0
  15. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/__init__.py +0 -0
  16. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/_client.py +0 -0
  17. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/_wire.py +0 -0
  18. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/adk.py +0 -0
  19. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/claude_agent_sdk.py +0 -0
  20. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/crewai.py +0 -0
  21. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/openai_agents.py +0 -0
  22. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/py.typed +0 -0
  23. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/pydantic_ai.py +0 -0
  24. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/receiver.py +0 -0
  25. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/src/agentapprovalprotocol/testing.py +0 -0
  26. {agentapprovalprotocol-0.3.0 → agentapprovalprotocol-0.3.2}/tests/test_wire_and_receiver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentapprovalprotocol
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Put an AI agent's tool calls behind an Agent Approval Protocol (AAP) approval provider.
5
5
  Project-URL: Homepage, https://agentapprovalprotocol.io
6
6
  Project-URL: Source, https://github.com/agentapprovalprotocol/agentapprovalprotocol
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentapprovalprotocol"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Put an AI agent's tool calls behind an Agent Approval Protocol (AAP) approval provider."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -117,7 +117,7 @@ def _settled(request: Request) -> Deny | None:
117
117
  """The deny for a terminal request, or None when it may be consumed."""
118
118
  if request.status == DENIED:
119
119
  note = request.decision.note if request.decision else None
120
- return Deny(f"The provider denied this call: {note}" if note else "The provider denied this call.", DENIED, request.id)
120
+ return Deny(f"The AAP provider denied this call: {note}" if note else "The AAP provider denied this call.", DENIED, request.id)
121
121
  if request.status == CANCELLED:
122
122
  return Deny(CANCELLED_TEXT, CANCELLED, request.id)
123
123
  if request.status == EXPIRED:
@@ -13,11 +13,13 @@ Install with ``pip install "agentapprovalprotocol[langgraph]"``.
13
13
  from __future__ import annotations
14
14
 
15
15
  import asyncio
16
+ import contextvars
16
17
  from typing import Any, Awaitable, Callable, Iterable, Literal, Mapping, Sequence
17
18
 
18
19
  from langchain.agents.middleware import AgentMiddleware
19
20
  from langchain_core.messages import AIMessage, ToolMessage
20
21
  from langchain_core.runnables.config import var_child_runnable_config
22
+ from langgraph.errors import GraphBubbleUp
21
23
  from langgraph.prebuilt.tool_node import ToolCallRequest
22
24
  from langgraph.types import Command, interrupt
23
25
 
@@ -27,6 +29,15 @@ from ._wire import Request
27
29
 
28
30
  INTERRUPT_KEY = "aap_request_id"
29
31
 
32
+ # Set while an approved tool runs. A call made inside it, such as one by an
33
+ # agent the tool invokes, waits instead of pausing: on resume LangGraph would
34
+ # run the outer tool again, and its approval is already consumed.
35
+ _running = contextvars.ContextVar("aap_running_tool", default=False)
36
+
37
+ # Deep Agents' delegation tool. It changes nothing itself, so once every
38
+ # subagent is gated it runs without approval, and its subagents can pause.
39
+ DEEP_AGENTS_TASK_TOOL = "task"
40
+
30
41
  Handler = Callable[[ToolCallRequest], "ToolMessage | Command"]
31
42
  AsyncHandler = Callable[[ToolCallRequest], Awaitable["ToolMessage | Command"]]
32
43
 
@@ -103,6 +114,7 @@ class ApprovalMiddleware(AgentMiddleware):
103
114
  self._async_gate = async_gate
104
115
  self._make = lambda client_cls, gate_cls: gate_cls(client_cls(base_url, token), mode=mode, **gate_options)
105
116
  self.mode = mode
117
+ self._ungated: set[str] = set()
106
118
 
107
119
  @property
108
120
  def gate(self) -> Gate:
@@ -132,9 +144,11 @@ class ApprovalMiddleware(AgentMiddleware):
132
144
  context=self.context,
133
145
  resume=resume,
134
146
  )
135
- return call, thread is not None
147
+ return call, thread is not None and not _running.get()
136
148
 
137
149
  def wrap_tool_call(self, request: ToolCallRequest, handler: Handler) -> ToolMessage | Command:
150
+ if request.tool_call["name"] in self._ungated:
151
+ return handler(request)
138
152
  call, can_pause = self._call(request)
139
153
  verdict: Verdict = self.gate.check(call, can_pause=can_pause)
140
154
  while isinstance(verdict, Pending):
@@ -142,17 +156,24 @@ class ApprovalMiddleware(AgentMiddleware):
142
156
  verdict = self.gate.check(call, can_pause=can_pause)
143
157
  if isinstance(verdict, Deny):
144
158
  return _denied(request, verdict)
159
+ running = _running.set(True)
145
160
  try:
146
161
  result = handler(request)
162
+ except GraphBubbleUp:
163
+ raise # an interrupt or a command for the parent graph, not a failure
147
164
  except BaseException:
148
165
  if self.gate.report_outcomes:
149
166
  verdict.report("failed")
150
167
  raise
168
+ finally:
169
+ _running.reset(running)
151
170
  if self.gate.report_outcomes:
152
171
  verdict.report("failed" if _failed(result) else "succeeded")
153
172
  return result
154
173
 
155
174
  async def awrap_tool_call(self, request: ToolCallRequest, handler: AsyncHandler) -> ToolMessage | Command:
175
+ if request.tool_call["name"] in self._ungated:
176
+ return await handler(request)
156
177
  call, can_pause = self._call(request)
157
178
  verdict: Verdict = await self.async_gate.check(call, can_pause=can_pause)
158
179
  while isinstance(verdict, Pending):
@@ -161,12 +182,17 @@ class ApprovalMiddleware(AgentMiddleware):
161
182
  if isinstance(verdict, Deny):
162
183
  return _denied(request, verdict)
163
184
  assert isinstance(verdict, Allow)
185
+ running = _running.set(True)
164
186
  try:
165
187
  result = await handler(request)
188
+ except GraphBubbleUp:
189
+ raise # an interrupt or a command for the parent graph, not a failure
166
190
  except BaseException:
167
191
  if self.async_gate.report_outcomes:
168
192
  await verdict.report("failed")
169
193
  raise
194
+ finally:
195
+ _running.reset(running)
170
196
  if self.async_gate.report_outcomes:
171
197
  await verdict.report("failed" if _failed(result) else "succeeded")
172
198
  return result
@@ -242,6 +268,11 @@ def deep_agent_subagents(middleware: ApprovalMiddleware, subagents: Sequence[Map
242
268
  the default general-purpose subagent with the same spec plus approvals.
243
269
  Compiled and async subagents run their own graphs and are refused: add
244
270
  the middleware to those graphs yourself.
271
+
272
+ With every subagent gated, the middleware stops gating the ``task`` tool
273
+ that delegates to them. ``task`` changes nothing itself, and gating it
274
+ would stop its subagents' calls from pausing: on resume LangGraph runs
275
+ ``task`` again, and its approval would already be consumed.
245
276
  """
246
277
  from deepagents.middleware.subagents import GENERAL_PURPOSE_SUBAGENT
247
278
 
@@ -252,4 +283,5 @@ def deep_agent_subagents(middleware: ApprovalMiddleware, subagents: Sequence[Map
252
283
  out.append({**spec, "middleware": [*spec.get("middleware", []), middleware]})
253
284
  if not any(spec.get("name") == GENERAL_PURPOSE_SUBAGENT["name"] for spec in out):
254
285
  out.append({**GENERAL_PURPOSE_SUBAGENT, "middleware": [middleware]})
286
+ middleware._ungated.add(DEEP_AGENTS_TASK_TOOL)
255
287
  return out
@@ -119,7 +119,7 @@ async def test_denied_call_returns_the_reason_to_the_model():
119
119
  runner, _ = build(provider, LlmAgent(name="support", model=Scripted(turn(REFUND), say("Understood.")), tools=[refund]))
120
120
  _, events = await start(runner)
121
121
  assert ran == []
122
- assert responses(events, "refund") == [{"error": "The provider denied this call."}]
122
+ assert responses(events, "refund") == [{"error": "The AAP provider denied this call."}]
123
123
  assert last_text(events) == "Understood."
124
124
 
125
125
 
@@ -204,7 +204,7 @@ async def test_async_denial_wakes_and_returns_the_reason():
204
204
  await resumer(runner)(provider.only().body["context"]["resume"], await provider.async_client().get(request_id))
205
205
  events = await session_events(runner, session_id)
206
206
  assert ran == []
207
- assert responses(events, "refund")[-1] == {"error": "The provider denied this call: Not this one."}
207
+ assert responses(events, "refund")[-1] == {"error": "The AAP provider denied this call: Not this one."}
208
208
  assert last_text(events) == "Understood."
209
209
 
210
210
 
@@ -312,7 +312,7 @@ async def test_invented_tool_names_are_gated():
312
312
  runner, _ = build(provider, LlmAgent(name="support", model=Scripted(turn(("c1", "wire_money", {"to": "x"})), say("Ok.")), tools=[refund]))
313
313
  _, events = await start(runner)
314
314
  assert provider.only().body["tool"] == "wire_money"
315
- assert responses(events, "wire_money") == [{"error": "The provider denied this call."}]
315
+ assert responses(events, "wire_money") == [{"error": "The AAP provider denied this call."}]
316
316
 
317
317
 
318
318
  async def test_gemini_built_in_tools_are_refused():
@@ -198,7 +198,7 @@ async def test_denied_call_returns_the_reason_to_the_model(cli):
198
198
  (result,) = await run(hooks(provider).options(base))
199
199
  assert ran == [] and result.result == "Understood."
200
200
  (denied,) = api.tool_results()
201
- assert denied["is_error"] and "The provider denied this call." in json.dumps(denied["content"])
201
+ assert denied["is_error"] and "The AAP provider denied this call." in json.dumps(denied["content"])
202
202
 
203
203
 
204
204
  @needs_cli
@@ -157,7 +157,7 @@ def test_denied_call_returns_the_reason_to_the_model():
157
157
  hooks(provider)
158
158
  crew(llm)[0].kickoff()
159
159
  assert ran == []
160
- assert llm.tool_results() == ["The provider denied this call: Not this one."]
160
+ assert llm.tool_results() == ["The AAP provider denied this call: Not this one."]
161
161
 
162
162
 
163
163
  def test_text_tool_calls_are_gated():
@@ -166,7 +166,7 @@ def test_text_tool_calls_are_gated():
166
166
  hooks(provider)
167
167
  out = crew(llm)[0].kickoff()
168
168
  assert ran == [] and out.raw == "Not refunded."
169
- assert "The provider denied this call." in llm.tool_results()[-1]
169
+ assert "The AAP provider denied this call." in llm.tool_results()[-1]
170
170
 
171
171
  provider.auto = "approved"
172
172
  llm = Scripted(model="scripted", native=False, turns=[react("refund", REFUND[2]), "Thought: done\nFinal Answer: Refunded."])
@@ -204,7 +204,7 @@ def test_parallel_calls_get_their_own_decisions():
204
204
  hooks(provider)
205
205
  crew(llm)[0].kickoff()
206
206
  assert ran == [{"payment_id": "payment_456", "amount": 100}]
207
- assert sorted(llm.tool_results()) == ["The provider denied this call: Too large.", "refunded payment_456"]
207
+ assert sorted(llm.tool_results()) == ["The AAP provider denied this call: Too large.", "refunded payment_456"]
208
208
  assert len(provider.requests) == 2
209
209
 
210
210
 
@@ -223,7 +223,7 @@ def test_delegated_coworker_calls_are_gated():
223
223
  Crew(agents=[lead, billing], tasks=[task]).kickoff()
224
224
  assert ran == []
225
225
  assert sorted(s.body["tool"] for s in provider.requests.values()) == ["delegate_work_to_coworker", "refund"]
226
- assert billing_llm.tool_results() == ["The provider denied this call: No refunds today."]
226
+ assert billing_llm.tool_results() == ["The AAP provider denied this call: No refunds today."]
227
227
  assert "The refund was denied." in lead_llm.tool_results()[0]
228
228
  delegated = next(s for s in provider.requests.values() if s.body["tool"] == "delegate_work_to_coworker")
229
229
  assert delegated.outcome["status"] == "succeeded"
@@ -235,7 +235,7 @@ def test_agent_kickoff_is_gated():
235
235
  hooks(provider)
236
236
  Agent(role="Support", goal="Refunds", backstory="b", llm=llm, tools=[refund]).kickoff("Refund payment_123")
237
237
  assert ran == [] and len(provider.requests) == 1
238
- assert llm.tool_results() == ["The provider denied this call."]
238
+ assert llm.tool_results() == ["The AAP provider denied this call."]
239
239
 
240
240
 
241
241
  def test_mcp_tools_send_the_server_and_original_name():
@@ -42,7 +42,7 @@ def test_denial_carries_the_reviewers_note():
42
42
  provider.decide(pending.request_id, "denied", "Refunds over 40 GBP need a manager.")
43
43
  verdict = gate.check(call())
44
44
  assert isinstance(verdict, Deny) and verdict.status == "denied"
45
- assert verdict.reason == "The provider denied this call: Refunds over 40 GBP need a manager."
45
+ assert verdict.reason == "The AAP provider denied this call: Refunds over 40 GBP need a manager."
46
46
 
47
47
 
48
48
  def test_same_call_cannot_run_twice_across_replicas():
@@ -67,7 +67,7 @@ def test_denied_call_returns_the_reason_to_the_model():
67
67
  result = agent(provider, turn(REFUND), AIMessage("Understood.")).invoke({"messages": [HumanMessage("Refund it")]})
68
68
  assert ran == []
69
69
  message = next(m for m in result["messages"] if isinstance(m, ToolMessage))
70
- assert message.status == "error" and message.content == "The provider denied this call."
70
+ assert message.status == "error" and message.content == "The AAP provider denied this call."
71
71
 
72
72
 
73
73
  def test_async_mode_pauses_ignores_forged_resumes_and_wakes_on_the_decision():
@@ -150,7 +150,75 @@ def test_deep_agent_subagent_calls_are_gated():
150
150
  graph = create_deep_agent(model=model, tools=[refund], middleware=[middleware], subagents=deep_agent_subagents(middleware))
151
151
  graph.invoke({"messages": [HumanMessage("Refund payment_123")]})
152
152
  assert ran == [{"payment_id": "payment_123", "amount": 4900}]
153
- assert sorted(s.body["tool"] for s in provider.requests.values()) == ["refund", "task"]
153
+ # task only delegates; the subagent's own call is the one sent for approval.
154
+ assert [s.body["tool"] for s in provider.requests.values()] == ["refund"]
155
+
156
+
157
+ @pytest.mark.skipif(sys.version_info < (3, 11), reason="Deep Agents needs Python 3.11")
158
+ def test_deep_agent_subagent_calls_pause_in_async_mode():
159
+ from deepagents import create_deep_agent
160
+
161
+ provider = FakeProvider(auto=None)
162
+ middleware = ApprovalMiddleware(gate=Gate(provider.client(), mode="async"), async_gate=AsyncGate(provider.async_client(), mode="async"))
163
+ model = Scripted(
164
+ messages=iter(
165
+ [
166
+ turn(("t1", "task", {"description": "Refund payment_123", "subagent_type": "general-purpose"})),
167
+ turn(REFUND),
168
+ AIMessage("Refunded."),
169
+ AIMessage("The subagent refunded it."),
170
+ ]
171
+ )
172
+ )
173
+ graph = create_deep_agent(model=model, tools=[refund], middleware=[middleware], subagents=deep_agent_subagents(middleware), checkpointer=InMemorySaver())
174
+ config = {"configurable": {"thread_id": "t3"}}
175
+ paused = graph.invoke({"messages": [HumanMessage("Refund payment_123")]}, config)
176
+ (pending,) = paused["__interrupt__"]
177
+ assert pending.value["tool"] == "refund" and ran == []
178
+
179
+ provider.decide(pending.value["aap_request_id"])
180
+ receiver = Receiver(secret=SECRET, client=provider.async_client(), resumers={"langgraph": resumer(graph)})
181
+ assert asyncio.run(receiver.wake(pending.value["aap_request_id"]))
182
+ # task runs again on resume, and the subagent continues from its own checkpoint.
183
+ assert ran == [{"payment_id": "payment_123", "amount": 4900}]
184
+ assert [(s.body["tool"], s.outcome["status"]) for s in provider.requests.values()] == [("refund", "succeeded")]
185
+ state = graph.get_state(config)
186
+ assert not state.interrupts and state.values["messages"][-1].content == "The subagent refunded it."
187
+
188
+
189
+ def test_calls_inside_a_running_tool_wait_in_async_mode():
190
+ provider = FakeProvider(auto=None)
191
+ middleware = ApprovalMiddleware(gate=Gate(provider.client(), mode="async"), async_gate=AsyncGate(provider.async_client(), mode="async"))
192
+ inner = create_agent(Scripted(messages=iter([turn(REFUND), AIMessage("Refunded.")])), tools=[refund], middleware=[middleware])
193
+
194
+ def billing(request: str) -> str:
195
+ """Ask the billing agent to do something."""
196
+ return inner.invoke({"messages": [HumanMessage(request)]})["messages"][-1].content
197
+
198
+ def approve_the_waiting_refund(request):
199
+ if request.method == "GET":
200
+ for stored in provider.requests.values():
201
+ if stored.body["tool"] == "refund":
202
+ provider._decide(stored, "approved", None)
203
+
204
+ provider.intercept = approve_the_waiting_refund
205
+ outer = create_agent(
206
+ Scripted(messages=iter([turn(("b1", "billing", {"request": "Refund payment_123"})), AIMessage("Billing refunded it.")])),
207
+ tools=[billing],
208
+ middleware=[middleware],
209
+ checkpointer=InMemorySaver(),
210
+ )
211
+ config = {"configurable": {"thread_id": "t4"}}
212
+ (pending,) = outer.invoke({"messages": [HumanMessage("Refund payment_123")]}, config)["__interrupt__"]
213
+ assert pending.value["tool"] == "billing"
214
+
215
+ provider.decide(pending.value["aap_request_id"])
216
+ receiver = Receiver(secret=SECRET, client=provider.async_client(), resumers={"langgraph": resumer(outer)})
217
+ assert asyncio.run(receiver.wake(pending.value["aap_request_id"]))
218
+ # The refund waited inside the approved billing call: pausing would run billing again, refused as consumed.
219
+ assert ran == [{"payment_id": "payment_123", "amount": 4900}]
220
+ assert sorted((s.body["tool"], s.outcome["status"]) for s in provider.requests.values()) == [("billing", "succeeded"), ("refund", "succeeded")]
221
+ assert outer.get_state(config).values["messages"][-1].content == "Billing refunded it."
154
222
 
155
223
 
156
224
  @pytest.mark.skipif(sys.version_info < (3, 11), reason="Deep Agents needs Python 3.11")
@@ -90,7 +90,7 @@ async def test_denied_call_returns_the_reason_to_the_model():
90
90
  agent = approvals(provider).protect(Agent(name="support", model=model, tools=[refund]))
91
91
  await Runner.run(agent, "Refund it")
92
92
  assert ran == []
93
- assert outputs(model) == ["The provider denied this call."]
93
+ assert outputs(model) == ["The AAP provider denied this call."]
94
94
 
95
95
 
96
96
  async def test_a_replayed_call_does_not_run_again():
@@ -181,7 +181,7 @@ async def test_async_mode_denial_returns_the_reason():
181
181
  receiver = Receiver(secret=SECRET, client=provider.async_client(), resumers={"openai-agents": gate.resumer(agent)})
182
182
  assert await receiver.wake(stored.id)
183
183
  assert ran == [] and model.remaining_steps == 0
184
- assert outputs(model) == ["The provider denied this call: Not today."]
184
+ assert outputs(model) == ["The AAP provider denied this call: Not today."]
185
185
 
186
186
 
187
187
  async def test_parallel_calls_wait_for_their_own_decisions():
@@ -204,7 +204,7 @@ async def test_parallel_calls_wait_for_their_own_decisions():
204
204
  await receiver.wake(ids["payment_123"])
205
205
  assert ran == [second]
206
206
  assert model.remaining_steps == 0
207
- assert sorted(outputs(model)) == ["The provider denied this call: Not this one.", "refunded payment_456"]
207
+ assert sorted(outputs(model)) == ["The AAP provider denied this call: Not this one.", "refunded payment_456"]
208
208
  assert len(provider.requests) == 2
209
209
 
210
210
 
@@ -341,7 +341,7 @@ async def test_local_mcp_denial_does_not_call_the_server():
341
341
  agent = approvals(provider).protect(Agent(name="support", model=model, mcp_servers=[crm]))
342
342
  await Runner.run(agent, "Update the contact")
343
343
  assert crm.calls == []
344
- assert outputs(model) == ["The provider denied this call."]
344
+ assert outputs(model) == ["The AAP provider denied this call."]
345
345
 
346
346
 
347
347
  @pytest.mark.parametrize("status", ["approved", "denied"])
@@ -106,7 +106,7 @@ async def test_denied_call_returns_the_reason_to_the_model():
106
106
  result = await agent(provider, turn(REFUND), done("Understood.")).run("Refund it")
107
107
  assert ran == []
108
108
  (part,) = returns(result.all_messages())
109
- assert part.outcome == "failed" and part.content == "The provider denied this call."
109
+ assert part.outcome == "failed" and part.content == "The AAP provider denied this call."
110
110
  assert result.output == "Understood."
111
111
 
112
112
 
@@ -27,7 +27,7 @@ constraints = [{ name = "onnxruntime", marker = "python_full_version < '3.11'",
27
27
 
28
28
  [[package]]
29
29
  name = "agentapprovalprotocol"
30
- version = "0.3.0"
30
+ version = "0.3.2"
31
31
  source = { editable = "." }
32
32
  dependencies = [
33
33
  { name = "httpx" },