diff --git a/src/a365/hosting/turnContextUtils.ts b/src/a365/hosting/turnContextUtils.ts index d9c4ae1..dacbb59 100644 --- a/src/a365/hosting/turnContextUtils.ts +++ b/src/a365/hosting/turnContextUtils.ts @@ -25,7 +25,7 @@ export function getCallerBaggagePairs(turnContext: TurnContextLike): Array<[stri } const from = turnContext.activity.from; const pairs: Array<[string, string | undefined]> = [ - [OpenTelemetryConstants.USER_ID_KEY, from.aadObjectId], + [OpenTelemetryConstants.USER_ID_KEY, from.aadObjectId || from.agenticUserId || from.id], [OpenTelemetryConstants.USER_NAME_KEY, from.name], [OpenTelemetryConstants.USER_EMAIL_KEY, from.agenticUserId], [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, from.agenticAppBlueprintId], diff --git a/src/a365/hosting/types.ts b/src/a365/hosting/types.ts index ca6d246..ea2ec49 100644 --- a/src/a365/hosting/types.ts +++ b/src/a365/hosting/types.ts @@ -20,6 +20,7 @@ export interface ActivityLike { channelIdSubChannel?: string | unknown; serviceUrl?: string; from?: { + id?: string; aadObjectId?: string; name?: string; role?: string; diff --git a/test/internal/unit/a365/hosting/turnContextUtils.test.ts b/test/internal/unit/a365/hosting/turnContextUtils.test.ts index 59c7a27..6f68776 100644 --- a/test/internal/unit/a365/hosting/turnContextUtils.test.ts +++ b/test/internal/unit/a365/hosting/turnContextUtils.test.ts @@ -81,6 +81,66 @@ describe("TurnContextUtils", () => { expect(pairs).toEqual([]); }); + it("should fall back to from.id when aadObjectId is undefined (non-Teams channel)", () => { + const ctx: TurnContextLike = { + activity: { + from: { id: "webchat-user-123", name: "Web User" }, + }, + turnState: new Map(), + }; + const pairs = getCallerBaggagePairs(ctx); + const obj = Object.fromEntries(pairs); + expect(obj[OpenTelemetryConstants.USER_ID_KEY]).toBe("webchat-user-123"); + expect(obj[OpenTelemetryConstants.USER_NAME_KEY]).toBe("Web User"); + }); + + it("should fall back to agenticUserId when aadObjectId is undefined (A2A)", () => { + const ctx: TurnContextLike = { + activity: { + from: { agenticUserId: "agent@contoso.com", name: "Upstream Agent" }, + }, + turnState: new Map(), + }; + const pairs = getCallerBaggagePairs(ctx); + const obj = Object.fromEntries(pairs); + expect(obj[OpenTelemetryConstants.USER_ID_KEY]).toBe("agent@contoso.com"); + }); + + it("should resolve userId to agenticUserId when it is a GUID (A2A with GUID)", () => { + const ctx: TurnContextLike = { + activity: { + from: { + id: "29:1sH5NArUwkWAX", + agenticUserId: "bef730f4-d6f5-4ffb-b759-26ffa449ed7e", + name: "Agent", + }, + }, + turnState: new Map(), + }; + const pairs = getCallerBaggagePairs(ctx); + const obj = Object.fromEntries(pairs); + expect(obj[OpenTelemetryConstants.USER_ID_KEY]).toBe( + "bef730f4-d6f5-4ffb-b759-26ffa449ed7e", + ); + }); + + it("should prefer aadObjectId over agenticUserId and from.id", () => { + const ctx: TurnContextLike = { + activity: { + from: { + id: "fallback-id", + aadObjectId: "aad-oid", + agenticUserId: "agent@contoso.com", + name: "User", + }, + }, + turnState: new Map(), + }; + const pairs = getCallerBaggagePairs(ctx); + const obj = Object.fromEntries(pairs); + expect(obj[OpenTelemetryConstants.USER_ID_KEY]).toBe("aad-oid"); + }); + it("should filter out undefined/empty values", () => { const ctx: TurnContextLike = { activity: { from: { name: "User", aadObjectId: "" } },