Skip to content
Merged
3 changes: 3 additions & 0 deletions packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"bugs": {
"url": "https://github.com/slackapi/node-slack-sdk/issues"
},
"dependencies": {
"@slack/logger": "^4.0.0"
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: If we remove the parseChunk method, then we can also remove this dependency 🎉

"scripts": {
"prepare": "npm run build",
"build": "npm run build:clean && tsc",
Expand Down
109 changes: 109 additions & 0 deletions packages/types/src/chunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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
*/
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;
}

/**
* 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
*/
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 | PlanUpdateChunk | 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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: This is a nice implementation, but I don't think we need it for the @slack/types package. This parsing was necessary for Python, but in TypeScript I think the AnyChunk union that you created will serve well! :two-cents:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh okay thank you for the clarification 🙌

if (!chunk || typeof chunk !== 'object') {
return null;
}

if (!('type' in chunk) || typeof chunk.type !== 'string') {
logger.debug('Unknown chunk detected and skipped (missing type)', chunk);
return null;
}

const type = chunk.type;

if (type === 'markdown_text') {
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 ('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 = chunk as Partial<TaskUpdateChunk>;
if (
typeof taskChunk.id === 'string' &&
typeof taskChunk.title === 'string' &&
typeof taskChunk.status === 'string' &&
['pending', 'in_progress', 'complete', 'error'].includes(taskChunk.status)
) {
return chunk as TaskUpdateChunk;
}
logger.debug('Invalid TaskUpdateChunk (missing required properties)', chunk);
return null;
}

logger.debug(`Unknown chunk type detected and skipped: ${type}`, chunk);
return null;
}
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Comment thread
srtaalej marked this conversation as resolved.
export * from './dialog';
export * from './events';
export * from './message-attachments';
Expand Down
2 changes: 1 addition & 1 deletion packages/web-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 17 additions & 1 deletion packages/web-api/src/types/request/chat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
AnyChunk,
Block, // TODO: these will be combined into one in a new types release
EntityMetadata,
KnownBlock,
Expand Down Expand Up @@ -168,7 +169,13 @@ export interface Unfurls {
unfurl_media?: boolean;
}

export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, MarkdownText {}
export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, Partial<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 {}
Expand Down Expand Up @@ -233,6 +240,11 @@ export type ChatScheduledMessagesListArguments = OptionalArgument<
>;

export interface ChatStartStreamArguments extends TokenOverridable, Channel, Partial<MarkdownText>, 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.
Expand All @@ -249,6 +261,10 @@ export type ChatStopStreamArguments = TokenOverridable &
ChannelAndTS &
Partial<MarkdownText> &
Partial<Metadata> & {
/**
* @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.
*/
Expand Down
70 changes: 70 additions & 0 deletions packages/web-api/test/types/methods/chat.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ expectAssignable<Parameters<typeof web.chat.appendStream>>([
markdown_text: 'hello',
},
]);
expectAssignable<Parameters<typeof web.chat.appendStream>>([
{
channel: 'C1234',
ts: '1234.56',
chunks: [
{
type: 'markdown_text',
text: 'Hello world',
},
{
type: 'plan_update',
title: 'Analyzing request',
},
{
type: 'task_update',
id: 'task-1',
title: 'Processing request',
status: 'in_progress',
details: 'Working on it...',
},
],
},
]);

// chat.delete
// -- sad path
Expand Down Expand Up @@ -631,6 +654,29 @@ expectAssignable<Parameters<typeof web.chat.startStream>>([
markdown_text: 'hello',
},
]);
expectAssignable<Parameters<typeof web.chat.startStream>>([
{
channel: 'C1234',
thread_ts: '1234.56',
chunks: [
{
type: 'markdown_text',
text: 'Hello world',
},
{
type: 'plan_update',
title: 'Analyzing request',
},
{
type: 'task_update',
id: 'task-1',
title: 'Processing request',
status: 'in_progress',
details: 'Working on it...',
},
],
},
]);
expectAssignable<Parameters<typeof web.chat.startStream>>([
{
channel: 'C1234',
Expand Down Expand Up @@ -670,6 +716,30 @@ expectAssignable<Parameters<typeof web.chat.stopStream>>([
blocks: [],
},
]);
expectAssignable<Parameters<typeof web.chat.stopStream>>([
{
channel: 'C1234',
ts: '1234.56',
chunks: [
{
type: 'markdown_text',
text: 'Hello world',
},
{
type: 'plan_update',
title: 'Analyzing request',
},
{
type: 'task_update',
id: 'task-1',
title: 'Processing request',
status: 'in_progress',
details: 'Working on it...',
},
],
blocks: [],
},
]);

// chat.unfurl
// -- sad path
Expand Down
Loading