echotools 2.3.78__tar.gz → 2.3.80__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 (239) hide show
  1. {echotools-2.3.78 → echotools-2.3.80}/PKG-INFO +1 -1
  2. {echotools-2.3.78 → echotools-2.3.80}/pyproject.toml +1 -1
  3. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/parsers/stream.py +19 -16
  4. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml.py +18 -4
  5. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_think/parse.py +50 -0
  6. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_tool_blocks.py +117 -15
  7. {echotools-2.3.78 → echotools-2.3.80}/src/echotools.egg-info/PKG-INFO +1 -1
  8. {echotools-2.3.78 → echotools-2.3.80}/src/echotools.egg-info/SOURCES.txt +1 -0
  9. echotools-2.3.80/src/tests/test_entml_batch_stream_comprehensive.py +358 -0
  10. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_tool_parse_leak.py +6 -5
  11. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_tool_pipeline.py +3 -3
  12. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_simulated_llm_tool_parse.py +21 -18
  13. {echotools-2.3.78 → echotools-2.3.80}/README.md +0 -0
  14. {echotools-2.3.78 → echotools-2.3.80}/docs/README.md +0 -0
  15. {echotools-2.3.78 → echotools-2.3.80}/setup.cfg +0 -0
  16. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/__init__.py +0 -0
  17. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/cache/__init__.py +0 -0
  18. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/cache/list_cache.py +0 -0
  19. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/cache/memory_cache.py +0 -0
  20. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/__init__.py +0 -0
  21. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/base.py +0 -0
  22. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/center.py +0 -0
  23. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/center_notify.py +0 -0
  24. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/center_template.py +0 -0
  25. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/loader.py +0 -0
  26. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/config/merge.py +0 -0
  27. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/errors/__init__.py +0 -0
  28. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/errors/base.py +0 -0
  29. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/errors/classify.py +0 -0
  30. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/errors/common.py +0 -0
  31. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/errors/http.py +0 -0
  32. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/ids/__init__.py +0 -0
  33. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/ids/generator.py +0 -0
  34. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/io/__init__.py +0 -0
  35. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/io/io_utils.py +0 -0
  36. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/io/printstream.py +0 -0
  37. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/logger/__init__.py +0 -0
  38. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/logger/manager.py +0 -0
  39. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/retry/__init__.py +0 -0
  40. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/base/retry/retry.py +0 -0
  41. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/compat.py +0 -0
  42. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/__init__.py +0 -0
  43. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/candidate.py +0 -0
  44. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  45. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  46. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/race.py +0 -0
  47. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/selector.py +0 -0
  48. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/dispatch/usage.py +0 -0
  49. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/__init__.py +0 -0
  50. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  51. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  52. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  53. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  54. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/prompt/history.py +0 -0
  55. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  56. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  57. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  58. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  59. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  60. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  61. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  62. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_stream_json.py +0 -0
  63. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  64. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  65. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  66. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  67. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  68. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/registry.py +0 -0
  69. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  70. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  71. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/history_markup.py +0 -0
  72. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  73. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  74. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  75. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  76. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/keys.py +0 -0
  77. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/lifecycle/__init__.py +0 -0
  78. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/lifecycle/manager.py +0 -0
  79. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/lifecycle/updater.py +0 -0
  80. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  81. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/process/__init__.py +0 -0
  82. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/process/port.py +0 -0
  83. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/protocol/__init__.py +0 -0
  84. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/protocol/base.py +0 -0
  85. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/__init__.py +0 -0
  86. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/conpty.py +0 -0
  87. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/local/__init__.py +0 -0
  88. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/local/core.py +0 -0
  89. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/local/proc.py +0 -0
  90. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/local/unix.py +0 -0
  91. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/local/win.py +0 -0
  92. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/sanitize.py +0 -0
  93. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/session/__init__.py +0 -0
  94. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/session/base.py +0 -0
  95. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/session/client.py +0 -0
  96. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/ssh.py +0 -0
  97. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/exec/terminal/tmux.py +0 -0
  98. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/__init__.py +0 -0
  99. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/charmap.py +0 -0
  100. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/spinner.py +0 -0
  101. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/ui.py +0 -0
  102. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  103. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  104. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  105. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  106. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  107. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  108. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/ui_console.py +0 -0
  109. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/ui_io.py +0 -0
  110. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/ui_log.py +0 -0
  111. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  112. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/ui_text.py +0 -0
  113. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uicore/ui_types.py +0 -0
  114. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  115. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  116. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  117. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  118. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  119. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  120. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  121. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  122. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  123. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  124. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  125. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/events/__init__.py +0 -0
  126. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/events/bus.py +0 -0
  127. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/events/event.py +0 -0
  128. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/spinner/__init__.py +0 -0
  129. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/spinner/spinner.py +0 -0
  130. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/spinner/spinner_color.py +0 -0
  131. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/tracing/__init__.py +0 -0
  132. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/tracing/context.py +0 -0
  133. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/tracing/span.py +0 -0
  134. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/tracing/tracer.py +0 -0
  135. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/translate.py +0 -0
  136. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/watcher/__init__.py +0 -0
  137. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/watcher/file_watcher.py +0 -0
  138. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/__init__.py +0 -0
  139. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/application.py +0 -0
  140. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/broker.py +0 -0
  141. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/app.js +0 -0
  142. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  143. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  144. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  145. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  146. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  147. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  148. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  149. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  150. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  151. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  152. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/inputbox.css +0 -0
  153. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/inputbox.js +0 -0
  154. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/style.css +0 -0
  155. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/input_box/voicegif.js +0 -0
  156. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/middleware.py +0 -0
  157. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/stats.py +0 -0
  158. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/media/web/utils.py +0 -0
  159. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/files/__init__.py +0 -0
  160. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/files/file_util.py +0 -0
  161. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/network/__init__.py +0 -0
  162. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/network/http_utils.py +0 -0
  163. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/plugin/__init__.py +0 -0
  164. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/plugin/base.py +0 -0
  165. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/plugin/discovery.py +0 -0
  166. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/plugin/registry.py +0 -0
  167. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/plugin/registry_ops.py +0 -0
  168. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/proxy/__init__.py +0 -0
  169. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/proxy/manager.py +0 -0
  170. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/runtime/__init__.py +0 -0
  171. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/runtime/collector.py +0 -0
  172. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/scheduler/__init__.py +0 -0
  173. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/scheduler/scheduler.py +0 -0
  174. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/sdk/__init__.py +0 -0
  175. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/plat/sdk/facade.py +0 -0
  176. {echotools-2.3.78 → echotools-2.3.80}/src/echotools/version.py +0 -0
  177. {echotools-2.3.78 → echotools-2.3.80}/src/echotools.egg-info/dependency_links.txt +0 -0
  178. {echotools-2.3.78 → echotools-2.3.80}/src/echotools.egg-info/requires.txt +0 -0
  179. {echotools-2.3.78 → echotools-2.3.80}/src/echotools.egg-info/top_level.txt +0 -0
  180. {echotools-2.3.78 → echotools-2.3.80}/src/tests/bench_import.py +0 -0
  181. {echotools-2.3.78 → echotools-2.3.80}/src/tests/conftest.py +0 -0
  182. {echotools-2.3.78 → echotools-2.3.80}/src/tests/fixtures/__init__.py +0 -0
  183. {echotools-2.3.78 → echotools-2.3.80}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
  184. {echotools-2.3.78 → echotools-2.3.80}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
  185. {echotools-2.3.78 → echotools-2.3.80}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  186. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_adversarial_stream_splits.py +0 -0
  187. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_cache_retry.py +0 -0
  188. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_completion.py +0 -0
  189. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_config.py +0 -0
  190. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_config_extended.py +0 -0
  191. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_console.py +0 -0
  192. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_coverage_core.py +0 -0
  193. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_dispatch.py +0 -0
  194. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_dispatch_extended.py +0 -0
  195. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_dispatch_race.py +0 -0
  196. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_agent_output_formats.py +0 -0
  197. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_bare_invoke_format.py +0 -0
  198. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_parser_comprehensive.py +0 -0
  199. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_protocol.py +0 -0
  200. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_stream_json.py +0 -0
  201. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  202. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_thinking_history.py +0 -0
  203. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_thinking_parse.py +0 -0
  204. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_entml_tools.py +0 -0
  205. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_errors.py +0 -0
  206. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_errors_common.py +0 -0
  207. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_events.py +0 -0
  208. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_events_event.py +0 -0
  209. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_facade.py +0 -0
  210. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fake_history_markup_batch.py +0 -0
  211. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
  212. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
  213. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fake_history_markup_stream.py +0 -0
  214. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fault_thinking_close_batch.py +0 -0
  215. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fault_thinking_close_stream.py +0 -0
  216. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fncall.py +0 -0
  217. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_fncall_stream_thinking.py +0 -0
  218. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_history_markup_filter.py +0 -0
  219. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_ids.py +0 -0
  220. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_imports.py +0 -0
  221. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_io.py +0 -0
  222. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_keys.py +0 -0
  223. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_lifecycle.py +0 -0
  224. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_logger.py +0 -0
  225. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_merge.py +0 -0
  226. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_model_branch_matrix.py +0 -0
  227. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_plugin.py +0 -0
  228. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_printstream.py +0 -0
  229. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_protocols.py +0 -0
  230. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_proxy.py +0 -0
  231. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_proxy_selector.py +0 -0
  232. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_runtime.py +0 -0
  233. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_scheduler.py +0 -0
  234. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_stream_partial_json_parity.py +0 -0
  235. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_stream_realtime_parse.py +0 -0
  236. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_tracing.py +0 -0
  237. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_usage.py +0 -0
  238. {echotools-2.3.78 → echotools-2.3.80}/src/tests/test_web.py +0 -0
  239. {echotools-2.3.78 → echotools-2.3.80}/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.78
3
+ Version: 2.3.80
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.78"
7
+ version = "2.3.80"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -615,23 +615,14 @@ class FncallStreamParser:
615
615
 
616
616
  def _stream_visible_buffer(self) -> str:
617
617
  """与 batch ``parse`` 一致:未闭合 thinking 之后的正文不参与可见/剥离。"""
618
- buf = self._raw_buf
619
- if not buf:
620
- return ""
621
618
  from echotools.exec.fncall.protocols.entml_think.parse import (
622
- _find_earliest_thinking_open,
623
- has_unclosed_entml_thinking,
619
+ stream_safe_visible_prefix,
624
620
  )
625
621
 
626
- if has_unclosed_entml_thinking(
627
- buf, thinking_enabled=self._thinking_enabled
628
- ):
629
- open_at, _ = _find_earliest_thinking_open(
630
- buf, thinking_enabled=self._thinking_enabled
631
- )
632
- if open_at >= 0:
633
- return buf[:open_at]
634
- return buf
622
+ return stream_safe_visible_prefix(
623
+ self._raw_buf,
624
+ thinking_enabled=self._thinking_enabled,
625
+ )
635
626
 
636
627
  def _stream_display_text(self) -> str:
637
628
  """流式可见正文:在完整 raw 缓冲上剥离伪 history(保留 thinking 保护区)。"""
@@ -671,7 +662,11 @@ class FncallStreamParser:
671
662
  self._text_parts.append(part)
672
663
 
673
664
  # 与 batch parse 同路径(须在含 thinking 标签的 raw 缓冲上剥离伪 history)。
674
- clean_text, tool_calls = self._protocol.parse(self._raw_buf, self._tools)
665
+ clean_text, tool_calls = self._protocol.parse(
666
+ self._raw_buf,
667
+ self._tools,
668
+ thinking_enabled=self._thinking_enabled,
669
+ )
675
670
 
676
671
  clean_fn = getattr(self._protocol, "clean_tool_tags", None)
677
672
  if callable(clean_fn):
@@ -728,7 +723,14 @@ class FncallStreamParser:
728
723
  if self._thinking_enabled and self._raw_buf:
729
724
  lower = self._raw_buf.lower()
730
725
  if "<entml:invoke" not in lower and "<entml:function_calls" not in lower:
731
- return self._stream_display_text()
726
+ display = self._stream_display_text()
727
+ if display:
728
+ from echotools.exec.fncall.shared.history_markup import (
729
+ strip_fake_history_markup_for_display,
730
+ )
731
+
732
+ cleaned, _ = strip_fake_history_markup_for_display(display)
733
+ return cleaned
732
734
  return ""
733
735
 
734
736
  @property
@@ -748,6 +750,7 @@ class FncallStreamParser:
748
750
  parse_kwargs: Dict[str, Any] = {}
749
751
  if getattr(self._protocol, "id", None) == "entml":
750
752
  parse_kwargs["include_tool_blocks"] = False
753
+ parse_kwargs["thinking_enabled"] = self._thinking_enabled
751
754
  _, all_calls = self._protocol.parse(
752
755
  self._raw_buf, self._tools, **parse_kwargs
753
756
  )
@@ -18,7 +18,10 @@ from echotools.exec.fncall.prompt.templates import (
18
18
  from echotools.exec.fncall.protocols.entml_invoke import (
19
19
  parse_entml_tool_calls,
20
20
  )
21
- from echotools.exec.fncall.protocols.entml_tool_blocks import parse_tool_block_calls
21
+ from echotools.exec.fncall.protocols.entml_tool_blocks import (
22
+ parse_tool_block_calls,
23
+ strip_tool_block_spans,
24
+ )
22
25
  from echotools.exec.fncall.protocols.entml_patterns import (
23
26
  BLOCK_RE,
24
27
  entml_invoke_open_may_be_streaming,
@@ -151,12 +154,13 @@ class EntmlProtocol(ToolProtocol):
151
154
  _THINKING_PREFIX = "<entml:thinking"
152
155
 
153
156
  def get_trigger_tags(self) -> List[str]:
154
- # invoke / thinking 前缀 holdback,避免 `<e` 歧义被提前吐出
157
+ # invoke / thinking / 伪 history tool 前缀 holdback,避免 `<e` / `<t` 歧义被提前吐出
155
158
  return [
156
159
  self._TRIGGER,
157
160
  self._TRIGGER_PREFIX,
158
161
  self._THINKING_PREFIX,
159
162
  f"{self._THINKING_PREFIX}>",
163
+ "<tool",
160
164
  ]
161
165
 
162
166
  def normalize_stream_buffer(self, buffer: str) -> str:
@@ -271,6 +275,7 @@ class EntmlProtocol(ToolProtocol):
271
275
  tools: Optional[List[Dict[str, Any]]] = None,
272
276
  *,
273
277
  include_tool_blocks: bool = True,
278
+ thinking_enabled: bool = True,
274
279
  ) -> Tuple[str, List[Dict[str, Any]]]:
275
280
  from echotools.exec.fncall.protocols.entml_think.parse import (
276
281
  _find_earliest_thinking_open,
@@ -279,6 +284,7 @@ class EntmlProtocol(ToolProtocol):
279
284
  )
280
285
 
281
286
  schema_index = _build_param_schema_index(tools) if tools else None
287
+ # 工具解析始终按 thinking 开启语义分流(fault ``</thinking>`` 等);可见正文再按 thinking_enabled 剥离。
282
288
  parse_text, _thinking = split_entml_thinking(text, thinking_enabled=True)
283
289
  unclosed_open_at = -1
284
290
  if has_unclosed_entml_thinking(text, thinking_enabled=True):
@@ -288,10 +294,18 @@ class EntmlProtocol(ToolProtocol):
288
294
  if unclosed_open_at >= 0:
289
295
  parse_text = text[:unclosed_open_at]
290
296
  tool_calls = parse_entml_tool_calls(parse_text, tools, schema_index)
297
+ tool_block_calls: List[Dict[str, Any]] = []
291
298
  if include_tool_blocks:
292
- tool_calls.extend(parse_tool_block_calls(parse_text, tools, schema_index))
299
+ tool_block_calls = parse_tool_block_calls(parse_text, tools, schema_index)
300
+ tool_calls.extend(tool_block_calls)
293
301
  clean = text[:unclosed_open_at] if unclosed_open_at >= 0 else text
294
- # 须在移除 invoke 之前剥离伪 history,否则未闭合 <tool> 会把 invoke 之后的可见回复误删。
302
+ if thinking_enabled:
303
+ # fault ``</thinking>`` 须借后续 invoke 判定闭合;thinking 剥离必须在 invoke 移除之前。
304
+ visible, _ = split_entml_thinking(clean, thinking_enabled=True)
305
+ clean = visible
306
+ if tool_block_calls:
307
+ clean = strip_tool_block_spans(clean)
308
+ # 须在移除 invoke 之前剥离伪 history,且须晚于已解析的 ``<tool>`` 块剥离。
295
309
  clean, _ = strip_fake_history_markup(clean)
296
310
  if tool_calls:
297
311
  clean = BLOCK_RE.sub("", clean)
@@ -26,6 +26,56 @@ _AMBIGUOUS_ENTML_PREFIXES = (
26
26
  "<entml:parameters",
27
27
  )
28
28
  _INVOKE_PREFIX = "<entml:invoke"
29
+ _FUNCTION_CALLS_PREFIX = "<entml:function_calls"
30
+ _LEADING_HOLD_PREFIXES = _AMBIGUOUS_ENTML_PREFIXES + (
31
+ _THINKING_OPEN_PREFIX,
32
+ _FUNCTION_CALLS_PREFIX,
33
+ "<tool",
34
+ "<assistant",
35
+ )
36
+
37
+
38
+ def leading_entml_tag_holdback_len(
39
+ text: str,
40
+ *,
41
+ thinking_enabled: bool = True,
42
+ ) -> int:
43
+ """buffer 开头是 entml/thinking/invoke 标签真前缀时,应 hold 的字节数。"""
44
+ if not text:
45
+ return 0
46
+ prefixes: List[str] = list(_LEADING_HOLD_PREFIXES)
47
+ if thinking_enabled:
48
+ prefixes.append(_PLAIN_THINKING_OPEN_PREFIX)
49
+ for prefix in prefixes:
50
+ if text.startswith(prefix):
51
+ return 0
52
+ max_hold = max(len(prefix) - 1 for prefix in prefixes)
53
+ for length in range(min(len(text), max_hold), 0, -1):
54
+ head = text[:length]
55
+ if any(prefix.startswith(head) and head != prefix for prefix in prefixes):
56
+ return length
57
+ return 0
58
+
59
+
60
+ def stream_safe_visible_prefix(
61
+ text: str,
62
+ *,
63
+ thinking_enabled: bool = True,
64
+ ) -> str:
65
+ """流式 UI 可安全展示的 prefix(不含未收齐的 entml/thinking 开标签)。"""
66
+ if not text:
67
+ return ""
68
+ hold = leading_entml_tag_holdback_len(text, thinking_enabled=thinking_enabled)
69
+ if hold >= len(text):
70
+ return ""
71
+ buf = text[hold:]
72
+ if has_unclosed_entml_thinking(buf, thinking_enabled=thinking_enabled):
73
+ open_at, _ = _find_earliest_thinking_open(
74
+ buf, thinking_enabled=thinking_enabled
75
+ )
76
+ if open_at >= 0:
77
+ return buf[:open_at]
78
+ return buf
29
79
 
30
80
 
31
81
  def find_complete_entml_invoke_open(buffer: str) -> int:
@@ -4,11 +4,17 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import re
7
- from typing import Any, Dict, List, Optional, Set, Tuple
7
+ from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
8
8
 
9
9
  from .entml_patterns import INVOKE_RE, normalize_entml_name
10
10
  from .entml_values import coerce_entml_arguments
11
11
 
12
+ _TOOL_BLOCK_OPEN_RE = re.compile(r"<tool\s*>", re.IGNORECASE)
13
+ _TOOL_BLOCK_CLOSE_RE = re.compile(
14
+ r"(?:</tool\s*>|</system\b[^>]*>|</assistant\s*>)",
15
+ re.IGNORECASE,
16
+ )
17
+ # legacy 整段匹配(测试/简单场景)
12
18
  _TOOL_BLOCK_RE = re.compile(
13
19
  r"<tool\s*>\s*([\s\S]*?)\s*</tool\s*>",
14
20
  re.IGNORECASE,
@@ -18,8 +24,9 @@ _INNER_TOOL_TAG_RE = re.compile(
18
24
  re.IGNORECASE,
19
25
  )
20
26
  _BRACE_TOOL_HEAD_RE = re.compile(r"\{([A-Za-z][A-Za-z0-9_]*)\s*:\s*")
21
- # 工具结果常见行号泄漏(Read 伪块)
27
+ # 工具结果常见行号泄漏(Read 伪块)— 仅用于多块 brace 判定
22
28
  _OUTPUT_LINE_RE = re.compile(r"^\s*\d+\s+\S", re.MULTILINE)
29
+ _SCALAR_ARG_KEYS = ("path", "command", "pattern", "query", "file_path")
23
30
 
24
31
 
25
32
  def _known_tool_names(
@@ -53,25 +60,116 @@ def _args_from_parsed(name: str, parsed: Any) -> Dict[str, Any]:
53
60
  return {"value": parsed}
54
61
 
55
62
 
63
+ def _args_from_scalar(
64
+ name: str,
65
+ scalar: str,
66
+ schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]],
67
+ ) -> Dict[str, Any]:
68
+ """``{Read: path/to/file}`` / ``{Bash: echo hi}`` 等标量行。"""
69
+ func_props = (schema_index or {}).get(name) or {}
70
+ if len(func_props) == 1:
71
+ key = next(iter(func_props))
72
+ return {key: scalar}
73
+ for key in _SCALAR_ARG_KEYS:
74
+ if key in func_props:
75
+ return {key: scalar}
76
+ return {"value": scalar}
77
+
78
+
79
+ def _looks_like_file_path(value: str) -> bool:
80
+ text = (value or "").strip()
81
+ if not text:
82
+ return False
83
+ if "/" in text or "\\" in text:
84
+ return True
85
+ if len(text) >= 2 and text[1] == ":" and text[0].isalpha():
86
+ return True
87
+ return False
88
+
89
+
56
90
  def _extract_first_brace_tool_call(body: str) -> Optional[Tuple[str, Any]]:
57
- """从正文提取首个 ``{Tool: json}``(允许后面跟工具输出 tail)。"""
91
+ """从正文提取首个 ``{Tool: json|scalar}``(允许后面跟工具输出 tail)。"""
58
92
  match = _BRACE_TOOL_HEAD_RE.search(body)
59
93
  if not match:
60
94
  return None
61
95
  name = normalize_entml_name(match.group(1))
62
96
  rest = body[match.end() :].lstrip()
63
- if not rest or rest[0] not in "{[":
97
+ if not rest:
64
98
  return None
65
- try:
66
- parsed, end = json.JSONDecoder().raw_decode(rest)
67
- except json.JSONDecodeError:
99
+ if rest[0] in "{[":
100
+ try:
101
+ parsed, end = json.JSONDecoder().raw_decode(rest)
102
+ except json.JSONDecodeError:
103
+ return None
104
+ after = rest[end:].lstrip()
105
+ if after.startswith("}"):
106
+ after = after[1:].lstrip()
107
+ # JSON + 行号 tail 为 history Read 伪块;标量 path + tail 仍解析(模型误格式真调用)
108
+ if after and _OUTPUT_LINE_RE.search(after):
109
+ return None
110
+ return name, parsed
111
+ close = rest.find("}")
112
+ if close < 0:
113
+ return None
114
+ scalar = rest[:close].strip()
115
+ if not scalar:
68
116
  return None
69
- after = rest[end:].lstrip()
70
- if after.startswith("}"):
71
- after = after[1:].lstrip()
72
- if after and _OUTPUT_LINE_RE.search(after):
117
+ after = rest[close + 1 :]
118
+ has_output_tail = bool(_OUTPUT_LINE_RE.search(after))
119
+ # 标量行:仅 path 型 Read/Glob 或带 Read 行号 tail;``{Bash: echo hi}`` 视为 history
120
+ if not has_output_tail and not _looks_like_file_path(scalar):
73
121
  return None
74
- return name, parsed
122
+ return name, scalar
123
+
124
+
125
+ def _iter_tool_block_spans(text: str) -> Iterator[Tuple[int, int, str]]:
126
+ """yield ``(open_start, close_end, body)``。"""
127
+ if not text or "<tool" not in text.lower():
128
+ return
129
+ pos = 0
130
+ while True:
131
+ open_m = _TOOL_BLOCK_OPEN_RE.search(text, pos)
132
+ if not open_m:
133
+ break
134
+ body_start = open_m.end()
135
+ close_m = _TOOL_BLOCK_CLOSE_RE.search(text, body_start)
136
+ if close_m:
137
+ close_end = close_m.end()
138
+ close_tag = text[close_m.start() : close_m.end()].lower()
139
+ if close_tag.startswith("</system"):
140
+ after = text[close_end:]
141
+ footer = re.match(
142
+ r"[^\n<]*(?:</system\s*>)?",
143
+ after,
144
+ re.IGNORECASE,
145
+ )
146
+ if footer:
147
+ close_end += footer.end()
148
+ after = text[close_end:]
149
+ asst = re.search(r"^\s*</assistant\s*>", after, re.IGNORECASE | re.MULTILINE)
150
+ if asst:
151
+ close_end += asst.end()
152
+ yield open_m.start(), close_end, text[body_start : close_m.start()]
153
+ pos = close_end
154
+ else:
155
+ yield open_m.start(), len(text), text[body_start:]
156
+ break
157
+
158
+
159
+ def strip_tool_block_spans(text: str) -> str:
160
+ """移除 ``<tool>…`` 块(含误用的 ``</system>`` / ``</assistant>`` 闭合)。"""
161
+ if not text or "<tool" not in text.lower():
162
+ return text
163
+ parts: List[str] = []
164
+ last = 0
165
+ for open_start, close_end, _body in _iter_tool_block_spans(text):
166
+ parts.append(text[last:open_start])
167
+ last = close_end
168
+ parts.append(text[last:])
169
+ out = "".join(parts)
170
+ out = re.sub(r"^\s*<assistant\s*>\s*\n+", "", out, count=1, flags=re.IGNORECASE)
171
+ out = re.sub(r"\n{3,}", "\n\n", out)
172
+ return out.strip()
75
173
 
76
174
 
77
175
  def _parse_inner_tag_calls(
@@ -114,7 +212,11 @@ def _parse_brace_calls(
114
212
  name, value = parsed
115
213
  if known and name not in known:
116
214
  return []
117
- args = coerce_entml_arguments(_args_from_parsed(name, value), name, schema_index)
215
+ if isinstance(value, str):
216
+ args = _args_from_scalar(name, value, schema_index)
217
+ else:
218
+ args = _args_from_parsed(name, value)
219
+ args = coerce_entml_arguments(args, name, schema_index)
118
220
  return [(name, args)]
119
221
 
120
222
 
@@ -152,9 +254,9 @@ def parse_tool_block_calls(
152
254
 
153
255
  allow_brace = not bool(INVOKE_RE.search(text))
154
256
  tool_calls: List[Dict[str, Any]] = []
155
- for match in _TOOL_BLOCK_RE.finditer(text):
257
+ for _open, _close, body in _iter_tool_block_spans(text):
156
258
  for name, args in parse_tool_block_body(
157
- match.group(1),
259
+ body,
158
260
  tools=tools,
159
261
  schema_index=schema_index,
160
262
  allow_brace_format=allow_brace,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.78
3
+ Version: 2.3.80
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -186,6 +186,7 @@ src/tests/test_dispatch_extended.py
186
186
  src/tests/test_dispatch_race.py
187
187
  src/tests/test_entml_agent_output_formats.py
188
188
  src/tests/test_entml_bare_invoke_format.py
189
+ src/tests/test_entml_batch_stream_comprehensive.py
189
190
  src/tests/test_entml_parser_comprehensive.py
190
191
  src/tests/test_entml_protocol.py
191
192
  src/tests/test_entml_stream_json.py