(params: {
chatId?: string
userMessageId: string
message: string
fileAttachments?: UnifiedChatRequest['fileAttachments']
contexts?: UnifiedChatRequest['contexts']
workspaceId?: string
notifyWorkspaceStatus: boolean
/**
* Root context for the mothership request. When present the persist
* span is created explicitly under it, which avoids relying on
* AsyncLocalStorage propagation — some upstream awaits (Next.js
* framework frames, Turbopack-instrumented I/O) can swap the active
* store out from under us in dev, which would otherwise leave this
* span parented to the about-to-be-dropped Next.js HTTP span.
*/
parentOtelContext?: OtelContext
})
| 292 | } |
| 293 | |
| 294 | async function persistUserMessage(params: { |
| 295 | chatId?: string |
| 296 | userMessageId: string |
| 297 | message: string |
| 298 | fileAttachments?: UnifiedChatRequest['fileAttachments'] |
| 299 | contexts?: UnifiedChatRequest['contexts'] |
| 300 | workspaceId?: string |
| 301 | notifyWorkspaceStatus: boolean |
| 302 | /** |
| 303 | * Root context for the mothership request. When present the persist |
| 304 | * span is created explicitly under it, which avoids relying on |
| 305 | * AsyncLocalStorage propagation — some upstream awaits (Next.js |
| 306 | * framework frames, Turbopack-instrumented I/O) can swap the active |
| 307 | * store out from under us in dev, which would otherwise leave this |
| 308 | * span parented to the about-to-be-dropped Next.js HTTP span. |
| 309 | */ |
| 310 | parentOtelContext?: OtelContext |
| 311 | }): Promise<void> { |
| 312 | const { |
| 313 | chatId, |
| 314 | userMessageId, |
| 315 | message, |
| 316 | fileAttachments, |
| 317 | contexts, |
| 318 | workspaceId, |
| 319 | notifyWorkspaceStatus, |
| 320 | parentOtelContext, |
| 321 | } = params |
| 322 | if (!chatId) return |
| 323 | |
| 324 | return withCopilotSpan( |
| 325 | TraceSpan.CopilotChatPersistUserMessage, |
| 326 | { |
| 327 | [TraceAttr.DbSystem]: 'postgresql', |
| 328 | [TraceAttr.DbSqlTable]: 'copilot_chats', |
| 329 | [TraceAttr.ChatId]: chatId, |
| 330 | [TraceAttr.ChatUserMessageId]: userMessageId, |
| 331 | [TraceAttr.ChatMessageBytes]: message.length, |
| 332 | [TraceAttr.ChatFileAttachmentCount]: fileAttachments?.length ?? 0, |
| 333 | [TraceAttr.ChatContextCount]: contexts?.length ?? 0, |
| 334 | ...(workspaceId ? { [TraceAttr.WorkspaceId]: workspaceId } : {}), |
| 335 | }, |
| 336 | async (span) => { |
| 337 | const userMsg = buildPersistedUserMessage({ |
| 338 | id: userMessageId, |
| 339 | content: message, |
| 340 | fileAttachments, |
| 341 | contexts, |
| 342 | }) |
| 343 | |
| 344 | const updated = await db.transaction(async (tx) => { |
| 345 | const [row] = await tx |
| 346 | .update(copilotChats) |
| 347 | .set({ |
| 348 | conversationId: userMessageId, |
| 349 | updatedAt: new Date(), |
| 350 | }) |
| 351 | .where(eq(copilotChats.id, chatId)) |
no test coverage detected