echotools 2.3.83__tar.gz → 2.3.85__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.83 → echotools-2.3.85}/PKG-INFO +1 -1
- {echotools-2.3.83 → echotools-2.3.85}/pyproject.toml +1 -1
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/parsers/stream.py +12 -3
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml.py +7 -17
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_invoke.py +8 -13
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_patterns.py +93 -5
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_stream_json.py +2 -32
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_think/parse.py +42 -27
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_values.py +41 -3
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/coercion.py +3 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/history_markup.py +6 -5
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/fixtures/simulated_fake_history_markup_responses.py +3 -3
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/fixtures/simulated_fault_thinking_responses.py +1 -1
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/fixtures/simulated_llm_tool_responses.py +2 -2
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_parser_comprehensive.py +1 -1
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_protocol.py +96 -16
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_tool_pipeline.py +24 -9
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fncall_stream_thinking.py +20 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_history_markup_filter.py +2 -2
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_stream_partial_json_parity.py +67 -0
- {echotools-2.3.83 → echotools-2.3.85}/README.md +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/docs/README.md +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/setup.cfg +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/compat.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_think/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_think/core.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_think/hist.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_tool_blocks.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/mixins/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/mixins/uibase.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/mixins/uicmds.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/mixins/uiinteract.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/mixins/uioutput.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/mixins/uistream.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/ui_console.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/ui_io.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/ui_log.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/ui_platform.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/ui_text.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uicore/ui_types.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uilayout/ui_countdown.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uilayout/ui_layout.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uilayout/ui_misc.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uilayout/ui_panel.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uilayout/ui_table.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uilayout/ui_tree.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uiwidgets/ui_box.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uiwidgets/ui_editor.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uiwidgets/ui_progress.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uiwidgets/ui_select.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/console/uiwidgets/ui_stream.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools/version.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools.egg-info/SOURCES.txt +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/_check_parity.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/bench_import.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/conftest.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/fixtures/__init__.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_adversarial_stream_splits.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_completion.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_config.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_console.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_agent_output_formats.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_bare_invoke_format.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_batch_stream_comprehensive.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_stream_json.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_stream_json_exhaustive.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_tool_parse_leak.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_errors.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_events.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_facade.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fake_history_markup_batch.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fake_history_markup_invoke_preserve.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fake_history_markup_reply_protect.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fake_history_markup_stream.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fault_thinking_close_batch.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fault_thinking_close_stream.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_ids.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_imports.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_io.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_keys.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_logger.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_merge.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_model_branch_matrix.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_simulated_llm_tool_parse.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_stream_realtime_parse.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_usage.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_web.py +0 -0
- {echotools-2.3.83 → echotools-2.3.85}/src/tests/test_web_broker.py +0 -0
|
@@ -273,6 +273,15 @@ class FncallStreamParser:
|
|
|
273
273
|
self._waiting_tail = remain
|
|
274
274
|
return
|
|
275
275
|
|
|
276
|
+
from echotools.exec.fncall.protocols.entml_think.parse import (
|
|
277
|
+
invoke_index_inside_any_thinking_block,
|
|
278
|
+
)
|
|
279
|
+
if invoke_index_inside_any_thinking_block(
|
|
280
|
+
text, pos, thinking_enabled=self._thinking_enabled,
|
|
281
|
+
):
|
|
282
|
+
self._append_content_text(text)
|
|
283
|
+
return
|
|
284
|
+
|
|
276
285
|
if pos > 0:
|
|
277
286
|
self._append_content_text(text[:pos])
|
|
278
287
|
|
|
@@ -319,10 +328,10 @@ class FncallStreamParser:
|
|
|
319
328
|
if found:
|
|
320
329
|
if self._thinking_filter is not None:
|
|
321
330
|
from echotools.exec.fncall.protocols.entml_think.parse import (
|
|
322
|
-
|
|
331
|
+
invoke_index_inside_any_thinking_block,
|
|
323
332
|
)
|
|
324
|
-
if
|
|
325
|
-
combined, pos, thinking_enabled=self._thinking_enabled
|
|
333
|
+
if invoke_index_inside_any_thinking_block(
|
|
334
|
+
combined, pos, thinking_enabled=self._thinking_enabled,
|
|
326
335
|
):
|
|
327
336
|
self._feed_waiting_thinking_plain(combined)
|
|
328
337
|
return
|
|
@@ -23,10 +23,10 @@ from echotools.exec.fncall.protocols.entml_tool_blocks import (
|
|
|
23
23
|
strip_tool_block_spans,
|
|
24
24
|
)
|
|
25
25
|
from echotools.exec.fncall.protocols.entml_patterns import (
|
|
26
|
-
BLOCK_RE,
|
|
27
26
|
entml_invoke_open_may_be_streaming,
|
|
28
27
|
extract_attr_value,
|
|
29
28
|
normalize_entml_name,
|
|
29
|
+
strip_actionable_entml_invoke_blocks,
|
|
30
30
|
strip_legacy_function_calls_wrapper,
|
|
31
31
|
strip_tool_entml_residue,
|
|
32
32
|
)
|
|
@@ -42,26 +42,15 @@ from echotools.exec.fncall.shared.normalization import (
|
|
|
42
42
|
)
|
|
43
43
|
from echotools.exec.protocol.base import ToolProtocol
|
|
44
44
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
r"<entml:[a-zA-Z_][\w]*\b[^>]*>.*?</entml:[a-zA-Z_][\w]*>",
|
|
48
|
-
re.DOTALL,
|
|
49
|
-
)
|
|
50
|
-
_ENTML_SELF_CLOSING_RE = re.compile(r"<entml:[a-zA-Z_][\w]*\b[^>]*/>", re.DOTALL)
|
|
51
|
-
_ENTML_ORPHAN_CLOSE_RE = re.compile(r"</entml:[a-zA-Z_][\w]*>", re.DOTALL)
|
|
52
|
-
_ENTML_ORPHAN_OPEN_RE = re.compile(r"<entml:[a-zA-Z_][\w]*\b[^>]*>", re.DOTALL)
|
|
45
|
+
# user 消息:仅去掉标签名中的 entml: 命名空间,保留标签与正文。
|
|
46
|
+
_ENTML_NAMESPACE_RE = re.compile(r"(</?)entml:", re.IGNORECASE)
|
|
53
47
|
|
|
54
48
|
|
|
55
49
|
def strip_entml_from_content(content: str) -> str:
|
|
56
|
-
"""
|
|
50
|
+
"""user 消息:去掉 ``entml:`` 标签前缀,保留标签结构与正文(含 ``//``)。"""
|
|
57
51
|
if not content:
|
|
58
52
|
return content
|
|
59
|
-
|
|
60
|
-
cleaned = _ENTML_PAIR_RE.sub("", cleaned)
|
|
61
|
-
cleaned = _ENTML_SELF_CLOSING_RE.sub("", cleaned)
|
|
62
|
-
cleaned = _ENTML_ORPHAN_CLOSE_RE.sub("", cleaned)
|
|
63
|
-
cleaned = _ENTML_ORPHAN_OPEN_RE.sub("", cleaned)
|
|
64
|
-
return cleaned.strip()
|
|
53
|
+
return _ENTML_NAMESPACE_RE.sub(r"\1", content)
|
|
65
54
|
|
|
66
55
|
|
|
67
56
|
def _parse_tool_call_args(tc: Dict[str, Any]) -> tuple[str, Dict[str, Any]]:
|
|
@@ -308,7 +297,7 @@ class EntmlProtocol(ToolProtocol):
|
|
|
308
297
|
# 须在移除 invoke 之前剥离伪 history,且须晚于已解析的 ``<tool>`` 块剥离。
|
|
309
298
|
clean, _ = strip_fake_history_markup(clean)
|
|
310
299
|
if tool_calls:
|
|
311
|
-
clean =
|
|
300
|
+
clean = strip_actionable_entml_invoke_blocks(clean)
|
|
312
301
|
clean = strip_tool_entml_residue(clean)
|
|
313
302
|
return (clean, normalize_tool_calls(tool_calls, tools))
|
|
314
303
|
|
|
@@ -321,6 +310,7 @@ class EntmlProtocol(ToolProtocol):
|
|
|
321
310
|
return tool_calls
|
|
322
311
|
|
|
323
312
|
def clean_tags(self, content: str) -> str:
|
|
313
|
+
"""user 消息:仅去掉 ``entml:`` 前缀,不做其它剥离。"""
|
|
324
314
|
return strip_entml_from_content(content)
|
|
325
315
|
|
|
326
316
|
def clean_tool_tags(self, content: str) -> str:
|
|
@@ -5,20 +5,19 @@ from typing import Any, Dict, List, Optional
|
|
|
5
5
|
|
|
6
6
|
from .entml_patterns import (
|
|
7
7
|
BARE_INVOKE_CHILD_RE,
|
|
8
|
-
INVOKE_DIRECT_CHILD_OPEN_RE,
|
|
9
8
|
INVOKE_DIRECT_CHILD_RE,
|
|
10
9
|
INVOKE_DIRECT_CHILD_SKIP,
|
|
11
|
-
INVOKE_RE,
|
|
12
10
|
PARAM_RE,
|
|
13
11
|
PARAMETERS_RE,
|
|
14
12
|
extract_attr_value,
|
|
15
13
|
extract_parameter_type_attr,
|
|
14
|
+
iter_actionable_entml_invoke_blocks,
|
|
16
15
|
normalize_entml_name,
|
|
17
16
|
parse_sub_tags,
|
|
18
17
|
split_mangled_json_param_tail,
|
|
19
18
|
synthetic_close_invoke_body,
|
|
20
19
|
)
|
|
21
|
-
from .entml_values import coerce_entml_arguments, coerce_entml_parameter_value
|
|
20
|
+
from .entml_values import coerce_entml_arguments, coerce_entml_parameter_value, _coerce_entml_arg_value
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
def _parse_direct_child_tags(
|
|
@@ -98,13 +97,10 @@ def parse_invoke_args(
|
|
|
98
97
|
if extra_key in args:
|
|
99
98
|
continue
|
|
100
99
|
extra_schema = func_props.get(extra_key) or {}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
str(extra_val),
|
|
106
|
-
extra_schema or None,
|
|
107
|
-
)
|
|
100
|
+
args[extra_key] = _coerce_entml_arg_value(
|
|
101
|
+
extra_val,
|
|
102
|
+
extra_schema or None,
|
|
103
|
+
)
|
|
108
104
|
|
|
109
105
|
_parse_bare_invoke_children(body, args, func_props)
|
|
110
106
|
_parse_direct_child_tags(body, args, func_props)
|
|
@@ -118,13 +114,12 @@ def parse_entml_tool_calls(
|
|
|
118
114
|
schema_index: Optional[Dict[str, Dict[str, Dict[str, Any]]]],
|
|
119
115
|
) -> List[Dict[str, Any]]:
|
|
120
116
|
tool_calls: List[Dict[str, Any]] = []
|
|
121
|
-
for
|
|
122
|
-
attrs = invoke_m.group(1) or ""
|
|
117
|
+
for _start, _end, attrs, body in iter_actionable_entml_invoke_blocks(text):
|
|
123
118
|
name = extract_attr_value(attrs, "name")
|
|
124
119
|
if not name:
|
|
125
120
|
continue
|
|
126
121
|
name = normalize_entml_name(name)
|
|
127
|
-
args = parse_invoke_args(
|
|
122
|
+
args = parse_invoke_args(body, name, schema_index)
|
|
128
123
|
arguments = json.dumps(args, ensure_ascii=False)
|
|
129
124
|
tool_calls.append(
|
|
130
125
|
{
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import re
|
|
4
|
-
from typing import Any, Dict, Optional, Tuple
|
|
5
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
5
6
|
|
|
6
7
|
from .entml_values import coerce_entml_parameter_value
|
|
7
8
|
|
|
@@ -50,7 +51,7 @@ PARAMETERS_RE = re.compile(
|
|
|
50
51
|
r"<entml:parameters>([\s\S]*?)</entml:parameters>",
|
|
51
52
|
re.DOTALL,
|
|
52
53
|
)
|
|
53
|
-
# invoke 内裸子标签(非 parameter
|
|
54
|
+
# invoke 内裸子标签(非 parameter 包裹),如部分 agent 输出的 description/timeout。
|
|
54
55
|
BARE_INVOKE_CHILD_RE = re.compile(
|
|
55
56
|
r"<entml:(description|timeout)>([\s\S]*?)</entml:\1>",
|
|
56
57
|
re.DOTALL | re.IGNORECASE,
|
|
@@ -101,6 +102,7 @@ _FENCE_ONLY_LINE_RE = re.compile(
|
|
|
101
102
|
re.IGNORECASE | re.MULTILINE,
|
|
102
103
|
)
|
|
103
104
|
_INVOKE_OPEN_PREFIX = "<entml:invoke"
|
|
105
|
+
_INVOKE_CLOSE = "</entml:invoke>"
|
|
104
106
|
_PLACEHOLDER_INVOKE_NAMES = frozenset({"$FUNCTION_NAME", "$FUNCTION_NAME2"})
|
|
105
107
|
|
|
106
108
|
|
|
@@ -137,6 +139,76 @@ def entml_invoke_open_may_be_streaming(buffer: str, pos: int) -> bool:
|
|
|
137
139
|
return False
|
|
138
140
|
|
|
139
141
|
|
|
142
|
+
def _actionable_invoke_open_before_close(content: str, close_pos: int) -> bool:
|
|
143
|
+
"""``close_pos`` 处的 ``</entml:invoke>`` 是否闭合带真实 name 的开标签。"""
|
|
144
|
+
search = close_pos - 1
|
|
145
|
+
while search >= 0:
|
|
146
|
+
pos = content.rfind(_INVOKE_OPEN_PREFIX, 0, search + 1)
|
|
147
|
+
if pos < 0:
|
|
148
|
+
return False
|
|
149
|
+
if entml_invoke_open_is_actionable(content, pos):
|
|
150
|
+
gt = content.find(">", pos)
|
|
151
|
+
if gt < 0 or gt >= close_pos:
|
|
152
|
+
search = pos - 1
|
|
153
|
+
continue
|
|
154
|
+
inner_close = content.find(_INVOKE_CLOSE, gt + 1, close_pos)
|
|
155
|
+
if inner_close < 0:
|
|
156
|
+
return True
|
|
157
|
+
search = pos - 1
|
|
158
|
+
continue
|
|
159
|
+
search = pos - 1
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _invoke_close_should_strip(content: str, close_pos: int) -> bool:
|
|
164
|
+
"""是否应剥离 ``</entml:invoke>``(孤儿噪声或真实工具块残留)。"""
|
|
165
|
+
if _actionable_invoke_open_before_close(content, close_pos):
|
|
166
|
+
return True
|
|
167
|
+
open_pos = content.rfind(_INVOKE_OPEN_PREFIX, 0, close_pos)
|
|
168
|
+
if open_pos < 0:
|
|
169
|
+
return True
|
|
170
|
+
return entml_invoke_open_is_actionable(content, open_pos)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def iter_actionable_entml_invoke_blocks(text: str) -> Iterator[Tuple[int, int, str, str]]:
|
|
174
|
+
"""迭代含真实 ``name`` 的完整 ``<entml:invoke>…</entml:invoke>`` 块。"""
|
|
175
|
+
if not text:
|
|
176
|
+
return
|
|
177
|
+
search_from = 0
|
|
178
|
+
prefix_len = len(_INVOKE_OPEN_PREFIX)
|
|
179
|
+
close_len = len(_INVOKE_CLOSE)
|
|
180
|
+
while search_from < len(text):
|
|
181
|
+
pos = text.find(_INVOKE_OPEN_PREFIX, search_from)
|
|
182
|
+
if pos < 0:
|
|
183
|
+
break
|
|
184
|
+
if not entml_invoke_open_is_actionable(text, pos):
|
|
185
|
+
search_from = pos + prefix_len
|
|
186
|
+
continue
|
|
187
|
+
gt = text.find(">", pos + prefix_len)
|
|
188
|
+
if gt < 0:
|
|
189
|
+
break
|
|
190
|
+
close = text.find(_INVOKE_CLOSE, gt + 1)
|
|
191
|
+
if close < 0:
|
|
192
|
+
break
|
|
193
|
+
attrs = text[pos + prefix_len : gt]
|
|
194
|
+
body = text[gt + 1 : close]
|
|
195
|
+
yield pos, close + close_len, attrs, body
|
|
196
|
+
search_from = close + close_len
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def strip_actionable_entml_invoke_blocks(text: str) -> str:
|
|
200
|
+
"""仅剥离含真实 ``name`` 的 invoke 块;保留 prose ``<entml:invoke>`` 提及。"""
|
|
201
|
+
if not text:
|
|
202
|
+
return text
|
|
203
|
+
parts: List[str] = []
|
|
204
|
+
last = 0
|
|
205
|
+
for start, end, _attrs, _body in iter_actionable_entml_invoke_blocks(text):
|
|
206
|
+
parts.append(text[last:start])
|
|
207
|
+
last = end
|
|
208
|
+
parts.append(text[last:])
|
|
209
|
+
return "".join(parts)
|
|
210
|
+
|
|
211
|
+
|
|
140
212
|
def _strip_orphan_invoke_tags(content: str) -> str:
|
|
141
213
|
"""仅剥离带真实 name 的 invoke 孤儿标签;保留 prose 中的 ``<entml:invoke>`` 提及。"""
|
|
142
214
|
pattern = re.compile(r"</?entml:invoke\b[^>]*/?>", re.DOTALL)
|
|
@@ -144,7 +216,9 @@ def _strip_orphan_invoke_tags(content: str) -> str:
|
|
|
144
216
|
def repl(match: re.Match[str]) -> str:
|
|
145
217
|
tag = match.group(0)
|
|
146
218
|
if tag.startswith("</"):
|
|
147
|
-
|
|
219
|
+
if _invoke_close_should_strip(content, match.start()):
|
|
220
|
+
return ""
|
|
221
|
+
return tag
|
|
148
222
|
if entml_invoke_open_is_actionable(content, match.start()):
|
|
149
223
|
return ""
|
|
150
224
|
return tag
|
|
@@ -224,15 +298,29 @@ _MANGLED_PARAM_JSON_COMMA_AFTER_QUOTE_RE = re.compile(
|
|
|
224
298
|
)
|
|
225
299
|
|
|
226
300
|
|
|
301
|
+
def _param_value_is_json_container(value: str) -> bool:
|
|
302
|
+
stripped = (value or "").lstrip()
|
|
303
|
+
return bool(stripped) and stripped[0] in "{["
|
|
304
|
+
|
|
305
|
+
|
|
227
306
|
def mangled_json_param_tail_in_progress(value: str) -> bool:
|
|
228
307
|
"""parameter 值中出现误写入 JSON 尾缀但尚未收齐时不应继续增长 partial_json。"""
|
|
229
|
-
|
|
308
|
+
if not value or _param_value_is_json_container(value):
|
|
309
|
+
return False
|
|
310
|
+
return bool(_MANGLED_PARAM_JSON_TAIL_IN_PROGRESS_RE.search(value))
|
|
230
311
|
|
|
231
312
|
|
|
232
313
|
def split_mangled_json_param_tail(value: str) -> Tuple[str, Dict[str, Any]]:
|
|
233
314
|
"""模型把 ``", "description": ..., "timeout": ...}}`` 误写入 parameter 值时的拆分。"""
|
|
234
315
|
if not value:
|
|
235
316
|
return value, {}
|
|
317
|
+
if _param_value_is_json_container(value):
|
|
318
|
+
try:
|
|
319
|
+
json.loads(value)
|
|
320
|
+
return value, {}
|
|
321
|
+
except json.JSONDecodeError:
|
|
322
|
+
# 合法 JSON 数组/对象(含 options.description 等)不得走标量 command 尾缀启发式。
|
|
323
|
+
return value, {}
|
|
236
324
|
match = _MANGLED_PARAM_JSON_TAIL_RE.search(value)
|
|
237
325
|
if match:
|
|
238
326
|
command = value[: match.start() + 1]
|
|
@@ -365,7 +453,7 @@ def strip_tool_entml_residue(content: str) -> str:
|
|
|
365
453
|
if not content:
|
|
366
454
|
return content
|
|
367
455
|
cleaned = _TOOL_WRAPPER_PAIR_RE.sub("", content)
|
|
368
|
-
cleaned =
|
|
456
|
+
cleaned = strip_actionable_entml_invoke_blocks(cleaned)
|
|
369
457
|
cleaned = _strip_orphan_invoke_tags(cleaned)
|
|
370
458
|
cleaned = _strip_orphan_non_invoke_tool_tags(cleaned)
|
|
371
459
|
cleaned = _EMPTY_FENCE_RE.sub("", cleaned)
|
{echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_stream_json.py
RENAMED
|
@@ -21,8 +21,7 @@ from .entml_patterns import (
|
|
|
21
21
|
split_mangled_json_param_tail,
|
|
22
22
|
synthetic_close_invoke_body,
|
|
23
23
|
)
|
|
24
|
-
from .entml_values import coerce_entml_parameter_value
|
|
25
|
-
from echotools.exec.fncall.shared.coercion import _resolve_effective_type
|
|
24
|
+
from .entml_values import coerce_entml_parameter_value, effective_entml_param_json_type
|
|
26
25
|
|
|
27
26
|
_PARAM_OPEN_RE = re.compile(rf"{PARAM_OPEN_PATTERN}([^>]*)>", re.IGNORECASE)
|
|
28
27
|
_PARAM_CLOSE = PARAM_CLOSE_ENTML
|
|
@@ -31,22 +30,6 @@ _PARAMETERS_CLOSE = "</entml:parameters>"
|
|
|
31
30
|
_INVOKE_CLOSE = "</entml:invoke>"
|
|
32
31
|
_INVOKE_OPEN_PREFIX = "<entml:invoke"
|
|
33
32
|
|
|
34
|
-
_TYPE_HINT_TO_JSON_TYPE = {
|
|
35
|
-
"str": "string",
|
|
36
|
-
"string": "string",
|
|
37
|
-
"int": "integer",
|
|
38
|
-
"integer": "integer",
|
|
39
|
-
"float": "number",
|
|
40
|
-
"number": "number",
|
|
41
|
-
"double": "number",
|
|
42
|
-
"bool": "boolean",
|
|
43
|
-
"boolean": "boolean",
|
|
44
|
-
"array": "array",
|
|
45
|
-
"list": "array",
|
|
46
|
-
"object": "object",
|
|
47
|
-
"dict": "object",
|
|
48
|
-
}
|
|
49
|
-
|
|
50
33
|
|
|
51
34
|
def _strip_incomplete_markup_suffix(value: str) -> str:
|
|
52
35
|
lt = value.rfind("<")
|
|
@@ -89,20 +72,7 @@ def _effective_param_type(
|
|
|
89
72
|
pschema: Optional[Dict[str, Any]],
|
|
90
73
|
type_hint: Optional[str],
|
|
91
74
|
) -> str:
|
|
92
|
-
|
|
93
|
-
mapped = _TYPE_HINT_TO_JSON_TYPE.get(type_hint.strip().lower())
|
|
94
|
-
if mapped:
|
|
95
|
-
return mapped
|
|
96
|
-
if pschema:
|
|
97
|
-
resolved = _resolve_effective_type(pschema)
|
|
98
|
-
if resolved:
|
|
99
|
-
return resolved
|
|
100
|
-
stripped = (value or "").lstrip()
|
|
101
|
-
if stripped.startswith("["):
|
|
102
|
-
return "array"
|
|
103
|
-
if stripped.startswith("{"):
|
|
104
|
-
return "object"
|
|
105
|
-
return "string"
|
|
75
|
+
return effective_entml_param_json_type(value, pschema, type_hint)
|
|
106
76
|
|
|
107
77
|
|
|
108
78
|
def _synthetic_close_body(inner: str) -> str:
|
{echotools-2.3.83 → echotools-2.3.85}/src/echotools/exec/fncall/protocols/entml_think/parse.py
RENAMED
|
@@ -353,26 +353,49 @@ def invoke_index_inside_unclosed_thinking(
|
|
|
353
353
|
thinking_enabled: bool = True,
|
|
354
354
|
) -> bool:
|
|
355
355
|
"""``invoke_at`` 是否落在尚未闭合的 thinking 块内。"""
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
think_open, opened_plain = _find_earliest_thinking_open(
|
|
359
|
-
text, thinking_enabled=thinking_enabled
|
|
356
|
+
return invoke_index_inside_any_thinking_block(
|
|
357
|
+
text, invoke_at, thinking_enabled=thinking_enabled, unclosed_only=True,
|
|
360
358
|
)
|
|
361
|
-
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def invoke_index_inside_any_thinking_block(
|
|
362
|
+
text: str,
|
|
363
|
+
invoke_at: int,
|
|
364
|
+
*,
|
|
365
|
+
thinking_enabled: bool = True,
|
|
366
|
+
unclosed_only: bool = False,
|
|
367
|
+
) -> bool:
|
|
368
|
+
"""``invoke_at`` 是否落在 thinking 块内(``unclosed_only`` 时仅检查未闭合块)。"""
|
|
369
|
+
if invoke_at < 0:
|
|
362
370
|
return False
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
i = 0
|
|
372
|
+
while i < len(text):
|
|
373
|
+
open_at, opened_plain = _find_earliest_thinking_open(
|
|
374
|
+
text, i, thinking_enabled=thinking_enabled,
|
|
375
|
+
)
|
|
376
|
+
if open_at < 0:
|
|
377
|
+
break
|
|
378
|
+
gt = text.find(">", open_at)
|
|
379
|
+
if gt < 0:
|
|
380
|
+
return invoke_at > open_at
|
|
381
|
+
body_start = gt + 1
|
|
382
|
+
close_at, close_len = _find_thinking_close(
|
|
383
|
+
text,
|
|
384
|
+
body_start,
|
|
385
|
+
opened_plain=opened_plain,
|
|
386
|
+
thinking_enabled=thinking_enabled,
|
|
387
|
+
)
|
|
388
|
+
if close_at < 0:
|
|
389
|
+
if open_at < invoke_at:
|
|
390
|
+
return True
|
|
391
|
+
break
|
|
392
|
+
block_end = close_at + close_len
|
|
393
|
+
if open_at < invoke_at < block_end:
|
|
394
|
+
return True
|
|
395
|
+
if unclosed_only:
|
|
396
|
+
return False
|
|
397
|
+
i = block_end
|
|
398
|
+
return False
|
|
376
399
|
|
|
377
400
|
|
|
378
401
|
def split_entml_thinking(
|
|
@@ -574,15 +597,7 @@ class EntmlThinkingStreamFilter:
|
|
|
574
597
|
self._pending = ""
|
|
575
598
|
return False
|
|
576
599
|
|
|
577
|
-
|
|
578
|
-
if tool_hold:
|
|
579
|
-
if safe:
|
|
580
|
-
emitted = self._emit_thinking_piece(safe)
|
|
581
|
-
if emitted:
|
|
582
|
-
out.append(("thinking", emitted))
|
|
583
|
-
self._pending = tool_hold
|
|
584
|
-
return False
|
|
585
|
-
|
|
600
|
+
# thinking 块内 ``<entml:invoke>`` 等仅为 prose/示例时仍计入 thinking,不按工具 hold 截断。
|
|
586
601
|
safe, hold = _hold_prefix(self._pending, _THINKING_CLOSE)
|
|
587
602
|
if not hold and self._thinking_enabled:
|
|
588
603
|
safe, hold = _hold_prefix(safe, _FAULT_THINKING_CLOSE)
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
from typing import Any, Dict, Optional
|
|
5
5
|
|
|
6
|
-
from echotools.exec.fncall.shared.coercion import _coerce_param_value
|
|
6
|
+
from echotools.exec.fncall.shared.coercion import _coerce_param_value, _resolve_effective_type
|
|
7
7
|
|
|
8
8
|
_TYPE_HINT_TO_JSON_TYPE = {
|
|
9
9
|
"str": "string",
|
|
@@ -71,6 +71,44 @@ def coerce_entml_parameter_value(
|
|
|
71
71
|
return stripped
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
def effective_entml_param_json_type(
|
|
75
|
+
value: str,
|
|
76
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
77
|
+
type_hint: Optional[str] = None,
|
|
78
|
+
) -> str:
|
|
79
|
+
"""流式 partial_json 与 batch 共用:模型 type 优先,否则 schema,再否则按值推断。"""
|
|
80
|
+
effective = resolve_entml_parameter_schema(schema, type_hint)
|
|
81
|
+
if effective:
|
|
82
|
+
resolved = _resolve_effective_type(effective)
|
|
83
|
+
if resolved:
|
|
84
|
+
return resolved
|
|
85
|
+
stripped = (value or "").lstrip()
|
|
86
|
+
if stripped.startswith("["):
|
|
87
|
+
return "array"
|
|
88
|
+
if stripped.startswith("{"):
|
|
89
|
+
return "object"
|
|
90
|
+
return "string"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _coerce_entml_arg_value(
|
|
94
|
+
value: Any,
|
|
95
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
96
|
+
type_hint: Optional[str] = None,
|
|
97
|
+
) -> Any:
|
|
98
|
+
"""将已解析的 Python 值再按 type_hint / schema 归一(与 batch 单参路径一致)。"""
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
return coerce_entml_parameter_value(value, schema, type_hint=type_hint)
|
|
101
|
+
if isinstance(value, (dict, list)):
|
|
102
|
+
return coerce_entml_parameter_value(
|
|
103
|
+
json.dumps(value, ensure_ascii=False),
|
|
104
|
+
schema,
|
|
105
|
+
type_hint=type_hint,
|
|
106
|
+
)
|
|
107
|
+
if value is None:
|
|
108
|
+
return coerce_entml_parameter_value("", schema, type_hint=type_hint)
|
|
109
|
+
return coerce_entml_parameter_value(str(value), schema, type_hint=type_hint)
|
|
110
|
+
|
|
111
|
+
|
|
74
112
|
def coerce_entml_arguments(
|
|
75
113
|
args: Dict[str, Any],
|
|
76
114
|
func_name: str,
|
|
@@ -86,8 +124,8 @@ def coerce_entml_arguments(
|
|
|
86
124
|
out: Dict[str, Any] = {}
|
|
87
125
|
for key, value in args.items():
|
|
88
126
|
pschema = func_schema.get(key) or {}
|
|
89
|
-
if
|
|
90
|
-
out[key] =
|
|
127
|
+
if pschema:
|
|
128
|
+
out[key] = _coerce_entml_arg_value(value, pschema)
|
|
91
129
|
else:
|
|
92
130
|
out[key] = value
|
|
93
131
|
return out
|
|
@@ -199,6 +199,9 @@ def _coerce_to_array(raw: str, parsed: Any, item_schema: Dict[str, Any]) -> Any:
|
|
|
199
199
|
if result_list is None:
|
|
200
200
|
result_list = _try_parse_json_list(raw)
|
|
201
201
|
|
|
202
|
+
if result_list is None and isinstance(parsed, dict):
|
|
203
|
+
result_list = [parsed]
|
|
204
|
+
|
|
202
205
|
if result_list is None:
|
|
203
206
|
logger.debug("_coerce_to_array: 无法解析为列表,原样返回 %r", raw[:100])
|
|
204
207
|
return parsed
|
|
@@ -98,10 +98,11 @@ def _paired_block_glued_brace_re(tag: str) -> re.Pattern[str]:
|
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def _orphan_close_line_re(tag: str) -> re.Pattern[str]:
|
|
102
|
+
"""无配对开标签的 ``</tag>`` 行:仅删该行,不删其前的合法可见正文。"""
|
|
102
103
|
return re.compile(
|
|
103
|
-
rf"
|
|
104
|
-
re.IGNORECASE,
|
|
104
|
+
rf"^\s*</{tag}\s*>\s*$",
|
|
105
|
+
re.IGNORECASE | re.MULTILINE,
|
|
105
106
|
)
|
|
106
107
|
|
|
107
108
|
|
|
@@ -193,9 +194,9 @@ def _strip_fake_blocks_in_unprotected(
|
|
|
193
194
|
break
|
|
194
195
|
found = True
|
|
195
196
|
text = new_text
|
|
196
|
-
close_re =
|
|
197
|
+
close_re = _orphan_close_line_re(tag)
|
|
197
198
|
while True:
|
|
198
|
-
new_text, n = close_re.subn("
|
|
199
|
+
new_text, n = close_re.subn("", text, count=1)
|
|
199
200
|
if n == 0:
|
|
200
201
|
break
|
|
201
202
|
found = True
|
{echotools-2.3.83 → echotools-2.3.85}/src/tests/fixtures/simulated_fake_history_markup_responses.py
RENAMED
|
@@ -110,7 +110,7 @@ def tools_for_markup_case(case: HistoryMarkupCase) -> List[Dict[str, Any]]:
|
|
|
110
110
|
FAKE_HISTORY_MARKUP_CASES: Tuple[HistoryMarkupCase, ...] = (
|
|
111
111
|
HistoryMarkupCase(
|
|
112
112
|
id="model_edit_mimic_orphan_thinking",
|
|
113
|
-
description="
|
|
113
|
+
description="高发语料:正文 + orphan </thinking> + 多块 Edit 伪 tool",
|
|
114
114
|
extra_tools=("Read", "Edit"),
|
|
115
115
|
response=(
|
|
116
116
|
"● 验证通过,无外部依赖残留。现在清理几个文件中遗留的旧路径注释。\n\n"
|
|
@@ -166,8 +166,8 @@ FAKE_HISTORY_MARKUP_CASES: Tuple[HistoryMarkupCase, ...] = (
|
|
|
166
166
|
id="orphan_assistant_close",
|
|
167
167
|
description="无开标签的 </assistant> 反向闭合",
|
|
168
168
|
response="分析中...\n</assistant>\n后续仍可见",
|
|
169
|
-
expect_clean_contains=("后续仍可见",),
|
|
170
|
-
expect_clean_excludes=("
|
|
169
|
+
expect_clean_contains=("后续仍可见", "分析中"),
|
|
170
|
+
expect_clean_excludes=("</assistant>",),
|
|
171
171
|
),
|
|
172
172
|
HistoryMarkupCase(
|
|
173
173
|
id="fake_tool_bash_scalar",
|
|
@@ -99,7 +99,7 @@ TOOLS: List[Dict[str, Any]] = [
|
|
|
99
99
|
},
|
|
100
100
|
]
|
|
101
101
|
|
|
102
|
-
#
|
|
102
|
+
# Anthropic 兼容客户端 / rogator 链路常见工具(不在默认 TOOLS 内)
|
|
103
103
|
AGENT_TOOLS: List[Dict[str, Any]] = [
|
|
104
104
|
{
|
|
105
105
|
"type": "function",
|
|
@@ -580,7 +580,7 @@ SIMULATED_LLM_RESPONSES: List[SimulatedCase] = [
|
|
|
580
580
|
expect_args=[{"city": "杭州", "unit": "c"}],
|
|
581
581
|
expect_clean_substrings=["参考历史:", "我再确认一次实时天气。"],
|
|
582
582
|
),
|
|
583
|
-
# --- 模型分支:
|
|
583
|
+
# --- 模型分支:Anthropic 兼容客户端 / rogator 常见 agent 工具 ---
|
|
584
584
|
SimulatedCase(
|
|
585
585
|
id="agent_write_windows_path",
|
|
586
586
|
description="Write:Windows 绝对路径 + 含引号 contents",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
"""entml 流式/批量解析器全方位测试:普通路径 + 边界 + rogator/
|
|
3
|
+
"""entml 流式/批量解析器全方位测试:普通路径 + 边界 + rogator/agent 客户端高发场景。
|
|
4
4
|
|
|
5
5
|
与 test_simulated_llm_tool_parse / test_adversarial_stream_splits 互补:
|
|
6
6
|
- 本文件聚焦 thinking/invoke 边界、fault ``</thinking>``、holdback 分片、
|