diff --git a/coderd/chatd/chatd.go b/coderd/chatd/chatd.go index 94650b51e6..d8f9b8c755 100644 --- a/coderd/chatd/chatd.go +++ b/coderd/chatd/chatd.go @@ -487,33 +487,30 @@ func (p *Server) CreateChat(ctx context.Context, opts CreateOptions) (database.C if err != nil { return xerrors.Errorf("marshal system prompt: %w", err) } - appendChatMessage(&msgParams, chatMessage{ - Role: database.ChatMessageRoleSystem, - Content: systemContent, - Visibility: database.ChatMessageVisibilityModel, - ModelConfigID: opts.ModelConfigID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - }) + appendChatMessage(&msgParams, newChatMessage( + database.ChatMessageRoleSystem, + systemContent, + database.ChatMessageVisibilityModel, + opts.ModelConfigID, + chatprompt.CurrentContentVersion, + )) } - appendChatMessage(&msgParams, chatMessage{ - Role: database.ChatMessageRoleSystem, - Content: workspaceAwarenessContent, - Visibility: database.ChatMessageVisibilityModel, - ModelConfigID: opts.ModelConfigID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - }) + appendChatMessage(&msgParams, newChatMessage( + database.ChatMessageRoleSystem, + workspaceAwarenessContent, + database.ChatMessageVisibilityModel, + opts.ModelConfigID, + chatprompt.CurrentContentVersion, + )) - appendChatMessage(&msgParams, chatMessage{ - Role: database.ChatMessageRoleUser, - Content: userContent, - Visibility: database.ChatMessageVisibilityBoth, - ModelConfigID: opts.ModelConfigID, - CreatedBy: opts.OwnerID, - ContentVersion: chatprompt.CurrentContentVersion, - }) + appendChatMessage(&msgParams, newChatMessage( + database.ChatMessageRoleUser, + userContent, + database.ChatMessageVisibilityBoth, + opts.ModelConfigID, + chatprompt.CurrentContentVersion, + ).withCreatedBy(opts.OwnerID)) _, err = tx.InsertChatMessages(ctx, msgParams) if err != nil { @@ -1109,27 +1106,82 @@ func insertChatMessageWithStore( } // chatMessage describes a single message to insert as part of a batch. -// For nullable UUID fields (ModelConfigID, CreatedBy), use uuid.Nil to -// represent NULL — the SQL uses NULLIF to convert zero UUIDs to NULL. -// For nullable int64 fields, use 0 to represent NULL — the SQL uses -// NULLIF to convert zeros to NULL. +// Use newChatMessage to create one, then chain builder methods for +// optional fields. For nullable UUID fields (ModelConfigID, CreatedBy), +// use uuid.Nil to represent NULL — the SQL uses NULLIF to convert zero +// UUIDs to NULL. For nullable int64 fields, use 0 to represent NULL — +// the SQL uses NULLIF to convert zeros to NULL. type chatMessage struct { - Role database.ChatMessageRole - Content pqtype.NullRawMessage - Visibility database.ChatMessageVisibility - ModelConfigID uuid.UUID - CreatedBy uuid.UUID - ContentVersion int16 - Compressed bool - InputTokens int64 - OutputTokens int64 - TotalTokens int64 - ReasoningTokens int64 - CacheCreationTokens int64 - CacheReadTokens int64 - ContextLimit int64 - TotalCostMicros int64 - RuntimeMs int64 + role database.ChatMessageRole + content pqtype.NullRawMessage + visibility database.ChatMessageVisibility + modelConfigID uuid.UUID + createdBy uuid.UUID + contentVersion int16 + compressed bool + inputTokens int64 + outputTokens int64 + totalTokens int64 + reasoningTokens int64 + cacheCreationTokens int64 + cacheReadTokens int64 + contextLimit int64 + totalCostMicros int64 + runtimeMs int64 +} + +func newChatMessage( + role database.ChatMessageRole, + content pqtype.NullRawMessage, + visibility database.ChatMessageVisibility, + modelConfigID uuid.UUID, + contentVersion int16, +) chatMessage { + return chatMessage{ + role: role, + content: content, + visibility: visibility, + modelConfigID: modelConfigID, + contentVersion: contentVersion, + } +} + +func (m chatMessage) withCreatedBy(id uuid.UUID) chatMessage { + m.createdBy = id + return m +} + +func (m chatMessage) withCompressed() chatMessage { + m.compressed = true + return m +} + +func (m chatMessage) withUsage( + inputTokens, outputTokens, totalTokens, reasoningTokens, + cacheCreationTokens, cacheReadTokens int64, +) chatMessage { + m.inputTokens = inputTokens + m.outputTokens = outputTokens + m.totalTokens = totalTokens + m.reasoningTokens = reasoningTokens + m.cacheCreationTokens = cacheCreationTokens + m.cacheReadTokens = cacheReadTokens + return m +} + +func (m chatMessage) withContextLimit(limit int64) chatMessage { + m.contextLimit = limit + return m +} + +func (m chatMessage) withTotalCostMicros(cost int64) chatMessage { + m.totalCostMicros = cost + return m +} + +func (m chatMessage) withRuntimeMs(ms int64) chatMessage { + m.runtimeMs = ms + return m } // appendChatMessage appends a single message to the batch insert params. @@ -1137,22 +1189,22 @@ func appendChatMessage( params *database.InsertChatMessagesParams, msg chatMessage, ) { - params.CreatedBy = append(params.CreatedBy, msg.CreatedBy) - params.ModelConfigID = append(params.ModelConfigID, msg.ModelConfigID) - params.Role = append(params.Role, msg.Role) - params.Content = append(params.Content, string(msg.Content.RawMessage)) - params.ContentVersion = append(params.ContentVersion, msg.ContentVersion) - params.Visibility = append(params.Visibility, msg.Visibility) - params.InputTokens = append(params.InputTokens, msg.InputTokens) - params.OutputTokens = append(params.OutputTokens, msg.OutputTokens) - params.TotalTokens = append(params.TotalTokens, msg.TotalTokens) - params.ReasoningTokens = append(params.ReasoningTokens, msg.ReasoningTokens) - params.CacheCreationTokens = append(params.CacheCreationTokens, msg.CacheCreationTokens) - params.CacheReadTokens = append(params.CacheReadTokens, msg.CacheReadTokens) - params.ContextLimit = append(params.ContextLimit, msg.ContextLimit) - params.Compressed = append(params.Compressed, msg.Compressed) - params.TotalCostMicros = append(params.TotalCostMicros, msg.TotalCostMicros) - params.RuntimeMs = append(params.RuntimeMs, msg.RuntimeMs) + params.CreatedBy = append(params.CreatedBy, msg.createdBy) + params.ModelConfigID = append(params.ModelConfigID, msg.modelConfigID) + params.Role = append(params.Role, msg.role) + params.Content = append(params.Content, string(msg.content.RawMessage)) + params.ContentVersion = append(params.ContentVersion, msg.contentVersion) + params.Visibility = append(params.Visibility, msg.visibility) + params.InputTokens = append(params.InputTokens, msg.inputTokens) + params.OutputTokens = append(params.OutputTokens, msg.outputTokens) + params.TotalTokens = append(params.TotalTokens, msg.totalTokens) + params.ReasoningTokens = append(params.ReasoningTokens, msg.reasoningTokens) + params.CacheCreationTokens = append(params.CacheCreationTokens, msg.cacheCreationTokens) + params.CacheReadTokens = append(params.CacheReadTokens, msg.cacheReadTokens) + params.ContextLimit = append(params.ContextLimit, msg.contextLimit) + params.Compressed = append(params.Compressed, msg.compressed) + params.TotalCostMicros = append(params.TotalCostMicros, msg.totalCostMicros) + params.RuntimeMs = append(params.RuntimeMs, msg.runtimeMs) } func insertUserMessageAndSetPending( @@ -1166,14 +1218,13 @@ func insertUserMessageAndSetPending( msgParams := database.InsertChatMessagesParams{ //nolint:exhaustruct // Fields populated by appendChatMessage. ChatID: lockedChat.ID, } - appendChatMessage(&msgParams, chatMessage{ - Role: database.ChatMessageRoleUser, - Content: content, - Visibility: database.ChatMessageVisibilityBoth, - ModelConfigID: modelConfigID, - CreatedBy: createdBy, - ContentVersion: chatprompt.CurrentContentVersion, - }) + appendChatMessage(&msgParams, newChatMessage( + database.ChatMessageRoleUser, + content, + database.ChatMessageVisibilityBoth, + modelConfigID, + chatprompt.CurrentContentVersion, + ).withCreatedBy(createdBy)) messages, err := insertChatMessageWithStore(ctx, store, msgParams) if err != nil { return database.ChatMessage{}, database.Chat{}, err @@ -2135,17 +2186,16 @@ func (p *Server) tryAutoPromoteQueuedMessage( msgParams := database.InsertChatMessagesParams{ //nolint:exhaustruct // Fields populated by appendChatMessage. ChatID: chat.ID, } - appendChatMessage(&msgParams, chatMessage{ - Role: database.ChatMessageRoleUser, - Content: pqtype.NullRawMessage{ + appendChatMessage(&msgParams, newChatMessage( + database.ChatMessageRoleUser, + pqtype.NullRawMessage{ RawMessage: nextQueued.Content, Valid: len(nextQueued.Content) > 0, }, - Visibility: database.ChatMessageVisibilityBoth, - ModelConfigID: chat.LastModelConfigID, - CreatedBy: chat.OwnerID, - ContentVersion: chatprompt.CurrentContentVersion, - }) + database.ChatMessageVisibilityBoth, + chat.LastModelConfigID, + chatprompt.CurrentContentVersion, + ).withCreatedBy(chat.OwnerID)) msgs, err := insertChatMessageWithStore(ctx, tx, msgParams) if err != nil { logger.Error(ctx, "failed to promote queued message", @@ -2715,34 +2765,28 @@ func (p *Server) runChat( } if assistantContent.Valid { - appendChatMessage(&stepParams, chatMessage{ - Role: database.ChatMessageRoleAssistant, - Content: assistantContent, - Visibility: database.ChatMessageVisibilityBoth, - ModelConfigID: modelConfig.ID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - InputTokens: inputTokens, - OutputTokens: outputTokens, - TotalTokens: totalTokens, - ReasoningTokens: reasoningTokens, - CacheCreationTokens: cacheCreationTokens, - CacheReadTokens: cacheReadTokens, - ContextLimit: contextLimit, - TotalCostMicros: totalCostVal, - RuntimeMs: runtimeMs, - }) + appendChatMessage(&stepParams, newChatMessage( + database.ChatMessageRoleAssistant, + assistantContent, + database.ChatMessageVisibilityBoth, + modelConfig.ID, + chatprompt.CurrentContentVersion, + ).withUsage( + inputTokens, outputTokens, totalTokens, + reasoningTokens, cacheCreationTokens, cacheReadTokens, + ).withContextLimit(contextLimit). + withTotalCostMicros(totalCostVal). + withRuntimeMs(runtimeMs)) } for _, resultContent := range toolResultContents { - appendChatMessage(&stepParams, chatMessage{ - Role: database.ChatMessageRoleTool, - Content: resultContent, - Visibility: database.ChatMessageVisibilityBoth, - ModelConfigID: modelConfig.ID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - }) + appendChatMessage(&stepParams, newChatMessage( + database.ChatMessageRoleTool, + resultContent, + database.ChatMessageVisibilityBoth, + modelConfig.ID, + chatprompt.CurrentContentVersion, + )) } if len(stepParams.Role) > 0 { @@ -3117,37 +3161,31 @@ func (p *Server) persistChatContextSummary( } // Hidden summary user message (not published to subscribers). - appendChatMessage(&summaryParams, chatMessage{ - Role: database.ChatMessageRoleUser, - Content: systemContent, - Visibility: database.ChatMessageVisibilityModel, - ModelConfigID: modelConfigID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - Compressed: true, - }) + appendChatMessage(&summaryParams, newChatMessage( + database.ChatMessageRoleUser, + systemContent, + database.ChatMessageVisibilityModel, + modelConfigID, + chatprompt.CurrentContentVersion, + ).withCompressed()) // Assistant tool-call message. - appendChatMessage(&summaryParams, chatMessage{ - Role: database.ChatMessageRoleAssistant, - Content: assistantContent, - Visibility: database.ChatMessageVisibilityUser, - ModelConfigID: modelConfigID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - Compressed: true, - }) + appendChatMessage(&summaryParams, newChatMessage( + database.ChatMessageRoleAssistant, + assistantContent, + database.ChatMessageVisibilityUser, + modelConfigID, + chatprompt.CurrentContentVersion, + ).withCompressed()) // Tool result message. - appendChatMessage(&summaryParams, chatMessage{ - Role: database.ChatMessageRoleTool, - Content: toolResult, - Visibility: database.ChatMessageVisibilityBoth, - ModelConfigID: modelConfigID, - CreatedBy: uuid.Nil, - ContentVersion: chatprompt.CurrentContentVersion, - Compressed: true, - }) + appendChatMessage(&summaryParams, newChatMessage( + database.ChatMessageRoleTool, + toolResult, + database.ChatMessageVisibilityBoth, + modelConfigID, + chatprompt.CurrentContentVersion, + ).withCompressed()) allInserted, txErr := tx.InsertChatMessages(ctx, summaryParams) if txErr != nil { diff --git a/coderd/chats_test.go b/coderd/chats_test.go index 221a2ed03e..7148c43682 100644 --- a/coderd/chats_test.go +++ b/coderd/chats_test.go @@ -4059,35 +4059,35 @@ func seedChatCostFixture(t *testing.T) chatCostTestFixture { }) require.NoError(t, err) - var earliestCreatedAt time.Time - var latestCreatedAt time.Time - for i := 0; i < 2; i++ { - results, err := db.InsertChatMessages(dbauthz.AsSystemRestricted(ctx), database.InsertChatMessagesParams{ - ChatID: chat.ID, - CreatedBy: []uuid.UUID{uuid.Nil}, - ModelConfigID: []uuid.UUID{modelConfig.ID}, - Role: []database.ChatMessageRole{"assistant"}, - Content: []string{"null"}, - ContentVersion: []int16{0}, - Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth}, - InputTokens: []int64{100}, - OutputTokens: []int64{50}, - TotalTokens: []int64{0}, - ReasoningTokens: []int64{0}, - CacheCreationTokens: []int64{0}, - CacheReadTokens: []int64{0}, - ContextLimit: []int64{0}, - Compressed: []bool{false}, - TotalCostMicros: []int64{500}, - RuntimeMs: []int64{0}, - }) - require.NoError(t, err) - message := results[0] - if i == 0 || message.CreatedAt.Before(earliestCreatedAt) { - earliestCreatedAt = message.CreatedAt + results, err := db.InsertChatMessages(dbauthz.AsSystemRestricted(ctx), database.InsertChatMessagesParams{ + ChatID: chat.ID, + CreatedBy: []uuid.UUID{uuid.Nil, uuid.Nil}, + ModelConfigID: []uuid.UUID{modelConfig.ID, modelConfig.ID}, + Role: []database.ChatMessageRole{"assistant", "assistant"}, + Content: []string{"null", "null"}, + ContentVersion: []int16{0, 0}, + Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth}, + InputTokens: []int64{100, 100}, + OutputTokens: []int64{50, 50}, + TotalTokens: []int64{0, 0}, + ReasoningTokens: []int64{0, 0}, + CacheCreationTokens: []int64{0, 0}, + CacheReadTokens: []int64{0, 0}, + ContextLimit: []int64{0, 0}, + Compressed: []bool{false, false}, + TotalCostMicros: []int64{500, 500}, + RuntimeMs: []int64{0, 0}, + }) + require.NoError(t, err) + require.Len(t, results, 2) + earliestCreatedAt := results[0].CreatedAt + latestCreatedAt := results[0].CreatedAt + for _, msg := range results { + if msg.CreatedAt.Before(earliestCreatedAt) { + earliestCreatedAt = msg.CreatedAt } - if i == 0 || message.CreatedAt.After(latestCreatedAt) { - latestCreatedAt = message.CreatedAt + if msg.CreatedAt.After(latestCreatedAt) { + latestCreatedAt = msg.CreatedAt } } diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 5dd774e4cf..af843c7fde 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -9534,6 +9534,68 @@ func TestInsertChatMessages(t *testing.T) { require.NoError(t, err) require.Equal(t, modelConfigA.ID, gotChat.LastModelConfigID) }) + + t.Run("BatchInsertMultipleMessages", func(t *testing.T) { + t.Parallel() + + store, ctx, user, chat, _, modelConfigA := setupChat(t) + + msgs, err := store.InsertChatMessages(ctx, database.InsertChatMessagesParams{ + ChatID: chat.ID, + CreatedBy: []uuid.UUID{user.ID, uuid.Nil, uuid.Nil}, + ModelConfigID: []uuid.UUID{modelConfigA.ID, modelConfigA.ID, modelConfigA.ID}, + Role: []database.ChatMessageRole{database.ChatMessageRoleUser, database.ChatMessageRoleAssistant, database.ChatMessageRoleTool}, + ContentVersion: []int16{chatprompt.CurrentContentVersion, chatprompt.CurrentContentVersion, chatprompt.CurrentContentVersion}, + Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth}, + Content: []string{`"hello"`, `"response"`, `"tool result"`}, + InputTokens: []int64{10, 0, 0}, + OutputTokens: []int64{0, 20, 0}, + TotalTokens: []int64{10, 20, 0}, + ReasoningTokens: []int64{0, 5, 0}, + CacheCreationTokens: []int64{0, 0, 0}, + CacheReadTokens: []int64{0, 0, 0}, + ContextLimit: []int64{0, 0, 0}, + Compressed: []bool{false, false, false}, + TotalCostMicros: []int64{0, 100, 0}, + RuntimeMs: []int64{0, 500, 0}, + }) + require.NoError(t, err) + require.Len(t, msgs, 3) + + // Verify ordering and roles. + require.Equal(t, database.ChatMessageRoleUser, msgs[0].Role) + require.Equal(t, database.ChatMessageRoleAssistant, msgs[1].Role) + require.Equal(t, database.ChatMessageRoleTool, msgs[2].Role) + + // Verify IDs are sequential. + require.Less(t, msgs[0].ID, msgs[1].ID) + require.Less(t, msgs[1].ID, msgs[2].ID) + + // Verify nullable fields: user message has CreatedBy set. + require.True(t, msgs[0].CreatedBy.Valid) + require.Equal(t, user.ID, msgs[0].CreatedBy.UUID) + // Assistant and tool messages have NULL CreatedBy. + require.False(t, msgs[1].CreatedBy.Valid) + require.False(t, msgs[2].CreatedBy.Valid) + + // Verify token fields stored as NULL when zero. + require.True(t, msgs[0].InputTokens.Valid) + require.Equal(t, int64(10), msgs[0].InputTokens.Int64) + require.False(t, msgs[0].OutputTokens.Valid) // 0 → NULL + require.True(t, msgs[1].OutputTokens.Valid) + require.Equal(t, int64(20), msgs[1].OutputTokens.Int64) + + // Verify cost: assistant has cost, others NULL. + require.True(t, msgs[1].TotalCostMicros.Valid) + require.Equal(t, int64(100), msgs[1].TotalCostMicros.Int64) + require.False(t, msgs[0].TotalCostMicros.Valid) + require.False(t, msgs[2].TotalCostMicros.Valid) + + // Verify runtime_ms on assistant message. + require.True(t, msgs[1].RuntimeMs.Valid) + require.Equal(t, int64(500), msgs[1].RuntimeMs.Int64) + require.False(t, msgs[0].RuntimeMs.Valid) + }) } func TestGetChatMessagesForPromptByChatID(t *testing.T) { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index bf50d312b7..8aba6e9cb8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4848,7 +4848,7 @@ WITH updated_chat AS ( SET last_model_config_id = ( SELECT val - FROM unnest($3::uuid[]) + FROM UNNEST($3::uuid[]) WITH ORDINALITY AS t(val, ord) WHERE val != '00000000-0000-0000-0000-000000000000'::uuid ORDER BY ord DESC @@ -4858,12 +4858,12 @@ WITH updated_chat AS ( id = $1::uuid AND EXISTS ( SELECT 1 - FROM unnest($3::uuid[]) + FROM UNNEST($3::uuid[]) WHERE unnest != '00000000-0000-0000-0000-000000000000'::uuid ) AND chats.last_model_config_id IS DISTINCT FROM ( SELECT val - FROM unnest($3::uuid[]) + FROM UNNEST($3::uuid[]) WITH ORDINALITY AS t(val, ord) WHERE val != '00000000-0000-0000-0000-000000000000'::uuid ORDER BY ord DESC @@ -4891,22 +4891,22 @@ INSERT INTO chat_messages ( ) SELECT $1::uuid, - NULLIF(unnest($2::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), - NULLIF(unnest($3::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), - unnest($4::chat_message_role[]), - unnest($5::text[])::jsonb, - unnest($6::smallint[]), - unnest($7::chat_message_visibility[]), - NULLIF(unnest($8::bigint[]), 0), - NULLIF(unnest($9::bigint[]), 0), - NULLIF(unnest($10::bigint[]), 0), - NULLIF(unnest($11::bigint[]), 0), - NULLIF(unnest($12::bigint[]), 0), - NULLIF(unnest($13::bigint[]), 0), - NULLIF(unnest($14::bigint[]), 0), - unnest($15::boolean[]), - NULLIF(unnest($16::bigint[]), 0), - NULLIF(unnest($17::bigint[]), 0) + NULLIF(UNNEST($2::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), + NULLIF(UNNEST($3::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), + UNNEST($4::chat_message_role[]), + UNNEST($5::text[])::jsonb, + UNNEST($6::smallint[]), + UNNEST($7::chat_message_visibility[]), + NULLIF(UNNEST($8::bigint[]), 0), + NULLIF(UNNEST($9::bigint[]), 0), + NULLIF(UNNEST($10::bigint[]), 0), + NULLIF(UNNEST($11::bigint[]), 0), + NULLIF(UNNEST($12::bigint[]), 0), + NULLIF(UNNEST($13::bigint[]), 0), + NULLIF(UNNEST($14::bigint[]), 0), + UNNEST($15::boolean[]), + NULLIF(UNNEST($16::bigint[]), 0), + NULLIF(UNNEST($17::bigint[]), 0) RETURNING id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms ` diff --git a/coderd/database/queries/chats.sql b/coderd/database/queries/chats.sql index da581de9c0..de10803224 100644 --- a/coderd/database/queries/chats.sql +++ b/coderd/database/queries/chats.sql @@ -185,7 +185,7 @@ WITH updated_chat AS ( SET last_model_config_id = ( SELECT val - FROM unnest(@model_config_id::uuid[]) + FROM UNNEST(@model_config_id::uuid[]) WITH ORDINALITY AS t(val, ord) WHERE val != '00000000-0000-0000-0000-000000000000'::uuid ORDER BY ord DESC @@ -195,12 +195,12 @@ WITH updated_chat AS ( id = @chat_id::uuid AND EXISTS ( SELECT 1 - FROM unnest(@model_config_id::uuid[]) + FROM UNNEST(@model_config_id::uuid[]) WHERE unnest != '00000000-0000-0000-0000-000000000000'::uuid ) AND chats.last_model_config_id IS DISTINCT FROM ( SELECT val - FROM unnest(@model_config_id::uuid[]) + FROM UNNEST(@model_config_id::uuid[]) WITH ORDINALITY AS t(val, ord) WHERE val != '00000000-0000-0000-0000-000000000000'::uuid ORDER BY ord DESC @@ -228,22 +228,22 @@ INSERT INTO chat_messages ( ) SELECT @chat_id::uuid, - NULLIF(unnest(@created_by::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), - NULLIF(unnest(@model_config_id::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), - unnest(@role::chat_message_role[]), - unnest(@content::text[])::jsonb, - unnest(@content_version::smallint[]), - unnest(@visibility::chat_message_visibility[]), - NULLIF(unnest(@input_tokens::bigint[]), 0), - NULLIF(unnest(@output_tokens::bigint[]), 0), - NULLIF(unnest(@total_tokens::bigint[]), 0), - NULLIF(unnest(@reasoning_tokens::bigint[]), 0), - NULLIF(unnest(@cache_creation_tokens::bigint[]), 0), - NULLIF(unnest(@cache_read_tokens::bigint[]), 0), - NULLIF(unnest(@context_limit::bigint[]), 0), - unnest(@compressed::boolean[]), - NULLIF(unnest(@total_cost_micros::bigint[]), 0), - NULLIF(unnest(@runtime_ms::bigint[]), 0) + NULLIF(UNNEST(@created_by::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), + NULLIF(UNNEST(@model_config_id::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid), + UNNEST(@role::chat_message_role[]), + UNNEST(@content::text[])::jsonb, + UNNEST(@content_version::smallint[]), + UNNEST(@visibility::chat_message_visibility[]), + NULLIF(UNNEST(@input_tokens::bigint[]), 0), + NULLIF(UNNEST(@output_tokens::bigint[]), 0), + NULLIF(UNNEST(@total_tokens::bigint[]), 0), + NULLIF(UNNEST(@reasoning_tokens::bigint[]), 0), + NULLIF(UNNEST(@cache_creation_tokens::bigint[]), 0), + NULLIF(UNNEST(@cache_read_tokens::bigint[]), 0), + NULLIF(UNNEST(@context_limit::bigint[]), 0), + UNNEST(@compressed::boolean[]), + NULLIF(UNNEST(@total_cost_micros::bigint[]), 0), + NULLIF(UNNEST(@runtime_ms::bigint[]), 0) RETURNING *;