Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
996b5e7
use Agent365ExporterOptions for exporter and create AgenticTokenCache…
jsl517 Nov 10, 2025
9c50c72
tests
jsl517 Nov 11, 2025
51b4d86
copilot comment
jsl517 Nov 11, 2025
a0de073
configure batch span processor
jsl517 Nov 11, 2025
88605eb
copilot comment
jsl517 Nov 11, 2025
ca17b96
checkpoint to use cached context and authorization to exchange token.
jsl517 Nov 12, 2025
1b89510
only do refresh token in message handler, unit tests
jsl517 Nov 13, 2025
fa95349
sample update
jsl517 Nov 14, 2025
1c3940e
comments
jsl517 Nov 14, 2025
36c5966
comments
jsl517 Nov 14, 2025
3d174b3
observability always use prod
jsl517 Nov 14, 2025
9ca9f6a
Merge branch 'main' into users/pefan/exportoption
jsl517 Nov 14, 2025
7271de5
should be lowercase
jsl517 Nov 14, 2025
ea0cfc6
Revert "observability always use prod"
jsl517 Nov 15, 2025
5221582
Merge branch 'main' into users/pefan/exportoption
jsl517 Nov 15, 2025
64728ca
default to prod
jsl517 Nov 15, 2025
f1d9e61
expose Agent365ExporterOptions
jsl517 Nov 18, 2025
e07318b
expose Agent365ExporterOptions
jsl517 Nov 18, 2025
057aae7
move azure token cache to its own package
jsl517 Nov 19, 2025
20133be
readme update
jsl517 Nov 19, 2025
d322096
Merge branch 'main' into users/pefan/exportoption
jsl517 Nov 19, 2025
6058c84
fix package.json error
jsl517 Nov 19, 2025
1149e09
fix logging
jsl517 Nov 19, 2025
5e3d93b
lint,jest config
jsl517 Nov 19, 2025
55b5536
cleanup
jsl517 Nov 19, 2025
cf31254
comment
jsl517 Nov 19, 2025
142ec28
rename
jsl517 Nov 19, 2025
54ddd3e
lint
jsl517 Nov 19, 2025
5303929
comment
jsl517 Nov 19, 2025
0b11ffa
Merge branch 'main' into users/pefan/exportoption
fpfp100 Nov 19, 2025
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
28 changes: 16 additions & 12 deletions packages/agents-a365-observability/src/ObservabilityBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ConsoleSpanExporter, BatchSpanProcessor } from '@opentelemetry/sdk-trac
import { SpanProcessor } from './tracing/processors/SpanProcessor';
import { isAgent365ExporterEnabled } from './tracing/util';
import { Agent365Exporter, TokenResolver } from './tracing/exporter/Agent365Exporter';
import { Agent365ExporterOptions } from './tracing/exporter/Agent365ExporterOptions';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { trace } from '@opentelemetry/api';
Expand Down Expand Up @@ -67,18 +68,21 @@ export class ObservabilityBuilder {
return this;
}

private getTraceExporter() {
if (isAgent365ExporterEnabled()){
if (!this.options.tokenResolver) {
throw new Error('tokenResolver must be provided when Agent365 exporter is enabled');
}
return new Agent365Exporter(
this.options.tokenResolver,
this.options.clusterCategory || 'prod'
);
} else {
return new ConsoleSpanExporter();
private createBatchProcessor(): BatchSpanProcessor {
if (!isAgent365ExporterEnabled()) {
return new BatchSpanProcessor(new ConsoleSpanExporter());
}
const opts = new Agent365ExporterOptions();
Comment thread
fpfp100 marked this conversation as resolved.
opts.clusterCategory = this.options.clusterCategory || 'prod';
if (this.options.tokenResolver) {
opts.tokenResolver = this.options.tokenResolver;
}
return new BatchSpanProcessor(new Agent365Exporter(opts), {
maxQueueSize: opts.maxQueueSize,
scheduledDelayMillis: opts.scheduledDelayMilliseconds,
exportTimeoutMillis: opts.exporterTimeoutMilliseconds,
maxExportBatchSize: opts.maxExportBatchSize
});
}

private createResource() {
Expand All @@ -104,7 +108,7 @@ export class ObservabilityBuilder {
const spanProcessor = new SpanProcessor();

// 2. batch processor that actually ships spans out
const batchProcessor = new BatchSpanProcessor(this.getTraceExporter());
const batchProcessor = this.createBatchProcessor();

const globalProvider: any = trace.getTracerProvider();

Expand Down
1 change: 1 addition & 0 deletions packages/agents-a365-observability/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ export { OpenTelemetryScope } from './tracing/scopes/OpenTelemetryScope';
export { ExecuteToolScope } from './tracing/scopes/ExecuteToolScope';
export { InvokeAgentScope } from './tracing/scopes/InvokeAgentScope';
export { InferenceScope} from './tracing/scopes/InferenceScope';
export { AgenticTokenCacheInstance } from './utils/AgenticTokenCache';
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------------------------

import { ExportResult,ExportResultCode } from '@opentelemetry/core';
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';

import { PowerPlatformApiDiscovery, ClusterCategory } from '@microsoft/agents-a365-runtime';
import { partitionByIdentity, parseIdentityKey, hexTraceId, hexSpanId, kindName, statusName } from './utils';
import logger, {formatError} from '../../utils/logging';
import logger, { formatError } from '../../utils/logging';
import { AgenticTokenCacheInstance } from '../../utils/AgenticTokenCache';
import { Agent365ExporterOptions } from './Agent365ExporterOptions';

const DEFAULT_HTTP_TIMEOUT_SECONDS = 30000; // 30 seconds in ms
const DEFAULT_MAX_RETRIES = 3;
Expand All @@ -14,7 +21,7 @@ interface OTLPExportRequest {

interface ResourceSpan {
resource: {
attributes: Record<string, any> | null;
attributes: Record<string, unknown> | null;
};
scopeSpans: ScopeSpan[];
}
Expand All @@ -35,7 +42,7 @@ interface OTLPSpan {
kind: string;
startTimeUnixNano: number;
endTimeUnixNano: number;
attributes: Record<string, any> | null;
attributes: Record<string, unknown> | null;
events?: OTLPEvent[] | null;
links?: OTLPLink[] | null;
status: OTLPStatus;
Expand All @@ -44,13 +51,13 @@ interface OTLPSpan {
interface OTLPEvent {
timeUnixNano: number;
name: string;
attributes?: Record<string, any> | null;
attributes?: Record<string, unknown> | null;
}

interface OTLPLink {
traceId: string;
spanId: string;
attributes?: Record<string, any> | null;
attributes?: Record<string, unknown> | null;
}

interface OTLPStatus {
Expand All @@ -71,20 +78,25 @@ export type TokenResolver = (agentId: string, tenantId: string) => string | null
* - Adds Bearer token via token_resolver(agentId, tenantId)
*/
export class Agent365Exporter implements SpanExporter {
private readonly tokenResolver: TokenResolver;
private readonly clusterCategory: ClusterCategory;
private closed = false;
private readonly options: Agent365ExporterOptions;
Comment thread
fpfp100 marked this conversation as resolved.

/**
* Initialize exporter with a fully constructed options instance.
* If tokenResolver is missing, installs cache-backed resolver.
*/
constructor(options: Agent365ExporterOptions) {
if (!options) {
throw new Error('Agent365ExporterOptions must be provided (was null/undefined)');
}

constructor(
tokenResolver: TokenResolver,
clusterCategory: ClusterCategory = 'prod'
) {
if (!tokenResolver) {
logger.error('[Agent365Exporter] token_resolver is not provided');
throw new Error('token_resolver must be provided.');
if (!options.tokenResolver) {
options.tokenResolver = AgenticTokenCacheInstance.getObservabilityToken.bind(AgenticTokenCacheInstance);
logger.info('Agent365Exporter initialized with agentic resolver', `clusterCategory=${options.clusterCategory}`);
} else {
logger.info('Agent365Exporter initialized with custom tokenResolver', `clusterCategory=${options.clusterCategory}`);
}
this.tokenResolver = tokenResolver;
this.clusterCategory = clusterCategory;
this.options = options;
}

/**
Expand Down Expand Up @@ -140,7 +152,7 @@ export class Agent365Exporter implements SpanExporter {
const body = JSON.stringify(payload);

// Resolve endpoint + token
const discovery = new PowerPlatformApiDiscovery(this.clusterCategory);
const discovery = new PowerPlatformApiDiscovery(this.options.clusterCategory as ClusterCategory);
const endpoint = discovery.getTenantIslandClusterEndpoint(tenantId);
const url = `https://${endpoint}/maven/agent365/agents/${agentId}/traces?api-version=1`;
logger.info(`[Agent365Exporter] Resolved endpoint: ${endpoint}`);
Expand All @@ -149,7 +161,7 @@ export class Agent365Exporter implements SpanExporter {
'content-type': 'application/json'
};

const tokenResult = this.tokenResolver(agentId, tenantId);
const tokenResult = this.options.tokenResolver!(agentId, tenantId,);
Comment thread
fpfp100 marked this conversation as resolved.
Outdated
const token = tokenResult instanceof Promise ? await tokenResult : tokenResult;
if (token) {
headers['authorization'] = `Bearer ${token}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------------------------

import { ClusterCategory } from '@microsoft/agents-a365-runtime';
import { TokenResolver } from './Agent365Exporter';

/**
Comment thread
fpfp100 marked this conversation as resolved.
Outdated
* Configuration for Agent365Exporter.
* Only ClusterCategory and TokenResolver are required for core operation.
*/
export class Agent365ExporterOptions {
/**
* Environment / cluster category
*/
public clusterCategory: ClusterCategory | string = 'preprod';

/**
* Resolver used to resolve the auth token. Optional - falls back to AgenticTokenCache when not provided.
*/
public tokenResolver?: TokenResolver;

/**
* When true, uses the service-to-service (S2S) endpoint path: /maven/agent365/service/agents/{agentId}/traces
* When false (default), uses the standard endpoint path: /maven/agent365/agents/{agentId}/traces
*/
public useS2SEndpoint: boolean = false;
Comment thread
fpfp100 marked this conversation as resolved.
Outdated

/**
* Maximum queue size for the batch processor.
*/
public maxQueueSize: number = 2048;

/**
* Delay in milliseconds between export batches.
*/
public scheduledDelayMilliseconds: number = 5000;

/**
* Timeout in milliseconds for the export operation.
*/
public exporterTimeoutMilliseconds: number = 30000;

/**
* Maximum batch size for export operations.
*/
Comment thread
fpfp100 marked this conversation as resolved.
Outdated
public maxExportBatchSize: number = 512;
}
Loading
Loading