Mhmd7-7/dsh-ai-model-hub

dsh-ai-model-hub

仓库尚未提供可用的公开描述。

未分類

dsh-ai-model-hub

A modular local AI model ecosystem for DeepSeek Harness (DSH).

DSH is the agent — it reasons, plans, calls tools, and orchestrates workflows. It is not the model runtime. The specialised models (text, image, 3D, audio, video) are external and local, reached through a capability interface. DSH never learns how to launch Stable Diffusion, which Python file starts a mesh generator, or how a given engine manages its process. It only asks:

What capabilities are available?

Everything below that question is this project's job.

┌──────────────────────────────────────────────────────────────────────┐
│  DeepSeek Harness                        agent reasoning, planning,  │
│  ─────────────────                       tool invocation, workflows  │
│                                                                      │
│      tools: invoke_model · list_models · list_capabilities ·         │
│             get_model_status · start_model · stop_model · …          │
└───────────────────────────────┬──────────────────────────────────────┘
                                │  dsh-ai-model-hub DSH plugin
                                │  (the ONLY DSH-aware code)
┌───────────────────────────────▼──────────────────────────────────────┐
│  ModelHub facade                        invokeModel({ capability })  │
│  ┌────────────────┬──────────────────┬───────────────────────────┐   │
│  │ Capability     │ Model Router     │ Runtime Manager           │   │
│  │ Catalog        │ deterministic    │ lifecycle · health ·      │   │
│  │ registry +     │ capability-first │ idle timeout · resources  │   │
│  │ validation     │ selection        │                           │   │
│  └────────────────┴──────────────────┴───────────────────────────┘   │
│  ┌──────────────────────────────┬────────────────────────────────┐   │
│  │ Model Adapters               │ Artifact Store                 │   │
│  │ openai_compatible · http_json│ durable, typed, addressable    │   │
│  │ comfyui · three_d ·          │ text/image/audio/video/3D/…    │   │
│  │ mock (tests only)            │                                │   │
│  └──────────────────────────────┴────────────────────────────────┘   │
└───────────────────────────────┬──────────────────────────────────────┘
                                │  spawn (argv, never a shell string)
┌───────────────────────────────▼──────────────────────────────────────┐
│  Local engines: Ollama · llama.cpp · A1111/Forge · ComfyUI ·         │
│  TRELLIS · Hunyuan3D · Stable Fast 3D · TripoSR · …                  │
└──────────────────────────────────────────────────────────────────────┘

Status

Live as a DSH profile plugin. The package installs as one unit — dsh plugin add mounts it, so the hub is composed on every boot — and it serves real local engines only: Ollama for text, ComfyUI / A1111 / Forge for images, llama.cpp for a second text engine, and any local image-to-3D Gradio app for meshes. There are no mock models anywhere in the shipped catalogs and no mock fallback at runtime: a wrong model name or a stopped engine is a loud error (or a cold start), never fixture output.

The whole architecture — catalog → routing → runtime → adapter → artifact → chained workflow — is covered by the test suite, which uses an in-process mock adapter as a test double. That adapter is not a model: nothing ships it in a catalog, and no deployment can reach it by accident.

PhaseScopeStatus
1Full architecture, DSH plugin, tests✅ done
2Real local text model (Ollama / llama.cpp / vLLM / LM Studio)✅ done — openai_compatible adapter ships
3Real local image model (A1111 / ComfyUI)✅ done — http_json and comfyui adapters ship
4Real local 3D model✅ done — three_d adapter ships; see docs/three-d.md
5Runtime lifecycle + resource-aware routing✅ done — the machine is probed and routed against
6Multi-model workflows✅ done

Phase 2 needs no new code: the openai_compatible adapter is implemented, and config/examples/real-models.example.json contains ready-to-copy Ollama and llama.cpp entries. Phase 3 is implemented by two adapters, because the two engine families are genuinely different:

  • http_json — engines that answer one request with a finished image: AUTOMATIC1111, Forge, and stable-diffusion.cpp's sd-server. It POSTs the familiar /sdapi/v1/txt2img body and decodes the base64 images that come back.
  • comfyui — ComfyUI takes an API-format node graph, not a prompt endpoint. Each explicitly configured workflow declares capabilities, exact input bindings and a typed output selector. The adapter treats graph internals as opaque, edits only declared inputs, queues on /prompt, polls /history, and retrieves the declared output. No checkpoint or saved-workflow enumeration creates providers.

Phase 4 is three_d: one adapter that speaks the shape the local 3D ecosystem actually has — a Gradio queue API or a JSON HTTP route, one or two calls, a mesh file at the end — so TRELLIS, Hunyuan3D, Stable Fast 3D, and TripoSR are catalog entries rather than code. It writes a typed model_3d artifact (GLB, GLTF, OBJ, STL, or PLY, sniffed from the bytes), and a discoverer verifies a declared engine against the API surface it actually exposes before publishing anything.

Phase 5's remaining gap is closed: ModelHub now probes RAM, VRAM, free VRAM, and disk, re-measures on demand, and routes against measured headroom — so a model that would not fit beside what is already loaded is refused with the shortfall in the message instead of crashing inside the engine.

All adapters are registered by default. See docs/adding-a-model.md, docs/three-d.md, and the protocol templates in config/workflows/.


Install

One command, on a machine that already has Node ≥ 22.6 and DeepSeek Harness:

# into the profile behind `dsh web` — the normal case
dsh plugin --profile web add github:Mhmd7-7/dsh-ai-model-hub

This package is the plugin: it declares dsh.bundle.patch, so dsh plugin add installs it and appends dsh-ai-model-hub to dsh.profile.bundles. The profile composes that bundle on every boot, which is what makes the plugin live, not merely installed. No profile file is edited by hand and no cordis.patch.yml has to be written.

Then restart DeepSeek Harness and ask:

List the available AI model capabilities.
What you wantCommand
Another profiledsh plugin --profile hubtest add github:Mhmd7-7/dsh-ai-model-hub
A pinned releasedsh plugin --profile web add github:Mhmd7-7/dsh-ai-model-hub#v0.7.0
A published copydsh plugin --profile web add dsh-ai-model-hub
A checkout you are editingnode --no-deprecation scripts/install-plugin.mjs --profile web

The last one links the working tree instead of copying it, so the plugin loads your sources; run npm run build there first if you edited anything under src/ or dsh-plugin/.

install.ps1 and install.sh remain one-line wrappers for the first row, and keep their flags (-Profile, -Ref, -Repository, -SkipDoctor):

irm https://raw.githubusercontent.com/Mhmd7-7/dsh-ai-model-hub/main/install.ps1 | iex
curl -fsSL https://raw.githubusercontent.com/Mhmd7-7/dsh-ai-model-hub/main/install.sh | sh

To remove an installation:

dsh plugin --profile web remove dsh-ai-model-hub

Which DeepSeek Harness versions this runs on

The plugin tracks the DSH 0.x line rather than a single release:

Supported@deepseek-ai/dsh* >=0.1.5-rc.2 <0.3.0
Developed and tested against0.1.5-rc.2 (this package's own tree) and 0.2.0-rc.2 (a current host)

Two independent things decide whether that holds, and they fail in different places — which is worth knowing, because only one of them is visible in the plugin's own logs.

The manifest gate runs first, before any plugin code. DSH reads peerDependencies and refuses the row when a @deepseek-ai/dsh* range does not match the harness. For a 0.x version, caret pins the minor: ^0.1.5-rc.2 means >=0.1.5-rc.2 <0.2.0, so it silently refuses every 0.2.x host. That is why the peers here are written as explicit ranges and why tests/peer-range.test.ts exists — it asserts against the harness's own evaluatePluginCompatibility when that package is installed, and against a written-out range matcher when it is not. The old caret form is demonstrated failing in the same file, so it cannot come back unnoticed.

Then the runtime surface is detected, not assumed. dsh-plugin/compat.ts is the only module that touches a host member. It reads the context once, reports what it found, and every registration goes through a helper that contains a missing or differently-shaped member to one diagnostic line:

If the host has no…What is lostWhat is not lost
toolseverythingnothing — the load fails, by name, on purpose
systemPromptthe capability snapshot in the promptevery tool; the agent can still call list_capabilities
webServerthe Local models and Priorities pagesevery tool
effectstopping the hub's processes on plugin unloadevery tool; the effect body is not run at all, because nothing could call its disposer
skillsthe bundled skillevery tool
sandboxPolicyartifacts in the calling session's workspaceevery tool; artifacts fall back to the hub's own store
getnothingthe injected-property path still resolves every optional service
loggerhost-formatted diagnosticsthe diagnostics — they go to the console instead

tools is the one hard requirement. Without a tool registry there is nothing for this plugin to do, and a hub the agent cannot reach is worse than an honest failure: it produces a settings page and the false impression that the capability is available.

Two things are deliberately not wrapped. defineTool, because it is a pure function from a closed input to a closed output whose signature has not moved across the whole 0.1.5-rc → 0.2.0-rc line. And the import type lines, which vanish at compile time — a host that renames a type cannot break a running plugin. The complete runtime DSH coupling is six value imports: defineTool, schemastery's default export, and cordis's Service.

Why the package ships compiled JavaScript

The plugin's sources are TypeScript, and Node runs them directly through type stripping while you work in a checkout. It refuses to do that inside node_modules:

ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING: Stripping types is currently
unsupported for files under node_modules

A package installed by name — from GitHub, from a registry — lands inside the profile's node_modules, so a .ts entry there could never be imported. That is the entire reason this repository commits lib/: lib/ is the same sources compiled to JavaScript, and the package's entry points (main, exports) point at it, so dsh plugin add needs no build step on your machine.

lib/ is generated, never edited: npm run build regenerates it from src/ and dsh-plugin/. The one setting that makes it work is rewriteRelativeImportExtensions, so the emitted JavaScript imports ./x.js where the source said ./x.ts.


Quick start

There are two halves, and you can use either without the other.

A. Use the hub as a library (no DSH needed)

cd dsh-ai-model-hub

npm install                  # typescript + the DSH packages the plugin links against
npm test                     # the suite, ~20 s, no engines or GPU required
npm run typecheck            # strict TypeScript, no errors
npm run build                # compile src/ + dsh-plugin/ into lib/
npm run demo                 # the vertical slice, end to end
npm run demo:workflow        # a multi-step cross-model pipeline
npm run demo:3d              # image → 3D → a real .glb, against a stand-in engine

The demo prints the full path — capability discovery, routing with reasons, invocation, and the artifact on disk:

=== catalog: capability discovery ===
  text_to_image        text → image  via comfyui_z_image_turbo
  text_to_text         text → text  via ollama_text_model
  not available here: image_to_3d, image_to_image, audio_generation, …

=== routing ===
  chose comfyui_z_image_turbo
  rationale: comfyui_z_image_turbo (score 1): …
    [rejected] ollama_text_model: does not declare capability "text_to_image"

=== artifact ===
  valid PNG: yes
  size:      1.2 MiB

It runs against config/models.json, so it needs the engines that catalog names: Ollama for text, ComfyUI for images. node examples/vertical-slice.ts <catalog> takes another catalog, and the last section deliberately asks for a capability nobody serves, to show what a refusal looks like.

npm run demo:3d needs nothing installed. It starts a stand-in 3D engine inside the example and runs the whole real path against it — discovery, health, resource-aware routing, a two-step Gradio protocol, and a .glb written to disk — then prints every HTTP request the engine saw, to make the boundary concrete. To point it at TRELLIS instead, swap the stand-in for your catalog entry; the three capability calls are identical. See docs/three-d.md.

B. Give the capability to the DeepSeek Harness agent

# Installs into the 'web' profile (the one behind `dsh web`), then verifies.
npm run install:plugin

# ...or target another profile:
node --no-deprecation scripts/install-plugin.mjs --profile <name>

Then restart DeepSeek Harness and ask the agent:

List the available AI model capabilities.

Checking an installation

npm run doctor                          # inspects: is it loadable, and if not, why not
node scripts/smoke.mjs --profile web    # acts: applies the plugin and lists what it registered

doctor inspects and reports; smoke actually runs the plugin against a real cordis context and prints the tools it registered. Use smoke when you want proof rather than a checklist:

Registered tools:
  check_model_health   explain_routing    get_model_status   invoke_model
  list_artifacts       list_capabilities  list_models        start_model
  stop_model           refresh_model_discovery
Catalog: model hub ready: 2 model(s), 2 capability(ies) from …/config/models.json

Note on flags. npm run forwards flags to the script, but npm 12 parses --profile as its own config flag and fails. Use the direct node scripts/smoke.mjs --profile <name> form for a non-default profile.

If DSH fails to boot after installing

If the harness dies with:

invalid plugin, expect function or object with an "apply" method, received object

then the row cordis.patch.yml inserts is pointing at something that is not a plugin. In this package that can only happen if the row's name was changed away from the bare package name: dsh-ai-model-hub resolves to the package's . export, which is the plugin (lib/dsh-plugin/index.js, exporting name/inject/apply/Config). The hub library lives behind the dsh-ai-model-hub/library subpath instead, precisely so the two cannot be confused.

The row has two names, and they are easy to conflate: id is the row's name in the composed tree (what a profile's own cordis.patch.yml uses to override or disable it), while name is the module specifier the loader imports. Keep name bare — a subpath such as dsh-ai-model-hub/plugin would load the host half but silently drop the Web UI half, because DSH's client module system only resolves a row's package manifest from a bare specifier. cordis.patch.yml documents this in place, and npm run doctor checks it.

Where does the catalog come from?

The plugin resolves its catalog from ordered anchors, first hit wins:

  1. configPath — an explicit file, if configured. When set it is the only candidate: a wrong path is an error, never a silent fallback.
  2. Each directory in searchRoots, for a deployment that names its own.
  3. The working directory of the DSH host process, walking upward.
  4. This plugin's own installation directory, walking upward.

At every level discovery accepts models.json, model-catalog.json, or dsh-ai-model-hub.json, directly or in a config/ subdirectory.

Anchor 4 is what makes the zero-config case work. DSH installs profile plugins as junction links and Node resolves them to their real path, so the plugin finds the config/models.json shipped by the checkout it was installed from — no matter where the host was launched. Anchor 3 covers the opposite case: a host started inside a project that carries its own catalog.

Why the host's working directory and not the session's? A plugin's apply runs when the profile boots — before any session exists — and one host serves many sessions with different workspaces, so there is no single "session working directory" to read at that moment. Anchors 1 and 2 are the explicit way to name a workspace. Per-session resolution is a deliberate future step, not an oversight; see docs/roadmap.md.

If no anchor yields a catalog the plugin logs model hub disabled together with every directory it tried, registers no tools, and lets DSH boot normally — a missing or broken catalog never makes the agent unusable.

Installing from a checkout instead of from GitHub

dsh plugin add <path> links the working tree — DSH installs profile plugins as junction links — so Node resolves the plugin to its real path in the repository instead of to a copy inside the profile. Peer dependencies then resolve by walking up from the package: from lib/dsh-plugin/ to <repo>/node_modules. Run npm install once in the repository and that directory exists, because dsh plugin add forwards to pnpm, which only warns about peer dependencies instead of installing them.

This is not theoretical: it was found by installing into a throwaway profile, deleting the workspace's node_modules, and watching the plugin fail to resolve @deepseek-ai/schemastery.

A linked checkout also loads lib/, exactly like an installed copy does — so run npm run build after editing src/ or dsh-plugin/. If you would rather not think about that, work on the hub as a library (npm test, npm run typecheck) and only build when you want the plugin to pick the change up.

The historical obstacle on the GitHub route — Node refusing to strip types for files under node_modules — is what committing lib/ removes; see Why the package ships compiled JavaScript.

What the agent can then do

Ask the agentTool it calls
"What AI models can you run?"list_models
"What can this machine actually do?"list_capabilities
"Create a futuristic city image."invoke_model (text_to_image) → router picks the model
"Now make a 3D model from that image."invoke_model (image_to_3d), chaining the artifact id
"Why did it pick that model?"explain_routing — read-only
"Is the image model running?"get_model_status, check_model_health
"Start / stop the image model."start_model, stop_model

The agent never names an engine, a command, or a path. That is the whole design.

The hub ships its own skill

The plugin also teaches the agent how to use it. skills/dsh-ai-model-hub/SKILL.md is registered as a skill provider on ctx.skills, the way DSH's own bundled dsh-badge skill is, so:

  • nothing is copied into ~/.dsh/skills or a project skill root, and no profile file is edited — installing or updating the plugin installs the skill;
  • the body stays the file in this checkout, so a git pull changes what the agent reads on its next load, with no reinstall step and no stale copy;
  • registration lands in the global layer, which is the layer DSH merges into every preset's session catalog, so no preset has to know about it.

The registration is deliberately not a declared inject. It goes through ctx.inject(['skills'], …), so a composition with no skill registry — headless, or the sdk-minimal profile — still gets the model tools and simply never runs the skill callback. A missing or malformed skill file costs one warning and the skill, never the tools beside it.

The file is an ordinary skill, so it also works the old-fashioned way: link or copy skills/dsh-ai-model-hub/ into a .dsh/skills root if you would rather not run the plugin at all. Its frontmatter is the single source of truth for the name and description on both paths.

A settings page for what is on this machine

The agent is not the only one who needs to know where the engines are. The plugin also adds a Local models section to the DSH settings, which answers the question a user actually has: which engines are on this PC, where is each one installed, is it running, and what is inside it.

RowWhere it comes from
Verified installationEngine markers or executables in configured/well-known locations; a configured cwd alone is not proof
Configured pathsCatalog launch paths, displayed separately from verified installation paths
Listens on / availabilityIndependent bounded endpoint checks on both Scan and Refresh
Hub can start itThe descriptor's startable, after the deployment's allowProcessLaunch
Catalog models / capabilitiesThe same live hub the tools read, so the page cannot disagree with behaviour

Scan is the page's main action, and it produces one list of two kinds of thing, each row labelled with what it is:

KindWhat it isWhere it comes from
Ollama ModelAn observed or catalog-known modelLive /api/tags, offline loopback-host manifests (OLLAMA_MODELS or ~/.ollama/models), active catalog, or last-good observations
ComfyUI WorkflowOne complete workflow or an explained unreadable entrySaved /userdata workflows, verified installation's workflow store, configured directories, and active catalog inline/pinned workflows

A ComfyUI workflow is the smallest thing the list exposes. Its checkpoints, LoRAs, VAEs, text encoders, ControlNets and individual nodes are not listed — they are the workflow's own business, which is why changing what a workflow loads never changes anything an agent or a user has to know.

The two halves are discovered independently, so an Ollama instance that is down cannot hide the workflows and a ComfyUI that is down cannot hide the models; each source reports its own status. Scanning twice refreshes the list rather than growing it, because every entry's id comes from its source location.

A saved workflow is discovered without a running ComfyUI. Workflow discovery reads the configured directories, the engine's own saved-workflow store when it can be found on disk, the active catalog, and the workflows earlier scans remembered — so a first launch with the engine stopped still lists the graphs that are on the machine, and an engine that stops mid-session cannot take them off the screen. The list says which evidence it has: ComfyUI stopped when a refused connection proves the process is not running, ComfyUI unreachable when a timeout or a routed address leaves that genuinely unknown, and a cached row whenever the contract came from a previous observation rather than this pass. Only a scan that completely read a source can conclude a workflow was deleted; a failure never can.

The remembered workflows live in `~/.dsh/plugins/dsh-ai-model-hub/workflo