JDBACK/dsh-hybrid--packages-dsh-web-hybrid0

dsh-web-hybrid

为 DSH 提供统一网页搜索与抓取服务的插件,支持通过直接 REST 调用 Exa 和 Firecrawl,并可在设置中选择后端与配置 API 密钥。

AI 分析

核心用途是为 DSH 接入 Exa 和 Firecrawl 网页搜索与抓取能力。适合需要增强 AI 联网搜索与网页内容提取能力,且拥有相关服务 API 密钥的开发者和高级用户。

包名
dsh-web-hybrid
版本
0.1.0
许可证
MIT
最近更新
2026年8月14日

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:JDBACK/dsh-hybrid#b14459e983e94d7749cc755ff7a70378ea6435d2&path:packages/dsh-web-hybrid

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 или literal apiKeyExa в 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 (fields searchBackend / 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»). Поэтому:

  1. В built-файле профиля node_modules/@deepseek-ai/dsh-host-apiproxy/lib/index.js сделан локальный патч (бэкап: index.js.apiproxy-patch.bak рядом с файлом): конфиг-ключ exposedSettingsNamespaces: z.array(z.string()), проброс в createApiProxy, и exposedNamespaces() добавляет эти неймспейсы. Патч переживает рестарты; откатывается при переустановке пакета (тогда карточка молча исчезнет — верни патч).
  2. 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.
  • Локальный 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).