echotools 2.4.9__tar.gz → 2.4.10__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.4.9 → echotools-2.4.10}/PKG-INFO +1 -1
- {echotools-2.4.9 → echotools-2.4.10}/pyproject.toml +1 -1
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/io/printstream.py +1 -1
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/parsers/stream_final.py +77 -15
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_patterns/invoke.py +53 -2
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/hold.py +100 -39
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/parse.py +13 -3
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/invoke.py +7 -1
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools.egg-info/SOURCES.txt +2 -0
- echotools-2.4.10/src/tests/fixtures/interleaved_corpus.py +116 -0
- echotools-2.4.10/src/tests/test_entml_interleaved_corpus.py +152 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_tool_pipeline.py +18 -0
- {echotools-2.4.9 → echotools-2.4.10}/README.md +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/setup.cfg +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/base.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/center.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/parsers/stream.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/parsers/stream_delta.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/parsers/stream_feed.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/prompt/behavior_blocks.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_patterns/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_patterns/params.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_patterns/regex.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_patterns/strip.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_schema/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_schema/mangled.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_schema/validate.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_stream/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_stream/body.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_stream/snap.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/filter.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/comment.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/fakemarkup.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/tools.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/values.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/entml_format.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/history_markup.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/tool_id.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/keys.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/ui_console.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/ui_io.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/ui_log.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/ui_platform.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/ui_text.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uicore/ui_types.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_bridge.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_table.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_richcli.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/console/uiwidgets/ui_themes.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/events/event.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/translate.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/application.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_monitor/api.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_monitor/config.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_monitor/linux.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_monitor/mac.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_monitor/types.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_monitor/win.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/api.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/config.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/linux.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/mac.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/win.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/win_wasapi.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/audio_record/win_winmm.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/api.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/config.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/linux.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/mac.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/win.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/win_dxgi.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/screenshot/win_gdi.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/shared/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/shared/bmp.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/shared/pcm.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/shared/platform.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/shared/wav.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/capture/shared/win_com.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/sdk/runtime.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/plat/sdk/scheduler.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools/version.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/_check_parity.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/bench_import.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/conftest.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/fixtures/__init__.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/fixtures/entml_golden.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/fixtures/entml_golden_update.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_adversarial_stream_splits.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_completion.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_config.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_config_extended.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_console.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_dispatch.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_agent_output_formats.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_bare_invoke_format.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_batch_stream_comprehensive.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_fake_structure_markup.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_golden.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_invoke_known_tool_gate.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_parser_comprehensive.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_protocol.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_stream_json.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_tool_parse_leak.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_errors.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_errors_common.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_events.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_events_event.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_facade.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fake_history_markup_batch.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fake_history_markup_stream.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fault_thinking_close_batch.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fault_thinking_close_stream.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fncall.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_fncall_stream_thinking.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_history_markup_filter.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_ids.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_imports.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_io.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_keys.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_logger.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_merge.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_model_branch_matrix.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_multiline_parameter_comprehensive.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_plat_capture.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_plugin.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_printstream.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_protocols.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_proxy.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_runtime.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_scheduler.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_schema_validate.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_simulated_llm_tool_parse.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_stream_partial_json_parity.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_stream_realtime_parse.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_tool_id.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_tracing.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_ui_bridge.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_usage.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_web.py +0 -0
- {echotools-2.4.9 → echotools-2.4.10}/src/tests/test_web_broker.py +0 -0
|
@@ -48,7 +48,10 @@ class StreamFinalMixin:
|
|
|
48
48
|
return visible
|
|
49
49
|
|
|
50
50
|
def _stream_partial_display_text(self) -> str:
|
|
51
|
-
"""流式 ``partial_text
|
|
51
|
+
"""流式 ``partial_text``:保留 invoke 之间的交错正文,仅截断未闭合 invoke 起点。
|
|
52
|
+
|
|
53
|
+
在 invoke 起点前截断 raw,保留尾部 holdback 空白。
|
|
54
|
+
"""
|
|
52
55
|
from echotools.exec.fncall.protocols.entml_think.parse import (
|
|
53
56
|
clean_stream_partial_visible,
|
|
54
57
|
split_entml_thinking,
|
|
@@ -61,17 +64,7 @@ class StreamFinalMixin:
|
|
|
61
64
|
raw = self._raw_buf
|
|
62
65
|
if not raw:
|
|
63
66
|
return ""
|
|
64
|
-
|
|
65
|
-
cut = len(raw)
|
|
66
|
-
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
67
|
-
find_actionable_entml_invoke_open,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
invoke_pos = find_actionable_entml_invoke_open(
|
|
71
|
-
raw, known_names=self._get_known_tool_names()
|
|
72
|
-
)
|
|
73
|
-
if invoke_pos >= 0:
|
|
74
|
-
cut = min(cut, invoke_pos)
|
|
67
|
+
cut, saw_invoke = self._invoke_cutoff(raw)
|
|
75
68
|
segment = raw[:cut]
|
|
76
69
|
|
|
77
70
|
buf = stream_safe_visible_prefix(
|
|
@@ -80,18 +73,87 @@ class StreamFinalMixin:
|
|
|
80
73
|
)
|
|
81
74
|
if not buf:
|
|
82
75
|
return ""
|
|
83
|
-
cleaned, _ = strip_fake_history_markup_for_display(buf)
|
|
84
76
|
visible, _ = split_entml_thinking(
|
|
85
|
-
|
|
77
|
+
buf,
|
|
86
78
|
thinking_enabled=self._thinking_enabled,
|
|
87
79
|
preserve_visible_whitespace=True,
|
|
88
80
|
)
|
|
81
|
+
# 与 stream_safe 无未闭合 thinking 的 split 分支对齐:剥前导空白,
|
|
82
|
+
# 避免 thinking 打开/闭合瞬间 partial_text 首部 \n 抖动导致增量错位
|
|
83
|
+
visible = visible.lstrip()
|
|
84
|
+
visible = self._strip_completed_invoke_blocks(visible)
|
|
85
|
+
cleaned, _ = strip_fake_history_markup_for_display(visible)
|
|
86
|
+
if saw_invoke:
|
|
87
|
+
# invoke 前缀(<entml:...)被剥除后可能留下尾部换行;统一剥掉,
|
|
88
|
+
# 否则 invoke 开标签收齐瞬间 rstrip 翻转导致长度回退。空格保留
|
|
89
|
+
cleaned = cleaned.rstrip("\n\r")
|
|
89
90
|
return clean_stream_partial_visible(
|
|
90
|
-
|
|
91
|
+
cleaned,
|
|
91
92
|
has_calls=self.has_calls,
|
|
92
93
|
thinking_enabled=self._thinking_enabled,
|
|
93
94
|
)
|
|
94
95
|
|
|
96
|
+
def _invoke_cutoff(self, raw: str) -> Tuple[int, bool]:
|
|
97
|
+
"""返回 ``(截断点, 是否出现 invoke 前缀)``。
|
|
98
|
+
|
|
99
|
+
截断到最后一个「打开但未闭合」的 invoke 起点:其后内容可能是参数,
|
|
100
|
+
不能进入 partial_text;已闭合 invoke 之间的正文保留。
|
|
101
|
+
"""
|
|
102
|
+
from echotools.exec.fncall.protocols.entml_patterns.invoke import (
|
|
103
|
+
_find_matching_close,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
cut = len(raw)
|
|
107
|
+
# `<entml:` 前缀即按 invoke 区域处理:前缀被剥除后留下的尾部换行
|
|
108
|
+
# 需统一剥掉,否则 invoke 开标签收齐瞬间 rstrip 翻转导致长度回退
|
|
109
|
+
saw_invoke = "<entml:" in raw
|
|
110
|
+
search_from = 0
|
|
111
|
+
while True:
|
|
112
|
+
open_pos = raw.find("<entml:invoke", search_from)
|
|
113
|
+
if open_pos < 0:
|
|
114
|
+
break
|
|
115
|
+
if not self._is_actionable_invoke_open(raw, open_pos):
|
|
116
|
+
gt = raw.find(">", open_pos + len("<entml:invoke"))
|
|
117
|
+
search_from = (gt + 1) if gt >= 0 else (open_pos + len("<entml:invoke"))
|
|
118
|
+
continue
|
|
119
|
+
gt = raw.find(">", open_pos + len("<entml:invoke"))
|
|
120
|
+
if gt < 0:
|
|
121
|
+
cut = min(cut, open_pos)
|
|
122
|
+
break
|
|
123
|
+
close = _find_matching_close(raw, gt + 1, "</entml:invoke>")
|
|
124
|
+
if close < 0:
|
|
125
|
+
cut = min(cut, open_pos)
|
|
126
|
+
break
|
|
127
|
+
search_from = close + len("</entml:invoke>")
|
|
128
|
+
return cut, saw_invoke
|
|
129
|
+
|
|
130
|
+
def _strip_completed_invoke_blocks(self, visible: str) -> str:
|
|
131
|
+
"""已闭合 invoke 块整体移除(含参数),避免标签剥离后参数文本泄漏。"""
|
|
132
|
+
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
133
|
+
iter_actionable_entml_invoke_blocks,
|
|
134
|
+
resolve_known_tool_names,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
known = resolve_known_tool_names(self._tools, self._get_schema_index())
|
|
138
|
+
removed: List[str] = []
|
|
139
|
+
last_end = 0
|
|
140
|
+
for start, end, _attrs, _body in iter_actionable_entml_invoke_blocks(
|
|
141
|
+
visible, known_names=known
|
|
142
|
+
):
|
|
143
|
+
removed.append(visible[last_end:start])
|
|
144
|
+
last_end = end
|
|
145
|
+
removed.append(visible[last_end:])
|
|
146
|
+
return "".join(removed)
|
|
147
|
+
|
|
148
|
+
def _is_actionable_invoke_open(self, raw: str, pos: int) -> bool:
|
|
149
|
+
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
150
|
+
entml_invoke_open_is_actionable,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return entml_invoke_open_is_actionable(
|
|
154
|
+
raw, pos, known_names=self._get_known_tool_names()
|
|
155
|
+
)
|
|
156
|
+
|
|
95
157
|
def _finalize_display_text(self, clean_text: str) -> str:
|
|
96
158
|
if not self._thinking_enabled:
|
|
97
159
|
return clean_text
|
{echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_patterns/invoke.py
RENAMED
|
@@ -7,6 +7,54 @@ from .params import extract_attr_value, normalize_entml_name
|
|
|
7
7
|
from .regex import _INVOKE_CLOSE, _INVOKE_OPEN_PREFIX
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
# 参数体是 opaque 文本:内部可能包含字面量 ``</entml:invoke>`` 或
|
|
11
|
+
# ``</entml:thinking>``(如 Python 正则字符串)。找闭合标签时须跳过参数体。
|
|
12
|
+
_SKIP_PARAM_BODIES: List[Tuple[str, str]] = [
|
|
13
|
+
("<entml:parameter", "</entml:parameter>"),
|
|
14
|
+
("<entml:parameters>", "</entml:parameters>"),
|
|
15
|
+
("<entml:parameter>", "</entml:parameter>"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _find_matching_close(
|
|
20
|
+
body: str,
|
|
21
|
+
start: int,
|
|
22
|
+
close_tag: str,
|
|
23
|
+
) -> int:
|
|
24
|
+
"""从 ``start`` 起找 ``close_tag``,跳过 ``<entml:parameter[...]>`` 体的内容。
|
|
25
|
+
|
|
26
|
+
参数体内部的字面量 ``</entml:invoke>`` / ``</entml:thinking>`` 是内容不是结构,
|
|
27
|
+
不能当作外层块的闭合。
|
|
28
|
+
"""
|
|
29
|
+
i = start
|
|
30
|
+
n = len(body)
|
|
31
|
+
while i < n:
|
|
32
|
+
lt = body.find("<", i)
|
|
33
|
+
if lt < 0:
|
|
34
|
+
return -1
|
|
35
|
+
gt = body.find(">", lt)
|
|
36
|
+
if gt < 0:
|
|
37
|
+
return -1
|
|
38
|
+
tag = body[lt : gt + 1]
|
|
39
|
+
if tag == close_tag:
|
|
40
|
+
return lt
|
|
41
|
+
# 精确匹配 parameter 开标签(含属性的形式);不匹配 siblings
|
|
42
|
+
if tag == "<entml:parameter>" or tag.startswith("<entml:parameter "):
|
|
43
|
+
close_pos = body.find("</entml:parameter>", gt)
|
|
44
|
+
if close_pos < 0:
|
|
45
|
+
return -1
|
|
46
|
+
i = close_pos + len("</entml:parameter>")
|
|
47
|
+
continue
|
|
48
|
+
if tag == "<entml:parameters>":
|
|
49
|
+
close_pos = body.find("</entml:parameters>", gt)
|
|
50
|
+
if close_pos < 0:
|
|
51
|
+
return -1
|
|
52
|
+
i = close_pos + len("</entml:parameters>")
|
|
53
|
+
continue
|
|
54
|
+
i = gt + 1
|
|
55
|
+
return -1
|
|
56
|
+
|
|
57
|
+
|
|
10
58
|
def is_placeholder_invoke_name(name: str) -> bool:
|
|
11
59
|
"""提示词占位符(如 ``$FUNCTION_NAME``)不算真实工具调用。"""
|
|
12
60
|
n = (name or "").strip()
|
|
@@ -158,7 +206,7 @@ def iter_actionable_entml_invoke_blocks(
|
|
|
158
206
|
gt = text.find(">", pos + prefix_len)
|
|
159
207
|
if gt < 0:
|
|
160
208
|
break
|
|
161
|
-
close = text
|
|
209
|
+
close = _find_matching_close(text, gt + 1, _INVOKE_CLOSE)
|
|
162
210
|
if close < 0:
|
|
163
211
|
break
|
|
164
212
|
attrs = text[pos + prefix_len : gt]
|
|
@@ -189,8 +237,11 @@ def _strip_orphan_invoke_tags(
|
|
|
189
237
|
|
|
190
238
|
|
|
191
239
|
def _strip_orphan_non_invoke_tool_tags(content: str) -> str:
|
|
240
|
+
# 剥离 invoke 块外部的畸形 entml:* 标签(如模型把参数名输出成
|
|
241
|
+
# <entml:old_string">),仅保留 invoke/thinking 相关;prose 提及的
|
|
242
|
+
# <entml:invoke> 与未知工具块不在此列,留给调用方按 known 判定。
|
|
192
243
|
return re.sub(
|
|
193
|
-
r"</?entml:(?:
|
|
244
|
+
r"</?entml:(?!invoke\b|thinking(?:_mode)?\b|max_thinking_length\b)\w+\b[^>]*/?>",
|
|
194
245
|
"",
|
|
195
246
|
content,
|
|
196
247
|
flags=re.DOTALL,
|
{echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/hold.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import List, Optional, Set, Tuple
|
|
4
|
+
from typing import Callable, List, Optional, Set, Tuple
|
|
5
5
|
|
|
6
6
|
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
7
7
|
entml_invoke_open_may_be_streaming,
|
|
@@ -84,15 +84,19 @@ def _find_thinking_close(
|
|
|
84
84
|
thinking_enabled: bool = True,
|
|
85
85
|
) -> Tuple[int, int]:
|
|
86
86
|
"""返回 ``(闭标签下标, 闭标签长度)``;未找到则 ``(-1, 0)``。"""
|
|
87
|
+
from echotools.exec.fncall.protocols.entml_patterns.invoke import (
|
|
88
|
+
_find_matching_close,
|
|
89
|
+
)
|
|
90
|
+
|
|
87
91
|
if not thinking_enabled:
|
|
88
|
-
|
|
89
|
-
if
|
|
90
|
-
return
|
|
92
|
+
entml_close = _find_matching_close(text, body_start, _THINKING_CLOSE)
|
|
93
|
+
if entml_close >= 0:
|
|
94
|
+
return entml_close, len(_THINKING_CLOSE)
|
|
91
95
|
return -1, 0
|
|
92
96
|
|
|
97
|
+
entml_close = _find_matching_close(text, body_start, _THINKING_CLOSE)
|
|
98
|
+
fault_close = _find_matching_close(text, body_start, _FAULT_THINKING_CLOSE)
|
|
93
99
|
if opened_plain:
|
|
94
|
-
entml_close = text.find(_THINKING_CLOSE, body_start)
|
|
95
|
-
fault_close = text.find(_FAULT_THINKING_CLOSE, body_start)
|
|
96
100
|
candidates: List[Tuple[int, int]] = []
|
|
97
101
|
if entml_close >= 0:
|
|
98
102
|
candidates.append((entml_close, len(_THINKING_CLOSE)))
|
|
@@ -100,11 +104,8 @@ def _find_thinking_close(
|
|
|
100
104
|
candidates.append((fault_close, len(_FAULT_THINKING_CLOSE)))
|
|
101
105
|
if not candidates:
|
|
102
106
|
return -1, 0
|
|
103
|
-
|
|
104
|
-
return close_at, close_len
|
|
107
|
+
return min(candidates, key=lambda item: item[0])
|
|
105
108
|
|
|
106
|
-
entml_close = text.find(_THINKING_CLOSE, body_start)
|
|
107
|
-
fault_close = text.find(_FAULT_THINKING_CLOSE, body_start)
|
|
108
109
|
if entml_close >= 0 and (fault_close < 0 or entml_close <= fault_close):
|
|
109
110
|
return entml_close, len(_THINKING_CLOSE)
|
|
110
111
|
if fault_close < 0:
|
|
@@ -112,7 +113,7 @@ def _find_thinking_close(
|
|
|
112
113
|
|
|
113
114
|
after_fault = fault_close + len(_FAULT_THINKING_CLOSE)
|
|
114
115
|
invoke_rel = find_complete_entml_invoke_open(text[after_fault:])
|
|
115
|
-
entml_after = text
|
|
116
|
+
entml_after = _find_matching_close(text, after_fault, _THINKING_CLOSE)
|
|
116
117
|
if invoke_rel >= 0:
|
|
117
118
|
invoke_abs = after_fault + invoke_rel
|
|
118
119
|
if entml_after < 0 or invoke_abs < entml_after:
|
|
@@ -174,41 +175,90 @@ def _hold_ambiguous_tool_markup(
|
|
|
174
175
|
return buffer, ""
|
|
175
176
|
|
|
176
177
|
|
|
177
|
-
def _find_plain_thinking_open(text: str, start: int = 0) -> int:
|
|
178
|
-
"""返回 plain ``<thinking>`` 开标签起始下标(``<entml:thinking>`` 内不含此子串)。"""
|
|
179
|
-
return text.find(_PLAIN_THINKING_OPEN_PREFIX, start)
|
|
180
|
-
|
|
181
178
|
def _find_earliest_thinking_open(
|
|
182
179
|
text: str,
|
|
183
180
|
start: int = 0,
|
|
184
181
|
*,
|
|
185
182
|
thinking_enabled: bool = True,
|
|
186
183
|
) -> Tuple[int, bool]:
|
|
187
|
-
"""返回 ``(开标签下标, 是否 plain)``;无则 ``(-1, False)``。
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
184
|
+
"""返回 ``(开标签下标, 是否 plain)``;无则 ``(-1, False)``。
|
|
185
|
+
|
|
186
|
+
按标签逐段扫描,跳过 ``<entml:parameter>`` / ``<entml:parameters>`` 体——
|
|
187
|
+
参数值内可能含 ``<entml:thinking\\b[^>]*>`` 这类字面量(正则字符串),
|
|
188
|
+
不能当作真实 thinking 块。
|
|
189
|
+
"""
|
|
190
|
+
from echotools.exec.fncall.protocols.entml_patterns.invoke import (
|
|
191
|
+
_find_matching_close,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def _skip_param_body(close_tag: str) -> Optional[int]:
|
|
195
|
+
pos = _find_matching_close(text, gt + 1, close_tag)
|
|
196
|
+
return pos + len(close_tag) if pos >= 0 else None
|
|
197
|
+
|
|
198
|
+
i = start
|
|
199
|
+
n = len(text)
|
|
200
|
+
while i < n:
|
|
201
|
+
lt = text.find("<", i)
|
|
202
|
+
if lt < 0:
|
|
191
203
|
return -1, False
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
gt = text.find(">", lt)
|
|
205
|
+
if gt < 0:
|
|
206
|
+
return _partial_open_check(text, lt, thinking_enabled)
|
|
207
|
+
tag = text[lt : gt + 1]
|
|
208
|
+
i = _advance_past_param_body(tag, gt, _skip_param_body)
|
|
209
|
+
if i is not None:
|
|
210
|
+
continue
|
|
211
|
+
if tag == (_THINKING_OPEN_PREFIX + ">") or tag.startswith(
|
|
212
|
+
_THINKING_OPEN_PREFIX + " "
|
|
213
|
+
):
|
|
214
|
+
return lt, False
|
|
215
|
+
if thinking_enabled and _is_plain_thinking_open(tag):
|
|
216
|
+
return lt, True
|
|
217
|
+
i = gt + 1
|
|
218
|
+
return -1, False
|
|
203
219
|
|
|
204
|
-
|
|
220
|
+
|
|
221
|
+
def _partial_open_check(text: str, lt: int, thinking_enabled: bool) -> Tuple[int, bool]:
|
|
222
|
+
"""处理未收齐的 ``<...`` 前缀:若是 thinking 开标签真前缀则视其为未闭合块。"""
|
|
223
|
+
partial = text[lt:]
|
|
224
|
+
if partial.startswith(_THINKING_OPEN_PREFIX):
|
|
225
|
+
return lt, False
|
|
226
|
+
if thinking_enabled and (
|
|
227
|
+
partial.startswith(_PLAIN_THINKING_OPEN_PREFIX)
|
|
228
|
+
and not partial.startswith(_THINKING_OPEN_PREFIX)
|
|
229
|
+
):
|
|
230
|
+
return lt, True
|
|
231
|
+
return -1, False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _is_plain_thinking_open(tag: str) -> bool:
|
|
235
|
+
"""``tag`` 是否为 plain ``<thinking...>``(不含 ``<entml:thinking`` 前缀)。"""
|
|
236
|
+
return tag.startswith(_PLAIN_THINKING_OPEN_PREFIX) and not tag.startswith(
|
|
237
|
+
_THINKING_OPEN_PREFIX
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _advance_past_param_body(
|
|
242
|
+
tag: str,
|
|
243
|
+
gt: int,
|
|
244
|
+
skip_fn: "Callable[[str], Optional[int]]",
|
|
245
|
+
) -> Optional[int]:
|
|
246
|
+
"""若是 ``<entml:parameter[...]>`` / ``<entml:parameters>``,返回体闭合标签后的下标。"""
|
|
247
|
+
if tag.startswith("<entml:parameter"):
|
|
248
|
+
return skip_fn("</entml:parameter>")
|
|
249
|
+
if tag == "<entml:parameters>":
|
|
250
|
+
return skip_fn("</entml:parameters>")
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def first_unclosed_thinking_open(
|
|
205
255
|
text: str,
|
|
206
256
|
*,
|
|
207
257
|
thinking_enabled: bool = True,
|
|
208
|
-
) ->
|
|
209
|
-
"""
|
|
258
|
+
) -> int:
|
|
259
|
+
"""返回第一个未闭合 thinking 块的开标签下标;无则 ``-1``。"""
|
|
210
260
|
if not text:
|
|
211
|
-
return
|
|
261
|
+
return -1
|
|
212
262
|
i = 0
|
|
213
263
|
while i < len(text):
|
|
214
264
|
open_at, opened_plain = _find_earliest_thinking_open(
|
|
@@ -218,7 +268,7 @@ def has_unclosed_entml_thinking(
|
|
|
218
268
|
break
|
|
219
269
|
gt = text.find(">", open_at)
|
|
220
270
|
if gt < 0:
|
|
221
|
-
return
|
|
271
|
+
return open_at
|
|
222
272
|
body_start = gt + 1
|
|
223
273
|
close_at, close_len = _find_thinking_close(
|
|
224
274
|
text,
|
|
@@ -227,7 +277,7 @@ def has_unclosed_entml_thinking(
|
|
|
227
277
|
thinking_enabled=thinking_enabled,
|
|
228
278
|
)
|
|
229
279
|
if close_at < 0:
|
|
230
|
-
return
|
|
280
|
+
return open_at
|
|
231
281
|
i = close_at + close_len
|
|
232
282
|
max_keep = len(_THINKING_OPEN_PREFIX) - 1
|
|
233
283
|
if thinking_enabled:
|
|
@@ -241,13 +291,24 @@ def has_unclosed_entml_thinking(
|
|
|
241
291
|
):
|
|
242
292
|
if any(other.startswith(suffix) for other in _AMBIGUOUS_ENTML_PREFIXES):
|
|
243
293
|
continue
|
|
244
|
-
return
|
|
294
|
+
return len(text) - length
|
|
245
295
|
if thinking_enabled and (
|
|
246
296
|
_PLAIN_THINKING_OPEN_PREFIX.startswith(suffix)
|
|
247
297
|
and suffix != _PLAIN_THINKING_OPEN_PREFIX
|
|
248
298
|
):
|
|
249
|
-
return
|
|
250
|
-
return
|
|
299
|
+
return len(text) - length
|
|
300
|
+
return -1
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def has_unclosed_entml_thinking(
|
|
304
|
+
text: str,
|
|
305
|
+
*,
|
|
306
|
+
thinking_enabled: bool = True,
|
|
307
|
+
) -> bool:
|
|
308
|
+
"""buffer 中是否存在尚未闭合的 thinking 块(含开标签未收齐)。"""
|
|
309
|
+
return first_unclosed_thinking_open(
|
|
310
|
+
text, thinking_enabled=thinking_enabled
|
|
311
|
+
) >= 0
|
|
251
312
|
|
|
252
313
|
|
|
253
314
|
def invoke_index_inside_unclosed_thinking(
|
{echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_think/parse.py
RENAMED
|
@@ -44,6 +44,7 @@ from echotools.exec.fncall.protocols.entml_think.hold import (
|
|
|
44
44
|
_find_earliest_thinking_open,
|
|
45
45
|
_find_thinking_close,
|
|
46
46
|
find_complete_entml_invoke_open,
|
|
47
|
+
first_unclosed_thinking_open,
|
|
47
48
|
has_unclosed_entml_thinking,
|
|
48
49
|
invoke_index_inside_any_thinking_block,
|
|
49
50
|
invoke_index_inside_unclosed_thinking,
|
|
@@ -71,6 +72,8 @@ def leading_entml_tag_holdback_len(
|
|
|
71
72
|
if comment_hold:
|
|
72
73
|
return comment_hold
|
|
73
74
|
prefixes: List[str] = list(_LEADING_HOLD_PREFIXES)
|
|
75
|
+
# 流式分片可能把伪 history 闭标签切成真前缀,同样需要 hold
|
|
76
|
+
prefixes.extend(("</assistant", "</tool"))
|
|
74
77
|
if thinking_enabled:
|
|
75
78
|
prefixes.append(_PLAIN_THINKING_OPEN_PREFIX)
|
|
76
79
|
for prefix in prefixes:
|
|
@@ -97,7 +100,8 @@ def stream_safe_visible_prefix(
|
|
|
97
100
|
return ""
|
|
98
101
|
buf = text[hold:]
|
|
99
102
|
if has_unclosed_entml_thinking(buf, thinking_enabled=thinking_enabled):
|
|
100
|
-
|
|
103
|
+
# 取第一个未闭合 thinking 块之前的内容;此前已闭合的 thinking 不应截断(交错场景)
|
|
104
|
+
open_at = first_unclosed_thinking_open(
|
|
101
105
|
buf, thinking_enabled=thinking_enabled
|
|
102
106
|
)
|
|
103
107
|
if open_at >= 0:
|
|
@@ -128,6 +132,8 @@ def trailing_entml_tag_holdback_len(text: str) -> int:
|
|
|
128
132
|
candidates: List[str] = list(_LEADING_HOLD_PREFIXES) + [
|
|
129
133
|
_THINKING_CLOSE,
|
|
130
134
|
_FAULT_THINKING_CLOSE,
|
|
135
|
+
"</assistant",
|
|
136
|
+
"</tool",
|
|
131
137
|
]
|
|
132
138
|
for tag in candidates:
|
|
133
139
|
if tag.startswith(tail) and tail != tag:
|
|
@@ -177,6 +183,9 @@ def clean_stream_partial_visible(
|
|
|
177
183
|
tail_hold = trailing_entml_tag_holdback_len(text)
|
|
178
184
|
if tail_hold:
|
|
179
185
|
text = text[:-tail_hold]
|
|
186
|
+
# 剥掉未收齐标签后统一去尾部换行空白:否则标签补全瞬间长度回退,
|
|
187
|
+
# 流式增量(按长度推进)会错位。不剥空格——词尾空格是正文的一部分
|
|
188
|
+
text = text.rstrip("\n\r")
|
|
180
189
|
text = strip_fake_entml_structure_markup_for_display(text)[0]
|
|
181
190
|
if has_calls:
|
|
182
191
|
text = text.rstrip()
|
|
@@ -239,7 +248,7 @@ def split_entml_thinking(
|
|
|
239
248
|
clean_parts.append(text[i:open_at])
|
|
240
249
|
gt = text.find(">", open_at)
|
|
241
250
|
if gt < 0:
|
|
242
|
-
|
|
251
|
+
# 半开标签(未收齐)——不进 content,只保留其前可见
|
|
243
252
|
break
|
|
244
253
|
body_start = gt + 1
|
|
245
254
|
close_at, close_len = _find_thinking_close(
|
|
@@ -249,7 +258,8 @@ def split_entml_thinking(
|
|
|
249
258
|
thinking_enabled=thinking_enabled,
|
|
250
259
|
)
|
|
251
260
|
if close_at < 0:
|
|
252
|
-
|
|
261
|
+
# thinking 开标签完整但未闭合——其内容保留为可见(内含的 invoke 仍可解析)
|
|
262
|
+
clean_parts.append(text[body_start:])
|
|
253
263
|
break
|
|
254
264
|
parts.append(text[body_start:close_at])
|
|
255
265
|
i = _advance_past_fault_thinking_close(
|
{echotools-2.4.9 → echotools-2.4.10}/src/echotools/exec/fncall/protocols/entml_tool/invoke.py
RENAMED
|
@@ -120,7 +120,13 @@ def parse_invoke_args(
|
|
|
120
120
|
) -> Dict[str, Any]:
|
|
121
121
|
func_props = (schema_index or {}).get(name) or {}
|
|
122
122
|
if "</entml:invoke>" in body:
|
|
123
|
-
|
|
123
|
+
# 参数体内可能含字面量 ``</entml:invoke>``(如 Python 正则字符串),
|
|
124
|
+
# 不能直接截断;用 tag-aware 查找器定位真实闭合(找不到才截断)。
|
|
125
|
+
from ..entml_patterns.invoke import _find_matching_close
|
|
126
|
+
|
|
127
|
+
real_close = _find_matching_close(body, 0, "</entml:invoke>")
|
|
128
|
+
if real_close >= 0:
|
|
129
|
+
body = body[:real_close]
|
|
124
130
|
body = synthetic_close_invoke_body(body)
|
|
125
131
|
params_m = PARAMETERS_RE.search(body)
|
|
126
132
|
if params_m:
|
|
@@ -231,6 +231,7 @@ src/tests/test_entml_bare_invoke_format.py
|
|
|
231
231
|
src/tests/test_entml_batch_stream_comprehensive.py
|
|
232
232
|
src/tests/test_entml_fake_structure_markup.py
|
|
233
233
|
src/tests/test_entml_golden.py
|
|
234
|
+
src/tests/test_entml_interleaved_corpus.py
|
|
234
235
|
src/tests/test_entml_invoke_known_tool_gate.py
|
|
235
236
|
src/tests/test_entml_parser_comprehensive.py
|
|
236
237
|
src/tests/test_entml_protocol.py
|
|
@@ -285,6 +286,7 @@ src/tests/test_web_broker.py
|
|
|
285
286
|
src/tests/fixtures/__init__.py
|
|
286
287
|
src/tests/fixtures/entml_golden.py
|
|
287
288
|
src/tests/fixtures/entml_golden_update.py
|
|
289
|
+
src/tests/fixtures/interleaved_corpus.py
|
|
288
290
|
src/tests/fixtures/simulated_fake_history_markup_responses.py
|
|
289
291
|
src/tests/fixtures/simulated_fault_thinking_responses.py
|
|
290
292
|
src/tests/fixtures/simulated_llm_tool_responses.py
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""交错 thinking / invoke / 正文 回归语料。
|
|
2
|
+
|
|
3
|
+
结构提炼自 Rogator 日志 `X:\\Project\\Public\\Rogator\\logs\\responses\\req-1787237525-7f1478678d5b.txt`:
|
|
4
|
+
多轮 ``<entml:thinking>`` → 正文 → ``<entml:invoke>`` 交错出现,
|
|
5
|
+
invoke 之间及 invoke 之后继续有正文与思考,最后以纯正文结尾。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
INTERLEAVED_TOOLS: List[Dict[str, Any]] = [
|
|
13
|
+
{
|
|
14
|
+
"type": "function",
|
|
15
|
+
"function": {
|
|
16
|
+
"name": "Edit",
|
|
17
|
+
"description": "编辑文件",
|
|
18
|
+
"parameters": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"file_path": {"type": "string"},
|
|
22
|
+
"old_string": {"type": "string"},
|
|
23
|
+
"new_string": {"type": "string"},
|
|
24
|
+
},
|
|
25
|
+
"required": ["file_path", "old_string", "new_string"],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "function",
|
|
31
|
+
"function": {
|
|
32
|
+
"name": "Read",
|
|
33
|
+
"description": "读取文件",
|
|
34
|
+
"parameters": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"file_path": {"type": "string"},
|
|
38
|
+
"offset": {"type": "integer"},
|
|
39
|
+
"limit": {"type": "integer"},
|
|
40
|
+
},
|
|
41
|
+
"required": ["file_path"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "function",
|
|
47
|
+
"function": {
|
|
48
|
+
"name": "Bash",
|
|
49
|
+
"description": "运行命令",
|
|
50
|
+
"parameters": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {"command": {"type": "string"}},
|
|
53
|
+
"required": ["command"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
INTERLEAVED_CORPUS = """<entml:thinking>
|
|
60
|
+
第一轮思考:需要先定位现有实现。
|
|
61
|
+
</entml:thinking>
|
|
62
|
+
|
|
63
|
+
第一轮正文:我先看看当前实现。
|
|
64
|
+
|
|
65
|
+
<entml:invoke name="Edit">
|
|
66
|
+
<entml:parameter name="file_path">src/config.py</entml:parameter>
|
|
67
|
+
<entml:parameter name="old_string">old value</entml:parameter>
|
|
68
|
+
<entml:parameter name="new_string">new value</entml:parameter>
|
|
69
|
+
</entml:invoke>
|
|
70
|
+
</assistant>
|
|
71
|
+
|
|
72
|
+
<entml:thinking>
|
|
73
|
+
第二轮思考:Edit 完成,继续检查调用点。
|
|
74
|
+
</entml:thinking>
|
|
75
|
+
|
|
76
|
+
第二轮正文:继续修复第二个位置。
|
|
77
|
+
|
|
78
|
+
<entml:invoke name="Read">
|
|
79
|
+
<entml:parameter name="file_path">src/config.py</entml:parameter>
|
|
80
|
+
<entml:parameter name="offset">10</entml:parameter>
|
|
81
|
+
<entml:parameter name="limit">25</entml:parameter>
|
|
82
|
+
</entml:invoke>
|
|
83
|
+
</assistant>
|
|
84
|
+
|
|
85
|
+
<entml:thinking>
|
|
86
|
+
第三轮思考:调用点确认,最后运行测试。
|
|
87
|
+
</entml:thinking>
|
|
88
|
+
|
|
89
|
+
第三轮正文:修改完成,运行验证。
|
|
90
|
+
|
|
91
|
+
<entml:invoke name="Bash">
|
|
92
|
+
<entml:parameter name="command">python -m pytest -q</entml:parameter>
|
|
93
|
+
</entml:invoke>
|
|
94
|
+
<entml:invoke name="Bash">
|
|
95
|
+
<entml:parameter name="command">python achecker.py</entml:parameter>
|
|
96
|
+
</entml:invoke>
|
|
97
|
+
</assistant>
|
|
98
|
+
|
|
99
|
+
全部通过,修复完成。
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
EXPECT_NAMES = ["Edit", "Read", "Bash", "Bash"]
|
|
104
|
+
|
|
105
|
+
EXPECT_VISIBLE_SEGMENTS = [
|
|
106
|
+
"第一轮正文:我先看看当前实现。",
|
|
107
|
+
"第二轮正文:继续修复第二个位置。",
|
|
108
|
+
"第三轮正文:修改完成,运行验证。",
|
|
109
|
+
"全部通过,修复完成。",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
EXPECT_THINKING_MARKS = [
|
|
113
|
+
"第一轮思考:需要先定位现有实现。",
|
|
114
|
+
"第二轮思考:Edit 完成,继续检查调用点。",
|
|
115
|
+
"第三轮思考:调用点确认,最后运行测试。",
|
|
116
|
+
]
|