Skip to content

Commit b8e1e4b

Browse files
authored
Merge pull request #456 from stainless-sdks/dmeadows/mutation-fix
fix(client): prevent mutations in message streaming
2 parents 8cfd227 + ae63aa6 commit b8e1e4b

File tree

3 files changed

+41
-17
lines changed

3 files changed

+41
-17
lines changed

.github/workflows/publish-npm.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
# You can run this workflow manually by navigating to https://www.github.com/anthropics/anthropic-sdk-typescript/actions/workflows/publish-npm.yml
33
name: Publish NPM
44
on:
5-
6-
75
workflow_dispatch:
86
inputs:
97
path:

src/lib/BetaMessageStream.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -583,14 +583,19 @@ export class BetaMessageStream implements AsyncIterable<BetaMessageStreamEvent>
583583
switch (event.delta.type) {
584584
case 'text_delta': {
585585
if (snapshotContent?.type === 'text') {
586-
snapshotContent.text += event.delta.text;
586+
snapshot.content[event.index] = {
587+
...snapshotContent,
588+
text: (snapshotContent.text || '') + event.delta.text,
589+
};
587590
}
588591
break;
589592
}
590593
case 'citations_delta': {
591594
if (snapshotContent?.type === 'text') {
592-
snapshotContent.citations ??= [];
593-
snapshotContent.citations.push(event.delta.citation);
595+
snapshot.content[event.index] = {
596+
...snapshotContent,
597+
citations: [...(snapshotContent.citations ?? []), event.delta.citation],
598+
};
594599
}
595600
break;
596601
}
@@ -602,34 +607,42 @@ export class BetaMessageStream implements AsyncIterable<BetaMessageStreamEvent>
602607
let jsonBuf = (snapshotContent as any)[JSON_BUF_PROPERTY] || '';
603608
jsonBuf += event.delta.partial_json;
604609

605-
Object.defineProperty(snapshotContent, JSON_BUF_PROPERTY, {
610+
const newContent = { ...snapshotContent };
611+
Object.defineProperty(newContent, JSON_BUF_PROPERTY, {
606612
value: jsonBuf,
607613
enumerable: false,
608614
writable: true,
609615
});
610616

611617
if (jsonBuf) {
612618
try {
613-
snapshotContent.input = partialParse(jsonBuf);
619+
newContent.input = partialParse(jsonBuf);
614620
} catch (err) {
615621
const error = new AnthropicError(
616622
`Unable to parse tool parameter JSON from model. Please retry your request or adjust your prompt. Error: ${err}. JSON: ${jsonBuf}`,
617623
);
618624
this.#handleError(error);
619625
}
620626
}
627+
snapshot.content[event.index] = newContent;
621628
}
622629
break;
623630
}
624631
case 'thinking_delta': {
625632
if (snapshotContent?.type === 'thinking') {
626-
snapshotContent.thinking += event.delta.thinking;
633+
snapshot.content[event.index] = {
634+
...snapshotContent,
635+
thinking: snapshotContent.thinking + event.delta.thinking,
636+
};
627637
}
628638
break;
629639
}
630640
case 'signature_delta': {
631641
if (snapshotContent?.type === 'thinking') {
632-
snapshotContent.signature = event.delta.signature;
642+
snapshot.content[event.index] = {
643+
...snapshotContent,
644+
signature: event.delta.signature,
645+
};
633646
}
634647
break;
635648
}

src/lib/MessageStream.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -574,22 +574,27 @@ export class MessageStream implements AsyncIterable<MessageStreamEvent> {
574574

575575
return snapshot;
576576
case 'content_block_start':
577-
snapshot.content.push(event.content_block);
577+
snapshot.content.push({ ...event.content_block });
578578
return snapshot;
579579
case 'content_block_delta': {
580580
const snapshotContent = snapshot.content.at(event.index);
581581

582582
switch (event.delta.type) {
583583
case 'text_delta': {
584584
if (snapshotContent?.type === 'text') {
585-
snapshotContent.text += event.delta.text;
585+
snapshot.content[event.index] = {
586+
...snapshotContent,
587+
text: (snapshotContent.text || '') + event.delta.text,
588+
};
586589
}
587590
break;
588591
}
589592
case 'citations_delta': {
590593
if (snapshotContent?.type === 'text') {
591-
snapshotContent.citations ??= [];
592-
snapshotContent.citations.push(event.delta.citation);
594+
snapshot.content[event.index] = {
595+
...snapshotContent,
596+
citations: [...(snapshotContent.citations ?? []), event.delta.citation],
597+
};
593598
}
594599
break;
595600
}
@@ -601,27 +606,35 @@ export class MessageStream implements AsyncIterable<MessageStreamEvent> {
601606
let jsonBuf = (snapshotContent as any)[JSON_BUF_PROPERTY] || '';
602607
jsonBuf += event.delta.partial_json;
603608

604-
Object.defineProperty(snapshotContent, JSON_BUF_PROPERTY, {
609+
const newContent = { ...snapshotContent };
610+
Object.defineProperty(newContent, JSON_BUF_PROPERTY, {
605611
value: jsonBuf,
606612
enumerable: false,
607613
writable: true,
608614
});
609615

610616
if (jsonBuf) {
611-
snapshotContent.input = partialParse(jsonBuf);
617+
newContent.input = partialParse(jsonBuf);
612618
}
619+
snapshot.content[event.index] = newContent;
613620
}
614621
break;
615622
}
616623
case 'thinking_delta': {
617624
if (snapshotContent?.type === 'thinking') {
618-
snapshotContent.thinking += event.delta.thinking;
625+
snapshot.content[event.index] = {
626+
...snapshotContent,
627+
thinking: snapshotContent.thinking + event.delta.thinking,
628+
};
619629
}
620630
break;
621631
}
622632
case 'signature_delta': {
623633
if (snapshotContent?.type === 'thinking') {
624-
snapshotContent.signature = event.delta.signature;
634+
snapshot.content[event.index] = {
635+
...snapshotContent,
636+
signature: event.delta.signature,
637+
};
625638
}
626639
break;
627640
}

0 commit comments

Comments
 (0)