mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: type both chat message parsers (#23176)
Both message parsers accepted untyped input and relied on scattered asRecord/asString calls to extract fields at runtime. With the discriminated ChatMessagePart union, both accept typed input directly and narrow via switch (part.type). parseMessageContent narrows from (content: unknown) to (content: readonly ChatMessagePart[] | undefined), removing legacy input shape handling the Go backend normalizes away. applyMessagePartToStreamState narrows from Record<string, unknown> to ChatMessagePart. The SSE type guards had a & Record<string, unknown> intersection that widened everything untyped downstream. Since the data comes from our own API, the intersection was removed and all handlers in ChatContext now use generated types directly. Fixes tool_call_id and tool_name variant tags in codersdk/chats.go: marked optional to match reality (Go guards against empty values, omitempty omits them at the wire level). Refs #23168, #23175
This commit is contained in:
+2
-2
@@ -129,8 +129,8 @@ type ChatMessagePart struct {
|
||||
Type ChatMessagePartType `json:"type"`
|
||||
Text string `json:"text,omitempty" variants:"text,reasoning"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty" variants:"tool-call,tool-result"`
|
||||
ToolName string `json:"tool_name,omitempty" variants:"tool-call,tool-result"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty" variants:"tool-call?,tool-result?"`
|
||||
ToolName string `json:"tool_name,omitempty" variants:"tool-call?,tool-result?"`
|
||||
Args json.RawMessage `json:"args,omitempty" variants:"tool-call?"`
|
||||
ArgsDelta string `json:"args_delta,omitempty" variants:"tool-call?"`
|
||||
Result json.RawMessage `json:"result,omitempty" variants:"tool-result?"`
|
||||
|
||||
@@ -223,7 +223,6 @@ func TestChatMessagePartVariantTags(t *testing.T) {
|
||||
// Parse all variants tags from the struct and validate them.
|
||||
typ := reflect.TypeOf(codersdk.ChatMessagePart{})
|
||||
coveredTypes := make(map[codersdk.ChatMessagePartType]bool)
|
||||
hasRequired := make(map[codersdk.ChatMessagePartType]bool)
|
||||
|
||||
for i := range typ.NumField() {
|
||||
f := typ.Field(i)
|
||||
@@ -245,7 +244,6 @@ func TestChatMessagePartVariantTags(t *testing.T) {
|
||||
"the discriminant field must not have a variants tag; %s", editHint)
|
||||
|
||||
for _, entry := range strings.Split(varTag, ",") {
|
||||
isOptional := strings.HasSuffix(entry, "?")
|
||||
typeLit := codersdk.ChatMessagePartType(strings.TrimSuffix(entry, "?"))
|
||||
|
||||
assert.True(t, knownTypes[typeLit],
|
||||
@@ -253,9 +251,6 @@ func TestChatMessagePartVariantTags(t *testing.T) {
|
||||
f.Name, typeLit, editHint)
|
||||
|
||||
coveredTypes[typeLit] = true
|
||||
if !isOptional {
|
||||
hasRequired[typeLit] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,12 +259,6 @@ func TestChatMessagePartVariantTags(t *testing.T) {
|
||||
assert.True(t, coveredTypes[pt],
|
||||
"ChatMessagePartType %q is not referenced by any variants tag; %s", pt, editHint)
|
||||
}
|
||||
|
||||
// Every variant must have at least one required field.
|
||||
for pt := range coveredTypes {
|
||||
assert.True(t, hasRequired[pt],
|
||||
"variant %q has no required fields (all have ? suffix); %s", pt, editHint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelCostConfig_LegacyNumericJSON(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user