Preface

DSH routes OpenAI-compatible providers through @earendil-works/pi-ai while processing agent turns. When a streaming response ends without a terminal finish_reason, the adapter throws a Stream ended without finish_reason error, causing turns that should have been valid to be aborted. Other agents (such as Codex) typically treat stream closure as the end of the response. This plugin rewrites error finish states so that DSH can tolerate this kind of gateway behavior.

Plugin Overview

This is a DSH plugin maintained by Canson666 and categorized under model inference. The core issue it solves is: when an OpenAI-compatible model (GPT family, gateways, proxies) returns complete content but does not provide a terminal finish_reason, it prevents the agent turn from being incorrectly aborted.

Core Features

The plugin works by hooking into the llm/stream pipeline:
* Rewrite erroneous finish states as normal stops: When a terminal finish is detected as an error type (finish {kind:'error', code:'TRANSPORT'}), it rewrites it to a normal stop finish (or to tool-calls if tool call deltas have already been passed).
* Tolerate empty responses: Accept empty responses from gateways that report outputTokens: 0 and have no finish_reason, avoiding retry loops.
* Preserve other error handling: All other error finishes (transport resets, rate limiting, HTTP errors) remain unchanged so that existing retry strategies still work.

Installation and Activation

The plugin does not provide a command-line installation command; manual configuration is required. It is recommended to install it as a persistent Profile Bundle.

  1. Place this folder as a package named dsh-finish-reason-patch in a location resolvable by the DSH profile. The common approach is to create a junction or symlink at <profile root>/node_modules/dsh-finish-reason-patch that points to this folder.
  2. In the package.json at the profile root, add the package to dsh.profile.bundles and dependencies. For example:
   {
     "dsh.profile.bundles": ["dsh-finish-reason-patch"],
     "dependencies": {
       "dsh-finish-reason-patch": "link:<绝对路径>"
     }
   }
  1. Restart DSH. The plugin will be loaded automatically.

Verification and Usage

  • Logic self-test: Run the self-test script; no network connection is required:
   node selftest.mjs
  • Real-world scenario: Run normal conversations with GPT-like models. Previously aborted turns should now complete, and you should see the log:
    [finish-reason-patch] provider finished without finish_reason -> mapped to "stop"

Removal

To remove the plugin, manually edit the profile’s package.json (remove the entry from dsh.profile.bundles and the dependency from dependencies), delete the junction at <profile root>/node_modules/dsh-finish-reason-patch, and then restart DSH. There is no need to modify internal DSH files.

Notes

  • License: MIT.
  • Runtime environment: The plugin runs with the permissions of the current DSH process.
  • Dependencies: Node.js >= 20.0.0 is required.
  • Removal method: Removal requires manual configuration editing and cannot be uninstalled through the plugin itself.