@@ -183,3 +183,46 @@ def test_invalid_chunk_returns_empty_parts(self, chunk_value: Any) -> None:
183183 assert out ["text" ] == ""
184184 assert out ["reasoning" ] == ""
185185 assert out ["tool_call_chunks" ] == []
186+
187+
188+ class TestIdlessContinuationChunks :
189+ """Per LangChain ``ToolCallChunk`` semantics, the FIRST chunk for a
190+ tool call carries id+name; later chunks for the same call have
191+ ``id=None, name=None`` and only ``args`` + ``index``. Live tool-call
192+ argument streaming relies on those idless continuation chunks
193+ flowing through ``_extract_chunk_parts`` UNTOUCHED so the upstream
194+ chunk-emission loop can still route them by ``index``.
195+ """
196+
197+ def test_idless_continuation_chunk_preserved_verbatim (self ) -> None :
198+ chunk = _FakeChunk (
199+ tool_call_chunks = [
200+ {"id" : None , "name" : None , "args" : '_path":"/x"}' , "index" : 0 }
201+ ]
202+ )
203+ out = _extract_chunk_parts (chunk )
204+ assert len (out ["tool_call_chunks" ]) == 1
205+ tcc = out ["tool_call_chunks" ][0 ]
206+ assert tcc .get ("id" ) is None
207+ assert tcc .get ("name" ) is None
208+ assert tcc .get ("args" ) == '_path":"/x"}'
209+ assert tcc .get ("index" ) == 0
210+
211+ def test_first_then_idless_sequence_preserves_index (self ) -> None :
212+ """Both chunks for the same call share an ``index`` key — the
213+ index-routing loop in ``stream_new_chat`` depends on it."""
214+ first = _FakeChunk (
215+ tool_call_chunks = [
216+ {"id" : "lc-1" , "name" : "write_file" , "args" : '{"file' , "index" : 0 }
217+ ]
218+ )
219+ cont = _FakeChunk (
220+ tool_call_chunks = [
221+ {"id" : None , "name" : None , "args" : '_path":"/x"}' , "index" : 0 }
222+ ]
223+ )
224+ out_first = _extract_chunk_parts (first )
225+ out_cont = _extract_chunk_parts (cont )
226+ assert out_first ["tool_call_chunks" ][0 ]["index" ] == 0
227+ assert out_cont ["tool_call_chunks" ][0 ]["index" ] == 0
228+ assert out_cont ["tool_call_chunks" ][0 ].get ("id" ) is None
0 commit comments