1- import { db } from '@sim/db'
2- import { copilotChats } from '@sim/db/schema'
31import { createLogger } from '@sim/logger'
42import { generateId } from '@sim/utils/id'
5- import { and , eq , sql } from 'drizzle-orm'
63import { type NextRequest , NextResponse } from 'next/server'
74import { copilotChatStopContract } from '@/lib/api/contracts/copilot'
85import { parseRequest } from '@/lib/api/server'
96import { getSession } from '@/lib/auth'
107import { normalizeMessage , type PersistedMessage } from '@/lib/copilot/chat/persisted-message'
8+ import { finalizeAssistantTurn } from '@/lib/copilot/chat/terminal-state'
119import { CopilotStopOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1210import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
1311import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
@@ -44,71 +42,33 @@ export const POST = withRouteHandler((req: NextRequest) =>
4442 ...( requestId ? { [ TraceAttr . RequestId ] : requestId } : { } ) ,
4543 } )
4644
47- const [ row ] = await db
48- . select ( {
49- workspaceId : copilotChats . workspaceId ,
50- messages : copilotChats . messages ,
51- } )
52- . from ( copilotChats )
53- . where ( and ( eq ( copilotChats . id , chatId ) , eq ( copilotChats . userId , session . user . id ) ) )
54- . limit ( 1 )
55-
56- if ( ! row ) {
57- span . setAttribute ( TraceAttr . CopilotStopOutcome , CopilotStopOutcome . ChatNotFound )
58- return NextResponse . json ( { success : true } )
59- }
60-
61- const messages : Record < string , unknown > [ ] = Array . isArray ( row . messages ) ? row . messages : [ ]
62- const userIdx = messages . findIndex ( ( message ) => message . id === streamId )
63- const alreadyHasResponse =
64- userIdx >= 0 &&
65- userIdx + 1 < messages . length &&
66- ( messages [ userIdx + 1 ] as Record < string , unknown > ) ?. role === 'assistant'
67- const canAppendAssistant =
68- userIdx >= 0 && userIdx === messages . length - 1 && ! alreadyHasResponse
69-
70- const updateWhere = and (
71- eq ( copilotChats . id , chatId ) ,
72- eq ( copilotChats . userId , session . user . id ) ,
73- eq ( copilotChats . conversationId , streamId )
74- )
75-
76- const setClause : Record < string , unknown > = {
77- conversationId : null ,
78- updatedAt : new Date ( ) ,
79- }
80-
8145 const hasContent = content . trim ( ) . length > 0
8246 const hasBlocks = Array . isArray ( contentBlocks ) && contentBlocks . length > 0
8347 const synthesizedStoppedBlocks = hasBlocks
8448 ? contentBlocks
8549 : hasContent
8650 ? [ { type : 'text' , channel : 'assistant' , content } , { type : 'stopped' } ]
8751 : [ { type : 'stopped' } ]
88- if ( canAppendAssistant ) {
89- const normalized = normalizeMessage ( {
90- id : generateId ( ) ,
91- role : 'assistant' ,
92- content,
93- timestamp : new Date ( ) . toISOString ( ) ,
94- contentBlocks : synthesizedStoppedBlocks ,
95- // Persist so the UI copy-request-id button survives refetch.
96- ...( requestId ? { requestId } : { } ) ,
97- } )
98- const assistantMessage : PersistedMessage = normalized
99- setClause . messages = sql `${ copilotChats . messages } || ${ JSON . stringify ( [ assistantMessage ] ) } ::jsonb`
100- }
101- span . setAttribute ( TraceAttr . CopilotStopAppendedAssistant , canAppendAssistant )
102-
103- const [ updated ] = await db
104- . update ( copilotChats )
105- . set ( setClause )
106- . where ( updateWhere )
107- . returning ( { workspaceId : copilotChats . workspaceId } )
52+ const assistantMessage : PersistedMessage = normalizeMessage ( {
53+ id : generateId ( ) ,
54+ role : 'assistant' ,
55+ content,
56+ timestamp : new Date ( ) . toISOString ( ) ,
57+ contentBlocks : synthesizedStoppedBlocks ,
58+ ...( requestId ? { requestId } : { } ) ,
59+ } )
60+ const result = await finalizeAssistantTurn ( {
61+ chatId,
62+ userId : session . user . id ,
63+ userMessageId : streamId ,
64+ assistantMessage,
65+ streamMarkerPolicy : 'active-or-cleared' ,
66+ } )
67+ span . setAttribute ( TraceAttr . CopilotStopAppendedAssistant , result . appendedAssistant )
10868
109- if ( updated ? .workspaceId ) {
69+ if ( result . updated && result . workspaceId ) {
11070 taskPubSub ?. publishStatusChanged ( {
111- workspaceId : updated . workspaceId ,
71+ workspaceId : result . workspaceId ,
11272 chatId,
11373 type : 'completed' ,
11474 streamId,
@@ -117,7 +77,11 @@ export const POST = withRouteHandler((req: NextRequest) =>
11777
11878 span . setAttribute (
11979 TraceAttr . CopilotStopOutcome ,
120- updated ? CopilotStopOutcome . Persisted : CopilotStopOutcome . NoMatchingRow
80+ result . found
81+ ? result . updated
82+ ? CopilotStopOutcome . Persisted
83+ : CopilotStopOutcome . NoMatchingRow
84+ : CopilotStopOutcome . ChatNotFound
12185 )
12286 return NextResponse . json ( { success : true } )
12387 } catch ( error ) {
0 commit comments