AI-Enabled Features: Loading States, Text Streaming, and Feedback Buttons
🍿 Preview
2025-10-06-loading-state-text-streaming-feedback.mov
⚡ Getting Started
Try the AI Agent Sample app to explore the AI-enabled features and existing Assistant helper:
# Create a new AI Agent app
$ slack create slack-ai-agent-app --template slack-samples/bolt-js-assistant-template
$ cd slack-ai-agent-app/
# Add your OPENAI_API_KEY
$ export OPENAI_API_KEY=sk-proj-ahM...
# Run the local dev server
$ slack run
⌛ Loading States
Loading states allows you to not only set the status (e.g. "My app is typing...") but also sprinkle some personality by cycling through a collection of loading messages:
Web Client SDK:
app.event('message', async ({ client, context, event, logger }) => {
// ...
await client.assistant.threads.setStatus({
channel_id: channelId,
thread_ts: threadTs,
status: 'thinking...',
loading_messages: [
'Teaching the hamsters to type faster…',
'Untangling the internet cables…',
'Consulting the office goldfish…',
'Polishing up the response just for you…',
'Convincing the AI to stop overthinking…',
],
});
// Start a new message stream
});
Assistant Class:
const assistant = new Assistant({
threadStarted: assistantThreadStarted,
threadContextChanged: assistantThreadContextChanged,
userMessage: async ({ client, context, logger, message, getThreadContext, say, setTitle, setStatus }) => {
await setStatus({
status: 'thinking...',
loading_messages: [
'Teaching the hamsters to type faster…',
'Untangling the internet cables…',
'Consulting the office goldfish…',
'Polishing up the response just for you…',
'Convincing the AI to stop overthinking…',
],
});
},
});
🔮 Text Streaming Helper
The client.chatStream()
helper utility can be used to streamline calling the 3 text streaming methods:
app.event('message', async ({ client, context, event, logger }) => {
// ...
// Start a new message stream
const streamer = client.chatStream({
channel: channelId,
recipient_team_id: teamId,
recipient_user_id: userId,
thread_ts: threadTs,
});
// Loop over OpenAI response stream
// https://platform.openai.com/docs/api-reference/responses/create
for await (const chunk of llmResponse) {
if (chunk.type === 'response.output_text.delta') {
await streamer.append({
markdown_text: chunk.delta,
});
}
}
// Stop the stream and attach feedback buttons
await streamer.stop({ blocks: [feedbackBlock] });
});
🔠 Text Streaming Methods
Alternative to the Text Streaming Helper is to call the individual methods.
1) client.chat.startStream
First, start a chat text stream to stream a response to any message:
app.event('message', async ({ client, context, event, logger }) => {
// ...
const streamResponse = await client.chat.startStream({
channel: channelId,
recipient_team_id: teamId,
recipient_user_id: userId,
thread_ts: threadTs,
});
const streamTs = streamResponse.ts
2) client.chat.appendStream
After starting a chat text stream, you can then append text to it in chunks (often from your favourite LLM SDK) to convey a streaming effect:
for await (const chunk of llmResponse) {
if (chunk.type === 'response.output_text.delta') {
await client.chat.appendSteam({
channel: channelId,
markdown_text: chunk.delta,
ts: streamTs,
});
}
}
3) client.chat.stopStream
Lastly, you can stop the chat text stream to finalize your message:
await client.chat.stopStream({
blocks: [feedbackBlock],
channel: channelId,
ts: streamTs,
});
👍🏻 Feedback Buttons
Add feedback buttons to the bottom of a message, after stopping a text stream, to gather user feedback:
const feedbackBlock = {
type: 'context_actions',
elements: [{
type: 'feedback_buttons',
action_id: 'feedback',
positive_button: {
text: { type: 'plain_text', text: 'Good Response' },
accessibility_label: 'Submit positive feedback on this response',
value: 'good-feedback',
},
negative_button: {
text: { type: 'plain_text', text: 'Bad Response' },
accessibility_label: 'Submit negative feedback on this response',
value: 'bad-feedback',
},
}],
};
// Using the Text Streaming Helper
await streamer.stop({ blocks: [feedbackBlock] });
// Or, using the Text Streaming Method
await client.chat.stopStream({
blocks: [feedbackBlock],
channel: channelId,
ts: streamTs,
});
What's Changed
👾 Enhancements
- feat: add ai-enabled features text streaming methods, feedback blocks, and loading state in #2674 - Thanks @zimeg!
🐛 Bug fixes
- Fix: allows Assistant say function to properly pass metadata in #2569 - Thanks @jamessimone!
- fix: better ES module support for App class in #2632 - Thanks @malewis5!
- fix(typescript): accept empty list of suggested prompts for the assistant class in #2650 - Thanks @zimeg!
📚 Documentation
- docs(fix): redirect links for project package migration guides to the tools site in #2539 - Thanks @zimeg!
- Docs: moved over custom steps dynamic options page. in #2552 - Thanks @technically-tracy!
- docs: updated nav in #2564 - Thanks @technically-tracy!
- docs: rotate tokens on a separate schedule with the oauth package in #2558 - Thanks @zimeg!
- docs: guide quick start app creation using the slack cli in #2535 - Thanks @zimeg!
- Update event-listening.md in #2584 - Thanks @jfbn!
- docs: Update language around AI apps in #2606 - Thanks @haleychaas!
- docs: fix listener middleware function and use global middleware examples in #2610 - Thanks @zimeg!
- docs: update ai hugging face tutorial examples in #2613 - Thanks @zimeg!
- docs: guide building an app tutorial using the slack cli in #2597 - Thanks @zimeg!
- docs: use the app logger in examples in #2651 - Thanks @zimeg!
- docs: updates for combined quickstart in #2661 - Thanks @haleychaas!
🤖 Dependencies
- Update axios dependency to version ^1.12.0 in #2657 - Thanks @malewis5!
- chore(deps): update @slack/web-api requirement from ^7.9.1 to ^7.9.2 in #2541 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/deploy-heroku in #2542 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/getting-started-typescript in #2543 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/oauth in #2544 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/oauth-express-receiver in #2545 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/socket-mode-oauth in #2546 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/deploy-aws-lambda in #2547 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/socket-mode in #2548 - Thanks @dependabot[bot]!
- chore(deps): update @slack/bolt requirement from ^4.3.0 to ^4.4.0 in /examples/message-metadata in #2549 - Thanks @dependabot[bot]!
- chore(deps): bump the docusaurus group in /docs with 5 updates in #2553 - Thanks @dependabot[bot]!
- chore(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /docs in #2574 - Thanks @dependabot[bot]!
- chore(deps): update @slack/web-api requirement from ^7.9.2 to ^7.9.3 in #2578 - Thanks @dependabot[bot]!
- chore(deps): bump dotenv from 16.6.1 to 17.0.0 in /examples/getting-started-typescript in #2589 - Thanks @dependabot[bot]!
- chore(deps): bump dotenv from 16.6.1 to 17.0.0 in /examples/custom-receiver in #2590 - Thanks @dependabot[bot]!
- chore(deps): update @slack/types requirement from ^2.14.0 to ^2.15.0 in #2600 - Thanks @dependabot[bot]!
- chore(deps): bump on-headers and compression in /docs in #2607 - Thanks @dependabot[bot]!
- chore(deps): bump slackapi/slack-github-action from 2.1.0 to 2.1.1 in #2615 - Thanks @dependabot[bot]!
- chore(deps): bump @koa/router from 13.1.1 to 14.0.0 in /examples/custom-receiver in #2622 - Thanks @dependabot[bot]!
- chore(deps): update @slack/types requirement from ^2.15.0 to ^2.16.0 in #2633 - Thanks @dependabot[bot]!
- chore(deps): update @slack/web-api requirement from ^7.9.3 to ^7.10.0 in #2634 - Thanks @dependabot[bot]!
- chore(deps): bump codecov/codecov-action from 5.4.3 to 5.5.0 in #2643 - Thanks @dependabot[bot]!
- chore(deps): bump actions/checkout from 4.2.2 to 5.0.0 in #2642 - Thanks @dependabot[bot]!
- chore(deps): update @slack/socket-mode requirement from ^2.0.4 to ^2.0.5 in #2646 - Thanks @dependabot[bot]!
- chore(deps): update @slack/oauth requirement from ^3.0.3 to ^3.0.4 in #2647 - Thanks @dependabot[bot]!
- chore(deps): bump codecov/codecov-action from 5.5.0 to 5.5.1 in #2662 - Thanks @dependabot[bot]!
- chore(deps): bump actions/stale from 9.1.0 to 10.0.0 in #2663 - Thanks @dependabot[bot]!
- chore(deps): bump actions/setup-node from 4.4.0 to 5.0.0 in #2664 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 22.15.19 to 22.15.21 in #2540 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.14.3 to ^4.14.4 in /examples/deploy-aws-lambda in #2554 - Thanks @dependabot[bot]!
- chore(deps-dev): bump stylelint from 16.19.1 to 16.20.0 in /docs in #2561 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.14.4 to ^4.15.1 in /examples/deploy-aws-lambda in #2562 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 22.15.21 to 22.15.29 in #2563 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 22.15.29 to 22.15.30 in #2566 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.15.1 to ^4.17.0 in /examples/deploy-aws-lambda in #2567 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 22.15.30 to 24.0.3 in #2570 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 22.15.32 to 24.0.3 in /examples/getting-started-typescript in #2571 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 22.15.32 to 24.0.3 in /examples/custom-receiver in #2572 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.17.0 to ^4.17.1 in /examples/deploy-aws-lambda in #2577 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.0.3 to 24.0.7 in #2591 - Thanks @dependabot[bot]!
- chore(deps-dev): bump stylelint from 16.20.0 to 16.21.0 in /docs in #2593 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.0.7 to 24.0.10 in #2594 - Thanks @dependabot[bot]!
- chore(deps-dev): bump stylelint from 16.21.0 to 16.21.1 in /docs in #2596 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.0.10 to 24.0.14 in #2604 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.0.14 to 24.0.15 in #2609 - Thanks @dependabot[bot]!
- chore(deps-dev): bump stylelint-config-standard from 38.0.0 to 39.0.0 in /docs in #2617 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.0.15 to 24.2.0 in #2619 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.17.1 to ^4.17.2 in /examples/deploy-aws-lambda in #2621 - Thanks @dependabot[bot]!
- chore(deps-dev): bump typescript from 5.8.3 to 5.9.2 in /examples/custom-receiver in #2620 - Thanks @dependabot[bot]!
- chore(deps-dev): bump typescript from 5.8.3 to 5.9.2 in /examples/getting-started-typescript in #2623 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.17.2 to ^4.18.0 in /examples/deploy-aws-lambda in #2624 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.2.0 to 24.2.1 in #2626 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.2.1 to 24.3.0 in #2635 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.18.0 to ^4.18.1 in /examples/deploy-aws-lambda in #2636 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.3.0 to 24.3.1 in #2648 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.3.1 to 24.4.0 in #2655 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.4.0 to 24.5.2 in #2658 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.18.1 to ^4.19.1 in /examples/deploy-aws-lambda in #2659 - Thanks @dependabot[bot]!
- chore(deps-dev): bump @types/node from 24.5.2 to 24.7.0 in #2669 - Thanks @dependabot[bot]!
- chore(deps-dev): update serverless requirement from ^4.19.1 to ^4.20.2 in /examples/deploy-aws-lambda in #2670 - Thanks @dependabot[bot]!
- chore(deps-dev): bump typescript from 5.9.2 to 5.9.3 in /examples/custom-receiver in #2671 - Thanks @dependabot[bot]!
- chore(deps-dev): bump typescript from 5.9.2 to 5.9.3 in /examples/getting-started-typescript in #2672 - Thanks @dependabot[bot]!
🧰 Maintaince
- ci: pin actions workflow step hashes and use minimum permissions in #2537 - Thanks @zimeg!
- chore: update dependabot to open one PR for all @slack dependencies in /examples in #2550 - Thanks @WilliamBergamin!
- Revert "chore: update dependabot to open one PR for all @slack depend… in #2551 - Thanks @WilliamBergamin!
- build: clone repository "docs" and configuration when syncing project docs + removing separate docusaurus build in #2586 - Thanks @lukegalbraithrussell!
- Revert "build: clone repository "docs" and configuration when syncing project docs + removing separate docusaurus build" in #2595 - Thanks @lukegalbraithrussell!
- refactor: check payload type before extracting assistant thread info in #2603 - Thanks @zimeg!
- ci: send a notification of scheduled failing tests in #2601 - Thanks @zimeg!
- ci: output verbose details when installing packages for tests in #2602 - Thanks @zimeg!
- Build: remove docusaurus configuration files in #2614 - Thanks @lukegalbraithrussell!
- test: swap rewiremock with proxyquire in #2629 - Thanks @WilliamBergamin!
- ci: post regression notifications if scheduled tests do not succeed in #2667 - Thanks @zimeg!
- chore(release): version @slack/bolt@4.5.0 in #2675 - Thanks @zimeg!
New Contributors 🎉
- @jamessimone made their first contribution in #2569
- @jfbn made their first contribution in #2584
- @malewis5 made their first contribution in #2632
Milestone: https://github.com/slackapi/bolt-js/milestone/59?closed=1
Full Changelog: https://github.com/slackapi/bolt-js/compare/@slack/bolt@4.4.0...@slack/bolt@4.5.0