echotools 2.3.61__tar.gz → 2.3.62__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 (227) hide show
  1. {echotools-2.3.61 → echotools-2.3.62}/PKG-INFO +1 -1
  2. {echotools-2.3.61 → echotools-2.3.62}/pyproject.toml +1 -1
  3. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/parsers/stream.py +18 -6
  4. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +2 -0
  5. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_think/core.py +10 -0
  6. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_think/parse.py +85 -22
  7. {echotools-2.3.61 → echotools-2.3.62}/src/echotools.egg-info/PKG-INFO +1 -1
  8. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_thinking_parse.py +19 -0
  9. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_fncall_stream_thinking.py +32 -0
  10. {echotools-2.3.61 → echotools-2.3.62}/README.md +0 -0
  11. {echotools-2.3.61 → echotools-2.3.62}/docs/README.md +0 -0
  12. {echotools-2.3.61 → echotools-2.3.62}/setup.cfg +0 -0
  13. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/__init__.py +0 -0
  14. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/cache/__init__.py +0 -0
  15. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/cache/list_cache.py +0 -0
  16. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/cache/memory_cache.py +0 -0
  17. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/__init__.py +0 -0
  18. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/base.py +0 -0
  19. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/center.py +0 -0
  20. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/center_notify.py +0 -0
  21. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/center_template.py +0 -0
  22. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/loader.py +0 -0
  23. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/config/merge.py +0 -0
  24. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/errors/__init__.py +0 -0
  25. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/errors/base.py +0 -0
  26. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/errors/classify.py +0 -0
  27. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/errors/common.py +0 -0
  28. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/errors/http.py +0 -0
  29. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/ids/__init__.py +0 -0
  30. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/ids/generator.py +0 -0
  31. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/io/__init__.py +0 -0
  32. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/io/io_utils.py +0 -0
  33. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/io/printstream.py +0 -0
  34. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/logger/__init__.py +0 -0
  35. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/logger/manager.py +0 -0
  36. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/retry/__init__.py +0 -0
  37. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/base/retry/retry.py +0 -0
  38. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/compat.py +0 -0
  39. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/__init__.py +0 -0
  40. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/candidate.py +0 -0
  41. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  42. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  43. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/race.py +0 -0
  44. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/selector.py +0 -0
  45. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/dispatch/usage.py +0 -0
  46. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/__init__.py +0 -0
  47. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  48. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  49. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  50. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  51. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/prompt/history.py +0 -0
  52. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  53. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  54. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  55. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  56. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  57. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  58. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  59. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  60. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_stream_json.py +0 -0
  61. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  62. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  63. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  64. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/registry.py +0 -0
  65. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  66. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  67. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  68. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  69. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  70. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  71. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/keys.py +0 -0
  72. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/lifecycle/__init__.py +0 -0
  73. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/lifecycle/manager.py +0 -0
  74. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/lifecycle/updater.py +0 -0
  75. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  76. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/process/__init__.py +0 -0
  77. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/process/port.py +0 -0
  78. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/protocol/__init__.py +0 -0
  79. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/protocol/base.py +0 -0
  80. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/__init__.py +0 -0
  81. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/conpty.py +0 -0
  82. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/local/__init__.py +0 -0
  83. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/local/core.py +0 -0
  84. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/local/proc.py +0 -0
  85. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/local/unix.py +0 -0
  86. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/local/win.py +0 -0
  87. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/sanitize.py +0 -0
  88. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/session/__init__.py +0 -0
  89. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/session/base.py +0 -0
  90. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/session/client.py +0 -0
  91. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/ssh.py +0 -0
  92. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/exec/terminal/tmux.py +0 -0
  93. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/__init__.py +0 -0
  94. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/charmap.py +0 -0
  95. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/spinner.py +0 -0
  96. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/ui.py +0 -0
  97. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  98. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  99. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  100. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  101. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  102. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  103. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/ui_console.py +0 -0
  104. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/ui_io.py +0 -0
  105. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/ui_log.py +0 -0
  106. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  107. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/ui_text.py +0 -0
  108. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uicore/ui_types.py +0 -0
  109. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  110. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  111. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  112. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  113. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  114. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  115. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  116. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  117. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  118. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  119. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  120. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/events/__init__.py +0 -0
  121. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/events/bus.py +0 -0
  122. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/events/event.py +0 -0
  123. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/spinner/__init__.py +0 -0
  124. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/spinner/spinner.py +0 -0
  125. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/spinner/spinner_color.py +0 -0
  126. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/tracing/__init__.py +0 -0
  127. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/tracing/context.py +0 -0
  128. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/tracing/span.py +0 -0
  129. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/tracing/tracer.py +0 -0
  130. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/translate.py +0 -0
  131. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/watcher/__init__.py +0 -0
  132. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/watcher/file_watcher.py +0 -0
  133. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/__init__.py +0 -0
  134. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/application.py +0 -0
  135. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/broker.py +0 -0
  136. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/app.js +0 -0
  137. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  138. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  139. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  140. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  141. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  142. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  143. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  144. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  145. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  146. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  147. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/inputbox.css +0 -0
  148. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/inputbox.js +0 -0
  149. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/style.css +0 -0
  150. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/input_box/voicegif.js +0 -0
  151. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/middleware.py +0 -0
  152. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/stats.py +0 -0
  153. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/media/web/utils.py +0 -0
  154. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/files/__init__.py +0 -0
  155. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/files/file_util.py +0 -0
  156. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/network/__init__.py +0 -0
  157. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/network/http_utils.py +0 -0
  158. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/plugin/__init__.py +0 -0
  159. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/plugin/base.py +0 -0
  160. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/plugin/discovery.py +0 -0
  161. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/plugin/registry.py +0 -0
  162. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/plugin/registry_ops.py +0 -0
  163. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/proxy/__init__.py +0 -0
  164. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/proxy/manager.py +0 -0
  165. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/runtime/__init__.py +0 -0
  166. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/runtime/collector.py +0 -0
  167. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/scheduler/__init__.py +0 -0
  168. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/scheduler/scheduler.py +0 -0
  169. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/sdk/__init__.py +0 -0
  170. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/plat/sdk/facade.py +0 -0
  171. {echotools-2.3.61 → echotools-2.3.62}/src/echotools/version.py +0 -0
  172. {echotools-2.3.61 → echotools-2.3.62}/src/echotools.egg-info/SOURCES.txt +0 -0
  173. {echotools-2.3.61 → echotools-2.3.62}/src/echotools.egg-info/dependency_links.txt +0 -0
  174. {echotools-2.3.61 → echotools-2.3.62}/src/echotools.egg-info/requires.txt +0 -0
  175. {echotools-2.3.61 → echotools-2.3.62}/src/echotools.egg-info/top_level.txt +0 -0
  176. {echotools-2.3.61 → echotools-2.3.62}/src/tests/bench_import.py +0 -0
  177. {echotools-2.3.61 → echotools-2.3.62}/src/tests/conftest.py +0 -0
  178. {echotools-2.3.61 → echotools-2.3.62}/src/tests/fixtures/__init__.py +0 -0
  179. {echotools-2.3.61 → echotools-2.3.62}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  180. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_adversarial_stream_splits.py +0 -0
  181. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_cache_retry.py +0 -0
  182. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_completion.py +0 -0
  183. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_config.py +0 -0
  184. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_config_extended.py +0 -0
  185. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_console.py +0 -0
  186. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_coverage_core.py +0 -0
  187. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_dispatch.py +0 -0
  188. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_dispatch_extended.py +0 -0
  189. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_dispatch_race.py +0 -0
  190. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_agent_output_formats.py +0 -0
  191. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_bare_invoke_format.py +0 -0
  192. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_parser_comprehensive.py +0 -0
  193. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_protocol.py +0 -0
  194. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_stream_json.py +0 -0
  195. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
  196. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_thinking_history.py +0 -0
  197. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_tool_parse_leak.py +0 -0
  198. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_tool_pipeline.py +0 -0
  199. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_entml_tools.py +0 -0
  200. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_errors.py +0 -0
  201. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_errors_common.py +0 -0
  202. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_events.py +0 -0
  203. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_events_event.py +0 -0
  204. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_facade.py +0 -0
  205. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_fncall.py +0 -0
  206. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_ids.py +0 -0
  207. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_imports.py +0 -0
  208. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_io.py +0 -0
  209. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_keys.py +0 -0
  210. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_lifecycle.py +0 -0
  211. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_logger.py +0 -0
  212. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_merge.py +0 -0
  213. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_model_branch_matrix.py +0 -0
  214. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_plugin.py +0 -0
  215. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_printstream.py +0 -0
  216. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_protocols.py +0 -0
  217. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_proxy.py +0 -0
  218. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_proxy_selector.py +0 -0
  219. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_runtime.py +0 -0
  220. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_scheduler.py +0 -0
  221. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  222. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_stream_partial_json_parity.py +0 -0
  223. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_stream_realtime_parse.py +0 -0
  224. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_tracing.py +0 -0
  225. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_usage.py +0 -0
  226. {echotools-2.3.61 → echotools-2.3.62}/src/tests/test_web.py +0 -0
  227. {echotools-2.3.61 → echotools-2.3.62}/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.61
3
+ Version: 2.3.62
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.61"
7
+ version = "2.3.62"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -12,12 +12,12 @@ from echotools.exec.protocol.base import ToolProtocol
12
12
 
13
13
 
14
14
  # 懒导入:仅 entml 协议需要
15
- def _make_thinking_filter(protocol: ToolProtocol):
15
+ def _make_thinking_filter(protocol: ToolProtocol, *, thinking_enabled: bool = True):
16
16
  if getattr(protocol, "id", None) == "entml":
17
17
  from echotools.exec.fncall.protocols.entml_think.parse import (
18
18
  EntmlThinkingStreamFilter,
19
19
  )
20
- return EntmlThinkingStreamFilter()
20
+ return EntmlThinkingStreamFilter(thinking_enabled=thinking_enabled)
21
21
  return None
22
22
 
23
23
 
@@ -40,9 +40,13 @@ class FncallStreamParser:
40
40
  self,
41
41
  protocol: ToolProtocol,
42
42
  tools: Optional[List[Dict[str, Any]]] = None,
43
+ protocol_options: Optional[Dict[str, Any]] = None,
43
44
  ) -> None:
44
45
  self._protocol = protocol
45
46
  self._tools = tools
47
+ from echotools.exec.fncall.protocols.entml_think.core import is_thinking_enabled
48
+
49
+ self._thinking_enabled = is_thinking_enabled(protocol_options)
46
50
  self._raw_buf: str = ""
47
51
  self._text_parts: List[str] = []
48
52
  self._waiting_tail: str = ""
@@ -51,7 +55,9 @@ class FncallStreamParser:
51
55
  self._state: str = self.WAITING_FOR_TAG
52
56
  self._finalized_result: Optional[Tuple[str, List[Dict[str, Any]]]] = None
53
57
  self._thinking_parts: List[str] = []
54
- self._thinking_filter = _make_thinking_filter(protocol)
58
+ self._thinking_filter = _make_thinking_filter(
59
+ protocol, thinking_enabled=self._thinking_enabled
60
+ )
55
61
  self._emitted_invoke_count: int = 0
56
62
  self._json_stream_encoder = None
57
63
  self._pending_stream_deltas: Deque[Tuple[str, str]] = deque()
@@ -298,7 +304,9 @@ class FncallStreamParser:
298
304
  from echotools.exec.fncall.protocols.entml_think.parse import (
299
305
  invoke_index_inside_unclosed_thinking,
300
306
  )
301
- if invoke_index_inside_unclosed_thinking(combined, pos):
307
+ if invoke_index_inside_unclosed_thinking(
308
+ combined, pos, thinking_enabled=self._thinking_enabled
309
+ ):
302
310
  self._feed_waiting_thinking_plain(combined)
303
311
  return
304
312
  if pos > 0:
@@ -314,7 +322,9 @@ class FncallStreamParser:
314
322
  from echotools.exec.fncall.protocols.entml_think.parse import (
315
323
  has_unclosed_entml_thinking,
316
324
  )
317
- if has_unclosed_entml_thinking(combined):
325
+ if has_unclosed_entml_thinking(
326
+ combined, thinking_enabled=self._thinking_enabled
327
+ ):
318
328
  self._feed_waiting_thinking_plain(combined)
319
329
  return
320
330
 
@@ -633,7 +643,9 @@ class FncallStreamParser:
633
643
  from echotools.exec.fncall.protocols.entml_think.parse import (
634
644
  split_entml_thinking,
635
645
  )
636
- clean_text, more_thinking = split_entml_thinking(clean_text)
646
+ clean_text, more_thinking = split_entml_thinking(
647
+ clean_text, thinking_enabled=self._thinking_enabled
648
+ )
637
649
  if more_thinking:
638
650
  self._thinking_parts.append(more_thinking)
639
651
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  from echotools.exec.fncall.protocols.entml_think.core import (
4
4
  build_entml_thinking_section,
5
+ is_thinking_enabled,
5
6
  parse_max_thinking_length,
6
7
  )
7
8
  from echotools.exec.fncall.protocols.entml_think.hist import (
@@ -24,6 +25,7 @@ __all__ = [
24
25
  "extract_reasoning_text",
25
26
  "format_entml_thinking_history_block",
26
27
  "has_unclosed_entml_thinking",
28
+ "is_thinking_enabled",
27
29
  "parse_include_thinking_in_history",
28
30
  "parse_interleaved_history",
29
31
  "parse_max_thinking_length",
@@ -159,6 +159,16 @@ def resolve_thinking_injection(
159
159
  return injection_mode, max_length
160
160
 
161
161
 
162
+ def is_thinking_enabled(protocol_options: Optional[Dict[str, Any]] = None) -> bool:
163
+ """是否开启思考模式(容错与 plain ``<thinking>`` 仅在此为 True 时生效)。
164
+
165
+ 未传 ``protocol_options`` 时视为已开启(兼容旧调用);显式 ``off`` / ``none`` 时关闭。
166
+ """
167
+ if protocol_options is None:
168
+ return True
169
+ return resolve_thinking_injection(protocol_options) is not None
170
+
171
+
162
172
  def _uses_forced_thinking_behavior(injection_mode: str) -> bool:
163
173
  return injection_mode != "auto"
164
174
 
@@ -122,9 +122,18 @@ def _find_plain_thinking_open(text: str, start: int = 0) -> int:
122
122
  return text.find(_PLAIN_THINKING_OPEN_PREFIX, start)
123
123
 
124
124
 
125
- def _find_earliest_thinking_open(text: str, start: int = 0) -> Tuple[int, bool]:
125
+ def _find_earliest_thinking_open(
126
+ text: str,
127
+ start: int = 0,
128
+ *,
129
+ thinking_enabled: bool = True,
130
+ ) -> Tuple[int, bool]:
126
131
  """返回 ``(开标签下标, 是否 plain)``;无则 ``(-1, False)``。"""
127
132
  entml_at = text.find(_THINKING_OPEN_PREFIX, start)
133
+ if not thinking_enabled:
134
+ if entml_at < 0:
135
+ return -1, False
136
+ return entml_at, False
128
137
  plain_at = _find_plain_thinking_open(text, start)
129
138
  if entml_at < 0 and plain_at < 0:
130
139
  return -1, False
@@ -142,8 +151,15 @@ def _find_thinking_close(
142
151
  body_start: int,
143
152
  *,
144
153
  opened_plain: bool,
154
+ thinking_enabled: bool = True,
145
155
  ) -> Tuple[int, int]:
146
156
  """返回 ``(闭标签下标, 闭标签长度)``;未找到则 ``(-1, 0)``。"""
157
+ if not thinking_enabled:
158
+ close_at = text.find(_THINKING_CLOSE, body_start)
159
+ if close_at >= 0:
160
+ return close_at, len(_THINKING_CLOSE)
161
+ return -1, 0
162
+
147
163
  if opened_plain:
148
164
  entml_close = text.find(_THINKING_CLOSE, body_start)
149
165
  fault_close = text.find(_FAULT_THINKING_CLOSE, body_start)
@@ -176,12 +192,18 @@ def _find_thinking_close(
176
192
  return -1, 0
177
193
 
178
194
 
179
- def _hold_thinking_open_prefixes(buffer: str) -> Tuple[str, str]:
195
+ def _hold_thinking_open_prefixes(
196
+ buffer: str,
197
+ *,
198
+ thinking_enabled: bool = True,
199
+ ) -> Tuple[str, str]:
180
200
  """hold ``<entml:thinking`` 或 plain ``<thinking`` 的真前缀。"""
181
201
  safe, hold = _hold_prefix(buffer, _THINKING_OPEN_PREFIX)
182
202
  if hold:
183
203
  return safe, hold
184
- return _hold_prefix(safe, _PLAIN_THINKING_OPEN_PREFIX)
204
+ if thinking_enabled:
205
+ return _hold_prefix(safe, _PLAIN_THINKING_OPEN_PREFIX)
206
+ return safe, ""
185
207
 
186
208
 
187
209
  def _hold_ambiguous_tool_markup(buffer: str) -> Tuple[str, str]:
@@ -211,13 +233,19 @@ def _hold_ambiguous_tool_markup(buffer: str) -> Tuple[str, str]:
211
233
  return buffer, ""
212
234
 
213
235
 
214
- def has_unclosed_entml_thinking(text: str) -> bool:
236
+ def has_unclosed_entml_thinking(
237
+ text: str,
238
+ *,
239
+ thinking_enabled: bool = True,
240
+ ) -> bool:
215
241
  """buffer 中是否存在尚未闭合的 thinking 块(含开标签未收齐)。"""
216
242
  if not text:
217
243
  return False
218
244
  i = 0
219
245
  while i < len(text):
220
- open_at, opened_plain = _find_earliest_thinking_open(text, i)
246
+ open_at, opened_plain = _find_earliest_thinking_open(
247
+ text, i, thinking_enabled=thinking_enabled
248
+ )
221
249
  if open_at < 0:
222
250
  break
223
251
  gt = text.find(">", open_at)
@@ -225,12 +253,17 @@ def has_unclosed_entml_thinking(text: str) -> bool:
225
253
  return True
226
254
  body_start = gt + 1
227
255
  close_at, close_len = _find_thinking_close(
228
- text, body_start, opened_plain=opened_plain
256
+ text,
257
+ body_start,
258
+ opened_plain=opened_plain,
259
+ thinking_enabled=thinking_enabled,
229
260
  )
230
261
  if close_at < 0:
231
262
  return True
232
263
  i = close_at + close_len
233
- max_keep = max(len(_THINKING_OPEN_PREFIX), len(_PLAIN_THINKING_OPEN_PREFIX)) - 1
264
+ max_keep = len(_THINKING_OPEN_PREFIX) - 1
265
+ if thinking_enabled:
266
+ max_keep = max(max_keep, len(_PLAIN_THINKING_OPEN_PREFIX) - 1)
234
267
  check_len = min(len(text), max_keep)
235
268
  for length in range(check_len, 0, -1):
236
269
  suffix = text[-length:]
@@ -241,7 +274,7 @@ def has_unclosed_entml_thinking(text: str) -> bool:
241
274
  if any(other.startswith(suffix) for other in _AMBIGUOUS_ENTML_PREFIXES):
242
275
  continue
243
276
  return True
244
- if (
277
+ if thinking_enabled and (
245
278
  _PLAIN_THINKING_OPEN_PREFIX.startswith(suffix)
246
279
  and suffix != _PLAIN_THINKING_OPEN_PREFIX
247
280
  ):
@@ -249,11 +282,18 @@ def has_unclosed_entml_thinking(text: str) -> bool:
249
282
  return False
250
283
 
251
284
 
252
- def invoke_index_inside_unclosed_thinking(text: str, invoke_at: int) -> bool:
285
+ def invoke_index_inside_unclosed_thinking(
286
+ text: str,
287
+ invoke_at: int,
288
+ *,
289
+ thinking_enabled: bool = True,
290
+ ) -> bool:
253
291
  """``invoke_at`` 是否落在尚未闭合的 thinking 块内。"""
254
292
  if invoke_at < 0:
255
293
  return False
256
- think_open, opened_plain = _find_earliest_thinking_open(text)
294
+ think_open, opened_plain = _find_earliest_thinking_open(
295
+ text, thinking_enabled=thinking_enabled
296
+ )
257
297
  if think_open < 0 or invoke_at <= think_open:
258
298
  return False
259
299
  gt = text.find(">", think_open)
@@ -261,14 +301,21 @@ def invoke_index_inside_unclosed_thinking(text: str, invoke_at: int) -> bool:
261
301
  return True
262
302
  body_start = gt + 1
263
303
  close_at, close_len = _find_thinking_close(
264
- text, body_start, opened_plain=opened_plain
304
+ text,
305
+ body_start,
306
+ opened_plain=opened_plain,
307
+ thinking_enabled=thinking_enabled,
265
308
  )
266
309
  if close_at < 0:
267
310
  return True
268
311
  return invoke_at < close_at + close_len
269
312
 
270
313
 
271
- def split_entml_thinking(text: str) -> Tuple[str, str]:
314
+ def split_entml_thinking(
315
+ text: str,
316
+ *,
317
+ thinking_enabled: bool = True,
318
+ ) -> Tuple[str, str]:
272
319
  """从文本中剥离 thinking 块,返回 (正文, 思考链拼接)。"""
273
320
  if not text:
274
321
  return "", ""
@@ -277,7 +324,9 @@ def split_entml_thinking(text: str) -> Tuple[str, str]:
277
324
  clean_parts: List[str] = []
278
325
  i = 0
279
326
  while i < len(text):
280
- open_at, opened_plain = _find_earliest_thinking_open(text, i)
327
+ open_at, opened_plain = _find_earliest_thinking_open(
328
+ text, i, thinking_enabled=thinking_enabled
329
+ )
281
330
  if open_at < 0:
282
331
  clean_parts.append(text[i:])
283
332
  break
@@ -290,7 +339,10 @@ def split_entml_thinking(text: str) -> Tuple[str, str]:
290
339
 
291
340
  body_start = gt + 1
292
341
  close_at, close_len = _find_thinking_close(
293
- text, body_start, opened_plain=opened_plain
342
+ text,
343
+ body_start,
344
+ opened_plain=opened_plain,
345
+ thinking_enabled=thinking_enabled,
294
346
  )
295
347
  if close_at < 0:
296
348
  clean_parts.append(text[open_at:])
@@ -336,7 +388,8 @@ class EntmlThinkingStreamFilter:
336
388
  块内正文在收到时即增量输出 thinking,无需等闭合标签。
337
389
  """
338
390
 
339
- def __init__(self) -> None:
391
+ def __init__(self, *, thinking_enabled: bool = True) -> None:
392
+ self._thinking_enabled = thinking_enabled
340
393
  self._pending = ""
341
394
  self._in_block = False
342
395
  self._opened_plain = False
@@ -358,12 +411,16 @@ class EntmlThinkingStreamFilter:
358
411
  """思考块已开始且尚未闭合(含开/闭标签分片 hold)。"""
359
412
  if self._in_block or self._fault_watch:
360
413
  return True
361
- open_at, _ = _find_earliest_thinking_open(self._pending)
414
+ open_at, _ = _find_earliest_thinking_open(
415
+ self._pending, thinking_enabled=self._thinking_enabled
416
+ )
362
417
  if open_at >= 0:
363
418
  gt = self._pending.find(">", open_at)
364
419
  if gt < 0:
365
420
  return True
366
- _, hold = _hold_thinking_open_prefixes(self._pending)
421
+ _, hold = _hold_thinking_open_prefixes(
422
+ self._pending, thinking_enabled=self._thinking_enabled
423
+ )
367
424
  if hold:
368
425
  return True
369
426
  return False
@@ -474,7 +531,7 @@ class EntmlThinkingStreamFilter:
474
531
  return True
475
532
 
476
533
  fault_at = self._pending.find(_FAULT_THINKING_CLOSE)
477
- if fault_at >= 0:
534
+ if fault_at >= 0 and self._thinking_enabled:
478
535
  before = self._pending[:fault_at]
479
536
  if self._opened_plain:
480
537
  emitted = self._emit_thinking_piece(before)
@@ -505,7 +562,7 @@ class EntmlThinkingStreamFilter:
505
562
  return False
506
563
 
507
564
  safe, hold = _hold_prefix(self._pending, _THINKING_CLOSE)
508
- if not hold:
565
+ if not hold and self._thinking_enabled:
509
566
  safe, hold = _hold_prefix(safe, _FAULT_THINKING_CLOSE)
510
567
  if safe:
511
568
  emitted = self._emit_thinking_piece(safe)
@@ -516,9 +573,13 @@ class EntmlThinkingStreamFilter:
516
573
 
517
574
  def _feed_before_block(self, out: List[Tuple[str, str]]) -> bool:
518
575
  """开标签之前或尚未进入块;返回 False 表示应退出 feed 主循环。"""
519
- open_at, opened_plain = _find_earliest_thinking_open(self._pending)
576
+ open_at, opened_plain = _find_earliest_thinking_open(
577
+ self._pending, thinking_enabled=self._thinking_enabled
578
+ )
520
579
  if open_at < 0:
521
- safe, self._pending = _hold_thinking_open_prefixes(self._pending)
580
+ safe, self._pending = _hold_thinking_open_prefixes(
581
+ self._pending, thinking_enabled=self._thinking_enabled
582
+ )
522
583
  if safe:
523
584
  out.append(("content", safe))
524
585
  return False
@@ -598,7 +659,9 @@ class EntmlThinkingStreamFilter:
598
659
  self._opened_plain = False
599
660
  self._thinking_started = False
600
661
  if self._pending:
601
- content, thinking = split_entml_thinking(self._pending)
662
+ content, thinking = split_entml_thinking(
663
+ self._pending, thinking_enabled=self._thinking_enabled
664
+ )
602
665
  if thinking:
603
666
  out.append(("thinking", thinking))
604
667
  if content:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.61
3
+ Version: 2.3.62
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -132,3 +132,22 @@ def test_plain_thinking_stream_fault_close() -> None:
132
132
  assert thinking.strip() == "plan"
133
133
  assert "hello" in content
134
134
  assert not filt.in_open_thinking()
135
+
136
+
137
+ def test_is_thinking_enabled() -> None:
138
+ from echotools.exec.fncall.protocols.entml_think.core import is_thinking_enabled
139
+
140
+ assert is_thinking_enabled(None)
141
+ assert is_thinking_enabled({"thinking_mode": "on"})
142
+ assert is_thinking_enabled({"thinking_mode": "auto"})
143
+ assert not is_thinking_enabled({"thinking_mode": "off"})
144
+ assert not is_thinking_enabled({"thinking_level": "none"})
145
+
146
+
147
+ def test_plain_thinking_requires_thinking_enabled_batch() -> None:
148
+ text = "<thinking>\nplan\n</thinking>\nanswer"
149
+ _, thinking_on = split_entml_thinking(text, thinking_enabled=True)
150
+ content_off, thinking_off = split_entml_thinking(text, thinking_enabled=False)
151
+ assert thinking_on == "plan"
152
+ assert thinking_off == ""
153
+ assert "<thinking>" in content_off
@@ -328,3 +328,35 @@ def test_entml_open_fault_close_then_invoke_still_works(chunk: int = 3) -> None:
328
328
  clean, calls = parser.finalize()
329
329
  assert len(calls) == 1
330
330
  assert "plan" in parser.partial_thinking
331
+
332
+
333
+ @pytest.mark.parametrize("chunk", [1, 5, 17])
334
+ def test_thinking_disabled_plain_open_stays_visible(chunk: int) -> None:
335
+ """未开思考时 plain ``<thinking>`` 不得进入思考链。"""
336
+ parser = FncallStreamParser(
337
+ protocol=get_protocol("entml"),
338
+ tools=TOOLS,
339
+ protocol_options={"thinking_mode": "off"},
340
+ )
341
+ text = "<thinking>\nplan\n</thinking>\nvisible\n"
342
+ for i in range(0, len(text), chunk):
343
+ parser.feed(text[i : i + chunk])
344
+ clean, calls = parser.finalize()
345
+ assert not calls
346
+ assert not parser.partial_thinking
347
+ assert "<thinking>" in parser.partial_text or "plan" in parser.partial_text
348
+ assert "visible" in clean
349
+
350
+
351
+ def test_thinking_disabled_fault_close_not_applied() -> None:
352
+ """未开思考时 ``</thinking>`` 不得作为 entml 块的 fault 闭合。"""
353
+ parser = FncallStreamParser(
354
+ protocol=get_protocol("entml"),
355
+ tools=TOOLS,
356
+ protocol_options={"thinking_mode": "off"},
357
+ )
358
+ text = f"<entml:thinking>\nplan\n</thinking>\n{INVOKE}"
359
+ parser.feed(text)
360
+ clean, calls = parser.finalize()
361
+ assert len(calls) == 1
362
+ assert "plan" not in parser.partial_thinking or "</thinking>" in parser.partial_thinking
File without changes
File without changes
File without changes