From 716b43df7b7ea16044613649697ae6fd984223d7 Mon Sep 17 00:00:00 2001 From: Mason Zhang <12515123+MasonPinZ@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:07:33 +0900 Subject: [PATCH 1/5] feat: make BC edgeSubPath configurable --- .../BrandConcierge/configValidators.js | 9 ++- .../createConversationServiceRequest.js | 9 ++- .../createSendConversationEvent.js | 2 + .../createConversationServiceRequest.spec.js | 30 ++++--- .../components/BrandConcierge/index.spec.js | 81 +++++++++++-------- 5 files changed, 86 insertions(+), 45 deletions(-) diff --git a/packages/core/src/components/BrandConcierge/configValidators.js b/packages/core/src/components/BrandConcierge/configValidators.js index 4fc4fdf1f..d62759e92 100644 --- a/packages/core/src/components/BrandConcierge/configValidators.js +++ b/packages/core/src/components/BrandConcierge/configValidators.js @@ -10,16 +10,23 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ import { STREAM_START_TIMEOUT_MS } from "./constants.js"; -import { number, objectOf, boolean } from "../../utils/validation/index.js"; +import { + number, + objectOf, + boolean, + string, +} from "../../utils/validation/index.js"; export default objectOf({ conversation: objectOf({ + edgeSubPath: string().nonEmpty().default("/brand-concierge"), stickyConversationSession: boolean().default(false), streamTimeout: number() .integer() .minimum(STREAM_START_TIMEOUT_MS) .default(STREAM_START_TIMEOUT_MS), }).default({ + edgeSubPath: "/brand-concierge", stickyConversationSession: false, streamTimeout: STREAM_START_TIMEOUT_MS, }), diff --git a/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js b/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js index 69d36c1ed..a25c233a2 100644 --- a/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js +++ b/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js @@ -11,10 +11,15 @@ governing permissions and limitations under the License. */ import { createRequest } from "../../utils/request/index.js"; -export default ({ payload, action = "conversations", sessionId }) => { +export default ({ + payload, + action = "conversations", + sessionId, + edgeSubPath = "/brand-concierge", +}) => { return createRequest({ payload: payload, - edgeSubPath: "/brand-concierge", + edgeSubPath, requestParams: { sessionId }, getAction() { return action; diff --git a/packages/core/src/components/BrandConcierge/createSendConversationEvent.js b/packages/core/src/components/BrandConcierge/createSendConversationEvent.js index de5368f01..173bd3274 100644 --- a/packages/core/src/components/BrandConcierge/createSendConversationEvent.js +++ b/packages/core/src/components/BrandConcierge/createSendConversationEvent.js @@ -36,6 +36,7 @@ export default ({ onBeforeEventSend, conversation, } = config; + const { edgeSubPath } = conversation; return (options) => { let streamingEnabled = false; @@ -46,6 +47,7 @@ export default ({ const request = createConversationServiceRequest({ payload, sessionId: sessionId, + edgeSubPath, }); const event = eventManager.createEvent(); diff --git a/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js b/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js index 08feff5a6..3d83bf684 100644 --- a/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js +++ b/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js @@ -19,7 +19,7 @@ describe("createConversationServiceRequest", () => { beforeEach(() => { mockPayload = { - message: "test message" + message: "test message", }; mockSessionId = "test-session-123"; }); @@ -29,7 +29,7 @@ describe("createConversationServiceRequest", () => { return createConversationServiceRequest({ payload: mockPayload, sessionId: mockSessionId, - ...options + ...options, }); }; @@ -38,7 +38,7 @@ describe("createConversationServiceRequest", () => { it("creates a request with proper payload", () => { const request = createConversationServiceRequest({ payload: mockPayload, - sessionId: mockSessionId + sessionId: mockSessionId, }); expect(request).toBeDefined(); @@ -49,7 +49,7 @@ describe("createConversationServiceRequest", () => { it("provides the appropriate action", () => { const request = createConversationServiceRequest({ payload: mockPayload, - sessionId: mockSessionId + sessionId: mockSessionId, }); expect(request.getAction()).toBe("conversations"); @@ -59,7 +59,7 @@ describe("createConversationServiceRequest", () => { const request = createConversationServiceRequest({ payload: mockPayload, action: "custom-action", - sessionId: mockSessionId + sessionId: mockSessionId, }); expect(request.getAction()).toBe("custom-action"); @@ -68,7 +68,7 @@ describe("createConversationServiceRequest", () => { it("never uses sendBeacon", () => { const request = createConversationServiceRequest({ payload: mockPayload, - sessionId: mockSessionId + sessionId: mockSessionId, }); expect(request.getUseSendBeacon()).toBe(false); @@ -77,20 +77,30 @@ describe("createConversationServiceRequest", () => { it("includes sessionId in request parameters", () => { const request = createConversationServiceRequest({ payload: mockPayload, - sessionId: mockSessionId + sessionId: mockSessionId, }); expect(request.getRequestParams()).toEqual({ - sessionId: mockSessionId + sessionId: mockSessionId, }); }); it("uses correct edge subpath", () => { const request = createConversationServiceRequest({ payload: mockPayload, - sessionId: mockSessionId + sessionId: mockSessionId, }); expect(request.getEdgeSubPath()).toBe("/brand-concierge"); }); -}); \ No newline at end of file + + it("allows custom edge subpath", () => { + const request = createConversationServiceRequest({ + payload: mockPayload, + sessionId: mockSessionId, + edgeSubPath: "/custom-brand-concierge", + }); + + expect(request.getEdgeSubPath()).toBe("/custom-brand-concierge"); + }); +}); diff --git a/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js b/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js index 377915e5d..c3e651fe0 100644 --- a/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js +++ b/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js @@ -24,21 +24,21 @@ describe("BrandConcierge", () => { loggingCookieJar: { remove: vi.fn(), get: vi.fn(), - set: vi.fn() + set: vi.fn(), }, logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), logOnBeforeNetworkRequest: vi.fn(), - logOnNetworkError: vi.fn() + logOnNetworkError: vi.fn(), }, eventManager: { - createEvent: vi.fn() + createEvent: vi.fn(), }, consent: { suspend: vi.fn(), - resume: vi.fn() + resume: vi.fn(), }, instanceName: "test-instance", sendEdgeNetworkRequest: vi.fn(), @@ -46,19 +46,19 @@ describe("BrandConcierge", () => { orgId: "testorgid@AdobeOrg", edgeConfigId: "test-edge-config-id", conversation: { - stickyConversationSession: false - } + stickyConversationSession: false, + }, }, lifecycle: { onBeforeEvent: vi.fn(), onBeforeRequest: vi.fn(), - onRequestFailure: vi.fn() + onRequestFailure: vi.fn(), }, cookieTransfer: { - cookiesToPayload: vi.fn() + cookiesToPayload: vi.fn(), }, createResponse: vi.fn(), - apexDomain: "adobe.com" + apexDomain: "adobe.com", }; }); @@ -78,18 +78,18 @@ describe("BrandConcierge", () => { const configWithSticky = { ...mockDependencies.config, conversation: { - stickyConversationSession: false - } + stickyConversationSession: false, + }, }; createConciergeComponent({ ...mockDependencies, - config: configWithSticky + config: configWithSticky, }); expect(mockDependencies.loggingCookieJar.remove).toHaveBeenCalledWith( "kndctr_testorgid_AdobeOrg_bc_session_id", - { domain: "adobe.com" } + { domain: "adobe.com" }, ); }); @@ -97,13 +97,13 @@ describe("BrandConcierge", () => { const configWithSticky = { ...mockDependencies.config, conversation: { - stickyConversationSession: true - } + stickyConversationSession: true, + }, }; createConciergeComponent({ ...mockDependencies, - config: configWithSticky + config: configWithSticky, }); expect(mockDependencies.loggingCookieJar.remove).not.toHaveBeenCalled(); @@ -112,15 +112,21 @@ describe("BrandConcierge", () => { it("sendConversationEvent command has options validator", () => { const component = createConciergeComponent(mockDependencies); - expect(component.commands.sendConversationEvent.optionsValidator).toBeDefined(); - expect(typeof component.commands.sendConversationEvent.optionsValidator).toBe("function"); + expect( + component.commands.sendConversationEvent.optionsValidator, + ).toBeDefined(); + expect( + typeof component.commands.sendConversationEvent.optionsValidator, + ).toBe("function"); }); it("sendConversationEvent command has run function", () => { const component = createConciergeComponent(mockDependencies); expect(component.commands.sendConversationEvent.run).toBeDefined(); - expect(typeof component.commands.sendConversationEvent.run).toBe("function"); + expect(typeof component.commands.sendConversationEvent.run).toBe( + "function", + ); }); }); @@ -128,25 +134,36 @@ describe("BrandConcierge config validators", () => { testConfigValidators({ configValidators: createConciergeComponent.configValidators, validConfigurations: [ - {conversation: { stickyConversationSession: true }}, - {conversation: { stickyConversationSession: false }}, - {conversation: { streamTimeout: 10000 }}, - {conversation: { streamTimeout: 20000 }}, - {conversation: { stickyConversationSession: true, streamTimeout: 10000 }}, - {} + { conversation: { edgeSubPath: "/brand-concierge" } }, + { conversation: { edgeSubPath: "/custom-brand-concierge" } }, + { conversation: { stickyConversationSession: true } }, + { conversation: { stickyConversationSession: false } }, + { conversation: { streamTimeout: 10000 } }, + { conversation: { streamTimeout: 20000 } }, + { + conversation: { + edgeSubPath: "/custom-brand-concierge", + stickyConversationSession: true, + streamTimeout: 10000, + }, + }, + {}, ], invalidConfigurations: [ - {conversation: { stickyConversationSession: "invalid" }}, - {conversation: { stickyConversationSession: 123 }}, - {conversation: { streamTimeout: "invalid" }}, - {conversation: { streamTimeout: -1 }}, - {conversation: { streamTimeout: 1.5 }} + { conversation: { edgeSubPath: "" } }, + { conversation: { edgeSubPath: 123 } }, + { conversation: { stickyConversationSession: "invalid" } }, + { conversation: { stickyConversationSession: 123 } }, + { conversation: { streamTimeout: "invalid" } }, + { conversation: { streamTimeout: -1 } }, + { conversation: { streamTimeout: 1.5 } }, ], - defaultValues: {} + defaultValues: {}, }); it("provides default values for concierge configuration", () => { const config = createConciergeComponent.configValidators({}); + expect(config.conversation.edgeSubPath).toBe("/brand-concierge"); expect(config.conversation.stickyConversationSession).toBe(false); expect(config.conversation.streamTimeout).toBe(10000); }); -}); \ No newline at end of file +}); From b1bdcdb6a4f412284078805244a805a42663dc3e Mon Sep 17 00:00:00 2001 From: Mason Zhang <12515123+MasonPinZ@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:28:38 +0900 Subject: [PATCH 2/5] build: add changeset --- .changeset/free-pots-peel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/free-pots-peel.md diff --git a/.changeset/free-pots-peel.md b/.changeset/free-pots-peel.md new file mode 100644 index 000000000..39944b8c1 --- /dev/null +++ b/.changeset/free-pots-peel.md @@ -0,0 +1,5 @@ +--- +"@adobe/alloy": patch +--- + +Added support for configuring Brand Concierge `conversation.edgeSubPath` so requests can target custom Edge subpaths. Included unit test coverage for default and custom subpath behavior. From 6cdb16ee8de59cf47bfb7cb8bab5bd3823f80cc2 Mon Sep 17 00:00:00 2001 From: Mason Zhang <12515123+MasonPinZ@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:22:37 +0900 Subject: [PATCH 3/5] feat: use boolean flag instead of arbitrary string edgeSubPath --- .../components/BrandConcierge/configValidators.js | 11 +++-------- .../createConversationServiceRequest.js | 6 +++++- .../BrandConcierge/createSendConversationEvent.js | 4 ++-- .../createConversationServiceRequest.spec.js | 8 ++++---- .../specs/components/BrandConcierge/index.spec.js | 12 ++++++------ 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/core/src/components/BrandConcierge/configValidators.js b/packages/core/src/components/BrandConcierge/configValidators.js index d62759e92..5c47d0d48 100644 --- a/packages/core/src/components/BrandConcierge/configValidators.js +++ b/packages/core/src/components/BrandConcierge/configValidators.js @@ -10,23 +10,18 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ import { STREAM_START_TIMEOUT_MS } from "./constants.js"; -import { - number, - objectOf, - boolean, - string, -} from "../../utils/validation/index.js"; +import { number, objectOf, boolean } from "../../utils/validation/index.js"; export default objectOf({ conversation: objectOf({ - edgeSubPath: string().nonEmpty().default("/brand-concierge"), + voiceEnabled: boolean().default(false), stickyConversationSession: boolean().default(false), streamTimeout: number() .integer() .minimum(STREAM_START_TIMEOUT_MS) .default(STREAM_START_TIMEOUT_MS), }).default({ - edgeSubPath: "/brand-concierge", + voiceEnabled: false, stickyConversationSession: false, streamTimeout: STREAM_START_TIMEOUT_MS, }), diff --git a/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js b/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js index a25c233a2..6e682f651 100644 --- a/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js +++ b/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js @@ -15,8 +15,12 @@ export default ({ payload, action = "conversations", sessionId, - edgeSubPath = "/brand-concierge", + voiceEnabled = false, }) => { + const edgeSubPath = voiceEnabled + ? "/brand-concierge-voice" + : "/brand-concierge"; + return createRequest({ payload: payload, edgeSubPath, diff --git a/packages/core/src/components/BrandConcierge/createSendConversationEvent.js b/packages/core/src/components/BrandConcierge/createSendConversationEvent.js index 173bd3274..0fb047916 100644 --- a/packages/core/src/components/BrandConcierge/createSendConversationEvent.js +++ b/packages/core/src/components/BrandConcierge/createSendConversationEvent.js @@ -36,7 +36,7 @@ export default ({ onBeforeEventSend, conversation, } = config; - const { edgeSubPath } = conversation; + const { voiceEnabled } = conversation; return (options) => { let streamingEnabled = false; @@ -47,7 +47,7 @@ export default ({ const request = createConversationServiceRequest({ payload, sessionId: sessionId, - edgeSubPath, + voiceEnabled, }); const event = eventManager.createEvent(); diff --git a/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js b/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js index 3d83bf684..2e4e92aa6 100644 --- a/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js +++ b/packages/core/test/unit/specs/components/BrandConcierge/createConversationServiceRequest.spec.js @@ -85,7 +85,7 @@ describe("createConversationServiceRequest", () => { }); }); - it("uses correct edge subpath", () => { + it("uses default edge subpath when voice is disabled", () => { const request = createConversationServiceRequest({ payload: mockPayload, sessionId: mockSessionId, @@ -94,13 +94,13 @@ describe("createConversationServiceRequest", () => { expect(request.getEdgeSubPath()).toBe("/brand-concierge"); }); - it("allows custom edge subpath", () => { + it("uses voice edge subpath when voice is enabled", () => { const request = createConversationServiceRequest({ payload: mockPayload, sessionId: mockSessionId, - edgeSubPath: "/custom-brand-concierge", + voiceEnabled: true, }); - expect(request.getEdgeSubPath()).toBe("/custom-brand-concierge"); + expect(request.getEdgeSubPath()).toBe("/brand-concierge-voice"); }); }); diff --git a/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js b/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js index c3e651fe0..ddc5e71a4 100644 --- a/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js +++ b/packages/core/test/unit/specs/components/BrandConcierge/index.spec.js @@ -134,15 +134,15 @@ describe("BrandConcierge config validators", () => { testConfigValidators({ configValidators: createConciergeComponent.configValidators, validConfigurations: [ - { conversation: { edgeSubPath: "/brand-concierge" } }, - { conversation: { edgeSubPath: "/custom-brand-concierge" } }, + { conversation: { voiceEnabled: true } }, + { conversation: { voiceEnabled: false } }, { conversation: { stickyConversationSession: true } }, { conversation: { stickyConversationSession: false } }, { conversation: { streamTimeout: 10000 } }, { conversation: { streamTimeout: 20000 } }, { conversation: { - edgeSubPath: "/custom-brand-concierge", + voiceEnabled: true, stickyConversationSession: true, streamTimeout: 10000, }, @@ -150,8 +150,8 @@ describe("BrandConcierge config validators", () => { {}, ], invalidConfigurations: [ - { conversation: { edgeSubPath: "" } }, - { conversation: { edgeSubPath: 123 } }, + { conversation: { voiceEnabled: "true" } }, + { conversation: { voiceEnabled: 123 } }, { conversation: { stickyConversationSession: "invalid" } }, { conversation: { stickyConversationSession: 123 } }, { conversation: { streamTimeout: "invalid" } }, @@ -162,7 +162,7 @@ describe("BrandConcierge config validators", () => { }); it("provides default values for concierge configuration", () => { const config = createConciergeComponent.configValidators({}); - expect(config.conversation.edgeSubPath).toBe("/brand-concierge"); + expect(config.conversation.voiceEnabled).toBe(false); expect(config.conversation.stickyConversationSession).toBe(false); expect(config.conversation.streamTimeout).toBe(10000); }); From d9075076ded9c1d6d0031ca3ab28dbafa7da2b9c Mon Sep 17 00:00:00 2001 From: Mason Zhang <12515123+MasonPinZ@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:29:24 +0900 Subject: [PATCH 4/5] build: update changeset description --- .changeset/free-pots-peel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/free-pots-peel.md b/.changeset/free-pots-peel.md index 39944b8c1..309d4f457 100644 --- a/.changeset/free-pots-peel.md +++ b/.changeset/free-pots-peel.md @@ -2,4 +2,4 @@ "@adobe/alloy": patch --- -Added support for configuring Brand Concierge `conversation.edgeSubPath` so requests can target custom Edge subpaths. Included unit test coverage for default and custom subpath behavior. +Added support for configuring Brand Concierge `conversation.voiceEnabled` so the SDK routes requests differently based on the voiceEnabled configuration. Included unit test coverage for config validation and voice subpath routing behavior. From 2af35a27b625feb86c4a37d13d36de7d3e65f0db Mon Sep 17 00:00:00 2001 From: Mason Zhang <12515123+MasonPinZ@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:29:15 +0900 Subject: [PATCH 5/5] refactor: extract path constants --- .../BrandConcierge/createConversationServiceRequest.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js b/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js index 6e682f651..4b835ff9d 100644 --- a/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js +++ b/packages/core/src/components/BrandConcierge/createConversationServiceRequest.js @@ -11,6 +11,9 @@ governing permissions and limitations under the License. */ import { createRequest } from "../../utils/request/index.js"; +const BRAND_CONCIERGE_PATH = "/brand-concierge"; +const VOICE_BRAND_CONCIERGE_PATH = "/brand-concierge-voice"; + export default ({ payload, action = "conversations", @@ -18,8 +21,8 @@ export default ({ voiceEnabled = false, }) => { const edgeSubPath = voiceEnabled - ? "/brand-concierge-voice" - : "/brand-concierge"; + ? VOICE_BRAND_CONCIERGE_PATH + : BRAND_CONCIERGE_PATH; return createRequest({ payload: payload,