JDBACK/dsh-hybrid--packages-dsh-web-hybrid ↗★ 0
dsh-web-hybrid
Unified web search + fetch providers for DSH: Exa and Firecrawl via direct REST, per-capability backend selection, with a Settings → Plugins card
AI Analysis
核心用途是为 DSH 接入 Exa 和 Firecrawl 网页搜索与抓取能力。适合需要增强 AI 联网搜索与网页内容提取能力,且拥有相关服务 API 密钥的开发者和高级用户。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:JDBACK/dsh-hybrid#b14459e983e94d7749cc755ff7a70378ea6435d2&path:packages/dsh-web-hybridREADME
Read the full README ↗dsh-web-hybrid
Unified web search + fetch providers for DeepSeek Harness: Exa and Firecrawl over direct REST, with per-capability backend selection. Ships a Settings → Plugins → Web Hybrid card in the GUI: backend pickers for search/fetch and the two vendor API keys.
Установка
dsh plugin --profile web add ~/dsh-hybrid/packages/dsh-web-hybrid
Лаунчер прогонит pnpm add, увидит dsh.bundle в манифесте и добавит пакет
в dsh.profile.bundles. Перезапусти dsh web.
Платформенные куски, которых апстрим пока не даёт декларативно, ставит
bash scripts/install.sh (идемпотентно): патч экспозиции apiproxy и пресет
hybrid (см. ниже). Те же шаги повторяет self-heal при каждом старте через
обёртку ~/bin/dsh.
Ключи:
EXA_API_KEY— dashboard.exa.ai (env, credentials-документ~/.dsh/.credentials.yamlили literalapiKeyExaв row);FIRECRAWL_API_KEY— уже есть в CLI:firecrawl envвытащит его в.env.
Оба ключа можно ввести прямо в GUI: Settings → Plugins → Web Hybrid (пишутся в credentials-домен, не в файл настроек).
GUI-карточка (client-бандл)
Пакет объявляет dsh.client + экспорт ./client — сервер раздаёт бандл по
/plugins/dsh-web-hybrid/client.js, браузер монтирует карточку в слот
settings.plugin.item (вкладка «конфигурация» в Settings → Plugins):
- Search backend / Fetch backend — селекты
exa|firecrawl, пишутся в секциюweb-hybrid(fieldssearchBackend/fetchBackend); - Exa API key / Firecrawl API key — write-only поля с бейджем
«configured», пишутся через
api.credentials.setвEXA_API_KEY/FIRECRAWL_API_KEY; - один Save пишет всё разом; откат — Discard.
Пресет hybrid (обязателен для web_fetch)
DSH по умолчанию отключает web_fetch (в shipped-пресете standard:
tool-web.fetch: false). Пользовательский пресет с id standard не может
переопределить shipped (shipped shadows), поэтому установка создаёт копию:
~/.dsh/.agent-presets/hybrid/=standard+tool-web.fetch: true(при обновлении standard-пресета копия может устареть — сравнить сnode_modules/@deepseek-ai/dsh/config/agent-presets/standard/);- патч ставит
agent-presets.default: hybrid— новые сессии получаютweb_search+web_fetch;standardостаётся в ростере.
Фолбэк для standard-сессий: плагин добавляет system-prompt секцию
(web-hybrid:fetch-fallback, order 111): если web_fetch в текущем пресете
недоступен — брать содержимое страниц скилами (firecrawl-scrape для
markdown/JS-рендеринга — по умолчанию firecrawl scrape без -o,
контент сразу в stdout; -o page.md — только для очень больших страниц;
exa-contents для батч-plain-text). tool-skill
смонтирован и в standard, а firecrawl CLI хранит свои креды сам, так что
фолбэк работает без ключей в env. В hybrid-сессиях секция лишь усиливает
основной путь.
Конфиг (cordis.patch.yml)
Один плагин-инстанс за фасадом с id hybrid; seam пинит обе способности на
него, а секция web-hybrid выбирает бэкенд per capability:
- insert:
- id: web-hybrid
name: 'dsh-web-hybrid'
config:
searchBackend: exa # web_search → Exa ($7/1k запросов flat, highlights входят)
fetchBackend: firecrawl # web_fetch → Firecrawl (scrape, markdown — базовый формат)
- id: web
config:
searchProvider: hybrid
fetchProvider: hybrid
searchBackend / fetchBackend редактируются живьём из GUI (карточка выше).
Per-vendor настройки секции: apiKeyExa/apiKeyFirecrawl (secret),
apiKeyEnvExa/apiKeyEnvFirecrawl, baseURLExa/baseURLFirecrawl,
общие fetchTimeoutMs (у Firecrawl дефолт 30s — как у Hermes и дефолт API),
hardBodyCapBytes (5 МБ), allowPrivateUrls (false), blocklist,
searchContents (highlights | none), onlyMainContent
(false; Firecrawl — пропускать nav/шапку, меньше токенов), removeBase64Images
(false; Firecrawl — убирать инлайн-картинки из markdown), blockAds (false).
Все дефолты — в схеме секции (settings.describe показывает полный
снапшот). Правила blocklist: example.com, example.com/docs или
*.example.com (эквивалентно голому домену — поддомены матчатся и так);
правило другого вида падает громко на загрузке и отклоняется при записи из GUI.
Скорость фетча (замеры, 2026-08): Firecrawl-скрейп — это тот же /v2/scrape,
что у Hermes (там SDK поверх того же API, последовательно, 60s Python-таймаут,
без timeout-параметра → дефолт API 30s). Разница в скорости, которая
заметна, — это кеш Firecrawl (warm cache: сотни мс; холодный рендер тяжёлых
страниц, напр. arxiv: 1–18s — одинаково для обеих реализаций) и таймаут:
наш прежний 60s давал движку в 2 раза больше ждать. Теперь дефолт 30s, как у
Hermes; onlyMainContent/removeBase64Images/blockAds — опциональные
ускорители (меньше работа движка и меньше токенов в контексте).
Экспозиция секции в GUI (локальный патч dsh-host-apiproxy)
API-гейтвей DSH отдаёт настройки клиенту только из явного allowlist
(WEB_SETTINGS_NAMESPACES в dsh-host-apiproxy); неймспейс вне списка
отвечает settings-not-exposed, даже если его владелец зарегистрировал секцию
(апстрим-комментарий: «adding a section to that page is a decision made here
rather than by the registering plugin … is deferred work»). Поэтому:
- В built-файле профиля
node_modules/@deepseek-ai/dsh-host-apiproxy/lib/index.jsсделан локальный патч (бэкап:index.js.apiproxy-patch.bakрядом с файлом): конфиг-ключexposedSettingsNamespaces: z.array(z.string()), проброс вcreateApiProxy, иexposedNamespaces()добавляет эти неймспейсы. Патч переживает рестарты; откатывается при переустановке пакета (тогда карточка молча исчезнет — верни патч). cordis.patch.ymlдобавляет ряд:
- id: api-gateway
config:
exposedSettingsNamespaces:
- web-hybrid
Безопасность (порт Hermes)
- секрет-скан URL до dispatch, включая percent-encoded и credential-query-параметры;
- SSRF-префильтр: hostname-правила (localhost/internal + metadata-хосты) и DNS-резолв с проверкой всех адресов — приватные (RFC1918), loopback, link-local/metadata 169.254/16, CGNAT 100.64/10 (Tailscale/WireGuard), TEST-NET/benchmark (192.0.2/24, 198.18/15, 198.51.100/24, 203.0.113/24, 192.0.0/24, 192.88.99/24), multicast/reserved (224/4, 240/4), IPv4-mapped, NAT64, IPv6 multicast ff00::/8 и documentation 2001:db8::/32;
- policy blocklist из конфига (домены, path-префиксы,
*.-правила; невалидное правило падает громко на загрузке и при записи); - post-redirect re-check: финальный URL, который вернул вендор, перепроверяется.
Нарушения → WEB_FETCH_URL_BLOCKED; нет ключа → WEB_PROVIDER_CREDENTIAL_MISSING
с именем env-переменной; таймаут → WEB_FETCH_TIMEOUT; отмена → WEB_ABORTED.
Тарифы (проверено 2026-08)
- Exa search: $7/1k запросов flat (highlights/text входят);
type: auto— без deep-режимов. - Exa contents (fetch): $1/1k страниц за тип — запрашиваем только text (1 тип).
- Firecrawl scrape: кредиты за страницу,
formats: ['markdown']— один формат, базовая ставка. - Обёртка/meta/форматирование — бесплатны (клиентская работа).
Тесты
- Unit (offline, node:test, без харнесса и сети):
pnpm test— суитыtest/{safety,http,config,providers,deploy}.test.mjs(144 кейса): SSRF/ секреты/blocklist, капы и таймауты HTTP, дефолты и валидация схемы, шейпинг запросов провайдеров на замоканном fetch, скрипты деплоя на фейковых деревьях (patchSource, resync, selfheal, install, dsh-wrapper). - Coverage-гейт:
pnpm test:coverage— те же суиты под порогами lines 85 / branches 90 / funcs 85 (нужен Node ≥ 22.1); шаг есть в CI. - Live smoke (нужны ключи):
pnpm test:live [safety|search|fetch]—test/probe.mjs, реальные вызовы вендоров, тратит квоту. - Lint/syntax/types:
pnpm check— biome +node --checkна каждом файле- tsc над
lib/types.js(JSDoc) иlib/types/*.d.ts.
- tsc над
- Локальный CI-эквивалент:
pnpm run ciиз корня — check + test + coverage + bash -n/shellcheck дляscripts/*.sh.
Заметки
- Ротация ключей (Hermes pr-65126 credential pool) — не входит. Квотные ответы (402 / «quota» / «insufficient» / «out of credit») классифицируются в тексте ошибки с подсказкой «quota exhausted … top up or switch backend»; 429 остаётся как есть — решение о ретрае за агентом, не за провайдером.
- Firecrawl-фетч всегда шлёт
storeInCache: false,skipTlsVerification: false,maxAge: 0— это инварианты приватности/свежести, не конфиг (дефолты вендора противоположны: кэш-запись включена, проверка TLS выключена, допустим 2-дневный кэш). - Truncate-and-store с кэшем (Hermes) — осознанно не переносится: политику
усечения держит
dsh-tool-web(maxOutputChars), провайдер ставит только аппаратный потолокhardBodyCapBytes. - Exa не отдаёт реальный HTTP-статус страницы: для Exa-фетча
statusCodeвсегда200(приближение; Firecrawl отдаёт честныйmetadata.statusCode).