adcp 0.1.2__py3-none-any.whl → 1.0.1__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 +80 -2
- adcp/__main__.py +284 -0
- adcp/client.py +292 -107
- adcp/config.py +82 -0
- adcp/exceptions.py +121 -0
- adcp/protocols/__init__.py +2 -0
- adcp/protocols/a2a.py +201 -87
- adcp/protocols/base.py +11 -0
- adcp/protocols/mcp.py +185 -25
- adcp/types/__init__.py +4 -0
- adcp/types/core.py +76 -1
- adcp/types/generated.py +615 -0
- adcp/types/tasks.py +281 -0
- adcp/utils/__init__.py +2 -0
- adcp/utils/operation_id.py +2 -0
- {adcp-0.1.2.dist-info → adcp-1.0.1.dist-info}/METADATA +184 -8
- adcp-1.0.1.dist-info/RECORD +21 -0
- adcp-1.0.1.dist-info/entry_points.txt +2 -0
- adcp-0.1.2.dist-info/RECORD +0 -15
- {adcp-0.1.2.dist-info → adcp-1.0.1.dist-info}/WHEEL +0 -0
- {adcp-0.1.2.dist-info → adcp-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {adcp-0.1.2.dist-info → adcp-1.0.1.dist-info}/top_level.txt +0 -0
adcp/client.py
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""Main client classes for AdCP."""
|
|
2
4
|
|
|
5
|
+
import hashlib
|
|
6
|
+
import hmac
|
|
3
7
|
import json
|
|
8
|
+
import logging
|
|
4
9
|
import os
|
|
5
10
|
from collections.abc import Callable
|
|
6
|
-
from datetime import datetime
|
|
11
|
+
from datetime import datetime, timezone
|
|
7
12
|
from typing import Any
|
|
8
|
-
from uuid import uuid4
|
|
9
13
|
|
|
14
|
+
from adcp.exceptions import ADCPWebhookSignatureError
|
|
10
15
|
from adcp.protocols.a2a import A2AAdapter
|
|
11
16
|
from adcp.protocols.base import ProtocolAdapter
|
|
12
17
|
from adcp.protocols.mcp import MCPAdapter
|
|
@@ -17,11 +22,29 @@ from adcp.types.core import (
|
|
|
17
22
|
Protocol,
|
|
18
23
|
TaskResult,
|
|
19
24
|
)
|
|
25
|
+
from adcp.types.generated import (
|
|
26
|
+
ActivateSignalRequest,
|
|
27
|
+
ActivateSignalResponse,
|
|
28
|
+
GetMediaBuyDeliveryRequest,
|
|
29
|
+
GetMediaBuyDeliveryResponse,
|
|
30
|
+
GetProductsRequest,
|
|
31
|
+
GetProductsResponse,
|
|
32
|
+
GetSignalsRequest,
|
|
33
|
+
GetSignalsResponse,
|
|
34
|
+
ListAuthorizedPropertiesRequest,
|
|
35
|
+
ListAuthorizedPropertiesResponse,
|
|
36
|
+
ListCreativeFormatsRequest,
|
|
37
|
+
ListCreativeFormatsResponse,
|
|
38
|
+
ListCreativesRequest,
|
|
39
|
+
ListCreativesResponse,
|
|
40
|
+
ProvidePerformanceFeedbackRequest,
|
|
41
|
+
ProvidePerformanceFeedbackResponse,
|
|
42
|
+
SyncCreativesRequest,
|
|
43
|
+
SyncCreativesResponse,
|
|
44
|
+
)
|
|
45
|
+
from adcp.utils.operation_id import create_operation_id
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
def create_operation_id() -> str:
|
|
23
|
-
"""Generate a unique operation ID."""
|
|
24
|
-
return f"op_{uuid4().hex[:12]}"
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
25
48
|
|
|
26
49
|
|
|
27
50
|
class ADCPClient:
|
|
@@ -74,10 +97,21 @@ class ADCPClient:
|
|
|
74
97
|
if self.on_activity:
|
|
75
98
|
self.on_activity(activity)
|
|
76
99
|
|
|
77
|
-
async def get_products(
|
|
78
|
-
|
|
100
|
+
async def get_products(
|
|
101
|
+
self,
|
|
102
|
+
request: GetProductsRequest,
|
|
103
|
+
) -> TaskResult[GetProductsResponse]:
|
|
104
|
+
"""
|
|
105
|
+
Get advertising products.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
request: Request parameters
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
TaskResult containing GetProductsResponse
|
|
112
|
+
"""
|
|
79
113
|
operation_id = create_operation_id()
|
|
80
|
-
params =
|
|
114
|
+
params = request.model_dump(exclude_none=True)
|
|
81
115
|
|
|
82
116
|
self._emit_activity(
|
|
83
117
|
Activity(
|
|
@@ -85,7 +119,7 @@ class ADCPClient:
|
|
|
85
119
|
operation_id=operation_id,
|
|
86
120
|
agent_id=self.agent_config.id,
|
|
87
121
|
task_type="get_products",
|
|
88
|
-
timestamp=datetime.
|
|
122
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
89
123
|
)
|
|
90
124
|
)
|
|
91
125
|
|
|
@@ -98,246 +132,351 @@ class ADCPClient:
|
|
|
98
132
|
agent_id=self.agent_config.id,
|
|
99
133
|
task_type="get_products",
|
|
100
134
|
status=result.status,
|
|
101
|
-
timestamp=datetime.
|
|
135
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
102
136
|
)
|
|
103
137
|
)
|
|
104
138
|
|
|
105
139
|
return result
|
|
106
140
|
|
|
107
|
-
async def list_creative_formats(
|
|
108
|
-
|
|
141
|
+
async def list_creative_formats(
|
|
142
|
+
self,
|
|
143
|
+
request: ListCreativeFormatsRequest,
|
|
144
|
+
) -> TaskResult[ListCreativeFormatsResponse]:
|
|
145
|
+
"""
|
|
146
|
+
List supported creative formats.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
request: Request parameters
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
TaskResult containing ListCreativeFormatsResponse
|
|
153
|
+
"""
|
|
109
154
|
operation_id = create_operation_id()
|
|
155
|
+
params = request.model_dump(exclude_none=True)
|
|
110
156
|
|
|
111
157
|
self._emit_activity(
|
|
112
158
|
Activity(
|
|
113
159
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
114
160
|
operation_id=operation_id,
|
|
115
161
|
agent_id=self.agent_config.id,
|
|
116
|
-
task_type="
|
|
117
|
-
timestamp=datetime.
|
|
162
|
+
task_type="update_media_buy",
|
|
163
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
118
164
|
)
|
|
119
165
|
)
|
|
120
166
|
|
|
121
|
-
result = await self.adapter.call_tool("
|
|
167
|
+
result = await self.adapter.call_tool("update_media_buy", params)
|
|
122
168
|
|
|
123
169
|
self._emit_activity(
|
|
124
170
|
Activity(
|
|
125
171
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
126
172
|
operation_id=operation_id,
|
|
127
173
|
agent_id=self.agent_config.id,
|
|
128
|
-
task_type="
|
|
174
|
+
task_type="update_media_buy",
|
|
129
175
|
status=result.status,
|
|
130
|
-
timestamp=datetime.
|
|
176
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
131
177
|
)
|
|
132
178
|
)
|
|
133
179
|
|
|
134
180
|
return result
|
|
135
181
|
|
|
136
|
-
async def
|
|
137
|
-
|
|
182
|
+
async def sync_creatives(
|
|
183
|
+
self,
|
|
184
|
+
request: SyncCreativesRequest,
|
|
185
|
+
) -> TaskResult[SyncCreativesResponse]:
|
|
186
|
+
"""
|
|
187
|
+
Sync Creatives.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
request: Request parameters
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
TaskResult containing SyncCreativesResponse
|
|
194
|
+
"""
|
|
138
195
|
operation_id = create_operation_id()
|
|
196
|
+
params = request.model_dump(exclude_none=True)
|
|
139
197
|
|
|
140
198
|
self._emit_activity(
|
|
141
199
|
Activity(
|
|
142
200
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
143
201
|
operation_id=operation_id,
|
|
144
202
|
agent_id=self.agent_config.id,
|
|
145
|
-
task_type="
|
|
146
|
-
timestamp=datetime.
|
|
203
|
+
task_type="sync_creatives",
|
|
204
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
147
205
|
)
|
|
148
206
|
)
|
|
149
207
|
|
|
150
|
-
result = await self.adapter.call_tool("
|
|
208
|
+
result = await self.adapter.call_tool("sync_creatives", params)
|
|
151
209
|
|
|
152
210
|
self._emit_activity(
|
|
153
211
|
Activity(
|
|
154
212
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
155
213
|
operation_id=operation_id,
|
|
156
214
|
agent_id=self.agent_config.id,
|
|
157
|
-
task_type="
|
|
215
|
+
task_type="sync_creatives",
|
|
158
216
|
status=result.status,
|
|
159
|
-
timestamp=datetime.
|
|
217
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
160
218
|
)
|
|
161
219
|
)
|
|
162
220
|
|
|
163
221
|
return result
|
|
164
222
|
|
|
165
|
-
async def
|
|
166
|
-
|
|
223
|
+
async def list_creatives(
|
|
224
|
+
self,
|
|
225
|
+
request: ListCreativesRequest,
|
|
226
|
+
) -> TaskResult[ListCreativesResponse]:
|
|
227
|
+
"""
|
|
228
|
+
List Creatives.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
request: Request parameters
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
TaskResult containing ListCreativesResponse
|
|
235
|
+
"""
|
|
167
236
|
operation_id = create_operation_id()
|
|
237
|
+
params = request.model_dump(exclude_none=True)
|
|
168
238
|
|
|
169
239
|
self._emit_activity(
|
|
170
240
|
Activity(
|
|
171
241
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
172
242
|
operation_id=operation_id,
|
|
173
243
|
agent_id=self.agent_config.id,
|
|
174
|
-
task_type="
|
|
175
|
-
timestamp=datetime.
|
|
244
|
+
task_type="list_creatives",
|
|
245
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
176
246
|
)
|
|
177
247
|
)
|
|
178
248
|
|
|
179
|
-
result = await self.adapter.call_tool("
|
|
249
|
+
result = await self.adapter.call_tool("list_creatives", params)
|
|
180
250
|
|
|
181
251
|
self._emit_activity(
|
|
182
252
|
Activity(
|
|
183
253
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
184
254
|
operation_id=operation_id,
|
|
185
255
|
agent_id=self.agent_config.id,
|
|
186
|
-
task_type="
|
|
256
|
+
task_type="list_creatives",
|
|
187
257
|
status=result.status,
|
|
188
|
-
timestamp=datetime.
|
|
258
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
189
259
|
)
|
|
190
260
|
)
|
|
191
261
|
|
|
192
262
|
return result
|
|
193
263
|
|
|
194
|
-
async def
|
|
195
|
-
|
|
264
|
+
async def get_media_buy_delivery(
|
|
265
|
+
self,
|
|
266
|
+
request: GetMediaBuyDeliveryRequest,
|
|
267
|
+
) -> TaskResult[GetMediaBuyDeliveryResponse]:
|
|
268
|
+
"""
|
|
269
|
+
Get Media Buy Delivery.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
request: Request parameters
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
TaskResult containing GetMediaBuyDeliveryResponse
|
|
276
|
+
"""
|
|
196
277
|
operation_id = create_operation_id()
|
|
278
|
+
params = request.model_dump(exclude_none=True)
|
|
197
279
|
|
|
198
280
|
self._emit_activity(
|
|
199
281
|
Activity(
|
|
200
282
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
201
283
|
operation_id=operation_id,
|
|
202
284
|
agent_id=self.agent_config.id,
|
|
203
|
-
task_type="
|
|
204
|
-
timestamp=datetime.
|
|
285
|
+
task_type="get_media_buy_delivery",
|
|
286
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
205
287
|
)
|
|
206
288
|
)
|
|
207
289
|
|
|
208
|
-
result = await self.adapter.call_tool("
|
|
290
|
+
result = await self.adapter.call_tool("get_media_buy_delivery", params)
|
|
209
291
|
|
|
210
292
|
self._emit_activity(
|
|
211
293
|
Activity(
|
|
212
294
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
213
295
|
operation_id=operation_id,
|
|
214
296
|
agent_id=self.agent_config.id,
|
|
215
|
-
task_type="
|
|
297
|
+
task_type="get_media_buy_delivery",
|
|
216
298
|
status=result.status,
|
|
217
|
-
timestamp=datetime.
|
|
299
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
218
300
|
)
|
|
219
301
|
)
|
|
220
302
|
|
|
221
303
|
return result
|
|
222
304
|
|
|
223
|
-
async def
|
|
224
|
-
|
|
305
|
+
async def list_authorized_properties(
|
|
306
|
+
self,
|
|
307
|
+
request: ListAuthorizedPropertiesRequest,
|
|
308
|
+
) -> TaskResult[ListAuthorizedPropertiesResponse]:
|
|
309
|
+
"""
|
|
310
|
+
List Authorized Properties.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
request: Request parameters
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
TaskResult containing ListAuthorizedPropertiesResponse
|
|
317
|
+
"""
|
|
225
318
|
operation_id = create_operation_id()
|
|
319
|
+
params = request.model_dump(exclude_none=True)
|
|
226
320
|
|
|
227
321
|
self._emit_activity(
|
|
228
322
|
Activity(
|
|
229
323
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
230
324
|
operation_id=operation_id,
|
|
231
325
|
agent_id=self.agent_config.id,
|
|
232
|
-
task_type="
|
|
233
|
-
timestamp=datetime.
|
|
326
|
+
task_type="list_authorized_properties",
|
|
327
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
234
328
|
)
|
|
235
329
|
)
|
|
236
330
|
|
|
237
|
-
result = await self.adapter.call_tool("
|
|
331
|
+
result = await self.adapter.call_tool("list_authorized_properties", params)
|
|
238
332
|
|
|
239
333
|
self._emit_activity(
|
|
240
334
|
Activity(
|
|
241
335
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
242
336
|
operation_id=operation_id,
|
|
243
337
|
agent_id=self.agent_config.id,
|
|
244
|
-
task_type="
|
|
338
|
+
task_type="list_authorized_properties",
|
|
245
339
|
status=result.status,
|
|
246
|
-
timestamp=datetime.
|
|
340
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
247
341
|
)
|
|
248
342
|
)
|
|
249
343
|
|
|
250
344
|
return result
|
|
251
345
|
|
|
252
|
-
async def
|
|
253
|
-
|
|
346
|
+
async def get_signals(
|
|
347
|
+
self,
|
|
348
|
+
request: GetSignalsRequest,
|
|
349
|
+
) -> TaskResult[GetSignalsResponse]:
|
|
350
|
+
"""
|
|
351
|
+
Get Signals.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
request: Request parameters
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
TaskResult containing GetSignalsResponse
|
|
358
|
+
"""
|
|
254
359
|
operation_id = create_operation_id()
|
|
360
|
+
params = request.model_dump(exclude_none=True)
|
|
255
361
|
|
|
256
362
|
self._emit_activity(
|
|
257
363
|
Activity(
|
|
258
364
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
259
365
|
operation_id=operation_id,
|
|
260
366
|
agent_id=self.agent_config.id,
|
|
261
|
-
task_type="
|
|
262
|
-
timestamp=datetime.
|
|
367
|
+
task_type="get_signals",
|
|
368
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
263
369
|
)
|
|
264
370
|
)
|
|
265
371
|
|
|
266
|
-
result = await self.adapter.call_tool("
|
|
372
|
+
result = await self.adapter.call_tool("get_signals", params)
|
|
267
373
|
|
|
268
374
|
self._emit_activity(
|
|
269
375
|
Activity(
|
|
270
376
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
271
377
|
operation_id=operation_id,
|
|
272
378
|
agent_id=self.agent_config.id,
|
|
273
|
-
task_type="
|
|
379
|
+
task_type="get_signals",
|
|
274
380
|
status=result.status,
|
|
275
|
-
timestamp=datetime.
|
|
381
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
276
382
|
)
|
|
277
383
|
)
|
|
278
384
|
|
|
279
385
|
return result
|
|
280
386
|
|
|
281
|
-
async def
|
|
282
|
-
|
|
387
|
+
async def activate_signal(
|
|
388
|
+
self,
|
|
389
|
+
request: ActivateSignalRequest,
|
|
390
|
+
) -> TaskResult[ActivateSignalResponse]:
|
|
391
|
+
"""
|
|
392
|
+
Activate Signal.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
request: Request parameters
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
TaskResult containing ActivateSignalResponse
|
|
399
|
+
"""
|
|
283
400
|
operation_id = create_operation_id()
|
|
401
|
+
params = request.model_dump(exclude_none=True)
|
|
284
402
|
|
|
285
403
|
self._emit_activity(
|
|
286
404
|
Activity(
|
|
287
405
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
288
406
|
operation_id=operation_id,
|
|
289
407
|
agent_id=self.agent_config.id,
|
|
290
|
-
task_type="
|
|
291
|
-
timestamp=datetime.
|
|
408
|
+
task_type="activate_signal",
|
|
409
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
292
410
|
)
|
|
293
411
|
)
|
|
294
412
|
|
|
295
|
-
result = await self.adapter.call_tool("
|
|
413
|
+
result = await self.adapter.call_tool("activate_signal", params)
|
|
296
414
|
|
|
297
415
|
self._emit_activity(
|
|
298
416
|
Activity(
|
|
299
417
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
300
418
|
operation_id=operation_id,
|
|
301
419
|
agent_id=self.agent_config.id,
|
|
302
|
-
task_type="
|
|
420
|
+
task_type="activate_signal",
|
|
303
421
|
status=result.status,
|
|
304
|
-
timestamp=datetime.
|
|
422
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
305
423
|
)
|
|
306
424
|
)
|
|
307
425
|
|
|
308
426
|
return result
|
|
309
427
|
|
|
310
|
-
async def
|
|
311
|
-
|
|
428
|
+
async def provide_performance_feedback(
|
|
429
|
+
self,
|
|
430
|
+
request: ProvidePerformanceFeedbackRequest,
|
|
431
|
+
) -> TaskResult[ProvidePerformanceFeedbackResponse]:
|
|
432
|
+
"""
|
|
433
|
+
Provide Performance Feedback.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
request: Request parameters
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
TaskResult containing ProvidePerformanceFeedbackResponse
|
|
440
|
+
"""
|
|
312
441
|
operation_id = create_operation_id()
|
|
442
|
+
params = request.model_dump(exclude_none=True)
|
|
313
443
|
|
|
314
444
|
self._emit_activity(
|
|
315
445
|
Activity(
|
|
316
446
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
317
447
|
operation_id=operation_id,
|
|
318
448
|
agent_id=self.agent_config.id,
|
|
319
|
-
task_type="
|
|
320
|
-
timestamp=datetime.
|
|
449
|
+
task_type="provide_performance_feedback",
|
|
450
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
321
451
|
)
|
|
322
452
|
)
|
|
323
453
|
|
|
324
|
-
result = await self.adapter.call_tool("
|
|
454
|
+
result = await self.adapter.call_tool("provide_performance_feedback", params)
|
|
325
455
|
|
|
326
456
|
self._emit_activity(
|
|
327
457
|
Activity(
|
|
328
458
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
329
459
|
operation_id=operation_id,
|
|
330
460
|
agent_id=self.agent_config.id,
|
|
331
|
-
task_type="
|
|
461
|
+
task_type="provide_performance_feedback",
|
|
332
462
|
status=result.status,
|
|
333
|
-
timestamp=datetime.
|
|
463
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
334
464
|
)
|
|
335
465
|
)
|
|
336
466
|
|
|
337
467
|
return result
|
|
338
468
|
|
|
339
|
-
async def
|
|
340
|
-
"""
|
|
469
|
+
async def call_tool(self, tool_name: str, params: dict[str, Any]) -> TaskResult[Any]:
|
|
470
|
+
"""
|
|
471
|
+
Call any tool on the agent.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
tool_name: Name of the tool to call
|
|
475
|
+
params: Tool parameters
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
TaskResult with the response
|
|
479
|
+
"""
|
|
341
480
|
operation_id = create_operation_id()
|
|
342
481
|
|
|
343
482
|
self._emit_activity(
|
|
@@ -345,54 +484,69 @@ class ADCPClient:
|
|
|
345
484
|
type=ActivityType.PROTOCOL_REQUEST,
|
|
346
485
|
operation_id=operation_id,
|
|
347
486
|
agent_id=self.agent_config.id,
|
|
348
|
-
task_type=
|
|
349
|
-
timestamp=datetime.
|
|
487
|
+
task_type=tool_name,
|
|
488
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
350
489
|
)
|
|
351
490
|
)
|
|
352
491
|
|
|
353
|
-
result = await self.adapter.call_tool(
|
|
492
|
+
result = await self.adapter.call_tool(tool_name, params)
|
|
354
493
|
|
|
355
494
|
self._emit_activity(
|
|
356
495
|
Activity(
|
|
357
496
|
type=ActivityType.PROTOCOL_RESPONSE,
|
|
358
497
|
operation_id=operation_id,
|
|
359
498
|
agent_id=self.agent_config.id,
|
|
360
|
-
task_type=
|
|
499
|
+
task_type=tool_name,
|
|
361
500
|
status=result.status,
|
|
362
|
-
timestamp=datetime.
|
|
501
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
363
502
|
)
|
|
364
503
|
)
|
|
365
504
|
|
|
366
505
|
return result
|
|
367
506
|
|
|
368
|
-
async def
|
|
369
|
-
"""
|
|
370
|
-
|
|
507
|
+
async def list_tools(self) -> list[str]:
|
|
508
|
+
"""
|
|
509
|
+
List available tools from the agent.
|
|
371
510
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
agent_id=self.agent_config.id,
|
|
377
|
-
task_type="provide_performance_feedback",
|
|
378
|
-
timestamp=datetime.utcnow().isoformat(),
|
|
379
|
-
)
|
|
380
|
-
)
|
|
511
|
+
Returns:
|
|
512
|
+
List of tool names
|
|
513
|
+
"""
|
|
514
|
+
return await self.adapter.list_tools()
|
|
381
515
|
|
|
382
|
-
|
|
516
|
+
async def close(self) -> None:
|
|
517
|
+
"""Close the adapter and clean up resources."""
|
|
518
|
+
if hasattr(self.adapter, "close"):
|
|
519
|
+
logger.debug(f"Closing adapter for agent {self.agent_config.id}")
|
|
520
|
+
await self.adapter.close()
|
|
383
521
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
operation_id=operation_id,
|
|
388
|
-
agent_id=self.agent_config.id,
|
|
389
|
-
task_type="provide_performance_feedback",
|
|
390
|
-
status=result.status,
|
|
391
|
-
timestamp=datetime.utcnow().isoformat(),
|
|
392
|
-
)
|
|
393
|
-
)
|
|
522
|
+
async def __aenter__(self) -> ADCPClient:
|
|
523
|
+
"""Async context manager entry."""
|
|
524
|
+
return self
|
|
394
525
|
|
|
395
|
-
|
|
526
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
527
|
+
"""Async context manager exit."""
|
|
528
|
+
await self.close()
|
|
529
|
+
|
|
530
|
+
def _verify_webhook_signature(self, payload: dict[str, Any], signature: str) -> bool:
|
|
531
|
+
"""
|
|
532
|
+
Verify HMAC-SHA256 signature of webhook payload.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
payload: Webhook payload dict
|
|
536
|
+
signature: Signature to verify
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
True if signature is valid, False otherwise
|
|
540
|
+
"""
|
|
541
|
+
if not self.webhook_secret:
|
|
542
|
+
return True
|
|
543
|
+
|
|
544
|
+
payload_bytes = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
|
545
|
+
expected_signature = hmac.new(
|
|
546
|
+
self.webhook_secret.encode("utf-8"), payload_bytes, hashlib.sha256
|
|
547
|
+
).hexdigest()
|
|
548
|
+
|
|
549
|
+
return hmac.compare_digest(signature, expected_signature)
|
|
396
550
|
|
|
397
551
|
async def handle_webhook(
|
|
398
552
|
self,
|
|
@@ -405,11 +559,15 @@ class ADCPClient:
|
|
|
405
559
|
Args:
|
|
406
560
|
payload: Webhook payload
|
|
407
561
|
signature: Webhook signature for verification
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
ADCPWebhookSignatureError: If signature verification fails
|
|
408
565
|
"""
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
566
|
+
if signature and not self._verify_webhook_signature(payload, signature):
|
|
567
|
+
logger.warning(
|
|
568
|
+
f"Webhook signature verification failed for agent {self.agent_config.id}"
|
|
569
|
+
)
|
|
570
|
+
raise ADCPWebhookSignatureError("Invalid webhook signature")
|
|
413
571
|
|
|
414
572
|
operation_id = payload.get("operation_id", "unknown")
|
|
415
573
|
task_type = payload.get("task_type", "unknown")
|
|
@@ -420,7 +578,7 @@ class ADCPClient:
|
|
|
420
578
|
operation_id=operation_id,
|
|
421
579
|
agent_id=self.agent_config.id,
|
|
422
580
|
task_type=task_type,
|
|
423
|
-
timestamp=datetime.
|
|
581
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
424
582
|
metadata={"payload": payload},
|
|
425
583
|
)
|
|
426
584
|
)
|
|
@@ -469,15 +627,42 @@ class ADCPMultiAgentClient:
|
|
|
469
627
|
"""Get list of agent IDs."""
|
|
470
628
|
return list(self.agents.keys())
|
|
471
629
|
|
|
472
|
-
async def
|
|
473
|
-
"""
|
|
630
|
+
async def close(self) -> None:
|
|
631
|
+
"""Close all agent clients and clean up resources."""
|
|
632
|
+
import asyncio
|
|
633
|
+
|
|
634
|
+
logger.debug("Closing all agent clients in multi-agent client")
|
|
635
|
+
close_tasks = [client.close() for client in self.agents.values()]
|
|
636
|
+
await asyncio.gather(*close_tasks, return_exceptions=True)
|
|
637
|
+
|
|
638
|
+
async def __aenter__(self) -> ADCPMultiAgentClient:
|
|
639
|
+
"""Async context manager entry."""
|
|
640
|
+
return self
|
|
641
|
+
|
|
642
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
643
|
+
"""Async context manager exit."""
|
|
644
|
+
await self.close()
|
|
645
|
+
|
|
646
|
+
async def get_products(
|
|
647
|
+
self,
|
|
648
|
+
request: GetProductsRequest,
|
|
649
|
+
) -> list[TaskResult[GetProductsResponse]]:
|
|
650
|
+
"""
|
|
651
|
+
Execute get_products across all agents in parallel.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
request: Request parameters
|
|
655
|
+
|
|
656
|
+
Returns:
|
|
657
|
+
List of TaskResults containing GetProductsResponse for each agent
|
|
658
|
+
"""
|
|
474
659
|
import asyncio
|
|
475
660
|
|
|
476
|
-
tasks = [agent.get_products(
|
|
661
|
+
tasks = [agent.get_products(request) for agent in self.agents.values()]
|
|
477
662
|
return await asyncio.gather(*tasks)
|
|
478
663
|
|
|
479
664
|
@classmethod
|
|
480
|
-
def from_env(cls) ->
|
|
665
|
+
def from_env(cls) -> ADCPMultiAgentClient:
|
|
481
666
|
"""Create client from environment variables."""
|
|
482
667
|
agents_json = os.getenv("ADCP_AGENTS")
|
|
483
668
|
if not agents_json:
|