Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX]: Include metadata with Assistant say util #2300

Merged
merged 4 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
19 changes: 13 additions & 6 deletions src/Assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
AssistantThreadsSetSuggestedPromptsResponse,
AssistantThreadsSetTitleResponse,
ChatPostMessageArguments,
MessageMetadataEventPayloadObject,
} from '@slack/web-api';
import {
type AssistantThreadContext,
Expand Down Expand Up @@ -132,7 +133,7 @@ export class Assistant {

private async processEvent(args: AllAssistantMiddlewareArgs): Promise<void> {
const { payload } = args;
const assistantArgs = enrichAssistantArgs(this.threadContextStore, args);
const assistantArgs = await enrichAssistantArgs(this.threadContextStore, args);
const assistantMiddleware = this.getAssistantMiddleware(payload);
return processAssistantMiddleware(assistantArgs, assistantMiddleware);
}
Expand Down Expand Up @@ -160,18 +161,18 @@ export class Assistant {
* events from continuing down the global middleware chain to subsequent listeners
* 2. Adds assistant-specific utilities (i.e., helper methods)
* */
export function enrichAssistantArgs(
export async function enrichAssistantArgs(
threadContextStore: AssistantThreadContextStore,
args: AllAssistantMiddlewareArgs<AssistantMiddlewareArgs>, // TODO: the type here states that these args already have the assistant utilities present? the type here needs likely changing.
): AllAssistantMiddlewareArgs {
): Promise<AllAssistantMiddlewareArgs> {
const { next: _next, ...assistantArgs } = args;
const preparedArgs = { ...(assistantArgs as Exclude<AllAssistantMiddlewareArgs<AssistantMiddlewareArgs>, 'next'>) };

// Do not pass preparedArgs (ie, do not add utilities to get/save)
preparedArgs.getThreadContext = () => threadContextStore.get(args);
preparedArgs.saveThreadContext = () => threadContextStore.save(args);

preparedArgs.say = createSay(preparedArgs);
preparedArgs.say = await createSay(preparedArgs);
preparedArgs.setStatus = createSetStatus(preparedArgs);
preparedArgs.setSuggestedPrompts = createSetSuggestedPrompts(preparedArgs);
preparedArgs.setTitle = createSetTitle(preparedArgs);
Expand Down Expand Up @@ -299,14 +300,20 @@ export async function processAssistantMiddleware(
* was received. Alias for `postMessage()`.
* https://api.slack.com/methods/chat.postMessage
*/
function createSay(args: AllAssistantMiddlewareArgs): SayFn {
async function createSay(args: AllAssistantMiddlewareArgs): Promise<SayFn> {
const { client, payload } = args;
const { channelId: channel, threadTs: thread_ts } = extractThreadInfo(payload);
const { channelId: channel, threadTs: thread_ts, context } = extractThreadInfo(payload);
const threadContext = context.channel_id ? context : await args.getThreadContext(args);
misscoded marked this conversation as resolved.
Show resolved Hide resolved

return (message: Parameters<SayFn>[0]) => {
const postMessageArgument: ChatPostMessageArguments =
typeof message === 'string' ? { text: message, channel, thread_ts } : { ...message, channel, thread_ts };

postMessageArgument.metadata = {
event_type: 'assistant_thread_context',
event_payload: threadContext as MessageMetadataEventPayloadObject,
};

return client.chat.postMessage(postMessageArgument);
};
}
Expand Down
76 changes: 66 additions & 10 deletions test/unit/Assistant.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,12 @@ describe('Assistant class', () => {

const { enrichAssistantArgs } = await importAssistant();

const threadStartedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);
const threadContextChangedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadContextChangedArgs);
const userMessageArgs = enrichAssistantArgs(mockThreadContextStore, mockUserMessageArgs);
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);
const threadContextChangedArgs = await enrichAssistantArgs(
mockThreadContextStore,
mockThreadContextChangedArgs,
);
const userMessageArgs = await enrichAssistantArgs(mockThreadContextStore, mockUserMessageArgs);

assert.notExists(threadStartedArgs.next);
assert.notExists(threadContextChangedArgs.next);
Expand All @@ -208,7 +211,9 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();
const { enrichAssistantArgs } = await importAssistant();
// TODO: enrichAssistantArgs likely needs a different argument type, as AssistantMiddlewareArgs type already has the assistant utility enrichments present.
const assistantArgs = enrichAssistantArgs(mockThreadContextStore, { payload } as AllAssistantMiddlewareArgs);
const assistantArgs = await enrichAssistantArgs(mockThreadContextStore, {
payload,
} as AllAssistantMiddlewareArgs);

assert.exists(assistantArgs.say);
assert.exists(assistantArgs.setStatus);
Expand All @@ -221,7 +226,9 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();
const { enrichAssistantArgs } = await importAssistant();
// TODO: enrichAssistantArgs likely needs a different argument type, as AssistantMiddlewareArgs type already has the assistant utility enrichments present.
const assistantArgs = enrichAssistantArgs(mockThreadContextStore, { payload } as AllAssistantMiddlewareArgs);
const assistantArgs = await enrichAssistantArgs(mockThreadContextStore, {
payload,
} as AllAssistantMiddlewareArgs);

assert.exists(assistantArgs.say);
assert.exists(assistantArgs.setStatus);
Expand All @@ -234,7 +241,9 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();
const { enrichAssistantArgs } = await importAssistant();
// TODO: enrichAssistantArgs likely needs a different argument type, as AssistantMiddlewareArgs type already has the assistant utility enrichments present.
const assistantArgs = enrichAssistantArgs(mockThreadContextStore, { payload } as AllAssistantMiddlewareArgs);
const assistantArgs = await enrichAssistantArgs(mockThreadContextStore, {
payload,
} as AllAssistantMiddlewareArgs);

assert.exists(assistantArgs.say);
assert.exists(assistantArgs.setStatus);
Expand Down Expand Up @@ -299,13 +308,60 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();

const { enrichAssistantArgs } = await importAssistant();
const threadStartedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);

await threadStartedArgs.say('Say called!');

sinon.assert.called(fakeClient.chat.postMessage);
});

it('say should be called with message_metadata that includes thread context', async () => {
const mockThreadStartedArgs = wrapMiddleware(createDummyAssistantThreadStartedEventMiddlewareArgs());

const fakeClient = { chat: { postMessage: sinon.spy() } };
mockThreadStartedArgs.client = fakeClient as unknown as WebClient;
const mockThreadContextStore = createMockThreadContextStore();

const { enrichAssistantArgs } = await importAssistant();
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);

await threadStartedArgs.say('Say called!');

const {
payload: {
assistant_thread: { channel_id, thread_ts, context },
},
} = mockThreadStartedArgs;

const expectedParams = {
text: 'Say called!',
channel: channel_id,
thread_ts,
metadata: {
event_type: 'assistant_thread_context',
event_payload: context,
},
};

sinon.assert.calledWith(fakeClient.chat.postMessage, expectedParams);
});

it('say should get context from store if no thread context is included in event', async () => {
const mockThreadStartedArgs = wrapMiddleware(createDummyAssistantThreadStartedEventMiddlewareArgs());
mockThreadStartedArgs.payload.assistant_thread.context = {};

const fakeClient = { chat: { postMessage: sinon.spy() } };
mockThreadStartedArgs.client = fakeClient as unknown as WebClient;
const mockThreadContextStore = { save: sinon.spy(), get: sinon.spy() };

const { enrichAssistantArgs } = await importAssistant();
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);

misscoded marked this conversation as resolved.
Show resolved Hide resolved
await threadStartedArgs.say('Say called!');

sinon.assert.calledOnce(mockThreadContextStore.get);
});

it('setStatus should call assistant.threads.setStatus', async () => {
const mockThreadStartedArgs = wrapMiddleware(createDummyAssistantThreadStartedEventMiddlewareArgs());

Expand All @@ -314,7 +370,7 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();

const { enrichAssistantArgs } = await importAssistant();
const threadStartedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);

await threadStartedArgs.setStatus('Status set!');

Expand All @@ -329,7 +385,7 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();

const { enrichAssistantArgs } = await importAssistant();
const threadStartedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);

await threadStartedArgs.setSuggestedPrompts({ prompts: [{ title: '', message: '' }] });

Expand All @@ -344,7 +400,7 @@ describe('Assistant class', () => {
const mockThreadContextStore = createMockThreadContextStore();

const { enrichAssistantArgs } = await importAssistant();
const threadStartedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);
const threadStartedArgs = await enrichAssistantArgs(mockThreadContextStore, mockThreadStartedArgs);

await threadStartedArgs.setTitle('Title set!');

Expand Down