@@ -3,28 +3,31 @@ import {
3
3
type LooseSingleCardDocument ,
4
4
type CardResource ,
5
5
} from '@cardstack/runtime-common' ;
6
- import { ToolChoice } from '@cardstack/runtime-common/helpers/ai' ;
6
+ import {
7
+ CommandRequestContent ,
8
+ ToolChoice ,
9
+ } from '@cardstack/runtime-common/helpers/ai' ;
7
10
import type {
8
11
MatrixEvent as DiscreteMatrixEvent ,
9
12
CardFragmentContent ,
10
- CommandEvent ,
11
13
Tool ,
12
14
SkillsConfigEvent ,
13
15
ActiveLLMEvent ,
16
+ CardMessageEvent ,
14
17
CommandResultEvent ,
15
18
} from 'https://cardstack.com/base/matrix-event' ;
16
19
import { MatrixEvent , type IRoomEvent } from 'matrix-js-sdk' ;
17
20
import { ChatCompletionMessageToolCall } from 'openai/resources/chat/completions' ;
18
21
import * as Sentry from '@sentry/node' ;
19
22
import { logger } from '@cardstack/runtime-common' ;
20
23
import {
24
+ APP_BOXEL_COMMAND_REQUESTS_KEY ,
21
25
APP_BOXEL_COMMAND_RESULT_EVENT_TYPE ,
22
26
APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE ,
23
27
} from '../runtime-common/matrix-constants' ;
24
28
import {
25
29
APP_BOXEL_CARDFRAGMENT_MSGTYPE ,
26
30
APP_BOXEL_MESSAGE_MSGTYPE ,
27
- APP_BOXEL_COMMAND_MSGTYPE ,
28
31
APP_BOXEL_ROOM_SKILLS_EVENT_TYPE ,
29
32
DEFAULT_LLM ,
30
33
APP_BOXEL_ACTIVE_LLM ,
@@ -169,8 +172,8 @@ export function constructHistory(
169
172
if ( event . content . msgtype === APP_BOXEL_MESSAGE_MSGTYPE ) {
170
173
let { attachedCardsEventIds } = event . content . data ;
171
174
if ( attachedCardsEventIds && attachedCardsEventIds . length > 0 ) {
172
- event . content . data . attachedCards = attachedCardsEventIds . map ( ( id ) =>
173
- serializedCardFromFragments ( id , cardFragments ) ,
175
+ event . content . data . attachedCards = attachedCardsEventIds . map (
176
+ ( id : string ) => serializedCardFromFragments ( id , cardFragments ) ,
174
177
) ;
175
178
}
176
179
}
@@ -377,57 +380,69 @@ export function getToolChoice(
377
380
return 'auto' ;
378
381
}
379
382
380
- function getCommandResult (
381
- commandEvent : CommandEvent ,
383
+ function getCommandResults (
384
+ cardMessageEvent : CardMessageEvent ,
382
385
history : DiscreteMatrixEvent [ ] ,
383
386
) {
384
- let commandResultEvent = history . find ( ( e ) => {
387
+ let commandResultEvents = history . filter ( ( e ) => {
385
388
if (
386
389
isCommandResultEvent ( e ) &&
387
- e . content [ 'm.relates_to' ] ?. event_id === commandEvent . event_id
390
+ e . content [ 'm.relates_to' ] ?. event_id === cardMessageEvent . event_id
388
391
) {
389
392
return true ;
390
393
}
391
394
return false ;
392
- } ) as CommandResultEvent | undefined ;
393
- return commandResultEvent ;
395
+ } ) as CommandResultEvent [ ] ;
396
+ return commandResultEvents ;
394
397
}
395
398
396
- function toToolCall ( event : CommandEvent ) : ChatCompletionMessageToolCall {
397
- return {
398
- id : event . content . data . toolCall . id ,
399
- function : {
400
- name : event . content . data . toolCall . name ,
401
- arguments : JSON . stringify ( event . content . data . toolCall . arguments ) ,
399
+ function toToolCalls ( event : CardMessageEvent ) : ChatCompletionMessageToolCall [ ] {
400
+ return ( event . content [ APP_BOXEL_COMMAND_REQUESTS_KEY ] ?? [ ] ) . map (
401
+ ( commandRequest : CommandRequestContent ) => {
402
+ return {
403
+ id : commandRequest . id ,
404
+ function : {
405
+ name : commandRequest . name ,
406
+ arguments : JSON . stringify ( commandRequest . arguments ) ,
407
+ } ,
408
+ type : 'function' ,
409
+ } ;
402
410
} ,
403
- type : 'function' ,
404
- } ;
411
+ ) ;
405
412
}
406
413
407
- function toPromptMessageWithToolResult (
408
- event : CommandEvent ,
414
+ function toPromptMessageWithToolResults (
415
+ event : CardMessageEvent ,
409
416
history : DiscreteMatrixEvent [ ] ,
410
- ) : OpenAIPromptMessage {
411
- let commandResult = getCommandResult ( event , history ) ;
412
- let content = 'pending' ;
413
- if ( commandResult ) {
414
- let status = commandResult . content [ 'm.relates_to' ] ?. key ;
415
- if (
416
- commandResult . content . msgtype ===
417
- APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE
418
- ) {
419
- content = `Command ${ status } , with result card: ${ JSON . stringify (
420
- commandResult . content . data . card ,
421
- ) } .\n`;
422
- } else {
423
- content = `Command ${ status } .\n` ;
424
- }
425
- }
426
- return {
427
- role : 'tool' ,
428
- content,
429
- tool_call_id : event . content . data . toolCall . id ,
430
- } ;
417
+ ) : OpenAIPromptMessage [ ] {
418
+ let commandResults = getCommandResults ( event , history ) ;
419
+ return ( event . content [ APP_BOXEL_COMMAND_REQUESTS_KEY ] ?? [ ] ) . map (
420
+ ( commandRequest : CommandRequestContent ) => {
421
+ let content = 'pending' ;
422
+ let commandResult = commandResults . find (
423
+ ( commandResult ) =>
424
+ commandResult . content . data . commandRequestId === commandRequest . id ,
425
+ ) ;
426
+ if ( commandResult ) {
427
+ let status = commandResult . content [ 'm.relates_to' ] ?. key ;
428
+ if (
429
+ commandResult . content . msgtype ===
430
+ APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE
431
+ ) {
432
+ content = `Command ${ status } , with result card: ${ JSON . stringify (
433
+ commandResult . content . data . card ,
434
+ ) } .\n`;
435
+ } else {
436
+ content = `Command ${ status } .\n` ;
437
+ }
438
+ }
439
+ return {
440
+ role : 'tool' ,
441
+ tool_call_id : commandRequest . id ,
442
+ content,
443
+ } ;
444
+ } ,
445
+ ) ;
431
446
}
432
447
433
448
export function getModifyPrompt (
@@ -460,20 +475,19 @@ export function getModifyPrompt(
460
475
let body = event . content . body ;
461
476
if ( body ) {
462
477
if ( event . sender === aiBotUserId ) {
463
- if ( isCommandEvent ( event ) ) {
464
- historicalMessages . push ( {
465
- role : 'assistant' ,
466
- content : body ,
467
- tool_calls : [ toToolCall ( event ) ] ,
468
- } ) ;
469
- historicalMessages . push (
470
- toPromptMessageWithToolResult ( event , history ) ,
478
+ let toolCalls = toToolCalls ( event ) ;
479
+ let historicalMessage : OpenAIPromptMessage = {
480
+ role : 'assistant' ,
481
+ content : body ,
482
+ } ;
483
+ if ( toolCalls . length ) {
484
+ historicalMessage . tool_calls = toolCalls ;
485
+ }
486
+ historicalMessages . push ( historicalMessage ) ;
487
+ if ( toolCalls . length ) {
488
+ toPromptMessageWithToolResults ( event , history ) . forEach ( ( message ) =>
489
+ historicalMessages . push ( message ) ,
471
490
) ;
472
- } else {
473
- historicalMessages . push ( {
474
- role : 'assistant' ,
475
- content : body ,
476
- } ) ;
477
491
}
478
492
} else {
479
493
if (
@@ -570,19 +584,6 @@ export const isCommandResultStatusApplied = (event?: MatrixEvent) => {
570
584
) ;
571
585
} ;
572
586
573
- export function isCommandEvent (
574
- event : DiscreteMatrixEvent ,
575
- ) : event is CommandEvent {
576
- return (
577
- event . type === 'm.room.message' &&
578
- typeof event . content === 'object' &&
579
- event . content . msgtype === APP_BOXEL_COMMAND_MSGTYPE &&
580
- event . content . format === 'org.matrix.custom.html' &&
581
- typeof event . content . data === 'object' &&
582
- typeof event . content . data . toolCall === 'object'
583
- ) ;
584
- }
585
-
586
587
function getModel ( eventlist : DiscreteMatrixEvent [ ] ) : string {
587
588
let activeLLMEvent = eventlist . findLast (
588
589
( event ) => event . type === APP_BOXEL_ACTIVE_LLM ,
0 commit comments