fix: remove omitempty from required ChatMessagePart fields (#23250)

ChatMessagePart uses a flat struct with omitempty on all fields,
but some fields are required in their TypeScript variant (no ?
suffix in the variants struct tag). When Go omits a zero-valued
required field, the frontend receives undefined where it expects
a concrete value.

Remove omitempty from fields that are required in at least one
variant: Text, URL, MediaType, FileName, StartLine, EndLine,
Content. Fields where all variants use ? keep omitempty.

Add a sub-test to TestChatMessagePartVariantTags that enforces
this invariant via reflection so future additions cannot
reintroduce the mismatch.

Supersedes #23249
This commit is contained in:
Mathias Fredriksson
2026-03-18 18:43:50 +00:00
committed by GitHub
parent 1f0d896fc9
commit fb61c48227
4 changed files with 68 additions and 11 deletions
+10 -1
View File
@@ -1320,6 +1320,15 @@ export interface ChatMessage {
* name = required, ? suffix = optional. Fields without a variants
* tag are excluded from the generated union. See
* scripts/apitypings/main.go for the codegen that reads these.
*
* omitempty rules (enforced by TestChatMessagePartVariantTags):
* - If a field is required (no ? suffix) in ANY variant, it
* must NOT use omitempty. Go would silently drop zero values
* that TypeScript expects to always be present.
* - If a field is optional (? suffix) in ALL of its variants,
* it MUST use omitempty. Sending zero values for fields that
* the frontend does not expect adds noise to the wire format
* and wastes space in persisted chat_messages rows.
*/
export type ChatMessagePart =
| ChatTextPart
@@ -1682,7 +1691,7 @@ export interface ChatQueuedMessage {
// From codersdk/chats.go
export interface ChatReasoningPart {
readonly type: "reasoning";
readonly text?: string;
readonly text: string;
}
// From codersdk/chats.go
@@ -131,9 +131,8 @@ export const parseMessageContent = (
break;
}
case "reasoning": {
const text = part.text ?? "";
parsed.reasoning = appendText(parsed.reasoning, text);
parsed.blocks = appendTextBlock(parsed.blocks, "thinking", text);
parsed.reasoning = appendText(parsed.reasoning, part.text);
parsed.blocks = appendTextBlock(parsed.blocks, "thinking", part.text);
break;
}
case "tool-call": {