Skip to content

fix: preserve SDK behavior while closing runtime resources - #396

Merged
WH-2099 merged 8 commits into
mainfrom
fix/sdk-runtime-compatibility
Oct 14, 2026
Merged

WH-2099 merged 8 commits into
mainfrom
fix/sdk-runtime-compatibility

Conversation

@WH-2099

@WH-2099 WH-2099 commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

SDK calls can leak HTTP responses and generators, interleave TCP frames, or leave CLI readers blocked after the child process exits.
This fixes cleanup on completion, failure and cancellation, serializes TCP writes, and handles reconnects and CLI/Serverless shutdown.

Also fixes tool-action and trigger registration, SSE line endings and event fields, manifest revalidation, and schema/documentation errors.
Public behavior and provider hooks are preserved against main at 86e81e72 (SDK 0.11.0).

Tests cover all 37 dispatch actions using local HTTP/TLS services, sockets, subprocesses and an installed SDK wheel.
Validation at b880bc18:

  • Python 3.12 and 3.13: 1,470 tests passed on each, with no skips.
  • SDK coverage: 96.87% statements, 93.22% branches; runtime and HTTP coverage gates pass.
  • just check, just build, just docs, prek hooks and git diff --check passed.

Remaining fixes: #397 (SSE comments/fields), #398 (Serverless error text), #399 (Agent token counting), #400 (literal stops), #401 (embedding validation).
#394 stays open for the full requirements.
Large datasource/Agent blobs can still exceed the daemon's 5 MiB frame limit after base64 encoding.
Deployed Dify, the released Go CLI, external APIs and sustained load were not tested.

Refs #394.
Replaces #395.

Pull Request Checklist

Compatibility Check

  • I have checked whether this change affects the backward compatibility of the plugin declared in README.md
  • I have checked whether this change affects the forward compatibility of the plugin declared in README.md
  • If this change introduces a breaking change, I have discussed it with the project maintainer and specified the release version in the README.md — not applicable: existing behavior is preserved against the baseline above.
  • I have described the compatibility impact and the corresponding version number in the PR description
  • I have checked whether the plugin version is updated in the README.md

Available Checks

  • just build has passed
  • Relevant documentation has been updated (if necessary)

Copilot AI balanced review requested due to automatic review settings October 13, 2026 20:25
@WH-2099 WH-2099 self-assigned this Oct 13, 2026
Comment thread tests/servers/test_serverless.py Resolved

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The SSE parser still rejects or discards valid standard SSE events involving comments or unknown fields.

1 open finding
What changed in this PR

Improves SDK lifecycle cleanup, transport reliability, protocol compatibility, and regression coverage while preserving existing 0.11.0 behavior.

Changes:

  • Closes runtime, HTTP, generator, process, and stream resources reliably.
  • Improves Serverless, stdio, TCP, SSE, manifest, and model handling.
  • Adds extensive contract, integration, lifecycle, and coverage-gate tests.
File Description
.github/​workflows/​test.yml Adds coverage execution and report artifacts.
.gitignore Ignores generated test and coverage files.
CONTRIBUTING.md Documents the expanded test workflow.
README.md Documents compatibility and cleanup guarantees.
justfile Adds patched test and coverage commands.
pyproject.toml Adds test dependencies and coverage configuration.
scripts/​__init__.py Marks scripts as a package.
scripts/​check_coverage.py Enforces scoped coverage thresholds.
src/​dify_plugin/​core/​documentation/​generator.py Corrects schema traversal and descriptions.
src/​dify_plugin/​core/​plugin_executor.py Closes endpoint and trigger responses.
src/​dify_plugin/​core/​plugin_registration.py Loads separately declared trigger constructors.
src/​dify_plugin/​core/​runtime.py Registers response subscriptions before sending.
src/​dify_plugin/​core/​server/​__base/​filter_reader.py Makes subscriber closure reliable.
src/​dify_plugin/​core/​server/​__base/​request_reader.py Adds reader shutdown state and cleanup.
src/​dify_plugin/​core/​server/​__base/​response_writer.py Writes protocol frames atomically.
src/​dify_plugin/​core/​server/​io_server.py Handles Serverless cancellation and finalization.
src/​dify_plugin/​core/​server/​serverless/​request_reader.py Stops HTTP services and response work.
src/​dify_plugin/​core/​server/​serverless/​response_writer.py Adds cancellation-aware response writing.
src/​dify_plugin/​core/​server/​stdio/​request_reader.py Stops reading cleanly at EOF.
src/​dify_plugin/​core/​utils/​position_helper.py Handles missing position files.
src/​dify_plugin/​entities/​model/​provider.py Preserves reusable manifest input.
src/​dify_plugin/​entities/​tool.py Preserves reusable credential declarations.
src/​dify_plugin/​interfaces/​model/​ai_model.py Handles short provider module names.
src/​dify_plugin/​interfaces/​model/​large_language_model.py Closes provider streams during processing.
src/​dify_plugin/​interfaces/​model/​openai_compatible/​llm.py Expands SSE and enum handling.
src/​dify_plugin/​interfaces/​model/​openai_compatible/​tts.py Closes HTTP audio streams.
src/​dify_plugin/​invocations/​model/​llm.py Shares blocking-result aggregation logic.
src/​dify_plugin/​invocations/​model/​llm_structured_output.py Reuses LLM chunk aggregation.
src/​dify_plugin/​invocations/​model/​tts.py Closes backwards-invocation streams.
src/​dify_plugin/​plugin.py Registers runtime parameters and closes generators.
tests/​__main__.py Applies gevent patches before pytest.
tests/​__mock_server/​__main__.py Removes the legacy mock-server entrypoint.
tests/​__mock_server/​openai.py Removes the legacy OpenAI server.
tests/​config/​test_config.py Expands configuration validation coverage.
tests/​config/​test_integration_config.py Tests CLI discovery and versions.
tests/​conftest.py Provides isolated HTTP/TLS fixtures.
tests/​consts/​mockserver.py Removes the fixed mock-server port.
tests/​core/​__init__.py Cleans the package marker.
tests/​core/​plugin_fixtures.py Adds reusable contract-plugin fixtures.
tests/​core/​test_class_loader.py Covers plugin class loading behavior.
tests/​core/​test_endpoint_lifecycle.py Verifies endpoint resource cleanup.
tests/​core/​test_invoke_speech_to_text.py Expands format and tempfile cleanup tests.
tests/​core/​test_position_helper.py Covers position loading and sorting.
tests/​core/​test_yaml_loader.py Refocuses YAML loading coverage.
tests/​entities/​models/​test_provider_manifest.py Tests model glob reuse and positions.
tests/​entities/​plugin/​test_declaration.py Validates minimum Dify versions.
tests/​entities/​test_datasource.py Covers numeric boolean defaults.
tests/​entities/​test_invoke_message.py Tests blob and variable round trips.
tests/​entities/​test_trigger.py Tests reusable trigger credentials.
tests/​file/​__init__.py Adds the file-test package marker.
tests/​file/​test_file.py Tests downloads, proxies, TLS, and caching.
tests/​file/​test_upload.py Tests signed multipart uploads.
tests/​fixtures/​__init__.py Adds the fixtures package marker.
tests/​fixtures/​plugin_cli.py Emulates the CLI JSON protocol.
tests/​fixtures/​plugins/​__init__.py Adds the plugin-fixtures package marker.
tests/​fixtures/​plugins/​contract_plugin/​__init__.py Adds the contract-plugin package marker.
tests/​fixtures/​plugins/​contract_plugin/​_assets/​icon.svg Adds a fixture icon.
tests/​fixtures/​plugins/​contract_plugin/​agents/​__init__.py Adds the agent package marker.
tests/​fixtures/​plugins/​contract_plugin/​agents/​echo.py Implements a fixture agent.
tests/​fixtures/​plugins/​contract_plugin/​agents/​echo.yaml Declares the fixture agent.
tests/​fixtures/​plugins/​contract_plugin/​agents/​provider.yaml Declares its agent provider.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​__init__.py Adds the datasource package marker.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​documents.py Implements document fixtures.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​documents.yaml Declares the document provider.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​documents_echo.yaml Declares the document datasource.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​drive.py Implements drive fixtures.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​drive.yaml Declares the drive provider.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​drive_echo.yaml Declares the drive datasource.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​provider.py Implements datasource authentication.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​website.py Implements website fixtures.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​website.yaml Declares the website provider.
tests/​fixtures/​plugins/​contract_plugin/​datasources/​website_echo.yaml Declares the website datasource.
tests/​fixtures/​plugins/​contract_plugin/​endpoints/​__init__.py Adds the endpoint package marker.
tests/​fixtures/​plugins/​contract_plugin/​endpoints/​echo.py Implements a fixture endpoint.
tests/​fixtures/​plugins/​contract_plugin/​endpoints/​provider.yaml Declares the fixture endpoint.
tests/​fixtures/​plugins/​contract_plugin/​main.py Runs the installed-wheel fixture.
tests/​fixtures/​plugins/​contract_plugin/​manifest.yaml Declares the contract plugin.
tests/​fixtures/​plugins/​contract_plugin/​models/​__init__.py Adds the model package marker.
tests/​fixtures/​plugins/​contract_plugin/​models/​embedding.yaml Declares an embedding model.
tests/​fixtures/​plugins/​contract_plugin/​models/​llm.yaml Declares a polling LLM.
tests/​fixtures/​plugins/​contract_plugin/​models/​moderation.yaml Declares a moderation model.
tests/​fixtures/​plugins/​contract_plugin/​models/​provider.py Implements model credential validation.
tests/​fixtures/​plugins/​contract_plugin/​models/​provider.yaml Declares the model provider.
tests/​fixtures/​plugins/​contract_plugin/​models/​rerank.yaml Declares a rerank model.
tests/​fixtures/​plugins/​contract_plugin/​models/​speech2text.yaml Declares speech-to-text.
tests/​fixtures/​plugins/​contract_plugin/​models/​tts.yaml Declares text-to-speech.
tests/​fixtures/​plugins/​contract_plugin/​provider/​__init__.py Adds the provider package marker.
tests/​fixtures/​plugins/​contract_plugin/​provider/​contract.py Implements tool credential validation.
tests/​fixtures/​plugins/​contract_plugin/​provider/​contract.yaml Declares the tool provider.
tests/​fixtures/​plugins/​contract_plugin/​tools/​__init__.py Adds the tools package marker.
tests/​fixtures/​plugins/​contract_plugin/​tools/​echo.py Implements the fixture echo tool.
tests/​fixtures/​plugins/​contract_plugin/​tools/​echo.yaml Declares the fixture echo tool.
tests/​fixtures/​plugins/​contract_plugin/​triggers/​__init__.py Adds the trigger package marker.
tests/​fixtures/​plugins/​contract_plugin/​triggers/​constructor.py Implements subscription workflows.
tests/​fixtures/​plugins/​contract_plugin/​triggers/​dispatch.py Implements webhook dispatch.
tests/​fixtures/​plugins/​contract_plugin/​triggers/​echo.yaml Declares the echo event.
tests/​fixtures/​plugins/​contract_plugin/​triggers/​event.py Implements the echo event.
tests/​fixtures/​plugins/​contract_plugin/​triggers/​events.yaml Declares the trigger provider.
tests/​http_server.py Adds local streaming HTTP fixtures.
tests/​integration/​conftest.py Builds and runs the installed SDK wheel.
tests/​integration/​stdio.py Adds robust stdio process helpers.
tests/​integration/​test_invoke_llm.py Removes the external plugin integration test.
tests/​integration/​test_stdio_plugin.py Tests installed-wheel stdio behavior.
tests/​interfaces/​datasource/​test_construct_datasource.py Removes narrow constructor-only tests.
tests/​interfaces/​model/​openai_compatible/​conftest.py Tracks live HTTP responses.
tests/​interfaces/​model/​openai_compatible/​test_llm_stream_lifecycle.py Adds fragmented SSE property tests.
tests/​interfaces/​model/​openai_compatible/​test_plugin_config.py Removes implementation-detail config tests.
tests/​interfaces/​model/​openai_compatible/​test_text_embedding.py Tests credential failure cleanup.
tests/​interfaces/​model/​test_ai_model_parameter_rules.py Expands template merge coverage.
tests/​interfaces/​model/​test_ai_model_timing_context.py Tests timing cleanup and concurrency.
tests/​interfaces/​model/​test_llm_stop_tokens.py Locks in stop-sequence behavior.
tests/​interfaces/​model/​test_llm_stream_errors.py Tests stream closure and error mapping.
tests/​interfaces/​model/​test_model_pricing.py Covers decimal pricing and usage.
tests/​interfaces/​model/​test_model_registry_get_model.py Tests concurrent instance isolation.
tests/​interfaces/​model/​test_rerank.py Covers multimodal rerank dispatch.
tests/​interfaces/​model/​test_text_embedding.py Covers embedding dispatch and timing.
tests/​interfaces/​model/​utils.py Adds a reusable LLM stub.
tests/​interfaces/​trigger/​test_construct_trigger.py Removes redundant constructor coverage.
tests/​interfaces/​trigger/​test_trigger_workflows.py Tests complete trigger workflows.
tests/​invocations/​conftest.py Adds backwards-invocation recording fixtures.
tests/​invocations/​test_storage.py Expands storage protocol assertions.
tests/​invocations/​test_tool.py Tests tool context and cancellation.
tests/​invocations/​test_workflow_node.py Tests workflow-node serialization.
tests/​regressions.md Maps behaviors to regression tests.
tests/​servers/​helpers.py Adds in-memory server test helpers.
tests/​servers/​test_response_writer.py Tests framing and concurrent writes.
tests/​servers/​test_router.py Tests route validation and precedence.
tests/​test_cli.py Covers CLI documentation commands.
tests/​test_coverage_gate.py Tests coverage threshold enforcement.
tests/​test_llm_result.py Tests complete-result conversion.
tests/​test_model_polling.py Expands polling dispatch and errors.
tests/​test_prompt_message.py Expands message compatibility coverage.
tests/​test_trigger_factory.py Uses the contract trigger constructor.
tests/​utils/​test_http_parser.py Expands HTTP envelope and header tests.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI balanced review requested due to automatic review settings October 13, 2026 21:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Several retained compatibility assertions directly contradict required behavior in the issue this pull request declares closed.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Low severity Validate embedding batches before acceptance and preserve index order

tests/​regressions.md:20

This pins behavior that contradicts #394 while the PR closes that issue. The issue requires validating each embedding batch’s shape, indices, and finite values before accepting it or sending the next batch, but the linked tests explicitly accept missing/duplicate/out-of-range indices, non-finite vectors, and provider-order attachment. Either implement those validations (and index-based ordering) or leave #394 open and track this requirement separately.

This issue also appears in the following locations of the same file:

  • line 27
  • line 33

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 13, 2026 21:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several new compatibility tests contradict explicit #394 requirements for literal stops, embedding validation, and Agent token handling.

3 open findings

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 13, 2026 21:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Stop sequences still use regex semantics, and several model HTTP-response paths remain outside the promised cleanup guarantee.

3 open findings
Previously missed (1)

In code that hasn't changed since last review

Medium severity Ensure HTTP responses are closed on every model path

src/​dify_plugin/​interfaces/​model/​openai_compatible/​tts.py:89

The advertised HTTP-response cleanup is incomplete: only the TTS path is made context-managed here, while openai_compatible/text_embedding.py:127, rerank.py:84, and speech2text.py:53 still create responses without closing them. In particular, embedding and rerank HTTP-error paths raise before consuming the body, so connections can remain occupied until garbage collection. Apply the same guaranteed cleanup to every response-owning model path.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 13, 2026 21:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new regressions intentionally preserve stop-token and embedding behaviors that contradict the required behavior in issue #394.

3 open findings

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 13, 2026 22:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The stop-token regression contradicts #394’s literal matching requirement.

3 open findings

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 13, 2026 22:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

TTS response cleanup can mask the primary provider result or error and falsely reject valid credentials.

3 open findings
Previously missed (1)

In code that hasn't changed since last review

Medium severity Suppress response cleanup errors to preserve primary request failures

src/​dify_plugin/​interfaces/​model/​openai_compatible/​tts.py:89

Using the response as a context manager allows an exception from Response.close() to replace the actual request/streaming error; it can also make validate_credentials() reject valid credentials after the sample chunk succeeded. The existing LLM and embedding HTTP paths explicitly suppress cleanup failures (openai_compatible/llm.py:331-333 and text_embedding.py:222-224) to preserve the primary result. Please close this response in a finally block under suppress(Exception) as well.

🧠 Review effort: Balanced

@WH-2099
WH-2099 merged commit a91e6b4 into main Oct 14, 2026
16 checks passed
@WH-2099
WH-2099 deleted the fix/sdk-runtime-compatibility branch October 14, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(model): preserve SSE data alongside comments and ignored fields

4 participants