Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 32 additions & 97 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,8 @@ 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).

## [0.2.0] - 2026-04-15

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

- **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.

---

<a id="upgrade-instructions-observability-permission-for-existing-agents"></a>

### Upgrade Instructions: Observability Permission for Existing Agents

Existing agent blueprints need `Agent365.Observability.OtelWrite` granted as both a **delegated permission** and an **application permission**. Choose either option below.

#### Option A — Agent 365 CLI (requires both config files)

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.

```bash
a365 setup admin --config-dir "<path-to-config-dir>"
```

This grants all missing permissions including the new Observability scopes.

#### Option B — Entra Portal (no config files required)

Requires Global Administrator access to the blueprint app registration.

1. Go to **Entra portal** > **App registrations** > select your Blueprint app
2. Go to **API permissions** > **Add a permission** > **APIs my organization uses** > search for `9b975845-388f-4429-889e-eab1ef63949c`
3. Select **Delegated permissions** > check `Agent365.Observability.OtelWrite` > **Add permissions**
4. Repeat step 2–3, this time select **Application permissions** > check `Agent365.Observability.OtelWrite` > **Add permissions**
5. Click **Grant admin consent** and confirm

Both `Agent365.Observability.OtelWrite` (Delegated) and `Agent365.Observability.OtelWrite` (Application) should show `Granted` status.

---

## [Unreleased]

### Breaking Changes (`@microsoft/agents-a365-tooling`)

- **`listToolServers(agenticAppId, authToken)` throws for V2 MCP servers** — The deprecated
two-argument overload now throws a hard error if the gateway returns any server whose
`audience` field does not match the shared ATG app ID (i.e. a V2 server). The legacy
signature cannot perform per-audience OBO because it has no `Authorization` object or
`authHandlerName`. Agents whose blueprints only have V1 permissions are unaffected.

**Migration** — switch to the preferred overload which handles both V1 and V2 automatically:
```typescript
// Before (deprecated)
const servers = await service.listToolServers(agenticAppId, authToken);

// After
const servers = await service.listToolServers(turnContext, authorization, 'graph');
// authToken is optional; omit it and the SDK auto-generates it via token exchange.
```

### Added (`@microsoft/agents-a365-tooling`)

- **V1/V2 per-audience token acquisition** — `resolveTokenScopeForServer` now supports explicit `scope` field for V2 MCP servers. When a V2 server provides a `scope` value, the token is requested as `{audience}/{scope}`; otherwise falls back to `{audience}/.default` (pre-consented permissions cover both cases).
- **`publisher` field preserved end-to-end** — `MCPServerConfig.publisher` is now carried through both gateway and manifest normalization and is available to callers of `listToolServers`.

### Fixed (`@microsoft/agents-a365-tooling-extensions-openai`, `@microsoft/agents-a365-tooling-extensions-langchain`)

- **Per-audience Authorization headers now correctly applied** — OpenAI and LangChain extensions now merge the per-server `Authorization: Bearer` token from `server.headers` (set by `attachPerAudienceTokens`) with base request headers, instead of applying a single shared discovery token to all MCP servers. This ensures V2 servers receive their own correctly-scoped token.

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

- **`InvokeAgentDetails` renamed to `InvokeAgentScopeDetails`** — Now contains only scope-level config (`endpoint`). Agent identity (`AgentDetails`) is a separate parameter. `sessionId` moved to `Request`.
Expand Down Expand Up @@ -101,25 +36,15 @@ Both `Agent365.Observability.OtelWrite` (Delegated) and `Agent365.Observability.
- **`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.
- **`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.

### Added
### Added (`@microsoft/agents-a365-observability`)

#### `@microsoft/agents-a365-observability`
- **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`).
- **`SpanDetails`** — New interface grouping `parentContext`, `startTime`, `endTime`, `spanKind` for scope construction.
- **`CallerDetails`** — New interface wrapping `userDetails` and `callerAgentDetails` for `InvokeAgentScope`.
- **`Request`** — Unified request context interface (`conversationId`, `channel`, `content`, `sessionId`) used across all scopes.
- **`OpenTelemetryScope.recordCancellation()`** — Records a cancellation event on the span with `error.type = 'TaskCanceledException'`.
- **`OpenTelemetryConstants.ERROR_TYPE_CANCELLED`** — Constant for the cancellation error type value.
- **`ObservabilityBuilder.withServiceNamespace()`** — Configures the `service.namespace` resource attribute.
- **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).
- **`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.
- **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.

#### `@microsoft/agents-a365-observability-hosting`
- **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`.

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

Expand All @@ -135,36 +60,46 @@ Both `Agent365.Observability.OtelWrite` (Delegated) and `Agent365.Observability.
- **`ScopeUtils.populateExecuteToolScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter.
- **`ScopeUtils.buildInvokeAgentDetails()`** — Now accepts `AgentDetails` (was `InvokeAgentDetails`) and returns flat `AgentDetails` instead of the old `InvokeAgentDetails` wrapper.

### Fixed

#### `@microsoft/agents-a365-observability`
- **Agent365ExporterOptions** — `exporterTimeoutMilliseconds` default increased from 30s to 90s to allow sufficient time for retries across multiple identity groups within a single export cycle.

### Changed
### Added

#### `@microsoft/agents-a365-observability`
- **InferenceScope.recordInputMessages() / recordOutputMessages()** — Now use JSON array format instead of comma-separated strings.
- **InvokeAgentScope.recordInputMessages() / recordOutputMessages()** — Now use JSON array format instead of comma-separated strings.
- **Environment variables** — Remove ENABLE_A365_OBSERVABILITY or ENABLE_OBSERVABILITY. No longer need to use environment variable for recordAttributes, setTagMaybe, and addBaggage.
- **EnhancedAgentDetails** — Merged `EnhancedAgentDetails` into `AgentDetails` to unify agent detail typing across scopes and middleware.
- **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).
- **`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.
- **`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`.
- **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.

#### `@microsoft/agents-a365-observability-hosting`
- **ObservabilityHostingManager** — `enableBaggage` option now defaults to `false` (was `true`). Callers must explicitly set `enableBaggage: true` to register the BaggageMiddleware.
- **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.
- **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 `agentEmail` 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.
### Fixed
- **Agent365ExporterOptions**: `exporterTimeoutMilliseconds` default increased from 30s to 90s to allow sufficient time for retries across multiple identity groups within a single export cycle.

---
### Changed
- **ObservabilityHostingManager**: `enableBaggage` option now defaults to `false` (was `true`). Callers must explicitly set `enableBaggage: true` to register the BaggageMiddleware.
- `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.
- `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 `agentEmail` 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.
- `InferenceScope.recordInputMessages()` / `recordOutputMessages()` now use JSON array format instead of comma-separated strings.
- `InvokeAgentScope.recordInputMessages()` / `recordOutputMessages()` now use JSON array format instead of comma-separated strings.

## [1.1.0] - 2025-12-09

### Changed
- Remove ENABLE_A365_OBSERVABILITY or ENABLE_OBSERVABILITY. No longer need to use environment variable for recordAttributes, setTagMaybe, and addBaggage.
- Merged `EnhancedAgentDetails` into `AgentDetails` to unify agent detail typing across scopes and middleware.

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

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

## [0.1.0] - 2025-01-03
## [1.0.0] - 2025-01-03

### Added
- Initial release of Agent365 TypeScript SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ class LangChainTraceInstrumentorImpl extends InstrumentationBase<Instrumentation
private _hasBeenEnabled = false;
private _isPatched = false;
protected otelTracer: Tracer;
private isContentRecordingEnabled: boolean;

private constructor() {
private constructor(options?: { isContentRecordingEnabled?: boolean }) {
if (LangChainTraceInstrumentorImpl._instance !== null) {
throw new Error("LangChainTraceInstrumentor can only be instantiated once.");
}
Expand All @@ -41,14 +42,15 @@ class LangChainTraceInstrumentorImpl extends InstrumentationBase<Instrumentation
"agent365-langchain",
"1.0.0"
);
this.isContentRecordingEnabled = options?.isContentRecordingEnabled ?? false;

LangChainTraceInstrumentorImpl._instance = this;
logger.info("[LangChainTraceInstrumentor] Initialized and automatically enabled");
}

static getInstance(): LangChainTraceInstrumentorImpl {
static getInstance(options?: { isContentRecordingEnabled?: boolean }): LangChainTraceInstrumentorImpl {
if (!LangChainTraceInstrumentorImpl._instance) {
LangChainTraceInstrumentorImpl._instance = new LangChainTraceInstrumentorImpl();
LangChainTraceInstrumentorImpl._instance = new LangChainTraceInstrumentorImpl(options);
}
return LangChainTraceInstrumentorImpl._instance;
}
Expand Down Expand Up @@ -102,7 +104,7 @@ class LangChainTraceInstrumentorImpl extends InstrumentationBase<Instrumentation
this: CallbackManagerModuleType,
...args: Parameters<typeof CallbackManager["_configureSync"]>
) {
args[0] = addTracerToHandlers(instrumentor.otelTracer, args[0]);
args[0] = addTracerToHandlers(instrumentor.otelTracer, args[0], { isContentRecordingEnabled: instrumentor.isContentRecordingEnabled });
logger.info("[LangChainTraceInstrumentor] _configureSync wrapped to add LangChainTracer");
return original.apply(this, args);
};
Expand Down Expand Up @@ -152,9 +154,10 @@ export class LangChainTraceInstrumentor {
/**
* Initialize and auto-instrument for LangChain
* @param module The CallbackManager module to instrument
* @param options Optional configuration options
*/
static instrument(module: CallbackManagerModuleType): void {
LangChainTraceInstrumentorImpl.getInstance().manuallyInstrumentImpl(module);
static instrument(module: CallbackManagerModuleType, options?: { isContentRecordingEnabled?: boolean }): void {
LangChainTraceInstrumentorImpl.getInstance(options).manuallyInstrumentImpl(module);
}

/**
Expand Down Expand Up @@ -188,20 +191,21 @@ export class LangChainTraceInstrumentor {
export function addTracerToHandlers(
tracer: Tracer,
handlers: CallbackManagerModule.Callbacks | undefined,
options?: { isContentRecordingEnabled?: boolean }
): CallbackManagerModule.Callbacks {
if (handlers == null) {
return [new LangChainTracer(tracer)];
return [new LangChainTracer(tracer, options)];
}

if (Array.isArray(handlers)) {
if (!handlers.some((h) => h instanceof LangChainTracer)) {
handlers.push(new LangChainTracer(tracer));
handlers.push(new LangChainTracer(tracer, options));
}
return handlers;
}

if (!handlers.inheritableHandlers.some((h) => h instanceof LangChainTracer)) {
handlers.addHandler(new LangChainTracer(tracer), true);
handlers.addHandler(new LangChainTracer(tracer, options), true);
}
return handlers;
}
Loading
Loading