@@ -1877,6 +1877,36 @@ Use pandas and summarize findings.`.split("\n"),
18771877 } ) ;
18781878
18791879 describe ( "Memory Integration" , ( ) => {
1880+ const persistedUsage = {
1881+ promptTokens : 10 ,
1882+ completionTokens : 5 ,
1883+ totalTokens : 15 ,
1884+ cachedInputTokens : 0 ,
1885+ reasoningTokens : 0 ,
1886+ } ;
1887+
1888+ const providerUsage = {
1889+ inputTokens : 10 ,
1890+ outputTokens : 5 ,
1891+ totalTokens : 15 ,
1892+ } ;
1893+
1894+ const createAssistantResponseMessages = ( text : string ) : ModelMessage [ ] => [
1895+ {
1896+ role : "assistant" ,
1897+ content : [ { type : "text" , text } ] ,
1898+ } ,
1899+ ] ;
1900+
1901+ const getLastAssistantMessage = async (
1902+ memory : Memory ,
1903+ userId : string ,
1904+ conversationId : string ,
1905+ ) => {
1906+ const messages = await memory . getMessages ( userId , conversationId ) ;
1907+ return [ ...messages ] . reverse ( ) . find ( ( message ) => message . role === "assistant" ) ;
1908+ } ;
1909+
18801910 it ( "should initialize with memory" , ( ) => {
18811911 const memory = new Memory ( {
18821912 storage : new InMemoryStorageAdapter ( ) ,
@@ -1957,6 +1987,166 @@ Use pandas and summarize findings.`.split("\n"),
19571987 // as they're handled by the MemoryManager class
19581988 } ) ;
19591989
1990+ it ( "should persist usage and finish reason in assistant message metadata for generateText" , async ( ) => {
1991+ const memory = new Memory ( {
1992+ storage : new InMemoryStorageAdapter ( ) ,
1993+ } ) ;
1994+
1995+ const agent = new Agent ( {
1996+ name : "TestAgent" ,
1997+ instructions : "Test" ,
1998+ model : mockModel as any ,
1999+ memory,
2000+ } ) ;
2001+
2002+ vi . mocked ( ai . generateText ) . mockResolvedValue ( {
2003+ text : "Persisted response" ,
2004+ content : [ { type : "text" , text : "Persisted response" } ] ,
2005+ reasoning : [ ] ,
2006+ files : [ ] ,
2007+ sources : [ ] ,
2008+ toolCalls : [ ] ,
2009+ toolResults : [ ] ,
2010+ finishReason : "stop" ,
2011+ usage : providerUsage ,
2012+ warnings : [ ] ,
2013+ request : { } ,
2014+ response : {
2015+ id : "test-response" ,
2016+ modelId : "test-model" ,
2017+ timestamp : new Date ( ) ,
2018+ messages : createAssistantResponseMessages ( "Persisted response" ) ,
2019+ } ,
2020+ steps : [ ] ,
2021+ } as any ) ;
2022+
2023+ await agent . generateText ( "Hello" , {
2024+ memory : {
2025+ userId : "user-metadata" ,
2026+ conversationId : "conv-metadata" ,
2027+ options : {
2028+ messageMetadataPersistence : true ,
2029+ } ,
2030+ } ,
2031+ } ) ;
2032+
2033+ const assistantMessage = await getLastAssistantMessage (
2034+ memory ,
2035+ "user-metadata" ,
2036+ "conv-metadata" ,
2037+ ) ;
2038+
2039+ expect ( assistantMessage ) . toBeDefined ( ) ;
2040+ expect ( assistantMessage ?. metadata ) . toEqual (
2041+ expect . objectContaining ( {
2042+ operationId : expect . any ( String ) ,
2043+ usage : persistedUsage ,
2044+ finishReason : "stop" ,
2045+ } ) ,
2046+ ) ;
2047+ } ) ;
2048+
2049+ it ( "should persist usage and finish reason in assistant message metadata for streamText" , async ( ) => {
2050+ const memory = new Memory ( {
2051+ storage : new InMemoryStorageAdapter ( ) ,
2052+ } ) ;
2053+
2054+ const agent = new Agent ( {
2055+ name : "TestAgent" ,
2056+ instructions : "Test" ,
2057+ model : mockModel as any ,
2058+ memory,
2059+ } ) ;
2060+
2061+ vi . mocked ( ai . streamText ) . mockImplementation ( ( args : any ) => {
2062+ const finalResult = {
2063+ text : "Persisted stream response" ,
2064+ finishReason : "stop" ,
2065+ usage : providerUsage ,
2066+ totalUsage : providerUsage ,
2067+ warnings : [ ] ,
2068+ response : {
2069+ id : "stream-response" ,
2070+ modelId : "test-model" ,
2071+ timestamp : new Date ( ) ,
2072+ messages : createAssistantResponseMessages ( "Persisted stream response" ) ,
2073+ } ,
2074+ steps : [ ] ,
2075+ providerMetadata : undefined ,
2076+ } ;
2077+
2078+ const fullStream = ( async function * ( ) {
2079+ try {
2080+ yield {
2081+ type : "start" as const ,
2082+ } ;
2083+ yield {
2084+ type : "text-delta" as const ,
2085+ id : "text-1" ,
2086+ delta : "Persisted stream response" ,
2087+ text : "Persisted stream response" ,
2088+ } ;
2089+ yield {
2090+ type : "finish" as const ,
2091+ finishReason : "stop" ,
2092+ totalUsage : providerUsage ,
2093+ } ;
2094+ } finally {
2095+ await args . onFinish ?.( finalResult ) ;
2096+ }
2097+ } ) ( ) ;
2098+
2099+ return {
2100+ text : Promise . resolve ( "Persisted stream response" ) ,
2101+ textStream : ( async function * ( ) {
2102+ yield "Persisted stream response" ;
2103+ } ) ( ) ,
2104+ fullStream,
2105+ usage : Promise . resolve ( providerUsage ) ,
2106+ finishReason : Promise . resolve ( "stop" ) ,
2107+ warnings : [ ] ,
2108+ toUIMessageStream : vi . fn ( ) ,
2109+ toUIMessageStreamResponse : vi . fn ( ) ,
2110+ pipeUIMessageStreamToResponse : vi . fn ( ) ,
2111+ pipeTextStreamToResponse : vi . fn ( ) ,
2112+ toTextStreamResponse : vi . fn ( ) ,
2113+ partialOutputStream : undefined ,
2114+ } as any ;
2115+ } ) ;
2116+
2117+ const result = await agent . streamText ( "Hello" , {
2118+ memory : {
2119+ userId : "user-stream-metadata" ,
2120+ conversationId : "conv-stream-metadata" ,
2121+ options : {
2122+ messageMetadataPersistence : {
2123+ usage : true ,
2124+ finishReason : true ,
2125+ } ,
2126+ } ,
2127+ } ,
2128+ } ) ;
2129+
2130+ for await ( const _part of result . fullStream ) {
2131+ // Consume stream to trigger mocked onFinish.
2132+ }
2133+
2134+ const assistantMessage = await getLastAssistantMessage (
2135+ memory ,
2136+ "user-stream-metadata" ,
2137+ "conv-stream-metadata" ,
2138+ ) ;
2139+
2140+ expect ( assistantMessage ) . toBeDefined ( ) ;
2141+ expect ( assistantMessage ?. metadata ) . toEqual (
2142+ expect . objectContaining ( {
2143+ operationId : expect . any ( String ) ,
2144+ usage : persistedUsage ,
2145+ finishReason : "stop" ,
2146+ } ) ,
2147+ ) ;
2148+ } ) ;
2149+
19602150 it ( "should read memory but skip persistence when memory.options.readOnly is true" , async ( ) => {
19612151 const memory = new Memory ( {
19622152 storage : new InMemoryStorageAdapter ( ) ,
@@ -2248,6 +2438,7 @@ Use pandas and summarize findings.`.split("\n"),
22482438 conversationPersistence : {
22492439 mode : "finish" ,
22502440 } ,
2441+ messageMetadataPersistence : false ,
22512442 memory : {
22522443 userId : "memory-user" ,
22532444 conversationId : "memory-conv" ,
@@ -2262,6 +2453,9 @@ Use pandas and summarize findings.`.split("\n"),
22622453 mode : "step" ,
22632454 debounceMs : 120 ,
22642455 } ,
2456+ messageMetadataPersistence : {
2457+ usage : true ,
2458+ } ,
22652459 } ,
22662460 } ,
22672461 } ) ;
@@ -2281,6 +2475,10 @@ Use pandas and summarize findings.`.split("\n"),
22812475 mode : "step" ,
22822476 debounceMs : 120 ,
22832477 } ,
2478+ messageMetadataPersistence : {
2479+ usage : true ,
2480+ finishReason : false ,
2481+ } ,
22842482 } ) ;
22852483 } ) ;
22862484
@@ -2305,6 +2503,9 @@ Use pandas and summarize findings.`.split("\n"),
23052503 conversationPersistence : {
23062504 mode : "finish" ,
23072505 } ,
2506+ messageMetadataPersistence : {
2507+ finishReason : true ,
2508+ } ,
23082509 } ,
23092510 } ,
23102511 } ) ;
@@ -2325,6 +2526,10 @@ Use pandas and summarize findings.`.split("\n"),
23252526 conversationPersistence : {
23262527 mode : "finish" ,
23272528 } ,
2529+ messageMetadataPersistence : {
2530+ usage : false ,
2531+ finishReason : true ,
2532+ } ,
23282533 } ) ;
23292534 } ) ;
23302535 } ) ;
0 commit comments