@@ -76,6 +76,41 @@ function writeCodexSession(filePath, sessionId, messages, options = {}) {
7676 fs . writeFileSync ( filePath , records . map ( ( r ) => JSON . stringify ( r ) ) . join ( '\n' ) + '\n' , 'utf-8' ) ;
7777}
7878
79+ function readJsonlRecords ( filePath ) {
80+ return fs . readFileSync ( filePath , 'utf-8' )
81+ . split ( / \r ? \n / )
82+ . map ( ( line ) => line . trim ( ) )
83+ . filter ( Boolean )
84+ . map ( ( line ) => JSON . parse ( line ) ) ;
85+ }
86+
87+ function assertCodexNativeMessageContent ( filePath ) {
88+ const records = readJsonlRecords ( filePath ) ;
89+ const messages = records . filter ( ( record ) => record && record . type === 'response_item' && record . payload && record . payload . type === 'message' ) ;
90+ assert ( messages . length > 0 , 'derived codex file should contain message records' ) ;
91+ for ( const record of messages ) {
92+ const role = record . payload . role ;
93+ const content = record . payload . content ;
94+ assert ( Array . isArray ( content ) , 'derived codex message content should use native typed content array' ) ;
95+ assert ( content . length === 1 , 'derived codex message content should contain one text item' ) ;
96+ assert ( content [ 0 ] . type === ( role === 'assistant' ? 'output_text' : 'input_text' ) , 'derived codex message content type mismatch' ) ;
97+ assert ( typeof content [ 0 ] . text === 'string' && content [ 0 ] . text , 'derived codex message text missing' ) ;
98+ }
99+ }
100+
101+ function assertClaudeNativeMessageContent ( filePath ) {
102+ const records = readJsonlRecords ( filePath ) ;
103+ assert ( records . length > 0 , 'derived claude file should contain message records' ) ;
104+ for ( const record of records ) {
105+ assert ( record . uuid && typeof record . uuid === 'string' , 'derived claude record should include uuid' ) ;
106+ assert ( Object . prototype . hasOwnProperty . call ( record , 'parentUuid' ) , 'derived claude record should include parentUuid' ) ;
107+ assert ( record . message && record . message . role , 'derived claude message role missing' ) ;
108+ const content = record . message . content ;
109+ assert ( Array . isArray ( content ) , 'derived claude message content should use native typed content array' ) ;
110+ assert ( content . length === 1 && content [ 0 ] . type === 'text' && typeof content [ 0 ] . text === 'string' && content [ 0 ] . text , 'derived claude message text missing' ) ;
111+ }
112+ }
113+
79114async function convertAndAssertListed ( api , tmpHome , source , target , params = { } , options = { } ) {
80115 const res = await api ( 'convert-session' , { source, target, ...params } ) ;
81116 assert ( ! res . error , `convert-session ${ source } ->${ target } failed: ${ res . error || '' } ` ) ;
@@ -139,6 +174,7 @@ module.exports = async function testSessionConvertDerived(ctx) {
139174 assert ( detailClaude . messages . length === 2 , 'session-detail(derived claude) should keep exact short length' ) ;
140175 assert ( detailClaude . messages [ 0 ] . text === 'hello' , 'session-detail(derived claude) user text mismatch' ) ;
141176 assert ( detailClaude . messages [ 1 ] . text === 'world' , 'session-detail(derived claude) assistant text mismatch' ) ;
177+ assertClaudeNativeMessageContent ( derivedClaudePath ) ;
142178
143179 const { res : derivedCodexRes , outPath : derivedCodexPath } = await convertAndAssertListed ( api , tmpHome , 'claude' , 'codex' , {
144180 filePath : derivedClaudePath ,
@@ -151,6 +187,7 @@ module.exports = async function testSessionConvertDerived(ctx) {
151187 assert ( detailCodex . messages . length === 2 , 'session-detail(derived codex) should keep exact short length' ) ;
152188 assert ( detailCodex . messages [ 0 ] . text === 'hello' , 'session-detail(derived codex) user text mismatch' ) ;
153189 assert ( detailCodex . messages [ 1 ] . text === 'world' , 'session-detail(derived codex) assistant text mismatch' ) ;
190+ assertCodexNativeMessageContent ( derivedCodexPath ) ;
154191
155192 const { outPath : derivedClaudePath2 } = await convertAndAssertListed ( api , tmpHome , 'codex' , 'claude' , {
156193 sessionId,
@@ -188,6 +225,7 @@ module.exports = async function testSessionConvertDerived(ctx) {
188225 assert ( texts . length === 2 , 'claude derived codex session should keep message count' ) ;
189226 assert ( texts [ 0 ] === 'hello from claude code session' , 'claude derived codex user text mismatch' ) ;
190227 assert ( texts [ 1 ] === 'initialized project' , 'claude derived codex assistant text mismatch' ) ;
228+ assertCodexNativeMessageContent ( claudeDerivedCodexPath ) ;
191229 assert ( sha256File ( claudeSessionPath ) === beforeClaudeHash , 'claude source session should remain unchanged after conversion' ) ;
192230 }
193231
0 commit comments