adcp 1.0.1__tar.gz → 1.0.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 (30) hide show
  1. {adcp-1.0.1/src/adcp.egg-info → adcp-1.0.2}/PKG-INFO +1 -1
  2. {adcp-1.0.1 → adcp-1.0.2}/pyproject.toml +1 -1
  3. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/__init__.py +1 -1
  4. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/client.py +3 -3
  5. {adcp-1.0.1 → adcp-1.0.2/src/adcp.egg-info}/PKG-INFO +1 -1
  6. {adcp-1.0.1 → adcp-1.0.2}/tests/test_cli.py +0 -28
  7. {adcp-1.0.1 → adcp-1.0.2}/tests/test_client.py +83 -9
  8. {adcp-1.0.1 → adcp-1.0.2}/tests/test_protocols.py +61 -0
  9. {adcp-1.0.1 → adcp-1.0.2}/LICENSE +0 -0
  10. {adcp-1.0.1 → adcp-1.0.2}/README.md +0 -0
  11. {adcp-1.0.1 → adcp-1.0.2}/setup.cfg +0 -0
  12. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/__main__.py +0 -0
  13. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/config.py +0 -0
  14. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/exceptions.py +0 -0
  15. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/protocols/__init__.py +0 -0
  16. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/protocols/a2a.py +0 -0
  17. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/protocols/base.py +0 -0
  18. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/protocols/mcp.py +0 -0
  19. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/types/__init__.py +0 -0
  20. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/types/core.py +0 -0
  21. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/types/generated.py +0 -0
  22. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/types/tasks.py +0 -0
  23. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/utils/__init__.py +0 -0
  24. {adcp-1.0.1 → adcp-1.0.2}/src/adcp/utils/operation_id.py +0 -0
  25. {adcp-1.0.1 → adcp-1.0.2}/src/adcp.egg-info/SOURCES.txt +0 -0
  26. {adcp-1.0.1 → adcp-1.0.2}/src/adcp.egg-info/dependency_links.txt +0 -0
  27. {adcp-1.0.1 → adcp-1.0.2}/src/adcp.egg-info/entry_points.txt +0 -0
  28. {adcp-1.0.1 → adcp-1.0.2}/src/adcp.egg-info/requires.txt +0 -0
  29. {adcp-1.0.1 → adcp-1.0.2}/src/adcp.egg-info/top_level.txt +0 -0
  30. {adcp-1.0.1 → adcp-1.0.2}/tests/test_code_generation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "adcp"
7
- version = "1.0.1"
7
+ version = "1.0.2"
8
8
  description = "Official Python client for the Ad Context Protocol (AdCP)"
9
9
  authors = [
10
10
  {name = "AdCP Community", email = "maintainers@adcontextprotocol.org"}
@@ -46,7 +46,7 @@ from adcp.types.generated import (
46
46
  UpdateMediaBuyResponse,
47
47
  )
48
48
 
49
- __version__ = "1.0.1"
49
+ __version__ = "1.0.2"
50
50
 
51
51
  __all__ = [
52
52
  # Client classes
@@ -159,19 +159,19 @@ class ADCPClient:
159
159
  type=ActivityType.PROTOCOL_REQUEST,
160
160
  operation_id=operation_id,
161
161
  agent_id=self.agent_config.id,
162
- task_type="update_media_buy",
162
+ task_type="list_creative_formats",
163
163
  timestamp=datetime.now(timezone.utc).isoformat(),
164
164
  )
165
165
  )
166
166
 
167
- result = await self.adapter.call_tool("update_media_buy", params)
167
+ result = await self.adapter.call_tool("list_creative_formats", params)
168
168
 
169
169
  self._emit_activity(
170
170
  Activity(
171
171
  type=ActivityType.PROTOCOL_RESPONSE,
172
172
  operation_id=operation_id,
173
173
  agent_id=self.agent_config.id,
174
- task_type="update_media_buy",
174
+ task_type="list_creative_formats",
175
175
  status=result.status,
176
176
  timestamp=datetime.now(timezone.utc).isoformat(),
177
177
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0
@@ -252,34 +252,6 @@ class TestCLIErrorHandling:
252
252
  class TestCLIIntegration:
253
253
  """Integration tests for CLI (with mocked network calls)."""
254
254
 
255
- @pytest.mark.asyncio
256
- async def test_tool_execution_flow(self, tmp_path, monkeypatch):
257
- """Test complete tool execution flow (mocked)."""
258
- # Setup config
259
- config_file = tmp_path / "config.json"
260
- config_data = {
261
- "agents": {
262
- "test": {
263
- "id": "test",
264
- "agent_uri": "https://test.com",
265
- "protocol": "mcp",
266
- }
267
- }
268
- }
269
- config_file.write_text(json.dumps(config_data))
270
-
271
- import adcp.config
272
- monkeypatch.setattr(adcp.config, "CONFIG_FILE", config_file)
273
-
274
- # This is an integration test concept - would need actual mocking
275
- # of ADCPClient to fully test. Showing the pattern here.
276
- # In practice, you'd mock the client's call_tool method.
277
-
278
- def test_json_output_format(self):
279
- """Test that --json flag produces valid JSON output."""
280
- # Would require mocking the actual tool call
281
- # Conceptual test showing what we'd verify
282
- pass
283
255
 
284
256
 
285
257
  class TestSpecialCharactersInPayload:
@@ -97,7 +97,8 @@ async def test_get_products():
97
97
  request = GetProductsRequest(brief="test campaign")
98
98
  result = await client.get_products(request)
99
99
 
100
- mock_call.assert_called_once()
100
+ # Verify correct tool name is called
101
+ mock_call.assert_called_once_with("get_products", {"brief": "test campaign"})
101
102
  assert result.success is True
102
103
  assert result.status == TaskStatus.COMPLETED
103
104
  assert "products" in result.data
@@ -126,11 +127,76 @@ async def test_all_client_methods():
126
127
  assert hasattr(client, "provide_performance_feedback")
127
128
 
128
129
 
130
+ @pytest.mark.parametrize(
131
+ "method_name,request_class,request_data",
132
+ [
133
+ ("get_products", "GetProductsRequest", {}),
134
+ ("list_creative_formats", "ListCreativeFormatsRequest", {}),
135
+ ("sync_creatives", "SyncCreativesRequest", {"creatives": []}),
136
+ ("list_creatives", "ListCreativesRequest", {}),
137
+ ("get_media_buy_delivery", "GetMediaBuyDeliveryRequest", {}),
138
+ ("list_authorized_properties", "ListAuthorizedPropertiesRequest", {}),
139
+ ("get_signals", "GetSignalsRequest", {"signal_spec": "test", "deliver_to": {}}),
140
+ (
141
+ "activate_signal",
142
+ "ActivateSignalRequest",
143
+ {"signal_agent_segment_id": "test", "platform": "test"},
144
+ ),
145
+ (
146
+ "provide_performance_feedback",
147
+ "ProvidePerformanceFeedbackRequest",
148
+ {"media_buy_id": "test", "measurement_period": {}, "performance_index": 0.5},
149
+ ),
150
+ ],
151
+ )
152
+ @pytest.mark.asyncio
153
+ async def test_method_calls_correct_tool_name(method_name, request_class, request_data):
154
+ """Test that each method calls adapter.call_tool with the correct tool name.
155
+
156
+ This test prevents copy-paste bugs where method bodies are copied but
157
+ tool names aren't updated to match the method name.
158
+ """
159
+ from unittest.mock import patch
160
+ from adcp.types.core import TaskResult, TaskStatus
161
+ import adcp.types.generated as gen
162
+
163
+ config = AgentConfig(
164
+ id="test_agent",
165
+ agent_uri="https://test.example.com",
166
+ protocol=Protocol.A2A,
167
+ )
168
+
169
+ client = ADCPClient(config)
170
+
171
+ # Create request instance with required fields
172
+ request_cls = getattr(gen, request_class)
173
+ request = request_cls(**request_data)
174
+
175
+ mock_result = TaskResult(
176
+ status=TaskStatus.COMPLETED,
177
+ data={},
178
+ success=True,
179
+ )
180
+
181
+ with patch.object(client.adapter, "call_tool", return_value=mock_result) as mock_call:
182
+ method = getattr(client, method_name)
183
+ await method(request)
184
+
185
+ # CRITICAL: Verify the tool name matches the method name
186
+ mock_call.assert_called_once()
187
+ actual_tool_name = mock_call.call_args[0][0]
188
+ assert actual_tool_name == method_name, (
189
+ f"Method {method_name} called tool '{actual_tool_name}' instead of '{method_name}'. "
190
+ f"This is likely a copy-paste bug."
191
+ )
192
+
193
+
129
194
  @pytest.mark.asyncio
130
195
  async def test_multi_agent_parallel_execution():
131
196
  """Test parallel execution across multiple agents."""
132
197
  from unittest.mock import patch
133
198
  from adcp.types.core import TaskResult, TaskStatus
199
+ from adcp.types.generated import GetProductsRequest
134
200
 
135
201
  agents = [
136
202
  AgentConfig(
@@ -153,11 +219,19 @@ async def test_multi_agent_parallel_execution():
153
219
  success=True,
154
220
  )
155
221
 
156
- # Mock both agents' adapters
157
- for agent_client in client.agents.values():
158
- with patch.object(agent_client.adapter, "call_tool", return_value=mock_result):
159
- pass
160
-
161
- # Test that get_products can be called on multi-agent client
162
- # (actual execution would require proper mocking of asyncio.gather)
163
- assert callable(client.get_products)
222
+ # Mock both agents' adapters - keep context active during execution
223
+ with patch.object(
224
+ client.agents["agent1"].adapter, "call_tool", return_value=mock_result
225
+ ) as mock1, patch.object(
226
+ client.agents["agent2"].adapter, "call_tool", return_value=mock_result
227
+ ) as mock2:
228
+ request = GetProductsRequest(brief="test")
229
+ results = await client.get_products(request)
230
+
231
+ # Verify both agents were called with correct tool name
232
+ mock1.assert_called_once_with("get_products", {"brief": "test"})
233
+ mock2.assert_called_once_with("get_products", {"brief": "test"})
234
+
235
+ # Verify results from both agents
236
+ assert len(results) == 2
237
+ assert all(r.success for r in results)
@@ -55,6 +55,26 @@ class TestA2AAdapter:
55
55
  with patch.object(adapter, "_get_client", return_value=mock_client):
56
56
  result = await adapter.call_tool("get_products", {"brief": "test"})
57
57
 
58
+ # Verify the adapter logic - check HTTP request details
59
+ mock_client.post.assert_called_once()
60
+ call_args = mock_client.post.call_args
61
+
62
+ # Verify URL includes /message/send endpoint
63
+ assert call_args[0][0] == "https://a2a.example.com/message/send"
64
+
65
+ # Verify headers include auth token (default auth_type is "token", not "bearer")
66
+ headers = call_args[1]["headers"]
67
+ assert "x-adcp-auth" in headers
68
+ assert headers["x-adcp-auth"] == "test_token"
69
+
70
+ # Verify request body structure matches A2A spec
71
+ json_body = call_args[1]["json"]
72
+ assert "message" in json_body
73
+ assert json_body["message"]["role"] == "user"
74
+ assert "parts" in json_body["message"]
75
+ assert "context_id" in json_body
76
+
77
+ # Verify result parsing
58
78
  assert result.success is True
59
79
  assert result.status == TaskStatus.COMPLETED
60
80
  assert result.data == {"result": "success"}
@@ -78,6 +98,14 @@ class TestA2AAdapter:
78
98
  with patch.object(adapter, "_get_client", return_value=mock_client):
79
99
  result = await adapter.call_tool("get_products", {"brief": "test"})
80
100
 
101
+ # Verify HTTP request was made with correct parameters
102
+ mock_client.post.assert_called_once()
103
+ call_args = mock_client.post.call_args
104
+ assert call_args[0][0] == "https://a2a.example.com/message/send"
105
+ assert call_args[1]["headers"]["x-adcp-auth"] == "test_token"
106
+ assert "message" in call_args[1]["json"]
107
+
108
+ # Verify failure handling
81
109
  assert result.success is False
82
110
  assert result.status == TaskStatus.FAILED
83
111
 
@@ -103,9 +131,22 @@ class TestA2AAdapter:
103
131
  with patch.object(adapter, "_get_client", return_value=mock_client):
104
132
  tools = await adapter.list_tools()
105
133
 
134
+ # Verify agent card URL construction (A2A spec uses agent.json)
135
+ mock_client.get.assert_called_once()
136
+ call_args = mock_client.get.call_args
137
+ expected_url = "https://a2a.example.com/.well-known/agent.json"
138
+ assert call_args[0][0] == expected_url
139
+
140
+ # Verify auth headers are included (default auth_type is "token")
141
+ headers = call_args[1]["headers"]
142
+ assert "x-adcp-auth" in headers
143
+ assert headers["x-adcp-auth"] == "test_token"
144
+
145
+ # Verify tool list parsing
106
146
  assert len(tools) == 3
107
147
  assert "get_products" in tools
108
148
  assert "create_media_buy" in tools
149
+ assert "list_creative_formats" in tools
109
150
 
110
151
 
111
152
  class TestMCPAdapter:
@@ -125,6 +166,15 @@ class TestMCPAdapter:
125
166
  with patch.object(adapter, "_get_session", return_value=mock_session):
126
167
  result = await adapter.call_tool("get_products", {"brief": "test"})
127
168
 
169
+ # Verify MCP protocol details - tool name and arguments
170
+ mock_session.call_tool.assert_called_once()
171
+ call_args = mock_session.call_tool.call_args
172
+
173
+ # Verify tool name and params are passed as positional args
174
+ assert call_args[0][0] == "get_products"
175
+ assert call_args[0][1] == {"brief": "test"}
176
+
177
+ # Verify result parsing
128
178
  assert result.success is True
129
179
  assert result.status == TaskStatus.COMPLETED
130
180
  assert result.data == [{"type": "text", "text": "Success"}]
@@ -140,6 +190,13 @@ class TestMCPAdapter:
140
190
  with patch.object(adapter, "_get_session", return_value=mock_session):
141
191
  result = await adapter.call_tool("get_products", {"brief": "test"})
142
192
 
193
+ # Verify call_tool was attempted with correct parameters (positional args)
194
+ mock_session.call_tool.assert_called_once()
195
+ call_args = mock_session.call_tool.call_args
196
+ assert call_args[0][0] == "get_products"
197
+ assert call_args[0][1] == {"brief": "test"}
198
+
199
+ # Verify error handling
143
200
  assert result.success is False
144
201
  assert result.status == TaskStatus.FAILED
145
202
  assert "Connection failed" in result.error
@@ -161,6 +218,10 @@ class TestMCPAdapter:
161
218
  with patch.object(adapter, "_get_session", return_value=mock_session):
162
219
  tools = await adapter.list_tools()
163
220
 
221
+ # Verify list_tools was called on the session
222
+ mock_session.list_tools.assert_called_once()
223
+
224
+ # Verify adapter correctly extracts tool names from MCP response
164
225
  assert len(tools) == 2
165
226
  assert "get_products" in tools
166
227
  assert "create_media_buy" in tools
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
File without changes
File without changes
File without changes
File without changes