Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
89 changes: 89 additions & 0 deletions packages/types/src/chunk.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;

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<TaskUpdateChunk>;
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;
}
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';
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, 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
74 changes: 74 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,26 @@ expectAssignable<Parameters<typeof web.chat.appendStream>>([
markdown_text: 'hello',
},
]);
expectAssignable<Parameters<typeof web.chat.appendStream>>([
{
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
Expand Down Expand Up @@ -636,6 +656,39 @@ expectAssignable<Parameters<typeof web.chat.startStream>>([
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<Parameters<typeof web.chat.startStream>>([
{
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',
},
Expand Down Expand Up @@ -670,6 +723,27 @@ expectAssignable<Parameters<typeof web.chat.stopStream>>([
blocks: [],
},
]);
expectAssignable<Parameters<typeof web.chat.stopStream>>([
{
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
Expand Down
Loading