DSHX is a plugin development toolchain for DeepSeek Harness. This article reviews how it evolved from a Vite plugin into a complete workflow covering Host/Client, Typed API, real Profile debugging, compatibility diagnostics, and the Framework Hub.
I've been working on DeepSeek Harness for a while.
At first, I just wanted to write a few plugins for DSH. After actually getting started, I found that the plugin logic itself wasn't as hard as I expected—the麻烦的是周围那一圈工程问题:
- Where do Host and Client run respectively;
- How should Cordis dependency injection and
injectbe declared; - Which Slot should the UI mount to;
- What DSH metadata is needed in
package.json; - How do you build and debug Host and Client separately;
- How should the plugin handle rapidly changing DSH versions.
When writing just one plugin, you can figure these out by reading the source code. But if you want to keep writing plugins in the future, or even get more developers involved, having everyone piece together the development process from source code is a bit wasteful.
So I built DSHX:
- Project: github.com/liyown/dshx
- Framework Hub: dshx.io
As of August 2026, DSHX is still at 0.1.x Preview. The current Authoring API is an API Candidate, not a 1.0 stable commitment.
Writing Plugins Isn't Hard, Making Them Actually Run Is
At first, I just wanted to make a Vite plugin for DSH.
The idea was straightforward:
TypeScript / React
↓
Vite
↓
Host + Client
↓
DSH Plugin
If I encapsulated the Host and Client build entries and added some types, the development experience should improve a lot.
But as I kept going, I ran into more practical problems. For example, when a Client component needs to call a Host API, the developer needs to know what the Host exposes, how the Client gets a Connection, which Provider the plugin should declare, what the input and output types are, and whether the current DSH Runtime actually supports this path.
This knowledge is scattered across build, runtime, Manifest, and specific services. Vite can only solve part of it.
DSHX gradually evolved from a Vite plugin into a toolchain that includes Authoring API, building, checking, scaffolding, real Profile debugging, and compatibility diagnostics.
My current understanding of it: DSHX is responsible for organizing "what DSH can do" into "how plugin developers should write it," but it doesn't reimplement DeepSeek Harness.
First, Clarify the Boundary: DSHX Doesn't Reimplement DSH Runtime
It's easy to keep expanding when building a Framework.
API not convenient? Wrap it. Communication not convenient? Build an RPC layer. State hard to manage? Add a Store. In the end, the Framework itself has DI, Runtime, RPC, Cache, and Event Bus, while the original DSH is just left as a底层驱动.
I deliberately avoided this path.
DSHX's principle: the development experience can be redesigned, but runtime semantics should be handed to DSH as much as possible. This table isn't a one-to-one mapping, but the responsibility boundary between the two:
| DSHX Handles | DSH / Cordis Continues to Handle |
|---|---|
| Types and declarations | Fiber and Scope |
| Code generation and static checks | Registry and dependency injection |
| Host / Client build | Connection and runtime communication |
| Scaffolding and development workflow | Persistence and Prompt Assembly |
| Compatibility diagnostics | HMR, disposal, and disposer lifecycle |
DSHX can provide Authoring APIs like defineHost, defineApi, defineSlot, but they ultimately map back to DSH's official capabilities. The build output also doesn't need to carry a private DSHX Runtime to run.
This might sound conservative, but DSH is still iterating quickly. If DSHX creates another Runtime on top, it might be convenient short-term, but maintaining two sets of semantics six months later will likely drag the project down.
Typed API: Let TypeScript Report Errors First
Host and Client communication is a typical example.
First, define a shared Contract:
import { defineApi, method } from "@becomeopc/dshx/api";
export const statusApi = defineApi({
id: "status",
version: 1,
methods: {
get: method<void, { readonly ready: boolean }>(),
},
});
Host implements it:
import { defineHost } from "@becomeopc/dshx/host";
import { statusApi } from "./api/status.js";
export default defineHost({
apis: [
statusApi.host({
get: () => ({ ready: true }),
}),
],
});
Client consumes the same Contract directly:
import { useApiQuery } from "@becomeopc/dshx/client";
import { statusApi } from "./api/status.js";
function Status() {
const query = useApiQuery(statusApi, "get", {
enabled: true,
});
if (query.status === "pending") {
return <span>Loading...</span>;
}
if (query.status === "error") {
return <button onClick={query.refetch}>Retry</button>;
}
return <span>{query.data.ready ? "Ready" : "Unavailable"}</span>;
}
What I care about here isn't writing less code, but whether errors can appear earlier:
- Wrong method name? TypeScript reports it;
- Host missing a Handler implementation? The precise Handler type blocks it;
- Input/output doesn't match Schema? Rejected at the Host boundary;
- Client uses a capability but the plugin didn't declare the corresponding Provider?
dshx checkprompts it; - Locally installed DSH isn't in the current Adapter's supported range? Build or development stage gives compatibility diagnostics.
I don't want to wait until the plugin is installed in DSH and the page is open before seeing a decontextualized Runtime Error.
Why dshx dev Must Run Real DSH
The easiest approach for a dev server is to Mock a DSH environment yourself.
This starts fast and is easy to control. But the Mocked Slot, Connection, Provider, and lifecycle are all fake. Everything working fine in the dev server doesn't guarantee the plugin will work after being installed in real DSH.
So dshx dev runs the real DSH Profile, not a parallel mock Runtime:
- Client changes go through DSH's official HMR;
- After Host modifications succeed, it's rebuilt and the Host restarts by default;
- DSH starts only after initial build passes;
- When config or dependency reload fails, the last available session is preserved.
Runtime Inspect follows the same principle:
dshx inspect slots
dshx inspect tools
dshx inspect services
dshx inspect events
inspect only reads the official Provider supported by the current Composition's Adapter. When Runtime is unavailable, it returns diagnostics instead of falling back to a seemingly complete but actually irrelevant offline catalog.
This is also very useful for Coding Agents. The Agent doesn't have to guess "there's probably a sidebar.xxx Slot here"—it can Inspect the Runtime first, then decide where to mount the code.
I want DSHX CLI commands to be as atomic, inspectable, and composable as possible. The CLI provides facts and diagnostics, and the next step is planned by the developer or Agent.
Compatibility Can't Be Exhaustively Tested for Every DSH Version
DSH is still in Developer Preview. Assuming it continues with:
0.1.0
0.1.1
0.1.2
0.2.0
...
If DSHX writes an Adapter for each version, then runs full tests for "every plugin × every DSH version," this maintenance model will quickly become unmanageable.
So DSHX uses "Protocol Generations" to manage compatibility: a new Protocol Generation only enters when official Contracts, API seams, Loader behavior, or Runtime invariants change in ways that require different adaptation. Simply releasing a patch or minor version doesn't automatically generate a new Adapter.
I've also started deliberately distinguishing three facts that are often mixed together:
Declared
The author declares support range through peerDependencies
Compatible
The version falls within a known protocol generation, but hasn't completed real verification on that version
Verified
This specific DSH version passed real Runtime smoke tests
For unverified prereleases, DSHX further marks them as experimental; versions without an Adapter to handle them are unsupported.
A version falling within a SemVer range only means it intersects with some protocol generation, not that it has actually run on a real Runtime. This distinction is especially important for the plugin marketplace.
Framework Hub Doesn't Make Guarantees for Plugin Authors
I once wanted to make the plugin marketplace very strict: automatically determine if a package is a DSH plugin, which versions it's compatible with, whether it can be installed, and if metadata is complete.
I quickly realized this would push maintenance costs to unacceptable levels. Third-party plugins won't all provide complete metadata according to DSHX's conventions, and I can't test all version combinations for all authors.
The positioning of dshx.io has since converged. The Framework Hub is more like a plugin information layer: organizing projects, versions, source code, authors, README, installation targets, compatibility declarations, and risk signals from public sources like GitHub and npm, while clearly distinguishing source facts, community organization, and real verification evidence.
The Hub won't reject a plugin just because it hasn't been verified by DSHX, nor will it guarantee it will successfully install in a user's DSH environment.
For community plugins, I'd rather present the facts, evidence, and risk warnings, and let users make the final decision. DSHX only takes responsibility for what it actually knows.
Writing a Plugin Marketplace Inside DSH with DSHX
There's a Dogfooding project in the repo that I really like:
@becomeopc/dshx-plugin-marketplace
It's a regular DSH Bundle itself. After installation, you can browse installable plugins from the Framework Hub at:
Settings → Plugins → Marketplace
This Marketplace fully uses DSHX's development path, including:
defineHostdefineSettingsdefineApidefineClientdefineLocaledefineSlot- Standard Schema
useApiQuery- CSS Modules
- Profile development workflow
- Client HMR
The Preview version can be installed like this:
dsh plugin --profile web add @becomeopc/dshx-plugin-marketplace@preview
dsh --profile web
If my own Framework makes writing its own plugin marketplace painful, the API probably isn't designed well yet. Compared to stacking dozens of independent demos, I prefer using one real plugin to continuously expose problems.
DSHX Finally Has Its First Usable Preview
After several rounds of API and architecture iteration, DSHX has entered its first practically usable Preview stage.
Create a plugin:
pnpm create dshx@preview my-plugin
cd my-plugin
pnpm check
pnpm dev
When you need more complete API examples:
pnpm create dshx@preview my-plugin --template showcase --style tailwind
It currently covers:
- Host / Client Authoring;
- Typed API, Settings, and Prompt;
- Slot and Locale;
- Vite Build, CSS Modules, and Tailwind;
- Profile development workflow and Client HMR;
- Runtime Inspect;
- CLI checks, diagnostics, and limited deterministic fixes;
- DSH Protocol Generations and compatible Adapters;
- Plugin scaffolding;
- Framework Hub;
- Marketplace plugin inside DSH.
Conversation Components are still in @becomeopc/dshx/experimental/conversation. Streaming hasn't been urgently made into a public abstraction.
These capabilities depend on more stable upstream event vocabulary, persistence, Connection Ownership, cancellation, reconnection, and backpressure semantics. Not providing them now is more stable than creating a private protocol that must be abandoned in six months.
Next, Continue Polishing with More Real Plugins
DSHX is still 0.1.x. I'm not in a rush to add more APIs. Instead, I want to write some real plugins next to see where this path still gets stuck:
Create project
↓
Discover DSH capabilities
↓
Write Host / Client
↓
Local check
↓
Real Profile debugging
↓
Build and publish to npm
↓
Enter Framework Hub
↓
User installation
If this path can stabilize, DSHX will have truly solved the DeepSeek Harness plugin development experience problem.
I initially just wanted to write a Vite plugin. Looking back, what I actually wanted to do was add a complete, inspectable, and continuously evolving plugin development workflow for DeepSeek Harness.
The project is still Preview, and APIs will still adjust. Exactly because of that, it's a good time to try it out with real plugins: