echotools 2.3.48__tar.gz → 2.3.49__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 (223) hide show
  1. {echotools-2.3.48 → echotools-2.3.49}/PKG-INFO +1 -1
  2. {echotools-2.3.48 → echotools-2.3.49}/pyproject.toml +1 -1
  3. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_think/parse.py +72 -49
  4. {echotools-2.3.48 → echotools-2.3.49}/src/echotools.egg-info/PKG-INFO +1 -1
  5. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_fncall_stream_thinking.py +44 -11
  6. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_stream_realtime_parse.py +4 -5
  7. {echotools-2.3.48 → echotools-2.3.49}/README.md +0 -0
  8. {echotools-2.3.48 → echotools-2.3.49}/docs/README.md +0 -0
  9. {echotools-2.3.48 → echotools-2.3.49}/setup.cfg +0 -0
  10. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/__init__.py +0 -0
  11. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/cache/__init__.py +0 -0
  12. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/cache/list_cache.py +0 -0
  13. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/cache/memory_cache.py +0 -0
  14. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/__init__.py +0 -0
  15. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/base.py +0 -0
  16. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/center.py +0 -0
  17. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/center_notify.py +0 -0
  18. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/center_template.py +0 -0
  19. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/loader.py +0 -0
  20. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/config/merge.py +0 -0
  21. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/errors/__init__.py +0 -0
  22. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/errors/base.py +0 -0
  23. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/errors/classify.py +0 -0
  24. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/errors/common.py +0 -0
  25. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/errors/http.py +0 -0
  26. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/ids/__init__.py +0 -0
  27. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/ids/generator.py +0 -0
  28. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/io/__init__.py +0 -0
  29. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/io/io_utils.py +0 -0
  30. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/io/printstream.py +0 -0
  31. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/logger/__init__.py +0 -0
  32. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/logger/manager.py +0 -0
  33. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/retry/__init__.py +0 -0
  34. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/base/retry/retry.py +0 -0
  35. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/compat.py +0 -0
  36. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/__init__.py +0 -0
  37. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/candidate.py +0 -0
  38. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  39. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  40. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/race.py +0 -0
  41. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/selector.py +0 -0
  42. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/dispatch/usage.py +0 -0
  43. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/__init__.py +0 -0
  44. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  45. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  46. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  47. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  48. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  49. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/prompt/history.py +0 -0
  50. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  51. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  52. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  53. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  54. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  55. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  56. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  57. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  58. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_stream_json.py +0 -0
  59. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  60. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  61. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  62. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  63. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  64. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/registry.py +0 -0
  65. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  66. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  67. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  68. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  69. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  70. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  71. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/keys.py +0 -0
  72. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/lifecycle/__init__.py +0 -0
  73. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/lifecycle/manager.py +0 -0
  74. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/lifecycle/updater.py +0 -0
  75. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  76. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/process/__init__.py +0 -0
  77. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/process/port.py +0 -0
  78. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/protocol/__init__.py +0 -0
  79. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/protocol/base.py +0 -0
  80. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/__init__.py +0 -0
  81. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/conpty.py +0 -0
  82. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/local/__init__.py +0 -0
  83. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/local/core.py +0 -0
  84. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/local/proc.py +0 -0
  85. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/local/unix.py +0 -0
  86. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/local/win.py +0 -0
  87. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/sanitize.py +0 -0
  88. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/session/__init__.py +0 -0
  89. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/session/base.py +0 -0
  90. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/session/client.py +0 -0
  91. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/ssh.py +0 -0
  92. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/exec/terminal/tmux.py +0 -0
  93. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/__init__.py +0 -0
  94. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/charmap.py +0 -0
  95. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/spinner.py +0 -0
  96. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/ui.py +0 -0
  97. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  98. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  99. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  100. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  101. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  102. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  103. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/ui_console.py +0 -0
  104. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/ui_io.py +0 -0
  105. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/ui_log.py +0 -0
  106. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  107. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/ui_text.py +0 -0
  108. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uicore/ui_types.py +0 -0
  109. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  110. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  111. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  112. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  113. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  114. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  115. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  116. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  117. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  118. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  119. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  120. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/events/__init__.py +0 -0
  121. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/events/bus.py +0 -0
  122. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/events/event.py +0 -0
  123. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/spinner/__init__.py +0 -0
  124. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/spinner/spinner.py +0 -0
  125. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/spinner/spinner_color.py +0 -0
  126. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/tracing/__init__.py +0 -0
  127. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/tracing/context.py +0 -0
  128. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/tracing/span.py +0 -0
  129. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/tracing/tracer.py +0 -0
  130. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/translate.py +0 -0
  131. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/watcher/__init__.py +0 -0
  132. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/watcher/file_watcher.py +0 -0
  133. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/__init__.py +0 -0
  134. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/application.py +0 -0
  135. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/broker.py +0 -0
  136. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/app.js +0 -0
  137. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  138. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  139. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  140. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  141. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  142. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  143. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  144. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  145. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  146. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  147. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/inputbox.css +0 -0
  148. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/inputbox.js +0 -0
  149. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/style.css +0 -0
  150. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/input_box/voicegif.js +0 -0
  151. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/middleware.py +0 -0
  152. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/stats.py +0 -0
  153. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/media/web/utils.py +0 -0
  154. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/files/__init__.py +0 -0
  155. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/files/file_util.py +0 -0
  156. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/network/__init__.py +0 -0
  157. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/network/http_utils.py +0 -0
  158. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/plugin/__init__.py +0 -0
  159. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/plugin/base.py +0 -0
  160. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/plugin/discovery.py +0 -0
  161. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/plugin/registry.py +0 -0
  162. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/plugin/registry_ops.py +0 -0
  163. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/proxy/__init__.py +0 -0
  164. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/proxy/manager.py +0 -0
  165. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/runtime/__init__.py +0 -0
  166. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/runtime/collector.py +0 -0
  167. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/scheduler/__init__.py +0 -0
  168. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/scheduler/scheduler.py +0 -0
  169. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/sdk/__init__.py +0 -0
  170. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/plat/sdk/facade.py +0 -0
  171. {echotools-2.3.48 → echotools-2.3.49}/src/echotools/version.py +0 -0
  172. {echotools-2.3.48 → echotools-2.3.49}/src/echotools.egg-info/SOURCES.txt +0 -0
  173. {echotools-2.3.48 → echotools-2.3.49}/src/echotools.egg-info/dependency_links.txt +0 -0
  174. {echotools-2.3.48 → echotools-2.3.49}/src/echotools.egg-info/requires.txt +0 -0
  175. {echotools-2.3.48 → echotools-2.3.49}/src/echotools.egg-info/top_level.txt +0 -0
  176. {echotools-2.3.48 → echotools-2.3.49}/src/tests/bench_import.py +0 -0
  177. {echotools-2.3.48 → echotools-2.3.49}/src/tests/conftest.py +0 -0
  178. {echotools-2.3.48 → echotools-2.3.49}/src/tests/fixtures/__init__.py +0 -0
  179. {echotools-2.3.48 → echotools-2.3.49}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  180. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_adversarial_stream_splits.py +0 -0
  181. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_cache_retry.py +0 -0
  182. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_completion.py +0 -0
  183. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_config.py +0 -0
  184. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_config_extended.py +0 -0
  185. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_console.py +0 -0
  186. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_coverage_core.py +0 -0
  187. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_dispatch.py +0 -0
  188. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_dispatch_extended.py +0 -0
  189. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_dispatch_race.py +0 -0
  190. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_bare_invoke_format.py +0 -0
  191. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_protocol.py +0 -0
  192. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_stream_json.py +0 -0
  193. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  194. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_thinking_history.py +0 -0
  195. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_thinking_parse.py +0 -0
  196. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_tool_parse_leak.py +0 -0
  197. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_tool_pipeline.py +0 -0
  198. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_entml_tools.py +0 -0
  199. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_errors.py +0 -0
  200. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_errors_common.py +0 -0
  201. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_events.py +0 -0
  202. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_events_event.py +0 -0
  203. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_facade.py +0 -0
  204. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_fncall.py +0 -0
  205. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_ids.py +0 -0
  206. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_imports.py +0 -0
  207. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_io.py +0 -0
  208. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_keys.py +0 -0
  209. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_lifecycle.py +0 -0
  210. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_logger.py +0 -0
  211. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_merge.py +0 -0
  212. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_plugin.py +0 -0
  213. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_printstream.py +0 -0
  214. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_protocols.py +0 -0
  215. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_proxy.py +0 -0
  216. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_proxy_selector.py +0 -0
  217. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_runtime.py +0 -0
  218. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_scheduler.py +0 -0
  219. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  220. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_tracing.py +0 -0
  221. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_usage.py +0 -0
  222. {echotools-2.3.48 → echotools-2.3.49}/src/tests/test_web.py +0 -0
  223. {echotools-2.3.48 → echotools-2.3.49}/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.48
3
+ Version: 2.3.49
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.48"
7
+ version = "2.3.49"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -15,6 +15,7 @@ THINKING_BLOCK_RE = re.compile(
15
15
 
16
16
  _THINKING_OPEN_PREFIX = "<entml:thinking"
17
17
  _THINKING_CLOSE = "</entml:thinking>"
18
+ _FAULT_THINKING_CLOSE = "</thinking>"
18
19
  # 与 thinking 共享 `<entml:` 前缀的其它标签:歧义 holdback 应交由工具流式状态机处理
19
20
  _AMBIGUOUS_ENTML_PREFIXES = (
20
21
  "<entml:invoke",
@@ -142,10 +143,13 @@ class EntmlThinkingStreamFilter:
142
143
  self._in_block = False
143
144
  # 当前 thinking 块是否已输出过任何内容(用于首片去前导空白)
144
145
  self._thinking_started = False
146
+ # 见到 ``</thinking>`` 后暂存,仅当后续出现 invoke 才视为闭合;否则当纯文本
147
+ self._fault_watch = False
148
+ self._fault_buffer = ""
145
149
 
146
150
  def in_open_thinking(self) -> bool:
147
- """思考块已开始且尚未收到 </entml:thinking>(含开/闭标签分片 hold)。"""
148
- if self._in_block:
151
+ """思考块已开始且尚未收到 ``</entml:thinking>``(含开/闭标签分片 hold)。"""
152
+ if self._in_block or self._fault_watch:
149
153
  return True
150
154
  open_at = self._pending.find(_THINKING_OPEN_PREFIX)
151
155
  if open_at >= 0:
@@ -157,47 +161,47 @@ class EntmlThinkingStreamFilter:
157
161
  return True
158
162
  return False
159
163
 
160
- def _split_thinking_at_tool_markup(self, out: List[Tuple[str, str]]) -> bool:
161
- """thinking 块内一旦出现工具 entml 前缀,立即切出为正文(含未收齐开标签)。"""
162
- tool_at = find_ambiguous_entml_tool_prefix(self._pending)
163
- if tool_at < 0:
164
+ def _try_resolve_fault_watch_on_invoke(self) -> bool:
165
+ """``</thinking>`` 之后若出现完整 invoke 开标签,则在该处结束 thinking。"""
166
+ if not self._fault_watch or not self._fault_buffer:
164
167
  return False
165
- close_at = self._pending.find(_THINKING_CLOSE)
166
- if close_at >= 0 and tool_at >= close_at:
168
+ invoke_at = find_complete_entml_invoke_open(self._fault_buffer)
169
+ if invoke_at < len(_FAULT_THINKING_CLOSE):
167
170
  return False
168
- if tool_at > 0:
169
- emitted = self._emit_thinking_piece(self._pending[:tool_at])
170
- if emitted:
171
- out.append(("thinking", emitted))
172
171
  self._in_block = False
172
+ self._fault_watch = False
173
173
  self._thinking_started = False
174
- self._pending = self._pending[tool_at:]
174
+ self._pending = self._fault_buffer[len(_FAULT_THINKING_CLOSE) :]
175
+ self._fault_buffer = ""
175
176
  return True
176
177
 
177
- def _split_thinking_at_invoke(self, out: List[Tuple[str, str]]) -> bool:
178
- """thinking 未闭合但已出现完整 invoke 开标签时,提前结束 thinking 块。"""
179
- invoke_at = find_complete_entml_invoke_open(self._pending)
180
- if invoke_at < 0:
178
+ def _try_resolve_fault_watch_on_entml_close(self, out: List[Tuple[str, str]]) -> bool:
179
+ """未见 invoke 时出现 ``</entml:thinking>`` → ``</thinking>`` 仅为思考内纯文本。"""
180
+ if not self._fault_watch:
181
181
  return False
182
- close_at = self._pending.find(_THINKING_CLOSE)
183
- if close_at >= 0 and invoke_at >= close_at:
182
+ close_at = self._fault_buffer.find(_THINKING_CLOSE)
183
+ if close_at < 0:
184
184
  return False
185
- if invoke_at > 0:
186
- emitted = self._emit_thinking_piece(self._pending[:invoke_at])
187
- if emitted:
188
- out.append(("thinking", emitted))
185
+ emitted = self._emit_thinking_piece(self._fault_buffer[:close_at])
186
+ if emitted:
187
+ out.append(("thinking", emitted))
189
188
  self._in_block = False
189
+ self._fault_watch = False
190
190
  self._thinking_started = False
191
- self._pending = self._pending[invoke_at:]
191
+ self._pending = self._fault_buffer[close_at + len(_THINKING_CLOSE) :]
192
+ self._fault_buffer = ""
192
193
  return True
193
194
 
194
- def _feed_in_block(self, out: List[Tuple[str, str]]) -> bool:
195
- """处理块内 pending;返回 False 表示应退出 feed 主循环。"""
196
- if self._split_thinking_at_invoke(out):
195
+ def _feed_fault_watch(self, out: List[Tuple[str, str]]) -> bool:
196
+ """处理 ``</thinking>`` 容错观察期;返回 True 表示可继续主循环。"""
197
+ if self._try_resolve_fault_watch_on_invoke():
197
198
  return True
198
- if self._split_thinking_at_tool_markup(out):
199
+ if self._try_resolve_fault_watch_on_entml_close(out):
199
200
  return True
201
+ return False
200
202
 
203
+ def _feed_in_block(self, out: List[Tuple[str, str]]) -> bool:
204
+ """处理块内 pending;返回 False 表示应退出 feed 主循环。"""
201
205
  close_at = self._pending.find(_THINKING_CLOSE)
202
206
  if close_at >= 0:
203
207
  piece = self._pending[:close_at]
@@ -205,10 +209,23 @@ class EntmlThinkingStreamFilter:
205
209
  if emitted:
206
210
  out.append(("thinking", emitted))
207
211
  self._in_block = False
212
+ self._fault_watch = False
213
+ self._fault_buffer = ""
208
214
  self._thinking_started = False
209
215
  self._pending = self._pending[close_at + len(_THINKING_CLOSE) :]
210
216
  return True
211
217
 
218
+ fault_at = self._pending.find(_FAULT_THINKING_CLOSE)
219
+ if fault_at >= 0:
220
+ before = self._pending[:fault_at]
221
+ emitted = self._emit_thinking_piece(before)
222
+ if emitted:
223
+ out.append(("thinking", emitted))
224
+ self._fault_watch = True
225
+ self._fault_buffer = self._pending[fault_at:]
226
+ self._pending = ""
227
+ return False
228
+
212
229
  safe, tool_hold = _hold_ambiguous_tool_markup(self._pending)
213
230
  if tool_hold:
214
231
  if safe:
@@ -219,6 +236,8 @@ class EntmlThinkingStreamFilter:
219
236
  return False
220
237
 
221
238
  safe, hold = _hold_prefix(self._pending, _THINKING_CLOSE)
239
+ if not hold:
240
+ safe, hold = _hold_prefix(safe, _FAULT_THINKING_CLOSE)
222
241
  if safe:
223
242
  emitted = self._emit_thinking_piece(safe)
224
243
  if emitted:
@@ -257,6 +276,12 @@ class EntmlThinkingStreamFilter:
257
276
  out: List[Tuple[str, str]] = []
258
277
 
259
278
  while self._pending:
279
+ if self._fault_watch:
280
+ self._fault_buffer += self._pending
281
+ self._pending = ""
282
+ if self._feed_fault_watch(out):
283
+ continue
284
+ break
260
285
  if self._in_block:
261
286
  if not self._feed_in_block(out):
262
287
  break
@@ -279,39 +304,37 @@ class EntmlThinkingStreamFilter:
279
304
 
280
305
  def finalize(self) -> List[Tuple[str, str]]:
281
306
  out: List[Tuple[str, str]] = []
307
+ if self._fault_watch:
308
+ if self._try_resolve_fault_watch_on_invoke():
309
+ pass
310
+ elif self._try_resolve_fault_watch_on_entml_close(out):
311
+ pass
312
+ else:
313
+ emitted = self._emit_thinking_piece(self._fault_buffer)
314
+ if emitted:
315
+ out.append(("thinking", emitted))
316
+ self._fault_buffer = ""
317
+ self._fault_watch = False
318
+ self._in_block = False
319
+ self._thinking_started = False
282
320
  if self._in_block:
283
- if not self._split_thinking_at_invoke(out):
284
- if not self._split_thinking_at_tool_markup(out):
285
- # 未闭合且无工具 markup:剩余 pending 视作思考
286
- emitted = self._emit_thinking_piece(self._pending)
287
- if emitted:
288
- out.append(("thinking", emitted))
289
- self._pending = ""
290
- self._in_block = False
291
- self._thinking_started = False
292
- return out
293
- # invoke 已切出,继续处理 _pending 中的正文
294
- while self._pending:
295
- if self._in_block:
296
- if not self._feed_in_block(out):
297
- break
298
- continue
299
- if not self._feed_before_block(out):
300
- break
321
+ emitted = self._emit_thinking_piece(self._pending)
322
+ if emitted:
323
+ out.append(("thinking", emitted))
301
324
  self._pending = ""
302
325
  self._in_block = False
303
326
  self._thinking_started = False
304
- return out
305
- elif self._pending:
327
+ if self._pending:
306
328
  content, thinking = split_entml_thinking(self._pending)
307
329
  if thinking:
308
330
  out.append(("thinking", thinking))
309
331
  if content:
310
332
  out.append(("content", content))
311
333
  elif self._pending and not thinking:
312
- # 仅 hold 了半截开标签:原样吐出,避免吞字
313
334
  out.append(("content", self._pending))
314
335
  self._pending = ""
315
336
  self._in_block = False
337
+ self._fault_watch = False
338
+ self._fault_buffer = ""
316
339
  self._thinking_started = False
317
340
  return out
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.48
3
+ Version: 2.3.49
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -53,19 +53,16 @@ def test_split_entml_thinking_ignores_unclosed_block() -> None:
53
53
  assert INVOKE in content
54
54
 
55
55
 
56
- def test_invoke_inside_unclosed_thinking_parsed_when_complete() -> None:
57
- """完整 invoke 出现在未闭合 thinking 内时,应提前切出并解析(模型常见误排)。"""
56
+ def test_invoke_inside_unclosed_thinking_not_parsed() -> None:
57
+ """未闭合 thinking 内的 entml 标签仅作思考文本,不解析为 tool call。"""
58
58
  parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
59
59
  stream = f"<entml:thinking>\nplan {INVOKE}\n"
60
60
  for i in range(0, len(stream), 7):
61
61
  parser.feed(stream[i : i + 7])
62
- assert parser.has_calls
63
- assert "plan" in parser.partial_thinking
64
- assert INVOKE not in parser.partial_thinking
62
+ assert not parser.has_calls
63
+ assert INVOKE in parser.partial_thinking or "entml:invoke" in parser.partial_thinking
65
64
  clean, calls = parser.finalize()
66
- assert len(calls) == 1
67
- assert calls[0]["function"]["name"] == "get_weather"
68
- assert INVOKE not in clean
65
+ assert len(calls) == 0
69
66
 
70
67
 
71
68
  def test_invoke_after_thinking_close_is_parsed() -> None:
@@ -98,16 +95,52 @@ def test_no_thinking_invoke_still_works() -> None:
98
95
  assert "Sure" in clean
99
96
 
100
97
 
101
- def test_stream_thinking_incremental_invoke_inside_parsed_when_complete() -> None:
98
+ def test_stream_thinking_invoke_inside_stays_thinking_until_close() -> None:
102
99
  parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
103
100
  parser.feed("<entml:thinking>\n")
104
101
  parser.feed("line one\n")
105
102
  assert "line one" in parser.partial_thinking
106
103
  assert not parser.has_calls
107
104
  parser.feed(f"mention {INVOKE}\n")
108
- assert INVOKE not in parser.partial_thinking
109
- assert parser.has_calls
105
+ assert not parser.has_calls
106
+ assert "entml:invoke" in parser.partial_thinking
110
107
  parser.feed("</entml:thinking>\nanswer")
111
108
  clean, calls = parser.finalize()
109
+ assert len(calls) == 0
110
+ assert "answer" in clean
111
+
112
+
113
+ def test_fault_thinking_close_then_invoke() -> None:
114
+ """``</thinking>`` 后若出现 invoke,则在该处结束思考并解析工具。"""
115
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
116
+ text = f"<entml:thinking>\nplan\n</thinking>\n{INVOKE}"
117
+ for i in range(0, len(text), 5):
118
+ parser.feed(text[i : i + 5])
119
+ clean, calls = parser.finalize()
120
+ assert "plan" in parser.partial_thinking
112
121
  assert len(calls) == 1
122
+ assert calls[0]["function"]["name"] == "get_weather"
123
+ assert INVOKE not in clean
124
+
125
+
126
+ def test_fault_thinking_close_without_invoke_is_plain_text() -> None:
127
+ """``</thinking>`` 后若无 invoke 直到 ``</entml:thinking>``,则视为思考内纯文本。"""
128
+ parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
129
+ text = "<entml:thinking>\nplan\n</thinking>\nmore\n</entml:thinking>\nanswer"
130
+ parser.feed(text)
131
+ clean, calls = parser.finalize()
132
+ assert len(calls) == 0
133
+ assert "plan" in parser.partial_thinking
134
+ assert "</thinking>" in parser.partial_thinking or "more" in parser.partial_thinking
113
135
  assert "answer" in clean
136
+
137
+
138
+ def test_fault_thinking_close_stays_open_while_waiting_for_invoke() -> None:
139
+ filt = EntmlThinkingStreamFilter()
140
+ filt.feed("<entml:thinking>\nplan\n</thinking>\nstill ")
141
+ assert filt.in_open_thinking()
142
+ filt.feed("waiting\n")
143
+ assert filt.in_open_thinking()
144
+ filt.feed(INVOKE)
145
+ kinds = [k for k, _ in filt.feed("")]
146
+ assert "content" in kinds or filt.in_open_thinking() is False
@@ -112,7 +112,7 @@ def test_char_by_char_ready_matches_final_for_all_tool_cases() -> None:
112
112
  assert _args(incremental) == _args(final) == case.expect_args, case.id
113
113
 
114
114
 
115
- def test_unclosed_thinking_invoke_parsed_once_inside_block() -> None:
115
+ def test_unclosed_thinking_invoke_not_parsed_until_close() -> None:
116
116
  proto = get_protocol("entml")
117
117
  tools = TOOLS
118
118
  invoke = (
@@ -124,11 +124,10 @@ def test_unclosed_thinking_invoke_parsed_once_inside_block() -> None:
124
124
  ready = []
125
125
  for ch in f"<entml:thinking>\nplan {invoke}\n":
126
126
  ready.extend(parser.feed(ch))
127
- assert len(ready) == 1
127
+ assert ready == []
128
128
  for ch in f"</entml:thinking>\n可见\n{invoke}":
129
129
  ready.extend(parser.feed(ch))
130
130
  ready.extend(parser.get_ready_tool_calls())
131
131
  _, final = parser.finalize()
132
- # thinking 内第一次 invoke 已解析;闭合后重复 invoke 会再解析一次
133
- assert _names(final) == ["get_weather", "get_weather"]
134
- assert len(final) == 2
132
+ assert _names(final) == ["get_weather"]
133
+ assert len(final) == 1
File without changes
File without changes
File without changes