@@ -101,18 +101,21 @@ type activeAIRun struct {
101101 last * assistantStreamState
102102 status * bridgev2.MessageStatusEventInfo
103103 approvals map [string ]* activeApproval
104+
105+ lastConsumedTimestamp time.Time
104106}
105107
106108type assistantStreamState struct {
107- messageID networkid.MessageID
108- eventID id.EventID
109- runID string
110- run * aistream.Run
111- metadata * aiid.MessageMetadata
112- entryID string
113- tools []toolOutputEvent
114- sources * sourceCollector
115- publish streamPublishCursor
109+ messageID networkid.MessageID
110+ eventID id.EventID
111+ runID string
112+ run * aistream.Run
113+ metadata * aiid.MessageMetadata
114+ entryID string
115+ anchorTimestamp time.Time
116+ tools []toolOutputEvent
117+ sources * sourceCollector
118+ publish streamPublishCursor
116119}
117120
118121type streamPublishCursor struct {
@@ -682,15 +685,14 @@ func (cl *Client) markConsumedFailed(ctx context.Context, pending *pendingAIMess
682685 cl .Main .Bridge .Matrix .SendMessageStatus (ctx , & status , bridgev2 .StatusEventInfoFromEvent (pending .msg .Event ))
683686}
684687
685- func (cl * Client ) queueConsumedUserEcho (ctx context.Context , pending * pendingAIMessage , userEntryID string ) {
688+ func (cl * Client ) queueConsumedUserEcho (ctx context.Context , pending * pendingAIMessage , userEntryID string , echoTimestamp time. Time ) {
686689 if pending == nil || pending .msg == nil {
687690 return
688691 }
689692 pending .metadata .SessionEntryID = userEntryID
690693 pending .metadata .Role = "user"
691694 pending .metadata .StreamStatus = "done"
692695 messageID := aiid .UserMessageID (userEntryID )
693- echoTimestamp := matrixEventTime (pending .msg .Event )
694696 cl .UserLogin .QueueRemoteEvent (& simplevent.PreConvertedMessage {
695697 EventMeta : simplevent.EventMeta {
696698 Type : bridgev2 .RemoteEventMessage ,
@@ -712,7 +714,7 @@ func (cl *Client) queueConsumedUserEcho(ctx context.Context, pending *pendingAIM
712714 })
713715}
714716
715- func (cl * Client ) queueAssistantRunError (portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , modelID string , runID string , run aistream.Run , metadata * aiid.MessageMetadata , err error ) {
717+ func (cl * Client ) queueAssistantRunError (portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , modelID string , runID string , run aistream.Run , metadata * aiid.MessageMetadata , timestamp time. Time , err error ) {
716718 message := ai.Message {
717719 Role : "assistant" ,
718720 StopReason : ai .StopReasonError ,
@@ -724,7 +726,7 @@ func (cl *Client) queueAssistantRunError(portalKey networkid.PortalKey, messageI
724726 metadata .StreamStatus = "error"
725727 }
726728 run = finalizedAssistantRun (run , message , 0 )
727- cl .UserLogin .QueueRemoteEvent (cl .assistantFinalEditWithProjection (portalKey , messageID , providerID , modelID , run , metadata ))
729+ cl .UserLogin .QueueRemoteEvent (cl .assistantFinalEditWithProjection (portalKey , messageID , providerID , modelID , run , metadata , timestamp ))
728730}
729731
730732func hookStreamError (err error ) * ai.AssistantMessageEventStream {
@@ -902,15 +904,15 @@ func (cl *Client) defaultConversationTitle(ctx context.Context, portal *bridgev2
902904 return "New AI Chat"
903905}
904906
905- func (cl * Client ) assistantEvent (ctx context.Context , portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , modelID string , runID string , descriptor * event.BeeperStreamInfo , run aistream.Run ) (* simplevent.PreConvertedMessage , * aiid.MessageMetadata ) {
907+ func (cl * Client ) assistantEvent (ctx context.Context , portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , modelID string , runID string , descriptor * event.BeeperStreamInfo , run aistream.Run , timestamp time. Time ) (* simplevent.PreConvertedMessage , * aiid.MessageMetadata ) {
906908 metadata := & aiid.MessageMetadata {
907909 Role : "assistant" ,
908910 ProviderID : providerID ,
909911 ModelID : modelID ,
910912 RunID : runID ,
911913 StreamStatus : "streaming" ,
912914 }
913- msg := aibridgev2 .Anchor (portalKey , aiid .AssistantUserID (), run , time . Now () )
915+ msg := aibridgev2 .Anchor (portalKey , aiid .AssistantUserID (), run , timestamp )
914916 if len (msg .Data .Parts ) > 0 {
915917 msg .Data .Parts [0 ].ID = aiid .PartID ("text" )
916918 msg .Data .Parts [0 ].Content .BeeperStream = descriptor
@@ -948,9 +950,9 @@ func isZeroAGUIUsage(usage agui.Usage) bool {
948950 usage .ContextLimit == 0
949951}
950952
951- func (cl * Client ) assistantFinalEditWithProjection (portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , modelID string , run aistream.Run , metadata * aiid.MessageMetadata ) * simplevent.Message [* aistream.Run ] {
953+ func (cl * Client ) assistantFinalEditWithProjection (portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , modelID string , run aistream.Run , metadata * aiid.MessageMetadata , timestamp time. Time ) * simplevent.Message [* aistream.Run ] {
952954 initialProjection := aimatrix .ProjectFinal (run , nil )
953- edit := aibridgev2 .FinalMetadataEditWithContent (portalKey , aiid .AssistantUserID (), messageID , run , initialProjection .Content , initialProjection .Extra , time . Now () )
955+ edit := aibridgev2 .FinalMetadataEditWithContent (portalKey , aiid .AssistantUserID (), messageID , run , initialProjection .Content , initialProjection .Extra , timestamp )
954956 edit .ConvertEditFunc = func (ctx context.Context , portal * bridgev2.Portal , intent bridgev2.MatrixAPI , existing []* database.Message , data * aistream.Run ) (* bridgev2.ConvertedEdit , error ) {
955957 if len (existing ) == 0 {
956958 return nil , nil
@@ -982,12 +984,12 @@ func (cl *Client) assistantFinalEditWithProjection(portalKey networkid.PortalKey
982984 return edit
983985}
984986
985- func (cl * Client ) queueAssistantFinal (portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , model ai.Model , run aistream.Run , message ai.Message , metadata * aiid.MessageMetadata ) {
987+ func (cl * Client ) queueAssistantFinal (portalKey networkid.PortalKey , messageID networkid.MessageID , providerID string , model ai.Model , run aistream.Run , message ai.Message , metadata * aiid.MessageMetadata , timestamp time. Time ) {
986988 if cl == nil || cl .UserLogin == nil {
987989 return
988990 }
989991 run = finalizedAssistantRun (run , message , model .ContextWindow )
990- cl .UserLogin .QueueRemoteEvent (cl .assistantFinalEditWithProjection (portalKey , messageID , providerID , model .ID , run , metadata ))
992+ cl .UserLogin .QueueRemoteEvent (cl .assistantFinalEditWithProjection (portalKey , messageID , providerID , model .ID , run , metadata , timestamp ))
991993}
992994
993995func uploadFinalPartsRef (ctx context.Context , portal * bridgev2.Portal , intent bridgev2.MatrixAPI , run aistream.Run , message aistream.UIMessage ) (* aistream.FinalPartsRef , error ) {
@@ -1070,7 +1072,11 @@ func (cl *Client) assistantStreamPublisher(publisher bridgev2.BeeperStreamPublis
10701072 }
10711073 runID := session .CreateSessionID ()
10721074 messageID := aiid .AssistantMessageID (runID )
1073- run := aistream .NewRun (runID , meta .SessionID , provider .ID + "/" + model .ID , string (aiid .AssistantUserID ()), "AI" , time .Now ())
1075+ anchorTimestamp := time .Now ()
1076+ if active := cl .getActiveRun (portal .PortalKey ); active != nil {
1077+ anchorTimestamp = active .nextAssistantAnchorTimestamp ()
1078+ }
1079+ run := aistream .NewRun (runID , meta .SessionID , provider .ID + "/" + model .ID , string (aiid .AssistantUserID ()), "AI" , anchorTimestamp )
10741080 run .MessageID = string (messageID )
10751081 enrichAIRunMetadata (run , model , options )
10761082 descriptor , err := publisher .NewDescriptor (ctx , portal .MXID , aiid .StreamType )
@@ -1081,7 +1087,7 @@ func (cl *Client) assistantStreamPublisher(publisher bridgev2.BeeperStreamPublis
10811087 cl .logStreamDebug (ctx , portal .MXID , "" , run , "Created AI stream descriptor" , func (evt * zerolog.Event ) {
10821088 evt .Str ("stream_type" , descriptor .Type ).Str ("descriptor_user_id" , string (descriptor .UserID ))
10831089 })
1084- assistantEvent , metadata := cl .assistantEvent (ctx , portal .PortalKey , messageID , provider .ID , model .ID , runID , descriptor , * run )
1090+ assistantEvent , metadata := cl .assistantEvent (ctx , portal .PortalKey , messageID , provider .ID , model .ID , runID , descriptor , * run , anchorTimestamp )
10851091 eventID , err := cl .queueAssistantStreamAnchor (ctx , portal , assistantEvent , messageID )
10861092 if err != nil {
10871093 cl .logStreamError (err , portal .MXID , "" , run , "Failed to queue AI stream anchor" )
@@ -1090,21 +1096,22 @@ func (cl *Client) assistantStreamPublisher(publisher bridgev2.BeeperStreamPublis
10901096 cl .logStreamDebug (ctx , portal .MXID , eventID , run , "Queued AI stream anchor" )
10911097 if err := publisher .Register (ctx , portal .MXID , eventID , descriptor ); err != nil {
10921098 cl .logStreamError (err , portal .MXID , eventID , run , "Failed to register AI stream publisher" )
1093- cl .queueAssistantRunError (portal .PortalKey , messageID , provider .ID , model .ID , runID , * run , metadata , err )
1099+ cl .queueAssistantRunError (portal .PortalKey , messageID , provider .ID , model .ID , runID , * run , metadata , afterMatrixTimestamp ( anchorTimestamp ), err )
10941100 return hookStreamError (err )
10951101 }
10961102 cl .logStreamDebug (ctx , portal .MXID , eventID , run , "Registered AI stream publisher" , func (evt * zerolog.Event ) {
10971103 evt .Str ("stream_type" , descriptor .Type )
10981104 })
10991105 if active := cl .getActiveRun (portal .PortalKey ); active != nil {
11001106 stream := & assistantStreamState {
1101- messageID : messageID ,
1102- eventID : eventID ,
1103- runID : runID ,
1104- run : run ,
1105- metadata : metadata ,
1106- sources : newSourceCollector (),
1107- publish : streamPublishCursor {nextSeq : 1 },
1107+ messageID : messageID ,
1108+ eventID : eventID ,
1109+ runID : runID ,
1110+ run : run ,
1111+ metadata : metadata ,
1112+ anchorTimestamp : anchorTimestamp ,
1113+ sources : newSourceCollector (),
1114+ publish : streamPublishCursor {nextSeq : 1 },
11081115 }
11091116 stream .publish .persist = func (ctx context.Context , run * aistream.Run ) error {
11101117 return cl .persistActiveStream (ctx , portal , provider .ID , model .ID , active , stream , run )
@@ -1389,6 +1396,7 @@ func (cl *Client) persistActiveStream(ctx context.Context, portal *bridgev2.Port
13891396 Run : * run ,
13901397 Metadata : metadata ,
13911398 StatusInfo : statusInfo ,
1399+ CreatedAt : stream .anchorTimestamp ,
13921400 UpdatedAt : time .Now (),
13931401 })
13941402}
@@ -1950,6 +1958,17 @@ func matrixEventTime(evt *event.Event) time.Time {
19501958 return time .UnixMilli (evt .Timestamp )
19511959}
19521960
1961+ func afterMatrixTimestamp (base time.Time ) time.Time {
1962+ if base .IsZero () {
1963+ return time .Now ()
1964+ }
1965+ min := base .Add (time .Millisecond )
1966+ if now := time .Now (); now .After (min ) {
1967+ return now
1968+ }
1969+ return min
1970+ }
1971+
19531972func (cl * Client ) setActiveHarness (key networkid.PortalKey , h * harness.AgentHarness ) {
19541973 cl .activeMu .Lock ()
19551974 defer cl .activeMu .Unlock ()
@@ -2108,7 +2127,7 @@ func (cl *Client) finishActiveStreamRecord(ctx context.Context, record aidb.Acti
21082127 cl .queueTerminalActiveStreamFinal (ctx , record )
21092128 default :
21102129 cl .finalizeInterruptedSessionTurn (ctx , record , err )
2111- cl .queueAssistantRunError (record .PortalKey , record .MessageID , record .ProviderID , record .ModelID , record .RunID , record .Run , & record .Metadata , err )
2130+ cl .queueAssistantRunError (record .PortalKey , record .MessageID , record .ProviderID , record .ModelID , record .RunID , record .Run , & record .Metadata , afterMatrixTimestamp ( record . CreatedAt ), err )
21122131 cl .sendActiveStreamRetriableStatus (ctx , record , err )
21132132 }
21142133}
@@ -2129,7 +2148,7 @@ func (cl *Client) queueTerminalActiveStreamFinal(ctx context.Context, record aid
21292148 metadata .StopReason = string (ai .StopReasonError )
21302149 }
21312150 }
2132- cl .UserLogin .QueueRemoteEvent (cl .assistantFinalEditWithProjection (record .PortalKey , record .MessageID , record .ProviderID , record .ModelID , record .Run , & metadata ))
2151+ cl .UserLogin .QueueRemoteEvent (cl .assistantFinalEditWithProjection (record .PortalKey , record .MessageID , record .ProviderID , record .ModelID , record .Run , & metadata , afterMatrixTimestamp ( record . CreatedAt ) ))
21332152}
21342153
21352154func (cl * Client ) finalizeInterruptedSessionTurn (ctx context.Context , record aidb.ActiveStreamRecord , cause error ) {
@@ -2353,11 +2372,18 @@ func (r *activeAIRun) markConsumed(ctx context.Context, cl *Client, entryID stri
23532372 pending := r .pending [0 ]
23542373 r .pending = r .pending [1 :]
23552374 r .consumed = append (r .consumed , pending )
2375+ echoTimestamp := time .Now ()
2376+ if pending != nil && pending .msg != nil {
2377+ echoTimestamp = matrixEventTime (pending .msg .Event )
2378+ }
2379+ if echoTimestamp .After (r .lastConsumedTimestamp ) {
2380+ r .lastConsumedTimestamp = echoTimestamp
2381+ }
23562382 if pending != nil && pending .msg != nil && pending .msg .Event != nil {
23572383 r .status = bridgev2 .StatusEventInfoFromEvent (pending .msg .Event )
23582384 }
23592385 r .mu .Unlock ()
2360- cl .queueConsumedUserEcho (ctx , pending , entryID )
2386+ cl .queueConsumedUserEcho (ctx , pending , entryID , echoTimestamp )
23612387}
23622388
23632389func (r * activeAIRun ) failAll (ctx context.Context , cl * Client , err error ) {
@@ -2386,6 +2412,12 @@ func (r *activeAIRun) addAssistantStream(stream *assistantStreamState) {
23862412 r .streams = append (r .streams , stream )
23872413}
23882414
2415+ func (r * activeAIRun ) nextAssistantAnchorTimestamp () time.Time {
2416+ r .mu .Lock ()
2417+ defer r .mu .Unlock ()
2418+ return afterMatrixTimestamp (r .lastConsumedTimestamp )
2419+ }
2420+
23892421func (r * activeAIRun ) hasAssistantRun (runID string ) bool {
23902422 r .mu .Lock ()
23912423 defer r .mu .Unlock ()
@@ -2479,7 +2511,7 @@ func (r *activeAIRun) finalizeAssistant(ctx context.Context, cl *Client, provide
24792511
24802512 fillAssistantMetadata (stream .metadata , stream .entryID , providerID , model .ID , stream .runID , message )
24812513 appendToolOutputs (stream .run , stream .tools , message )
2482- cl .queueAssistantFinal (r .portalKey , stream .messageID , providerID , model , * stream .run , message , stream .metadata )
2514+ cl .queueAssistantFinal (r .portalKey , stream .messageID , providerID , model , * stream .run , message , stream .metadata , afterMatrixTimestamp ( stream . anchorTimestamp ) )
24832515 cl .deleteActiveStream (ctx , stream .runID )
24842516 cl .queueAssistantMediaMessages (r .portalKey , stream .messageID , providerID , model .ID , stream .runID , message )
24852517}
@@ -2490,7 +2522,7 @@ func (r *activeAIRun) failOpenAssistant(ctx context.Context, cl *Client, provide
24902522 r .streams = nil
24912523 r .mu .Unlock ()
24922524 for _ , stream := range streams {
2493- cl .queueAssistantRunError (r .portalKey , stream .messageID , providerID , modelID , stream .runID , * stream .run , stream .metadata , err )
2525+ cl .queueAssistantRunError (r .portalKey , stream .messageID , providerID , modelID , stream .runID , * stream .run , stream .metadata , afterMatrixTimestamp ( stream . anchorTimestamp ), err )
24942526 cl .deleteActiveStream (ctx , stream .runID )
24952527 }
24962528}
0 commit comments