From e3d7b9b534899aeb7cac0e367c38fa6cc679874a Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Sun, 11 Jan 2026 16:27:26 -0500 Subject: [PATCH 01/13] feat: accept chunks as arguments to chat.{start,append,stop}Stream methods --- packages/types/src/chunk.ts | 89 +++++++++++++++++++ packages/types/src/index.ts | 1 + packages/web-api/src/types/request/chat.ts | 18 +++- .../web-api/test/types/methods/chat.test-d.ts | 74 +++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 packages/types/src/chunk.ts diff --git a/packages/types/src/chunk.ts b/packages/types/src/chunk.ts new file mode 100644 index 000000000..acc7cdc23 --- /dev/null +++ b/packages/types/src/chunk.ts @@ -0,0 +1,89 @@ +/** + * Base interface for streaming message chunks. + * https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + */ +export interface Chunk { + type: string; +} + +/** + * Used for streaming text content with markdown formatting support. + * https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + */ +export interface MarkdownTextChunk extends Chunk { + type: 'markdown_text'; + text: string; +} + +/** + * URL source for task update chunks. + */ +export interface URLSource { + type: 'url'; + url: string; + text: string; + icon_url?: string; +} + +/** + * Used for displaying tool execution progress in a timeline-style UI. + * https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + */ +export interface TaskUpdateChunk extends Chunk { + type: 'task_update'; + id: string; + title: string; + status: 'pending' | 'in_progress' | 'complete' | 'error'; + details?: string; + output?: string; + sources?: URLSource[]; +} + +/** + * Union type of all possible chunk types + */ +export type AnyChunk = MarkdownTextChunk | TaskUpdateChunk; + +/** + * Parse a chunk object and return the appropriate typed chunk. + * Returns null if the chunk is invalid or unknown. + */ +export function parseChunk(chunk: unknown): AnyChunk | null { + if (!chunk || typeof chunk !== 'object') { + return null; + } + + const chunkObj = chunk as Record; + + if (!('type' in chunkObj) || typeof chunkObj.type !== 'string') { + console.warn('Unknown chunk detected and skipped (missing type)', chunk); + return null; + } + + const { type } = chunkObj; + + if (type === 'markdown_text') { + if (typeof chunkObj.text === 'string') { + return chunkObj as unknown as MarkdownTextChunk; + } + console.warn('Invalid MarkdownTextChunk (missing text property)', chunk); + return null; + } + + if (type === 'task_update') { + const taskChunk = chunkObj as Partial; + if ( + typeof taskChunk.id === 'string' && + typeof taskChunk.title === 'string' && + typeof taskChunk.status === 'string' && + ['pending', 'in_progress', 'complete', 'error'].includes(taskChunk.status) + ) { + return chunkObj as unknown as TaskUpdateChunk; + } + console.warn('Invalid TaskUpdateChunk (missing required properties)', chunk); + return null; + } + + console.warn(`Unknown chunk type detected and skipped: ${type}`, chunk); + return null; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 9992536de..2740a2290 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -3,6 +3,7 @@ export * from './block-kit/blocks'; export * from './block-kit/composition-objects'; export * from './block-kit/extensions'; export * from './calls'; +export * from './chunk'; export * from './dialog'; export * from './events'; export * from './message-attachments'; diff --git a/packages/web-api/src/types/request/chat.ts b/packages/web-api/src/types/request/chat.ts index 7734d7a7f..bc43a9fb8 100644 --- a/packages/web-api/src/types/request/chat.ts +++ b/packages/web-api/src/types/request/chat.ts @@ -1,4 +1,5 @@ import type { + AnyChunk, Block, // TODO: these will be combined into one in a new types release EntityMetadata, KnownBlock, @@ -168,7 +169,13 @@ export interface Unfurls { unfurl_media?: boolean; } -export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, MarkdownText {} +export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, MarkdownText { + /** + * @description An array of {@link https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming chunk objects} to append to the stream. + * Either `markdown_text` or `chunks` is required. + */ + chunks?: AnyChunk[]; +} // https://docs.slack.dev/reference/methods/chat.delete export interface ChatDeleteArguments extends ChannelAndTS, AsUser, TokenOverridable {} @@ -233,6 +240,11 @@ export type ChatScheduledMessagesListArguments = OptionalArgument< >; export interface ChatStartStreamArguments extends TokenOverridable, Channel, Partial, ThreadTS { + /** + * @description An array of {@link https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming chunk objects} to start the stream with. + * Either `markdown_text` or `chunks` is required. + */ + chunks?: AnyChunk[]; /** * @description The ID of the team that is associated with `recipient_user_id`. * This is required when starting a streaming conversation outside of a DM. @@ -249,6 +261,10 @@ export type ChatStopStreamArguments = TokenOverridable & ChannelAndTS & Partial & Partial & { + /** + * @description An array of {@link https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming chunk objects} to finalize the stream with. + */ + chunks?: AnyChunk[]; /** * Block formatted elements will be appended to the end of the message. */ diff --git a/packages/web-api/test/types/methods/chat.test-d.ts b/packages/web-api/test/types/methods/chat.test-d.ts index f1ec5f025..09916b0bb 100644 --- a/packages/web-api/test/types/methods/chat.test-d.ts +++ b/packages/web-api/test/types/methods/chat.test-d.ts @@ -32,6 +32,26 @@ expectAssignable>([ markdown_text: 'hello', }, ]); +expectAssignable>([ + { + channel: 'C1234', + ts: '1234.56', + markdown_text: 'hello', + chunks: [ + { + type: 'markdown_text', + text: 'Hello world', + }, + { + type: 'task_update', + id: 'task-1', + title: 'Processing request', + status: 'in_progress', + details: 'Working on it...', + }, + ], + }, +]); // chat.delete // -- sad path @@ -636,6 +656,39 @@ expectAssignable>([ channel: 'C1234', thread_ts: '1234.56', markdown_text: 'hello', + chunks: [ + { + type: 'markdown_text', + text: 'Hello world', + }, + { + type: 'task_update', + id: 'task-1', + title: 'Processing request', + status: 'in_progress', + details: 'Working on it...', + }, + ], + }, +]); +expectAssignable>([ + { + channel: 'C1234', + thread_ts: '1234.56', + markdown_text: 'hello', + chunks: [ + { + type: 'markdown_text', + text: 'Hello world', + }, + { + type: 'task_update', + id: 'task-1', + title: 'Processing request', + status: 'in_progress', + details: 'Working on it...', + }, + ], recipient_team_id: 'T1234', recipient_user_id: 'U1234', }, @@ -670,6 +723,27 @@ expectAssignable>([ blocks: [], }, ]); +expectAssignable>([ + { + channel: 'C1234', + ts: '1234.56', + markdown_text: 'hello', + chunks: [ + { + type: 'markdown_text', + text: 'Hello world', + }, + { + type: 'task_update', + id: 'task-1', + title: 'Processing request', + status: 'in_progress', + details: 'Working on it...', + }, + ], + blocks: [], + }, +]); // chat.unfurl // -- sad path From 260119954e2df1757d6c1e0d28add29efc27b888 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Sun, 11 Jan 2026 17:17:17 -0500 Subject: [PATCH 02/13] build: update slack/types version in package json --- packages/web-api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-api/package.json b/packages/web-api/package.json index 36ffc23bf..c41da4903 100644 --- a/packages/web-api/package.json +++ b/packages/web-api/package.json @@ -49,7 +49,7 @@ }, "dependencies": { "@slack/logger": "^4.0.0", - "@slack/types": "^2.18.0", + "@slack/types": "^2.19.0", "@types/node": ">=18.0.0", "@types/retry": "0.12.0", "axios": "^1.11.0", From 160cfb7aee50dce5759a2968267cbb0da7055562 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Thu, 15 Jan 2026 17:43:38 -0500 Subject: [PATCH 03/13] fix: make markdown text optional and add missing chunk type --- packages/types/src/chunk.ts | 19 ++++++++++++++++++- packages/web-api/src/types/request/chat.ts | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/types/src/chunk.ts b/packages/types/src/chunk.ts index acc7cdc23..6acddb06b 100644 --- a/packages/types/src/chunk.ts +++ b/packages/types/src/chunk.ts @@ -25,6 +25,15 @@ export interface URLSource { icon_url?: string; } +/** + * An updated title of plans for task and tool calls. + * https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + */ +export interface PlanUpdateChunk extends Chunk { + type: 'plan_update'; + title: string; +} + /** * Used for displaying tool execution progress in a timeline-style UI. * https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming @@ -42,7 +51,7 @@ export interface TaskUpdateChunk extends Chunk { /** * Union type of all possible chunk types */ -export type AnyChunk = MarkdownTextChunk | TaskUpdateChunk; +export type AnyChunk = MarkdownTextChunk | PlanUpdateChunk | TaskUpdateChunk; /** * Parse a chunk object and return the appropriate typed chunk. @@ -70,6 +79,14 @@ export function parseChunk(chunk: unknown): AnyChunk | null { return null; } + if(type === 'plan_update') { + if (typeof chunkObj.title === 'string') { + return chunkObj as unknown as PlanUpdateChunk; + } + console.warn('Invalid PlanUpdateChunk (missing title property)', chunk); + return null; + } + if (type === 'task_update') { const taskChunk = chunkObj as Partial; if ( diff --git a/packages/web-api/src/types/request/chat.ts b/packages/web-api/src/types/request/chat.ts index bc43a9fb8..9c5c2292c 100644 --- a/packages/web-api/src/types/request/chat.ts +++ b/packages/web-api/src/types/request/chat.ts @@ -169,7 +169,7 @@ export interface Unfurls { unfurl_media?: boolean; } -export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, MarkdownText { +export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, Partial { /** * @description An array of {@link https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming chunk objects} to append to the stream. * Either `markdown_text` or `chunks` is required. From 81140ccaea3eb4afa9db09f14a2aa39734d5411b Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Fri, 16 Jan 2026 15:20:46 -0500 Subject: [PATCH 04/13] tests: add missing chunk type to tests --- .../web-api/test/types/methods/chat.test-d.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/web-api/test/types/methods/chat.test-d.ts b/packages/web-api/test/types/methods/chat.test-d.ts index 09916b0bb..97ac7f59e 100644 --- a/packages/web-api/test/types/methods/chat.test-d.ts +++ b/packages/web-api/test/types/methods/chat.test-d.ts @@ -42,6 +42,10 @@ expectAssignable>([ type: 'markdown_text', text: 'Hello world', }, + { + type: 'plan_update', + title: 'Analyzing request', + }, { type: 'task_update', id: 'task-1', @@ -655,12 +659,15 @@ expectAssignable>([ { channel: 'C1234', thread_ts: '1234.56', - markdown_text: 'hello', chunks: [ { type: 'markdown_text', text: 'Hello world', }, + { + type: 'plan_update', + title: 'Analyzing request', + }, { type: 'task_update', id: 'task-1', @@ -681,6 +688,10 @@ expectAssignable>([ type: 'markdown_text', text: 'Hello world', }, + { + type: 'plan_update', + title: 'Analyzing request', + }, { type: 'task_update', id: 'task-1', @@ -727,12 +738,15 @@ expectAssignable>([ { channel: 'C1234', ts: '1234.56', - markdown_text: 'hello', chunks: [ { type: 'markdown_text', text: 'Hello world', }, + { + type: 'plan_update', + title: 'Analyzing request', + }, { type: 'task_update', id: 'task-1', From dbca9c953d18709f1067238d2ab2f6cf24ceb86b Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Thu, 22 Jan 2026 13:27:39 -0500 Subject: [PATCH 05/13] build: use @slack/logger for debugging parsechunks --- packages/types/package.json | 3 +++ packages/types/src/chunk.ts | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/types/package.json b/packages/types/package.json index 59af1faee..e5210345a 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -27,6 +27,9 @@ "bugs": { "url": "https://github.com/slackapi/node-slack-sdk/issues" }, + "dependencies": { + "@slack/logger": "^4.0.0" + }, "scripts": { "prepare": "npm run build", "build": "npm run build:clean && tsc", diff --git a/packages/types/src/chunk.ts b/packages/types/src/chunk.ts index 6acddb06b..6b3986cd1 100644 --- a/packages/types/src/chunk.ts +++ b/packages/types/src/chunk.ts @@ -1,3 +1,8 @@ +import { ConsoleLogger, LogLevel } from '@slack/logger'; + +const logger = new ConsoleLogger(); +logger.setLevel(LogLevel.DEBUG); + /** * Base interface for streaming message chunks. * https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming @@ -65,7 +70,7 @@ export function parseChunk(chunk: unknown): AnyChunk | null { const chunkObj = chunk as Record; if (!('type' in chunkObj) || typeof chunkObj.type !== 'string') { - console.warn('Unknown chunk detected and skipped (missing type)', chunk); + logger.debug('Unknown chunk detected and skipped (missing type)', chunk); return null; } @@ -75,15 +80,15 @@ export function parseChunk(chunk: unknown): AnyChunk | null { if (typeof chunkObj.text === 'string') { return chunkObj as unknown as MarkdownTextChunk; } - console.warn('Invalid MarkdownTextChunk (missing text property)', chunk); + logger.debug('Invalid MarkdownTextChunk (missing text property)', chunk); return null; } - if(type === 'plan_update') { + if (type === 'plan_update') { if (typeof chunkObj.title === 'string') { return chunkObj as unknown as PlanUpdateChunk; } - console.warn('Invalid PlanUpdateChunk (missing title property)', chunk); + logger.debug('Invalid PlanUpdateChunk (missing title property)', chunk); return null; } @@ -97,10 +102,10 @@ export function parseChunk(chunk: unknown): AnyChunk | null { ) { return chunkObj as unknown as TaskUpdateChunk; } - console.warn('Invalid TaskUpdateChunk (missing required properties)', chunk); + logger.debug('Invalid TaskUpdateChunk (missing required properties)', chunk); return null; } - console.warn(`Unknown chunk type detected and skipped: ${type}`, chunk); + logger.debug(`Unknown chunk type detected and skipped: ${type}`, chunk); return null; } From 1d480f6312e056d1f1c5d0561069ab5e2702f690 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Thu, 22 Jan 2026 15:39:51 -0500 Subject: [PATCH 06/13] type narrowing for parseChunks --- packages/types/src/chunk.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/types/src/chunk.ts b/packages/types/src/chunk.ts index 6b3986cd1..892c31d21 100644 --- a/packages/types/src/chunk.ts +++ b/packages/types/src/chunk.ts @@ -67,40 +67,38 @@ export function parseChunk(chunk: unknown): AnyChunk | null { return null; } - const chunkObj = chunk as Record; - - if (!('type' in chunkObj) || typeof chunkObj.type !== 'string') { + if (!('type' in chunk) || typeof chunk.type !== 'string') { logger.debug('Unknown chunk detected and skipped (missing type)', chunk); return null; } - const { type } = chunkObj; + const type = chunk.type; if (type === 'markdown_text') { - if (typeof chunkObj.text === 'string') { - return chunkObj as unknown as MarkdownTextChunk; + if ('text' in chunk && typeof chunk.text === 'string') { + return chunk as MarkdownTextChunk; } logger.debug('Invalid MarkdownTextChunk (missing text property)', chunk); return null; } if (type === 'plan_update') { - if (typeof chunkObj.title === 'string') { - return chunkObj as unknown as PlanUpdateChunk; + if ('title' in chunk && typeof chunk.title === 'string') { + return chunk as PlanUpdateChunk; } logger.debug('Invalid PlanUpdateChunk (missing title property)', chunk); return null; } if (type === 'task_update') { - const taskChunk = chunkObj as Partial; + const taskChunk = chunk as Partial; if ( typeof taskChunk.id === 'string' && typeof taskChunk.title === 'string' && typeof taskChunk.status === 'string' && ['pending', 'in_progress', 'complete', 'error'].includes(taskChunk.status) ) { - return chunkObj as unknown as TaskUpdateChunk; + return chunk as TaskUpdateChunk; } logger.debug('Invalid TaskUpdateChunk (missing required properties)', chunk); return null; From 14c9f36bcfd6422a022ef1dc8ed7d79d16ae8476 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Fri, 16 Jan 2026 14:49:03 -0500 Subject: [PATCH 07/13] feat: chunk processing in chat stream helper --- packages/web-api/src/chat-stream.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/web-api/src/chat-stream.ts b/packages/web-api/src/chat-stream.ts index 695100b14..4db44b6a6 100644 --- a/packages/web-api/src/chat-stream.ts +++ b/packages/web-api/src/chat-stream.ts @@ -89,7 +89,14 @@ export class ChatStreamer { if (args.token) { this.token = args.token; } - this.buffer += args.markdown_text; + + if (args.chunks) { + return await this.flushBuffer(args); + } + + if (args.markdown_text) { + this.buffer += args.markdown_text; + } if (this.buffer.length >= this.options.buffer_size) { return await this.flushBuffer(args); } @@ -144,12 +151,13 @@ export class ChatStreamer { this.streamTs = response.ts; this.state = 'in_progress'; } + const finalArgs = args?.chunks ? args : { ...args, markdown_text: this.buffer }; + const response = await this.client.chat.stopStream({ token: this.token, channel: this.streamArgs.channel, ts: this.streamTs, - ...args, - markdown_text: this.buffer, + ...finalArgs, }); this.state = 'completed'; return response; @@ -158,12 +166,14 @@ export class ChatStreamer { private async flushBuffer( args: Omit, ): Promise { + + const finalArgs = args.chunks ? args : { ...args, markdown_text: this.buffer }; + if (!this.streamTs) { const response = await this.client.chat.startStream({ ...this.streamArgs, token: this.token, - ...args, - markdown_text: this.buffer, + ...finalArgs, }); this.buffer = ''; this.streamTs = response.ts; @@ -174,8 +184,7 @@ export class ChatStreamer { token: this.token, channel: this.streamArgs.channel, ts: this.streamTs, - ...args, - markdown_text: this.buffer, + ...finalArgs, }); this.buffer = ''; return response; From 35cceedec3bc2902275e701a8fe759a16e51348f Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Tue, 20 Jan 2026 11:58:42 -0500 Subject: [PATCH 08/13] fix: buffer logic for chunks and markdown --- packages/web-api/src/chat-stream.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/web-api/src/chat-stream.ts b/packages/web-api/src/chat-stream.ts index 4db44b6a6..2808a5282 100644 --- a/packages/web-api/src/chat-stream.ts +++ b/packages/web-api/src/chat-stream.ts @@ -151,8 +151,9 @@ export class ChatStreamer { this.streamTs = response.ts; this.state = 'in_progress'; } - const finalArgs = args?.chunks ? args : { ...args, markdown_text: this.buffer }; - + const finalArgs = this.buffer.length > 0 && !args?.chunks + ? { ...args, markdown_text: this.buffer } + : args; const response = await this.client.chat.stopStream({ token: this.token, channel: this.streamArgs.channel, @@ -167,7 +168,9 @@ export class ChatStreamer { args: Omit, ): Promise { - const finalArgs = args.chunks ? args : { ...args, markdown_text: this.buffer }; + const finalArgs = this.buffer.length > 0 && !args.chunks + ? { ...args, markdown_text: this.buffer } + : args; if (!this.streamTs) { const response = await this.client.chat.startStream({ From 6f5f8f14517070d70c01121f5f86115bd79dbcb5 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Wed, 21 Jan 2026 16:14:52 -0500 Subject: [PATCH 09/13] bug: handle undefined args in ChatStream append --- packages/web-api/src/chat-stream.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web-api/src/chat-stream.ts b/packages/web-api/src/chat-stream.ts index 2808a5282..8bca267fe 100644 --- a/packages/web-api/src/chat-stream.ts +++ b/packages/web-api/src/chat-stream.ts @@ -90,11 +90,11 @@ export class ChatStreamer { this.token = args.token; } - if (args.chunks) { + if (args?.chunks) { return await this.flushBuffer(args); } - if (args.markdown_text) { + if (args?.markdown_text) { this.buffer += args.markdown_text; } if (this.buffer.length >= this.options.buffer_size) { From 5e86b29928d03c4f4f471b61f6857602493c5c2e Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Thu, 22 Jan 2026 16:09:32 -0500 Subject: [PATCH 10/13] linter errors --- packages/web-api/src/chat-stream.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/web-api/src/chat-stream.ts b/packages/web-api/src/chat-stream.ts index 8bca267fe..0ff177240 100644 --- a/packages/web-api/src/chat-stream.ts +++ b/packages/web-api/src/chat-stream.ts @@ -89,14 +89,15 @@ export class ChatStreamer { if (args.token) { this.token = args.token; } - + if (args?.chunks) { return await this.flushBuffer(args); } - + if (args?.markdown_text) { this.buffer += args.markdown_text; } + if (this.buffer.length >= this.options.buffer_size) { return await this.flushBuffer(args); } @@ -151,9 +152,7 @@ export class ChatStreamer { this.streamTs = response.ts; this.state = 'in_progress'; } - const finalArgs = this.buffer.length > 0 && !args?.chunks - ? { ...args, markdown_text: this.buffer } - : args; + const finalArgs = this.buffer.length > 0 && !args?.chunks ? { ...args, markdown_text: this.buffer } : args; const response = await this.client.chat.stopStream({ token: this.token, channel: this.streamArgs.channel, @@ -167,10 +166,7 @@ export class ChatStreamer { private async flushBuffer( args: Omit, ): Promise { - - const finalArgs = this.buffer.length > 0 && !args.chunks - ? { ...args, markdown_text: this.buffer } - : args; + const finalArgs = this.buffer.length > 0 && !args.chunks ? { ...args, markdown_text: this.buffer } : args; if (!this.streamTs) { const response = await this.client.chat.startStream({ From 076d3a58c749f6f38bdce96df66b18fc2638ab2b Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Fri, 23 Jan 2026 14:16:59 -0800 Subject: [PATCH 11/13] Revert "build: use @slack/logger for debugging parsechunks" This reverts commit dbca9c953d18709f1067238d2ab2f6cf24ceb86b. --- packages/types/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/types/package.json b/packages/types/package.json index e5210345a..59af1faee 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -27,9 +27,6 @@ "bugs": { "url": "https://github.com/slackapi/node-slack-sdk/issues" }, - "dependencies": { - "@slack/logger": "^4.0.0" - }, "scripts": { "prepare": "npm run build", "build": "npm run build:clean && tsc", From 0e553a7d08704bfe8981dff9aa5cdfc7130ed51d Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Fri, 23 Jan 2026 18:30:14 -0500 Subject: [PATCH 12/13] fix: flush logic for chunks --- packages/web-api/src/chat-stream.ts | 41 +++++++++++++------ .../web-api/test/types/methods/chat.test-d.ts | 1 - 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/web-api/src/chat-stream.ts b/packages/web-api/src/chat-stream.ts index 0ff177240..e7b72badd 100644 --- a/packages/web-api/src/chat-stream.ts +++ b/packages/web-api/src/chat-stream.ts @@ -1,4 +1,5 @@ import type { Logger } from '@slack/logger'; +import type { AnyChunk } from '@slack/types'; import type { ChatAppendStreamArguments, ChatStartStreamArguments, ChatStopStreamArguments } from './types/request'; import type { ChatAppendStreamResponse, ChatStartStreamResponse, ChatStopStreamResponse } from './types/response'; import type WebClient from './WebClient'; @@ -89,16 +90,10 @@ export class ChatStreamer { if (args.token) { this.token = args.token; } - - if (args?.chunks) { - return await this.flushBuffer(args); - } - if (args?.markdown_text) { this.buffer += args.markdown_text; } - - if (this.buffer.length >= this.options.buffer_size) { + if (this.buffer.length >= this.options.buffer_size || args?.chunks) { return await this.flushBuffer(args); } const details = { @@ -152,12 +147,24 @@ export class ChatStreamer { this.streamTs = response.ts; this.state = 'in_progress'; } - const finalArgs = this.buffer.length > 0 && !args?.chunks ? { ...args, markdown_text: this.buffer } : args; + + const flushings: AnyChunk[] = []; + + if (this.buffer.length > 0) { + flushings.push({ + type: 'markdown_text', + text: this.buffer, + }); + } + if (args?.chunks) { + flushings.push(...args.chunks); + } + const response = await this.client.chat.stopStream({ token: this.token, channel: this.streamArgs.channel, ts: this.streamTs, - ...finalArgs, + chunks: flushings, }); this.state = 'completed'; return response; @@ -166,13 +173,23 @@ export class ChatStreamer { private async flushBuffer( args: Omit, ): Promise { - const finalArgs = this.buffer.length > 0 && !args.chunks ? { ...args, markdown_text: this.buffer } : args; + const flushings: AnyChunk[] = []; + + if (this.buffer.length > 0) { + flushings.push({ + type: 'markdown_text', + text: this.buffer, + }); + } + if (args?.chunks) { + flushings.push(...args.chunks); + } if (!this.streamTs) { const response = await this.client.chat.startStream({ ...this.streamArgs, token: this.token, - ...finalArgs, + chunks: flushings, }); this.buffer = ''; this.streamTs = response.ts; @@ -183,7 +200,7 @@ export class ChatStreamer { token: this.token, channel: this.streamArgs.channel, ts: this.streamTs, - ...finalArgs, + chunks: flushings, }); this.buffer = ''; return response; diff --git a/packages/web-api/test/types/methods/chat.test-d.ts b/packages/web-api/test/types/methods/chat.test-d.ts index 88c5a4b5f..a73e40249 100644 --- a/packages/web-api/test/types/methods/chat.test-d.ts +++ b/packages/web-api/test/types/methods/chat.test-d.ts @@ -676,7 +676,6 @@ expectAssignable>([ { channel: 'C1234', thread_ts: '1234.56', - markdown_text: 'hello', chunks: [ { type: 'markdown_text', From fc5ed1ab6bd948daf2b258e87bfaad82ec0d4490 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Fri, 23 Jan 2026 17:23:39 -0800 Subject: [PATCH 13/13] test: update charStream tests to support chunks --- packages/web-api/src/WebClient.spec.ts | 18 +++++++++++++----- packages/web-api/src/chat-stream.ts | 5 +++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/web-api/src/WebClient.spec.ts b/packages/web-api/src/WebClient.spec.ts index 15dcb3fbe..c0c326297 100644 --- a/packages/web-api/src/WebClient.spec.ts +++ b/packages/web-api/src/WebClient.spec.ts @@ -1228,7 +1228,11 @@ describe('WebClient', () => { ok: true, ts: '123.123', }) - .post('/api/chat.stopStream', { channel: 'C0123456789', ts: '123.123', markdown_text: 'nice!' }) + .post('/api/chat.stopStream', { + channel: 'C0123456789', + ts: '123.123', + chunks: JSON.stringify([{ type: 'markdown_text', text: 'nice!' }]), + }) .reply(200, { ok: true, }); @@ -1279,7 +1283,7 @@ describe('WebClient', () => { const scope = nock('https://slack.com') .post('/api/chat.startStream', { channel: 'C0123456789', - markdown_text: '**this messag', + chunks: JSON.stringify([{ type: 'markdown_text', text: '**this messag' }]), recipient_team_id: 'T0123456789', recipient_user_id: 'U0123456789', thread_ts: '123.000', @@ -1290,7 +1294,7 @@ describe('WebClient', () => { }) .post('/api/chat.appendStream', { channel: 'C0123456789', - markdown_text: 'e is bold!', + chunks: JSON.stringify([{ type: 'markdown_text', text: 'e is bold!' }]), token: 'xoxb-updated-1', ts: '123.123', }) @@ -1300,7 +1304,7 @@ describe('WebClient', () => { .post('/api/chat.stopStream', { blocks: JSON.stringify([contextActionsBlock]), channel: 'C0123456789', - markdown_text: '**', + chunks: JSON.stringify([{ type: 'markdown_text', text: '**' }]), token: 'xoxb-updated-2', ts: '123.123', }) @@ -1370,7 +1374,11 @@ describe('WebClient', () => { ok: true, ts: '123.123', }) - .post('/api/chat.stopStream', { channel: 'C0123456789', ts: '123.123', markdown_text: 'nice!' }) + .post('/api/chat.stopStream', { + channel: 'C0123456789', + ts: '123.123', + chunks: JSON.stringify([{ type: 'markdown_text', text: 'nice!' }]), + }) .reply(200, { ok: true, }); diff --git a/packages/web-api/src/chat-stream.ts b/packages/web-api/src/chat-stream.ts index e7b72badd..779aee80d 100644 --- a/packages/web-api/src/chat-stream.ts +++ b/packages/web-api/src/chat-stream.ts @@ -92,6 +92,7 @@ export class ChatStreamer { } if (args?.markdown_text) { this.buffer += args.markdown_text; + args.markdown_text = undefined; } if (this.buffer.length >= this.options.buffer_size || args?.chunks) { return await this.flushBuffer(args); @@ -135,6 +136,7 @@ export class ChatStreamer { } if (args?.markdown_text) { this.buffer += args.markdown_text; + args.markdown_text = undefined; } if (!this.streamTs) { const response = await this.client.chat.startStream({ @@ -165,6 +167,7 @@ export class ChatStreamer { channel: this.streamArgs.channel, ts: this.streamTs, chunks: flushings, + ...args, }); this.state = 'completed'; return response; @@ -190,6 +193,7 @@ export class ChatStreamer { ...this.streamArgs, token: this.token, chunks: flushings, + ...args, }); this.buffer = ''; this.streamTs = response.ts; @@ -201,6 +205,7 @@ export class ChatStreamer { channel: this.streamArgs.channel, ts: this.streamTs, chunks: flushings, + ...args, }); this.buffer = ''; return response;