@@ -15,7 +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"
18+ import { Effect , Option , Stream } from "effect"
1919
2020/** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */
2121interface FetchDecompressionError extends Error {
@@ -574,11 +574,11 @@ export namespace MessageV2 {
574574 } ) )
575575 }
576576
577- export async function toModelMessages (
577+ export const toModelMessagesEffect = Effect . fnUntraced ( function * (
578578 input : WithParts [ ] ,
579579 model : Provider . Model ,
580580 options ?: { stripMedia ?: boolean } ,
581- ) : Promise < ModelMessage [ ] > {
581+ ) {
582582 const result : UIMessage [ ] = [ ]
583583 const toolNames = new Set < string > ( )
584584 // Track media from tool results that need to be injected as user messages
@@ -801,24 +801,26 @@ export namespace MessageV2 {
801801
802802 const tools = Object . fromEntries ( Array . from ( toolNames ) . map ( ( toolName ) => [ toolName , { toModelOutput } ] ) )
803803
804- return await convertToModelMessages (
805- result . filter ( ( msg ) => msg . parts . some ( ( part ) => part . type !== "step-start" ) ) ,
806- {
807- //@ts -expect-error (convertToModelMessages expects a ToolSet but only actually needs tools[name]?.toModelOutput)
808- tools,
809- } ,
804+ return yield * Effect . promise ( ( ) =>
805+ convertToModelMessages (
806+ result . filter ( ( msg ) => msg . parts . some ( ( part ) => part . type !== "step-start" ) ) ,
807+ {
808+ //@ts -expect-error (convertToModelMessages expects a ToolSet but only actually needs tools[name]?.toModelOutput)
809+ tools,
810+ } ,
811+ ) ,
810812 )
811- }
813+ } )
812814
813- export const toModelMessagesEffect = Effect . fnUntraced ( function * (
815+ export function toModelMessages (
814816 input : WithParts [ ] ,
815817 model : Provider . Model ,
816818 options ?: { stripMedia ?: boolean } ,
817- ) {
818- return yield * Effect . promise ( ( ) => toModelMessages ( input , model , options ) )
819- } )
819+ ) : Promise < ModelMessage [ ] > {
820+ return Effect . runPromise ( toModelMessagesEffect ( input , model , options ) )
821+ }
820822
821- function pageSync ( input : { sessionID : SessionID ; limit : number ; before ?: string } ) {
823+ export function pageSync ( input : { sessionID : SessionID ; limit : number ; before ?: string } ) {
822824 const before = input . before ? cursor . decode ( input . before ) : undefined
823825 const where = before
824826 ? and ( eq ( MessageTable . session_id , input . sessionID ) , older ( before ) )
@@ -878,7 +880,7 @@ export namespace MessageV2 {
878880 }
879881 } )
880882
881- function partsSync ( message_id : MessageID ) {
883+ export function partsSync ( message_id : MessageID ) {
882884 const rows = Database . use ( ( db ) =>
883885 db . select ( ) . from ( PartTable ) . where ( eq ( PartTable . message_id , message_id ) ) . orderBy ( PartTable . id ) . all ( ) ,
884886 )
@@ -895,7 +897,7 @@ export namespace MessageV2 {
895897
896898 export const parts = fn ( MessageID . zod , async ( message_id ) => partsSync ( message_id ) )
897899
898- function getSync ( input : { sessionID : SessionID ; messageID : MessageID } ) : WithParts {
900+ export function getSync ( input : { sessionID : SessionID ; messageID : MessageID } ) : WithParts {
899901 const row = Database . use ( ( db ) =>
900902 db
901903 . select ( )
@@ -918,54 +920,30 @@ export namespace MessageV2 {
918920 async ( input ) : Promise < WithParts > => getSync ( input ) ,
919921 )
920922
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- } )
936923
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- } )
924+ export const streamMessages = ( sessionID : SessionID ) : Stream . Stream < WithParts > =>
925+ Stream . paginate ( undefined as string | undefined , ( before ) =>
926+ Effect . sync ( ( ) => {
927+ const next = pageSync ( { sessionID, limit : 50 , before } )
928+ const items = next . items . toReversed ( )
929+ const nextCursor = next . more && next . cursor ? Option . some ( next . cursor ) : Option . none ( )
930+ return [ items , nextCursor ] as const
931+ } ) ,
932+ )
952933
953- function applyCompactionFilter ( msgs : MessageV2 . WithParts [ ] ) {
954- const result = [ ] as MessageV2 . WithParts [ ]
934+ export const filterCompactedMessages = ( sessionID : SessionID ) : Stream . Stream < WithParts > => {
955935 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
936+ return streamMessages ( sessionID ) . pipe (
937+ Stream . takeUntil ( ( msg ) => {
938+ if ( msg . info . role === "assistant" && msg . info . summary && msg . info . finish && ! msg . info . error )
939+ completed . add ( msg . info . parentID )
940+ return (
941+ msg . info . role === "user" &&
942+ completed . has ( msg . info . id ) &&
943+ msg . parts . some ( ( part ) => part . type === "compaction" )
944+ )
945+ } ) ,
946+ )
969947 }
970948
971949 export async function filterCompacted ( stream : AsyncIterable < MessageV2 . WithParts > ) {
@@ -987,7 +965,8 @@ export namespace MessageV2 {
987965 }
988966
989967 export const filterCompactedEffect = Effect . fnUntraced ( function * ( sessionID : SessionID ) {
990- return applyCompactionFilter ( yield * streamEffect ( sessionID ) )
968+ const all = yield * Stream . runCollect ( filterCompactedMessages ( sessionID ) )
969+ return Array . from ( all ) . reverse ( )
991970 } )
992971
993972 export function fromError (
0 commit comments