echotools 2.3.14__tar.gz → 2.3.16__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 (181) hide show
  1. {echotools-2.3.14 → echotools-2.3.16}/PKG-INFO +1 -1
  2. {echotools-2.3.14 → echotools-2.3.16}/pyproject.toml +1 -1
  3. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/entml_thinking.py +4 -4
  4. echotools-2.3.16/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +122 -0
  5. {echotools-2.3.14 → echotools-2.3.16}/src/echotools.egg-info/PKG-INFO +1 -1
  6. {echotools-2.3.14 → echotools-2.3.16}/src/echotools.egg-info/SOURCES.txt +2 -0
  7. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_entml_protocol.py +1 -2
  8. echotools-2.3.16/src/tests/test_entml_thinking_parse.py +27 -0
  9. {echotools-2.3.14 → echotools-2.3.16}/README.md +0 -0
  10. {echotools-2.3.14 → echotools-2.3.16}/docs/README.md +0 -0
  11. {echotools-2.3.14 → echotools-2.3.16}/setup.cfg +0 -0
  12. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/__init__.py +0 -0
  13. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/cache/__init__.py +0 -0
  14. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/cache/list_cache.py +0 -0
  15. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/cache/memory_cache.py +0 -0
  16. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/__init__.py +0 -0
  17. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/base.py +0 -0
  18. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/center.py +0 -0
  19. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/center_notify.py +0 -0
  20. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/center_template.py +0 -0
  21. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/loader.py +0 -0
  22. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/config/merge.py +0 -0
  23. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/errors/__init__.py +0 -0
  24. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/errors/base.py +0 -0
  25. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/errors/classify.py +0 -0
  26. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/errors/common.py +0 -0
  27. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/errors/http.py +0 -0
  28. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/ids/__init__.py +0 -0
  29. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/ids/generator.py +0 -0
  30. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/io/__init__.py +0 -0
  31. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/io/io_utils.py +0 -0
  32. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/io/printstream.py +0 -0
  33. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/logger/__init__.py +0 -0
  34. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/logger/manager.py +0 -0
  35. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/retry/__init__.py +0 -0
  36. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/base/retry/retry.py +0 -0
  37. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/compat.py +0 -0
  38. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/__init__.py +0 -0
  39. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/candidate.py +0 -0
  40. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  41. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  42. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/race.py +0 -0
  43. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/selector.py +0 -0
  44. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/dispatch/usage.py +0 -0
  45. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/__init__.py +0 -0
  46. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  47. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  48. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  49. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  50. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  51. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/prompt/history.py +0 -0
  52. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  53. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  54. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  55. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  56. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  57. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  58. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  59. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  60. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  61. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  62. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/registry.py +0 -0
  63. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  64. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  65. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  66. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  67. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  68. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  69. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/keys.py +0 -0
  70. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/lifecycle/__init__.py +0 -0
  71. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/lifecycle/manager.py +0 -0
  72. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/lifecycle/updater.py +0 -0
  73. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  74. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/process/__init__.py +0 -0
  75. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/process/port.py +0 -0
  76. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/protocol/__init__.py +0 -0
  77. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/protocol/base.py +0 -0
  78. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/__init__.py +0 -0
  79. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/conpty.py +0 -0
  80. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/local/__init__.py +0 -0
  81. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/local/core.py +0 -0
  82. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/local/proc.py +0 -0
  83. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/local/unix.py +0 -0
  84. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/local/win.py +0 -0
  85. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/sanitize.py +0 -0
  86. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/session/__init__.py +0 -0
  87. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/session/base.py +0 -0
  88. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/session/client.py +0 -0
  89. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/ssh.py +0 -0
  90. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/exec/terminal/tmux.py +0 -0
  91. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/events/__init__.py +0 -0
  92. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/events/bus.py +0 -0
  93. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/events/event.py +0 -0
  94. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/spinner/__init__.py +0 -0
  95. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/spinner/spinner.py +0 -0
  96. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/spinner/spinner_color.py +0 -0
  97. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/tracing/__init__.py +0 -0
  98. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/tracing/context.py +0 -0
  99. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/tracing/span.py +0 -0
  100. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/tracing/tracer.py +0 -0
  101. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/translate.py +0 -0
  102. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/watcher/__init__.py +0 -0
  103. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/watcher/file_watcher.py +0 -0
  104. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/__init__.py +0 -0
  105. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/application.py +0 -0
  106. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/broker.py +0 -0
  107. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/app.js +0 -0
  108. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  109. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  110. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  111. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  112. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  113. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  114. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  115. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  116. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  117. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  118. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
  119. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/inputbox.css +0 -0
  120. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/inputbox.js +0 -0
  121. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/style.css +0 -0
  122. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/input_box/voicegif.js +0 -0
  123. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/middleware.py +0 -0
  124. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/stats.py +0 -0
  125. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/media/web/utils.py +0 -0
  126. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/files/__init__.py +0 -0
  127. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/files/file_util.py +0 -0
  128. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/network/__init__.py +0 -0
  129. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/network/http_utils.py +0 -0
  130. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/plugin/__init__.py +0 -0
  131. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/plugin/base.py +0 -0
  132. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/plugin/discovery.py +0 -0
  133. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/plugin/registry.py +0 -0
  134. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/plugin/registry_ops.py +0 -0
  135. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/proxy/__init__.py +0 -0
  136. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/proxy/manager.py +0 -0
  137. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/runtime/__init__.py +0 -0
  138. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/runtime/collector.py +0 -0
  139. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/scheduler/__init__.py +0 -0
  140. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/scheduler/scheduler.py +0 -0
  141. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/sdk/__init__.py +0 -0
  142. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/plat/sdk/facade.py +0 -0
  143. {echotools-2.3.14 → echotools-2.3.16}/src/echotools/version.py +0 -0
  144. {echotools-2.3.14 → echotools-2.3.16}/src/echotools.egg-info/dependency_links.txt +0 -0
  145. {echotools-2.3.14 → echotools-2.3.16}/src/echotools.egg-info/requires.txt +0 -0
  146. {echotools-2.3.14 → echotools-2.3.16}/src/echotools.egg-info/top_level.txt +0 -0
  147. {echotools-2.3.14 → echotools-2.3.16}/src/tests/bench_import.py +0 -0
  148. {echotools-2.3.14 → echotools-2.3.16}/src/tests/conftest.py +0 -0
  149. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_cache_retry.py +0 -0
  150. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_completion.py +0 -0
  151. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_config.py +0 -0
  152. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_config_extended.py +0 -0
  153. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_coverage_core.py +0 -0
  154. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_dispatch.py +0 -0
  155. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_dispatch_extended.py +0 -0
  156. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_dispatch_race.py +0 -0
  157. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_entml_tools.py +0 -0
  158. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_errors.py +0 -0
  159. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_errors_common.py +0 -0
  160. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_events.py +0 -0
  161. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_events_event.py +0 -0
  162. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_facade.py +0 -0
  163. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_fncall.py +0 -0
  164. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_ids.py +0 -0
  165. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_imports.py +0 -0
  166. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_io.py +0 -0
  167. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_keys.py +0 -0
  168. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_lifecycle.py +0 -0
  169. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_logger.py +0 -0
  170. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_merge.py +0 -0
  171. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_plugin.py +0 -0
  172. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_printstream.py +0 -0
  173. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_protocols.py +0 -0
  174. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_proxy.py +0 -0
  175. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_proxy_selector.py +0 -0
  176. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_runtime.py +0 -0
  177. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_scheduler.py +0 -0
  178. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_tracing.py +0 -0
  179. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_usage.py +0 -0
  180. {echotools-2.3.14 → echotools-2.3.16}/src/tests/test_web.py +0 -0
  181. {echotools-2.3.14 → echotools-2.3.16}/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.14
3
+ Version: 2.3.16
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "echotools"
7
- version = "2.3.14"
7
+ version = "2.3.16"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -41,12 +41,12 @@ def build_entml_thinking_section(
41
41
  "...",
42
42
  "</function_results>",
43
43
  "",
44
- "<thinking>",
44
+ "<entml:thinking>",
45
45
  "...thinking about results",
46
- "</thinking>",
46
+ "</entml:thinking>",
47
47
  "At the very start of your response, think carefully about whether "
48
- "a `<thinking>` `</thinking>` block would be appropriate and "
49
- "strongly prefer to output a thinking block if you are uncertain.",
48
+ "an `<entml:thinking>` `</entml:thinking>` block would be appropriate "
49
+ "and strongly prefer to output a thinking block if you are uncertain.",
50
50
  ]
51
51
  )
52
52
 
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import List, Tuple
5
+
6
+ THINKING_BLOCK_RE = re.compile(
7
+ r"<entml:thinking\b[^>]*>([\s\S]*?)</entml:thinking>",
8
+ re.DOTALL,
9
+ )
10
+
11
+ _THINKING_OPEN_PREFIX = "<entml:thinking"
12
+ _THINKING_CLOSE = "</entml:thinking>"
13
+
14
+
15
+ def split_entml_thinking(text: str) -> Tuple[str, str]:
16
+ """从文本中剥离 <entml:thinking> 块,返回 (正文, 思考链拼接)。"""
17
+ if not text:
18
+ return "", ""
19
+
20
+ parts: List[str] = []
21
+
22
+ def _collect(match: re.Match[str]) -> str:
23
+ parts.append(match.group(1))
24
+ return ""
25
+
26
+ clean = THINKING_BLOCK_RE.sub(_collect, text)
27
+ thinking = "\n".join(part.strip() for part in parts if part.strip())
28
+ return clean.strip(), thinking
29
+
30
+
31
+ def _split_safe_prefix(buffer: str) -> Tuple[str, str]:
32
+ """保留可能未闭合的 <entml:thinking 开标签尾部。"""
33
+ if not buffer:
34
+ return "", ""
35
+
36
+ open_pos = buffer.rfind(_THINKING_OPEN_PREFIX)
37
+ if open_pos < 0:
38
+ return buffer, ""
39
+
40
+ tail = buffer[open_pos:]
41
+ if ">" in tail and _THINKING_CLOSE not in tail:
42
+ return buffer, ""
43
+
44
+ close_pos = buffer.rfind(_THINKING_CLOSE)
45
+ if close_pos >= 0 and close_pos > open_pos:
46
+ return buffer, ""
47
+
48
+ if tail == _THINKING_OPEN_PREFIX[: len(tail)]:
49
+ return buffer[:open_pos], tail
50
+
51
+ return buffer[:open_pos], tail
52
+
53
+
54
+ class EntmlThinkingStreamFilter:
55
+ """流式拆分 entml:thinking 与可见正文。"""
56
+
57
+ def __init__(self) -> None:
58
+ self._pending = ""
59
+ self._in_block = False
60
+ self._block_buf = ""
61
+
62
+ def feed(self, chunk: str) -> List[Tuple[str, str]]:
63
+ """返回 [(kind, text), ...],kind 为 content 或 thinking。"""
64
+ if not chunk:
65
+ return []
66
+
67
+ self._pending += chunk
68
+ out: List[Tuple[str, str]] = []
69
+
70
+ while self._pending:
71
+ if self._in_block:
72
+ close_at = self._pending.find(_THINKING_CLOSE)
73
+ if close_at < 0:
74
+ self._block_buf += self._pending
75
+ self._pending = ""
76
+ break
77
+ self._block_buf += self._pending[:close_at]
78
+ thinking = self._block_buf.strip()
79
+ if thinking:
80
+ out.append(("thinking", thinking))
81
+ self._block_buf = ""
82
+ self._in_block = False
83
+ self._pending = self._pending[close_at + len(_THINKING_CLOSE) :]
84
+ continue
85
+
86
+ open_at = self._pending.find(_THINKING_OPEN_PREFIX)
87
+ if open_at < 0:
88
+ safe, self._pending = _split_safe_prefix(self._pending)
89
+ if safe:
90
+ out.append(("content", safe))
91
+ break
92
+
93
+ if open_at > 0:
94
+ prefix = self._pending[:open_at]
95
+ out.append(("content", prefix))
96
+ self._pending = self._pending[open_at:]
97
+
98
+ gt = self._pending.find(">")
99
+ if gt < 0:
100
+ break
101
+
102
+ self._in_block = True
103
+ self._pending = self._pending[gt + 1 :]
104
+
105
+ return out
106
+
107
+ def finalize(self) -> List[Tuple[str, str]]:
108
+ out: List[Tuple[str, str]] = []
109
+ if self._in_block:
110
+ thinking = (self._block_buf + self._pending).strip()
111
+ if thinking:
112
+ out.append(("thinking", thinking))
113
+ elif self._pending:
114
+ content, thinking = split_entml_thinking(self._pending)
115
+ if thinking:
116
+ out.append(("thinking", thinking))
117
+ if content:
118
+ out.append(("content", content))
119
+ self._pending = ""
120
+ self._block_buf = ""
121
+ self._in_block = False
122
+ return out
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.14
3
+ Version: 2.3.16
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
6
  License: MIT
@@ -58,6 +58,7 @@ src/echotools/exec/fncall/protocols/entml.py
58
58
  src/echotools/exec/fncall/protocols/entml_invoke.py
59
59
  src/echotools/exec/fncall/protocols/entml_patterns.py
60
60
  src/echotools/exec/fncall/protocols/entml_thinking.py
61
+ src/echotools/exec/fncall/protocols/entml_thinking_parse.py
61
62
  src/echotools/exec/fncall/protocols/entml_tools.py
62
63
  src/echotools/exec/fncall/protocols/entml_values.py
63
64
  src/echotools/exec/fncall/shared/__init__.py
@@ -150,6 +151,7 @@ src/tests/test_dispatch.py
150
151
  src/tests/test_dispatch_extended.py
151
152
  src/tests/test_dispatch_race.py
152
153
  src/tests/test_entml_protocol.py
154
+ src/tests/test_entml_thinking_parse.py
153
155
  src/tests/test_entml_tools.py
154
156
  src/tests/test_errors.py
155
157
  src/tests/test_errors_common.py
@@ -60,9 +60,8 @@ def test_inject_with_thinking_options_only_when_declared() -> None:
60
60
  )[0]["content"]
61
61
  assert "<entml:thinking_mode>interleaved</entml:thinking_mode>" in with_opts
62
62
  assert "<entml:max_thinking_length>22000</entml:max_thinking_length>" in with_opts
63
- assert "<thinking>" in with_opts
63
+ assert "<entml:thinking>" in with_opts
64
64
  assert "At the very start of your response" in with_opts
65
- assert "<entml:thinking>" not in with_opts
66
65
 
67
66
 
68
67
  def test_build_entml_thinking_section_empty_without_options() -> None:
@@ -0,0 +1,27 @@
1
+ from echotools.exec.fncall.protocols.entml_thinking_parse import (
2
+ EntmlThinkingStreamFilter,
3
+ split_entml_thinking,
4
+ )
5
+
6
+
7
+ def test_split_entml_thinking() -> None:
8
+ text = (
9
+ "prefix\n<entml:thinking>\nstep one\n</entml:thinking>\n"
10
+ "answer tail"
11
+ )
12
+ content, thinking = split_entml_thinking(text)
13
+ assert thinking == "step one"
14
+ assert "prefix" in content
15
+ assert "answer tail" in content
16
+ assert "<entml:thinking>" not in content
17
+
18
+
19
+ def test_entml_thinking_stream_filter() -> None:
20
+ filt = EntmlThinkingStreamFilter()
21
+ parts = []
22
+ for chunk in ["hello ", "<entml:thinking>rea", "son</entml:thinking> world"]:
23
+ parts.extend(filt.feed(chunk))
24
+ parts.extend(filt.finalize())
25
+ kinds = {kind for kind, _ in parts}
26
+ assert "thinking" in kinds
27
+ assert "content" in kinds
File without changes
File without changes
File without changes