11import { isToolOrDynamicToolUIPart , type UIMessage } from "ai" ;
22import type { Client } from "../agent/client" ;
3+ import type { Source } from "../react/use-logger" ;
34import {
45 createDiskStore ,
56 createDiskStoreWatcher ,
@@ -14,6 +15,8 @@ import {
1415 type StoredMessage ,
1516} from "./types" ;
1617import type { ID } from "../agent/types" ;
18+ import { stripVTControlCharacters } from "node:util" ;
19+ import { RWLock } from "./rw-lock" ;
1720
1821export type ChatStatus = "idle" | "streaming" | "error" ;
1922
@@ -27,6 +30,7 @@ export interface ChatState {
2730 readonly streamingMessage ?: StoredMessage ;
2831 readonly loading : boolean ;
2932 readonly queuedMessages : StoredMessage [ ] ;
33+ readonly queuedLogs : StoredMessage [ ] ;
3034}
3135
3236export interface ChatManagerOptions {
@@ -67,6 +71,7 @@ export class ChatManager {
6771 private streamingMessage : StoredMessage | undefined ;
6872 private status : ChatStatus = "idle" ;
6973 private queue : StoredMessage [ ] = [ ] ;
74+ private logQueue : StoredMessage [ ] = [ ] ;
7075 private abortController : AbortController | undefined ;
7176 private isProcessingQueue = false ;
7277
@@ -87,7 +92,6 @@ export class ChatManager {
8792 this . serializeMessage = options . serializeMessage ;
8893 this . filterMessages = options . filterMessages ;
8994 this . onError = options . onError ;
90-
9195 // Start disk watcher
9296 this . watcher = createDiskStoreWatcher < StoredChat > ( options . chatsDirectory , {
9397 pollInterval : 1000 ,
@@ -187,6 +191,7 @@ export class ChatManager {
187191 streamingMessage : this . streamingMessage ,
188192 loading : this . loading ,
189193 queuedMessages : this . queue ,
194+ queuedLogs : this . logQueue ,
190195 } ;
191196 }
192197
@@ -288,6 +293,44 @@ export class ChatManager {
288293 }
289294 }
290295
296+ async queueLogMessage ( {
297+ message : logMessage ,
298+ level,
299+ source,
300+ } : {
301+ message : string ;
302+ level : "error" | "log" ;
303+ source : Source ;
304+ } ) : Promise < void > {
305+ const formattedMessage = `(EDIT MODE NOTE) ${ source === "agent" ? "The agent" : "The `blink dev` CLI" } printed the following ${ level } :\n\`\`\`\n${ stripVTControlCharacters ( logMessage ) } \n\`\`\`\n` ;
306+ const message = {
307+ id : crypto . randomUUID ( ) ,
308+ created_at : new Date ( ) . toISOString ( ) ,
309+ role : "user" ,
310+ parts : [ { type : "text" , text : formattedMessage } ] ,
311+ metadata : {
312+ __blink_log : true ,
313+ level,
314+ source,
315+ message : logMessage ,
316+ } ,
317+ mode : "edit" ,
318+ } satisfies StoredMessage ;
319+ this . logQueue . push ( message ) ;
320+ this . notifyListeners ( ) ;
321+ }
322+
323+ private async flushLogQueue (
324+ locked : LockedStoreEntry < StoredChat >
325+ ) : Promise < void > {
326+ if ( this . logQueue . length === 0 ) {
327+ return ;
328+ }
329+ const messages = [ ...this . logQueue ] ;
330+ this . logQueue = [ ] ;
331+ await this . upsertMessages ( messages , locked ) ;
332+ }
333+
291334 /**
292335 * Send a message to the agent
293336 */
@@ -337,6 +380,7 @@ export class ChatManager {
337380 let locked : LockedStoreEntry < StoredChat > | undefined ;
338381 try {
339382 locked = await this . chatStore . lock ( this . chatId ) ;
383+ await this . flushLogQueue ( locked ) ;
340384 let first = true ;
341385 while ( this . queue . length > 0 || first ) {
342386 first = false ;
@@ -484,6 +528,8 @@ export class ChatManager {
484528 this . status = "idle" ;
485529
486530 if ( locked ) {
531+ await this . flushLogQueue ( locked ) ;
532+
487533 this . chat . updated_at = new Date ( ) . toISOString ( ) ;
488534 await locked . set ( this . chat ) ;
489535 await locked . release ( ) ;
@@ -556,6 +602,7 @@ export class ChatManager {
556602 this . streamingMessage = undefined ;
557603 this . status = "idle" ;
558604 this . queue = [ ] ;
605+ this . logQueue = [ ] ;
559606 }
560607
561608 private notifyListeners ( ) : void {
0 commit comments