Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sap-cloud-sdk"
version = "0.56.0"
version = "0.56.1"
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
4 changes: 4 additions & 0 deletions src/sap_cloud_sdk/core/telemetry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
ATTR_EXTENSION_URL,
ATTR_SOLUTION_ID,
ATTR_JOULE_STUDIO_GSID,
ATTR_AGENT_EXT_VERSION,
ATTR_SUMMARY_TOTAL_OPERATION_COUNT,
ATTR_SUMMARY_TOTAL_DURATION_MS,
ATTR_SUMMARY_TOOL_CALL_COUNT,
Expand All @@ -46,6 +47,7 @@
ATTR_SUMMARY_JOULE_STUDIO_GSID,
ATTR_SUMMARY_IS_EXTENSION,
ATTR_SUMMARY_SOLUTION_ID,
ATTR_SUMMARY_AGENT_EXT_VERSION,
resolve_source_info,
build_extension_span_attributes,
reset_tool_call_metrics,
Expand Down Expand Up @@ -94,6 +96,7 @@
"ATTR_EXTENSION_URL",
"ATTR_SOLUTION_ID",
"ATTR_JOULE_STUDIO_GSID",
"ATTR_AGENT_EXT_VERSION",
"ATTR_SUMMARY_TOTAL_OPERATION_COUNT",
"ATTR_SUMMARY_TOTAL_DURATION_MS",
"ATTR_SUMMARY_TOOL_CALL_COUNT",
Expand All @@ -102,6 +105,7 @@
"ATTR_SUMMARY_JOULE_STUDIO_GSID",
"ATTR_SUMMARY_IS_EXTENSION",
"ATTR_SUMMARY_SOLUTION_ID",
"ATTR_SUMMARY_AGENT_EXT_VERSION",
"resolve_source_info",
"build_extension_span_attributes",
"reset_tool_call_metrics",
Expand Down
40 changes: 36 additions & 4 deletions src/sap_cloud_sdk/core/telemetry/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
ATTR_EXTENSION_URL = "sap.extension.extensionUrl"
ATTR_SOLUTION_ID = "sap.extension.solution_id"
ATTR_JOULE_STUDIO_GSID = "sap.extension.joule_studio_gsid"
ATTR_AGENT_EXT_VERSION = "sap.extension.agentExtVersion"


class ExtensionType(str, Enum):
Expand Down Expand Up @@ -74,6 +75,7 @@ def extension_context(
extension_url: str = "",
solution_id: str = "",
joule_studio_gsid: str = "",
agent_ext_version: str = "",
) -> Generator[None, None, None]:
"""Set extension context in OTel baggage for propagation.

Expand All @@ -93,6 +95,7 @@ def extension_context(
- ``sap.extension.extensionUrl``: The extension URL (when provided)
- ``sap.extension.solution_id``: The solution ID (when provided)
- ``sap.extension.joule_studio_gsid``: The global solution ID of Joule Studio (when provided)
- ``sap.extension.agentExtVersion``: The agent extension version counter (when provided)

Args:
capability_id: The capability ID for the extension
Expand All @@ -106,6 +109,9 @@ def extension_context(
extension_url: The build extension URL (empty string if not available).
solution_id: The solution ID (empty string if not available).
joule_studio_gsid: The global solution ID of Joule Studio (empty string if not available).
agent_ext_version: The agent extension version counter from UMS
``AgentExtensionMetadata`` (e.g. ``"5"``). Empty string if not
available; the baggage key is omitted in that case.

Yields:
None. The context is active for the duration of the with block.
Expand All @@ -126,6 +132,7 @@ def extension_context(
item_name="create_ticket",
solution_id="my-solution-42",
joule_studio_gsid="gsid-value",
agent_ext_version="5",
):
result = await mcp_client.call_tool("create_ticket", args)
```
Expand All @@ -150,6 +157,10 @@ def extension_context(
ctx = baggage.set_baggage(
ATTR_JOULE_STUDIO_GSID, joule_studio_gsid, context=ctx
)
if agent_ext_version:
ctx = baggage.set_baggage(
ATTR_AGENT_EXT_VERSION, agent_ext_version, context=ctx
)

token = attach(ctx)
try:
Expand Down Expand Up @@ -178,6 +189,7 @@ def get_extension_context() -> dict[str, Any] | None:
- ``extension_url``: The extension URL (empty string if not set)
- ``solution_id``: The solution ID (empty string if not set)
- ``joule_studio_gsid``: The global solution ID of Joule Studio (empty string if not set)
- ``agent_ext_version``: The agent extension version counter (empty string if not set)

Returns ``None`` if not in an extension context.

Expand Down Expand Up @@ -205,6 +217,7 @@ def get_extension_context() -> dict[str, Any] | None:
"extension_url": baggage.get_baggage(ATTR_EXTENSION_URL) or "",
"solution_id": baggage.get_baggage(ATTR_SOLUTION_ID) or "",
"joule_studio_gsid": baggage.get_baggage(ATTR_JOULE_STUDIO_GSID) or "",
"agent_ext_version": baggage.get_baggage(ATTR_AGENT_EXT_VERSION) or "",
}


Expand All @@ -220,6 +233,7 @@ def get_extension_context() -> dict[str, Any] | None:
ATTR_SUMMARY_JOULE_STUDIO_GSID = "sap.extension.joule_studio_gsid"
ATTR_SUMMARY_IS_EXTENSION = "sap.extension.isExtension"
ATTR_SUMMARY_SOLUTION_ID = "sap.extension.solutionId"
ATTR_SUMMARY_AGENT_EXT_VERSION = "sap.extension.agentExtVersion"

# ---------------------------------------------------------------------------
# Private state
Expand All @@ -242,6 +256,7 @@ def get_extension_context() -> dict[str, Any] | None:
(ATTR_EXTENSION_URL, "ext_extension_url"),
(ATTR_SOLUTION_ID, "ext_solution_id"),
(ATTR_JOULE_STUDIO_GSID, "ext_joule_studio_gsid"),
(ATTR_AGENT_EXT_VERSION, "ext_agent_ext_version"),
]


Expand Down Expand Up @@ -318,6 +333,7 @@ def build_extension_span_attributes(
extension_url: str = "",
solution_id: str = "",
joule_studio_gsid: str = "",
agent_ext_version: str = "",
) -> dict[str, Any]:
"""Build the full set of ``sap.extension.*`` span attributes.

Expand All @@ -331,6 +347,7 @@ def build_extension_span_attributes(
extension_url: Build extension URL (empty string if not available).
solution_id: Solution ID (empty string if not available).
joule_studio_gsid: Global solution ID (empty string if not available).
agent_ext_version: Agent extension version from UMS (empty string if not available).

Returns:
Dict with all ``sap.extension.*`` attribute keys.
Expand All @@ -350,6 +367,8 @@ def build_extension_span_attributes(
attrs[ATTR_SOLUTION_ID] = solution_id
if joule_studio_gsid:
attrs[ATTR_JOULE_STUDIO_GSID] = joule_studio_gsid
if agent_ext_version:
attrs[ATTR_AGENT_EXT_VERSION] = agent_ext_version
return attrs


Expand Down Expand Up @@ -440,13 +459,13 @@ async def call_extension_tool(
args: dict[str, Any],
capability: str = "default",
source_mapping: dict[str, Any] | None = None,
agent_ext_version: str = "",
) -> Any:
"""Call an MCP tool with telemetry instrumentation.

Wraps the tool call with ``extension_context`` (sets OTel baggage for
downstream propagation) and creates an explicit tracer span with all
seven ``sap.extension.*`` attributes so the call is visible in
agent-side traces.
``sap.extension.*`` attributes so the call is visible in agent-side traces.

Args:
mcp_client: The MCP client session connected to the tool's server.
Expand All @@ -461,6 +480,8 @@ async def call_extension_tool(
objects (from ``ext_impl.source.tools``). Keys must match the
*tool_name* values passed to this function.
See :class:`~sap_cloud_sdk.extensibility.ExtensionSourceMapping`.
agent_ext_version: Agent extension version from UMS AgentExtensionMetadata
(pass ``ext_impl.agent_ext_version``; empty string if not available).

Returns:
The tool's response from the MCP server.
Expand All @@ -487,6 +508,7 @@ async def call_extension_tool(
extension_url=resolved_url,
solution_id=resolved_solution_id,
joule_studio_gsid=resolved_joule_studio_gsid,
agent_ext_version=agent_ext_version,
)

t0 = time.monotonic()
Expand All @@ -502,6 +524,7 @@ async def call_extension_tool(
extension_url=resolved_url,
solution_id=resolved_solution_id,
joule_studio_gsid=resolved_joule_studio_gsid,
agent_ext_version=agent_ext_version,
),
_tracer.start_as_current_span(
f"extension_tool {tool_name}",
Expand Down Expand Up @@ -529,13 +552,13 @@ async def call_extension_hook(
capability: str = "default",
source_mapping: dict[str, Any] | None = None,
hook_id: str = "",
agent_ext_version: str = "",
) -> Any:
"""Call an extension hook with telemetry instrumentation.

Wraps the hook call with ``extension_context`` (sets OTel baggage for
downstream propagation) and creates an explicit tracer span with all
seven ``sap.extension.*`` attributes so the call is visible in
agent-side traces.
``sap.extension.*`` attributes so the call is visible in agent-side traces.

Args:
extensibility_client: The extensibility client. Must have an async
Expand All @@ -550,6 +573,8 @@ async def call_extension_hook(
objects (from ``ext_impl.source.hooks``).
hook_id: The unique hook ``id`` (UUID), used as lookup key in
*source_mapping*.
agent_ext_version: Agent extension version from UMS AgentExtensionMetadata
(pass ``ext_impl.agent_ext_version``; empty string if not available).

Returns:
The hook's response.
Expand All @@ -575,6 +600,7 @@ async def call_extension_hook(
extension_url=resolved_url,
solution_id=resolved_solution_id,
joule_studio_gsid=resolved_joule_studio_gsid,
agent_ext_version=agent_ext_version,
)

t0 = time.monotonic()
Expand All @@ -590,6 +616,7 @@ async def call_extension_hook(
extension_url=resolved_url,
solution_id=resolved_solution_id,
joule_studio_gsid=resolved_joule_studio_gsid,
agent_ext_version=agent_ext_version,
),
_tracer.start_as_current_span(
f"extension_hook {item_name}",
Expand Down Expand Up @@ -617,6 +644,7 @@ def emit_extensions_summary_span(
total_duration_ms: float,
joule_studio_gsid: str = "",
solution_id: str = "",
agent_ext_version: str = "",
) -> None:
"""Emit a sibling summary span with aggregate extension metrics.

Expand All @@ -642,6 +670,8 @@ def emit_extensions_summary_span(
if not available).
solution_id: Solution ID of the contributing extension (empty string
if not available).
agent_ext_version: Agent extension version from UMS AgentExtensionMetadata
(empty string if not available).
"""
total = tool_call_count + hook_call_count + (1 if has_instruction else 0)
attrs = {
Expand All @@ -656,6 +686,8 @@ def emit_extensions_summary_span(
attrs[ATTR_SUMMARY_JOULE_STUDIO_GSID] = joule_studio_gsid
if solution_id:
attrs[ATTR_SUMMARY_SOLUTION_ID] = solution_id
if agent_ext_version:
attrs[ATTR_SUMMARY_AGENT_EXT_VERSION] = agent_ext_version
span = _tracer.start_span("agent_extensions_summary", attributes=attrs)
span.end()

Expand Down
4 changes: 4 additions & 0 deletions src/sap_cloud_sdk/extensibility/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,9 @@ class ExtensionCapabilityImplementation:
joule_studio_gsid: Global solution ID of Joule Studio. Set when a
single Joule Studio extension contributes to this capability;
empty string otherwise.
agent_ext_version: The agent's current extension version counter
(e.g., ``"5"``). ``None`` when no extensions are deployed or
``AgentExtensionMetadata`` is not yet populated in UMS.
"""

capability_id: str
Expand All @@ -668,6 +671,7 @@ class ExtensionCapabilityImplementation:
hooks: List[Hook] = field(default_factory=list)
source: Optional[ExtensionSourceMapping] = None
joule_studio_gsid: str = ""
agent_ext_version: Optional[str] = None

@classmethod
def from_dict(cls, obj: Dict[str, Any]) -> ExtensionCapabilityImplementation:
Expand Down
Loading
Loading