echotools 2.3.81__tar.gz → 2.3.83__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.
- {echotools-2.3.81 → echotools-2.3.83}/PKG-INFO +15 -1
- {echotools-2.3.81 → echotools-2.3.83}/docs/README.md +14 -0
- {echotools-2.3.81 → echotools-2.3.83}/pyproject.toml +1 -1
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/parsers/stream.py +12 -7
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_invoke.py +17 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_patterns.py +87 -1
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_stream_json.py +23 -23
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_think/parse.py +21 -2
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_tool_blocks.py +37 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools.egg-info/PKG-INFO +15 -1
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_protocol.py +49 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fncall_stream_thinking.py +53 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_stream_partial_json_parity.py +105 -0
- {echotools-2.3.81 → echotools-2.3.83}/README.md +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/setup.cfg +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/compat.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/history_markup.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/ui_console.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/ui_io.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/ui_log.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/ui_platform.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/ui_text.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uicore/ui_types.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uilayout/ui_table.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools/version.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools.egg-info/SOURCES.txt +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/_check_parity.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/bench_import.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/conftest.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/fixtures/__init__.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_adversarial_stream_splits.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_completion.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_config.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_console.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_agent_output_formats.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_bare_invoke_format.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_batch_stream_comprehensive.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_parser_comprehensive.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_stream_json.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_tool_parse_leak.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_tool_pipeline.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_errors.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_events.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_facade.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fake_history_markup_batch.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fake_history_markup_stream.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fault_thinking_close_batch.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fault_thinking_close_stream.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_history_markup_filter.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_ids.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_imports.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_io.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_keys.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_logger.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_merge.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_model_branch_matrix.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_simulated_llm_tool_parse.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_stream_realtime_parse.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_usage.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/src/tests/test_web.py +0 -0
- {echotools-2.3.81 → echotools-2.3.83}/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.
|
|
3
|
+
Version: 2.3.83
|
|
4
4
|
Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
|
|
5
5
|
Home-page: https://github.com/nichengfuben/echotools
|
|
6
6
|
Author: nichengfuben
|
|
@@ -82,6 +82,20 @@ pip install echotools[all]
|
|
|
82
82
|
| `all` | 全部可选依赖 |
|
|
83
83
|
| `dev` | pytest, ruff, mypy |
|
|
84
84
|
|
|
85
|
+
### PyPI 重装(`uninstall-no-record-file`)
|
|
86
|
+
|
|
87
|
+
本地 `pip install -e .` 或中断的 wheel 安装可能留下无 `RECORD` 的元数据,导致
|
|
88
|
+
`pip install --upgrade echotools` 报 `Cannot uninstall echotools None`。
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
# 在仓库根目录
|
|
92
|
+
powershell -File scripts/reinstall_from_pypi.ps1 -Version 2.3.81
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
或手动:删除 `src/echotools.egg-info` 与 site-packages 里仅有 `direct_url.json` 的
|
|
96
|
+
`echotools-*.dist-info`,再执行
|
|
97
|
+
`pip install --force-reinstall --no-cache-dir echotools==2.3.81`。
|
|
98
|
+
|
|
85
99
|
## 快速开始
|
|
86
100
|
|
|
87
101
|
```python
|
|
@@ -26,6 +26,20 @@ pip install echotools[all]
|
|
|
26
26
|
| `all` | 全部可选依赖 |
|
|
27
27
|
| `dev` | pytest, ruff, mypy |
|
|
28
28
|
|
|
29
|
+
### PyPI 重装(`uninstall-no-record-file`)
|
|
30
|
+
|
|
31
|
+
本地 `pip install -e .` 或中断的 wheel 安装可能留下无 `RECORD` 的元数据,导致
|
|
32
|
+
`pip install --upgrade echotools` 报 `Cannot uninstall echotools None`。
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
# 在仓库根目录
|
|
36
|
+
powershell -File scripts/reinstall_from_pypi.ps1 -Version 2.3.81
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
或手动:删除 `src/echotools.egg-info` 与 site-packages 里仅有 `direct_url.json` 的
|
|
40
|
+
`echotools-*.dist-info`,再执行
|
|
41
|
+
`pip install --force-reinstall --no-cache-dir echotools==2.3.81`。
|
|
42
|
+
|
|
29
43
|
## 快速开始
|
|
30
44
|
|
|
31
45
|
```python
|
|
@@ -439,7 +439,7 @@ class FncallStreamParser:
|
|
|
439
439
|
else:
|
|
440
440
|
tail = ""
|
|
441
441
|
else:
|
|
442
|
-
tail =
|
|
442
|
+
tail = ""
|
|
443
443
|
if tail:
|
|
444
444
|
name = call["function"]["name"]
|
|
445
445
|
if multi:
|
|
@@ -635,10 +635,16 @@ class FncallStreamParser:
|
|
|
635
635
|
stream_safe_visible_prefix,
|
|
636
636
|
)
|
|
637
637
|
|
|
638
|
+
from echotools.exec.fncall.protocols.entml_think.parse import (
|
|
639
|
+
split_entml_thinking,
|
|
640
|
+
)
|
|
641
|
+
|
|
638
642
|
buf = stream_safe_visible_prefix(
|
|
639
643
|
self._raw_buf,
|
|
640
644
|
thinking_enabled=self._thinking_enabled,
|
|
641
645
|
)
|
|
646
|
+
if self._thinking_enabled and buf:
|
|
647
|
+
buf, _ = split_entml_thinking(buf, thinking_enabled=True)
|
|
642
648
|
return clean_stream_partial_visible(
|
|
643
649
|
buf,
|
|
644
650
|
has_calls=self.has_calls,
|
|
@@ -690,11 +696,6 @@ class FncallStreamParser:
|
|
|
690
696
|
segment,
|
|
691
697
|
thinking_enabled=self._thinking_enabled,
|
|
692
698
|
)
|
|
693
|
-
buf = clean_stream_partial_visible(
|
|
694
|
-
buf,
|
|
695
|
-
has_calls=self.has_calls,
|
|
696
|
-
thinking_enabled=self._thinking_enabled,
|
|
697
|
-
)
|
|
698
699
|
if not buf:
|
|
699
700
|
return ""
|
|
700
701
|
cleaned, _ = strip_fake_history_markup_for_display(buf)
|
|
@@ -703,7 +704,11 @@ class FncallStreamParser:
|
|
|
703
704
|
thinking_enabled=self._thinking_enabled,
|
|
704
705
|
preserve_visible_whitespace=True,
|
|
705
706
|
)
|
|
706
|
-
return
|
|
707
|
+
return clean_stream_partial_visible(
|
|
708
|
+
visible,
|
|
709
|
+
has_calls=self.has_calls,
|
|
710
|
+
thinking_enabled=self._thinking_enabled,
|
|
711
|
+
)
|
|
707
712
|
|
|
708
713
|
def finalize(self) -> Tuple[str, List[Dict[str, Any]]]:
|
|
709
714
|
"""结束流式解析,返回 (清理后文本, tool_calls 列表)。幂等。"""
|
|
@@ -15,6 +15,8 @@ from .entml_patterns import (
|
|
|
15
15
|
extract_parameter_type_attr,
|
|
16
16
|
normalize_entml_name,
|
|
17
17
|
parse_sub_tags,
|
|
18
|
+
split_mangled_json_param_tail,
|
|
19
|
+
synthetic_close_invoke_body,
|
|
18
20
|
)
|
|
19
21
|
from .entml_values import coerce_entml_arguments, coerce_entml_parameter_value
|
|
20
22
|
|
|
@@ -58,6 +60,9 @@ def parse_invoke_args(
|
|
|
58
60
|
schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]],
|
|
59
61
|
) -> Dict[str, Any]:
|
|
60
62
|
func_props = (schema_index or {}).get(name) or {}
|
|
63
|
+
if "</entml:invoke>" in body:
|
|
64
|
+
body = body[: body.index("</entml:invoke>")]
|
|
65
|
+
body = synthetic_close_invoke_body(body)
|
|
61
66
|
|
|
62
67
|
params_m = PARAMETERS_RE.search(body)
|
|
63
68
|
if params_m:
|
|
@@ -81,6 +86,7 @@ def parse_invoke_args(
|
|
|
81
86
|
continue
|
|
82
87
|
pname = normalize_entml_name(pname)
|
|
83
88
|
pval = (param_m.group(2) or "").strip()
|
|
89
|
+
pval, extra = split_mangled_json_param_tail(pval)
|
|
84
90
|
type_hint = extract_parameter_type_attr(attrs)
|
|
85
91
|
pschema = func_props.get(pname) or {}
|
|
86
92
|
args[pname] = coerce_entml_parameter_value(
|
|
@@ -88,6 +94,17 @@ def parse_invoke_args(
|
|
|
88
94
|
pschema or None,
|
|
89
95
|
type_hint=type_hint,
|
|
90
96
|
)
|
|
97
|
+
for extra_key, extra_val in extra.items():
|
|
98
|
+
if extra_key in args:
|
|
99
|
+
continue
|
|
100
|
+
extra_schema = func_props.get(extra_key) or {}
|
|
101
|
+
if isinstance(extra_val, (int, float, bool)):
|
|
102
|
+
args[extra_key] = extra_val
|
|
103
|
+
else:
|
|
104
|
+
args[extra_key] = coerce_entml_parameter_value(
|
|
105
|
+
str(extra_val),
|
|
106
|
+
extra_schema or None,
|
|
107
|
+
)
|
|
91
108
|
|
|
92
109
|
_parse_bare_invoke_children(body, args, func_props)
|
|
93
110
|
_parse_direct_child_tags(body, args, func_props)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import Any, Dict, Optional
|
|
4
|
+
from typing import Any, Dict, Optional, Tuple
|
|
5
5
|
|
|
6
6
|
from .entml_values import coerce_entml_parameter_value
|
|
7
7
|
|
|
@@ -195,6 +195,92 @@ def _parameter_close_follower_ok(after: str, *, allow_end: bool) -> bool:
|
|
|
195
195
|
return False
|
|
196
196
|
|
|
197
197
|
|
|
198
|
+
_PARAM_OPEN_TAG_RE = re.compile(rf"{PARAM_OPEN_PATTERN}([^>]*)>", re.IGNORECASE)
|
|
199
|
+
_MANGLED_PARAM_JSON_TAIL_RE = re.compile(
|
|
200
|
+
r'"\s*,\s*"description"\s*:\s*"((?:[^"\\]|\\.)*)"\s*,\s*"timeout"\s*:\s*(\d+)\s*\}\}?\s*$',
|
|
201
|
+
re.DOTALL,
|
|
202
|
+
)
|
|
203
|
+
_MANGLED_PARAM_JSON_TAIL_START_RE = re.compile(
|
|
204
|
+
r'"\s*,\s*"description"\s*:\s*"',
|
|
205
|
+
re.DOTALL,
|
|
206
|
+
)
|
|
207
|
+
_MANGLED_PARAM_JSON_TAIL_EARLY_RE = re.compile(
|
|
208
|
+
r'"\s*,\s*"(?:description|timeout)\b',
|
|
209
|
+
re.DOTALL,
|
|
210
|
+
)
|
|
211
|
+
_MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE = re.compile(
|
|
212
|
+
r'"\s*,\s*"(?:d|t)',
|
|
213
|
+
re.DOTALL,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
_MANGLED_PARAM_JSON_COMMA_QUOTE_END_RE = re.compile(
|
|
218
|
+
r'"\s*,\s*"$',
|
|
219
|
+
re.DOTALL,
|
|
220
|
+
)
|
|
221
|
+
_MANGLED_PARAM_JSON_COMMA_AFTER_QUOTE_RE = re.compile(
|
|
222
|
+
r'"\s*,\s*$',
|
|
223
|
+
re.DOTALL,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def mangled_json_param_tail_in_progress(value: str) -> bool:
|
|
228
|
+
"""parameter 值中出现误写入 JSON 尾缀但尚未收齐时不应继续增长 partial_json。"""
|
|
229
|
+
return bool(value and _MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE.search(value))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def split_mangled_json_param_tail(value: str) -> Tuple[str, Dict[str, Any]]:
|
|
233
|
+
"""模型把 ``", "description": ..., "timeout": ...}}`` 误写入 parameter 值时的拆分。"""
|
|
234
|
+
if not value:
|
|
235
|
+
return value, {}
|
|
236
|
+
match = _MANGLED_PARAM_JSON_TAIL_RE.search(value)
|
|
237
|
+
if match:
|
|
238
|
+
command = value[: match.start() + 1]
|
|
239
|
+
extra: Dict[str, Any] = {
|
|
240
|
+
"description": match.group(1),
|
|
241
|
+
"timeout": int(match.group(2)),
|
|
242
|
+
}
|
|
243
|
+
return command, extra
|
|
244
|
+
partial = _MANGLED_PARAM_JSON_TAIL_START_RE.search(value)
|
|
245
|
+
if partial:
|
|
246
|
+
return value[: partial.start() + 1], {}
|
|
247
|
+
early = _MANGLED_PARAM_JSON_TAIL_EARLY_RE.search(value)
|
|
248
|
+
if early:
|
|
249
|
+
return value[: early.start() + 1], {}
|
|
250
|
+
if _MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE.search(value):
|
|
251
|
+
matches = list(re.finditer(r'"\s*,\s*"', value))
|
|
252
|
+
if matches:
|
|
253
|
+
return value[: matches[-1].start() + 1], {}
|
|
254
|
+
comma_quote_end = _MANGLED_PARAM_JSON_COMMA_QUOTE_END_RE.search(value)
|
|
255
|
+
if comma_quote_end:
|
|
256
|
+
return value[: comma_quote_end.start() + 1], {}
|
|
257
|
+
comma_after_quote = _MANGLED_PARAM_JSON_COMMA_AFTER_QUOTE_RE.search(value)
|
|
258
|
+
if comma_after_quote:
|
|
259
|
+
return value[: comma_after_quote.start() + 1], {}
|
|
260
|
+
return value, {}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def synthetic_close_invoke_body(inner: str) -> str:
|
|
264
|
+
"""为 force_close / invoke 已闭合但未闭合的 parameter 补齐结构闭合标签。"""
|
|
265
|
+
if not inner:
|
|
266
|
+
return inner
|
|
267
|
+
closed = inner
|
|
268
|
+
if "<entml:parameters>" in closed and "</entml:parameters>" not in closed:
|
|
269
|
+
closed = closed + "</entml:parameters>"
|
|
270
|
+
matches = list(_PARAM_OPEN_TAG_RE.finditer(closed))
|
|
271
|
+
if matches:
|
|
272
|
+
last = matches[-1]
|
|
273
|
+
after = closed[last.end() :]
|
|
274
|
+
open_snip = closed[last.start() : last.end()].lower()
|
|
275
|
+
if open_snip.startswith("<parameter") and not open_snip.startswith("<entml:"):
|
|
276
|
+
need = PARAM_CLOSE_BARE
|
|
277
|
+
else:
|
|
278
|
+
need = PARAM_CLOSE_ENTML
|
|
279
|
+
if need not in after and find_valid_parameter_close(closed, last.end(), allow_end=True) < 0:
|
|
280
|
+
closed = closed + need
|
|
281
|
+
return closed
|
|
282
|
+
|
|
283
|
+
|
|
198
284
|
def find_valid_parameter_close(body: str, search_from: int = 0, *, allow_end: bool = True) -> int:
|
|
199
285
|
"""返回 parameter 闭合标签在 ``body`` 中的起始下标;忽略参数值内的假闭合。"""
|
|
200
286
|
pos = search_from
|
{echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_stream_json.py
RENAMED
|
@@ -18,8 +18,11 @@ from .entml_patterns import (
|
|
|
18
18
|
find_valid_parameter_close,
|
|
19
19
|
normalize_entml_name,
|
|
20
20
|
parameter_close_at,
|
|
21
|
+
split_mangled_json_param_tail,
|
|
22
|
+
synthetic_close_invoke_body,
|
|
21
23
|
)
|
|
22
24
|
from .entml_values import coerce_entml_parameter_value
|
|
25
|
+
from echotools.exec.fncall.shared.coercion import _resolve_effective_type
|
|
23
26
|
|
|
24
27
|
_PARAM_OPEN_RE = re.compile(rf"{PARAM_OPEN_PATTERN}([^>]*)>", re.IGNORECASE)
|
|
25
28
|
_PARAM_CLOSE = PARAM_CLOSE_ENTML
|
|
@@ -90,8 +93,10 @@ def _effective_param_type(
|
|
|
90
93
|
mapped = _TYPE_HINT_TO_JSON_TYPE.get(type_hint.strip().lower())
|
|
91
94
|
if mapped:
|
|
92
95
|
return mapped
|
|
93
|
-
if pschema
|
|
94
|
-
|
|
96
|
+
if pschema:
|
|
97
|
+
resolved = _resolve_effective_type(pschema)
|
|
98
|
+
if resolved:
|
|
99
|
+
return resolved
|
|
95
100
|
stripped = (value or "").lstrip()
|
|
96
101
|
if stripped.startswith("["):
|
|
97
102
|
return "array"
|
|
@@ -101,24 +106,8 @@ def _effective_param_type(
|
|
|
101
106
|
|
|
102
107
|
|
|
103
108
|
def _synthetic_close_body(inner: str) -> str:
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
inner = inner[: inner.index(_INVOKE_CLOSE)]
|
|
107
|
-
closed = inner
|
|
108
|
-
if _PARAMETERS_OPEN in closed and _PARAMETERS_CLOSE not in closed:
|
|
109
|
-
closed = closed + _PARAMETERS_CLOSE
|
|
110
|
-
matches = list(_PARAM_OPEN_RE.finditer(closed))
|
|
111
|
-
if matches:
|
|
112
|
-
last = matches[-1]
|
|
113
|
-
after = closed[last.end() :]
|
|
114
|
-
open_snip = closed[last.start() : last.end()].lower()
|
|
115
|
-
if open_snip.startswith("<parameter") and not open_snip.startswith("<entml:"):
|
|
116
|
-
need = PARAM_CLOSE_BARE
|
|
117
|
-
else:
|
|
118
|
-
need = _PARAM_CLOSE
|
|
119
|
-
if need not in after and find_valid_parameter_close(closed, last.end(), allow_end=True) < 0:
|
|
120
|
-
closed = closed + need
|
|
121
|
-
return closed
|
|
109
|
+
"""兼容旧名;委托 ``synthetic_close_invoke_body``。"""
|
|
110
|
+
return synthetic_close_invoke_body(inner)
|
|
122
111
|
|
|
123
112
|
|
|
124
113
|
def _final_invoke_arguments_json(
|
|
@@ -133,7 +122,7 @@ def _final_invoke_arguments_json(
|
|
|
133
122
|
|
|
134
123
|
invoke_closed = _INVOKE_CLOSE in body
|
|
135
124
|
inner = body[: body.index(_INVOKE_CLOSE)] if invoke_closed else body
|
|
136
|
-
parse_body =
|
|
125
|
+
parse_body = synthetic_close_invoke_body(inner)
|
|
137
126
|
args = parse_invoke_args(parse_body, tool_name, schema_index)
|
|
138
127
|
return json.dumps(args, ensure_ascii=False)
|
|
139
128
|
|
|
@@ -309,16 +298,24 @@ def _parse_parameter_entries(body: str) -> List[Tuple[str, str, bool, Optional[s
|
|
|
309
298
|
val_start = match.end()
|
|
310
299
|
close = _resolve_parameter_close(body, val_start)
|
|
311
300
|
if close < 0:
|
|
301
|
+
raw = _incomplete_parameter_raw(body, val_start)
|
|
302
|
+
raw, extra = split_mangled_json_param_tail(raw)
|
|
312
303
|
entries.append(
|
|
313
304
|
(
|
|
314
305
|
key,
|
|
315
|
-
|
|
306
|
+
raw,
|
|
316
307
|
False,
|
|
317
308
|
type_hint,
|
|
318
309
|
)
|
|
319
310
|
)
|
|
311
|
+
for extra_key, extra_val in extra.items():
|
|
312
|
+
entries.append((extra_key, str(extra_val), True, None))
|
|
320
313
|
break
|
|
321
|
-
|
|
314
|
+
raw = body[val_start:close].strip()
|
|
315
|
+
raw, extra = split_mangled_json_param_tail(raw)
|
|
316
|
+
entries.append((key, raw, True, type_hint))
|
|
317
|
+
for extra_key, extra_val in extra.items():
|
|
318
|
+
entries.append((extra_key, str(extra_val), True, None))
|
|
322
319
|
i = close + parameter_close_at(body, close)
|
|
323
320
|
return entries
|
|
324
321
|
|
|
@@ -464,6 +461,9 @@ class EntmlInvokeJsonStreamEncoder:
|
|
|
464
461
|
if a != b:
|
|
465
462
|
break
|
|
466
463
|
common += 1
|
|
464
|
+
if common < len(self._emitted):
|
|
465
|
+
self._emitted = snapshot
|
|
466
|
+
return ""
|
|
467
467
|
delta = snapshot[common:]
|
|
468
468
|
self._emitted = snapshot
|
|
469
469
|
return delta
|
{echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_think/parse.py
RENAMED
|
@@ -79,6 +79,10 @@ def stream_safe_visible_prefix(
|
|
|
79
79
|
)
|
|
80
80
|
if open_at >= 0:
|
|
81
81
|
return buf[:open_at]
|
|
82
|
+
if thinking_enabled:
|
|
83
|
+
visible, thinking = split_entml_thinking(buf, thinking_enabled=True)
|
|
84
|
+
if thinking:
|
|
85
|
+
return visible
|
|
82
86
|
return buf
|
|
83
87
|
|
|
84
88
|
|
|
@@ -124,8 +128,23 @@ def clean_stream_partial_visible(
|
|
|
124
128
|
if not text.strip():
|
|
125
129
|
return ""
|
|
126
130
|
if re.search(r"</?entml:", text, re.IGNORECASE):
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
if thinking_enabled:
|
|
132
|
+
# 保留 thinking 标签供 split_entml_thinking 剥离;仅去掉工具相关 entml 残留。
|
|
133
|
+
text = re.sub(
|
|
134
|
+
r"</?entml:(?!thinking\b)[^>]*>",
|
|
135
|
+
"",
|
|
136
|
+
text,
|
|
137
|
+
flags=re.IGNORECASE,
|
|
138
|
+
)
|
|
139
|
+
text = re.sub(
|
|
140
|
+
r"</?entml:(?!thinking\b)\w*$",
|
|
141
|
+
"",
|
|
142
|
+
text,
|
|
143
|
+
flags=re.IGNORECASE,
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
text = re.sub(r"</?entml:[^>]*>", "", text, flags=re.IGNORECASE)
|
|
147
|
+
text = re.sub(r"</?entml:\w*$", "", text, flags=re.IGNORECASE)
|
|
129
148
|
text = text.rstrip()
|
|
130
149
|
return text
|
|
131
150
|
|
{echotools-2.3.81 → echotools-2.3.83}/src/echotools/exec/fncall/protocols/entml_tool_blocks.py
RENAMED
|
@@ -24,6 +24,15 @@ _INNER_TOOL_TAG_RE = re.compile(
|
|
|
24
24
|
re.IGNORECASE,
|
|
25
25
|
)
|
|
26
26
|
_BRACE_TOOL_HEAD_RE = re.compile(r"\{([A-Za-z][A-Za-z0-9_]*)\s*:\s*")
|
|
27
|
+
# ``{Bash>`` 后接 ``<entml:parameter>`` 的混合格式(``{Edit: json}`` 仍走 history brace)
|
|
28
|
+
_MANGLED_BRACE_ENTML_HEAD_RE = re.compile(
|
|
29
|
+
r"^\s*\{([A-Za-z][A-Za-z0-9_]*)\s*>\s*",
|
|
30
|
+
re.MULTILINE,
|
|
31
|
+
)
|
|
32
|
+
_PARAM_MARKER_RE = re.compile(
|
|
33
|
+
r"<\s*(?:entml:)?parameter\b",
|
|
34
|
+
re.IGNORECASE,
|
|
35
|
+
)
|
|
27
36
|
# 工具结果常见行号泄漏(Read 伪块)— 仅用于多块 brace 判定
|
|
28
37
|
_OUTPUT_LINE_RE = re.compile(r"^\s*\d+\s+\S", re.MULTILINE)
|
|
29
38
|
_SCALAR_ARG_KEYS = ("path", "command", "pattern", "query", "file_path")
|
|
@@ -220,6 +229,30 @@ def _parse_brace_calls(
|
|
|
220
229
|
return [(name, args)]
|
|
221
230
|
|
|
222
231
|
|
|
232
|
+
def _parse_mangled_brace_entml_params(
|
|
233
|
+
body: str,
|
|
234
|
+
known: Set[str],
|
|
235
|
+
schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]],
|
|
236
|
+
) -> List[Tuple[str, Dict[str, Any]]]:
|
|
237
|
+
"""``<tool>\\n{Bash>\\n<entml:parameter>...`` 混合格式。"""
|
|
238
|
+
match = _MANGLED_BRACE_ENTML_HEAD_RE.search(body)
|
|
239
|
+
if not match:
|
|
240
|
+
return []
|
|
241
|
+
name = normalize_entml_name(match.group(1))
|
|
242
|
+
if known and name not in known:
|
|
243
|
+
return []
|
|
244
|
+
rest = body[match.end() :].lstrip()
|
|
245
|
+
if not _PARAM_MARKER_RE.search(rest):
|
|
246
|
+
return []
|
|
247
|
+
from .entml_invoke import parse_invoke_args
|
|
248
|
+
|
|
249
|
+
args = parse_invoke_args(rest, name, schema_index)
|
|
250
|
+
if not args:
|
|
251
|
+
return []
|
|
252
|
+
args = coerce_entml_arguments(args, name, schema_index)
|
|
253
|
+
return [(name, args)]
|
|
254
|
+
|
|
255
|
+
|
|
223
256
|
def parse_tool_block_body(
|
|
224
257
|
body: str,
|
|
225
258
|
*,
|
|
@@ -237,6 +270,10 @@ def parse_tool_block_body(
|
|
|
237
270
|
if inner:
|
|
238
271
|
return inner
|
|
239
272
|
|
|
273
|
+
mangled = _parse_mangled_brace_entml_params(text, known, schema_index)
|
|
274
|
+
if mangled:
|
|
275
|
+
return mangled
|
|
276
|
+
|
|
240
277
|
if not allow_brace_format:
|
|
241
278
|
return []
|
|
242
279
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: echotools
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.83
|
|
4
4
|
Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
|
|
5
5
|
Home-page: https://github.com/nichengfuben/echotools
|
|
6
6
|
Author: nichengfuben
|
|
@@ -82,6 +82,20 @@ pip install echotools[all]
|
|
|
82
82
|
| `all` | 全部可选依赖 |
|
|
83
83
|
| `dev` | pytest, ruff, mypy |
|
|
84
84
|
|
|
85
|
+
### PyPI 重装(`uninstall-no-record-file`)
|
|
86
|
+
|
|
87
|
+
本地 `pip install -e .` 或中断的 wheel 安装可能留下无 `RECORD` 的元数据,导致
|
|
88
|
+
`pip install --upgrade echotools` 报 `Cannot uninstall echotools None`。
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
# 在仓库根目录
|
|
92
|
+
powershell -File scripts/reinstall_from_pypi.ps1 -Version 2.3.81
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
或手动:删除 `src/echotools.egg-info` 与 site-packages 里仅有 `direct_url.json` 的
|
|
96
|
+
`echotools-*.dist-info`,再执行
|
|
97
|
+
`pip install --force-reinstall --no-cache-dir echotools==2.3.81`。
|
|
98
|
+
|
|
85
99
|
## 快速开始
|
|
86
100
|
|
|
87
101
|
```python
|
|
@@ -1414,3 +1414,52 @@ def test_entml_tool_block_bash_with_output_tail_batch_and_finalize() -> None:
|
|
|
1414
1414
|
stream_args = json.loads(stream_calls[0]["function"]["arguments"])
|
|
1415
1415
|
assert stream_args == batch_args
|
|
1416
1416
|
|
|
1417
|
+
|
|
1418
|
+
def test_entml_tool_block_mangled_brace_entml_params() -> None:
|
|
1419
|
+
"""``<tool>{Bash>\\n<entml:parameter>...`` 混合格式 batch/stream 均解析 Bash。"""
|
|
1420
|
+
from pathlib import Path
|
|
1421
|
+
|
|
1422
|
+
from echotools.exec.fncall.parsers.stream import FncallStreamParser
|
|
1423
|
+
|
|
1424
|
+
sample_path = Path(
|
|
1425
|
+
r"X:/Project/Public/Qwen/logs/responses/req-1785297403-7c656ac5fe40.txt"
|
|
1426
|
+
)
|
|
1427
|
+
if not sample_path.is_file():
|
|
1428
|
+
pytest.skip("corpus file not available")
|
|
1429
|
+
sample = sample_path.read_text(encoding="utf-8")
|
|
1430
|
+
tools = [
|
|
1431
|
+
{
|
|
1432
|
+
"type": "function",
|
|
1433
|
+
"function": {
|
|
1434
|
+
"name": "Bash",
|
|
1435
|
+
"parameters": {
|
|
1436
|
+
"type": "object",
|
|
1437
|
+
"properties": {
|
|
1438
|
+
"command": {"type": "string"},
|
|
1439
|
+
"description": {"type": "string"},
|
|
1440
|
+
"timeout": {"type": "integer"},
|
|
1441
|
+
},
|
|
1442
|
+
"required": ["command"],
|
|
1443
|
+
},
|
|
1444
|
+
},
|
|
1445
|
+
}
|
|
1446
|
+
]
|
|
1447
|
+
clean, batch_calls = get_protocol("entml").parse(sample, tools)
|
|
1448
|
+
assert len(batch_calls) == 1
|
|
1449
|
+
batch_args = json.loads(batch_calls[0]["function"]["arguments"])
|
|
1450
|
+
assert batch_calls[0]["function"]["name"] == "Bash"
|
|
1451
|
+
assert "alicdn-analytics" in batch_args["command"]
|
|
1452
|
+
assert batch_args.get("timeout") == 30
|
|
1453
|
+
assert "RC4" in clean
|
|
1454
|
+
|
|
1455
|
+
for chunk in (1, 17, 64):
|
|
1456
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=tools)
|
|
1457
|
+
for i in range(0, len(sample), chunk):
|
|
1458
|
+
parser.feed(sample[i : i + chunk])
|
|
1459
|
+
stream_clean, stream_calls = parser.finalize()
|
|
1460
|
+
assert len(stream_calls) == 1, f"chunk={chunk}"
|
|
1461
|
+
stream_args = json.loads(stream_calls[0]["function"]["arguments"])
|
|
1462
|
+
assert stream_args == batch_args, f"chunk={chunk}"
|
|
1463
|
+
assert len(parser.partial_thinking) > 100, f"chunk={chunk}"
|
|
1464
|
+
assert "RC4" in stream_clean or "RC4" in parser.partial_text, f"chunk={chunk}"
|
|
1465
|
+
|
|
@@ -90,6 +90,59 @@ def test_thinking_close_and_invoke_in_one_chunk() -> None:
|
|
|
90
90
|
assert "reason" not in clean or clean.strip() == ""
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
def test_thinking_close_does_not_leak_into_partial_text() -> None:
|
|
94
|
+
"""thinking 闭合后 partial_text 不得重复输出思考正文(Rogator text/reasoning 双发)。"""
|
|
95
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
96
|
+
thinking_body = (
|
|
97
|
+
"The concurrent test shows SSE received 0 bytes even though BidiAppend returned 200.\n"
|
|
98
|
+
"Default reply language: Simplified Chinese\n"
|
|
99
|
+
)
|
|
100
|
+
answer = "并发测试显示SSE线程收到状态200但0字节数据。"
|
|
101
|
+
text = (
|
|
102
|
+
f"<entml:thinking>\n{thinking_body}</entml:thinking>\n\n{answer}\n{INVOKE}"
|
|
103
|
+
)
|
|
104
|
+
dup_count = 0
|
|
105
|
+
for i in range(0, len(text), 17):
|
|
106
|
+
parser.feed(text[i : i + 17])
|
|
107
|
+
pt = parser.partial_text
|
|
108
|
+
pth = parser.partial_thinking
|
|
109
|
+
if pth and thinking_body[:40] in pt:
|
|
110
|
+
dup_count += 1
|
|
111
|
+
clean, calls = parser.finalize()
|
|
112
|
+
assert dup_count == 0, "thinking body leaked into partial_text during stream"
|
|
113
|
+
assert thinking_body.strip() in parser.partial_thinking
|
|
114
|
+
assert "The concurrent test shows" not in clean
|
|
115
|
+
assert answer in clean
|
|
116
|
+
assert len(calls) == 1
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@pytest.mark.parametrize("chunk", [1, 17, 64])
|
|
120
|
+
def test_thinking_then_answer_no_partial_text_dup(chunk: int) -> None:
|
|
121
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
122
|
+
text = (
|
|
123
|
+
"<entml:thinking>\nlong reasoning line one\nline two\n</entml:thinking>\n"
|
|
124
|
+
"visible answer\n"
|
|
125
|
+
f"{INVOKE}"
|
|
126
|
+
)
|
|
127
|
+
for i in range(0, len(text), chunk):
|
|
128
|
+
parser.feed(text[i : i + chunk])
|
|
129
|
+
assert "long reasoning" not in parser.partial_text
|
|
130
|
+
clean, calls = parser.finalize()
|
|
131
|
+
assert "long reasoning" in parser.partial_thinking
|
|
132
|
+
assert "visible answer" in clean
|
|
133
|
+
assert "long reasoning" not in clean
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
137
|
+
chunks = ["<entml:thinking>reason</entml:thinking>", INVOKE]
|
|
138
|
+
for chunk in chunks:
|
|
139
|
+
parser.feed(chunk)
|
|
140
|
+
clean, calls = parser.finalize()
|
|
141
|
+
assert parser.partial_thinking.strip() == "reason"
|
|
142
|
+
assert len(calls) == 1
|
|
143
|
+
assert "reason" not in clean or clean.strip() == ""
|
|
144
|
+
|
|
145
|
+
|
|
93
146
|
def test_no_thinking_invoke_still_works() -> None:
|
|
94
147
|
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
95
148
|
parser.feed(f"Sure.\n{INVOKE}")
|