fix: render tool calls above text response for external providers

- Add insertChildBefore() to Box component for positional insertion
- In chat controller, insert tool_execution components before the last
  assistant message component (instead of appending after) when tools
  were executed externally
- Simplify agent-loop externalToolExecution path back to basic
  tool_execution_start/end emission
- Toolcall streaming events are filtered in the Claude Code adapter
  to prevent duplicate rendering via message_update

Result: externally-executed tool calls render above the text response,
matching the expected visual flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-25 14:38:39 -06:00
parent bbea8460b5
commit 263d725ecd
3 changed files with 25 additions and 3 deletions

View file

@ -234,8 +234,9 @@ async function runLoop(
const toolResults: ToolResultMessage[] = [];
if (hasMoreToolCalls && config.externalToolExecution) {
// External execution mode: tools were handled by the provider (e.g., Claude Code SDK).
// Emit synthetic tool events for TUI rendering but skip local dispatch.
// External execution mode: tools were handled by the provider
// (e.g., Claude Code SDK). Emit tool_execution events for each
// tool call. The TUI adds these as components after the message.
for (const tc of toolCalls as AgentToolCall[]) {
stream.push({
type: "tool_execution_start",

View file

@ -210,7 +210,18 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
host.ui,
);
component.setExpanded(host.toolOutputExpanded);
host.chatContainer.addChild(component);
// For external tool execution: insert tool components before the
// last message component so tools render above the text response.
// The last child is the message that just finished streaming.
const children = host.chatContainer.children;
const lastChild = children.length > 0 ? children[children.length - 1] : undefined;
if (lastChild instanceof AssistantMessageComponent && !host.streamingComponent) {
host.chatContainer.insertChildBefore(component, lastChild);
} else {
host.chatContainer.addChild(component);
}
host.pendingTools.set(event.toolCallId, component);
host.ui.requestRender();
}

View file

@ -31,6 +31,16 @@ export class Box implements Component {
this.invalidateCache();
}
insertChildBefore(component: Component, before: Component): void {
const index = this.children.indexOf(before);
if (index !== -1) {
this.children.splice(index, 0, component);
} else {
this.children.push(component);
}
this.invalidateCache();
}
removeChild(component: Component): void {
const index = this.children.indexOf(component);
if (index !== -1) {