Skip to content

Commit c3ddf4f

Browse files
authored
Merge branch 'main' into pmohapatra-MCP-V1-V2-nodeJs
2 parents e7f4ccf + 6179f6d commit c3ddf4f

8 files changed

Lines changed: 82 additions & 120 deletions

File tree

CHANGELOG.md

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,44 @@ All notable changes to the Agent365 TypeScript SDK will be documented in this fi
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.0] - 2026-04-15
9+
10+
### Breaking Changes (`@microsoft/agents-a365-observability`)
11+
12+
- **New permission required: `Agent365.Observability.OtelWrite`** — The observability exporter now requires this scope as both a delegated and application permission on your agent blueprint. See [Upgrade Instructions](#upgrade-instructions-observability-permission-for-existing-agents) below.
13+
14+
---
15+
16+
<a id="upgrade-instructions-observability-permission-for-existing-agents"></a>
17+
18+
### Upgrade Instructions: Observability Permission for Existing Agents
19+
20+
Existing agent blueprints need `Agent365.Observability.OtelWrite` granted as both a **delegated permission** and an **application permission**. Choose either option below.
21+
22+
#### Option A — Agent 365 CLI (requires both config files)
23+
24+
Requires `a365.config.json` and `a365.generated.config.json` in your config directory, a Global Administrator account, and [Agent 365 CLI v1.1.139-preview](https://www.nuget.org/packages/Microsoft.Agents.A365.DevTools.Cli/1.1.139-preview) or later.
25+
26+
```bash
27+
a365 setup admin --config-dir "<path-to-config-dir>"
28+
```
29+
30+
This grants all missing permissions including the new Observability scopes.
31+
32+
#### Option B — Entra Portal (no config files required)
33+
34+
Requires Global Administrator access to the blueprint app registration.
35+
36+
1. Go to **Entra portal** > **App registrations** > select your Blueprint app
37+
2. Go to **API permissions** > **Add a permission** > **APIs my organization uses** > search for `9b975845-388f-4429-889e-eab1ef63949c`
38+
3. Select **Delegated permissions** > check `Agent365.Observability.OtelWrite` > **Add permissions**
39+
4. Repeat step 2–3, this time select **Application permissions** > check `Agent365.Observability.OtelWrite` > **Add permissions**
40+
5. Click **Grant admin consent** and confirm
41+
42+
Both `Agent365.Observability.OtelWrite` (Delegated) and `Agent365.Observability.OtelWrite` (Application) should show `Granted` status.
43+
44+
---
45+
846
## [Unreleased]
947

1048
### Added (`@microsoft/agents-a365-tooling`)
@@ -44,15 +82,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4482
- **`OutputResponse.messages` type changed from `string[]` to `OutputMessagesParam`** — The `OutputMessagesParam` union type (`string[] | OutputMessages`) allows passing either plain strings or a versioned `OutputMessages` wrapper (`{ version, messages: OutputMessage[] }`) with `finish_reason`, multi-modal parts, etc. Existing code passing `string[]` continues to work (auto-converted to OTEL format internally), preserving backward compatibility.
4583
- **`recordInputMessages()` / `recordOutputMessages()` parameter type widened** — Methods now accept `InputMessagesParam` (`string[] | InputMessages`) and `OutputMessagesParam` (`string[] | OutputMessages`). `InputMessages` is a versioned wrapper `{ version, messages: ChatMessage[] }` and `OutputMessages` is a versioned wrapper `{ version, messages: OutputMessage[] }`. Plain `string[]` input is auto-wrapped to OTEL gen-ai format.
4684

47-
### Added (`@microsoft/agents-a365-observability`)
85+
### Added
4886

87+
#### `@microsoft/agents-a365-observability`
4988
- **OTEL Gen-AI Message Format types** — New types aligned with [OpenTelemetry Gen-AI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/): `MessageRole`, `FinishReason`, `Modality`, `ChatMessage`, `OutputMessage`, `InputMessages`, `OutputMessages`, and discriminated `MessagePart` union (`TextPart`, `ToolCallRequestPart`, `ToolCallResponsePart`, `ReasoningPart`, `BlobPart`, `FilePart`, `UriPart`, `ServerToolCallPart`, `ServerToolCallResponsePart`, `GenericPart`).
5089
- **`SpanDetails`** — New interface grouping `parentContext`, `startTime`, `endTime`, `spanKind` for scope construction.
5190
- **`CallerDetails`** — New interface wrapping `userDetails` and `callerAgentDetails` for `InvokeAgentScope`.
5291
- **`Request`** — Unified request context interface (`conversationId`, `channel`, `content`, `sessionId`) used across all scopes.
5392
- **`OpenTelemetryScope.recordCancellation()`** — Records a cancellation event on the span with `error.type = 'TaskCanceledException'`.
5493
- **`OpenTelemetryConstants.ERROR_TYPE_CANCELLED`** — Constant for the cancellation error type value.
5594
- **`ObservabilityBuilder.withServiceNamespace()`** — Configures the `service.namespace` resource attribute.
95+
- **Span links support** — All scope classes (`InvokeAgentScope`, `InferenceScope`, `ExecuteToolScope`, `OutputScope`) now support span links via `SpanDetails.spanLinks` (passed through the existing `spanDetails?` argument) to establish causal relationships to other spans (e.g. linking a batch operation to individual trigger spans).
96+
- **`BaggageBuilder.invokeAgentServer(address, port?)`** — Fluent setter for server address and port baggage values. Port is only recorded when different from 443 (default HTTPS). Clears stale port entries when port is omitted or 443.
97+
- **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.
98+
99+
#### `@microsoft/agents-a365-observability-hosting`
100+
- **OutputScope** — Tracing scope for outgoing agent messages with caller details, conversation ID, channel information, and parent span linking.
101+
- **BaggageMiddleware** — Middleware for automatic OpenTelemetry baggage propagation from TurnContext.
102+
- **OutputLoggingMiddleware** — Middleware that creates OutputScope spans for outgoing messages with lazy parent span linking via `A365_PARENT_SPAN_KEY`.
103+
- **ObservabilityHostingManager** — Manager for configuring hosting-layer observability middleware with `ObservabilityHostingOptions`.
56104

57105
### Breaking Changes (`@microsoft/agents-a365-observability-hosting`)
58106

@@ -68,46 +116,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
68116
- **`ScopeUtils.populateExecuteToolScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter.
69117
- **`ScopeUtils.buildInvokeAgentDetails()`** — Now accepts `AgentDetails` (was `InvokeAgentDetails`) and returns flat `AgentDetails` instead of the old `InvokeAgentDetails` wrapper.
70118

71-
### Added
72-
73-
- **Span links support** — All scope classes (`InvokeAgentScope`, `InferenceScope`, `ExecuteToolScope`, `OutputScope`) now support span links via `SpanDetails.spanLinks` (passed through the existing `spanDetails?` argument) to establish causal relationships to other spans (e.g. linking a batch operation to individual trigger spans).
74-
- **`BaggageBuilder.invokeAgentServer(address, port?)`** — Fluent setter for server address and port baggage values. Port is only recorded when different from 443 (default HTTPS). Clears stale port entries when port is omitted or 443.
75-
- **`OpenAIAgentsInstrumentationConfig.isContentRecordingEnabled`** — Optional `boolean` to enable content recording in OpenAI trace processor.
76-
- **`LangChainTraceInstrumentor.instrument(module, options?)`** — New optional `{ isContentRecordingEnabled?: boolean }` parameter to enable content recording in LangChain tracer.
77-
- **`truncateValue`** / **`MAX_ATTRIBUTE_LENGTH`** — Exported utilities for attribute value truncation (8192 char limit).
78-
- **OutputScope**: Tracing scope for outgoing agent messages with caller details, conversation ID, channel information, and parent span linking.
79-
- **BaggageMiddleware**: Middleware for automatic OpenTelemetry baggage propagation from TurnContext.
80-
- **OutputLoggingMiddleware**: Middleware that creates OutputScope spans for outgoing messages with lazy parent span linking via `A365_PARENT_SPAN_KEY`.
81-
- **ObservabilityHostingManager**: Manager for configuring hosting-layer observability middleware with `ObservabilityHostingOptions`.
82-
- **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.
83-
84119
### Fixed
85-
- **Agent365ExporterOptions**: `exporterTimeoutMilliseconds` default increased from 30s to 90s to allow sufficient time for retries across multiple identity groups within a single export cycle.
86120

87-
### Changed
88-
- **ObservabilityHostingManager**: `enableBaggage` option now defaults to `false` (was `true`). Callers must explicitly set `enableBaggage: true` to register the BaggageMiddleware.
89-
- `ScopeUtils.deriveAgentDetails` now resolves `agentId` via `activity.getAgenticInstanceId()` for embodied (agentic) agents only. For non-embodied agents, `agentId` is `undefined` since the token's app ID cannot reliably be attributed to the agent.
90-
- `ScopeUtils.deriveAgentDetails` now resolves `agentBlueprintId` from the JWT `xms_par_app_azp` claim via `RuntimeUtility.getAgentIdFromToken()` instead of reading `recipient.agenticAppBlueprintId`.
91-
- `ScopeUtils.deriveAgentDetails` now resolves `agentEmail` via `activity.getAgenticUser()` instead of `recipient.agenticUserId`.
92-
- `ScopeUtils.deriveTenantDetails` now uses `activity.getAgenticTenantId()` instead of `recipient.tenantId`.
93-
- `getTargetAgentBaggagePairs` now uses `activity.getAgenticInstanceId()` instead of `recipient.agenticAppId`.
94-
- `getTenantIdPair` now uses `activity.getAgenticTenantId()` instead of manual `channelData` parsing.
95-
- `InferenceScope.recordInputMessages()` / `recordOutputMessages()` now use JSON array format instead of comma-separated strings.
96-
- `InvokeAgentScope.recordInputMessages()` / `recordOutputMessages()` now use JSON array format instead of comma-separated strings.
97-
98-
## [1.1.0] - 2025-12-09
121+
#### `@microsoft/agents-a365-observability`
122+
- **Agent365ExporterOptions**`exporterTimeoutMilliseconds` default increased from 30s to 90s to allow sufficient time for retries across multiple identity groups within a single export cycle.
99123

100124
### Changed
101-
- Remove ENABLE_A365_OBSERVABILITY or ENABLE_OBSERVABILITY. No longer need to use environment variable for recordAttributes, setTagMaybe, and addBaggage.
102-
- Merged `EnhancedAgentDetails` into `AgentDetails` to unify agent detail typing across scopes and middleware.
125+
126+
#### `@microsoft/agents-a365-observability`
127+
- **InferenceScope.recordInputMessages() / recordOutputMessages()** — Now use JSON array format instead of comma-separated strings.
128+
- **InvokeAgentScope.recordInputMessages() / recordOutputMessages()** — Now use JSON array format instead of comma-separated strings.
129+
- **Environment variables** — Remove ENABLE_A365_OBSERVABILITY or ENABLE_OBSERVABILITY. No longer need to use environment variable for recordAttributes, setTagMaybe, and addBaggage.
130+
- **EnhancedAgentDetails** — Merged `EnhancedAgentDetails` into `AgentDetails` to unify agent detail typing across scopes and middleware.
131+
132+
#### `@microsoft/agents-a365-observability-hosting`
133+
- **ObservabilityHostingManager**`enableBaggage` option now defaults to `false` (was `true`). Callers must explicitly set `enableBaggage: true` to register the BaggageMiddleware.
134+
- **ScopeUtils.deriveAgentDetails** — Now resolves `agentId` via `activity.getAgenticInstanceId()` for embodied (agentic) agents only. For non-embodied agents, `agentId` is `undefined` since the token's app ID cannot reliably be attributed to the agent.
135+
- **ScopeUtils.deriveAgentDetails** — Now resolves `agentBlueprintId` from the JWT `xms_par_app_azp` claim via `RuntimeUtility.getAgentIdFromToken()` instead of reading `recipient.agenticAppBlueprintId`.
136+
- **ScopeUtils.deriveAgentDetails** — Now resolves `agentEmail` via `activity.getAgenticUser()` instead of `recipient.agenticUserId`.
137+
- **ScopeUtils.deriveTenantDetails** — Now uses `activity.getAgenticTenantId()` instead of `recipient.tenantId`.
138+
- **getTargetAgentBaggagePairs** — Now uses `activity.getAgenticInstanceId()` instead of `recipient.agenticAppId`.
139+
- **getTenantIdPair** — Now uses `activity.getAgenticTenantId()` instead of manual `channelData` parsing.
140+
141+
---
142+
103143

104144
### Deprecated
105145
- `EnhancedAgentDetails` is now an alias of `AgentDetails` and marked as deprecated. Existing imports continue to work without breaking changes; migrate to `AgentDetails` when convenient.
106146

107-
### Notes
108-
- This release is non-breaking. A minor version bump reflects additive API changes and deprecation guidance.
109147

110-
## [1.0.0] - 2025-01-03
148+
## [0.1.0] - 2025-01-03
111149

112150
### Added
113151
- Initial release of Agent365 TypeScript SDK

packages/agents-a365-observability/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export { InferenceScope } from './tracing/scopes/InferenceScope';
8080
export { OutputScope } from './tracing/scopes/OutputScope';
8181
export { logger, setLogger, getLogger, resetLogger, formatError } from './utils/logging';
8282
export type { ILogger } from './utils/logging';
83-
export { truncateValue, MAX_ATTRIBUTE_LENGTH, safeSerializeToJson } from './tracing/util';
83+
export { safeSerializeToJson } from './tracing/util';
8484

8585
// Message utilities
8686
export { serializeMessages, normalizeInputMessages, normalizeOutputMessages } from './tracing/message-utils';

packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ interface OTLPStatus {
8181
* Observability span exporter for Agent365:
8282
* - Partitions spans by (tenantId, agentId)
8383
* - Builds OTLP-like JSON: resourceSpans -> scopeSpans -> spans
84-
* - POSTs per group to https://{endpoint}/observability/tenants/{tenantId}/agents/{agentId}/traces?api-version=1
85-
* or, when useS2SEndpoint is true, https://{endpoint}/observabilityService/tenants/{tenantId}/agents/{agentId}/traces?api-version=1
84+
* - POSTs per group to https://{endpoint}/observability/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1
85+
* or, when useS2SEndpoint is true, https://{endpoint}/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1
8686
* - Adds Bearer token via token_resolver(agentId, tenantId)
8787
*/
8888
export class Agent365Exporter implements SpanExporter {
@@ -170,10 +170,8 @@ export class Agent365Exporter implements SpanExporter {
170170
const payload = this.buildExportRequest(spans);
171171
const body = JSON.stringify(payload);
172172
// Select endpoint path based on S2S flag (includes tenantId in path)
173-
const endpointRelativePath =
174-
this.options.useS2SEndpoint
175-
? `/observabilityService/tenants/${encodeURIComponent(tenantId)}/agents/${encodeURIComponent(agentId)}/traces`
176-
: `/observability/tenants/${encodeURIComponent(tenantId)}/agents/${encodeURIComponent(agentId)}/traces`;
173+
const servicePrefix = this.options.useS2SEndpoint ? '/observabilityService' : '/observability';
174+
const endpointRelativePath = `${servicePrefix}/tenants/${encodeURIComponent(tenantId)}/otlp/agents/${encodeURIComponent(agentId)}/traces`;
177175

178176
let url: string;
179177
const domainOverride = getAgent365ObservabilityDomainOverride(this.configProvider);

packages/agents-a365-observability/src/tracing/exporter/Agent365ExporterOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type TokenResolver = (agentId: string, tenantId: string) => string | null
2121
* @property {ClusterCategory | string} clusterCategory Environment / cluster category (e.g. ClusterCategory.preprod, ClusterCategory.prod, default to ClusterCategory.prod).
2222
* @property {TokenResolver} [tokenResolver] Optional delegate to obtain an auth token. If omitted the exporter will
2323
* fall back to reading the cached token (AgenticTokenCacheInstance.getObservabilityToken).
24-
* @property {boolean} [useS2SEndpoint] When true, exporter will POST to the S2S path (/observabilityService/tenants/{tenantId}/agents/{agentId}/traces).
24+
* @property {boolean} [useS2SEndpoint] When true, exporter will POST to the S2S path (/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces).
2525
* @property {number} maxQueueSize Maximum span queue size before drops occur (passed to BatchSpanProcessor).
2626
* @property {number} scheduledDelayMilliseconds Delay between automatic batch flush attempts.
2727
* @property {number} exporterTimeoutMilliseconds Maximum time (ms) the BatchSpanProcessor waits for the entire export() call to complete before giving up. Covers partitioning, token resolution, and all HTTP retries.

packages/agents-a365-observability/src/tracing/util.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,6 @@ export const isAgent365ExporterEnabled = (
2222
return provider.getConfiguration().isObservabilityExporterEnabled;
2323
};
2424

25-
/**
26-
* Maximum length for span attribute values.
27-
* Values exceeding this limit will be truncated with a suffix.
28-
*/
29-
export const MAX_ATTRIBUTE_LENGTH = 8_192;
30-
31-
const TRUNCATION_SUFFIX = '...[truncated]';
32-
33-
/**
34-
* Truncate a string value to {@link MAX_ATTRIBUTE_LENGTH} characters.
35-
* If the value exceeds the limit, it is trimmed and a truncation suffix is appended,
36-
* with the total length capped at exactly {@link MAX_ATTRIBUTE_LENGTH}.
37-
* @param value The string to truncate
38-
* @returns The original string if within limits, otherwise the truncated string
39-
*/
40-
export function truncateValue(value: string): string {
41-
if (value.length > MAX_ATTRIBUTE_LENGTH) {
42-
return value.substring(0, MAX_ATTRIBUTE_LENGTH - TRUNCATION_SUFFIX.length) + TRUNCATION_SUFFIX;
43-
}
44-
return value;
45-
}
46-
4725
/**
4826
* Ensures the value is always a JSON-parseable string.
4927
* - Objects are serialized via JSON.stringify.

tests/observability/core/agent365-exporter.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('Agent365Exporter', () => {
135135
expect(fetchCalls.length).toBe(1);
136136
const urlArg = fetchCalls[0][0];
137137
const headersArg = fetchCalls[0][1].headers;
138-
expect(urlArg).toBe(`${expectedUrl}/observability/tenants/${tenantId}/agents/${agentId}/traces?api-version=1`);
138+
expect(urlArg).toBe(`${expectedUrl}/observability/tenants/${tenantId}/otlp/agents/${agentId}/traces?api-version=1`);
139139
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);
140140
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
141141
});
@@ -190,7 +190,7 @@ describe('Agent365Exporter', () => {
190190
const urlArg = fetchCalls[0][0] as string;
191191
const headersArg = fetchCalls[0][1].headers as Record<string, string>;
192192

193-
expect(urlArg).toBe(`${expectedBaseUrl}/observability/tenants/${tenantId}/agents/${agentId}/traces?api-version=1`);
193+
expect(urlArg).toBe(`${expectedBaseUrl}/observability/tenants/${tenantId}/otlp/agents/${agentId}/traces?api-version=1`);
194194
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);
195195
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
196196
});
@@ -215,7 +215,7 @@ describe('Agent365Exporter', () => {
215215
expect(fetchCalls.length).toBe(1);
216216
const urlArg = fetchCalls[0][0];
217217
const headersArg = fetchCalls[0][1].headers;
218-
expect(urlArg).toBe(`https://agent365.svc.cloud.microsoft/observability/tenants/${tenantId}/agents/${agentId}/traces?api-version=1`);
218+
expect(urlArg).toBe(`https://agent365.svc.cloud.microsoft/observability/tenants/${tenantId}/otlp/agents/${agentId}/traces?api-version=1`);
219219
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);
220220
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
221221
});
@@ -250,7 +250,7 @@ describe('Agent365Exporter', () => {
250250
expect(fetchCalls.length).toBe(1);
251251

252252
const urlArg = fetchCalls[0][0] as string;
253-
expect(urlArg).toMatch(`/observabilityService/tenants/${tenantId}/agents/${agentId}/traces?api-version=1`);
253+
expect(urlArg).toBe(`https://agent365.svc.cloud.microsoft/observabilityService/tenants/${tenantId}/otlp/agents/${agentId}/traces?api-version=1`);
254254
const headersArg = fetchCalls[0][1].headers as Record<string, string>;
255255
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
256256
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);
@@ -279,8 +279,7 @@ describe('Agent365Exporter', () => {
279279
const fetchCalls = getFetchCalls();
280280
expect(fetchCalls.length).toBe(1);
281281
const urlArg = fetchCalls[0][0] as string;
282-
expect(urlArg).toMatch(`/observabilityService/tenants/${tenantId}/agents/${agentId}/traces?api-version=1`);
283-
expect(urlArg).toContain('https://custom.domain');
282+
expect(urlArg).toBe(`https://custom.domain/observabilityService/tenants/${tenantId}/otlp/agents/${agentId}/traces?api-version=1`);
284283
const headersArg = fetchCalls[0][1].headers as Record<string, string>;
285284
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
286285
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);

0 commit comments

Comments
 (0)