sd / src /chat.js
pigracing's picture
Upload 22 files
bd27f3c verified
Raw History Blame Contribute Delete
6.75 kB
import { setRequestToken, reportTokenError, reportTokenSuccess } from './auth.js';
import { solvePowChallengeWithToken } from './pow.js';
import { getSession } from './session.js';
import { streamHeaders, proxiedFetch } from './headers.js';
import { enqueueRequest, dispatchQueued } from './queue.js';
const BASE_URL = 'https://chat.deepseek.com';
export async function completion({ modelType, prompt, thinkingEnabled = false, searchEnabled = false, parentMessageId = null, refFileIds = [], preferVision = false }) {
// Step 1: Acquire token slot first — PoW and completion must use the same token
const slot = await enqueueRequest(preferVision);
try {
// Step 2: Solve PoW using the same token
const { powResponse } = await solvePowChallengeWithToken(slot.token);
setRequestToken(slot.token);
const session = await getSession(slot.token, modelType);
setRequestToken(null);
const body = {
chat_session_id: session.id,
parent_message_id: parentMessageId,
model_type: modelType,
prompt,
ref_file_ids: refFileIds,
thinking_enabled: thinkingEnabled,
search_enabled: searchEnabled,
action: null,
preempt: false,
};
const res = await proxiedFetch(`${BASE_URL}/api/v0/chat/completion`, {
method: 'POST',
headers: await streamHeaders(slot.token, powResponse),
body: JSON.stringify(body),
});
if (!res.ok) {
const text = await res.text();
// Check for specific DeepSeek error codes
try {
const errJson = JSON.parse(text);
const code = errJson.code;
// 40003 = invalid/expired token
if (code === 40003) {
reportTokenError(slot.token);
throw new Error('Token invalid (40003)');
}
// 40004 = account banned
if (code === 40004) {
reportTokenError(slot.token);
const entry = (await import('./auth.js')).getPoolInfo().find(t => slot.token.startsWith(t.token.replace('...', '')));
console.error(`Account BANNED during completion: ${entry?.email || slot.token.slice(0, 12)}...`);
throw new Error('Account banned (40004)');
}
} catch (parseErr) {
if (parseErr.message.includes('Token invalid') || parseErr.message.includes('Account banned')) throw parseErr;
}
throw new Error(`Completion request failed: ${res.status} ${text}`);
}
reportTokenSuccess(slot.token);
return { body: res.body, slot };
} catch (err) {
slot.release();
dispatchQueued();
throw err;
}
}
export async function* parseSSEStream(body) {
const reader = body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let messageIds = {};
let currentFragmentType = null;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('event:')) {
if (line.slice(6).trim() === 'close') return;
continue;
}
if (!line.startsWith('data:')) continue;
const data = line.slice(5).trim();
if (!data) continue;
try {
const parsed = JSON.parse(data);
// Check for error codes mid-stream
if (parsed.code === 40003) {
yield { type: 'error', code: 40003, message: 'Token invalid' };
return;
}
if (parsed.code === 40004) {
yield { type: 'error', code: 40004, message: 'Account banned' };
return;
}
if (parsed.request_message_id != null) {
messageIds.requestMessageId = parsed.request_message_id;
messageIds.responseMessageId = parsed.response_message_id;
}
if (parsed.v?.response?.fragments) {
for (const frag of parsed.v.response.fragments) {
if (frag.type === 'THINK' && frag.content) {
currentFragmentType = 'THINK';
yield { type: 'thinking', content: frag.content, messageIds };
} else if (frag.type === 'RESPONSE' && frag.content) {
currentFragmentType = 'RESPONSE';
yield { type: 'content', content: frag.content, messageIds };
}
}
if (parsed.v.response.accumulated_token_usage != null) {
yield { type: 'usage', usage: parsed.v.response.accumulated_token_usage, messageIds };
}
}
if (parsed.p && parsed.o) {
if (parsed.p === 'response/fragments/-1/content' && parsed.o === 'APPEND' && typeof parsed.v === 'string') {
yield { type: currentFragmentType === 'THINK' ? 'thinking' : 'content', content: parsed.v, messageIds };
} else if (parsed.p === 'response/fragments/-1/content' && !parsed.o && typeof parsed.v === 'string') {
yield { type: currentFragmentType === 'THINK' ? 'thinking' : 'content', content: parsed.v, messageIds };
} else if (parsed.p === 'response/status' && parsed.v === 'FINISHED') {
yield { type: 'done', messageIds };
} else if (parsed.p === 'response' && parsed.o === 'BATCH' && Array.isArray(parsed.v)) {
for (const item of parsed.v) {
if (item.p === 'accumulated_token_usage') {
yield { type: 'usage', usage: item.v, messageIds };
}
}
} else if (parsed.p === 'response/fragments' && parsed.o === 'APPEND' && Array.isArray(parsed.v)) {
for (const frag of parsed.v) {
if (frag.type === 'RESPONSE' && frag.content) {
currentFragmentType = 'RESPONSE';
yield { type: 'content', content: frag.content, messageIds };
} else if (frag.type === 'THINK' && frag.content) {
currentFragmentType = 'THINK';
yield { type: 'thinking', content: frag.content, messageIds };
}
}
}
continue;
}
if (typeof parsed.v === 'string') {
yield { type: currentFragmentType === 'THINK' ? 'thinking' : 'content', content: parsed.v, messageIds };
}
if (Array.isArray(parsed.v)) {
for (const item of parsed.v) {
if (item.p === 'accumulated_token_usage') {
yield { type: 'usage', usage: item.v, messageIds };
}
}
}
} catch {
// skip unparseable lines
}
}
}
} finally {
reader.releaseLock();
}
}