echotools 2.3.99__tar.gz → 2.3.100__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 (254) hide show
  1. {echotools-2.3.99 → echotools-2.3.100}/PKG-INFO +1 -1
  2. {echotools-2.3.99 → echotools-2.3.100}/pyproject.toml +8 -2
  3. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/stream.py +4 -2
  4. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/stream_feed.py +0 -2
  5. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/stream_final.py +8 -11
  6. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml.py +2 -6
  7. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_invoke.py +5 -1
  8. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_patterns.py +7 -5
  9. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_schema.py +166 -65
  10. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_stream/snap.py +36 -40
  11. echotools-2.3.100/src/echotools/exec/fncall/protocols/entml_stream_json.py +460 -0
  12. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/filter.py +2 -11
  13. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/parse.py +2 -3
  14. echotools-2.3.100/src/echotools/exec/fncall/protocols/entml_tool_blocks.py +362 -0
  15. echotools-2.3.100/src/echotools/exec/fncall/protocols/entml_tools.py +156 -0
  16. echotools-2.3.100/src/echotools/exec/fncall/protocols/entml_values.py +134 -0
  17. {echotools-2.3.99 → echotools-2.3.100}/src/echotools.egg-info/PKG-INFO +1 -1
  18. {echotools-2.3.99 → echotools-2.3.100}/src/echotools.egg-info/SOURCES.txt +5 -0
  19. echotools-2.3.100/src/tests/_check_parity.py +77 -0
  20. {echotools-2.3.99 → echotools-2.3.100}/src/tests/fixtures/simulated_fault_thinking_responses.py +2 -2
  21. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_bare_invoke_format.py +0 -1
  22. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_batch_stream_comprehensive.py +1 -1
  23. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_invoke_known_tool_gate.py +1 -2
  24. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_protocol.py +1 -2
  25. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_tool_pipeline.py +2 -2
  26. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_stream_partial_json_parity.py +113 -2
  27. {echotools-2.3.99 → echotools-2.3.100}/README.md +0 -0
  28. {echotools-2.3.99 → echotools-2.3.100}/setup.cfg +0 -0
  29. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/__init__.py +0 -0
  30. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/cache/__init__.py +0 -0
  31. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/cache/list_cache.py +0 -0
  32. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/cache/memory_cache.py +0 -0
  33. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/__init__.py +0 -0
  34. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/base.py +0 -0
  35. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/center.py +0 -0
  36. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/center_notify.py +0 -0
  37. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/center_template.py +0 -0
  38. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/loader.py +0 -0
  39. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/config/merge.py +0 -0
  40. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/errors/__init__.py +0 -0
  41. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/errors/base.py +0 -0
  42. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/errors/classify.py +0 -0
  43. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/errors/common.py +0 -0
  44. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/errors/http.py +0 -0
  45. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/ids/__init__.py +0 -0
  46. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/ids/generator.py +0 -0
  47. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/io/__init__.py +0 -0
  48. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/io/io_utils.py +0 -0
  49. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/io/printstream.py +0 -0
  50. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/logger/__init__.py +0 -0
  51. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/logger/manager.py +0 -0
  52. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/retry/__init__.py +0 -0
  53. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/base/retry/retry.py +0 -0
  54. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/compat.py +0 -0
  55. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/__init__.py +0 -0
  56. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/candidate.py +0 -0
  57. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  58. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  59. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/race.py +0 -0
  60. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/selector.py +0 -0
  61. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/dispatch/usage.py +0 -0
  62. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/__init__.py +0 -0
  63. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  64. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/stream_delta.py +0 -0
  65. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  66. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  67. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  68. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/behavior_blocks.py +0 -0
  69. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/history.py +0 -0
  70. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  71. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  72. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  73. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  74. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  75. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_fake_structure_markup.py +0 -0
  76. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_stream/__init__.py +0 -0
  77. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_stream/body.py +0 -0
  78. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  79. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/blocks.py +0 -0
  80. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  81. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  82. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_think/hold.py +0 -0
  83. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/protocols/entml_tool_result_comment.py +0 -0
  84. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/registry.py +0 -0
  85. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  86. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  87. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/history_markup.py +0 -0
  88. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  89. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  90. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  91. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  92. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/keys.py +0 -0
  93. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/lifecycle/__init__.py +0 -0
  94. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/lifecycle/manager.py +0 -0
  95. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/lifecycle/updater.py +0 -0
  96. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  97. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/process/__init__.py +0 -0
  98. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/process/port.py +0 -0
  99. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/protocol/__init__.py +0 -0
  100. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/protocol/base.py +0 -0
  101. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/__init__.py +0 -0
  102. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/conpty.py +0 -0
  103. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/local/__init__.py +0 -0
  104. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/local/core.py +0 -0
  105. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/local/proc.py +0 -0
  106. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/local/unix.py +0 -0
  107. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/local/win.py +0 -0
  108. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/sanitize.py +0 -0
  109. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/session/__init__.py +0 -0
  110. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/session/base.py +0 -0
  111. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/session/client.py +0 -0
  112. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/ssh.py +0 -0
  113. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/exec/terminal/tmux.py +0 -0
  114. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/__init__.py +0 -0
  115. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/charmap.py +0 -0
  116. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/spinner.py +0 -0
  117. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/ui.py +0 -0
  118. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  119. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  120. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  121. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  122. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  123. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  124. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/ui_console.py +0 -0
  125. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/ui_io.py +0 -0
  126. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/ui_log.py +0 -0
  127. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  128. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/ui_text.py +0 -0
  129. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uicore/ui_types.py +0 -0
  130. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  131. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  132. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  133. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  134. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  135. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  136. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  137. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  138. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  139. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  140. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  141. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/events/__init__.py +0 -0
  142. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/events/bus.py +0 -0
  143. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/events/event.py +0 -0
  144. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/spinner/__init__.py +0 -0
  145. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/spinner/spinner.py +0 -0
  146. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/spinner/spinner_color.py +0 -0
  147. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/tracing/__init__.py +0 -0
  148. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/tracing/context.py +0 -0
  149. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/tracing/span.py +0 -0
  150. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/tracing/tracer.py +0 -0
  151. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/translate.py +0 -0
  152. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/watcher/__init__.py +0 -0
  153. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/watcher/file_watcher.py +0 -0
  154. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/__init__.py +0 -0
  155. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/application.py +0 -0
  156. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/broker.py +0 -0
  157. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/app.js +0 -0
  158. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  159. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  160. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  161. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  162. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  163. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  164. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  165. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  166. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  167. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  168. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/inputbox.css +0 -0
  169. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/inputbox.js +0 -0
  170. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/style.css +0 -0
  171. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/input_box/voicegif.js +0 -0
  172. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/middleware.py +0 -0
  173. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/stats.py +0 -0
  174. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/media/web/utils.py +0 -0
  175. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/files/__init__.py +0 -0
  176. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/files/file_util.py +0 -0
  177. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/network/__init__.py +0 -0
  178. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/network/http_utils.py +0 -0
  179. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/plugin/__init__.py +0 -0
  180. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/plugin/base.py +0 -0
  181. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/plugin/discovery.py +0 -0
  182. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/plugin/registry.py +0 -0
  183. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/plugin/registry_ops.py +0 -0
  184. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/proxy/__init__.py +0 -0
  185. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/proxy/manager.py +0 -0
  186. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/runtime/__init__.py +0 -0
  187. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/runtime/collector.py +0 -0
  188. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/scheduler/__init__.py +0 -0
  189. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/scheduler/scheduler.py +0 -0
  190. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/sdk/__init__.py +0 -0
  191. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/plat/sdk/facade.py +0 -0
  192. {echotools-2.3.99 → echotools-2.3.100}/src/echotools/version.py +0 -0
  193. {echotools-2.3.99 → echotools-2.3.100}/src/echotools.egg-info/dependency_links.txt +0 -0
  194. {echotools-2.3.99 → echotools-2.3.100}/src/echotools.egg-info/requires.txt +0 -0
  195. {echotools-2.3.99 → echotools-2.3.100}/src/echotools.egg-info/top_level.txt +0 -0
  196. {echotools-2.3.99 → echotools-2.3.100}/src/tests/bench_import.py +0 -0
  197. {echotools-2.3.99 → echotools-2.3.100}/src/tests/conftest.py +0 -0
  198. {echotools-2.3.99 → echotools-2.3.100}/src/tests/fixtures/__init__.py +0 -0
  199. {echotools-2.3.99 → echotools-2.3.100}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
  200. {echotools-2.3.99 → echotools-2.3.100}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  201. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_adversarial_stream_splits.py +0 -0
  202. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_cache_retry.py +0 -0
  203. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_completion.py +0 -0
  204. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_config.py +0 -0
  205. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_config_extended.py +0 -0
  206. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_console.py +0 -0
  207. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_coverage_core.py +0 -0
  208. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_dispatch.py +0 -0
  209. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_dispatch_extended.py +0 -0
  210. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_dispatch_race.py +0 -0
  211. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_agent_output_formats.py +0 -0
  212. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_fake_structure_markup.py +0 -0
  213. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_parser_comprehensive.py +0 -0
  214. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_stream_json.py +0 -0
  215. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  216. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_thinking_history.py +0 -0
  217. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_thinking_parse.py +0 -0
  218. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_tool_parse_leak.py +0 -0
  219. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_entml_tools.py +0 -0
  220. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_errors.py +0 -0
  221. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_errors_common.py +0 -0
  222. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_events.py +0 -0
  223. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_events_event.py +0 -0
  224. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_facade.py +0 -0
  225. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fake_history_markup_batch.py +0 -0
  226. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
  227. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
  228. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fake_history_markup_stream.py +0 -0
  229. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fault_thinking_close_batch.py +5 -5
  230. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fault_thinking_close_stream.py +4 -4
  231. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fncall.py +0 -0
  232. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_fncall_stream_thinking.py +0 -0
  233. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_history_markup_filter.py +0 -0
  234. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_ids.py +0 -0
  235. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_imports.py +0 -0
  236. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_io.py +0 -0
  237. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_keys.py +0 -0
  238. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_lifecycle.py +0 -0
  239. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_logger.py +0 -0
  240. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_merge.py +0 -0
  241. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_model_branch_matrix.py +0 -0
  242. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_plugin.py +0 -0
  243. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_printstream.py +0 -0
  244. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_protocols.py +0 -0
  245. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_proxy.py +0 -0
  246. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_proxy_selector.py +0 -0
  247. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_runtime.py +0 -0
  248. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_scheduler.py +0 -0
  249. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  250. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_stream_realtime_parse.py +0 -0
  251. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_tracing.py +0 -0
  252. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_usage.py +0 -0
  253. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_web.py +0 -0
  254. {echotools-2.3.99 → echotools-2.3.100}/src/tests/test_web_broker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.99
3
+ Version: 2.3.100
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "echotools"
7
- version = "2.3.99"
7
+ version = "2.3.100"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -76,7 +76,7 @@ ignore = ["E501", "E402"]
76
76
  known-first-party = ["echotools"]
77
77
 
78
78
  [tool.mypy]
79
- python_version = "3.8"
79
+ python_version = "3.10"
80
80
  warn_return_any = true
81
81
  warn_unused_ignores = true
82
82
  disallow_untyped_defs = false
@@ -91,6 +91,8 @@ disable_error_code = [
91
91
  "arg-type",
92
92
  "attr-defined",
93
93
  "var-annotated",
94
+ "has-type",
95
+ "misc",
94
96
  ]
95
97
 
96
98
  [[tool.mypy.overrides]]
@@ -101,6 +103,10 @@ ignore_errors = true
101
103
  module = "echotools.media.console.*"
102
104
  ignore_errors = true
103
105
 
106
+ [[tool.mypy.overrides]]
107
+ module = "echotools.exec.fncall.parsers.*"
108
+ disable_error_code = ["has-type", "misc", "return-value"]
109
+
104
110
  [tool.pytest.ini_options]
105
111
  testpaths = ["src/tests"]
106
112
  pythonpath = ["src", "src/tests"]
@@ -23,8 +23,8 @@ def _make_thinking_filter(protocol: ToolProtocol, *, thinking_enabled: bool = Tr
23
23
  return None
24
24
 
25
25
 
26
- from echotools.exec.fncall.parsers.stream_feed import StreamFeedMixin
27
26
  from echotools.exec.fncall.parsers.stream_delta import StreamDeltaMixin
27
+ from echotools.exec.fncall.parsers.stream_feed import StreamFeedMixin
28
28
  from echotools.exec.fncall.parsers.stream_final import StreamFinalMixin
29
29
 
30
30
 
@@ -101,7 +101,9 @@ class FncallStreamParser(StreamFeedMixin, StreamDeltaMixin, StreamFinalMixin):
101
101
  return self._schema_index
102
102
 
103
103
  def _get_known_tool_names(self):
104
- from echotools.exec.fncall.protocols.entml_patterns import resolve_known_tool_names
104
+ from echotools.exec.fncall.protocols.entml_patterns import (
105
+ resolve_known_tool_names,
106
+ )
105
107
 
106
108
  return resolve_known_tool_names(self._tools, self._get_schema_index())
107
109
 
@@ -1,8 +1,6 @@
1
1
  """FncallStreamParser waiting/feed helpers."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Dict, List, Optional, Tuple
5
-
6
4
 
7
5
  class StreamFeedMixin:
8
6
  def _normalize_stream_chunk(self, text: str) -> str:
@@ -1,7 +1,7 @@
1
1
  """FncallStreamParser finalize / partial display helpers."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Dict, List, Optional, Tuple
4
+ from typing import Any, Dict, List, Tuple
5
5
 
6
6
 
7
7
  class StreamFinalMixin:
@@ -13,11 +13,8 @@ class StreamFinalMixin:
13
13
  """与 batch ``parse`` 一致:未闭合 thinking 之后的正文不参与可见/剥离。"""
14
14
  from echotools.exec.fncall.protocols.entml_think.parse import (
15
15
  clean_stream_partial_visible,
16
- stream_safe_visible_prefix,
17
- )
18
-
19
- from echotools.exec.fncall.protocols.entml_think.parse import (
20
16
  split_entml_thinking,
17
+ stream_safe_visible_prefix,
21
18
  )
22
19
 
23
20
  buf = stream_safe_visible_prefix(
@@ -34,12 +31,12 @@ class StreamFinalMixin:
34
31
 
35
32
  def _stream_display_text(self) -> str:
36
33
  """流式可见正文:在完整 raw 缓冲上剥离伪 history(保留 thinking 保护区)。"""
37
- from echotools.exec.fncall.shared.history_markup import (
38
- strip_fake_history_markup_for_display,
39
- )
40
34
  from echotools.exec.fncall.protocols.entml_think.parse import (
41
35
  split_entml_thinking,
42
36
  )
37
+ from echotools.exec.fncall.shared.history_markup import (
38
+ strip_fake_history_markup_for_display,
39
+ )
43
40
 
44
41
  buf = self._stream_visible_buffer()
45
42
  if not buf:
@@ -52,14 +49,14 @@ class StreamFinalMixin:
52
49
 
53
50
  def _stream_partial_display_text(self) -> str:
54
51
  """流式 ``partial_text``:在 invoke 起点前截断 raw,保留尾部 holdback 空白。"""
55
- from echotools.exec.fncall.shared.history_markup import (
56
- strip_fake_history_markup_for_display,
57
- )
58
52
  from echotools.exec.fncall.protocols.entml_think.parse import (
59
53
  clean_stream_partial_visible,
60
54
  split_entml_thinking,
61
55
  stream_safe_visible_prefix,
62
56
  )
57
+ from echotools.exec.fncall.shared.history_markup import (
58
+ strip_fake_history_markup_for_display,
59
+ )
63
60
 
64
61
  raw = self._raw_buf
65
62
  if not raw:
@@ -14,7 +14,6 @@ from typing import Any, Dict, List, Optional, Tuple
14
14
  from echotools.exec.fncall.prompt.behavior_blocks import (
15
15
  format_function_calling_behavior,
16
16
  format_hard_constraint_restatement,
17
- format_thinking_behavior,
18
17
  )
19
18
  from echotools.exec.fncall.prompt.templates import (
20
19
  _HISTORY_CLARIFY_EN,
@@ -25,23 +24,20 @@ from echotools.exec.fncall.protocols.entml_invoke import (
25
24
  )
26
25
  from echotools.exec.fncall.protocols.entml_patterns import (
27
26
  entml_invoke_open_may_be_streaming,
28
- extract_attr_value,
29
27
  find_actionable_entml_invoke_open,
30
- normalize_entml_name,
31
28
  resolve_known_tool_names,
32
29
  strip_actionable_entml_invoke_blocks,
33
30
  strip_legacy_function_calls_wrapper,
34
31
  strip_tool_entml_residue,
35
32
  )
33
+ from echotools.exec.fncall.protocols.entml_schema import format_entml_tool_descs
36
34
  from echotools.exec.fncall.protocols.entml_think.core import (
37
35
  build_entml_thinking_behavior_section,
38
36
  build_entml_thinking_meta_section,
39
37
  )
40
- from echotools.exec.fncall.protocols.entml_schema import format_entml_tool_descs
41
38
  from echotools.exec.fncall.shared.coercion import _build_param_schema_index
42
39
  from echotools.exec.fncall.shared.history_markup import strip_fake_history_markup
43
40
  from echotools.exec.fncall.shared.normalization import (
44
- normalize_content,
45
41
  normalize_tool_calls,
46
42
  )
47
43
  from echotools.exec.protocol.base import ToolProtocol
@@ -139,7 +135,7 @@ def format_entml_functions_results(
139
135
  if not lines:
140
136
  return ""
141
137
  return (
142
- f"<entml:funtions_results>\n"
138
+ "<entml:funtions_results>\n"
143
139
  + "\n".join(lines)
144
140
  + "\n</entml:funtions_results>"
145
141
  )
@@ -19,7 +19,11 @@ from .entml_patterns import (
19
19
  split_mangled_json_param_tail,
20
20
  synthetic_close_invoke_body,
21
21
  )
22
- from .entml_schema import coerce_entml_arguments, coerce_entml_parameter_value, _coerce_entml_arg_value
22
+ from .entml_schema import (
23
+ _coerce_entml_arg_value,
24
+ coerce_entml_arguments,
25
+ coerce_entml_parameter_value,
26
+ )
23
27
 
24
28
 
25
29
  def _parse_direct_child_tags(
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  import re
5
4
  from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
6
5
 
@@ -545,7 +544,10 @@ def strip_tool_entml_residue(
545
544
  cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
546
545
  return cleaned.strip()
547
546
 
548
- from echotools.exec.fncall.protocols.entml_schema import (
549
- mangled_json_param_tail_in_progress,
550
- split_mangled_json_param_tail,
551
- )
547
+
548
+ # 兼容旧 import 路径(body/invoke/stream 等仍从本模块取 mangled helpers)
549
+ import echotools.exec.fncall.protocols.entml_schema as _entml_schema
550
+
551
+ mangled_json_param_tail_in_progress = _entml_schema.mangled_json_param_tail_in_progress
552
+ split_mangled_json_param_tail = _entml_schema.split_mangled_json_param_tail
553
+
@@ -4,7 +4,10 @@ import json
4
4
  import re
5
5
  from typing import Any, Dict, List, Mapping, Optional, Tuple
6
6
 
7
- from echotools.exec.fncall.shared.coercion import _coerce_param_value, _resolve_effective_type
7
+ from echotools.exec.fncall.shared.coercion import (
8
+ _coerce_param_value,
9
+ _resolve_effective_type,
10
+ )
8
11
 
9
12
  _TYPE_HINT_TO_JSON_TYPE = {
10
13
  "str": "string",
@@ -286,48 +289,19 @@ def format_entml_tool_descs(tools: List[Dict[str, Any]]) -> str:
286
289
  )
287
290
  return "\n\n".join(blocks)
288
291
 
289
- # --- mangled param tail (from entml_patterns) ---
292
+ # --- mangled param tail ---
293
+ #
294
+ # 模型偶发把 schema 尾缀 ``", "description": "...", "timeout": N}}`` 粘进标量 parameter。
295
+ # 通用规则:仅当「从候选起点到 EOS」整段都是该尾缀(可未写完)时才截断;
296
+ # 一旦后续偏离(例如内嵌 JSON 的 ``"method"``),一律视为正文。不依赖参数名黑名单。
290
297
 
291
298
  _MANGLED_PARAM_JSON_TAIL_RE = re.compile(
292
299
  r'"\s*,\s*"description"\s*:\s*"((?:[^"\\]|\\.)*)"\s*,\s*"timeout"\s*:\s*(\d+)\s*\}\}?\s*$',
293
300
  re.DOTALL,
294
301
  )
295
- _MANGLED_PARAM_JSON_TAIL_START_RE = re.compile(
296
- r'"\s*,\s*"description"\s*:\s*"',
297
- re.DOTALL,
298
- )
299
- _MANGLED_PARAM_JSON_TAIL_EARLY_RE = re.compile(
300
- r'"\s*,\s*"(?:description|timeout)\b',
301
- re.DOTALL,
302
- )
303
- _MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE = re.compile(
304
- r'"\s*,\s*"(?:d|t)',
305
- re.DOTALL,
306
- )
307
- _MANGLED_TAIL_SKIP_PARAM_NAMES = frozenset(
308
- {
309
- "content",
310
- "contents",
311
- "text",
312
- "body",
313
- "file_path",
314
- "path",
315
- "message",
316
- "html",
317
- "data",
318
- "code",
319
- }
320
- )
321
302
 
322
-
323
- _MANGLED_PARAM_JSON_COMMA_QUOTE_END_RE = re.compile(
324
- r'"\s*,\s*"$',
325
- re.DOTALL,
326
- )
327
- _MANGLED_PARAM_JSON_COMMA_AFTER_QUOTE_RE = re.compile(
328
- r'"\s*,\s*$',
329
- re.DOTALL,
330
- )
303
+ _DESC_KEY = "description"
304
+ _TIMEOUT_KEY = "timeout"
331
305
 
332
306
 
333
307
  def _param_value_is_json_container(value: str) -> bool:
@@ -335,11 +309,156 @@ def _param_value_is_json_container(value: str) -> bool:
335
309
  return bool(stripped) and stripped[0] in "{["
336
310
 
337
311
 
312
+ def _match_schema_key_prefix(
313
+ text: str,
314
+ pos: int,
315
+ *,
316
+ allowed: Tuple[str, ...] = (_DESC_KEY, _TIMEOUT_KEY),
317
+ ) -> Optional[Tuple[str, int]]:
318
+ """匹配 schema 键。
319
+
320
+ 返回 ``("<key>"|"partial", new_pos)``;已偏离则 ``None``。
321
+ """
322
+ if pos >= len(text):
323
+ return ("partial", pos)
324
+ for key in allowed:
325
+ if text.startswith(key, pos):
326
+ return (key, pos + len(key))
327
+ remain = text[pos:]
328
+ for key in allowed:
329
+ if key.startswith(remain):
330
+ return ("partial", len(text))
331
+ return None
332
+
333
+
334
+ def _consume_json_string_body(text: str, pos: int) -> Tuple[int, bool]:
335
+ """从 JSON 字符串内容起点消费(不含开引号)。返回 ``(new_pos, closed)``。"""
336
+ i = pos
337
+ while i < len(text):
338
+ ch = text[i]
339
+ if ch == "\\":
340
+ if i + 1 >= len(text):
341
+ return len(text), False
342
+ i += 2
343
+ continue
344
+ if ch == '"':
345
+ return i + 1, True
346
+ i += 1
347
+ return len(text), False
348
+
349
+
350
+ def _is_mangled_schema_tail_suffix(suffix: str) -> bool:
351
+ """``suffix`` 是否整段为 mangled schema 尾缀(允许流式未写完)。
352
+
353
+ 必须以 ``description``(或其真前缀)起头;禁止把文末单独的
354
+ ``", "timeout": N`` 当成尾缀(避免截断正文 JSON)。
355
+ """
356
+ if not suffix:
357
+ return False
358
+ m = re.match(r'^"\s*,\s*"', suffix)
359
+ if not m:
360
+ return False
361
+ pos = m.end()
362
+ # 首键只能是 description(或其真前缀),避免 ", \"t" 误伤 type/timeout 正文
363
+ key_match = _match_schema_key_prefix(suffix, pos, allowed=(_DESC_KEY,))
364
+ if key_match is None:
365
+ return False
366
+ key, pos = key_match
367
+ if key == "partial":
368
+ return pos == len(suffix)
369
+ if key != _DESC_KEY:
370
+ return False
371
+ if pos >= len(suffix):
372
+ return True
373
+ if suffix[pos] != '"':
374
+ return False
375
+ pos += 1
376
+ m_colon = re.match(r"\s*:\s*", suffix[pos:])
377
+ if not m_colon:
378
+ return bool(re.match(r"\s*$", suffix[pos:]))
379
+ pos += m_colon.end()
380
+ if pos >= len(suffix):
381
+ return True
382
+ if suffix[pos] != '"':
383
+ return False
384
+ pos += 1
385
+ pos, closed = _consume_json_string_body(suffix, pos)
386
+ if not closed:
387
+ return pos == len(suffix)
388
+ if pos == len(suffix):
389
+ # description 已闭合、timeout 尚未出现:流式未完成或残缺尾缀,仍视为 mangled
390
+ return True
391
+ m_rest = re.match(r"\s*,\s*", suffix[pos:])
392
+ if not m_rest:
393
+ return re.match(r"\s*\}{0,2}\s*$", suffix[pos:]) is not None
394
+ pos += m_rest.end()
395
+ if pos >= len(suffix):
396
+ return True
397
+ if suffix[pos] != '"':
398
+ return False
399
+ pos += 1
400
+ key2 = _match_schema_key_prefix(suffix, pos, allowed=(_TIMEOUT_KEY,))
401
+ if key2 is None:
402
+ return False
403
+ key2_name, pos = key2
404
+ if key2_name == "partial":
405
+ return pos == len(suffix)
406
+ if key2_name != _TIMEOUT_KEY:
407
+ return False
408
+ if pos >= len(suffix):
409
+ return True
410
+ if suffix[pos] != '"':
411
+ return False
412
+ pos += 1
413
+ m_colon2 = re.match(r"\s*:\s*", suffix[pos:])
414
+ if not m_colon2:
415
+ return bool(re.match(r"\s*$", suffix[pos:]))
416
+ pos += m_colon2.end()
417
+ m_num2 = re.match(r"\d*", suffix[pos:])
418
+ assert m_num2 is not None
419
+ pos += m_num2.end()
420
+ return re.match(r"\s*\}{0,2}\s*$", suffix[pos:]) is not None
421
+
422
+
423
+ def _ambiguous_comma_hold_end(value: str) -> int:
424
+ """值以 ``",`` / ``", "`` 结尾且下一键未明时,返回应保留到的终点(含引号)。
425
+
426
+ 流式在键名出现前无法区分 mangled 尾缀与正文 JSON 换键;先截到引号以保持
427
+ partial_json 单调,待后续字符证明不是 description 后再放出全文。
428
+ """
429
+ m = re.search(r'"\s*,\s*"?$', value)
430
+ if not m:
431
+ return -1
432
+ return m.start() + 1
433
+
434
+
435
+ def _find_mangled_schema_tail_start(value: str) -> int:
436
+ """返回 mangled 尾缀起点;无则 -1。
437
+
438
+ 取最左合法起点,保证一旦出现 ``", "description"...`` 就截在 command 侧,
439
+ 避免流式先发出更长前缀再无法回缩。
440
+ """
441
+ starts = [m.start() for m in re.finditer(r'"\s*,\s*"', value)]
442
+ for start in starts:
443
+ if _is_mangled_schema_tail_suffix(value[start:]):
444
+ return start
445
+ hold = _ambiguous_comma_hold_end(value)
446
+ if hold >= 0:
447
+ # 把 hold 点前的引号视为尾缀起点(与合法 mangled 起点同形)
448
+ return hold - 1
449
+ return -1
450
+
451
+
338
452
  def mangled_json_param_tail_in_progress(value: str) -> bool:
339
- """parameter 值中出现误写入 JSON 尾缀但尚未收齐时不应继续增长 partial_json。"""
453
+ """值末尾正在形成 mangled schema 尾缀(尚未收齐)时抑制 partial_json 增长。"""
340
454
  if not value or _param_value_is_json_container(value):
341
455
  return False
342
- return bool(_MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE.search(value))
456
+ start = _find_mangled_schema_tail_start(value)
457
+ if start < 0:
458
+ return False
459
+ if _MANGLED_PARAM_JSON_TAIL_RE.search(value[start:]):
460
+ return False
461
+ return True
343
462
 
344
463
 
345
464
  def split_mangled_json_param_tail(
@@ -348,45 +467,27 @@ def split_mangled_json_param_tail(
348
467
  param_name: str = "",
349
468
  ) -> Tuple[str, Dict[str, Any]]:
350
469
  """模型把 ``", "description": ..., "timeout": ...}}`` 误写入 parameter 值时的拆分。"""
470
+ _ = param_name
351
471
  if not value:
352
472
  return value, {}
353
- if param_name in _MANGLED_TAIL_SKIP_PARAM_NAMES:
354
- return value, {}
355
473
  if _param_value_is_json_container(value):
356
474
  try:
357
475
  json.loads(value)
358
476
  return value, {}
359
477
  except json.JSONDecodeError:
360
- # 合法 JSON 数组/对象(含 options.description 等)不得走标量 command 尾缀启发式。
361
478
  return value, {}
479
+
362
480
  match = _MANGLED_PARAM_JSON_TAIL_RE.search(value)
363
481
  if match:
364
- command = value[: match.start() + 1]
365
- extra: Dict[str, Any] = {
482
+ return value[: match.start() + 1], {
366
483
  "description": match.group(1),
367
484
  "timeout": int(match.group(2)),
368
485
  }
369
- return command, extra
370
- partial = _MANGLED_PARAM_JSON_TAIL_START_RE.search(value)
371
- if partial:
372
- return value[: partial.start() + 1], {}
373
- early = _MANGLED_PARAM_JSON_TAIL_EARLY_RE.search(value)
374
- if early:
375
- return value[: early.start() + 1], {}
376
- inprog = _MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE.search(value)
377
- if inprog:
378
- before = value[: inprog.start()]
379
- matches = list(re.finditer(r'"\s*,\s*"', before))
380
- if matches:
381
- return value[: matches[-1].start() + 1], {}
382
- return value[: inprog.start() + 1], {}
383
- comma_quote_end = _MANGLED_PARAM_JSON_COMMA_QUOTE_END_RE.search(value)
384
- if comma_quote_end:
385
- return value[: comma_quote_end.start() + 1], {}
386
- comma_after_quote = _MANGLED_PARAM_JSON_COMMA_AFTER_QUOTE_RE.search(value)
387
- if comma_after_quote:
388
- return value[: comma_after_quote.start() + 1], {}
389
- return value, {}
486
+
487
+ start = _find_mangled_schema_tail_start(value)
488
+ if start < 0:
489
+ return value, {}
490
+ return value[: start + 1], {}
390
491
 
391
492
 
392
493
 
@@ -15,6 +15,7 @@ from echotools.exec.fncall.protocols.entml_stream.body import (
15
15
  _parse_parameter_entries,
16
16
  )
17
17
 
18
+
18
19
  def _parameters_block_snapshot(
19
20
  inner: str,
20
21
  *,
@@ -36,7 +37,35 @@ def _parameters_block_snapshot(
36
37
  return ""
37
38
 
38
39
 
39
- def _collect_streaming_entries(inner: str):
40
+ def build_streaming_json_snapshot(
41
+ body: str,
42
+ *,
43
+ tool_name: str = "",
44
+ schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]] = None,
45
+ force_close: bool = False,
46
+ ) -> str:
47
+ """构造当前应已发出的 partial_json 累积串(可未完成)。
48
+
49
+ invoke 闭合(或 force_close)时直接走 ``parse_invoke_args``,与批量解析一致。
50
+ 未完成时仅输出合法 JSON 前缀,且不在 ``</entml:invoke>`` 前闭合最外层 ``}``。
51
+ """
52
+ invoke_closed = _INVOKE_CLOSE in body
53
+ inner = body[: body.index(_INVOKE_CLOSE)] if invoke_closed else body
54
+
55
+ if invoke_closed or force_close:
56
+ return _final_invoke_arguments_json(
57
+ body,
58
+ tool_name=tool_name,
59
+ schema_index=schema_index,
60
+ force_close=force_close,
61
+ )
62
+
63
+ params_snap = _parameters_block_snapshot(
64
+ inner, tool_name=tool_name, schema_index=schema_index
65
+ )
66
+ if params_snap is not None:
67
+ return params_snap
68
+
40
69
  entries = _parse_parameter_entries(inner)
41
70
  seen_keys = {key for key, *_rest in entries}
42
71
  for bare in _parse_bare_invoke_entries(inner):
@@ -47,15 +76,9 @@ def _collect_streaming_entries(inner: str):
47
76
  if direct[0] not in seen_keys:
48
77
  entries.append(direct)
49
78
  seen_keys.add(direct[0])
50
- return entries
51
-
79
+ if not entries:
80
+ return ""
52
81
 
53
- def _entries_to_json_prefix(
54
- entries,
55
- *,
56
- tool_name: str,
57
- schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]],
58
- ) -> str:
59
82
  func_schema = (schema_index or {}).get(tool_name) or {}
60
83
  parts: List[str] = ["{"]
61
84
  for idx, (key, value, is_complete, type_hint) in enumerate(entries):
@@ -69,41 +92,14 @@ def _entries_to_json_prefix(
69
92
  value, is_complete, param_type, pschema, type_hint
70
93
  )
71
94
  if fragment is None:
72
- return "" if idx == 0 else "".join(parts)
95
+ if idx == 0:
96
+ return ""
97
+ return "".join(parts)
73
98
  parts.append(fragment)
74
99
  if not is_complete:
75
100
  return "".join(parts)
76
- return "".join(parts)
77
-
78
101
 
79
- def build_streaming_json_snapshot(
80
- body: str,
81
- *,
82
- tool_name: str = "",
83
- schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]] = None,
84
- force_close: bool = False,
85
- ) -> str:
86
- """构造当前应已发出的 partial_json 累积串(可未完成)。"""
87
- invoke_closed = _INVOKE_CLOSE in body
88
- inner = body[: body.index(_INVOKE_CLOSE)] if invoke_closed else body
89
- if invoke_closed or force_close:
90
- return _final_invoke_arguments_json(
91
- body,
92
- tool_name=tool_name,
93
- schema_index=schema_index,
94
- force_close=force_close,
95
- )
96
- params_snap = _parameters_block_snapshot(
97
- inner, tool_name=tool_name, schema_index=schema_index
98
- )
99
- if params_snap is not None:
100
- return params_snap
101
- entries = _collect_streaming_entries(inner)
102
- if not entries:
103
- return ""
104
- return _entries_to_json_prefix(
105
- entries, tool_name=tool_name, schema_index=schema_index
106
- )
102
+ return "".join(parts)
107
103
 
108
104
 
109
105
  def _parse_partial_parameter_body(body: str) -> Dict[str, Any]: