diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a1a9a8a..63ef94f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added +### Breaking Changes (`@microsoft/agents-a365-observability`) -- **`OpenAIAgentsInstrumentationConfig.isContentRecordingEnabled`** — Optional `boolean` to enable content recording in OpenAI trace processor. -- **`LangChainTraceInstrumentor.instrument(module, options?)`** — New optional `{ isContentRecordingEnabled?: boolean }` parameter to enable content recording in LangChain tracer. -- **`truncateValue`** / **`MAX_ATTRIBUTE_LENGTH`** — Exported utilities for attribute value truncation (8192 char limit). +- **`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. +- **`BaggageBuilder.sourceMetadataName()` renamed to `BaggageBuilder.channelName()`** — Fluent setter for the channel name baggage value. +- **`BaggageBuilder.sourceMetadataDescription()` renamed to `BaggageBuilder.channelLink()`** — Fluent setter for the channel link baggage value. +- **`InferenceScope.start()` parameter `sourceMetadata` renamed to `channel`** — Type changed from `Pick` to `Pick`. +- **`ExecuteToolScope.start()` parameter `sourceMetadata` renamed to `channel`** — Type changed from `Pick` to `Pick`. +- **`InvokeAgentScope`** now reads `request.channel` instead of `request.sourceMetadata` for channel name/link tags. ### Breaking Changes (`@microsoft/agents-a365-observability-hosting`) +- **`ScopeUtils.deriveSourceMetadataObject()` renamed to `ScopeUtils.deriveChannelObject()`**. +- **`BaggageBuilderUtils.setSourceMetadataBaggage()` renamed to `BaggageBuilderUtils.setChannelBaggage()`**. +- **`getSourceMetadataBaggagePairs()` renamed to `getChannelBaggagePairs()`** in `TurnContextUtils`. - **`ScopeUtils.deriveAgentDetails(turnContext, authToken)`** — New required `authToken: string` parameter. - **`ScopeUtils.populateInferenceScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter. - **`ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter. @@ -22,12 +30,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`ScopeUtils.buildInvokeAgentDetails(details, turnContext, authToken)`** — New required `authToken: string` parameter. ### Added -- **OutputScope**: Tracing scope for outgoing agent messages with caller details, conversation ID, source metadata, and parent span linking. + +- **`OpenAIAgentsInstrumentationConfig.isContentRecordingEnabled`** — Optional `boolean` to enable content recording in OpenAI trace processor. +- **`LangChainTraceInstrumentor.instrument(module, options?)`** — New optional `{ isContentRecordingEnabled?: boolean }` parameter to enable content recording in LangChain tracer. +- **`truncateValue`** / **`MAX_ATTRIBUTE_LENGTH`** — Exported utilities for attribute value truncation (8192 char limit). +- **OutputScope**: Tracing scope for outgoing agent messages with caller details, conversation ID, channel information, and parent span linking. - **BaggageMiddleware**: Middleware for automatic OpenTelemetry baggage propagation from TurnContext. - **OutputLoggingMiddleware**: Middleware that creates OutputScope spans for outgoing messages with lazy parent span linking via `A365_PARENT_SPAN_KEY`. - **ObservabilityHostingManager**: Manager for configuring hosting-layer observability middleware with `ObservabilityHostingOptions`. - -### Added - **Agent365ExporterOptions**: New `httpRequestTimeoutMilliseconds` option (default 30s) controls the per-HTTP-request timeout for backend calls. This is distinct from `exporterTimeoutMilliseconds` which controls the overall BatchSpanProcessor export deadline. ### Fixed @@ -86,7 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Agent-to-agent invocations - Error tracking and performance metrics - Request/response content (configurable) -- Source metadata and execution context +- Channel metadata and execution context ### Requirements - Node.js 18.0 or later diff --git a/packages/agents-a365-observability-hosting/docs/design.md b/packages/agents-a365-observability-hosting/docs/design.md index d9a17aba..9bc3da18 100644 --- a/packages/agents-a365-observability-hosting/docs/design.md +++ b/packages/agents-a365-observability-hosting/docs/design.md @@ -53,7 +53,7 @@ BaggageBuilderUtils.setCallerBaggage(builder, turnContext); BaggageBuilderUtils.setExecutionTypeBaggage(builder, turnContext); BaggageBuilderUtils.setTargetAgentBaggage(builder, turnContext); BaggageBuilderUtils.setTenantIdBaggage(builder, turnContext); -BaggageBuilderUtils.setSourceMetadataBaggage(builder, turnContext); +BaggageBuilderUtils.setChannelBaggage(builder, turnContext); BaggageBuilderUtils.setConversationIdBaggage(builder, turnContext); // Build and use the scope @@ -72,7 +72,7 @@ scope.run(() => { | `setExecutionTypeBaggage(builder, turnContext)` | Set execution type (HumanToAgent, Agent2Agent) | | `setTargetAgentBaggage(builder, turnContext)` | Set target agent ID, name, description | | `setTenantIdBaggage(builder, turnContext)` | Set tenant ID from recipient or channel data | -| `setSourceMetadataBaggage(builder, turnContext)` | Set channel name and subchannel | +| `setChannelBaggage(builder, turnContext)` | Set channel name and subchannel | | `setConversationIdBaggage(builder, turnContext)` | Set conversation ID and item link | ### TurnContextUtils ([TurnContextUtils.ts](../src/utils/TurnContextUtils.ts)) @@ -85,7 +85,7 @@ import { getExecutionTypePair, getTargetAgentBaggagePairs, getTenantIdPair, - getSourceMetadataBaggagePairs, + getChannelBaggagePairs, getConversationIdAndItemLinkPairs } from '@microsoft/agents-a365-observability-hosting'; @@ -110,7 +110,7 @@ const agentPairs = getTargetAgentBaggagePairs(turnContext); | `getExecutionTypePair()` | `gen_ai.execution.type` | | `getTargetAgentBaggagePairs()` | `gen_ai.agent.id`, `gen_ai.agent.name`, `gen_ai.agent.description`, `gen_ai.agent.auid` | | `getTenantIdPair()` | `tenant_id` | -| `getSourceMetadataBaggagePairs()` | `gen_ai.execution.source.name`, `gen_ai.execution.source.description` | +| `getChannelBaggagePairs()` | `gen_ai.execution.source.name`, `gen_ai.execution.source.description` | | `getConversationIdAndItemLinkPairs()` | `gen_ai.conversation.id`, `gen_ai.conversation.item_link` | ### AgenticTokenCacheInstance ([AgenticTokenCache.ts](../src/caching/AgenticTokenCache.ts)) diff --git a/packages/agents-a365-observability-hosting/src/middleware/BaggageMiddleware.ts b/packages/agents-a365-observability-hosting/src/middleware/BaggageMiddleware.ts index 344f67c8..48e5d563 100644 --- a/packages/agents-a365-observability-hosting/src/middleware/BaggageMiddleware.ts +++ b/packages/agents-a365-observability-hosting/src/middleware/BaggageMiddleware.ts @@ -9,7 +9,7 @@ import { getCallerBaggagePairs, getTargetAgentBaggagePairs, getTenantIdPair, - getSourceMetadataBaggagePairs, + getChannelBaggagePairs, getConversationIdAndItemLinkPairs, } from '../utils/TurnContextUtils'; @@ -33,7 +33,7 @@ export class BaggageMiddleware implements Middleware { .setPairs(getCallerBaggagePairs(context)) .setPairs(getTargetAgentBaggagePairs(context)) .setPairs(getTenantIdPair(context)) - .setPairs(getSourceMetadataBaggagePairs(context)) + .setPairs(getChannelBaggagePairs(context)) .setPairs(getConversationIdAndItemLinkPairs(context)) .setPairs(getExecutionTypePair(context)) .build(); diff --git a/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts b/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts index c7db34f8..3e6b429e 100644 --- a/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts +++ b/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts @@ -48,10 +48,10 @@ export class OutputLoggingMiddleware implements Middleware { const callerDetails = ScopeUtils.deriveCallerDetails(context); const conversationId = ScopeUtils.deriveConversationId(context); - const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(context); + const channel = ScopeUtils.deriveChannelObject(context); context.onSendActivities( - this._createSendHandler(context, agentDetails, tenantDetails, callerDetails, conversationId, sourceMetadata) + this._createSendHandler(context, agentDetails, tenantDetails, callerDetails, conversationId, channel) ); await next(); @@ -84,7 +84,7 @@ export class OutputLoggingMiddleware implements Middleware { tenantDetails: TenantDetails, callerDetails?: CallerDetails, conversationId?: string, - sourceMetadata?: { name?: string; description?: string }, + channel?: { name?: string; description?: string }, ): SendActivitiesHandler { return async (_ctx, activities, sendNext) => { const messages = activities @@ -108,7 +108,7 @@ export class OutputLoggingMiddleware implements Middleware { tenantDetails, callerDetails, conversationId, - sourceMetadata, + channel, parentSpanRef, ); try { diff --git a/packages/agents-a365-observability-hosting/src/utils/BaggageBuilderUtils.ts b/packages/agents-a365-observability-hosting/src/utils/BaggageBuilderUtils.ts index e32ddaec..05d45eb0 100644 --- a/packages/agents-a365-observability-hosting/src/utils/BaggageBuilderUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/BaggageBuilderUtils.ts @@ -9,7 +9,7 @@ import { getExecutionTypePair, getTargetAgentBaggagePairs, getTenantIdPair, - getSourceMetadataBaggagePairs, + getChannelBaggagePairs, getConversationIdAndItemLinkPairs } from './TurnContextUtils'; import { BaggageBuilder } from '@microsoft/agents-a365-observability'; @@ -34,7 +34,7 @@ export class BaggageBuilderUtils { this.setExecutionTypeBaggage(builder, turnContext); this.setTargetAgentBaggage(builder, turnContext); this.setTenantIdBaggage(builder, turnContext); - this.setSourceMetadataBaggage(builder, turnContext); + this.setChannelBaggage(builder, turnContext); this.setConversationIdBaggage(builder, turnContext); return builder; } @@ -88,13 +88,13 @@ export class BaggageBuilderUtils { /** - * Sets the source metadata baggage values from the TurnContext. + * Sets the channel baggage values from the TurnContext. * @param builder The BaggageBuilder instance. * @param turnContext The TurnContext containing activity information. * @returns The updated BaggageBuilder instance. */ - static setSourceMetadataBaggage(builder: BaggageBuilder, turnContext: TurnContext): BaggageBuilder { - builder.setPairs(getSourceMetadataBaggagePairs(turnContext)); + static setChannelBaggage(builder: BaggageBuilder, turnContext: TurnContext): BaggageBuilder { + builder.setPairs(getChannelBaggagePairs(turnContext)); return builder; } diff --git a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts index 63a1a306..7d05868a 100644 --- a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts @@ -118,11 +118,11 @@ export class ScopeUtils { } /** - * Derive source metadata (channel name and description/link) from the TurnContext. + * Derive channel (name and description) from the TurnContext. * @param turnContext Activity context * @returns Object with optional name and description fields. */ - public static deriveSourceMetadataObject(turnContext: TurnContext): { name?: string; description?: string } { + public static deriveChannelObject(turnContext: TurnContext): { name?: string; description?: string } { return { name: turnContext?.activity?.channelId, description: turnContext?.activity?.channelIdSubChannel as string | undefined @@ -131,7 +131,7 @@ export class ScopeUtils { /** * Create an `InferenceScope` using `details` and values derived from the provided `TurnContext`. - * Derives `agentDetails`, `tenantDetails`, `conversationId`, and `sourceMetadata` (channel name/link) from context. + * Derives `agentDetails`, `tenantDetails`, `conversationId`, and `channel` (name/description) from context. * Also records input messages from the context if present. * @param details The inference call details (model, provider, tokens, etc.). * @param turnContext The current activity context to derive scope parameters from. @@ -150,7 +150,7 @@ export class ScopeUtils { const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); const tenant = ScopeUtils.deriveTenantDetails(turnContext); const conversationId = ScopeUtils.deriveConversationId(turnContext); - const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext); + const channel = ScopeUtils.deriveChannelObject(turnContext); if (!agent) { throw new Error('populateInferenceScopeFromTurnContext: Missing agent details on TurnContext (recipient)'); @@ -159,14 +159,14 @@ export class ScopeUtils { throw new Error('populateInferenceScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); } - const scope = InferenceScope.start(details, agent, tenant, conversationId, sourceMetadata, undefined, startTime, endTime); + const scope = InferenceScope.start(details, agent, tenant, conversationId, channel, undefined, startTime, endTime); this.setInputMessageTags(scope, turnContext); return scope; } /** * Create an `InvokeAgentScope` using `details` and values derived from the provided `TurnContext`. - * Populates `conversationId` and `request.sourceMetadata` (channel name/link) in `details` from the `TurnContext`, overriding any existing values. + * Populates `conversationId` and `request.channel` (name/link) in `details` from the `TurnContext`, overriding any existing values. * Derives `tenantDetails`, `callerAgentDetails` (from caller), and `callerDetails` (from user). * Also sets execution type and input messages from the context if present. * @param details The invoke-agent call details to be augmented and used for the scope. @@ -200,7 +200,7 @@ export class ScopeUtils { } /** - * Build InvokeAgentDetails by merging provided details with agent info, conversation id and source metadata from the TurnContext. + * Build InvokeAgentDetails by merging provided details with agent info, conversation id and channel from the TurnContext. * @param details Base invoke-agent details to augment * @param turnContext Activity context * @param authToken Auth token for resolving agent identity from token claims. @@ -212,13 +212,13 @@ export class ScopeUtils { private static buildInvokeAgentDetailsCore(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails { const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); - const srcMetaFromContext = ScopeUtils.deriveSourceMetadataObject(turnContext); + const channelFromContext = ScopeUtils.deriveChannelObject(turnContext); const baseRequest = details.request ?? {}; - const baseSource = baseRequest.sourceMetadata ?? {}; - const mergedSourceMetadata = { - ...baseSource, - ...(srcMetaFromContext.name !== undefined ? { name: srcMetaFromContext.name } : {}), - ...(srcMetaFromContext.description !== undefined ? { description: srcMetaFromContext.description } : {}), + const baseChannel = baseRequest.channel ?? {}; + const mergedChannel = { + ...baseChannel, + ...(channelFromContext.name !== undefined ? { name: channelFromContext.name } : {}), + ...(channelFromContext.description !== undefined ? { description: channelFromContext.description } : {}), }; return { ...details, @@ -226,14 +226,14 @@ export class ScopeUtils { conversationId: ScopeUtils.deriveConversationId(turnContext), request: { ...baseRequest, - sourceMetadata: mergedSourceMetadata + channel: mergedChannel } }; } /** * Create an `ExecuteToolScope` using `details` and values derived from the provided `TurnContext`. - * Derives `agentDetails`, `tenantDetails`, `conversationId`, and `sourceMetadata` (channel name/link) from context. + * Derives `agentDetails`, `tenantDetails`, `conversationId`, and `channel` (name/link) from context. * @param details The tool call details (name, type, args, call id, etc.). * @param turnContext The current activity context to derive scope parameters from. * @param authToken Auth token for resolving agent identity from token claims. @@ -254,14 +254,14 @@ export class ScopeUtils { const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); const tenant = ScopeUtils.deriveTenantDetails(turnContext); const conversationId = ScopeUtils.deriveConversationId(turnContext); - const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext); + const channel = ScopeUtils.deriveChannelObject(turnContext); if (!agent) { throw new Error('populateExecuteToolScopeFromTurnContext: Missing agent details on TurnContext (recipient)'); } if (!tenant) { throw new Error('populateExecuteToolScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); } - const scope = ExecuteToolScope.start(details, agent, tenant, conversationId, sourceMetadata, undefined, startTime, endTime, undefined, spanKind); + const scope = ExecuteToolScope.start(details, agent, tenant, conversationId, channel, undefined, startTime, endTime, undefined, spanKind); return scope; } diff --git a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts index dd23ef28..daa08c7b 100644 --- a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts @@ -118,11 +118,11 @@ export function getTenantIdPair(turnContext: TurnContext): Array<[string, string } /** - * Extracts source metadata baggage pairs from the TurnContext. + * Extracts channel baggage pairs from the TurnContext. * @param turnContext The current TurnContext (activity context) - * @returns Array of [key, value] pairs for channel name and link + * @returns Array of [key, value] pairs for channel name and subchannel description */ -export function getSourceMetadataBaggagePairs(turnContext: TurnContext): Array<[string, string]> { +export function getChannelBaggagePairs(turnContext: TurnContext): Array<[string, string]> { if (!turnContext) { return []; } diff --git a/packages/agents-a365-observability/docs/design.md b/packages/agents-a365-observability/docs/design.md index 341f1d3b..7a9c48ba 100644 --- a/packages/agents-a365-observability/docs/design.md +++ b/packages/agents-a365-observability/docs/design.md @@ -156,7 +156,7 @@ scope.recordOutputMessages(['Hi there!']); **Span attributes recorded:** - Server address and port - Session ID -- Execution type and source metadata +- Execution type and channel - Input/output messages - Caller details (ID, UPN, name, tenant, client IP) - Caller agent details (if agent-to-agent) @@ -177,7 +177,7 @@ using scope = InferenceScope.start( agentDetails, tenantDetails, conversationId, - sourceMetadata + channel ); scope.recordInputMessages(['User message']); @@ -207,7 +207,7 @@ using scope = ExecuteToolScope.start( agentDetails, tenantDetails, conversationId, - sourceMetadata + channel ); // ... tool execution ... @@ -258,7 +258,9 @@ const scope2 = BaggageBuilder.setRequestContext( | `sessionId(value)` | `session_id` | | `conversationId(value)` | `gen_ai.conversation.id` | | `callerUpn(value)` | `gen_ai.caller.upn` | -| `sourceMetadataName(value)` | `gen_ai.execution.source.name` | +| `operationSource(value)` | `service.name` | +| `channelName(value)` | `gen_ai.execution.source.name` | +| `channelLink(value)` | `gen_ai.execution.source.description` | ## Data Interfaces diff --git a/packages/agents-a365-observability/src/index.ts b/packages/agents-a365-observability/src/index.ts index 3ff38e27..2f4fe96a 100644 --- a/packages/agents-a365-observability/src/index.ts +++ b/packages/agents-a365-observability/src/index.ts @@ -32,7 +32,7 @@ export { export { ExecutionType, InvocationRole, - SourceMetadata, + Channel, AgentRequest, AgentDetails, TenantDetails, diff --git a/packages/agents-a365-observability/src/tracing/contracts.ts b/packages/agents-a365-observability/src/tracing/contracts.ts index ae499d27..5cab41b4 100644 --- a/packages/agents-a365-observability/src/tracing/contracts.ts +++ b/packages/agents-a365-observability/src/tracing/contracts.ts @@ -48,22 +48,22 @@ export enum InferenceOperationType { /** - * Represents metadata about the source of an invocation + * Represents channel for an invocation */ -export interface SourceMetadata { - /** Unique identifier for the source (e.g., agent ID, user ID, system component ID) */ +export interface Channel { + /** Unique identifier for the channel (e.g., Teams channel ID, Slack workspace ID) */ id?: string; - /** Human-readable name of the source */ + /** Human-readable name of the channel (e.g., "Teams", "Slack", "web") */ name?: string; - /** Optional icon identifier or URL for visual representation of the source */ + /** Optional icon identifier or URL for visual representation of the channel */ iconUri?: string; - /** The role of the source invoking the agent */ + /** The role of the entity invoking through this channel */ role?: InvocationRole; - /** Optional description providing additional context about the source */ + /** Optional description or link providing additional context about the channel */ description?: string; } @@ -80,8 +80,8 @@ export interface AgentRequest { /** Optional session identifier for grouping related requests */ sessionId?: string; - /** Optional metadata about the source of the invocation */ - sourceMetadata?: SourceMetadata; + /** Optional channel for the invocation */ + channel?: Channel; } /** diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 6bf4fa13..9e69097a 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -29,12 +29,12 @@ export class BaggageBuilder { private pairs: Map = new Map(); /** - * Set the service name baggage value. + * Set the operation source baggage value. * Used for server spans to identify the service (e.g., ATG, ACF). - * @param value The service name + * @param value The operation source * @returns Self for method chaining */ - serviceName(value: string | null | undefined): BaggageBuilder { + operationSource(value: string | null | undefined): BaggageBuilder { this.set(OpenTelemetryConstants.SERVICE_NAME_KEY, value); return this; } @@ -216,7 +216,7 @@ export class BaggageBuilder { * @param value The channel name * @returns Self for method chaining */ - sourceMetadataName(value: string | null | undefined): BaggageBuilder { + channelName(value: string | null | undefined): BaggageBuilder { this.set(OpenTelemetryConstants.CHANNEL_NAME_KEY, value); return this; } @@ -226,7 +226,7 @@ export class BaggageBuilder { * @param value The channel link * @returns Self for method chaining */ - sourceMetadataDescription(value: string | null | undefined): BaggageBuilder { + channelLink(value: string | null | undefined): BaggageBuilder { this.set(OpenTelemetryConstants.CHANNEL_LINK_KEY, value); return this; } diff --git a/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts b/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts index 6971c16b..428d475f 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts @@ -3,7 +3,7 @@ import { SpanKind, TimeInput } from '@opentelemetry/api'; import { OpenTelemetryScope } from './OpenTelemetryScope'; -import { ToolCallDetails, AgentDetails, TenantDetails, SourceMetadata, CallerDetails } from '../contracts'; +import { ToolCallDetails, AgentDetails, TenantDetails, Channel, CallerDetails } from '../contracts'; import { ParentContext } from '../context/trace-context-propagation'; import { OpenTelemetryConstants } from '../constants'; @@ -17,7 +17,7 @@ export class ExecuteToolScope extends OpenTelemetryScope { * @param agentDetails The agent details * @param tenantDetails The tenant details * @param conversationId Optional conversation id to tag on the span (`gen_ai.conversation.id`). - * @param sourceMetadata Optional source metadata; only `name` (channel name) and `description` (channel link/URL) are used for tagging. + * @param channel Optional channel; only `name` (channel name) and `description` (channel link/URL) are used for tagging. * @param parentContext Optional parent context for cross-async-boundary tracing. * Accepts a ParentSpanRef (manual traceId/spanId) or an OTel Context (e.g. from extractTraceContext). * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). Useful when recording a @@ -34,14 +34,14 @@ export class ExecuteToolScope extends OpenTelemetryScope { agentDetails: AgentDetails, tenantDetails: TenantDetails, conversationId?: string, - sourceMetadata?: Pick, + channel?: Pick, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput, callerDetails?: CallerDetails, spanKind?: SpanKind ): ExecuteToolScope { - return new ExecuteToolScope(details, agentDetails, tenantDetails, conversationId, sourceMetadata, parentContext, startTime, endTime, callerDetails, spanKind); + return new ExecuteToolScope(details, agentDetails, tenantDetails, conversationId, channel, parentContext, startTime, endTime, callerDetails, spanKind); } private constructor( @@ -49,7 +49,7 @@ export class ExecuteToolScope extends OpenTelemetryScope { agentDetails: AgentDetails, tenantDetails: TenantDetails, conversationId?: string, - sourceMetadata?: Pick, + channel?: Pick, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput, @@ -77,8 +77,8 @@ export class ExecuteToolScope extends OpenTelemetryScope { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_CALL_ID_KEY, toolCallId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_DESCRIPTION_KEY, description); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, conversationId); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, sourceMetadata?.name); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, sourceMetadata?.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, channel?.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, channel?.description); // Set endpoint information if provided @@ -87,7 +87,7 @@ export class ExecuteToolScope extends OpenTelemetryScope { // Only record port if it is different from 443 (default HTTPS port) if (endpoint.port && endpoint.port !== 443) { - this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, endpoint.port.toString()); + this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, endpoint.port); } } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts b/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts index 569d6792..4a2e394c 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts @@ -8,7 +8,7 @@ import { InferenceDetails, AgentDetails, TenantDetails, - SourceMetadata, + Channel, CallerDetails } from '../contracts'; import { ParentContext } from '../context/trace-context-propagation'; @@ -23,7 +23,7 @@ export class InferenceScope extends OpenTelemetryScope { * @param agentDetails The agent details * @param tenantDetails The tenant details * @param conversationId Optional conversation id to tag on the span (`gen_ai.conversation.id`). - * @param sourceMetadata Optional source metadata; only `name` (channel name) and `description` (channel link/URL) are used for tagging. + * @param channel Optional channel; only `name` (channel name) and `description` (channel link/URL) are used for tagging. * @param parentContext Optional parent context for cross-async-boundary tracing. * Accepts a ParentSpanRef (manual traceId/spanId) or an OTel Context (e.g. from extractTraceContext). * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). @@ -36,13 +36,13 @@ export class InferenceScope extends OpenTelemetryScope { agentDetails: AgentDetails, tenantDetails: TenantDetails, conversationId?: string, - sourceMetadata?: Pick, + channel?: Pick, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput, callerDetails?: CallerDetails ): InferenceScope { - return new InferenceScope(details, agentDetails, tenantDetails, conversationId, sourceMetadata, parentContext, startTime, endTime, callerDetails); + return new InferenceScope(details, agentDetails, tenantDetails, conversationId, channel, parentContext, startTime, endTime, callerDetails); } private constructor( @@ -50,7 +50,7 @@ export class InferenceScope extends OpenTelemetryScope { agentDetails: AgentDetails, tenantDetails: TenantDetails, conversationId?: string, - sourceMetadata?: Pick, + channel?: Pick, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput, @@ -77,8 +77,8 @@ export class InferenceScope extends OpenTelemetryScope { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_FINISH_REASONS_KEY, details.finishReasons); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_THOUGHT_PROCESS_KEY, details.thoughtProcess); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, conversationId); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, sourceMetadata?.name); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, sourceMetadata?.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, channel?.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, channel?.description); // Set endpoint information if provided if (details.endpoint) { @@ -86,7 +86,7 @@ export class InferenceScope extends OpenTelemetryScope { // Only record port if it is different from 443 (default HTTPS port) if (details.endpoint.port && details.endpoint.port !== 443) { - this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, details.endpoint.port.toString()); + this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, details.endpoint.port); } } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts b/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts index 32e10c1a..c5537e19 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts @@ -80,16 +80,16 @@ export class InvokeAgentScope extends OpenTelemetryScope { // Only record port if it is different from 443 (default HTTPS port) if (invokeAgentDetails.endpoint.port && invokeAgentDetails.endpoint.port !== 443) { - this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, invokeAgentDetails.endpoint.port.toString()); + this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, invokeAgentDetails.endpoint.port); } } // Set request-related tags const requestToUse = invokeAgentDetails.request; if (requestToUse) { - if (requestToUse.sourceMetadata) { - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, requestToUse.sourceMetadata.name); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, requestToUse.sourceMetadata.description); + if (requestToUse.channel) { + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, requestToUse.channel.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, requestToUse.channel.description); } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts b/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts index 9e50c119..8f0036b3 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts @@ -3,7 +3,7 @@ import { SpanKind, TimeInput } from '@opentelemetry/api'; import { OpenTelemetryScope } from './OpenTelemetryScope'; -import { AgentDetails, TenantDetails, CallerDetails, OutputResponse, SourceMetadata } from '../contracts'; +import { AgentDetails, TenantDetails, CallerDetails, OutputResponse, Channel } from '../contracts'; import { ParentContext } from '../context/trace-context-propagation'; import { OpenTelemetryConstants } from '../constants'; @@ -21,7 +21,7 @@ export class OutputScope extends OpenTelemetryScope { * @param tenantDetails The tenant details. * @param callerDetails Optional caller identity details (id, upn, name, client ip). * @param conversationId Optional conversation identifier. - * @param sourceMetadata Optional source metadata; only `name` and `description` are used for tagging. + * @param channel Optional channel metadata; only `name` and `description` are used for tagging. * @param parentContext Optional parent context for cross-async-boundary tracing. * Accepts a ParentSpanRef (manual traceId/spanId) or an OTel Context (e.g. from extractTraceContext). * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). @@ -34,12 +34,12 @@ export class OutputScope extends OpenTelemetryScope { tenantDetails: TenantDetails, callerDetails?: CallerDetails, conversationId?: string, - sourceMetadata?: Pick, + channel?: Pick, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput ): OutputScope { - return new OutputScope(response, agentDetails, tenantDetails, callerDetails, conversationId, sourceMetadata, parentContext, startTime, endTime); + return new OutputScope(response, agentDetails, tenantDetails, callerDetails, conversationId, channel, parentContext, startTime, endTime); } private constructor( @@ -48,7 +48,7 @@ export class OutputScope extends OpenTelemetryScope { tenantDetails: TenantDetails, callerDetails?: CallerDetails, conversationId?: string, - sourceMetadata?: Pick, + channel?: Pick, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput @@ -76,10 +76,10 @@ export class OutputScope extends OpenTelemetryScope { JSON.stringify(this._outputMessages) ); - // Set conversation, execution type, and source metadata + // Set conversation and channel this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, conversationId); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, sourceMetadata?.name); - this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, sourceMetadata?.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, channel?.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, channel?.description); } diff --git a/tests/observability/core/BaggageBuilder.test.ts b/tests/observability/core/BaggageBuilder.test.ts index 0c1dacf0..1bdd751d 100644 --- a/tests/observability/core/BaggageBuilder.test.ts +++ b/tests/observability/core/BaggageBuilder.test.ts @@ -130,6 +130,20 @@ describe('BaggageBuilder', () => { }); }); + describe('operationSource, channelName, and channelLink', () => { + it.each([ + ['operationSource', 'ATG', OpenTelemetryConstants.SERVICE_NAME_KEY], + ['channelName', 'teams', OpenTelemetryConstants.CHANNEL_NAME_KEY], + ['channelLink', 'https://teams/channel', OpenTelemetryConstants.CHANNEL_LINK_KEY], + ] as const)('%s should set the correct baggage key', (method, value, expectedKey) => { + const builder = new BaggageBuilder(); + (builder as any)[method](value); + const scope = builder.build(); + const bag = propagation.getBaggage((scope as any).contextWithBaggage); + expect(bag?.getEntry(expectedKey)?.value).toBe(value); + }); + }); + describe('setRequestContext static method', () => { it('should create scope with common fields', () => { const scope = BaggageBuilder.setRequestContext( diff --git a/tests/observability/core/scopes.test.ts b/tests/observability/core/scopes.test.ts index 625cdfb4..8de343bd 100644 --- a/tests/observability/core/scopes.test.ts +++ b/tests/observability/core/scopes.test.ts @@ -262,37 +262,108 @@ describe('Scopes', () => { scope?.dispose(); }); - it('should set conversationId when provided', () => { + it('should set conversationId and channel tags when provided', () => { const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); - const scope = (ExecuteToolScope as unknown as any).start({ toolName: 'test-tool' }, testAgentDetails, testTenantDetails, 'conv-tool-123'); + const scope = (ExecuteToolScope as unknown as any).start({ toolName: 'test-tool' }, testAgentDetails, testTenantDetails, 'conv-tool-123', { name: 'ChannelTool', description: 'https://channel/tool' }); 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_CONVERSATION_ID_KEY, val: 'conv-tool-123' }) + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, val: 'conv-tool-123' }), + expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_NAME_KEY, val: 'ChannelTool' }), + expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_LINK_KEY, val: 'https://channel/tool' }) ])); scope?.dispose(); spy.mockRestore(); }); + }); - it('should set source metadata tags when provided', () => { + describe('endpoint.port serialization', () => { + it('should record non-443 port as a number on ExecuteToolScope', () => { const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); - const scope = (ExecuteToolScope as unknown as any).start({ toolName: 'test-tool' }, testAgentDetails, testTenantDetails, undefined, { name: 'ChannelTool', description: 'https://channel/tool' }); - expect(scope).toBeInstanceOf(ExecuteToolScope); + const scope = ExecuteToolScope.start( + { toolName: 'test-tool', endpoint: { host: 'tools.example.com', port: 8080 } }, + testAgentDetails, testTenantDetails + ); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.SERVER_PORT_KEY, val: 8080 }) + ])); + scope?.dispose(); + spy.mockRestore(); + }); + + it('should omit port 443 on ExecuteToolScope', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const scope = ExecuteToolScope.start( + { toolName: 'test-tool', endpoint: { host: 'tools.example.com', port: 443 } }, + testAgentDetails, testTenantDetails + ); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.SERVER_PORT_KEY }) + ])); + scope?.dispose(); + spy.mockRestore(); + }); + it('should record non-443 port as a number on InferenceScope', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const scope = InferenceScope.start( + { operationName: InferenceOperationType.CHAT, model: 'gpt-4', endpoint: { host: 'api.openai.com', port: 8443 } }, + testAgentDetails, testTenantDetails + ); const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); expect(calls).toEqual(expect.arrayContaining([ - expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_NAME_KEY, val: 'ChannelTool' }), - expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_LINK_KEY, val: 'https://channel/tool' }) + expect.objectContaining({ key: OpenTelemetryConstants.SERVER_PORT_KEY, val: 8443 }) ])); + scope?.dispose(); + spy.mockRestore(); + }); + it('should omit port 443 on InferenceScope', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const scope = InferenceScope.start( + { operationName: InferenceOperationType.CHAT, model: 'gpt-4', endpoint: { host: 'api.openai.com', port: 443 } }, + testAgentDetails, testTenantDetails + ); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.SERVER_PORT_KEY }) + ])); scope?.dispose(); spy.mockRestore(); }); - }); + it('should record non-443 port as a number on InvokeAgentScope', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const scope = InvokeAgentScope.start( + { agentId: 'test-agent', endpoint: { host: 'agent.example.com', port: 9090 } }, + testTenantDetails + ); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.SERVER_PORT_KEY, val: 9090 }) + ])); + scope?.dispose(); + spy.mockRestore(); + }); + it('should omit port 443 on InvokeAgentScope', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const scope = InvokeAgentScope.start( + { agentId: 'test-agent', endpoint: { host: 'agent.example.com', port: 443 } }, + testTenantDetails + ); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.SERVER_PORT_KEY }) + ])); + scope?.dispose(); + spy.mockRestore(); + }); + }); describe('InferenceScope', () => { it('should create scope with inference details', () => { @@ -359,37 +430,19 @@ describe('Scopes', () => { scope?.dispose(); }); - it('should set conversationId when provided', () => { - const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); - const inferenceDetails: InferenceDetails = { - operationName: InferenceOperationType.CHAT, - model: 'gpt-4' - }; - - const scope = (InferenceScope as unknown as any).start(inferenceDetails, testAgentDetails, testTenantDetails, 'conv-inf-123'); - 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_CONVERSATION_ID_KEY, val: 'conv-inf-123' }) - ])); - - scope?.dispose(); - spy.mockRestore(); - }); - - it('should set source metadata tags when provided', () => { + it('should set conversationId and channel tags when provided', () => { const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); const inferenceDetails: InferenceDetails = { operationName: InferenceOperationType.CHAT, model: 'gpt-4' }; - const scope = (InferenceScope as unknown as any).start(inferenceDetails, testAgentDetails, testTenantDetails, undefined, { name: 'ChannelInf', description: 'https://channel/inf' }); + const scope = (InferenceScope as unknown as any).start(inferenceDetails, testAgentDetails, testTenantDetails, 'conv-inf-123', { name: 'ChannelInf', description: 'https://channel/inf' }); 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_CONVERSATION_ID_KEY, val: 'conv-inf-123' }), expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_NAME_KEY, val: 'ChannelInf' }), expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_LINK_KEY, val: 'https://channel/inf' }) ])); diff --git a/tests/observability/extension/hosting/TurnContextUtils.test.ts b/tests/observability/extension/hosting/TurnContextUtils.test.ts index d2abf2b6..060bd2e2 100644 --- a/tests/observability/extension/hosting/TurnContextUtils.test.ts +++ b/tests/observability/extension/hosting/TurnContextUtils.test.ts @@ -8,7 +8,7 @@ import { getExecutionTypePair, getTargetAgentBaggagePairs, getTenantIdPair, - getSourceMetadataBaggagePairs, + getChannelBaggagePairs, getConversationIdAndItemLinkPairs } from '@microsoft/agents-a365-observability-hosting'; import { OpenTelemetryConstants, ExecutionType } from '@microsoft/agents-a365-observability'; @@ -59,8 +59,8 @@ describe('TurnContextUtils', () => { expect(pairs.length).toBeGreaterThan(0); }); - it('should get source metadata baggage pairs', () => { - const pairs = getSourceMetadataBaggagePairs(mockTurnContext); + it('should get channel baggage pairs', () => { + const pairs = getChannelBaggagePairs(mockTurnContext); expect(Array.isArray(pairs)).toBe(true); }); diff --git a/tests/observability/extension/hosting/scope-utils.test.ts b/tests/observability/extension/hosting/scope-utils.test.ts index 6f02c6a3..96ccf6bb 100644 --- a/tests/observability/extension/hosting/scope-utils.test.ts +++ b/tests/observability/extension/hosting/scope-utils.test.ts @@ -261,20 +261,20 @@ test('deriveConversationId returns undefined when missing', () => { expect(ScopeUtils.deriveConversationId(ctx)).toBeUndefined(); }); -test('deriveSourceMetadataObject maps channel name/description', () => { +test('deriveChannelObject maps channel name/description', () => { const ctx = makeCtx({ activity: { channelId: 'teams', channelIdSubChannel: 'chat' } as any }); - expect(ScopeUtils.deriveSourceMetadataObject(ctx)).toEqual({ name: 'teams', description: 'chat' }); + expect(ScopeUtils.deriveChannelObject(ctx)).toEqual({ name: 'teams', description: 'chat' }); }); -test('deriveSourceMetadataObject returns undefined fields when none', () => { +test('deriveChannelObject returns undefined fields when none', () => { const ctx = makeCtx({ activity: {} as any }); - expect(ScopeUtils.deriveSourceMetadataObject(ctx)).toEqual({ name: undefined, description: undefined }); + expect(ScopeUtils.deriveChannelObject(ctx)).toEqual({ name: undefined, description: undefined }); }); -test('buildInvokeAgentDetails merges agent (recipient), conversationId, sourceMetadata', () => { +test('buildInvokeAgentDetails merges agent (recipient), conversationId, channel', () => { const invokeAgentDetails: InvokeAgentDetails = { agentId: 'provided', - request: { content: 'hi', executionType: ExecutionType.HumanToAgent, sourceMetadata: { id: 'orig-id' } }, + request: { content: 'hi', executionType: ExecutionType.HumanToAgent, channel: { id: 'orig-id' } }, }; const ctx = makeCtx({ activity: { @@ -292,19 +292,19 @@ test('buildInvokeAgentDetails merges agent (recipient), conversationId, sourceMe const result = ScopeUtils.buildInvokeAgentDetails(invokeAgentDetails, ctx, testAuthToken); expect(result.agentId).toBeUndefined(); expect(result.conversationId).toBe('c-2'); - expect(result.request?.sourceMetadata).toEqual({ id: 'orig-id', name: 'web', description: 'inbox' }); + expect(result.request?.channel).toEqual({ id: 'orig-id', name: 'web', description: 'inbox' }); }); test('buildInvokeAgentDetails keeps base request when TurnContext has no overrides', () => { const invokeAgentDetails: InvokeAgentDetails = { agentId: 'base-agent', - request: { content: 'hi', executionType: ExecutionType.HumanToAgent, sourceMetadata: { description: 'keep', name: 'keep-name' }}, + request: { content: 'hi', executionType: ExecutionType.HumanToAgent, channel: { description: 'keep', name: 'keep-name' }}, }; const ctx = makeCtx({ activity: {} as any }); const result = ScopeUtils.buildInvokeAgentDetails(invokeAgentDetails, ctx, testAuthToken); expect(result.agentId).toBe('base-agent'); expect(result.conversationId).toBeUndefined(); - expect(result.request?.sourceMetadata).toEqual({ description: 'keep', name: 'keep-name' }); + expect(result.request?.channel).toEqual({ description: 'keep', name: 'keep-name' }); }); describe('ScopeUtils spanKind forwarding', () => {