Duo workflow responses do not comply with the Duo-UI data contract
Problem to solve
The response from api/v4/ai/duo_workflows
does not match the format expected by Duo-UI components which seems to be the root cause behind the typebug GFM rendering is broken in messages (duo-ui#105 - closed).
The problem is that Duo-UI references several props on the message
object which aren't defined in the API response. One such example is message.chunks
which Duo-UI relies on to determine if a message is streaming or not.
The data
From the API
Response from api/v4/ai/duo_workflows
. Does not contain or mention chunks
or chunkId
/ chunk_id
.
Click to expand
{
requestID: "",
newCheckpoint: {
status: "INPUT_REQUIRED",
checkpoint: {
channel_values: {
ui_chat_log: [
{
status: "success",
content: "Hi",
timestamp: "2025-10-15T12:19:50.449731+00:00",
tool_info: null,
message_type: "user",
correlation_id: null,
message_sub_type: null,
additional_context: [
{
id: "",
content: {
id: 622,
iid: 3,
description: "Hello, World!\n\nIt's nice to meet you. First we should:\n- [ ] Meet and greet\n- [ ] Get to know each other\n- [ ] Say our goodbyes",
title: "hello.txt 2",
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: null,
human_total_time_spent: null,
state: "opened",
milestone_id: null,
updated_by_id: null,
created_at: "2025-09-25T11:38:37Z",
updated_at: "2025-10-03T13:04:37Z",
milestone: null,
labels: [],
lock_version: 0,
author_id: 1,
confidential: false,
discussion_locked: null,
assignees: [],
due_date: null,
project_id: 1000000,
moved_to_id: null,
duplicated_to_id: null,
web_url: "/gitlab-duo/test/-/issues/3",
current_user: {
can_create_note: true,
can_create_confidential_note: true,
can_update: true,
can_set_issue_metadata: true,
can_award_emoji: true
},
create_note_path: "/gitlab-duo/test/notes?target_id=622&target_type=issue",
preview_note_path: "/gitlab-duo/test/-/preview_markdown?target_id=3&target_type=Issue",
is_project_archived: false,
issue_email_participants: [],
type: "ISSUE",
weight: null,
issue_comments: []
},
category: "repository",
metadata: {},
type: "AdditionalContext"
}
]
},
{
status: "success",
content: "Hello! I can see you're working on the Test project and there's an open issue (#3) titled \"hello.txt 2\" with a friendly greeting and some checklist items.\n\nHow can I help you today? Are you looking to work on this issue, or is there something else you'd like to do with your project?",
timestamp: "2025-10-15T12:19:54.308217+00:00",
tool_info: null,
message_type: "agent",
correlation_id: null,
message_sub_type: null,
additional_context: null
},
{
message_type: "user",
message_sub_type: null,
content: "hi",
timestamp: "2025-10-15T12:21:05.662013+00:00",
status: "success",
correlation_id: null,
tool_info: null,
additional_context: [
{
category: "repository",
id: "",
content: {
id: 622,
iid: 3,
description: "Hello, World!\n\nIt's nice to meet you. First we should:\n- [ ] Meet and greet\n- [ ] Get to know each other\n- [ ] Say our goodbyes",
title: "hello.txt 2",
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: null,
human_total_time_spent: null,
state: "opened",
milestone_id: null,
updated_by_id: null,
created_at: "2025-09-25T11:38:37Z",
updated_at: "2025-10-03T13:04:37Z",
milestone: null,
labels: [],
lock_version: 0,
author_id: 1,
confidential: false,
discussion_locked: null,
assignees: [],
due_date: null,
project_id: 1000000,
moved_to_id: null,
duplicated_to_id: null,
web_url: "/gitlab-duo/test/-/issues/3",
current_user: {
can_create_note: true,
can_create_confidential_note: true,
can_update: true,
can_set_issue_metadata: true,
can_award_emoji: true
},
create_note_path: "/gitlab-duo/test/notes?target_id=622&target_type=issue",
preview_note_path: "/gitlab-duo/test/-/preview_markdown?target_id=3&target_type=Issue",
is_project_archived: false,
issue_email_participants: [],
type: "ISSUE",
weight: null,
issue_comments: []
},
metadata: {},
type: "AdditionalContext"
}
]
},
{
message_type: "agent",
message_sub_type: null,
content: "Hi there! 👋 \n\nWhat would you like to work on today? I can help you with coding, reviewing issues, analyzing your project, or anything else related to your GitLab project.",
timestamp: "2025-10-15T12:21:08.947510+00:00",
status: "success",
correlation_id: null,
tool_info: null,
additional_context: null
}
],
plan: {
steps: []
}
}
},
goal: "hi",
errors: []
}
}
Duo-UI messages
This is a sample of message
obj inside of the messages
array that's passed from the monolith to Duo-UI. These examples are of the same message taken at different time snapshots. None of the examples contain or mention chunks
or chunkId
/ chunk_id
.
Click to expand
{
"status": null,
"correlation_id": null,
"message_type": "agent",
"message_sub_type": null,
"timestamp": "2025-10-14T12:22:07.971538+00:00",
"content": "Here",
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
}
Still streaming…
{
"status": null,
"correlation_id": null,
"message_type": "agent",
"message_sub_type": null,
"timestamp": "2025-10-14T12:22:07.971538+00:00",
"content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql",
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
}
Finished streaming
{
"message_type": "agent",
"message_sub_type": null,
"content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql\ndisplay: list\nfields: title\nlimit: 25\nquery: state = closed\n```",
"timestamp": "2025-10-14T12:22:08.690550+00:00",
"status": "success",
"correlation_id": null,
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
}
Expected output
I don't have an real example of what a message
used to look like, but I expect it was something like this based on what Duo-UI attempts to reference.
{
"message_type": "agent",
"message_sub_type": null,
"content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql\ndisplay: list\nfields: title\nlimit: 25\nquery: state = closed\n```",
"timestamp": "2025-10-14T12:22:08.690550+00:00",
"status": "success",
"correlation_id": null,
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
// MISSING ATTRS
"chunks": [],
"chunk_id: "123"
}
Further details
I don't know what the exact cause is related to the API response being parsed wrongly in the monolith? I am not a domain expert and I have not yet fully investigated this.
Open questions
Are there any integration tests to verify the data contract between model responses and Duo-UI?
Steps to reproduce
To view the API response:
- In .com or GDK open up Duo chat and say
hi
- In your browser's network tab look for the socket response from
api/v4/ai/duo_workflows
. - View the binary message - you may need to convert to UTF-8 and pretty print it.
To view the messages in the monolith frontend.
- In .com or GDK open up Duo chat and say
hi
- Either use Vue dev tools to inspect the
messages
array OR paste this code to output the array to your browser console:
// Inside ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat.vue
// Within the watch: {} object
messages(msgs) {
console.log('messages: ', msgs);
},