Skip to content

Use Activity helpers and ResolveAgentIdentity for agent telemetry#211

Open
fpfp100 wants to merge 9 commits intomainfrom
users/pefan/use-activity-helpers
Open

Use Activity helpers and ResolveAgentIdentity for agent telemetry#211
fpfp100 wants to merge 9 commits intomainfrom
users/pefan/use-activity-helpers

Conversation

@fpfp100
Copy link
Contributor

@fpfp100 fpfp100 commented Feb 25, 2026

Summary

  • Fix agent identity resolution: ScopeUtils.deriveAgentDetails now uses RuntimeUtility.ResolveAgentIdentity() for agentId (works for both app-based and blueprint-based agents) instead of recipient.agenticAppId which only worked for blueprint agents.
  • Fix blueprint ID resolution: agentBlueprintId is now resolved from the JWT xms_par_app_azp claim via RuntimeUtility.getAgentIdFromToken() for agentic requests, instead of incorrectly using the instance ID.
  • Replace direct ChannelAccount property access with Activity helpers (getAgenticInstanceId(), getAgenticUser(), getAgenticTenantId()) in ScopeUtils and TurnContextUtils.
  • Remove channelData tenant ID fallback in getTenantIdPair() — now uses getAgenticTenantId().
  • Upgrade @microsoft/agents-hosting and @microsoft/agents-activity from ^1.1.0-alpha.85 to ^1.3.1.

Breaking Changes

  • ScopeUtils.deriveAgentDetails(turnContext, authToken) — new required authToken parameter.
  • ScopeUtils.populateInferenceScopeFromTurnContext(details, turnContext, authToken, ...) — new required authToken parameter.
  • ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, turnContext, authToken, ...) — new required authToken parameter.
  • ScopeUtils.populateExecuteToolScopeFromTurnContext(details, turnContext, authToken, ...) — new required authToken parameter.
  • ScopeUtils.buildInvokeAgentDetails(details, turnContext, authToken) — new required authToken parameter.

Test plan

  • All 53 test suites pass (1059 tests)
  • Full pnpm build succeeds (including CJS + ESM for all packages)
  • Verify deriveAgentDetails resolves agentId via ResolveAgentIdentity() and agentBlueprintId via getAgentIdFromToken()
  • Verify deriveTenantDetails uses getAgenticTenantId() helper
  • Verify getTargetAgentBaggagePairs uses getAgenticInstanceId()
  • Verify getTenantIdPair uses getAgenticTenantId()

🤖 Generated with Claude Code

Replace direct ChannelAccount property access with Activity class helpers
(getAgenticInstanceId, getAgenticUser, getAgenticTenantId) in ScopeUtils
and TurnContextUtils. Use RuntimeUtility.ResolveAgentIdentity for blueprint
ID resolution when authToken is provided, falling back to
recipient.agenticAppBlueprintId. Add authToken parameter with overloaded
signatures (required + deprecated no-arg) for backward compatibility.
Upgrade @microsoft/agents-hosting and agents-activity to ^1.3.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 02:12
@fpfp100 fpfp100 requested review from a team as code owners February 25, 2026 02:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request refactors agent telemetry collection to use Activity class helper methods instead of direct ChannelAccount property access, adds authentication token-based agent identity resolution, and upgrades key dependencies. The changes aim to improve blueprint ID resolution for telemetry and standardize how agent identity information is extracted from TurnContext.

Changes:

  • Replace direct ChannelAccount property access with Activity helper methods (getAgenticInstanceId(), getAgenticUser(), getAgenticTenantId())
  • Add authToken parameter to telemetry scope population methods with TypeScript overloads for backward compatibility
  • Upgrade @microsoft/agents-hosting and @microsoft/agents-activity from alpha to stable release (1.3.1)

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Updates catalog versions for agents-hosting and agents-activity to 1.3.1
pnpm-lock.yaml Reflects dependency upgrades including msal-node 5.0.4, axios 1.13.5, jwks-rsa 3.2.2, and @types/express 5.0.6
packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts Adds authToken parameter with overloads to scope population methods; uses RuntimeUtility.ResolveAgentIdentity for blueprint resolution; switches to Activity helpers
packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts Replaces direct property access with getAgenticInstanceId() and getAgenticTenantId() helpers; removes channelData tenant ID fallback
tests/observability/extension/hosting/scope-utils.test.ts Updates test fixtures to use Activity helper methods and removes agenticAppBlueprintId from recipient to test undefined fallback
tests/observability/extension/hosting/TurnContextUtils.test.ts Updates test fixtures to use Activity helpers and adjusts assertions to expect undefined for aadObjectId and agenticAppBlueprintId
tests/observability/extension/hosting/BaggageBuilderUtils.test.ts Updates test fixtures to use Activity helpers and adjusts baggage assertions to expect undefined values
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

…atures

- Extract deriveAgentDetailsCore and buildInvokeAgentDetailsCore as private
  methods so internal callers bypass deprecated overloads (fixes
  @typescript-eslint/no-deprecated lint errors)
- Remove `public` from implementation signatures of overloaded methods
  (only the two declared overloads should be public-facing)
- Fix stale JSDoc on getTenantIdPair (removed ChannelData reference)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fpfp100 fpfp100 marked this pull request as draft February 25, 2026 17:39
private static deriveAgentDetailsCore(turnContext: TurnContext, authToken?: string): AgentDetails | undefined {
const recipient = turnContext?.activity?.recipient;
if (!recipient) return undefined;
const agentBlueprintId = authToken
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: its not aways a blueprintID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to use getAgentIdFromToken

if (!recipient) return undefined;
const agentBlueprintId = authToken
? RuntimeUtility.ResolveAgentIdentity(turnContext, authToken)
: recipient.agenticAppBlueprintId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not use this property ever.. this can and will be spoofed,
if you are trying to resolve an appID for an app that does not have user auth, then we should work this out based on the default connection or active connection to the upstream, or support Guid.empty here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

endTime?: TimeInput
): ExecuteToolScope {
const agent = ScopeUtils.deriveAgentDetails(turnContext);
const agent = ScopeUtils.deriveAgentDetailsCore(turnContext, authToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are you getting the authtoken here? Im am worried about it becoming stale over time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The developer needs to provide the agent uer token when calling the method.

jsl517 and others added 2 commits February 26, 2026 17:57
…token blueprint claim for agentBlueprintId

- agentId now uses ResolveAgentIdentity (works for both app-based and blueprint agents)
- agentBlueprintId now reads xms_par_app_azp from token for blueprint agents
- Remove deprecated overloads and backward-compat code, authToken is now required
- Consolidate deriveAgentDetailsCore into deriveAgentDetails

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
const recipient = turnContext?.activity?.recipient;
if (!recipient) return undefined;
const agentId = RuntimeUtility.ResolveAgentIdentity(turnContext, authToken);
const agentBlueprintId = turnContext?.activity?.isAgenticRequest()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on this

Image

even in blueprint case it does not return the blueprintId, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. Based on the implementation of method, if it is blueprint case, it will call getAgenticInstanceId() which returns recipient.agenticAppId not blueprintId.

jsl517 and others added 2 commits February 27, 2026 14:27
…adoption

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gMiddleware

- Resolve merge conflicts in ScopeUtils.ts and CHANGELOG.md
- OutputLoggingMiddleware reads auth token from turnState (A365_AUTH_TOKEN_KEY)
  for non-agentic requests, passes empty string for agentic requests
- Add Activity helper methods to test mocks for new middleware tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fpfp100 fpfp100 marked this pull request as ready for review March 2, 2026 18:49
Copilot AI review requested due to automatic review settings March 2, 2026 18:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines 43 to 45
public static deriveTenantDetails(turnContext: TurnContext): TenantDetails | undefined {
const tenantId = turnContext?.activity?.recipient?.tenantId;
const tenantId = turnContext?.activity?.getAgenticTenantId();
return tenantId ? { tenantId } : undefined;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deriveTenantDetails calls activity.getAgenticTenantId() directly. If callers pass a TurnContext whose activity is a plain object (common in tests/mocks) and the helper isn’t attached, this will throw. Consider turnContext?.activity?.getAgenticTenantId?.() (and a fallback if needed).

Copilot uses AI. Check for mistakes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@nikhilNava nikhilNava left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BlueprintId is a critical identifier used for downstream analytics and reporting.

While it is technically possible to derive the BlueprintId on the server (since it may be present in the token), this is not currently enforced because the value is not guaranteed to exist on every token.

To ensure accuracy and intentionality, the I recommend BlueprintId should be explicitly set by the agent developer rather than inferred implicitly on the SDK or the server.

* Set this in `turnState` so middleware can resolve agent identity from token claims
* when the request is not an agentic request.
*/
export const A365_AUTH_TOKEN_KEY = 'A365AuthToken';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to reuse the TokenCache?
If possible id dont want to introduce another way to access the token.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, we might not know if the token for non‑embodied agent case has app id or not or the appid in the token claim is the right id for the agent or not. For implication and before that question is answered, I will not make any change to use the cached token.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static getAgentIdFromToken(token: string): string {

this implementation does not guarantee blueprint ID is always returned.
It can return app id as well.
If the middleware is used in non embodied scenario it will return incorrect results for blueprint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fine since the method is only called when the request is isAgenticRequest. For non embodied scenario, it always returns undefined since non embodied has no blueprint ID

const recipient = turnContext?.activity?.recipient;
if (!recipient) return undefined;
const agentId = RuntimeUtility.ResolveAgentIdentity(turnContext, authToken);
const agentBlueprintId = turnContext?.activity?.isAgenticRequest?.()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this turnContext?.activity?.isAgenticRequest?.() agent id so its only resolved in agentic scenarios

…rage

- Extract resolveEmbodiedAgentIds() into TurnContextUtils as shared helper
- Align getTargetAgentBaggagePairs with deriveAgentDetails (no agentId for non-agentic)
- Add resolveAuthToken to OutputLoggingMiddleware with per-request/cache dual path
- Export isPerRequestExportEnabled from observability public API
- Fix CHANGELOG to reflect actual agentId resolution behavior
- Add test coverage for resolveAuthToken (per-request, cache, fallback paths)
- Remove unused ResolveAgentIdentity mock from scope-utils tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 3, 2026 20:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts:226

  • In buildInvokeAgentDetailsCore, spreading ...agent after ...details can overwrite caller-provided values with undefined (e.g., details.agentId becomes undefined when deriveAgentDetails returns { agentId: undefined, ... }). This can silently drop required fields and lead to incorrect telemetry. Consider only overriding fields when the derived value is defined/non-empty, or have deriveAgentDetails return undefined (so the spread doesn’t occur) when no agent id can be derived.
  private static buildInvokeAgentDetailsCore(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails {
    const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken);
    const srcMetaFromContext = ScopeUtils.deriveSourceMetadataObject(turnContext);
    const baseRequest = details.request ?? {};
    const baseSource = baseRequest.sourceMetadata ?? {};
    const mergedSourceMetadata = {
      ...baseSource,
      ...(srcMetaFromContext.name !== undefined ? { name: srcMetaFromContext.name } : {}),
      ...(srcMetaFromContext.description !== undefined ? { description: srcMetaFromContext.description } : {}),
    };
    return {
      ...details,
      ...agent,
      conversationId: ScopeUtils.deriveConversationId(turnContext),
      request: {
        ...baseRequest,
        sourceMetadata: mergedSourceMetadata
      }
    };

Comment on lines +23 to +28
/**
* TurnState key for the auth token.
* Set this in `turnState` so middleware can resolve agent identity from token claims
* when the request is not an agentic request.
*/
export const A365_AUTH_TOKEN_KEY = 'A365AuthToken';
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment for A365_AUTH_TOKEN_KEY says the token is used “when the request is not an agentic request”, but ScopeUtils.deriveAgentDetails/resolveEmbodiedAgentIds currently only use authToken when activity.isAgenticRequest() is true (blueprint id resolution). Please update the comment (or the logic) so the intended usage matches the implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +81
return {
agentId: isAgentic ? turnContext.activity.getAgenticInstanceId?.() : undefined,
agentBlueprintId: isAgentic ? RuntimeUtility.getAgentIdFromToken(authToken) : undefined,
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveEmbodiedAgentIds can return empty strings (e.g., when getAgentIdFromToken receives an empty/invalid token, it returns ''). Downstream span tagging (setTagMaybe) will still set attributes for empty strings, which produces noisy/incorrect telemetry. Consider normalizing '' to undefined for both agentId and agentBlueprintId (and similarly treat getAgenticInstanceId() returning '' as undefined).

Suggested change
return {
agentId: isAgentic ? turnContext.activity.getAgenticInstanceId?.() : undefined,
agentBlueprintId: isAgentic ? RuntimeUtility.getAgentIdFromToken(authToken) : undefined,
const rawAgentId = isAgentic ? turnContext.activity.getAgenticInstanceId?.() : undefined;
const rawAgentBlueprintId = isAgentic ? RuntimeUtility.getAgentIdFromToken(authToken) : undefined;
const agentId = typeof rawAgentId === 'string' && rawAgentId.trim() !== '' ? rawAgentId : undefined;
const agentBlueprintId =
typeof rawAgentBlueprintId === 'string' && rawAgentBlueprintId.trim() !== '' ? rawAgentBlueprintId : undefined;
return {
agentId,
agentBlueprintId,

Copilot uses AI. Check for mistakes.
…mbodiedAgentIds

- Correct JSDoc for A365_AUTH_TOKEN_KEY: token is used for embodied/agentic
  requests (blueprint ID resolution), not non-agentic requests
- Normalize empty strings to undefined in resolveEmbodiedAgentIds to prevent
  noisy telemetry attributes from empty agentId/agentBlueprintId values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants