Skip to content

refactor: type both chat message parsers#23176

Merged
mafredri merged 1 commit into
mainfrom
refactor/chat-parser-typing
Mar 18, 2026
Merged

refactor: type both chat message parsers#23176
mafredri merged 1 commit into
mainfrom
refactor/chat-parser-typing

Conversation

@mafredri
Copy link
Copy Markdown
Member

@mafredri mafredri commented Mar 17, 2026

PR 3 of 3 in a series re-architecting ChatMessagePart from a flat interface into a discriminated union.

Both message parsers previously accepted untyped input (unknown / Record<string, unknown>) and used scattered asRecord/asString calls to extract fields at runtime. With the discriminated union from PR 1, both accept typed ChatMessagePart directly and narrow via switch (part.type).

The historical parser (parseMessageContent) signature narrows from (content: unknown) to (content: readonly ChatMessagePart[] | undefined), removing legacy input shape handling the Go backend normalizes away. The streaming parser (applyMessagePartToStreamState) narrows from Record<string, unknown> to ChatMessagePart.

The SSE event type guards had a & Record<string, unknown> intersection that widened everything untyped downstream. Since the SSE data comes from our own API serializing the same Go structs the types are generated from, the intersection was removed. All handlers in ChatContext.ts now use the generated types directly, removing asRecord/asString from the file entirely.

Fixes tool_call_id and tool_name variant tags in codersdk/chats.go: they were marked required but Go code guards against them being empty in multiple places (persistInterruptedStep, ConvertMessagesWithFiles, etc.) and omitempty omits them at the wire level. Changed to optional so the generated TypeScript matches reality. Relaxed the test that required every variant to have at least one required field.

Both parser switches use satisfies never exhaustiveness checks so the compiler catches missing cases when new part types are added.

Refs #23168, #23175

🤖 This PR was created with the help of AI, and has been reviewed by a human. 🏂🏻

@mafredri mafredri force-pushed the refactor/chat-parser-typing branch from ac518b6 to 8c96ab4 Compare March 17, 2026 17:52
@mafredri mafredri force-pushed the refactor/chat-renderblock-cleanup branch 3 times, most recently from f88b532 to 4e9c9ae Compare March 18, 2026 10:24
@mafredri mafredri force-pushed the refactor/chat-parser-typing branch 4 times, most recently from e2835a1 to c4e0f14 Compare March 18, 2026 11:26
@mafredri mafredri changed the title refactor(site/src/pages/AgentsPage/AgentDetail): type both chat message parsers refactor(site): type both chat message parsers Mar 18, 2026
@mafredri mafredri force-pushed the refactor/chat-parser-typing branch from c4e0f14 to 4077eb8 Compare March 18, 2026 11:49
@mafredri mafredri changed the title refactor(site): type both chat message parsers refactor: type both chat message parsers Mar 18, 2026
@mafredri mafredri force-pushed the refactor/chat-parser-typing branch from 4077eb8 to cff66c6 Compare March 18, 2026 12:10
@mafredri mafredri force-pushed the refactor/chat-renderblock-cleanup branch from 4e9c9ae to 77fe83d Compare March 18, 2026 12:16
Base automatically changed from refactor/chat-renderblock-cleanup to main March 18, 2026 12:25
Both parsers (historical REST and streaming SSE) previously accepted
untyped input and relied on scattered asRecord/asString calls to
extract fields at runtime. With the discriminated ChatMessagePart union
from PR 1, the parsers can accept typed input directly.

Historical parser (parseMessageContent):
- Signature changes from (content: unknown) to
  (content: readonly ChatMessagePart[] | undefined)
- Removes legacy input shape handling (plain string, null, bare object,
  non-object array entries) that the Go backend normalizes away
- switch narrows on part.type automatically, no asRecord/asString needed

Streaming parser (applyMessagePartToStreamState):
- Signature changes from (part: Record<string, unknown>) to
  (part: ChatMessagePart)
- SSE type guards no longer widen with & Record<string, unknown>,
  so streamEvent.message_part?.part is typed directly

ChatContext.ts:
- Removes asRecord/asString import entirely
- All stream event handlers use generated types directly

codersdk/chats.go:
- Marks tool_call_id and tool_name as optional in variant tags
  (Go code guards against empty values, omitempty omits them)
- Relaxes test requiring every variant to have a required field
@mafredri mafredri force-pushed the refactor/chat-parser-typing branch from cff66c6 to 719c40f Compare March 18, 2026 12:27
@mafredri mafredri marked this pull request as ready for review March 18, 2026 12:27
@mafredri mafredri requested review from DanielleMaywood and kylecarbs and removed request for kylecarbs March 18, 2026 12:30
@mafredri
Copy link
Copy Markdown
Member Author

Ok looks like the Streamed Reasoning changed, need to look into that.

Copy link
Copy Markdown
Contributor

@DanielleMaywood DanielleMaywood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes me happy

@mafredri mafredri merged commit 8b4d357 into main Mar 18, 2026
32 checks passed
@mafredri mafredri deleted the refactor/chat-parser-typing branch March 18, 2026 13:51
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants