echotools 2.3.88__tar.gz → 2.3.89__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.88 → echotools-2.3.89}/PKG-INFO +1 -1
- {echotools-2.3.88 → echotools-2.3.89}/pyproject.toml +1 -1
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/stream.py +5 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/stream_delta.py +5 -5
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/stream_feed.py +5 -5
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/stream_final.py +27 -6
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml.py +43 -26
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_invoke.py +5 -1
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_patterns.py +109 -25
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_stream/body.py +14 -2
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/hold.py +17 -23
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/parse.py +29 -13
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/protocol/base.py +5 -1
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools.egg-info/SOURCES.txt +1 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_bare_invoke_format.py +30 -9
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_batch_stream_comprehensive.py +2 -1
- echotools-2.3.89/src/tests/test_entml_invoke_known_tool_gate.py +220 -0
- {echotools-2.3.88 → echotools-2.3.89}/README.md +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/setup.cfg +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/compat.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_schema.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_stream/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_stream/snap.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/blocks.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/filter.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/history_markup.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/ui_console.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/ui_io.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/ui_log.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/ui_platform.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/ui_text.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uicore/ui_types.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uilayout/ui_table.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools/version.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/bench_import.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/conftest.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/fixtures/__init__.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/fixtures/simulated_fake_history_markup_responses.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/fixtures/simulated_fault_thinking_responses.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_adversarial_stream_splits.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_completion.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_config.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_console.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_agent_output_formats.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_parser_comprehensive.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_protocol.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_stream_json.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_tool_parse_leak.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_tool_pipeline.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_errors.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_events.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_facade.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fake_history_markup_batch.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fake_history_markup_stream.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fault_thinking_close_batch.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fault_thinking_close_stream.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_fncall_stream_thinking.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_history_markup_filter.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_ids.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_imports.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_io.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_keys.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_logger.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_merge.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_model_branch_matrix.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_simulated_llm_tool_parse.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_stream_partial_json_parity.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_stream_realtime_parse.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_usage.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_web.py +0 -0
- {echotools-2.3.88 → echotools-2.3.89}/src/tests/test_web_broker.py +0 -0
|
@@ -100,6 +100,11 @@ class FncallStreamParser(StreamFeedMixin, StreamDeltaMixin, StreamFinalMixin):
|
|
|
100
100
|
self._schema_index = _build_param_schema_index(self._tools)
|
|
101
101
|
return self._schema_index
|
|
102
102
|
|
|
103
|
+
def _get_known_tool_names(self):
|
|
104
|
+
from echotools.exec.fncall.protocols.entml_patterns import resolve_known_tool_names
|
|
105
|
+
|
|
106
|
+
return resolve_known_tool_names(self._tools, self._get_schema_index())
|
|
107
|
+
|
|
103
108
|
def _emit_text(self, text: str) -> None:
|
|
104
109
|
"""将文本路由给思考过滤器(若启用)或直接追加到 _text_parts。"""
|
|
105
110
|
if not text:
|
|
@@ -26,7 +26,7 @@ class StreamDeltaMixin:
|
|
|
26
26
|
|
|
27
27
|
complete = list(INVOKE_RE.finditer(self._fncall_buf))
|
|
28
28
|
complete_n = len(complete)
|
|
29
|
-
parsed = split_invoke_open(self._fncall_buf)
|
|
29
|
+
parsed = split_invoke_open(self._fncall_buf, known_names=self._get_known_tool_names())
|
|
30
30
|
if parsed is None:
|
|
31
31
|
return max(0, complete_n - 1)
|
|
32
32
|
_name, body_start = parsed
|
|
@@ -77,7 +77,7 @@ class StreamDeltaMixin:
|
|
|
77
77
|
build_streaming_json_snapshot,
|
|
78
78
|
split_invoke_open,
|
|
79
79
|
)
|
|
80
|
-
parsed = split_invoke_open(self._fncall_buf)
|
|
80
|
+
parsed = split_invoke_open(self._fncall_buf, known_names=self._get_known_tool_names())
|
|
81
81
|
if not parsed:
|
|
82
82
|
self._json_stream_encoder.reset()
|
|
83
83
|
return
|
|
@@ -125,7 +125,7 @@ class StreamDeltaMixin:
|
|
|
125
125
|
split_invoke_open,
|
|
126
126
|
)
|
|
127
127
|
|
|
128
|
-
parsed = split_invoke_open(self._fncall_buf)
|
|
128
|
+
parsed = split_invoke_open(self._fncall_buf, known_names=self._get_known_tool_names())
|
|
129
129
|
if not parsed:
|
|
130
130
|
self._json_stream_encoder.reset()
|
|
131
131
|
return
|
|
@@ -172,7 +172,7 @@ class StreamDeltaMixin:
|
|
|
172
172
|
split_invoke_open,
|
|
173
173
|
)
|
|
174
174
|
|
|
175
|
-
parsed = split_invoke_open(self._fncall_buf)
|
|
175
|
+
parsed = split_invoke_open(self._fncall_buf, known_names=self._get_known_tool_names())
|
|
176
176
|
if not parsed:
|
|
177
177
|
return None
|
|
178
178
|
name, body_start = parsed
|
|
@@ -241,7 +241,7 @@ class StreamDeltaMixin:
|
|
|
241
241
|
split_invoke_open,
|
|
242
242
|
)
|
|
243
243
|
|
|
244
|
-
parsed = split_invoke_open(self._fncall_buf)
|
|
244
|
+
parsed = split_invoke_open(self._fncall_buf, known_names=self._get_known_tool_names())
|
|
245
245
|
if not parsed:
|
|
246
246
|
return None
|
|
247
247
|
name, body_start = parsed
|
|
@@ -37,7 +37,7 @@ class StreamFeedMixin:
|
|
|
37
37
|
"""True 表示已消费 text(含 holdback)。"""
|
|
38
38
|
hold_from_fn = getattr(self._protocol, "find_fncall_hold_from", None)
|
|
39
39
|
if hold_from_fn is not None:
|
|
40
|
-
hold_from = hold_from_fn(text)
|
|
40
|
+
hold_from = hold_from_fn(text, tools=self._tools)
|
|
41
41
|
if hold_from is not None:
|
|
42
42
|
if hold_from > 0:
|
|
43
43
|
self._append_content_text(text[:hold_from])
|
|
@@ -52,7 +52,7 @@ class StreamFeedMixin:
|
|
|
52
52
|
def _hold_or_split_emit(self, text: str, trigger_tags) -> None:
|
|
53
53
|
hold_from_fn = getattr(self._protocol, "find_fncall_hold_from", None)
|
|
54
54
|
if hold_from_fn is not None:
|
|
55
|
-
hold_from = hold_from_fn(text)
|
|
55
|
+
hold_from = hold_from_fn(text, tools=self._tools)
|
|
56
56
|
if hold_from is not None:
|
|
57
57
|
if hold_from > 0:
|
|
58
58
|
self._emit_text(text[:hold_from])
|
|
@@ -81,7 +81,7 @@ class StreamFeedMixin:
|
|
|
81
81
|
self._fncall_buf += text
|
|
82
82
|
return
|
|
83
83
|
trigger_tags = self._protocol.get_trigger_tags()
|
|
84
|
-
found, pos = self._protocol.detect_start(text)
|
|
84
|
+
found, pos = self._protocol.detect_start(text, tools=self._tools)
|
|
85
85
|
if found:
|
|
86
86
|
if pos > 0:
|
|
87
87
|
self._append_content_text(text[:pos])
|
|
@@ -90,7 +90,7 @@ class StreamFeedMixin:
|
|
|
90
90
|
self._hold_or_split_content(text, trigger_tags)
|
|
91
91
|
return
|
|
92
92
|
trigger_tags = self._protocol.get_trigger_tags()
|
|
93
|
-
found, pos = self._protocol.detect_start(text)
|
|
93
|
+
found, pos = self._protocol.detect_start(text, tools=self._tools)
|
|
94
94
|
if not found:
|
|
95
95
|
self._hold_or_split_content(text, trigger_tags)
|
|
96
96
|
return
|
|
@@ -138,7 +138,7 @@ class StreamFeedMixin:
|
|
|
138
138
|
self._feed_waiting_thinking_plain(combined)
|
|
139
139
|
return
|
|
140
140
|
trigger_tags = self._protocol.get_trigger_tags()
|
|
141
|
-
found, pos = self._protocol.detect_start(combined)
|
|
141
|
+
found, pos = self._protocol.detect_start(combined, tools=self._tools)
|
|
142
142
|
if found:
|
|
143
143
|
if self._thinking_filter is not None:
|
|
144
144
|
from echotools.exec.fncall.protocols.entml_think.parse import (
|
|
@@ -67,10 +67,18 @@ class StreamFinalMixin:
|
|
|
67
67
|
|
|
68
68
|
lower = raw.lower()
|
|
69
69
|
cut = len(raw)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
71
|
+
find_actionable_entml_invoke_open,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
invoke_pos = find_actionable_entml_invoke_open(
|
|
75
|
+
raw, known_names=self._get_known_tool_names()
|
|
76
|
+
)
|
|
77
|
+
if invoke_pos >= 0:
|
|
78
|
+
cut = min(cut, invoke_pos)
|
|
79
|
+
legacy_pos = lower.find("<entml:function_calls")
|
|
80
|
+
if legacy_pos >= 0:
|
|
81
|
+
cut = min(cut, legacy_pos)
|
|
74
82
|
segment = raw[:cut]
|
|
75
83
|
|
|
76
84
|
buf = stream_safe_visible_prefix(
|
|
@@ -128,12 +136,25 @@ class StreamFinalMixin:
|
|
|
128
136
|
)
|
|
129
137
|
clean_fn = getattr(self._protocol, "clean_tool_tags", None)
|
|
130
138
|
if callable(clean_fn):
|
|
131
|
-
|
|
139
|
+
import inspect
|
|
140
|
+
|
|
141
|
+
sig = inspect.signature(clean_fn)
|
|
142
|
+
if "tools" in sig.parameters:
|
|
143
|
+
clean_text = clean_fn(clean_text, tools=self._tools)
|
|
144
|
+
else:
|
|
145
|
+
clean_text = clean_fn(clean_text)
|
|
132
146
|
elif getattr(self._protocol, "id", None) == "entml":
|
|
133
147
|
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
148
|
+
resolve_known_tool_names,
|
|
134
149
|
strip_tool_entml_residue,
|
|
135
150
|
)
|
|
136
|
-
|
|
151
|
+
|
|
152
|
+
clean_text = strip_tool_entml_residue(
|
|
153
|
+
clean_text,
|
|
154
|
+
known_names=resolve_known_tool_names(
|
|
155
|
+
self._tools, self._get_schema_index()
|
|
156
|
+
),
|
|
157
|
+
)
|
|
137
158
|
display_text = self._finalize_display_text(clean_text)
|
|
138
159
|
self._text_parts = [display_text] if display_text else []
|
|
139
160
|
self._finalized_result = (display_text, tool_calls)
|
|
@@ -25,7 +25,9 @@ from echotools.exec.fncall.protocols.entml_think.blocks import (
|
|
|
25
25
|
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
26
26
|
entml_invoke_open_may_be_streaming,
|
|
27
27
|
extract_attr_value,
|
|
28
|
+
find_actionable_entml_invoke_open,
|
|
28
29
|
normalize_entml_name,
|
|
30
|
+
resolve_known_tool_names,
|
|
29
31
|
strip_actionable_entml_invoke_blocks,
|
|
30
32
|
strip_legacy_function_calls_wrapper,
|
|
31
33
|
strip_tool_entml_residue,
|
|
@@ -215,16 +217,28 @@ class EntmlProtocol(ToolProtocol):
|
|
|
215
217
|
|
|
216
218
|
return "\n\n".join(sections)
|
|
217
219
|
|
|
218
|
-
def detect_start(
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
def detect_start(
|
|
221
|
+
self,
|
|
222
|
+
buffer: str,
|
|
223
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
224
|
+
) -> Tuple[bool, int]:
|
|
225
|
+
"""invoke 开标签(闭合 ``>`` 且 name 在 tools 内)稳定后才视为工具流开始。"""
|
|
226
|
+
schema_index = _build_param_schema_index(tools) if tools else None
|
|
227
|
+
known = resolve_known_tool_names(tools, schema_index)
|
|
228
|
+
invoke_pos = find_actionable_entml_invoke_open(buffer, known_names=known)
|
|
221
229
|
if invoke_pos < 0:
|
|
222
230
|
return (False, -1)
|
|
223
231
|
return (True, invoke_pos)
|
|
224
232
|
|
|
225
|
-
def find_fncall_hold_from(
|
|
233
|
+
def find_fncall_hold_from(
|
|
234
|
+
self,
|
|
235
|
+
buffer: str,
|
|
236
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
237
|
+
) -> Optional[int]:
|
|
226
238
|
"""invoke 未稳定前 hold 前缀;legacy 外壳开标签未闭合时同样 hold。"""
|
|
227
|
-
|
|
239
|
+
schema_index = _build_param_schema_index(tools) if tools else None
|
|
240
|
+
known = resolve_known_tool_names(tools, schema_index)
|
|
241
|
+
if find_actionable_entml_invoke_open(buffer, known_names=known) >= 0:
|
|
228
242
|
return None
|
|
229
243
|
hold: Optional[int] = None
|
|
230
244
|
legacy_pos = buffer.find(self._LEGACY_WRAPPER_PREFIX)
|
|
@@ -235,28 +249,23 @@ class EntmlProtocol(ToolProtocol):
|
|
|
235
249
|
invoke_pos = buffer.find(self._TRIGGER_PREFIX)
|
|
236
250
|
if (
|
|
237
251
|
invoke_pos >= 0
|
|
238
|
-
and entml_invoke_open_may_be_streaming(
|
|
252
|
+
and entml_invoke_open_may_be_streaming(
|
|
253
|
+
buffer, invoke_pos, known_names=known
|
|
254
|
+
)
|
|
239
255
|
and (hold is None or invoke_pos < hold)
|
|
240
256
|
):
|
|
241
257
|
hold = invoke_pos
|
|
242
258
|
return hold
|
|
243
259
|
|
|
244
|
-
def _find_complete_invoke_open(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if close < 0:
|
|
254
|
-
return -1
|
|
255
|
-
attrs = buffer[pos + prefix_len : close]
|
|
256
|
-
name = extract_attr_value(attrs, "name")
|
|
257
|
-
if name and normalize_entml_name(name):
|
|
258
|
-
return pos
|
|
259
|
-
search_from = close + 1
|
|
260
|
+
def _find_complete_invoke_open(
|
|
261
|
+
self,
|
|
262
|
+
buffer: str,
|
|
263
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
264
|
+
) -> int:
|
|
265
|
+
"""返回首个可解析 ``<entml:invoke`` 起始下标;否则 ``-1``。"""
|
|
266
|
+
schema_index = _build_param_schema_index(tools) if tools else None
|
|
267
|
+
known = resolve_known_tool_names(tools, schema_index)
|
|
268
|
+
return find_actionable_entml_invoke_open(buffer, known_names=known)
|
|
260
269
|
|
|
261
270
|
def parse(
|
|
262
271
|
self,
|
|
@@ -282,6 +291,8 @@ class EntmlProtocol(ToolProtocol):
|
|
|
282
291
|
)
|
|
283
292
|
if unclosed_open_at >= 0:
|
|
284
293
|
parse_text = text[:unclosed_open_at]
|
|
294
|
+
schema_index = _build_param_schema_index(tools) if tools else None
|
|
295
|
+
known = resolve_known_tool_names(tools, schema_index)
|
|
285
296
|
tool_calls = parse_entml_tool_calls(parse_text, tools, schema_index)
|
|
286
297
|
tool_block_calls: List[Dict[str, Any]] = []
|
|
287
298
|
if include_tool_blocks:
|
|
@@ -297,8 +308,8 @@ class EntmlProtocol(ToolProtocol):
|
|
|
297
308
|
# 须在移除 invoke 之前剥离伪 history,且须晚于已解析的 ``<tool>`` 块剥离。
|
|
298
309
|
clean, _ = strip_fake_history_markup(clean)
|
|
299
310
|
if tool_calls:
|
|
300
|
-
clean = strip_actionable_entml_invoke_blocks(clean)
|
|
301
|
-
clean = strip_tool_entml_residue(clean)
|
|
311
|
+
clean = strip_actionable_entml_invoke_blocks(clean, known_names=known)
|
|
312
|
+
clean = strip_tool_entml_residue(clean, known_names=known)
|
|
302
313
|
return (clean, normalize_tool_calls(tool_calls, tools))
|
|
303
314
|
|
|
304
315
|
def parse_fragment(
|
|
@@ -313,9 +324,15 @@ class EntmlProtocol(ToolProtocol):
|
|
|
313
324
|
"""user 消息:仅去掉 ``entml:`` 前缀,不做其它剥离。"""
|
|
314
325
|
return strip_entml_from_content(content)
|
|
315
326
|
|
|
316
|
-
def clean_tool_tags(
|
|
327
|
+
def clean_tool_tags(
|
|
328
|
+
self,
|
|
329
|
+
content: str,
|
|
330
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
331
|
+
) -> str:
|
|
317
332
|
"""仅剥离工具相关标签残留,保留 thinking;并移除伪 history 块。"""
|
|
318
|
-
|
|
333
|
+
schema_index = _build_param_schema_index(tools) if tools else None
|
|
334
|
+
known = resolve_known_tool_names(tools, schema_index)
|
|
335
|
+
cleaned = strip_tool_entml_residue(content, known_names=known)
|
|
319
336
|
cleaned, _ = strip_fake_history_markup(cleaned)
|
|
320
337
|
return cleaned
|
|
321
338
|
|
|
@@ -15,6 +15,7 @@ from .entml_patterns import (
|
|
|
15
15
|
iter_actionable_entml_invoke_blocks,
|
|
16
16
|
normalize_entml_name,
|
|
17
17
|
parse_sub_tags,
|
|
18
|
+
resolve_known_tool_names,
|
|
18
19
|
split_mangled_json_param_tail,
|
|
19
20
|
synthetic_close_invoke_body,
|
|
20
21
|
)
|
|
@@ -143,7 +144,10 @@ def parse_entml_tool_calls(
|
|
|
143
144
|
schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]],
|
|
144
145
|
) -> List[Dict[str, Any]]:
|
|
145
146
|
tool_calls: List[Dict[str, Any]] = []
|
|
146
|
-
|
|
147
|
+
known = resolve_known_tool_names(tools, schema_index)
|
|
148
|
+
for _start, _end, attrs, body in iter_actionable_entml_invoke_blocks(
|
|
149
|
+
text, known_names=known
|
|
150
|
+
):
|
|
147
151
|
name = extract_attr_value(attrs, "name")
|
|
148
152
|
if not name:
|
|
149
153
|
continue
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
|
-
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
5
|
+
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
|
|
6
6
|
|
|
7
7
|
from .entml_schema import coerce_entml_parameter_value
|
|
8
8
|
|
|
@@ -116,8 +116,37 @@ def is_placeholder_invoke_name(name: str) -> bool:
|
|
|
116
116
|
return not n or "$" in n
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def
|
|
120
|
-
|
|
119
|
+
def resolve_known_tool_names(
|
|
120
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
121
|
+
schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]] = None,
|
|
122
|
+
) -> Optional[Set[str]]:
|
|
123
|
+
"""从 tools / schema_index 解析已知工具名;无约束时返回 ``None``。"""
|
|
124
|
+
if schema_index:
|
|
125
|
+
return {normalize_entml_name(str(k)) for k in schema_index.keys()}
|
|
126
|
+
names: Set[str] = set()
|
|
127
|
+
for tool in tools or []:
|
|
128
|
+
fn = tool.get("function") or {}
|
|
129
|
+
name = fn.get("name")
|
|
130
|
+
if name:
|
|
131
|
+
names.add(normalize_entml_name(str(name)))
|
|
132
|
+
return names if names else None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _name_is_known(name: str, known_names: Optional[Set[str]]) -> bool:
|
|
136
|
+
if known_names is None:
|
|
137
|
+
return True
|
|
138
|
+
if not known_names:
|
|
139
|
+
return False
|
|
140
|
+
return normalize_entml_name(name) in known_names
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def entml_invoke_open_is_actionable(
|
|
144
|
+
buffer: str,
|
|
145
|
+
pos: int,
|
|
146
|
+
*,
|
|
147
|
+
known_names: Optional[Set[str]] = None,
|
|
148
|
+
) -> bool:
|
|
149
|
+
"""``pos`` 处 ``<entml:invoke`` 是否为已闭合、含真实 name 且(若提供)在已知工具表内的开标签。"""
|
|
121
150
|
if pos < 0 or not buffer.startswith(_INVOKE_OPEN_PREFIX, pos):
|
|
122
151
|
return False
|
|
123
152
|
gt = buffer.find(">", pos + len(_INVOKE_OPEN_PREFIX))
|
|
@@ -128,29 +157,60 @@ def entml_invoke_open_is_actionable(buffer: str, pos: int) -> bool:
|
|
|
128
157
|
if not name:
|
|
129
158
|
return False
|
|
130
159
|
name = normalize_entml_name(name)
|
|
131
|
-
|
|
160
|
+
if not name or is_placeholder_invoke_name(name):
|
|
161
|
+
return False
|
|
162
|
+
return _name_is_known(name, known_names)
|
|
132
163
|
|
|
133
164
|
|
|
134
|
-
def entml_invoke_open_may_be_streaming(
|
|
135
|
-
|
|
165
|
+
def entml_invoke_open_may_be_streaming(
|
|
166
|
+
buffer: str,
|
|
167
|
+
pos: int,
|
|
168
|
+
*,
|
|
169
|
+
known_names: Optional[Set[str]] = None,
|
|
170
|
+
) -> bool:
|
|
171
|
+
"""``pos`` 处 ``<entml:invoke`` 是否仍可能长成可解析工具开标签(非 prose 提及)。"""
|
|
136
172
|
if pos < 0 or not buffer.startswith(_INVOKE_OPEN_PREFIX, pos):
|
|
137
173
|
return False
|
|
138
|
-
if entml_invoke_open_is_actionable(buffer, pos):
|
|
139
|
-
return True
|
|
140
174
|
gt = buffer.find(">", pos + len(_INVOKE_OPEN_PREFIX))
|
|
141
175
|
if gt < 0:
|
|
142
176
|
return True
|
|
143
|
-
return
|
|
177
|
+
return entml_invoke_open_is_actionable(buffer, pos, known_names=known_names)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def find_actionable_entml_invoke_open(
|
|
181
|
+
buffer: str,
|
|
182
|
+
*,
|
|
183
|
+
known_names: Optional[Set[str]] = None,
|
|
184
|
+
) -> int:
|
|
185
|
+
"""返回首个可解析 ``<entml:invoke`` 起始下标;否则 ``-1``。"""
|
|
186
|
+
if not buffer:
|
|
187
|
+
return -1
|
|
188
|
+
search_from = 0
|
|
189
|
+
prefix_len = len(_INVOKE_OPEN_PREFIX)
|
|
190
|
+
while search_from < len(buffer):
|
|
191
|
+
pos = buffer.find(_INVOKE_OPEN_PREFIX, search_from)
|
|
192
|
+
if pos < 0:
|
|
193
|
+
return -1
|
|
194
|
+
if entml_invoke_open_is_actionable(buffer, pos, known_names=known_names):
|
|
195
|
+
return pos
|
|
196
|
+
gt = buffer.find(">", pos + prefix_len)
|
|
197
|
+
search_from = (gt + 1) if gt >= 0 else (pos + prefix_len)
|
|
198
|
+
return -1
|
|
144
199
|
|
|
145
200
|
|
|
146
|
-
def _actionable_invoke_open_before_close(
|
|
201
|
+
def _actionable_invoke_open_before_close(
|
|
202
|
+
content: str,
|
|
203
|
+
close_pos: int,
|
|
204
|
+
*,
|
|
205
|
+
known_names: Optional[Set[str]] = None,
|
|
206
|
+
) -> bool:
|
|
147
207
|
"""``close_pos`` 处的 ``</entml:invoke>`` 是否闭合带真实 name 的开标签。"""
|
|
148
208
|
search = close_pos - 1
|
|
149
209
|
while search >= 0:
|
|
150
210
|
pos = content.rfind(_INVOKE_OPEN_PREFIX, 0, search + 1)
|
|
151
211
|
if pos < 0:
|
|
152
212
|
return False
|
|
153
|
-
if entml_invoke_open_is_actionable(content, pos):
|
|
213
|
+
if entml_invoke_open_is_actionable(content, pos, known_names=known_names):
|
|
154
214
|
gt = content.find(">", pos)
|
|
155
215
|
if gt < 0 or gt >= close_pos:
|
|
156
216
|
search = pos - 1
|
|
@@ -164,17 +224,26 @@ def _actionable_invoke_open_before_close(content: str, close_pos: int) -> bool:
|
|
|
164
224
|
return False
|
|
165
225
|
|
|
166
226
|
|
|
167
|
-
def _invoke_close_should_strip(
|
|
227
|
+
def _invoke_close_should_strip(
|
|
228
|
+
content: str,
|
|
229
|
+
close_pos: int,
|
|
230
|
+
*,
|
|
231
|
+
known_names: Optional[Set[str]] = None,
|
|
232
|
+
) -> bool:
|
|
168
233
|
"""是否应剥离 ``</entml:invoke>``(孤儿噪声或真实工具块残留)。"""
|
|
169
|
-
if _actionable_invoke_open_before_close(content, close_pos):
|
|
234
|
+
if _actionable_invoke_open_before_close(content, close_pos, known_names=known_names):
|
|
170
235
|
return True
|
|
171
236
|
open_pos = content.rfind(_INVOKE_OPEN_PREFIX, 0, close_pos)
|
|
172
237
|
if open_pos < 0:
|
|
173
238
|
return True
|
|
174
|
-
return entml_invoke_open_is_actionable(content, open_pos)
|
|
239
|
+
return entml_invoke_open_is_actionable(content, open_pos, known_names=known_names)
|
|
175
240
|
|
|
176
241
|
|
|
177
|
-
def iter_actionable_entml_invoke_blocks(
|
|
242
|
+
def iter_actionable_entml_invoke_blocks(
|
|
243
|
+
text: str,
|
|
244
|
+
*,
|
|
245
|
+
known_names: Optional[Set[str]] = None,
|
|
246
|
+
) -> Iterator[Tuple[int, int, str, str]]:
|
|
178
247
|
"""迭代含真实 ``name`` 的完整 ``<entml:invoke>…</entml:invoke>`` 块。"""
|
|
179
248
|
if not text:
|
|
180
249
|
return
|
|
@@ -185,8 +254,9 @@ def iter_actionable_entml_invoke_blocks(text: str) -> Iterator[Tuple[int, int, s
|
|
|
185
254
|
pos = text.find(_INVOKE_OPEN_PREFIX, search_from)
|
|
186
255
|
if pos < 0:
|
|
187
256
|
break
|
|
188
|
-
if not entml_invoke_open_is_actionable(text, pos):
|
|
189
|
-
|
|
257
|
+
if not entml_invoke_open_is_actionable(text, pos, known_names=known_names):
|
|
258
|
+
gt = text.find(">", pos + prefix_len)
|
|
259
|
+
search_from = (gt + 1) if gt >= 0 else (pos + prefix_len)
|
|
190
260
|
continue
|
|
191
261
|
gt = text.find(">", pos + prefix_len)
|
|
192
262
|
if gt < 0:
|
|
@@ -200,30 +270,40 @@ def iter_actionable_entml_invoke_blocks(text: str) -> Iterator[Tuple[int, int, s
|
|
|
200
270
|
search_from = close + close_len
|
|
201
271
|
|
|
202
272
|
|
|
203
|
-
def strip_actionable_entml_invoke_blocks(
|
|
273
|
+
def strip_actionable_entml_invoke_blocks(
|
|
274
|
+
text: str,
|
|
275
|
+
*,
|
|
276
|
+
known_names: Optional[Set[str]] = None,
|
|
277
|
+
) -> str:
|
|
204
278
|
"""仅剥离含真实 ``name`` 的 invoke 块;保留 prose ``<entml:invoke>`` 提及。"""
|
|
205
279
|
if not text:
|
|
206
280
|
return text
|
|
207
281
|
parts: List[str] = []
|
|
208
282
|
last = 0
|
|
209
|
-
for start, end, _attrs, _body in iter_actionable_entml_invoke_blocks(
|
|
283
|
+
for start, end, _attrs, _body in iter_actionable_entml_invoke_blocks(
|
|
284
|
+
text, known_names=known_names
|
|
285
|
+
):
|
|
210
286
|
parts.append(text[last:start])
|
|
211
287
|
last = end
|
|
212
288
|
parts.append(text[last:])
|
|
213
289
|
return "".join(parts)
|
|
214
290
|
|
|
215
291
|
|
|
216
|
-
def _strip_orphan_invoke_tags(
|
|
292
|
+
def _strip_orphan_invoke_tags(
|
|
293
|
+
content: str,
|
|
294
|
+
*,
|
|
295
|
+
known_names: Optional[Set[str]] = None,
|
|
296
|
+
) -> str:
|
|
217
297
|
"""仅剥离带真实 name 的 invoke 孤儿标签;保留 prose 中的 ``<entml:invoke>`` 提及。"""
|
|
218
298
|
pattern = re.compile(r"</?entml:invoke\b[^>]*/?>", re.DOTALL)
|
|
219
299
|
|
|
220
300
|
def repl(match: re.Match[str]) -> str:
|
|
221
301
|
tag = match.group(0)
|
|
222
302
|
if tag.startswith("</"):
|
|
223
|
-
if _invoke_close_should_strip(content, match.start()):
|
|
303
|
+
if _invoke_close_should_strip(content, match.start(), known_names=known_names):
|
|
224
304
|
return ""
|
|
225
305
|
return tag
|
|
226
|
-
if entml_invoke_open_is_actionable(content, match.start()):
|
|
306
|
+
if entml_invoke_open_is_actionable(content, match.start(), known_names=known_names):
|
|
227
307
|
return ""
|
|
228
308
|
return tag
|
|
229
309
|
|
|
@@ -446,13 +526,17 @@ def strip_legacy_function_calls_wrapper(text: str) -> str:
|
|
|
446
526
|
return out
|
|
447
527
|
|
|
448
528
|
|
|
449
|
-
def strip_tool_entml_residue(
|
|
529
|
+
def strip_tool_entml_residue(
|
|
530
|
+
content: str,
|
|
531
|
+
*,
|
|
532
|
+
known_names: Optional[Set[str]] = None,
|
|
533
|
+
) -> str:
|
|
450
534
|
"""剥离工具相关 entml 标签残留,保留 thinking 与非工具 prose 提及。"""
|
|
451
535
|
if not content:
|
|
452
536
|
return content
|
|
453
537
|
cleaned = _TOOL_WRAPPER_PAIR_RE.sub("", content)
|
|
454
|
-
cleaned = strip_actionable_entml_invoke_blocks(cleaned)
|
|
455
|
-
cleaned = _strip_orphan_invoke_tags(cleaned)
|
|
538
|
+
cleaned = strip_actionable_entml_invoke_blocks(cleaned, known_names=known_names)
|
|
539
|
+
cleaned = _strip_orphan_invoke_tags(cleaned, known_names=known_names)
|
|
456
540
|
cleaned = _strip_orphan_non_invoke_tool_tags(cleaned)
|
|
457
541
|
cleaned = _EMPTY_FENCE_RE.sub("", cleaned)
|
|
458
542
|
cleaned = _FENCE_ONLY_LINE_RE.sub("", cleaned)
|
{echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_stream/body.py
RENAMED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
5
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
6
6
|
|
|
7
7
|
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
8
8
|
BARE_INVOKE_CHILD_OPEN_RE,
|
|
@@ -59,7 +59,11 @@ def _strip_incomplete_child_close_suffix(value: str, key: str) -> str:
|
|
|
59
59
|
return value
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def split_invoke_open(
|
|
62
|
+
def split_invoke_open(
|
|
63
|
+
buffer: str,
|
|
64
|
+
*,
|
|
65
|
+
known_names: Optional[Set[str]] = None,
|
|
66
|
+
) -> Optional[Tuple[str, int]]:
|
|
63
67
|
"""返回当前正在流式写入的 invoke(最后一个有效开标签)。"""
|
|
64
68
|
search_from = 0
|
|
65
69
|
prefix_len = len(_INVOKE_OPEN_PREFIX)
|
|
@@ -68,6 +72,14 @@ def split_invoke_open(buffer: str) -> Optional[Tuple[str, int]]:
|
|
|
68
72
|
pos = buffer.find(_INVOKE_OPEN_PREFIX, search_from)
|
|
69
73
|
if pos < 0:
|
|
70
74
|
break
|
|
75
|
+
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
76
|
+
entml_invoke_open_is_actionable,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if not entml_invoke_open_is_actionable(buffer, pos, known_names=known_names):
|
|
80
|
+
gt = buffer.find(">", pos + prefix_len)
|
|
81
|
+
search_from = (gt + 1) if gt >= 0 else (pos + prefix_len)
|
|
82
|
+
continue
|
|
71
83
|
gt = buffer.find(">", pos + prefix_len)
|
|
72
84
|
if gt < 0:
|
|
73
85
|
break
|
{echotools-2.3.88 → echotools-2.3.89}/src/echotools/exec/fncall/protocols/entml_think/hold.py
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import List, Optional, Tuple
|
|
4
|
+
from typing import List, Optional, Set, Tuple
|
|
5
5
|
|
|
6
6
|
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
7
7
|
entml_invoke_open_may_be_streaming,
|
|
8
|
-
|
|
9
|
-
normalize_entml_name,
|
|
8
|
+
find_actionable_entml_invoke_open,
|
|
10
9
|
)
|
|
11
10
|
|
|
12
11
|
THINKING_BLOCK_RE = re.compile(
|
|
@@ -39,24 +38,13 @@ _LEADING_HOLD_PREFIXES = _AMBIGUOUS_ENTML_PREFIXES + _ORPHAN_CLOSE_PREFIXES + (
|
|
|
39
38
|
)
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
def find_complete_entml_invoke_open(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
pos = buffer.find(_INVOKE_PREFIX, search_from)
|
|
50
|
-
if pos < 0:
|
|
51
|
-
return -1
|
|
52
|
-
close = buffer.find(">", pos + prefix_len)
|
|
53
|
-
if close < 0:
|
|
54
|
-
return -1
|
|
55
|
-
attrs = buffer[pos + prefix_len : close]
|
|
56
|
-
name = extract_attr_value(attrs, "name")
|
|
57
|
-
if name and normalize_entml_name(name):
|
|
58
|
-
return pos
|
|
59
|
-
search_from = close + 1
|
|
41
|
+
def find_complete_entml_invoke_open(
|
|
42
|
+
buffer: str,
|
|
43
|
+
*,
|
|
44
|
+
known_names: Optional[set[str]] = None,
|
|
45
|
+
) -> int:
|
|
46
|
+
"""返回首个可解析 ``<entml:invoke`` 起始下标;否则 ``-1``。"""
|
|
47
|
+
return find_actionable_entml_invoke_open(buffer, known_names=known_names)
|
|
60
48
|
|
|
61
49
|
|
|
62
50
|
|
|
@@ -153,13 +141,19 @@ def _hold_thinking_open_prefixes(
|
|
|
153
141
|
return safe, ""
|
|
154
142
|
|
|
155
143
|
|
|
156
|
-
def _hold_ambiguous_tool_markup(
|
|
144
|
+
def _hold_ambiguous_tool_markup(
|
|
145
|
+
buffer: str,
|
|
146
|
+
*,
|
|
147
|
+
known_names: Optional[Set[str]] = None,
|
|
148
|
+
) -> Tuple[str, str]:
|
|
157
149
|
"""thinking 块内:hold 可能长成真实工具调用的 markup(非 prose ``<entml:invoke>`` 提及)。"""
|
|
158
150
|
if not buffer:
|
|
159
151
|
return "", ""
|
|
160
152
|
hold_from: Optional[int] = None
|
|
161
153
|
invoke_pos = buffer.find(_INVOKE_PREFIX)
|
|
162
|
-
if invoke_pos >= 0 and entml_invoke_open_may_be_streaming(
|
|
154
|
+
if invoke_pos >= 0 and entml_invoke_open_may_be_streaming(
|
|
155
|
+
buffer, invoke_pos, known_names=known_names
|
|
156
|
+
):
|
|
163
157
|
if hold_from is None or invoke_pos < hold_from:
|
|
164
158
|
hold_from = invoke_pos
|
|
165
159
|
for prefix in _AMBIGUOUS_ENTML_PREFIXES:
|