-FreeDeepseekAPI поднимает локальный API-сервер для **DeepSeek Web Chat** (`chat.deepseek.com`) и позволяет подключать DeepSeek Web к Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style клиентам и другим OpenAI-compatible инструментам.
+FreeDeepseekAPI runs a local API server for **DeepSeek Web Chat** (`chat.deepseek.com`) and lets you connect DeepSeek Web to Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style clients, and other OpenAI-compatible tools.
-Проект работает через ваш обычный залогиненный аккаунт DeepSeek в отдельном Chrome-профиле. Локальный сервер принимает API-запросы, а дальше сам ходит в DeepSeek Web через сохранённую browser-сессию.
+The project works through your regular logged-in DeepSeek account in a separate Chrome profile. The local server accepts API requests and then talks to DeepSeek Web through the saved browser session.
-> ⚠️ Это экспериментальный web-chat proxy. DeepSeek может менять внутренний Web API без предупреждения. Для production-кейсов надёжнее официальный платный API DeepSeek.
+> ⚠️ This is an experimental web-chat proxy. DeepSeek may change the internal Web API without warning. For production use cases, the official paid DeepSeek API is more reliable.
ForgetMeAI: https://t.me/forgetmeai
---
-## Навигация
+## Navigation
-- [Что это даёт](#-что-это-даёт)
-- [Возможности](#-возможности)
-- [Быстрый старт](#-быстрый-старт)
-- [Windows запуск](#-windows-запуск)
-- [Linux / Chromium запуск](#-linux--chromium-запуск)
-- [VPS / headless запуск](#-vps--headless-запуск)
+- [What this gives you](#-what-this-gives-you)
+- [Features](#-features)
+- [Quick Start](#-quick-start)
+- [Windows launch](#-windows-launch)
+- [Linux / Chromium launch](#-linux--chromium-launch)
+- [VPS / headless launch](#-vps--headless-launch)
+- [Rootless Podman](#-rootless-podman)
- [Diagnostics / doctor](#-diagnostics--doctor)
-- [Session reuse и сброс чатов](#-session-reuse-и-сброс-чатов)
+- [Session reuse and chat reset](#-session-reuse-and-chat-reset)
- [Multi-account pool](#-multi-account-pool)
-- [Идеи для консольной авторизации](#-идеи-для-консольной-авторизации)
-- [Проверка работы](#-проверка-работы)
-- [Примеры запросов](#-примеры-запросов)
+- [Console auth ideas](#-console-auth-ideas)
+- [Verify it works](#-verify-it-works)
+- [Request examples](#-request-examples)
- [Chat Completions](#chat-completions)
- [Reasoning](#reasoning)
- [Web search](#web-search)
@@ -51,40 +52,41 @@ ForgetMeAI: https://t.me/forgetmeai
- [Anthropic Messages API](#anthropic-messages-api)
- [OpenAI Responses API](#openai-responses-api)
- [Tool calling](#tool-calling)
-- [Модели](#-модели)
+- [Models](#-models)
- [Endpoints](#-endpoints)
- [Open WebUI](#-open-webui)
-- [Обновить логин](#-обновить-логин)
-- [Статус проекта](#-статус-проекта)
+- [Update login](#-update-login)
+- [Tests](#-tests)
+- [Project status](#-project-status)
---
-## ✨ Что это даёт
+## ✨ What this gives you
-- Использовать DeepSeek Web как локальный API endpoint.
-- Подключать DeepSeek к Open WebUI и другим OpenAI-compatible клиентам.
-- Получать обычные JSON-ответы или streaming SSE.
-- Использовать reasoning-модели с отдельным `reasoning_content`.
-- Работать с Anthropic Messages API shim для Claude Code / Anthropic SDK.
-- Использовать OpenAI Responses API shim для новых OpenAI/Codex-style клиентов.
-- Держать отдельные web-сессии для разных агентов/users.
+- Use DeepSeek Web as a local API endpoint.
+- Connect DeepSeek to Open WebUI and other OpenAI-compatible clients.
+- Get regular JSON responses or streaming SSE.
+- Use reasoning models with separate `reasoning_content`.
+- Work with Anthropic Messages API shim for Claude Code / Anthropic SDK.
+- Use OpenAI Responses API shim for new OpenAI/Codex-style clients.
+- Keep separate web sessions for different agents/users.
-## 🚀 Возможности
+## Features
- **OpenAI-compatible API:** `POST /v1/chat/completions`
- **Anthropic-compatible shim:** `POST /v1/messages`
- **OpenAI Responses shim:** `POST /v1/responses`
-- **Streaming:** SSE chunks и обычные non-stream JSON-ответы
-- **Reasoning output:** отдельный `reasoning_content` для thinking-моделей
-- **Tool calling:** парсинг OpenAI tools, Anthropic tools и Responses function tools
-- **Model capabilities:** `GET /v1/model-capabilities` с alias → real web mode
-- **Agent sessions:** отдельная DeepSeek-сессия на `user` / agent id
-- **Session recovery:** авто-сброс устаревших chains/sessions
-- **Zero dependencies:** Node.js 18+, без npm-зависимостей
+- **Streaming:** SSE chunks and regular non-stream JSON responses
+- **Reasoning output:** separate `reasoning_content` for thinking models
+- **Tool calling:** parsing OpenAI tools, Anthropic tools, and Responses function tools
+- **Model capabilities:** `GET /v1/model-capabilities` with alias → real web mode
+- **Agent sessions:** separate DeepSeek session per `user` / agent id
+- **Session recovery:** auto-reset of stale chains/sessions
+- **Zero dependencies:** Node.js 18+, no npm dependencies
---
-## ⚡ Быстрый старт
+## ⚡ Quick Start
```bash
git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git
@@ -93,37 +95,52 @@ npm run auth
npm start
```
-`npm run auth` открывает меню авторизации:
+`npm run auth` opens the auth menu:
-1. выберите пункт `1`;
-2. войдите в DeepSeek в отдельном Chrome-профиле;
-3. отправьте короткое сообщение вроде `ok`;
-4. вернитесь в терминал и нажмите Enter.
+1. select item `1`;
+2. log in to DeepSeek in a separate Chrome profile;
+3. send a short message like `ok`;
+4. return to terminal and press Enter.
-`npm start` показывает меню запуска:
+`npm start` shows the launch menu:
-- `1` — авторизоваться / обновить DeepSeek login
-- `2` — показать модели и статусы
-- `3` — запустить proxy
-- `4` — выйти
+- `1` — authorize / update DeepSeek login
+- `2` — show models and statuses
+- `3` — start proxy
+- `4` — exit
-Для headless/CI-запуска без меню:
+For headless/CI launch without menu:
```bash
NON_INTERACTIVE=1 npm start
-# или
+# or
SKIP_ACCOUNT_MENU=1 npm start
```
-По умолчанию сервер слушает:
+By default the server listens on:
```text
http://localhost:9655
```
+By default the proxy is only accessible from the same machine. To allow
+network access, explicitly set the bind address and a proxy API key:
+
+```bash
+HOST=0.0.0.0 PROXY_API_KEY='replace-with-a-long-random-value' npm start
+```
+
+Then pass the key as `Authorization: Bearer `. Without `PROXY_API_KEY`
+non-health endpoints remain unauthenticated, so do not expose such an
+instance to the network.
+
+Browser requests are allowed from loopback origins. If the UI is opened on a
+different address, add its exact origin via comma, e.g.
+`PROXY_CORS_ORIGINS=https://ui.example.com,http://192.168.1.20:3000`.
+
---
-## 🪟 Windows запуск
+## Windows Launch
```powershell
git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git
@@ -132,18 +149,18 @@ npm run auth
npm start
```
-Если Chrome установлен нестандартно, явно укажите путь:
+If Chrome is installed in a non-standard location, specify the path explicitly:
```powershell
$env:CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe"
npm run auth
```
-Если Chrome не найден, `npm run auth` теперь печатает готовые инструкции для Windows/macOS/Linux вместо загадочного stack trace.
+If Chrome is not found, `npm run auth` now prints ready-to-use instructions for Windows/macOS/Linux instead of a cryptic stack trace.
---
-## 🐧 Linux / Chromium запуск
+## Linux / Chromium Launch
```bash
git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git
@@ -152,33 +169,33 @@ CHROME_PATH=$(which chromium) npm run auth
npm start
```
-Если Chromium называется иначе:
+If Chromium has a different name:
```bash
CHROME_PATH=$(which chromium-browser) npm run auth
-# или
+# or
CHROME_PATH=$(which google-chrome) npm run auth
```
---
-## 🖥 VPS / headless запуск
+## VPS / Headless Launch
-Самый надёжный flow без Chrome на сервере:
+The most reliable flow without Chrome on the server:
-1. На домашнем ПК, где есть GUI/Chrome:
+1. On your home PC (where you have GUI/Chrome):
```bash
npm run auth
```
-2. Скопируйте `deepseek-auth.json` на VPS:
+2. Copy `deepseek-auth.json` to VPS:
```bash
scp deepseek-auth.json user@your-vps:/opt/FreeDeepseekAPI/deepseek-auth.json
```
-3. На VPS импортируйте/проверьте файл и выставьте безопасные права:
+3. On VPS import/verify the file and set safe permissions:
```bash
cd /opt/FreeDeepseekAPI
@@ -186,87 +203,191 @@ npm run auth:import -- --input ./deepseek-auth.json
npm run doctor -- --offline
```
-4. Запускайте proxy без интерактивного меню:
+4. Start proxy without interactive menu:
```bash
NON_INTERACTIVE=1 npm start
```
-Можно импортировать не только готовый `deepseek-auth.json`, но и browser cookie export:
+You can import not only a ready-made `deepseek-auth.json`, but also a browser cookie export:
```bash
DEEPSEEK_TOKEN="" npm run auth:import -- --input ./cookies.json
```
-> Важно: `deepseek-auth.json` — это доступ к вашему DeepSeek Web login. Не коммитьте, не публикуйте, храните с правами `0600`.
+> Important: `deepseek-auth.json` gives access to your DeepSeek Web login. Do not commit, do not publish, store with `0600` permissions.
+
+---
+
+## 🦭 Rootless Podman
+
+The container is intended for non-interactive proxy launch only. Perform
+browser-based authorization on the host with `npm run auth`: auth scripts and
+`deepseek-auth.json` are not copied into the image.
+
+Run Podman as a regular user, without `sudo`.
+
+1. Build the local image:
+
+```bash
+podman build --tag localhost/free-deepseek-api:local --file Containerfile .
+```
+
+2. Pass DeepSeek auth and a separate proxy API key via Podman secrets:
+
+```bash
+podman secret create --replace free-deepseek-auth ./deepseek-auth.json
+
+printf 'Proxy API key: '
+IFS= read -r -s PROXY_API_KEY
+printf '\n'
+printf '%s' "$PROXY_API_KEY" |
+ podman secret create --replace free-deepseek-proxy-key -
+```
+
+Use a long random key. The value stays in the current shell's `PROXY_API_KEY`
+variable for API verification; it does not end up in the image or the Podman
+command line.
+
+3. Run the container with minimal privileges:
+
+```bash
+podman run --detach \
+ --name free-deepseek-api \
+ --publish 127.0.0.1:9655:9655 \
+ --secret free-deepseek-auth,target=deepseek-auth.json,uid=1000,gid=1000,mode=0400 \
+ --secret free-deepseek-proxy-key,target=proxy-api-key,uid=1000,gid=1000,mode=0400 \
+ --read-only \
+ --cap-drop=ALL \
+ --security-opt=no-new-privileges \
+ localhost/free-deepseek-api:local
+```
+
+Inside the container, `NON_INTERACTIVE=1`, `HOST=0.0.0.0`, and paths to both
+secrets are pre-configured. `REQUIRE_PROXY_API_KEY=1` prevents the container
+from starting if the key secret is missing or empty. On the host, the port is
+only published to `127.0.0.1`; do not remove this address without a separate
+network firewall/access policy.
+
+4. Verify liveness, account readiness, and the protected endpoint:
+
+```bash
+podman healthcheck run free-deepseek-api
+curl --fail http://127.0.0.1:9655/readyz
+curl --fail \
+ -H "Authorization: Bearer $PROXY_API_KEY" \
+ http://127.0.0.1:9655/v1/models
+```
+
+The built-in healthcheck verifies the local `/health` (whether the process is
+alive). `/readyz` additionally returns `503` if no DeepSeek auth account is
+currently ready to serve requests. Container diagnostics:
+
+```bash
+podman logs free-deepseek-api
+podman inspect --format '{{.State.Health.Status}}' free-deepseek-api
+```
+
+Stop and remove the container along with the saved Podman secrets:
+
+```bash
+podman stop free-deepseek-api
+podman rm free-deepseek-api
+podman secret rm free-deepseek-auth free-deepseek-proxy-key
+unset PROXY_API_KEY
+```
+
+When rotating auth or proxy key, replace the corresponding secret and recreate
+the container so that behavior does not depend on the Podman version.
---
-## 🩺 Diagnostics / doctor
+## Diagnostics / doctor
```bash
npm run doctor
-# без сетевых запросов к DeepSeek:
+# without network requests to DeepSeek:
npm run doctor -- --offline
```
-`doctor` проверяет:
+`doctor` checks:
-- найден ли `deepseek-auth.json` / `DEEPSEEK_AUTH_DIR`;
-- валидный ли JSON;
-- есть ли `token`, `cookie`, `wasmUrl`;
-- безопасные ли права файла на macOS/Linux (`0600`);
-- при обычном запуске — доступен ли DeepSeek PoW endpoint.
+- whether `deepseek-auth.json` / `DEEPSEEK_AUTH_DIR` is found;
+- whether JSON is valid;
+- whether `token`, `cookie`, `wasmUrl` exist;
+- whether file permissions are safe on macOS/Linux (`0600`);
+- on normal run — whether DeepSeek PoW endpoint is reachable.
-Если видите `data.biz_data is null`, `fetch failed`, `401/403/429` или Hermes/OpenCode не видит модели — первым делом запускайте `npm run doctor`.
+If you see `data.biz_data is null`, `fetch failed`, `401/403/429` or Hermes/OpenCode doesn't see models — first run `npm run doctor`.
---
-## ♻️ Session reuse и сброс чатов
+## ♻️ Session reuse and chat reset
-FreeDeepseekAPI не создаёт новый DeepSeek чат на каждый HTTP-запрос без причины. Логика такая:
+FreeDeepseekAPI doesn't create a new DeepSeek chat on every HTTP request without reason. The logic is:
-- один `x-agent-session`, `session` или `user` → одна DeepSeek chat session;
-- если session id уже есть — proxy переиспользует его и продолжает chain через `parent_message_id`;
-- auto-reset происходит при TTL, ошибке DeepSeek session или слишком длинной цепочке сообщений;
-- локальная history сохраняется коротким контекстом, чтобы новая DeepSeek session могла продолжить разговор.
+- one `x-agent-session`, `session`, or `user` → one DeepSeek chat session;
+- if session id already exists — proxy reuses it and continues chain via `parent_message_id`;
+- auto-reset happens on TTL, DeepSeek session error, or too long message chain;
+- local history is saved as short context so new DeepSeek session can continue the conversation.
+- long agent requests are trimmed before sending via `DEEPSEEK_MAX_PROMPT_CHARS` (default 80,000 characters): the beginning of the task, fresh tool results, and tool adapter are preserved;
+- if the client already sent multi-turn history, local recovery-history is not added a second time;
+- empty responses are retried up to `DEEPSEEK_MAX_RETRIES` times (default 2), with context shrinking on each retry.
-Явно задать agent/session:
+Explicitly set agent/session:
```bash
curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-agent-session: my-agent" \
- -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Привет"}]}'
+ -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hi"}]}'
```
-Посмотреть активные sessions:
+View active sessions:
```bash
curl http://localhost:9655/v1/sessions
```
-Сбросить одну session:
+Reset a single session:
```bash
curl -X POST "http://localhost:9655/reset-session?agent=my-agent"
```
-Сбросить все sessions:
+Reset all sessions:
```bash
curl -X POST "http://localhost:9655/reset-session?agent=all"
```
-Почему чаты всё равно появляются в DeepSeek Web: proxy работает через внутренний Web Chat API, а DeepSeek хранит реальные chat sessions у себя. Это нормально для web-proxy. Задача session reuse — не плодить новые чаты без необходимости и аккуратно сбрасываться только когда chain протух/сломался.
+Clear sessions on server restart:
+
+```bash
+# Start server with sessions cleared (fresh context each restart)
+DEEPSEEK_CLEAR_SESSIONS_ON_START=1 npm start
+```
+
+This deletes `sessions.json` on startup, so the model starts with no memory of previous conversations. Useful for development/testing when you want clean context each restart.
+
+Debug tool loading:
+
+```bash
+# Log all MCP tool names loaded per agent (for debugging)
+DEEPSEEK_LOG_TOOLS=1 npm start
+```
+
+Shows which tools are being sent by the client and cached by the server. Disabled by default.
+
+Why chats still appear in DeepSeek Web: proxy works through internal Web Chat API, and DeepSeek stores real chat sessions on their side. This is normal for web-proxy. The task of session reuse is not to spawn new chats unnecessarily and to reset cleanly only when the chain has gone stale/broken.
---
-## 👥 Multi-account pool
+## Multi-account pool
-Можно подключить несколько auth-файлов. Правильная модель: sticky account per agent/session — proxy не переключает аккаунт внутри живой DeepSeek-сессии. Если аккаунт получил `401/403/429` и ушёл в cooldown, session безопасно сбрасывается и новый запрос может перейти на другой доступный аккаунт.
+You can connect multiple auth files. Correct model: sticky account per agent/session — proxy doesn't switch account inside a live DeepSeek session. If an account gets `401/403/429` and goes into cooldown, the session is safely reset and a new request may switch to another available account.
-Вариант 1 — директория с auth-файлами:
+Option 1 — directory with auth files:
```bash
mkdir -p accounts
@@ -276,22 +397,22 @@ chmod 600 accounts/*.json
DEEPSEEK_AUTH_DIR=./accounts NON_INTERACTIVE=1 npm start
```
-Вариант 2 — список файлов:
+Option 2 — file list:
```bash
DEEPSEEK_AUTH_PATH="./accounts/main.json,./accounts/backup.json" NON_INTERACTIVE=1 npm start
```
-Как работает pool:
+How the pool works:
-- новый agent/session получает доступный аккаунт round-robin;
-- выбранный аккаунт закрепляется за session (`sticky`);
-- при `401`, `403`, `429` аккаунт уходит в cooldown;
-- если sticky-аккаунт session ушёл в cooldown, старая DeepSeek-сессия сбрасывается, чтобы не долбить rate-limited/expired аккаунт;
-- статус аккаунтов виден в `/health` без путей к auth-файлам и без имён файлов;
-- auth-файлы должны храниться с правами `0600`.
+- new agent/session gets an available account round-robin;
+- selected account is pinned to session (`sticky`);
+- on `401`, `403`, `429` account goes into cooldown;
+- if sticky-account session went into cooldown, old DeepSeek session is reset to avoid hammering rate-limited/expired account;
+- account status visible in `/health` without auth file paths or file names;
+- auth files must be stored with `0600` permissions.
-Настроить cooldown:
+Configure cooldown:
```bash
DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start
@@ -299,22 +420,22 @@ DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start
---
-## 🔑 Идеи для консольной авторизации
+## Console auth ideas
-Парольный flow из PR #3 можно делать, но безопаснее не хранить пароль и не делать это дефолтом. Нормальная реализация:
+Password flow from PR #3 can be done, but safer not to store password and not make it default. Normal implementation:
-1. `npm run auth:console` спрашивает email/телефон и пароль через hidden prompt.
-2. Пароль держится только в памяти процесса, не пишется в файлы/logs/history.
-3. Скрипт повторяет Web login flow через `fetch`/CDP: получает captcha/verify challenge, отдаёт человеку ссылку/код, ждёт подтверждение.
-4. После успешного login сохраняется только `deepseek-auth.json` стандартного формата.
-5. Если DeepSeek просит captcha/2FA — скрипт честно говорит “открой ссылку, пройди проверку, нажми Enter”, а не пытается обходить защиту.
-6. Для VPS лучше режим `auth:console --no-save-password --output deepseek-auth.json`.
+1. `npm run auth:console` asks for email/phone and password via hidden prompt.
+2. Password stays only in process memory, not written to files/logs/history.
+3. Script replicates Web login flow via `fetch`/CDP: gets captcha/verify challenge, gives human link/code, waits for confirmation.
+4. After successful login, only standard-format `deepseek-auth.json` is saved.
+5. If DeepSeek asks for captcha/2FA — script honestly says "open link, pass check, press Enter", doesn't try to bypass protection.
+6. For VPS better mode `auth:console --no-save-password --output deepseek-auth.json`.
-Минимальный безопасный MVP: console auth только интерактивный, без env-пароля. Допустимый automation-вариант: `DEEPSEEK_EMAIL=... npm run auth:console`, но пароль всё равно вводится hidden prompt.
+Minimal safe MVP: console auth only interactive, no env password. Acceptable automation variant: `DEEPSEEK_EMAIL=... npm run auth:console`, but password still entered via hidden prompt.
---
-## ✅ Проверка работы
+## ✅ Verify it works
```bash
curl http://localhost:9655/
@@ -322,11 +443,11 @@ curl http://localhost:9655/v1/models
curl http://localhost:9655/v1/model-capabilities
```
-Если всё ок, `/health` вернёт статус сервера, список поддерживаемых aliases и `config_ready: true`.
+If all good, `/health` returns server status, list of supported aliases, and `config_ready: true`.
---
-## 🧪 Примеры запросов
+## Request examples
### Chat Completions
@@ -335,7 +456,7 @@ curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
- "messages": [{"role": "user", "content": "Привет! Ответь одной фразой."}],
+ "messages": [{"role": "user", "content": "Hi! Reply with one phrase."}],
"stream": false
}'
```
@@ -347,18 +468,18 @@ curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-reasoner",
- "messages": [{"role": "user", "content": "Реши коротко: почему небо голубое?"}],
+ "messages": [{"role": "user", "content": "Solve briefly: why is the sky blue?"}],
"stream": false
}'
```
-Для reasoning-моделей API отдаёт цепочку размышления отдельно от финального ответа:
+For reasoning models, API returns the reasoning chain separately from the final answer:
- non-stream: `choices[0].message.reasoning_content`
- stream: `choices[0].delta.reasoning_content`
- usage: `usage.completion_tokens_details.reasoning_tokens`
-`reasoning_tokens` — приблизительная оценка по извлечённому DeepSeek Web `THINK`-тексту, потому что web stream не отдаёт официальный token usage по reasoning отдельно.
+`reasoning_tokens` — approximate estimate based on extracted DeepSeek Web `THINK` text, because web stream doesn't return official token usage for reasoning separately.
### Web search
@@ -367,7 +488,7 @@ curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat-search",
- "messages": [{"role": "user", "content": "Найди свежий факт про DeepSeek и ответь кратко."}],
+ "messages": [{"role": "user", "content": "Find a fresh fact about DeepSeek and reply briefly."}],
"stream": false
}'
```
@@ -379,7 +500,7 @@ curl -N -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
- "messages": [{"role": "user", "content": "Напиши короткую шутку."}],
+ "messages": [{"role": "user", "content": "Write a short joke."}],
"stream": true
}'
```
@@ -392,12 +513,12 @@ curl -X POST http://localhost:9655/v1/messages \
-d '{
"model": "deepseek-chat",
"max_tokens": 512,
- "messages": [{"role": "user", "content": "Ответь ровно OK"}],
+ "messages": [{"role": "user", "content": "Reply exactly OK"}],
"stream": false
}'
```
-Для Claude Code можно указывать backend напрямую:
+For Claude Code you can specify backend directly:
```bash
export ANTHROPIC_BASE_URL="http://127.0.0.1:9655"
@@ -413,127 +534,130 @@ curl -X POST http://localhost:9655/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
- "input": "Ответь ровно OK",
+ "input": "Reply exactly OK",
"stream": false
}'
```
### Tool calling
-FreeDeepseekAPI принимает:
+FreeDeepseekAPI accepts:
- OpenAI `tools`;
- Anthropic `tools`;
- Responses API function tools.
-Прокси просит DeepSeek вернуть строгий JSON tool call, но также умеет парсить fallback-форматы:
+Proxy asks DeepSeek to return strict JSON tool call, but also parses fallback formats:
- `TOOL_CALL:`
-- fenced JSON
+- fenced JSON with an explicit `tool_call`, `tool_calls`, or `function_call` envelope
- `...`
+- DeepSeek DSML (`<|DSML|tool_calls>...`) and web variant with `<||DSML|| Tool Calls>`
---
-## 🧠 Модели
+## Models
-`GET /v1/models` возвращает только aliases, которые сейчас проверены и работают через этот proxy.
+`GET /v1/models` returns only aliases that are currently verified and working through this proxy.
-### Рабочие aliases
+### Working aliases
-| Alias | Web mode | Reasoning | Web search | Комментарий |
+| Alias | Web mode | Reasoning | Web search | Notes |
| --- | --- | --- | --- | --- |
-| `deepseek-chat` | `Быстрый` / `default` | нет | нет | базовый chat |
-| `deepseek-v3` | `Быстрый` / `default` | нет | нет | совместимый alias |
-| `deepseek-default` | `Быстрый` / `default` | нет | нет | совместимый alias |
-| `deepseek-reasoner` | `Быстрый` / `default` | да | нет | `thinking_enabled=true` |
-| `deepseek-r1` | `Быстрый` / `default` | да | нет | R1-compatible alias |
-| `deepseek-chat-search` | `Быстрый` / `default` | нет | да | web search |
-| `deepseek-default-search` | `Быстрый` / `default` | нет | да | web search alias |
-| `deepseek-reasoner-search` | `Быстрый` / `default` | да | да | reasoning + search |
-| `deepseek-r1-search` | `Быстрый` / `default` | да | да | R1-compatible + search |
-| `deepseek-expert` | `Эксперт` / `expert` | нет | нет | Expert mode |
-| `deepseek-v4-pro` | `Эксперт` / `expert` | да | нет | Expert + reasoning |
-
-Полный маппинг:
+| `deepseek-chat` | `Fast` / `default` | no | no | basic chat |
+| `deepseek-v3` | `Fast` / `default` | no | no | compatibility alias |
+| `deepseek-default` | `Fast` / `default` | no | no | compatibility alias |
+| `deepseek-reasoner` | `Fast` / `default` | yes | no | `thinking_enabled=true` |
+| `deepseek-r1` | `Fast` / `default` | yes | no | R1-compatible alias |
+| `deepseek-chat-search` | `Fast` / `default` | no | yes | web search |
+| `deepseek-default-search` | `Fast` / `default` | no | yes | web search alias |
+| `deepseek-reasoner-search` | `Fast` / `default` | yes | yes | reasoning + search |
+| `deepseek-r1-search` | `Fast` / `default` | yes | yes | R1-compatible + search |
+| `deepseek-expert` | `Expert` / `expert` | no | no | Expert mode |
+| `deepseek-v4-pro` | `Expert` / `expert` | yes | no | Expert + reasoning |
+
+Full mapping:
```bash
curl http://localhost:9655/v1/model-capabilities
```
-По официальной странице DeepSeek V4 Preview `deepseek-chat` и `deepseek-reasoner` сейчас route'ятся в `deepseek-v4-flash` non-thinking/thinking. В самом `chat.deepseek.com` direct stream точное имя чекпойнта не отдаётся (`model: ""`), поэтому proxy фиксирует одновременно web-режим (`default` / `Быстрый`) и актуальную официальную маршрутизацию (`DeepSeek-V4-Flash`).
+According to the official DeepSeek V4 Preview page, `deepseek-chat` and `deepseek-reasoner` are currently routed to `deepseek-v4-flash` non-thinking/thinking. In `chat.deepseek.com` direct stream the exact checkpoint name is not returned (`model: ""`), so the proxy records both the web mode (`default` / `Fast`) and the current official routing (`DeepSeek-V4-Flash`).
-Текущий вывод DeepSeek Web remote config показывает такие web-режимы:
+Current DeepSeek Web remote config shows these web modes:
-- `default` / UI `Быстрый` — работает; поддерживает `thinking_enabled` и `search_enabled`.
-- `expert` / UI `Эксперт` — работает через актуальный web-контракт (`x-client-version=2.0.0`) и поддерживает `thinking_enabled`. В `/v1/models` выдаются `deepseek-expert` без reasoning и `deepseek-v4-pro` как Expert + reasoning.
-- `vision` / UI `Распознавание` — виден в remote config, но сейчас direct Web API возвращает `backend_err_by_model` (`Vision is temporarily unavailable`). Поэтому `deepseek-vision` скрыт из `/v1/models`.
+- `default` / UI `Fast` — works; supports `thinking_enabled` and `search_enabled`.
+- `expert` / UI `Expert` — works via the current web contract (`x-client-version=2.0.0`) and supports `thinking_enabled`. In `/v1/models`, `deepseek-expert` is served without reasoning and `deepseek-v4-pro` as Expert + reasoning.
+- `vision` / UI `Recognition` — visible in remote config, but currently the direct Web API returns `backend_err_by_model` (`Vision is temporarily unavailable`). Therefore `deepseek-vision` is hidden from `/v1/models`.
-Search для Expert по remote config недоступен, поэтому `deepseek-expert-search` остаётся unsupported.
+Search for Expert is unavailable per remote config, so `deepseek-expert-search` remains unsupported.
---
-## 🔌 Endpoints
+## Endpoints
-| Method | Path | Назначение |
+| Method | Path | Purpose |
| --- | --- | --- |
-| `GET` | `/` или `/health` | статус proxy |
-| `GET` | `/v1/models` | список рабочих OpenAI-compatible aliases |
-| `GET` | `/v1/model-capabilities` | полный маппинг aliases, real model, capabilities |
+| `GET` | `/` or `/health` | proxy status |
+| `GET` | `/v1/models` | list of working OpenAI-compatible aliases |
+| `GET` | `/v1/model-capabilities` | full alias mapping, real model, capabilities |
| `POST` | `/v1/chat/completions` | OpenAI-compatible Chat Completions |
| `POST` | `/v1/messages` | Anthropic Messages API shim |
| `POST` | `/v1/responses` | OpenAI Responses API shim |
-| `GET` | `/v1/sessions` | активные локальные agent sessions |
-| `POST` | `/reset-session?agent=` | сбросить одну session |
-| `POST` | `/reset-session?agent=all` | сбросить все sessions |
+| `GET` | `/v1/sessions` | active local agent sessions |
+| `POST` | `/reset-session?agent=` | reset a single session |
+| `POST` | `/reset-session?agent=all` | reset all sessions |
---
-## 🖥 Open WebUI
+## Open WebUI
-Base URL для Open WebUI в Docker:
+Base URL for Open WebUI in Docker:
```text
http://host.docker.internal:9655/v1
```
-Для локального запуска без Docker:
+For local launch without Docker:
```text
http://localhost:9655/v1
```
-API key можно указать любой: proxy сам ходит в DeepSeek Web через сохранённую browser-сессию.
+If `PROXY_API_KEY` is not set, any API key can be used. If the key is set,
+the client must send exactly that value — proxy verifies the bearer token
+before granting access to models, sessions, and completions.
---
-## 🔐 Обновить логин
+## Update login
```bash
npm run auth
npm start
```
-Если DeepSeek начал отвечать `401`, `403` или просит новый PoW/session — повторите `npm run auth` и обновите сохранённую browser-сессию.
+If DeepSeek starts returning `401`, `403` or asks for a new PoW/session — re-run `npm run auth` to refresh the saved browser session.
-Локальные файлы авторизации не должны попадать в GitHub:
+Local auth files must not be committed to GitHub:
- `deepseek-auth.json`
- `.chrome-profile-deepseek/`
- `.env`
-Они уже добавлены в `.gitignore`.
+They are already in `.gitignore`.
---
-## 🧪 Тесты
+## 🧪 Tests
-Синтаксическая проверка проекта:
+Syntax check for the project:
```bash
npm test
```
-Live smoke-тесты против запущенного локального proxy:
+Live smoke tests against a running local proxy:
```bash
BASE_URL=http://127.0.0.1:9655 MODEL=deepseek-chat npm run test:live
@@ -541,16 +665,16 @@ BASE_URL=http://127.0.0.1:9655 MODEL=deepseek-chat npm run test:live
---
-## 📌 Статус проекта
+## Project status
-FreeDeepseekAPI — экспериментальный web-chat proxy для локального использования и интеграций. Он зависит от текущего контракта DeepSeek Web Chat, поэтому при изменениях на стороне DeepSeek может потребоваться обновление auth/session logic или model mapping.
+FreeDeepseekAPI is an experimental web-chat proxy for local use and integrations. It depends on the current DeepSeek Web Chat contract, so when DeepSeek makes changes, auth/session logic or model mapping may need updating.
-Если что-то перестало работать:
+If something stops working:
-1. обновите логин через `npm run auth`;
-2. проверьте `/v1/model-capabilities`;
-3. повторите запрос на свежей сессии;
-4. если проблема сохраняется — вероятно, DeepSeek изменил внутренний Web API.
+1. refresh login via `npm run auth`;
+2. check `/v1/model-capabilities`;
+3. retry the request on a fresh session;
+4. if the problem persists — DeepSeek likely changed the internal Web API.
---
diff --git a/chrome-extension/README.md b/chrome-extension/README.md
deleted file mode 100644
index 169369d..0000000
--- a/chrome-extension/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# DeepSeek → FreeDeepseekAPI (расширение)
-
-Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI **одним кликом**:
-перехватывает заголовки реального запроса к `chat.deepseek.com/api/...`
-(`token` из `Authorization`, все cookie, `hif_*`) и отправляет на
-`http://localhost:9655/api/accounts/import`.
-
-Работает в Firefox и Chrome/Edge (Manifest V3).
-
-## Установка
-
-**Firefox**
-1. Откройте `about:debugging#/runtime/this-firefox`
-2. «Загрузить временное дополнение» → выберите `manifest.json` из этой папки.
- (Временное дополнение: после перезапуска Firefox установить заново.)
-
-**Chrome / Edge**
-1. Откройте `chrome://extensions`
-2. Включите «Режим разработчика».
-3. «Загрузить распакованное» → выберите эту папку.
-
-## Использование
-1. Запустите FreeDeepseekAPI (порт 9655).
-2. Откройте `chat.deepseek.com` и войдите в нужный аккаунт.
-3. **Отправьте любое сообщение** (например `ok`) — чтобы прошёл запрос, из которого берутся креды.
-4. Клик по иконке расширения → **«➕ Добавить в FreeDeepseekAPI»**.
-
-Для нескольких аккаунтов повторите из разных профилей/логинов браузера.
-
-Вспомогательные кнопки: «Собрать» (показать креды), «Копировать JSON»,
-«Скачать файл» (`deepseek-auth.json`) — на случай ручного импорта через дашборд.
diff --git a/chrome-extension/background.js b/chrome-extension/background.js
index 481c500..7b32d94 100644
--- a/chrome-extension/background.js
+++ b/chrome-extension/background.js
@@ -1,45 +1,97 @@
-// DeepSeek → FreeDeepseekAPI — перехват заголовков реального запроса.
-// token (Authorization: Bearer), cookie (все), hif (x-hif-*) берутся из
-// настоящего запроса к chat.deepseek.com/api/... — как в HAR/cURL.
-
-const WASM_DEFAULT = 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm';
-const KEY = 'deepseek_capture';
-
-// extraHeaders нужен Chrome для доступа к Cookie/Authorization; Firefox даёт их без него.
-const opts = ['requestHeaders'];
-try {
- if (chrome.webRequest.OnBeforeSendHeadersOptions &&
- chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS) {
- opts.push('extraHeaders');
- }
-} catch (e) { /* Firefox: опции нет — это нормально */ }
-
-chrome.webRequest.onBeforeSendHeaders.addListener(
- (details) => {
- const h = {};
- for (const x of (details.requestHeaders || [])) h[x.name.toLowerCase()] = x.value;
- const auth = h['authorization'] || '';
- const token = /^bearer\s+\S/i.test(auth) ? auth.replace(/^bearer\s+/i, '').trim() : '';
- const cookie = h['cookie'] || '';
- if (token && cookie) {
- const cap = {
- token,
- cookie,
- hif_dliq: h['x-hif-dliq'] || '',
- hif_leim: h['x-hif-leim'] || '',
- wasmUrl: WASM_DEFAULT,
- _t: Date.now(),
- };
- chrome.storage.local.set({ [KEY]: cap });
+// DeepSeek Auth Exporter — Background Service Worker
+// Reads cookies from Chrome, forwards content-script localStorage data.
+
+const STORAGE_KEY = 'deepseek_auth';
+
+// Read all needed cookies from chat.deepseek.com
+async function readCookies() {
+ const needed = ['token', 'ds_session_id', 'smidV2'];
+ const results = {};
+ for (const name of needed) {
+ const cookie = await new Promise((resolve) =>
+ chrome.cookies.get({ url: 'https://chat.deepseek.com', name }, resolve)
+ );
+ results[name] = cookie ? cookie.value : '';
+ }
+
+ // Build cookie header string
+ const parts = [];
+ if (results.ds_session_id) parts.push(`ds_session_id=${results.ds_session_id}`);
+ if (results.smidV2) parts.push(`smidV2=${results.smidV2}`);
+ results.cookie = parts.join('; ');
+
+ return results;
+}
+
+// Read localStorage values via content script injection
+async function readLocalStorage(tabId) {
+ const keys = ['hif_dliq', 'hif_leim'];
+ try {
+ const results = await new Promise((resolve, reject) => {
+ chrome.tabs.sendMessage(
+ tabId,
+ { action: 'readLocalStorage', keys },
+ (response) => {
+ if (chrome.runtime.lastError) reject(chrome.runtime.lastError.message);
+ else resolve(response.data || {});
}
- },
- { urls: ['https://chat.deepseek.com/api/*'] },
- opts
-);
-
-chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
- if (req.action === 'get') {
- chrome.storage.local.get(KEY, (r) => sendResponse({ success: true, cap: r[KEY] || null }));
- return true; // async
- }
+ );
+ });
+ return results;
+ } catch (e) {
+ return {};
+ }
+}
+
+// Find an open DeepSeek tab
+function findDeepSeekTab() {
+ return new Promise((resolve) => {
+ chrome.tabs.query(
+ { url: 'https://chat.deepseek.com/*' },
+ (tabs) => resolve(tabs.length > 0 ? tabs[0] : null)
+ );
+ });
+}
+
+async function collectAndStore(tabId) {
+ const cookies = await readCookies();
+ let ls = {};
+ if (tabId) ls = await readLocalStorage(tabId);
+
+ const merged = {
+ token: cookies.token || '',
+ ds_session_id: cookies.ds_session_id || '',
+ smidV2: cookies.smidV2 || '',
+ cookie: cookies.cookie || '',
+ hif_dliq: ls.hif_dliq || '',
+ hif_leim: ls.hif_leim || '',
+ _lastUpdated: new Date().toISOString(),
+ };
+
+ await new Promise((resolve) =>
+ chrome.storage.local.set({ [STORAGE_KEY]: merged }, resolve)
+ );
+ return merged;
+}
+
+// Message handler — popup requests
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ if (request.action === 'collect') {
+ findDeepSeekTab().then(async (tab) => {
+ if (!tab) {
+ sendResponse({ success: false, error: 'No DeepSeek tab open' });
+ return;
+ }
+ const auth = await collectAndStore(tab.id);
+ sendResponse({ success: true, auth });
+ });
+ return true; // keep channel open for async
+ }
+
+ if (request.action === 'export') {
+ chrome.storage.local.get(STORAGE_KEY, (result) => {
+ sendResponse({ success: true, auth: result[STORAGE_KEY] || {} });
+ });
+ return true;
+ }
});
diff --git a/chrome-extension/icons/icon128.png b/chrome-extension/icons/icon128.png
deleted file mode 100644
index ccce267..0000000
Binary files a/chrome-extension/icons/icon128.png and /dev/null differ
diff --git a/chrome-extension/icons/icon16.png b/chrome-extension/icons/icon16.png
deleted file mode 100644
index 14af773..0000000
Binary files a/chrome-extension/icons/icon16.png and /dev/null differ
diff --git a/chrome-extension/icons/icon48.png b/chrome-extension/icons/icon48.png
deleted file mode 100644
index dc6a5a2..0000000
Binary files a/chrome-extension/icons/icon48.png and /dev/null differ
diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json
index 5501740..8b51a78 100644
--- a/chrome-extension/manifest.json
+++ b/chrome-extension/manifest.json
@@ -1,30 +1,21 @@
{
"manifest_version": 3,
- "name": "DeepSeek → FreeDeepseekAPI",
- "version": "1.4",
- "description": "Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI одним кликом. Перехватывает заголовки запроса chat.deepseek.com (token + cookie + hif).",
- "author": "FreeDeepseekAPI",
- "browser_specific_settings": {
- "gecko": {
- "id": "freedeepseek-auth@forgetmeai",
- "strict_min_version": "121.0",
- "data_collection_permissions": { "required": ["none"] }
+ "name": "DeepSeek Auth Exporter",
+ "version": "1.0",
+ "description": "Extract DeepSeek Chat credentials from cookies + localStorage for use with the web API proxy",
+ "permissions": ["cookies", "storage", "downloads"],
+ "host_permissions": ["https://chat.deepseek.com/*"],
+ "content_scripts": [
+ {
+ "matches": ["https://chat.deepseek.com/*"],
+ "js": ["content.js"],
+ "run_at": "document_idle"
}
- },
- "permissions": ["webRequest", "storage"],
- "host_permissions": [
- "https://chat.deepseek.com/*",
- "http://localhost:9655/*",
- "http://127.0.0.1:9655/*"
],
"background": {
- "service_worker": "background.js",
- "scripts": ["background.js"]
+ "service_worker": "background.js"
},
"action": {
- "default_popup": "popup.html",
- "default_title": "DeepSeek → FreeDeepseekAPI",
- "default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }
- },
- "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }
+ "default_popup": "popup.html"
+ }
}
diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html
index 05f1be5..f5837d7 100644
--- a/chrome-extension/popup.html
+++ b/chrome-extension/popup.html
@@ -22,54 +22,18 @@
.json-display { background: #0d1117; border: 1px solid #333; border-radius: 4px; padding: 8px; font-size: 10px; font-family: 'Courier New', monospace; color: #7ee787; white-space: pre-wrap; word-break: break-all; max-height: 180px; overflow-y: auto; margin-bottom: 8px; }
.field label { display: block; font-size: 11px; color: #999; margin-bottom: 4px; }
.detail { font-size: 10px; color: #666; text-align: center; }
- .pool-section { margin-top: 14px; border-top: 1px solid #333; padding-top: 10px; }
- .pool-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
- .pool-header .title { font-size: 12px; color: #4fc3f7; font-weight: 600; }
- .link-btn { background: none; border: 1px solid #444; color: #9ccc65; border-radius: 5px; padding: 4px 8px; font-size: 11px; cursor: pointer; }
- .link-btn:hover { background: #ffffff10; }
- .link-btn:disabled { opacity: 0.5; cursor: default; }
- .pool-list { display: flex; flex-direction: column; gap: 4px; max-height: 220px; overflow-y: auto; }
- .pool-row { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #0d1117; border: 1px solid #2a2a3a; border-radius: 5px; font-size: 11px; }
- .pool-row .id { color: #888; font-family: 'Courier New', monospace; min-width: 40px; }
- .pool-row .email { flex: 1; color: #ccc; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
- .badge { padding: 2px 7px; border-radius: 10px; font-size: 10px; font-weight: 600; white-space: nowrap; }
- .badge.ok { background: #1b5e2033; color: #66bb6a; border: 1px solid #2e7d32; }
- .badge.invalid { background: #b71c1c33; color: #ef5350; border: 1px solid #c62828; }
- .badge.wait { background: #e6510033; color: #ff9800; border: 1px solid #e65100; }
- .badge.expired { background: #44444433; color: #999; border: 1px solid #555; }
- .badge.checking { background: #1565c033; color: #4fc3f7; border: 1px solid #1565c0; }
- .row-actions { display: flex; gap: 4px; }
- .acc-btn { background: #ffffff0d; border: 1px solid #444; color: #ddd; border-radius: 4px; padding: 3px 7px; font-size: 11px; cursor: pointer; }
- .acc-btn:hover { background: #ffffff1a; }
- .acc-btn.danger:hover { background: #b71c1c44; color: #ef5350; }
- .acc-btn.confirm { background: #b71c1c; color: #fff; border-color: #c62828; }
- .pool-empty { color: #666; font-size: 11px; text-align: center; padding: 10px; }
- .hdr { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
- .hdr h1 { margin: 0; }
- .srv-dot { width: 9px; height: 9px; border-radius: 50%; background: #666; flex: none; }
- .srv-dot.online { background: #66bb6a; box-shadow: 0 0 5px #2e7d32; }
- .srv-dot.offline { background: #ef5350; box-shadow: 0 0 5px #c62828; }
- .btn-row button:disabled { opacity: 0.45; cursor: default; }
- .pool-row .email { cursor: pointer; }
- .pool-row .email:hover { color: #4fc3f7; }
-
-
🔑 DeepSeek → FreeDeepseekAPI
-
-
-
Откройте chat.deepseek.com, отправьте любое сообщение, затем нажмите кнопку