Introduction¶
When integrating OpenAI-compatible models with agents, there is a subtle but fatal compatibility issue: some gateways only push content increments and an end marker for streaming responses, never sending the finish_reason field from start to finish. DSH’s model client, pi-ai, classifies this stream as truncated (Stream ended without finish_reason). The host then maps this into a TRANSPORT error finish. The result is: content arrives completely for every request, yet the turn ends in failure.
A typical example is the Snowflake Cortex REST gateway (*.snowflakecomputing.com/api/v2/cortex/v1): its Chat Completions SSE stream pushes content increments and a terminating data: [DONE], but never sends finish_reason—whether it is plain text or tool calls (non-streaming responses return finish_reason: "").
To work around this, you either have to modify the pi-ai source code or set up a proxy layer in front of the gateway to add the field, both of which require additional maintenance. The dsh-llm-finish-reason-tolerance introduced below chooses a third path: rewriting this specific terminal state error on the harness side into the success finish that the content should have received.
What is this¶
dsh-llm-finish-reason-tolerance is a DeepSeek Harness (DSH) host plugin maintained by michael-han-il, version 0.2.0, under the MIT license. It does something restrained: it only rewrites this specific terminal state error where the streaming response ends without finish_reason but the content has been delivered; everything else is passed through as-is.
Implementation-wise, it does not modify any code in pi-ai, dsh-llm, or dsh-llm-pi-ai; it works purely via the harness’s mounting mechanism.
How it works¶
The plugin listens to the harness’s llm/stream waterfall stream. It is registered globally and prepended, and the iterable object it returns is exactly what the consumer iterates over, so it can wrap every model stream. The rewrite rules fall into three cases:
- Delivered tool-call block → Rewrite terminal finish to
{ kind: 'tool-calls' }; - Delivered text content → Rewrite terminal finish to
{ kind: 'stop' }; - No content delivered, or other errors → Pass through as-is.
The rewrite is double-gated: pi-ai’s terminal error message must be exactly Stream ended without finish_reason, and content has already been delivered. True mid-session truncation in pi-ai appears as a different error message and won’t be masked by this rule.
Installation and Activation¶
First, confirm the environment: you need DeepSeek Harness (newer deployments come with @deepseek-ai/cordis ≥ 4 and @deepseek-ai/schemastery ≥ 3), and Node ≥ 20.
This plugin is host-level and single-instance: it must be installed into a profile (e.g., web) and mounted into the host composition; do not mount it into an agent preset.
Bundle Installation (Recommended)¶
The package is released as a profile bundle with a built-in cordis.patch.yml patch layer (declared via dsh.bundle.patch in package.json). Installation mounts it automatically; you don’t need to manually edit the host composition:
dsh plugin --profile web add /path/to/dsh-llm-finish-reason-tolerance-0.2.0.tgz
This single command does three things:
- pnpm packages the dependency tree into the profile;
- The
dsh pluginCLI detectsdsh.bundle.patchand automatically appends the package to the profile’spackage.jsondsh.profile.bundleslist:
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-llm-finish-reason-tolerance"] } }
- On the next startup, the bundle’s built-in patch layer inserts the
llm-finish-reason-toleranceline into the host composition.
You can just replace <profile> with the corresponding name for other profiles, e.g., dsh plugin --profile tui add …. Note that the installation form provided in the official documentation is a local tgz path; there is no command provided to install directly from npm or GitHub.
Plain Installation (Alternative)¶
If you prefer a standard dependency method, install the package first in the profile directory (e.g., ~/.dsh/profiles/web):
pnpm add /path/to/dsh-llm-finish-reason-tolerance-0.2.0.tgz
Then manually add the mount line in ~/.dsh/profiles/<profile>/cordis.patch.yml. The profile patch layer is a list of patch operations; new lines must be wrapped in insert:—a bare id: line is an override operation on existing entries and will silently skip if no match is found:
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- insert:
- id: llm-finish-reason-tolerance
name: dsh-llm-finish-reason-tolerance
config:
providers:
- snowflake-cortex
Do not mix the two installation methods; otherwise, the mount line will be inserted twice.
Restart¶
Regardless of the method, you must restart the harness after installation—the plugin is a new module and only mounts at startup. After restarting, llm-finish-reason-tolerance will appear in the plugin list.
Configuration¶
There is only one configuration item:
| Field | Type | Default | Meaning |
|---|---|---|---|
providers |
string[] |
[] |
Provider route keys on which this tolerance behavior takes effect (corresponding to the names in the llm-pi-ai setting section). [] means all providers. |
Bundle installation comes with providers: [] by default, meaning it applies to all provider routes. To limit it to specific routes, override this config line in your own profile patch layer:
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- id: llm-finish-reason-tolerance
config:
providers:
- snowflake-cortex
Because the bundle layer is applied first, the line it inserts can be located by subsequent layers. Note that config is a full replacement, so you must write the full set. Plain installation simply changes the providers value in the insert: line.
Integrating with Snowflake Cortex¶
The Snowflake provider needs to be configured under the llm-pi-ai setting section in ~/.dsh/settings.yaml. The route key must correspond to the plugin’s providers list. The default empty list applies to all routes, so no matching is needed; if you limit the scope, the route key must be exactly consistent (e.g., snowflake-cortex).
# ~/.dsh/settings.yaml
llm-pi-ai:
providers:
snowflake-cortex:
displayName: Snowflake Cortex (SG)
apiKeyEnv: SNOWFLAKE_CORTEX_API_KEY
api: openai-completions
baseURL: https://<account>.snowflakecomputing.com/api/v2/cortex/v1
models:
- id: claude-sonnet-5
name: Claude Sonnet 5
# For example other models available in your account
# - id: deepseek-r1
# name: DeepSeek R1
Points to note:
- Use
openai-completionsforapiand write the full…/api/v2/cortex/v1forbaseURL; - The OpenAI SDK automatically uses the environment variable pointed to by
apiKeyEnvforAuthorization: Bearerauthentication, so no extra headers configuration is needed; - The plugin only solves the missing
finish_reason;deepseek-r1is deprecated at this endpoint, so it won’t work regardless of whether the plugin is installed.
Use Cases and Considerations¶
The suitable scenarios are clear: your DSH connects to OpenAI-compatible gateways that omit finish_reason (Snowflake Cortex is typical), content arrives for every request, but the turn ends with a TRANSPORT error. Install the plugin and restart, and the turn will complete normally.
Regarding security, the rewrite conditions are tight:
- It only triggers when pi-ai’s terminal error is exactly
Stream ended without finish_reasonand content has been delivered; - Errors like authentication, rate limiting, quotas, true mid-session truncation, empty responses, etc., are all passed through as-is;
- Responses with neither
finish_reasonnor content retain the original error finish.
Points to note yourself:
- The plugin runs with the current dsh process’s permissions; it is recommended to review the source code and license before installing;
- It is host-level and single-instance; install into a profile, do not mount into an agent preset;
- The two installation methods cannot be mixed.
If you want to participate in development, the repository structure is straightforward: lib/index.js is the plugin itself (pure ESM, no build steps, harness loads it directly), and test/finish-reason.test.mjs is a self-contained wrapper test. Common commands:
npm test # node --test, uses synthetic chunked streams to test wrapper logic
npm pack # builds the distribution package
Summary¶
In one sentence: When the OpenAI-compatible gateway you connect to does not send finish_reason, this plugin turns “content delivered but turn failed” into “content delivered, turn completed successfully,” and it only rewrites this specific case without masking any other errors. The installation cost is also low—a single command in bundle form, effective after a restart.
Plugin page in the community directory: https://www.skillhub.cn/plugins/michael-han-il/dsh-llm-finish-reason-tolerance (The directory categorizes it under “Model Inference”; the directory is an independent site with no official affiliation to DeepSeek or HF). Source code and README: https://github.com/michael-han-il/dsh-llm-finish-reason-tolerance .