Files
deepseek-api/README.md
T
Alex CubeandClaude Opus 5 6ccb5dd46b Одна модель вместо трёх: DeepSeek убрал выбор из веб-морды
DeepSeek больше не даёт выбирать модель: radiogroup Instant / Expert /
Vision (div[data-model-type][role="radio"]) со страницы исчез, оставшаяся
модель универсальна и разбирает в том числе картинки. Вместе с выбором
отпали и три роли — think, search, vision, — а с ними отдельный чат под
каждую: теперь чат один.

Заодно выяснилось, что тумблеры DeepThink и Search больше не пропадают
после первого сообщения — они видны и переключаются в любом чате, в том
числе начатом. На их исчезновении держалась вся прежняя схема с чатами
под роль, так что нужда в ней отпала и по этой причине тоже.

DeepThink выключается перед каждым вопросом: он только показывает
размышления модели и раздувает ответ. Search не трогаем намеренно — это
тумблер человека: как поставлен во вкладке, так мост и спрашивает.

Попутно два бага, которые вылезли на живом прогоне:

- data-virtual-list-item-key у вопросов теперь отрицательные (-4, 2, -11,
  8 для двух обменов подряд), и __firstMsg сортировкой по возрастанию
  отдавал последний вопрос вместо первого. Правка уходила в чужое
  сообщение. Берём порядок DOM — список рисуется сверху вниз.

- Вопрос с картинкой уходил новым сообщением в рабочий чат и ломал
  уговор «в чате ровно один обмен», на котором держится правка первого
  сообщения. После такого чат оставался из двух обменов, и наружу
  отдавался ответ на предыдущий вопрос. Теперь картинка всегда открывает
  новый чат, а старый удаляется как обычно.

Снаружи: /api/tags отдаёт одну модель deepseek:chat, имя присланной
клиентом модели не смотрится вовсе, ключ defaultRole из config.json убран,
chats.json стал {"id": "..."} вместо карты ролей. /api/probe показывает
положение тумблеров.

Проверено живым прогоном: обычный вопрос, вопрос с картинкой, снова
обычный — ответы верные, чат ротируется, сайдбар чистится.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ez8B2cR3mbm1fjmAMa8JXS
2026-09-11 10:26:03 +03:00

503 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<img src="icon.png" alt="" width="128" align="right">
# deepseek-api
**DeepSeek, притворяющийся Ollama.**
У веб-версии DeepSeek нет API: ни ключа, ни эндпойнта — только вкладка
в браузере, куда человек руками пишет вопросы. Эта программа открывает такую
вкладку, пишет в неё за вас и отдаёт наружу обычный HTTP — тот самый протокол,
на котором говорит [Ollama](https://ollama.com).
Смысл: **любая программа, которая уже умеет работать с Ollama, начинает
работать с DeepSeek после смены одного адреса.** Ни строчки кода в ней менять
не нужно.
```
ваша программа ──HTTP──> deepseek-api ──> браузер ──> chat.deepseek.com
(думает, что │
говорит с Ollama) <──────────── ответ ───────────────┘
```
Платить не нужно — используется ваш обычный аккаунт DeepSeek. Взамен всё
медленно (секунды, а не миллисекунды) и работает только пока открыт браузер.
Программа самодостаточна: кроме Node и браузера ей ничего не требуется,
внешних пакетов нет.
---
## Оглавление
1. [Что понадобится](#что-понадобится)
2. [Установка по шагам](#установка-по-шагам)
3. [Окно с индикатором](#окно-с-индикатором)
4. [Проверка, что всё работает](#проверка-что-всё-работает)
5. [Как подключить свою программу](#как-подключить-свою-программу)
6. [Что умеет и чего не умеет](#что-умеет-и-чего-не-умеет)
7. [Настройка](#настройка)
8. [Если не работает](#если-не-работает)
9. [Безопасность](#безопасность)
10. [Как это устроено внутри](#как-это-устроено-внутри)
---
## Что понадобится
| Что | Зачем | Как проверить, что есть |
|---|---|---|
| **Windows** | скрипт запуска написан под неё | — |
| **Node.js 22 или новее** | на нём написана программа | `node --version` → должно быть `v22.…` и выше |
| **Браузер на движке Chromium** | Яндекс.Браузер, Chrome, Edge — любой | — |
| **Аккаунт DeepSeek** | бесплатный, регистрация на [chat.deepseek.com](https://chat.deepseek.com) | — |
Node.js, если его нет, ставится с [nodejs.org](https://nodejs.org) — берите
версию LTS, кнопка слева. После установки **закройте и откройте заново**
командную строку, иначе `node` в ней не найдётся.
---
## Установка по шагам
Всего шагов пять. Делайте по порядку, каждый заканчивается проверкой —
не переходите к следующему, пока предыдущий не дал ожидаемого результата.
### Шаг 1. Скачать
```cmd
cd C:\#Projects
git clone https://git.08h.ru/alex/deepseek-api.git
```
Без git можно скачать ZIP с той же страницы и распаковать. Папка может лежать
где угодно.
> **Проверка:** в папке есть `server.mjs`, `browser.mjs` и `start.cmd`.
### Шаг 2. Завести отдельный профиль браузера
Браузером будет управлять программа, поэтому работать в нём вам не нужно —
и не стоит: он будет сам открываться, переключать вкладки и удалять чаты.
Заведите под него **отдельный профиль** — это просто пустая папка, браузер
сам её заполнит:
```cmd
mkdir C:\Browsers\deepseek
```
Имя любое, запомните путь — понадобится на следующем шаге.
> **Профиль отдельный — чтобы не мешать друг другу.** Окно сервиса нельзя
> сворачивать, а к профилю, уже открытому без порта управления, программа
> подключиться не может. На своём рабочем профиле это неудобно вам, а ваши
> окна мешают сервису.
>
> **А вот чаты — это про аккаунт, не про профиль.** Перед каждым вопросом
> программа удаляет из сайдбара всё, кроме своего рабочего чата. Чаты живут
> на сервере DeepSeek и привязаны к аккаунту, поэтому другой профиль браузера
> с тем же логином не спасёт: две такие программы на одном аккаунте удаляют
> чаты друг друга, а ваша личная переписка в DeepSeek на этом аккаунте
> пропадёт вместе с ними. На этом уже обжигались — см.
> [OLD_DEEPSHIM.md](OLD_DEEPSHIM.md). Нужен свой аккаунт — заводите второй.
### Шаг 3. Указать путь к браузеру и профилю
Откройте `config.json` и поправьте два значения:
```json
{
"browserExe": "C:\\Program Files\\Yandex\\YandexBrowser\\Application\\browser.exe",
"profileDir": "C:\\Browsers\\deepseek"
}
```
- **`profileDir`** — папка из шага 2.
- **`browserExe`** — путь к браузеру. Типичные:
| Браузер | Путь |
|---|---|
| Яндекс.Браузер | `C:\Program Files\Yandex\YandexBrowser\Application\browser.exe` |
| Google Chrome | `C:\Program Files\Google\Chrome\Application\chrome.exe` |
| Microsoft Edge | `C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe` |
**Обратные слэши в JSON удваиваются**`C:\\Browsers\\deepseek`, а не
`C:\Browsers\deepseek`. С одинарными файл не прочитается.
> Чтобы обновление программы не затирало ваши пути, их можно положить
> не в `config.json`, а в `config.local.json` рядом — он перебивает основной
> и в репозиторий не попадает.
> **Проверка:** перетащите `config.json` в окно браузера. Показалась
> структура — JSON верный. Ошибка — ищите пропущенную запятую или одинарный
> слэш.
### Шаг 4. Запустить
```cmd
start.cmd
```
> **Проверка:** в окне появилось
> ```
> DeepSeek прикидывается Ollama: http://localhost:11500
> Модель: deepseek:chat
> ```
Это окно должно оставаться открытым — пока оно живо, работает и сервис.
Останов — `Ctrl+C` или просто закрыть окно.
Чёрная консоль не обязательна: рядом лежит окно с индикатором в трее —
см. [Окно с индикатором](#окно-с-индикатором).
### Шаг 5. Войти в DeepSeek
Браузер поднимается сам, но в пустом профиле вы ещё не залогинены. Дайте
сервису первый запрос — он откроет окно:
```cmd
curl http://localhost:11500/api/tags
```
Первый вызов ответит `503` и поднимет браузер. **Войдите в аккаунт DeepSeek
в открывшемся окне.** Логин сохранится в профиле — больше делать это
не придётся.
**Окно браузера не сворачивайте и не закрывайте.** Свёрнутое окно Windows
не отрисовывает, и программа не сможет прочитать ответ. Достаточно перевести
его на другой рабочий стол или оставить позади других окон.
> **Проверка:** повторите `curl http://localhost:11500/api/tags` — теперь
> должна прийти одна «модель».
**Готово.** Рабочий чат программа заведёт сама при первом же вопросе —
создавать его руками не нужно.
---
## Окно с индикатором
Тем, кому чёрная консоль мешает, есть маленькое окно: тот же вывод, но
сворачивается в трей, а цвет значка показывает состояние сервиса.
```cmd
build.cmd
deepseek-api.exe
```
`build.cmd` собирает `deepseek-api.exe` (около 20 КБ) компилятором C# из
состава Windows — `csc.exe`, он есть на любой машине с .NET Framework 4.
Значок берётся из `icon.ico` и вшивается в exe, так что рядом с ним ничего
лежать не обязано.
Ставить ничего не нужно, прав администратора тоже. Сам `.exe` в репозиторий
не кладётся: собирается за секунду.
Окно запускает `node server.mjs` дочерним процессом и показывает его вывод.
Крестик прячет окно в трей, выход — из меню значка. Node уходит вместе с окном
в любом случае, даже если окно снять диспетчером задач; браузер переживает —
он отдельный процесс и нужен вам.
Цвет значка:
| Цвет | Что значит |
|---|---|
| зелёный | готов, вопросы принимаются |
| синий | идёт запрос: в подсказке — сколько секунд и длина очереди |
| жёлтый | браузер есть, а вкладки с чатом нет; или сервер не отвечает |
| красный | браузер не отвечает; текст беды — в подсказке и всплывающей записке |
| серый | node не запущен или снимок состояния устарел (сторож молчит) |
Второй запуск не поднимает второй сервер, а разворачивает уже открытое окно —
заодно не будет `EADDRINUSE`.
`start.cmd` никуда не делся: он для запуска без графики.
---
## Проверка, что всё работает
Откройте **вторую** командную строку (первая занята сервисом):
```cmd
curl http://localhost:11500/api/chat -d "{\"model\":\"deepseek:chat\",\"stream\":false,\"messages\":[{\"role\":\"user\",\"content\":\"Ответь одним словом: столица Японии?\"}]}"
```
Через несколько секунд:
```json
{"model":"deepseek:chat","created_at":"…","message":{"role":"assistant","content":"Токио"},"done":true,"done_reason":"stop"}
```
В окне сервиса при этом видно, что происходило:
```
вопрос 37 симв.
рабочий чат не найден, создаю сразу с вопросом
открыт новый чат
создан новый чат: 660347d5-…
ответ 5 симв. за 9 с
```
Отдельная диагностика — что сейчас видно во вкладке:
```cmd
curl http://localhost:11500/api/probe
```
Покажет адрес вкладки, найдено ли поле ввода, идёт ли генерация, список чатов,
положение тумблеров DeepThink и Search и не просит ли сайт залогиниться.
---
## Как подключить свою программу
Везде, где программа спрашивает **адрес Ollama**, укажите:
```
http://localhost:11500
```
Моделью выберите `deepseek:chat` — или оставьте, что стоит: имя модели
программа не смотрит вовсе.
Если программа работает на **другом компьютере** в вашей сети — вместо
`localhost` укажите адрес этого компьютера, например
`http://192.168.0.10:11500`. Сервис слушает все адреса, ничего дополнительно
настраивать не нужно.
### Модель одна
Раньше их было три (`think`, `search`, `vision`) — по числу моделей, между
которыми давала выбирать веб-морда DeepSeek. **Выбора больше нет:** в сентябре
2026 DeepSeek убрал переключатель Instant / Expert / Vision, оставшаяся модель
универсальна и разбирает в том числе картинки. Вслед за этим ушли и три режима.
Осталось одно имя — `deepseek:chat`. **Имя модели вообще не смотрится:**
пришлёт программа свою `llama3` или `qwen3:8b` — запрос всё равно уйдёт
в DeepSeek. Это сделано нарочно: чужую программу можно переключить, не
разыскивая в её настройках поле с моделью. В ответе имя возвращается то же,
которое прислали, — иначе клиент решит, что ответила не та модель.
Картинку прикладывайте так же, как это делает Ollama — в поле `images`
массивом base64.
**Тумблер Search остаётся вашим.** Рядом с полем ввода у DeepSeek два
тумблера: DeepThink и Search. DeepThink программа выключает перед каждым
вопросом — он только показывает размышления модели и раздувает ответ.
А Search не трогает: как поставите его во вкладке руками, так и будет
работать. Хотите ответы со свежими данными из интернета — включите его
в окне браузера один раз.
### Пример: помощник hlamingo
В [hlamingo](https://git.08h.ru/alex/hlamingo) на вкладке **⚙** есть выбор
«Кто думает» — переключите на «DeepSeek», укажите адрес, сохраните.
---
## Что умеет и чего не умеет
### Эндпойнты
| Адрес | Что делает |
|---|---|
| `GET /api/tags` | список «моделей» — в нём одна. **Отвечает 503, пока браузер не поднят** — по этому клиенты понимают, что сервис не готов |
| `GET /api/version` | опознавательная строка |
| `GET /api/probe` | сверх протокола Ollama: что видно во вкладке прямо сейчас |
| `POST /api/chat` | сообщения → ответ |
| `POST /api/generate` | то же, но вопрос одной строкой |
| `POST /api/embeddings` | **501** — веб-чат векторов не отдаёт |
### Чего нет и не будет
- **Настоящего потока.** Ollama печатает ответ по словам; здесь он приходит
целиком. Формат потока при этом соблюдается (клиент получит две строки JSON
вместо многих), поэтому программы, которые ждут поток, не ломаются —
просто ответ появляется разом. Хотите один объект вместо потока — присылайте
`"stream": false`.
- **`temperature`, `num_predict` и прочих настроек генерации.** Веб-чат их
не принимает. Присланные — молча игнорируются.
- **Эмбеддингов** (векторов для поиска по смыслу). Веб-чат их не считает.
Вместо пустого вектора возвращается честная ошибка 501: молчаливая подделка
испортила бы поиск так, что вы бы этого не заметили. Если вашей программе
нужен и чат, и поиск по смыслу — оставьте поиск на настоящей Ollama.
- **Параллельных запросов.** Вкладка одна, и два запроса разом затрут друг
другу поле ввода. Запросы встают в очередь и идут по одному.
### Сколько ждать
Порядок величин, замерено на коротких вопросах:
| | Ollama (qwen3:8b, локально) | DeepSeek (веб) |
|---|---|---|
| короткий вопрос | 2–3 с | 7–12 с |
| запрос, на котором меняется чат | — | ~16 с |
| первый запрос после запуска браузера | — | +15 с на подъём окна |
Если ваша программа считает, что модель обязана ответить за пару секунд, —
увеличьте в ней таймаут.
---
## Настройка
### `config.json`
| Ключ | По умолчанию | Смысл |
|---|---|---|
| `port` | `11500` | порт, на котором сервис слушает |
| `host` | `0.0.0.0` | какие адреса слушать; `127.0.0.1` — только этот компьютер |
| `cdpPort` | `9333` | порт, по которому программа управляет браузером |
| `browserExe` | Яндекс.Браузер | путь к браузеру |
| `profileDir` | `C:\Browsers\deepseek` | папка профиля браузера |
| `chatUrl` | `https://chat.deepseek.com/` | что открывать во вкладке |
| `timeout` | `300` | сколько секунд ждать **бездействия** страницы |
Про `timeout`: это не «сколько ждать ответа». Пока DeepSeek печатает, ждём
сколько угодно; срок отсчитывается от последнего признака жизни страницы.
`config.local.json` рядом перебивает `config.json` и в репозиторий не
попадает — туда удобно класть пути конкретной машины.
### Переменные окружения
Перебивают файлы, удобны для разовых запусков:
```cmd
set DEEPSEEK_API_PORT=11501
set DEEPSEEK_CDP_PORT=9444
start.cmd
```
### `chats.json`
Заводится сам, руками его трогать не нужно — см.
[Чаты изнашиваются](#чаты-изнашиваются-и-меняются-сами).
---
## Если не работает
Сначала посмотрите в окно сервиса — там пишется, что происходит.
| Что видно | В чём дело | Что делать |
|---|---|---|
| `Браузер с отладочным портом 9333 не отвечает` в ответе на `/api/tags` | браузер закрыт | Ничего: он поднимется сам при первом запросе к `/api/chat`. Если не поднялся — следующая строка |
| `Браузер так и не открыл отладочный порт` | этот профиль уже открыт **без** порта управления: новый процесс отдаёт ссылку старому окну и молча завершается | Закройте **все** окна браузера с этим профилем и повторите. Проверить, что не осталось: диспетчер задач |
| `Браузер не найден: …` | неверный `browserExe` | Проверьте путь и удвоенные слэши |
| `не задан browserExe или profileDir` | шаг 3 пропущен | Заполните оба ключа |
| `Вкладка chat.deepseek.com не открыта` | вкладку закрыли руками | Сервис откроет её сам на следующем запросе; если нет — откройте `chat.deepseek.com` в том же окне |
| Ответ приходит **обрезанным**, без кода | окно браузера свёрнуто — Windows перестаёт его отрисовывать, а DeepSeek дорисовывает блоки кода лениво | Разверните окно. Не сворачивайте: уводите на другой рабочий стол или за другие окна |
| Ответ приходит **чужой**, не на ваш вопрос | вкладка не успела обновиться | Повторите запрос. Если повторяется — перезапустите браузер |
| В логе `правки в этом чате кончились, завожу новый` | исчерпан лимит в шесть правок одного сообщения | Ничего. Это штатная смена чата, см. [Чаты изнашиваются](#чаты-изнашиваются-и-меняются-сами) |
| Чаты пропадают сами, хотя вы их не удаляли | сайдбар подчищается от всего, кроме рабочего чата, а чаты привязаны к **аккаунту** DeepSeek, а не к профилю браузера | Держите на этом аккаунте только рабочие чаты. Другой программе, управляющей чатами, нужен другой аккаунт — разными профилями браузера тут не разойтись |
| `EADDRINUSE: address already in use` | сервис уже запущен в другом окне | Закройте то окно либо возьмите другой порт (`DEEPSEEK_API_PORT`) |
| `node не является внутренней или внешней командой` | Node не установлен или командная строка открыта до его установки | Поставьте Node, **закройте и откройте заново** командную строку |
| Просит войти в DeepSeek при каждом запуске | профиль каждый раз новый | Проверьте, что `profileDir` указывает на одну и ту же существующую папку |
Проверить перевод формата, не поднимая браузер:
```cmd
node test.mjs
```
---
## Безопасность
Прочтите, это важнее, чем кажется.
- **Сервис слушает все сетевые адреса** (`0.0.0.0`), а не только этот
компьютер. Так задумано: подключаться должны и соседние машины, ровно как
к самой Ollama. **Аутентификации нет.**
- **Он ходит в DeepSeek под вашим логином.** Любой, кто достучится до порта
11500, задаёт вопросы от вашего имени и расходует ваши лимиты.
- Поэтому: только домашняя или доверенная сеть. **Не пробрасывайте порт
наружу** и не открывайте его на публичном Wi-Fi.
- Всё, что вы отправляете, уходит на серверы DeepSeek — как если бы вы
написали это в чат руками. Для приватных данных берите локальную Ollama.
Если сервис нужен только на этом компьютере, впишите в `config.json`:
```json
"host": "127.0.0.1"
```
Тогда снаружи к нему не подключиться вовсе.
---
## Как это устроено внутри
```
POST /api/chat
├─ склеить system + user в один текст (у веб-чата нет ролей)
├─ встать в очередь (вкладка одна)
├─> поднять браузер, если он не отвечает
├─> Chrome DevTools Protocol
│ ├─> выключить DeepThink
│ └─> правка ПЕРВОГО сообщения чата
│ └─> дождаться, пока DeepSeek допечатает
└─ отдать в формате Ollama
```
Почему правка первого сообщения, а не новое: DeepSeek отбрасывает всё,
что было после отредактированного сообщения, и чат остаётся из одного обмена.
Контекст не растёт, мусор между запросами не копится, каждый вопрос —
с чистого листа.
Почему браузер запускается напрямую из программы, а не батником: батник
рассчитан на человека за консолью — на занятом профиле он задаёт вопрос
и ждёт ответа, а у сервиса некому отвечать.
### Чаты изнашиваются и меняются сами
**Одно сообщение DeepSeek разрешает править ровно шесть раз.** После шестой
правки кнопка перестаёт открывать поле ввода — и чат становится непригоден.
Ничего делать не нужно, это предусмотрено. На седьмом вопросе программа
заводит новый чат, задаёт вопрос сразу в нём, записывает новый номер
в `chats.json` и **удаляет отработавший чат**. Снаружи не видно ничего:
тот же запрос, тот же ответ, разве что на несколько секунд дольше.
Отсюда следствия:
- **`chats.json` — не настройка, а переменная.** Номер в нём меняется сам
по мере работы. Не правьте его руками; в репозиторий он не попадает,
образец структуры — `chats.example.json`.
- **В сайдбаре DeepSeek не копится мусор.** Старые чаты удаляются, плюс перед
каждым вопросом сайдбар подчищается от всего, кроме рабочего чата:
всё чужое программа считает мусором. Чистка идёт по **аккаунту** — чаты
хранятся на сервере DeepSeek, и сменой профиля браузера от неё не укрыться.
Поэтому аккаунт под сервис отдавайте целиком.
- Если вы удалите рабочий чат вручную — ничего страшного: следующий запрос
заметит пропажу и создаст новый.
### Файлы
| Файл | Что в нём |
|---|---|
| `server.mjs` | HTTP, перевод формата Ollama, очередь |
| `browser.mjs` | всё про браузер: CDP, разметка DeepSeek, отправка, ожидание ответа, чаты |
| `config.json` | настройки; `config.local.json` рядом перебивает их |
| `chats.json` | рабочее состояние: id рабочего чата (заводится сам) |
| `start.cmd` | запуск в консоли |
| `tray.cs`, `build.cmd`, `icon.ico` | окно с индикатором в трее; `.exe` собирается на месте |
| `test.mjs` | проверка перевода формата, браузер не нужен |
Зависимостей нет — только то, что есть в Node 22.
Движок управления вкладкой перенесён из проекта DeepShim, который на этом
и закрылся: своего в нём не осталось ничего. Всё, что удалось выяснить про
разметку DeepSeek замерами, лежит рядом с кодом комментариями, а разбор
«почему именно так» — в [OLD_DEEPSHIM.md](OLD_DEEPSHIM.md). Это знание дороже
кода: оно добывалось живыми прогонами и переоткрывается неделями.
---
## Лицензия
MIT.