Skip to content

convertMessages

convertMessages(messages, allowedToolNames?): BaseMessage<MessageStructure<MessageToolSet>, MessageType>[]

Defined in: _worktrees/docs-release/gaunt-sloth/packages/agent/src/modules/apiAgUiModule.ts:245

Convert a whole AG-UI history array to LangChain messages, applying TWO symmetric replay guards so a poisoned history can never abort every subsequent turn on the thread.

EXT-43 (forward, dangling-CALL): EXT-35’s per-message promotion is unconditional, which is correct for a call that WILL be executed this turn. But when replaying HISTORY, promoting a STALLED text call (one the client recorded but that never ran) yields an AIMessage with tool_calls and NO following tool_result — a shape a strict provider (Anthropic) 400s on, where the pre-EXT-35 plain text was valid. So promotion is allowed ONLY when the assistant message is immediately followed by a tool result message; a dangling call stays plain text (allowTextCallPromotion: false).

RC-18 (backward, orphan-RESULT): the mirror image. A replayed role:'tool' message whose matching tool_call id is absent from EVERY PRECEDING assistant message is an ORPHAN — converting it to a ToolMessage yields a tool result with no preceding AIMessage.tool_calls, which the same strict provider 400s on (Invalid parameter: messages with role 'tool' must be a response to a preceding message with 'tool_calls', INVALID_TOOL_RESULTS). Such orphans arise when a terminal (returnDirect) tool call’s result is reconstructed by the client without its parenting assistant tool_call. We DROP the orphan (match on tool_call_id, NOT adjacency; keep genuine pairs; do NOT fabricate a synthetic call — mirroring EXT-43’s demote-don’t-invent spirit). Ids are accumulated in iteration order, so a result whose matching call appears only LATER is still an orphan.

RC-32 (tool NAME restoration): the AG-UI wire message for a tool result carries toolCallId but no tool name, so a naively-converted ToolMessage has none — and every middleware that inspects results by name is blind to it. frontend-image-injection keys on msg.name === 'capture_image', so a captured photo reached the model on the resume turn (where the graph builds the ToolMessage itself, with a name) and then vanished from every later turn, leaving the model to answer questions about a picture it could no longer see. The name is recoverable from the parenting assistant tool_call, which this function already walks for the RC-18 guard, so it is resolved there and stamped back on.

The live middleware path (GthLangChainAgent, fixing the CURRENT turn) is unaffected — the two guards and the name restoration are history-replay only.

AgUiWireMessage[]

Set<string>

BaseMessage<MessageStructure<MessageToolSet>, MessageType>[]