Repository: slack-go/slack
Pull request #1331 (2024-10-08 23:09:09)

feat: Slack AI & Assistants Compatibility
Added new function for Slack AI & Assistants, information can be found here: https://api.slack.com/docs/apps/ai

New Events:
https://api.slack.com/events/assistant_thread_started
https://api.slack.com/events/assistant_thread_context_changed

New API Calls:
https://api.slack.com/methods/assistant.threads.setStatus
https://api.slack.com/methods/assistant.threads.setSuggestedPrompts
https://api.slack.com/methods/assistant.threads.setTitle

Tests have been added and pass all checks via `make pr-prep`.



MattDavisRV: This PR resolves [issue 1326](https://github.com/slack-go/slack/issues/1326).
MattDavisRV: @lorenzoaiello, sorry about the initially cluttered PR, I didn't look at the changed files and I'm unsure how all of the additional changes were added in.  I've cleaned up the PR to only be my changes.

I've also added in an additional change for the Section Block that was added due to the AI changes.  I was going to submit an additional PR with it, but I decided to add it in here.  
lorenzoaiello: @MattDavisRV , can you please resolve the conflicts?
MattDavisRV: @lorenzoaiello all set, I refactored my tests, it looks like i can't use a handler function with form variables, I noticed other tests were using the standard "ok handler", so I switched to that.  All tests are passing now.
MattDavisRV: Hey @lorenzoaiello, anything else I need to do here to get this merged in?

Patch:
diff --git a/assistant.go b/assistant.go
new file mode 100644
index 0000000..ef1425f
--- /dev/null
+++ b/assistant.go
@@ -0,0 +1,153 @@
+package slack
+
+import (
+	"context"
+	"encoding/json"
+	"net/url"
+)
+
+// AssistantThreadSetStatusParameters are the parameters for AssistantThreadSetStatus
+type AssistantThreadsSetStatusParameters struct {
+	ChannelID string `json:"channel_id"`
+	Status    string `json:"status"`
+	ThreadTS  string `json:"thread_ts"`
+}
+
+// AssistantThreadSetTitleParameters are the parameters for AssistantThreadSetTitle
+type AssistantThreadsSetTitleParameters struct {
+	ChannelID string `json:"channel_id"`
+	ThreadTS  string `json:"thread_ts"`
+	Title     string `json:"title"`
+}
+
+// AssistantThreadSetSuggestedPromptsParameters are the parameters for AssistantThreadSetSuggestedPrompts
+type AssistantThreadsSetSuggestedPromptsParameters struct {
+	Title     string                   `json:"title"`
+	ChannelID string                   `json:"channel_id"`
+	ThreadTS  string                   `json:"thread_ts"`
+	Prompts   []AssistantThreadsPrompt `json:"prompts"`
+}
+
+// AssistantThreadPrompt is a suggested prompt for a thread
+type AssistantThreadsPrompt struct {
+	Title   string `json:"title"`
+	Message string `json:"message"`
+}
+
+// AssistantThreadSetSuggestedPrompts sets the suggested prompts for a thread
+func (p *AssistantThreadsSetSuggestedPromptsParameters) AddPrompt(title, message string) {
+	p.Prompts = append(p.Prompts, AssistantThreadsPrompt{
+		Title:   title,
+		Message: message,
+	})
+}
+
+// SetAssistantThreadsSugesstedPrompts sets the suggested prompts for a thread
+// @see https://api.slack.com/methods/assistant.threads.setSuggestedPrompts
+func (api *Client) SetAssistantThreadsSuggestedPrompts(params AssistantThreadsSetSuggestedPromptsParameters) (err error) {
+	return api.SetAssistantThreadsSuggestedPromptsContext(context.Background(), params)
+}
+
+// SetAssistantThreadSuggestedPromptsContext sets the suggested prompts for a thread with a custom context
+// @see https://api.slack.com/methods/assistant.threads.setSuggestedPrompts
+func (api *Client) SetAssistantThreadsSuggestedPromptsContext(ctx context.Context, params AssistantThreadsSetSuggestedPromptsParameters) (err error) {
+
+	values := url.Values{
+		"token": {api.token},
+	}
+
+	if params.ThreadTS != "" {
+		values.Add("thread_ts", params.ThreadTS)
+	}
+
+	// Send Prompts as JSON
+	prompts, err := json.Marshal(params.Prompts)
+	if err != nil {
+		return err
+	}
+
+	values.Add("prompts", string(prompts))
+
+	response := struct {
+		SlackResponse
+	}{}
+
+	err = api.postMethod(ctx, "assistant.threads.setSuggestedPrompts", values, &response)
+	if err != nil {
+		return
+	}
+
+	return response.Err()
+}
+
+// SetAssistantThreadStatus sets the status of a thread
+// @see https://api.slack.com/methods/assistant.threads.setStatus
+func (api *Client) SetAssistantThreadsStatus(params AssistantThreadsSetStatusParameters) (err error) {
+	return api.SetAssistantThreadsStatusContext(context.Background(), params)
+}
+
+// SetAssistantThreadStatusContext sets the status of a thread with a custom context
+// @see https://api.slack.com/methods/assistant.threads.setStatus
+func (api *Client) SetAssistantThreadsStatusContext(ctx context.Context, params AssistantThreadsSetStatusParameters) (err error) {
+
+	values := url.Values{
+		"token": {api.token},
+	}
+
+	if params.ThreadTS != "" {
+		values.Add("thread_ts", params.ThreadTS)
+	}
+
+	// Always send the status parameter, if empty, it will clear any existing status
+	values.Add("status", params.Status)
+
+	response := struct {
+		SlackResponse
+	}{}
+
+	err = api.postMethod(ctx, "assistant.threads.setStatus", values, &response)
+	if err != nil {
+		return
+	}
+
+	return response.Err()
+}
+
+// SetAssistantThreadsTitle sets the title of a thread
+// @see https://api.slack.com/methods/assistant.threads.setTitle
+func (api *Client) SetAssistantThreadsTitle(params AssistantThreadsSetTitleParameters) (err error) {
+	return api.SetAssistantThreadsTitleContext(context.Background(), params)
+}
+
+// SetAssistantThreadsTitleContext sets the title of a thread with a custom context
+// @see https://api.slack.com/methods/assistant.threads.setTitle
+func (api *Client) SetAssistantThreadsTitleContext(ctx context.Context, params AssistantThreadsSetTitleParameters) (err error) {
+
+	values := url.Values{
+		"token": {api.token},
+	}
+
+	if params.ChannelID != "" {
+		values.Add("channel_id", params.ChannelID)
+	}
+
+	if params.ThreadTS != "" {
+		values.Add("thread_ts", params.ThreadTS)
+	}
+
+	if params.Title != "" {
+		values.Add("title", params.Title)
+	}
+
+	response := struct {
+		SlackResponse
+	}{}
+
+	err = api.postMethod(ctx, "assistant.threads.setTitle", values, &response)
+	if err != nil {
+		return
+	}
+
+	return response.Err()
+
+}
diff --git a/block_section.go b/block_section.go
index 01ffd5a..1ffc17e 100644
--- a/block_section.go
+++ b/block_section.go
@@ -9,6 +9,7 @@ type SectionBlock struct {
 	BlockID   string             `json:"block_id,omitempty"`
 	Fields    []*TextBlockObject `json:"fields,omitempty"`
 	Accessory *Accessory         `json:"accessory,omitempty"`
+	Expand    bool               `json:"expand,omitempty"`
 }
 
 // BlockType returns the type of the block
@@ -25,6 +26,15 @@ func SectionBlockOptionBlockID(blockID string) SectionBlockOption {
 	}
 }
 
+// SectionBlockOptionExpand allows long text to be auto-expanded when displaying
+//
+// @see https://api.slack.com/reference/block-kit/blocks#section
+func SectionBlockOptionExpand(shouldExpand bool) SectionBlockOption {
+	return func(block *SectionBlock) {
+		block.Expand = shouldExpand
+	}
+}
+
 // NewSectionBlock returns a new instance of a section block to be rendered
 func NewSectionBlock(textObj *TextBlockObject, fields []*TextBlockObject, accessory *Accessory, options ...SectionBlockOption) *SectionBlock {
 	block := SectionBlock{
diff --git a/slackevents/inner_events.go b/slackevents/inner_events.go
index 8f8effa..720640b 100644
--- a/slackevents/inner_events.go
+++ b/slackevents/inner_events.go
@@ -12,6 +12,35 @@ type EventsAPIInnerEvent struct {
 	Data interface{}
 }
 
+// AssistantThreadMessageEvent is an (inner) EventsAPI subscribable event.
+type AssistantThreadStartedEvent struct {
+	Type            string          `json:"type"`
+	AssistantThread AssistantThread `json:"assistant_thread"`
+	EventTimestamp  string          `json:"event_ts"`
+}
+
+// AssistantThreadChangedEvent is an (inner) EventsAPI subscribable event.
+type AssistantThreadContextChangedEvent struct {
+	Type            string          `json:"type"`
+	AssistantThread AssistantThread `json:"assistant_thread"`
+	EventTimestamp  string          `json:"event_ts"`
+}
+
+// AssistantThread is an object that represents a thread of messages between a user and an assistant.
+type AssistantThread struct {
+	UserID          string                 `json:"user_id"`
+	Context         AssistantThreadContext `json:"context"`
+	ChannelID       string                 `json:"channel_id"`
+	ThreadTimeStamp string                 `json:"thread_ts"`
+}
+
+// AssistantThreadContext is an object that represents the context of an assistant thread.
+type AssistantThreadContext struct {
+	ChannelID    string `json:"channel_id"`
+	TeamID       string `json:"team_id"`
+	EnterpriseID string `json:"enterprise_id"`
+}
+
 // AppMentionEvent is an (inner) EventsAPI subscribable event.
 type AppMentionEvent struct {
 	Type            string `json:"type"`
@@ -1117,6 +1146,10 @@ const (
 	AppHomeOpened = EventsAPIType("app_home_opened")
 	// AppUninstalled Your Slack app was uninstalled.
 	AppUninstalled = EventsAPIType("app_uninstalled")
+	// AssistantThreadStarted Your Slack AI Assistant has started a new thread
+	AssistantThreadStarted = EventsAPIType("assistant_thread_started")
+	// AssistantThreadContextChanged Your Slack AI Assistant has changed the context of a thread
+	AssistantThreadContextChanged = EventsAPIType("assistant_thread_context_changed")
 	// ChannelCreated is sent when a new channel is created.
 	ChannelCreated = EventsAPIType("channel_created")
 	// ChannelDeleted is sent when a channel is deleted.
@@ -1273,81 +1306,83 @@ const (
 // implementations. The structs should be instances of the unmarshalling
 // target for the matching event type.
 var EventsAPIInnerEventMapping = map[EventsAPIType]interface{}{
-	AppMention:                   AppMentionEvent{},
-	AppHomeOpened:                AppHomeOpenedEvent{},
-	AppUninstalled:               AppUninstalledEvent{},
-	ChannelCreated:               ChannelCreatedEvent{},
-	ChannelDeleted:               ChannelDeletedEvent{},
-	ChannelArchive:               ChannelArchiveEvent{},
-	ChannelUnarchive:             ChannelUnarchiveEvent{},
-	ChannelLeft:                  ChannelLeftEvent{},
-	ChannelRename:                ChannelRenameEvent{},
-	ChannelIDChanged:             ChannelIDChangedEvent{},
-	FileChange:                   FileChangeEvent{},
-	FileDeleted:                  FileDeletedEvent{},
-	FileShared:                   FileSharedEvent{},
-	FileUnshared:                 FileUnsharedEvent{},
-	GroupDeleted:                 GroupDeletedEvent{},
-	GroupArchive:                 GroupArchiveEvent{},
-	GroupUnarchive:               GroupUnarchiveEvent{},
-	GroupLeft:                    GroupLeftEvent{},
-	GroupRename:                  GroupRenameEvent{},
-	GridMigrationFinished:        GridMigrationFinishedEvent{},
-	GridMigrationStarted:         GridMigrationStartedEvent{},
-	LinkShared:                   LinkSharedEvent{},
-	Message:                      MessageEvent{},
-	MemberJoinedChannel:          MemberJoinedChannelEvent{},
-	MemberLeftChannel:            MemberLeftChannelEvent{},
-	PinAdded:                     PinAddedEvent{},
-	PinRemoved:                   PinRemovedEvent{},
-	ReactionAdded:                ReactionAddedEvent{},
-	ReactionRemoved:              ReactionRemovedEvent{},
-	SharedChannelInviteApproved:  SharedChannelInviteApprovedEvent{},
-	SharedChannelInviteAccepted:  SharedChannelInviteAcceptedEvent{},
-	SharedChannelInviteDeclined:  SharedChannelInviteDeclinedEvent{},
-	SharedChannelInviteReceived:  SharedChannelInviteReceivedEvent{},
-	TeamJoin:                     TeamJoinEvent{},
-	TokensRevoked:                TokensRevokedEvent{},
-	EmojiChanged:                 EmojiChangedEvent{},
-	WorkflowStepExecute:          WorkflowStepExecuteEvent{},
-	MessageMetadataPosted:        MessageMetadataPostedEvent{},
-	MessageMetadataUpdated:       MessageMetadataUpdatedEvent{},
-	MessageMetadataDeleted:       MessageMetadataDeletedEvent{},
-	TeamAccessGranted:            TeamAccessGrantedEvent{},
-	TeamAccessRevoked:            TeamAccessRevokedEvent{},
-	UserProfileChanged:           UserProfileChangedEvent{},
-	ChannelHistoryChanged:        ChannelHistoryChangedEvent{},
-	DndUpdated:                   DndUpdatedEvent{},
-	DndUpdatedUser:               DndUpdatedUserEvent{},
-	EmailDomainChanged:           EmailDomainChangedEvent{},
-	GroupClose:                   GroupCloseEvent{},
-	GroupHistoryChanged:          GroupHistoryChangedEvent{},
-	GroupOpen:                    GroupOpenEvent{},
-	ImClose:                      ImCloseEvent{},
-	ImCreated:                    ImCreatedEvent{},
-	ImHistoryChanged:             ImHistoryChangedEvent{},
-	ImOpen:                       ImOpenEvent{},
-	SubteamCreated:               SubteamCreatedEvent{},
-	SubteamMembersChanged:        SubteamMembersChangedEvent{},
-	SubteamSelfAdded:             SubteamSelfAddedEvent{},
-	SubteamSelfRemoved:           SubteamSelfRemovedEvent{},
-	SubteamUpdated:               SubteamUpdatedEvent{},
-	TeamDomainChange:             TeamDomainChangeEvent{},
-	TeamRename:                   TeamRenameEvent{},
-	UserChange:                   UserChangeEvent{},
-	AppDeleted:                   AppDeletedEvent{},
-	AppInstalled:                 AppInstalledEvent{},
-	AppRequested:                 AppRequestedEvent{},
-	AppUninstalledTeam:           AppUninstalledTeamEvent{},
-	CallRejected:                 CallRejectedEvent{},
-	ChannelShared:                ChannelSharedEvent{},
-	FileCreated:                  FileCreatedEvent{},
-	FilePublic:                   FilePublicEvent{},
-	FunctionExecuted:             FunctionExecutedEvent{},
-	InviteRequested:              InviteRequestedEvent{},
-	SharedChannelInviteRequested: SharedChannelInviteRequestedEvent{},
-	StarAdded:                    StarAddedEvent{},
-	StarRemoved:                  StarRemovedEvent{},
-	UserHuddleChanged:            UserHuddleChangedEvent{},
-	UserStatusChanged:            UserStatusChangedEvent{},
+	AppMention:                    AppMentionEvent{},
+	AppHomeOpened:                 AppHomeOpenedEvent{},
+	AppUninstalled:                AppUninstalledEvent{},
+	AssistantThreadStarted:        AssistantThreadStartedEvent{},
+	AssistantThreadContextChanged: AssistantThreadContextChangedEvent{},
+	ChannelCreated:                ChannelCreatedEvent{},
+	ChannelDeleted:                ChannelDeletedEvent{},
+	ChannelArchive:                ChannelArchiveEvent{},
+	ChannelUnarchive:              ChannelUnarchiveEvent{},
+	ChannelLeft:                   ChannelLeftEvent{},
+	ChannelRename:                 ChannelRenameEvent{},
+	ChannelIDChanged:              ChannelIDChangedEvent{},
+	FileChange:                    FileChangeEvent{},
+	FileDeleted:                   FileDeletedEvent{},
+	FileShared:                    FileSharedEvent{},
+	FileUnshared:                  FileUnsharedEvent{},
+	GroupDeleted:                  GroupDeletedEvent{},
+	GroupArchive:                  GroupArchiveEvent{},
+	GroupUnarchive:                GroupUnarchiveEvent{},
+	GroupLeft:                     GroupLeftEvent{},
+	GroupRename:                   GroupRenameEvent{},
+	GridMigrationFinished:         GridMigrationFinishedEvent{},
+	GridMigrationStarted:          GridMigrationStartedEvent{},
+	LinkShared:                    LinkSharedEvent{},
+	Message:                       MessageEvent{},
+	MemberJoinedChannel:           MemberJoinedChannelEvent{},
+	MemberLeftChannel:             MemberLeftChannelEvent{},
+	PinAdded:                      PinAddedEvent{},
+	PinRemoved:                    PinRemovedEvent{},
+	ReactionAdded:                 ReactionAddedEvent{},
+	ReactionRemoved:               ReactionRemovedEvent{},
+	SharedChannelInviteApproved:   SharedChannelInviteApprovedEvent{},
+	SharedChannelInviteAccepted:   SharedChannelInviteAcceptedEvent{},
+	SharedChannelInviteDeclined:   SharedChannelInviteDeclinedEvent{},
+	SharedChannelInviteReceived:   SharedChannelInviteReceivedEvent{},
+	TeamJoin:                      TeamJoinEvent{},
+	TokensRevoked:                 TokensRevokedEvent{},
+	EmojiChanged:                  EmojiChangedEvent{},
+	WorkflowStepExecute:           WorkflowStepExecuteEvent{},
+	MessageMetadataPosted:         MessageMetadataPostedEvent{},
+	MessageMetadataUpdated:        MessageMetadataUpdatedEvent{},
+	MessageMetadataDeleted:        MessageMetadataDeletedEvent{},
+	TeamAccessGranted:             TeamAccessGrantedEvent{},
+	TeamAccessRevoked:             TeamAccessRevokedEvent{},
+	UserProfileChanged:            UserProfileChangedEvent{},
+	ChannelHistoryChanged:         ChannelHistoryChangedEvent{},
+	DndUpdated:                    DndUpdatedEvent{},
+	DndUpdatedUser:                DndUpdatedUserEvent{},
+	EmailDomainChanged:            EmailDomainChangedEvent{},
+	GroupClose:                    GroupCloseEvent{},
+	GroupHistoryChanged:           GroupHistoryChangedEvent{},
+	GroupOpen:                     GroupOpenEvent{},
+	ImClose:                       ImCloseEvent{},
+	ImCreated:                     ImCreatedEvent{},
+	ImHistoryChanged:              ImHistoryChangedEvent{},
+	ImOpen:                        ImOpenEvent{},
+	SubteamCreated:                SubteamCreatedEvent{},
+	SubteamMembersChanged:         SubteamMembersChangedEvent{},
+	SubteamSelfAdded:              SubteamSelfAddedEvent{},
+	SubteamSelfRemoved:            SubteamSelfRemovedEvent{},
+	SubteamUpdated:                SubteamUpdatedEvent{},
+	TeamDomainChange:              TeamDomainChangeEvent{},
+	TeamRename:                    TeamRenameEvent{},
+	UserChange:                    UserChangeEvent{},
+	AppDeleted:                    AppDeletedEvent{},
+	AppInstalled:                  AppInstalledEvent{},
+	AppRequested:                  AppRequestedEvent{},
+	AppUninstalledTeam:            AppUninstalledTeamEvent{},
+	CallRejected:                  CallRejectedEvent{},
+	ChannelShared:                 ChannelSharedEvent{},
+	FileCreated:                   FileCreatedEvent{},
+	FilePublic:                    FilePublicEvent{},
+	FunctionExecuted:              FunctionExecutedEvent{},
+	InviteRequested:               InviteRequestedEvent{},
+	SharedChannelInviteRequested:  SharedChannelInviteRequestedEvent{},
+	StarAdded:                     StarAddedEvent{},
+	StarRemoved:                   StarRemovedEvent{},
+	UserHuddleChanged:             UserHuddleChangedEvent{},
+	UserStatusChanged:             UserStatusChangedEvent{},
 }


Test patch:
diff --git a/assistant_test.go b/assistant_test.go
new file mode 100644
index 0000000..51c2716
--- /dev/null
+++ b/assistant_test.go
@@ -0,0 +1,86 @@
+package slack
+
+import (
+	"encoding/json"
+	"net/http"
+	"testing"
+)
+
+func TestAssistantThreadsSuggestedPrompts(t *testing.T) {
+
+	http.HandleFunc("/assistant.threads.setSuggestedPrompts", okJSONHandler)
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	params := AssistantThreadsSetSuggestedPromptsParameters{
+		ChannelID: "CXXXXXXXX",
+		ThreadTS:  "1234567890.123456",
+	}
+
+	params.AddPrompt("title1", "message1")
+	params.AddPrompt("title2", "message2")
+
+	err := api.SetAssistantThreadsSuggestedPrompts(params)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+}
+
+func TestSetAssistantThreadsStatus(t *testing.T) {
+
+	http.HandleFunc("/assistant.threads.setStatus", okJSONHandler)
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	params := AssistantThreadsSetStatusParameters{
+		ChannelID: "CXXXXXXXX",
+		ThreadTS:  "1234567890.123456",
+		Status:    "updated status",
+	}
+
+	err := api.SetAssistantThreadsStatus(params)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+}
+
+func assistantThreadsTitleHandler(rw http.ResponseWriter, r *http.Request) {
+
+	channelID := r.FormValue("channel_id")
+	threadTS := r.FormValue("thread_ts")
+	title := r.FormValue("title")
+
+	rw.Header().Set("Content-Type", "application/json")
+
+	if channelID != "" && threadTS != "" && title != "" {
+
+		resp, _ := json.Marshal(&addBookmarkResponse{
+			SlackResponse: SlackResponse{Ok: true},
+		})
+		rw.Write(resp)
+	} else {
+		rw.Write([]byte(`{ "ok": false, "error": "errored" }`))
+	}
+
+}
+
+func TestSetAssistantThreadsTitle(t *testing.T) {
+
+	http.HandleFunc("/assistant.threads.setTitle", assistantThreadsTitleHandler)
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	params := AssistantThreadsSetTitleParameters{
+		ChannelID: "CXXXXXXXX",
+		ThreadTS:  "1234567890.123456",
+		Title:     "updated title",
+	}
+
+	err := api.SetAssistantThreadsTitle(params)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+}
diff --git a/slackevents/inner_events_test.go b/slackevents/inner_events_test.go
index 4307e8a..baa4e2c 100644
--- a/slackevents/inner_events_test.go
+++ b/slackevents/inner_events_test.go
@@ -8,6 +8,59 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func TestAssistantThreadStartedEvent(t *testing.T) {
+
+	rawE := []byte(`
+		{
+			"type": "assistant_thread_started",
+			"assistant_thread": {
+				"user_id": "U123ABC456",
+				"context": { 
+					"channel_id": "C123ABC456", 
+					"team_id": "T07XY8FPJ5C", 
+					"enterprise_id": "E480293PS82"
+					},
+				"channel_id": "D123ABC456",
+				"thread_ts": "1729999327.187299"
+
+			},
+			"event_ts": "1715873754.429808"
+		}
+	`)
+
+	err := json.Unmarshal(rawE, &AssistantThreadStartedEvent{})
+	if err != nil {
+		t.Error(err)
+	}
+
+}
+
+func TestAssistantThreadContextChangedEvent(t *testing.T) {
+
+	rawE := []byte(`
+		{
+			"type": "assistant_thread_context_changed",
+			"assistant_thread": {
+				"user_id": "U123ABC456",
+				"context": { 
+					"channel_id": "C123ABC456", 
+					"team_id": "T07XY8FPJ5C", 
+					"enterprise_id": "E480293PS82"
+					},
+				"channel_id": "D123ABC456",
+				"thread_ts": "1729999327.187299"
+			},
+			"event_ts": "17298244.022142"
+		}
+	`)
+
+	err := json.Unmarshal(rawE, &AssistantThreadContextChangedEvent{})
+	if err != nil {
+		t.Error(err)
+	}
+
+}
+
 func TestAppMention(t *testing.T) {
 	rawE := []byte(`
 			{