agenthub-python 0.4.14__tar.gz → 0.4.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/PKG-INFO +1 -1
  2. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/deepseek_v4/client.py +53 -26
  3. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/gpt6/client.py +53 -26
  4. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/minimax_m3/client.py +51 -24
  5. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_responses/client.py +53 -26
  6. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/types.py +3 -0
  7. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/pyproject.toml +1 -1
  8. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/pyproject.toml.orig +1 -1
  9. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/README.md +0 -0
  10. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/__init__.py +0 -0
  11. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/abort_signal.py +0 -0
  12. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/ant_messages/__init__.py +0 -0
  13. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/ant_messages/client.py +0 -0
  14. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/auto_client.py +0 -0
  15. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/base_client.py +0 -0
  16. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/claude5/__init__.py +0 -0
  17. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/claude5/client.py +0 -0
  18. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/deepseek_v4/__init__.py +0 -0
  19. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/errors.py +0 -0
  20. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/gemini3_8/__init__.py +0 -0
  21. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/gemini3_8/client.py +0 -0
  22. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/glm5_3/__init__.py +0 -0
  23. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/glm5_3/client.py +0 -0
  24. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/gpt6/__init__.py +0 -0
  25. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/integration/__init__.py +0 -0
  26. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/integration/playground.py +0 -0
  27. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/integration/tracer.py +0 -0
  28. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/kimi_k3/__init__.py +0 -0
  29. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/kimi_k3/client.py +0 -0
  30. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/minimax_m3/__init__.py +0 -0
  31. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_chat/__init__.py +0 -0
  32. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_chat/client.py +0 -0
  33. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_chat_vllm_adapter/__init__.py +0 -0
  34. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_chat_vllm_adapter/client.py +0 -0
  35. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_embedding/__init__.py +0 -0
  36. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_embedding/client.py +0 -0
  37. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/openai_responses/__init__.py +0 -0
  38. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/registry.py +0 -0
  39. {agenthub_python-0.4.14 → agenthub_python-0.4.15}/agenthub/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthub-python
3
- Version: 0.4.14
3
+ Version: 0.4.15
4
4
  Summary: AgentHub is the LLM API Hub for the Agent era, built for high-precision autonomous agents.
5
5
  Keywords: agent,llm,gemini,claude,gpt
6
6
  Author: PrismShadow
@@ -260,6 +260,7 @@ class DeepSeekV4Client(LLMClient):
260
260
  "name": model_output.item.name,
261
261
  "arguments": "",
262
262
  "tool_call_id": model_output.item.call_id,
263
+ "item_id": model_output.item.id,
263
264
  }
264
265
  )
265
266
  else:
@@ -268,11 +269,27 @@ class DeepSeekV4Client(LLMClient):
268
269
  elif deepseek_event_type == "response.function_call_arguments.delta":
269
270
  event_type = "delta"
270
271
  content_items.append(
271
- {"type": "partial_tool_call", "name": "", "arguments": model_output.delta, "tool_call_id": ""}
272
+ {
273
+ "type": "partial_tool_call",
274
+ "name": "",
275
+ "arguments": model_output.delta,
276
+ "tool_call_id": "",
277
+ "item_id": model_output.item_id,
278
+ }
272
279
  )
273
280
 
274
281
  elif deepseek_event_type == "response.function_call_arguments.done":
282
+ # a stop naming the item closes that call
275
283
  event_type = "stop"
284
+ content_items.append(
285
+ {
286
+ "type": "partial_tool_call",
287
+ "name": "",
288
+ "arguments": "",
289
+ "tool_call_id": "",
290
+ "item_id": model_output.item_id,
291
+ }
292
+ )
276
293
 
277
294
  elif deepseek_event_type in ("response.completed", "response.incomplete"):
278
295
  event_type = "stop"
@@ -334,51 +351,61 @@ class DeepSeekV4Client(LLMClient):
334
351
  # Use unified message conversion
335
352
  input_list = self.transform_uni_message_to_model_input(messages)
336
353
 
354
+ # Calls still streaming, keyed by the item id their fragments carry (the call id when a
355
+ # server sends none): a gateway may open several before closing any of them.
356
+ open_tool_calls: dict[str, dict] = {}
357
+ last_opened = ""
358
+
359
+ def key_of(item_id: str | None) -> str:
360
+ return item_id if item_id in open_tool_calls else last_opened
361
+
337
362
  # Stream generate
338
- partial_tool_call = {}
339
363
  stream = await self._client.responses.create(**deepseek_config, input=input_list, stream=True)
340
- async for event in stream:
341
- event = self.transform_model_output_to_uni_event(event)
364
+ async for model_event in stream:
365
+ event = self.transform_model_output_to_uni_event(model_event)
366
+ fragments = [item for item in event["content_items"] if item["type"] == "partial_tool_call"]
342
367
  if event["event_type"] == "start":
343
- for item in event["content_items"]:
344
- if item["type"] == "partial_tool_call":
345
- # initialize partial_tool_call
346
- partial_tool_call = {
347
- "name": item["name"],
348
- "arguments": "",
349
- "tool_call_id": item["tool_call_id"],
350
- }
351
- yield event
368
+ for item in fragments:
369
+ last_opened = item.get("item_id") or item["tool_call_id"]
370
+ open_tool_calls[last_opened] = {
371
+ "name": item["name"],
372
+ "tool_call_id": item["tool_call_id"],
373
+ "arguments": "",
374
+ }
375
+ yield event
352
376
  elif event["event_type"] == "delta":
353
- for item in event["content_items"]:
354
- if item["type"] == "partial_tool_call":
355
- # update partial_tool_call
356
- partial_tool_call["arguments"] += item["arguments"]
357
-
377
+ for item in fragments:
378
+ tool_call = open_tool_calls.get(key_of(item.get("item_id")))
379
+ if tool_call is not None:
380
+ tool_call["arguments"] += item["arguments"]
358
381
  yield event
359
382
  elif event["event_type"] == "stop":
360
- if "name" in partial_tool_call and "arguments" in partial_tool_call:
361
- # finish partial_tool_call
383
+ # a stop that names calls closes them; the end of the response closes whatever
384
+ # a gateway never closed on its own
385
+ closing = [key_of(item.get("item_id")) for item in fragments] or list(open_tool_calls)
386
+ for key in closing:
387
+ tool_call = open_tool_calls.pop(key, None)
388
+ if tool_call is None:
389
+ continue
362
390
  yield {
363
391
  "role": "assistant",
364
392
  "event_type": "delta",
365
393
  "content_items": [
366
394
  {
367
395
  "type": "tool_call",
368
- "name": partial_tool_call["name"],
396
+ "name": tool_call["name"],
369
397
  "arguments": parse_tool_call_arguments(
370
- partial_tool_call["arguments"],
398
+ tool_call["arguments"],
371
399
  self.__class__.__name__,
372
- partial_tool_call["name"],
373
- partial_tool_call["tool_call_id"],
400
+ tool_call["name"],
401
+ tool_call["tool_call_id"],
374
402
  ),
375
- "tool_call_id": partial_tool_call["tool_call_id"],
403
+ "tool_call_id": tool_call["tool_call_id"],
376
404
  }
377
405
  ],
378
406
  "usage_metadata": None,
379
407
  "finish_reason": None,
380
408
  }
381
- partial_tool_call = {}
382
409
 
383
410
  if event["finish_reason"] or event["usage_metadata"]:
384
411
  yield event
@@ -272,6 +272,7 @@ class GPT6Client(LLMClient):
272
272
  "name": model_output.item.name,
273
273
  "arguments": "",
274
274
  "tool_call_id": model_output.item.call_id,
275
+ "item_id": model_output.item.id,
275
276
  }
276
277
  )
277
278
  elif model_output.item.type == "message":
@@ -288,11 +289,27 @@ class GPT6Client(LLMClient):
288
289
  elif openai_event_type == "response.function_call_arguments.delta":
289
290
  event_type = "delta"
290
291
  content_items.append(
291
- {"type": "partial_tool_call", "name": "", "arguments": model_output.delta, "tool_call_id": ""}
292
+ {
293
+ "type": "partial_tool_call",
294
+ "name": "",
295
+ "arguments": model_output.delta,
296
+ "tool_call_id": "",
297
+ "item_id": model_output.item_id,
298
+ }
292
299
  )
293
300
 
294
301
  elif openai_event_type == "response.function_call_arguments.done":
302
+ # a stop naming the item closes that call
295
303
  event_type = "stop"
304
+ content_items.append(
305
+ {
306
+ "type": "partial_tool_call",
307
+ "name": "",
308
+ "arguments": "",
309
+ "tool_call_id": "",
310
+ "item_id": model_output.item_id,
311
+ }
312
+ )
296
313
 
297
314
  elif openai_event_type in ("response.completed", "response.incomplete"):
298
315
  event_type = "stop"
@@ -382,51 +399,61 @@ class GPT6Client(LLMClient):
382
399
  # Use unified message conversion
383
400
  input_list = self.transform_uni_message_to_model_input(messages)
384
401
 
402
+ # Calls still streaming, keyed by the item id their fragments carry (the call id when a
403
+ # server sends none): a gateway may open several before closing any of them.
404
+ open_tool_calls: dict[str, dict] = {}
405
+ last_opened = ""
406
+
407
+ def key_of(item_id: str | None) -> str:
408
+ return item_id if item_id in open_tool_calls else last_opened
409
+
385
410
  # Stream generate
386
- partial_tool_call = {}
387
411
  stream = await self._client.responses.create(**openai_config, input=input_list, stream=True)
388
- async for event in stream:
389
- event = self.transform_model_output_to_uni_event(event)
412
+ async for model_event in stream:
413
+ event = self.transform_model_output_to_uni_event(model_event)
414
+ fragments = [item for item in event["content_items"] if item["type"] == "partial_tool_call"]
390
415
  if event["event_type"] == "start":
391
- for item in event["content_items"]:
392
- if item["type"] == "partial_tool_call":
393
- # initialize partial_tool_call
394
- partial_tool_call = {
395
- "name": item["name"],
396
- "arguments": "",
397
- "tool_call_id": item["tool_call_id"],
398
- }
399
- yield event
416
+ for item in fragments:
417
+ last_opened = item.get("item_id") or item["tool_call_id"]
418
+ open_tool_calls[last_opened] = {
419
+ "name": item["name"],
420
+ "tool_call_id": item["tool_call_id"],
421
+ "arguments": "",
422
+ }
423
+ yield event
400
424
  elif event["event_type"] == "delta":
401
- for item in event["content_items"]:
402
- if item["type"] == "partial_tool_call":
403
- # update partial_tool_call
404
- partial_tool_call["arguments"] += item["arguments"]
405
-
425
+ for item in fragments:
426
+ tool_call = open_tool_calls.get(key_of(item.get("item_id")))
427
+ if tool_call is not None:
428
+ tool_call["arguments"] += item["arguments"]
406
429
  yield event
407
430
  elif event["event_type"] == "stop":
408
- if "name" in partial_tool_call and "arguments" in partial_tool_call:
409
- # finish partial_tool_call
431
+ # a stop that names calls closes them; the end of the response closes whatever
432
+ # a gateway never closed on its own
433
+ closing = [key_of(item.get("item_id")) for item in fragments] or list(open_tool_calls)
434
+ for key in closing:
435
+ tool_call = open_tool_calls.pop(key, None)
436
+ if tool_call is None:
437
+ continue
410
438
  yield {
411
439
  "role": "assistant",
412
440
  "event_type": "delta",
413
441
  "content_items": [
414
442
  {
415
443
  "type": "tool_call",
416
- "name": partial_tool_call["name"],
444
+ "name": tool_call["name"],
417
445
  "arguments": parse_tool_call_arguments(
418
- partial_tool_call["arguments"],
446
+ tool_call["arguments"],
419
447
  self.__class__.__name__,
420
- partial_tool_call["name"],
421
- partial_tool_call["tool_call_id"],
448
+ tool_call["name"],
449
+ tool_call["tool_call_id"],
422
450
  ),
423
- "tool_call_id": partial_tool_call["tool_call_id"],
451
+ "tool_call_id": tool_call["tool_call_id"],
424
452
  }
425
453
  ],
426
454
  "usage_metadata": None,
427
455
  "finish_reason": None,
428
456
  }
429
- partial_tool_call = {}
430
457
 
431
458
  if event["finish_reason"] or event["usage_metadata"]:
432
459
  yield event
@@ -217,17 +217,34 @@ class MiniMaxM3Client(LLMClient):
217
217
  "name": model_output.item.name,
218
218
  "arguments": "",
219
219
  "tool_call_id": model_output.item.call_id,
220
+ "item_id": model_output.item.id,
220
221
  }
221
222
  )
222
223
 
223
224
  elif minimax_event_type == "response.function_call_arguments.delta":
224
225
  event_type = "delta"
225
226
  content_items.append(
226
- {"type": "partial_tool_call", "name": "", "arguments": model_output.delta, "tool_call_id": ""}
227
+ {
228
+ "type": "partial_tool_call",
229
+ "name": "",
230
+ "arguments": model_output.delta,
231
+ "tool_call_id": "",
232
+ "item_id": model_output.item_id,
233
+ }
227
234
  )
228
235
 
229
236
  elif minimax_event_type == "response.function_call_arguments.done":
237
+ # a stop naming the item closes that call
230
238
  event_type = "stop"
239
+ content_items.append(
240
+ {
241
+ "type": "partial_tool_call",
242
+ "name": "",
243
+ "arguments": "",
244
+ "tool_call_id": "",
245
+ "item_id": model_output.item_id,
246
+ }
247
+ )
231
248
 
232
249
  elif minimax_event_type in ("response.completed", "response.incomplete"):
233
250
  event_type = "stop"
@@ -279,50 +296,60 @@ class MiniMaxM3Client(LLMClient):
279
296
  minimax_config = self.transform_uni_config_to_model_config(config)
280
297
  input_list = self.transform_uni_message_to_model_input(messages)
281
298
 
282
- partial_tool_call = {}
299
+ # Calls still streaming, keyed by the item id their fragments carry (the call id when a
300
+ # server sends none): a gateway may open several before closing any of them.
301
+ open_tool_calls: dict[str, dict] = {}
302
+ last_opened = ""
303
+
304
+ def key_of(item_id: str | None) -> str:
305
+ return item_id if item_id in open_tool_calls else last_opened
306
+
283
307
  stream = await self._client.responses.create(**minimax_config, input=input_list, stream=True)
284
308
  async for model_event in stream:
285
309
  event = self.transform_model_output_to_uni_event(model_event)
310
+ fragments = [item for item in event["content_items"] if item["type"] == "partial_tool_call"]
286
311
  if event["event_type"] == "start":
287
- for item in event["content_items"]:
288
- if item["type"] == "partial_tool_call":
289
- # initialize partial_tool_call
290
- partial_tool_call = {
291
- "name": item["name"],
292
- "arguments": "",
293
- "tool_call_id": item["tool_call_id"],
294
- }
295
- yield event
312
+ for item in fragments:
313
+ last_opened = item.get("item_id") or item["tool_call_id"]
314
+ open_tool_calls[last_opened] = {
315
+ "name": item["name"],
316
+ "tool_call_id": item["tool_call_id"],
317
+ "arguments": "",
318
+ }
319
+ yield event
296
320
  elif event["event_type"] == "delta":
297
- for item in event["content_items"]:
298
- if item["type"] == "partial_tool_call":
299
- # update partial_tool_call
300
- partial_tool_call["arguments"] += item["arguments"]
301
-
321
+ for item in fragments:
322
+ tool_call = open_tool_calls.get(key_of(item.get("item_id")))
323
+ if tool_call is not None:
324
+ tool_call["arguments"] += item["arguments"]
302
325
  yield event
303
326
  elif event["event_type"] == "stop":
304
- if "name" in partial_tool_call and "arguments" in partial_tool_call:
305
- # finish partial_tool_call
327
+ # a stop that names calls closes them; the end of the response closes whatever
328
+ # a gateway never closed on its own
329
+ closing = [key_of(item.get("item_id")) for item in fragments] or list(open_tool_calls)
330
+ for key in closing:
331
+ tool_call = open_tool_calls.pop(key, None)
332
+ if tool_call is None:
333
+ continue
306
334
  yield {
307
335
  "role": "assistant",
308
336
  "event_type": "delta",
309
337
  "content_items": [
310
338
  {
311
339
  "type": "tool_call",
312
- "name": partial_tool_call["name"],
340
+ "name": tool_call["name"],
313
341
  "arguments": parse_tool_call_arguments(
314
- partial_tool_call["arguments"],
342
+ tool_call["arguments"],
315
343
  self.__class__.__name__,
316
- partial_tool_call["name"],
317
- partial_tool_call["tool_call_id"],
344
+ tool_call["name"],
345
+ tool_call["tool_call_id"],
318
346
  ),
319
- "tool_call_id": partial_tool_call["tool_call_id"],
347
+ "tool_call_id": tool_call["tool_call_id"],
320
348
  }
321
349
  ],
322
350
  "usage_metadata": None,
323
351
  "finish_reason": None,
324
352
  }
325
- partial_tool_call = {}
326
353
 
327
354
  if event["finish_reason"] or event["usage_metadata"]:
328
355
  yield event
@@ -268,6 +268,7 @@ class OpenaiResponsesClient(LLMClient):
268
268
  "name": model_output.item.name,
269
269
  "arguments": "",
270
270
  "tool_call_id": model_output.item.call_id,
271
+ "item_id": model_output.item.id,
271
272
  }
272
273
  )
273
274
  elif model_output.item.type == "message" and getattr(model_output.item, "phase", None):
@@ -295,11 +296,27 @@ class OpenaiResponsesClient(LLMClient):
295
296
  elif openai_event_type == "response.function_call_arguments.delta":
296
297
  event_type = "delta"
297
298
  content_items.append(
298
- {"type": "partial_tool_call", "name": "", "arguments": model_output.delta, "tool_call_id": ""}
299
+ {
300
+ "type": "partial_tool_call",
301
+ "name": "",
302
+ "arguments": model_output.delta,
303
+ "tool_call_id": "",
304
+ "item_id": model_output.item_id,
305
+ }
299
306
  )
300
307
 
301
308
  elif openai_event_type == "response.function_call_arguments.done":
309
+ # a stop naming the item closes that call
302
310
  event_type = "stop"
311
+ content_items.append(
312
+ {
313
+ "type": "partial_tool_call",
314
+ "name": "",
315
+ "arguments": "",
316
+ "tool_call_id": "",
317
+ "item_id": model_output.item_id,
318
+ }
319
+ )
303
320
 
304
321
  elif openai_event_type in ("response.completed", "response.incomplete"):
305
322
  event_type = "stop"
@@ -364,51 +381,61 @@ class OpenaiResponsesClient(LLMClient):
364
381
  # Use unified message conversion
365
382
  input_list = self.transform_uni_message_to_model_input(messages)
366
383
 
384
+ # Calls still streaming, keyed by the item id their fragments carry (the call id when a
385
+ # server sends none): a gateway may open several before closing any of them.
386
+ open_tool_calls: dict[str, dict] = {}
387
+ last_opened = ""
388
+
389
+ def key_of(item_id: str | None) -> str:
390
+ return item_id if item_id in open_tool_calls else last_opened
391
+
367
392
  # Stream generate
368
- partial_tool_call = {}
369
393
  stream = await self._client.responses.create(**openai_config, input=input_list, stream=True)
370
- async for event in stream:
371
- event = self.transform_model_output_to_uni_event(event)
394
+ async for model_event in stream:
395
+ event = self.transform_model_output_to_uni_event(model_event)
396
+ fragments = [item for item in event["content_items"] if item["type"] == "partial_tool_call"]
372
397
  if event["event_type"] == "start":
373
- for item in event["content_items"]:
374
- if item["type"] == "partial_tool_call":
375
- # initialize partial_tool_call
376
- partial_tool_call = {
377
- "name": item["name"],
378
- "arguments": "",
379
- "tool_call_id": item["tool_call_id"],
380
- }
381
- yield event
398
+ for item in fragments:
399
+ last_opened = item.get("item_id") or item["tool_call_id"]
400
+ open_tool_calls[last_opened] = {
401
+ "name": item["name"],
402
+ "tool_call_id": item["tool_call_id"],
403
+ "arguments": "",
404
+ }
405
+ yield event
382
406
  elif event["event_type"] == "delta":
383
- for item in event["content_items"]:
384
- if item["type"] == "partial_tool_call":
385
- # update partial_tool_call
386
- partial_tool_call["arguments"] += item["arguments"]
387
-
407
+ for item in fragments:
408
+ tool_call = open_tool_calls.get(key_of(item.get("item_id")))
409
+ if tool_call is not None:
410
+ tool_call["arguments"] += item["arguments"]
388
411
  yield event
389
412
  elif event["event_type"] == "stop":
390
- if "name" in partial_tool_call and "arguments" in partial_tool_call:
391
- # finish partial_tool_call
413
+ # a stop that names calls closes them; the end of the response closes whatever
414
+ # a gateway never closed on its own
415
+ closing = [key_of(item.get("item_id")) for item in fragments] or list(open_tool_calls)
416
+ for key in closing:
417
+ tool_call = open_tool_calls.pop(key, None)
418
+ if tool_call is None:
419
+ continue
392
420
  yield {
393
421
  "role": "assistant",
394
422
  "event_type": "delta",
395
423
  "content_items": [
396
424
  {
397
425
  "type": "tool_call",
398
- "name": partial_tool_call["name"],
426
+ "name": tool_call["name"],
399
427
  "arguments": parse_tool_call_arguments(
400
- partial_tool_call["arguments"],
428
+ tool_call["arguments"],
401
429
  self.__class__.__name__,
402
- partial_tool_call["name"],
403
- partial_tool_call["tool_call_id"],
430
+ tool_call["name"],
431
+ tool_call["tool_call_id"],
404
432
  ),
405
- "tool_call_id": partial_tool_call["tool_call_id"],
433
+ "tool_call_id": tool_call["tool_call_id"],
406
434
  }
407
435
  ],
408
436
  "usage_metadata": None,
409
437
  "finish_reason": None,
410
438
  }
411
- partial_tool_call = {}
412
439
 
413
440
  if event["finish_reason"] or event["usage_metadata"]:
414
441
  yield event
@@ -96,6 +96,9 @@ class PartialToolCallContentItem(TypedDict):
96
96
  name: str
97
97
  arguments: str
98
98
  tool_call_id: str
99
+ # id of the streamed item this fragment belongs to, when the provider sends one (the
100
+ # Responses output item id); ties fragments to their call when several calls stream at once
101
+ item_id: NotRequired[str | None]
99
102
  fidelity: NotRequired[Fidelity]
100
103
 
101
104
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agenthub-python"
3
- version = "0.4.14"
3
+ version = "0.4.15"
4
4
  description = "AgentHub is the LLM API Hub for the Agent era, built for high-precision autonomous agents."
5
5
  keywords = [
6
6
  "agent",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agenthub-python"
3
- version = "0.4.14"
3
+ version = "0.4.15"
4
4
  description = "AgentHub is the LLM API Hub for the Agent era, built for high-precision autonomous agents."
5
5
  keywords = ["agent", "llm", "gemini", "claude", "gpt"]
6
6
  readme = "README.md"