@@ -784,24 +784,30 @@ describe("sanitizeToolCallInputs allowed-name filtering", () => {
784784 expect ( ids ) . toEqual ( expectedIds ) ;
785785 } ) ;
786786
787- it ( "keeps finalized OpenAI Responses blocks and drops interrupted partialJson artifacts" , ( ) => {
787+ it ( "keeps finalized OpenAI Responses calls and drops partialJson streaming artifacts" , ( ) => {
788788 const input = castAgentMessages ( [
789789 {
790790 role : "assistant" ,
791- stopReason : "stop " ,
791+ stopReason : "toolUse " ,
792792 content : [
793793 // complete tool call — kept as-is
794794 { type : "toolCall" , id : "call_ok" , name : "read" , arguments : { path : "/a" } } ,
795- // Finalized OpenAI Responses blocks keep parsed arguments plus
796- // partialJson in persisted history; repair should strip only
797- // partialJson and keep the finished tool call.
795+ // Legacy generic Responses transport persisted finalized toolUse
796+ // turns with partialJson; repair strips the scratch field.
798797 {
799798 type : "toolCall" ,
800799 id : "call_partial|fc_123" ,
801800 name : "Bash" ,
802801 arguments : { command : "ls" } ,
803802 partialJson : '{"command": "ls"}' ,
804803 } ,
804+ {
805+ type : "toolCall" ,
806+ id : "call_empty|fc_789" ,
807+ name : "session_status" ,
808+ arguments : { } ,
809+ partialJson : "" ,
810+ } ,
805811 // Anthropic can persist initialized tool calls with arguments: {}
806812 // plus partialJson if the stream aborts before content_block_stop.
807813 // Those incomplete artifacts must be dropped.
@@ -812,6 +818,15 @@ describe("sanitizeToolCallInputs allowed-name filtering", () => {
812818 arguments : { } ,
813819 partialJson : '{"command":' ,
814820 } ,
821+ // An OpenAI-shaped id and parsed partial arguments do not prove that
822+ // response.output_item.done arrived.
823+ {
824+ type : "toolCall" ,
825+ id : "call_truncated|fc_456" ,
826+ name : "Bash" ,
827+ arguments : { command : "ls" } ,
828+ partialJson : '{"command":"ls"' ,
829+ } ,
815830 // Missing required input is also an interrupted artifact and should drop.
816831 {
817832 type : "toolUse" ,
@@ -827,16 +842,16 @@ describe("sanitizeToolCallInputs allowed-name filtering", () => {
827842 const out = sanitizeToolCallInputs ( input ) ;
828843 const toolCalls = getAssistantToolCallBlocks ( out ) ;
829844 const ids = toolCalls . map ( ( t ) => ( t as { id ?: unknown } ) . id ) ;
830- expect ( ids ) . toEqual ( [ "call_ok" , "call_partial|fc_123" ] ) ;
831- const keptPartial = toolCalls . find ( ( t ) => ( t as { id ?: unknown } ) . id === "call_partial|fc_123 ") ;
832- expect ( keptPartial ) . not . toHaveProperty ( "partialJson" ) ;
845+ expect ( ids ) . toEqual ( [ "call_ok" , "call_partial|fc_123" , "call_empty|fc_789" ] ) ;
846+ expect ( toolCalls [ 1 ] ) . not . toHaveProperty ( "partialJson ") ;
847+ expect ( toolCalls [ 2 ] ) . not . toHaveProperty ( "partialJson" ) ;
833848 } ) ;
834849
835- it ( "strips partialJson and preserves sessions_spawn attachment content " , ( ) => {
850+ it ( "strips finalized partialJson without rewriting sessions_spawn arguments " , ( ) => {
836851 const input = castAgentMessages ( [
837852 {
838853 role : "assistant" ,
839- stopReason : "stop " ,
854+ stopReason : "toolUse " ,
840855 content : [
841856 {
842857 type : "toolCall" ,
@@ -852,15 +867,13 @@ describe("sanitizeToolCallInputs allowed-name filtering", () => {
852867 const out = sanitizeToolCallInputs ( input ) ;
853868 const toolCalls = getAssistantToolCallBlocks ( out ) ;
854869 expect ( toolCalls ) . toHaveLength ( 1 ) ;
855- const spawn = toolCalls [ 0 ] as { id ?: unknown ; arguments ?: unknown } ;
856- // partialJson must be stripped
857- expect ( spawn ) . not . toHaveProperty ( "partialJson" ) ;
858- // sessions_spawn attachment content must be preserved on current main.
859- const args = spawn . arguments as { attachments ?: Array < { content ?: unknown } > } ;
860- expect ( args ?. attachments ?. [ 0 ] ?. content ) . toBe ( "secret data" ) ;
870+ expect ( toolCalls [ 0 ] ) . not . toHaveProperty ( "partialJson" ) ;
871+ expect ( ( toolCalls [ 0 ] as { arguments ?: unknown } ) . arguments ) . toEqual ( {
872+ attachments : [ { content : "secret data" } ] ,
873+ } ) ;
861874 } ) ;
862875
863- it . each ( [ "aborted" , "error" ] as const ) (
876+ it . each ( [ "stop" , " aborted", "error" , "length "] as const ) (
864877 "drops OpenAI Responses partialJson blocks on %s assistant turns" ,
865878 ( stopReason ) => {
866879 const input = castAgentMessages ( [
@@ -873,7 +886,7 @@ describe("sanitizeToolCallInputs allowed-name filtering", () => {
873886 id : "call_partial|fc_123" ,
874887 name : "Bash" ,
875888 arguments : { command : "ls" } ,
876- partialJson : '{"command":"ls"' ,
889+ partialJson : '{"command":"ls"} ' ,
877890 } ,
878891 ] ,
879892 } ,
@@ -936,6 +949,36 @@ describe("sanitizeToolCallInputs allowed-name filtering", () => {
936949 expect ( out ) . toStrictEqual ( [ ] ) ;
937950 } ) ;
938951
952+ it ( "drops signed-thinking assistant turns with partialJson tool calls" , ( ) => {
953+ const input = castAgentMessages ( [
954+ {
955+ role : "assistant" ,
956+ stopReason : "toolUse" ,
957+ content : [
958+ {
959+ type : "thinking" ,
960+ thinking : "Let me run a command." ,
961+ thinkingSignature : "sig_partial" ,
962+ } ,
963+ {
964+ type : "toolCall" ,
965+ id : "call_partial|fc_123" ,
966+ name : "exec" ,
967+ arguments : { } ,
968+ partialJson : '{"command":"ls"}' ,
969+ } ,
970+ ] ,
971+ } ,
972+ ] ) ;
973+
974+ const out = sanitizeToolCallInputs ( input , {
975+ allowedToolNames : [ "exec" ] ,
976+ allowProviderOwnedThinkingReplay : true ,
977+ } ) ;
978+
979+ expect ( out ) . toStrictEqual ( [ ] ) ;
980+ } ) ;
981+
939982 it ( "drops signed-thinking assistant turns when sibling tool calls reuse an id" , ( ) => {
940983 const input = castAgentMessages ( [
941984 {
0 commit comments