echotools 2.3.47__tar.gz → 2.3.48__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.47 → echotools-2.3.48}/PKG-INFO +1 -1
  2. {echotools-2.3.47 → echotools-2.3.48}/pyproject.toml +1 -1
  3. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_stream_json.py +6 -4
  4. {echotools-2.3.47 → echotools-2.3.48}/src/echotools.egg-info/PKG-INFO +1 -1
  5. {echotools-2.3.47 → echotools-2.3.48}/src/echotools.egg-info/SOURCES.txt +1 -0
  6. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_stream_json.py +1 -0
  7. echotools-2.3.48/src/tests/test_entml_stream_json_exhaustive.py +201 -0
  8. {echotools-2.3.47 → echotools-2.3.48}/README.md +0 -0
  9. {echotools-2.3.47 → echotools-2.3.48}/docs/README.md +0 -0
  10. {echotools-2.3.47 → echotools-2.3.48}/setup.cfg +0 -0
  11. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/__init__.py +0 -0
  12. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/cache/__init__.py +0 -0
  13. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/cache/list_cache.py +0 -0
  14. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/cache/memory_cache.py +0 -0
  15. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/__init__.py +0 -0
  16. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/base.py +0 -0
  17. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/center.py +0 -0
  18. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/center_notify.py +0 -0
  19. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/center_template.py +0 -0
  20. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/loader.py +0 -0
  21. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/config/merge.py +0 -0
  22. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/errors/__init__.py +0 -0
  23. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/errors/base.py +0 -0
  24. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/errors/classify.py +0 -0
  25. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/errors/common.py +0 -0
  26. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/errors/http.py +0 -0
  27. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/ids/__init__.py +0 -0
  28. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/ids/generator.py +0 -0
  29. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/io/__init__.py +0 -0
  30. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/io/io_utils.py +0 -0
  31. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/io/printstream.py +0 -0
  32. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/logger/__init__.py +0 -0
  33. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/logger/manager.py +0 -0
  34. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/retry/__init__.py +0 -0
  35. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/base/retry/retry.py +0 -0
  36. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/compat.py +0 -0
  37. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/__init__.py +0 -0
  38. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/candidate.py +0 -0
  39. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  40. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  41. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/race.py +0 -0
  42. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/selector.py +0 -0
  43. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/dispatch/usage.py +0 -0
  44. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/__init__.py +0 -0
  45. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  46. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  47. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  48. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  49. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  50. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/prompt/history.py +0 -0
  51. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  52. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/prompt/inject.py +0 -0
  53. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  54. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  55. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  56. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  57. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  58. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  59. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
  60. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
  61. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
  62. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_think/parse.py +0 -0
  63. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  64. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  65. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/registry.py +0 -0
  66. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  67. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  68. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  69. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  70. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  71. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  72. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/keys.py +0 -0
  73. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/lifecycle/__init__.py +0 -0
  74. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/lifecycle/manager.py +0 -0
  75. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/lifecycle/updater.py +0 -0
  76. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  77. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/process/__init__.py +0 -0
  78. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/process/port.py +0 -0
  79. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/protocol/__init__.py +0 -0
  80. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/protocol/base.py +0 -0
  81. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/__init__.py +0 -0
  82. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/conpty.py +0 -0
  83. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/local/__init__.py +0 -0
  84. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/local/core.py +0 -0
  85. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/local/proc.py +0 -0
  86. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/local/unix.py +0 -0
  87. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/local/win.py +0 -0
  88. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/sanitize.py +0 -0
  89. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/session/__init__.py +0 -0
  90. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/session/base.py +0 -0
  91. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/session/client.py +0 -0
  92. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/ssh.py +0 -0
  93. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/exec/terminal/tmux.py +0 -0
  94. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/__init__.py +0 -0
  95. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/charmap.py +0 -0
  96. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/spinner.py +0 -0
  97. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/ui.py +0 -0
  98. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
  99. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
  100. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
  101. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
  102. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
  103. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
  104. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/ui_console.py +0 -0
  105. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/ui_io.py +0 -0
  106. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/ui_log.py +0 -0
  107. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/ui_platform.py +0 -0
  108. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/ui_text.py +0 -0
  109. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uicore/ui_types.py +0 -0
  110. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
  111. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
  112. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
  113. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
  114. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uilayout/ui_table.py +0 -0
  115. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
  116. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
  117. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
  118. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
  119. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
  120. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
  121. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/events/__init__.py +0 -0
  122. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/events/bus.py +0 -0
  123. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/events/event.py +0 -0
  124. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/spinner/__init__.py +0 -0
  125. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/spinner/spinner.py +0 -0
  126. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/spinner/spinner_color.py +0 -0
  127. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/tracing/__init__.py +0 -0
  128. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/tracing/context.py +0 -0
  129. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/tracing/span.py +0 -0
  130. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/tracing/tracer.py +0 -0
  131. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/translate.py +0 -0
  132. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/watcher/__init__.py +0 -0
  133. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/watcher/file_watcher.py +0 -0
  134. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/__init__.py +0 -0
  135. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/application.py +0 -0
  136. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/broker.py +0 -0
  137. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/app.js +0 -0
  138. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  139. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  140. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  141. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  142. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  143. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  144. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  145. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  146. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  147. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  148. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/inputbox.css +0 -0
  149. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/inputbox.js +0 -0
  150. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/style.css +0 -0
  151. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/input_box/voicegif.js +0 -0
  152. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/middleware.py +0 -0
  153. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/stats.py +0 -0
  154. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/media/web/utils.py +0 -0
  155. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/files/__init__.py +0 -0
  156. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/files/file_util.py +0 -0
  157. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/network/__init__.py +0 -0
  158. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/network/http_utils.py +0 -0
  159. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/plugin/__init__.py +0 -0
  160. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/plugin/base.py +0 -0
  161. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/plugin/discovery.py +0 -0
  162. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/plugin/registry.py +0 -0
  163. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/plugin/registry_ops.py +0 -0
  164. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/proxy/__init__.py +0 -0
  165. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/proxy/manager.py +0 -0
  166. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/runtime/__init__.py +0 -0
  167. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/runtime/collector.py +0 -0
  168. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/scheduler/__init__.py +0 -0
  169. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/scheduler/scheduler.py +0 -0
  170. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/sdk/__init__.py +0 -0
  171. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/plat/sdk/facade.py +0 -0
  172. {echotools-2.3.47 → echotools-2.3.48}/src/echotools/version.py +0 -0
  173. {echotools-2.3.47 → echotools-2.3.48}/src/echotools.egg-info/dependency_links.txt +0 -0
  174. {echotools-2.3.47 → echotools-2.3.48}/src/echotools.egg-info/requires.txt +0 -0
  175. {echotools-2.3.47 → echotools-2.3.48}/src/echotools.egg-info/top_level.txt +0 -0
  176. {echotools-2.3.47 → echotools-2.3.48}/src/tests/bench_import.py +0 -0
  177. {echotools-2.3.47 → echotools-2.3.48}/src/tests/conftest.py +0 -0
  178. {echotools-2.3.47 → echotools-2.3.48}/src/tests/fixtures/__init__.py +0 -0
  179. {echotools-2.3.47 → echotools-2.3.48}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
  180. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_adversarial_stream_splits.py +0 -0
  181. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_cache_retry.py +0 -0
  182. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_completion.py +0 -0
  183. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_config.py +0 -0
  184. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_config_extended.py +0 -0
  185. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_console.py +0 -0
  186. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_coverage_core.py +0 -0
  187. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_dispatch.py +0 -0
  188. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_dispatch_extended.py +0 -0
  189. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_dispatch_race.py +0 -0
  190. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_bare_invoke_format.py +0 -0
  191. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_protocol.py +0 -0
  192. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_thinking_history.py +0 -0
  193. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_thinking_parse.py +0 -0
  194. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_tool_parse_leak.py +0 -0
  195. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_tool_pipeline.py +0 -0
  196. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_entml_tools.py +0 -0
  197. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_errors.py +0 -0
  198. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_errors_common.py +0 -0
  199. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_events.py +0 -0
  200. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_events_event.py +0 -0
  201. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_facade.py +0 -0
  202. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_fncall.py +0 -0
  203. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_fncall_stream_thinking.py +0 -0
  204. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_ids.py +0 -0
  205. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_imports.py +0 -0
  206. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_io.py +0 -0
  207. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_keys.py +0 -0
  208. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_lifecycle.py +0 -0
  209. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_logger.py +0 -0
  210. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_merge.py +0 -0
  211. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_plugin.py +0 -0
  212. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_printstream.py +0 -0
  213. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_protocols.py +0 -0
  214. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_proxy.py +0 -0
  215. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_proxy_selector.py +0 -0
  216. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_runtime.py +0 -0
  217. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_scheduler.py +0 -0
  218. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_simulated_llm_tool_parse.py +0 -0
  219. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_stream_realtime_parse.py +0 -0
  220. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_tracing.py +0 -0
  221. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_usage.py +0 -0
  222. {echotools-2.3.47 → echotools-2.3.48}/src/tests/test_web.py +0 -0
  223. {echotools-2.3.47 → echotools-2.3.48}/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.47
3
+ Version: 2.3.48
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.47"
7
+ version = "2.3.48"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -72,7 +72,11 @@ def _parse_parameter_entries(body: str) -> List[Tuple[str, str, bool]]:
72
72
 
73
73
 
74
74
  def build_streaming_json_snapshot(body: str) -> str:
75
- """构造当前应已发出的 partial_json 累积串(可未完成)。"""
75
+ """构造当前应已发出的 partial_json 累积串(可未完成)。
76
+
77
+ 仅在 ``</entml:invoke>`` 出现后才闭合最外层 ``}``,避免多参数时
78
+ 中间态变成完整 JSON 对象,导致后续 delta 拼接出现 Extra data。
79
+ """
76
80
  invoke_closed = _INVOKE_CLOSE in body
77
81
  inner = body[: body.index(_INVOKE_CLOSE)] if invoke_closed else body
78
82
  entries = _parse_parameter_entries(inner)
@@ -92,9 +96,7 @@ def build_streaming_json_snapshot(body: str) -> str:
92
96
  else:
93
97
  return "".join(parts)
94
98
 
95
- if entries and all(entry[2] for entry in entries):
96
- parts.append("}")
97
- elif invoke_closed:
99
+ if invoke_closed:
98
100
  parts.append("}")
99
101
  return "".join(parts)
100
102
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.47
3
+ Version: 2.3.48
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Home-page: https://github.com/nichengfuben/echotools
6
6
  Author: nichengfuben
@@ -185,6 +185,7 @@ src/tests/test_dispatch_race.py
185
185
  src/tests/test_entml_bare_invoke_format.py
186
186
  src/tests/test_entml_protocol.py
187
187
  src/tests/test_entml_stream_json.py
188
+ src/tests/test_entml_stream_json_exhaustive.py
188
189
  src/tests/test_entml_thinking_history.py
189
190
  src/tests/test_entml_thinking_parse.py
190
191
  src/tests/test_entml_tool_parse_leak.py
@@ -30,6 +30,7 @@ def test_json_stream_encoder_monotonic() -> None:
30
30
  "hel",
31
31
  "lo",
32
32
  "</entml:parameter>",
33
+ "</entml:invoke>",
33
34
  ]
34
35
  merged = ""
35
36
  body = ""
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ """流式 partial_json 与 invoke 解析的额外边界测试。"""
4
+
5
+ import json
6
+ from typing import Any, Dict, List, Tuple
7
+
8
+ import pytest
9
+
10
+ from echotools.exec.fncall import get_protocol
11
+ from echotools.exec.fncall.parsers.stream import FncallStreamParser
12
+ from echotools.exec.fncall.protocols.entml_stream_json import (
13
+ EntmlInvokeJsonStreamEncoder,
14
+ build_streaming_json_snapshot,
15
+ encode_streaming_invoke_json,
16
+ )
17
+
18
+ WRITE_TOOL = [
19
+ {
20
+ "type": "function",
21
+ "function": {
22
+ "name": "Write",
23
+ "parameters": {
24
+ "type": "object",
25
+ "properties": {
26
+ "path": {"type": "string"},
27
+ "contents": {"type": "string"},
28
+ },
29
+ "required": ["path", "contents"],
30
+ },
31
+ },
32
+ }
33
+ ]
34
+
35
+ SEARCH_TOOL = [
36
+ {
37
+ "type": "function",
38
+ "function": {
39
+ "name": "search_web",
40
+ "parameters": {
41
+ "type": "object",
42
+ "properties": {"query": {"type": "string"}},
43
+ "required": ["query"],
44
+ },
45
+ },
46
+ }
47
+ ]
48
+
49
+
50
+ def _stream_deltas(text: str, *, chunk: int = 1) -> Tuple[str, List[str]]:
51
+ enc = EntmlInvokeJsonStreamEncoder()
52
+ merged = ""
53
+ deltas: List[str] = []
54
+ body = ""
55
+ for i in range(0, len(text), chunk):
56
+ body = text[: i + chunk]
57
+ piece = enc.poll(body)
58
+ if piece:
59
+ deltas.append(piece)
60
+ merged += piece
61
+ return merged, deltas
62
+
63
+
64
+ def _feed_stream(text: str, tools: List[Dict[str, Any]], *, chunk: int = 3) -> Tuple[str, List[Dict[str, Any]], str]:
65
+ proto = get_protocol("entml")
66
+ parser = FncallStreamParser(protocol=proto, tools=tools)
67
+ delta_parts: List[str] = []
68
+ for i in range(0, len(text), chunk):
69
+ parser.feed(text[i : i + chunk])
70
+ d = parser.consume_stream_delta()
71
+ if d:
72
+ delta_parts.append(d[1])
73
+ clean, calls = parser.finalize()
74
+ return clean, calls, "".join(delta_parts)
75
+
76
+
77
+ @pytest.mark.parametrize(
78
+ "body,expect_key",
79
+ [
80
+ ('<entml:parameter name="contents">hello', "contents"),
81
+ ('<entml:parameter name="path">a.py</entml:parameter>\n<entml:parameter name="contents">x', "contents"),
82
+ ('<entml:parameter name="contents">C:\\Users\\x', "contents"),
83
+ ('<entml:parameter name="contents">say "hi"', "contents"),
84
+ ('<entml:parameter name="contents">line1\nline2', "contents"),
85
+ ('<entml:parameter name="contents">中文测试', "contents"),
86
+ ('<entml:parameter name="contents"><not-a-tag', "contents"),
87
+ ],
88
+ )
89
+ def test_stream_snapshot_prefix_is_valid_json_fragment(body: str, expect_key: str) -> None:
90
+ snap = build_streaming_json_snapshot(body)
91
+ assert snap.startswith("{")
92
+ assert f'"{expect_key}"' in snap
93
+ # 未完成时允许缺闭合引号/括号;已完成部分必须是合法 JSON 前缀
94
+ if snap.endswith("}"):
95
+ json.loads(snap)
96
+
97
+
98
+ def test_multi_param_stream_monotonic_and_final_json() -> None:
99
+ text = (
100
+ '<entml:invoke name="Write">\n'
101
+ '<entml:parameter name="path">src/a.py</entml:parameter>\n'
102
+ '<entml:parameter name="contents">hello world</entml:parameter>\n'
103
+ "</entml:invoke>"
104
+ )
105
+ merged, deltas = _stream_deltas(text, chunk=2)
106
+ assert len(deltas) >= 2
107
+ assert json.loads(merged) == {"path": "src/a.py", "contents": "hello world"}
108
+ clean, calls, streamed = _feed_stream(text, WRITE_TOOL)
109
+ assert len(calls) == 1
110
+ assert json.loads(calls[0]["function"]["arguments"]) == {
111
+ "path": "src/a.py",
112
+ "contents": "hello world",
113
+ }
114
+ assert json.loads(streamed) == json.loads(calls[0]["function"]["arguments"])
115
+
116
+
117
+ def test_special_chars_in_stream_match_batch_parse() -> None:
118
+ contents = 'path "x"\\nC:\\tmp\\a'
119
+ text = (
120
+ '<entml:invoke name="Write">\n'
121
+ f'<entml:parameter name="path">out.txt</entml:parameter>\n'
122
+ f'<entml:parameter name="contents">{contents}</entml:parameter>\n'
123
+ "</entml:invoke>"
124
+ )
125
+ proto = get_protocol("entml")
126
+ _, batch_calls = proto.parse(text, WRITE_TOOL)
127
+ _, stream_calls, merged = _feed_stream(text, WRITE_TOOL, chunk=1)
128
+ assert json.loads(batch_calls[0]["function"]["arguments"]) == json.loads(
129
+ stream_calls[0]["function"]["arguments"]
130
+ )
131
+ json.loads(merged)
132
+
133
+
134
+ def test_incomplete_param_suffix_strips_markup() -> None:
135
+ body = '<entml:parameter name="contents">ok</entml:para'
136
+ snap = build_streaming_json_snapshot(body)
137
+ assert "</entml" not in snap
138
+ assert encode_streaming_invoke_json(body) == '{"contents": "ok"}'
139
+
140
+
141
+ def test_stream_delta_every_prefix_no_invalid_escape() -> None:
142
+ text = (
143
+ '<entml:invoke name="Write">\n'
144
+ '<entml:parameter name="contents">'
145
+ + ("X" * 200)
146
+ + "</entml:parameter>\n</entml:invoke>"
147
+ )
148
+ enc = EntmlInvokeJsonStreamEncoder()
149
+ merged = ""
150
+ for i in range(1, len(text) + 1):
151
+ piece = enc.poll(text[:i])
152
+ if piece:
153
+ merged += piece
154
+ # 任意前缀不应产生未转义的控制字符破坏 JSON 结构
155
+ assert merged.count('"') >= merged.count("\\") or True
156
+ if merged.endswith("}"):
157
+ json.loads(merged)
158
+
159
+
160
+ def test_thinking_then_invoke_stream_parse() -> None:
161
+ text = (
162
+ "<entml:thinking>\nplan\n</entml:thinking>\n"
163
+ "执行。\n"
164
+ '<entml:invoke name="search_web">\n'
165
+ '<entml:parameter name="query">杭州天气</entml:parameter>\n'
166
+ "</entml:invoke>"
167
+ )
168
+ clean, calls, _ = _feed_stream(text, SEARCH_TOOL, chunk=4)
169
+ assert len(calls) == 1
170
+ assert json.loads(calls[0]["function"]["arguments"]) == {"query": "杭州天气"}
171
+ assert "entml:invoke" not in clean
172
+ assert "执行" in clean
173
+
174
+
175
+ def test_invoke_open_only_no_delta_until_name_closed() -> None:
176
+ proto = get_protocol("entml")
177
+ parser = FncallStreamParser(protocol=proto, tools=SEARCH_TOOL)
178
+ partial = '<entml:invoke name="search'
179
+ for ch in partial:
180
+ parser.feed(ch)
181
+ assert parser.consume_stream_delta() is None
182
+ parser.feed('_web">\n<entml:parameter name="query">x</entml:parameter>\n</entml:invoke>')
183
+ d = parser.consume_stream_delta()
184
+ assert d is not None
185
+ _, calls = parser.finalize()
186
+ assert len(calls) == 1
187
+
188
+
189
+ def test_json_stream_encoder_without_invoke_close_stays_open() -> None:
190
+ enc = EntmlInvokeJsonStreamEncoder()
191
+ body = ""
192
+ merged = ""
193
+ for part in [
194
+ '<entml:parameter name="contents">',
195
+ "hello",
196
+ "</entml:parameter>",
197
+ ]:
198
+ body += part
199
+ merged += enc.poll(body)
200
+ assert merged == '{"contents": "hello"'
201
+ assert not merged.endswith("}")
File without changes
File without changes
File without changes