Skip to content

Commit a7a2014

Browse files
authored
Merge branch 'main' into users/pefan/bugfix_12_19
2 parents 969daf3 + 7a2f094 commit a7a2014

5 files changed

Lines changed: 122 additions & 32 deletions

File tree

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Licensed under the MIT License.
44
// ------------------------------------------------------------------------------
55

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

99
import { PowerPlatformApiDiscovery, ClusterCategory } from '@microsoft/agents-a365-runtime';
@@ -65,12 +65,12 @@ interface OTLPStatus {
6565
message?: string;
6666
}
6767

68-
6968
/**
7069
* Observability span exporter for Agent365:
7170
* - Partitions spans by (tenantId, agentId)
7271
* - Builds OTLP-like JSON: resourceSpans -> scopeSpans -> spans
7372
* - POSTs per group to https://{endpoint}/maven/agent365/agents/{agentId}/traces?api-version=1
73+
* or, when useS2SEndpoint is true, https://{endpoint}/maven/agent365/service/agents/{agentId}/traces?api-version=1
7474
* - Adds Bearer token via token_resolver(agentId, tenantId)
7575
*/
7676
export class Agent365Exporter implements SpanExporter {
@@ -147,16 +147,23 @@ export class Agent365Exporter implements SpanExporter {
147147

148148
const usingCustomServiceEndpoint = useCustomDomainForObservability();
149149

150+
// Select endpoint path based on S2S flag
151+
const endpointPath =
152+
this.options.useS2SEndpoint
153+
? `/maven/agent365/service/agents/${agentId}/traces`
154+
: `/maven/agent365/agents/${agentId}/traces`;
155+
150156
let url: string;
151157
if (usingCustomServiceEndpoint) {
152-
url = resolveAgent365Endpoint(this.options.clusterCategory as ClusterCategory);
158+
const base = resolveAgent365Endpoint(this.options.clusterCategory as ClusterCategory);
159+
url = `${base}${endpointPath}?api-version=1`;
153160
logger.info(`[Agent365Exporter] Using custom domain endpoint: ${url}`);
154161
} else {
155162
// Default behavior: discover PPAPI gateway endpoint per-tenant
156163
const discovery = new PowerPlatformApiDiscovery(this.options.clusterCategory as ClusterCategory);
157164
const endpoint = discovery.getTenantIslandClusterEndpoint(tenantId);
158-
url = `https://${endpoint}/maven/agent365/agents/${agentId}/traces?api-version=1`;
159-
logger.info(`[Agent365Exporter] Resolved endpoint: ${endpoint}`);
165+
url = `https://${endpoint}${endpointPath}?api-version=1`;
166+
logger.info(`[Agent365Exporter] Resolved endpoint: ${url}`);
160167
}
161168

162169
const headers: Record<string, string> = {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type TokenResolver = (agentId: string, tenantId: string) => string | null
2020
* @property {ClusterCategory | string} clusterCategory Environment / cluster category (e.g. "preprod", "prod", default to "prod").
2121
* @property {TokenResolver} [tokenResolver] Optional delegate to obtain an auth token. If omitted the exporter will
2222
* fall back to reading the cached token (AgenticTokenCacheInstance.getObservabilityToken).
23+
* @property {boolean} [useS2SEndpoint] When true, exporter will POST to the S2S path (/maven/agent365/service/agents/{agentId}/traces).
2324
* @property {number} maxQueueSize Maximum span queue size before drops occur (passed to BatchSpanProcessor).
2425
* @property {number} scheduledDelayMilliseconds Delay between automatic batch flush attempts.
2526
* @property {number} exporterTimeoutMilliseconds Per-export timeout (abort if exceeded).
@@ -32,6 +33,9 @@ export class Agent365ExporterOptions {
3233
/** Optional delegate to resolve auth token used by exporter */
3334
public tokenResolver?: TokenResolver; // Optional if ENABLE_A365_OBSERVABILITY_EXPORTER is false
3435

36+
/** When true, use S2S endpoint path for export. */
37+
public useS2SEndpoint: boolean = false;
38+
3539
/** Maximum span queue size before new spans are dropped. */
3640
public maxQueueSize: number = 2048;
3741

pnpm-lock.yaml

Lines changed: 45 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ catalog:
1818
"jsonwebtoken": "^9.0.3"
1919

2020
# LangChain packages - use latest stable
21-
"langchain": "^1.0.1"
22-
"@langchain/core": "^1.0.2"
23-
"@langchain/mcp-adapters": "^1.0.0"
21+
"langchain": "^1.2.3"
22+
"@langchain/core": "^1.1.8"
23+
"@langchain/mcp-adapters": "^1.1.1"
2424

2525
# Microsoft 365 Agents SDK packages
2626
"@microsoft/agents-hosting": "^1.1.0-alpha.85"

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe('Agent365Exporter', () => {
126126
expect(fetchCalls.length).toBe(1);
127127
const urlArg = fetchCalls[0][0];
128128
const headersArg = fetchCalls[0][1].headers;
129-
expect(urlArg).toBe(expectedUrl);
129+
expect(urlArg).toBe(`${expectedUrl}/maven/agent365/agents/${agentId}/traces?api-version=1`);
130130
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);
131131
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
132132
});
@@ -164,4 +164,61 @@ describe('Agent365Exporter', () => {
164164
// Intentionally omit tokenResolver
165165
expect(() => new Agent365Exporter(opts)).toThrow(/tokenResolver must be provided/);
166166
});
167+
it('uses S2S endpoint path when useS2SEndpoint is true (discovery flow)', async () => {
168+
mockFetchSequence([200]);
169+
const token = 'tok-s2s';
170+
const opts = new Agent365ExporterOptions();
171+
opts.clusterCategory = 'prod';
172+
opts.tokenResolver = () => token;
173+
opts.useS2SEndpoint = true;
174+
175+
const exporter = new Agent365Exporter(opts);
176+
const spans = [
177+
makeSpan({
178+
[OpenTelemetryConstants.TENANT_ID_KEY]: tenantId,
179+
[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: agentId
180+
}, 's2s-span')
181+
];
182+
183+
const callback = jest.fn();
184+
await exporter.export(spans, callback);
185+
186+
expect(callback).toHaveBeenCalledWith({ code: ExportResultCode.SUCCESS });
187+
const fetchCalls = (global.fetch as unknown as { mock: { calls: any[] } }).mock.calls;
188+
expect(fetchCalls.length).toBe(1);
189+
190+
const urlArg = fetchCalls[0][0] as string;
191+
expect(urlArg).toMatch(`/maven/agent365/service/agents/${agentId}/traces?api-version=1`);
192+
const headersArg = fetchCalls[0][1].headers as Record<string, string>;
193+
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
194+
});
195+
196+
it('uses S2S endpoint path with custom domain and sets x-ms-tenant-id', async () => {
197+
mockFetchSequence([200]);
198+
process.env.A365_OBSERVABILITY_USE_CUSTOM_DOMAIN = 'true';
199+
const token = 'tok-s2s-custom';
200+
const opts = new Agent365ExporterOptions();
201+
opts.clusterCategory = 'prod';
202+
opts.tokenResolver = () => token;
203+
opts.useS2SEndpoint = true;
204+
205+
const exporter = new Agent365Exporter(opts);
206+
const spans = [
207+
makeSpan({
208+
[OpenTelemetryConstants.TENANT_ID_KEY]: tenantId,
209+
[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: agentId
210+
}, 's2s-custom-span')
211+
];
212+
213+
const callback = jest.fn();
214+
await exporter.export(spans, callback);
215+
216+
const fetchCalls = (global.fetch as unknown as { mock: { calls: any[] } }).mock.calls;
217+
expect(fetchCalls.length).toBe(1);
218+
const urlArg = fetchCalls[0][0] as string;
219+
expect(urlArg).toMatch(`/maven/agent365/service/agents/${agentId}/traces?api-version=1`);
220+
const headersArg = fetchCalls[0][1].headers as Record<string, string>;
221+
expect(headersArg['authorization']).toBe(`Bearer ${token}`);
222+
expect(headersArg['x-ms-tenant-id']).toBe(tenantId);
223+
})
167224
});

0 commit comments

Comments
 (0)