echotools 2.3.93__tar.gz → 2.3.96__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.93 → echotools-2.3.96}/PKG-INFO +1 -1
  2. {echotools-2.3.93 → echotools-2.3.96}/pyproject.toml +1 -1
  3. echotools-2.3.96/src/echotools/exec/fncall/protocols/entml_fake_structure_markup.py +177 -0
  4. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/parse.py +12 -4
  5. echotools-2.3.96/src/echotools/exec/fncall/protocols/entml_tool_result_comment.py +70 -0
  6. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/history_markup.py +9 -2
  7. {echotools-2.3.93 → echotools-2.3.96}/src/echotools.egg-info/PKG-INFO +1 -1
  8. {echotools-2.3.93 → echotools-2.3.96}/src/echotools.egg-info/SOURCES.txt +3 -0
  9. echotools-2.3.96/src/tests/test_entml_fake_structure_markup.py +176 -0
  10. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_protocol.py +10 -6
  11. {echotools-2.3.93 → echotools-2.3.96}/README.md +0 -0
  12. {echotools-2.3.93 → echotools-2.3.96}/setup.cfg +0 -0
  13. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/__init__.py +0 -0
  14. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/cache/__init__.py +0 -0
  15. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/cache/list_cache.py +0 -0
  16. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/cache/memory_cache.py +0 -0
  17. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/__init__.py +0 -0
  18. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/base.py +0 -0
  19. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/center.py +0 -0
  20. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/center_notify.py +0 -0
  21. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/center_template.py +0 -0
  22. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/loader.py +0 -0
  23. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/config/merge.py +0 -0
  24. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/errors/__init__.py +0 -0
  25. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/errors/base.py +0 -0
  26. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/errors/classify.py +0 -0
  27. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/errors/common.py +0 -0
  28. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/errors/http.py +0 -0
  29. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/ids/__init__.py +0 -0
  30. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/ids/generator.py +0 -0
  31. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/io/__init__.py +0 -0
  32. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/io/io_utils.py +0 -0
  33. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/io/printstream.py +0 -0
  34. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/logger/__init__.py +0 -0
  35. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/logger/manager.py +0 -0
  36. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/retry/__init__.py +0 -0
  37. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/base/retry/retry.py +0 -0
  38. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/compat.py +0 -0
  39. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/__init__.py +0 -0
  40. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/candidate.py +0 -0
  41. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  42. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  43. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/race.py +0 -0
  44. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/selector.py +0 -0
  45. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/dispatch/usage.py +0 -0
  46. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/__init__.py +0 -0
  47. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  48. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  49. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/stream_delta.py +0 -0
  50. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/stream_feed.py +0 -0
  51. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/stream_final.py +0 -0
  52. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  53. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  54. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  55. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/behavior_blocks.py +0 -0
  56. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/history.py +0 -0
  57. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  58. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/inject.py +2 -2
  59. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  60. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  61. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  62. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml.py +8 -8
  63. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  64. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  65. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_schema.py +0 -0
  66. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_stream/__init__.py +0 -0
  67. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_stream/body.py +0 -0
  68. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_stream/snap.py +0 -0
  69. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  70. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/blocks.py +0 -0
  71. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  72. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/filter.py +0 -0
  73. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  74. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/protocols/entml_think/hold.py +0 -0
  75. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/registry.py +0 -0
  76. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  77. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  78. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  79. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  80. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  81. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  82. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/keys.py +0 -0
  83. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/lifecycle/__init__.py +0 -0
  84. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/lifecycle/manager.py +0 -0
  85. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/lifecycle/updater.py +0 -0
  86. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  87. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/process/__init__.py +0 -0
  88. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/process/port.py +0 -0
  89. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/protocol/__init__.py +0 -0
  90. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/protocol/base.py +0 -0
  91. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/__init__.py +0 -0
  92. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/conpty.py +0 -0
  93. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/local/__init__.py +0 -0
  94. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/local/core.py +0 -0
  95. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/local/proc.py +0 -0
  96. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/local/unix.py +0 -0
  97. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/local/win.py +0 -0
  98. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/sanitize.py +0 -0
  99. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/session/__init__.py +0 -0
  100. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/session/base.py +0 -0
  101. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/session/client.py +0 -0
  102. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/ssh.py +0 -0
  103. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/exec/terminal/tmux.py +0 -0
  104. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/__init__.py +0 -0
  105. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/charmap.py +0 -0
  106. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/spinner.py +0 -0
  107. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/ui.py +0 -0
  108. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  109. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  110. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  111. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  112. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  113. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  114. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/ui_console.py +0 -0
  115. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/ui_io.py +0 -0
  116. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/ui_log.py +0 -0
  117. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  118. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/ui_text.py +0 -0
  119. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uicore/ui_types.py +0 -0
  120. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  121. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  122. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  123. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  124. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  125. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  126. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  127. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  128. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  129. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  130. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  131. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/events/__init__.py +0 -0
  132. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/events/bus.py +0 -0
  133. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/events/event.py +0 -0
  134. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/spinner/__init__.py +0 -0
  135. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/spinner/spinner.py +0 -0
  136. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/spinner/spinner_color.py +0 -0
  137. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/tracing/__init__.py +0 -0
  138. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/tracing/context.py +0 -0
  139. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/tracing/span.py +0 -0
  140. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/tracing/tracer.py +0 -0
  141. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/translate.py +0 -0
  142. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/watcher/__init__.py +0 -0
  143. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/watcher/file_watcher.py +0 -0
  144. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/__init__.py +0 -0
  145. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/application.py +0 -0
  146. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/broker.py +0 -0
  147. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/app.js +0 -0
  148. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  149. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  150. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  151. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  152. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  153. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  154. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  155. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  156. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  157. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  158. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/inputbox.css +0 -0
  159. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/inputbox.js +0 -0
  160. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/style.css +0 -0
  161. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/input_box/voicegif.js +0 -0
  162. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/middleware.py +0 -0
  163. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/stats.py +0 -0
  164. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/media/web/utils.py +0 -0
  165. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/files/__init__.py +0 -0
  166. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/files/file_util.py +0 -0
  167. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/network/__init__.py +0 -0
  168. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/network/http_utils.py +0 -0
  169. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/plugin/__init__.py +0 -0
  170. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/plugin/base.py +0 -0
  171. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/plugin/discovery.py +0 -0
  172. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/plugin/registry.py +0 -0
  173. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/plugin/registry_ops.py +0 -0
  174. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/proxy/__init__.py +0 -0
  175. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/proxy/manager.py +0 -0
  176. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/runtime/__init__.py +0 -0
  177. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/runtime/collector.py +0 -0
  178. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/scheduler/__init__.py +0 -0
  179. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/scheduler/scheduler.py +0 -0
  180. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/sdk/__init__.py +0 -0
  181. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/plat/sdk/facade.py +0 -0
  182. {echotools-2.3.93 → echotools-2.3.96}/src/echotools/version.py +0 -0
  183. {echotools-2.3.93 → echotools-2.3.96}/src/echotools.egg-info/dependency_links.txt +0 -0
  184. {echotools-2.3.93 → echotools-2.3.96}/src/echotools.egg-info/requires.txt +0 -0
  185. {echotools-2.3.93 → echotools-2.3.96}/src/echotools.egg-info/top_level.txt +0 -0
  186. {echotools-2.3.93 → echotools-2.3.96}/src/tests/bench_import.py +0 -0
  187. {echotools-2.3.93 → echotools-2.3.96}/src/tests/conftest.py +0 -0
  188. {echotools-2.3.93 → echotools-2.3.96}/src/tests/fixtures/__init__.py +0 -0
  189. {echotools-2.3.93 → echotools-2.3.96}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
  190. {echotools-2.3.93 → echotools-2.3.96}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
  191. {echotools-2.3.93 → echotools-2.3.96}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  192. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_adversarial_stream_splits.py +0 -0
  193. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_cache_retry.py +0 -0
  194. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_completion.py +0 -0
  195. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_config.py +0 -0
  196. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_config_extended.py +0 -0
  197. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_console.py +0 -0
  198. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_coverage_core.py +0 -0
  199. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_dispatch.py +0 -0
  200. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_dispatch_extended.py +0 -0
  201. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_dispatch_race.py +0 -0
  202. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_agent_output_formats.py +0 -0
  203. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_bare_invoke_format.py +0 -0
  204. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_batch_stream_comprehensive.py +0 -0
  205. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_invoke_known_tool_gate.py +0 -0
  206. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_parser_comprehensive.py +0 -0
  207. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_stream_json.py +0 -0
  208. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  209. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_thinking_history.py +0 -0
  210. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_thinking_parse.py +0 -0
  211. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_tool_parse_leak.py +0 -0
  212. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_tool_pipeline.py +2 -2
  213. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_entml_tools.py +0 -0
  214. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_errors.py +0 -0
  215. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_errors_common.py +0 -0
  216. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_events.py +0 -0
  217. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_events_event.py +0 -0
  218. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_facade.py +0 -0
  219. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fake_history_markup_batch.py +0 -0
  220. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
  221. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
  222. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fake_history_markup_stream.py +0 -0
  223. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fault_thinking_close_batch.py +0 -0
  224. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fault_thinking_close_stream.py +0 -0
  225. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fncall.py +0 -0
  226. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_fncall_stream_thinking.py +0 -0
  227. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_history_markup_filter.py +0 -0
  228. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_ids.py +0 -0
  229. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_imports.py +0 -0
  230. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_io.py +0 -0
  231. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_keys.py +0 -0
  232. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_lifecycle.py +0 -0
  233. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_logger.py +0 -0
  234. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_merge.py +0 -0
  235. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_model_branch_matrix.py +0 -0
  236. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_plugin.py +0 -0
  237. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_printstream.py +0 -0
  238. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_protocols.py +0 -0
  239. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_proxy.py +0 -0
  240. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_proxy_selector.py +0 -0
  241. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_runtime.py +0 -0
  242. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_scheduler.py +0 -0
  243. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  244. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_stream_partial_json_parity.py +0 -0
  245. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_stream_realtime_parse.py +0 -0
  246. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_tracing.py +0 -0
  247. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_usage.py +0 -0
  248. {echotools-2.3.93 → echotools-2.3.96}/src/tests/test_web.py +0 -0
  249. {echotools-2.3.93 → echotools-2.3.96}/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.93
3
+ Version: 2.3.96
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.93"
7
+ version = "2.3.96"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,177 @@
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
+ )
56
+
57
+
58
+ def _strip_fake_entml_result_id_blocks(text: str) -> Tuple[str, bool]:
59
+ text, n = _ENTML_RESULT_ID_BLOCK_RE.subn("", text)
60
+ return text, n > 0
61
+
62
+
63
+ def _strip_tag_only_markup(text: str) -> Tuple[str, bool]:
64
+ found = False
65
+ for pattern in _TAG_ONLY_OPEN_RES + _TAG_ONLY_CLOSE_RES:
66
+ text, n = pattern.subn("", text)
67
+ if n:
68
+ found = True
69
+ return text, found
70
+
71
+
72
+ def _could_be_fake_entml_tag_prefix(fragment: str) -> bool:
73
+ frag = fragment.lower()
74
+ if not frag.startswith("<"):
75
+ return False
76
+ if ">" in frag:
77
+ return False
78
+ for prefix in _FAKE_ENTML_TAG_PREFIXES:
79
+ pl = prefix.lower()
80
+ if pl.startswith(frag) or frag.startswith(pl):
81
+ return True
82
+ return False
83
+
84
+
85
+ def trailing_partial_fake_entml_structure_len(text: str) -> int:
86
+ """尾部未收齐至 ``>`` 的伪 entml 结构标签应 hold 的字节数。"""
87
+ hold = trailing_partial_tool_result_id_comment_len(text)
88
+ if hold:
89
+ return hold
90
+ lt = text.rfind("<")
91
+ if lt < 0:
92
+ return 0
93
+ suffix = text[lt:]
94
+ if ">" in suffix:
95
+ return 0
96
+ if _could_be_fake_entml_tag_prefix(suffix):
97
+ return len(suffix)
98
+ return 0
99
+
100
+
101
+ def leading_partial_fake_entml_structure_len(text: str) -> int:
102
+ """开头未收齐至 ``>`` 的伪 entml 结构标签应 hold 的字节数。"""
103
+ hold = leading_partial_tool_result_id_comment_len(text)
104
+ if hold:
105
+ return hold
106
+ stripped = text.lstrip()
107
+ if not stripped.startswith("<"):
108
+ return 0
109
+ if ">" in stripped:
110
+ return 0
111
+ if _could_be_fake_entml_tag_prefix(stripped):
112
+ return len(text)
113
+ return 0
114
+
115
+
116
+ def _truncate_unclosed_fake_result_id_tail(text: str) -> Tuple[str, bool]:
117
+ """流式/batch:``<entml:result id=...>`` 未闭合至 ``</entml:result>`` 前均不可见。"""
118
+ found = False
119
+ search_from = 0
120
+ while True:
121
+ open_m = _ENTML_RESULT_ID_OPEN_INNER_RE.search(text[search_from:])
122
+ if not open_m:
123
+ break
124
+ abs_start = search_from + open_m.start()
125
+ gt = text.find(">", abs_start)
126
+ if gt < 0:
127
+ text = text[:abs_start]
128
+ found = True
129
+ break
130
+ abs_end = gt + 1
131
+ tail = text[abs_end:]
132
+ close_m = re.search(r"</entml:result\s*>", tail, re.IGNORECASE)
133
+ if close_m:
134
+ search_from = abs_end + close_m.end()
135
+ continue
136
+ text = text[:abs_start]
137
+ found = True
138
+ break
139
+ return text, found
140
+
141
+
142
+ def strip_fake_entml_structure_markup(content: str) -> Tuple[str, bool]:
143
+ """batch:id result 整块 → 仅标签 → Tool Result ID 注释。"""
144
+ if not content:
145
+ return content, False
146
+ found = False
147
+ text = strip_complete_tool_result_id_comments(content)
148
+ if text != content:
149
+ found = True
150
+ text, hit = _strip_fake_entml_result_id_blocks(text)
151
+ found = found or hit
152
+ text, hit = _truncate_unclosed_fake_result_id_tail(text)
153
+ found = found or hit
154
+ text, hit = _strip_tag_only_markup(text)
155
+ found = found or hit
156
+ text = re.sub(r"\n{3,}", "\n\n", text)
157
+ return text, found
158
+
159
+
160
+ def strip_fake_entml_structure_markup_for_display(content: str) -> Tuple[str, bool]:
161
+ """流式 ``partial_text``:hold 未收齐标签 + 截断未闭合 id result + 剥离。"""
162
+ if not content:
163
+ return content, False
164
+ found = False
165
+ tail_hold = trailing_partial_fake_entml_structure_len(content)
166
+ text = content[:-tail_hold] if tail_hold else content
167
+ if tail_hold:
168
+ found = True
169
+ text, hit = _truncate_unclosed_fake_result_id_tail(text)
170
+ found = found or hit
171
+ text, hit = strip_fake_entml_structure_markup(text)
172
+ found = found or hit
173
+ tail_hold2 = trailing_partial_fake_entml_structure_len(text)
174
+ if tail_hold2:
175
+ text = text[:-tail_hold2]
176
+ found = True
177
+ 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.93
3
+ Version: 2.3.96
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,176 @@
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_batch_parse_strips_fake_structure() -> None:
139
+ text = (
140
+ "回答正文\n"
141
+ '<!-- Tool Result ID:call_0000 -->\n'
142
+ '<entml:result id="toolu_abc">\n{"x":1}\n</entml:result>\n'
143
+ "<entml:funtions_results>\n标签间保留\n</entml:funtions_results>"
144
+ )
145
+ proto = get_protocol("entml")
146
+ clean, calls = proto.parse(text, [READ_TOOL])
147
+ assert calls == []
148
+ assert "回答正文" in clean
149
+ assert "Tool Result ID" not in clean
150
+ assert "entml:result" not in clean
151
+ assert '{"x":1}' not in clean
152
+ assert "标签间保留" in clean
153
+ assert "funtions_results" not in clean
154
+
155
+
156
+ @pytest.mark.parametrize("chunk", [1, 4, 17, 32])
157
+ def test_stream_partial_no_fake_structure_leak(chunk: int) -> None:
158
+ text = (
159
+ "流式可见\n"
160
+ '<!-- Tool Result ID:toolu_leak -->\n'
161
+ '<entml:result id="toolu_x">\n{"fake":true}\n</entml:result>\n'
162
+ "<entml:conversation_history>\n中间保留\n"
163
+ )
164
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=[READ_TOOL])
165
+ for i in range(0, len(text), chunk):
166
+ parser.feed(text[i : i + chunk])
167
+ pt = parser.partial_text
168
+ assert "Tool Result ID" not in pt
169
+ assert "entml:result" not in pt
170
+ assert "conversation_history" not in pt
171
+ assert "fake" not in pt
172
+ clean, calls = parser.finalize()
173
+ assert calls == []
174
+ assert "流式可见" in clean
175
+ assert "中间保留" in clean
176
+ assert "Tool Result ID" not in clean
@@ -382,15 +382,15 @@ def test_render_prompt_thinking_after_current_user() -> None:
382
382
  current_user_message="new",
383
383
  protocol_options={"thinking_mode": "on", "max_thinking_length": 1000},
384
384
  )
385
- assert prompt.index("In this environment") < prompt.index("<function_calling_behavior>")
385
+ assert prompt.index("In this environment") < prompt.index("<entml:conversation_history>\n")
386
+ idx_hist = prompt.index("<entml:conversation_history>\n")
386
387
  idx_fc = prompt.index("<function_calling_behavior>\n")
387
388
  idx_behavior = prompt.index("<thinking_behavior>\n")
388
- idx_hist = prompt.index("<entml:conversation_history>\n")
389
389
  idx_hard = prompt.index("<entml:hard_constraint_restatement>\n")
390
390
  idx_user = prompt.index("<current_user_message>\n")
391
391
  idx_max = prompt.index("<entml:max_thinking_length>")
392
392
  idx_mode = prompt.index("<entml:thinking_mode>")
393
- assert idx_fc < idx_behavior < idx_hist < idx_hard < idx_user < idx_max < idx_mode
393
+ assert idx_hist < idx_fc < idx_behavior < idx_hard < idx_user < idx_max < idx_mode
394
394
  assert prompt.rstrip().endswith("</entml:thinking_mode>")
395
395
 
396
396
 
@@ -516,7 +516,9 @@ def test_entml_history_tool_invoke_reminder_when_tools_in_history() -> None:
516
516
  {"role": "tool", "tool_call_id": "call_1", "content": "found 3"},
517
517
  {"role": "user", "content": "summarize"},
518
518
  ]
519
- content = inject_fncall(msgs, tools, proto)[0]["content"]
519
+ content = inject_fncall(
520
+ msgs, tools, proto, protocol_options={"thinking_level": "high"},
521
+ )[0]["content"]
520
522
  assert '<entml:invoke name="search">' in content
521
523
  assert '"query": "docs"' in content or "docs" in content
522
524
  assert "<!-- Tool Result ID:call_1 -->" in content
@@ -525,11 +527,13 @@ def test_entml_history_tool_invoke_reminder_when_tools_in_history() -> None:
525
527
  assert "<entml:funtions_results>" in content
526
528
  assert "<function_calling_behavior>" in content
527
529
  assert "IMPORTANT: The <entml:funtions_results> block is a top-level block" in content
528
- fc_idx = content.index("<function_calling_behavior>\n")
529
530
  hist_idx = content.index("<entml:conversation_history>\n")
530
531
  results_idx = content.index("<entml:funtions_results>\n")
532
+ fc_idx = content.index("<function_calling_behavior>\n")
533
+ behavior_idx = content.index("<thinking_behavior>\n")
534
+ hard_idx = content.index("<entml:hard_constraint_restatement>\n")
531
535
  user_idx = content.index("<current_user_message>\n")
532
- assert fc_idx < hist_idx < results_idx < user_idx
536
+ assert hist_idx < results_idx < fc_idx < behavior_idx < hard_idx < user_idx
533
537
 
534
538
 
535
539
  def test_strip_entml_from_user_content() -> None:
File without changes
File without changes