-
Notifications
You must be signed in to change notification settings - Fork 3.8k
fix(ai): throw error for missing start chunks in processUIMessageStream #11463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
5659f3d
3258205
83912fe
77255e6
2ba5d81
e034dfc
9cbbe21
92d5dd6
6e2b1b9
047e713
b374c01
67db24b
f491882
cbee82a
a61eb8e
a6bdf9d
1415615
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,14 +57,14 @@ export function createStreamingUIMessageState<UI_MESSAGE extends UIMessage>({ | |
| lastMessage?.role === 'assistant' | ||
| ? lastMessage | ||
| : ({ | ||
| id: messageId, | ||
| metadata: undefined, | ||
| role: 'assistant', | ||
| parts: [] as UIMessagePart< | ||
| InferUIMessageData<UI_MESSAGE>, | ||
| InferUIMessageTools<UI_MESSAGE> | ||
| >[], | ||
| } as UI_MESSAGE), | ||
| id: messageId, | ||
| metadata: undefined, | ||
| role: 'assistant', | ||
| parts: [] as UIMessagePart< | ||
| InferUIMessageData<UI_MESSAGE>, | ||
| InferUIMessageTools<UI_MESSAGE> | ||
| >[], | ||
| } as UI_MESSAGE), | ||
| activeTextParts: {}, | ||
| activeReasoningParts: {}, | ||
| partialToolCalls: {}, | ||
|
|
@@ -123,33 +123,33 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
| providerExecuted?: boolean; | ||
| title?: string; | ||
| } & ( | ||
| | { | ||
| | { | ||
| state: 'input-streaming'; | ||
| input: unknown; | ||
| providerExecuted?: boolean; | ||
| } | ||
| | { | ||
| | { | ||
| state: 'input-available'; | ||
| input: unknown; | ||
| providerExecuted?: boolean; | ||
| providerMetadata?: ProviderMetadata; | ||
| } | ||
| | { | ||
| | { | ||
| state: 'output-available'; | ||
| input: unknown; | ||
| output: unknown; | ||
| providerExecuted?: boolean; | ||
| preliminary?: boolean; | ||
| } | ||
| | { | ||
| | { | ||
| state: 'output-error'; | ||
| input: unknown; | ||
| rawInput?: unknown; | ||
| errorText: string; | ||
| providerExecuted?: boolean; | ||
| providerMetadata?: ProviderMetadata; | ||
| } | ||
| ), | ||
| ), | ||
| ) { | ||
| const part = state.message.parts.find( | ||
| part => | ||
|
|
@@ -206,28 +206,28 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
| providerExecuted?: boolean; | ||
| title?: string; | ||
| } & ( | ||
| | { | ||
| | { | ||
| state: 'input-streaming'; | ||
| input: unknown; | ||
| } | ||
| | { | ||
| | { | ||
| state: 'input-available'; | ||
| input: unknown; | ||
| providerMetadata?: ProviderMetadata; | ||
| } | ||
| | { | ||
| | { | ||
| state: 'output-available'; | ||
| input: unknown; | ||
| output: unknown; | ||
| preliminary: boolean | undefined; | ||
| } | ||
| | { | ||
| | { | ||
| state: 'output-error'; | ||
| input: unknown; | ||
| errorText: string; | ||
| providerMetadata?: ProviderMetadata; | ||
| } | ||
| ), | ||
| ), | ||
| ) { | ||
| const part = state.message.parts.find( | ||
| part => | ||
|
|
@@ -313,6 +313,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
|
|
||
| case 'text-delta': { | ||
| const textPart = state.activeTextParts[chunk.id]; | ||
| if (textPart == null) { | ||
| throw new Error( | ||
| `Received text-delta for missing text part with ID "${chunk.id}". ` + | ||
| `Ensure a "text-start" chunk is sent before any "text-delta" chunks.`, | ||
| ); | ||
| } | ||
| textPart.text += chunk.delta; | ||
| textPart.providerMetadata = | ||
| chunk.providerMetadata ?? textPart.providerMetadata; | ||
|
|
@@ -322,6 +328,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
|
|
||
| case 'text-end': { | ||
| const textPart = state.activeTextParts[chunk.id]; | ||
| if (textPart == null) { | ||
| throw new Error( | ||
|
||
| `Received text-end for missing text part with ID "${chunk.id}". ` + | ||
| `Ensure a "text-start" chunk is sent before any "text-end" chunks.`, | ||
| ); | ||
| } | ||
codewarnab marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| textPart.state = 'done'; | ||
| textPart.providerMetadata = | ||
| chunk.providerMetadata ?? textPart.providerMetadata; | ||
|
|
@@ -345,6 +357,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
|
|
||
| case 'reasoning-delta': { | ||
| const reasoningPart = state.activeReasoningParts[chunk.id]; | ||
| if (reasoningPart == null) { | ||
| throw new Error( | ||
| `Received reasoning-delta for missing reasoning part with ID "${chunk.id}". ` + | ||
| `Ensure a "reasoning-start" chunk is sent before any "reasoning-delta" chunks.`, | ||
| ); | ||
| } | ||
| reasoningPart.text += chunk.delta; | ||
| reasoningPart.providerMetadata = | ||
| chunk.providerMetadata ?? reasoningPart.providerMetadata; | ||
|
|
@@ -354,6 +372,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
|
|
||
| case 'reasoning-end': { | ||
| const reasoningPart = state.activeReasoningParts[chunk.id]; | ||
| if (reasoningPart == null) { | ||
| throw new Error( | ||
| `Received reasoning-end for missing reasoning part with ID "${chunk.id}". ` + | ||
| `Ensure a "reasoning-start" chunk is sent before any "reasoning-end" chunks.`, | ||
| ); | ||
| } | ||
codewarnab marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| reasoningPart.providerMetadata = | ||
| chunk.providerMetadata ?? reasoningPart.providerMetadata; | ||
| reasoningPart.state = 'done'; | ||
|
|
@@ -440,6 +464,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
|
|
||
| case 'tool-input-delta': { | ||
| const partialToolCall = state.partialToolCalls[chunk.toolCallId]; | ||
| if (partialToolCall == null) { | ||
| throw new Error( | ||
| `Received tool-input-delta for missing tool call with ID "${chunk.toolCallId}". ` + | ||
| `Ensure a "tool-input-start" chunk is sent before any "tool-input-delta" chunks.`, | ||
| ); | ||
| } | ||
|
|
||
| partialToolCall.text += chunk.inputTextDelta; | ||
|
|
||
|
|
@@ -684,12 +714,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({ | |
| const existingUIPart = | ||
| dataChunk.id != null | ||
| ? (state.message.parts.find( | ||
| chunkArg => | ||
| dataChunk.type === chunkArg.type && | ||
| dataChunk.id === chunkArg.id, | ||
| ) as | ||
| | DataUIPart<InferUIMessageData<UI_MESSAGE>> | ||
| | undefined) | ||
| chunkArg => | ||
| dataChunk.type === chunkArg.type && | ||
| dataChunk.id === chunkArg.id, | ||
| ) as | ||
| | DataUIPart<InferUIMessageData<UI_MESSAGE>> | ||
| | undefined) | ||
| : undefined; | ||
|
|
||
| if (existingUIPart != null) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.