From d712b185ebc4b45eb11b9281ba5713af64ffdbf2 Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 23 Mar 2026 13:10:25 -0700 Subject: [PATCH 1/2] Rename caller/user attributes to align with OTel conventions and .NET SDK 1. Rename caller dimension constants to user.* namespace: - CallerIdKey (microsoft.caller.id) -> UserIdKey (user.id) - CallerUpnKey (microsoft.caller.upn) -> UserEmailKey (user.email) - CallerNameKey (microsoft.caller.name) -> UserNameKey (user.name) 2. Rename agent UPN constants to use email: - AgentUPNKey (microsoft.agent.user.upn) -> AgentEmailKey (microsoft.agent.user.email) - CallerAgentUPNKey (microsoft.a365.caller.agent.user.upn) -> CallerAgentEmailKey (microsoft.a365.caller.agent.user.email) 3. Rename data contract properties: - CallerDetails: callerId -> userId, callerUpn -> userEmail, callerName -> userName - AgentDetails: agentUPN -> agentEmail 4. Rename BaggageBuilder methods: - callerId() -> userId(), callerName() -> userName(), callerUpn() -> userEmail(), agentUpn() -> agentEmail() 5. Update all consumers: scopes, BaggageBuilder, processors, TurnContextUtils, ScopeUtils 6. Update all tests to use new naming Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 14 ++++- .../src/utils/ScopeUtils.ts | 12 ++--- .../src/utils/TurnContextUtils.ts | 6 +-- .../src/tracing/constants.ts | 12 ++--- .../src/tracing/contracts.ts | 12 ++--- .../src/tracing/middleware/BaggageBuilder.ts | 32 +++++------ .../src/tracing/processors/util.ts | 10 ++-- .../src/tracing/scopes/InvokeAgentScope.ts | 2 +- .../src/tracing/scopes/OpenTelemetryScope.ts | 8 +-- .../observability/core/SpanProcessor.test.ts | 10 ++-- tests/observability/core/scopes.test.ts | 54 +++++++++++-------- .../hosting/BaggageBuilderUtils.test.ts | 8 +-- .../hosting/baggage-middleware.test.ts | 2 +- .../hosting/output-logging-middleware.test.ts | 4 +- .../extension/hosting/scope-utils.test.ts | 19 +++---- 15 files changed, 115 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ef94f7..4500b9b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes (`@microsoft/agents-a365-observability`) +- **Caller dimension constants renamed to `user.*` namespace** — Aligns with OpenTelemetry semantic conventions and .NET SDK: + - `GEN_AI_CALLER_ID_KEY` (`microsoft.caller.id`) → `USER_ID_KEY` (`user.id`) + - `GEN_AI_CALLER_NAME_KEY` (`microsoft.caller.name`) → `USER_NAME_KEY` (`user.name`) + - `GEN_AI_CALLER_UPN_KEY` (`microsoft.caller.upn`) → `USER_EMAIL_KEY` (`user.email`) + - `GEN_AI_AGENT_UPN_KEY` (`microsoft.agent.user.upn`) → `GEN_AI_AGENT_EMAIL_KEY` (`microsoft.agent.user.email`) + - `GEN_AI_CALLER_AGENT_UPN_KEY` (`microsoft.a365.caller.agent.user.upn`) → `GEN_AI_CALLER_AGENT_EMAIL_KEY` (`microsoft.a365.caller.agent.user.email`) +- **`CallerDetails` properties renamed** — `callerId` → `userId`, `callerUpn` → `userEmail`, `callerName` → `userName`. +- **`AgentDetails.agentUPN` renamed to `AgentDetails.agentEmail`**. +- **`BaggageBuilder` methods renamed** — `callerId()` → `userId()`, `callerName()` → `userName()`, `callerUpn()` → `userEmail()`, `agentUpn()` → `agentEmail()`. - **`SourceMetadata` renamed to `Channel`** — The exported interface representing invocation channel information is renamed from `SourceMetadata` to `Channel`. - **`AgentRequest.sourceMetadata` renamed to `AgentRequest.channel`** — The optional property on `AgentRequest` is renamed from `sourceMetadata` to `channel` (type changed from `SourceMetadata` to `Channel`). - **`BaggageBuilder.serviceName()` renamed to `BaggageBuilder.operationSource()`** — Fluent setter for the service name baggage value. @@ -20,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes (`@microsoft/agents-a365-observability-hosting`) +- **`ScopeUtils.deriveCallerDetails()` now returns renamed properties** — `callerId` → `userId`, `callerUpn` → `userEmail`, `callerName` → `userName` (matching `CallerDetails` rename). +- **`ScopeUtils.deriveAgentDetails()` / `deriveCallerAgent()` now return `agentEmail` instead of `agentUPN`** (matching `AgentDetails` rename). +- **`getCallerBaggagePairs()` now emits `user.id`, `user.name`, `user.email`** instead of `microsoft.caller.id`, `microsoft.caller.name`, `microsoft.caller.upn`. - **`ScopeUtils.deriveSourceMetadataObject()` renamed to `ScopeUtils.deriveChannelObject()`**. - **`BaggageBuilderUtils.setSourceMetadataBaggage()` renamed to `BaggageBuilderUtils.setChannelBaggage()`**. - **`getSourceMetadataBaggagePairs()` renamed to `getChannelBaggagePairs()`** in `TurnContextUtils`. @@ -47,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **ObservabilityHostingManager**: `enableBaggage` option now defaults to `false` (was `true`). Callers must explicitly set `enableBaggage: true` to register the BaggageMiddleware. - `ScopeUtils.deriveAgentDetails` now resolves `agentId` via `activity.getAgenticInstanceId()` for embodied (agentic) agents only. For non-embodied agents, `agentId` is `undefined` since the token's app ID cannot reliably be attributed to the agent. - `ScopeUtils.deriveAgentDetails` now resolves `agentBlueprintId` from the JWT `xms_par_app_azp` claim via `RuntimeUtility.getAgentIdFromToken()` instead of reading `recipient.agenticAppBlueprintId`. -- `ScopeUtils.deriveAgentDetails` now resolves `agentUPN` via `activity.getAgenticUser()` instead of `recipient.agenticUserId`. +- `ScopeUtils.deriveAgentDetails` now resolves `agentEmail` via `activity.getAgenticUser()` instead of `recipient.agenticUserId`. - `ScopeUtils.deriveTenantDetails` now uses `activity.getAgenticTenantId()` instead of `recipient.tenantId`. - `getTargetAgentBaggagePairs` now uses `activity.getAgenticInstanceId()` instead of `recipient.agenticAppId`. - `getTenantIdPair` now uses `activity.getAgenticTenantId()` instead of manual `channelData` parsing. diff --git a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts index 7d05868a..76de448d 100644 --- a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts @@ -65,7 +65,7 @@ export class ScopeUtils { agentName: recipient.name, agentAUID: recipient.aadObjectId, agentBlueprintId, - agentUPN: turnContext?.activity?.getAgenticUser?.(), + agentEmail: turnContext?.activity?.getAgenticUser?.(), agentDescription: recipient.role, tenantId: turnContext?.activity?.getAgenticTenantId?.() } as AgentDetails; @@ -87,13 +87,13 @@ export class ScopeUtils { agentDescription: from.role, tenantId: from.tenantId, agentId: from.agenticAppId, - agentUPN: from.agenticUserId + agentEmail: from.agenticUserId } as AgentDetails; } /** - * Derive caller identity details (id, upn, name, tenant, client ip) from the activity from. + * Derive caller identity details (id, email, name, tenant, client ip) from the activity from. * @param turnContext Activity context * @returns Caller details when available; otherwise undefined. */ @@ -101,9 +101,9 @@ export class ScopeUtils { const from = turnContext?.activity?.from; if (!from) return undefined; return { - callerId: from.aadObjectId, - callerUpn: from.agenticUserId, - callerName: from.name, + userId: from.aadObjectId, + userEmail: from.agenticUserId, + userName: from.name, tenantId: from.tenantId, } as CallerDetails; } diff --git a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts index daa08c7b..05277b64 100644 --- a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts @@ -31,9 +31,9 @@ export function getCallerBaggagePairs(turnContext: TurnContext): Array<[string, const upn = from.agenticUserId; const pairs: Array<[string, string | undefined]> = [ - [OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, from.aadObjectId], - [OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, from.name], - [OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, upn], + [OpenTelemetryConstants.USER_ID_KEY, from.aadObjectId], + [OpenTelemetryConstants.USER_NAME_KEY, from.name], + [OpenTelemetryConstants.USER_EMAIL_KEY, upn], [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, from.agenticAppBlueprintId] ]; return normalizePairs(pairs); diff --git a/packages/agents-a365-observability/src/tracing/constants.ts b/packages/agents-a365-observability/src/tracing/constants.ts index d6fd4168..64cd5dec 100644 --- a/packages/agents-a365-observability/src/tracing/constants.ts +++ b/packages/agents-a365-observability/src/tracing/constants.ts @@ -63,15 +63,15 @@ export class OpenTelemetryConstants { public static readonly GEN_AI_TOOL_CALL_RESULT_KEY = 'gen_ai.tool.call.result'; public static readonly GEN_AI_TOOL_TYPE_KEY = 'gen_ai.tool.type'; - // Agent user (user tied to agent instance during creation) or caller dimensions - public static readonly GEN_AI_CALLER_ID_KEY = 'microsoft.caller.id'; - public static readonly GEN_AI_CALLER_NAME_KEY = 'microsoft.caller.name'; - public static readonly GEN_AI_CALLER_UPN_KEY = 'microsoft.caller.upn'; + // Human caller dimensions (OTel user.* namespace) + public static readonly USER_ID_KEY = 'user.id'; + public static readonly USER_NAME_KEY = 'user.name'; + public static readonly USER_EMAIL_KEY = 'user.email'; public static readonly GEN_AI_CALLER_CLIENT_IP_KEY = 'client.address'; // Agent to Agent caller agent dimensions public static readonly GEN_AI_CALLER_AGENT_USER_ID_KEY = 'microsoft.a365.caller.agent.user.id'; - public static readonly GEN_AI_CALLER_AGENT_UPN_KEY = 'microsoft.a365.caller.agent.user.upn'; + public static readonly GEN_AI_CALLER_AGENT_EMAIL_KEY = 'microsoft.a365.caller.agent.user.email'; public static readonly GEN_AI_CALLER_AGENT_NAME_KEY = 'microsoft.a365.caller.agent.name'; public static readonly GEN_AI_CALLER_AGENT_ID_KEY = 'microsoft.a365.caller.agent.id'; public static readonly GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = 'microsoft.a365.caller.agent.blueprint.id'; @@ -86,7 +86,7 @@ export class OpenTelemetryConstants { // Baggage keys public static readonly GEN_AI_AGENT_AUID_KEY = 'microsoft.agent.user.id'; - public static readonly GEN_AI_AGENT_UPN_KEY = 'microsoft.agent.user.upn'; + public static readonly GEN_AI_AGENT_EMAIL_KEY = 'microsoft.agent.user.email'; public static readonly GEN_AI_AGENT_BLUEPRINT_ID_KEY = 'microsoft.a365.agent.blueprint.id'; // Execution context dimensions diff --git a/packages/agents-a365-observability/src/tracing/contracts.ts b/packages/agents-a365-observability/src/tracing/contracts.ts index 5cab41b4..dddb9726 100644 --- a/packages/agents-a365-observability/src/tracing/contracts.ts +++ b/packages/agents-a365-observability/src/tracing/contracts.ts @@ -117,8 +117,8 @@ export interface AgentDetails { /** The agent user ID (AUID) */ agentAUID?: string; - /** The agent user principal name (UPN) */ - agentUPN?: string; + /** The agent email address */ + agentEmail?: string; /** The agent blueprint/application ID */ agentBlueprintId?: string; @@ -158,13 +158,13 @@ export interface ToolCallDetails { */ export interface CallerDetails { /** The unique identifier for the caller */ - callerId?: string; + userId?: string; - /** The user principal name (UPN) of the caller */ - callerUpn?: string; + /** The email address of the caller */ + userEmail?: string; /** The display name of the caller */ - callerName?: string; + userName?: string; /** The user ID of the caller */ callerUserId?: string; diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 9e69097a..80a401c5 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -70,12 +70,12 @@ export class BaggageBuilder { } /** - * Set the agent UPN baggage value. - * @param value The agent UPN + * Set the agent email baggage value. + * @param value The agent email * @returns Self for method chaining */ - agentUpn(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY, value); + agentEmail(value: string | null | undefined): BaggageBuilder { + this.set(OpenTelemetryConstants.GEN_AI_AGENT_EMAIL_KEY, value); return this; } @@ -100,12 +100,12 @@ export class BaggageBuilder { } /** - * Set the caller ID baggage value. - * @param value The caller ID + * Set the user ID baggage value. + * @param value The user ID * @returns Self for method chaining */ - callerId(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, value); + userId(value: string | null | undefined): BaggageBuilder { + this.set(OpenTelemetryConstants.USER_ID_KEY, value); return this; } @@ -150,22 +150,22 @@ export class BaggageBuilder { } /** - * Set the caller name baggage value. - * @param value The caller name + * Set the user name baggage value. + * @param value The user name * @returns Self for method chaining */ - callerName(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, value); + userName(value: string | null | undefined): BaggageBuilder { + this.set(OpenTelemetryConstants.USER_NAME_KEY, value); return this; } /** - * Set the caller UPN baggage value. - * @param value The caller UPN + * Set the user email baggage value. + * @param value The user email * @returns Self for method chaining */ - callerUpn(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, value); + userEmail(value: string | null | undefined): BaggageBuilder { + this.set(OpenTelemetryConstants.USER_EMAIL_KEY, value); return this; } diff --git a/packages/agents-a365-observability/src/tracing/processors/util.ts b/packages/agents-a365-observability/src/tracing/processors/util.ts index 5994a953..37543777 100644 --- a/packages/agents-a365-observability/src/tracing/processors/util.ts +++ b/packages/agents-a365-observability/src/tracing/processors/util.ts @@ -18,15 +18,15 @@ export const GENERIC_ATTRIBUTES: readonly string[] = [ consts.GEN_AI_AGENT_NAME_KEY, consts.GEN_AI_AGENT_DESCRIPTION_KEY, consts.SESSION_DESCRIPTION_KEY, - consts.GEN_AI_AGENT_UPN_KEY, + consts.GEN_AI_AGENT_EMAIL_KEY, consts.GEN_AI_AGENT_AUID_KEY, consts.GEN_AI_AGENT_PLATFORM_ID_KEY, consts.GEN_AI_AGENT_BLUEPRINT_ID_KEY, consts.SERVICE_NAME_KEY, // Caller / Invoker attributes - consts.GEN_AI_CALLER_ID_KEY, - consts.GEN_AI_CALLER_NAME_KEY, - consts.GEN_AI_CALLER_UPN_KEY, + consts.USER_ID_KEY, + consts.USER_NAME_KEY, + consts.USER_EMAIL_KEY, consts.GEN_AI_CALLER_CLIENT_IP_KEY, // Channel attributes consts.CHANNEL_NAME_KEY, @@ -41,7 +41,7 @@ export const INVOKE_AGENT_ATTRIBUTES: readonly string[] = [ consts.GEN_AI_CALLER_AGENT_ID_KEY, consts.GEN_AI_CALLER_AGENT_NAME_KEY, consts.GEN_AI_CALLER_AGENT_USER_ID_KEY, - consts.GEN_AI_CALLER_AGENT_UPN_KEY, + consts.GEN_AI_CALLER_AGENT_EMAIL_KEY, consts.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, consts.GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, ]; diff --git a/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts b/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts index c5537e19..1834ced4 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts @@ -101,7 +101,7 @@ export class InvokeAgentScope extends OpenTelemetryScope { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY, callerAgentDetails.agentId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, callerAgentDetails.agentBlueprintId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_USER_ID_KEY, callerAgentDetails.agentAUID); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_UPN_KEY, callerAgentDetails.agentUPN); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_EMAIL_KEY, callerAgentDetails.agentEmail); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, callerAgentDetails.platformId); } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts b/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts index 6e514cd0..ad417108 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts @@ -91,7 +91,7 @@ export abstract class OpenTelemetryScope implements Disposable { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, agentDetails.conversationId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_ICON_URI_KEY, agentDetails.iconUri); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY, agentDetails.agentAUID); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY, agentDetails.agentUPN); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_EMAIL_KEY, agentDetails.agentEmail); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, agentDetails.agentBlueprintId); } @@ -102,9 +102,9 @@ export abstract class OpenTelemetryScope implements Disposable { // Set caller details if provided if (callerDetails) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, callerDetails.callerId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, callerDetails.callerUpn); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, callerDetails.callerName); + this.setTagMaybe(OpenTelemetryConstants.USER_ID_KEY, callerDetails.userId); + this.setTagMaybe(OpenTelemetryConstants.USER_EMAIL_KEY, callerDetails.userEmail); + this.setTagMaybe(OpenTelemetryConstants.USER_NAME_KEY, callerDetails.userName); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, callerDetails.callerClientIp); } } diff --git a/tests/observability/core/SpanProcessor.test.ts b/tests/observability/core/SpanProcessor.test.ts index 25c5890c..690d1682 100644 --- a/tests/observability/core/SpanProcessor.test.ts +++ b/tests/observability/core/SpanProcessor.test.ts @@ -85,7 +85,7 @@ describe('SpanProcessor', () => { const baggageEntries = { [OpenTelemetryConstants.GEN_AI_OPERATION_NAME_KEY]: OpenTelemetryConstants.INVOKE_AGENT_OPERATION_NAME, [OpenTelemetryConstants.TENANT_ID_KEY]: 'tenant-123', - [OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]: 'caller-456' + [OpenTelemetryConstants.USER_ID_KEY]: 'caller-456' }; let baggage = propagation.createBaggage(); @@ -163,10 +163,11 @@ describe('SpanProcessor', () => { expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.TENANT_ID_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.SESSION_ID_KEY); - expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY); - expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY); - expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.USER_ID_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.USER_NAME_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.USER_EMAIL_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_AGENT_EMAIL_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.CHANNEL_NAME_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.CHANNEL_LINK_KEY); expect(GENERIC_ATTRIBUTES).not.toContain('correlation.id'); @@ -174,6 +175,7 @@ describe('SpanProcessor', () => { it('should apply invoke agent specific attributes', () => { expect(INVOKE_AGENT_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY); + expect(INVOKE_AGENT_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_EMAIL_KEY); }); it('should include blueprint ID in generic attributes', () => { diff --git a/tests/observability/core/scopes.test.ts b/tests/observability/core/scopes.test.ts index 8de343bd..6b8fd347 100644 --- a/tests/observability/core/scopes.test.ts +++ b/tests/observability/core/scopes.test.ts @@ -102,9 +102,9 @@ describe('Scopes', () => { }; const callerDetails: CallerDetails = { - callerId: 'user-123', - callerName: 'Test User', - callerUpn: 'test.user@contoso.com', + userId: 'user-123', + userName: 'Test User', + userEmail: 'test.user@contoso.com', tenantId: 'test-tenant' }; @@ -193,7 +193,7 @@ describe('Scopes', () => { agentName: 'Test Agent' }; const callerDetails: CallerDetails = { - callerId: 'user-123', + userId: 'user-123', tenantId: 'test-tenant', callerClientIp: '10.0.0.5' }; @@ -225,9 +225,9 @@ describe('Scopes', () => { it('should create scope with tool details', () => { const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); const callerDetails: CallerDetails = { - callerId: 'caller-tool-1', - callerUpn: 'tool.user@contoso.com', - callerName: 'Tool User', + userId: 'caller-tool-1', + userEmail: 'tool.user@contoso.com', + userName: 'Tool User', tenantId: 'tool-tenant', callerClientIp: '10.0.0.10' }; @@ -242,14 +242,16 @@ describe('Scopes', () => { expect(scope).toBeInstanceOf(ExecuteToolScope); const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); expect(calls).toEqual(expect.arrayContaining([ - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, val: 'caller-tool-1' }), - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, val: 'Tool User' }), + expect.objectContaining({ key: OpenTelemetryConstants.USER_ID_KEY, val: 'caller-tool-1' }), + expect.objectContaining({ key: OpenTelemetryConstants.USER_NAME_KEY, val: 'Tool User' }), + expect.objectContaining({ key: OpenTelemetryConstants.USER_EMAIL_KEY, val: 'tool.user@contoso.com' }), expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, val: '10.0.0.10' }) ])); // Validate raw attribute key strings for schema correctness const keySet = new Set(calls.map(c => c.key)); - expect(keySet).toContain('microsoft.caller.id'); - expect(keySet).toContain('microsoft.caller.name'); + expect(keySet).toContain('user.id'); + expect(keySet).toContain('user.name'); + expect(keySet).toContain('user.email'); expect(keySet).toContain('client.address'); scope?.dispose(); spy.mockRestore(); @@ -369,9 +371,9 @@ describe('Scopes', () => { it('should create scope with inference details', () => { const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); const callerDetails: CallerDetails = { - callerId: 'caller-inf-1', - callerUpn: 'inf.user@contoso.com', - callerName: 'Inf User', + userId: 'caller-inf-1', + userEmail: 'inf.user@contoso.com', + userName: 'Inf User', tenantId: 'inf-tenant', callerClientIp: '10.0.0.20' }; @@ -389,14 +391,16 @@ describe('Scopes', () => { expect(scope).toBeInstanceOf(InferenceScope); const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); expect(calls).toEqual(expect.arrayContaining([ - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, val: 'caller-inf-1' }), - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, val: 'Inf User' }), + expect.objectContaining({ key: OpenTelemetryConstants.USER_ID_KEY, val: 'caller-inf-1' }), + expect.objectContaining({ key: OpenTelemetryConstants.USER_NAME_KEY, val: 'Inf User' }), + expect.objectContaining({ key: OpenTelemetryConstants.USER_EMAIL_KEY, val: 'inf.user@contoso.com' }), expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, val: '10.0.0.20' }) ])); // Validate raw attribute key strings for schema correctness const keySet = new Set(calls.map(c => c.key)); - expect(keySet).toContain('microsoft.caller.id'); - expect(keySet).toContain('microsoft.caller.name'); + expect(keySet).toContain('user.id'); + expect(keySet).toContain('user.name'); + expect(keySet).toContain('user.email'); expect(keySet).toContain('client.address'); scope?.dispose(); spy.mockRestore(); @@ -624,17 +628,23 @@ describe('Scopes', () => { // Validate attribute key constant values use the new schema namespace. describe('Attribute key schema values', () => { - it('caller keys use microsoft.* / client.* namespace', () => { - expect(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY).toBe('microsoft.caller.id'); - expect(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY).toBe('microsoft.caller.name'); - expect(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY).toBe('microsoft.caller.upn'); + it('caller keys use user.* / client.* namespace', () => { + expect(OpenTelemetryConstants.USER_ID_KEY).toBe('user.id'); + expect(OpenTelemetryConstants.USER_NAME_KEY).toBe('user.name'); + expect(OpenTelemetryConstants.USER_EMAIL_KEY).toBe('user.email'); expect(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY).toBe('client.address'); }); + it('agent baggage keys use microsoft.agent.* namespace', () => { + expect(OpenTelemetryConstants.GEN_AI_AGENT_EMAIL_KEY).toBe('microsoft.agent.user.email'); + expect(OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY).toBe('microsoft.agent.user.id'); + }); + it('caller agent keys use microsoft.a365.* namespace', () => { expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY).toBe('microsoft.a365.caller.agent.id'); expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_NAME_KEY).toBe('microsoft.a365.caller.agent.name'); expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY).toBe('microsoft.a365.caller.agent.blueprint.id'); + expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_EMAIL_KEY).toBe('microsoft.a365.caller.agent.user.email'); }); it('channel keys use microsoft.channel.* namespace', () => { diff --git a/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts b/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts index 2f369c09..023d2d03 100644 --- a/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts +++ b/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts @@ -45,15 +45,15 @@ describe('BaggageBuilderUtils', () => { expect(result).toBe(builder); // Validate every expected OpenTelemetry baggage key and value const asObj = Object.fromEntries(capturedPairs); - expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]).toBeUndefined(); - expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY]).toBe('User One'); - expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY]).toBe('agentic-user-1'); + expect(asObj[OpenTelemetryConstants.USER_ID_KEY]).toBeUndefined(); + expect(asObj[OpenTelemetryConstants.USER_NAME_KEY]).toBe('User One'); + expect(asObj[OpenTelemetryConstants.USER_EMAIL_KEY]).toBe('agentic-user-1'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe('agent-app-1'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY]).toBe('Agent One'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY]).toBeUndefined(); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY]).toBe('agent'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY]).toBeUndefined(); - expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY]).toBeUndefined(); + expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_EMAIL_KEY]).toBeUndefined(); expect(asObj[OpenTelemetryConstants.TENANT_ID_KEY]).toBe('tenant1'); }); diff --git a/tests/observability/extension/hosting/baggage-middleware.test.ts b/tests/observability/extension/hosting/baggage-middleware.test.ts index fb1c8e5d..4a1fd00c 100644 --- a/tests/observability/extension/hosting/baggage-middleware.test.ts +++ b/tests/observability/extension/hosting/baggage-middleware.test.ts @@ -100,7 +100,7 @@ describe('BaggageMiddleware', () => { } }); - expect(capturedBaggage[OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]).toBe('user-oid'); + expect(capturedBaggage[OpenTelemetryConstants.USER_ID_KEY]).toBe('user-oid'); expect(capturedBaggage[OpenTelemetryConstants.TENANT_ID_KEY]).toBe('tenant-123'); expect(capturedBaggage[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBeUndefined(); expect(capturedBaggage[OpenTelemetryConstants.CHANNEL_NAME_KEY]).toBe('web'); diff --git a/tests/observability/extension/hosting/output-logging-middleware.test.ts b/tests/observability/extension/hosting/output-logging-middleware.test.ts index ed335b5f..fcaa82ee 100644 --- a/tests/observability/extension/hosting/output-logging-middleware.test.ts +++ b/tests/observability/extension/hosting/output-logging-middleware.test.ts @@ -199,8 +199,8 @@ describe('OutputLoggingMiddleware', () => { const outputSpan = exporter.getFinishedSpans().find(s => s.name.includes('output_messages')); expect(outputSpan).toBeDefined(); - expect(outputSpan!.attributes[OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]).toBe('user-oid'); - expect(outputSpan!.attributes[OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY]).toBe('Test User'); + expect(outputSpan!.attributes[OpenTelemetryConstants.USER_ID_KEY]).toBe('user-oid'); + expect(outputSpan!.attributes[OpenTelemetryConstants.USER_NAME_KEY]).toBe('Test User'); expect(outputSpan!.attributes[OpenTelemetryConstants.CHANNEL_NAME_KEY]).toBe('teams'); }); diff --git a/tests/observability/extension/hosting/scope-utils.test.ts b/tests/observability/extension/hosting/scope-utils.test.ts index 96ccf6bb..f5590ad7 100644 --- a/tests/observability/extension/hosting/scope-utils.test.ts +++ b/tests/observability/extension/hosting/scope-utils.test.ts @@ -88,7 +88,7 @@ describe('ScopeUtils.populateFromTurnContext', () => { [OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY, 'agent-oid'], [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], [OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, 'test-blueprint-id'], - [OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY, 'agent-upn@contoso.com'], + [OpenTelemetryConstants.GEN_AI_AGENT_EMAIL_KEY, 'agent-upn@contoso.com'], [OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY, 'assistant'], [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], [OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, JSON.stringify(['input text'])] @@ -146,13 +146,14 @@ describe('ScopeUtils.populateFromTurnContext', () => { [OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, 'conv-B'], [OpenTelemetryConstants.CHANNEL_NAME_KEY, 'teams'], [OpenTelemetryConstants.CHANNEL_LINK_KEY, 'https://teams'], - [OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, 'user-oid'], - [OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, 'Test User'], - [OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, 'user@contoso.com'], + [OpenTelemetryConstants.USER_ID_KEY, 'user-oid'], + [OpenTelemetryConstants.USER_NAME_KEY, 'Test User'], + [OpenTelemetryConstants.USER_EMAIL_KEY, 'user@contoso.com'], [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_USER_ID_KEY, 'user-oid'], [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_NAME_KEY, 'Test User'], [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY, 'callerAgent-1'], [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, 'caller-agentBlueprintId'], + [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_EMAIL_KEY, 'user@contoso.com'], [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], [OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, JSON.stringify(['invoke message'])], [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], @@ -207,7 +208,7 @@ test('deriveAgentDetails maps recipient fields to AgentDetails', () => { agentName: 'A', agentAUID: 'auid', agentBlueprintId: undefined, - agentUPN: 'upn1', + agentEmail: 'upn1', agentDescription: 'bot', tenantId: 't1', }); @@ -224,7 +225,7 @@ test('deriveCallerAgent maps from fields to caller AgentDetails', () => { agentBlueprintId: 'bp', agentName: 'Caller', agentAUID: 'uid', - agentUPN: 'caller-upn', + agentEmail: 'caller-upn', agentDescription: 'agent', tenantId: 't2', agentId: 'agent-caller', @@ -239,9 +240,9 @@ test('deriveCallerAgent returns undefined without from', () => { test('deriveCallerDetails maps from to CallerDetails', () => { const ctx = makeCtx({ activity: { from: { aadObjectId: 'uid', agenticUserId: 'upn', name: 'User', tenantId: 't3' } } as any }); expect(ScopeUtils.deriveCallerDetails(ctx)).toEqual({ - callerId: 'uid', - callerUpn: 'upn', - callerName: 'User', + userId: 'uid', + userEmail: 'upn', + userName: 'User', tenantId: 't3', }); }); From 6421e9d150ea8cc1efd503bcd20c64c9b35c6616 Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 23 Mar 2026 13:33:09 -0700 Subject: [PATCH 2/2] Address PR review: remove unused callerUserId, fix stale JSDoc - Remove unused `callerUserId` property from `CallerDetails` (not used in source, tests, or dotnet SDK) - Fix `deriveCallerDetails` JSDoc that incorrectly mentioned "client ip" Co-Authored-By: Claude Opus 4.6 (1M context) --- .../agents-a365-observability-hosting/src/utils/ScopeUtils.ts | 2 +- packages/agents-a365-observability/src/tracing/contracts.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts index 76de448d..09cd720b 100644 --- a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts @@ -93,7 +93,7 @@ export class ScopeUtils { /** - * Derive caller identity details (id, email, name, tenant, client ip) from the activity from. + * Derive caller identity details (id, email, name, tenant) from the activity from. * @param turnContext Activity context * @returns Caller details when available; otherwise undefined. */ diff --git a/packages/agents-a365-observability/src/tracing/contracts.ts b/packages/agents-a365-observability/src/tracing/contracts.ts index dddb9726..1ff4824f 100644 --- a/packages/agents-a365-observability/src/tracing/contracts.ts +++ b/packages/agents-a365-observability/src/tracing/contracts.ts @@ -166,9 +166,6 @@ export interface CallerDetails { /** The display name of the caller */ userName?: string; - /** The user ID of the caller */ - callerUserId?: string; - /** The tenant ID of the caller */ tenantId?: string;