mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd/chatd): unify chat storage on SDK parts and fix file-reference rendering (#22958)
File-reference parts in user messages were flattened to `TextContent` at write time because fantasy has no file-reference content type. The frontend never saw them as structured parts. This moves all write paths (user, assistant, tool) from fantasy envelope format to `codersdk.ChatMessagePart`. The streaming layer (`chatloop`) is untouched, the conversion happens at the serialization boundary in `persistStep`. Old rows are still readable. `ParseContent` uses a structural heuristic (`isFantasyEnvelopeFormat`) to distinguish legacy envelopes from SDK parts. We chose this over try/fallback because fantasy envelopes partially unmarshal into `ChatMessagePart` (the `type` field matches) while silently losing content. A guard test enforces that no SDK part can produce the envelope shape. This is forward-only: new rows are unreadable by old code. Chat is behind a feature flag so rollback risk is contained. Also adds a typed `ChatMessageRole` to replace raw strings and `fantasy.MessageRole*` casts at the persistence boundary. The type covers `ChatMessage.Role`, `ChatStreamMessagePart.Role`, the `PublishMessagePart` callback chain, and all DB write sites. `fantasy.MessageRole*` remains only where we build `fantasy.Message` structs for LLM dispatch. Separately, `ProviderMetadata` was leaking to SSE clients via `publishMessagePart`. `StripInternal` now runs on both the SSE and REST paths, covering this. Other cleanup: - Old `db2sdk.contentBlockToPart` silently dropped metadata on text/reasoning/tool-call content. New code preserves it. - `providerMetadataToOptions` now logs warnings instead of silently returning nil. - `db2sdk` shrinks from ~250 lines of parallel conversion to ~15 lines delegating to `chatprompt.ParseContent()`, removing the `fantasy` import entirely. Refs #22821
This commit is contained in:
Generated
+24
-3
@@ -1158,7 +1158,7 @@ export interface ChatMessage {
|
||||
readonly created_by?: string;
|
||||
readonly model_config_id?: string;
|
||||
readonly created_at: string;
|
||||
readonly role: string;
|
||||
readonly role: ChatMessageRole;
|
||||
readonly content?: readonly ChatMessagePart[];
|
||||
readonly usage?: ChatMessageUsage;
|
||||
}
|
||||
@@ -1166,6 +1166,13 @@ export interface ChatMessage {
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatMessagePart is a structured chunk of a chat message.
|
||||
*
|
||||
* WARNING: This type is both an API wire type and a database
|
||||
* persistence format. Its JSON layout is stored in the
|
||||
* chat_messages.content column. Field additions, renames, type
|
||||
* changes, and omitempty behavior all affect backward-compatible
|
||||
* deserialization of stored rows. Treat changes to this struct
|
||||
* with the same care as a database migration.
|
||||
*/
|
||||
export interface ChatMessagePart {
|
||||
readonly type: ChatMessagePartType;
|
||||
@@ -1178,7 +1185,6 @@ export interface ChatMessagePart {
|
||||
readonly result?: Record<string, string>;
|
||||
readonly result_delta?: string;
|
||||
readonly is_error?: boolean;
|
||||
readonly provider_executed?: boolean;
|
||||
readonly source_id?: string;
|
||||
readonly url?: string;
|
||||
readonly title?: string;
|
||||
@@ -1196,6 +1202,11 @@ export interface ChatMessagePart {
|
||||
* The code content from the diff that was commented on.
|
||||
*/
|
||||
readonly content?: string;
|
||||
/**
|
||||
* ProviderExecuted indicates the tool call was executed by
|
||||
* the provider (e.g. Anthropic computer use).
|
||||
*/
|
||||
readonly provider_executed?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
@@ -1218,6 +1229,16 @@ export const ChatMessagePartTypes: ChatMessagePartType[] = [
|
||||
"tool-result",
|
||||
];
|
||||
|
||||
// From codersdk/chats.go
|
||||
export type ChatMessageRole = "assistant" | "system" | "tool" | "user";
|
||||
|
||||
export const ChatMessageRoles: ChatMessageRole[] = [
|
||||
"assistant",
|
||||
"system",
|
||||
"tool",
|
||||
"user",
|
||||
];
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatMessageUsage contains token usage information for a chat message.
|
||||
@@ -1599,7 +1620,7 @@ export const ChatStreamEventTypes: ChatStreamEventType[] = [
|
||||
* ChatStreamMessagePart is a streamed message part update.
|
||||
*/
|
||||
export interface ChatStreamMessagePart {
|
||||
readonly role?: string;
|
||||
readonly role?: ChatMessageRole;
|
||||
readonly part: ChatMessagePart;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ const makeChat = (chatID: string): TypesGen.Chat => ({
|
||||
const makeMessage = (
|
||||
chatID: string,
|
||||
id: number,
|
||||
role: string,
|
||||
role: TypesGen.ChatMessageRole,
|
||||
text: string,
|
||||
): TypesGen.ChatMessage => ({
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user