From 917a3f0b8db0878758c8cff89e0122960dd09574 Mon Sep 17 00:00:00 2001 From: Rishi Kunnath Date: Tue, 15 Sep 2026 14:21:43 +0530 Subject: [PATCH 1/3] adding gsid to summary span --- src/sap_cloud_sdk/core/telemetry/__init__.py | 2 ++ .../core/telemetry/extensions.py | 6 ++++ src/sap_cloud_sdk/extensibility/_models.py | 4 +++ .../extensibility/_ums_transport.py | 6 ++++ tests/core/unit/telemetry/test_extensions.py | 32 +++++++++++++++++++ 5 files changed, 50 insertions(+) diff --git a/src/sap_cloud_sdk/core/telemetry/__init__.py b/src/sap_cloud_sdk/core/telemetry/__init__.py index b1d616e4..ad8a88e6 100644 --- a/src/sap_cloud_sdk/core/telemetry/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/__init__.py @@ -43,6 +43,7 @@ ATTR_SUMMARY_TOOL_CALL_COUNT, ATTR_SUMMARY_HOOK_CALL_COUNT, ATTR_SUMMARY_HAS_INSTRUCTION, + ATTR_SUMMARY_JOULE_STUDIO_GSID, resolve_source_info, build_extension_span_attributes, reset_tool_call_metrics, @@ -96,6 +97,7 @@ "ATTR_SUMMARY_TOOL_CALL_COUNT", "ATTR_SUMMARY_HOOK_CALL_COUNT", "ATTR_SUMMARY_HAS_INSTRUCTION", + "ATTR_SUMMARY_JOULE_STUDIO_GSID", "resolve_source_info", "build_extension_span_attributes", "reset_tool_call_metrics", diff --git a/src/sap_cloud_sdk/core/telemetry/extensions.py b/src/sap_cloud_sdk/core/telemetry/extensions.py index e07737d4..b6254a36 100644 --- a/src/sap_cloud_sdk/core/telemetry/extensions.py +++ b/src/sap_cloud_sdk/core/telemetry/extensions.py @@ -217,6 +217,7 @@ def get_extension_context() -> dict[str, Any] | None: ATTR_SUMMARY_TOOL_CALL_COUNT = "sap.extension.summary.toolCallCount" ATTR_SUMMARY_HOOK_CALL_COUNT = "sap.extension.summary.hookCallCount" ATTR_SUMMARY_HAS_INSTRUCTION = "sap.extension.summary.hasInstruction" +ATTR_SUMMARY_JOULE_STUDIO_GSID = "sap.extension.summary.joule_studio_gsid" # --------------------------------------------------------------------------- # Private state @@ -612,6 +613,7 @@ def emit_extensions_summary_span( hook_call_count: int, has_instruction: bool, total_duration_ms: float, + joule_studio_gsid: str = "", ) -> None: """Emit a sibling summary span with aggregate extension metrics. @@ -633,6 +635,8 @@ def emit_extensions_summary_span( into the system prompt. total_duration_ms: Wall-clock sum (milliseconds) of all extension operations. + joule_studio_gsid: Global solution ID of Joule Studio (empty string + if not available). """ total = tool_call_count + hook_call_count + (1 if has_instruction else 0) attrs = { @@ -642,6 +646,8 @@ def emit_extensions_summary_span( ATTR_SUMMARY_HOOK_CALL_COUNT: hook_call_count, ATTR_SUMMARY_HAS_INSTRUCTION: has_instruction, } + if joule_studio_gsid: + attrs[ATTR_SUMMARY_JOULE_STUDIO_GSID] = joule_studio_gsid span = _tracer.start_span("agent_extensions_summary", attributes=attrs) span.end() diff --git a/src/sap_cloud_sdk/extensibility/_models.py b/src/sap_cloud_sdk/extensibility/_models.py index b3c73a48..771aa1c4 100644 --- a/src/sap_cloud_sdk/extensibility/_models.py +++ b/src/sap_cloud_sdk/extensibility/_models.py @@ -641,6 +641,9 @@ class ExtensionCapabilityImplementation: hooks: List of hooks attached for this extension capability. source: Per-tool and per-hook attribution mapping. ``None`` when the backend does not provide source information. + joule_studio_gsid: Global solution ID of Joule Studio. Set when a + single Joule Studio extension contributes to this capability; + empty string otherwise. """ capability_id: str @@ -649,6 +652,7 @@ class ExtensionCapabilityImplementation: instruction: Optional[str] = None hooks: List[Hook] = field(default_factory=list) source: Optional[ExtensionSourceMapping] = None + joule_studio_gsid: str = "" @classmethod def from_dict(cls, obj: Dict[str, Any]) -> ExtensionCapabilityImplementation: diff --git a/src/sap_cloud_sdk/extensibility/_ums_transport.py b/src/sap_cloud_sdk/extensibility/_ums_transport.py index 896feead..d4711cb5 100644 --- a/src/sap_cloud_sdk/extensibility/_ums_transport.py +++ b/src/sap_cloud_sdk/extensibility/_ums_transport.py @@ -404,6 +404,11 @@ def _transform_ums_response( instruction = "\n\n".join(instructions) if instructions else None + joule_studio_gsid = next( + (node.get("jouleStudioGsid") or "" for node in nodes if node.get("jouleStudioGsid")), + "", + ) + return ExtensionCapabilityImplementation( capability_id=capability_id, extension_names=extension_names, @@ -411,6 +416,7 @@ def _transform_ums_response( instruction=instruction, hooks=hooks, source=source, + joule_studio_gsid=joule_studio_gsid, ) diff --git a/tests/core/unit/telemetry/test_extensions.py b/tests/core/unit/telemetry/test_extensions.py index 90607c53..047eb4e8 100644 --- a/tests/core/unit/telemetry/test_extensions.py +++ b/tests/core/unit/telemetry/test_extensions.py @@ -25,6 +25,7 @@ ATTR_SUMMARY_TOOL_CALL_COUNT, ATTR_SUMMARY_HOOK_CALL_COUNT, ATTR_SUMMARY_HAS_INSTRUCTION, + ATTR_SUMMARY_JOULE_STUDIO_GSID, resolve_source_info, build_extension_span_attributes, reset_tool_call_metrics, @@ -1073,6 +1074,37 @@ def test_no_instruction_count(self): attrs = mock_tracer.start_span.call_args[1]["attributes"] assert attrs[ATTR_SUMMARY_TOTAL_OPERATION_COUNT] == 1 # no +1 + + def test_joule_studio_gsid_included_when_provided(self): + with patch("sap_cloud_sdk.core.telemetry.extensions._tracer") as mock_tracer: + mock_span = MagicMock() + mock_tracer.start_span.return_value = mock_span + + emit_extensions_summary_span( + tool_call_count=1, + hook_call_count=0, + has_instruction=False, + total_duration_ms=100.0, + joule_studio_gsid="019ffc41-01a5-7b73-bda2-cb33b2eae292", + ) + + attrs = mock_tracer.start_span.call_args[1]["attributes"] + assert attrs[ATTR_SUMMARY_JOULE_STUDIO_GSID] == "019ffc41-01a5-7b73-bda2-cb33b2eae292" + + def test_joule_studio_gsid_omitted_when_empty(self): + with patch("sap_cloud_sdk.core.telemetry.extensions._tracer") as mock_tracer: + mock_span = MagicMock() + mock_tracer.start_span.return_value = mock_span + + emit_extensions_summary_span( + tool_call_count=1, + hook_call_count=0, + has_instruction=False, + total_duration_ms=100.0, + ) + + attrs = mock_tracer.start_span.call_args[1]["attributes"] + assert ATTR_SUMMARY_JOULE_STUDIO_GSID not in attrs # --------------------------------------------------------------------------- From 2b0a3b2f01d052d4dfcef402be42ee2100fd0abe Mon Sep 17 00:00:00 2001 From: Rishi Kunnath Date: Tue, 15 Sep 2026 18:52:49 +0530 Subject: [PATCH 2/3] modifying attribute --- src/sap_cloud_sdk/core/telemetry/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sap_cloud_sdk/core/telemetry/extensions.py b/src/sap_cloud_sdk/core/telemetry/extensions.py index b6254a36..6c77f159 100644 --- a/src/sap_cloud_sdk/core/telemetry/extensions.py +++ b/src/sap_cloud_sdk/core/telemetry/extensions.py @@ -217,7 +217,7 @@ def get_extension_context() -> dict[str, Any] | None: ATTR_SUMMARY_TOOL_CALL_COUNT = "sap.extension.summary.toolCallCount" ATTR_SUMMARY_HOOK_CALL_COUNT = "sap.extension.summary.hookCallCount" ATTR_SUMMARY_HAS_INSTRUCTION = "sap.extension.summary.hasInstruction" -ATTR_SUMMARY_JOULE_STUDIO_GSID = "sap.extension.summary.joule_studio_gsid" +ATTR_SUMMARY_JOULE_STUDIO_GSID = "sap.extension.joule_studio_gsid" # --------------------------------------------------------------------------- # Private state From 93f119594acb9edcd2c5e7d0ada846918346cfcb Mon Sep 17 00:00:00 2001 From: Rishi Kunnath Date: Tue, 15 Sep 2026 23:35:07 +0530 Subject: [PATCH 3/3] fix job failures --- pyproject.toml | 2 +- src/sap_cloud_sdk/extensibility/_ums_transport.py | 6 +++++- tests/core/unit/telemetry/test_extensions.py | 2 +- uv.lock | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 82d3c108..2e750880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sap-cloud-sdk" -version = "0.53.0" +version = "0.53.1" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/src/sap_cloud_sdk/extensibility/_ums_transport.py b/src/sap_cloud_sdk/extensibility/_ums_transport.py index 167fc771..1db87be3 100644 --- a/src/sap_cloud_sdk/extensibility/_ums_transport.py +++ b/src/sap_cloud_sdk/extensibility/_ums_transport.py @@ -405,7 +405,11 @@ def _transform_ums_response( instruction = "\n\n".join(instructions) if instructions else None joule_studio_gsid = next( - (node.get("jouleStudioGsid") or "" for node in nodes if node.get("jouleStudioGsid")), + ( + node.get("jouleStudioGsid") or "" + for node in nodes + if node.get("jouleStudioGsid") + ), "", ) diff --git a/tests/core/unit/telemetry/test_extensions.py b/tests/core/unit/telemetry/test_extensions.py index 047eb4e8..f32fcfea 100644 --- a/tests/core/unit/telemetry/test_extensions.py +++ b/tests/core/unit/telemetry/test_extensions.py @@ -1074,7 +1074,7 @@ def test_no_instruction_count(self): attrs = mock_tracer.start_span.call_args[1]["attributes"] assert attrs[ATTR_SUMMARY_TOTAL_OPERATION_COUNT] == 1 # no +1 - + def test_joule_studio_gsid_included_when_provided(self): with patch("sap_cloud_sdk.core.telemetry.extensions._tracer") as mock_tracer: mock_span = MagicMock() diff --git a/uv.lock b/uv.lock index 2e7c15f3..96a0de6e 100644 --- a/uv.lock +++ b/uv.lock @@ -4286,7 +4286,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.53.0" +version = "0.53.1" source = { editable = "." } dependencies = [ { name = "cryptography" },