echotools 2.3.52__tar.gz → 2.3.53__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 (225) hide show
  1. {echotools-2.3.52 → echotools-2.3.53}/PKG-INFO +1 -1
  2. {echotools-2.3.52 → echotools-2.3.53}/pyproject.toml +1 -1
  3. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/parsers/stream.py +21 -0
  4. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml.py +6 -1
  5. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_patterns.py +62 -2
  6. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_think/parse.py +34 -4
  7. {echotools-2.3.52 → echotools-2.3.53}/src/echotools.egg-info/PKG-INFO +1 -1
  8. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_fncall_stream_thinking.py +31 -0
  9. {echotools-2.3.52 → echotools-2.3.53}/README.md +0 -0
  10. {echotools-2.3.52 → echotools-2.3.53}/docs/README.md +0 -0
  11. {echotools-2.3.52 → echotools-2.3.53}/setup.cfg +0 -0
  12. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/__init__.py +0 -0
  13. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/cache/__init__.py +0 -0
  14. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/cache/list_cache.py +0 -0
  15. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/cache/memory_cache.py +0 -0
  16. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/__init__.py +0 -0
  17. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/base.py +0 -0
  18. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/center.py +0 -0
  19. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/center_notify.py +0 -0
  20. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/center_template.py +0 -0
  21. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/loader.py +0 -0
  22. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/config/merge.py +0 -0
  23. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/errors/__init__.py +0 -0
  24. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/errors/base.py +0 -0
  25. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/errors/classify.py +0 -0
  26. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/errors/common.py +0 -0
  27. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/errors/http.py +0 -0
  28. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/ids/__init__.py +0 -0
  29. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/ids/generator.py +0 -0
  30. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/io/__init__.py +0 -0
  31. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/io/io_utils.py +0 -0
  32. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/io/printstream.py +0 -0
  33. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/logger/__init__.py +0 -0
  34. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/logger/manager.py +0 -0
  35. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/retry/__init__.py +0 -0
  36. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/base/retry/retry.py +0 -0
  37. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/compat.py +0 -0
  38. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/__init__.py +0 -0
  39. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/candidate.py +0 -0
  40. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  41. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  42. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/race.py +0 -0
  43. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/selector.py +0 -0
  44. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/dispatch/usage.py +0 -0
  45. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/__init__.py +0 -0
  46. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  47. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  48. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  49. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  50. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/prompt/history.py +0 -0
  51. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  52. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  53. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  54. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  55. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  56. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  57. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_stream_json.py +0 -0
  58. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  59. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  60. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  61. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  62. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  63. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/registry.py +0 -0
  64. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  65. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  66. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  67. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  68. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  69. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  70. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/keys.py +0 -0
  71. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/lifecycle/__init__.py +0 -0
  72. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/lifecycle/manager.py +0 -0
  73. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/lifecycle/updater.py +0 -0
  74. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  75. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/process/__init__.py +0 -0
  76. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/process/port.py +0 -0
  77. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/protocol/__init__.py +0 -0
  78. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/protocol/base.py +0 -0
  79. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/__init__.py +0 -0
  80. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/conpty.py +0 -0
  81. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/local/__init__.py +0 -0
  82. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/local/core.py +0 -0
  83. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/local/proc.py +0 -0
  84. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/local/unix.py +0 -0
  85. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/local/win.py +0 -0
  86. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/sanitize.py +0 -0
  87. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/session/__init__.py +0 -0
  88. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/session/base.py +0 -0
  89. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/session/client.py +0 -0
  90. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/ssh.py +0 -0
  91. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/exec/terminal/tmux.py +0 -0
  92. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/__init__.py +0 -0
  93. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/charmap.py +0 -0
  94. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/spinner.py +0 -0
  95. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/ui.py +0 -0
  96. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  97. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  98. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  99. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  100. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  101. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  102. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/ui_console.py +0 -0
  103. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/ui_io.py +0 -0
  104. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/ui_log.py +0 -0
  105. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  106. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/ui_text.py +0 -0
  107. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uicore/ui_types.py +0 -0
  108. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  109. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  110. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  111. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  112. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  113. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  114. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  115. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  116. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  117. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  118. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  119. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/events/__init__.py +0 -0
  120. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/events/bus.py +0 -0
  121. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/events/event.py +0 -0
  122. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/spinner/__init__.py +0 -0
  123. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/spinner/spinner.py +0 -0
  124. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/spinner/spinner_color.py +0 -0
  125. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/tracing/__init__.py +0 -0
  126. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/tracing/context.py +0 -0
  127. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/tracing/span.py +0 -0
  128. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/tracing/tracer.py +0 -0
  129. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/translate.py +0 -0
  130. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/watcher/__init__.py +0 -0
  131. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/watcher/file_watcher.py +0 -0
  132. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/__init__.py +0 -0
  133. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/application.py +0 -0
  134. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/broker.py +0 -0
  135. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/app.js +0 -0
  136. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  137. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  138. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  139. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  140. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  141. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  142. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  143. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  144. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  145. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  146. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/inputbox.css +0 -0
  147. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/inputbox.js +0 -0
  148. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/style.css +0 -0
  149. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/input_box/voicegif.js +0 -0
  150. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/middleware.py +0 -0
  151. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/stats.py +0 -0
  152. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/media/web/utils.py +0 -0
  153. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/files/__init__.py +0 -0
  154. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/files/file_util.py +0 -0
  155. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/network/__init__.py +0 -0
  156. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/network/http_utils.py +0 -0
  157. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/plugin/__init__.py +0 -0
  158. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/plugin/base.py +0 -0
  159. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/plugin/discovery.py +0 -0
  160. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/plugin/registry.py +0 -0
  161. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/plugin/registry_ops.py +0 -0
  162. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/proxy/__init__.py +0 -0
  163. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/proxy/manager.py +0 -0
  164. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/runtime/__init__.py +0 -0
  165. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/runtime/collector.py +0 -0
  166. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/scheduler/__init__.py +0 -0
  167. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/scheduler/scheduler.py +0 -0
  168. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/sdk/__init__.py +0 -0
  169. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/plat/sdk/facade.py +0 -0
  170. {echotools-2.3.52 → echotools-2.3.53}/src/echotools/version.py +0 -0
  171. {echotools-2.3.52 → echotools-2.3.53}/src/echotools.egg-info/SOURCES.txt +0 -0
  172. {echotools-2.3.52 → echotools-2.3.53}/src/echotools.egg-info/dependency_links.txt +0 -0
  173. {echotools-2.3.52 → echotools-2.3.53}/src/echotools.egg-info/requires.txt +0 -0
  174. {echotools-2.3.52 → echotools-2.3.53}/src/echotools.egg-info/top_level.txt +0 -0
  175. {echotools-2.3.52 → echotools-2.3.53}/src/tests/bench_import.py +0 -0
  176. {echotools-2.3.52 → echotools-2.3.53}/src/tests/conftest.py +0 -0
  177. {echotools-2.3.52 → echotools-2.3.53}/src/tests/fixtures/__init__.py +0 -0
  178. {echotools-2.3.52 → echotools-2.3.53}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  179. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_adversarial_stream_splits.py +0 -0
  180. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_cache_retry.py +0 -0
  181. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_completion.py +0 -0
  182. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_config.py +0 -0
  183. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_config_extended.py +0 -0
  184. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_console.py +0 -0
  185. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_coverage_core.py +0 -0
  186. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_dispatch.py +0 -0
  187. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_dispatch_extended.py +0 -0
  188. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_dispatch_race.py +0 -0
  189. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_agent_output_formats.py +0 -0
  190. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_bare_invoke_format.py +0 -0
  191. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_parser_comprehensive.py +0 -0
  192. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_protocol.py +0 -0
  193. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_stream_json.py +0 -0
  194. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  195. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_thinking_history.py +0 -0
  196. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_thinking_parse.py +0 -0
  197. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_tool_parse_leak.py +0 -0
  198. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_tool_pipeline.py +0 -0
  199. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_entml_tools.py +0 -0
  200. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_errors.py +0 -0
  201. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_errors_common.py +0 -0
  202. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_events.py +0 -0
  203. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_events_event.py +0 -0
  204. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_facade.py +0 -0
  205. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_fncall.py +0 -0
  206. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_ids.py +0 -0
  207. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_imports.py +0 -0
  208. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_io.py +0 -0
  209. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_keys.py +0 -0
  210. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_lifecycle.py +0 -0
  211. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_logger.py +0 -0
  212. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_merge.py +0 -0
  213. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_plugin.py +0 -0
  214. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_printstream.py +0 -0
  215. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_protocols.py +0 -0
  216. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_proxy.py +0 -0
  217. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_proxy_selector.py +0 -0
  218. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_runtime.py +0 -0
  219. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_scheduler.py +0 -0
  220. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  221. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_stream_realtime_parse.py +0 -0
  222. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_tracing.py +0 -0
  223. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_usage.py +0 -0
  224. {echotools-2.3.52 → echotools-2.3.53}/src/tests/test_web.py +0 -0
  225. {echotools-2.3.52 → echotools-2.3.53}/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.52
3
+ Version: 2.3.53
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.52"
7
+ version = "2.3.53"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -349,6 +349,26 @@ class FncallStreamParser:
349
349
  body = self._fncall_buf[body_start:]
350
350
  self._json_stream_encoder._emitted = build_streaming_json_snapshot(body)
351
351
 
352
+ def _reclassify_orphan_thinking_close_in_text(self) -> None:
353
+ """将误进 visible 的「无开标签 + 闭标签」思考正文移回 thinking。"""
354
+ if self._thinking_filter is not None and self._thinking_filter.in_open_thinking():
355
+ return
356
+ from echotools.exec.fncall.protocols.entml_think.parse import (
357
+ _THINKING_CLOSE,
358
+ _THINKING_OPEN_PREFIX,
359
+ )
360
+
361
+ visible = "".join(self._text_parts)
362
+ if not visible or _THINKING_OPEN_PREFIX in visible:
363
+ return
364
+ close_at = visible.find(_THINKING_CLOSE)
365
+ if close_at < 0:
366
+ return
367
+ thinking_body = visible[:close_at]
368
+ after = visible[close_at + len(_THINKING_CLOSE) :].lstrip("\n")
369
+ self._thinking_parts.append(thinking_body)
370
+ self._text_parts = [after] if after else []
371
+
352
372
  def feed(self, chunk: str) -> List[Dict[str, Any]]:
353
373
  """喂入新的流式文本块。
354
374
 
@@ -370,6 +390,7 @@ class FncallStreamParser:
370
390
  ready = self.get_ready_tool_calls()
371
391
  if ready and self._json_stream_encoder is not None:
372
392
  self._sync_json_stream_encoder_emitted()
393
+ self._reclassify_orphan_thinking_close_in_text()
373
394
  return ready
374
395
 
375
396
  def _poll_streaming_tool_input_delta(self) -> Optional[Tuple[str, str]]:
@@ -20,6 +20,7 @@ from echotools.exec.fncall.protocols.entml_invoke import (
20
20
  )
21
21
  from echotools.exec.fncall.protocols.entml_patterns import (
22
22
  BLOCK_RE,
23
+ entml_invoke_open_may_be_streaming,
23
24
  extract_attr_value,
24
25
  normalize_entml_name,
25
26
  strip_legacy_function_calls_wrapper,
@@ -225,7 +226,11 @@ class EntmlProtocol(ToolProtocol):
225
226
  if close < 0 and (hold is None or legacy_pos < hold):
226
227
  hold = legacy_pos
227
228
  invoke_pos = buffer.find(self._TRIGGER_PREFIX)
228
- if invoke_pos >= 0 and (hold is None or invoke_pos < hold):
229
+ if (
230
+ invoke_pos >= 0
231
+ and entml_invoke_open_may_be_streaming(buffer, invoke_pos)
232
+ and (hold is None or invoke_pos < hold)
233
+ ):
229
234
  hold = invoke_pos
230
235
  return hold
231
236
 
@@ -60,6 +60,65 @@ _FENCE_ONLY_LINE_RE = re.compile(
60
60
  r"^\s*```(?:xml|entml|text)?\s*$",
61
61
  re.IGNORECASE | re.MULTILINE,
62
62
  )
63
+ _INVOKE_OPEN_PREFIX = "<entml:invoke"
64
+ _PLACEHOLDER_INVOKE_NAMES = frozenset({"$FUNCTION_NAME", "$FUNCTION_NAME2"})
65
+
66
+
67
+ def is_placeholder_invoke_name(name: str) -> bool:
68
+ """提示词占位符(如 ``$FUNCTION_NAME``)不算真实工具调用。"""
69
+ n = (name or "").strip()
70
+ return not n or "$" in n
71
+
72
+
73
+ def entml_invoke_open_is_actionable(buffer: str, pos: int) -> bool:
74
+ """``pos`` 处 ``<entml:invoke`` 是否为已闭合且含真实 name 的工具开标签。"""
75
+ if pos < 0 or not buffer.startswith(_INVOKE_OPEN_PREFIX, pos):
76
+ return False
77
+ gt = buffer.find(">", pos + len(_INVOKE_OPEN_PREFIX))
78
+ if gt < 0:
79
+ return False
80
+ attrs = buffer[pos + len(_INVOKE_OPEN_PREFIX) : gt]
81
+ name = extract_attr_value(attrs, "name")
82
+ if not name:
83
+ return False
84
+ name = normalize_entml_name(name)
85
+ return bool(name) and not is_placeholder_invoke_name(name)
86
+
87
+
88
+ def entml_invoke_open_may_be_streaming(buffer: str, pos: int) -> bool:
89
+ """``pos`` 处 ``<entml:invoke`` 是否仍可能长成真实工具开标签(非 prose 提及)。"""
90
+ if pos < 0 or not buffer.startswith(_INVOKE_OPEN_PREFIX, pos):
91
+ return False
92
+ if entml_invoke_open_is_actionable(buffer, pos):
93
+ return True
94
+ gt = buffer.find(">", pos + len(_INVOKE_OPEN_PREFIX))
95
+ if gt < 0:
96
+ return True
97
+ return False
98
+
99
+
100
+ def _strip_orphan_invoke_tags(content: str) -> str:
101
+ """仅剥离带真实 name 的 invoke 孤儿标签;保留 prose 中的 ``<entml:invoke>`` 提及。"""
102
+ pattern = re.compile(r"</?entml:invoke\b[^>]*/?>", re.DOTALL)
103
+
104
+ def repl(match: re.Match[str]) -> str:
105
+ tag = match.group(0)
106
+ if tag.startswith("</"):
107
+ return ""
108
+ if entml_invoke_open_is_actionable(content, match.start()):
109
+ return ""
110
+ return tag
111
+
112
+ return pattern.sub(repl, content)
113
+
114
+
115
+ def _strip_orphan_non_invoke_tool_tags(content: str) -> str:
116
+ return re.sub(
117
+ r"</?entml:(?:function_calls|parameter|parameters)\b[^>]*/?>",
118
+ "",
119
+ content,
120
+ flags=re.DOTALL,
121
+ )
63
122
 
64
123
 
65
124
  def extract_attr_value(attrs: str, attr_name: str = "name") -> Optional[str]:
@@ -117,12 +176,13 @@ def strip_legacy_function_calls_wrapper(text: str) -> str:
117
176
 
118
177
 
119
178
  def strip_tool_entml_residue(content: str) -> str:
120
- """剥离工具相关 entml 标签残留,保留 thinking 等非工具标签。"""
179
+ """剥离工具相关 entml 标签残留,保留 thinking 与非工具 prose 提及。"""
121
180
  if not content:
122
181
  return content
123
182
  cleaned = _TOOL_WRAPPER_PAIR_RE.sub("", content)
124
183
  cleaned = BLOCK_RE.sub("", cleaned)
125
- cleaned = _TOOL_ORPHAN_TAG_RE.sub("", cleaned)
184
+ cleaned = _strip_orphan_invoke_tags(cleaned)
185
+ cleaned = _strip_orphan_non_invoke_tool_tags(cleaned)
126
186
  cleaned = _EMPTY_FENCE_RE.sub("", cleaned)
127
187
  cleaned = _FENCE_ONLY_LINE_RE.sub("", cleaned)
128
188
  # 折叠因剥离产生的多余空行
@@ -5,6 +5,7 @@ from typing import List, Optional, Tuple
5
5
 
6
6
  from echotools.exec.fncall.protocols.entml_patterns import (
7
7
  BLOCK_RE,
8
+ entml_invoke_open_may_be_streaming,
8
9
  extract_attr_value,
9
10
  normalize_entml_name,
10
11
  )
@@ -116,12 +117,22 @@ def find_ambiguous_entml_tool_prefix(buffer: str) -> int:
116
117
 
117
118
 
118
119
  def _hold_ambiguous_tool_markup(buffer: str) -> Tuple[str, str]:
119
- """thinking 块内:从最早工具 entml 前缀处 hold,避免 invoke 被当作 thinking 吐出。"""
120
+ """thinking 块内:hold 可能长成真实工具调用的 markup(非 prose ``<entml:invoke>`` 提及)。"""
120
121
  if not buffer:
121
122
  return "", ""
122
- ambig = find_ambiguous_entml_tool_prefix(buffer)
123
- if ambig >= 0:
124
- return buffer[:ambig], buffer[ambig:]
123
+ hold_from: Optional[int] = None
124
+ invoke_pos = buffer.find(_INVOKE_PREFIX)
125
+ if invoke_pos >= 0 and entml_invoke_open_may_be_streaming(buffer, invoke_pos):
126
+ if hold_from is None or invoke_pos < hold_from:
127
+ hold_from = invoke_pos
128
+ for prefix in _AMBIGUOUS_ENTML_PREFIXES:
129
+ if prefix == _INVOKE_PREFIX:
130
+ continue
131
+ pos = buffer.find(prefix)
132
+ if pos >= 0 and (hold_from is None or pos < hold_from):
133
+ hold_from = pos
134
+ if hold_from is not None:
135
+ return buffer[:hold_from], buffer[hold_from:]
125
136
  lt = buffer.rfind("<")
126
137
  if lt < 0:
127
138
  return buffer, ""
@@ -233,6 +244,22 @@ class EntmlThinkingStreamFilter:
233
244
  self._fault_buffer = ""
234
245
  self._thinking_started = False
235
246
 
247
+ def _resolve_orphan_thinking_close(self, out: List[Tuple[str, str]]) -> bool:
248
+ """仅有 ``</entml:thinking>`` 闭标签、无开标签时,将闭标签前 pending 视为 thinking。"""
249
+ close_at = self._pending.find(_THINKING_CLOSE)
250
+ if close_at < 0:
251
+ return False
252
+ if self._pending.find(_THINKING_OPEN_PREFIX) >= 0:
253
+ return False
254
+ before = self._pending[:close_at]
255
+ if before:
256
+ emitted = self._emit_thinking_piece(before)
257
+ if emitted:
258
+ out.append(("thinking", emitted))
259
+ self._pending = self._pending[close_at + len(_THINKING_CLOSE) :]
260
+ self._thinking_started = False
261
+ return True
262
+
236
263
  def in_open_thinking(self) -> bool:
237
264
  """思考块已开始且尚未收到 ``</entml:thinking>``(含开/闭标签分片 hold)。"""
238
265
  if self._in_block or self._fault_watch:
@@ -378,6 +405,9 @@ class EntmlThinkingStreamFilter:
378
405
 
379
406
  def _feed_before_block(self, out: List[Tuple[str, str]]) -> bool:
380
407
  """开标签之前或尚未进入块;返回 False 表示应退出 feed 主循环。"""
408
+ if self._resolve_orphan_thinking_close(out):
409
+ return True
410
+
381
411
  open_at = self._pending.find(_THINKING_OPEN_PREFIX)
382
412
  if open_at < 0:
383
413
  safe, self._pending = _hold_prefix(self._pending, _THINKING_OPEN_PREFIX)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.52
3
+ Version: 2.3.53
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -170,6 +170,37 @@ def test_fault_thinking_close_without_invoke_is_plain_text() -> None:
170
170
  assert "answer" in clean
171
171
 
172
172
 
173
+ def test_orphan_thinking_close_without_open_reclassified_from_visible() -> None:
174
+ """漏写 ``<entml:thinking>`` 仅有闭标签时,闭标签前正文应进 thinking 而非 visible。"""
175
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
176
+ text = "plan step one\nplan step two\n</entml:thinking>\nvisible reply\n"
177
+ for i in range(0, len(text), 9):
178
+ parser.feed(text[i : i + 9])
179
+ assert "plan step one" in parser.partial_thinking
180
+ assert "visible reply" in parser.partial_text
181
+ assert "</entml:thinking>" not in parser.partial_text
182
+
183
+
184
+ def test_prose_invoke_mention_preserved_in_visible_text() -> None:
185
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
186
+ text = "当我使用`<entml:invoke>`格式时,参数要简单。\n"
187
+ parser.feed(text)
188
+ clean, calls = parser.finalize()
189
+ assert not calls
190
+ assert "<entml:invoke>" in parser.partial_text
191
+ assert "<entml:invoke>" in clean
192
+
193
+
194
+ def test_prose_invoke_mention_preserved_in_thinking_stream() -> None:
195
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
196
+ text = "<entml:thinking>\n当我使用`<entml:invoke>`格式时\n</entml:thinking>\n"
197
+ for i in range(0, len(text), 4):
198
+ parser.feed(text[i : i + 4])
199
+ _, calls = parser.finalize()
200
+ assert not calls
201
+ assert "<entml:invoke>" in parser.partial_thinking
202
+
203
+
173
204
  def test_stream_delta_not_duplicated_after_invoke_in_thinking() -> None:
174
205
  """thinking 内 invoke ready 后,下一 chunk 不应重发整段 partial_json。"""
175
206
  import json
File without changes
File without changes
File without changes