# deepseek-driver

**[中文](./README.md)** | English

A browser-side TypeScript SDK that wraps the full API of DeepSeek's web client (chat.deepseek.com), with ESM and UMD dual-format output.

## Features

- Full session management (create / list / rename / delete / pin)
- Streaming message send (SSE AsyncIterator with separate `thinking` / `response` / `search` deltas)
- Message editing, regeneration, continuation, and feedback
- File upload with automatic polling for processing status
- PoW challenge solving (reuses official WASM, runs in Web Worker to avoid blocking the main thread)
- hif heartbeat token auto-fetch
- **Token auto-discovery**: supports page injection scripts, browser extensions, and standalone frontend apps
- DOM helpers for page injection scenarios
- Zero runtime dependencies

## Installation

```bash
npm install deepseek-driver
```

After installation, copy the PoW WASM file to your project's static assets directory (`public/`):

```bash
cp node_modules/deepseek-driver/dist/sha3_wasm_bg.wasm public/
```

Then point to it via the `wasmUrl` option (see examples below).

## Token Acquisition

The SDK supports three usage scenarios with different token strategies:

### Scenario 1: Browser Extension (Chrome Extension)

Extensions cannot directly access page cookies, but can auto-discover the token via:

**Recommended: Intercept request headers (most reliable)**

Listen to DeepSeek API requests in the Background Service Worker to automatically extract and cache the token:

```js
// background.js
import { extractTokenFromRequestHeaders, cacheTokenToExtensionStorage } from 'deepseek-driver'

chrome.webRequest.onBeforeSendHeaders.addListener(
  (details) => {
    const token = extractTokenFromRequestHeaders(details.requestHeaders)
    if (token) cacheTokenToExtensionStorage(token)  // cache to chrome.storage.local
  },
  { urls: ['https://chat.deepseek.com/api/*'] },
  ['requestHeaders']
)
```

Then create a driver anywhere — it will auto-read the cached token from `chrome.storage`:

```ts
// popup.js or content_script.js
import { DeepSeekDriver } from 'deepseek-driver'

// skipApiDiscover: true — skip direct API calls (unavailable in Background),
// prefer chrome.storage / chrome.cookies
const driver = new DeepSeekDriver({
  wasmUrl: chrome.runtime.getURL('sha3_wasm_bg.wasm'),
  skipApiDiscover: true,
})

// token is auto-discovered from chrome.storage, no manual input needed
const session = await driver.createSession()
```

Declare the required permissions in `manifest.json`:

```json
{
  "permissions": ["webRequest", "storage", "cookies"],
  "host_permissions": ["https://chat.deepseek.com/*"]
}
```

### Scenario 2: Page Injection Script / Userscript (same-origin)

Scripts running within the `chat.deepseek.com` domain (content scripts, Tampermonkey scripts, etc.) automatically carry page cookies with fetch. The SDK calls `/api/v0/users/current` directly to retrieve the token — **no configuration needed**:

```ts
import { DeepSeekDriver } from 'deepseek-driver'

const driver = new DeepSeekDriver({
  wasmUrl: '/sha3_wasm_bg.wasm',
  // no token passed — SDK auto-discovers via API (fetch carries page cookies)
})

// token discovery happens automatically on the first API call
const session = await driver.createSession()

// stream a message
for await (const event of driver.sendMessage({
  chatSessionId: session.id,
  prompt: 'Hello!',
  searchEnabled: true,
})) {
  if (event.type === 'thinking') {
    process.stdout.write('[Thinking] ' + event.content)
  } else if (event.type === 'response') {
    process.stdout.write(event.content)
  } else if (event.type === 'done') {
    console.log('\nDone')
  }
}
```

### Scenario 3: Manual token (any environment)

```ts
const driver = new DeepSeekDriver({
  token: 'your-bearer-token',
  wasmUrl: '/sha3_wasm_bg.wasm',
})
```

### Convenience method: get the full reply at once

```ts
const { thinking, response } = await driver.chat(session.id, 'Explain quantum computing', {
  thinkingEnabled: true,
  searchEnabled: false,
})
console.log('Thinking:', thinking)
console.log('Response:', response)
```

## Configuration

```ts
interface DeepSeekDriverOptions {
  token?: string            // Bearer token; auto-discovered if omitted
  wasmUrl?: string          // PoW WASM file URL, default: './sha3_wasm_bg.wasm'
  baseUrl?: string          // API base URL, default: 'https://chat.deepseek.com'
  skipApiDiscover?: boolean // Skip API discovery strategy; set true in extension Background
}
```

## Token Auto-Discovery API

Can also be called independently:

```ts
import { autoDiscover, isExtensionContext, isDeepSeekPage } from 'deepseek-driver'

console.log('Is extension context:', isExtensionContext())
console.log('Is DeepSeek page:', isDeepSeekPage())

// auto-discover by priority: storage → cookies → API → document.cookie → window globals
const creds = await autoDiscover()
if (creds) {
  console.log('Token found, source:', creds.source)
  console.log('Token:', creds.token)
}
```

Discovery priority (`source` field):

| source | Description | Scenario |
|--------|-------------|----------|
| `extension-storage` | chrome.storage cache | Extension (fastest, no request) |
| `extension-cookie` | chrome.cookies API | Extension Background |
| `api` | /api/v0/users/current | Same-origin page / Content Script |
| `cookie` | document.cookie parsing | Same-origin page |
| `page-global` | window global variables | Special injection environments |
| `manual` | Manually provided | Any environment |

## PoW WASM File

PoW solving requires DeepSeek's official WASM file (`sha3_wasm_bg.wasm`). The file is bundled in this project's root. Host it at a path accessible from the same origin and configure it via `wasmUrl`.

If WASM loading fails, the SDK automatically falls back to a pure-JS implementation (slightly slower).

## API Reference

### Authentication

| Method | Description |
|--------|-------------|
| `getCurrentUser()` | Get current logged-in user; auto-caches token |
| `setToken(token)` | Manually set token |
| `getToken()` | Get currently cached token |

### Session Management

| Method | Description |
|--------|-------------|
| `createSession()` | Create a new session |
| `listSessions(cursor?, count?)` | List sessions (paginated) |
| `listAllSessions()` | List all sessions (auto-paginate) |
| `renameSession(id, title)` | Rename a session |
| `deleteSession(id)` | Delete a session |
| `deleteAllSessions()` | Delete all sessions |
| `pinSession(id, pinned)` | Pin / unpin a session |
| `getHistoryMessages(id)` | Get history messages |

### Messaging

| Method | Description |
|--------|-------------|
| `sendMessage(params)` | Send a message, returns `AsyncGenerator<StreamEvent>` |
| `editMessage(params)` | Edit a message, returns `AsyncGenerator<StreamEvent>` |
| `regenerate(params)` | Regenerate, returns `AsyncGenerator<StreamEvent>` |
| `continueMessage(params)` | Continue generation |
| `messageFeedback(sessionId, messageId, type)` | Message feedback (`'GOOD'`/`'BAD'`/`null`) |
| `chat(sessionId, prompt, opts?)` | Convenience method, returns full `{ thinking, response }` |

#### SendMessageParams

```ts
{
  chatSessionId: string
  prompt: string
  parentMessageId?: number | null    // pass previous message ID for continuation
  modelType?: 'default' | 'expert' | 'vision' | null
  thinkingEnabled?: boolean          // deep thinking mode, default false
  searchEnabled?: boolean            // web search, default true
  refFileIds?: string[]              // uploaded file ID list
  preempt?: boolean
}
```

#### StreamEvent Types

| type | Description | Fields |
|------|-------------|--------|
| `ready` | Stream ready | `requestMessageId`, `responseMessageId`, `modelType` |
| `thinking` | Thinking delta | `content` |
| `response` | Response delta | `content` |
| `search` | Search info | `queries?`, `results?` |
| `status` | Status change | `status` |
| `title` | Session title generated | `title` |
| `done` | Stream ended | `autoResume` |

### File Upload

```ts
const file = new File(['content'], 'test.txt', { type: 'text/plain' })
const uploaded = await driver.uploadFile(file)
// uploaded.id can be used in refFileIds

const result = await driver.chat(session.id, 'Analyze this file', {
  refFileIds: [uploaded.id],
})
```

### DOM Helpers (page injection scenario)

```ts
import {
  clickNewChat,
  toggleThinking,
  toggleSearch,
  setInputText,
  clickSend,
  getMessages,
  getSessionList,
  getCurrentSessionId,
} from 'deepseek-driver'

clickNewChat()                   // click "New Conversation"
toggleThinking(true)             // enable deep thinking
setInputText('Hello!')           // set input text
clickSend()                      // click send

const msgs = getMessages()       // read page message list
const sessions = getSessionList() // read sidebar session list
```

## Test Page

The project includes a local interactive test page for verifying SDK API functionality.

### Setup

```bash
cp test/.env.example test/.env
```

Edit `test/.env` and fill in your DeepSeek Bearer Token:

```
VITE_DEEPSEEK_TOKEN=your_bearer_token_here
```

To get a token:
1. Log in to https://chat.deepseek.com
2. Open DevTools → Network
3. Send a message, find the `Authorization: Bearer <token>` request header
4. Copy the token value after `Bearer`

### Run

```bash
npm run test:serve
```

Browser opens automatically at http://localhost:5174

## Changelog

### 0.1.0

- Initial release
- Full session / message / file API
- PoW WASM + Web Worker solver
- hif heartbeat token auto-fetch
- SSE stream parser (SET/APPEND/BATCH patches)
- DOM helper module
- Token auto-discovery module
- Browser extension test suite (`test-extension/`)

## Acknowledgements & Disclaimer

This project is based on reverse engineering and research of [DeepSeek](https://www.deepseek.com/)'s web client (chat.deepseek.com).

> **⚠️ Disclaimer**
>
> - This is an **unofficial** community-maintained project and has no affiliation with DeepSeek.
> - This project is intended for learning, research, and personal use only. Do not use it in any way that violates DeepSeek's Terms of Service.
> - DeepSeek may update its web client implementation at any time; continued availability of this project is not guaranteed.
> - Any consequences arising from the use of this project are solely the responsibility of the user.

Thanks to the DeepSeek team for building an excellent AI product.

## License

MIT
