@@ -18,15 +18,14 @@ import { message as cleanMessage } from "@/utils/diffs"
1818import { sessionNotFoundError } from "@/utils/server-errors"
1919import { rootSession } from "@/utils/session-route"
2020import { normalizeSessionInfo } from "@/utils/session"
21- import { normalizeSessionMessages } from "@/utils/session-message"
21+ import { compareMessages , messageKey , normalizeSessionMessages } from "@/utils/session-message"
2222import { dropSessionCaches , pickSessionCacheEvictions , SESSION_CACHE_LIMIT } from "./global-sync/session-cache"
2323import { createV2SessionReducer , type V2SessionReduction } from "./server-session-v2-reducer"
2424import type { ServerApi } from "@/utils/server"
2525
2626type MessageApi = ServerApi [ "message" ]
2727
2828const cmp = ( a : string , b : string ) => ( a < b ? - 1 : a > b ? 1 : 0 )
29- const cmpMessage = ( a : Message , b : Message ) => a . time . created - b . time . created || cmp ( a . id , b . id )
3029const SKIP_PARTS = new Set ( [ "patch" , "step-start" , "step-finish" ] )
3130const initialMessagePageSize = 20
3231const historyMessagePageSize = 200
@@ -64,7 +63,7 @@ type MessagePage = {
6463function legacyMessageSource ( items : { info : Message ; parts : Part [ ] } [ ] ) : SessionMessageInfo [ ] {
6564 return items
6665 . slice ( )
67- . sort ( ( a , b ) => cmp ( a . info . id , b . info . id ) )
66+ . sort ( ( a , b ) => compareMessages ( a . info , b . info ) )
6867 . map ( ( item ) => {
6968 if ( item . info . role === "user" ) {
7069 return {
@@ -111,17 +110,16 @@ function mergeOptimisticPage(page: MessagePage, items: OptimisticItem[]) {
111110 const part = new Map ( page . part . map ( ( item ) => [ item . id , item . part ] ) )
112111 const observed : { messageID : string ; parts : Part [ ] } [ ] = [ ]
113112 for ( const item of items ) {
114- const result = Binary . search ( session , item . message . id , ( message ) => message . id )
115- if ( ! result . found ) session . splice ( result . index , 0 , item . message )
113+ const result = Binary . search ( session , messageKey ( item . message ) , messageKey )
114+ const found = result . found
115+ if ( ! found ) session . splice ( result . index , 0 , item . message )
116116 const current = part . get ( item . message . id )
117- const confirmed = result . found
118- ? item . parts . filter ( ( part ) => Binary . search ( current ?? [ ] , part . id , ( value ) => value . id ) . found )
119- : [ ]
120- if ( result . found ) observed . push ( { messageID : item . message . id , parts : confirmed } )
117+ const confirmed = found ? item . parts . filter ( ( part ) => current ?. some ( ( value ) => value . id === part . id ) ) : [ ]
118+ if ( found ) observed . push ( { messageID : item . message . id , parts : confirmed } )
121119 part . set (
122120 item . message . id ,
123121 merge (
124- result . found ? ( current ?? [ ] ) : merge ( item . confirmedParts ?? [ ] , current ?? [ ] ) ,
122+ found ? ( current ?? [ ] ) : merge ( item . confirmedParts ?? [ ] , current ?? [ ] ) ,
125123 item . parts . filter ( ( part ) => ! confirmed . includes ( part ) ) ,
126124 ) ,
127125 )
@@ -158,6 +156,7 @@ function reconcileFetched<T extends { id: string }>(
158156 retained ?: ReadonlySet < string >
159157 removed ?: ReadonlySet < string >
160158 preserveUnfetched ?: boolean | ( ( item : T ) => boolean )
159+ compare ?: ( a : T , b : T ) => number
161160 } = { } ,
162161) {
163162 const result = new Map ( fetched . map ( ( item ) => [ item . id , item ] ) )
@@ -180,7 +179,8 @@ function reconcileFetched<T extends { id: string }>(
180179 if ( ! item ) result . delete ( id )
181180 }
182181 for ( const id of options . removed ?? emptyIDs ) result . delete ( id )
183- return [ ...result . values ( ) ] . sort ( ( a , b ) => cmp ( a . id , b . id ) )
182+ const items = [ ...result . values ( ) ]
183+ return options . compare ? items . sort ( options . compare ) : items
184184}
185185
186186type ServerSessionOptions = { retry ?: typeof retry ; protocol ?: Promise < "v1" | "v2" > }
@@ -413,8 +413,7 @@ export function createServerSession(
413413 if ( ! load ) return
414414 // A part event keeps an existing parent when the fetched page omits it without overriding fetched metadata.
415415 const messages = data . message [ sessionID ]
416- if ( messages && Binary . search ( messages , messageID , ( message ) => message . id ) . found )
417- load . retainedMessages . add ( messageID )
416+ if ( messages ?. some ( ( message ) => message . id === messageID ) ) load . retainedMessages . add ( messageID )
418417 const parts = load . touchedParts . get ( messageID )
419418 if ( parts ) {
420419 parts . add ( partID )
@@ -437,16 +436,14 @@ export function createServerSession(
437436 load . touchedParts . set ( messageID , new Set ( parts ) )
438437 load . carriedDeltaParts . set ( messageID , new Set ( parts ) )
439438 const messages = data . message [ sessionID ]
440- if ( messages && Binary . search ( messages , messageID , ( message ) => message . id ) . found )
441- load . retainedMessages . add ( messageID )
439+ if ( messages ?. some ( ( message ) => message . id === messageID ) ) load . retainedMessages . add ( messageID )
442440 }
443441 for ( const [ messageID , parts ] of load . removedParts ) {
444442 const touched = load . touchedParts . get ( messageID ) ?? new Set < string > ( )
445443 parts . forEach ( ( partID ) => touched . add ( partID ) )
446444 load . touchedParts . set ( messageID , touched )
447445 const messages = data . message [ sessionID ]
448- if ( messages && Binary . search ( messages , messageID , ( message ) => message . id ) . found )
449- load . retainedMessages . add ( messageID )
446+ if ( messages ?. some ( ( message ) => message . id === messageID ) ) load . retainedMessages . add ( messageID )
450447 }
451448 for ( const [ messageID , parts ] of load . optimisticParts ) {
452449 load . removedMessages . delete ( messageID )
@@ -555,7 +552,7 @@ export function createServerSession(
555552 const source = pages . flatMap ( ( page ) => page . data ) . toReversed ( )
556553 const normalized = normalizeSessionMessages ( sessionID , source )
557554 return {
558- session : normalized . messages . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
555+ session : normalized . messages . sort ( compareMessages ) ,
559556 part : [ ...normalized . parts . entries ( ) ]
560557 . map ( ( [ id , part ] ) => ( { id, part : part . sort ( ( a , b ) => cmp ( a . id , b . id ) ) } ) )
561558 . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
@@ -572,7 +569,7 @@ export function createServerSession(
572569 } )
573570 const items = ( response . data ?? [ ] ) . filter ( ( item ) => ! ! item ?. info ?. id )
574571 return {
575- session : items . map ( ( item ) => cleanMessage ( item . info ) ) . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
572+ session : items . map ( ( item ) => cleanMessage ( item . info ) ) . sort ( compareMessages ) ,
576573 part : items . map ( ( item ) => ( {
577574 id : item . info . id ,
578575 part : item . parts . filter ( ( part ) => ! ! part ?. id ) . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
@@ -696,7 +693,7 @@ export function createServerSession(
696693 const normalized = normalizeSessionMessages ( sessionID , source )
697694 return {
698695 ...page ,
699- session : normalized . messages . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
696+ session : normalized . messages . sort ( compareMessages ) ,
700697 part : [ ...normalized . parts . entries ( ) ]
701698 . map ( ( [ id , part ] ) => ( { id, part : part . sort ( ( a , b ) => cmp ( a . id , b . id ) ) } ) )
702699 . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
@@ -713,6 +710,7 @@ export function createServerSession(
713710 retained : load ?. retainedMessages ,
714711 removed : load ?. removedMessages ,
715712 preserveUnfetched,
713+ compare : compareMessages ,
716714 } )
717715 batch ( ( ) => {
718716 if ( source ) setData ( "session_message" , sessionID , reconcile ( source ) )
@@ -754,7 +752,7 @@ export function createServerSession(
754752 try {
755753 const page = await fetchMessages ( sessionID , limit , before , ( ) => resetMessageLoad ( sessionID , load ) )
756754 const first = page . session . reduce < Message | undefined > (
757- ( oldest , message ) => ( ! oldest || cmpMessage ( message , oldest ) < 0 ? message : oldest ) ,
755+ ( oldest , message ) => ( ! oldest || compareMessages ( message , oldest ) < 0 ? message : oldest ) ,
758756 undefined ,
759757 )
760758 if ( generations . get ( sessionID ) !== active ) return
@@ -804,14 +802,15 @@ export function createServerSession(
804802 session : merge (
805803 page . session ,
806804 parents . map ( ( parent ) => parent . message ) ,
807- ) ,
805+ ) . sort ( compareMessages ) ,
808806 part : merge (
809807 page . part ,
810808 parents . map ( ( parent ) => ( { id : parent . message . id , part : parent . parts } ) ) ,
811809 ) ,
812810 }
813811 const preserveUnfetched =
814- mode === "prepend" || ( ! result . complete && ( ! first || ( ( message : Message ) => cmpMessage ( message , first ) < 0 ) ) )
812+ mode === "prepend" ||
813+ ( ! result . complete && ( ! first || ( ( message : Message ) => compareMessages ( message , first ) < 0 ) ) )
815814 applyMessagePage (
816815 sessionID ,
817816 result ,
@@ -928,7 +927,7 @@ export function createServerSession(
928927 . message ( { sessionID, messageID } )
929928 . then ( ( message ) => {
930929 const current = data . session_message [ sessionID ] ?? [ ]
931- const messages = [ ...current . filter ( ( item ) => item . id !== message . id ) , message ] . sort ( ( a , b ) => cmp ( a . id , b . id ) )
930+ const messages = [ ...current . filter ( ( item ) => item . id !== message . id ) , message ] . sort ( compareMessages )
932931 projectV2 ( { sessionID, messages, touched : [ message . id ] } )
933932 } )
934933 . catch ( ( ) => { } )
@@ -1051,7 +1050,7 @@ export function createServerSession(
10511050 setData ( "message" , info . sessionID , [ info ] )
10521051 return
10531052 }
1054- const result = Binary . search ( messages , info . id , ( message ) => message . id )
1053+ const result = Binary . search ( messages , messageKey ( info ) , messageKey )
10551054 if ( result . found ) setData ( "message" , info . sessionID , result . index , reconcile ( info ) )
10561055 if ( ! result . found )
10571056 setData ( "message" , info . sessionID , ( value = [ ] ) => {
@@ -1084,8 +1083,8 @@ export function createServerSession(
10841083 produce ( ( draft ) => {
10851084 const messages = draft . message [ props . sessionID ]
10861085 if ( messages ) {
1087- const result = Binary . search ( messages , props . messageID , ( message ) => message . id )
1088- if ( result . found ) messages . splice ( result . index , 1 )
1086+ const index = messages . findIndex ( ( message ) => message . id === props . messageID )
1087+ if ( index >= 0 ) messages . splice ( index , 1 )
10891088 }
10901089 deleteMessageParts ( draft , props . messageID )
10911090 } ) ,
@@ -1097,7 +1096,7 @@ export function createServerSession(
10971096 if ( SKIP_PARTS . has ( part . type ) ) return
10981097 const messages = data . message [ part . sessionID ]
10991098 const load = messageLoads . get ( part . sessionID )
1100- const missing = ! messages || ! Binary . search ( messages , part . messageID , ( message ) => message . id ) . found
1099+ const missing = ! messages ?. some ( ( message ) => message . id === part . messageID )
11011100 // Outside a page load, accepting a part without its ordered parent event would create an unbounded orphan.
11021101 if (
11031102 missing &&
@@ -1341,7 +1340,7 @@ export function createServerSession(
13411340 if ( items ) items . set ( input . message . id , { ...input , parts, confirmedParts : [ ] } )
13421341 if ( ! items )
13431342 optimistic . set ( input . sessionID , new Map ( [ [ input . message . id , { ...input , parts, confirmedParts : [ ] } ] ] ) )
1344- setData ( "message" , input . sessionID , ( messages = [ ] ) => merge ( messages , [ input . message ] ) )
1343+ setData ( "message" , input . sessionID , ( messages = [ ] ) => merge ( messages , [ input . message ] ) . sort ( compareMessages ) )
13451344 setData (
13461345 "part_text_accum_delta" ,
13471346 produce ( ( draft ) => {
0 commit comments