diff --git a/CHANGELOG.md b/CHANGELOG.md index 334eadff..7488c42b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to the Agent365 TypeScript SDK will be documented in this fi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Breaking Changes (`@microsoft/agents-a365-observability-hosting`) + +- **`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. +- **`ScopeUtils.populateExecuteToolScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter. +- **`ScopeUtils.buildInvokeAgentDetails(details, turnContext, authToken)`** — New required `authToken: string` parameter. + +### Changed (`@microsoft/agents-a365-observability-hosting`) + +- `ScopeUtils.deriveAgentDetails` now resolves `agentId` via `RuntimeUtility.ResolveAgentIdentity()` (works for both app-based and blueprint-based agents) instead of reading `recipient.agenticAppId` directly. +- `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.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. + ## [1.1.0] - 2025-12-09 ### Changed diff --git a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts index 1f8531c3..9e18e28c 100644 --- a/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts @@ -17,6 +17,7 @@ import { ToolCallDetails, ExecutionType } from '@microsoft/agents-a365-observability'; +import { Utility as RuntimeUtility } from '@microsoft/agents-a365-runtime'; import { getExecutionTypePair, } from './TurnContextUtils'; @@ -44,26 +45,34 @@ export class ScopeUtils { * @returns Tenant details if a recipient tenant id is present; otherwise undefined. */ public static deriveTenantDetails(turnContext: TurnContext): TenantDetails | undefined { - const tenantId = turnContext?.activity?.recipient?.tenantId; + const tenantId = turnContext?.activity?.getAgenticTenantId(); return tenantId ? { tenantId } : undefined; } /** * Derive target agent details from the activity recipient. + * Uses {@link RuntimeUtility.ResolveAgentIdentity} to resolve the agent ID (instance ID for blueprint agents, + * appid/azp from token for app-based agents). For blueprint agents, the blueprint ID is resolved + * from the token's xms_par_app_azp claim via {@link RuntimeUtility.getAgentIdFromToken}. * @param turnContext Activity context + * @param authToken Auth token for resolving agent identity from token claims. * @returns Agent details built from recipient properties; otherwise undefined. */ - public static deriveAgentDetails(turnContext: TurnContext): AgentDetails | undefined { + public static deriveAgentDetails(turnContext: TurnContext, authToken: string): AgentDetails | undefined { const recipient = turnContext?.activity?.recipient; if (!recipient) return undefined; + const agentId = RuntimeUtility.ResolveAgentIdentity(turnContext, authToken); + const agentBlueprintId = turnContext?.activity?.isAgenticRequest() + ? RuntimeUtility.getAgentIdFromToken(authToken) + : undefined; return { - agentId: recipient.agenticAppId, + agentId, agentName: recipient.name, agentAUID: recipient.aadObjectId, - agentBlueprintId: recipient.agenticAppBlueprintId, - agentUPN: recipient.agenticUserId, + agentBlueprintId, + agentUPN: turnContext?.activity?.getAgenticUser(), agentDescription: recipient.role, - tenantId: recipient.tenantId + tenantId: turnContext?.activity?.getAgenticTenantId() } as AgentDetails; } @@ -131,6 +140,7 @@ export class ScopeUtils { * 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. + * @param authToken Auth token for resolving agent identity from token claims. * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). * @returns A started `InferenceScope` enriched with context-derived parameters. @@ -138,10 +148,11 @@ export class ScopeUtils { static populateInferenceScopeFromTurnContext( details: InferenceDetails, turnContext: TurnContext, + authToken: string, startTime?: TimeInput, endTime?: TimeInput ): InferenceScope { - const agent = ScopeUtils.deriveAgentDetails(turnContext); + const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); const tenant = ScopeUtils.deriveTenantDetails(turnContext); const conversationId = ScopeUtils.deriveConversationId(turnContext); const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext); @@ -165,6 +176,7 @@ export class ScopeUtils { * 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. * @param turnContext The current activity context to derive scope parameters from. + * @param authToken Auth token for resolving agent identity from token claims. * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). * @returns A started `InvokeAgentScope` enriched with context-derived parameters. @@ -172,13 +184,14 @@ export class ScopeUtils { static populateInvokeAgentScopeFromTurnContext( details: InvokeAgentDetails, turnContext: TurnContext, + authToken: string, startTime?: TimeInput, endTime?: TimeInput ): InvokeAgentScope { const tenant = ScopeUtils.deriveTenantDetails(turnContext); const callerAgent = ScopeUtils.deriveCallerAgent(turnContext); const caller = ScopeUtils.deriveCallerDetails(turnContext); - const invokeAgentDetails = ScopeUtils.buildInvokeAgentDetails(details, turnContext); + const invokeAgentDetails = ScopeUtils.buildInvokeAgentDetailsCore(details, turnContext, authToken); if (!tenant) { throw new Error('populateInvokeAgentScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); @@ -193,10 +206,15 @@ export class ScopeUtils { * Build InvokeAgentDetails by merging provided details with agent info, conversation id and source metadata 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. * @returns New InvokeAgentDetails suitable for starting an InvokeAgentScope. */ - public static buildInvokeAgentDetails(details: InvokeAgentDetails, turnContext: TurnContext): InvokeAgentDetails { - const agent = ScopeUtils.deriveAgentDetails(turnContext); + public static buildInvokeAgentDetails(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails { + return ScopeUtils.buildInvokeAgentDetailsCore(details, turnContext, authToken); + } + + private static buildInvokeAgentDetailsCore(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails { + const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); const srcMetaFromContext = ScopeUtils.deriveSourceMetadataObject(turnContext); const executionTypePair = getExecutionTypePair(turnContext); const baseRequest = details.request ?? {}; @@ -223,6 +241,7 @@ export class ScopeUtils { * Derives `agentDetails`, `tenantDetails`, `conversationId`, and `sourceMetadata` (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. * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). Useful when recording a * tool call after execution has already completed. * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). @@ -231,10 +250,11 @@ export class ScopeUtils { static populateExecuteToolScopeFromTurnContext( details: ToolCallDetails, turnContext: TurnContext, + authToken: string, startTime?: TimeInput, endTime?: TimeInput ): ExecuteToolScope { - const agent = ScopeUtils.deriveAgentDetails(turnContext); + const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); const tenant = ScopeUtils.deriveTenantDetails(turnContext); const conversationId = ScopeUtils.deriveConversationId(turnContext); const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext); diff --git a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts index 981d4ead..3c1ac425 100644 --- a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts @@ -70,11 +70,11 @@ export function getExecutionTypePair(turnContext: TurnContext): Array<[string, s * @returns Array of [key, value] pairs for agent identity and description */ export function getTargetAgentBaggagePairs(turnContext: TurnContext): Array<[string, string]> { - if (!turnContext || !turnContext.activity?.recipient) { + if (!turnContext || !turnContext.activity?.recipient) { return []; } - const recipient = turnContext.activity.recipient; - const agentId = recipient.agenticAppId; + const recipient = turnContext.activity.recipient; + const agentId = turnContext.activity?.getAgenticInstanceId(); const agentName = recipient.name; const aadObjectId = recipient.aadObjectId; const agentDescription = recipient.role; @@ -88,29 +88,12 @@ export function getTargetAgentBaggagePairs(turnContext: TurnContext): Array<[str } /** - * Extracts the tenant ID baggage key-value pair, attempting to retrieve from ChannelData if necessary. + * Extracts the tenant ID baggage key-value pair using the Activity's getAgenticTenantId() helper. * @param turnContext The current TurnContext (activity context) * @returns Array of [key, value] for tenant ID */ export function getTenantIdPair(turnContext: TurnContext): Array<[string, string]> { - let tenantId = turnContext.activity?.recipient?.tenantId; - - - // If not found, try to extract from channelData. Accepts both object and JSON string. - if (!tenantId && turnContext.activity?.channelData) { - try { - let channelData: unknown = turnContext.activity.channelData; - if (typeof channelData === 'string') { - channelData = JSON.parse(channelData); - } - if ( - typeof channelData === 'object' && channelData !== null) { - tenantId = (channelData as { tenant: { id?: string } })?.tenant?.id; - } - } catch (_err) { - // ignore JSON parse errors - } - } + const tenantId = turnContext.activity?.getAgenticTenantId(); return tenantId ? [[OpenTelemetryConstants.TENANT_ID_KEY, tenantId]] : []; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23cccb37..7cf4f654 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,11 +31,11 @@ catalogs: specifier: ^1.1.1 version: 1.1.1 '@microsoft/agents-activity': - specifier: ^1.1.0-alpha.85 - version: 1.1.0-alpha.85 + specifier: ^1.3.1 + version: 1.3.1 '@microsoft/agents-hosting': - specifier: ^1.1.0-alpha.85 - version: 1.1.0-alpha.85 + specifier: ^1.3.1 + version: 1.3.1 '@modelcontextprotocol/sdk': specifier: ^1.25.2 version: 1.25.2 @@ -69,6 +69,9 @@ catalogs: '@opentelemetry/semantic-conventions': specifier: ^1.37.0 version: 1.38.0 + '@types/express': + specifier: ^5.0.0 + version: 5.0.6 '@types/jest': specifier: ^30.0.0 version: 30.0.0 @@ -173,10 +176,10 @@ importers: version: link:../agents-a365-runtime '@microsoft/agents-activity': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 devDependencies: '@eslint/js': specifier: 'catalog:' @@ -393,7 +396,7 @@ importers: version: link:../agents-a365-runtime '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 '@opentelemetry/api': specifier: 'catalog:' version: 1.9.0 @@ -439,7 +442,7 @@ importers: version: 4.13.0 '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 jsonwebtoken: specifier: 'catalog:' version: 9.0.3 @@ -488,7 +491,7 @@ importers: version: link:../agents-a365-runtime '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.1.13) @@ -546,7 +549,7 @@ importers: version: link:../agents-a365-tooling '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.1.13) @@ -604,7 +607,7 @@ importers: version: link:../agents-a365-tooling '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 hono: specifier: ^4.11.7 version: 4.11.7 @@ -662,7 +665,7 @@ importers: version: link:../agents-a365-tooling '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 '@openai/agents': specifier: 'catalog:' version: 0.4.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(ws@8.18.3)(zod@4.1.13) @@ -732,7 +735,7 @@ importers: version: link:../packages/agents-a365-runtime '@microsoft/agents-hosting': specifier: 'catalog:' - version: 1.1.0-alpha.85 + version: 1.3.1 '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.1.13) @@ -852,11 +855,11 @@ importers: specifier: workspace:* version: link:../../packages/agents-a365-tooling-extensions-langchain '@microsoft/agents-activity': - specifier: ^1.1.0-alpha.85 - version: 1.1.0-alpha.85 + specifier: ^1.3.1 + version: 1.3.1 '@microsoft/agents-hosting': - specifier: ^1.1.0-alpha.85 - version: 1.1.0-alpha.85 + specifier: ^1.3.1 + version: 1.3.1 dotenv: specifier: ^17.2.3 version: 17.2.3 @@ -889,8 +892,8 @@ importers: specifier: ^0.2.18 version: 0.2.23 '@types/express': - specifier: ^4.17.21 - version: 4.17.25 + specifier: 'catalog:' + version: 5.0.6 '@types/node': specifier: ^20.14.9 version: 20.19.25 @@ -953,10 +956,18 @@ packages: resolution: {integrity: sha512-cNwUoCk3FF8VQ7Ln/MdcJVIv3sF73/OT86cRH81ECsydh7F4CNfIo2OAx6Cegtg8Yv75x4506wN4q+Emo6erOA==} engines: {node: '>=0.8.0'} + '@azure/msal-common@16.0.4': + resolution: {integrity: sha512-0KZ9/wbUyZN65JLAx5bGNfWjkD0kRMUgM99oSpZFg7wEOb3XcKIiHrFnIpgyc8zZ70fHodyh8JKEOel1oN24Gw==} + engines: {node: '>=0.8.0'} + '@azure/msal-node@3.8.3': resolution: {integrity: sha512-Ul7A4gwmaHzYWj2Z5xBDly/W8JSC1vnKgJ898zPMZr0oSf1ah0tiL15sytjycU/PMhDZAlkWtEL1+MzNMU6uww==} engines: {node: '>=16'} + '@azure/msal-node@5.0.4': + resolution: {integrity: sha512-WbA77m68noCw4qV+1tMm5nodll34JCDF0KmrSrp9LskS0bGbgHt98ZRxq69BQK5mjMqDD5ThHJOrrGSfzPybxw==} + engines: {node: '>=20'} + '@babel/cli@7.28.6': resolution: {integrity: sha512-6EUNcuBbNkj08Oj4gAZ+BUU8yLCgKzgVX4gaTh09Ya2C8ICM4P+G30g4m3akRxSYAp3A/gnWchrNst7px4/nUQ==} engines: {node: '>=6.9.0'} @@ -1944,12 +1955,12 @@ packages: peerDependencies: '@langchain/core': ^1.0.0 - '@microsoft/agents-activity@1.1.0-alpha.85': - resolution: {integrity: sha512-iMzeYFJZPSrXkhHpHesKQ1gjvCm6uyPlH0ojsNf8Z3EODCeiFsxHOahoLyzCuqzcWBYhNCcQ45aarNbeJ84HgA==} + '@microsoft/agents-activity@1.3.1': + resolution: {integrity: sha512-4k44NrfEqXiSg49ofj8geV8ylPocqDLtZKKt0PFL9BvFV0n57X3y1s/fEbsf7Fkl3+P/R2XLyMB5atEGf/eRGg==} engines: {node: '>=20.0.0'} - '@microsoft/agents-hosting@1.1.0-alpha.85': - resolution: {integrity: sha512-1Ii92EJSaQTuqjOBWUqoioClum+6Dh82uBmzoExMi3ZDJ9UTV6Kqg4W1h4PYhUZPmqIP3HLSVNvty84Gx/mqYg==} + '@microsoft/agents-hosting@1.3.1': + resolution: {integrity: sha512-570oJr93l1RcCNNaMVpOm+PgQkRgno/F65nH1aCWLIKLnw0o7iPoj+8Z5b7mnLMidg9lldVSCcf0dBxqTGE1/w==} engines: {node: '>=20.0.0'} '@microsoft/m365agentsplayground@0.2.23': @@ -2329,11 +2340,11 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/express-serve-static-core@4.19.7': - resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} - '@types/express@4.17.25': - resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -2356,9 +2367,6 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -2377,14 +2385,11 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/send@0.17.6': - resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} - '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - '@types/serve-static@1.15.10': - resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -2651,8 +2656,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.13.2: - resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} babel-jest@30.2.0: resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} @@ -3628,8 +3633,8 @@ packages: jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - jwks-rsa@3.2.0: - resolution: {integrity: sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==} + jwks-rsa@3.2.2: + resolution: {integrity: sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==} engines: {node: '>=14'} jws@4.0.1: @@ -4718,12 +4723,20 @@ snapshots: '@azure/msal-common@15.13.2': {} + '@azure/msal-common@16.0.4': {} + '@azure/msal-node@3.8.3': dependencies: '@azure/msal-common': 15.13.2 jsonwebtoken: 9.0.3 uuid: 10.0.0 + '@azure/msal-node@5.0.4': + dependencies: + '@azure/msal-common': 16.0.4 + jsonwebtoken: 9.0.3 + uuid: 10.0.0 + '@babel/cli@7.28.6(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -6034,7 +6047,7 @@ snapshots: transitivePeerDependencies: - ws - '@microsoft/agents-activity@1.1.0-alpha.85': + '@microsoft/agents-activity@1.3.1': dependencies: debug: 4.4.3(supports-color@5.5.0) uuid: 10.0.0 @@ -6042,15 +6055,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@microsoft/agents-hosting@1.1.0-alpha.85': + '@microsoft/agents-hosting@1.3.1': dependencies: '@azure/core-auth': 1.10.1 - '@azure/msal-node': 3.8.3 - '@microsoft/agents-activity': 1.1.0-alpha.85 - axios: 1.13.2 + '@azure/msal-node': 5.0.4 + '@microsoft/agents-activity': 1.3.1 + axios: 1.13.5 jsonwebtoken: 9.0.3 - jwks-rsa: 3.2.0 + jwks-rsa: 3.2.2 object-path: 0.11.8 + zod: 4.1.13 transitivePeerDependencies: - debug - supports-color @@ -6553,19 +6567,18 @@ snapshots: '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.19.7': + '@types/express-serve-static-core@5.1.1': dependencies: '@types/node': 20.19.25 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express@4.17.25': + '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.7 - '@types/qs': 6.14.0 - '@types/serve-static': 1.15.10 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 '@types/http-errors@2.0.5': {} @@ -6591,8 +6604,6 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 20.19.25 - '@types/mime@1.3.5': {} - '@types/ms@2.1.0': {} '@types/node-fetch@2.6.13': @@ -6612,20 +6623,14 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/send@0.17.6': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 20.19.25 - '@types/send@1.2.1': dependencies: '@types/node': 20.19.25 - '@types/serve-static@1.15.10': + '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 '@types/node': 20.19.25 - '@types/send': 0.17.6 '@types/stack-utils@2.0.3': {} @@ -6881,7 +6886,7 @@ snapshots: asynckit@0.4.0: {} - axios@1.13.2: + axios@1.13.5: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 @@ -8085,9 +8090,8 @@ snapshots: ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jwks-rsa@3.2.0: + jwks-rsa@3.2.2: dependencies: - '@types/express': 4.17.25 '@types/jsonwebtoken': 9.0.10 debug: 4.4.3(supports-color@5.5.0) jose: 4.15.9 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 223985a7..a24be527 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -24,8 +24,8 @@ catalog: "@langchain/mcp-adapters": "^1.1.1" # Microsoft 365 Agents SDK packages - "@microsoft/agents-hosting": "^1.1.0-alpha.85" - "@microsoft/agents-activity": "^1.1.0-alpha.85" + "@microsoft/agents-hosting": "^1.3.1" + "@microsoft/agents-activity": "^1.3.1" # Hono - required peer dependency for MCP SDK and OpenAI agents "hono": "^4.11.7" diff --git a/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts b/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts index f6d65821..3aaf516b 100644 --- a/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts +++ b/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts @@ -9,9 +9,12 @@ import { BaggageBuilder, OpenTelemetryConstants } from '@microsoft/agents-a365-o describe('BaggageBuilderUtils', () => { const mockTurnContext = { activity: { - from: { id: 'user1', name: 'User One', agenticUserId: 'agentic-user-1', tenantId: 'tenant1', aadObjectId: 'aad-object-1', agenticAppBlueprintId: 'blueprint-123', role: 'user' }, - recipient: { id: 'agent1', name: 'Agent One', agenticAppId: 'agent-app-1', agenticUserId: 'agentic-agent-1', tenantId: 'tenant1', aadObjectId: 'aad-object-2', role: 'agent' }, - channelData: {}, + from: { id: 'user1', name: 'User One', agenticUserId: 'agentic-user-1', tenantId: 'tenant1', role: 'user' }, + recipient: { id: 'agent1', name: 'Agent One', agenticAppId: 'agent-app-1', agenticUserId: 'agentic-agent-1', tenantId: 'tenant1', role: 'agent' }, + conversation: { id: 'conv-1', tenantId: 'tenant1' }, + getAgenticInstanceId: () => 'agent-app-1', + getAgenticUser: () => 'agentic-agent-1', + getAgenticTenantId: () => 'tenant1', }, } as any; @@ -41,16 +44,16 @@ 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]).toBe('aad-object-1'); + 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.GEN_AI_CALLER_TENANT_ID_KEY]).toBe('tenant1'); 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]).toBe('aad-object-2'); + 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]).toBe('blueprint-123'); - expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY]).toBe(undefined); + expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY]).toBeUndefined(); + expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY]).toBeUndefined(); expect(asObj[OpenTelemetryConstants.TENANT_ID_KEY]).toBe('tenant1'); }); diff --git a/tests/observability/extension/hosting/TurnContextUtils.test.ts b/tests/observability/extension/hosting/TurnContextUtils.test.ts index f9dfcc9f..04806c26 100644 --- a/tests/observability/extension/hosting/TurnContextUtils.test.ts +++ b/tests/observability/extension/hosting/TurnContextUtils.test.ts @@ -16,10 +16,13 @@ import { OpenTelemetryConstants, ExecutionType } from '@microsoft/agents-a365-ob describe('TurnContextUtils', () => { const mockTurnContext = { activity: { - from: { id: 'user1', name: 'User One', agenticUserId: 'agentic-user-1', tenantId: 'tenant1', role: 'agenticUser', agenticAppBlueprintId: 'blueprint-123' }, - recipient: { id: 'agent1', name: 'Agent One', agenticAppId: 'agent-app-1', agenticUserId: 'agentic-agent-1', tenantId: 'tenant1', role: 'agenticUser', aadObjectId: 'aad-object-1' }, - channelData: {}, + from: { id: 'user1', name: 'User One', agenticUserId: 'agentic-user-1', tenantId: 'tenant1', role: 'agenticUser' }, + recipient: { id: 'agent1', name: 'Agent One', agenticAppId: 'agent-app-1', agenticUserId: 'agentic-agent-1', tenantId: 'tenant1', role: 'agenticUser' }, + conversation: { id: 'conv-1', tenantId: 'tenant1' }, text: 'Hello world', + getAgenticInstanceId: () => 'agent-app-1', + getAgenticUser: () => 'agentic-agent-1', + getAgenticTenantId: () => 'tenant1', }, } as any; @@ -45,8 +48,8 @@ describe('TurnContextUtils', () => { const obj = Object.fromEntries(pairs); expect(obj[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe('agent-app-1'); expect(obj[OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY]).toBe('Agent One'); - expect(obj[OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY]).toBe('aad-object-1'); - expect(obj[OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY]).toBe(undefined); + expect(obj[OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY]).toBeUndefined(); + expect(obj[OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY]).toBeUndefined(); }); it('should get tenant id pair', () => { diff --git a/tests/observability/extension/hosting/scope-utils.test.ts b/tests/observability/extension/hosting/scope-utils.test.ts index a5ad82f7..c0442a1b 100644 --- a/tests/observability/extension/hosting/scope-utils.test.ts +++ b/tests/observability/extension/hosting/scope-utils.test.ts @@ -3,11 +3,28 @@ // Licensed under the MIT License. // ------------------------------------------------------------------------------ +// Mock RuntimeUtility methods so tests don't depend on real JWT parsing +jest.mock('@microsoft/agents-a365-runtime', () => { + const actual = jest.requireActual('@microsoft/agents-a365-runtime'); + return { + ...actual, + Utility: { + ...actual.Utility, + ResolveAgentIdentity: (context: any, _authToken: string) => + context.activity?.isAgenticRequest?.() + ? context.activity?.getAgenticInstanceId?.() || '' + : 'test-app-id', + getAgentIdFromToken: () => 'test-blueprint-id', + }, + }; +}); + import { ScopeUtils } from '../../../../packages/agents-a365-observability-hosting/src/utils/ScopeUtils'; import { InferenceScope, InvokeAgentScope, ExecuteToolScope, OpenTelemetryConstants, ExecutionType, OpenTelemetryScope, InvokeAgentDetails } from '@microsoft/agents-a365-observability'; import { RoleTypes } from '@microsoft/agents-activity'; import type { TurnContext } from '@microsoft/agents-hosting'; +const testAuthToken = 'mock-auth-token'; function makeTurnContext( text?: string, @@ -20,7 +37,11 @@ function makeTurnContext( text: text ?? 'hello world', channelId: channelName ?? 'web', channelIdSubChannel: channelLink ?? 'https://example/channel', - conversation: { id: conversationId ?? 'conv-001' } + conversation: { id: conversationId ?? 'conv-001', tenantId: 'tenant-123' }, + isAgenticRequest: () => true, + getAgenticInstanceId: () => 'agent-1', + getAgenticUser: () => 'agent-upn@contoso.com', + getAgenticTenantId: () => 'tenant-123', } }; @@ -35,9 +56,9 @@ function makeTurnContext( }; base.activity.recipient = { agenticAppId: 'agent-1', + agenticAppBlueprintId: 'agent-blueprint-1', name: 'Agent One', aadObjectId: 'agent-oid', - agenticAppBlueprintId: 'agent-blueprint-1', agenticUserId: 'agent-upn@contoso.com', role: 'assistant', tenantId: 'tenant-123' @@ -58,7 +79,7 @@ describe('ScopeUtils.populateFromTurnContext', () => { test('build InferenceScope based on turn context', () => { const details = { operationName: 'inference', model: 'gpt-4o', providerName: 'openai' } as any; const ctx = makeTurnContext('input text', 'web', 'https://web', 'conv-A'); - const scope = ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx) as InferenceScope; + const scope = ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx, testAuthToken) as InferenceScope; expect(scope).toBeInstanceOf(InferenceScope); const calls = spy.mock.calls.map(args => [args[0], args[1]]); expect(calls).toEqual( @@ -69,7 +90,7 @@ describe('ScopeUtils.populateFromTurnContext', () => { [OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, 'Agent One'], [OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY, 'agent-oid'], [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], - [OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, 'agent-blueprint-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_DESCRIPTION_KEY, 'assistant'], [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], @@ -83,36 +104,36 @@ describe('ScopeUtils.populateFromTurnContext', () => { describe('error conditions', () => { test('populateInferenceScopeFromTurnContext throws when agent details are missing', () => { const details: any = { operationName: 'inference', model: 'm', providerName: 'prov' }; - const ctx = makeCtx({ activity: { /* no recipient */ } as any }); - expect(() => ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx)) + const ctx = makeCtx({ activity: { /* no recipient */ getAgenticTenantId: () => 't1' } as any }); + expect(() => ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx, testAuthToken)) .toThrow('populateInferenceScopeFromTurnContext: Missing agent details on TurnContext (recipient)'); }); test('populateInferenceScopeFromTurnContext throws when tenant details are missing', () => { const details: any = { operationName: 'inference', model: 'm', providerName: 'prov' }; - const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid' } } as any }); // agent ok, no tenantId - expect(() => ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx)) + const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid' }, isAgenticRequest: () => false, getAgenticInstanceId: () => 'aid', getAgenticUser: () => undefined, getAgenticTenantId: () => undefined } as any }); // agent ok, no tenantId + expect(() => ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx, testAuthToken)) .toThrow('populateInferenceScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); }); test('populateExecuteToolScopeFromTurnContext throws when agent details are missing', () => { const details: any = { toolName: 'tool' }; - const ctx = makeCtx({ activity: { /* no recipient */ } as any }); - expect(() => ScopeUtils.populateExecuteToolScopeFromTurnContext(details, ctx)) + const ctx = makeCtx({ activity: { /* no recipient */ getAgenticTenantId: () => 't1' } as any }); + expect(() => ScopeUtils.populateExecuteToolScopeFromTurnContext(details, ctx, testAuthToken)) .toThrow('populateExecuteToolScopeFromTurnContext: Missing agent details on TurnContext (recipient)'); }); test('populateExecuteToolScopeFromTurnContext throws when tenant details are missing', () => { const details: any = { toolName: 'tool' }; - const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid' } } as any }); // agent ok, no tenantId - expect(() => ScopeUtils.populateExecuteToolScopeFromTurnContext(details, ctx)) + const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid' }, isAgenticRequest: () => false, getAgenticInstanceId: () => 'aid', getAgenticUser: () => undefined, getAgenticTenantId: () => undefined } as any }); // agent ok, no tenantId + expect(() => ScopeUtils.populateExecuteToolScopeFromTurnContext(details, ctx, testAuthToken)) .toThrow('populateExecuteToolScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); }); test('populateInvokeAgentScopeFromTurnContext throws when tenant details are missing', () => { const details: InvokeAgentDetails = { agentId: 'aid' } as any; - const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid' } } as any }); // no tenantId - expect(() => ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, ctx)) + const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid' }, isAgenticRequest: () => false, getAgenticInstanceId: () => 'aid', getAgenticUser: () => undefined, getAgenticTenantId: () => undefined } as any }); // no tenantId + expect(() => ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, ctx, testAuthToken)) .toThrow('populateInvokeAgentScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); }); }); @@ -121,7 +142,7 @@ describe('ScopeUtils.populateFromTurnContext', () => { const details = { operationName: 'invoke', model: 'n/a', providerName: 'internal' } as any; const ctx = makeTurnContext('invoke message', 'teams', 'https://teams', 'conv-B'); ctx.activity.from!.role = RoleTypes.AgenticUser; - const scope = ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, ctx) as InvokeAgentScope; + const scope = ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, ctx, testAuthToken) as InvokeAgentScope; expect(scope).toBeInstanceOf(InvokeAgentScope); const calls = spy.mock.calls.map(args => [args[0], args[1]]); expect(calls).toEqual( @@ -151,7 +172,7 @@ describe('ScopeUtils.populateFromTurnContext', () => { test('build ExecuteToolScope based on turn context', () => { const details = { toolName: 'search', arguments: '{}' } as any; const ctx = makeTurnContext(undefined, 'cli', 'https://cli', 'conv-C'); - const scope = ScopeUtils.populateExecuteToolScopeFromTurnContext(details, ctx) as ExecuteToolScope; + const scope = ScopeUtils.populateExecuteToolScopeFromTurnContext(details, ctx, testAuthToken) as ExecuteToolScope; expect(scope).toBeInstanceOf(ExecuteToolScope); const calls = spy.mock.calls.map(args => [args[0], args[1]]); expect(calls).toEqual( @@ -175,23 +196,23 @@ function makeCtx(partial: Partial): TurnContext { return partial as unknown as TurnContext; } -test('deriveTenantDetails prefers recipient.tenantId', () => { - const ctx = makeCtx({ activity: { recipient: { tenantId: 't-rec' }, from: { tenantId: 't-from' } } as any }); +test('deriveTenantDetails returns tenantId from getAgenticTenantId()', () => { + const ctx = makeCtx({ activity: { getAgenticTenantId: () => 't-rec' } as any }); expect(ScopeUtils.deriveTenantDetails(ctx)).toEqual({ tenantId: 't-rec' }); }); -test('deriveTenantDetails returns undefined when only from.tenantId is present', () => { - const ctx = makeCtx({ activity: { from: { tenantId: 't-from' } } as any }); +test('deriveTenantDetails returns undefined when getAgenticTenantId() returns undefined', () => { + const ctx = makeCtx({ activity: { getAgenticTenantId: () => undefined } as any }); expect(ScopeUtils.deriveTenantDetails(ctx)).toBeUndefined(); }); test('deriveAgentDetails maps recipient fields to AgentDetails', () => { - const ctx = makeCtx({ activity: { recipient: { agenticAppId: 'aid', name: 'A', aadObjectId: 'auid', agenticAppBlueprintId: 'bp1', agenticUserId: 'upn1', role: 'bot', tenantId: 't1' } } as any }); - expect(ScopeUtils.deriveAgentDetails(ctx)).toEqual({ - agentId: 'aid', + const ctx = makeCtx({ activity: { recipient: { name: 'A', aadObjectId: 'auid', role: 'bot' }, isAgenticRequest: () => false, getAgenticInstanceId: () => 'aid', getAgenticUser: () => 'upn1', getAgenticTenantId: () => 't1' } as any }); + expect(ScopeUtils.deriveAgentDetails(ctx, testAuthToken)).toEqual({ + agentId: 'test-app-id', agentName: 'A', agentAUID: 'auid', - agentBlueprintId: 'bp1', + agentBlueprintId: undefined, agentUPN: 'upn1', agentDescription: 'bot', tenantId: 't1', @@ -200,7 +221,7 @@ test('deriveAgentDetails maps recipient fields to AgentDetails', () => { test('deriveAgentDetails returns undefined without recipient', () => { const ctx = makeCtx({ activity: {} as any }); - expect(ScopeUtils.deriveAgentDetails(ctx)).toBeUndefined(); + expect(ScopeUtils.deriveAgentDetails(ctx, testAuthToken)).toBeUndefined(); }); test('deriveCallerAgent maps from fields to caller AgentDetails', () => { @@ -263,15 +284,19 @@ test('buildInvokeAgentDetails merges agent (recipient), conversationId, sourceMe }; const ctx = makeCtx({ activity: { - recipient: { agenticAppId: 'rec-agent', name: 'Rec', aadObjectId: 'auid', role: 'bot', tenantId: 'tX' }, + recipient: { name: 'Rec', role: 'bot' }, conversation: { id: 'c-2' }, channelId: 'web', channelIdSubChannel: 'inbox', + isAgenticRequest: () => false, + getAgenticInstanceId: () => 'rec-agent', + getAgenticUser: () => undefined, + getAgenticTenantId: () => 'tX', } as any }); - const result = ScopeUtils.buildInvokeAgentDetails(invokeAgentDetails, ctx); - expect(result.agentId).toBe('rec-agent'); + const result = ScopeUtils.buildInvokeAgentDetails(invokeAgentDetails, ctx, testAuthToken); + expect(result.agentId).toBe('test-app-id'); expect(result.conversationId).toBe('c-2'); expect(result.request?.sourceMetadata).toEqual({ id: 'orig-id', name: 'web', description: 'inbox' }); }); @@ -282,7 +307,7 @@ test('buildInvokeAgentDetails keeps base request when TurnContext has no overrid request: { content: 'hi', executionType: ExecutionType.HumanToAgent, sourceMetadata: { description: 'keep', name: 'keep-name' }}, }; const ctx = makeCtx({ activity: {} as any }); - const result = ScopeUtils.buildInvokeAgentDetails(invokeAgentDetails, ctx); + 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' });