Skip to content

Commit a37056e

Browse files
authored
Merge pull request #402 from stainless-sdks/dmeadows/add-server-tool-use-input-delta
fix(client): correctly track input from server_tool_use input deltas
2 parents 8e62803 + 6f9f684 commit a37056e

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

src/lib/BetaMessageStream.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import {
1010
type MessageCreateParamsBase as BetaMessageCreateParamsBase,
1111
type BetaTextBlock,
1212
type BetaTextCitation,
13+
type BetaToolUseBlock,
14+
type BetaServerToolUseBlock,
15+
type BetaMCPToolUseBlock,
1316
} from '../resources/beta/messages/messages';
1417
import { Stream } from '../streaming';
1518
import { partialParse } from '../_vendor/partial-json-parser/parser';
@@ -39,6 +42,12 @@ type MessageStreamEventListeners<Event extends keyof MessageStreamEvents> = {
3942

4043
const JSON_BUF_PROPERTY = '__json_buf';
4144

45+
export type TracksToolInput = BetaToolUseBlock | BetaServerToolUseBlock | BetaMCPToolUseBlock;
46+
47+
function tracksToolInput(content: BetaContentBlock): content is TracksToolInput {
48+
return content.type === 'tool_use' || content.type === 'server_tool_use' || content.type === 'mcp_tool_use';
49+
}
50+
4251
export class BetaMessageStream implements AsyncIterable<BetaMessageStreamEvent> {
4352
messages: BetaMessageParam[] = [];
4453
receivedMessages: BetaMessage[] = [];
@@ -432,7 +441,7 @@ export class BetaMessageStream implements AsyncIterable<BetaMessageStreamEvent>
432441
break;
433442
}
434443
case 'input_json_delta': {
435-
if ((content.type === 'tool_use' || content.type === 'mcp_tool_use') && content.input) {
444+
if (tracksToolInput(content) && content.input) {
436445
this._emit('inputJson', event.delta.partial_json, content.input);
437446
}
438447
break;
@@ -571,7 +580,7 @@ export class BetaMessageStream implements AsyncIterable<BetaMessageStreamEvent>
571580
break;
572581
}
573582
case 'input_json_delta': {
574-
if (snapshotContent?.type === 'tool_use' || snapshotContent?.type === 'mcp_tool_use') {
583+
if (snapshotContent && tracksToolInput(snapshotContent)) {
575584
// we need to keep track of the raw JSON string as well so that we can
576585
// re-parse it for each delta, for now we just store it as an untyped
577586
// non-enumerable property on the snapshot

src/lib/MessageStream.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
type MessageCreateParamsBase,
1111
type TextBlock,
1212
type TextCitation,
13+
type ToolUseBlock,
14+
type ServerToolUseBlock,
1315
} from '../resources/messages';
1416
import { Stream } from '../streaming';
1517
import { partialParse } from '../_vendor/partial-json-parser/parser';
@@ -39,6 +41,12 @@ type MessageStreamEventListeners<Event extends keyof MessageStreamEvents> = {
3941

4042
const JSON_BUF_PROPERTY = '__json_buf';
4143

44+
export type TracksToolInput = ToolUseBlock | ServerToolUseBlock;
45+
46+
function tracksToolInput(content: ContentBlock): content is TracksToolInput {
47+
return content.type === 'tool_use' || content.type === 'server_tool_use';
48+
}
49+
4250
export class MessageStream implements AsyncIterable<MessageStreamEvent> {
4351
messages: MessageParam[] = [];
4452
receivedMessages: Message[] = [];
@@ -432,7 +440,7 @@ export class MessageStream implements AsyncIterable<MessageStreamEvent> {
432440
break;
433441
}
434442
case 'input_json_delta': {
435-
if (content.type === 'tool_use' && content.input) {
443+
if (tracksToolInput(content) && content.input) {
436444
this._emit('inputJson', event.delta.partial_json, content.input);
437445
}
438446
break;
@@ -571,7 +579,7 @@ export class MessageStream implements AsyncIterable<MessageStreamEvent> {
571579
break;
572580
}
573581
case 'input_json_delta': {
574-
if (snapshotContent?.type === 'tool_use') {
582+
if (snapshotContent && tracksToolInput(snapshotContent)) {
575583
// we need to keep track of the raw JSON string as well so that we can
576584
// re-parse it for each delta, for now we just store it as an untyped
577585
// non-enumerable property on the snapshot

tests/lib/TracksToolInput.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { ContentBlock } from '../../src/resources/messages';
2+
3+
import { TracksToolInput } from '@anthropic-ai/sdk/lib/MessageStream';
4+
import { TracksToolInput as BetaTracksToolInput } from '@anthropic-ai/sdk/lib/BetaMessageStream';
5+
import { BetaContentBlock } from '@anthropic-ai/sdk/resources/beta';
6+
7+
/**
8+
* This test ensures that our TracksToolInput type includes all content block types that have an input property.
9+
* If any new content block types with input properties are added, they should be added to the TracksToolInput types.
10+
*/
11+
12+
describe('TracksToolInput type', () => {
13+
describe('Regular MessageStream', () => {
14+
type ContentBlockWithInput = Extract<ContentBlock, { input: unknown }>;
15+
16+
it('TracksToolInput includes all content block types with input properties', () => {
17+
type Test = ContentBlockWithInput extends TracksToolInput ? true : false;
18+
const test: Test = true;
19+
expect(test).toBe(true);
20+
});
21+
22+
it('all TracksToolInput types should have an input property', () => {
23+
type Test2 = TracksToolInput extends ContentBlockWithInput ? true : false;
24+
const test2: Test2 = true;
25+
expect(test2).toBe(true);
26+
});
27+
});
28+
29+
describe('Beta MessageStream', () => {
30+
type BetaContentBlockWithInput = Extract<BetaContentBlock, { input: unknown }>;
31+
32+
it('TracksToolInput includes all content block types with input properties', () => {
33+
type Test = BetaContentBlockWithInput extends BetaTracksToolInput ? true : false;
34+
const test: Test = true;
35+
expect(test).toBe(true);
36+
});
37+
38+
it('all BetaTracksToolInput types should have an input property', () => {
39+
type Test2 = BetaTracksToolInput extends BetaContentBlockWithInput ? true : false;
40+
const test2: Test2 = true;
41+
expect(test2).toBe(true);
42+
});
43+
});
44+
});

0 commit comments

Comments
 (0)