@@ -21,7 +21,7 @@ import { Log } from "../../src/util/log"
2121import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
2222import { provideTmpdirServer } from "../fixture/fixture"
2323import { testEffect } from "../lib/effect"
24- import { reply , TestLLMServer } from "../lib/llm-server"
24+ import { raw , reply , TestLLMServer } from "../lib/llm-server"
2525
2626Log . init ( { print : false } )
2727
@@ -218,6 +218,93 @@ it.live("session.processor effect tests capture llm input cleanly", () =>
218218 ) ,
219219)
220220
221+ it . live ( "session.processor effect tests preserve text start time" , ( ) =>
222+ provideTmpdirServer (
223+ ( { dir, llm } ) =>
224+ Effect . gen ( function * ( ) {
225+ const gate = defer < void > ( )
226+ const { processors, session, provider } = yield * boot ( )
227+
228+ yield * llm . push (
229+ raw ( {
230+ head : [
231+ {
232+ id : "chatcmpl-test" ,
233+ object : "chat.completion.chunk" ,
234+ choices : [ { delta : { role : "assistant" } } ] ,
235+ } ,
236+ {
237+ id : "chatcmpl-test" ,
238+ object : "chat.completion.chunk" ,
239+ choices : [ { delta : { content : "hello" } } ] ,
240+ } ,
241+ ] ,
242+ wait : gate . promise ,
243+ tail : [
244+ {
245+ id : "chatcmpl-test" ,
246+ object : "chat.completion.chunk" ,
247+ choices : [ { delta : { } , finish_reason : "stop" } ] ,
248+ } ,
249+ ] ,
250+ } ) ,
251+ )
252+
253+ const chat = yield * session . create ( { } )
254+ const parent = yield * user ( chat . id , "hi" )
255+ const msg = yield * assistant ( chat . id , parent . id , path . resolve ( dir ) )
256+ const mdl = yield * provider . getModel ( ref . providerID , ref . modelID )
257+ const handle = yield * processors . create ( {
258+ assistantMessage : msg ,
259+ sessionID : chat . id ,
260+ model : mdl ,
261+ } )
262+
263+ const run = yield * handle
264+ . process ( {
265+ user : {
266+ id : parent . id ,
267+ sessionID : chat . id ,
268+ role : "user" ,
269+ time : parent . time ,
270+ agent : parent . agent ,
271+ model : { providerID : ref . providerID , modelID : ref . modelID } ,
272+ } satisfies MessageV2 . User ,
273+ sessionID : chat . id ,
274+ model : mdl ,
275+ agent : agent ( ) ,
276+ system : [ ] ,
277+ messages : [ { role : "user" , content : "hi" } ] ,
278+ tools : { } ,
279+ } )
280+ . pipe ( Effect . forkChild )
281+
282+ yield * Effect . promise ( async ( ) => {
283+ const stop = Date . now ( ) + 500
284+ while ( Date . now ( ) < stop ) {
285+ const text = MessageV2 . parts ( msg . id ) . find ( ( part ) : part is MessageV2 . TextPart => part . type === "text" )
286+ if ( text ?. time ?. start ) return
287+ await Bun . sleep ( 10 )
288+ }
289+ throw new Error ( "timed out waiting for text part" )
290+ } )
291+ yield * Effect . sleep ( "20 millis" )
292+ gate . resolve ( )
293+
294+ const exit = yield * Fiber . await ( run )
295+ const text = MessageV2 . parts ( msg . id ) . find ( ( part ) : part is MessageV2 . TextPart => part . type === "text" )
296+
297+ expect ( Exit . isSuccess ( exit ) ) . toBe ( true )
298+ expect ( text ?. text ) . toBe ( "hello" )
299+ expect ( text ?. time ?. start ) . toBeDefined ( )
300+ expect ( text ?. time ?. end ) . toBeDefined ( )
301+ if ( ! text ?. time ?. start || ! text . time . end ) return
302+ expect ( text . time . start ) . toBeLessThan ( text . time . end )
303+ } ) ,
304+ { git : true , config : ( url ) => providerCfg ( url ) } ,
305+ ) ,
306+ )
307+
221308it . live ( "session.processor effect tests stop after token overflow requests compaction" , ( ) =>
222309 provideTmpdirServer (
223310 ( { dir, llm } ) =>
0 commit comments