echotools 2.3.94__tar.gz → 2.3.97__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 (249) hide show
  1. {echotools-2.3.94 → echotools-2.3.97}/PKG-INFO +1 -1
  2. {echotools-2.3.94 → echotools-2.3.97}/pyproject.toml +1 -1
  3. echotools-2.3.97/src/echotools/exec/fncall/protocols/entml_fake_structure_markup.py +198 -0
  4. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/parse.py +12 -4
  5. echotools-2.3.97/src/echotools/exec/fncall/protocols/entml_tool_result_comment.py +70 -0
  6. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/history_markup.py +9 -2
  7. {echotools-2.3.94 → echotools-2.3.97}/src/echotools.egg-info/PKG-INFO +1 -1
  8. {echotools-2.3.94 → echotools-2.3.97}/src/echotools.egg-info/SOURCES.txt +3 -0
  9. echotools-2.3.97/src/tests/test_entml_fake_structure_markup.py +211 -0
  10. {echotools-2.3.94 → echotools-2.3.97}/README.md +0 -0
  11. {echotools-2.3.94 → echotools-2.3.97}/setup.cfg +0 -0
  12. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/__init__.py +0 -0
  13. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/cache/__init__.py +0 -0
  14. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/cache/list_cache.py +0 -0
  15. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/cache/memory_cache.py +0 -0
  16. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/__init__.py +0 -0
  17. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/base.py +0 -0
  18. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/center.py +0 -0
  19. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/center_notify.py +0 -0
  20. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/center_template.py +0 -0
  21. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/loader.py +0 -0
  22. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/config/merge.py +0 -0
  23. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/errors/__init__.py +0 -0
  24. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/errors/base.py +0 -0
  25. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/errors/classify.py +0 -0
  26. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/errors/common.py +0 -0
  27. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/errors/http.py +0 -0
  28. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/ids/__init__.py +0 -0
  29. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/ids/generator.py +0 -0
  30. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/io/__init__.py +0 -0
  31. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/io/io_utils.py +0 -0
  32. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/io/printstream.py +0 -0
  33. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/logger/__init__.py +0 -0
  34. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/logger/manager.py +0 -0
  35. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/retry/__init__.py +0 -0
  36. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/base/retry/retry.py +0 -0
  37. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/compat.py +0 -0
  38. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/__init__.py +0 -0
  39. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/candidate.py +0 -0
  40. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  41. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  42. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/race.py +0 -0
  43. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/selector.py +0 -0
  44. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/dispatch/usage.py +0 -0
  45. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/__init__.py +0 -0
  46. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  47. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  48. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/stream_delta.py +0 -0
  49. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/stream_feed.py +0 -0
  50. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/stream_final.py +0 -0
  51. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  52. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  53. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  54. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/behavior_blocks.py +0 -0
  55. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/history.py +0 -0
  56. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  57. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  58. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  59. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  60. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  61. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  62. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  63. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  64. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_schema.py +0 -0
  65. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_stream/__init__.py +0 -0
  66. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_stream/body.py +0 -0
  67. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_stream/snap.py +0 -0
  68. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  69. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/blocks.py +0 -0
  70. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  71. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/filter.py +0 -0
  72. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  73. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/protocols/entml_think/hold.py +0 -0
  74. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/registry.py +0 -0
  75. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  76. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  77. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  78. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  79. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  80. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  81. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/keys.py +0 -0
  82. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/lifecycle/__init__.py +0 -0
  83. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/lifecycle/manager.py +0 -0
  84. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/lifecycle/updater.py +0 -0
  85. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  86. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/process/__init__.py +0 -0
  87. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/process/port.py +0 -0
  88. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/protocol/__init__.py +0 -0
  89. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/protocol/base.py +0 -0
  90. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/__init__.py +0 -0
  91. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/conpty.py +0 -0
  92. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/local/__init__.py +0 -0
  93. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/local/core.py +0 -0
  94. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/local/proc.py +0 -0
  95. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/local/unix.py +0 -0
  96. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/local/win.py +0 -0
  97. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/sanitize.py +0 -0
  98. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/session/__init__.py +0 -0
  99. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/session/base.py +0 -0
  100. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/session/client.py +0 -0
  101. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/ssh.py +0 -0
  102. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/exec/terminal/tmux.py +0 -0
  103. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/__init__.py +0 -0
  104. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/charmap.py +0 -0
  105. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/spinner.py +0 -0
  106. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/ui.py +0 -0
  107. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  108. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  109. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  110. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  111. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  112. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  113. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/ui_console.py +0 -0
  114. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/ui_io.py +0 -0
  115. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/ui_log.py +0 -0
  116. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  117. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/ui_text.py +0 -0
  118. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uicore/ui_types.py +0 -0
  119. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  120. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  121. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  122. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  123. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  124. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  125. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  126. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  127. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  128. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  129. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  130. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/events/__init__.py +0 -0
  131. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/events/bus.py +0 -0
  132. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/events/event.py +0 -0
  133. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/spinner/__init__.py +0 -0
  134. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/spinner/spinner.py +0 -0
  135. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/spinner/spinner_color.py +0 -0
  136. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/tracing/__init__.py +0 -0
  137. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/tracing/context.py +0 -0
  138. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/tracing/span.py +0 -0
  139. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/tracing/tracer.py +0 -0
  140. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/translate.py +0 -0
  141. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/watcher/__init__.py +0 -0
  142. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/watcher/file_watcher.py +0 -0
  143. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/__init__.py +0 -0
  144. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/application.py +0 -0
  145. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/broker.py +0 -0
  146. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/app.js +0 -0
  147. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  148. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  149. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  150. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  151. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  152. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  153. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  154. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  155. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  156. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  157. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/inputbox.css +0 -0
  158. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/inputbox.js +0 -0
  159. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/style.css +0 -0
  160. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/input_box/voicegif.js +0 -0
  161. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/middleware.py +0 -0
  162. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/stats.py +0 -0
  163. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/media/web/utils.py +0 -0
  164. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/files/__init__.py +0 -0
  165. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/files/file_util.py +0 -0
  166. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/network/__init__.py +0 -0
  167. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/network/http_utils.py +0 -0
  168. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/plugin/__init__.py +0 -0
  169. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/plugin/base.py +0 -0
  170. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/plugin/discovery.py +0 -0
  171. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/plugin/registry.py +0 -0
  172. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/plugin/registry_ops.py +0 -0
  173. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/proxy/__init__.py +0 -0
  174. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/proxy/manager.py +0 -0
  175. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/runtime/__init__.py +0 -0
  176. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/runtime/collector.py +0 -0
  177. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/scheduler/__init__.py +0 -0
  178. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/scheduler/scheduler.py +0 -0
  179. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/sdk/__init__.py +0 -0
  180. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/plat/sdk/facade.py +0 -0
  181. {echotools-2.3.94 → echotools-2.3.97}/src/echotools/version.py +0 -0
  182. {echotools-2.3.94 → echotools-2.3.97}/src/echotools.egg-info/dependency_links.txt +0 -0
  183. {echotools-2.3.94 → echotools-2.3.97}/src/echotools.egg-info/requires.txt +0 -0
  184. {echotools-2.3.94 → echotools-2.3.97}/src/echotools.egg-info/top_level.txt +0 -0
  185. {echotools-2.3.94 → echotools-2.3.97}/src/tests/bench_import.py +0 -0
  186. {echotools-2.3.94 → echotools-2.3.97}/src/tests/conftest.py +0 -0
  187. {echotools-2.3.94 → echotools-2.3.97}/src/tests/fixtures/__init__.py +0 -0
  188. {echotools-2.3.94 → echotools-2.3.97}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
  189. {echotools-2.3.94 → echotools-2.3.97}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
  190. {echotools-2.3.94 → echotools-2.3.97}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  191. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_adversarial_stream_splits.py +0 -0
  192. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_cache_retry.py +0 -0
  193. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_completion.py +0 -0
  194. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_config.py +0 -0
  195. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_config_extended.py +0 -0
  196. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_console.py +0 -0
  197. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_coverage_core.py +0 -0
  198. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_dispatch.py +0 -0
  199. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_dispatch_extended.py +0 -0
  200. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_dispatch_race.py +0 -0
  201. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_agent_output_formats.py +0 -0
  202. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_bare_invoke_format.py +0 -0
  203. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_batch_stream_comprehensive.py +0 -0
  204. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_invoke_known_tool_gate.py +0 -0
  205. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_parser_comprehensive.py +0 -0
  206. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_protocol.py +0 -0
  207. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_stream_json.py +0 -0
  208. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  209. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_thinking_history.py +0 -0
  210. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_thinking_parse.py +0 -0
  211. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_tool_parse_leak.py +0 -0
  212. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_tool_pipeline.py +0 -0
  213. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_entml_tools.py +0 -0
  214. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_errors.py +0 -0
  215. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_errors_common.py +0 -0
  216. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_events.py +0 -0
  217. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_events_event.py +0 -0
  218. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_facade.py +0 -0
  219. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fake_history_markup_batch.py +0 -0
  220. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
  221. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
  222. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fake_history_markup_stream.py +0 -0
  223. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fault_thinking_close_batch.py +0 -0
  224. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fault_thinking_close_stream.py +0 -0
  225. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fncall.py +0 -0
  226. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_fncall_stream_thinking.py +0 -0
  227. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_history_markup_filter.py +0 -0
  228. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_ids.py +0 -0
  229. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_imports.py +0 -0
  230. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_io.py +0 -0
  231. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_keys.py +0 -0
  232. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_lifecycle.py +0 -0
  233. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_logger.py +0 -0
  234. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_merge.py +0 -0
  235. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_model_branch_matrix.py +0 -0
  236. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_plugin.py +0 -0
  237. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_printstream.py +0 -0
  238. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_protocols.py +0 -0
  239. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_proxy.py +0 -0
  240. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_proxy_selector.py +0 -0
  241. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_runtime.py +0 -0
  242. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_scheduler.py +0 -0
  243. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  244. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_stream_partial_json_parity.py +0 -0
  245. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_stream_realtime_parse.py +0 -0
  246. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_tracing.py +0 -0
  247. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_usage.py +0 -0
  248. {echotools-2.3.94 → echotools-2.3.97}/src/tests/test_web.py +0 -0
  249. {echotools-2.3.94 → echotools-2.3.97}/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.94
3
+ Version: 2.3.97
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.94"
7
+ version = "2.3.97"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,198 @@
1
+ """剥离模型误生成的 entml 顶层结构标签与 Tool Result ID 注释。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Tuple
7
+
8
+ from echotools.exec.fncall.protocols.entml_tool_result_comment import (
9
+ leading_partial_tool_result_id_comment_len,
10
+ strip_complete_tool_result_id_comments,
11
+ trailing_partial_tool_result_id_comment_len,
12
+ )
13
+
14
+ # Step 1:带 id 的 result 整块(含内部正文)剥离。
15
+ _ENTML_RESULT_ID_BLOCK_RE = re.compile(
16
+ r"<entml:result\b[^>]*\bid\s*=[^>]*>[\s\S]*?</entml:result\s*>",
17
+ re.IGNORECASE,
18
+ )
19
+ _ENTML_RESULT_ID_OPEN_INNER_RE = re.compile(
20
+ r"<entml:result\b[^>]*\bid\s*=",
21
+ re.IGNORECASE,
22
+ )
23
+
24
+ # Step 2:仅剥离开/闭标签(``>`` 闭合即移除,不删标签间/后正文)。
25
+ _TAG_ONLY_OPEN_RES = (
26
+ re.compile(r"<entml:funtions_results\b[^>]*>", re.IGNORECASE),
27
+ re.compile(r"<entml:conversation_history\b[^>]*>", re.IGNORECASE),
28
+ re.compile(r"<entml:calls\b[^>]*>", re.IGNORECASE),
29
+ re.compile(r"<function_calling_behavior\b[^>]*>", re.IGNORECASE),
30
+ re.compile(r"<thinking_behavior\b[^>]*>", re.IGNORECASE),
31
+ re.compile(r"<entml:result\b[^>]*>", re.IGNORECASE),
32
+ )
33
+ _TAG_ONLY_CLOSE_RES = (
34
+ re.compile(r"</entml:funtions_results\s*>", re.IGNORECASE),
35
+ re.compile(r"</entml:conversation_history\s*>", re.IGNORECASE),
36
+ re.compile(r"</entml:calls\s*>", re.IGNORECASE),
37
+ re.compile(r"</function_calling_behavior\s*>", re.IGNORECASE),
38
+ re.compile(r"</thinking_behavior\s*>", re.IGNORECASE),
39
+ re.compile(r"</entml:result\s*>", re.IGNORECASE),
40
+ )
41
+
42
+ _FAKE_ENTML_TAG_PREFIXES: Tuple[str, ...] = (
43
+ "<entml:result",
44
+ "</entml:result",
45
+ "<entml:funtions_results",
46
+ "</entml:funtions_results",
47
+ "<entml:conversation_history",
48
+ "</entml:conversation_history",
49
+ "<entml:calls",
50
+ "</entml:calls",
51
+ "<function_calling_behavior",
52
+ "</function_calling_behavior",
53
+ "<thinking_behavior",
54
+ "</thinking_behavior",
55
+ "</entml:invoke",
56
+ "</entml:parameter",
57
+ "</entml:thinking",
58
+ )
59
+
60
+ # 模型误输出的残缺/单行 ``</entml:…``(无完整闭标签名或未收齐 ``>``)。
61
+ _ORPHAN_ENTML_CLOSE_LINE_RE = re.compile(
62
+ r"(?m)^\s*(?:●\s*)?</entml:?[a-z0-9_-]*\s*$",
63
+ re.IGNORECASE,
64
+ )
65
+ _TRAILING_INCOMPLETE_ENTML_CLOSE_RE = re.compile(
66
+ r"</entml:?[a-z0-9_-]*\s*$",
67
+ re.IGNORECASE,
68
+ )
69
+
70
+
71
+ def _strip_fake_entml_result_id_blocks(text: str) -> Tuple[str, bool]:
72
+ text, n = _ENTML_RESULT_ID_BLOCK_RE.subn("", text)
73
+ return text, n > 0
74
+
75
+
76
+ def _strip_tag_only_markup(text: str) -> Tuple[str, bool]:
77
+ found = False
78
+ for pattern in _TAG_ONLY_OPEN_RES + _TAG_ONLY_CLOSE_RES:
79
+ text, n = pattern.subn("", text)
80
+ if n:
81
+ found = True
82
+ return text, found
83
+
84
+
85
+ def _strip_orphan_entml_close_leaks(text: str) -> Tuple[str, bool]:
86
+ text, n1 = _ORPHAN_ENTML_CLOSE_LINE_RE.subn("", text)
87
+ text, n2 = _TRAILING_INCOMPLETE_ENTML_CLOSE_RE.subn("", text)
88
+ return text, (n1 + n2) > 0
89
+
90
+
91
+ def _could_be_fake_entml_tag_prefix(fragment: str) -> bool:
92
+ frag = fragment.lower()
93
+ if not frag.startswith("<"):
94
+ return False
95
+ if ">" in frag:
96
+ return False
97
+ for prefix in _FAKE_ENTML_TAG_PREFIXES:
98
+ pl = prefix.lower()
99
+ if pl.startswith(frag) or frag.startswith(pl):
100
+ return True
101
+ return False
102
+
103
+
104
+ def trailing_partial_fake_entml_structure_len(text: str) -> int:
105
+ """尾部未收齐至 ``>`` 的伪 entml 结构标签应 hold 的字节数。"""
106
+ hold = trailing_partial_tool_result_id_comment_len(text)
107
+ if hold:
108
+ return hold
109
+ lt = text.rfind("<")
110
+ if lt < 0:
111
+ return 0
112
+ suffix = text[lt:]
113
+ if ">" in suffix:
114
+ return 0
115
+ if _could_be_fake_entml_tag_prefix(suffix):
116
+ return len(suffix)
117
+ return 0
118
+
119
+
120
+ def leading_partial_fake_entml_structure_len(text: str) -> int:
121
+ """开头未收齐至 ``>`` 的伪 entml 结构标签应 hold 的字节数。"""
122
+ hold = leading_partial_tool_result_id_comment_len(text)
123
+ if hold:
124
+ return hold
125
+ stripped = text.lstrip()
126
+ if not stripped.startswith("<"):
127
+ return 0
128
+ if ">" in stripped:
129
+ return 0
130
+ if _could_be_fake_entml_tag_prefix(stripped):
131
+ return len(text)
132
+ return 0
133
+
134
+
135
+ def _truncate_unclosed_fake_result_id_tail(text: str) -> Tuple[str, bool]:
136
+ """流式/batch:``<entml:result id=...>`` 未闭合至 ``</entml:result>`` 前均不可见。"""
137
+ found = False
138
+ search_from = 0
139
+ while True:
140
+ open_m = _ENTML_RESULT_ID_OPEN_INNER_RE.search(text[search_from:])
141
+ if not open_m:
142
+ break
143
+ abs_start = search_from + open_m.start()
144
+ gt = text.find(">", abs_start)
145
+ if gt < 0:
146
+ text = text[:abs_start]
147
+ found = True
148
+ break
149
+ abs_end = gt + 1
150
+ tail = text[abs_end:]
151
+ close_m = re.search(r"</entml:result\s*>", tail, re.IGNORECASE)
152
+ if close_m:
153
+ search_from = abs_end + close_m.end()
154
+ continue
155
+ text = text[:abs_start]
156
+ found = True
157
+ break
158
+ return text, found
159
+
160
+
161
+ def strip_fake_entml_structure_markup(content: str) -> Tuple[str, bool]:
162
+ """batch:id result 整块 → 仅标签 → Tool Result ID 注释。"""
163
+ if not content:
164
+ return content, False
165
+ found = False
166
+ text = strip_complete_tool_result_id_comments(content)
167
+ if text != content:
168
+ found = True
169
+ text, hit = _strip_fake_entml_result_id_blocks(text)
170
+ found = found or hit
171
+ text, hit = _truncate_unclosed_fake_result_id_tail(text)
172
+ found = found or hit
173
+ text, hit = _strip_tag_only_markup(text)
174
+ found = found or hit
175
+ text, hit = _strip_orphan_entml_close_leaks(text)
176
+ found = found or hit
177
+ text = re.sub(r"\n{3,}", "\n\n", text)
178
+ return text, found
179
+
180
+
181
+ def strip_fake_entml_structure_markup_for_display(content: str) -> Tuple[str, bool]:
182
+ """流式 ``partial_text``:hold 未收齐标签 + 截断未闭合 id result + 剥离。"""
183
+ if not content:
184
+ return content, False
185
+ found = False
186
+ tail_hold = trailing_partial_fake_entml_structure_len(content)
187
+ text = content[:-tail_hold] if tail_hold else content
188
+ if tail_hold:
189
+ found = True
190
+ text, hit = _truncate_unclosed_fake_result_id_tail(text)
191
+ found = found or hit
192
+ text, hit = strip_fake_entml_structure_markup(text)
193
+ found = found or hit
194
+ tail_hold2 = trailing_partial_fake_entml_structure_len(text)
195
+ if tail_hold2:
196
+ text = text[:-tail_hold2]
197
+ found = True
198
+ return text, found
@@ -3,10 +3,11 @@ from __future__ import annotations
3
3
  import re
4
4
  from typing import List, Optional, Tuple
5
5
 
6
- from echotools.exec.fncall.protocols.entml_patterns import (
7
- entml_invoke_open_may_be_streaming,
8
- extract_attr_value,
9
- normalize_entml_name,
6
+ from echotools.exec.fncall.protocols.entml_fake_structure_markup import (
7
+ leading_partial_fake_entml_structure_len,
8
+ strip_fake_entml_structure_markup,
9
+ strip_fake_entml_structure_markup_for_display,
10
+ trailing_partial_fake_entml_structure_len,
10
11
  )
11
12
 
12
13
  THINKING_BLOCK_RE = re.compile(
@@ -67,6 +68,9 @@ def leading_entml_tag_holdback_len(
67
68
  """buffer 开头是 entml/thinking/invoke 标签真前缀时,应 hold 的字节数。"""
68
69
  if not text:
69
70
  return 0
71
+ comment_hold = leading_partial_fake_entml_structure_len(text)
72
+ if comment_hold:
73
+ return comment_hold
70
74
  prefixes: List[str] = list(_LEADING_HOLD_PREFIXES)
71
75
  if thinking_enabled:
72
76
  prefixes.append(_PLAIN_THINKING_OPEN_PREFIX)
@@ -110,6 +114,9 @@ def trailing_entml_tag_holdback_len(text: str) -> int:
110
114
  """buffer 尾部是 entml/thinking 标签真前缀时,应 hold 的字节数。"""
111
115
  if not text:
112
116
  return 0
117
+ comment_hold = trailing_partial_fake_entml_structure_len(text)
118
+ if comment_hold:
119
+ return comment_hold
113
120
  invoke_idx = text.rfind("<entml:invoke")
114
121
  if invoke_idx >= 0:
115
122
  tail_from_invoke = text[invoke_idx:]
@@ -171,6 +178,7 @@ def clean_stream_partial_visible(
171
178
  tail_hold = trailing_entml_tag_holdback_len(text)
172
179
  if tail_hold:
173
180
  text = text[:-tail_hold]
181
+ text = strip_fake_entml_structure_markup_for_display(text)[0]
174
182
  if has_calls:
175
183
  text = text.rstrip()
176
184
  if not text.strip():
@@ -0,0 +1,70 @@
1
+ """剥离模型误生成的 ``<!-- Tool Result ID:… -->``(仅 history 注入合法)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ # 与 format_tool_result_id_comment() 输出格式一致;允许空白变化。
8
+ _TOOL_RESULT_ID_COMMENT_RE = re.compile(
9
+ r"<!--\s*Tool\s+Result\s+ID\s*:\s*.*?-->",
10
+ re.IGNORECASE | re.DOTALL,
11
+ )
12
+
13
+ _MARKER_LOWER = "<!-- tool result id:"
14
+
15
+
16
+ def _could_be_tool_result_id_comment_open(fragment: str) -> bool:
17
+ """``fragment`` 以 ``<!--`` 开头且可能长成 Tool Result ID 注释(未闭合)。"""
18
+ frag = fragment.strip().lower()
19
+ if not frag.startswith("<!--"):
20
+ return False
21
+ if "-->" in frag:
22
+ return False
23
+ return _MARKER_LOWER.startswith(frag) or frag.startswith(_MARKER_LOWER)
24
+
25
+
26
+ def strip_complete_tool_result_id_comments(text: str) -> str:
27
+ """移除已完整闭合的 Tool Result ID HTML 注释。"""
28
+ if not text:
29
+ return ""
30
+ return _TOOL_RESULT_ID_COMMENT_RE.sub("", text)
31
+
32
+
33
+ def trailing_partial_tool_result_id_comment_len(text: str) -> int:
34
+ """尾部未闭合的 Tool Result ID 注释应 hold 的字节数(含 ``<!--`` 起)。"""
35
+ if not text:
36
+ return 0
37
+ idx = text.rfind("<!--")
38
+ if idx < 0:
39
+ return 0
40
+ suffix = text[idx:]
41
+ if "-->" in suffix:
42
+ return 0
43
+ if _could_be_tool_result_id_comment_open(suffix):
44
+ return len(suffix)
45
+ return 0
46
+
47
+
48
+ def leading_partial_tool_result_id_comment_len(text: str) -> int:
49
+ """开头未闭合的 Tool Result ID 注释应 hold 的字节数。"""
50
+ if not text:
51
+ return 0
52
+ stripped = text.lstrip()
53
+ if not stripped.startswith("<!--"):
54
+ return 0
55
+ if "-->" in stripped:
56
+ return 0
57
+ if _could_be_tool_result_id_comment_open(stripped):
58
+ return len(text)
59
+ return 0
60
+
61
+
62
+ def strip_tool_result_id_comments_for_display(text: str) -> str:
63
+ """流式可见区:去掉完整注释;截断尾部未闭合的伪注释。"""
64
+ if not text:
65
+ return ""
66
+ cleaned = strip_complete_tool_result_id_comments(text)
67
+ tail_hold = trailing_partial_tool_result_id_comment_len(cleaned)
68
+ if tail_hold:
69
+ cleaned = cleaned[:-tail_hold]
70
+ return cleaned
@@ -5,6 +5,10 @@ from __future__ import annotations
5
5
  import re
6
6
  from typing import Any, Dict, List, Tuple
7
7
 
8
+ from echotools.exec.fncall.protocols.entml_fake_structure_markup import (
9
+ strip_fake_entml_structure_markup,
10
+ strip_fake_entml_structure_markup_for_display,
11
+ )
8
12
  from echotools.exec.fncall.shared.normalization import normalize_content
9
13
 
10
14
  _FAKE_HISTORY_TAGS = ("assistant", "tool")
@@ -266,7 +270,9 @@ def strip_fake_history_markup(content: str) -> Tuple[str, bool]:
266
270
  parts.append(content[open_m.start() :])
267
271
  break
268
272
 
269
- return "".join(parts), found
273
+ merged = "".join(parts)
274
+ merged, hit = strip_fake_entml_structure_markup(merged)
275
+ return merged, found or hit
270
276
 
271
277
 
272
278
  def _truncate_display_fake_tail(cleaned: str) -> Tuple[str, bool]:
@@ -323,4 +329,5 @@ def strip_fake_history_markup_for_display(content: str) -> Tuple[str, bool]:
323
329
  ):
324
330
  return cleaned, found
325
331
  cleaned2, hit = _truncate_display_fake_tail(cleaned)
326
- return cleaned2, found or hit
332
+ cleaned3, hit2 = strip_fake_entml_structure_markup_for_display(cleaned2)
333
+ return cleaned3, found or hit or hit2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.94
3
+ Version: 2.3.97
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -59,9 +59,11 @@ src/echotools/exec/fncall/prompt/prompt_helpers.py
59
59
  src/echotools/exec/fncall/prompt/templates.py
60
60
  src/echotools/exec/fncall/protocols/__init__.py
61
61
  src/echotools/exec/fncall/protocols/entml.py
62
+ src/echotools/exec/fncall/protocols/entml_fake_structure_markup.py
62
63
  src/echotools/exec/fncall/protocols/entml_invoke.py
63
64
  src/echotools/exec/fncall/protocols/entml_patterns.py
64
65
  src/echotools/exec/fncall/protocols/entml_schema.py
66
+ src/echotools/exec/fncall/protocols/entml_tool_result_comment.py
65
67
  src/echotools/exec/fncall/protocols/entml_stream/__init__.py
66
68
  src/echotools/exec/fncall/protocols/entml_stream/body.py
67
69
  src/echotools/exec/fncall/protocols/entml_stream/snap.py
@@ -193,6 +195,7 @@ src/tests/test_dispatch_race.py
193
195
  src/tests/test_entml_agent_output_formats.py
194
196
  src/tests/test_entml_bare_invoke_format.py
195
197
  src/tests/test_entml_batch_stream_comprehensive.py
198
+ src/tests/test_entml_fake_structure_markup.py
196
199
  src/tests/test_entml_invoke_known_tool_gate.py
197
200
  src/tests/test_entml_parser_comprehensive.py
198
201
  src/tests/test_entml_protocol.py
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from echotools.exec.fncall import get_protocol
6
+ from echotools.exec.fncall.parsers.stream import FncallStreamParser
7
+ from echotools.exec.fncall.protocols.entml_fake_structure_markup import (
8
+ strip_fake_entml_structure_markup,
9
+ strip_fake_entml_structure_markup_for_display,
10
+ )
11
+ from echotools.exec.fncall.protocols.entml_tool_result_comment import (
12
+ trailing_partial_tool_result_id_comment_len,
13
+ )
14
+
15
+ READ_TOOL = {
16
+ "type": "function",
17
+ "function": {
18
+ "name": "Read",
19
+ "parameters": {
20
+ "type": "object",
21
+ "properties": {"path": {"type": "string"}},
22
+ "required": ["path"],
23
+ },
24
+ },
25
+ }
26
+
27
+
28
+ @pytest.mark.parametrize(
29
+ "raw,expect_sub,expect_absent,expect_present",
30
+ [
31
+ (
32
+ '前言\n<!-- Tool Result ID:toolu_f1a2b3c4 -->\n后缀',
33
+ "前言",
34
+ "Tool Result ID",
35
+ None,
36
+ ),
37
+ (
38
+ '可见\n<entml:result id="toolu_46bd973b2dbd48e681b54714">\n{"ok":1}\n</entml:result>\n尾',
39
+ "可见",
40
+ '{"ok":1}',
41
+ "尾",
42
+ ),
43
+ (
44
+ "说明\n<entml:funtions_results>\n假正文\n</entml:funtions_results>\n继续",
45
+ "假正文",
46
+ "funtions_results",
47
+ "继续",
48
+ ),
49
+ (
50
+ "正文\n<entml:conversation_history>\n历史假数据\n</entml:conversation_history>\n完",
51
+ "历史假数据",
52
+ "conversation_history",
53
+ "完",
54
+ ),
55
+ (
56
+ "前\n<entml:calls>\n调用块正文\n</entml:calls>\n后",
57
+ "调用块正文",
58
+ "entml:calls",
59
+ "后",
60
+ ),
61
+ (
62
+ "前\n<function_calling_behavior>\n行为块正文\n</function_calling_behavior>\n后",
63
+ "行为块正文",
64
+ "function_calling_behavior",
65
+ "后",
66
+ ),
67
+ (
68
+ "前\n<thinking_behavior>\n思考行为\n</thinking_behavior>\n后",
69
+ "思考行为",
70
+ "thinking_behavior",
71
+ "后",
72
+ ),
73
+ (
74
+ "<entml:result>\nbody\n</entml:result>\nok",
75
+ "body",
76
+ "entml:result",
77
+ "ok",
78
+ ),
79
+ ],
80
+ )
81
+ def test_strip_fake_entml_structure_batch(
82
+ raw: str,
83
+ expect_sub: str,
84
+ expect_absent: str,
85
+ expect_present: str | None,
86
+ ) -> None:
87
+ cleaned, found = strip_fake_entml_structure_markup(raw)
88
+ assert found
89
+ assert expect_sub in cleaned
90
+ assert expect_absent not in cleaned
91
+ if expect_present:
92
+ assert expect_present in cleaned
93
+
94
+
95
+ def test_tool_result_comment_partial_hold() -> None:
96
+ partial = "可见\n<!-- Tool Result ID:toolu_abc"
97
+ assert trailing_partial_tool_result_id_comment_len(partial) == len(
98
+ "<!-- Tool Result ID:toolu_abc"
99
+ )
100
+ display, found = strip_fake_entml_structure_markup_for_display(partial)
101
+ assert found
102
+ assert display == "可见\n"
103
+ assert "Tool Result ID" not in display
104
+
105
+
106
+ def test_open_tag_stripped_at_gt_content_kept() -> None:
107
+ partial = "前言\n<entml:funtions_results>"
108
+ display, found = strip_fake_entml_structure_markup_for_display(partial)
109
+ assert found
110
+ assert display == "前言\n"
111
+ raw = "前言\n<entml:funtions_results>保留正文"
112
+ cleaned, _ = strip_fake_entml_structure_markup(raw)
113
+ assert cleaned == "前言\n保留正文"
114
+ assert "funtions_results" not in cleaned
115
+
116
+
117
+ def test_result_id_block_holds_and_strips_body() -> None:
118
+ partial = '可见\n<entml:result id="toolu_x'
119
+ display, found = strip_fake_entml_structure_markup_for_display(partial)
120
+ assert found
121
+ assert display == "可见\n"
122
+ complete = partial + '">\nleak'
123
+ display2, _ = strip_fake_entml_structure_markup_for_display(complete)
124
+ assert "entml:result" not in display2
125
+ assert "leak" not in display2
126
+
127
+
128
+ def test_bare_result_tags_only_not_body() -> None:
129
+ raw = "可见\n<entml:result>\n正文保留\n</entml:result>\n尾"
130
+ cleaned, found = strip_fake_entml_structure_markup(raw)
131
+ assert found
132
+ assert "正文保留" in cleaned
133
+ assert "entml:result" not in cleaned
134
+ assert "可见" in cleaned
135
+ assert "尾" in cleaned
136
+
137
+
138
+ def test_orphan_incomplete_entml_close_leak() -> None:
139
+ raw = (
140
+ "说明正文\n"
141
+ "<!-- Tool Result ID:toolu_check_theme_btn -->\n"
142
+ "</entml:"
143
+ )
144
+ cleaned, found = strip_fake_entml_structure_markup(raw)
145
+ assert found
146
+ assert "说明正文" in cleaned
147
+ assert "entml" not in cleaned.lower()
148
+ assert "Tool Result ID" not in cleaned
149
+
150
+
151
+ def test_rogator_response_log_corpus_no_entml_tail() -> None:
152
+ from pathlib import Path
153
+
154
+ log = Path(__file__).resolve().parent / "fixtures" / "rogator_entml_close_leak.txt"
155
+ raw = log.read_text(encoding="utf-8")
156
+ READ = {
157
+ "type": "function",
158
+ "function": {
159
+ "name": "Grep",
160
+ "parameters": {
161
+ "type": "object",
162
+ "properties": {"pattern": {"type": "string"}, "path": {"type": "string"}},
163
+ "required": ["pattern"],
164
+ },
165
+ },
166
+ }
167
+ proto = get_protocol("entml")
168
+ clean, calls = proto.parse(raw, [READ])
169
+ assert calls
170
+ assert "entml" not in clean.lower()
171
+
172
+
173
+ def test_batch_parse_strips_fake_structure() -> None:
174
+ text = (
175
+ "回答正文\n"
176
+ '<!-- Tool Result ID:call_0000 -->\n'
177
+ '<entml:result id="toolu_abc">\n{"x":1}\n</entml:result>\n'
178
+ "<entml:funtions_results>\n标签间保留\n</entml:funtions_results>"
179
+ )
180
+ proto = get_protocol("entml")
181
+ clean, calls = proto.parse(text, [READ_TOOL])
182
+ assert calls == []
183
+ assert "回答正文" in clean
184
+ assert "Tool Result ID" not in clean
185
+ assert "entml:result" not in clean
186
+ assert '{"x":1}' not in clean
187
+ assert "标签间保留" in clean
188
+ assert "funtions_results" not in clean
189
+
190
+
191
+ @pytest.mark.parametrize("chunk", [1, 4, 17, 32])
192
+ def test_stream_partial_no_fake_structure_leak(chunk: int) -> None:
193
+ text = (
194
+ "流式可见\n"
195
+ '<!-- Tool Result ID:toolu_leak -->\n'
196
+ '<entml:result id="toolu_x">\n{"fake":true}\n</entml:result>\n'
197
+ "<entml:conversation_history>\n中间保留\n"
198
+ )
199
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=[READ_TOOL])
200
+ for i in range(0, len(text), chunk):
201
+ parser.feed(text[i : i + chunk])
202
+ pt = parser.partial_text
203
+ assert "Tool Result ID" not in pt
204
+ assert "entml:result" not in pt
205
+ assert "conversation_history" not in pt
206
+ assert "fake" not in pt
207
+ clean, calls = parser.finalize()
208
+ assert calls == []
209
+ assert "流式可见" in clean
210
+ assert "中间保留" in clean
211
+ assert "Tool Result ID" not in clean
File without changes
File without changes