Локальный OpenAI-совместимый мост к бесплатному веб-чату DeepSeek (chat.deepseek.com), который можно подключить к opencode как обычного провайдера моделей и использовать как полноценного агента (чтение/правка файлов, bash, поиск по коду и т.д.) — без API-ключа и оплаты.
English version: README.en.md
Оригинал проекта: https://github.com/sums001/Deepseek-API Это неофициальный проект, не связанный с DeepSeek. Вы используете свой обычный аккаунт DeepSeek и несёте ответственность за соблюдение его условий.
- Как это работает
- Требования
- Единый путь развёртки (копировать целиком)
- Пошагово, с пояснениями
- Проверка работоспособности
- Куда слать запросы: URL и API-ключ
- Чат в терминале
- Интеграция с opencode
- Переключение моделей, DeepThink и веб-поиск
- Переменные окружения
- Ограничения и важные нюансы
- Обслуживание и troubleshooting
- Структура проекта
- Безопасность
- Лицензия
┌──────────────┐ OpenAI API ┌──────────────────────┐ внутренний ┌──────────────────┐
│ opencode │ ───────────────▶│ server/api.py │ ── протокол ──▶│ chat.deepseek │
│ (агент/CLI) │ localhost:8000 │ (FastAPI, /v1/...) │ + PoW/WASM │ .com (ваш аккаунт)│
└──────────────┘ └──────────────────────┘ └──────────────────┘
│
├── deepseek/auth.py (вход через браузер, сессия в session/)
└── deepseek/pow.py (решение proof-of-work через wasmtime)
- opencode думает, что общается с обычным OpenAI-провайдером.
- Мост транслирует запросы в веб-чат DeepSeek, решает PoW-челлендж
(
sha3_wasm_bg.wasmв песочницеwasmtime) и отдаёт поток токенов обратно. - Сессия входа сохраняется в
session/(в git не попадает) и обновляется автоматически примерно раз в 5 часов.
- Python 3.9+ (рекомендуется 3.11/3.12)
- Node.js 18+ — нужен только для opencode (не для моста)
- Аккаунт DeepSeek (бесплатный, тот же, что для chat.deepseek.com)
- opencode — https://opencode.ai
- ОС: Windows, macOS, Linux
Ниже — весь путь от нуля до работающего агента. Выполняйте блоки по порядку. Замените
~/projectsна удобную вам папку.
# 0. Предпосылки: Python 3.9+, Node 18+, opencode
python3 --version && node --version && opencode --version
# 1. Клон проекта
mkdir -p ~/projects && cd ~/projects
git clone https://github.com/Tsuev/opencode-deepseek.git
cd opencode-deepseek
# 2. Виртуальное окружение
python3 -m venv venv
source venv/bin/activate
# 3. Зависимости + браузер для Playwright
pip install --upgrade pip
pip install -r requirements.txt
playwright install chromium
# 4. Одноразовый вход в аккаунт DeepSeek (откроется окно браузера)
python -m deepseek.auth
# 5. Конфиг (значения по умолчанию подходят для локального запуска)
cp .env.example .env
# 6. Запуск сервера (оставьте терминал открытым)
python app.py
# -> http://127.0.0.1:8000Проверка (во втором терминале):
curl http://127.0.0.1:8000/healthz
curl http://127.0.0.1:8000/v1/modelspython --version; node --version; opencode --version
mkdir $HOME\projects; cd $HOME\projects
git clone https://github.com/Tsuev/opencode-deepseek.git
cd opencode-deepseek
python -m venv venv
.\venv\Scripts\Activate.ps1
pip install --upgrade pip
pip install -r requirements.txt
playwright install chromium
python -m deepseek.auth
Copy-Item .env.example .env
python app.pyЕсли PowerShell блокирует активацию venv:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned(или активируйте черезvenv\Scripts\activate.batвcmd.exe).
Содержимое файлов opencode.json и .opencode/agent/*.md — в разделе
Интеграция с opencode. После их создания
перезапустите opencode.
git clone https://github.com/Tsuev/opencode-deepseek.git
cd opencode-deepseekИзолирует зависимости проекта от системного Python.
python3 -m venv venv
source venv/bin/activate # macOS/Linux
# .\venv\Scripts\Activate.ps1 # Windows PowerShellpip install -r requirements.txt
playwright install chromium # разовая установка браузераrequirements.txt тянет: playwright (вход + PoW), httpx, fastapi,
uvicorn, pydantic, python-dotenv, wasmtime, openai.
python -m deepseek.authОткроется настоящий браузер — войдите в аккаунт и пройдите капчу
(human-check). После этого токен и cookies сохранятся в session/ и будут
переиспользоваться. Сессия обновляется автоматически; повторный вход нужен,
только если она полностью истекла.
cp .env.example .envДля локального запуска значения по умолчанию подходят. Пароли в .env не
хранятся — вход выполняется вручную в браузере.
python app.py
# -> DeepSeek OpenAI-compatible API on http://127.0.0.1:8000Альтернатива через uvicorn с другим адресом:
HOST=0.0.0.0 PORT=8080 python app.py
# или
uvicorn server.api:app --host 0.0.0.0 --port 8080Чтобы сервер поднимался сам и перезапускался при падении, заведите LaunchAgent
~/Library/LaunchAgents/com.deepseek.api.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.deepseek.api</string>
<key>ProgramArguments</key>
<array>
<string>/Users/ВЫ/root/other/Deepseek-API/venv/bin/python</string>
<string>app.py</string>
</array>
<key>WorkingDirectory</key><string>/Users/ВЫ/root/other/Deepseek-API</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOST</key><string>127.0.0.1</string>
<key>PORT</key><string>8000</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/Users/ВЫ/root/other/Deepseek-API/logs/server.log</string>
<key>StandardErrorPath</key><string>/Users/ВЫ/root/other/Deepseek-API/logs/server.err</string>
</dict>
</plist>mkdir -p logs
launchctl load ~/Library/LaunchAgents/com.deepseek.api.plist # включить
launchctl kickstart -k "gui/$(id -u)/com.deepseek.api" # перезапуск после правок кода
launchctl unload ~/Library/LaunchAgents/com.deepseek.api.plist # выключитьВажно: при
reload=False(как вapp.py) изменения кода подхватываются только после перезапуска — используйтеkickstart -k. Логи смотрите вlogs/server.logиlogs/server.err.
# 1) Живость
curl http://127.0.0.1:8000/healthz
# {"status":"ok"}
# 2) Список моделей
curl http://127.0.0.1:8000/v1/models
# 3) Простой чат
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Привет!"}]}'Python SDK:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="unused")
r = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Hello!"}],
)
print(r.choices[0].message.content)Готовые примеры: examples/ (01_* — напрямую из Python, 04_*–06_* — через сервер).
Базовый адрес — http://localhost:8000/v1 (или http://127.0.0.1:8000/v1).
Порт меняется переменной PORT в .env / launchd.
| Метод | URL | Назначение |
|---|---|---|
POST |
http://localhost:8000/v1/chat/completions |
Чат; стриминг через "stream": true |
GET |
http://localhost:8000/v1/models |
Список моделей (deepseek-chat, deepseek-expert) |
GET |
http://localhost:8000/healthz |
Проверка живости (без рейт-лимита) |
API-ключ: любой. Мост не проверяет Authorization и не читает его
(server/api.py не смотрит заголовок), поэтому в SDK, где ключ обязателен
синтаксически, пишут заглушку — исторически api_key="unused". В чистом HTTP
через curl заголовок Authorization вообще не нужен.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="unused")curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hi"}]}'Нестандартные поля (вне схемы OpenAI) передаются через extra_body:
thinking (DeepThink), search (веб-поиск), conversation_id (продолжить
диалог; при resume модель игнорируется). Лимит — RATE_LIMIT_PER_MINUTE
(по умолчанию 30/мин на IP), /healthz не считается.
chat.py — интерактивный REPL, который ходит в chat.deepseek.com напрямую
через DeepSeekClient. Сервер (app.py) для этого не нужен.
./ds # продолжить последний диалог
./ds --new # начать новый диалог
./ds --model expert # Expert — сильнее и медленнее, чем Instant
./ds --think --search # DeepThink-размышление + веб-поиск
./ds --no-think # выключить DeepThink для этого запускаВнутри чата:
| Команда | Что делает |
|---|---|
/new |
новый диалог (история на сайте сохраняется) |
/model default | /model expert |
сменить модель (модель фиксируется при создании треда, поэтому смена открывает новый диалог) |
/think |
переключить DeepThink |
/search |
переключить веб-поиск |
/status |
текущая модель и переключатели |
/exit |
выйти (работает и Ctrl-D) |
Ответ печатается по мере генерации. Строка, заканчивающаяся на \, продолжается
на следующей. Текущий диалог и настройки сохраняются в session/chat_state.json,
так что следующий запуск ./ds его продолжит.
Запуск из любой директории — поставьте shim один раз:
mkdir -p ~/bin && ln -sf "$(pwd)/bin/ds-chat" ~/bin/ds-chat
# убедитесь, что ~/bin в PATHВариант Command+L прямо в терминале Kaku — в
docs/TERMINAL_CHAT.md.
Демонстрация Command+L в Kaku (25 секунд):
Видео: docs/media/kaku-ai.mp4 — H.264/AAC,
1276×992, 1.4 МБ. Превью: docs/media/kaku-ai.png (нажмите на картинку, чтобы
открыть видео).
opencode подключается к мосту как к OpenAI-совместимому провайдеру.
Создайте opencode.json в корне вашего рабочего проекта (или в
~/.config/opencode/opencode.json для глобальной настройки):
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"local-deepseek": {
"npm": "@ai-sdk/openai-compatible",
"name": "DeepSeek (local bridge)",
"options": {
"baseURL": "http://127.0.0.1:8000/v1",
"apiKey": "unused"
},
"models": {
"deepseek-chat": {
"name": "DeepSeek Chat (Instant)",
"tool_call": true,
"reasoning": false,
"limit": { "context": 64000, "output": 8192 }
},
"deepseek-expert": {
"name": "DeepSeek Expert",
"tool_call": true,
"reasoning": false,
"limit": { "context": 64000, "output": 8192 }
}
}
}
}
}npm: "@ai-sdk/openai-compatible"— универсальный OpenAI-совместимый драйвер.apiKeyобязателен для SDK, но мост его игнорирует.modelв opencode указывается какlocal-deepseek/deepseek-chatилиlocal-deepseek/deepseek-expert.limit.context— приблизительный; уменьшите, если получаете ошибки о переполнении контекста.
{
"$schema": "https://opencode.ai/config.json",
"model": "local-deepseek/deepseek-chat",
"small_model": "local-deepseek/deepseek-chat",
"provider": { "local-deepseek": { "npm": "@ai-sdk/openai-compatible", "name": "DeepSeek (local bridge)", "options": { "baseURL": "http://127.0.0.1:8000/v1", "apiKey": "unused" }, "models": { "deepseek-chat": { "name": "DeepSeek Chat (Instant)", "tool_call": true }, "deepseek-expert": { "name": "DeepSeek Expert", "tool_call": true } } } }
}
small_modelиспользуется для служебных задач (генерация заголовков и т.п.).
Файл .opencode/agent/deepseek.md в корне рабочего проекта:
---
description: Инженерный агент на DeepSeek через локальный мост
mode: primary
model: local-deepseek/deepseek-expert
temperature: 0.3
permission:
edit: allow
webfetch: allow
bash:
"git *": allow
"*": ask
---
Ты — аккуратный инженерный агент. Сначала изучаешь код, затем предлагаешь
минимальные точечные правки. Не добавляешь комментарии без просьбы. После
изменений запускаешь линтер и тесты проекта, если они есть.mode: primary— агент доступен как основной (переключается в TUI/черезdefault_agent). Для вспомогательного агента используйтеmode: subagent.- Тело файла — это системный промпт агента.
Чтобы назначить его агентом по умолчанию, добавьте в opencode.json:
{ "default_agent": "deepseek" }opencode читает конфиг один раз при старте. Полностью закройте и заново
запустите opencode после создания/правки opencode.json и файлов агентов.
Проверка: в opencode откройте выбор модели — должны появиться
DeepSeek Chat (Instant) и DeepSeek Expert под провайдером
DeepSeek (local bridge).
У веб-чата DeepSeek нет канала function calling, поэтому мост эмулирует его
текстом: подмешивает в промпт инструкцию «ответь ровно одним блоком
```tool_calls JSON» и затем парсит ответ обратно
(server/openai_format.py). Если модель пишет прозу
вместо вызова, opencode просто печатает текст и ничего не выполняет.
Что сделано для надёжности:
- парсер принимает фенсы, JSON внутри прозы, а также нативный XML/DSML DeepSeek
(
<tool_call>,function<|tool_sep|>name); - инструкция усилена (запрет прозы + пример + напоминание в конце);
- плагин дисциплины добавляет позднюю системную инструкцию.
Диагностика: запустите сервер с DEBUG_TOOLCALLS=1 — при нераспознанном вызове
в лог (logs/server.log или stdout) попадёт сырой ответ модели.
DEBUG_TOOLCALLS=1 python app.pyПроверить мост напрямую (без opencode):
curl http://127.0.0.1:8000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "deepseek-chat",
"messages": [{"role":"user","content":"Create a file /tmp/x.txt with hello"}],
"tools": [{"type":"function","function":{"name":"write","parameters":{"type":"object","properties":{"filePath":{"type":"string"},"content":{"type":"string"}}}}}]
}'Успех — "finish_reason": "tool_calls" и заполненный message.tool_calls.
| Что | Значение в opencode | Примечание |
|---|---|---|
| Быстрая модель | local-deepseek/deepseek-chat |
По умолчанию |
| Экспертная модель | local-deepseek/deepseek-expert |
Сильнее, медленнее |
| DeepThink (reasoning) | — | Через extra_body, см. ниже |
| Веб-поиск | — | Через extra_body, см. ниже |
Модель задаётся в opencode штатно (выбор модели или поле model в агенте).
thinking (DeepThink) и search (веб-поиск) — это не модели, а
дополнительные флаги тела запроса, которые opencode напрямую не передаёт.
Их можно включить плагином (см. ниже) либо запросами через curl/SDK:
resp = client.chat.completions.create(
model="deepseek-expert",
messages=[{"role": "user", "content": "Что нового в мире?"}],
extra_body={"thinking": True, "search": True},
)Из-за эмуляции tool calling (см. выше) модель склонна «описывать» действие вместо вызова. Плагин добавляет позднюю системную инструкцию, требующую реальных tool calls. Файл авто-подхватывается opencode без правки конфига:
- глобально:
~/.config/opencode/plugin/deepseek-tool-discipline.js - в проекте:
.opencode/plugin/deepseek-tool-discipline.js(лежит в репозитории)
export const DeepSeekToolDiscipline = async () => ({
"experimental.chat.system.transform": async (input, output) => {
if (input?.model?.providerID !== "local-deepseek") return;
output.system.push(
"CRITICAL: to act you MUST emit real tool calls; never describe the " +
"action, never print JSON/XML or a tool_calls block as visible text."
);
},
});Хуки
experimental.*помечены экспериментальными (проверено на opencode 1.18.x). Если их переименуют, плагин просто перестанет срабатывать и не сломает запуск.
thinking/search — нестандартные поля тела запроса; opencode их напрямую не
передаёт. Их можно добавить хуком chat.params (output.options.thinking = true), но не включайте reasoning по умолчанию — он ухудшает соблюдение
формата tool calls. Через curl/SDK они работают сразу:
resp = client.chat.completions.create(
model="deepseek-expert",
messages=[{"role": "user", "content": "Что нового в мире?"}],
extra_body={"thinking": True, "search": True},
)Файл .env (копия .env.example):
| Переменная | По умолчанию | Назначение |
|---|---|---|
HOST |
127.0.0.1 |
Адрес прослушивания |
PORT |
8000 |
Порт |
RATE_LIMIT_PER_MINUTE |
30 |
Лимит запросов/мин на IP клиента (/healthz не считается) |
DEEPSEEK_PROFILE_DIR |
— | Переиспользовать существующий профиль Chrome с активной сессией |
SERVER_INTERACTIVE_LOGIN |
1 |
Открывать окно браузера при отсутствии сессии; 0 — отдавать 503 (для headless) |
SESSION_REFRESH_ENABLED |
1 |
Фоновое обновление сессии |
SESSION_REFRESH_INTERVAL |
18000 (5 ч) |
Интервал обновления, сек (меньше SESSION_MAX_AGE = 6 ч) |
REFRESH_BROWSER_CHANNEL |
chromium-headless-shell |
Канал Playwright для headless-обновления (меньше RAM) |
REFRESH_BROWSER_CHANNEL_FALLBACK |
chrome |
Запасной канал, если headless не читает cookies; пусто — отключить |
TOOLCALL_MAX_CONTINUATIONS |
3 |
Сколько раз допрашивать модель «продолжи», если tool call обрезан лимитом вывода |
DEBUG_TOOLCALLS |
— | 1 — писать в лог сырой ответ модели, если вызов не распознан |
Пример:
HOST=0.0.0.0 PORT=8080 RATE_LIMIT_PER_MINUTE=60 python app.py- Сериализация запросов. Один общий аккаунт, PoW-хранилище
wasmtimeне реентерабельно, поэтому апстрим-вызовы выполняются по одному (см.server/api.py). Параллельные запросы встают в очередь — не стоит запускать несколько агентов/сессий одновременно. - Tool calling эмулируется. Мост буферизует ответ и парсит его в tool
calls (
server/openai_format.py,parse_tool_calls). Парсер терпим к прозе, фенсам и XML/DSML DeepSeek, но это всё равно менее надёжно, чем нативный API: возможны сбои на длинных многошаговых цепочках. Помогает плагин дисциплины иDEBUG_TOOLCALLS=1для диагностики. - Нет реального подсчёта токенов.
usage— грубая оценка ~4 символа/токен. - Большинство OpenAI-параметров игнорируется (
temperature,top_p,max_tokensи т.д.). Действуют толькоmodel,messages,stream,conversation_id,thinking,search. - Vision не поддерживается (нет загрузки изображений).
- Идентификатор диалога.
conversation_idфиксирует модель при создании потока; при продолженииmodelигнорируется. - Лимит рейта. При
429используйте экспоненциальную задержку — официальныйopenaiSDK делает это автоматически. - Не хаммерьте аккаунт. Это ваш обычный аккаунт DeepSeek; массовые автоматические запросы могут привести к блокировке.
Сессия истекла / 503 login_required
python -m deepseek.auth # войдите зановоУбедитесь, что SERVER_INTERACTIVE_LOGIN=0 на headless-деплое, а окно входа
выполнено заранее.
Playwright Sync API inside the asyncio loop
Не вызывайте синхронный Playwright из event loop — мост уже оборачивает вызовы
в run_in_threadpool. Такое сообщение обычно означает нестандартную
модификацию кода.
Ошибки PoW / wasmtime
pip install --upgrade wasmtimeПустая headless-сессия (macOS, Chrome-профиль)
По умолчанию фолбэк на chrome. Оставьте REFRESH_BROWSER_CHANNEL_FALLBACK=chrome
или задайте DEEPSEEK_PROFILE_DIR.
429 Too Many Requests
Поднимите RATE_LIMIT_PER_MINUTE и/или добавьте ретраи с backoff.
Порт занят
PORT=8080 python app.pyи укажите http://127.0.0.1:8080/v1 в baseURL провайдера opencode.
Агент отвечает текстом, но файлы не меняет (нет tool calls)
- В
opencode.jsonу моделей должно быть"tool_call": true. - Проверьте мост напрямую
curl-запросом сtools(см. Интеграция с opencode): должен вернуться"finish_reason": "tool_calls"и непустойmessage.tool_calls. - Поставьте плагин
deepseek-tool-discipline.js(глобально или в проект). - Запустите сервер с
DEBUG_TOOLCALLS=1и изучите сырой ответ модели в логе. - Увеличьте
stepsу агента и используйтеdeepseek-expert.
Правки кода не применились после редактирования (launchd)
Сервер под launchd с reload=False — перезапустите его:
launchctl kickstart -k "gui/$(id -u)/com.deepseek.api".
Изменения в opencode.json/агентах не применились
Перезапустите opencode — конфиг не перезагружается на лету.
| Путь | Назначение |
|---|---|
app.py |
Точка входа — запускает сервер |
deepseek/ |
Ядро: DeepSeekClient, вход (auth.py), HTTP-драйвер (client.py), PoW (pow.py) |
server/ |
FastAPI OpenAI-совместимый сервер (api.py, config.py, openai_format.py — парсер tool calls, ratelimit.py, schemas.py) |
.opencode/plugin/ |
Плагин дисциплины инструментов для opencode |
examples/ |
Запускаемые примеры (прямой Python и через сервер) |
session/ |
Сохранённая сессия (cookies + токен), git-ignored |
logs/ |
Логи launchd-сервиса, git-ignored |
.env.example |
Шаблон конфигурации |
requirements.txt |
Python-зависимости |
- Всё в
session/(cookies + bearer-токен) остаётся на вашей машине и исключено из git (.gitignore). Никогда не коммитьтеsession/. - Пароли/секреты в
.envне хранятся — вход выполняется вручную в браузере. - При
HOST=0.0.0.0мост доступен в сети без аутентификации. Биндитесь только на127.0.0.1или закрывайте firewall'ом/прокси. - Не публикуйте
session/и.envв публичных репозиториях.
MIT License. Проект неофициальный; вы отвечаете за соблюдение условий использования DeepSeek.
Оригинал: https://github.com/sums001/Deepseek-API
