Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ describe('createAnswerRunner', () => {
};
const navigatorProvider = vi.fn(() => navigatorContext);
const exampleStrategy = {onRunStartedEvent: () => {}};
const analytics = {
logGeneratedAnswerStreamEnd: vi.fn(),
logGeneratedAnswerResponseLinked: vi.fn(),
} as any;

beforeEach(() => {
vi.clearAllMocks();
Expand All @@ -86,8 +90,8 @@ describe('createAnswerRunner', () => {
const runner = buildRunner();

await Promise.all([
runner.run(state, dispatch, navigatorProvider),
runner.run(state, dispatch, navigatorProvider),
runner.run(state, dispatch, navigatorProvider, analytics),
runner.run(state, dispatch, navigatorProvider, analytics),
]);

expect(abortRunMock).toHaveBeenCalledTimes(1);
Expand All @@ -96,7 +100,7 @@ describe('createAnswerRunner', () => {
it('creates the agent using configuration selectors', async () => {
const runner = buildRunner();

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);

expect(createAnswerAgent).toHaveBeenCalledWith(
'agent-123',
Expand All @@ -108,9 +112,9 @@ describe('createAnswerRunner', () => {
it('builds the strategy and executes the agent with forwarded props', async () => {
const runner = buildRunner();

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);

expect(createHeadAnswerStrategy).toHaveBeenCalledWith(dispatch);
expect(createHeadAnswerStrategy).toHaveBeenCalledWith(dispatch, analytics);
expect(constructGenerateHeadAnswerParams).toHaveBeenCalledWith(
state,
navigatorContext
Expand All @@ -132,7 +136,7 @@ describe('createAnswerRunner', () => {
vi.mocked(selectDebugAgentSession).mockReturnValue(true);
const runner = buildRunner();

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);

expect(mockAgent.runAgent).toHaveBeenCalledWith(
{
Expand All @@ -149,7 +153,7 @@ describe('createAnswerRunner', () => {
it('exposes an abortRun helper that cancels the active agent', async () => {
const runner = buildRunner();

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);
runner.abortRun();

expect(abortRunMock).toHaveBeenCalledTimes(1);
Expand All @@ -158,7 +162,7 @@ describe('createAnswerRunner', () => {
it('sets isRunning to false on the agent when abortRun is called', async () => {
const runner = buildRunner();

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);
mockAgent.isRunning = true;
runner.abortRun();

Expand All @@ -168,7 +172,7 @@ describe('createAnswerRunner', () => {
it('dispatches the loading state when a run starts', async () => {
const runner = buildRunner();

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);

expect(dispatch).toHaveBeenCalledWith(setIsLoading(true));
});
Expand All @@ -179,7 +183,7 @@ describe('createAnswerRunner', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
runAgentMock.mockRejectedValueOnce(error);

await runner.run(state, dispatch, navigatorProvider);
await runner.run(state, dispatch, navigatorProvider, analytics);

expect(dispatch).toHaveBeenCalledWith(setIsLoading(true));
expect(dispatch).toHaveBeenCalledWith(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {Dispatch} from '@reduxjs/toolkit';
import type {NavigatorContext} from '../../../../../app/navigator-context-provider.js';
import type {AnswerGenerationAnalyticsClient} from '../../../../../features/generated-answer/answer-generation-analytics-client.js';
import {
selectAccessToken,
selectAgentId,
Expand Down Expand Up @@ -39,7 +40,8 @@ export const createAnswerRunner = () => {
const run = async (
state: StateNeededForHeadAnswerParams,
dispatch: Dispatch,
getNavigatorContext: () => NavigatorContext
getNavigatorContext: () => NavigatorContext,
analytics: AnswerGenerationAnalyticsClient
) => {
abortRun();

Expand All @@ -53,7 +55,7 @@ export const createAnswerRunner = () => {

currentAgent = agent;

const strategy = createHeadAnswerStrategy(dispatch);
const strategy = createHeadAnswerStrategy(dispatch, analytics);
const params = constructGenerateHeadAnswerParams(
state,
getNavigatorContext()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,28 @@ import {
updateError,
updateMessage,
} from '../../../../../features/generated-answer/generated-answer-actions.js';
import * as generatedAnswerAnalyticsActions from '../../../../../features/generated-answer/generated-answer-analytics-actions.js';
import {GeneratedAnswerSseErrorCode} from '../../../../../features/generated-answer/sse-generated-answer-errors.js';
import {createHeadAnswerStrategy} from './head-answer-strategy.js';

describe('createHeadAnswerStrategy', () => {
let dispatch: ReturnType<typeof vi.fn> & Dispatch;
let strategy: AgentSubscriber;
let analytics: {
logGeneratedAnswerStreamEnd: ReturnType<typeof vi.fn>;
logGeneratedAnswerResponseLinked: ReturnType<typeof vi.fn>;
};

afterEach(() => {
vi.restoreAllMocks();
});

beforeEach(() => {
dispatch = vi.fn() as unknown as ReturnType<typeof vi.fn> & Dispatch;
strategy = createHeadAnswerStrategy(dispatch);
analytics = {
logGeneratedAnswerStreamEnd: vi.fn(),
logGeneratedAnswerResponseLinked: vi.fn(),
};
strategy = createHeadAnswerStrategy(dispatch, analytics as any);
});

it('initializes identifiers and streaming state when a run starts', () => {
Expand Down Expand Up @@ -154,14 +161,11 @@ describe('createHeadAnswerStrategy', () => {
it('finalizes the run with the answer generation result', () => {
const streamEndAction = vi.fn() as any;
const responseLinkedAction = vi.fn() as any;
const streamEndSpy = vi
.spyOn(generatedAnswerAnalyticsActions, 'logGeneratedAnswerStreamEnd')
.mockReturnValue(streamEndAction);
vi.spyOn(
generatedAnswerAnalyticsActions,
'logGeneratedAnswerResponseLinked'
).mockReturnValue(responseLinkedAction);
strategy = createHeadAnswerStrategy(dispatch);
analytics.logGeneratedAnswerStreamEnd.mockReturnValue(streamEndAction);
analytics.logGeneratedAnswerResponseLinked.mockReturnValue(
responseLinkedAction
);
strategy = createHeadAnswerStrategy(dispatch, analytics as any);
strategy.onRunStartedEvent!({
event: {runId: 'run-001', threadId: 'thread-007'},
} as any);
Expand All @@ -180,22 +184,23 @@ describe('createHeadAnswerStrategy', () => {
expect(dispatch).toHaveBeenNthCalledWith(1, setIsAnswerGenerated(true));
expect(dispatch).toHaveBeenNthCalledWith(2, setCannotAnswer(false));
expect(dispatch).toHaveBeenNthCalledWith(3, setIsStreaming(false));
expect(streamEndSpy).toHaveBeenCalledWith(true, 'run-001', true);
expect(analytics.logGeneratedAnswerStreamEnd).toHaveBeenCalledWith(
true,
'run-001',
true
);
expect(dispatch).toHaveBeenNthCalledWith(4, streamEndAction);
expect(dispatch).toHaveBeenNthCalledWith(5, responseLinkedAction);
});

it('disables answer flags when no answer was generated', () => {
const streamEndAction = vi.fn() as any;
const responseLinkedAction = vi.fn() as any;
const streamEndSpy = vi
.spyOn(generatedAnswerAnalyticsActions, 'logGeneratedAnswerStreamEnd')
.mockReturnValue(streamEndAction);
vi.spyOn(
generatedAnswerAnalyticsActions,
'logGeneratedAnswerResponseLinked'
).mockReturnValue(responseLinkedAction);
strategy = createHeadAnswerStrategy(dispatch);
analytics.logGeneratedAnswerStreamEnd.mockReturnValue(streamEndAction);
analytics.logGeneratedAnswerResponseLinked.mockReturnValue(
responseLinkedAction
);
strategy = createHeadAnswerStrategy(dispatch, analytics as any);
strategy.onRunStartedEvent!({
event: {runId: 'run-001', threadId: 'thread-007'},
} as any);
Expand All @@ -214,7 +219,11 @@ describe('createHeadAnswerStrategy', () => {
expect(dispatch).toHaveBeenNthCalledWith(1, setIsAnswerGenerated(false));
expect(dispatch).toHaveBeenNthCalledWith(2, setCannotAnswer(true));
expect(dispatch).toHaveBeenNthCalledWith(3, setIsStreaming(false));
expect(streamEndSpy).toHaveBeenCalledWith(false, 'run-001', undefined);
expect(analytics.logGeneratedAnswerStreamEnd).toHaveBeenCalledWith(
false,
'run-001',
undefined
);
expect(dispatch).toHaveBeenNthCalledWith(4, streamEndAction);
expect(dispatch).toHaveBeenNthCalledWith(5, responseLinkedAction);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ import {
updateError,
updateMessage,
} from '../../../../../features/generated-answer/generated-answer-actions.js';
import {
logGeneratedAnswerResponseLinked,
logGeneratedAnswerStreamEnd,
} from '../../../../../features/generated-answer/generated-answer-analytics-actions.js';
import type {AnswerGenerationAnalyticsClient} from '../../../../../features/generated-answer/answer-generation-analytics-client.js';
import type {
GenerationStepName,
GenerationToolCallArgsGeneric,
Expand All @@ -37,7 +34,8 @@ import {mapRunErrorCode} from '../../../../../features/generated-answer/sse-gene
* Creates an AgentSubscriber that handles answer streaming events
*/
export const createHeadAnswerStrategy = (
dispatch: ThunkDispatch<unknown, unknown, UnknownAction>
dispatch: ThunkDispatch<unknown, unknown, UnknownAction>,
analytics: AnswerGenerationAnalyticsClient
): AgentSubscriber => {
let runId = '';
let answerHasText = false;
Expand Down Expand Up @@ -191,9 +189,13 @@ export const createHeadAnswerStrategy = (
dispatch(setCannotAnswer(!answerGenerated));
dispatch(setIsStreaming(false));
dispatch(
logGeneratedAnswerStreamEnd(answerGenerated, runId, answerTextIsEmpty)
analytics.logGeneratedAnswerStreamEnd(
answerGenerated,
runId,
answerTextIsEmpty
)
);
dispatch(logGeneratedAnswerResponseLinked());
dispatch(analytics.logGeneratedAnswerResponseLinked());
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import {
setFollowUpIsLoading,
setFollowUpIsStreaming,
} from '../../../../../features/follow-up-answers/follow-up-answers-actions.js';
import * as generatedAnswerAnalyticsActions from '../../../../../features/generated-answer/generated-answer-analytics-actions.js';
import {GeneratedAnswerSseErrorCode} from '../../../../../features/generated-answer/sse-generated-answer-errors.js';
import {createFollowUpStrategy} from './follow-up-answer-strategy.js';

describe('createFollowUpStrategy', () => {
let dispatch: ReturnType<typeof vi.fn> & Dispatch;
let strategy: AgentSubscriber;
let analytics: {
logGeneratedAnswerStreamEnd: ReturnType<typeof vi.fn>;
logGeneratedAnswerResponseLinked: ReturnType<typeof vi.fn>;
};

afterEach(() => {
vi.restoreAllMocks();
Expand All @@ -34,7 +37,11 @@ describe('createFollowUpStrategy', () => {

beforeEach(() => {
dispatch = vi.fn() as unknown as ReturnType<typeof vi.fn> & Dispatch;
strategy = createFollowUpStrategy(dispatch);
analytics = {
logGeneratedAnswerStreamEnd: vi.fn(),
logGeneratedAnswerResponseLinked: vi.fn(),
};
strategy = createFollowUpStrategy(dispatch, analytics as any);
strategy.onRunStartedEvent!({event: {runId}} as any);
vi.clearAllMocks();
});
Expand Down Expand Up @@ -154,7 +161,7 @@ describe('createFollowUpStrategy', () => {
});

it('records turn limit failures using the input run id when no run was started', () => {
strategy = createFollowUpStrategy(dispatch);
strategy = createFollowUpStrategy(dispatch, analytics as any);

strategy.onRunErrorEvent!({
input: {runId: 'run-456'},
Expand Down Expand Up @@ -225,16 +232,11 @@ describe('createFollowUpStrategy', () => {
it('dispatches stream end and response linked analytics when a run finishes', () => {
const streamEndAction = vi.fn() as any;
const responseLinkedAction = vi.fn() as any;
const streamEndSpy = vi
.spyOn(generatedAnswerAnalyticsActions, 'logGeneratedAnswerStreamEnd')
.mockReturnValue(streamEndAction);
const responseLinkedSpy = vi
.spyOn(
generatedAnswerAnalyticsActions,
'logGeneratedAnswerResponseLinked'
)
.mockReturnValue(responseLinkedAction);
strategy = createFollowUpStrategy(dispatch);
analytics.logGeneratedAnswerStreamEnd.mockReturnValue(streamEndAction);
analytics.logGeneratedAnswerResponseLinked.mockReturnValue(
responseLinkedAction
);
strategy = createFollowUpStrategy(dispatch, analytics as any);
strategy.onRunStartedEvent!({event: {runId}} as any);
vi.clearAllMocks();

Expand All @@ -249,8 +251,14 @@ describe('createFollowUpStrategy', () => {
1,
followUpCompleted({answerId: runId, cannotAnswer: false})
);
expect(streamEndSpy).toHaveBeenCalledWith(true, runId, true);
expect(responseLinkedSpy).toHaveBeenCalledWith(runId);
expect(analytics.logGeneratedAnswerStreamEnd).toHaveBeenCalledWith(
true,
runId,
true
);
expect(analytics.logGeneratedAnswerResponseLinked).toHaveBeenCalledWith(
runId
);
expect(dispatch).toHaveBeenNthCalledWith(2, streamEndAction);
expect(dispatch).toHaveBeenNthCalledWith(3, responseLinkedAction);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import {
setFollowUpIsLoading,
setFollowUpIsStreaming,
} from '../../../../../features/follow-up-answers/follow-up-answers-actions.js';
import {
logGeneratedAnswerResponseLinked,
logGeneratedAnswerStreamEnd,
} from '../../../../../features/generated-answer/generated-answer-analytics-actions.js';
import type {AnswerGenerationAnalyticsClient} from '../../../../../features/generated-answer/answer-generation-analytics-client.js';
import type {
GenerationStepName,
GenerationToolCallArgsGeneric,
Expand All @@ -30,7 +27,8 @@ import {mapRunErrorCode} from '../../../../../features/generated-answer/sse-gene
* Creates an AgentSubscriber that handles follow-up answer streaming events
*/
export const createFollowUpStrategy = (
dispatch: ThunkDispatch<unknown, unknown, UnknownAction>
dispatch: ThunkDispatch<unknown, unknown, UnknownAction>,
analytics: AnswerGenerationAnalyticsClient
): AgentSubscriber => {
let runId = '';
let answerHasText = false;
Expand Down Expand Up @@ -184,9 +182,13 @@ export const createFollowUpStrategy = (
})
);
dispatch(
logGeneratedAnswerStreamEnd(answerGenerated, runId, answerTextIsEmpty)
analytics.logGeneratedAnswerStreamEnd(
answerGenerated,
runId,
answerTextIsEmpty
)
);
dispatch(logGeneratedAnswerResponseLinked(runId));
dispatch(analytics.logGeneratedAnswerResponseLinked(runId));
runId = '';
answerHasText = false;
},
Expand Down
Loading
Loading