echotools 2.3.39__tar.gz → 2.3.40__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.39 → echotools-2.3.40}/PKG-INFO +1 -1
- {echotools-2.3.39 → echotools-2.3.40}/pyproject.toml +1 -1
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/parsers/stream.py +48 -2
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +39 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools.egg-info/SOURCES.txt +1 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_entml_thinking_parse.py +6 -0
- echotools-2.3.40/src/tests/test_fncall_stream_thinking.py +110 -0
- {echotools-2.3.39 → echotools-2.3.40}/README.md +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/docs/README.md +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/setup.cfg +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/compat.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_sanitize.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_thinking.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_thinking_history.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools/version.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/bench_import.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/conftest.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_completion.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_config.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_console.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_entml_protocol.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_errors.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_events.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_facade.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_ids.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_imports.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_io.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_keys.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_logger.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_merge.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_usage.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_web.py +0 -0
- {echotools-2.3.39 → echotools-2.3.40}/src/tests/test_web_broker.py +0 -0
|
@@ -136,12 +136,59 @@ class FncallStreamParser:
|
|
|
136
136
|
|
|
137
137
|
return buffer, ""
|
|
138
138
|
|
|
139
|
+
def _append_content_text(self, text: str) -> None:
|
|
140
|
+
if text:
|
|
141
|
+
self._text_parts.append(text)
|
|
142
|
+
|
|
143
|
+
def _feed_content_waiting(self, text: str) -> None:
|
|
144
|
+
"""thinking 已闭合后,对可见正文做 invoke 检测(不再经过 thinking 过滤器)。"""
|
|
145
|
+
if not text:
|
|
146
|
+
return
|
|
147
|
+
trigger_tags = self._protocol.get_trigger_tags()
|
|
148
|
+
found, pos = self._protocol.detect_start(text)
|
|
149
|
+
if not found:
|
|
150
|
+
safe, remain = self._split_safe_text(text, trigger_tags)
|
|
151
|
+
if safe:
|
|
152
|
+
self._append_content_text(safe)
|
|
153
|
+
self._waiting_tail = remain
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
if pos > 0:
|
|
157
|
+
self._append_content_text(text[:pos])
|
|
158
|
+
|
|
159
|
+
self._fncall_buf = text[pos:]
|
|
160
|
+
self._detected = True
|
|
161
|
+
self._state = self.IN_FUNCTION_CALLS
|
|
162
|
+
if self._is_call_closed():
|
|
163
|
+
self._state = self.DONE
|
|
164
|
+
|
|
165
|
+
def _feed_waiting_thinking_plain(self, combined: str) -> None:
|
|
166
|
+
"""未闭合 thinking 阶段:块内一律按纯文本进 thinking,不检测 invoke。"""
|
|
167
|
+
assert self._thinking_filter is not None
|
|
168
|
+
for kind, part in self._thinking_filter.feed(combined):
|
|
169
|
+
if kind == "thinking":
|
|
170
|
+
self._thinking_parts.append(part)
|
|
171
|
+
elif part:
|
|
172
|
+
self._feed_content_waiting(part)
|
|
173
|
+
|
|
139
174
|
def _feed_waiting(self, chunk: str) -> None:
|
|
140
175
|
"""在 WAITING_FOR_TAG 状态下处理新块。"""
|
|
141
176
|
combined = self._waiting_tail + chunk
|
|
142
|
-
|
|
177
|
+
self._waiting_tail = ""
|
|
178
|
+
|
|
179
|
+
if self._thinking_filter is not None:
|
|
180
|
+
from echotools.exec.fncall.protocols.entml_thinking_parse import (
|
|
181
|
+
has_unclosed_entml_thinking,
|
|
182
|
+
)
|
|
183
|
+
if (
|
|
184
|
+
self._thinking_filter.in_open_thinking()
|
|
185
|
+
or has_unclosed_entml_thinking(combined)
|
|
186
|
+
):
|
|
187
|
+
self._feed_waiting_thinking_plain(combined)
|
|
188
|
+
return
|
|
143
189
|
|
|
144
190
|
trigger_tags = self._protocol.get_trigger_tags()
|
|
191
|
+
found, pos = self._protocol.detect_start(combined)
|
|
145
192
|
|
|
146
193
|
if not found:
|
|
147
194
|
safe, remain = self._split_safe_text(combined, trigger_tags)
|
|
@@ -154,7 +201,6 @@ class FncallStreamParser:
|
|
|
154
201
|
self._emit_text(combined[:pos])
|
|
155
202
|
|
|
156
203
|
self._fncall_buf = combined[pos:]
|
|
157
|
-
self._waiting_tail = ""
|
|
158
204
|
self._detected = True
|
|
159
205
|
self._state = self.IN_FUNCTION_CALLS
|
|
160
206
|
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py
RENAMED
|
@@ -12,6 +12,31 @@ _THINKING_OPEN_PREFIX = "<entml:thinking"
|
|
|
12
12
|
_THINKING_CLOSE = "</entml:thinking>"
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def has_unclosed_entml_thinking(text: str) -> bool:
|
|
16
|
+
"""buffer 中是否存在尚未闭合的 <entml:thinking> 块(含开标签未收齐)。"""
|
|
17
|
+
if not text:
|
|
18
|
+
return False
|
|
19
|
+
i = 0
|
|
20
|
+
while i < len(text):
|
|
21
|
+
open_at = text.find(_THINKING_OPEN_PREFIX, i)
|
|
22
|
+
if open_at < 0:
|
|
23
|
+
break
|
|
24
|
+
gt = text.find(">", open_at)
|
|
25
|
+
if gt < 0:
|
|
26
|
+
return True
|
|
27
|
+
close_at = text.find(_THINKING_CLOSE, gt + 1)
|
|
28
|
+
if close_at < 0:
|
|
29
|
+
return True
|
|
30
|
+
i = close_at + len(_THINKING_CLOSE)
|
|
31
|
+
max_keep = len(_THINKING_OPEN_PREFIX) - 1
|
|
32
|
+
check_len = min(len(text), max_keep)
|
|
33
|
+
for length in range(check_len, 0, -1):
|
|
34
|
+
suffix = text[-length:]
|
|
35
|
+
if _THINKING_OPEN_PREFIX.startswith(suffix) and suffix != _THINKING_OPEN_PREFIX:
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
15
40
|
def split_entml_thinking(text: str) -> Tuple[str, str]:
|
|
16
41
|
"""从文本中剥离 <entml:thinking> 块,返回 (正文, 思考链拼接)。"""
|
|
17
42
|
if not text:
|
|
@@ -53,6 +78,20 @@ class EntmlThinkingStreamFilter:
|
|
|
53
78
|
# 当前 thinking 块是否已输出过任何内容(用于首片去前导空白)
|
|
54
79
|
self._thinking_started = False
|
|
55
80
|
|
|
81
|
+
def in_open_thinking(self) -> bool:
|
|
82
|
+
"""思考块已开始且尚未收到 </entml:thinking>(含开/闭标签分片 hold)。"""
|
|
83
|
+
if self._in_block:
|
|
84
|
+
return True
|
|
85
|
+
open_at = self._pending.find(_THINKING_OPEN_PREFIX)
|
|
86
|
+
if open_at >= 0:
|
|
87
|
+
gt = self._pending.find(">", open_at)
|
|
88
|
+
if gt < 0:
|
|
89
|
+
return True
|
|
90
|
+
_, hold = _hold_prefix(self._pending, _THINKING_OPEN_PREFIX)
|
|
91
|
+
if hold:
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
94
|
+
|
|
56
95
|
def feed(self, chunk: str) -> List[Tuple[str, str]]:
|
|
57
96
|
"""返回 [(kind, text), ...],kind 为 content 或 thinking。"""
|
|
58
97
|
if not chunk:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from echotools.exec.fncall.protocols.entml_thinking_parse import (
|
|
2
2
|
EntmlThinkingStreamFilter,
|
|
3
|
+
has_unclosed_entml_thinking,
|
|
3
4
|
split_entml_thinking,
|
|
4
5
|
)
|
|
5
6
|
|
|
@@ -16,6 +17,11 @@ def test_split_entml_thinking() -> None:
|
|
|
16
17
|
assert "<entml:thinking>" not in content
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def test_has_unclosed_entml_thinking_streaming_prefix() -> None:
|
|
21
|
+
assert has_unclosed_entml_thinking("<entml:think")
|
|
22
|
+
assert not has_unclosed_entml_thinking("<entml:thinking>done</entml:thinking>")
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
def test_entml_thinking_stream_filter() -> None:
|
|
20
26
|
filt = EntmlThinkingStreamFilter()
|
|
21
27
|
parts = []
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from echotools.exec.fncall import get_protocol
|
|
4
|
+
from echotools.exec.fncall.parsers.stream import FncallStreamParser
|
|
5
|
+
from echotools.exec.fncall.protocols.entml_thinking_parse import (
|
|
6
|
+
EntmlThinkingStreamFilter,
|
|
7
|
+
has_unclosed_entml_thinking,
|
|
8
|
+
split_entml_thinking,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
TOOLS = [
|
|
12
|
+
{
|
|
13
|
+
"type": "function",
|
|
14
|
+
"function": {
|
|
15
|
+
"name": "get_weather",
|
|
16
|
+
"parameters": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {"city": {"type": "string"}},
|
|
19
|
+
"required": ["city"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
INVOKE = (
|
|
26
|
+
'<entml:invoke name="get_weather">'
|
|
27
|
+
'<entml:parameter name="city">Hangzhou</entml:parameter>'
|
|
28
|
+
"</entml:invoke>"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_has_unclosed_entml_thinking() -> None:
|
|
33
|
+
assert not has_unclosed_entml_thinking("")
|
|
34
|
+
assert not has_unclosed_entml_thinking("hello")
|
|
35
|
+
assert has_unclosed_entml_thinking("<entml:thinking>\nstep")
|
|
36
|
+
assert has_unclosed_entml_thinking("<entml:thinking>\nstep</entml:think")
|
|
37
|
+
assert not has_unclosed_entml_thinking(
|
|
38
|
+
"<entml:thinking>\nstep\n</entml:thinking>\nanswer"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_in_open_thinking_hold_open_tag() -> None:
|
|
43
|
+
filt = EntmlThinkingStreamFilter()
|
|
44
|
+
assert not filt.in_open_thinking()
|
|
45
|
+
filt.feed("prefix <entml:think")
|
|
46
|
+
assert filt.in_open_thinking()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_split_entml_thinking_ignores_unclosed_block() -> None:
|
|
50
|
+
text = "<entml:thinking>still going\n" + INVOKE
|
|
51
|
+
content, thinking = split_entml_thinking(text)
|
|
52
|
+
assert thinking == ""
|
|
53
|
+
assert INVOKE in content
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_invoke_inside_unclosed_thinking_not_parsed_as_tool() -> None:
|
|
57
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
58
|
+
stream = f"<entml:thinking>\nplan {INVOKE}\n"
|
|
59
|
+
for i in range(0, len(stream), 7):
|
|
60
|
+
parser.feed(stream[i : i + 7])
|
|
61
|
+
assert not parser.has_calls
|
|
62
|
+
assert INVOKE in parser.partial_thinking
|
|
63
|
+
clean, calls = parser.finalize()
|
|
64
|
+
assert not calls
|
|
65
|
+
assert INVOKE not in clean
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_invoke_after_thinking_close_is_parsed() -> None:
|
|
69
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
70
|
+
text = f"<entml:thinking>ok</entml:thinking>\n\n{INVOKE}"
|
|
71
|
+
parser.feed(text)
|
|
72
|
+
assert parser.has_calls
|
|
73
|
+
clean, calls = parser.finalize()
|
|
74
|
+
assert len(calls) == 1
|
|
75
|
+
assert calls[0]["function"]["name"] == "get_weather"
|
|
76
|
+
assert "Hangzhou" in calls[0]["function"]["arguments"]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_thinking_close_and_invoke_in_one_chunk() -> None:
|
|
80
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
81
|
+
chunks = ["<entml:thinking>reason</entml:thinking>", INVOKE]
|
|
82
|
+
for chunk in chunks:
|
|
83
|
+
parser.feed(chunk)
|
|
84
|
+
clean, calls = parser.finalize()
|
|
85
|
+
assert parser.partial_thinking.strip() == "reason"
|
|
86
|
+
assert len(calls) == 1
|
|
87
|
+
assert "reason" not in clean or clean.strip() == ""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_no_thinking_invoke_still_works() -> None:
|
|
91
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
92
|
+
parser.feed(f"Sure.\n{INVOKE}")
|
|
93
|
+
clean, calls = parser.finalize()
|
|
94
|
+
assert len(calls) == 1
|
|
95
|
+
assert "Sure" in clean
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_stream_thinking_incremental_before_close() -> None:
|
|
99
|
+
parser = FncallStreamParser(protocol=get_protocol("entml"), tools=TOOLS)
|
|
100
|
+
parser.feed("<entml:thinking>\n")
|
|
101
|
+
parser.feed("line one\n")
|
|
102
|
+
assert "line one" in parser.partial_thinking
|
|
103
|
+
assert not parser.has_calls
|
|
104
|
+
parser.feed(f"mention {INVOKE}\n")
|
|
105
|
+
assert INVOKE in parser.partial_thinking
|
|
106
|
+
assert not parser.has_calls
|
|
107
|
+
parser.feed("</entml:thinking>\nanswer")
|
|
108
|
+
clean, calls = parser.finalize()
|
|
109
|
+
assert not calls
|
|
110
|
+
assert "answer" in clean
|
|
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
|
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/exec/fncall/protocols/entml_thinking_history.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
|
|
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.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/filezone.js
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/motionkit.js
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/sendbutton.js
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/sortablelist.css
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/sortablelist.js
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/textinput.js
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/components/voiceinput.js
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/inter-500.woff2
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/inter-600.woff2
RENAMED
|
File without changes
|
{echotools-2.3.39 → echotools-2.3.40}/src/echotools/media/web/input_box/fonts/inter400.woff2
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
|
|
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
|