deepseek-local-api 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/README.md +138 -0
- package/bin/cli.js +66 -0
- package/package.json +49 -0
- package/src/client/ChatSession.js +187 -0
- package/src/client/DeepseekClient.js +134 -0
- package/src/config/constants.js +29 -0
- package/src/config/headers.js +45 -0
- package/src/index.js +307 -0
- package/src/services/PowService.js +66 -0
- package/src/services/WasmService.js +117 -0
- package/src/services/autocomplete.js +48 -0
- package/src/services/server.js +289 -0
- package/src/utils/encoding.js +94 -0
- package/src/utils/memory.js +65 -0
- package/wasm/sha3_wasm_bg.7b9ca65ddd.wasm +0 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# DeepSeek Local API & CLI Client (`deepseek-local-api`)
|
|
2
|
+
|
|
3
|
+
Convert DeepSeek Web Chat into a zero-overhead, OpenAI-compatible local API server and interactive terminal CLI.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> ### ⚠️ Capabilities & Limitations Notice
|
|
9
|
+
> - **Text & Reasoning Generation Only**: This tool is designed strictly for high-quality **text generation and reasoning** (DeepSeek Chat & DeepSeek Reasoner R1).
|
|
10
|
+
> - **No Native Tool/Function Calling**: The DeepSeek Web API does not support native OpenAI-style JSON schema tool calling. If you are using this with an agent harness (such as **OpenCode**, **π / Pi**, **Hermes**, **Cursor**, etc.) that requires tool execution, configure your harness to use **prompt-based text tool calling** (where the model outputs tool calls in text blocks such as JSON/XML for the harness to parse).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ⚡ Quick Start
|
|
15
|
+
|
|
16
|
+
### 1. Set Your Token
|
|
17
|
+
Obtain your token from [chat.deepseek.com](https://chat.deepseek.com):
|
|
18
|
+
1. Sign in to [chat.deepseek.com](https://chat.deepseek.com).
|
|
19
|
+
2. Open DevTools (**F12**), go to **Application** > **Local Storage**.
|
|
20
|
+
3. Copy the value of `userToken`.
|
|
21
|
+
4. Create a `.env` file in your directory:
|
|
22
|
+
```env
|
|
23
|
+
DEEPSEEK_TOKEN="your_token_here"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Run Directly with npx
|
|
27
|
+
```bash
|
|
28
|
+
# Run interactive chat
|
|
29
|
+
npx deepseek-local-api
|
|
30
|
+
|
|
31
|
+
# Or start the OpenAI-compatible local server
|
|
32
|
+
npx deepseek-local-api --server 3000
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Or Install Globally
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g deepseek-local-api
|
|
38
|
+
|
|
39
|
+
# Now available anywhere:
|
|
40
|
+
deepseek-local-api
|
|
41
|
+
# Or via short alias:
|
|
42
|
+
deepseek
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🛠️ Usage Modes
|
|
48
|
+
|
|
49
|
+
### Mode 1: Interactive Terminal Chat
|
|
50
|
+
Run without arguments to start an interactive multi-turn conversation:
|
|
51
|
+
```bash
|
|
52
|
+
deepseek-local-api
|
|
53
|
+
```
|
|
54
|
+
**Interactive Features:**
|
|
55
|
+
- **Tab Autocomplete**: Type `/` and press `Tab` to see and autocomplete commands.
|
|
56
|
+
- **Arrow-Key Session Picker**: Use `↑` / `↓` to select from past saved sessions.
|
|
57
|
+
- **Live In-Chat Commands:**
|
|
58
|
+
- `/help`: Display all available commands.
|
|
59
|
+
- `/thinking`: Toggle reasoning/thinking mode on or off.
|
|
60
|
+
- `/search`: Toggle web search mode on or off.
|
|
61
|
+
- `/server [port]`: Launch the local OpenAI-compatible server on the fly.
|
|
62
|
+
- `/new`: Start a fresh session.
|
|
63
|
+
- `/id`: Print current session ID and direct web URL.
|
|
64
|
+
- `/exit`: Quit session.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Mode 2: Single-Turn CLI Prompt
|
|
69
|
+
Send a quick prompt and stream the result directly to your terminal:
|
|
70
|
+
```bash
|
|
71
|
+
deepseek-local-api "Explain Dijkstra algorithm in simple terms"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### Mode 3: OpenAI-Compatible Local Server
|
|
77
|
+
Run a local API server compatible with any OpenAI API client:
|
|
78
|
+
```bash
|
|
79
|
+
# Start on localhost (127.0.0.1:3000)
|
|
80
|
+
deepseek-local-api --server 3000
|
|
81
|
+
|
|
82
|
+
# Expose to your local network / LAN (0.0.0.0:8080)
|
|
83
|
+
deepseek-local-api --server 8080 --network
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Supported Endpoints:
|
|
87
|
+
- `POST http://localhost:3000/v1/chat/completions`
|
|
88
|
+
- Supports standard OpenAI payload (`model`, `messages`, `stream`).
|
|
89
|
+
- Supports both **Streaming** (`stream: true` via SSE) and **Non-Streaming** (`stream: false`).
|
|
90
|
+
- Includes `reasoning_content` delta for thinking process.
|
|
91
|
+
- Full **CORS** enabled.
|
|
92
|
+
- `GET http://localhost:3000/v1/models`
|
|
93
|
+
- Returns `deepseek-chat` and `deepseek-reasoner`.
|
|
94
|
+
- `GET http://localhost:3000/health`
|
|
95
|
+
- Health check endpoint.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🤖 Harness Integration Guide (OpenCode, π / Pi, Hermes, Cursor, Continue)
|
|
100
|
+
|
|
101
|
+
You can plug this server into any AI harness or coding tool by pointing it to your local endpoint:
|
|
102
|
+
|
|
103
|
+
### Configuration Settings
|
|
104
|
+
| Setting | Value |
|
|
105
|
+
| :--- | :--- |
|
|
106
|
+
| **Base URL** | `http://localhost:3000/v1` |
|
|
107
|
+
| **API Key** | `dummy-token` *(any non-empty string)* |
|
|
108
|
+
| **Models** | `deepseek-chat` or `deepseek-reasoner` |
|
|
109
|
+
| **Streaming** | Enabled (`true`) |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Recommended System Prompt for Harnesses & Agents
|
|
114
|
+
|
|
115
|
+
Copy and paste this system prompt into your harness configuration (OpenCode, Pi, Hermes, etc.) to ensure optimal performance and prompt-based tool parsing:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
You are an AI programming assistant powered by DeepSeek via a local OpenAI-compatible API provider.
|
|
119
|
+
|
|
120
|
+
CRITICAL OPERATIONAL RULES:
|
|
121
|
+
1. Native API-level function/tool calling is NOT supported by this provider.
|
|
122
|
+
2. All tool usage must follow a strict text-based format. When you need to execute an action (reading a file, running a shell command, searching, etc.), output your request in a structured code block:
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"action": "<tool_name>",
|
|
126
|
+
"parameters": {
|
|
127
|
+
"<param_key>": "<param_value>"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
3. When reasoning is required, clearly structure your thinking before providing the final answer.
|
|
132
|
+
4. Keep answers concise, actionable, and focused on code execution.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 📄 License
|
|
138
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// Attempt to load .env from current working directory, then fallback to package root
|
|
7
|
+
const cwdEnv = path.resolve(process.cwd(), '.env');
|
|
8
|
+
const pkgEnv = path.join(__dirname, '..', '.env');
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(cwdEnv)) {
|
|
11
|
+
require('dotenv').config({ path: cwdEnv });
|
|
12
|
+
} else if (fs.existsSync(pkgEnv)) {
|
|
13
|
+
require('dotenv').config({ path: pkgEnv });
|
|
14
|
+
} else {
|
|
15
|
+
require('dotenv').config();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
// Handle --help / -h
|
|
21
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
22
|
+
console.log(`
|
|
23
|
+
\x1b[1;36mDeepSeek Local API & CLI Client\x1b[0m
|
|
24
|
+
|
|
25
|
+
\x1b[1mUSAGE:\x1b[0m
|
|
26
|
+
deepseek-local-api [options] [message]
|
|
27
|
+
deepseek [options] [message]
|
|
28
|
+
|
|
29
|
+
\x1b[1mMODES:\x1b[0m
|
|
30
|
+
\x1b[32mInteractive Chat:\x1b[0m
|
|
31
|
+
deepseek-local-api
|
|
32
|
+
(Launches multi-turn interactive session with Tab autocomplete)
|
|
33
|
+
|
|
34
|
+
\x1b[32mSingle Prompt:\x1b[0m
|
|
35
|
+
deepseek-local-api "Your prompt message"
|
|
36
|
+
deepseek-local-api "Your prompt" [session-id]
|
|
37
|
+
|
|
38
|
+
\x1b[32mOpenAI-Compatible Local API Server:\x1b[0m
|
|
39
|
+
deepseek-local-api --server 3000
|
|
40
|
+
deepseek-local-api --server 8080 --network
|
|
41
|
+
|
|
42
|
+
\x1b[1mSERVER OPTIONS:\x1b[0m
|
|
43
|
+
--server, -s [port] Start OpenAI-compatible HTTP server (default port: 3000)
|
|
44
|
+
--port, -p [port] Alternative port flag
|
|
45
|
+
--network, -n, --public Enable local network / LAN access (binds to 0.0.0.0)
|
|
46
|
+
|
|
47
|
+
\x1b[1mGENERAL OPTIONS:\x1b[0m
|
|
48
|
+
--help, -h Show this help screen
|
|
49
|
+
--version, -v Show package version
|
|
50
|
+
|
|
51
|
+
\x1b[1mENVIRONMENT VARIABLES:\x1b[0m
|
|
52
|
+
DEEPSEEK_TOKEN Your DeepSeek auth token (from chat.deepseek.com)
|
|
53
|
+
DEEPSEEK_COOKIE Optional WAF / session cookie
|
|
54
|
+
`);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle --version / -v
|
|
59
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
60
|
+
const pkg = require('../package.json');
|
|
61
|
+
console.log(`deepseek-local-api v${pkg.version}`);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Forward execution to main index runner
|
|
66
|
+
require('../src/index.js');
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deepseek-local-api",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Convert DeepSeek Web Chat to an OpenAI-compatible local API and interactive CLI for any harness or agent",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"deepseek-local-api": "bin/cli.js",
|
|
8
|
+
"deepseek": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node -r dotenv/config src/index.js",
|
|
12
|
+
"server": "node -r dotenv/config src/index.js --server 3000",
|
|
13
|
+
"test": "echo \"No tests specified\" && exit 0"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"src",
|
|
18
|
+
"wasm",
|
|
19
|
+
".env.example",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"deepseek",
|
|
24
|
+
"chat",
|
|
25
|
+
"api",
|
|
26
|
+
"openai-compatible",
|
|
27
|
+
"llm",
|
|
28
|
+
"harness",
|
|
29
|
+
"reasoner",
|
|
30
|
+
"client",
|
|
31
|
+
"cli"
|
|
32
|
+
],
|
|
33
|
+
"author": "m-elramsesy",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/m-elramsesy/deepseek-local-api.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/m-elramsesy/deepseek-local-api/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/m-elramsesy/deepseek-local-api#readme",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"dotenv": "^16.0.3"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=14.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { API_ENDPOINTS } = require('../config/constants');
|
|
4
|
+
const { HeadersBuilder } = require('../config/headers');
|
|
5
|
+
|
|
6
|
+
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
|
|
7
|
+
const SESSIONS_FILE = path.join(DATA_DIR, 'sessions.json');
|
|
8
|
+
|
|
9
|
+
class ChatSession {
|
|
10
|
+
constructor(sessionId = null, parentMessageId = null, title = null) {
|
|
11
|
+
this.sessionId = ChatSession.parseSessionId(sessionId);
|
|
12
|
+
this.currentMessageId = 0;
|
|
13
|
+
this.parentMessageId = parentMessageId;
|
|
14
|
+
this.title = title || 'New Chat';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse session ID from either a raw UUID or a full DeepSeek web URL
|
|
19
|
+
* e.g. https://chat.deepseek.com/a/chat/s/48893d3d-cb96-4e8c-a7ef-344038a36e15
|
|
20
|
+
*/
|
|
21
|
+
static parseSessionId(input) {
|
|
22
|
+
if (!input || typeof input !== 'string') return null;
|
|
23
|
+
const trimmed = input.trim();
|
|
24
|
+
const uuidMatch = trimmed.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
|
|
25
|
+
return uuidMatch ? uuidMatch[0] : trimmed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get direct web URL for this session
|
|
30
|
+
*/
|
|
31
|
+
getWebUrl() {
|
|
32
|
+
return this.sessionId ? API_ENDPOINTS.WEB_SESSION_URL(this.sessionId) : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a brand new session on DeepSeek
|
|
37
|
+
*/
|
|
38
|
+
static async create(token, parentMessageId = null) {
|
|
39
|
+
const headers = HeadersBuilder.getAuthHeaders(token);
|
|
40
|
+
const payload = { character_id: null };
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(API_ENDPOINTS.CREATE_SESSION, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: headers,
|
|
46
|
+
body: JSON.stringify(payload)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const errorData = await response.text();
|
|
51
|
+
console.error("Session creation error:", errorData);
|
|
52
|
+
throw new Error(`Session creation failed: ${response.status}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
const sessionId = data?.data?.biz_data?.id || data?.biz_data?.id;
|
|
57
|
+
if (!sessionId) {
|
|
58
|
+
throw new Error("Could not find session ID in response");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const session = new ChatSession(sessionId, parentMessageId);
|
|
62
|
+
session.save();
|
|
63
|
+
return session;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Error in createSession:", error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resume an existing session by ID or Web URL.
|
|
72
|
+
* Attempts to fetch message history from DeepSeek to determine the latest parent_message_id.
|
|
73
|
+
*/
|
|
74
|
+
static async resume(token, sessionIdOrUrl) {
|
|
75
|
+
const sessionId = this.parseSessionId(sessionIdOrUrl);
|
|
76
|
+
if (!sessionId) {
|
|
77
|
+
throw new Error("Invalid session ID or URL provided");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check local saved session first
|
|
81
|
+
const saved = this.getSavedSession(sessionId);
|
|
82
|
+
let parentMessageId = saved ? saved.parentMessageId : null;
|
|
83
|
+
let title = saved ? saved.title : 'Resumed Chat';
|
|
84
|
+
|
|
85
|
+
// Try to query DeepSeek for latest messages in this session
|
|
86
|
+
try {
|
|
87
|
+
const headers = HeadersBuilder.getAuthHeaders(token);
|
|
88
|
+
const historyUrl = `${API_ENDPOINTS.HISTORY_MESSAGES}?chat_session_id=${sessionId}`;
|
|
89
|
+
const res = await fetch(historyUrl, {
|
|
90
|
+
method: 'GET',
|
|
91
|
+
headers: headers
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (res.ok) {
|
|
95
|
+
const data = await res.json();
|
|
96
|
+
const messages = data?.data?.biz_data?.chat_messages || data?.biz_data?.chat_messages || [];
|
|
97
|
+
if (messages.length > 0) {
|
|
98
|
+
const lastMsg = messages[messages.length - 1];
|
|
99
|
+
parentMessageId = lastMsg.message_id || lastMsg.id || parentMessageId;
|
|
100
|
+
if (!saved?.title && messages[0]?.content) {
|
|
101
|
+
title = messages[0].content.slice(0, 35);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// Fallback to local state if offline or API call fails
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const session = new ChatSession(sessionId, parentMessageId, title);
|
|
110
|
+
session.save();
|
|
111
|
+
return session;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getCurrentMessageId() {
|
|
115
|
+
return this.currentMessageId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
incrementMessageId() {
|
|
119
|
+
this.currentMessageId += 1;
|
|
120
|
+
return this.currentMessageId;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getParentMessageId() {
|
|
124
|
+
return this.parentMessageId === null ? null : this.parentMessageId;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setParentMessageId(id) {
|
|
128
|
+
this.parentMessageId = id;
|
|
129
|
+
this.save();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setTitle(title) {
|
|
133
|
+
this.title = title;
|
|
134
|
+
this.save();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getId() {
|
|
138
|
+
return this.sessionId;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Lightweight Local Persistence ---
|
|
142
|
+
|
|
143
|
+
static ensureDataDir() {
|
|
144
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
145
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static loadAllSavedSessions() {
|
|
150
|
+
this.ensureDataDir();
|
|
151
|
+
if (!fs.existsSync(SESSIONS_FILE)) return [];
|
|
152
|
+
try {
|
|
153
|
+
return JSON.parse(fs.readFileSync(SESSIONS_FILE, 'utf8'));
|
|
154
|
+
} catch (e) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static getSavedSession(id) {
|
|
160
|
+
const sessions = this.loadAllSavedSessions();
|
|
161
|
+
return sessions.find(s => s.id === id) || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
save() {
|
|
165
|
+
if (!this.sessionId) return;
|
|
166
|
+
ChatSession.ensureDataDir();
|
|
167
|
+
const sessions = ChatSession.loadAllSavedSessions();
|
|
168
|
+
const idx = sessions.findIndex(s => s.id === this.sessionId);
|
|
169
|
+
const entry = {
|
|
170
|
+
id: this.sessionId,
|
|
171
|
+
webUrl: this.getWebUrl(),
|
|
172
|
+
title: this.title,
|
|
173
|
+
parentMessageId: this.parentMessageId,
|
|
174
|
+
updatedAt: new Date().toISOString()
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (idx >= 0) {
|
|
178
|
+
sessions[idx] = { ...sessions[idx], ...entry };
|
|
179
|
+
} else {
|
|
180
|
+
sessions.unshift(entry);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2), 'utf8');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = ChatSession;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const { API_ENDPOINTS, CHAT_CONFIG } = require('../config/constants');
|
|
2
|
+
const { HeadersBuilder } = require('../config/headers');
|
|
3
|
+
const PowService = require('../services/PowService');
|
|
4
|
+
const ChatSession = require('./ChatSession');
|
|
5
|
+
|
|
6
|
+
class DeepseekClient {
|
|
7
|
+
constructor(token, parentMessageId = null) {
|
|
8
|
+
this.token = token;
|
|
9
|
+
this.powService = new PowService();
|
|
10
|
+
this.currentSession = null;
|
|
11
|
+
this.parentMessageId = parentMessageId;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async initialize() {
|
|
15
|
+
await this.powService.initialize();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async createSession() {
|
|
19
|
+
this.currentSession = await ChatSession.create(this.token, this.parentMessageId);
|
|
20
|
+
return this.currentSession;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async resumeSession(sessionIdOrUrl) {
|
|
24
|
+
this.currentSession = await ChatSession.resume(this.token, sessionIdOrUrl);
|
|
25
|
+
return this.currentSession;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async sendMessage(message, session = null, { thinking_enabled = false, search_enabled = false } = {}) {
|
|
29
|
+
if (!session && !this.currentSession) {
|
|
30
|
+
session = await this.createSession();
|
|
31
|
+
}
|
|
32
|
+
const chatSession = session || this.currentSession;
|
|
33
|
+
|
|
34
|
+
if (chatSession.title === 'New Chat') {
|
|
35
|
+
chatSession.setTitle(message.slice(0, 35));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Increment message ID for human message
|
|
39
|
+
chatSession.incrementMessageId();
|
|
40
|
+
|
|
41
|
+
const powDataB64 = await this.powService.getPowResponse(
|
|
42
|
+
this.token,
|
|
43
|
+
API_ENDPOINTS.TARGET_PATH
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const headers = HeadersBuilder.getChatHeaders(this.token, powDataB64);
|
|
47
|
+
|
|
48
|
+
const payload = {
|
|
49
|
+
prompt: message,
|
|
50
|
+
model: CHAT_CONFIG.DEFAULT_MODEL,
|
|
51
|
+
stream: true,
|
|
52
|
+
temperature: CHAT_CONFIG.DEFAULT_TEMPERATURE,
|
|
53
|
+
max_tokens: CHAT_CONFIG.DEFAULT_MAX_TOKENS,
|
|
54
|
+
ref_file_ids: [],
|
|
55
|
+
thinking_enabled,
|
|
56
|
+
search_enabled,
|
|
57
|
+
chat_session_id: chatSession.getId(),
|
|
58
|
+
parent_message_id: chatSession.getParentMessageId()
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(API_ENDPOINTS.COMPLETION, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: headers,
|
|
65
|
+
body: JSON.stringify(payload)
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const errorData = await response.text();
|
|
70
|
+
console.error("Chat completion error:", errorData);
|
|
71
|
+
throw new Error(`Chat completion failed: ${response.status}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Increment message ID for AI response
|
|
75
|
+
chatSession.incrementMessageId();
|
|
76
|
+
|
|
77
|
+
return response;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Error in sendMessage:", error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async *streamResponse(response, session = null) {
|
|
85
|
+
const chatSession = session || this.currentSession;
|
|
86
|
+
const reader = response.body.getReader();
|
|
87
|
+
const decoder = new TextDecoder();
|
|
88
|
+
let buffer = '';
|
|
89
|
+
let currentPath = null;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
while (true) {
|
|
93
|
+
const { done, value } = await reader.read();
|
|
94
|
+
if (done) break;
|
|
95
|
+
|
|
96
|
+
buffer += decoder.decode(value, { stream: true });
|
|
97
|
+
const lines = buffer.split('\n');
|
|
98
|
+
buffer = lines.pop();
|
|
99
|
+
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (!trimmed || trimmed.startsWith('event:')) continue;
|
|
103
|
+
|
|
104
|
+
if (trimmed.startsWith('data:')) {
|
|
105
|
+
const dataStr = trimmed.slice(5).trim();
|
|
106
|
+
if (!dataStr || dataStr === '{}') continue;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(dataStr);
|
|
110
|
+
|
|
111
|
+
// Capture and save the response_message_id for threading next message
|
|
112
|
+
if (parsed.response_message_id && chatSession) {
|
|
113
|
+
chatSession.setParentMessageId(parsed.response_message_id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (parsed.p) currentPath = parsed.p;
|
|
117
|
+
if (parsed.v !== undefined && typeof parsed.v === 'string') {
|
|
118
|
+
const isThinking = currentPath === 'response/thinking_content';
|
|
119
|
+
yield {
|
|
120
|
+
type: isThinking ? 'thinking' : 'content',
|
|
121
|
+
text: parsed.v
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
reader.releaseLock();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = DeepseekClient;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const DEEPSEEK_HOST = "chat.deepseek.com";
|
|
2
|
+
|
|
3
|
+
const API_ENDPOINTS = {
|
|
4
|
+
CREATE_POW: `https://${DEEPSEEK_HOST}/api/v0/chat/create_pow_challenge`,
|
|
5
|
+
COMPLETION: `https://${DEEPSEEK_HOST}/api/v0/chat/completion`,
|
|
6
|
+
CREATE_SESSION: `https://${DEEPSEEK_HOST}/api/v0/chat_session/create`,
|
|
7
|
+
FETCH_SESSIONS: `https://${DEEPSEEK_HOST}/api/v0/chat_session/fetch_page`,
|
|
8
|
+
HISTORY_MESSAGES: `https://${DEEPSEEK_HOST}/api/v0/chat/history_messages`,
|
|
9
|
+
TARGET_PATH: '/api/v0/chat/completion',
|
|
10
|
+
WEB_SESSION_URL: (id) => `https://${DEEPSEEK_HOST}/a/chat/s/${id}`
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const WASM_CONFIG = {
|
|
14
|
+
DEFAULT_PATH: "./wasm/sha3_wasm_bg.7b9ca65ddd.wasm",
|
|
15
|
+
SUPPORTED_ALGORITHMS: ["DeepSeekHashV1"]
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CHAT_CONFIG = {
|
|
19
|
+
DEFAULT_MODEL: "deepseek-chat",
|
|
20
|
+
DEFAULT_TEMPERATURE: 0.7,
|
|
21
|
+
DEFAULT_MAX_TOKENS: 4096
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
DEEPSEEK_HOST,
|
|
26
|
+
API_ENDPOINTS,
|
|
27
|
+
WASM_CONFIG,
|
|
28
|
+
CHAT_CONFIG
|
|
29
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const BASE_HEADERS = {
|
|
2
|
+
"Host": "chat.deepseek.com",
|
|
3
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36",
|
|
4
|
+
"Accept": "*/*",
|
|
5
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
6
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
"Origin": "https://chat.deepseek.com",
|
|
9
|
+
"Referer": "https://chat.deepseek.com/",
|
|
10
|
+
"Sec-Ch-Ua": "\"Not A(Brand\";v=\"8\", \"Chromium\";v=\"152\", \"Google Chrome\";v=\"152\"",
|
|
11
|
+
"Sec-Ch-Ua-Mobile": "?0",
|
|
12
|
+
"Sec-Ch-Ua-Platform": "\"Windows\"",
|
|
13
|
+
"Sec-Fetch-Dest": "empty",
|
|
14
|
+
"Sec-Fetch-Mode": "cors",
|
|
15
|
+
"Sec-Fetch-Site": "same-origin",
|
|
16
|
+
"x-app-version": "20241129.1",
|
|
17
|
+
"x-client-locale": "en_US",
|
|
18
|
+
"x-client-platform": "web",
|
|
19
|
+
"x-client-version": "1.0.0-always"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
class HeadersBuilder {
|
|
23
|
+
static getAuthHeaders(token, cookie = process.env.DEEPSEEK_COOKIE) {
|
|
24
|
+
const headers = {
|
|
25
|
+
...BASE_HEADERS,
|
|
26
|
+
"Authorization": `Bearer ${token}`
|
|
27
|
+
};
|
|
28
|
+
if (cookie) {
|
|
29
|
+
headers["Cookie"] = cookie;
|
|
30
|
+
}
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static getChatHeaders(token, powDataB64, cookie = process.env.DEEPSEEK_COOKIE) {
|
|
35
|
+
return {
|
|
36
|
+
...this.getAuthHeaders(token, cookie),
|
|
37
|
+
"x-ds-pow-response": powDataB64
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
BASE_HEADERS,
|
|
44
|
+
HeadersBuilder
|
|
45
|
+
};
|