diff --git a/package-lock.json b/package-lock.json
index b84f3a9c0b3..cfe9f31f248 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47176,6 +47176,7 @@
"@mongodb-js/atlas-service": "^0.56.0",
"@mongodb-js/compass-app-registry": "^9.4.20",
"@mongodb-js/compass-components": "^1.49.0",
+ "@mongodb-js/compass-generative-ai": "^0.51.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/connection-info": "^0.17.1",
@@ -49348,11 +49349,11 @@
"@mongodb-js/atlas-service": "^0.56.0",
"@mongodb-js/compass-app-registry": "^9.4.20",
"@mongodb-js/compass-components": "^1.49.0",
- "@mongodb-js/compass-connections": "^1.71.0",
"@mongodb-js/compass-intercom": "^0.35.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/compass-utils": "^0.9.11",
+ "@mongodb-js/connection-info": "^0.17.2",
"bson": "^6.10.4",
"compass-preferences-model": "^2.51.0",
"mongodb": "^6.19.0",
@@ -49364,7 +49365,6 @@
"zod": "^3.25.76"
},
"devDependencies": {
- "@mongodb-js/connection-info": "^0.17.2",
"@mongodb-js/eslint-config-compass": "^1.4.7",
"@mongodb-js/mocha-config-compass": "^1.7.0",
"@mongodb-js/prettier-config-compass": "^1.2.8",
@@ -60584,6 +60584,7 @@
"@mongodb-js/atlas-service": "^0.56.0",
"@mongodb-js/compass-app-registry": "^9.4.20",
"@mongodb-js/compass-components": "^1.49.0",
+ "@mongodb-js/compass-generative-ai": "^0.51.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/connection-info": "^0.17.1",
@@ -62107,7 +62108,6 @@
"@mongodb-js/atlas-service": "^0.56.0",
"@mongodb-js/compass-app-registry": "^9.4.20",
"@mongodb-js/compass-components": "^1.49.0",
- "@mongodb-js/compass-connections": "^1.71.0",
"@mongodb-js/compass-intercom": "^0.35.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
diff --git a/packages/compass-assistant/package.json b/packages/compass-assistant/package.json
index 48d2832bdcb..39b5b420f2d 100644
--- a/packages/compass-assistant/package.json
+++ b/packages/compass-assistant/package.json
@@ -56,6 +56,7 @@
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/connection-info": "^0.17.1",
"@mongodb-js/compass-logging": "^1.7.12",
+ "@mongodb-js/compass-generative-ai": "^0.51.0",
"mongodb-connection-string-url": "^3.0.1",
"ai": "^5.0.26",
"compass-preferences-model": "^2.51.0",
diff --git a/packages/compass-assistant/src/assistant-chat.spec.tsx b/packages/compass-assistant/src/assistant-chat.spec.tsx
index 7fc97e6986b..768f338c799 100644
--- a/packages/compass-assistant/src/assistant-chat.spec.tsx
+++ b/packages/compass-assistant/src/assistant-chat.spec.tsx
@@ -8,7 +8,11 @@ import {
import { AssistantChat } from './assistant-chat';
import { expect } from 'chai';
import { createMockChat } from '../test/utils';
-import type { AssistantMessage } from './compass-assistant-provider';
+import {
+ AssistantActionsContext,
+ type AssistantMessage,
+} from './compass-assistant-provider';
+import sinon from 'sinon';
describe('AssistantChat', function () {
const mockMessages: AssistantMessage[] = [
@@ -38,10 +42,30 @@ describe('AssistantChat', function () {
} = {}
) {
const chat = createMockChat({ messages, status });
- const result = render();
+
+ // The chat component does not use chat.sendMessage() directly, it uses
+ // ensureOptInAndSend() via the AssistantActionsContext.
+ const ensureOptInAndSendStub = sinon
+ .stub()
+ .callsFake(async (message, options, callback) => {
+ // call the callback so we can test the tracking
+ callback();
+
+ await chat.sendMessage(message, options);
+ });
+
+ const assistantActionsContext = {
+ ensureOptInAndSend: ensureOptInAndSendStub,
+ };
+ const result = render(
+
+
+
+ );
return {
result,
chat,
+ ensureOptInAndSendStub,
};
}
@@ -156,8 +180,8 @@ describe('AssistantChat', function () {
);
});
- it('calls sendMessage when form is submitted', async function () {
- const { chat, result } = renderWithChat([]);
+ it('calls ensureOptInAndSend when form is submitted', async function () {
+ const { ensureOptInAndSendStub, result } = renderWithChat([]);
const { track } = result;
const inputField = screen.getByPlaceholderText(
'Ask MongoDB Assistant a question'
@@ -167,8 +191,7 @@ describe('AssistantChat', function () {
userEvent.type(inputField, 'What is aggregation?');
userEvent.click(sendButton);
- expect(chat.sendMessage.calledWith({ text: 'What is aggregation?' })).to.be
- .true;
+ expect(ensureOptInAndSendStub.called).to.be.true;
await waitFor(() => {
expect(track).to.have.been.calledWith('Assistant Prompt Submitted', {
@@ -193,7 +216,7 @@ describe('AssistantChat', function () {
});
it('trims whitespace from input before sending', async function () {
- const { chat, result } = renderWithChat([]);
+ const { ensureOptInAndSendStub, result } = renderWithChat([]);
const { track } = result;
const inputField = screen.getByPlaceholderText(
@@ -203,8 +226,7 @@ describe('AssistantChat', function () {
userEvent.type(inputField, ' What is sharding? ');
userEvent.click(screen.getByLabelText('Send message'));
- expect(chat.sendMessage.calledWith({ text: 'What is sharding?' })).to.be
- .true;
+ expect(ensureOptInAndSendStub.called).to.be.true;
await waitFor(() => {
expect(track).to.have.been.calledWith('Assistant Prompt Submitted', {
@@ -213,8 +235,8 @@ describe('AssistantChat', function () {
});
});
- it('does not call sendMessage when input is empty or whitespace-only', function () {
- const { chat } = renderWithChat([]);
+ it('does not call ensureOptInAndSend when input is empty or whitespace-only', function () {
+ const { ensureOptInAndSendStub } = renderWithChat([]);
const inputField = screen.getByPlaceholderText(
'Ask MongoDB Assistant a question'
@@ -223,12 +245,12 @@ describe('AssistantChat', function () {
// Test empty input
userEvent.click(chatForm);
- expect(chat.sendMessage.notCalled).to.be.true;
+ expect(ensureOptInAndSendStub.notCalled).to.be.true;
// Test whitespace-only input
userEvent.type(inputField, ' ');
userEvent.click(chatForm);
- expect(chat.sendMessage.notCalled).to.be.true;
+ expect(ensureOptInAndSendStub.notCalled).to.be.true;
});
it('displays user and assistant messages with different styling', function () {
diff --git a/packages/compass-assistant/src/assistant-chat.tsx b/packages/compass-assistant/src/assistant-chat.tsx
index 5a509f1cd88..f2c88747b32 100644
--- a/packages/compass-assistant/src/assistant-chat.tsx
+++ b/packages/compass-assistant/src/assistant-chat.tsx
@@ -1,5 +1,6 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useContext } from 'react';
import type { AssistantMessage } from './compass-assistant-provider';
+import { AssistantActionsContext } from './compass-assistant-provider';
import type { Chat } from './@ai-sdk/react/chat-react';
import { useChat } from './@ai-sdk/react/use-chat';
import {
@@ -121,7 +122,8 @@ export const AssistantChat: React.FunctionComponent = ({
}) => {
const track = useTelemetry();
const darkMode = useDarkMode();
- const { messages, sendMessage, status, error, clearError } = useChat({
+ const { ensureOptInAndSend } = useContext(AssistantActionsContext);
+ const { messages, status, error, clearError } = useChat({
chat,
onError: (error) => {
track('Assistant Response Failed', () => ({
@@ -147,13 +149,14 @@ export const AssistantChat: React.FunctionComponent = ({
(messageBody: string) => {
const trimmedMessageBody = messageBody.trim();
if (trimmedMessageBody) {
- track('Assistant Prompt Submitted', {
- user_input_length: trimmedMessageBody.length,
+ void ensureOptInAndSend?.({ text: trimmedMessageBody }, {}, () => {
+ track('Assistant Prompt Submitted', {
+ user_input_length: trimmedMessageBody.length,
+ });
});
- void sendMessage({ text: trimmedMessageBody });
}
},
- [sendMessage, track]
+ [track, ensureOptInAndSend]
);
const handleFeedback = useCallback(
diff --git a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx
index 357ee8a249c..16c9abb9145 100644
--- a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx
+++ b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx
@@ -9,7 +9,6 @@ import {
within,
} from '@mongodb-js/testing-library-compass';
import {
- AssistantProvider,
CompassAssistantProvider,
useAssistantActions,
type AssistantMessage,
@@ -22,23 +21,74 @@ import {
DrawerAnchor,
DrawerContentProvider,
} from '@mongodb-js/compass-components';
+import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider';
import type { AtlasService } from '@mongodb-js/atlas-service/provider';
import { CompassAssistantDrawer } from './compass-assistant-drawer';
import { createMockChat } from '../test/utils';
+import { type AtlasAiService } from '@mongodb-js/compass-generative-ai/provider';
+
+function createMockProvider({
+ mockAtlasService,
+ mockAtlasAiService,
+ mockAtlasAuthService,
+}: {
+ mockAtlasService?: any;
+ mockAtlasAiService?: any;
+ mockAtlasAuthService?: any;
+} = {}) {
+ if (!mockAtlasService) {
+ mockAtlasService = {
+ assistantApiEndpoint: sinon
+ .stub()
+ .returns('https://example.com/assistant/api/v1'),
+ };
+ }
+
+ if (!mockAtlasAiService) {
+ mockAtlasAiService = {
+ ensureAiFeatureAccess: sinon.stub().resolves(),
+ };
+ }
+
+ if (!mockAtlasAuthService) {
+ mockAtlasAuthService = {};
+ }
+
+ return CompassAssistantProvider.withMockServices({
+ atlasService: mockAtlasService as unknown as AtlasService,
+ atlasAiService: mockAtlasAiService as unknown as AtlasAiService,
+ atlasAuthService: mockAtlasAuthService as unknown as AtlasAuthService,
+ });
+}
-// Test component that renders AssistantProvider with children
+// Test component that renders CompassAssistantProvider (and AssistantProvider) with children
const TestComponent: React.FunctionComponent<{
chat: Chat;
autoOpen?: boolean;
-}> = ({ chat, autoOpen }) => {
+ mockAtlasService?: any;
+ mockAtlasAiService?: any;
+ mockAtlasAuthService?: any;
+}> = ({
+ chat,
+ autoOpen,
+ mockAtlasService,
+ mockAtlasAiService,
+ mockAtlasAuthService,
+}) => {
+ const MockedProvider = createMockProvider({
+ mockAtlasService: mockAtlasService as unknown as AtlasService,
+ mockAtlasAiService: mockAtlasAiService as unknown as AtlasAiService,
+ mockAtlasAuthService: mockAtlasAuthService as unknown as AtlasAuthService,
+ });
+
return (
-
+
Provider children
-
+
);
};
@@ -46,16 +96,18 @@ const TestComponent: React.FunctionComponent<{
describe('useAssistantActions', function () {
const createWrapper = (chat: Chat) => {
function TestWrapper({ children }: { children: React.ReactNode }) {
+ const MockedProvider = createMockProvider();
+
return (
- {children}
+ {children}
);
}
return TestWrapper;
};
- it('returns empty object when AI features are disabled via isAIFeatureEnabled', function () {
+ it('returns mostly empty object when AI features are disabled via isAIFeatureEnabled', function () {
const { result } = renderHook(() => useAssistantActions(), {
wrapper: createWrapper(createMockChat({ messages: [] })),
preferences: {
@@ -67,10 +119,10 @@ describe('useAssistantActions', function () {
},
});
- expect(result.current).to.deep.equal({});
+ expect(result.current).to.have.keys(['getIsAssistantEnabled']);
});
- it('returns empty object when enableGenAIFeaturesAtlasOrg is disabled', function () {
+ it('returns mostly empty object when enableGenAIFeaturesAtlasOrg is disabled', function () {
const { result } = renderHook(() => useAssistantActions(), {
wrapper: createWrapper(createMockChat({ messages: [] })),
preferences: {
@@ -81,10 +133,10 @@ describe('useAssistantActions', function () {
},
});
- expect(result.current).to.deep.equal({});
+ expect(result.current).to.have.keys(['getIsAssistantEnabled']);
});
- it('returns empty object when cloudFeatureRolloutAccess is disabled', function () {
+ it('returns mostly empty object when cloudFeatureRolloutAccess is disabled', function () {
const { result } = renderHook(() => useAssistantActions(), {
wrapper: createWrapper(createMockChat({ messages: [] })),
preferences: {
@@ -95,10 +147,10 @@ describe('useAssistantActions', function () {
},
});
- expect(result.current).to.deep.equal({});
+ expect(result.current).to.have.keys(['getIsAssistantEnabled']);
});
- it('returns empty object when enableAIAssistant preference is disabled', function () {
+ it('returns mostly empty object when enableAIAssistant preference is disabled', function () {
const { result } = renderHook(() => useAssistantActions(), {
wrapper: createWrapper(createMockChat({ messages: [] })),
preferences: {
@@ -109,7 +161,7 @@ describe('useAssistantActions', function () {
},
});
- expect(result.current).to.deep.equal({});
+ expect(result.current).to.have.keys(['getIsAssistantEnabled']);
});
it('returns actions when both AI features and assistant flag are enabled', function () {
@@ -127,11 +179,10 @@ describe('useAssistantActions', function () {
expect(result.current.interpretExplainPlan).to.be.a('function');
expect(result.current.interpretConnectionError).to.be.a('function');
expect(result.current.tellMoreAboutInsight).to.be.a('function');
- expect(result.current.clearChat).to.be.a('function');
});
});
-describe('AssistantProvider', function () {
+describe('CompassAssistantProvider', function () {
const mockMessages: AssistantMessage[] = [
{
id: '1',
@@ -198,16 +249,24 @@ describe('AssistantProvider', function () {
});
async function renderOpenAssistantDrawer(
- mockChat: Chat
+ mockChat: Chat,
+ mockAtlasAiService?: any
): Promise> {
- const result = render(, {
- preferences: {
- enableAIAssistant: true,
- enableGenAIFeatures: true,
- enableGenAIFeaturesAtlasOrg: true,
- cloudFeatureRolloutAccess: { GEN_AI_COMPASS: true },
- },
- });
+ const result = render(
+ ,
+ {
+ preferences: {
+ enableAIAssistant: true,
+ enableGenAIFeatures: true,
+ enableGenAIFeaturesAtlasOrg: true,
+ cloudFeatureRolloutAccess: { GEN_AI_COMPASS: true },
+ },
+ }
+ );
await waitFor(() => {
expect(screen.getByTestId('assistant-chat')).to.exist;
@@ -221,6 +280,11 @@ describe('AssistantProvider', function () {
await renderOpenAssistantDrawer(mockChat);
+ // ensureAiFeatureAccess is async
+ await waitFor(() => {
+ expect(screen.getByTestId('assistant-message-1')).to.exist;
+ });
+
expect(screen.getByTestId('assistant-message-1')).to.exist;
expect(screen.getByTestId('assistant-message-1')).to.have.text(
'Test message'
@@ -255,8 +319,8 @@ describe('AssistantProvider', function () {
userEvent.type(input, 'Hello assistant');
userEvent.click(sendButton);
- expect(sendMessageSpy.calledOnce).to.be.true;
await waitFor(() => {
+ expect(sendMessageSpy.calledOnce).to.be.true;
expect(sendMessageSpy.firstCall.args[0]).to.deep.include({
text: 'Hello assistant',
});
@@ -292,14 +356,54 @@ describe('AssistantProvider', function () {
);
userEvent.click(screen.getByLabelText('Send message'));
- expect(sendMessageSpy.calledOnce).to.be.true;
- expect(sendMessageSpy.firstCall.args[0]).to.deep.include({
- text: 'Hello assistant!',
+ await waitFor(() => {
+ expect(sendMessageSpy.calledOnce).to.be.true;
+ expect(sendMessageSpy.firstCall.args[0]).to.deep.include({
+ text: 'Hello assistant!',
+ });
+
+ expect(screen.getByText('Hello assistant!')).to.exist;
+ });
+ });
+
+ it('will not send new messages if the user does not opt in', async function () {
+ const mockChat = new Chat({
+ messages: [
+ {
+ id: 'assistant',
+ role: 'assistant',
+ parts: [{ type: 'text', text: 'Hello user!' }],
+ },
+ ],
+ transport: {
+ sendMessages: sinon.stub().returns(
+ new Promise(() => {
+ return new ReadableStream({});
+ })
+ ),
+ reconnectToStream: sinon.stub(),
+ },
});
+ const mockAtlasAiService = {
+ ensureAiFeatureAccess: sinon.stub().rejects(),
+ };
+
+ const sendMessageSpy = sinon.spy(mockChat, 'sendMessage');
+
+ await renderOpenAssistantDrawer(mockChat, mockAtlasAiService);
+
+ userEvent.type(
+ screen.getByPlaceholderText('Ask MongoDB Assistant a question'),
+ 'Hello assistant!'
+ );
+ userEvent.click(screen.getByLabelText('Send message'));
+
await waitFor(() => {
- expect(screen.getByText('Hello assistant!')).to.exist;
+ expect(mockAtlasAiService.ensureAiFeatureAccess.calledOnce).to.be.true;
+ expect(sendMessageSpy.called).to.be.false;
});
+ expect(screen.queryByText('Hello assistant!')).to.not.exist;
});
describe('clear chat button', function () {
@@ -366,29 +470,31 @@ describe('AssistantProvider', function () {
});
describe('CompassAssistantProvider', function () {
- beforeEach(function () {
- process.env.COMPASS_ASSISTANT_USE_ATLAS_SERVICE_URL = 'true';
- });
-
- afterEach(function () {
- delete process.env.COMPASS_ASSISTANT_USE_ATLAS_SERVICE_URL;
- });
-
- it('uses the Atlas Service assistantApiEndpoint', function () {
+ it('uses the Atlas Service assistantApiEndpoint', async function () {
const mockAtlasService = {
assistantApiEndpoint: sinon
.stub()
.returns('https://example.com/assistant/api/v1'),
};
+ const mockAtlasAiService = {
+ ensureAiFeatureAccess: sinon.stub().callsFake(() => {
+ return Promise.resolve();
+ }),
+ };
+
+ const mockAtlasAuthService = {};
+
const MockedProvider = CompassAssistantProvider.withMockServices({
atlasService: mockAtlasService as unknown as AtlasService,
+ atlasAiService: mockAtlasAiService as unknown as AtlasAiService,
+ atlasAuthService: mockAtlasAuthService as unknown as AtlasAuthService,
});
render(
-
+
,
{
preferences: {
@@ -400,7 +506,9 @@ describe('AssistantProvider', function () {
}
);
- expect(mockAtlasService.assistantApiEndpoint.calledOnce).to.be.true;
+ await waitFor(() => {
+ expect(mockAtlasService.assistantApiEndpoint.calledOnce).to.be.true;
+ });
});
});
});
diff --git a/packages/compass-assistant/src/compass-assistant-provider.tsx b/packages/compass-assistant/src/compass-assistant-provider.tsx
index 472e8168276..bd9a7058346 100644
--- a/packages/compass-assistant/src/compass-assistant-provider.tsx
+++ b/packages/compass-assistant/src/compass-assistant-provider.tsx
@@ -6,7 +6,10 @@ import {
createServiceLocator,
registerCompassPlugin,
} from '@mongodb-js/compass-app-registry';
-import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider';
+import {
+ atlasAuthServiceLocator,
+ atlasServiceLocator,
+} from '@mongodb-js/atlas-service/provider';
import { DocsProviderTransport } from './docs-provider-transport';
import { useDrawerActions } from '@mongodb-js/compass-components';
import {
@@ -23,6 +26,8 @@ import {
import { createLoggerLocator } from '@mongodb-js/compass-logging/provider';
import type { ConnectionInfo } from '@mongodb-js/connection-info';
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
+import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider';
+import { atlasAiServiceLocator } from '@mongodb-js/compass-generative-ai/provider';
export const ASSISTANT_DRAWER_ID = 'compass-assistant-drawer';
@@ -39,6 +44,9 @@ export const AssistantContext = createContext(
null
);
+type SendMessage = Parameters['sendMessage']>[0];
+type SendOptions = Parameters['sendMessage']>[1];
+
type AssistantActionsContextType = {
interpretExplainPlan?: ({
namespace,
@@ -56,43 +64,90 @@ type AssistantActionsContextType = {
}) => void;
clearChat?: () => void;
tellMoreAboutInsight?: (context: ProactiveInsightsContext) => void;
+ ensureOptInAndSend?: (
+ message: SendMessage,
+ options: SendOptions,
+ callback: () => void
+ ) => Promise;
+};
+
+type AssistantActionsType = Omit<
+ AssistantActionsContextType,
+ 'ensureOptInAndSend' | 'clearChat'
+> & {
+ getIsAssistantEnabled: () => boolean;
};
+
export const AssistantActionsContext =
createContext({
interpretExplainPlan: () => {},
interpretConnectionError: () => {},
tellMoreAboutInsight: () => {},
clearChat: () => {},
+ ensureOptInAndSend: async () => {},
});
-export function useAssistantActions(): AssistantActionsContextType {
+export function useAssistantActions(): AssistantActionsType {
+ const actions = useContext(AssistantActionsContext);
const isAIFeatureEnabled = useIsAIFeatureEnabled();
const isAssistantFlagEnabled = usePreference('enableAIAssistant');
- const actions = useContext(AssistantActionsContext);
if (!isAIFeatureEnabled || !isAssistantFlagEnabled) {
- return {};
+ return {
+ getIsAssistantEnabled: () => false,
+ };
}
- return actions;
+ const {
+ interpretExplainPlan,
+ interpretConnectionError,
+ tellMoreAboutInsight,
+ } = actions;
+
+ return {
+ interpretExplainPlan,
+ interpretConnectionError,
+ tellMoreAboutInsight,
+ getIsAssistantEnabled: () => true,
+ };
}
-export const compassAssistantServiceLocator = createServiceLocator(function () {
+export const compassAssistantServiceLocator = createServiceLocator(() => {
const actions = useAssistantActions();
- return actions;
+ const interpretConnectionErrorRef = useRef(actions.interpretConnectionError);
+ interpretConnectionErrorRef.current = actions.interpretConnectionError;
+
+ const getIsAssistantEnabledRef = useRef(actions.getIsAssistantEnabled);
+ getIsAssistantEnabledRef.current = actions.getIsAssistantEnabled;
+
+ return {
+ interpretConnectionError: (options: {
+ connectionInfo: ConnectionInfo;
+ error: Error;
+ }) => interpretConnectionErrorRef.current?.(options),
+ getIsAssistantEnabled: () => {
+ return getIsAssistantEnabledRef.current();
+ },
+ };
}, 'compassAssistantLocator');
-export type CompassAssistantService = ReturnType<
- typeof compassAssistantServiceLocator
->;
+export type CompassAssistantService = {
+ interpretConnectionError: (options: {
+ connectionInfo: ConnectionInfo;
+ error: Error;
+ }) => void;
+ getIsAssistantEnabled: () => boolean;
+};
export const AssistantProvider: React.FunctionComponent<
PropsWithChildren<{
chat: Chat;
+ atlasAiService: AtlasAiService;
}>
-> = ({ chat, children }) => {
+> = ({ chat, atlasAiService, children }) => {
const { openDrawer } = useDrawerActions();
const track = useTelemetry();
+
const createEntryPointHandler = useRef(function (
entryPointName:
| 'explain plan'
@@ -101,12 +156,22 @@ export const AssistantProvider: React.FunctionComponent<
builder: (props: T) => EntryPointMessage
) {
return (props: T) => {
- openDrawer(ASSISTANT_DRAWER_ID);
+ if (!assistantActionsContext.current.ensureOptInAndSend) {
+ return;
+ }
+
const { prompt, displayText } = builder(props);
- void chat.sendMessage({ text: prompt, metadata: { displayText } }, {});
- track('Assistant Entry Point Used', {
- source: entryPointName,
- });
+ void assistantActionsContext.current.ensureOptInAndSend(
+ { text: prompt, metadata: { displayText } },
+ {},
+ () => {
+ openDrawer(ASSISTANT_DRAWER_ID);
+
+ track('Assistant Entry Point Used', {
+ source: entryPointName,
+ });
+ }
+ );
};
});
const assistantActionsContext = useRef({
@@ -125,6 +190,24 @@ export const AssistantProvider: React.FunctionComponent<
clearChat: () => {
chat.messages = [];
},
+ ensureOptInAndSend: async (
+ message: SendMessage,
+ options: SendOptions,
+ callback: () => void
+ ) => {
+ try {
+ await atlasAiService.ensureAiFeatureAccess();
+ } catch {
+ // opt-in failed: just do nothing
+ return;
+ }
+
+ // Call the callback to indicate that the opt-in was successful. A good
+ // place to do tracking.
+ callback();
+
+ await chat.sendMessage(message, options);
+ },
});
return (
@@ -141,37 +224,50 @@ export const CompassAssistantProvider = registerCompassPlugin(
name: 'CompassAssistant',
component: ({
chat,
+ atlasAiService,
children,
}: PropsWithChildren<{
chat?: Chat;
+ atlasAiService?: AtlasAiService;
}>) => {
if (!chat) {
throw new Error('Chat was not provided by the state');
}
- return {children};
+ if (!atlasAiService) {
+ throw new Error('atlasAiService was not provided by the state');
+ }
+ return (
+
+ {children}
+
+ );
},
- activate: (initialProps, { atlasService, logger }) => {
- const chat = new Chat({
- transport: new DocsProviderTransport({
- baseUrl: atlasService.assistantApiEndpoint(),
- }),
- onError: (err: Error) => {
- logger.log.error(
- logger.mongoLogId(1_001_000_370),
- 'Assistant',
- 'Failed to send a message',
- { err }
- );
- },
- });
+ activate: (initialProps, { atlasService, atlasAiService, logger }) => {
+ const chat =
+ initialProps.chat ??
+ new Chat({
+ transport: new DocsProviderTransport({
+ baseUrl: atlasService.assistantApiEndpoint(),
+ }),
+ onError: (err: Error) => {
+ logger.log.error(
+ logger.mongoLogId(1_001_000_370),
+ 'Assistant',
+ 'Failed to send a message',
+ { err }
+ );
+ },
+ });
return {
- store: { state: { chat } },
+ store: { state: { chat, atlasAiService } },
deactivate: () => {},
};
},
},
{
atlasService: atlasServiceLocator,
+ atlasAiService: atlasAiServiceLocator,
+ atlasAuthService: atlasAuthServiceLocator,
logger: createLoggerLocator('COMPASS-ASSISTANT'),
}
);
diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts
index fc056078fbe..92da56e5653 100644
--- a/packages/compass-connections/src/stores/connections-store-redux.ts
+++ b/packages/compass-connections/src/stores/connections-store-redux.ts
@@ -1284,9 +1284,9 @@ const connectionAttemptError = (
}
: undefined,
onDebugClick:
- compassAssistant.interpretConnectionError && connectionInfo
+ compassAssistant.getIsAssistantEnabled() && connectionInfo
? () => {
- compassAssistant.interpretConnectionError?.({
+ compassAssistant.interpretConnectionError({
connectionInfo,
error: err,
});
diff --git a/packages/compass-generative-ai/package.json b/packages/compass-generative-ai/package.json
index 2cf393ce179..2535c8e8d13 100644
--- a/packages/compass-generative-ai/package.json
+++ b/packages/compass-generative-ai/package.json
@@ -55,11 +55,11 @@
"@mongodb-js/atlas-service": "^0.56.0",
"@mongodb-js/compass-app-registry": "^9.4.20",
"@mongodb-js/compass-components": "^1.49.0",
- "@mongodb-js/compass-connections": "^1.71.0",
"@mongodb-js/compass-intercom": "^0.35.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/compass-utils": "^0.9.11",
+ "@mongodb-js/connection-info": "^0.17.2",
"bson": "^6.10.4",
"compass-preferences-model": "^2.51.0",
"mongodb": "^6.19.0",
@@ -71,7 +71,6 @@
"zod": "^3.25.76"
},
"devDependencies": {
- "@mongodb-js/connection-info": "^0.17.2",
"@mongodb-js/eslint-config-compass": "^1.4.7",
"@mongodb-js/mocha-config-compass": "^1.7.0",
"@mongodb-js/prettier-config-compass": "^1.2.8",
diff --git a/packages/compass-generative-ai/src/atlas-ai-service.ts b/packages/compass-generative-ai/src/atlas-ai-service.ts
index 3dd8d727b47..70f7e49b60b 100644
--- a/packages/compass-generative-ai/src/atlas-ai-service.ts
+++ b/packages/compass-generative-ai/src/atlas-ai-service.ts
@@ -5,7 +5,7 @@ import {
} from 'compass-preferences-model/provider';
import type { AtlasService } from '@mongodb-js/atlas-service/provider';
import { AtlasServiceError } from '@mongodb-js/atlas-service/renderer';
-import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider';
+import type { ConnectionInfo } from '@mongodb-js/connection-info';
import type { Document } from 'mongodb';
import type { Logger } from '@mongodb-js/compass-logging';
import { EJSON } from 'bson';