@@ -333,9 +333,66 @@ export abstract class AiAgent<T> {
333333 get isHistoryEntry ( ) : boolean {
334334 return this . #generatedFromHistory;
335335 }
336-
337336 async * run ( initialQuery : string , options : {
338337 signal ?: AbortSignal , selected : ConversationContext < T > | null ,
338+ } ) : AsyncGenerator < ResponseData , void , void > {
339+ for await ( const response of this . #run( initialQuery , options ) ) {
340+ this . #addHistory( response ) ;
341+ yield response ;
342+ }
343+ }
344+
345+ async * runFromHistory ( ) : AsyncGenerator < ResponseData , void , void > {
346+ if ( this . isEmpty ) {
347+ return ;
348+ }
349+
350+ this . #generatedFromHistory = true ;
351+ for ( const entry of this . #history) {
352+ yield entry ;
353+ }
354+ }
355+
356+ parseResponse ( response : Host . AidaClient . AidaResponse ) : ParsedResponse {
357+ if ( response . functionCalls && response . completed ) {
358+ throw new Error ( 'Function calling not supported yet' ) ;
359+ }
360+ return {
361+ answer : response . explanation ,
362+ } ;
363+ }
364+
365+ /**
366+ * Declare a function that the AI model can call.
367+ * @param name - The name of the function
368+ * @param declaration - the function declaration. Currently functions must:
369+ * 1. Return an object of serializable key/value pairs. You cannot return
370+ * anything other than a plain JavaScript object that can be serialized.
371+ * 2. Take one parameter which is an object that can have
372+ * multiple keys and values. For example, rather than a function being called
373+ * with two args, `foo` and `bar`, you should instead have the function be
374+ * called with one object with `foo` and `bar` keys.
375+ */
376+ protected declareFunction < Args extends Record < string , unknown > , ReturnType = unknown > (
377+ name : string , declaration : FunctionDeclaration < Args , ReturnType > ) : void {
378+ if ( this . #functionDeclarations. has ( name ) ) {
379+ throw new Error ( `Duplicate function declaration ${ name } ` ) ;
380+ }
381+ this . #functionDeclarations. set ( name , declaration as FunctionDeclaration < Record < string , unknown > , ReturnType > ) ;
382+ }
383+
384+ protected formatParsedAnswer ( { answer} : ParsedAnswer ) : string {
385+ return answer ;
386+ }
387+
388+ protected handleAction ( action : string , options ?: { signal ?: AbortSignal } ) :
389+ AsyncGenerator < SideEffectResponse , ActionResponse , void > ;
390+ protected handleAction ( ) : never {
391+ throw new Error ( 'Unexpected action found' ) ;
392+ }
393+
394+ async * #run( initialQuery : string , options : {
395+ signal ?: AbortSignal , selected : ConversationContext < T > | null ,
339396 } ) : AsyncGenerator < ResponseData , void , void > {
340397 if ( this . #generatedFromHistory) {
341398 throw new Error ( 'History entries are read-only.' ) ;
@@ -360,24 +417,17 @@ export abstract class AiAgent<T> {
360417 // Request is built here to capture history up to this point.
361418 let request = this . buildRequest ( query , Host . AidaClient . Role . USER ) ;
362419
363- const response = {
420+ yield {
364421 type : ResponseType . USER_QUERY ,
365422 query : initialQuery ,
366- } as const ;
367- this . #addHistory( response ) ;
368- yield response ;
423+ } ;
369424
370- for await ( const response of this . handleContextDetails ( options . selected ) ) {
371- this . #addHistory( response ) ;
372- yield response ;
373- }
425+ yield * this . handleContextDetails ( options . selected ) ;
374426
375427 for ( let i = 0 ; i < MAX_STEPS ; i ++ ) {
376- const queryResponse = {
428+ yield {
377429 type : ResponseType . QUERYING ,
378- } as const ;
379- this . #addHistory( queryResponse ) ;
380- yield queryResponse ;
430+ } ;
381431
382432 let rpcId : Host . AidaClient . RpcGlobalId | undefined ;
383433 let parsedResponse : ParsedResponse | undefined = undefined ;
@@ -400,44 +450,33 @@ export abstract class AiAgent<T> {
400450 } catch ( err ) {
401451 debugLog ( 'Error calling the AIDA API' , err ) ;
402452
403- this . #removeLastRunParts ( ) ;
453+ let error = ErrorType . UNKNOWN ;
404454 if ( err instanceof Host . AidaClient . AidaAbortError ) {
405- const response = this . #createAbortResponse( ) ;
406- this . #addHistory( response ) ;
407- yield response ;
408- break ;
455+ error = ErrorType . ABORT ;
456+ } else if ( err instanceof Host . AidaClient . AidaBlockError ) {
457+ error = ErrorType . BLOCK ;
409458 }
410-
411- const error = ( err instanceof Host . AidaClient . AidaBlockError ) ? ErrorType . BLOCK : ErrorType . UNKNOWN ;
412- const response = {
413- type : ResponseType . ERROR ,
414- error,
415- } as const ;
416- this . #addHistory( response ) ;
417- Host . userMetrics . actionTaken ( Host . UserMetrics . Action . AiAssistanceError ) ;
418- yield response ;
459+ yield this . #createErrorResponse( error ) ;
419460
420461 break ;
421462 }
422463
423464 this . #partsHistory. push ( request . current_message ) ;
424465
425466 if ( parsedResponse && 'answer' in parsedResponse && Boolean ( parsedResponse . answer ) ) {
426- const response = {
427- type : ResponseType . ANSWER ,
428- text : parsedResponse . answer ,
429- suggestions : parsedResponse . suggestions ,
430- rpcId,
431- } as const ;
432467 this . #partsHistory. push ( {
433468 parts : [ {
434469 text : this . formatParsedAnswer ( parsedResponse ) ,
435470 } ] ,
436471 role : Host . AidaClient . Role . MODEL ,
437472 } ) ;
438473 Host . userMetrics . actionTaken ( Host . UserMetrics . Action . AiAssistanceAnswerReceived ) ;
439- this . #addHistory( response ) ;
440- yield response ;
474+ yield {
475+ type : ResponseType . ANSWER ,
476+ text : parsedResponse . answer ,
477+ suggestions : parsedResponse . suggestions ,
478+ rpcId,
479+ } ;
441480 break ;
442481 } else if ( parsedResponse && ! ( 'answer' in parsedResponse ) ) {
443482 const {
@@ -447,23 +486,19 @@ export abstract class AiAgent<T> {
447486 } = parsedResponse ;
448487
449488 if ( title ) {
450- const response = {
489+ yield {
451490 type : ResponseType . TITLE ,
452491 title,
453492 rpcId,
454- } as const ;
455- this . #addHistory( response ) ;
456- yield response ;
493+ } ;
457494 }
458495
459496 if ( thought ) {
460- const response = {
497+ yield {
461498 type : ResponseType . THOUGHT ,
462499 thought,
463500 rpcId,
464- } as const ;
465- this . #addHistory( response ) ;
466- yield response ;
501+ } ;
467502 }
468503
469504 this . #partsHistory. push ( {
@@ -476,14 +511,9 @@ export abstract class AiAgent<T> {
476511 if ( action ) {
477512 const result = yield * this . handleAction ( action , { signal : options . signal } ) ;
478513 if ( options ?. signal ?. aborted ) {
479- this . #removeLastRunParts( ) ;
480- const response = this . #createAbortResponse( ) ;
481- this . #addHistory( response ) ;
482-
483- yield response ;
514+ yield this . #createErrorResponse( ErrorType . ABORT ) ;
484515 break ;
485516 }
486- this . #addHistory( result ) ;
487517 query = { text : `${ OBSERVATION_PREFIX } ${ result . output } ` } ;
488518 // Capture history state for the next iteration query.
489519 request = this . buildRequest ( query , Host . AidaClient . Role . USER ) ;
@@ -494,12 +524,11 @@ export abstract class AiAgent<T> {
494524 const result = yield * this . #callFunction( functionCall . name , functionCall . args ) ;
495525
496526 if ( result . result ) {
497- const response = {
527+ yield {
498528 type : ResponseType . ACTION ,
499529 output : JSON . stringify ( result . result ) ,
500530 canceled : false ,
501- } as const ;
502- yield response ;
531+ } ;
503532 }
504533
505534 query = {
@@ -510,23 +539,11 @@ export abstract class AiAgent<T> {
510539 } ;
511540 request = this . buildRequest ( query , Host . AidaClient . Role . ROLE_UNSPECIFIED ) ;
512541 } catch {
513- this . #removeLastRunParts( ) ;
514- const response = {
515- type : ResponseType . ERROR ,
516- error : ErrorType . UNKNOWN ,
517- } as const ;
518- Host . userMetrics . actionTaken ( Host . UserMetrics . Action . AiAssistanceError ) ;
519- yield response ;
542+ yield this . #createErrorResponse( ErrorType . UNKNOWN ) ;
520543 break ;
521544 }
522545 } else {
523- this . #removeLastRunParts( ) ;
524- const response = {
525- type : ResponseType . ERROR ,
526- error : i - 1 === MAX_STEPS ? ErrorType . MAX_STEPS : ErrorType . UNKNOWN ,
527- } as const ;
528- Host . userMetrics . actionTaken ( Host . UserMetrics . Action . AiAssistanceError ) ;
529- yield response ;
546+ yield this . #createErrorResponse( i - 1 === MAX_STEPS ? ErrorType . MAX_STEPS : ErrorType . UNKNOWN ) ;
530547 break ;
531548 }
532549 }
@@ -536,55 +553,6 @@ export abstract class AiAgent<T> {
536553 }
537554 }
538555
539- async * runFromHistory ( ) : AsyncGenerator < ResponseData , void , void > {
540- if ( this . isEmpty ) {
541- return ;
542- }
543-
544- this . #generatedFromHistory = true ;
545- for ( const entry of this . #history) {
546- yield entry ;
547- }
548- }
549-
550- parseResponse ( response : Host . AidaClient . AidaResponse ) : ParsedResponse {
551- if ( response . functionCalls && response . completed ) {
552- throw new Error ( 'Function calling not supported yet' ) ;
553- }
554- return {
555- answer : response . explanation ,
556- } ;
557- }
558-
559- /**
560- * Declare a function that the AI model can call.
561- * @param name - The name of the function
562- * @param declaration - the function declaration. Currently functions must:
563- * 1. Return an object of serializable key/value pairs. You cannot return
564- * anything other than a plain JavaScript object that can be serialized.
565- * 2. Take one parameter which is an object that can have
566- * multiple keys and values. For example, rather than a function being called
567- * with two args, `foo` and `bar`, you should instead have the function be
568- * called with one object with `foo` and `bar` keys.
569- */
570- protected declareFunction < Args extends Record < string , unknown > , ReturnType = unknown > (
571- name : string , declaration : FunctionDeclaration < Args , ReturnType > ) : void {
572- if ( this . #functionDeclarations. has ( name ) ) {
573- throw new Error ( `Duplicate function declaration ${ name } ` ) ;
574- }
575- this . #functionDeclarations. set ( name , declaration as FunctionDeclaration < Record < string , unknown > , ReturnType > ) ;
576- }
577-
578- protected formatParsedAnswer ( { answer} : ParsedAnswer ) : string {
579- return answer ;
580- }
581-
582- protected handleAction ( action : string , options ?: { signal ?: AbortSignal } ) :
583- AsyncGenerator < SideEffectResponse , ActionResponse , void > ;
584- protected handleAction ( ) : never {
585- throw new Error ( 'Unexpected action found' ) ;
586- }
587-
588556 async * #callFunction( name : string , args : Record < string , unknown > , options ?: {
589557 signal ?: AbortSignal ,
590558 approved ?: boolean ,
@@ -606,39 +574,32 @@ export abstract class AiAgent<T> {
606574 if ( call . displayInfoFromArgs ) {
607575 const { title, thought, code, suggestions} = call . displayInfoFromArgs ( args ) ;
608576 if ( title ) {
609- const response = {
577+ yield {
610578 type : ResponseType . TITLE ,
611579 title,
612- } as const ;
613- this . #addHistory( response ) ;
614- yield response ;
580+ } ;
615581 }
616582
617583 if ( thought ) {
618- const response = {
584+ yield {
619585 type : ResponseType . THOUGHT ,
620586 thought,
621- } as const ;
622- this . #addHistory( response ) ;
623- yield response ;
587+ } ;
624588 }
625589
626590 if ( code ) {
627- const response = {
591+ yield {
628592 type : ResponseType . ACTION ,
629593 code,
630594 canceled : false ,
631- } as const ;
632- this . #addHistory( response ) ;
633- yield response ;
595+ } ;
634596 }
635597
636598 if ( suggestions ) {
637- const response = {
599+ yield {
638600 type : ResponseType . SUGGESTIONS ,
639601 suggestions,
640- } as const ;
641- yield response ;
602+ } ;
642603 }
643604 }
644605
@@ -671,12 +632,11 @@ export abstract class AiAgent<T> {
671632
672633 const approvedRun = await sideEffectConfirmationPromiseWithResolvers . promise ;
673634 if ( ! approvedRun ) {
674- const response = {
635+ yield {
675636 type : ResponseType . ACTION ,
676637 code : '' ,
677638 canceled : true ,
678- } as const ;
679- yield response ;
639+ } ;
680640 return {
681641 result : 'Error: User denied code execution with side effects.' ,
682642 } ;
@@ -775,10 +735,15 @@ STOP`;
775735 } ) ) ;
776736 }
777737
778- #createAbortResponse( ) : ResponseData {
738+ #createErrorResponse( error : ErrorType ) : ResponseData {
739+ this . #removeLastRunParts( ) ;
740+ if ( error !== ErrorType . ABORT ) {
741+ Host . userMetrics . actionTaken ( Host . UserMetrics . Action . AiAssistanceError ) ;
742+ }
743+
779744 return {
780745 type : ResponseType . ERROR ,
781- error : ErrorType . ABORT ,
746+ error,
782747 } ;
783748 }
784749}
0 commit comments