Skip to content

Commit 649bca1

Browse files
azulusclaude
andcommitted
fix(seer-explorer): Do not open a reasoning box that renders nothing
`hasTrace` opened the box whenever a block had tool calls, but `ToolCallList` suppresses a call that reported no rows, links, todos or markdown. A settled Code Mode call that produced nothing therefore left an empty box between the previous answer and the next spinner. Both now go through one predicate, so the container and its contents cannot disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 440b54f commit 649bca1

3 files changed

Lines changed: 122 additions & 2 deletions

File tree

static/app/views/seerExplorer/components/chat/responseGroup.spec.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,79 @@ describe('ResponseGroup', () => {
120120
expect(screen.getByText('The final answer')).toBeInTheDocument();
121121
});
122122

123+
it('does not open a reasoning box for a call that renders nothing', () => {
124+
// `ToolCallList` suppresses a settled call that reported no rows, links, todos or markdown.
125+
// Counting it as a trace anyway left an empty box between the previous answer and the next
126+
// spinner.
127+
const group: Block[] = [
128+
{
129+
id: 't1',
130+
message: {
131+
role: 'tool_use',
132+
content: null,
133+
tool_calls: [{id: 't1-call', function: 'sentry_api_execute', args: '{}'}],
134+
},
135+
timestamp: '2024-01-01T00:01:00Z',
136+
loading: false,
137+
tool_results: [
138+
{
139+
tool_call_id: 't1-call',
140+
tool_call_function: 'sentry_api_execute',
141+
content: 'ok',
142+
structuredContent: null,
143+
},
144+
],
145+
},
146+
];
147+
148+
render(<ResponseGroup group={group} blockIndex={1} blocks={group} showThinking />, {
149+
organization,
150+
});
151+
152+
expect(screen.queryByRole('button', {name: /Thinking/})).not.toBeInTheDocument();
153+
});
154+
155+
it('still opens the box when the call reported call records', () => {
156+
const group: Block[] = [
157+
{
158+
id: 't1',
159+
message: {
160+
role: 'tool_use',
161+
content: null,
162+
tool_calls: [{id: 't1-call', function: 'sentry_api_execute', args: '{}'}],
163+
},
164+
timestamp: '2024-01-01T00:01:00Z',
165+
loading: false,
166+
tool_results: [
167+
{
168+
tool_call_id: 't1-call',
169+
tool_call_function: 'sentry_api_execute',
170+
content: 'ok',
171+
structuredContent: {
172+
calls: [{id: 1, kind: 'api', title: 'Retrieving issue 4521'}],
173+
},
174+
},
175+
],
176+
},
177+
];
178+
179+
render(<ResponseGroup group={group} blockIndex={1} blocks={group} showThinking />, {
180+
organization,
181+
});
182+
183+
expect(screen.getByRole('button', {name: /Thinking|Retrieving/})).toBeInTheDocument();
184+
});
185+
186+
it('still opens the box for a classic tool call', () => {
187+
const group = [toolUseBlock('t1')];
188+
189+
render(<ResponseGroup group={group} blockIndex={1} blocks={group} showThinking />, {
190+
organization,
191+
});
192+
193+
expect(screen.getByRole('button', {name: /Queried/})).toBeInTheDocument();
194+
});
195+
123196
it('collapses the reasoning until it is expanded', async () => {
124197
const group = [
125198
toolUseBlock('t1', {thinking_content: 'my private reasoning'}),

static/app/views/seerExplorer/components/chat/responseGroup.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {getToolsStringFromBlock} from 'sentry/views/seerExplorer/utils';
1818

1919
import {AssistantBlock} from './assistant';
2020
import {MessagePlaceholder, hasValidContent} from './shared';
21-
import {CODE_MODE_TOOLS, ToolCallList} from './toolUse';
21+
import {CODE_MODE_TOOLS, ToolCallList, blockRendersToolContent} from './toolUse';
2222

2323
/**
2424
* One assistant response: a run of consecutive `assistant`/`tool_use` blocks that follows a user
@@ -171,7 +171,9 @@ export function ResponseGroup({
171171
return (
172172
(showThinking && hasValidContent(block.message.thinking_content)) ||
173173
(!isAnswer && hasValidContent(block.message.content)) ||
174-
Boolean(block.message.tool_calls?.length)
174+
// Not `tool_calls.length`: a call that reported nothing renders no row, and counting it
175+
// opens a reasoning box with an empty body.
176+
blockRendersToolContent(block, blocks)
175177
);
176178
});
177179

static/app/views/seerExplorer/components/chat/toolUse.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,51 @@ function linkKey(link: ToolLink) {
9494
return `${link.kind}:${sorted}`;
9595
}
9696

97+
/**
98+
* Whether `ToolCallList` will render anything for this block.
99+
*
100+
* `ToolCallList` suppresses a tool call that reported nothing, so a caller deciding whether to open
101+
* a container around it cannot go by `tool_calls.length` — that opens an empty box.
102+
*
103+
* Deliberately the same terms as the per-call `hasContent` guard below, minus the residual-link
104+
* filtering: residual links are only consumed by rows, and rows already make this true.
105+
*/
106+
export function blockRendersToolContent(block: Block, blocks?: Block[]): boolean {
107+
const toolCalls = block.message.tool_calls ?? [];
108+
if (!toolCalls.length) {
109+
return false;
110+
}
111+
const toolsUsed = getToolsStringFromBlock(block);
112+
const results = block.tool_results ?? [];
113+
const latestTodos = findLatestTodos(blocks);
114+
115+
if (latestTodos?.block === block) {
116+
return true;
117+
}
118+
if (block.live_calls?.length) {
119+
return true;
120+
}
121+
if ((block.tool_links ?? []).some(link => link && !link.params?.is_error)) {
122+
return true;
123+
}
124+
if (
125+
results.some(result => {
126+
const structured = result?.structuredContent;
127+
return Boolean(
128+
structured?.calls?.length ||
129+
structured?.links?.length ||
130+
structured?.todos?.length ||
131+
(structured && result.content.trimStart().startsWith('{%'))
132+
);
133+
})
134+
) {
135+
return true;
136+
}
137+
return toolCalls.some(
138+
(toolCall, idx) => !CODE_MODE_TOOLS.has(toolCall.function) && Boolean(toolsUsed[idx])
139+
);
140+
}
141+
97142
export function ToolUseBlock({
98143
block,
99144
showThinking,

0 commit comments

Comments
 (0)