Replies: 2 comments
|
This will land together with the upcoming persistence PR. |
0 replies
|
This has landed and has first party support |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
I'm using
@tanstack/ai/@tanstack/ai-clienton the frontend, with a backend built on LangGraph / LangChain that speaks the AG-UI protocol.First off — I can see that TanStack AI already supports tool approvals via the CUSTOM
approval-requestedevent (needsApproval→approval-requested→addToolApprovalResponse), and that flow works well. My question is specifically about interop with the native AG-UI interrupt contract, which appears to be a different mechanism.What I'm trying to do
Support the AG-UI human-in-the-loop interrupt flow from the spec: https://docs.ag-ui.com/concepts/interrupts
The spec models HITL as an interrupt-aware run lifecycle:
RUN_FINISHEDcarrying an interrupt outcome:{ "type": "RUN_FINISHED", "threadId": "thread-1", "runId": "run-1", "outcome": { "type": "interrupt", "interrupts": [ { "id": "int-abc", "reason": "tool_call", "toolCallId": "tc-001", "message": "Send email to a@b.com?", "responseSchema": { "type": "object", "properties": { "approved": { "type": "boolean" } }, "required": ["approved"] } } ] } }RunAgentInputon the same thread with a top-levelresumearray:{ "threadId": "thread-1", "runId": "run-2", "resume": [ { "interruptId": "int-abc", "status": "resolved", "payload": { "approved": true } } ] .... }TOOL_CALL_RESULTagainst the originaltoolCallId(it does not re-emitTOOL_CALL_START/TOOL_CALL_ARGS/TOOL_CALL_END), thenRUN_FINISHEDwithoutcome: { type: "success" }.LangGraph natively supports this (it accepts
RunAgentInput.resume[]and can emitRunFinished.outcome = { type: "interrupt" }).What I found looking at the client internals
approval-requested) and stores the decision on the message part (state: "approval-responded",approval.approved) — butuiMessagesToWiredoesn't serializeapproval/approved. So on the resume run, a non-TanStack backend never receives the approve/deny decision.RUN_FINISHED.outcomeis ignored.handleRunFinishedEventonly readschunk.finishReason, so the client never surfacesoutcome.interrupt.resumefield.buildRunAgentInputBodyproduces a fixed shape (threadId,runId,state,messages,tools,context,forwardedProps,data), andforwardedProps/body/dataall land nested underforwardedProps— not at the request root where the spec putsresume.My understanding is that TanStack's approval flow is designed around the TanStack engine (which recovers the decision from the parts model, e.g.
extractClientStateFromOriginalMessages) rather than the AG-UI interrupt wire contract. That works great TanStack-engine ↔ TanStack-client, but doesn't interop with an AG-UI agent that expectsoutcome.interrupt+resume[].Questions
RUN_FINISHED.outcome.interruptinbound, top-levelresume[]outbound) supported today, or on the roadmap? Is the intended approach to keep usingapproval-requestedand have the backend adapt, or to speak the interrupt contract directly?outcome.interruptfrom the stream and building the request body myself to inject a top-levelresume[]? Is there a cleaner/supported hook I'm missing?addToolResult/addToolApprovalResponseauto-continuation?RUN_FINISHED.outcome(success / interrupt) first-class in the client state, so UIs can render interrupt prompts frommessage/responseSchema/reason?Any guidance on the recommended pattern for AG-UI interrupt interop would be much appreciated.
All reactions