ioa-observe-sdk 1.0.28__py3-none-any.whl → 1.0.30__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.
- ioa_observe/sdk/instrumentations/a2a.py +431 -122
- ioa_observe/sdk/instrumentations/slim.py +367 -866
- ioa_observe/sdk/tracing/tracing.py +70 -13
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.30.dist-info}/METADATA +29 -29
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.30.dist-info}/RECORD +8 -8
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.30.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.30.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.30.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,72 @@ _instruments = ("a2a-sdk >= 0.3.0",)
|
|
|
19
19
|
_global_tracer = None
|
|
20
20
|
_kv_lock = threading.RLock() # Add thread-safety for kv_store operations
|
|
21
21
|
|
|
22
|
+
# Track original methods for uninstrumentation
|
|
23
|
+
_original_methods = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _inject_observe_metadata(request, span_name: str):
|
|
27
|
+
"""
|
|
28
|
+
Helper function to inject observability metadata into request.
|
|
29
|
+
This is shared between legacy and new client instrumentation.
|
|
30
|
+
"""
|
|
31
|
+
traceparent = get_current_traceparent()
|
|
32
|
+
session_id = None
|
|
33
|
+
if traceparent:
|
|
34
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
35
|
+
if not session_id:
|
|
36
|
+
session_id = get_value("session.id")
|
|
37
|
+
if session_id:
|
|
38
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
39
|
+
|
|
40
|
+
# Ensure metadata dict exists - handle both request.params.metadata and request.metadata
|
|
41
|
+
try:
|
|
42
|
+
if hasattr(request, "params") and hasattr(request.params, "metadata"):
|
|
43
|
+
md = getattr(request.params, "metadata", None)
|
|
44
|
+
else:
|
|
45
|
+
md = getattr(request, "metadata", None)
|
|
46
|
+
except AttributeError:
|
|
47
|
+
md = None
|
|
48
|
+
metadata = md if isinstance(md, dict) else {}
|
|
49
|
+
|
|
50
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
51
|
+
|
|
52
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
53
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
54
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
55
|
+
|
|
56
|
+
if traceparent:
|
|
57
|
+
observe_meta["traceparent"] = traceparent
|
|
58
|
+
if session_id:
|
|
59
|
+
observe_meta["session_id"] = session_id
|
|
60
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
61
|
+
|
|
62
|
+
metadata["observe"] = observe_meta
|
|
63
|
+
|
|
64
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
65
|
+
try:
|
|
66
|
+
if hasattr(request, "params") and hasattr(request.params, "metadata"):
|
|
67
|
+
request.params.metadata = metadata
|
|
68
|
+
else:
|
|
69
|
+
request.metadata = metadata
|
|
70
|
+
except Exception:
|
|
71
|
+
# Fallback - create a new request with updated metadata
|
|
72
|
+
try:
|
|
73
|
+
if hasattr(request, "params"):
|
|
74
|
+
request = request.model_copy(
|
|
75
|
+
update={
|
|
76
|
+
"params": request.params.model_copy(
|
|
77
|
+
update={"metadata": metadata}
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
83
|
+
except Exception:
|
|
84
|
+
pass # If all else fails, continue without metadata injection
|
|
85
|
+
|
|
86
|
+
return request
|
|
87
|
+
|
|
22
88
|
|
|
23
89
|
class A2AInstrumentor(BaseInstrumentor):
|
|
24
90
|
def __init__(self):
|
|
@@ -35,10 +101,105 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
35
101
|
if importlib.util.find_spec("a2a") is None:
|
|
36
102
|
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
37
103
|
|
|
38
|
-
# Instrument client
|
|
39
|
-
|
|
104
|
+
# Instrument new A2AClient (BaseClient) from a2a.client.base_client (v0.3.0+)
|
|
105
|
+
self._instrument_base_client()
|
|
106
|
+
|
|
107
|
+
# Instrument legacy A2AClient for backward compatibility
|
|
108
|
+
self._instrument_legacy_client()
|
|
109
|
+
|
|
110
|
+
# Instrument server handler
|
|
111
|
+
self._instrument_server_handler()
|
|
112
|
+
|
|
113
|
+
# Instrument slima2a if available
|
|
114
|
+
self._instrument_slima2a()
|
|
115
|
+
|
|
116
|
+
def _instrument_base_client(self):
|
|
117
|
+
"""Instrument the new A2AClient (BaseClient) pattern introduced in v0.3.0."""
|
|
118
|
+
import importlib
|
|
119
|
+
|
|
120
|
+
# Check if the new base_client module exists
|
|
121
|
+
if importlib.util.find_spec("a2a.client.base_client") is None:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
from a2a.client.base_client import A2AClient as BaseA2AClient
|
|
126
|
+
except ImportError:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
# Instrument send_message
|
|
130
|
+
if hasattr(BaseA2AClient, "send_message"):
|
|
131
|
+
original_send_message = BaseA2AClient.send_message
|
|
132
|
+
_original_methods["BaseA2AClient.send_message"] = original_send_message
|
|
133
|
+
|
|
134
|
+
@functools.wraps(original_send_message)
|
|
135
|
+
async def instrumented_send_message(self, request, *args, **kwargs):
|
|
136
|
+
nonlocal original_send_message
|
|
137
|
+
with _global_tracer.start_as_current_span("a2a.client.send_message"):
|
|
138
|
+
request = _inject_observe_metadata(
|
|
139
|
+
request, "a2a.client.send_message"
|
|
140
|
+
)
|
|
141
|
+
return await original_send_message(self, request, *args, **kwargs)
|
|
142
|
+
|
|
143
|
+
BaseA2AClient.send_message = instrumented_send_message
|
|
144
|
+
|
|
145
|
+
# Instrument send_message_streaming
|
|
146
|
+
if hasattr(BaseA2AClient, "send_message_streaming"):
|
|
147
|
+
original_send_message_streaming = BaseA2AClient.send_message_streaming
|
|
148
|
+
_original_methods["BaseA2AClient.send_message_streaming"] = (
|
|
149
|
+
original_send_message_streaming
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@functools.wraps(original_send_message_streaming)
|
|
153
|
+
async def instrumented_send_message_streaming(
|
|
154
|
+
self, request, *args, **kwargs
|
|
155
|
+
):
|
|
156
|
+
nonlocal original_send_message_streaming
|
|
157
|
+
with _global_tracer.start_as_current_span(
|
|
158
|
+
"a2a.client.send_message_streaming"
|
|
159
|
+
):
|
|
160
|
+
request = _inject_observe_metadata(
|
|
161
|
+
request, "a2a.client.send_message_streaming"
|
|
162
|
+
)
|
|
163
|
+
# This is an async generator, so we need to yield from it
|
|
164
|
+
async for response in original_send_message_streaming(
|
|
165
|
+
self, request, *args, **kwargs
|
|
166
|
+
):
|
|
167
|
+
yield response
|
|
168
|
+
|
|
169
|
+
BaseA2AClient.send_message_streaming = instrumented_send_message_streaming
|
|
170
|
+
|
|
171
|
+
def _instrument_legacy_client(self):
|
|
172
|
+
"""Instrument the legacy A2AClient for backward compatibility."""
|
|
173
|
+
|
|
174
|
+
# Try to import from legacy location first (v0.3.0+)
|
|
175
|
+
legacy_client = None
|
|
176
|
+
legacy_module_path = None
|
|
40
177
|
|
|
41
|
-
|
|
178
|
+
try:
|
|
179
|
+
from a2a.client.legacy import A2AClient as LegacyA2AClient
|
|
180
|
+
|
|
181
|
+
legacy_client = LegacyA2AClient
|
|
182
|
+
legacy_module_path = "a2a.client.legacy"
|
|
183
|
+
except ImportError:
|
|
184
|
+
# Fall back to old import path (pre-v0.3.0)
|
|
185
|
+
try:
|
|
186
|
+
from a2a.client import A2AClient as LegacyA2AClient
|
|
187
|
+
|
|
188
|
+
# Check if this is the legacy client (has send_message method directly)
|
|
189
|
+
if hasattr(LegacyA2AClient, "send_message"):
|
|
190
|
+
legacy_client = LegacyA2AClient
|
|
191
|
+
legacy_module_path = "a2a.client"
|
|
192
|
+
except ImportError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
if legacy_client is None:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# Store original methods for uninstrumentation
|
|
199
|
+
original_send_message = legacy_client.send_message
|
|
200
|
+
_original_methods[f"{legacy_module_path}.A2AClient.send_message"] = (
|
|
201
|
+
original_send_message
|
|
202
|
+
)
|
|
42
203
|
|
|
43
204
|
@functools.wraps(original_send_message)
|
|
44
205
|
async def instrumented_send_message(self, request, *args, **kwargs):
|
|
@@ -90,11 +251,14 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
90
251
|
# Call through without transport-specific kwargs
|
|
91
252
|
return await original_send_message(self, request, *args, **kwargs)
|
|
92
253
|
|
|
93
|
-
|
|
254
|
+
legacy_client.send_message = instrumented_send_message
|
|
94
255
|
|
|
95
|
-
# Instrument broadcast_message
|
|
96
|
-
if hasattr(
|
|
97
|
-
original_broadcast_message =
|
|
256
|
+
# Instrument broadcast_message if it exists
|
|
257
|
+
if hasattr(legacy_client, "broadcast_message"):
|
|
258
|
+
original_broadcast_message = legacy_client.broadcast_message
|
|
259
|
+
_original_methods[f"{legacy_module_path}.A2AClient.broadcast_message"] = (
|
|
260
|
+
original_broadcast_message
|
|
261
|
+
)
|
|
98
262
|
|
|
99
263
|
@functools.wraps(original_broadcast_message)
|
|
100
264
|
async def instrumented_broadcast_message(self, request, *args, **kwargs):
|
|
@@ -146,12 +310,16 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
146
310
|
# Call through without transport-specific kwargs
|
|
147
311
|
return await original_broadcast_message(self, request, *args, **kwargs)
|
|
148
312
|
|
|
149
|
-
|
|
313
|
+
legacy_client.broadcast_message = instrumented_broadcast_message
|
|
150
314
|
|
|
151
|
-
|
|
315
|
+
def _instrument_server_handler(self):
|
|
316
|
+
"""Instrument the server-side request handlers."""
|
|
152
317
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
153
318
|
|
|
154
319
|
original_server_on_message_send = DefaultRequestHandler.on_message_send
|
|
320
|
+
_original_methods["DefaultRequestHandler.on_message_send"] = (
|
|
321
|
+
original_server_on_message_send
|
|
322
|
+
)
|
|
155
323
|
|
|
156
324
|
@functools.wraps(original_server_on_message_send)
|
|
157
325
|
async def instrumented_on_message_send(self, params, context):
|
|
@@ -190,173 +358,314 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
190
358
|
finally:
|
|
191
359
|
if token is not None:
|
|
192
360
|
try:
|
|
361
|
+
from opentelemetry import context as otel_ctx
|
|
362
|
+
|
|
193
363
|
otel_ctx.detach(token)
|
|
194
364
|
except Exception:
|
|
195
365
|
pass
|
|
196
366
|
|
|
197
367
|
DefaultRequestHandler.on_message_send = instrumented_on_message_send
|
|
198
368
|
|
|
199
|
-
#
|
|
369
|
+
# Instrument on_message_send_stream for streaming message reception
|
|
370
|
+
if hasattr(DefaultRequestHandler, "on_message_send_stream"):
|
|
371
|
+
original_on_message_send_stream = (
|
|
372
|
+
DefaultRequestHandler.on_message_send_stream
|
|
373
|
+
)
|
|
374
|
+
_original_methods["DefaultRequestHandler.on_message_send_stream"] = (
|
|
375
|
+
original_on_message_send_stream
|
|
376
|
+
)
|
|
200
377
|
|
|
201
|
-
|
|
378
|
+
@functools.wraps(original_on_message_send_stream)
|
|
379
|
+
async def instrumented_on_message_send_stream(self, params, context):
|
|
380
|
+
# Read context from A2A message metadata (transport-agnostic)
|
|
381
|
+
try:
|
|
382
|
+
metadata = getattr(params, "metadata", {}) or {}
|
|
383
|
+
except Exception:
|
|
384
|
+
metadata = {}
|
|
385
|
+
|
|
386
|
+
carrier = {}
|
|
387
|
+
observe_meta = metadata.get("observe", {}) or {}
|
|
388
|
+
for k in ("traceparent", "baggage", "session_id"):
|
|
389
|
+
if k in observe_meta:
|
|
390
|
+
carrier[k] = observe_meta[k]
|
|
391
|
+
|
|
392
|
+
token = None
|
|
393
|
+
if carrier.get("traceparent"):
|
|
394
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
395
|
+
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
396
|
+
try:
|
|
397
|
+
from opentelemetry import context as otel_ctx
|
|
202
398
|
|
|
203
|
-
|
|
399
|
+
token = otel_ctx.attach(ctx)
|
|
400
|
+
except Exception:
|
|
401
|
+
token = None
|
|
204
402
|
|
|
205
|
-
|
|
403
|
+
session_id = observe_meta.get("session_id")
|
|
404
|
+
if session_id and session_id != "None":
|
|
405
|
+
set_session_id(
|
|
406
|
+
session_id, traceparent=carrier.get("traceparent")
|
|
407
|
+
)
|
|
408
|
+
kv_store.set(
|
|
409
|
+
f"execution.{carrier.get('traceparent')}", session_id
|
|
410
|
+
)
|
|
206
411
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
412
|
+
try:
|
|
413
|
+
# This is an async generator
|
|
414
|
+
async for event in original_on_message_send_stream(
|
|
415
|
+
self, params, context
|
|
416
|
+
):
|
|
417
|
+
yield event
|
|
418
|
+
finally:
|
|
419
|
+
if token is not None:
|
|
420
|
+
try:
|
|
421
|
+
from opentelemetry import context as otel_ctx
|
|
422
|
+
|
|
423
|
+
otel_ctx.detach(token)
|
|
424
|
+
except Exception:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
DefaultRequestHandler.on_message_send_stream = (
|
|
428
|
+
instrumented_on_message_send_stream
|
|
429
|
+
)
|
|
210
430
|
|
|
211
|
-
|
|
212
|
-
|
|
431
|
+
def _instrument_slima2a(self):
|
|
432
|
+
"""Instrument slima2a transport if available."""
|
|
433
|
+
import importlib
|
|
213
434
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
self, request, *args, **kwargs
|
|
217
|
-
):
|
|
218
|
-
# Put context into A2A message metadata instead of HTTP headers
|
|
219
|
-
with _global_tracer.start_as_current_span("slima2a.send_message"):
|
|
220
|
-
traceparent = get_current_traceparent()
|
|
221
|
-
session_id = None
|
|
222
|
-
if traceparent:
|
|
223
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
224
|
-
if not session_id:
|
|
225
|
-
session_id = get_value("session.id")
|
|
226
|
-
if session_id:
|
|
227
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
435
|
+
if importlib.util.find_spec("slima2a") is None:
|
|
436
|
+
return
|
|
228
437
|
|
|
229
|
-
|
|
230
|
-
try:
|
|
231
|
-
md = getattr(request.params, "metadata", None)
|
|
232
|
-
except AttributeError:
|
|
233
|
-
md = None
|
|
234
|
-
metadata = md if isinstance(md, dict) else {}
|
|
438
|
+
from slima2a.client_transport import SRPCTransport
|
|
235
439
|
|
|
236
|
-
|
|
440
|
+
# send_message
|
|
441
|
+
original_srpc_transport_send_message = SRPCTransport.send_message
|
|
442
|
+
_original_methods["SRPCTransport.send_message"] = (
|
|
443
|
+
original_srpc_transport_send_message
|
|
444
|
+
)
|
|
237
445
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
446
|
+
@functools.wraps(original_srpc_transport_send_message)
|
|
447
|
+
async def instrumented_srpc_transport_send_message(
|
|
448
|
+
self, request, *args, **kwargs
|
|
449
|
+
):
|
|
450
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
451
|
+
with _global_tracer.start_as_current_span("slima2a.send_message"):
|
|
452
|
+
traceparent = get_current_traceparent()
|
|
453
|
+
session_id = None
|
|
454
|
+
if traceparent:
|
|
455
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
456
|
+
if not session_id:
|
|
457
|
+
session_id = get_value("session.id")
|
|
458
|
+
if session_id:
|
|
459
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
241
460
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
461
|
+
# Ensure metadata dict exists
|
|
462
|
+
try:
|
|
463
|
+
md = getattr(request.params, "metadata", None)
|
|
464
|
+
except AttributeError:
|
|
465
|
+
md = None
|
|
466
|
+
metadata = md if isinstance(md, dict) else {}
|
|
247
467
|
|
|
248
|
-
|
|
468
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
249
469
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
except Exception:
|
|
254
|
-
# Fallback
|
|
255
|
-
request = request.model_copy(update={"metadata": metadata})
|
|
470
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
471
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
472
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
256
473
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
474
|
+
if traceparent:
|
|
475
|
+
observe_meta["traceparent"] = traceparent
|
|
476
|
+
if session_id:
|
|
477
|
+
observe_meta["session_id"] = session_id
|
|
478
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
261
479
|
|
|
262
|
-
|
|
480
|
+
metadata["observe"] = observe_meta
|
|
263
481
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
482
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
483
|
+
try:
|
|
484
|
+
request.metadata = metadata
|
|
485
|
+
except Exception:
|
|
486
|
+
# Fallback
|
|
487
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
268
488
|
|
|
269
|
-
|
|
270
|
-
|
|
489
|
+
# Call through without transport-specific kwargs
|
|
490
|
+
return await original_srpc_transport_send_message(
|
|
271
491
|
self, request, *args, **kwargs
|
|
272
|
-
)
|
|
273
|
-
# Put context into A2A message metadata instead of HTTP headers
|
|
274
|
-
with _global_tracer.start_as_current_span(
|
|
275
|
-
"slima2a.send_message_streaming"
|
|
276
|
-
):
|
|
277
|
-
traceparent = get_current_traceparent()
|
|
278
|
-
session_id = None
|
|
279
|
-
if traceparent:
|
|
280
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
281
|
-
if not session_id:
|
|
282
|
-
session_id = get_value("session.id")
|
|
283
|
-
if session_id:
|
|
284
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
492
|
+
)
|
|
285
493
|
|
|
286
|
-
|
|
287
|
-
try:
|
|
288
|
-
md = getattr(request.params, "metadata", None)
|
|
289
|
-
except AttributeError:
|
|
290
|
-
md = None
|
|
291
|
-
metadata = md if isinstance(md, dict) else {}
|
|
494
|
+
SRPCTransport.send_message = instrumented_srpc_transport_send_message
|
|
292
495
|
|
|
293
|
-
|
|
496
|
+
# send_message_streaming
|
|
497
|
+
original_srpc_transport_send_message_streaming = (
|
|
498
|
+
SRPCTransport.send_message_streaming
|
|
499
|
+
)
|
|
500
|
+
_original_methods["SRPCTransport.send_message_streaming"] = (
|
|
501
|
+
original_srpc_transport_send_message_streaming
|
|
502
|
+
)
|
|
294
503
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
504
|
+
@functools.wraps(original_srpc_transport_send_message_streaming)
|
|
505
|
+
async def instrumented_srpc_transport_send_message_streaming(
|
|
506
|
+
self, request, *args, **kwargs
|
|
507
|
+
):
|
|
508
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
509
|
+
with _global_tracer.start_as_current_span("slima2a.send_message_streaming"):
|
|
510
|
+
traceparent = get_current_traceparent()
|
|
511
|
+
session_id = None
|
|
512
|
+
if traceparent:
|
|
513
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
514
|
+
if not session_id:
|
|
515
|
+
session_id = get_value("session.id")
|
|
516
|
+
if session_id:
|
|
517
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
298
518
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
519
|
+
# Ensure metadata dict exists
|
|
520
|
+
try:
|
|
521
|
+
md = getattr(request.params, "metadata", None)
|
|
522
|
+
except AttributeError:
|
|
523
|
+
md = None
|
|
524
|
+
metadata = md if isinstance(md, dict) else {}
|
|
304
525
|
|
|
305
|
-
|
|
526
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
306
527
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
except Exception:
|
|
311
|
-
# Fallback
|
|
312
|
-
request = request.model_copy(update={"metadata": metadata})
|
|
528
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
529
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
530
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
313
531
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
532
|
+
if traceparent:
|
|
533
|
+
observe_meta["traceparent"] = traceparent
|
|
534
|
+
if session_id:
|
|
535
|
+
observe_meta["session_id"] = session_id
|
|
536
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
318
537
|
|
|
319
|
-
|
|
320
|
-
|
|
538
|
+
metadata["observe"] = observe_meta
|
|
539
|
+
|
|
540
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
541
|
+
try:
|
|
542
|
+
request.metadata = metadata
|
|
543
|
+
except Exception:
|
|
544
|
+
# Fallback
|
|
545
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
546
|
+
|
|
547
|
+
# Call through without transport-specific kwargs
|
|
548
|
+
return await original_srpc_transport_send_message_streaming(
|
|
549
|
+
self, request, *args, **kwargs
|
|
321
550
|
)
|
|
322
551
|
|
|
552
|
+
SRPCTransport.send_message_streaming = (
|
|
553
|
+
instrumented_srpc_transport_send_message_streaming
|
|
554
|
+
)
|
|
555
|
+
|
|
323
556
|
def _uninstrument(self, **kwargs):
|
|
324
557
|
import importlib
|
|
325
558
|
|
|
326
559
|
if importlib.util.find_spec("a2a") is None:
|
|
327
560
|
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
328
561
|
|
|
329
|
-
# Uninstrument
|
|
330
|
-
|
|
562
|
+
# Uninstrument BaseA2AClient (new pattern v0.3.0+)
|
|
563
|
+
if importlib.util.find_spec("a2a.client.base_client") is not None:
|
|
564
|
+
try:
|
|
565
|
+
from a2a.client.base_client import A2AClient as BaseA2AClient
|
|
566
|
+
|
|
567
|
+
if "BaseA2AClient.send_message" in _original_methods:
|
|
568
|
+
BaseA2AClient.send_message = _original_methods[
|
|
569
|
+
"BaseA2AClient.send_message"
|
|
570
|
+
]
|
|
571
|
+
elif hasattr(BaseA2AClient.send_message, "__wrapped__"):
|
|
572
|
+
BaseA2AClient.send_message = BaseA2AClient.send_message.__wrapped__
|
|
573
|
+
|
|
574
|
+
if "BaseA2AClient.send_message_streaming" in _original_methods:
|
|
575
|
+
BaseA2AClient.send_message_streaming = _original_methods[
|
|
576
|
+
"BaseA2AClient.send_message_streaming"
|
|
577
|
+
]
|
|
578
|
+
elif hasattr(BaseA2AClient, "send_message_streaming") and hasattr(
|
|
579
|
+
BaseA2AClient.send_message_streaming, "__wrapped__"
|
|
580
|
+
):
|
|
581
|
+
BaseA2AClient.send_message_streaming = (
|
|
582
|
+
BaseA2AClient.send_message_streaming.__wrapped__
|
|
583
|
+
)
|
|
584
|
+
except ImportError:
|
|
585
|
+
pass
|
|
331
586
|
|
|
332
|
-
|
|
587
|
+
# Uninstrument legacy A2AClient
|
|
588
|
+
legacy_client = None
|
|
589
|
+
legacy_module_path = None
|
|
333
590
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
591
|
+
try:
|
|
592
|
+
from a2a.client.legacy import A2AClient as LegacyA2AClient
|
|
593
|
+
|
|
594
|
+
legacy_client = LegacyA2AClient
|
|
595
|
+
legacy_module_path = "a2a.client.legacy"
|
|
596
|
+
except ImportError:
|
|
597
|
+
try:
|
|
598
|
+
from a2a.client import A2AClient as LegacyA2AClient
|
|
599
|
+
|
|
600
|
+
if hasattr(LegacyA2AClient, "send_message"):
|
|
601
|
+
legacy_client = LegacyA2AClient
|
|
602
|
+
legacy_module_path = "a2a.client"
|
|
603
|
+
except ImportError:
|
|
604
|
+
pass
|
|
605
|
+
|
|
606
|
+
if legacy_client is not None:
|
|
607
|
+
key = f"{legacy_module_path}.A2AClient.send_message"
|
|
608
|
+
if key in _original_methods:
|
|
609
|
+
legacy_client.send_message = _original_methods[key]
|
|
610
|
+
elif hasattr(legacy_client.send_message, "__wrapped__"):
|
|
611
|
+
legacy_client.send_message = legacy_client.send_message.__wrapped__
|
|
612
|
+
|
|
613
|
+
broadcast_key = f"{legacy_module_path}.A2AClient.broadcast_message"
|
|
614
|
+
if broadcast_key in _original_methods:
|
|
615
|
+
legacy_client.broadcast_message = _original_methods[broadcast_key]
|
|
616
|
+
elif hasattr(legacy_client, "broadcast_message") and hasattr(
|
|
617
|
+
legacy_client.broadcast_message, "__wrapped__"
|
|
618
|
+
):
|
|
619
|
+
legacy_client.broadcast_message = (
|
|
620
|
+
legacy_client.broadcast_message.__wrapped__
|
|
621
|
+
)
|
|
339
622
|
|
|
340
623
|
# Uninstrument server handler
|
|
341
624
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
342
625
|
|
|
343
|
-
DefaultRequestHandler.on_message_send
|
|
344
|
-
DefaultRequestHandler.on_message_send
|
|
345
|
-
|
|
626
|
+
if "DefaultRequestHandler.on_message_send" in _original_methods:
|
|
627
|
+
DefaultRequestHandler.on_message_send = _original_methods[
|
|
628
|
+
"DefaultRequestHandler.on_message_send"
|
|
629
|
+
]
|
|
630
|
+
elif hasattr(DefaultRequestHandler.on_message_send, "__wrapped__"):
|
|
631
|
+
DefaultRequestHandler.on_message_send = (
|
|
632
|
+
DefaultRequestHandler.on_message_send.__wrapped__
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
if "DefaultRequestHandler.on_message_send_stream" in _original_methods:
|
|
636
|
+
DefaultRequestHandler.on_message_send_stream = _original_methods[
|
|
637
|
+
"DefaultRequestHandler.on_message_send_stream"
|
|
638
|
+
]
|
|
639
|
+
elif hasattr(DefaultRequestHandler, "on_message_send_stream") and hasattr(
|
|
640
|
+
DefaultRequestHandler.on_message_send_stream, "__wrapped__"
|
|
641
|
+
):
|
|
642
|
+
DefaultRequestHandler.on_message_send_stream = (
|
|
643
|
+
DefaultRequestHandler.on_message_send_stream.__wrapped__
|
|
644
|
+
)
|
|
346
645
|
|
|
347
|
-
#
|
|
646
|
+
# Uninstrument slima2a
|
|
348
647
|
if importlib.util.find_spec("slima2a"):
|
|
349
648
|
from slima2a.client_transport import SRPCTransport
|
|
350
649
|
|
|
351
|
-
|
|
352
|
-
|
|
650
|
+
if "SRPCTransport.send_message" in _original_methods:
|
|
651
|
+
SRPCTransport.send_message = _original_methods[
|
|
652
|
+
"SRPCTransport.send_message"
|
|
653
|
+
]
|
|
654
|
+
elif hasattr(SRPCTransport, "send_message") and hasattr(
|
|
353
655
|
SRPCTransport.send_message, "__wrapped__"
|
|
354
656
|
):
|
|
355
657
|
SRPCTransport.send_message = SRPCTransport.send_message.__wrapped__
|
|
356
658
|
|
|
357
|
-
if
|
|
659
|
+
if "SRPCTransport.send_message_streaming" in _original_methods:
|
|
660
|
+
SRPCTransport.send_message_streaming = _original_methods[
|
|
661
|
+
"SRPCTransport.send_message_streaming"
|
|
662
|
+
]
|
|
663
|
+
elif hasattr(SRPCTransport, "send_message_streaming") and hasattr(
|
|
358
664
|
SRPCTransport.send_message_streaming, "__wrapped__"
|
|
359
665
|
):
|
|
360
666
|
SRPCTransport.send_message_streaming = (
|
|
361
|
-
SRPCTransport.
|
|
667
|
+
SRPCTransport.send_message_streaming.__wrapped__
|
|
362
668
|
)
|
|
669
|
+
|
|
670
|
+
# Clear stored original methods
|
|
671
|
+
_original_methods.clear()
|