Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ node_modules/
*.log
auth.json
deepseek-auth.json
# Aggregate account export holding live tokens/cookies — never commit.
deepseek-accounts.json
# Runtime-added auth files contain secrets — keep the dir, ignore its contents.
data/accounts/*.json
.chrome-profile-deepseek/
.chrome-for-testing-profile-deepseek/
.chrome-for-testing-profile-deepseek.stale-*/
Expand Down
43 changes: 15 additions & 28 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

const fs = require('fs');
const path = require('path');
const os = require('os');
const { solvePOW } = require('./lib/pow');

// Load from env or config file
const CONFIG = {
Expand Down Expand Up @@ -57,28 +59,8 @@ const BASE_HEADERS = {
'Cookie': CONFIG.cookie, 'Content-Type': 'application/json',
};

async function solvePOW(challenge) {
const resp = await fetch(CONFIG.wasmUrl);
const wasmBytes = await resp.arrayBuffer();
const mod = await WebAssembly.instantiate(wasmBytes, { wbg: {} });
const e = mod.instance.exports;
const encoder = new TextEncoder();
const prefix = challenge.salt + '_' + challenge.expire_at + '_';
const cBytes = encoder.encode(challenge.challenge);
const pBytes = encoder.encode(prefix);
const cP = e.__wbindgen_export_0(cBytes.length, 1) >>> 0;
const pP = e.__wbindgen_export_0(pBytes.length, 1) >>> 0;
new Uint8Array(e.memory.buffer, cP, cBytes.length).set(cBytes);
new Uint8Array(e.memory.buffer, pP, pBytes.length).set(pBytes);
const sp = e.__wbindgen_add_to_stack_pointer(-16);
e.wasm_solve(sp, cP, cBytes.length, pP, pBytes.length, challenge.difficulty);
const dv = new DataView(e.memory.buffer);
const code = dv.getInt32(sp, true);
const ans = dv.getFloat64(sp + 8, true);
e.__wbindgen_add_to_stack_pointer(16);
if (code === 0 || !Number.isFinite(ans) || ans <= 0) throw new Error('POW solve failed');
return Math.floor(ans);
}
// solvePOW() is imported from lib/pow (compiled-module cache + WASM-fetch timeout),
// shared with server.js.

async function askDeepSeek(prompt, onChunk) {
const chalResp = await fetch('https://chat.deepseek.com/api/v0/chat/create_pow_challenge', {
Expand All @@ -87,7 +69,7 @@ async function askDeepSeek(prompt, onChunk) {
});
const chalData = await chalResp.json();
const challenge = chalData.data.biz_data.challenge;
const answer = await solvePOW(challenge);
const answer = await solvePOW(challenge, CONFIG.wasmUrl);

const sessResp = await fetch('https://chat.deepseek.com/api/v0/chat_session/create', {
method: 'POST', headers: BASE_HEADERS, body: '{}'
Expand Down Expand Up @@ -145,23 +127,28 @@ async function askDeepSeek(prompt, onChunk) {
return fullResponse;
}

function readStdin() {
// fd 0 works on Windows too (unlike '/dev/stdin'); empty if no pipe.
try { return fs.readFileSync(0, 'utf8').trim(); } catch { return ''; }
}

async function main() {
const prompt = process.argv.slice(2).join(' ') || fs.readFileSync('/dev/stdin', 'utf8').trim();
const prompt = process.argv.slice(2).join(' ') || readStdin();
if (!prompt) {
console.error('Usage: node client.js "your prompt here"');
process.exit(1);
}

let fullText = '';
const response = await askDeepSeek(prompt, (chunk) => {
await askDeepSeek(prompt, (chunk) => {
process.stdout.write(chunk);
fullText += chunk;
});
process.stdout.write('\n');

const ts = Date.now();
fs.writeFileSync(`/tmp/deepseek_response_${ts}.txt`, fullText.trim());
console.error(`\n[*] Saved /tmp/deepseek_response_${ts}.txt`);
const outFile = path.join(os.tmpdir(), `deepseek_response_${Date.now()}.txt`);
fs.writeFileSync(outFile, fullText.trim());
console.error(`\n[*] Saved ${outFile}`);
}

main().catch(e => {
Expand Down
54 changes: 54 additions & 0 deletions lib/pow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict';
// Proof-of-work solver for the DeepSeek Web API.
//
// The PoW WASM module is immutable per URL, so we compile it ONCE and cache the
// compiled WebAssembly.Module; each solve only spins up a fresh instance (clean
// linear memory). This removes a network download + recompile from every single
// completion (and every retry), and gives the WASM fetch a hard timeout so a
// stalled CDN can never hang a request forever.
//
// Shared by server.js and client.js (previously duplicated verbatim).

const moduleCache = new Map(); // wasmUrl -> Promise<WebAssembly.Module>

async function loadModule(wasmUrl, { timeoutMs = 15000 } = {}) {
if (!wasmUrl) throw new Error('POW: missing wasmUrl');
if (!moduleCache.has(wasmUrl)) {
const p = (async () => {
const resp = await fetch(wasmUrl, { signal: AbortSignal.timeout(timeoutMs) });
if (!resp.ok) throw new Error(`POW: could not fetch WASM (HTTP ${resp.status})`);
const bytes = await resp.arrayBuffer();
return WebAssembly.compile(bytes);
})();
moduleCache.set(wasmUrl, p);
// Don't cache a failed download — let the next solve retry the fetch.
p.catch(() => moduleCache.delete(wasmUrl));
}
return moduleCache.get(wasmUrl);
}

async function solvePOW(challenge, wasmUrl, opts = {}) {
const module = await loadModule(wasmUrl, opts);
// Instantiating from a compiled Module returns the Instance directly
// (no { instance, module } wrapper, unlike the bytes form).
const instance = await WebAssembly.instantiate(module, { wbg: {} });
const e = instance.exports;
const encoder = new TextEncoder();
const prefix = challenge.salt + '_' + challenge.expire_at + '_';
const cBytes = encoder.encode(challenge.challenge);
const pBytes = encoder.encode(prefix);
const cP = e.__wbindgen_export_0(cBytes.length, 1) >>> 0;
const pP = e.__wbindgen_export_0(pBytes.length, 1) >>> 0;
new Uint8Array(e.memory.buffer, cP, cBytes.length).set(cBytes);
new Uint8Array(e.memory.buffer, pP, pBytes.length).set(pBytes);
const sp = e.__wbindgen_add_to_stack_pointer(-16);
e.wasm_solve(sp, cP, cBytes.length, pP, pBytes.length, challenge.difficulty);
const dv = new DataView(e.memory.buffer);
const code = dv.getInt32(sp, true);
const ans = dv.getFloat64(sp + 8, true);
e.__wbindgen_add_to_stack_pointer(16);
if (code === 0 || !Number.isFinite(ans) || ans <= 0) throw new Error('POW failed');
return Math.floor(ans);
}

module.exports = { solvePOW, _moduleCache: moduleCache };
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"doctor": "node scripts/doctor.js",
"deepseek:auth": "node scripts/deepseek_chrome_auth.js",
"client": "node client.js",
"test": "node --check server.js && node --check scripts/auth.js && node --check scripts/auth_import.js && node --check scripts/doctor.js && node --check scripts/deepseek_chrome_auth.js && node --check scripts/probe_deepseek_models.js && node --check client.js && node --check scripts/live_agentic_smoke_tests.mjs && node --test tests/unit.test.js",
"test": "node --check server.js && node --check lib/pow.js && node --check scripts/auth.js && node --check scripts/auth_import.js && node --check scripts/doctor.js && node --check scripts/deepseek_chrome_auth.js && node --check scripts/probe_deepseek_models.js && node --check client.js && node --check scripts/live_agentic_smoke_tests.mjs && node --test tests/unit.test.js",
"test:live": "node scripts/live_agentic_smoke_tests.mjs"
},
"keywords": [
Expand Down
47 changes: 18 additions & 29 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ const os = require('os');
const path = require('path');
const readline = require('readline');
const { spawnSync } = require('child_process');
const { solvePOW } = require('./lib/pow');

// Per-DeepSeek-request network timeout. Plain fetch() has NO default timeout, so a
// stalled upstream would hang the inbound request (and pin the account) forever.
const DS_FETCH_TIMEOUT_MS = Number(process.env.DEEPSEEK_FETCH_TIMEOUT_MS || 60000);
function dsFetch(url, options = {}, timeoutMs = DS_FETCH_TIMEOUT_MS) {
return fetch(url, { ...options, signal: options.signal || AbortSignal.timeout(timeoutMs) });
}

const SERVER_HOST = os.hostname(); // Dynamic hostname detection
const SERVER_PUBLIC_IP = (() => {
Expand Down Expand Up @@ -224,28 +232,8 @@ function getOrCreateAgentSession(agentId) {
return sessions.get(agentId);
}

async function solvePOW(challenge, config = DS_CONFIG) {
const resp = await fetch(config.wasmUrl);
const wasmBytes = await resp.arrayBuffer();
const mod = await WebAssembly.instantiate(wasmBytes, { wbg: {} });
const e = mod.instance.exports;
const encoder = new TextEncoder();
const prefix = challenge.salt + '_' + challenge.expire_at + '_';
const cBytes = encoder.encode(challenge.challenge);
const pBytes = encoder.encode(prefix);
const cP = e.__wbindgen_export_0(cBytes.length, 1) >>> 0;
const pP = e.__wbindgen_export_0(pBytes.length, 1) >>> 0;
new Uint8Array(e.memory.buffer, cP, cBytes.length).set(cBytes);
new Uint8Array(e.memory.buffer, pP, pBytes.length).set(pBytes);
const sp = e.__wbindgen_add_to_stack_pointer(-16);
e.wasm_solve(sp, cP, cBytes.length, pP, pBytes.length, challenge.difficulty);
const dv = new DataView(e.memory.buffer);
const code = dv.getInt32(sp, true);
const ans = dv.getFloat64(sp + 8, true);
e.__wbindgen_add_to_stack_pointer(16);
if (code === 0 || !Number.isFinite(ans) || ans <= 0) throw new Error('POW failed');
return Math.floor(ans);
}
// solvePOW() lives in lib/pow (compiled-module cache + WASM-fetch timeout),
// shared with client.js. Called as solvePOW(challenge, wasmUrl).

const MODEL_CONFIGS = {
// DeepSeek Web real model_type: default / UI name: "Быстрый".
Expand Down Expand Up @@ -421,7 +409,7 @@ async function askDeepSeekStream(prompt, agentId, model = 'deepseek-default') {
session.messageCount = 0;
}

const cr = await fetch('https://chat.deepseek.com/api/v0/chat/create_pow_challenge', {
const cr = await dsFetch('https://chat.deepseek.com/api/v0/chat/create_pow_challenge', {
method: 'POST', headers: dsHeaders,
body: JSON.stringify({ target_path: '/api/v0/chat/completion' })
});
Expand All @@ -437,10 +425,10 @@ async function askDeepSeekStream(prompt, agentId, model = 'deepseek-default') {
if (!challenge) {
throw new Error('DeepSeek PoW response has no data.biz_data.challenge. Auth may be expired, captcha may be required, or DeepSeek changed Web API. Run npm run doctor, then npm run auth.');
}
const answer = await solvePOW(challenge, account.config);
const answer = await solvePOW(challenge, account.config.wasmUrl);

if (!session.id) {
const sr = await fetch('https://chat.deepseek.com/api/v0/chat_session/create', {
const sr = await dsFetch('https://chat.deepseek.com/api/v0/chat_session/create', {
method: 'POST', headers: dsHeaders, body: '{}'
});
const { json: sessionData, text: sessionText } = await readDeepSeekJsonResponse(sr, 'session create', account);
Expand All @@ -463,7 +451,7 @@ async function askDeepSeekStream(prompt, agentId, model = 'deepseek-default') {
salt: challenge.salt, answer: answer,
signature: challenge.signature, target_path: '/api/v0/chat/completion'
})).toString('base64');
const resp = await fetch('https://chat.deepseek.com/api/v0/chat/completion', {
const resp = await dsFetch('https://chat.deepseek.com/api/v0/chat/completion', {
method: 'POST',
headers: { ...dsHeaders, 'X-DS-PoW-Response': powB64 },
body: JSON.stringify({
Expand All @@ -489,7 +477,7 @@ async function askDeepSeekStream(prompt, agentId, model = 'deepseek-default') {
session.createdAt = null;
session.messageCount = 0;

const sr2 = await fetch('https://chat.deepseek.com/api/v0/chat_session/create', {
const sr2 = await dsFetch('https://chat.deepseek.com/api/v0/chat_session/create', {
method: 'POST', headers: dsHeaders, body: '{}'
});
const { json: sessionData2, text: sessionText2 } = await readDeepSeekJsonResponse(sr2, 'session recreate', account);
Expand All @@ -508,7 +496,7 @@ async function askDeepSeekStream(prompt, agentId, model = 'deepseek-default') {
salt: challenge.salt, answer: answer,
signature: challenge.signature, target_path: '/api/v0/chat/completion'
})).toString('base64');
const resp2 = await fetch('https://chat.deepseek.com/api/v0/chat/completion', {
const resp2 = await dsFetch('https://chat.deepseek.com/api/v0/chat/completion', {
method: 'POST',
headers: { ...dsHeaders, 'X-DS-PoW-Response': newPowB64 },
body: JSON.stringify({
Expand Down Expand Up @@ -1311,8 +1299,9 @@ const server = http.createServer(async (req, res) => {
rebuildFragmentState();
};

const decoder = new TextDecoder(); // one instance: preserves multi-byte (Cyrillic/emoji) split across chunks
for await (const chunk of readable) {
buffer += new TextDecoder().decode(chunk, { stream: true });
buffer += decoder.decode(chunk, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
Expand Down