Bundle
dsh-periscope
DSH plugin: keep text-only models (deepseek-v4-flash / deepseek-v4-pro) as the session default, and automatically route requests that carry image content to a configured vision-capable model (deepseek-v4-flash-vision-exp) 鈥?no manual model switching (periscope).
- Source
- lmh-2026
- License
- MIT
- Updated
- Updated 18 days ago
Readme
**English** | [简体中文](./README.zh.md)
# 🔭 dsh-periscope
[](https://github.com/deepseek-ai/deepseek-harness)
[](./LICENSE)
[](https://www.npmjs.com/package/dsh-periscope)
[](./package.json)
> 🎯 Like a periscope, dsh-periscope lets a **text-only** DeepSeek model "see"
> the imagery without ever switching models.
📎 Keep `deepseek-v4-flash` / `deepseek-v4-pro` as the session default and
**automatically route every request that carries image content** to the
official vision-capable model **`deepseek-v4-flash-vision-exp`** on the same
provider. Text-only requests stay on the text model. No manual switching, no
third-party vision model, no OCR — the image is sent **raw** to DeepSeek's own
vision model. ✨
## 🤔 Why
Most DSH "vision" plugins bridge a **third-party** vision language model (GLM /
Qwen / Gemini / Zhipu …) that *transcribes* the image to text, then hand the
description to DeepSeek. That needs an extra API key, adds a lossy
image→text step, and often requires picking a different provider. 😮💨
dsh-periscope takes the *transparent* route instead: it keeps your session on
a text model and swaps the **wire model** to `deepseek-v4-flash-vision-exp`
only for turns that actually contain images, using your existing
`deepseek-official` route and API key. The vision model sees the original
pixels. 🔍
## ⚙️ How it works
Two gates stand between pasting an image and it reaching a vision-capable
provider in a text-only session; this plugin clears both:
1. **🚪 Host image admission** — when you submit a message with an image, the
host's `prompt` handler rejects it unless the session model declares image
input (`MODEL_DOES_NOT_SUPPORT_IMAGES`). The plugin wraps
`llm.resolveModelInfo` so every configured text model reports image input,
letting the prompt (and its image) into the conversation.
2. **🔀 Stream routing** — every request funnels through
`llm.streamWithRegistration`. The plugin wraps it: if messages contain an
image block and the request is routed to one of the configured text models,
it re-dispatches on the configured vision model, so the image blocks are
actually sent (no `[image omitted because this model accepts text only]`
projection, no `UNSUPPORTED_CONTENT` refusal).
```text
text-only session (flash/pro)
│ attach an image 📷
▼
host admission ── resolveModelInfo wrapped → image admitted ✅
▼
llm.stream ── streamWithRegistration wrapped
│ messages contain an image?
│ ├─ no → pass through on deepseek-v4-flash / deepseek-v4-pro (zero-cost) ⚡
│ └─ yes → re-dispatch on deepseek-v4-flash-vision-exp (official vision API) 🎯
▼
wire request ── same provider + API key, image sent raw 📤
```
## 📦 Install
```powershell
# from npm
dsh plugin --profile web add dsh-periscope
# from a local tarball
dsh plugin --profile web add .\dsh-periscope-0.2.0.tgz
```
Then **restart the DSH app** (the bundle list is read at startup). 🔄
## 🛠️ Configuration
Defaults work out of the box for the official DeepSeek setup:
| Field | Default | Meaning |
| --- | --- | --- |
| `provider` | `deepseek-official` | LLM route provider owning the models |
| `textModels` | `["deepseek-v4-flash", "deepseek-v4-pro"]` | Text-only models whose image-bearing requests are routed to the vision model |
| `visionModel` | `deepseek-v4-flash-vision-exp` | Model used for requests with images |
Override in the profile's `cordis.patch.yml` (user layer replaces the whole
row config):
```yaml
- id: periscope
config:
provider: deepseek-official
textModels: [deepseek-v4-flash, deepseek-v4-pro]
visionModel: deepseek-v4-flash-vision-exp
```
The provider's catalog must contain the text models and the vision model whose
entry declares `image` input (the DeepSeek catalog already does). ✅
## 📝 Notes
- The switch is per-request and content-driven: image turns run on the vision
model (including the conversation history), text turns run on the text model.
- The request-header log and token metering still record the text model (the
header is written before the stream starts); this is a known cosmetic
inaccuracy.
- The `read_image` tool's own capability gate is not affected; it still uses
the session route's declared input.
## 📄 License
[MIT](./LICENSE)
Install
dsh plugin --profile web add github:lmh-2026/dsh-periscope
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-periscope from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.