fix(core): Display native web search results in agent session timeline (no-changelog) (#31420)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bjorger 2026-06-01 11:01:00 +02:00 committed by GitHub
parent d3405acb82
commit 419cfad98f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 2 deletions

View File

@ -3,6 +3,7 @@ import type { TextStreamPart, ToolSet } from 'ai';
import { convertChunk } from '../stream';
type ToolCallChunk = Extract<TextStreamPart<ToolSet>, { type: 'tool-call' }>;
type ToolResultChunk = Extract<TextStreamPart<ToolSet>, { type: 'tool-result' }>;
describe('convertChunk — tool-call invalid/error handling', () => {
it('returns a tool-result with isError when c.invalid is true', () => {
@ -78,3 +79,62 @@ describe('convertChunk — tool-call invalid/error handling', () => {
expect(result).toMatchObject({ type: 'tool-result', toolName: '', isError: true });
});
});
describe('convertChunk — tool-result output passthrough', () => {
it('passes a raw array output through verbatim (e.g. native web search results)', () => {
const output = [
{ title: 'n8n', url: 'https://n8n.io' },
{ title: 'Docs', url: 'https://docs.n8n.io' },
];
const chunk = {
type: 'tool-result',
toolCallId: 'tc-1',
toolName: 'anthropic.web_search_20250305',
input: { query: 'n8n' },
output,
providerExecuted: true,
} as unknown as ToolResultChunk;
expect(convertChunk(chunk)).toEqual({
type: 'tool-result',
toolCallId: 'tc-1',
toolName: 'anthropic.web_search_20250305',
output,
});
});
it('passes a raw object output through verbatim without unwrapping a coincidental "value" key', () => {
const output = { value: 42, unit: 'C' };
const chunk = {
type: 'tool-result',
toolCallId: 'tc-2',
toolName: 'get_temperature',
input: {},
output,
} as unknown as ToolResultChunk;
expect(convertChunk(chunk)).toEqual({
type: 'tool-result',
toolCallId: 'tc-2',
toolName: 'get_temperature',
output,
});
});
it('falls back to empty strings when toolCallId and toolName are absent', () => {
const chunk = {
type: 'tool-result',
toolCallId: undefined,
toolName: undefined,
input: {},
output: 'plain text result',
} as unknown as ToolResultChunk;
expect(convertChunk(chunk)).toEqual({
type: 'tool-result',
toolCallId: '',
toolName: '',
output: 'plain text result',
});
});
});

View File

@ -96,12 +96,16 @@ export function convertChunk(c: TextStreamPart<ToolSet>): StreamChunk | undefine
}
case 'tool-result':
// The fullStream emits the raw tool output here, not the
// `{ type, value }` ToolResultOutput wrapper used on the message
// side — so pass it through verbatim. Only provider-executed tools
// (e.g. native web search) reach this branch; local tool results are
// written directly by the runtime and never pass through convertChunk.
return {
type: 'tool-result',
toolCallId: c.toolCallId ?? '',
toolName: c.toolName ?? '',
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
output: c.output && 'value' in c.output ? (c.output.value as JSONValue) : null,
output: c.output,
};
case 'error':