@@ -15,6 +15,7 @@ import { errorMessage } from "@/util/error"
1515import type { SystemError } from "bun"
1616import type { Provider } from "@/provider/provider"
1717import { ModelID , ProviderID } from "@/provider/schema"
18+ import { Effect } from "effect"
1819
1920/** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */
2021interface FetchDecompressionError extends Error {
@@ -547,7 +548,7 @@ export namespace MessageV2 {
547548 and ( eq ( MessageTable . time_created , row . time ) , lt ( MessageTable . id , row . id ) ) ,
548549 )
549550
550- async function hydrate ( rows : ( typeof MessageTable . $inferSelect ) [ ] ) {
551+ function hydrate ( rows : ( typeof MessageTable . $inferSelect ) [ ] ) {
551552 const ids = rows . map ( ( row ) => row . id )
552553 const partByMessage = new Map < string , MessageV2 . Part [ ] > ( )
553554 if ( ids . length > 0 ) {
@@ -809,48 +810,58 @@ export namespace MessageV2 {
809810 )
810811 }
811812
813+ export const toModelMessagesEffect = Effect . fnUntraced ( function * (
814+ input : WithParts [ ] ,
815+ model : Provider . Model ,
816+ options ?: { stripMedia ?: boolean } ,
817+ ) {
818+ return yield * Effect . promise ( ( ) => toModelMessages ( input , model , options ) )
819+ } )
820+
821+ function pageSync ( input : { sessionID : SessionID ; limit : number ; before ?: string } ) {
822+ const before = input . before ? cursor . decode ( input . before ) : undefined
823+ const where = before
824+ ? and ( eq ( MessageTable . session_id , input . sessionID ) , older ( before ) )
825+ : eq ( MessageTable . session_id , input . sessionID )
826+ const rows = Database . use ( ( db ) =>
827+ db
828+ . select ( )
829+ . from ( MessageTable )
830+ . where ( where )
831+ . orderBy ( desc ( MessageTable . time_created ) , desc ( MessageTable . id ) )
832+ . limit ( input . limit + 1 )
833+ . all ( ) ,
834+ )
835+ if ( rows . length === 0 ) {
836+ const row = Database . use ( ( db ) =>
837+ db . select ( { id : SessionTable . id } ) . from ( SessionTable ) . where ( eq ( SessionTable . id , input . sessionID ) ) . get ( ) ,
838+ )
839+ if ( ! row ) throw new NotFoundError ( { message : `Session not found: ${ input . sessionID } ` } )
840+ return {
841+ items : [ ] as MessageV2 . WithParts [ ] ,
842+ more : false ,
843+ }
844+ }
845+
846+ const more = rows . length > input . limit
847+ const slice = more ? rows . slice ( 0 , input . limit ) : rows
848+ const items = hydrate ( slice )
849+ items . reverse ( )
850+ const tail = slice . at ( - 1 )
851+ return {
852+ items,
853+ more,
854+ cursor : more && tail ? cursor . encode ( { id : tail . id , time : tail . time_created } ) : undefined ,
855+ }
856+ }
857+
812858 export const page = fn (
813859 z . object ( {
814860 sessionID : SessionID . zod ,
815861 limit : z . number ( ) . int ( ) . positive ( ) ,
816862 before : z . string ( ) . optional ( ) ,
817863 } ) ,
818- async ( input ) => {
819- const before = input . before ? cursor . decode ( input . before ) : undefined
820- const where = before
821- ? and ( eq ( MessageTable . session_id , input . sessionID ) , older ( before ) )
822- : eq ( MessageTable . session_id , input . sessionID )
823- const rows = Database . use ( ( db ) =>
824- db
825- . select ( )
826- . from ( MessageTable )
827- . where ( where )
828- . orderBy ( desc ( MessageTable . time_created ) , desc ( MessageTable . id ) )
829- . limit ( input . limit + 1 )
830- . all ( ) ,
831- )
832- if ( rows . length === 0 ) {
833- const row = Database . use ( ( db ) =>
834- db . select ( { id : SessionTable . id } ) . from ( SessionTable ) . where ( eq ( SessionTable . id , input . sessionID ) ) . get ( ) ,
835- )
836- if ( ! row ) throw new NotFoundError ( { message : `Session not found: ${ input . sessionID } ` } )
837- return {
838- items : [ ] as MessageV2 . WithParts [ ] ,
839- more : false ,
840- }
841- }
842-
843- const more = rows . length > input . limit
844- const page = more ? rows . slice ( 0 , input . limit ) : rows
845- const items = await hydrate ( page )
846- items . reverse ( )
847- const tail = page . at ( - 1 )
848- return {
849- items,
850- more,
851- cursor : more && tail ? cursor . encode ( { id : tail . id , time : tail . time_created } ) : undefined ,
852- }
853- } ,
864+ async ( input ) => pageSync ( input ) ,
854865 )
855866
856867 export const stream = fn ( SessionID . zod , async function * ( sessionID ) {
@@ -867,7 +878,7 @@ export namespace MessageV2 {
867878 }
868879 } )
869880
870- export const parts = fn ( MessageID . zod , async ( message_id ) => {
881+ function partsSync ( message_id : MessageID ) {
871882 const rows = Database . use ( ( db ) =>
872883 db . select ( ) . from ( PartTable ) . where ( eq ( PartTable . message_id , message_id ) ) . orderBy ( PartTable . id ) . all ( ) ,
873884 )
@@ -880,29 +891,83 @@ export namespace MessageV2 {
880891 messageID : row . message_id ,
881892 } ) as MessageV2 . Part ,
882893 )
883- } )
894+ }
895+
896+ export const parts = fn ( MessageID . zod , async ( message_id ) => partsSync ( message_id ) )
897+
898+ function getSync ( input : { sessionID : SessionID ; messageID : MessageID } ) : WithParts {
899+ const row = Database . use ( ( db ) =>
900+ db
901+ . select ( )
902+ . from ( MessageTable )
903+ . where ( and ( eq ( MessageTable . id , input . messageID ) , eq ( MessageTable . session_id , input . sessionID ) ) )
904+ . get ( ) ,
905+ )
906+ if ( ! row ) throw new NotFoundError ( { message : `Message not found: ${ input . messageID } ` } )
907+ return {
908+ info : info ( row ) ,
909+ parts : partsSync ( input . messageID ) ,
910+ }
911+ }
884912
885913 export const get = fn (
886914 z . object ( {
887915 sessionID : SessionID . zod ,
888916 messageID : MessageID . zod ,
889917 } ) ,
890- async ( input ) : Promise < WithParts > => {
891- const row = Database . use ( ( db ) =>
892- db
893- . select ( )
894- . from ( MessageTable )
895- . where ( and ( eq ( MessageTable . id , input . messageID ) , eq ( MessageTable . session_id , input . sessionID ) ) )
896- . get ( ) ,
897- )
898- if ( ! row ) throw new NotFoundError ( { message : `Message not found: ${ input . messageID } ` } )
899- return {
900- info : info ( row ) ,
901- parts : await parts ( input . messageID ) ,
902- }
903- } ,
918+ async ( input ) : Promise < WithParts > => getSync ( input ) ,
904919 )
905920
921+ export const partsEffect = Effect . fnUntraced ( function * ( id : MessageID ) {
922+ return partsSync ( id )
923+ } )
924+
925+ export const getEffect = Effect . fnUntraced ( function * ( input : { sessionID : SessionID ; messageID : MessageID } ) {
926+ return getSync ( input )
927+ } )
928+
929+ export const pageEffect = Effect . fnUntraced ( function * ( input : {
930+ sessionID : SessionID
931+ limit : number
932+ before ?: string
933+ } ) {
934+ return pageSync ( input )
935+ } )
936+
937+ export const streamEffect = Effect . fnUntraced ( function * ( sessionID : SessionID ) {
938+ const result : WithParts [ ] = [ ]
939+ const size = 50
940+ let before : string | undefined
941+ while ( true ) {
942+ const next = pageSync ( { sessionID, limit : size , before } )
943+ if ( next . items . length === 0 ) break
944+ for ( let i = next . items . length - 1 ; i >= 0 ; i -- ) {
945+ result . push ( next . items [ i ] )
946+ }
947+ if ( ! next . more || ! next . cursor ) break
948+ before = next . cursor
949+ }
950+ return result
951+ } )
952+
953+ function applyCompactionFilter ( msgs : MessageV2 . WithParts [ ] ) {
954+ const result = [ ] as MessageV2 . WithParts [ ]
955+ const completed = new Set < string > ( )
956+ for ( const msg of msgs ) {
957+ result . push ( msg )
958+ if (
959+ msg . info . role === "user" &&
960+ completed . has ( msg . info . id ) &&
961+ msg . parts . some ( ( part ) => part . type === "compaction" )
962+ )
963+ break
964+ if ( msg . info . role === "assistant" && msg . info . summary && msg . info . finish && ! msg . info . error )
965+ completed . add ( msg . info . parentID )
966+ }
967+ result . reverse ( )
968+ return result
969+ }
970+
906971 export async function filterCompacted ( stream : AsyncIterable < MessageV2 . WithParts > ) {
907972 const result = [ ] as MessageV2 . WithParts [ ]
908973 const completed = new Set < string > ( )
@@ -921,6 +986,10 @@ export namespace MessageV2 {
921986 return result
922987 }
923988
989+ export const filterCompactedEffect = Effect . fnUntraced ( function * ( sessionID : SessionID ) {
990+ return applyCompactionFilter ( yield * streamEffect ( sessionID ) )
991+ } )
992+
924993 export function fromError (
925994 e : unknown ,
926995 ctx : { providerID : ProviderID ; aborted ?: boolean } ,
0 commit comments