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.
@@ -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 send_message
39
- from a2a.client import A2AClient
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
- original_send_message = A2AClient.send_message
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
- A2AClient.send_message = instrumented_send_message
254
+ legacy_client.send_message = instrumented_send_message
94
255
 
95
- # Instrument broadcast_message
96
- if hasattr(A2AClient, "broadcast_message"):
97
- original_broadcast_message = A2AClient.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
- A2AClient.broadcast_message = instrumented_broadcast_message
313
+ legacy_client.broadcast_message = instrumented_broadcast_message
150
314
 
151
- # Instrument server handler
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
- # from a2a.client import A2AClient
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
- # A2AClient.send_message = instrumented_send_message
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
- # from a2a.server.request_handlers import DefaultRequestHandler
399
+ token = otel_ctx.attach(ctx)
400
+ except Exception:
401
+ token = None
204
402
 
205
- # original_server_on_message_send = DefaultRequestHandler.on_message_send
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
- # Instrumentation for slima2a
208
- if importlib.util.find_spec("slima2a"):
209
- from slima2a.client_transport import SRPCTransport
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
- # send_message
212
- original_srpc_transport_send_message = SRPCTransport.send_message
431
+ def _instrument_slima2a(self):
432
+ """Instrument slima2a transport if available."""
433
+ import importlib
213
434
 
214
- @functools.wraps(original_srpc_transport_send_message)
215
- async def instrumented_srpc_transport_send_message(
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
- # Ensure metadata dict exists
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
- observe_meta = dict(metadata.get("observe", {}))
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
- # Inject W3C trace context + baggage into observe_meta
239
- TraceContextTextMapPropagator().inject(carrier=observe_meta)
240
- W3CBaggagePropagator().inject(carrier=observe_meta)
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
- if traceparent:
243
- observe_meta["traceparent"] = traceparent
244
- if session_id:
245
- observe_meta["session_id"] = session_id
246
- baggage.set_baggage(f"execution.{traceparent}", session_id)
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
- metadata["observe"] = observe_meta
468
+ observe_meta = dict(metadata.get("observe", {}))
249
469
 
250
- # Write back metadata (pydantic models are mutable by default in v2)
251
- try:
252
- request.metadata = metadata
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
- # Call through without transport-specific kwargs
258
- return await original_srpc_transport_send_message(
259
- self, request, *args, **kwargs
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
- SRPCTransport.send_message = instrumented_srpc_transport_send_message
480
+ metadata["observe"] = observe_meta
263
481
 
264
- # send_message_streaming
265
- original_srpc_transport_send_message_streaming = (
266
- SRPCTransport.send_message_streaming
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
- @functools.wraps(original_srpc_transport_send_message_streaming)
270
- async def instrumented_srpc_transport_send_message_streaming(
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
- # Ensure metadata dict exists
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
- observe_meta = dict(metadata.get("observe", {}))
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
- # Inject W3C trace context + baggage into observe_meta
296
- TraceContextTextMapPropagator().inject(carrier=observe_meta)
297
- W3CBaggagePropagator().inject(carrier=observe_meta)
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
- if traceparent:
300
- observe_meta["traceparent"] = traceparent
301
- if session_id:
302
- observe_meta["session_id"] = session_id
303
- baggage.set_baggage(f"execution.{traceparent}", session_id)
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
- metadata["observe"] = observe_meta
526
+ observe_meta = dict(metadata.get("observe", {}))
306
527
 
307
- # Write back metadata (pydantic models are mutable by default in v2)
308
- try:
309
- request.metadata = metadata
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
- # Call through without transport-specific kwargs
315
- return await original_srpc_transport_send_message_streaming(
316
- self, request, *args, **kwargs
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
- SRPCTransport.send_message_streaming = (
320
- instrumented_srpc_transport_send_message_streaming
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 `send_message`
330
- from a2a.client import A2AClient
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
- A2AClient.send_message = A2AClient.send_message.__wrapped__
587
+ # Uninstrument legacy A2AClient
588
+ legacy_client = None
589
+ legacy_module_path = None
333
590
 
334
- # Uninstrument `broadcast_message`
335
- if hasattr(A2AClient, "broadcast_message") and hasattr(
336
- A2AClient.broadcast_message, "__wrapped__"
337
- ):
338
- A2AClient.broadcast_message = A2AClient.broadcast_message.__wrapped__
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.__wrapped__
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
- # handle slima2a
646
+ # Uninstrument slima2a
348
647
  if importlib.util.find_spec("slima2a"):
349
648
  from slima2a.client_transport import SRPCTransport
350
649
 
351
- # Uninstrument `send_message`
352
- if hasattr(SRPCTransport, "send_message") and hasattr(
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 hasattr(SRPCTransport, "send_message_streaming") and hasattr(
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.send_message.__wrapped__
667
+ SRPCTransport.send_message_streaming.__wrapped__
362
668
  )
669
+
670
+ # Clear stored original methods
671
+ _original_methods.clear()