From db89ca3bdc0b228edb08d3e5373aafb558b8bc29 Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 17 Nov 2025 12:55:27 -0800 Subject: [PATCH 1/5] session id --- .../src/tracing/middleware/BaggageBuilder.ts | 16 ++++++++- .../src/tracing/processors/util.ts | 1 + .../observability/core/BaggageBuilder.test.ts | 34 ++++++++++++++++++- .../observability/core/SpanProcessor.test.ts | 27 +++++++++++++-- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 19b6f297..66f50f2e 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -17,6 +17,7 @@ import { OpenTelemetryConstants } from '../constants'; * .tenantId("tenant-123") * .agentId("agent-456") * .correlationId("corr-789") + * .sessionId("session-0001") * .build(); * * scope.enter(); @@ -99,6 +100,16 @@ export class BaggageBuilder { return this; } + /** + * Set the session ID baggage value. + * @param value The session ID + * @returns Self for method chaining + */ + sessionId(value: string | null | undefined): BaggageBuilder { + this.set(OpenTelemetryConstants.SESSION_ID_KEY, value); + return this; + } + /** * Set the caller ID baggage value. * @param value The caller ID @@ -262,17 +273,20 @@ export class BaggageBuilder { * @param tenantId The tenant ID * @param agentId The agent ID * @param correlationId The correlation ID + * @param sessionId Optional session ID * @returns A context manager that restores the previous baggage on exit */ static setRequestContext( tenantId?: string | null, agentId?: string | null, - correlationId?: string | null + correlationId?: string | null, + sessionId?: string | null ): BaggageScope { return new BaggageBuilder() .tenantId(tenantId) .agentId(agentId) .correlationId(correlationId) + .sessionId(sessionId) .build(); } } diff --git a/packages/agents-a365-observability/src/tracing/processors/util.ts b/packages/agents-a365-observability/src/tracing/processors/util.ts index b9aa94c4..4b960dae 100644 --- a/packages/agents-a365-observability/src/tracing/processors/util.ts +++ b/packages/agents-a365-observability/src/tracing/processors/util.ts @@ -12,6 +12,7 @@ export const GENERIC_ATTRIBUTES: readonly string[] = [ consts.CUSTOM_PARENT_SPAN_ID_KEY, consts.CUSTOM_SPAN_NAME_KEY, consts.CORRELATION_ID_KEY, + consts.SESSION_ID_KEY, consts.GEN_AI_CONVERSATION_ID_KEY, consts.GEN_AI_CONVERSATION_ITEM_LINK_KEY, consts.GEN_AI_OPERATION_NAME_KEY, diff --git a/tests/observability/core/BaggageBuilder.test.ts b/tests/observability/core/BaggageBuilder.test.ts index 73ab2e41..7630c73c 100644 --- a/tests/observability/core/BaggageBuilder.test.ts +++ b/tests/observability/core/BaggageBuilder.test.ts @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------------ -import { context } from '@opentelemetry/api'; +import { context, propagation } from '@opentelemetry/api'; import { BaggageBuilder, BaggageScope } from '@microsoft/agents-a365-observability/dist/cjs/tracing/middleware/BaggageBuilder'; import { OpenTelemetryConstants } from '@microsoft/agents-a365-observability/dist/cjs/tracing/constants'; @@ -133,6 +133,38 @@ describe('BaggageBuilder', () => { }); }); + describe('sessionId support', () => { + it('should set sessionId via fluent API', () => { + const scope = new BaggageBuilder() + .tenantId('tenant-123') + .agentId('agent-456') + .correlationId('corr-789') + .sessionId('session-0001') + .build(); + const bag = propagation.getBaggage((scope as any).contextWithBaggage); + expect(bag?.getEntry(OpenTelemetryConstants.SESSION_ID_KEY)?.value).toBe('session-0001'); + }); + + it('should set sessionId via static setRequestContext', () => { + const scope = BaggageBuilder.setRequestContext( + 'tenant-123', + 'agent-456', + 'corr-789', + 'session-0002' + ); + const bag = propagation.getBaggage((scope as any).contextWithBaggage); + expect(bag?.getEntry(OpenTelemetryConstants.SESSION_ID_KEY)?.value).toBe('session-0002'); + }); + + it('should omit empty sessionId value', () => { + const scope = new BaggageBuilder() + .sessionId(' ') + .build(); + const bag = propagation.getBaggage((scope as any).contextWithBaggage); + expect(bag?.getEntry(OpenTelemetryConstants.SESSION_ID_KEY)).toBeUndefined(); + }); + }); + }); describe('BaggageScope', () => { diff --git a/tests/observability/core/SpanProcessor.test.ts b/tests/observability/core/SpanProcessor.test.ts index 32b75f8b..7b89ca43 100644 --- a/tests/observability/core/SpanProcessor.test.ts +++ b/tests/observability/core/SpanProcessor.test.ts @@ -31,7 +31,8 @@ describe('SpanProcessor', () => { const baggageEntries = { [OpenTelemetryConstants.TENANT_ID_KEY]: 'tenant-123', [OpenTelemetryConstants.CORRELATION_ID_KEY]: 'corr-456', - [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: 'agent-789' + [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: 'agent-789', + [OpenTelemetryConstants.SESSION_ID_KEY]: 'session-001' }; let baggage = propagation.createBaggage(); @@ -52,8 +53,29 @@ describe('SpanProcessor', () => { } }); - // The span processor should have copied baggage to attributes expect(testSpan).toBeDefined(); + const attrs = (testSpan as any).attributes || {}; + expect(attrs[OpenTelemetryConstants.SESSION_ID_KEY]).toBe('session-001'); + }); + + it('should copy sessionId from baggage to span', () => { + let baggage = propagation.createBaggage(); + baggage = baggage.setEntry(OpenTelemetryConstants.SESSION_ID_KEY, { value: 'session-abc' }); + + const ctx = propagation.setBaggage(context.active(), baggage); + + const tracer = provider.getTracer('test'); + let testSpan: Span | undefined; + context.with(ctx, () => { + testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }); + if (testSpan) { + testSpan.end(); + } + }); + + expect(testSpan).toBeDefined(); + const attrs = (testSpan as any).attributes || {}; + expect(attrs[OpenTelemetryConstants.SESSION_ID_KEY]).toBe('session-abc'); }); it('should copy invoke agent attributes for invoke_agent operations', () => { @@ -139,6 +161,7 @@ describe('SpanProcessor', () => { expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.TENANT_ID_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.CORRELATION_ID_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.SESSION_ID_KEY); }); it('should apply invoke agent specific attributes', () => { From aa494707e894f6c96991322074aa208e0ac5648d Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 17 Nov 2025 13:21:09 -0800 Subject: [PATCH 2/5] fix test --- .../observability/core/SpanProcessor.test.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/observability/core/SpanProcessor.test.ts b/tests/observability/core/SpanProcessor.test.ts index 7b89ca43..1cdd9145 100644 --- a/tests/observability/core/SpanProcessor.test.ts +++ b/tests/observability/core/SpanProcessor.test.ts @@ -45,17 +45,16 @@ describe('SpanProcessor', () => { // Create a span in this context const tracer = provider.getTracer('test'); let testSpan: Span | undefined; - - context.with(ctx, () => { - testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }); - if (testSpan) { - testSpan.end(); - } - }); + // Pass baggage context explicitly to ensure processor receives parentContext + testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }, ctx as any); + (testSpan as Span).end(); expect(testSpan).toBeDefined(); - const attrs = (testSpan as any).attributes || {}; + const attrs = (testSpan as any)._attributes || {}; expect(attrs[OpenTelemetryConstants.SESSION_ID_KEY]).toBe('session-001'); + expect(attrs[OpenTelemetryConstants.TENANT_ID_KEY]).toBe('tenant-123'); + expect(attrs[OpenTelemetryConstants.CORRELATION_ID_KEY]).toBe('corr-456'); + expect(attrs[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe('agent-789'); }); it('should copy sessionId from baggage to span', () => { @@ -66,15 +65,11 @@ describe('SpanProcessor', () => { const tracer = provider.getTracer('test'); let testSpan: Span | undefined; - context.with(ctx, () => { - testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }); - if (testSpan) { - testSpan.end(); - } - }); + testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }, ctx as any); + (testSpan as Span).end(); expect(testSpan).toBeDefined(); - const attrs = (testSpan as any).attributes || {}; + const attrs = (testSpan as any)._attributes || {}; expect(attrs[OpenTelemetryConstants.SESSION_ID_KEY]).toBe('session-abc'); }); From 9a8fa4d19efd07fd0cdb5711290247e09455885b Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 17 Nov 2025 13:40:06 -0800 Subject: [PATCH 3/5] comment and fix test --- .../src/tracing/middleware/BaggageBuilder.ts | 2 +- .../observability/core/SpanProcessor.test.ts | 31 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 66f50f2e..2a00b9f3 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -105,7 +105,7 @@ export class BaggageBuilder { * @param value The session ID * @returns Self for method chaining */ - sessionId(value: string | null | undefined): BaggageBuilder { + sessionId(value: string | undefined): BaggageBuilder { this.set(OpenTelemetryConstants.SESSION_ID_KEY, value); return this; } diff --git a/tests/observability/core/SpanProcessor.test.ts b/tests/observability/core/SpanProcessor.test.ts index 1cdd9145..2b175904 100644 --- a/tests/observability/core/SpanProcessor.test.ts +++ b/tests/observability/core/SpanProcessor.test.ts @@ -27,12 +27,10 @@ describe('SpanProcessor', () => { describe('baggage to span attribute enrichment', () => { it('should copy generic attributes from baggage to span', () => { - // Set baggage const baggageEntries = { [OpenTelemetryConstants.TENANT_ID_KEY]: 'tenant-123', [OpenTelemetryConstants.CORRELATION_ID_KEY]: 'corr-456', - [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: 'agent-789', - [OpenTelemetryConstants.SESSION_ID_KEY]: 'session-001' + [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: 'agent-789' }; let baggage = propagation.createBaggage(); @@ -42,34 +40,31 @@ describe('SpanProcessor', () => { const ctx = propagation.setBaggage(context.active(), baggage); - // Create a span in this context + // Create a span in this context (parentContext not passed so processor may no-op) const tracer = provider.getTracer('test'); let testSpan: Span | undefined; - // Pass baggage context explicitly to ensure processor receives parentContext - testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }, ctx as any); - (testSpan as Span).end(); + + context.with(ctx, () => { + testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }); + if (testSpan) { + testSpan.end(); + } + }); expect(testSpan).toBeDefined(); - const attrs = (testSpan as any)._attributes || {}; - expect(attrs[OpenTelemetryConstants.SESSION_ID_KEY]).toBe('session-001'); - expect(attrs[OpenTelemetryConstants.TENANT_ID_KEY]).toBe('tenant-123'); - expect(attrs[OpenTelemetryConstants.CORRELATION_ID_KEY]).toBe('corr-456'); - expect(attrs[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe('agent-789'); }); + it('should copy sessionId from baggage to span', () => { let baggage = propagation.createBaggage(); baggage = baggage.setEntry(OpenTelemetryConstants.SESSION_ID_KEY, { value: 'session-abc' }); const ctx = propagation.setBaggage(context.active(), baggage); - const tracer = provider.getTracer('test'); - let testSpan: Span | undefined; - testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }, ctx as any); - (testSpan as Span).end(); + const testSpan = tracer.startSpan('test-span', { kind: SpanKind.CLIENT }, ctx as any); + testSpan.end(); - expect(testSpan).toBeDefined(); - const attrs = (testSpan as any)._attributes || {}; + const attrs = (testSpan as any)._attributes ?? (testSpan as any).attributes ?? {}; expect(attrs[OpenTelemetryConstants.SESSION_ID_KEY]).toBe('session-abc'); }); From e57e025748779764487f65a335315270951a9ac7 Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 17 Nov 2025 13:47:22 -0800 Subject: [PATCH 4/5] fix build failure --- .../src/tracing/middleware/BaggageBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 2a00b9f3..4e55ac35 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -280,7 +280,7 @@ export class BaggageBuilder { tenantId?: string | null, agentId?: string | null, correlationId?: string | null, - sessionId?: string | null + sessionId?: string ): BaggageScope { return new BaggageBuilder() .tenantId(tenantId) From 21bc46613c710382b255b7dae8b519684e615eb5 Mon Sep 17 00:00:00 2001 From: jsl517 Date: Mon, 17 Nov 2025 16:36:09 -0800 Subject: [PATCH 5/5] remove session id from setRequestContext --- .../src/tracing/middleware/BaggageBuilder.ts | 6 +----- tests/observability/core/BaggageBuilder.test.ts | 11 ----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 4e55ac35..fc2e1345 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -17,7 +17,6 @@ import { OpenTelemetryConstants } from '../constants'; * .tenantId("tenant-123") * .agentId("agent-456") * .correlationId("corr-789") - * .sessionId("session-0001") * .build(); * * scope.enter(); @@ -105,7 +104,7 @@ export class BaggageBuilder { * @param value The session ID * @returns Self for method chaining */ - sessionId(value: string | undefined): BaggageBuilder { + sessionId(value: string): BaggageBuilder { this.set(OpenTelemetryConstants.SESSION_ID_KEY, value); return this; } @@ -273,20 +272,17 @@ export class BaggageBuilder { * @param tenantId The tenant ID * @param agentId The agent ID * @param correlationId The correlation ID - * @param sessionId Optional session ID * @returns A context manager that restores the previous baggage on exit */ static setRequestContext( tenantId?: string | null, agentId?: string | null, correlationId?: string | null, - sessionId?: string ): BaggageScope { return new BaggageBuilder() .tenantId(tenantId) .agentId(agentId) .correlationId(correlationId) - .sessionId(sessionId) .build(); } } diff --git a/tests/observability/core/BaggageBuilder.test.ts b/tests/observability/core/BaggageBuilder.test.ts index 7630c73c..e3afd04a 100644 --- a/tests/observability/core/BaggageBuilder.test.ts +++ b/tests/observability/core/BaggageBuilder.test.ts @@ -145,17 +145,6 @@ describe('BaggageBuilder', () => { expect(bag?.getEntry(OpenTelemetryConstants.SESSION_ID_KEY)?.value).toBe('session-0001'); }); - it('should set sessionId via static setRequestContext', () => { - const scope = BaggageBuilder.setRequestContext( - 'tenant-123', - 'agent-456', - 'corr-789', - 'session-0002' - ); - const bag = propagation.getBaggage((scope as any).contextWithBaggage); - expect(bag?.getEntry(OpenTelemetryConstants.SESSION_ID_KEY)?.value).toBe('session-0002'); - }); - it('should omit empty sessionId value', () => { const scope = new BaggageBuilder() .sessionId(' ')