11import { JsEnvironment } from "./environment.js" ;
2+ import type { RlmEvent , RlmEventSink } from "./events.js" ;
23import { buildModelTable , buildSystemPrompt } from "./system-prompt.js" ;
34
45export interface CallLLMResponse {
@@ -34,6 +35,7 @@ export interface RlmOptions {
3435 /** Keyed by name; looked up when a parent calls `rlm(query, ctx, { app: "name" })`. */
3536 childApps ?: Record < string , string > ;
3637 reasoningEffort ?: string ;
38+ observer ?: RlmEventSink ;
3739}
3840
3941export interface RlmResult {
@@ -99,6 +101,11 @@ export async function rlm(query: string, context: string | undefined, options: R
99101 reasoningEffort : options . reasoningEffort ,
100102 } ;
101103
104+ const emit : ( ( event : RlmEvent ) => void ) | undefined = options . observer
105+ ? ( event ) => options . observer ! . emit ( event )
106+ : undefined ;
107+ const runId = globalThis . crypto . randomUUID ( ) ;
108+
102109 const modelTable = buildModelTable ( opts . models ) ;
103110
104111 const env = new JsEnvironment ( ) ;
@@ -230,6 +237,17 @@ export async function rlm(query: string, context: string | undefined, options: R
230237 modelTable,
231238 } ) ;
232239
240+ emit ?.( {
241+ type : "invocation:start" ,
242+ runId,
243+ timestamp : performance . now ( ) ,
244+ invocationId,
245+ parentId,
246+ depth,
247+ query,
248+ systemPrompt : effectiveSystemPrompt ,
249+ } ) ;
250+
233251 if ( ! contextStore . locals . has ( invocationId ) ) {
234252 contextStore . locals . set ( invocationId , { } ) ;
235253 }
@@ -279,22 +297,83 @@ export async function rlm(query: string, context: string | undefined, options: R
279297
280298 const messages : Array < { role : string ; content : string ; meta ?: Record < string , unknown > } > = [ { role : "user" , content : query } ] ;
281299
300+ let invocationResult : RlmResult | undefined ;
301+ let invocationError : unknown ;
302+ try {
282303 for ( let iteration = 0 ; iteration < effectiveMaxIterations ; iteration ++ ) {
304+ emit ?.( {
305+ type : "iteration:start" ,
306+ runId,
307+ timestamp : performance . now ( ) ,
308+ invocationId,
309+ parentId,
310+ depth,
311+ iteration,
312+ budgetRemaining : effectiveMaxIterations - iteration ,
313+ } ) ;
314+
315+ let iterationReturned = false ;
316+ let iterationCode : string | null = null ;
317+ let iterationOutput = "" ;
318+ let iterationError : string | null = null ;
319+
320+ try {
321+
322+ const llmStart = performance . now ( ) ;
323+ emit ?.( {
324+ type : "llm:request" ,
325+ runId,
326+ timestamp : llmStart ,
327+ invocationId,
328+ parentId,
329+ depth,
330+ iteration,
331+ messageCount : messages . length ,
332+ systemPromptLength : effectiveSystemPrompt . length ,
333+ } ) ;
334+
283335 let response : CallLLMResponse ;
284336 try {
285337 response = await callLLM ( messages , effectiveSystemPrompt , effectiveReasoningEffort ? { reasoningEffort : effectiveReasoningEffort } : undefined ) ;
286338 } catch ( err ) {
287- throw new RlmError (
288- err instanceof Error ? err . message : String ( err ) ,
339+ const llmError = err instanceof Error ? err . message : String ( err ) ;
340+ emit ?.( {
341+ type : "llm:error" ,
342+ runId,
343+ timestamp : performance . now ( ) ,
344+ invocationId,
345+ parentId,
346+ depth,
289347 iteration,
290- ) ;
348+ error : llmError ,
349+ duration : performance . now ( ) - llmStart ,
350+ } ) ;
351+ iterationError = llmError ;
352+ throw new RlmError ( llmError , iteration ) ;
291353 }
292354
355+ emit ?.( {
356+ type : "llm:response" ,
357+ runId,
358+ timestamp : performance . now ( ) ,
359+ invocationId,
360+ parentId,
361+ depth,
362+ iteration,
363+ duration : performance . now ( ) - llmStart ,
364+ reasoning : response . reasoning ,
365+ code : response . code ,
366+ hasToolUse : ! ! response . toolUseId ,
367+ usage : ( response as unknown as Record < string , unknown > ) . usage as import ( "./events.js" ) . TokenUsage | undefined ,
368+ } ) ;
369+
293370 const reasoning = response . reasoning ;
294371 const codeBlocks = response . code !== null ? [ response . code ] : [ ] ;
295372 const toolUseId = response . toolUseId ?? null ;
296373 const reasoningDetails = response . reasoningDetails ?? null ;
297374
375+ iterationCode = response . code ;
376+
298377 let combinedOutput = "" ;
299378 let combinedError : string | null = null ;
300379
@@ -334,6 +413,15 @@ export async function rlm(query: string, context: string | undefined, options: R
334413 await new Promise ( ( r ) => setTimeout ( r , 0 ) ) ;
335414 if ( pendingRlmCalls . size > 0 ) {
336415 const count = pendingRlmCalls . size ;
416+ emit ?.( {
417+ type : "delegation:unawaited" ,
418+ runId,
419+ timestamp : performance . now ( ) ,
420+ invocationId,
421+ parentId,
422+ depth,
423+ count,
424+ } ) ;
337425 const warning =
338426 `[ERROR] ${ count } rlm() call(s) were NOT awaited. Their results are LOST and the API calls were wasted. ` +
339427 `You MUST write: const result = await rlm("query", context). ` +
@@ -352,10 +440,16 @@ export async function rlm(query: string, context: string | undefined, options: R
352440 break ;
353441 }
354442 const answer = typeof returnValue === "object" ? JSON . stringify ( returnValue ) : String ( returnValue ) ;
355- return { answer, iterations : iteration + 1 } ;
443+ iterationReturned = true ;
444+ iterationOutput = combinedOutput ;
445+ invocationResult = { answer, iterations : iteration + 1 } ;
446+ return invocationResult ;
356447 }
357448 }
358449
450+ iterationOutput = combinedOutput ;
451+ iterationError = combinedError ;
452+
359453 // Build iteration context for the next turn (if there will be one)
360454 const nextIterContext = ( effectiveMaxIterations > 1 && iteration + 1 < effectiveMaxIterations )
361455 ? buildIterationContext ( iteration + 1 ) + "\n"
@@ -386,9 +480,57 @@ export async function rlm(query: string, context: string | undefined, options: R
386480 "[WARNING] No code was executed. Use the execute_code tool to run JavaScript and make progress." ,
387481 } ) ;
388482 }
483+
484+ } finally {
485+ emit ?.( {
486+ type : "iteration:end" ,
487+ runId,
488+ timestamp : performance . now ( ) ,
489+ invocationId,
490+ parentId,
491+ depth,
492+ iteration,
493+ code : iterationCode ,
494+ output : iterationOutput ,
495+ error : iterationError ,
496+ returned : iterationReturned ,
497+ } ) ;
498+
499+ if ( emit ) {
500+ emit ( {
501+ type : "sandbox:snapshot" ,
502+ runId,
503+ timestamp : performance . now ( ) ,
504+ invocationId,
505+ parentId,
506+ depth,
507+ iteration,
508+ state : env . snapshot ( snapshotExcludeKeys ) ,
509+ } ) ;
510+ }
511+ }
389512 }
390513
391- throw new RlmMaxIterationsError ( effectiveMaxIterations ) ;
514+ const maxIterErr = new RlmMaxIterationsError ( effectiveMaxIterations ) ;
515+ invocationError = maxIterErr ;
516+ throw maxIterErr ;
517+
518+ } catch ( err ) {
519+ invocationError = err ;
520+ throw err ;
521+ } finally {
522+ emit ?.( {
523+ type : "invocation:end" ,
524+ runId,
525+ timestamp : performance . now ( ) ,
526+ invocationId,
527+ parentId,
528+ depth,
529+ answer : invocationResult ?. answer ?? null ,
530+ error : invocationError instanceof Error ? invocationError . message : invocationError ? String ( invocationError ) : null ,
531+ iterations : invocationResult ?. iterations ?? ( invocationError instanceof RlmError ? invocationError . iterations : 0 ) ,
532+ } ) ;
533+ }
392534 }
393535
394536 env . set ( "rlm" , ( q : string , c ?: string , rlmOpts ?: { systemPrompt ?: string ; model ?: string ; maxIterations ?: number ; app ?: string ; reasoning ?: string } ) : Promise < string > => {
@@ -440,10 +582,48 @@ export async function rlm(query: string, context: string | undefined, options: R
440582 ? childDepthLabel
441583 : `${ callerInvocationId } .${ childDepthLabel } ` ;
442584
585+ emit ?.( {
586+ type : "delegation:spawn" ,
587+ runId,
588+ timestamp : performance . now ( ) ,
589+ invocationId : callerInvocationId ,
590+ parentId : ( env . get ( "__rlm" ) as DelegationContext | undefined ) ?. parentId ?? null ,
591+ depth : savedDepth ,
592+ childId : childInvocationId ,
593+ query : q ,
594+ modelAlias : rlmOpts ?. model ,
595+ maxIterations : rlmOpts ?. maxIterations ,
596+ appName : rlmOpts ?. app ,
597+ } ) ;
598+
443599 const promise = ( async ( ) => {
444600 try {
445601 const result = await rlmInternal ( q , c , savedDepth + 1 , childLineage , childInvocationId , callerInvocationId , resolvedSystemPrompt , modelCallLLM , rlmOpts ?. maxIterations , rlmOpts ?. reasoning ) ;
602+ emit ?.( {
603+ type : "delegation:return" ,
604+ runId,
605+ timestamp : performance . now ( ) ,
606+ invocationId : callerInvocationId ,
607+ parentId : ( env . get ( "__rlm" ) as DelegationContext | undefined ) ?. parentId ?? null ,
608+ depth : savedDepth ,
609+ childId : childInvocationId ,
610+ answer : result . answer ,
611+ iterations : result . iterations ,
612+ } ) ;
446613 return result . answer ;
614+ } catch ( err ) {
615+ emit ?.( {
616+ type : "delegation:error" ,
617+ runId,
618+ timestamp : performance . now ( ) ,
619+ invocationId : callerInvocationId ,
620+ parentId : ( env . get ( "__rlm" ) as DelegationContext | undefined ) ?. parentId ?? null ,
621+ depth : savedDepth ,
622+ childId : childInvocationId ,
623+ error : err instanceof Error ? err . message : String ( err ) ,
624+ iterations : err instanceof RlmError ? err . iterations : 0 ,
625+ } ) ;
626+ throw err ;
447627 } finally {
448628 activeDepth = savedDepth ;
449629 }
@@ -455,5 +635,37 @@ export async function rlm(query: string, context: string | undefined, options: R
455635 return promise ;
456636 } ) ;
457637
458- return rlmInternal ( query , context , 0 , [ query ] , "root" , null ) ;
638+ emit ?.( {
639+ type : "run:start" ,
640+ runId,
641+ timestamp : performance . now ( ) ,
642+ invocationId : "root" ,
643+ parentId : null ,
644+ depth : 0 ,
645+ query,
646+ maxIterations : opts . maxIterations ,
647+ maxDepth : opts . maxDepth ,
648+ } ) ;
649+
650+ let runResult : RlmResult | undefined ;
651+ let runError : unknown ;
652+ try {
653+ runResult = await rlmInternal ( query , context , 0 , [ query ] , "root" , null ) ;
654+ return runResult ;
655+ } catch ( err ) {
656+ runError = err ;
657+ throw err ;
658+ } finally {
659+ emit ?.( {
660+ type : "run:end" ,
661+ runId,
662+ timestamp : performance . now ( ) ,
663+ invocationId : "root" ,
664+ parentId : null ,
665+ depth : 0 ,
666+ answer : runResult ?. answer ?? null ,
667+ error : runError instanceof Error ? runError . message : runError ? String ( runError ) : null ,
668+ iterations : runResult ?. iterations ?? ( runError instanceof RlmError ? runError . iterations : 0 ) ,
669+ } ) ;
670+ }
459671}
0 commit comments