ioa-observe-sdk 1.0.14__py3-none-any.whl → 1.0.16__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.
@@ -89,7 +89,7 @@ class Observe:
89
89
 
90
90
  if (
91
91
  observe_sync_enabled
92
- and api_endpoint.find("observe.com") != -1
92
+ and api_endpoint.find("agntcy-observe.com") != -1
93
93
  and api_key
94
94
  and (exporter is None)
95
95
  and (processor is None)
@@ -112,13 +112,13 @@ class Observe:
112
112
  if (
113
113
  not exporter
114
114
  and not processor
115
- and api_endpoint == "https://api.observe.com"
115
+ and api_endpoint == "https://api.agntcy-observe.com"
116
116
  and not api_key
117
117
  ):
118
118
  print(
119
119
  Fore.RED
120
120
  + "Error: Missing observe API key,"
121
- + " go to https://app.observe.com/settings/api-keys to create one"
121
+ + " go to https://app.agntcy-observe.com/settings/api-keys to create one"
122
122
  )
123
123
  print("Set the OBSERVE_API_KEY environment variable to the key")
124
124
  print(Fore.RESET)
@@ -27,7 +27,7 @@ class Client:
27
27
  self,
28
28
  api_key: str,
29
29
  app_name: str = sys.argv[0],
30
- api_endpoint: str = "https://api.observe.com",
30
+ api_endpoint: str = "https://api.agntcy-observe.com",
31
31
  ):
32
32
  """
33
33
  Initialize a new observe client.
@@ -35,13 +35,13 @@ class Client:
35
35
  Args:
36
36
  api_key (str): Your observe API key
37
37
  app_name (Optional[str], optional): The name of your application. Defaults to sys.argv[0].
38
- api_endpoint (Optional[str], optional): Custom API endpoint. Defaults to https://api.observe.com.
38
+ api_endpoint (Optional[str], optional): Custom API endpoint. Defaults to https://api.agntcy-observe.com.
39
39
  """
40
40
  if not api_key or not api_key.strip():
41
41
  raise ValueError("API key is required")
42
42
 
43
43
  self.app_name = app_name
44
- self.api_endpoint = api_endpoint or "https://api.observe.com"
44
+ self.api_endpoint = api_endpoint or "https://api.agntcy-observe.com"
45
45
  self.api_key = api_key
46
46
  self._http = HTTPClient(
47
47
  base_url=self.api_endpoint, api_key=self.api_key, version=__version__
@@ -1,7 +1,7 @@
1
1
  # Copyright AGNTCY Contributors (https://github.com/agntcy)
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  import inspect
4
- from typing import Optional, Union, TypeVar, Callable, Awaitable
4
+ from typing import Optional, Union, TypeVar, Callable, Any
5
5
 
6
6
  from typing_extensions import ParamSpec
7
7
 
@@ -14,7 +14,7 @@ from ioa_observe.sdk.utils.const import ObserveSpanKindValues
14
14
 
15
15
  P = ParamSpec("P")
16
16
  R = TypeVar("R")
17
- F = TypeVar("F", bound=Callable[P, Union[R, Awaitable[R]]])
17
+ F = TypeVar("F", bound=Callable[..., Any])
18
18
 
19
19
 
20
20
  def task(
@@ -7,7 +7,7 @@ import traceback
7
7
  from functools import wraps
8
8
  import os
9
9
  import types
10
- from typing import Optional, TypeVar, Callable, Awaitable, Any, Union
10
+ from typing import Optional, TypeVar, Callable, Any
11
11
  import inspect
12
12
 
13
13
  from ioa_observe.sdk.decorators.helpers import (
@@ -53,7 +53,7 @@ from ioa_observe.sdk.metrics.agent import topology_dynamism, determinism_score
53
53
  P = ParamSpec("P")
54
54
 
55
55
  R = TypeVar("R")
56
- F = TypeVar("F", bound=Callable[P, Union[R, Awaitable[R]]])
56
+ F = TypeVar("F", bound=Callable[..., Any])
57
57
 
58
58
 
59
59
  def _is_json_size_valid(json_str: str) -> bool:
@@ -14,7 +14,7 @@ from ioa_observe.sdk import TracerWrapper
14
14
  from ioa_observe.sdk.client import kv_store
15
15
  from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
16
16
 
17
- _instruments = ("a2a-sdk >= 0.2.5",)
17
+ _instruments = ("a2a-sdk >= 0.3.0",)
18
18
  _global_tracer = None
19
19
  _kv_lock = threading.RLock() # Add thread-safety for kv_store operations
20
20
 
@@ -34,15 +34,14 @@ class A2AInstrumentor(BaseInstrumentor):
34
34
  if importlib.util.find_spec("a2a") is None:
35
35
  raise ImportError("No module named 'a2a-sdk'. Please install it first.")
36
36
 
37
- # Instrument `publish`
37
+ # Instrument client send_message
38
38
  from a2a.client import A2AClient
39
39
 
40
40
  original_send_message = A2AClient.send_message
41
41
 
42
42
  @functools.wraps(original_send_message)
43
- async def instrumented_send_message(
44
- self, request, *, http_kwargs=None, context=None
45
- ):
43
+ async def instrumented_send_message(self, request, *args, **kwargs):
44
+ # Put context into A2A message metadata instead of HTTP headers
46
45
  with _global_tracer.start_as_current_span("a2a.send_message"):
47
46
  traceparent = get_current_traceparent()
48
47
  session_id = None
@@ -50,47 +49,101 @@ class A2AInstrumentor(BaseInstrumentor):
50
49
  session_id = kv_store.get(f"execution.{traceparent}")
51
50
  if session_id:
52
51
  kv_store.set(f"execution.{traceparent}", session_id)
53
- # Inject headers into http_kwargs
54
- if http_kwargs is None:
55
- http_kwargs = {}
56
- headers = http_kwargs.get("headers", {})
57
- headers["traceparent"] = traceparent
52
+
53
+ # Ensure metadata dict exists
54
+ try:
55
+ md = getattr(request.params, "metadata", None)
56
+ except AttributeError:
57
+ md = None
58
+ metadata = md if isinstance(md, dict) else {}
59
+
60
+ observe_meta = dict(metadata.get("observe", {}))
61
+
62
+ # Inject W3C trace context + baggage into observe_meta
63
+ TraceContextTextMapPropagator().inject(carrier=observe_meta)
64
+ W3CBaggagePropagator().inject(carrier=observe_meta)
65
+
66
+ if traceparent:
67
+ observe_meta["traceparent"] = traceparent
58
68
  if session_id:
59
- headers["session_id"] = session_id
69
+ observe_meta["session_id"] = session_id
60
70
  baggage.set_baggage(f"execution.{traceparent}", session_id)
61
- http_kwargs["headers"] = headers
62
- return await original_send_message(self, request, http_kwargs=http_kwargs)
63
71
 
64
- from a2a.client import A2AClient
72
+ metadata["observe"] = observe_meta
73
+
74
+ # Write back metadata (pydantic models are mutable by default in v2)
75
+ try:
76
+ request.params.metadata = metadata
77
+ except Exception:
78
+ # Fallback
79
+ request = request.model_copy(
80
+ update={
81
+ "params": request.params.model_copy(
82
+ update={"metadata": metadata}
83
+ )
84
+ }
85
+ )
86
+
87
+ # Call through without transport-specific kwargs
88
+ return await original_send_message(self, request, *args, **kwargs)
65
89
 
66
90
  A2AClient.send_message = instrumented_send_message
67
91
 
92
+ # Instrument server handler
68
93
  from a2a.server.request_handlers import DefaultRequestHandler
69
94
 
70
95
  original_server_on_message_send = DefaultRequestHandler.on_message_send
71
96
 
72
97
  @functools.wraps(original_server_on_message_send)
73
- async def instrumented_execute(self, params, context):
74
- # Extract headers from context (assume context.request.headers)
75
-
76
- traceparent = context.state.get("headers", {}).get("traceparent")
77
- session_id = context.state.get("headers", {}).get("session_id")
78
- carrier = {
79
- k.lower(): v
80
- for k, v in context.state.get("headers", {}).items()
81
- if k.lower() in ["traceparent", "baggage"]
82
- }
83
- if carrier and traceparent:
98
+ async def instrumented_on_message_send(self, params, context):
99
+ # Read context from A2A message metadata (transport-agnostic)
100
+ try:
101
+ metadata = getattr(params, "metadata", {}) or {}
102
+ except Exception:
103
+ metadata = {}
104
+
105
+ carrier = {}
106
+ observe_meta = metadata.get("observe", {}) or {}
107
+ # Accept keys we inject
108
+ for k in ("traceparent", "baggage", "session_id"):
109
+ if k in observe_meta:
110
+ carrier[k] = observe_meta[k]
111
+
112
+ token = None
113
+ if carrier.get("traceparent"):
114
+ # Extract and attach parent context
84
115
  ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
85
116
  ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
117
+ try:
118
+ from opentelemetry import context as otel_ctx
119
+
120
+ token = otel_ctx.attach(ctx)
121
+ except Exception:
122
+ token = None
123
+
124
+ session_id = observe_meta.get("session_id")
86
125
  if session_id and session_id != "None":
87
- set_session_id(session_id, traceparent=traceparent)
88
- kv_store.set(f"execution.{traceparent}", session_id)
89
- return await original_server_on_message_send(self, params, context)
126
+ set_session_id(session_id, traceparent=carrier.get("traceparent"))
127
+ kv_store.set(f"execution.{carrier.get('traceparent')}", session_id)
90
128
 
91
- from a2a.server.request_handlers import DefaultRequestHandler
129
+ try:
130
+ return await original_server_on_message_send(self, params, context)
131
+ finally:
132
+ if token is not None:
133
+ try:
134
+ otel_ctx.detach(token)
135
+ except Exception:
136
+ pass
137
+
138
+ DefaultRequestHandler.on_message_send = instrumented_on_message_send
139
+
140
+ # from a2a.client import A2AClient
141
+
142
+ # A2AClient.send_message = instrumented_send_message
143
+
144
+ # from a2a.server.request_handlers import DefaultRequestHandler
92
145
 
93
- DefaultRequestHandler.on_message_send = instrumented_execute
146
+ # original_server_on_message_send = DefaultRequestHandler.on_message_send
94
147
 
95
148
  def _uninstrument(self, **kwargs):
96
149
  import importlib
@@ -103,7 +156,7 @@ class A2AInstrumentor(BaseInstrumentor):
103
156
 
104
157
  A2AClient.send_message = A2AClient.send_message.__wrapped__
105
158
 
106
- # Uninstrument `execute`
159
+ # Uninstrument server handler
107
160
  from a2a.server.request_handlers import DefaultRequestHandler
108
161
 
109
162
  DefaultRequestHandler.on_message_send = (