Symptom: the model calls a client-side tool, the UI shows the call (maybe a confirmation dialog), but no result ever appears and the conversation stalls.

Cause: with client tools, you own the result. The server cannot execute them, so if nothing calls addToolOutput, the round never completes.

Confirmation:
1. In the client, find the tool invocation parts. Is there an onToolCall handler, or a UI branch for the tool's approval state?
2. Check whether addToolOutput is called with the right toolCallId in every branch, including dismiss/cancel.
3. Check sendAutomaticallyWhen. Without it (or manual resubmission), completed tool results sit unsent.

Fix pattern:

const { messages, addToolOutput } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
});

// when your UI finishes the tool interaction:
addToolOutput({ tool: 'askForConfirmation', toolCallId, output: 'confirmed' });

Rules:
1. addToolOutput needs tool (the tool name), toolCallId, and output. A wrong toolCallId orphans the result.
2. For approval-style tools, both approve and deny paths must call addToolOutput (deny with a denial output). A deny button that just closes the dialog stalls the chat.
3. sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls (imported from ai) resubmits when every call has a result. Without it, call sendMessage again manually after results land.
4. Server-side tools with execute do not need any of this; they resolve in the stream. This diagnostic is client-tools only.

Verification: trigger the client tool, complete the interaction, and confirm the model writes a follow-up referencing the result.