11import assert from 'node:assert' ;
22
3- import type { AgentStreamMessage , AgentStreamMessagePayload } from '@eggjs/tegg-types/agent-runtime' ;
4- import { MessageRole , MessageStatus , AgentObjectType , ContentBlockType } from '@eggjs/tegg-types/agent-runtime' ;
3+ import type {
4+ AgentStreamMessage ,
5+ AgentStreamMessagePayload ,
6+ ToolUseContentBlock ,
7+ ToolResultContentBlock ,
8+ } from '@eggjs/tegg-types/agent-runtime' ;
9+ import {
10+ MessageRole ,
11+ MessageStatus ,
12+ AgentObjectType ,
13+ ContentBlockType ,
14+ } from '@eggjs/tegg-types/agent-runtime' ;
515
6- import { MessageConverter } from '../src/MessageConverter' ;
16+ import { MessageConverter , isTextBlock , isToolUseBlock , isToolResultBlock } from '../src/MessageConverter' ;
717
818describe ( 'test/MessageConverter.test.ts' , ( ) => {
919 describe ( 'toContentBlocks' , ( ) => {
@@ -17,6 +27,7 @@ describe('test/MessageConverter.test.ts', () => {
1727 const result = MessageConverter . toContentBlocks ( payload ) ;
1828 assert . equal ( result . length , 1 ) ;
1929 assert . equal ( result [ 0 ] . type , ContentBlockType . Text ) ;
30+ assert ( isTextBlock ( result [ 0 ] ) ) ;
2031 assert . equal ( result [ 0 ] . text . value , 'hello world' ) ;
2132 assert . deepStrictEqual ( result [ 0 ] . text . annotations , [ ] ) ;
2233 } ) ;
@@ -30,20 +41,89 @@ describe('test/MessageConverter.test.ts', () => {
3041 } ;
3142 const result = MessageConverter . toContentBlocks ( payload ) ;
3243 assert . equal ( result . length , 2 ) ;
44+ assert ( isTextBlock ( result [ 0 ] ) ) ;
3345 assert . equal ( result [ 0 ] . text . value , 'part1' ) ;
46+ assert ( isTextBlock ( result [ 1 ] ) ) ;
3447 assert . equal ( result [ 1 ] . text . value , 'part2' ) ;
3548 } ) ;
3649
37- it ( 'should filter out non-text content parts' , ( ) => {
38- const payload : AgentStreamMessagePayload = {
50+ it ( 'should preserve tool_use content parts' , ( ) => {
51+ const payload = {
3952 content : [
40- { type : 'text' , text : 'keep ' } ,
41- { type : 'image' as 'text ', text : 'discard' } ,
53+ { type : 'text' , text : 'Let me call a tool ' } ,
54+ { type : 'tool_use' , id : 'toolu_123 ', name : 'get_weather' , input : { city : 'beijing' } } ,
4255 ] ,
43- } ;
56+ } as unknown as AgentStreamMessagePayload ;
57+ const result = MessageConverter . toContentBlocks ( payload ) ;
58+ assert . equal ( result . length , 2 ) ;
59+
60+ assert ( isTextBlock ( result [ 0 ] ) ) ;
61+ assert . equal ( result [ 0 ] . text . value , 'Let me call a tool' ) ;
62+
63+ assert ( isToolUseBlock ( result [ 1 ] ) ) ;
64+ assert . equal ( result [ 1 ] . id , 'toolu_123' ) ;
65+ assert . equal ( result [ 1 ] . name , 'get_weather' ) ;
66+ assert . deepStrictEqual ( result [ 1 ] . input , { city : 'beijing' } ) ;
67+ } ) ;
68+
69+ it ( 'should preserve tool_result content parts' , ( ) => {
70+ const payload = {
71+ content : [
72+ { type : 'tool_result' , tool_use_id : 'toolu_123' , content : 'sunny, 25°C' } ,
73+ ] ,
74+ } as unknown as AgentStreamMessagePayload ;
75+ const result = MessageConverter . toContentBlocks ( payload ) ;
76+ assert . equal ( result . length , 1 ) ;
77+
78+ assert ( isToolResultBlock ( result [ 0 ] ) ) ;
79+ assert . equal ( result [ 0 ] . tool_use_id , 'toolu_123' ) ;
80+ assert . equal ( result [ 0 ] . content , 'sunny, 25°C' ) ;
81+ } ) ;
82+
83+ it ( 'should preserve tool_result with is_error flag' , ( ) => {
84+ const payload = {
85+ content : [
86+ { type : 'tool_result' , tool_use_id : 'toolu_456' , content : 'Tool not found' , is_error : true } ,
87+ ] ,
88+ } as unknown as AgentStreamMessagePayload ;
89+ const result = MessageConverter . toContentBlocks ( payload ) ;
90+ assert . equal ( result . length , 1 ) ;
91+
92+ assert ( isToolResultBlock ( result [ 0 ] ) ) ;
93+ assert . equal ( ( result [ 0 ] as ToolResultContentBlock ) . is_error , true ) ;
94+ } ) ;
95+
96+ it ( 'should preserve unknown content types as generic blocks' , ( ) => {
97+ const payload = {
98+ content : [
99+ { type : 'thinking' , thinking : 'let me think...' } ,
100+ ] ,
101+ } as unknown as AgentStreamMessagePayload ;
44102 const result = MessageConverter . toContentBlocks ( payload ) ;
45103 assert . equal ( result . length , 1 ) ;
46- assert . equal ( result [ 0 ] . text . value , 'keep' ) ;
104+ assert . equal ( result [ 0 ] . type , 'thinking' ) ;
105+ assert . equal ( ( result [ 0 ] as any ) . thinking , 'let me think...' ) ;
106+ } ) ;
107+
108+ it ( 'should preserve mixed content types in order' , ( ) => {
109+ const payload = {
110+ content : [
111+ { type : 'text' , text : 'I will search for you' } ,
112+ { type : 'tool_use' , id : 'toolu_1' , name : 'search' , input : { q : 'test' } } ,
113+ { type : 'text' , text : 'Here are the results' } ,
114+ ] ,
115+ } as unknown as AgentStreamMessagePayload ;
116+ const result = MessageConverter . toContentBlocks ( payload ) ;
117+ assert . equal ( result . length , 3 ) ;
118+
119+ assert ( isTextBlock ( result [ 0 ] ) ) ;
120+ assert . equal ( result [ 0 ] . text . value , 'I will search for you' ) ;
121+
122+ assert ( isToolUseBlock ( result [ 1 ] ) ) ;
123+ assert . equal ( result [ 1 ] . name , 'search' ) ;
124+
125+ assert ( isTextBlock ( result [ 2 ] ) ) ;
126+ assert . equal ( result [ 2 ] . text . value , 'Here are the results' ) ;
47127 } ) ;
48128
49129 it ( 'should return empty array for non-string non-array content' , ( ) => {
@@ -66,6 +146,7 @@ describe('test/MessageConverter.test.ts', () => {
66146 assert . equal ( typeof msg . createdAt , 'number' ) ;
67147 const content = msg . content ;
68148 assert . equal ( content . length , 1 ) ;
149+ assert ( isTextBlock ( content [ 0 ] ) ) ;
69150 assert . equal ( content [ 0 ] . text . value , 'reply' ) ;
70151 } ) ;
71152
@@ -74,6 +155,20 @@ describe('test/MessageConverter.test.ts', () => {
74155 const msg = MessageConverter . toMessageObject ( payload ) ;
75156 assert . equal ( msg . runId , undefined ) ;
76157 } ) ;
158+
159+ it ( 'should preserve tool_use blocks in message object' , ( ) => {
160+ const payload = {
161+ content : [
162+ { type : 'text' , text : 'calling tool' } ,
163+ { type : 'tool_use' , id : 'toolu_1' , name : 'search' , input : { q : 'test' } } ,
164+ ] ,
165+ } as unknown as AgentStreamMessagePayload ;
166+ const msg = MessageConverter . toMessageObject ( payload , 'run_1' ) ;
167+ assert . equal ( msg . content . length , 2 ) ;
168+ assert ( isTextBlock ( msg . content [ 0 ] ) ) ;
169+ assert ( isToolUseBlock ( msg . content [ 1 ] ) ) ;
170+ assert . equal ( ( msg . content [ 1 ] as ToolUseContentBlock ) . name , 'search' ) ;
171+ } ) ;
77172 } ) ;
78173
79174 describe ( 'createStreamMessage' , ( ) => {
@@ -99,14 +194,34 @@ describe('test/MessageConverter.test.ts', () => {
99194 const { output, usage } = MessageConverter . extractFromStreamMessages ( messages , 'run_1' ) ;
100195
101196 assert . equal ( output . length , 2 ) ;
197+ assert ( isTextBlock ( output [ 0 ] . content [ 0 ] ) ) ;
102198 assert . equal ( output [ 0 ] . content [ 0 ] . text . value , 'chunk1' ) ;
199+ assert ( isTextBlock ( output [ 1 ] . content [ 0 ] ) ) ;
103200 assert . equal ( output [ 1 ] . content [ 0 ] . text . value , 'chunk2' ) ;
104201 assert . ok ( usage ) ;
105202 assert . equal ( usage . promptTokens , 10 ) ;
106203 assert . equal ( usage . completionTokens , 13 ) ;
107204 assert . equal ( usage . totalTokens , 23 ) ;
108205 } ) ;
109206
207+ it ( 'should extract messages with tool_use content' , ( ) => {
208+ const messages : AgentStreamMessage [ ] = [
209+ {
210+ message : {
211+ content : [
212+ { type : 'text' , text : 'let me search' } ,
213+ { type : 'tool_use' , id : 'toolu_1' , name : 'search' , input : { q : 'test' } } ,
214+ ] as any ,
215+ } ,
216+ } ,
217+ ] ;
218+ const { output } = MessageConverter . extractFromStreamMessages ( messages , 'run_1' ) ;
219+ assert . equal ( output . length , 1 ) ;
220+ assert . equal ( output [ 0 ] . content . length , 2 ) ;
221+ assert ( isTextBlock ( output [ 0 ] . content [ 0 ] ) ) ;
222+ assert ( isToolUseBlock ( output [ 0 ] . content [ 1 ] ) ) ;
223+ } ) ;
224+
110225 it ( 'should return undefined usage when no usage info' , ( ) => {
111226 const messages : AgentStreamMessage [ ] = [ { message : { content : 'data' } } ] ;
112227 const { output, usage } = MessageConverter . extractFromStreamMessages ( messages ) ;
@@ -143,6 +258,7 @@ describe('test/MessageConverter.test.ts', () => {
143258 assert . equal ( result [ 1 ] . role , MessageRole . Assistant ) ;
144259
145260 const content0 = result [ 0 ] . content ;
261+ assert ( isTextBlock ( content0 [ 0 ] ) ) ;
146262 assert . equal ( content0 [ 0 ] . text . value , 'hi' ) ;
147263 } ) ;
148264
@@ -169,14 +285,79 @@ describe('test/MessageConverter.test.ts', () => {
169285 const result = MessageConverter . toInputMessageObjects ( messages ) ;
170286 const content = result [ 0 ] . content ;
171287 assert . equal ( content . length , 2 ) ;
288+ assert ( isTextBlock ( content [ 0 ] ) ) ;
172289 assert . equal ( content [ 0 ] . text . value , 'part1' ) ;
290+ assert ( isTextBlock ( content [ 1 ] ) ) ;
173291 assert . equal ( content [ 1 ] . text . value , 'part2' ) ;
174292 } ) ;
175293
294+ it ( 'should preserve tool_use blocks in input messages' , ( ) => {
295+ const messages = [
296+ {
297+ role : MessageRole . Assistant as MessageRole ,
298+ content : [
299+ { type : 'text' , text : 'I will search for you' } ,
300+ { type : 'tool_use' , id : 'toolu_1' , name : 'search' , input : { q : 'test' } } ,
301+ ] as any ,
302+ } ,
303+ ] ;
304+ const result = MessageConverter . toInputMessageObjects ( messages ) ;
305+ assert . equal ( result [ 0 ] . content . length , 2 ) ;
306+ assert ( isTextBlock ( result [ 0 ] . content [ 0 ] ) ) ;
307+ assert . equal ( result [ 0 ] . content [ 0 ] . text . value , 'I will search for you' ) ;
308+ assert ( isToolUseBlock ( result [ 0 ] . content [ 1 ] ) ) ;
309+ assert . equal ( ( result [ 0 ] . content [ 1 ] as ToolUseContentBlock ) . name , 'search' ) ;
310+ } ) ;
311+
312+ it ( 'should preserve tool_result blocks in input messages' , ( ) => {
313+ const messages = [
314+ {
315+ role : MessageRole . User as MessageRole ,
316+ content : [
317+ { type : 'tool_result' , tool_use_id : 'toolu_1' , content : 'search result here' } ,
318+ ] as any ,
319+ } ,
320+ ] ;
321+ const result = MessageConverter . toInputMessageObjects ( messages ) ;
322+ assert . equal ( result [ 0 ] . content . length , 1 ) ;
323+ assert ( isToolResultBlock ( result [ 0 ] . content [ 0 ] ) ) ;
324+ assert . equal ( ( result [ 0 ] . content [ 0 ] as ToolResultContentBlock ) . tool_use_id , 'toolu_1' ) ;
325+ assert . equal ( ( result [ 0 ] . content [ 0 ] as ToolResultContentBlock ) . content , 'search result here' ) ;
326+ } ) ;
327+
176328 it ( 'should work without threadId' , ( ) => {
177329 const messages = [ { role : MessageRole . User as MessageRole , content : 'hi' } ] ;
178330 const result = MessageConverter . toInputMessageObjects ( messages ) ;
179331 assert . equal ( result [ 0 ] . threadId , undefined ) ;
180332 } ) ;
181333 } ) ;
334+
335+ describe ( 'type guards' , ( ) => {
336+ it ( 'isTextBlock should narrow to TextContentBlock' , ( ) => {
337+ const block = { type : ContentBlockType . Text , text : { value : 'hello' , annotations : [ ] } } ;
338+ assert ( isTextBlock ( block ) ) ;
339+ assert . equal ( block . text . value , 'hello' ) ;
340+ } ) ;
341+
342+ it ( 'isToolUseBlock should narrow to ToolUseContentBlock' , ( ) => {
343+ const block = { type : ContentBlockType . ToolUse , id : 'toolu_1' , name : 'search' , input : { q : 'test' } } ;
344+ assert ( isToolUseBlock ( block ) ) ;
345+ assert . equal ( block . name , 'search' ) ;
346+ } ) ;
347+
348+ it ( 'isToolResultBlock should narrow to ToolResultContentBlock' , ( ) => {
349+ const block = { type : ContentBlockType . ToolResult , tool_use_id : 'toolu_1' , content : 'result' } ;
350+ assert ( isToolResultBlock ( block ) ) ;
351+ assert . equal ( block . tool_use_id , 'toolu_1' ) ;
352+ } ) ;
353+
354+ it ( 'type guards should return false for non-matching blocks' , ( ) => {
355+ const textBlock = { type : ContentBlockType . Text , text : { value : 'hi' , annotations : [ ] } } ;
356+ const toolUseBlock = { type : ContentBlockType . ToolUse , id : 'id' , name : 'n' , input : { } } ;
357+
358+ assert ( ! isToolUseBlock ( textBlock ) ) ;
359+ assert ( ! isToolResultBlock ( textBlock ) ) ;
360+ assert ( ! isTextBlock ( toolUseBlock ) ) ;
361+ } ) ;
362+ } ) ;
182363} ) ;
0 commit comments