Skip to content

Commit b094c07

Browse files
leog25Embedderaayush-kapoor
authored
fix(anthropic): allow null content in compaction_delta streaming schema (#12471)
## Background PR #12384 introduced support for Anthropic's compaction feature, but the streaming Zod schema for compaction_delta events requires content to be a non-nullable string. Anthropic's API sends compaction_delta events with content: null (e.g. the initial frame before the compaction summary text), which causes Zod validation to fail at runtime. ## Summary Changed content: z.string() to content: z.string().nullish() in the compaction_delta streaming schema in packages/anthropic/src/anthropic-messages-api.ts. This matches the existing content_block_start schema for compaction, which already uses .nullish(). ## Manual Verification Reproduction requires streaming a conversation that exceeds the compaction trigger threshold (50k+ tokens) with @ai-sdk/anthropic, which triggers a compaction_delta event with content: null. This is not practical to verify without an API key and a large conversation, but the added regression test simulates this exact scenario by streaming a compaction_delta chunk with content: null. ## Checklist - Tests have been added / updated (for bug fixes / features) - Documentation has been added / updated (for bug fixes / features) - A patch changeset for relevant packages has been added (for bug fixes / features - run pnpm changeset in the project root) - I have reviewed this pull request (self-review) ## Related Issues Fixes #12470 --------- Co-authored-by: Embedder <215220128+embedder-dev@users.noreply.github.com> Co-authored-by: Aayush Kapoor <83492835+aayush-kapoor@users.noreply.github.com>
1 parent 0c531f9 commit b094c07

File tree

4 files changed

+47
-6
lines changed

4 files changed

+47
-6
lines changed

.changeset/great-grapes-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/anthropic': patch
3+
---
4+
5+
fix compaction_delta streaming schema to allow null content

packages/anthropic/src/anthropic-messages-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ export const anthropicMessagesChunkSchema = lazySchema(() =>
11281128
}),
11291129
z.object({
11301130
type: z.literal('compaction_delta'),
1131-
content: z.string(),
1131+
content: z.string().nullish(),
11321132
}),
11331133
z.object({
11341134
type: z.literal('citations_delta'),

packages/anthropic/src/anthropic-messages-language-model.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5679,6 +5679,40 @@ describe('AnthropicMessagesLanguageModel', () => {
56795679
});
56805680
});
56815681

5682+
it('should handle compaction_delta with null content', async () => {
5683+
server.urls['https://api.anthropic.com/v1/messages'].response = {
5684+
type: 'stream-chunks',
5685+
chunks: [
5686+
`data: {"type":"message_start","message":{"id":"msg_test","type":"message","role":"assistant","model":"claude-3-haiku-20240307","stop_sequence":null,"usage":{"input_tokens":100,"output_tokens":0},"content":[],"stop_reason":null}}\n\n`,
5687+
`data: {"type":"content_block_start","index":0,"content_block":{"type":"compaction","content":null}}\n\n`,
5688+
`data: {"type":"content_block_delta","index":0,"delta":{"type":"compaction_delta","content":null}}\n\n`,
5689+
`data: {"type":"content_block_delta","index":0,"delta":{"type":"compaction_delta","content":"Summary of conversation."}}\n\n`,
5690+
`data: {"type":"content_block_stop","index":0}\n\n`,
5691+
`data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":10}}\n\n`,
5692+
`data: {"type":"message_stop"}\n\n`,
5693+
],
5694+
};
5695+
5696+
const { stream } = await model.doStream({
5697+
prompt: TEST_PROMPT,
5698+
});
5699+
5700+
const result = await convertReadableStreamToArray(stream);
5701+
5702+
// No parsing errors should occur (schema must accept null content)
5703+
const errors = result.filter(part => part.type === 'error');
5704+
expect(errors).toHaveLength(0);
5705+
5706+
const compactionDeltas = result.filter(
5707+
part => part.type === 'text-delta' && part.id === '0',
5708+
);
5709+
// null content delta should be skipped, only the string delta comes through
5710+
expect(compactionDeltas).toHaveLength(1);
5711+
expect((compactionDeltas[0] as { delta: string }).delta).toBe(
5712+
'Summary of conversation.',
5713+
);
5714+
});
5715+
56825716
it('should stream tool deltas', async () => {
56835717
server.urls['https://api.anthropic.com/v1/messages'].response = {
56845718
type: 'stream-chunks',

packages/anthropic/src/anthropic-messages-language-model.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,11 +1840,13 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
18401840
}
18411841

18421842
case 'compaction_delta': {
1843-
controller.enqueue({
1844-
type: 'text-delta',
1845-
id: String(value.index),
1846-
delta: value.delta.content,
1847-
});
1843+
if (value.delta.content != null) {
1844+
controller.enqueue({
1845+
type: 'text-delta',
1846+
id: String(value.index),
1847+
delta: value.delta.content,
1848+
});
1849+
}
18481850

18491851
return;
18501852
}

0 commit comments

Comments
 (0)