echotools 2.3.60__tar.gz → 2.3.61__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.60 → echotools-2.3.61}/PKG-INFO +1 -1
- {echotools-2.3.60 → echotools-2.3.61}/pyproject.toml +1 -1
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/parse.py +152 -34
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_thinking_parse.py +40 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_fncall_stream_thinking.py +33 -0
- {echotools-2.3.60 → echotools-2.3.61}/README.md +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/docs/README.md +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/setup.cfg +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/compat.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/parsers/stream.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_stream_json.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/ui_console.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/ui_io.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/ui_log.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/ui_platform.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/ui_text.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uicore/ui_types.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uilayout/ui_table.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools/version.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools.egg-info/SOURCES.txt +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/bench_import.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/conftest.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/fixtures/__init__.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/fixtures/simulated_llm_tool_responses.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_adversarial_stream_splits.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_completion.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_config.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_console.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_agent_output_formats.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_bare_invoke_format.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_parser_comprehensive.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_protocol.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_stream_json.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_tool_parse_leak.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_tool_pipeline.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_errors.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_events.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_facade.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_ids.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_imports.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_io.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_keys.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_logger.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_merge.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_model_branch_matrix.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_simulated_llm_tool_parse.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_stream_partial_json_parity.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_stream_realtime_parse.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_usage.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_web.py +0 -0
- {echotools-2.3.60 → echotools-2.3.61}/src/tests/test_web_broker.py +0 -0
{echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/parse.py
RENAMED
|
@@ -16,6 +16,7 @@ THINKING_BLOCK_RE = re.compile(
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
_THINKING_OPEN_PREFIX = "<entml:thinking"
|
|
19
|
+
_PLAIN_THINKING_OPEN_PREFIX = "<thinking"
|
|
19
20
|
_THINKING_CLOSE = "</entml:thinking>"
|
|
20
21
|
_FAULT_THINKING_CLOSE = "</thinking>"
|
|
21
22
|
# 与 thinking 共享 `<entml:` 前缀的其它标签:歧义 holdback 应交由工具流式状态机处理
|
|
@@ -116,6 +117,73 @@ def find_ambiguous_entml_tool_prefix(buffer: str) -> int:
|
|
|
116
117
|
return earliest
|
|
117
118
|
|
|
118
119
|
|
|
120
|
+
def _find_plain_thinking_open(text: str, start: int = 0) -> int:
|
|
121
|
+
"""返回 plain ``<thinking>`` 开标签起始下标(``<entml:thinking>`` 内不含此子串)。"""
|
|
122
|
+
return text.find(_PLAIN_THINKING_OPEN_PREFIX, start)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _find_earliest_thinking_open(text: str, start: int = 0) -> Tuple[int, bool]:
|
|
126
|
+
"""返回 ``(开标签下标, 是否 plain)``;无则 ``(-1, False)``。"""
|
|
127
|
+
entml_at = text.find(_THINKING_OPEN_PREFIX, start)
|
|
128
|
+
plain_at = _find_plain_thinking_open(text, start)
|
|
129
|
+
if entml_at < 0 and plain_at < 0:
|
|
130
|
+
return -1, False
|
|
131
|
+
if entml_at < 0:
|
|
132
|
+
return plain_at, True
|
|
133
|
+
if plain_at < 0:
|
|
134
|
+
return entml_at, False
|
|
135
|
+
if plain_at < entml_at:
|
|
136
|
+
return plain_at, True
|
|
137
|
+
return entml_at, False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _find_thinking_close(
|
|
141
|
+
text: str,
|
|
142
|
+
body_start: int,
|
|
143
|
+
*,
|
|
144
|
+
opened_plain: bool,
|
|
145
|
+
) -> Tuple[int, int]:
|
|
146
|
+
"""返回 ``(闭标签下标, 闭标签长度)``;未找到则 ``(-1, 0)``。"""
|
|
147
|
+
if opened_plain:
|
|
148
|
+
entml_close = text.find(_THINKING_CLOSE, body_start)
|
|
149
|
+
fault_close = text.find(_FAULT_THINKING_CLOSE, body_start)
|
|
150
|
+
candidates: List[Tuple[int, int]] = []
|
|
151
|
+
if entml_close >= 0:
|
|
152
|
+
candidates.append((entml_close, len(_THINKING_CLOSE)))
|
|
153
|
+
if fault_close >= 0:
|
|
154
|
+
candidates.append((fault_close, len(_FAULT_THINKING_CLOSE)))
|
|
155
|
+
if not candidates:
|
|
156
|
+
return -1, 0
|
|
157
|
+
close_at, close_len = min(candidates, key=lambda item: item[0])
|
|
158
|
+
return close_at, close_len
|
|
159
|
+
|
|
160
|
+
entml_close = text.find(_THINKING_CLOSE, body_start)
|
|
161
|
+
fault_close = text.find(_FAULT_THINKING_CLOSE, body_start)
|
|
162
|
+
if entml_close >= 0 and (fault_close < 0 or entml_close <= fault_close):
|
|
163
|
+
return entml_close, len(_THINKING_CLOSE)
|
|
164
|
+
if fault_close < 0:
|
|
165
|
+
return -1, 0
|
|
166
|
+
|
|
167
|
+
after_fault = fault_close + len(_FAULT_THINKING_CLOSE)
|
|
168
|
+
invoke_rel = find_complete_entml_invoke_open(text[after_fault:])
|
|
169
|
+
entml_after = text.find(_THINKING_CLOSE, after_fault)
|
|
170
|
+
if invoke_rel >= 0:
|
|
171
|
+
invoke_abs = after_fault + invoke_rel
|
|
172
|
+
if entml_after < 0 or invoke_abs < entml_after:
|
|
173
|
+
return fault_close, len(_FAULT_THINKING_CLOSE)
|
|
174
|
+
if entml_after >= 0:
|
|
175
|
+
return entml_after, len(_THINKING_CLOSE)
|
|
176
|
+
return -1, 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _hold_thinking_open_prefixes(buffer: str) -> Tuple[str, str]:
|
|
180
|
+
"""hold ``<entml:thinking`` 或 plain ``<thinking`` 的真前缀。"""
|
|
181
|
+
safe, hold = _hold_prefix(buffer, _THINKING_OPEN_PREFIX)
|
|
182
|
+
if hold:
|
|
183
|
+
return safe, hold
|
|
184
|
+
return _hold_prefix(safe, _PLAIN_THINKING_OPEN_PREFIX)
|
|
185
|
+
|
|
186
|
+
|
|
119
187
|
def _hold_ambiguous_tool_markup(buffer: str) -> Tuple[str, str]:
|
|
120
188
|
"""thinking 块内:hold 可能长成真实工具调用的 markup(非 prose ``<entml:invoke>`` 提及)。"""
|
|
121
189
|
if not buffer:
|
|
@@ -144,67 +212,94 @@ def _hold_ambiguous_tool_markup(buffer: str) -> Tuple[str, str]:
|
|
|
144
212
|
|
|
145
213
|
|
|
146
214
|
def has_unclosed_entml_thinking(text: str) -> bool:
|
|
147
|
-
"""buffer 中是否存在尚未闭合的
|
|
215
|
+
"""buffer 中是否存在尚未闭合的 thinking 块(含开标签未收齐)。"""
|
|
148
216
|
if not text:
|
|
149
217
|
return False
|
|
150
218
|
i = 0
|
|
151
219
|
while i < len(text):
|
|
152
|
-
open_at = text
|
|
220
|
+
open_at, opened_plain = _find_earliest_thinking_open(text, i)
|
|
153
221
|
if open_at < 0:
|
|
154
222
|
break
|
|
155
223
|
gt = text.find(">", open_at)
|
|
156
224
|
if gt < 0:
|
|
157
225
|
return True
|
|
158
|
-
|
|
226
|
+
body_start = gt + 1
|
|
227
|
+
close_at, close_len = _find_thinking_close(
|
|
228
|
+
text, body_start, opened_plain=opened_plain
|
|
229
|
+
)
|
|
159
230
|
if close_at < 0:
|
|
160
231
|
return True
|
|
161
|
-
i = close_at +
|
|
162
|
-
max_keep = len(_THINKING_OPEN_PREFIX) - 1
|
|
232
|
+
i = close_at + close_len
|
|
233
|
+
max_keep = max(len(_THINKING_OPEN_PREFIX), len(_PLAIN_THINKING_OPEN_PREFIX)) - 1
|
|
163
234
|
check_len = min(len(text), max_keep)
|
|
164
235
|
for length in range(check_len, 0, -1):
|
|
165
236
|
suffix = text[-length:]
|
|
166
|
-
if
|
|
167
|
-
_THINKING_OPEN_PREFIX.startswith(suffix)
|
|
237
|
+
if (
|
|
238
|
+
_THINKING_OPEN_PREFIX.startswith(suffix)
|
|
239
|
+
and suffix != _THINKING_OPEN_PREFIX
|
|
168
240
|
):
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
241
|
+
if any(other.startswith(suffix) for other in _AMBIGUOUS_ENTML_PREFIXES):
|
|
242
|
+
continue
|
|
243
|
+
return True
|
|
244
|
+
if (
|
|
245
|
+
_PLAIN_THINKING_OPEN_PREFIX.startswith(suffix)
|
|
246
|
+
and suffix != _PLAIN_THINKING_OPEN_PREFIX
|
|
247
|
+
):
|
|
248
|
+
return True
|
|
174
249
|
return False
|
|
175
250
|
|
|
176
251
|
|
|
177
252
|
def invoke_index_inside_unclosed_thinking(text: str, invoke_at: int) -> bool:
|
|
178
|
-
"""``invoke_at``
|
|
179
|
-
|
|
180
|
-
已出现容错 ``</thinking>`` 且 invoke 在其后的段落,视为 thinking 外。
|
|
181
|
-
"""
|
|
253
|
+
"""``invoke_at`` 是否落在尚未闭合的 thinking 块内。"""
|
|
182
254
|
if invoke_at < 0:
|
|
183
255
|
return False
|
|
184
|
-
think_open = text
|
|
256
|
+
think_open, opened_plain = _find_earliest_thinking_open(text)
|
|
185
257
|
if think_open < 0 or invoke_at <= think_open:
|
|
186
258
|
return False
|
|
187
|
-
|
|
188
|
-
if
|
|
189
|
-
return
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
259
|
+
gt = text.find(">", think_open)
|
|
260
|
+
if gt < 0:
|
|
261
|
+
return True
|
|
262
|
+
body_start = gt + 1
|
|
263
|
+
close_at, close_len = _find_thinking_close(
|
|
264
|
+
text, body_start, opened_plain=opened_plain
|
|
265
|
+
)
|
|
266
|
+
if close_at < 0:
|
|
267
|
+
return True
|
|
268
|
+
return invoke_at < close_at + close_len
|
|
194
269
|
|
|
195
270
|
|
|
196
271
|
def split_entml_thinking(text: str) -> Tuple[str, str]:
|
|
197
|
-
"""从文本中剥离
|
|
272
|
+
"""从文本中剥离 thinking 块,返回 (正文, 思考链拼接)。"""
|
|
198
273
|
if not text:
|
|
199
274
|
return "", ""
|
|
200
275
|
|
|
201
276
|
parts: List[str] = []
|
|
277
|
+
clean_parts: List[str] = []
|
|
278
|
+
i = 0
|
|
279
|
+
while i < len(text):
|
|
280
|
+
open_at, opened_plain = _find_earliest_thinking_open(text, i)
|
|
281
|
+
if open_at < 0:
|
|
282
|
+
clean_parts.append(text[i:])
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
clean_parts.append(text[i:open_at])
|
|
286
|
+
gt = text.find(">", open_at)
|
|
287
|
+
if gt < 0:
|
|
288
|
+
clean_parts.append(text[open_at:])
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
body_start = gt + 1
|
|
292
|
+
close_at, close_len = _find_thinking_close(
|
|
293
|
+
text, body_start, opened_plain=opened_plain
|
|
294
|
+
)
|
|
295
|
+
if close_at < 0:
|
|
296
|
+
clean_parts.append(text[open_at:])
|
|
297
|
+
break
|
|
202
298
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return ""
|
|
299
|
+
parts.append(text[body_start:close_at])
|
|
300
|
+
i = close_at + close_len
|
|
206
301
|
|
|
207
|
-
clean =
|
|
302
|
+
clean = "".join(clean_parts)
|
|
208
303
|
thinking = "\n".join(part.strip() for part in parts if part.strip())
|
|
209
304
|
return strip_orphan_thinking_close_prefix(clean.strip()), thinking
|
|
210
305
|
|
|
@@ -244,6 +339,7 @@ class EntmlThinkingStreamFilter:
|
|
|
244
339
|
def __init__(self) -> None:
|
|
245
340
|
self._pending = ""
|
|
246
341
|
self._in_block = False
|
|
342
|
+
self._opened_plain = False
|
|
247
343
|
# 当前 thinking 块是否已输出过任何内容(用于首片去前导空白)
|
|
248
344
|
self._thinking_started = False
|
|
249
345
|
# 见到 ``</thinking>`` 后暂存,仅当后续出现 invoke 才视为闭合;否则当纯文本
|
|
@@ -253,20 +349,21 @@ class EntmlThinkingStreamFilter:
|
|
|
253
349
|
def clear_invoke_handoff_state(self) -> None:
|
|
254
350
|
"""invoke 已交给正文/工具解析后,结束 thinking / fault 观察状态。"""
|
|
255
351
|
self._in_block = False
|
|
352
|
+
self._opened_plain = False
|
|
256
353
|
self._fault_watch = False
|
|
257
354
|
self._fault_buffer = ""
|
|
258
355
|
self._thinking_started = False
|
|
259
356
|
|
|
260
357
|
def in_open_thinking(self) -> bool:
|
|
261
|
-
"""
|
|
358
|
+
"""思考块已开始且尚未闭合(含开/闭标签分片 hold)。"""
|
|
262
359
|
if self._in_block or self._fault_watch:
|
|
263
360
|
return True
|
|
264
|
-
open_at = self._pending
|
|
361
|
+
open_at, _ = _find_earliest_thinking_open(self._pending)
|
|
265
362
|
if open_at >= 0:
|
|
266
363
|
gt = self._pending.find(">", open_at)
|
|
267
364
|
if gt < 0:
|
|
268
365
|
return True
|
|
269
|
-
_, hold =
|
|
366
|
+
_, hold = _hold_thinking_open_prefixes(self._pending)
|
|
270
367
|
if hold:
|
|
271
368
|
return True
|
|
272
369
|
return False
|
|
@@ -279,6 +376,7 @@ class EntmlThinkingStreamFilter:
|
|
|
279
376
|
if invoke_at < len(_FAULT_THINKING_CLOSE):
|
|
280
377
|
return False
|
|
281
378
|
self._in_block = False
|
|
379
|
+
self._opened_plain = False
|
|
282
380
|
self._fault_watch = False
|
|
283
381
|
self._thinking_started = False
|
|
284
382
|
self._pending = self._fault_buffer[len(_FAULT_THINKING_CLOSE) :]
|
|
@@ -296,6 +394,7 @@ class EntmlThinkingStreamFilter:
|
|
|
296
394
|
if emitted:
|
|
297
395
|
out.append(("thinking", emitted))
|
|
298
396
|
self._in_block = False
|
|
397
|
+
self._opened_plain = False
|
|
299
398
|
self._fault_watch = False
|
|
300
399
|
self._thinking_started = False
|
|
301
400
|
self._pending = self._fault_buffer[close_at + len(_THINKING_CLOSE) :]
|
|
@@ -323,12 +422,15 @@ class EntmlThinkingStreamFilter:
|
|
|
323
422
|
out.append(("thinking", emitted))
|
|
324
423
|
out.append(("content", invoke_block))
|
|
325
424
|
self._in_block = False
|
|
425
|
+
self._opened_plain = False
|
|
326
426
|
self._fault_watch = False
|
|
327
427
|
self._fault_buffer = ""
|
|
328
428
|
self._thinking_started = False
|
|
329
429
|
rest = after.lstrip()
|
|
330
430
|
if rest.startswith(_THINKING_CLOSE):
|
|
331
431
|
rest = rest[len(_THINKING_CLOSE) :].lstrip()
|
|
432
|
+
if rest.startswith(_FAULT_THINKING_CLOSE):
|
|
433
|
+
rest = rest[len(_FAULT_THINKING_CLOSE) :].lstrip()
|
|
332
434
|
self._pending = rest
|
|
333
435
|
return True
|
|
334
436
|
return False
|
|
@@ -361,6 +463,7 @@ class EntmlThinkingStreamFilter:
|
|
|
361
463
|
if emitted:
|
|
362
464
|
out.append(("thinking", emitted))
|
|
363
465
|
self._in_block = False
|
|
466
|
+
self._opened_plain = False
|
|
364
467
|
self._fault_watch = False
|
|
365
468
|
self._fault_buffer = ""
|
|
366
469
|
self._thinking_started = False
|
|
@@ -373,6 +476,17 @@ class EntmlThinkingStreamFilter:
|
|
|
373
476
|
fault_at = self._pending.find(_FAULT_THINKING_CLOSE)
|
|
374
477
|
if fault_at >= 0:
|
|
375
478
|
before = self._pending[:fault_at]
|
|
479
|
+
if self._opened_plain:
|
|
480
|
+
emitted = self._emit_thinking_piece(before)
|
|
481
|
+
if emitted:
|
|
482
|
+
out.append(("thinking", emitted))
|
|
483
|
+
self._in_block = False
|
|
484
|
+
self._opened_plain = False
|
|
485
|
+
self._fault_watch = False
|
|
486
|
+
self._fault_buffer = ""
|
|
487
|
+
self._thinking_started = False
|
|
488
|
+
self._pending = self._pending[fault_at + len(_FAULT_THINKING_CLOSE) :]
|
|
489
|
+
return True
|
|
376
490
|
emitted = self._emit_thinking_piece(before)
|
|
377
491
|
if emitted:
|
|
378
492
|
out.append(("thinking", emitted))
|
|
@@ -402,9 +516,9 @@ class EntmlThinkingStreamFilter:
|
|
|
402
516
|
|
|
403
517
|
def _feed_before_block(self, out: List[Tuple[str, str]]) -> bool:
|
|
404
518
|
"""开标签之前或尚未进入块;返回 False 表示应退出 feed 主循环。"""
|
|
405
|
-
open_at = self._pending
|
|
519
|
+
open_at, opened_plain = _find_earliest_thinking_open(self._pending)
|
|
406
520
|
if open_at < 0:
|
|
407
|
-
safe, self._pending =
|
|
521
|
+
safe, self._pending = _hold_thinking_open_prefixes(self._pending)
|
|
408
522
|
if safe:
|
|
409
523
|
out.append(("content", safe))
|
|
410
524
|
return False
|
|
@@ -418,6 +532,7 @@ class EntmlThinkingStreamFilter:
|
|
|
418
532
|
return False
|
|
419
533
|
|
|
420
534
|
self._in_block = True
|
|
535
|
+
self._opened_plain = opened_plain
|
|
421
536
|
self._thinking_started = False
|
|
422
537
|
self._pending = self._pending[gt + 1 :]
|
|
423
538
|
return True
|
|
@@ -471,6 +586,7 @@ class EntmlThinkingStreamFilter:
|
|
|
471
586
|
self._fault_buffer = ""
|
|
472
587
|
self._fault_watch = False
|
|
473
588
|
self._in_block = False
|
|
589
|
+
self._opened_plain = False
|
|
474
590
|
self._thinking_started = False
|
|
475
591
|
if self._in_block:
|
|
476
592
|
if not self._try_emit_actionable_invoke_from_in_block(out):
|
|
@@ -479,6 +595,7 @@ class EntmlThinkingStreamFilter:
|
|
|
479
595
|
out.append(("thinking", emitted))
|
|
480
596
|
self._pending = ""
|
|
481
597
|
self._in_block = False
|
|
598
|
+
self._opened_plain = False
|
|
482
599
|
self._thinking_started = False
|
|
483
600
|
if self._pending:
|
|
484
601
|
content, thinking = split_entml_thinking(self._pending)
|
|
@@ -490,6 +607,7 @@ class EntmlThinkingStreamFilter:
|
|
|
490
607
|
out.append(("content", self._pending))
|
|
491
608
|
self._pending = ""
|
|
492
609
|
self._in_block = False
|
|
610
|
+
self._opened_plain = False
|
|
493
611
|
self._fault_watch = False
|
|
494
612
|
self._fault_buffer = ""
|
|
495
613
|
self._thinking_started = False
|
|
@@ -92,3 +92,43 @@ def test_entml_thinking_hold_close_prefix() -> None:
|
|
|
92
92
|
content = "".join(t for k, t in parts if k == "content")
|
|
93
93
|
assert thinking == "ab"
|
|
94
94
|
assert content == "cd"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_plain_thinking_open_entml_close_batch() -> None:
|
|
98
|
+
text = "<thinking>\nplan step\n</entml:thinking>\nanswer"
|
|
99
|
+
content, thinking = split_entml_thinking(text)
|
|
100
|
+
assert thinking == "plan step"
|
|
101
|
+
assert "answer" in content
|
|
102
|
+
assert "<thinking>" not in content
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_plain_thinking_open_fault_close_batch() -> None:
|
|
106
|
+
text = "<thinking>\nplan step\n</thinking>\nanswer"
|
|
107
|
+
content, thinking = split_entml_thinking(text)
|
|
108
|
+
assert thinking == "plan step"
|
|
109
|
+
assert "answer" in content
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_plain_thinking_stream_entml_close() -> None:
|
|
113
|
+
filt = EntmlThinkingStreamFilter()
|
|
114
|
+
parts = []
|
|
115
|
+
for chunk in ["<thinking>\nplan", "\n</entml:thinking>\n", "hello"]:
|
|
116
|
+
parts.extend(filt.feed(chunk))
|
|
117
|
+
parts.extend(filt.finalize())
|
|
118
|
+
thinking = "".join(t for k, t in parts if k == "thinking")
|
|
119
|
+
content = "".join(t for k, t in parts if k == "content")
|
|
120
|
+
assert thinking.strip() == "plan"
|
|
121
|
+
assert "hello" in content
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_plain_thinking_stream_fault_close() -> None:
|
|
125
|
+
filt = EntmlThinkingStreamFilter()
|
|
126
|
+
parts = []
|
|
127
|
+
for chunk in ["<thinking>\nplan\n</thinking>\n", "hello"]:
|
|
128
|
+
parts.extend(filt.feed(chunk))
|
|
129
|
+
parts.extend(filt.finalize())
|
|
130
|
+
thinking = "".join(t for k, t in parts if k == "thinking")
|
|
131
|
+
content = "".join(t for k, t in parts if k == "content")
|
|
132
|
+
assert thinking.strip() == "plan"
|
|
133
|
+
assert "hello" in content
|
|
134
|
+
assert not filt.in_open_thinking()
|
|
@@ -295,3 +295,36 @@ def test_fault_thinking_close_stays_open_while_waiting_for_invoke() -> None:
|
|
|
295
295
|
filt.feed(INVOKE)
|
|
296
296
|
kinds = [k for k, _ in filt.feed("")]
|
|
297
297
|
assert "content" in kinds or filt.in_open_thinking() is False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_plain_thinking_open_then_invoke_stream() -> None:
|
|
301
|
+
"""plain ``<thinking>`` 开标签 + ``</thinking>`` 闭合后应能解析工具。"""
|
|
302
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
303
|
+
text = f"<thinking>\nplan\n</thinking>\n{INVOKE}"
|
|
304
|
+
for i in range(0, len(text), 7):
|
|
305
|
+
parser.feed(text[i : i + 7])
|
|
306
|
+
clean, calls = parser.finalize()
|
|
307
|
+
assert len(calls) == 1
|
|
308
|
+
assert calls[0]["function"]["name"] == "get_weather"
|
|
309
|
+
assert "plan" in parser.partial_thinking
|
|
310
|
+
assert "Hangzhou" in clean or len(calls) == 1
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_plain_thinking_open_entml_close_then_invoke() -> None:
|
|
314
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
315
|
+
text = f"<thinking>\nplan\n</entml:thinking>\n{INVOKE}"
|
|
316
|
+
parser.feed(text)
|
|
317
|
+
clean, calls = parser.finalize()
|
|
318
|
+
assert len(calls) == 1
|
|
319
|
+
assert "plan" in parser.partial_thinking
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def test_entml_open_fault_close_then_invoke_still_works(chunk: int = 3) -> None:
|
|
323
|
+
"""``<entml:thinking>`` + ``</thinking>`` + 工具:fault 闭合。"""
|
|
324
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
325
|
+
text = f"<entml:thinking>\nplan\n</thinking>\n{INVOKE}"
|
|
326
|
+
for i in range(0, len(text), chunk):
|
|
327
|
+
parser.feed(text[i : i + chunk])
|
|
328
|
+
clean, calls = parser.finalize()
|
|
329
|
+
assert len(calls) == 1
|
|
330
|
+
assert "plan" in parser.partial_thinking
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_stream_json.py
RENAMED
|
File without changes
|
{echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/__init__.py
RENAMED
|
File without changes
|
{echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/core.py
RENAMED
|
File without changes
|
{echotools-2.3.60 → echotools-2.3.61}/src/echotools/exec/fncall/protocols/entml_think/hist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|