adcp 2.16.0__py3-none-any.whl → 2.17.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.
adcp/__init__.py CHANGED
@@ -186,7 +186,7 @@ from adcp.webhooks import (
186
186
  get_adcp_signed_headers_for_webhook,
187
187
  )
188
188
 
189
- __version__ = "2.16.0"
189
+ __version__ = "2.17.0"
190
190
 
191
191
 
192
192
  def get_adcp_version() -> str:
@@ -129,12 +129,67 @@ def parse_mcp_content(content: list[dict[str, Any]], response_type: type[T]) ->
129
129
  )
130
130
 
131
131
 
132
+ # Protocol-level fields from ProtocolResponse (core/response.json) and
133
+ # ProtocolEnvelope (core/protocol_envelope.json). These are separated from
134
+ # task data for schema validation, but preserved at the TaskResult level.
135
+ # Note: 'data' and 'payload' are handled separately as wrapper fields.
136
+ PROTOCOL_FIELDS = {
137
+ "message", # Human-readable summary
138
+ "context_id", # Session continuity identifier
139
+ "task_id", # Async operation identifier
140
+ "status", # Task execution state
141
+ "timestamp", # Response timestamp
142
+ }
143
+
144
+
145
+ def _extract_task_data(data: dict[str, Any]) -> dict[str, Any]:
146
+ """
147
+ Extract task-specific data from a protocol response.
148
+
149
+ Servers may return responses in ProtocolResponse format:
150
+ {"message": "...", "context_id": "...", "data": {...}}
151
+
152
+ Or ProtocolEnvelope format:
153
+ {"message": "...", "status": "...", "payload": {...}}
154
+
155
+ Or task data directly with protocol fields mixed in:
156
+ {"message": "...", "products": [...], ...}
157
+
158
+ This function separates task-specific data for schema validation.
159
+ Protocol fields are preserved at the TaskResult level.
160
+
161
+ Args:
162
+ data: Response data dict
163
+
164
+ Returns:
165
+ Task-specific data suitable for schema validation.
166
+ Returns the same dict object if no extraction is needed.
167
+ """
168
+ # Check for wrapped payload fields
169
+ # (ProtocolResponse uses 'data', ProtocolEnvelope uses 'payload')
170
+ if "data" in data and isinstance(data["data"], dict):
171
+ return data["data"]
172
+ if "payload" in data and isinstance(data["payload"], dict):
173
+ return data["payload"]
174
+
175
+ # Check if any protocol fields are present
176
+ if not any(k in PROTOCOL_FIELDS for k in data):
177
+ return data # Return same object for identity check
178
+
179
+ # Separate task data from protocol fields
180
+ return {k: v for k, v in data.items() if k not in PROTOCOL_FIELDS}
181
+
182
+
132
183
  def parse_json_or_text(data: Any, response_type: type[T]) -> T:
133
184
  """
134
185
  Parse data that might be JSON string, dict, or other format.
135
186
 
136
187
  Used by A2A adapter for flexible response parsing.
137
188
 
189
+ Handles protocol-level wrapping where servers return:
190
+ - {"message": "...", "data": {...task_data...}}
191
+ - {"message": "...", ...task_fields...}
192
+
138
193
  Args:
139
194
  data: Response data (string, dict, or other)
140
195
  response_type: Expected Pydantic model type
@@ -147,22 +202,42 @@ def parse_json_or_text(data: Any, response_type: type[T]) -> T:
147
202
  """
148
203
  # If already a dict, try direct validation
149
204
  if isinstance(data, dict):
205
+ # Try direct validation first
206
+ original_error: Exception | None = None
150
207
  try:
151
208
  return _validate_union_type(data, response_type)
152
- except ValidationError as e:
153
- # Get the type name, handling Union types
154
- type_name = getattr(response_type, "__name__", str(response_type))
155
- raise ValueError(f"Response doesn't match expected schema {type_name}: {e}") from e
209
+ except (ValidationError, ValueError) as e:
210
+ original_error = e
211
+
212
+ # Try extracting task data (separates protocol fields)
213
+ task_data = _extract_task_data(data)
214
+ if task_data is not data:
215
+ try:
216
+ return _validate_union_type(task_data, response_type)
217
+ except (ValidationError, ValueError):
218
+ pass # Fall through to raise original error
219
+
220
+ # Report the original validation error
221
+ type_name = getattr(response_type, "__name__", str(response_type))
222
+ raise ValueError(
223
+ f"Response doesn't match expected schema {type_name}: {original_error}"
224
+ ) from original_error
156
225
 
157
226
  # If string, try JSON parsing
158
227
  if isinstance(data, str):
159
228
  try:
160
229
  parsed = json.loads(data)
161
- return _validate_union_type(parsed, response_type)
162
230
  except json.JSONDecodeError as e:
163
231
  raise ValueError(f"Response is not valid JSON: {e}") from e
232
+
233
+ # Recursively handle dict parsing (which includes protocol field extraction)
234
+ if isinstance(parsed, dict):
235
+ return parse_json_or_text(parsed, response_type)
236
+
237
+ # Non-dict JSON (shouldn't happen for AdCP responses)
238
+ try:
239
+ return _validate_union_type(parsed, response_type)
164
240
  except ValidationError as e:
165
- # Get the type name, handling Union types
166
241
  type_name = getattr(response_type, "__name__", str(response_type))
167
242
  raise ValueError(f"Response doesn't match expected schema {type_name}: {e}") from e
168
243
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 2.16.0
3
+ Version: 2.17.0
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
@@ -1,5 +1,5 @@
1
1
  adcp/ADCP_VERSION,sha256=cy9k2HT5B4jAGZsMGb_Zs7ZDbhQBFOU-RigyUy10xhw,6
2
- adcp/__init__.py,sha256=SVea2r6sPQTljwVo_f6PNFSDJNYnt1DQ-oSOJEohWqw,10319
2
+ adcp/__init__.py,sha256=0ihzp5E5L6yBTaALFz3HKjSSN3eTb_rY3EAJTMMVYH4,10319
3
3
  adcp/__main__.py,sha256=8v-j_W9IIWDIcvhaR1yuZAhBCDRfehUyN0tusstzyfQ,15139
4
4
  adcp/adagents.py,sha256=HY7vZ8QqC8Zjzc_jRA0ZMhAfvm1pJN_VJfS8a8Fbc6c,24481
5
5
  adcp/client.py,sha256=gdDnyCyotRKMBHgxyONq3BWr4Seo7U2KZE18gqpw3nU,50839
@@ -185,10 +185,10 @@ adcp/types/generated_poc/signals/get_signals_response.py,sha256=F-JQXrXg3jXBZfm-
185
185
  adcp/utils/__init__.py,sha256=uetvSJB19CjQbtwEYZiTnumJG11GsafQmXm5eR3hL7E,153
186
186
  adcp/utils/operation_id.py,sha256=wQX9Bb5epXzRq23xoeYPTqzu5yLuhshg7lKJZihcM2k,294
187
187
  adcp/utils/preview_cache.py,sha256=fy792IGXX9385FGsOhyuN1ZjoagsCstmcC1a8HAwtlI,18771
188
- adcp/utils/response_parser.py,sha256=uPk2vIH-RYZmq7y3i8lC4HTMQ3FfKdlgXKTjgJ1955M,6253
189
- adcp-2.16.0.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
190
- adcp-2.16.0.dist-info/METADATA,sha256=eZC137xJ4Wd7Ka6kCyxPsE3iHWMMYqdmavn2mjP-Ab8,31359
191
- adcp-2.16.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
192
- adcp-2.16.0.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
193
- adcp-2.16.0.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
194
- adcp-2.16.0.dist-info/RECORD,,
188
+ adcp/utils/response_parser.py,sha256=WBYq8bZpPiVrG70PNhHDyE5_NyexS1qlsA8YRkxpZaQ,8986
189
+ adcp-2.17.0.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
190
+ adcp-2.17.0.dist-info/METADATA,sha256=3ZsiYib0KMw4oolH1jg9KsGsUV8MgJ-oc-ysyox6Rjg,31359
191
+ adcp-2.17.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
192
+ adcp-2.17.0.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
193
+ adcp-2.17.0.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
194
+ adcp-2.17.0.dist-info/RECORD,,
File without changes