echotools 2.3.70__tar.gz → 2.3.71__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 (237) hide show
  1. {echotools-2.3.70 → echotools-2.3.71}/PKG-INFO +1 -1
  2. {echotools-2.3.70 → echotools-2.3.71}/pyproject.toml +1 -1
  3. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml.py +2 -2
  4. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/history_markup.py +34 -8
  5. {echotools-2.3.70 → echotools-2.3.71}/src/echotools.egg-info/PKG-INFO +1 -1
  6. {echotools-2.3.70 → echotools-2.3.71}/src/echotools.egg-info/SOURCES.txt +1 -0
  7. {echotools-2.3.70 → echotools-2.3.71}/src/tests/fixtures/simulated_fake_history_markup_responses.py +76 -2
  8. echotools-2.3.71/src/tests/test_fake_history_markup_reply_protect.py +166 -0
  9. {echotools-2.3.70 → echotools-2.3.71}/README.md +0 -0
  10. {echotools-2.3.70 → echotools-2.3.71}/docs/README.md +0 -0
  11. {echotools-2.3.70 → echotools-2.3.71}/setup.cfg +0 -0
  12. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/__init__.py +0 -0
  13. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/cache/__init__.py +0 -0
  14. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/cache/list_cache.py +0 -0
  15. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/cache/memory_cache.py +0 -0
  16. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/__init__.py +0 -0
  17. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/base.py +0 -0
  18. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/center.py +0 -0
  19. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/center_notify.py +0 -0
  20. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/center_template.py +0 -0
  21. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/loader.py +0 -0
  22. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/config/merge.py +0 -0
  23. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/errors/__init__.py +0 -0
  24. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/errors/base.py +0 -0
  25. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/errors/classify.py +0 -0
  26. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/errors/common.py +0 -0
  27. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/errors/http.py +0 -0
  28. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/ids/__init__.py +0 -0
  29. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/ids/generator.py +0 -0
  30. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/io/__init__.py +0 -0
  31. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/io/io_utils.py +0 -0
  32. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/io/printstream.py +0 -0
  33. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/logger/__init__.py +0 -0
  34. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/logger/manager.py +0 -0
  35. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/retry/__init__.py +0 -0
  36. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/base/retry/retry.py +0 -0
  37. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/compat.py +0 -0
  38. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/__init__.py +0 -0
  39. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/candidate.py +0 -0
  40. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  41. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  42. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/race.py +0 -0
  43. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/selector.py +0 -0
  44. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/dispatch/usage.py +0 -0
  45. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/__init__.py +0 -0
  46. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  47. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  48. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  49. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  50. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  51. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/prompt/history.py +0 -0
  52. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  53. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  54. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  55. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  56. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  57. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  58. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  59. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_stream_json.py +0 -0
  60. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  61. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  62. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  63. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_think/parse.py +0 -0
  64. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  65. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  66. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/registry.py +0 -0
  67. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  68. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  69. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  70. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  71. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  72. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  73. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/keys.py +0 -0
  74. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/lifecycle/__init__.py +0 -0
  75. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/lifecycle/manager.py +0 -0
  76. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/lifecycle/updater.py +0 -0
  77. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  78. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/process/__init__.py +0 -0
  79. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/process/port.py +0 -0
  80. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/protocol/__init__.py +0 -0
  81. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/protocol/base.py +0 -0
  82. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/__init__.py +0 -0
  83. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/conpty.py +0 -0
  84. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/local/__init__.py +0 -0
  85. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/local/core.py +0 -0
  86. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/local/proc.py +0 -0
  87. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/local/unix.py +0 -0
  88. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/local/win.py +0 -0
  89. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/sanitize.py +0 -0
  90. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/session/__init__.py +0 -0
  91. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/session/base.py +0 -0
  92. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/session/client.py +0 -0
  93. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/ssh.py +0 -0
  94. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/exec/terminal/tmux.py +0 -0
  95. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/__init__.py +0 -0
  96. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/charmap.py +0 -0
  97. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/spinner.py +0 -0
  98. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/ui.py +0 -0
  99. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  100. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  101. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  102. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  103. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  104. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  105. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/ui_console.py +0 -0
  106. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/ui_io.py +0 -0
  107. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/ui_log.py +0 -0
  108. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  109. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/ui_text.py +0 -0
  110. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uicore/ui_types.py +0 -0
  111. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  112. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  113. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  114. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  115. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  116. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  117. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  118. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  119. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  120. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  121. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  122. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/events/__init__.py +0 -0
  123. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/events/bus.py +0 -0
  124. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/events/event.py +0 -0
  125. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/spinner/__init__.py +0 -0
  126. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/spinner/spinner.py +0 -0
  127. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/spinner/spinner_color.py +0 -0
  128. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/tracing/__init__.py +0 -0
  129. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/tracing/context.py +0 -0
  130. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/tracing/span.py +0 -0
  131. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/tracing/tracer.py +0 -0
  132. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/translate.py +0 -0
  133. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/watcher/__init__.py +0 -0
  134. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/watcher/file_watcher.py +0 -0
  135. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/__init__.py +0 -0
  136. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/application.py +0 -0
  137. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/broker.py +0 -0
  138. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/app.js +0 -0
  139. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  140. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  141. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  142. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  143. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  144. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  145. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  146. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  147. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  148. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  149. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/inputbox.css +0 -0
  150. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/inputbox.js +0 -0
  151. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/style.css +0 -0
  152. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/input_box/voicegif.js +0 -0
  153. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/middleware.py +0 -0
  154. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/stats.py +0 -0
  155. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/media/web/utils.py +0 -0
  156. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/files/__init__.py +0 -0
  157. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/files/file_util.py +0 -0
  158. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/network/__init__.py +0 -0
  159. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/network/http_utils.py +0 -0
  160. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/plugin/__init__.py +0 -0
  161. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/plugin/base.py +0 -0
  162. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/plugin/discovery.py +0 -0
  163. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/plugin/registry.py +0 -0
  164. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/plugin/registry_ops.py +0 -0
  165. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/proxy/__init__.py +0 -0
  166. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/proxy/manager.py +0 -0
  167. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/runtime/__init__.py +0 -0
  168. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/runtime/collector.py +0 -0
  169. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/scheduler/__init__.py +0 -0
  170. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/scheduler/scheduler.py +0 -0
  171. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/sdk/__init__.py +0 -0
  172. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/plat/sdk/facade.py +0 -0
  173. {echotools-2.3.70 → echotools-2.3.71}/src/echotools/version.py +0 -0
  174. {echotools-2.3.70 → echotools-2.3.71}/src/echotools.egg-info/dependency_links.txt +0 -0
  175. {echotools-2.3.70 → echotools-2.3.71}/src/echotools.egg-info/requires.txt +0 -0
  176. {echotools-2.3.70 → echotools-2.3.71}/src/echotools.egg-info/top_level.txt +0 -0
  177. {echotools-2.3.70 → echotools-2.3.71}/src/tests/bench_import.py +0 -0
  178. {echotools-2.3.70 → echotools-2.3.71}/src/tests/conftest.py +0 -0
  179. {echotools-2.3.70 → echotools-2.3.71}/src/tests/fixtures/__init__.py +0 -0
  180. {echotools-2.3.70 → echotools-2.3.71}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
  181. {echotools-2.3.70 → echotools-2.3.71}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  182. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_adversarial_stream_splits.py +0 -0
  183. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_cache_retry.py +0 -0
  184. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_completion.py +0 -0
  185. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_config.py +0 -0
  186. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_config_extended.py +0 -0
  187. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_console.py +0 -0
  188. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_coverage_core.py +0 -0
  189. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_dispatch.py +0 -0
  190. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_dispatch_extended.py +0 -0
  191. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_dispatch_race.py +0 -0
  192. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_agent_output_formats.py +0 -0
  193. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_bare_invoke_format.py +0 -0
  194. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_parser_comprehensive.py +0 -0
  195. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_protocol.py +0 -0
  196. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_stream_json.py +0 -0
  197. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  198. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_thinking_history.py +0 -0
  199. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_thinking_parse.py +0 -0
  200. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_tool_parse_leak.py +0 -0
  201. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_tool_pipeline.py +0 -0
  202. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_entml_tools.py +0 -0
  203. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_errors.py +0 -0
  204. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_errors_common.py +0 -0
  205. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_events.py +0 -0
  206. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_events_event.py +0 -0
  207. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_facade.py +0 -0
  208. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fake_history_markup_batch.py +0 -0
  209. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
  210. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fake_history_markup_stream.py +0 -0
  211. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fault_thinking_close_batch.py +0 -0
  212. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fault_thinking_close_stream.py +0 -0
  213. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fncall.py +0 -0
  214. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_fncall_stream_thinking.py +0 -0
  215. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_history_markup_filter.py +0 -0
  216. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_ids.py +0 -0
  217. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_imports.py +0 -0
  218. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_io.py +0 -0
  219. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_keys.py +0 -0
  220. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_lifecycle.py +0 -0
  221. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_logger.py +0 -0
  222. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_merge.py +0 -0
  223. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_model_branch_matrix.py +0 -0
  224. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_plugin.py +0 -0
  225. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_printstream.py +0 -0
  226. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_protocols.py +0 -0
  227. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_proxy.py +0 -0
  228. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_proxy_selector.py +0 -0
  229. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_runtime.py +0 -0
  230. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_scheduler.py +0 -0
  231. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  232. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_stream_partial_json_parity.py +0 -0
  233. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_stream_realtime_parse.py +0 -0
  234. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_tracing.py +0 -0
  235. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_usage.py +0 -0
  236. {echotools-2.3.70 → echotools-2.3.71}/src/tests/test_web.py +0 -0
  237. {echotools-2.3.70 → echotools-2.3.71}/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.70
3
+ Version: 2.3.71
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.70"
7
+ version = "2.3.71"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -287,11 +287,11 @@ class EntmlProtocol(ToolProtocol):
287
287
  parse_text, _ = strip_fake_history_markup(parse_text)
288
288
  tool_calls = parse_entml_tool_calls(parse_text, tools, schema_index)
289
289
  clean = text[:unclosed_open_at] if unclosed_open_at >= 0 else text
290
+ # 须在移除 invoke 之前剥离伪 history,否则未闭合 <tool> 会把 invoke 之后的可见回复误删。
291
+ clean, _ = strip_fake_history_markup(clean)
290
292
  if tool_calls:
291
293
  clean = BLOCK_RE.sub("", clean)
292
- # 无论是否解析成功,都剥离工具相关残留,避免标签泄露;thinking 保留给后续 split。
293
294
  clean = strip_tool_entml_residue(clean)
294
- clean, _ = strip_fake_history_markup(clean)
295
295
  return (clean, normalize_tool_calls(tool_calls, tools))
296
296
 
297
297
  def parse_fragment(
@@ -33,6 +33,15 @@ _ENTML_INVOKE_BLOCK_RE = re.compile(
33
33
  r"<entml:invoke\b[^>]*>[\s\S]*?</entml:invoke>",
34
34
  re.IGNORECASE,
35
35
  )
36
+ _ENTML_FUNCTION_CALLS_OPEN_RE = re.compile(
37
+ r"<entml:function_calls\b",
38
+ re.IGNORECASE,
39
+ )
40
+ _ENTML_FUNCTION_CALLS_BLOCK_RE = re.compile(
41
+ r"<entml:function_calls\b[^>]*>[\s\S]*?</entml:function_calls>",
42
+ re.IGNORECASE,
43
+ )
44
+ _ENTML_FUNCTION_CALLS_CLOSE = "</entml:function_calls>"
36
45
 
37
46
 
38
47
  class HistoryMarkupDetectionResult:
@@ -96,19 +105,25 @@ def _orphan_open_re(tag: str) -> re.Pattern[str]:
96
105
 
97
106
 
98
107
  def _split_entml_invoke_protected(segment: str) -> List[Tuple[str, bool]]:
99
- """按 ``<entml:invoke>`` 切分;invoke 块(含未闭合尾部)不参与伪 history 剥离。"""
108
+ """按 ``<entml:function_calls>`` / ``<entml:invoke>`` 切分;保护区不参与伪 history 剥离。"""
100
109
  if not segment:
101
110
  return []
102
111
  parts: List[Tuple[str, bool]] = []
103
112
  i = 0
104
113
  while i < len(segment):
105
- open_m = _ENTML_INVOKE_OPEN_RE.search(segment, i)
106
- if not open_m:
114
+ fc_m = _ENTML_FUNCTION_CALLS_OPEN_RE.search(segment, i)
115
+ inv_m = _ENTML_INVOKE_OPEN_RE.search(segment, i)
116
+ candidates = [m for m in (fc_m, inv_m) if m]
117
+ if not candidates:
107
118
  parts.append((segment[i:], False))
108
119
  break
120
+ open_m = min(candidates, key=lambda m: m.start())
109
121
  if open_m.start() > i:
110
122
  parts.append((segment[i : open_m.start()], False))
111
- block_m = _ENTML_INVOKE_BLOCK_RE.match(segment, open_m.start())
123
+ if open_m is fc_m:
124
+ block_m = _ENTML_FUNCTION_CALLS_BLOCK_RE.match(segment, open_m.start())
125
+ else:
126
+ block_m = _ENTML_INVOKE_BLOCK_RE.match(segment, open_m.start())
112
127
  if block_m:
113
128
  parts.append((block_m.group(0), True))
114
129
  i = block_m.end()
@@ -119,9 +134,9 @@ def _split_entml_invoke_protected(segment: str) -> List[Tuple[str, bool]]:
119
134
 
120
135
 
121
136
  def _open_before_entml_re(tag: str) -> re.Pattern[str]:
122
- """``<tool>\\n…`` 未闭合但在 ``<entml:invoke`` 前 — 整段剥离。"""
137
+ """``<tool>\\n…`` 未闭合但在 ``<entml:(function_calls|invoke)`` 前 — 整段剥离。"""
123
138
  return re.compile(
124
- rf"<{tag}\s*>\s*\n[\s\S]*?(?=<entml:invoke\b)",
139
+ rf"<{tag}\s*>\s*\n[\s\S]*?(?=<entml:(?:function_calls|invoke)\b)",
125
140
  re.IGNORECASE,
126
141
  )
127
142
 
@@ -250,8 +265,19 @@ def strip_fake_history_markup_for_display(content: str) -> Tuple[str, bool]:
250
265
  """流式 ``partial_text`` 用:完整块剥离 + 截断行尾未收齐的伪标签。"""
251
266
  cleaned, found = strip_fake_history_markup(content)
252
267
  invoke_m = _ENTML_INVOKE_OPEN_RE.search(cleaned)
253
- if invoke_m and "</entml:invoke>" not in cleaned[invoke_m.start() :]:
254
- return cleaned, found
268
+ fc_m = _ENTML_FUNCTION_CALLS_OPEN_RE.search(cleaned)
269
+ prot_m = None
270
+ if invoke_m and fc_m:
271
+ prot_m = invoke_m if invoke_m.start() <= fc_m.start() else fc_m
272
+ else:
273
+ prot_m = invoke_m or fc_m
274
+ if prot_m:
275
+ tail = cleaned[prot_m.start() :]
276
+ invoke_open = _ENTML_INVOKE_OPEN_RE.search(tail)
277
+ if invoke_open and "</entml:invoke>" not in tail[invoke_open.start() :]:
278
+ return cleaned, found
279
+ if _ENTML_FUNCTION_CALLS_OPEN_RE.search(tail) and _ENTML_FUNCTION_CALLS_CLOSE not in tail:
280
+ return cleaned, found
255
281
  tail = re.search(
256
282
  r"(?:^|\n)\s*(?:</?(?:assistant|tool)\b[^\n]*|</thinking>\s*(?:<(?:assistant|tool)\b[^\n]*)?)$",
257
283
  cleaned,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.70
3
+ Version: 2.3.71
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -201,6 +201,7 @@ src/tests/test_events_event.py
201
201
  src/tests/test_facade.py
202
202
  src/tests/test_fake_history_markup_batch.py
203
203
  src/tests/test_fake_history_markup_invoke_preserve.py
204
+ src/tests/test_fake_history_markup_reply_protect.py
204
205
  src/tests/test_fake_history_markup_stream.py
205
206
  src/tests/test_fault_thinking_close_batch.py
206
207
  src/tests/test_fault_thinking_close_stream.py
@@ -5,10 +5,10 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- from dataclasses import dataclass, field
8
+ from dataclasses import dataclass
9
9
  from typing import Any, Dict, List, Optional, Tuple
10
10
 
11
- from fixtures.simulated_llm_tool_responses import TOOLS, _TOOL_BY_NAME
11
+ from fixtures.simulated_llm_tool_responses import _TOOL_BY_NAME, TOOLS
12
12
 
13
13
  READ_TOOL: Dict[str, Any] = {
14
14
  "type": "function",
@@ -400,6 +400,80 @@ FAKE_HISTORY_MARKUP_CASES: Tuple[HistoryMarkupCase, ...] = (
400
400
  expect_clean_excludes=("{Read: x}",),
401
401
  worst_split=True,
402
402
  ),
403
+ HistoryMarkupCase(
404
+ id="unclosed_fake_tool_invoke_trailing_reply",
405
+ description="未闭合伪 <tool> + invoke + 可见尾句:batch/stream 均须保留尾句",
406
+ extra_tools=("Read", "Edit"),
407
+ response=(
408
+ "前言\n"
409
+ "<tool>\n"
410
+ '{Edit: {"path": "a.py", "old_string": "x", "new_string": "y"}}\n'
411
+ f"{REAL_READ_INVOKE}\n"
412
+ "尾句保留。"
413
+ ),
414
+ expect_names=("Read",),
415
+ expect_args=({"path": "src/main.py"},),
416
+ expect_call_count=1,
417
+ expect_clean_contains=("前言", "尾句保留"),
418
+ expect_clean_excludes=("Edit", "<tool>"),
419
+ partial_leak_markers=("<tool>", "{Edit:", "entml:parameter"),
420
+ worst_split=True,
421
+ ),
422
+ HistoryMarkupCase(
423
+ id="function_calls_wrap_trailing_reply",
424
+ description="function_calls 包裹 invoke + 尾部可见回复",
425
+ extra_tools=("Read",),
426
+ response=(
427
+ "说明。\n"
428
+ "<entml:function_calls>\n"
429
+ f"{REAL_READ_INVOKE}\n"
430
+ "</entml:function_calls>\n"
431
+ "后续说明。"
432
+ ),
433
+ expect_names=("Read",),
434
+ expect_args=({"path": "src/main.py"},),
435
+ expect_call_count=1,
436
+ expect_clean_contains=("说明", "后续说明"),
437
+ expect_clean_excludes=("<tool>",),
438
+ partial_leak_markers=("<tool>", "{Edit:"),
439
+ ),
440
+ HistoryMarkupCase(
441
+ id="invoke_then_fake_tool_then_reply",
442
+ description="真实 invoke 后误写伪 tool,须剥离伪块保留尾句",
443
+ extra_tools=("Read", "Edit"),
444
+ response=(
445
+ f"{REAL_READ_INVOKE}\n"
446
+ "<tool>\n"
447
+ '{Edit: {"path": "a.py"}}\n'
448
+ "</tool>\n"
449
+ "尾句。"
450
+ ),
451
+ expect_names=("Read",),
452
+ expect_args=({"path": "src/main.py"},),
453
+ expect_call_count=1,
454
+ expect_clean_contains=("尾句",),
455
+ expect_clean_excludes=("Edit", "<tool>"),
456
+ ),
457
+ HistoryMarkupCase(
458
+ id="fake_tool_inside_function_calls_with_invoke",
459
+ description="伪 <tool> 块内含 function_calls+invoke,不得误删 invoke",
460
+ extra_tools=("Read", "Edit"),
461
+ response=(
462
+ "<tool>\n"
463
+ '{Edit: {"path": "a.py"}}\n'
464
+ "<entml:function_calls>\n"
465
+ f"{REAL_READ_INVOKE}\n"
466
+ "</entml:function_calls>\n"
467
+ "</tool>\n"
468
+ "尾句。"
469
+ ),
470
+ expect_names=("Read",),
471
+ expect_args=({"path": "src/main.py"},),
472
+ expect_call_count=1,
473
+ expect_clean_contains=("尾句",),
474
+ expect_clean_excludes=("Edit", "<tool>"),
475
+ worst_split=True,
476
+ ),
403
477
  )
404
478
 
405
479
 
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ """batch parse 与 stream finalize 对 invoke / 可见回复的保护须一致。"""
4
+
5
+ import json
6
+ from typing import Any, Dict, List
7
+
8
+ import pytest
9
+ from fixtures.simulated_fake_history_markup_responses import (
10
+ REAL_READ_INVOKE,
11
+ REAL_WEATHER_INVOKE,
12
+ HistoryMarkupCase,
13
+ iter_fake_history_markup_cases,
14
+ tools_for_markup_case,
15
+ )
16
+
17
+ from echotools.exec.fncall import get_protocol
18
+ from echotools.exec.fncall.parsers.stream import FncallStreamParser
19
+ from echotools.exec.fncall.shared.history_markup import strip_fake_history_markup
20
+
21
+ INVOKE_REPLY_CASES = [
22
+ c
23
+ for c in iter_fake_history_markup_cases()
24
+ if c.expect_names
25
+ or c.id
26
+ in (
27
+ "unclosed_fake_tool_invoke_trailing_reply",
28
+ "function_calls_wrap_trailing_reply",
29
+ "invoke_then_fake_tool_then_reply",
30
+ "fake_tool_inside_function_calls_with_invoke",
31
+ )
32
+ ]
33
+
34
+ EXTRA_CASES = (
35
+ HistoryMarkupCase(
36
+ id="visible_reply_before_and_after_invoke",
37
+ description="invoke 前后均有可见说明",
38
+ extra_tools=("Read",),
39
+ response=(
40
+ "开头说明。\n"
41
+ f"{REAL_READ_INVOKE}\n"
42
+ "结尾说明。"
43
+ ),
44
+ expect_names=("Read",),
45
+ expect_args=({"path": "src/main.py"},),
46
+ expect_call_count=1,
47
+ expect_clean_contains=("开头说明", "结尾说明"),
48
+ expect_clean_excludes=("<tool>",),
49
+ partial_leak_markers=("{Edit:", "\n<tool>\n"),
50
+ ),
51
+ HistoryMarkupCase(
52
+ id="thinking_visible_fake_invoke_reply",
53
+ description="thinking + 可见区伪块 + invoke + 尾句",
54
+ extra_tools=("Read", "Edit"),
55
+ response=(
56
+ "<entml:thinking>\nplan\n</entml:thinking>\n"
57
+ "中间说明\n"
58
+ "<tool>\n{Edit: x}\n</tool>\n"
59
+ f"{REAL_READ_INVOKE}\n"
60
+ "最终回复"
61
+ ),
62
+ expect_names=("Read",),
63
+ expect_args=({"path": "src/main.py"},),
64
+ expect_call_count=1,
65
+ expect_clean_contains=("中间说明", "最终回复"),
66
+ expect_clean_excludes=("Edit", "<tool>"),
67
+ ),
68
+ HistoryMarkupCase(
69
+ id="weather_after_unclosed_fake_tool",
70
+ description="未闭合伪 tool 后直接 weather invoke + 尾句",
71
+ response=(
72
+ "参考\n<tool>\n{get_weather: x}\n"
73
+ f"{REAL_WEATHER_INVOKE}\n"
74
+ "确认完毕。"
75
+ ),
76
+ expect_names=("get_weather",),
77
+ expect_args=({"city": "杭州", "unit": "c"},),
78
+ expect_call_count=1,
79
+ expect_clean_contains=("参考", "确认完毕"),
80
+ expect_clean_excludes=("{get_weather:", "<tool>"),
81
+ worst_split=True,
82
+ ),
83
+ )
84
+
85
+ ALL_CASES = INVOKE_REPLY_CASES + list(EXTRA_CASES)
86
+
87
+
88
+ def _names(calls: List[Dict[str, Any]]) -> List[str]:
89
+ return [c["function"]["name"] for c in calls]
90
+
91
+
92
+ def _args(calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
93
+ return [json.loads(c["function"]["arguments"]) for c in calls]
94
+
95
+
96
+ def _batch(case: HistoryMarkupCase) -> tuple[str, List[Dict[str, Any]]]:
97
+ return get_protocol("entml").parse(case.response, tools_for_markup_case(case))
98
+
99
+
100
+ def _stream(case: HistoryMarkupCase, chunk: int) -> tuple[str, List[Dict[str, Any]]]:
101
+ parser = FncallStreamParser(
102
+ protocol=get_protocol("entml"),
103
+ tools=tools_for_markup_case(case),
104
+ )
105
+ text = case.response
106
+ for i in range(0, len(text), chunk):
107
+ parser.feed(text[i : i + chunk])
108
+ return parser.finalize()
109
+
110
+
111
+ def _assert_case(case: HistoryMarkupCase, clean: str, calls: List[Dict[str, Any]], label: str) -> None:
112
+ assert len(calls) == case.expect_call_count, f"{case.id}/{label}: call count"
113
+ if case.expect_names:
114
+ assert _names(calls) == list(case.expect_names), f"{case.id}/{label}: names"
115
+ if case.expect_args:
116
+ assert _args(calls) == list(case.expect_args), f"{case.id}/{label}: args"
117
+ for needle in case.expect_clean_contains:
118
+ assert needle in clean, f"{case.id}/{label}: missing {needle!r}"
119
+ for bad in case.expect_clean_excludes:
120
+ assert bad not in clean, f"{case.id}/{label}: leaked {bad!r}"
121
+ assert "entml:invoke" not in clean, f"{case.id}/{label}: invoke tag in clean"
122
+
123
+
124
+ @pytest.mark.parametrize("case", ALL_CASES, ids=lambda c: c.id)
125
+ def test_batch_stream_parity(case: HistoryMarkupCase) -> None:
126
+ batch_clean, batch_calls = _batch(case)
127
+ stream_clean, stream_calls = _stream(case, 3)
128
+ _assert_case(case, batch_clean, batch_calls, "batch")
129
+ _assert_case(case, stream_clean, stream_calls, "stream")
130
+ for needle in case.expect_clean_contains:
131
+ assert needle in batch_clean and needle in stream_clean, case.id
132
+
133
+
134
+ @pytest.mark.parametrize("case", ALL_CASES, ids=lambda c: c.id)
135
+ @pytest.mark.parametrize("chunk", [1, 8, 17, 64], ids=lambda n: f"c{n}")
136
+ def test_stream_chunk_sizes(case: HistoryMarkupCase, chunk: int) -> None:
137
+ clean, calls = _stream(case, chunk)
138
+ _assert_case(case, clean, calls, f"c{chunk}")
139
+
140
+
141
+ @pytest.mark.parametrize("case", ALL_CASES, ids=lambda c: c.id)
142
+ def test_strip_preserves_invoke_and_reply(case: HistoryMarkupCase) -> None:
143
+ if not case.expect_names:
144
+ return
145
+ stripped, _ = strip_fake_history_markup(case.response)
146
+ assert "entml:invoke" in stripped, case.id
147
+ for needle in case.expect_clean_contains:
148
+ if needle not in ("说明", "参考历史", "我再确认一次"):
149
+ assert needle in stripped or needle in case.response, case.id
150
+
151
+
152
+ def test_function_calls_partial_display() -> None:
153
+ partial = (
154
+ "说明。\n<tool>\n{Edit: x}\n"
155
+ "<entml:function_calls>\n"
156
+ '<entml:invoke name="Read">\n'
157
+ '<entml:parameter name="path">a.py'
158
+ )
159
+ from echotools.exec.fncall.shared.history_markup import (
160
+ strip_fake_history_markup_for_display,
161
+ )
162
+
163
+ display, _ = strip_fake_history_markup_for_display(partial)
164
+ assert "说明" in display
165
+ assert "<entml:invoke" in display
166
+ assert "entml:parameter" not in display or "<entml:invoke" in display
File without changes
File without changes
File without changes