From 854d5454d3ec4dd550e2488968c99594f81a321e Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Fri, 27 Feb 2026 15:51:44 +0300 Subject: [PATCH 01/17] add agent-overview module --- spec/src/modules/agent-overview.js | 205 +++++++++++++++++++++++ spec/src/modules/agent.js | 159 ++++++++++++++++++ src/constructorio.js | 3 + src/modules/agent-overview.js | 49 ++++++ src/modules/agent.js | 73 +++++++- src/types/agent-overview.d.ts | 10 ++ src/types/agent.d.ts | 15 +- src/types/constructorio.d.ts | 5 +- src/types/index.d.ts | 1 + src/types/tests/agent-overview.test-d.ts | 21 +++ 10 files changed, 531 insertions(+), 10 deletions(-) create mode 100644 spec/src/modules/agent-overview.js create mode 100644 src/modules/agent-overview.js create mode 100644 src/types/agent-overview.d.ts create mode 100644 src/types/tests/agent-overview.test-d.ts diff --git a/spec/src/modules/agent-overview.js b/spec/src/modules/agent-overview.js new file mode 100644 index 00000000..f76ad4ac --- /dev/null +++ b/spec/src/modules/agent-overview.js @@ -0,0 +1,205 @@ +/* eslint-disable no-unused-expressions, import/no-unresolved */ +const dotenv = require('dotenv'); +const chai = require('chai'); +const sinon = require('sinon'); +const sinonChai = require('sinon-chai'); +const EventSource = require('eventsource'); +const { ReadableStream } = require('web-streams-polyfill'); +const AgentOverview = require('../../../src/modules/agent-overview'); +const Agent = require('../../../src/modules/agent'); +const { setupEventListeners } = require('../../../src/modules/agent'); +let ConstructorIO = require('../../../test/constructorio'); // eslint-disable-line import/extensions +const jsdom = require('../utils/jsdom-global'); + +const bundled = process.env.BUNDLED === 'true'; +const bundledDescriptionSuffix = bundled ? ' - Bundled' : ''; + +chai.use(sinonChai); +dotenv.config(); + +const testApiKey = process.env.TEST_REQUEST_API_KEY; +const clientVersion = 'cio-mocha'; + +const defaultOptions = { + apiKey: testApiKey, + version: clientVersion, + agentServiceUrl: 'https://agent.cnstrc.com', + clientId: '123', + sessionId: 123, +}; + +describe(`ConstructorIO - Agent Overview${bundledDescriptionSuffix}`, () => { + const jsdomOptions = { url: 'http://localhost' }; + let cleanup; + + beforeEach(() => { + cleanup = jsdom(jsdomOptions); + global.CLIENT_VERSION = clientVersion; + window.CLIENT_VERSION = clientVersion; + + if (bundled) { + ConstructorIO = window.ConstructorioClient; + } + }); + + afterEach(() => { + delete global.CLIENT_VERSION; + delete window.CLIENT_VERSION; + cleanup(); + }); + + describe('EventTypes', () => { + it('should include all Agent event types', () => { + Object.keys(Agent.EventTypes).forEach((key) => { + expect(AgentOverview.EventTypes).to.have.property(key); + expect(AgentOverview.EventTypes[key]).to.equal(Agent.EventTypes[key]); + }); + }); + + it('should include MESSAGE event type', () => { + expect(AgentOverview.EventTypes).to.have.property('MESSAGE').to.equal('message'); + }); + + it('should include FOLLOW_UP_QUESTIONS event type', () => { + expect(AgentOverview.EventTypes).to.have.property('FOLLOW_UP_QUESTIONS').to.equal('follow_up_questions'); + }); + }); + + describe('setupEventListeners with AgentOverview EventTypes', () => { + let mockEventSource; + let mockStreamController; + + beforeEach(() => { + mockEventSource = { + addEventListener: sinon.stub(), + close: sinon.stub(), + }; + + mockStreamController = { + enqueue: sinon.stub(), + close: sinon.stub(), + error: sinon.stub(), + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should enqueue MESSAGE event data into the stream', (done) => { + const eventType = AgentOverview.EventTypes.MESSAGE; + const eventData = { text: 'Here are some recommendations' }; + + setupEventListeners(mockEventSource, mockStreamController, AgentOverview.EventTypes); + + const messageCallback = mockEventSource.addEventListener + .getCalls() + .find((call) => call.args[0] === eventType).args[1]; + + messageCallback({ data: JSON.stringify(eventData) }); + + setImmediate(() => { + expect(mockStreamController.enqueue.calledWith({ type: eventType, data: eventData })).to.be.true; + done(); + }); + }); + + it('should enqueue FOLLOW_UP_QUESTIONS event data into the stream', (done) => { + const eventType = AgentOverview.EventTypes.FOLLOW_UP_QUESTIONS; + const eventData = { questions: ['What size?', 'What color?'] }; + + setupEventListeners(mockEventSource, mockStreamController, AgentOverview.EventTypes); + + const followUpCallback = mockEventSource.addEventListener + .getCalls() + .find((call) => call.args[0] === eventType).args[1]; + + followUpCallback({ data: JSON.stringify(eventData) }); + + setImmediate(() => { + expect(mockStreamController.enqueue.calledWith({ type: eventType, data: eventData })).to.be.true; + done(); + }); + }); + }); + + describe('getIntentResults', () => { + beforeEach(() => { + global.EventSource = EventSource; + global.ReadableStream = ReadableStream; + window.EventSource = EventSource; + window.ReadableStream = ReadableStream; + }); + + afterEach(() => { + delete global.EventSource; + delete global.ReadableStream; + delete window.EventSource; + delete window.ReadableStream; + }); + + it('should create a readable stream', () => { + const { agentOverview } = new ConstructorIO(defaultOptions); + const stream = agentOverview.getIntentResults('I want shoes', { + domain: 'agent', + }); + + expect(stream).to.have.property('getReader'); + }); + + it('should throw an error if missing domain parameter', () => { + const { agentOverview } = new ConstructorIO(defaultOptions); + + expect(() => agentOverview.getIntentResults('I want shoes', {})).throw( + 'parameters.domain is a required parameter of type string', + ); + }); + + it('should throw an error if missing intent', () => { + const { agentOverview } = new ConstructorIO(defaultOptions); + + expect(() => + agentOverview.getIntentResults('', { domain: 'agent' }), + ).throw('intent is a required parameter of type string'); + }); + + it('should push expected data to the stream', async () => { + const { agentOverview } = new ConstructorIO(defaultOptions); + const stream = await agentOverview.getIntentResults( + 'I want running shoes', + { domain: 'agent' }, + ); + const reader = stream.getReader(); + const { value, done } = await reader.read(); + + expect(done).to.be.false; + expect(value.type).to.equal('start'); + reader.releaseLock(); + }); + + it('should handle cancel to the stream gracefully', async () => { + const { agentOverview } = new ConstructorIO(defaultOptions); + const stream = await agentOverview.getIntentResults( + 'I want running shoes', + { domain: 'agent' }, + ); + const reader = stream.getReader(); + const { value, done } = await reader.read(); + + expect(done).to.be.false; + expect(value.type).to.equal('start'); + reader.cancel(); + }); + + it('should handle premature cancel before reading any data', async () => { + const { agentOverview } = new ConstructorIO(defaultOptions); + const stream = await agentOverview.getIntentResults( + 'I want running shoes', + { domain: 'agent' }, + ); + const reader = stream.getReader(); + + reader.cancel(); + }); + }); +}); diff --git a/spec/src/modules/agent.js b/spec/src/modules/agent.js index 7810d0a3..a28d6cb2 100644 --- a/spec/src/modules/agent.js +++ b/spec/src/modules/agent.js @@ -93,6 +93,165 @@ describe(`ConstructorIO - Agent${bundledDescriptionSuffix}`, () => { expect(url).not.contain(' '); expect(url).contain(encodeURIComponentRFC3986(intentWithSpaces)); }); + + it('should include threadId parameter when provided', () => { + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, threadId: 'test-thread-id' }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('thread_id').to.equal('test-thread-id'); + }); + + it('should include guard parameter when provided as true', () => { + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, guard: true }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('guard').to.equal('true'); + }); + + it('should include guard parameter when provided as false', () => { + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, guard: false }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('guard').to.equal('false'); + }); + + it('should include numResultsPerEvent parameter when provided', () => { + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, numResultsPerEvent: 5 }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('num_results_per_event').to.equal('5'); + }); + + it('should include numResultEvents parameter when provided', () => { + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, numResultEvents: 3 }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('num_result_events').to.equal('3'); + }); + + it('should include numResultsPerPage parameter when provided', () => { + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, numResultsPerPage: 10 }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('num_results_per_page').to.equal('10'); + }); + + it('should include preFilterExpression as JSON string when provided as object', () => { + const preFilterExpression = { and: [{ name: 'brand', value: 'Nike' }] }; + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, preFilterExpression }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(JSON.stringify(preFilterExpression)); + }); + + it('should include preFilterExpression as string when provided as string', () => { + const preFilterExpression = '{"and":[{"name":"brand","value":"Nike"}]}'; + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, preFilterExpression }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(preFilterExpression); + }); + + it('should include qs as JSON string when provided as object', () => { + const qsParam = { section: 'Products' }; + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, qs: qsParam }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('qs').to.equal(JSON.stringify(qsParam)); + }); + + it('should include qs as string when provided as string', () => { + const qsParam = 'section=Products'; + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, qs: qsParam }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('qs').to.equal(qsParam); + }); + + it('should include fmtOptions when provided', () => { + const fmtOptions = { fields: ['title', 'image_url'], hidden_fields: ['price'] }; + const url = createAgentUrl( + 'running shoes', + { ...defaultParameters, fmtOptions }, + defaultOptions, + ); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('fmt_options'); + expect(requestedUrlParams.fmt_options).to.have.property('fields'); + expect(requestedUrlParams.fmt_options).to.have.property('hidden_fields'); + }); + + it('should include user segments when provided in options', () => { + const url = createAgentUrl('running shoes', defaultParameters, { + ...defaultOptions, + segments: ['segment1', 'segment2'], + }); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('us'); + }); + + it('should include userId when provided in options', () => { + const url = createAgentUrl('running shoes', defaultParameters, { + ...defaultOptions, + userId: 'user-123', + }); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('ui').to.equal('user-123'); + }); + + it('should include testCells when provided in options', () => { + const url = createAgentUrl('running shoes', defaultParameters, { + ...defaultOptions, + testCells: { cellA: 'variant1', cellB: 'variant2' }, + }); + const requestedUrlParams = qs.parse(url.split('?')?.[1]); + + expect(requestedUrlParams).to.have.property('ef-cellA').to.equal('variant1'); + expect(requestedUrlParams).to.have.property('ef-cellB').to.equal('variant2'); + }); }); // setupEventListeners util Tests diff --git a/src/constructorio.js b/src/constructorio.js index 4c74668d..1b655b54 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -12,6 +12,7 @@ const helpers = require('./utils/helpers'); const { default: packageVersion } = require('./version'); const Quizzes = require('./modules/quizzes'); const Agent = require('./modules/agent'); +const AgentOverview = require('./modules/agent-overview'); const Assistant = require('./modules/assistant'); // Compute package version string @@ -64,6 +65,7 @@ class ConstructorIO { * @property {object} tracker - Interface to {@link module:tracker} * @property {object} quizzes - Interface to {@link module:quizzes} * @property {object} agent - Interface to {@link module:agent} + * @property {object} agentOverview - Interface to {@link module:agentOverview} * @property {object} assistant - Interface to {@link module:assistant} @deprecated This property is deprecated and will be removed in a future version. Use the agent property instead. * @returns {class} */ @@ -146,6 +148,7 @@ class ConstructorIO { this.tracker = new Tracker(this.options); this.quizzes = new Quizzes(this.options); this.agent = new Agent(this.options); + this.agentOverview = new AgentOverview(this.options); this.assistant = new Assistant(this.options); // Dispatch initialization event diff --git a/src/modules/agent-overview.js b/src/modules/agent-overview.js new file mode 100644 index 00000000..86cc0926 --- /dev/null +++ b/src/modules/agent-overview.js @@ -0,0 +1,49 @@ +const Agent = require('./agent'); + +/** + * Interface to agent overview SSE. + * Extends Agent with additional event types for overview responses. + * + * @module agentOverview + * @inner + * @returns {object} + */ +class AgentOverview extends Agent { + static EventTypes = { + ...Agent.EventTypes, + MESSAGE: 'message', // Represents a textual message from the agent + FOLLOW_UP_QUESTIONS: 'follow_up_questions', // Represents follow-up question suggestions + }; + + /** + * Retrieve intent results from EventStream + * + * @function getIntentResults + * @description Retrieve a stream of intent results from Constructor.io API + * @param {string} intent - Intent to use to perform an intent based recommendations + * @param {object} [parameters] - Additional parameters to refine result set + * @param {string} parameters.domain - Domain name (e.g. "sportsgear", "recipes") + * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue + * @param {boolean} [parameters.guard] - Enable content moderation + * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event + * @param {number} [parameters.numResultEvents] - Max number of search_result events (categories) + * @param {object|string} [parameters.qs] - Additional query parameters for the search client + * @param {object|string} [parameters.preFilterExpression] - Pre-filter expression for results + * @param {object} [parameters.fmtOptions] - Format options for results + * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return + * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return + * @returns {ReadableStream} Returns a ReadableStream. + * @example + * const readableStream = constructorio.agentOverview.getIntentResults('I want to get shoes', { + * domain: 'sportsgear', + * numResultsPerEvent: 5, + * }); + * const reader = readableStream.getReader(); + * const { value, done } = await reader.read(); + */ + getIntentResults(intent, parameters) { + return this.getAgentResultsStream(intent, parameters); + } +} + +module.exports = AgentOverview; diff --git a/src/modules/agent.js b/src/modules/agent.js index 3358938c..c1b85293 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -1,4 +1,4 @@ -const { cleanParams, trimNonBreakingSpaces, encodeURIComponentRFC3986, stringify } = require('../utils/helpers'); +const { cleanParams, trimNonBreakingSpaces, encodeURIComponentRFC3986, stringify, isNil } = require('../utils/helpers'); // Create URL from supplied intent (term) and parameters function createAgentUrl(intent, parameters, options) { @@ -26,7 +26,7 @@ function createAgentUrl(intent, parameters, options) { } // Validate domain is provided - if (!parameters.domain || typeof parameters.domain !== 'string') { + if (!parameters || !parameters.domain || typeof parameters.domain !== 'string') { throw new Error('parameters.domain is a required parameter of type string'); } @@ -48,7 +48,17 @@ function createAgentUrl(intent, parameters, options) { } if (parameters) { - const { domain, numResultsPerPage } = parameters; + const { + domain, + numResultsPerPage, + threadId, + guard, + numResultsPerEvent, + numResultEvents, + qs, + preFilterExpression, + fmtOptions, + } = parameters; // Pull domain from parameters if (domain) { @@ -56,9 +66,46 @@ function createAgentUrl(intent, parameters, options) { } // Pull results number from parameters - if (numResultsPerPage) { + if (!isNil(numResultsPerPage)) { queryParams.num_results_per_page = numResultsPerPage; } + + // Pull thread_id from parameters + if (!isNil(threadId)) { + queryParams.thread_id = threadId; + } + + // Pull guard from parameters + if (!isNil(guard)) { + queryParams.guard = guard; + } + + // Pull num_results_per_event from parameters + if (!isNil(numResultsPerEvent)) { + queryParams.num_results_per_event = numResultsPerEvent; + } + + // Pull num_result_events from parameters + if (!isNil(numResultEvents)) { + queryParams.num_result_events = numResultEvents; + } + + // Pull qs from parameters + if (qs) { + queryParams.qs = typeof qs === 'string' ? qs : JSON.stringify(qs); + } + + // Pull pre_filter_expression from parameters + if (preFilterExpression) { + queryParams.pre_filter_expression = typeof preFilterExpression === 'string' + ? preFilterExpression + : JSON.stringify(preFilterExpression); + } + + // Pull fmt_options from parameters + if (fmtOptions) { + queryParams.fmt_options = fmtOptions; + } } // eslint-disable-next-line no-underscore-dangle @@ -134,8 +181,17 @@ class Agent { * @description Retrieve a stream of agent results from Constructor.io API * @param {string} intent - Intent to use to perform an intent based recommendations * @param {object} [parameters] - Additional parameters to refine result set - * @param {string} [parameters.domain] - domain name e.g. swimming sports gear, groceries - * @param {number} [parameters.numResultsPerPage] - The total number of results to return + * @param {string} parameters.domain - Domain name (e.g. "sportsgear", "recipes") + * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue + * @param {boolean} [parameters.guard] - Enable content moderation + * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event + * @param {number} [parameters.numResultEvents] - Max number of search_result events + * @param {number} [parameters.numResultsPerPage] - The total number of results to return @deprecated Use numResultsPerEvent instead + * @param {object|string} [parameters.qs] - Additional query parameters for the search client + * @param {object|string} [parameters.preFilterExpression] - Pre-filter expression for results + * @param {object} [parameters.fmtOptions] - Format options for results + * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return + * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return * @returns {ReadableStream} Returns a ReadableStream. * @example * const readableStream = constructorio.agent.getAgentResultsStream('I want to get shoes', { @@ -145,6 +201,7 @@ class Agent { * const { value, done } = await reader.read(); */ getAgentResultsStream(query, parameters) { + const eventTypes = this.constructor.EventTypes; let eventSource; let readableStream; @@ -158,8 +215,8 @@ class Agent { readableStream = new ReadableStream({ // To be called on stream start start(controller) { - // Listen to events emitted from ASA Server Sent Events and push data to the ReadableStream - setupEventListeners(eventSource, controller, Agent.EventTypes); + // Listen to events emitted from SSE and push data to the ReadableStream + setupEventListeners(eventSource, controller, eventTypes); }, // To be called on stream cancelling cancel() { diff --git a/src/types/agent-overview.d.ts b/src/types/agent-overview.d.ts new file mode 100644 index 00000000..e0386a51 --- /dev/null +++ b/src/types/agent-overview.d.ts @@ -0,0 +1,10 @@ +import Agent, { IAgentParameters } from './agent'; + +export default AgentOverview; + +declare class AgentOverview extends Agent { + getIntentResults( + intent: string, + parameters: IAgentParameters, + ): ReadableStream; +} diff --git a/src/types/agent.d.ts b/src/types/agent.d.ts index eeb63f25..27783501 100644 --- a/src/types/agent.d.ts +++ b/src/types/agent.d.ts @@ -4,10 +4,23 @@ import { export default Agent; +export interface IAgentFmtOptions { + fields?: string[]; + hidden_fields?: string[]; +} + export interface IAgentParameters { domain: string; + /** @deprecated Use numResultsPerEvent instead */ numResultsPerPage?: number; filters?: Record; + threadId?: string; + guard?: boolean; + numResultsPerEvent?: number; + numResultEvents?: number; + qs?: Record | string; + preFilterExpression?: Record | string; + fmtOptions?: IAgentFmtOptions; } declare class Agent { @@ -17,6 +30,6 @@ declare class Agent { getAgentResultsStream( intent: string, - parameters?: IAgentParameters, + parameters: IAgentParameters, ): ReadableStream; } diff --git a/src/types/constructorio.d.ts b/src/types/constructorio.d.ts index f68df4af..3a284ddd 100644 --- a/src/types/constructorio.d.ts +++ b/src/types/constructorio.d.ts @@ -4,6 +4,7 @@ import Autocomplete from './autocomplete'; import Recommendations from './recommendations'; import Quizzes from './quizzes'; import Agent from './agent'; +import AgentOverview from './agent-overview'; import Assistant from './assistant'; import Tracker from './tracker'; import { ConstructorClientOptions } from '.'; @@ -27,6 +28,8 @@ declare class ConstructorIO { agent: Agent; + agentOverview: AgentOverview; + assistant: Assistant; tracker: Tracker; @@ -35,5 +38,5 @@ declare class ConstructorIO { } declare namespace ConstructorIO { - export { Search, Browse, Autocomplete, Recommendations, Quizzes, Tracker, Agent, Assistant }; + export { Search, Browse, Autocomplete, Recommendations, Quizzes, Tracker, Agent, AgentOverview, Assistant }; } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index ff95cde4..7047d2e6 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -6,6 +6,7 @@ export default ConstructorIO; export * from './search'; export * from './autocomplete'; export * from './quizzes'; +export * from './agent-overview'; export * from './recommendations'; export * from './browse'; export * from './tracker'; diff --git a/src/types/tests/agent-overview.test-d.ts b/src/types/tests/agent-overview.test-d.ts new file mode 100644 index 00000000..0b63012a --- /dev/null +++ b/src/types/tests/agent-overview.test-d.ts @@ -0,0 +1,21 @@ +import { expectAssignable } from 'tsd'; +import { IAgentParameters } from '../agent'; + +expectAssignable({ + domain: 'sportsgear', +}); + +expectAssignable({ + domain: 'sportsgear', + threadId: 'f0e1d2c3-b4a5-6789-0abc-def123456789', + guard: true, + numResultsPerEvent: 5, + numResultEvents: 3, + numResultsPerPage: 10, + qs: { section: 'Products' }, + preFilterExpression: { and: [{ name: 'brand', value: 'Nike' }] }, + fmtOptions: { + fields: ['title', 'price'], + hidden_fields: ['internal_id'], + }, +}); From 1c1e4722b8ab19f6d2f1e2a32008dbca0ba7257c Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Fri, 27 Feb 2026 15:52:56 +0300 Subject: [PATCH 02/17] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ade3501..c110859b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ const constructorio = new ConstructorioClient({ ## 4. Retrieve Results -After instantiating an instance of the client, seven modules will be exposed as properties to help retrieve data or send behavioral events: `search`, `browse`, `autocomplete`, `recommendations`, `quizzes`, `agent`, and `tracker`. +After instantiating an instance of the client, eight modules will be exposed as properties to help retrieve data or send behavioral events: `search`, `browse`, `autocomplete`, `recommendations`, `quizzes`, `agent`, `agentOverview`, and `tracker`. #### Dispatched events From 0b0bd35660be344bb5b8a38948f5ab6e00423303 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Fri, 27 Feb 2026 15:59:46 +0300 Subject: [PATCH 03/17] fix linter --- spec/src/modules/agent-overview.js | 4 +--- src/modules/agent-overview.js | 4 ++-- src/modules/agent.js | 6 ++++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/src/modules/agent-overview.js b/spec/src/modules/agent-overview.js index f76ad4ac..f482577d 100644 --- a/spec/src/modules/agent-overview.js +++ b/spec/src/modules/agent-overview.js @@ -158,9 +158,7 @@ describe(`ConstructorIO - Agent Overview${bundledDescriptionSuffix}`, () => { it('should throw an error if missing intent', () => { const { agentOverview } = new ConstructorIO(defaultOptions); - expect(() => - agentOverview.getIntentResults('', { domain: 'agent' }), - ).throw('intent is a required parameter of type string'); + expect(() => agentOverview.getIntentResults('', { domain: 'agent' })).throw('intent is a required parameter of type string'); }); it('should push expected data to the stream', async () => { diff --git a/src/modules/agent-overview.js b/src/modules/agent-overview.js index 86cc0926..1fef7e13 100644 --- a/src/modules/agent-overview.js +++ b/src/modules/agent-overview.js @@ -22,7 +22,7 @@ class AgentOverview extends Agent { * @description Retrieve a stream of intent results from Constructor.io API * @param {string} intent - Intent to use to perform an intent based recommendations * @param {object} [parameters] - Additional parameters to refine result set - * @param {string} parameters.domain - Domain name (e.g. "sportsgear", "recipes") + * @param {string} parameters.domain - Domain name (e.g. "recipes", "recipes") * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue * @param {boolean} [parameters.guard] - Enable content moderation * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event @@ -35,7 +35,7 @@ class AgentOverview extends Agent { * @returns {ReadableStream} Returns a ReadableStream. * @example * const readableStream = constructorio.agentOverview.getIntentResults('I want to get shoes', { - * domain: 'sportsgear', + * domain: 'recipes', * numResultsPerEvent: 5, * }); * const reader = readableStream.getReader(); diff --git a/src/modules/agent.js b/src/modules/agent.js index c1b85293..b346e301 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -1,6 +1,7 @@ const { cleanParams, trimNonBreakingSpaces, encodeURIComponentRFC3986, stringify, isNil } = require('../utils/helpers'); // Create URL from supplied intent (term) and parameters +// eslint-disable-next-line complexity function createAgentUrl(intent, parameters, options) { const { apiKey, @@ -181,12 +182,13 @@ class Agent { * @description Retrieve a stream of agent results from Constructor.io API * @param {string} intent - Intent to use to perform an intent based recommendations * @param {object} [parameters] - Additional parameters to refine result set - * @param {string} parameters.domain - Domain name (e.g. "sportsgear", "recipes") + * @param {string} parameters.domain - Domain name (e.g. "recipes", "recipes") * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue * @param {boolean} [parameters.guard] - Enable content moderation * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event * @param {number} [parameters.numResultEvents] - Max number of search_result events - * @param {number} [parameters.numResultsPerPage] - The total number of results to return @deprecated Use numResultsPerEvent instead + * @param {number} [parameters.numResultsPerPage] - The total number of results to return + * @deprecated Use numResultsPerEvent instead * @param {object|string} [parameters.qs] - Additional query parameters for the search client * @param {object|string} [parameters.preFilterExpression] - Pre-filter expression for results * @param {object} [parameters.fmtOptions] - Format options for results From acace63b745aaedfde59d53a35cacbc89792a3a7 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Fri, 27 Feb 2026 17:13:03 +0300 Subject: [PATCH 04/17] fixes after copilot review --- src/modules/agent-overview.js | 4 ++-- src/modules/agent.js | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/modules/agent-overview.js b/src/modules/agent-overview.js index 1fef7e13..e0fb73f7 100644 --- a/src/modules/agent-overview.js +++ b/src/modules/agent-overview.js @@ -21,8 +21,8 @@ class AgentOverview extends Agent { * @function getIntentResults * @description Retrieve a stream of intent results from Constructor.io API * @param {string} intent - Intent to use to perform an intent based recommendations - * @param {object} [parameters] - Additional parameters to refine result set - * @param {string} parameters.domain - Domain name (e.g. "recipes", "recipes") + * @param {object} parameters - Additional parameters to refine result set + * @param {string} parameters.domain - Domain name (e.g. "groceries", "recipes") * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue * @param {boolean} [parameters.guard] - Enable content moderation * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event diff --git a/src/modules/agent.js b/src/modules/agent.js index b346e301..f27c7e99 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -181,14 +181,13 @@ class Agent { * @function getAgentResultsStream * @description Retrieve a stream of agent results from Constructor.io API * @param {string} intent - Intent to use to perform an intent based recommendations - * @param {object} [parameters] - Additional parameters to refine result set - * @param {string} parameters.domain - Domain name (e.g. "recipes", "recipes") + * @param {object} parameters - Additional parameters to refine result set + * @param {string} parameters.domain - Domain name (e.g. "groceries", "recipes") * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue * @param {boolean} [parameters.guard] - Enable content moderation * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event * @param {number} [parameters.numResultEvents] - Max number of search_result events - * @param {number} [parameters.numResultsPerPage] - The total number of results to return - * @deprecated Use numResultsPerEvent instead + * @param {number} [parameters.numResultsPerPage] - Deprecated: use numResultsPerEvent instead * @param {object|string} [parameters.qs] - Additional query parameters for the search client * @param {object|string} [parameters.preFilterExpression] - Pre-filter expression for results * @param {object} [parameters.fmtOptions] - Format options for results From 510e3a6291ec6bc6849f9b019bb8de60cf087fdd Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Tue, 3 Mar 2026 16:36:40 +0300 Subject: [PATCH 05/17] fix copilot review --- src/modules/agent.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index f27c7e99..bbfdb6e0 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -93,14 +93,12 @@ function createAgentUrl(intent, parameters, options) { // Pull qs from parameters if (qs) { - queryParams.qs = typeof qs === 'string' ? qs : JSON.stringify(qs); + queryParams.qs = JSON.stringify(qs); } // Pull pre_filter_expression from parameters if (preFilterExpression) { - queryParams.pre_filter_expression = typeof preFilterExpression === 'string' - ? preFilterExpression - : JSON.stringify(preFilterExpression); + queryParams.pre_filter_expression = JSON.stringify(preFilterExpression); } // Pull fmt_options from parameters From 574382c94bf9a234eedfa80943419f9d257be4f4 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Tue, 3 Mar 2026 17:13:37 +0300 Subject: [PATCH 06/17] return condition for strings --- src/modules/agent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index bbfdb6e0..4e9ef1a3 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -93,12 +93,12 @@ function createAgentUrl(intent, parameters, options) { // Pull qs from parameters if (qs) { - queryParams.qs = JSON.stringify(qs); + queryParams.qs = typeof qs === 'string' ? qs : JSON.stringify(qs); } // Pull pre_filter_expression from parameters if (preFilterExpression) { - queryParams.pre_filter_expression = JSON.stringify(preFilterExpression); + queryParams.pre_filter_expression = typeof preFilterExpression === 'string' ? preFilterExpression : JSON.stringify(preFilterExpression); } // Pull fmt_options from parameters From 6e4d11d4d04db583e3e9c725076dc9a2b450c7c1 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Wed, 4 Mar 2026 13:16:19 +0300 Subject: [PATCH 07/17] remove agent-overview module & update agent module --- spec/src/modules/agent-overview.js | 203 ----------------------------- spec/src/modules/agent.js | 37 ++++++ src/constructorio.js | 3 - src/modules/agent-overview.js | 49 ------- src/modules/agent.js | 2 + 5 files changed, 39 insertions(+), 255 deletions(-) delete mode 100644 spec/src/modules/agent-overview.js delete mode 100644 src/modules/agent-overview.js diff --git a/spec/src/modules/agent-overview.js b/spec/src/modules/agent-overview.js deleted file mode 100644 index f482577d..00000000 --- a/spec/src/modules/agent-overview.js +++ /dev/null @@ -1,203 +0,0 @@ -/* eslint-disable no-unused-expressions, import/no-unresolved */ -const dotenv = require('dotenv'); -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); -const EventSource = require('eventsource'); -const { ReadableStream } = require('web-streams-polyfill'); -const AgentOverview = require('../../../src/modules/agent-overview'); -const Agent = require('../../../src/modules/agent'); -const { setupEventListeners } = require('../../../src/modules/agent'); -let ConstructorIO = require('../../../test/constructorio'); // eslint-disable-line import/extensions -const jsdom = require('../utils/jsdom-global'); - -const bundled = process.env.BUNDLED === 'true'; -const bundledDescriptionSuffix = bundled ? ' - Bundled' : ''; - -chai.use(sinonChai); -dotenv.config(); - -const testApiKey = process.env.TEST_REQUEST_API_KEY; -const clientVersion = 'cio-mocha'; - -const defaultOptions = { - apiKey: testApiKey, - version: clientVersion, - agentServiceUrl: 'https://agent.cnstrc.com', - clientId: '123', - sessionId: 123, -}; - -describe(`ConstructorIO - Agent Overview${bundledDescriptionSuffix}`, () => { - const jsdomOptions = { url: 'http://localhost' }; - let cleanup; - - beforeEach(() => { - cleanup = jsdom(jsdomOptions); - global.CLIENT_VERSION = clientVersion; - window.CLIENT_VERSION = clientVersion; - - if (bundled) { - ConstructorIO = window.ConstructorioClient; - } - }); - - afterEach(() => { - delete global.CLIENT_VERSION; - delete window.CLIENT_VERSION; - cleanup(); - }); - - describe('EventTypes', () => { - it('should include all Agent event types', () => { - Object.keys(Agent.EventTypes).forEach((key) => { - expect(AgentOverview.EventTypes).to.have.property(key); - expect(AgentOverview.EventTypes[key]).to.equal(Agent.EventTypes[key]); - }); - }); - - it('should include MESSAGE event type', () => { - expect(AgentOverview.EventTypes).to.have.property('MESSAGE').to.equal('message'); - }); - - it('should include FOLLOW_UP_QUESTIONS event type', () => { - expect(AgentOverview.EventTypes).to.have.property('FOLLOW_UP_QUESTIONS').to.equal('follow_up_questions'); - }); - }); - - describe('setupEventListeners with AgentOverview EventTypes', () => { - let mockEventSource; - let mockStreamController; - - beforeEach(() => { - mockEventSource = { - addEventListener: sinon.stub(), - close: sinon.stub(), - }; - - mockStreamController = { - enqueue: sinon.stub(), - close: sinon.stub(), - error: sinon.stub(), - }; - }); - - afterEach(() => { - sinon.restore(); - }); - - it('should enqueue MESSAGE event data into the stream', (done) => { - const eventType = AgentOverview.EventTypes.MESSAGE; - const eventData = { text: 'Here are some recommendations' }; - - setupEventListeners(mockEventSource, mockStreamController, AgentOverview.EventTypes); - - const messageCallback = mockEventSource.addEventListener - .getCalls() - .find((call) => call.args[0] === eventType).args[1]; - - messageCallback({ data: JSON.stringify(eventData) }); - - setImmediate(() => { - expect(mockStreamController.enqueue.calledWith({ type: eventType, data: eventData })).to.be.true; - done(); - }); - }); - - it('should enqueue FOLLOW_UP_QUESTIONS event data into the stream', (done) => { - const eventType = AgentOverview.EventTypes.FOLLOW_UP_QUESTIONS; - const eventData = { questions: ['What size?', 'What color?'] }; - - setupEventListeners(mockEventSource, mockStreamController, AgentOverview.EventTypes); - - const followUpCallback = mockEventSource.addEventListener - .getCalls() - .find((call) => call.args[0] === eventType).args[1]; - - followUpCallback({ data: JSON.stringify(eventData) }); - - setImmediate(() => { - expect(mockStreamController.enqueue.calledWith({ type: eventType, data: eventData })).to.be.true; - done(); - }); - }); - }); - - describe('getIntentResults', () => { - beforeEach(() => { - global.EventSource = EventSource; - global.ReadableStream = ReadableStream; - window.EventSource = EventSource; - window.ReadableStream = ReadableStream; - }); - - afterEach(() => { - delete global.EventSource; - delete global.ReadableStream; - delete window.EventSource; - delete window.ReadableStream; - }); - - it('should create a readable stream', () => { - const { agentOverview } = new ConstructorIO(defaultOptions); - const stream = agentOverview.getIntentResults('I want shoes', { - domain: 'agent', - }); - - expect(stream).to.have.property('getReader'); - }); - - it('should throw an error if missing domain parameter', () => { - const { agentOverview } = new ConstructorIO(defaultOptions); - - expect(() => agentOverview.getIntentResults('I want shoes', {})).throw( - 'parameters.domain is a required parameter of type string', - ); - }); - - it('should throw an error if missing intent', () => { - const { agentOverview } = new ConstructorIO(defaultOptions); - - expect(() => agentOverview.getIntentResults('', { domain: 'agent' })).throw('intent is a required parameter of type string'); - }); - - it('should push expected data to the stream', async () => { - const { agentOverview } = new ConstructorIO(defaultOptions); - const stream = await agentOverview.getIntentResults( - 'I want running shoes', - { domain: 'agent' }, - ); - const reader = stream.getReader(); - const { value, done } = await reader.read(); - - expect(done).to.be.false; - expect(value.type).to.equal('start'); - reader.releaseLock(); - }); - - it('should handle cancel to the stream gracefully', async () => { - const { agentOverview } = new ConstructorIO(defaultOptions); - const stream = await agentOverview.getIntentResults( - 'I want running shoes', - { domain: 'agent' }, - ); - const reader = stream.getReader(); - const { value, done } = await reader.read(); - - expect(done).to.be.false; - expect(value.type).to.equal('start'); - reader.cancel(); - }); - - it('should handle premature cancel before reading any data', async () => { - const { agentOverview } = new ConstructorIO(defaultOptions); - const stream = await agentOverview.getIntentResults( - 'I want running shoes', - { domain: 'agent' }, - ); - const reader = stream.getReader(); - - reader.cancel(); - }); - }); -}); diff --git a/spec/src/modules/agent.js b/spec/src/modules/agent.js index a28d6cb2..124bb0cc 100644 --- a/spec/src/modules/agent.js +++ b/spec/src/modules/agent.js @@ -355,6 +355,42 @@ describe(`ConstructorIO - Agent${bundledDescriptionSuffix}`, () => { done(); }); }); + + it('should enqueue MESSAGE event data into the stream', (done) => { + const eventType = Agent.EventTypes.MESSAGE; + const eventData = { text: 'Here are some recommendations' }; + + setupEventListeners(mockEventSource, mockStreamController, Agent.EventTypes); + + const messageCallback = mockEventSource.addEventListener + .getCalls() + .find((call) => call.args[0] === eventType).args[1]; + + messageCallback({ data: JSON.stringify(eventData) }); + + setImmediate(() => { + expect(mockStreamController.enqueue.calledWith({ type: eventType, data: eventData })).to.be.true; + done(); + }); + }); + + it('should enqueue FOLLOW_UP_QUESTIONS event data into the stream', (done) => { + const eventType = Agent.EventTypes.FOLLOW_UP_QUESTIONS; + const eventData = { questions: ['What size?', 'What color?'] }; + + setupEventListeners(mockEventSource, mockStreamController, Agent.EventTypes); + + const followUpCallback = mockEventSource.addEventListener + .getCalls() + .find((call) => call.args[0] === eventType).args[1]; + + followUpCallback({ data: JSON.stringify(eventData) }); + + setImmediate(() => { + expect(mockStreamController.enqueue.calledWith({ type: eventType, data: eventData })).to.be.true; + done(); + }); + }); }); describe('getAgentResultsStream', () => { @@ -424,4 +460,5 @@ describe(`ConstructorIO - Agent${bundledDescriptionSuffix}`, () => { reader.cancel(); }); }); + }); diff --git a/src/constructorio.js b/src/constructorio.js index 38beba5f..8a469785 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -12,7 +12,6 @@ const helpers = require('./utils/helpers'); const { default: packageVersion } = require('./version'); const Quizzes = require('./modules/quizzes'); const Agent = require('./modules/agent'); -const AgentOverview = require('./modules/agent-overview'); const Assistant = require('./modules/assistant'); // Compute package version string @@ -66,7 +65,6 @@ class ConstructorIO { * @property {object} tracker - Interface to {@link module:tracker} * @property {object} quizzes - Interface to {@link module:quizzes} * @property {object} agent - Interface to {@link module:agent} - * @property {object} agentOverview - Interface to {@link module:agentOverview} * @property {object} assistant - Interface to {@link module:assistant} @deprecated This property is deprecated and will be removed in a future version. Use the agent property instead. * @returns {class} */ @@ -151,7 +149,6 @@ class ConstructorIO { this.tracker = new Tracker(this.options); this.quizzes = new Quizzes(this.options); this.agent = new Agent(this.options); - this.agentOverview = new AgentOverview(this.options); this.assistant = new Assistant(this.options); // Dispatch initialization event diff --git a/src/modules/agent-overview.js b/src/modules/agent-overview.js deleted file mode 100644 index e0fb73f7..00000000 --- a/src/modules/agent-overview.js +++ /dev/null @@ -1,49 +0,0 @@ -const Agent = require('./agent'); - -/** - * Interface to agent overview SSE. - * Extends Agent with additional event types for overview responses. - * - * @module agentOverview - * @inner - * @returns {object} - */ -class AgentOverview extends Agent { - static EventTypes = { - ...Agent.EventTypes, - MESSAGE: 'message', // Represents a textual message from the agent - FOLLOW_UP_QUESTIONS: 'follow_up_questions', // Represents follow-up question suggestions - }; - - /** - * Retrieve intent results from EventStream - * - * @function getIntentResults - * @description Retrieve a stream of intent results from Constructor.io API - * @param {string} intent - Intent to use to perform an intent based recommendations - * @param {object} parameters - Additional parameters to refine result set - * @param {string} parameters.domain - Domain name (e.g. "groceries", "recipes") - * @param {string} [parameters.threadId] - Conversation thread ID for multi-turn dialogue - * @param {boolean} [parameters.guard] - Enable content moderation - * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event - * @param {number} [parameters.numResultEvents] - Max number of search_result events (categories) - * @param {object|string} [parameters.qs] - Additional query parameters for the search client - * @param {object|string} [parameters.preFilterExpression] - Pre-filter expression for results - * @param {object} [parameters.fmtOptions] - Format options for results - * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return - * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return - * @returns {ReadableStream} Returns a ReadableStream. - * @example - * const readableStream = constructorio.agentOverview.getIntentResults('I want to get shoes', { - * domain: 'recipes', - * numResultsPerEvent: 5, - * }); - * const reader = readableStream.getReader(); - * const { value, done } = await reader.read(); - */ - getIntentResults(intent, parameters) { - return this.getAgentResultsStream(intent, parameters); - } -} - -module.exports = AgentOverview; diff --git a/src/modules/agent.js b/src/modules/agent.js index 4e9ef1a3..e0fd46e1 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -170,6 +170,8 @@ class Agent { RECIPE_INSTRUCTIONS: 'recipe_instructions', // Represents recipe instructions SERVER_ERROR: 'server_error', // Server Error event IMAGE_META: 'image_meta', // This event type is used for enhancing recommendations with media content such as images + MESSAGE: 'message', // Represents a textual message from the agent + FOLLOW_UP_QUESTIONS: 'follow_up_questions', // Represents follow-up question suggestions END: 'end', // Represents the end of data stream }; From f225f0a306d7a5223534b437ec6d9bd5b836901d Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Wed, 4 Mar 2026 13:20:38 +0300 Subject: [PATCH 08/17] remove agent-overview module --- README.md | 2 +- src/types/agent-overview.d.ts | 10 ---------- src/types/constructorio.d.ts | 5 +---- src/types/index.d.ts | 2 +- src/types/tests/agent-overview.test-d.ts | 21 --------------------- 5 files changed, 3 insertions(+), 37 deletions(-) delete mode 100644 src/types/agent-overview.d.ts delete mode 100644 src/types/tests/agent-overview.test-d.ts diff --git a/README.md b/README.md index c110859b..9ade3501 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ const constructorio = new ConstructorioClient({ ## 4. Retrieve Results -After instantiating an instance of the client, eight modules will be exposed as properties to help retrieve data or send behavioral events: `search`, `browse`, `autocomplete`, `recommendations`, `quizzes`, `agent`, `agentOverview`, and `tracker`. +After instantiating an instance of the client, seven modules will be exposed as properties to help retrieve data or send behavioral events: `search`, `browse`, `autocomplete`, `recommendations`, `quizzes`, `agent`, and `tracker`. #### Dispatched events diff --git a/src/types/agent-overview.d.ts b/src/types/agent-overview.d.ts deleted file mode 100644 index e0386a51..00000000 --- a/src/types/agent-overview.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Agent, { IAgentParameters } from './agent'; - -export default AgentOverview; - -declare class AgentOverview extends Agent { - getIntentResults( - intent: string, - parameters: IAgentParameters, - ): ReadableStream; -} diff --git a/src/types/constructorio.d.ts b/src/types/constructorio.d.ts index 3a284ddd..f68df4af 100644 --- a/src/types/constructorio.d.ts +++ b/src/types/constructorio.d.ts @@ -4,7 +4,6 @@ import Autocomplete from './autocomplete'; import Recommendations from './recommendations'; import Quizzes from './quizzes'; import Agent from './agent'; -import AgentOverview from './agent-overview'; import Assistant from './assistant'; import Tracker from './tracker'; import { ConstructorClientOptions } from '.'; @@ -28,8 +27,6 @@ declare class ConstructorIO { agent: Agent; - agentOverview: AgentOverview; - assistant: Assistant; tracker: Tracker; @@ -38,5 +35,5 @@ declare class ConstructorIO { } declare namespace ConstructorIO { - export { Search, Browse, Autocomplete, Recommendations, Quizzes, Tracker, Agent, AgentOverview, Assistant }; + export { Search, Browse, Autocomplete, Recommendations, Quizzes, Tracker, Agent, Assistant }; } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 800bfdb8..998e7784 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -6,7 +6,7 @@ export default ConstructorIO; export * from './search'; export * from './autocomplete'; export * from './quizzes'; -export * from './agent-overview'; +export * from './agent'; export * from './recommendations'; export * from './browse'; export * from './tracker'; diff --git a/src/types/tests/agent-overview.test-d.ts b/src/types/tests/agent-overview.test-d.ts deleted file mode 100644 index 0b63012a..00000000 --- a/src/types/tests/agent-overview.test-d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expectAssignable } from 'tsd'; -import { IAgentParameters } from '../agent'; - -expectAssignable({ - domain: 'sportsgear', -}); - -expectAssignable({ - domain: 'sportsgear', - threadId: 'f0e1d2c3-b4a5-6789-0abc-def123456789', - guard: true, - numResultsPerEvent: 5, - numResultEvents: 3, - numResultsPerPage: 10, - qs: { section: 'Products' }, - preFilterExpression: { and: [{ name: 'brand', value: 'Nike' }] }, - fmtOptions: { - fields: ['title', 'price'], - hidden_fields: ['internal_id'], - }, -}); From efb58e65d0636919f2367ee895dd6a36f4239820 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Wed, 4 Mar 2026 13:23:26 +0300 Subject: [PATCH 09/17] return event types --- src/modules/agent.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index e0fd46e1..f4215fd3 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -202,7 +202,6 @@ class Agent { * const { value, done } = await reader.read(); */ getAgentResultsStream(query, parameters) { - const eventTypes = this.constructor.EventTypes; let eventSource; let readableStream; @@ -217,7 +216,7 @@ class Agent { // To be called on stream start start(controller) { // Listen to events emitted from SSE and push data to the ReadableStream - setupEventListeners(eventSource, controller, eventTypes); + setupEventListeners(eventSource, controller, Agent.EventTypes); }, // To be called on stream cancelling cancel() { From 1dfca12774f474b325af8af8b8566ff358f0c5c6 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Wed, 4 Mar 2026 13:24:11 +0300 Subject: [PATCH 10/17] return even types comment --- src/modules/agent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index f4215fd3..11e923f9 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -215,7 +215,7 @@ class Agent { readableStream = new ReadableStream({ // To be called on stream start start(controller) { - // Listen to events emitted from SSE and push data to the ReadableStream + // Listen to events emitted from ASA Server Sent Events and push data to the ReadableStream setupEventListeners(eventSource, controller, Agent.EventTypes); }, // To be called on stream cancelling From 29c48bb17ff7ec803658682bca85317cde143c34 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 13:11:11 +0300 Subject: [PATCH 11/17] fix types --- src/types/agent.d.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/types/agent.d.ts b/src/types/agent.d.ts index 27783501..3ded0ed1 100644 --- a/src/types/agent.d.ts +++ b/src/types/agent.d.ts @@ -1,14 +1,11 @@ import { ConstructorClientOptions, + FmtOptions, + FilterExpression, } from '.'; export default Agent; -export interface IAgentFmtOptions { - fields?: string[]; - hidden_fields?: string[]; -} - export interface IAgentParameters { domain: string; /** @deprecated Use numResultsPerEvent instead */ @@ -18,9 +15,9 @@ export interface IAgentParameters { guard?: boolean; numResultsPerEvent?: number; numResultEvents?: number; - qs?: Record | string; - preFilterExpression?: Record | string; - fmtOptions?: IAgentFmtOptions; + qsParam?: Record; + preFilterExpression?: FilterExpression | string; + fmtOptions?: Pick; } declare class Agent { @@ -30,6 +27,6 @@ declare class Agent { getAgentResultsStream( intent: string, - parameters: IAgentParameters, + parameters?: IAgentParameters, ): ReadableStream; } From 645de7247471bd754ea5137b072dc8328c6cd6c2 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 13:11:47 +0300 Subject: [PATCH 12/17] fix conditions for agent & description --- src/modules/agent.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index 11e923f9..3c6b5b43 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -56,7 +56,7 @@ function createAgentUrl(intent, parameters, options) { guard, numResultsPerEvent, numResultEvents, - qs, + qsParam, preFilterExpression, fmtOptions, } = parameters; @@ -67,12 +67,12 @@ function createAgentUrl(intent, parameters, options) { } // Pull results number from parameters - if (!isNil(numResultsPerPage)) { + if (numResultsPerPage) { queryParams.num_results_per_page = numResultsPerPage; } // Pull thread_id from parameters - if (!isNil(threadId)) { + if (threadId) { queryParams.thread_id = threadId; } @@ -82,23 +82,23 @@ function createAgentUrl(intent, parameters, options) { } // Pull num_results_per_event from parameters - if (!isNil(numResultsPerEvent)) { + if (numResultsPerEvent) { queryParams.num_results_per_event = numResultsPerEvent; } // Pull num_result_events from parameters - if (!isNil(numResultEvents)) { + if (numResultEvents) { queryParams.num_result_events = numResultEvents; } - // Pull qs from parameters - if (qs) { - queryParams.qs = typeof qs === 'string' ? qs : JSON.stringify(qs); + // Pull qsParam from parameters + if (qsParam) { + queryParams.qs = JSON.stringify(qsParam); } // Pull pre_filter_expression from parameters if (preFilterExpression) { - queryParams.pre_filter_expression = typeof preFilterExpression === 'string' ? preFilterExpression : JSON.stringify(preFilterExpression); + queryParams.pre_filter_expression = JSON.stringify(preFilterExpression); } // Pull fmt_options from parameters @@ -188,9 +188,9 @@ class Agent { * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event * @param {number} [parameters.numResultEvents] - Max number of search_result events * @param {number} [parameters.numResultsPerPage] - Deprecated: use numResultsPerEvent instead - * @param {object|string} [parameters.qs] - Additional query parameters for the search client - * @param {object|string} [parameters.preFilterExpression] - Pre-filter expression for results - * @param {object} [parameters.fmtOptions] - Format options for results + * @param {object} [parameters.qsParam] - Parameters listed above can be serialized into a JSON object and parsed through this parameter. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params + * @param {object} [parameters.preFilterExpression] - Faceting expression to scope search results. Please refer to https://docs.constructor.com/reference/configuration-collections + * @param {object} [parameters.fmtOptions] - The format options used to refine result groups. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params for details * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return * @returns {ReadableStream} Returns a ReadableStream. From 90a9ecaacb31c509a4040e7d0986cb60b2a730e4 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 13:12:45 +0300 Subject: [PATCH 13/17] fix types --- src/types/agent.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/agent.d.ts b/src/types/agent.d.ts index 3ded0ed1..88f80c90 100644 --- a/src/types/agent.d.ts +++ b/src/types/agent.d.ts @@ -16,7 +16,7 @@ export interface IAgentParameters { numResultsPerEvent?: number; numResultEvents?: number; qsParam?: Record; - preFilterExpression?: FilterExpression | string; + preFilterExpression?: FilterExpression; fmtOptions?: Pick; } From dd6761ebb47564eb50a82ae0f0da22e1d50aa18b Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 13:16:38 +0300 Subject: [PATCH 14/17] update description --- src/modules/agent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index 3c6b5b43..da201b48 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -188,11 +188,11 @@ class Agent { * @param {number} [parameters.numResultsPerEvent] - Max products per search_result event * @param {number} [parameters.numResultEvents] - Max number of search_result events * @param {number} [parameters.numResultsPerPage] - Deprecated: use numResultsPerEvent instead - * @param {object} [parameters.qsParam] - Parameters listed above can be serialized into a JSON object and parsed through this parameter. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params * @param {object} [parameters.preFilterExpression] - Faceting expression to scope search results. Please refer to https://docs.constructor.com/reference/configuration-collections * @param {object} [parameters.fmtOptions] - The format options used to refine result groups. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params for details * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return + * @param {object} [parameters.qsParam] - Parameters listed above can be serialized into a JSON object and parsed through this parameter. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params * @returns {ReadableStream} Returns a ReadableStream. * @example * const readableStream = constructorio.agent.getAgentResultsStream('I want to get shoes', { From 1cdfbed4ffab72cda8f66f50f000b1994062e140 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 13:24:51 +0300 Subject: [PATCH 15/17] update agent test --- spec/src/modules/agent.js | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/spec/src/modules/agent.js b/spec/src/modules/agent.js index 124bb0cc..5c355408 100644 --- a/spec/src/modules/agent.js +++ b/spec/src/modules/agent.js @@ -172,23 +172,11 @@ describe(`ConstructorIO - Agent${bundledDescriptionSuffix}`, () => { expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(JSON.stringify(preFilterExpression)); }); - it('should include preFilterExpression as string when provided as string', () => { - const preFilterExpression = '{"and":[{"name":"brand","value":"Nike"}]}'; - const url = createAgentUrl( - 'running shoes', - { ...defaultParameters, preFilterExpression }, - defaultOptions, - ); - const requestedUrlParams = qs.parse(url.split('?')?.[1]); - - expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(preFilterExpression); - }); - - it('should include qs as JSON string when provided as object', () => { + it('should include qsParam as JSON string when provided as object', () => { const qsParam = { section: 'Products' }; const url = createAgentUrl( 'running shoes', - { ...defaultParameters, qs: qsParam }, + { ...defaultParameters, qsParam }, defaultOptions, ); const requestedUrlParams = qs.parse(url.split('?')?.[1]); @@ -196,18 +184,6 @@ describe(`ConstructorIO - Agent${bundledDescriptionSuffix}`, () => { expect(requestedUrlParams).to.have.property('qs').to.equal(JSON.stringify(qsParam)); }); - it('should include qs as string when provided as string', () => { - const qsParam = 'section=Products'; - const url = createAgentUrl( - 'running shoes', - { ...defaultParameters, qs: qsParam }, - defaultOptions, - ); - const requestedUrlParams = qs.parse(url.split('?')?.[1]); - - expect(requestedUrlParams).to.have.property('qs').to.equal(qsParam); - }); - it('should include fmtOptions when provided', () => { const fmtOptions = { fields: ['title', 'image_url'], hidden_fields: ['price'] }; const url = createAgentUrl( From 7e14f1e72da21bade2838eefffca53bff5d79b75 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 16:59:35 +0300 Subject: [PATCH 16/17] update doc URLs --- src/modules/agent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index da201b48..b1bacd05 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -189,10 +189,10 @@ class Agent { * @param {number} [parameters.numResultEvents] - Max number of search_result events * @param {number} [parameters.numResultsPerPage] - Deprecated: use numResultsPerEvent instead * @param {object} [parameters.preFilterExpression] - Faceting expression to scope search results. Please refer to https://docs.constructor.com/reference/configuration-collections - * @param {object} [parameters.fmtOptions] - The format options used to refine result groups. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params for details + * @param {object} [parameters.fmtOptions] - The format options used to refine result groups. Please refer to https://docs.constructor.com/reference/v1-asa-retrieve-intent#query-params for details for details * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return - * @param {object} [parameters.qsParam] - Parameters listed above can be serialized into a JSON object and parsed through this parameter. Please refer to https://docs.constructor.com/reference/v1-search-get-search-results#query-params + * @param {object} [parameters.qsParam] - Parameters listed above can be serialized into a JSON object and parsed through this parameter. Please refer to https://docs.constructor.com/reference/v1-asa-retrieve-intent#query-params * @returns {ReadableStream} Returns a ReadableStream. * @example * const readableStream = constructorio.agent.getAgentResultsStream('I want to get shoes', { From 63ff9c7fe12c6398d5cbca2da76ebff9fb9a05d5 Mon Sep 17 00:00:00 2001 From: dmitrycnstrc Date: Thu, 5 Mar 2026 17:00:57 +0300 Subject: [PATCH 17/17] fix typo --- src/modules/agent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/agent.js b/src/modules/agent.js index b1bacd05..6dbed5e3 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -189,7 +189,7 @@ class Agent { * @param {number} [parameters.numResultEvents] - Max number of search_result events * @param {number} [parameters.numResultsPerPage] - Deprecated: use numResultsPerEvent instead * @param {object} [parameters.preFilterExpression] - Faceting expression to scope search results. Please refer to https://docs.constructor.com/reference/configuration-collections - * @param {object} [parameters.fmtOptions] - The format options used to refine result groups. Please refer to https://docs.constructor.com/reference/v1-asa-retrieve-intent#query-params for details for details + * @param {object} [parameters.fmtOptions] - The format options used to refine result groups. Please refer to https://docs.constructor.com/reference/v1-asa-retrieve-intent#query-params for details * @param {string[]} [parameters.fmtOptions.fields] - Product fields to return * @param {string[]} [parameters.fmtOptions.hidden_fields] - Hidden fields to return * @param {object} [parameters.qsParam] - Parameters listed above can be serialized into a JSON object and parsed through this parameter. Please refer to https://docs.constructor.com/reference/v1-asa-retrieve-intent#query-params