echotools 2.3.21__tar.gz → 2.3.22__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 (183) hide show
  1. {echotools-2.3.21 → echotools-2.3.22}/PKG-INFO +1 -1
  2. {echotools-2.3.21 → echotools-2.3.22}/pyproject.toml +1 -1
  3. echotools-2.3.22/src/echotools/exec/fncall/protocols/entml_thinking_history.py +133 -0
  4. {echotools-2.3.21 → echotools-2.3.22}/src/echotools.egg-info/PKG-INFO +1 -1
  5. {echotools-2.3.21 → echotools-2.3.22}/src/echotools.egg-info/SOURCES.txt +2 -0
  6. echotools-2.3.22/src/tests/test_entml_thinking_history.py +71 -0
  7. {echotools-2.3.21 → echotools-2.3.22}/README.md +0 -0
  8. {echotools-2.3.21 → echotools-2.3.22}/docs/README.md +0 -0
  9. {echotools-2.3.21 → echotools-2.3.22}/setup.cfg +0 -0
  10. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/__init__.py +0 -0
  11. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/cache/__init__.py +0 -0
  12. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/cache/list_cache.py +0 -0
  13. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/cache/memory_cache.py +0 -0
  14. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/__init__.py +0 -0
  15. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/base.py +0 -0
  16. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/center.py +0 -0
  17. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/center_notify.py +0 -0
  18. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/center_template.py +0 -0
  19. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/loader.py +0 -0
  20. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/config/merge.py +0 -0
  21. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/errors/__init__.py +0 -0
  22. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/errors/base.py +0 -0
  23. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/errors/classify.py +0 -0
  24. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/errors/common.py +0 -0
  25. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/errors/http.py +0 -0
  26. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/ids/__init__.py +0 -0
  27. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/ids/generator.py +0 -0
  28. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/io/__init__.py +0 -0
  29. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/io/io_utils.py +0 -0
  30. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/io/printstream.py +0 -0
  31. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/logger/__init__.py +0 -0
  32. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/logger/manager.py +0 -0
  33. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/retry/__init__.py +0 -0
  34. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/base/retry/retry.py +0 -0
  35. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/compat.py +0 -0
  36. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/__init__.py +0 -0
  37. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/candidate.py +0 -0
  38. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  39. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  40. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/race.py +0 -0
  41. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/selector.py +0 -0
  42. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/dispatch/usage.py +0 -0
  43. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/__init__.py +0 -0
  44. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  45. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  46. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  47. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  48. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  49. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/history.py +0 -0
  50. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  51. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  52. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  53. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  54. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  55. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  56. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  57. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  58. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_thinking.py +0 -0
  59. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +0 -0
  60. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  61. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  62. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/registry.py +0 -0
  63. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  64. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  65. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  66. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  67. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  68. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  69. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/keys.py +0 -0
  70. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/lifecycle/__init__.py +0 -0
  71. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/lifecycle/manager.py +0 -0
  72. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/lifecycle/updater.py +0 -0
  73. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  74. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/process/__init__.py +0 -0
  75. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/process/port.py +0 -0
  76. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/protocol/__init__.py +0 -0
  77. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/protocol/base.py +0 -0
  78. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/__init__.py +0 -0
  79. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/conpty.py +0 -0
  80. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/local/__init__.py +0 -0
  81. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/local/core.py +0 -0
  82. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/local/proc.py +0 -0
  83. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/local/unix.py +0 -0
  84. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/local/win.py +0 -0
  85. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/sanitize.py +0 -0
  86. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/session/__init__.py +0 -0
  87. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/session/base.py +0 -0
  88. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/session/client.py +0 -0
  89. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/ssh.py +0 -0
  90. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/exec/terminal/tmux.py +0 -0
  91. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/events/__init__.py +0 -0
  92. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/events/bus.py +0 -0
  93. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/events/event.py +0 -0
  94. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/spinner/__init__.py +0 -0
  95. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/spinner/spinner.py +0 -0
  96. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/spinner/spinner_color.py +0 -0
  97. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/tracing/__init__.py +0 -0
  98. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/tracing/context.py +0 -0
  99. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/tracing/span.py +0 -0
  100. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/tracing/tracer.py +0 -0
  101. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/translate.py +0 -0
  102. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/watcher/__init__.py +0 -0
  103. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/watcher/file_watcher.py +0 -0
  104. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/__init__.py +0 -0
  105. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/application.py +0 -0
  106. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/broker.py +0 -0
  107. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/app.js +0 -0
  108. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  109. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  110. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  111. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  112. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  113. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  114. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  115. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  116. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  117. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  118. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
  119. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/inputbox.css +0 -0
  120. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/inputbox.js +0 -0
  121. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/style.css +0 -0
  122. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/input_box/voicegif.js +0 -0
  123. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/middleware.py +0 -0
  124. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/stats.py +0 -0
  125. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/media/web/utils.py +0 -0
  126. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/files/__init__.py +0 -0
  127. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/files/file_util.py +0 -0
  128. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/network/__init__.py +0 -0
  129. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/network/http_utils.py +0 -0
  130. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/plugin/__init__.py +0 -0
  131. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/plugin/base.py +0 -0
  132. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/plugin/discovery.py +0 -0
  133. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/plugin/registry.py +0 -0
  134. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/plugin/registry_ops.py +0 -0
  135. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/proxy/__init__.py +0 -0
  136. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/proxy/manager.py +0 -0
  137. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/runtime/__init__.py +0 -0
  138. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/runtime/collector.py +0 -0
  139. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/scheduler/__init__.py +0 -0
  140. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/scheduler/scheduler.py +0 -0
  141. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/sdk/__init__.py +0 -0
  142. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/plat/sdk/facade.py +0 -0
  143. {echotools-2.3.21 → echotools-2.3.22}/src/echotools/version.py +0 -0
  144. {echotools-2.3.21 → echotools-2.3.22}/src/echotools.egg-info/dependency_links.txt +0 -0
  145. {echotools-2.3.21 → echotools-2.3.22}/src/echotools.egg-info/requires.txt +0 -0
  146. {echotools-2.3.21 → echotools-2.3.22}/src/echotools.egg-info/top_level.txt +0 -0
  147. {echotools-2.3.21 → echotools-2.3.22}/src/tests/bench_import.py +0 -0
  148. {echotools-2.3.21 → echotools-2.3.22}/src/tests/conftest.py +0 -0
  149. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_cache_retry.py +0 -0
  150. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_completion.py +0 -0
  151. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_config.py +0 -0
  152. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_config_extended.py +0 -0
  153. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_coverage_core.py +0 -0
  154. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_dispatch.py +0 -0
  155. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_dispatch_extended.py +0 -0
  156. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_dispatch_race.py +0 -0
  157. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_entml_protocol.py +0 -0
  158. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_entml_thinking_parse.py +0 -0
  159. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_entml_tools.py +0 -0
  160. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_errors.py +0 -0
  161. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_errors_common.py +0 -0
  162. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_events.py +0 -0
  163. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_events_event.py +0 -0
  164. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_facade.py +0 -0
  165. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_fncall.py +0 -0
  166. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_ids.py +0 -0
  167. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_imports.py +0 -0
  168. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_io.py +0 -0
  169. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_keys.py +0 -0
  170. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_lifecycle.py +0 -0
  171. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_logger.py +0 -0
  172. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_merge.py +0 -0
  173. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_plugin.py +0 -0
  174. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_printstream.py +0 -0
  175. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_protocols.py +0 -0
  176. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_proxy.py +0 -0
  177. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_proxy_selector.py +0 -0
  178. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_runtime.py +0 -0
  179. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_scheduler.py +0 -0
  180. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_tracing.py +0 -0
  181. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_usage.py +0 -0
  182. {echotools-2.3.21 → echotools-2.3.22}/src/tests/test_web.py +0 -0
  183. {echotools-2.3.21 → echotools-2.3.22}/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.21
3
+ Version: 2.3.22
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.21"
7
+ version = "2.3.22"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Mapping, Optional
4
+
5
+ REASONING_KEYS = ("reasoning", "reasoning_content", "reasoning_details")
6
+ HISTORY_FLAG_KEYS = (
7
+ "include_thinking_in_history",
8
+ "pass_thinking",
9
+ "include_thinking",
10
+ "interleaved_history",
11
+ )
12
+ _THINKING_BLOCK_TYPES = frozenset({"thinking", "reasoning", "redacted_thinking"})
13
+
14
+
15
+ def parse_interleaved_history(
16
+ body: Mapping[str, Any],
17
+ extra: Optional[Mapping[str, Any]] = None,
18
+ thinking: Any = None,
19
+ ) -> bool:
20
+ """解析是否将历史 assistant 思考链传给模型(Entropy interleaved_history)。"""
21
+ extra = extra or {}
22
+ if thinking is None:
23
+ thinking = body.get("thinking")
24
+ for key in ("interleaved_history", "include_in_history"):
25
+ if key in body:
26
+ return bool(body[key])
27
+ if key in extra:
28
+ return bool(extra[key])
29
+ if isinstance(thinking, dict):
30
+ for key in ("interleaved_history", "include_in_history"):
31
+ if key in thinking:
32
+ return bool(thinking[key])
33
+ return False
34
+
35
+
36
+ def _thinking_from_content_block(block: Any) -> str:
37
+ if not isinstance(block, dict):
38
+ return ""
39
+ btype = str(block.get("type", "")).strip().lower()
40
+ if btype in ("thinking", "redacted_thinking"):
41
+ val = block.get("thinking") or block.get("data")
42
+ return str(val).strip() if val else ""
43
+ if btype == "reasoning":
44
+ val = block.get("text") or block.get("reasoning")
45
+ return str(val).strip() if val else ""
46
+ return ""
47
+
48
+
49
+ def extract_reasoning_text(msg: Mapping[str, Any]) -> str:
50
+ """从 assistant 消息字段或 Entropy/Anthropic 内容块提取思考文本。"""
51
+ for key in ("reasoning", "reasoning_content"):
52
+ val = msg.get(key)
53
+ if isinstance(val, str) and val.strip():
54
+ return val.strip()
55
+
56
+ details = msg.get("reasoning_details")
57
+ if isinstance(details, list):
58
+ parts: List[str] = []
59
+ for item in details:
60
+ if not isinstance(item, dict):
61
+ continue
62
+ text = item.get("text")
63
+ if text:
64
+ parts.append(str(text))
65
+ if parts:
66
+ return "".join(parts)
67
+
68
+ content = msg.get("content")
69
+ if isinstance(content, list):
70
+ parts = []
71
+ for block in content:
72
+ text = _thinking_from_content_block(block)
73
+ if text:
74
+ parts.append(text)
75
+ if parts:
76
+ return "\n".join(parts)
77
+ return ""
78
+
79
+
80
+ def _strip_thinking_blocks(content: List[Any]) -> List[Any]:
81
+ kept: List[Any] = []
82
+ for block in content:
83
+ if isinstance(block, dict) and str(block.get("type", "")).lower() in _THINKING_BLOCK_TYPES:
84
+ continue
85
+ kept.append(block)
86
+ return kept
87
+
88
+
89
+ def _collapse_text_content(blocks: List[Any]) -> Any:
90
+ if len(blocks) == 1:
91
+ only = blocks[0]
92
+ if isinstance(only, dict) and only.get("type") == "text":
93
+ return only.get("text", "")
94
+ return blocks
95
+
96
+
97
+ def _strip_reasoning_from_message(msg: Dict[str, Any]) -> Dict[str, Any]:
98
+ out = dict(msg)
99
+ for key in REASONING_KEYS:
100
+ out.pop(key, None)
101
+ content = out.get("content")
102
+ if isinstance(content, list):
103
+ stripped = _strip_thinking_blocks(content)
104
+ out["content"] = _collapse_text_content(stripped) if stripped else ""
105
+ return out
106
+
107
+
108
+ def _normalize_message_with_reasoning(msg: Dict[str, Any]) -> Dict[str, Any]:
109
+ out = dict(msg)
110
+ if out.get("role") != "assistant":
111
+ for key in REASONING_KEYS:
112
+ out.pop(key, None)
113
+ return out
114
+
115
+ text = extract_reasoning_text(out)
116
+ if text:
117
+ out["reasoning"] = text
118
+ out.setdefault("reasoning_content", text)
119
+ return out
120
+
121
+
122
+ def apply_thinking_history_policy(
123
+ messages: List[Dict[str, Any]],
124
+ include: bool,
125
+ ) -> List[Dict[str, Any]]:
126
+ """按 interleaved_history 保留或剥离历史思考链。
127
+
128
+ include=True: 保留 reasoning 字段与 content 中的 thinking 块。
129
+ include=False: 仅保留可见回复,剥离思考链。
130
+ """
131
+ if include:
132
+ return [_normalize_message_with_reasoning(dict(m)) for m in messages]
133
+ return [_strip_reasoning_from_message(dict(m)) for m in messages]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.21
3
+ Version: 2.3.22
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_history.py
61
62
  src/echotools/exec/fncall/protocols/entml_thinking_parse.py
62
63
  src/echotools/exec/fncall/protocols/entml_tools.py
63
64
  src/echotools/exec/fncall/protocols/entml_values.py
@@ -151,6 +152,7 @@ src/tests/test_dispatch.py
151
152
  src/tests/test_dispatch_extended.py
152
153
  src/tests/test_dispatch_race.py
153
154
  src/tests/test_entml_protocol.py
155
+ src/tests/test_entml_thinking_history.py
154
156
  src/tests/test_entml_thinking_parse.py
155
157
  src/tests/test_entml_tools.py
156
158
  src/tests/test_errors.py
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from echotools.exec.fncall.protocols.entml_thinking_history import (
4
+ apply_thinking_history_policy,
5
+ extract_reasoning_text,
6
+ parse_interleaved_history,
7
+ )
8
+
9
+
10
+ def test_parse_interleaved_history_from_thinking_object() -> None:
11
+ body = {"thinking": {"mode": "on", "interleaved_history": True}}
12
+ assert parse_interleaved_history(body) is True
13
+ assert parse_interleaved_history({"thinking": {"interleaved_history": False}}) is False
14
+
15
+
16
+ def test_parse_interleaved_history_top_level() -> None:
17
+ assert parse_interleaved_history({"interleaved_history": True}) is True
18
+ assert parse_interleaved_history({}, extra={"interleaved_history": True}) is True
19
+
20
+
21
+ def test_strip_reasoning_fields_when_disabled() -> None:
22
+ msgs = [
23
+ {
24
+ "role": "assistant",
25
+ "content": "answer",
26
+ "reasoning": "thought",
27
+ "reasoning_content": "thought",
28
+ }
29
+ ]
30
+ out = apply_thinking_history_policy(msgs, False)
31
+ assert out[0]["content"] == "answer"
32
+ assert "reasoning" not in out[0]
33
+
34
+
35
+ def test_strip_entropy_thinking_blocks_when_disabled() -> None:
36
+ msgs = [
37
+ {
38
+ "role": "assistant",
39
+ "content": [
40
+ {"type": "thinking", "thinking": "step 1"},
41
+ {"type": "text", "text": "hello"},
42
+ ],
43
+ }
44
+ ]
45
+ out = apply_thinking_history_policy(msgs, False)
46
+ assert out[0]["content"] == "hello"
47
+ assert "reasoning" not in out[0]
48
+
49
+
50
+ def test_preserve_entropy_thinking_blocks_when_enabled() -> None:
51
+ msgs = [
52
+ {
53
+ "role": "assistant",
54
+ "content": [
55
+ {"type": "thinking", "thinking": "step 1"},
56
+ {"type": "text", "text": "hello"},
57
+ ],
58
+ }
59
+ ]
60
+ out = apply_thinking_history_policy(msgs, True)
61
+ assert out[0]["reasoning"] == "step 1"
62
+ assert out[0]["reasoning_content"] == "step 1"
63
+ assert out[0]["content"][0]["type"] == "thinking"
64
+
65
+
66
+ def test_extract_reasoning_text_from_blocks() -> None:
67
+ msg = {
68
+ "role": "assistant",
69
+ "content": [{"type": "thinking", "thinking": "a"}, {"type": "text", "text": "b"}],
70
+ }
71
+ assert extract_reasoning_text(msg) == "a"
File without changes
File without changes
File without changes