From ef1056a07cd4cc99ca9b1943dec647a2bf8cba10 Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Sat, 23 May 2026 03:59:45 +0700 Subject: [PATCH 1/2] fix: return empty content for null/undefined tool results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Void/null-returning tools were producing TextBlock('') and TextBlock('') — strings that look like placeholders and are not valid JSON. Side-effectful tools that don't return a value should signal success with an empty content array instead. --- strands-ts/src/tools/function-tool.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/strands-ts/src/tools/function-tool.ts b/strands-ts/src/tools/function-tool.ts index ebd2569bf..634adc8d2 100644 --- a/strands-ts/src/tools/function-tool.ts +++ b/strands-ts/src/tools/function-tool.ts @@ -250,7 +250,7 @@ export class FunctionTool extends Tool implements InvokableTool')], - }) - } - - // Handle undefined with special string representation as text content - if (value === undefined) { - return new ToolResultBlock({ - toolUseId, - status: 'success', - content: [new TextBlock('')], + content: [], }) } From 4d7386911bf3c2f025d54d4dab8723272a709d1a Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Sat, 23 May 2026 04:00:21 +0700 Subject: [PATCH 2/2] test: update null/undefined tool result assertions Update existing tests to expect empty content arrays instead of ''/'' placeholders. Add a dedicated void-callback test covering the common side-effectful tool pattern. --- strands-ts/src/tools/__tests__/tool.test.ts | 43 ++++++++++++++------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/strands-ts/src/tools/__tests__/tool.test.ts b/strands-ts/src/tools/__tests__/tool.test.ts index c6d7447e7..4fe7dbe2c 100644 --- a/strands-ts/src/tools/__tests__/tool.test.ts +++ b/strands-ts/src/tools/__tests__/tool.test.ts @@ -257,7 +257,7 @@ describe('FunctionTool', () => { expect(receivedInput).toEqual(inputData) }) - it('handles null return values correctly', async () => { + it('handles null return values with empty content', async () => { const tool = new FunctionTool({ name: 'nullTool', description: 'Returns null', @@ -273,16 +273,11 @@ describe('FunctionTool', () => { type: 'toolResultBlock', toolUseId: 'test-null', status: 'success', - content: [ - expect.objectContaining({ - type: 'textBlock', - text: '', - }), - ], + content: [], }) }) - it('handles undefined return values correctly', async () => { + it('handles undefined return values with empty content', async () => { const tool = new FunctionTool({ name: 'undefinedTool', description: 'Returns undefined', @@ -299,12 +294,32 @@ describe('FunctionTool', () => { type: 'toolResultBlock', toolUseId: 'test-undefined', status: 'success', - content: [ - expect.objectContaining({ - type: 'textBlock', - text: '', - }), - ], + content: [], + }) + }) + + it('handles void (no return) callbacks with empty content', async () => { + const tool = new FunctionTool({ + name: 'voidTool', + description: 'Side-effectful tool with no return value', + inputSchema: { type: 'object', properties: { name: { type: 'string' } } }, + // @ts-expect-error void is not assignable to JSONValue, but this is the real-world pattern for side-effectful tools + callback: (input: unknown): void => { + const { name } = input as { name: string } + // side effect only — console.log(`Hello, ${name}!`) + void name + }, + }) + + const { result } = await collectGenerator( + tool.stream(createMockContext({ name: 'voidTool', toolUseId: 'test-void', input: { name: 'Alice' } })) + ) + + expect(result).toEqual({ + type: 'toolResultBlock', + toolUseId: 'test-void', + status: 'success', + content: [], }) })