@@ -89,6 +89,9 @@ const createSessionBodySchema = z.object({
8989 triggerMessage : z . string ( ) . optional ( ) ,
9090} ) . strict ( ) ;
9191
92+ const VEGA_LITE_FENCE_START = "```vega-lite" ;
93+ const COMPLETE_VEGA_LITE_BLOCK_RE = / ` ` ` v e g a - l i t e [ \s \S ] * ?` ` ` / ;
94+
9295function isAbortError ( error : unknown ) : boolean {
9396 return (
9497 error instanceof DOMException && error . name === "AbortError"
@@ -279,6 +282,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
279282
280283 private async runAgentTurn ( input : AgentTurnRunInput ) {
281284 let fullResponse = "" ;
285+ let bufferedTextDelta = "" ;
286+ let isRenderingVegaLite = false ;
282287 const maxTokens = this . options . maxTokens ?? 1000 ;
283288 const selectedMode = this . options . modes . find ( ( mode ) => mode . name === input . modeName ) ?? this . options . modes [ 0 ] ;
284289 const [ primaryModelSpec , summaryModelSpec ] = await Promise . all ( [
@@ -386,13 +391,63 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
386391
387392 if ( textDelta ) {
388393 fullResponse += textDelta ;
394+ bufferedTextDelta += textDelta ;
395+
396+ if (
397+ bufferedTextDelta . includes ( VEGA_LITE_FENCE_START ) &&
398+ ! COMPLETE_VEGA_LITE_BLOCK_RE . test ( bufferedTextDelta )
399+ ) {
400+ if ( ! isRenderingVegaLite ) {
401+ isRenderingVegaLite = true ;
402+ await input . emit ?.( {
403+ type : "rendering" ,
404+ phase : "start" ,
405+ label : "Rendering..." ,
406+ } ) ;
407+ }
408+ continue ;
409+ }
410+
411+ if ( isRenderingVegaLite ) {
412+ isRenderingVegaLite = false ;
413+ await input . emit ?.( {
414+ type : "rendering" ,
415+ phase : "end" ,
416+ label : "Rendering..." ,
417+ } ) ;
418+ }
419+
420+ const streamableLength = bufferedTextDelta . includes ( VEGA_LITE_FENCE_START )
421+ ? bufferedTextDelta . length
422+ : bufferedTextDelta . length - getPartialVegaLiteFenceStartLength ( bufferedTextDelta ) ;
423+
424+ if ( ! streamableLength ) {
425+ continue ;
426+ }
427+
389428 await input . emit ?.( {
390429 type : "text-delta" ,
391- delta : textDelta ,
430+ delta : bufferedTextDelta . slice ( 0 , streamableLength ) ,
392431 } ) ;
432+ bufferedTextDelta = bufferedTextDelta . slice ( streamableLength ) ;
393433 }
394434 }
395435
436+ if ( isRenderingVegaLite ) {
437+ await input . emit ?.( {
438+ type : "rendering" ,
439+ phase : "end" ,
440+ label : "Rendering..." ,
441+ } ) ;
442+ }
443+
444+ if ( bufferedTextDelta ) {
445+ await input . emit ?.( {
446+ type : "text-delta" ,
447+ delta : bufferedTextDelta ,
448+ } ) ;
449+ }
450+
396451 return {
397452 text : fullResponse ,
398453 } ;
@@ -956,3 +1011,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
9561011 } )
9571012 }
9581013}
1014+
1015+ function getPartialVegaLiteFenceStartLength ( text : string ) : number {
1016+ for ( let length = Math . min ( text . length , VEGA_LITE_FENCE_START . length - 1 ) ; length > 0 ; length -= 1 ) {
1017+ if ( VEGA_LITE_FENCE_START . startsWith ( text . slice ( - length ) ) ) {
1018+ return length ;
1019+ }
1020+ }
1021+
1022+ return 0 ;
1023+ }
0 commit comments