@@ -1565,6 +1565,66 @@ test("Write tool params prefer file_path even when content appears first", () =>
15651565 assert . equal ( toolMessage . meta ?. paramsMd , filePath ) ;
15661566} ) ;
15671567
1568+ test ( "LLM tool calls without ids receive generated 32 character ids" , async ( ) => {
1569+ const workspace = createTempDir ( "deepcode-tool-call-id-workspace-" ) ;
1570+ const home = createTempDir ( "deepcode-tool-call-id-home-" ) ;
1571+ setHomeDir ( home ) ;
1572+
1573+ const filePath = path . join ( workspace , "note.txt" ) ;
1574+ fs . writeFileSync ( filePath , "hello\n" , "utf8" ) ;
1575+ const plan = "## Task List\n\n- [ ] Inspect current behavior" ;
1576+ const manager = createMockedClientSessionManager ( workspace , [
1577+ {
1578+ choices : [
1579+ {
1580+ message : {
1581+ content : "" ,
1582+ tool_calls : [
1583+ {
1584+ id : "" ,
1585+ type : "function" ,
1586+ function : {
1587+ name : "UpdatePlan" ,
1588+ arguments : JSON . stringify ( { plan, explanation : "Initial plan" } ) ,
1589+ } ,
1590+ } ,
1591+ {
1592+ type : "function" ,
1593+ function : {
1594+ name : "read" ,
1595+ arguments : JSON . stringify ( { file_path : filePath } ) ,
1596+ } ,
1597+ } ,
1598+ ] ,
1599+ } ,
1600+ } ,
1601+ ] ,
1602+ } ,
1603+ createChatResponse ( "done" , { prompt_tokens : 1 , completion_tokens : 1 , total_tokens : 2 } ) ,
1604+ ] ) ;
1605+
1606+ const sessionId = await manager . createSession ( { text : "inspect note" } ) ;
1607+ const assistantMessage = manager
1608+ . listSessionMessages ( sessionId )
1609+ . find ( ( message ) => message . role === "assistant" && ( message . messageParams as any ) ?. tool_calls ) ;
1610+ const toolCalls = ( assistantMessage ?. messageParams as { tool_calls ?: Array < { id ?: unknown } > } | null ) ?. tool_calls ;
1611+
1612+ assert . equal ( toolCalls ?. length , 2 ) ;
1613+ assert . match ( String ( toolCalls ?. [ 0 ] ?. id ) , / ^ [ 0 - 9 a - f ] { 32 } $ / ) ;
1614+ assert . match ( String ( toolCalls ?. [ 1 ] ?. id ) , / ^ [ 0 - 9 a - f ] { 32 } $ / ) ;
1615+ assert . notEqual ( toolCalls ?. [ 0 ] ?. id , toolCalls ?. [ 1 ] ?. id ) ;
1616+
1617+ const toolMessages = manager . listSessionMessages ( sessionId ) . filter ( ( message ) => message . role === "tool" ) ;
1618+ assert . deepEqual (
1619+ toolMessages . map ( ( message ) => ( message . messageParams as { tool_call_id ?: unknown } | null ) ?. tool_call_id ) ,
1620+ toolCalls ?. map ( ( toolCall ) => toolCall . id )
1621+ ) ;
1622+
1623+ const readToolMessage = toolMessages . find ( ( message ) => JSON . parse ( message . content ?? "{}" ) . name === "read" ) ;
1624+ assert . equal ( ( readToolMessage ?. meta ?. function as { name ?: string } | undefined ) ?. name , "read" ) ;
1625+ assert . equal ( readToolMessage ?. meta ?. paramsMd , "note.txt" ) ;
1626+ } ) ;
1627+
15681628test ( "buildOpenAIMessages repairs mixed missing duplicate and orphan tool messages" , ( ) => {
15691629 const manager = createSessionManager ( process . cwd ( ) , "machine-id-mixed-tool-badcase" ) ;
15701630 const assistantMessage = ( manager as any ) . buildAssistantMessage (
0 commit comments