fix(ag-ui): preserve feedback metadata in AG-UI streams#1074
fix(ag-ui): preserve feedback metadata in AG-UI streams#1074
Conversation
🦋 Changeset detectedLatest commit: 81c8de5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This comment has been minimized.
This comment has been minimized.
📝 WalkthroughWalkthroughAdds preservation of assistant feedback metadata across AG‑UI streams by mapping VoltAgent "message-metadata" stream parts to AG‑UI CUSTOM events and synthesizing legacy TOOL_CALL_RESULT events with a prefixed toolCallId; message-conversion skips internal metadata carrier tool calls. Changes
Sequence Diagram(s)sequenceDiagram
participant VoltAgent as VoltAgent Stream
participant Converter as convertVoltStreamPartToEvents
participant Emitter as Event Emitter
participant AGUI as AG-UI Client
VoltAgent->>Converter: "message-metadata" stream part
activate Converter
Converter->>Converter: parse metadata, build prefixed toolCallId
Converter->>Emitter: emit CustomEvent (voltagent.message_metadata) with messageId + metadata
Converter->>Emitter: emit legacy TOOL_CALL_RESULT (prefixed toolCallId + metadata)
deactivate Converter
Emitter->>AGUI: deliver events to client
AGUI->>AGUI: update UI state / attach metadata to message
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/ag-ui/src/voltagent-agent.ts (1)
336-354:⚠️ Potential issue | 🟡 MinorMetadata carrier filtering is incomplete in the assistant tool-call loop.
Lines 337–339 correctly skip
ToolMessageentries with metadata-carriertoolCallIdvalues. However, the loop processingAssistantMessage.toolCalls(lines 308–317) does not filter such entries. While the current event-to-message flow does not populate metadata carriers into assistanttoolCalls, a more defensive approach would filter them at this point to prevent issues if the message reconstruction pattern changes.Consider filtering metadata-carrier entries in the assistant tool-call loop:
Proposed fix
for (const call of msg.toolCalls ?? []) { + if (isMetadataCarrierToolCallId(call.id)) { + continue; + } const args = safelyParseJson(call.function.arguments);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ag-ui/src/voltagent-agent.ts` around lines 336 - 354, The assistant tool-call processing loop must also skip metadata-carrier entries: when iterating AssistantMessage.toolCalls, call isMetadataCarrierToolCallId(toolCallId) and continue/skip those entries (the same check used in the isToolMessage branch). Update the AssistantMessage.toolCalls handling (the loop that reads each toolCall, uses toolCallId and toolNameById) to perform this guard before converting/pushing the tool-call into convertedMessages so metadata-carrier toolCallIds are not included.
🧹 Nitpick comments (1)
packages/ag-ui/src/voltagent-agent.ts (1)
412-423: Null metadata silently falls through to thedefaultcase.When
partType === "message-metadata"butmessageMetadatais falsy (line 421–423),nullis returned. Then execution would not reach the switch (line 438) since line 422 returns. This is correct—no bug here. However, a debug log when metadata is unexpectedly missing could help with troubleshooting in production.Proposed enhancement
if (!messageMetadata || typeof messageMetadata !== "object") { + debugLog("message-metadata part has no valid metadata object", { messageId }); return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ag-ui/src/voltagent-agent.ts` around lines 412 - 423, When partType === "message-metadata" and messageMetadata is falsy, add a debug log before returning null to aid troubleshooting: log the messageId (computed from messageId or fallbackMessageId), partType and any relevant part/payload identifiers; use the module's existing logger (e.g., processLogger or logger) or console.debug if none exists, then return null as before. This change touches the block that computes messageId and messageMetadata (symbols: partType, messageId, messageMetadata, fallbackMessageId).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/ag-ui/src/voltagent-agent.ts`:
- Around line 336-354: The assistant tool-call processing loop must also skip
metadata-carrier entries: when iterating AssistantMessage.toolCalls, call
isMetadataCarrierToolCallId(toolCallId) and continue/skip those entries (the
same check used in the isToolMessage branch). Update the
AssistantMessage.toolCalls handling (the loop that reads each toolCall, uses
toolCallId and toolNameById) to perform this guard before converting/pushing the
tool-call into convertedMessages so metadata-carrier toolCallIds are not
included.
---
Nitpick comments:
In `@packages/ag-ui/src/voltagent-agent.ts`:
- Around line 412-423: When partType === "message-metadata" and messageMetadata
is falsy, add a debug log before returning null to aid troubleshooting: log the
messageId (computed from messageId or fallbackMessageId), partType and any
relevant part/payload identifiers; use the module's existing logger (e.g.,
processLogger or logger) or console.debug if none exists, then return null as
before. This change touches the block that computes messageId and
messageMetadata (symbols: partType, messageId, messageMetadata,
fallbackMessageId).
Deploying voltagent with
|
| Latest commit: |
81c8de5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://45157141.voltagent.pages.dev |
| Branch Preview URL: | https://fix-ag-ui-feedback-metadata.voltagent.pages.dev |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/ag-ui/src/voltagent-agent.ts (1)
439-448: The backward-compatToolCallResultEventusesgenerateId()formessageId— consider using the actualmessageIdinstead.Line 446 assigns a fresh random ID as the
messageIdof the synthesizedToolCallResultEvent, while the actual message'smessageIdis already available and embedded in thetoolCallIdprefix and thecontentpayload. Clients trying to correlate this event back to the originating assistant message won't be able to do so viamessageId.Was this intentional to avoid ID collisions, or should it mirror the source message's ID for traceability?
Suggested change for consideration
const resultEvent: ToolCallResultEvent = { type: EventType.TOOL_CALL_RESULT, toolCallId: `${VOLTAGENT_METADATA_TOOL_CALL_ID_PREFIX}${messageId || generateId()}`, content: safeStringify({ messageId: messageId || undefined, metadata: messageMetadata, }), - messageId: generateId(), + messageId: messageId || generateId(), role: "tool", };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ag-ui/src/voltagent-agent.ts` around lines 439 - 448, The ToolCallResultEvent currently always sets messageId to generateId(), which breaks traceability to the original assistant message; update the construction in voltagent-agent.ts to reuse the originating messageId when present (e.g., compute a single id like const correlatedId = messageId || generateId() and use correlatedId for both toolCallId suffix and the event's messageId) so ToolCallResultEvent (type EventType.TOOL_CALL_RESULT) and its content/metadata consistently reference the same message identifier (referencing VOLTAGENT_METADATA_TOOL_CALL_ID_PREFIX, messageId, generateId(), and ToolCallResultEvent).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/ag-ui/src/voltagent-agent.ts`:
- Around line 439-448: The ToolCallResultEvent currently always sets messageId
to generateId(), which breaks traceability to the original assistant message;
update the construction in voltagent-agent.ts to reuse the originating messageId
when present (e.g., compute a single id like const correlatedId = messageId ||
generateId() and use correlatedId for both toolCallId suffix and the event's
messageId) so ToolCallResultEvent (type EventType.TOOL_CALL_RESULT) and its
content/metadata consistently reference the same message identifier (referencing
VOLTAGENT_METADATA_TOOL_CALL_ID_PREFIX, messageId, generateId(), and
ToolCallResultEvent).
PR Checklist
Please check if your PR fulfills the following requirements:
Bugs / Features
What is the current behavior?
Agent.streamText(...).toUIMessageStream()can emitmessage-metadatachunks (for example feedback metadata), but the AG-UI adapter drops unknown chunk types.As a result, chat clients do not reliably receive assistant feedback metadata.
What is the new behavior?
@voltagent/ag-uinow mapsmessage-metadatachunks to AG-UICUSTOMevents (voltagent.message_metadata), which is the protocol-native channel for app-specific metadata.fixes (issue)
N/A
Notes for reviewers
packages/ag-ui/src/voltagent-agent.ts.pnpm -C packages/ag-ui exec tsc --noEmit.Summary by CodeRabbit