Skip to content

Commit 97c35c0

Browse files
authored
feat (ui): transient data parts (#6987)
## Background In some cases, the data sent to the client is ephemeral and should not be added to the message history. ## Summary Add a flag on data part chunks that prevents them from being added to the message parts. ## Related Issues #6975
1 parent f04ffe4 commit 97c35c0

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

‎.changeset/quiet-peas-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': patch
3+
---
4+
5+
feat (ui): transient data parts

‎packages/ai/src/ui-message-stream/ui-message-stream-parts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export const uiMessageStreamPartSchema = z.union([
104104
type: z.string().startsWith('data-'),
105105
id: z.string().optional(),
106106
data: z.unknown(),
107+
transient: z.boolean().optional(),
107108
}),
108109
z.strictObject({
109110
type: z.literal('start-step'),
@@ -131,6 +132,7 @@ export type DataUIMessageStreamPart<DATA_TYPES extends UIDataTypes> = ValueOf<{
131132
type: `data-${NAME}`;
132133
id?: string;
133134
data: DATA_TYPES[NAME];
135+
transient?: boolean;
134136
};
135137
}>;
136138

‎packages/ai/src/ui/process-ui-message-stream.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4139,6 +4139,86 @@ describe('processUIMessageStream', () => {
41394139
});
41404140
});
41414141

4142+
describe('data ui parts (transient part)', () => {
4143+
let dataCalls: InferUIMessageData<UIMessage>[] = [];
4144+
4145+
beforeEach(async () => {
4146+
dataCalls = [];
4147+
4148+
const stream = createUIMessageStream([
4149+
{ type: 'start', messageId: 'msg-123' },
4150+
{ type: 'start-step' },
4151+
{
4152+
type: 'data-test',
4153+
data: 'example-data-can-be-anything',
4154+
transient: true,
4155+
},
4156+
{ type: 'finish-step' },
4157+
{ type: 'finish' },
4158+
]);
4159+
4160+
state = createStreamingUIMessageState({
4161+
messageId: 'msg-123',
4162+
lastMessage: undefined,
4163+
});
4164+
4165+
await consumeStream({
4166+
stream: processUIMessageStream({
4167+
stream,
4168+
runUpdateMessageJob,
4169+
onError: error => {
4170+
throw error;
4171+
},
4172+
onData: data => {
4173+
dataCalls.push(data);
4174+
},
4175+
}),
4176+
});
4177+
});
4178+
4179+
it('should not call the update function with the transient part', async () => {
4180+
expect(writeCalls).toMatchInlineSnapshot(`
4181+
[
4182+
{
4183+
"message": {
4184+
"id": "msg-123",
4185+
"metadata": undefined,
4186+
"parts": [],
4187+
"role": "assistant",
4188+
},
4189+
},
4190+
]
4191+
`);
4192+
});
4193+
4194+
it('should not have the transient part in the final message state', async () => {
4195+
expect(state!.message).toMatchInlineSnapshot(`
4196+
{
4197+
"id": "msg-123",
4198+
"metadata": undefined,
4199+
"parts": [
4200+
{
4201+
"type": "step-start",
4202+
},
4203+
],
4204+
"role": "assistant",
4205+
}
4206+
`);
4207+
});
4208+
4209+
it('should call the onData callback with the transient part', async () => {
4210+
expect(dataCalls).toMatchInlineSnapshot(`
4211+
[
4212+
{
4213+
"data": "example-data-can-be-anything",
4214+
"transient": true,
4215+
"type": "data-test",
4216+
},
4217+
]
4218+
`);
4219+
});
4220+
});
4221+
41424222
describe('data ui parts (single part with id and replacement update)', () => {
41434223
beforeEach(async () => {
41444224
const stream = createUIMessageStream([

‎packages/ai/src/ui/process-ui-message-stream.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
477477
InferUIMessageData<UI_MESSAGE>
478478
>;
479479

480+
// transient parts are not added to the message state
481+
if (dataPart.transient) {
482+
onData?.(dataPart);
483+
break;
484+
}
485+
480486
// TODO improve type safety
481487
const existingPart: any =
482488
dataPart.id != null

0 commit comments

Comments
 (0)