needsApproval client tools can't be approved without a server (custom connection adapters)
Los mantenedores suelen responder en 1 día
Evaluación
Este issue todavía no se ha evaluado.
Descripción
TanStack AI version
@tanstack/ai 1.0.0, @tanstack/ai-client 1.0.0, @tanstack/ai-react 1.0.0 (same code on main @ caaa97b)
Framework/Library version
React 19.3.1, Vite 8
Describe the bug and the steps to reproduce it
The model runs entirely in the browser (Transformers.js in a Web Worker, or an on-device model behind a local endpoint). There is no server and no chat(). Each model is a custom ConnectConnectionAdapter passed to useChat({ connection, tools }). The adapter yields AG-UI events and ends a tool-calling run with RUN_FINISHED and outcome: { type: 'success', pendingToolCallIds }. useChat then runs the client tools and continues. That works.
A client tool with needsApproval: true never runs and never prompts:
import { toolDefinition, EventType } from '@tanstack/ai'
import { useChat, type ConnectConnectionAdapter } from '@tanstack/ai-react'
import { z } from 'zod'
const addCards = toolDefinition({
name: 'add_cards',
description: 'Add cards to the deck',
inputSchema: z.object({ names: z.array(z.string()) }),
outputSchema: z.object({ added: z.number() }),
needsApproval: true,
}).client(({ names }) => ({ added: names.length }))
const localModel: ConnectConnectionAdapter = {
async *connect(_messages, _data, _signal, { threadId, runId }) {
// ...the in-browser model decides to call add_cards
const toolCallId = 'call_1'
yield { type: EventType.RUN_STARTED, threadId, runId }
yield { type: EventType.TOOL_CALL_START, toolCallId, toolCallName: 'add_cards' }
yield { type: EventType.TOOL_CALL_ARGS, toolCallId, delta: '{"names":["Sol Ring"]}' }
yield { type: EventType.TOOL_CALL_END, toolCallId }
yield {
type: EventType.RUN_FINISHED,
threadId,
runId,
outcome: { type: 'success', pendingToolCallIds: [toolCallId] },
}
},
}
function Chat() {
const { interrupts } = useChat({ connection: localModel, tools: [addCards] })
// ...
}
Expected: a kind: 'tool-approval' item in useChat().interrupts, so the same Approve/Deny UI from the Tool Approval docs works, and resolveInterrupt(true) runs the .client() function.
Actual: interrupts stays empty, the tool never runs, and the console shows:
[ChatClient] Did not run tool call call_1: add_cards needs approval, and the server did not ask for it
This comes from onToolCall in chat-client (source):
if (
this.handingOverPendingToolCalls &&
clientTool?.needsApproval === true
) {
console.warn(`[ChatClient] Did not run tool call ${args.toolCallId}: ${args.toolName} needs approval, and the server did not ask for it`)
return
}
The Client Tools docs describe this as intended for AG-UI servers. The gap is that a tool-approval interrupt can only come from a server. The ChatClient only creates one from a RUN_FINISHED interrupt outcome. interrupt-manager binds it as tool-approval only when the binding's inputSchemaHash, approvalSchemaHash and responseSchemaHash match the local tool (source). After approval, the server side also emits a client-tool-execution interrupt (buildActionableInterrupts in activities/chat).
withInterruptBinding, hashSchemaInput and normalizeApprovalSchema are exported from @tanstack/ai/client. So a connection adapter could, in theory, rebuild that whole interrupt and resume protocol by hand. But no docs cover it, and it would mean copying chat() internals into each adapter. So in practice, needsApproval is unusable for client-only apps.
Suggested direction
Either of these would cover it:
- When a success
RUN_FINISHEDhands over a pending call to a client tool withneedsApproval: true, haveChatClientraise a localtool-approvalinterrupt for it, instead of dropping it. On approve, run the.client()function and continue as it does today. On deny, add adeniedtool result. - Or export a documented helper so a connection adapter can emit a valid approval interrupt for a client tool, and document the resume flow the adapter must handle.
Option 1 needs no changes to adapters, and it keeps the Approve/Deny UI the same as with a server.
Your Minimal, Reproducible Example
Code sketch above; no hosted sandbox.
- Lenguaje dominante
- TypeScript
- Estrellas
- 3.3k
- Forks
- 378
- Merge medio
- 1 d 2 h
- PR fusionados (30 d)
- 219
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Posiblemente ocupada @tombeckenham la tomó hace 4 días. Abiertohas-pr waiting-on: maintainer
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
TanStack/ai#1916 · 1 comentario · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
openai-base: leaving a Responses stream at `response.completed` aborts its request, so complete calls are logged as client-closed (499)Posiblemente ocupada @AlemTuzlak la tomó hace 1 día. Abiertohas-pr waiting-on: maintainer
TanStack/ai#1965 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
StreamProcessor folds long text and tool-call streams in quadratic memory and timePosiblemente ocupada @jherr la tomó hace 1 día. Abiertowaiting-on: maintainer
TanStack/ai#1963 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
ai-harness: view.dispose() doesn't cancel snapshot, transcript or describe requestsPosiblemente ocupada @AlemTuzlak la tomó hace 2 días. Abiertoneeds-repro waiting-on: maintainer
TanStack/ai#1960 · 2 comentarios · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
ai-anthropic: 400 on signed thinking blocks after compaction rewrites history (missing block_binding drop_block)Posiblemente ocupada @tombeckenham la tomó hace 2 días. Abiertowaiting-on: maintainer
TanStack/ai#1959 · 1 asignado ·
Los mantenedores suelen responder en 1 día
Todos los issues de TanStack/ai
Issues similares
-
needs-area
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Azure/typespec-azure#5936 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 Medio día Aptitud para principiantes 72/100
vercel-labs/scriptc#2910 ·
Los mantenedores suelen responder en 1 día
-
automation missing-model model-sync provider:fireworks-ai
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
anomalyco/models.dev#12458 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día