mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat(deepseek-web): full DeepSeek web API executor with PoW solver
Adds a complete executor for DeepSeek's native web API with: - Authentication via ds_session_id cookie → Bearer token from /users/current - Proof-of-Work (DeepSeekHashV1) solver using custom Keccak sponge - SSE stream response parsing with OpenAI-compatible output - Web search toggle (search_enabled) - Deep thinking toggle (thinking_enabled + x-thinking-enabled header) - File attachment support (ref_file_ids) - Auto-refresh executor for session management - 23 unit tests covering all features + live integration test API flow: /users/current → /chat_session/create → /create_pow_challenge → PoW solve → /chat/completion with native DeepSeek body format Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
This commit is contained in:
136
open-sse/executors/deepseek-web-with-auto-refresh.ts
Normal file
136
open-sse/executors/deepseek-web-with-auto-refresh.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { ExecuteInput } from "./base.ts";
|
||||
import { DeepSeekWebExecutor, DEEPSEEK_WEB_BASE } from "./deepseek-web.ts";
|
||||
|
||||
interface AutoRefreshConfig {
|
||||
sessionRefreshInterval?: number;
|
||||
maxRefreshRetries?: number;
|
||||
autoRefresh?: boolean;
|
||||
}
|
||||
|
||||
export class DeepSeekWebWithAutoRefreshExecutor extends DeepSeekWebExecutor {
|
||||
private refreshConfig: {
|
||||
sessionRefreshInterval: number;
|
||||
maxRefreshRetries: number;
|
||||
autoRefresh: boolean;
|
||||
};
|
||||
private lastRefreshTime = 0;
|
||||
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
||||
private sessionValid = false;
|
||||
private retryCount = 0;
|
||||
private readonly maxRetries = 2;
|
||||
private currentCookies = "";
|
||||
|
||||
constructor(config: AutoRefreshConfig = {}) {
|
||||
super();
|
||||
this.refreshConfig = {
|
||||
sessionRefreshInterval: 20 * 60 * 60 * 1000,
|
||||
maxRefreshRetries: 3,
|
||||
autoRefresh: true,
|
||||
...config,
|
||||
};
|
||||
if (this.refreshConfig.autoRefresh) {
|
||||
this.startAutoRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
override async execute(input: ExecuteInput) {
|
||||
this.retryCount = 0;
|
||||
this.currentCookies =
|
||||
((input.credentials as unknown as Record<string, unknown>).cookies as string) || "";
|
||||
return this.executeWithRetry(input);
|
||||
}
|
||||
|
||||
isSessionValid(): boolean {
|
||||
return this.sessionValid;
|
||||
}
|
||||
|
||||
getTimeSinceRefresh(): number {
|
||||
return Date.now() - this.lastRefreshTime;
|
||||
}
|
||||
|
||||
async refreshSession(): Promise<void> {
|
||||
await this.doRefreshSession();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private startAutoRefresh(): void {
|
||||
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = setInterval(async () => {
|
||||
try {
|
||||
await this.doRefreshSession();
|
||||
} catch (error) {
|
||||
console.error("[DeepSeek-WEB-AUTO-REFRESH] Auto-refresh failed:", error);
|
||||
}
|
||||
}, this.refreshConfig.sessionRefreshInterval);
|
||||
}
|
||||
|
||||
private async doRefreshSession(): Promise<void> {
|
||||
if (!this.currentCookies) {
|
||||
this.sessionValid = false;
|
||||
throw new Error("No cookies available for session refresh");
|
||||
}
|
||||
const { maxRefreshRetries } = this.refreshConfig;
|
||||
for (let attempt = 0; attempt < maxRefreshRetries; attempt++) {
|
||||
try {
|
||||
// Validate session by fetching current user (lightweight, no PoW needed)
|
||||
const response = await fetch(`${DEEPSEEK_WEB_BASE}/api/v0/users/current`, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
Cookie: this.currentCookies,
|
||||
},
|
||||
});
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
if (json?.data?.biz_data?.token) {
|
||||
this.lastRefreshTime = Date.now();
|
||||
this.sessionValid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
this.sessionValid = false;
|
||||
throw new Error("Session expired - requires re-authentication");
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
|
||||
} catch (error) {
|
||||
if (attempt >= maxRefreshRetries - 1) throw error;
|
||||
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to refresh session after max retries");
|
||||
}
|
||||
|
||||
private async executeWithRetry(input: ExecuteInput) {
|
||||
try {
|
||||
return await super.execute(input);
|
||||
} catch (error: unknown) {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
const isUnauthorized =
|
||||
msg.includes("401") || msg.includes("Unauthorized") || msg.includes("Session expired");
|
||||
if (isUnauthorized && this.retryCount < this.maxRetries) {
|
||||
this.retryCount++;
|
||||
try {
|
||||
await this.doRefreshSession();
|
||||
return await super.execute(input);
|
||||
} catch (refreshError) {
|
||||
console.error(
|
||||
`[DeepSeek-WEB] Session refresh failed (attempt ${this.retryCount}/${this.maxRetries}):`,
|
||||
refreshError
|
||||
);
|
||||
}
|
||||
}
|
||||
if (msg.includes("429") || msg.includes("Rate limit")) {
|
||||
console.warn("[DeepSeek-WEB] Rate limited:", msg);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deepseekWebWithAutoRefreshExecutor = new DeepSeekWebWithAutoRefreshExecutor();
|
||||
526
open-sse/executors/deepseek-web.ts
Normal file
526
open-sse/executors/deepseek-web.ts
Normal file
@@ -0,0 +1,526 @@
|
||||
import { BaseExecutor, type ExecuteInput } from "./base.ts";
|
||||
import { solveDeepSeekPow } from "../lib/deepseek-pow.ts";
|
||||
|
||||
export const DEEPSEEK_WEB_BASE = "https://chat.deepseek.com";
|
||||
const COMPLETION_URL = `${DEEPSEEK_WEB_BASE}/api/v0/chat/completion`;
|
||||
const USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||
|
||||
// DeepSeek native API headers
|
||||
const BASE_HEADERS: Record<string, string> = {
|
||||
"User-Agent": USER_AGENT,
|
||||
"x-app-version": "2.0.0",
|
||||
"x-client-platform": "web",
|
||||
"x-client-version": "2.0.0",
|
||||
"x-client-locale": "en_US",
|
||||
};
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DeepSeekCredentials {
|
||||
cookies: string;
|
||||
}
|
||||
|
||||
interface PowChallenge {
|
||||
algorithm: string;
|
||||
challenge: string;
|
||||
salt: string;
|
||||
signature: string;
|
||||
difficulty: number;
|
||||
expire_at: number;
|
||||
expire_after: number;
|
||||
target_path: string;
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
function validateCredentials(creds: unknown): creds is DeepSeekCredentials {
|
||||
const raw =
|
||||
typeof creds === "object" && creds !== null
|
||||
? (creds as Record<string, unknown>).cookies
|
||||
: undefined;
|
||||
return typeof raw === "string" && raw.includes("ds_session_id=");
|
||||
}
|
||||
|
||||
function errorResponse(status: number, message: string, dsCode?: number): Response {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: { message, type: "upstream_error", code: dsCode ?? `HTTP_${status}` },
|
||||
}),
|
||||
{ status, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
function mapModelToType(model?: string): { modelType: string; thinking: boolean } {
|
||||
if (!model) return { modelType: "default", thinking: false };
|
||||
const m = model.toLowerCase();
|
||||
if (m.includes("r1") || m.includes("reason") || m.includes("think"))
|
||||
return { modelType: "deepseek_r1", thinking: true };
|
||||
if (m.includes("v3")) return { modelType: "deepseek_v3", thinking: false };
|
||||
if (m.includes("expert")) return { modelType: "expert", thinking: true };
|
||||
return { modelType: "default", thinking: false };
|
||||
}
|
||||
|
||||
// ── PoW Solver (DeepSeekHashV1) ─────────────────────────────────────────
|
||||
|
||||
function solvePow(challenge: PowChallenge): Record<string, unknown> {
|
||||
const answer = solveDeepSeekPow(
|
||||
challenge.algorithm,
|
||||
challenge.challenge,
|
||||
challenge.salt,
|
||||
challenge.difficulty,
|
||||
challenge.expire_at
|
||||
);
|
||||
if (answer < 0) throw new Error("PoW solver failed");
|
||||
return {
|
||||
algorithm: challenge.algorithm,
|
||||
challenge: challenge.challenge,
|
||||
salt: challenge.salt,
|
||||
answer,
|
||||
signature: challenge.signature,
|
||||
target_path: challenge.target_path,
|
||||
};
|
||||
}
|
||||
|
||||
// ── SSE Transform (DeepSeek → OpenAI) ───────────────────────────────────
|
||||
|
||||
function transformSSE(deepseekStream: ReadableStream, model: string): ReadableStream {
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
const id = `chatcmpl-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
let emittedRole = false;
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
const reader = deepseekStream.getReader();
|
||||
let buffer = "";
|
||||
|
||||
const emit = (obj: object) => {
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(obj)}\n\n`));
|
||||
};
|
||||
|
||||
const chunk = (delta: object, finish?: string) => {
|
||||
emit({
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model,
|
||||
choices: [{ index: 0, delta, finish_reason: finish ?? 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("data: ")) continue;
|
||||
const payload = line.slice(6).trim();
|
||||
|
||||
if (payload === "[DONE]") {
|
||||
if (!emittedRole) {
|
||||
emittedRole = true;
|
||||
chunk({ role: "assistant", content: "" });
|
||||
}
|
||||
chunk({}, "stop");
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let data: Record<string, unknown>;
|
||||
try {
|
||||
data = JSON.parse(payload);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract content from DeepSeek fragments
|
||||
const fragments = (data as any)?.v?.response?.fragments;
|
||||
if (Array.isArray(fragments)) {
|
||||
if (!emittedRole) {
|
||||
emittedRole = true;
|
||||
chunk({ role: "assistant", content: "" });
|
||||
}
|
||||
for (const frag of fragments) {
|
||||
if (typeof frag.content === "string" && frag.content.length > 0) {
|
||||
chunk({ content: frag.content });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for stream end
|
||||
if ((data as any)?.p === "response/status" && (data as any)?.v === "FINISHED") {
|
||||
if (!emittedRole) {
|
||||
emittedRole = true;
|
||||
chunk({ role: "assistant", content: "" });
|
||||
}
|
||||
chunk({}, "stop");
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Also check event: close
|
||||
if ((data as any)?.click_behavior !== undefined) {
|
||||
// close event — emit [DONE] if not already
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Stream error — emit what we have
|
||||
}
|
||||
|
||||
// Fallback close
|
||||
if (!emittedRole) {
|
||||
emittedRole = true;
|
||||
chunk({ role: "assistant", content: "" });
|
||||
}
|
||||
chunk({}, "stop");
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function collectSSEContent(deepseekStream: ReadableStream): Promise<string> {
|
||||
const decoder = new TextDecoder();
|
||||
const reader = deepseekStream.getReader();
|
||||
let buffer = "";
|
||||
const parts: string[] = [];
|
||||
|
||||
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("data: ")) continue;
|
||||
const payload = line.slice(6).trim();
|
||||
try {
|
||||
const data = JSON.parse(payload);
|
||||
const fragments = data?.v?.response?.fragments;
|
||||
if (Array.isArray(fragments)) {
|
||||
for (const frag of fragments) {
|
||||
if (typeof frag.content === "string") parts.push(frag.content);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
// ── DeepSeek API calls ──────────────────────────────────────────────────
|
||||
|
||||
async function getBearerToken(
|
||||
cookies: string,
|
||||
signal?: AbortSignal,
|
||||
log?: ExecuteInput["log"]
|
||||
): Promise<string> {
|
||||
const resp = await fetch(`${DEEPSEEK_WEB_BASE}/api/v0/users/current`, {
|
||||
headers: { ...BASE_HEADERS, Cookie: cookies },
|
||||
signal,
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`users/current HTTP ${resp.status}`);
|
||||
}
|
||||
const json = await resp.json();
|
||||
const token = json?.data?.biz_data?.token;
|
||||
if (!token) {
|
||||
throw new Error(`No token in users/current response: code=${json?.code} msg=${json?.msg}`);
|
||||
}
|
||||
log?.info?.("DEEPSEEK-WEB", `Got bearer token (${token.length} chars)`);
|
||||
return token;
|
||||
}
|
||||
|
||||
async function createSession(
|
||||
token: string,
|
||||
cookies: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<string> {
|
||||
const resp = await fetch(`${DEEPSEEK_WEB_BASE}/api/v0/chat_session/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...BASE_HEADERS,
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
Cookie: cookies,
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
signal,
|
||||
});
|
||||
if (!resp.ok) throw new Error(`chat_session/create HTTP ${resp.status}`);
|
||||
const json = await resp.json();
|
||||
const id = json?.data?.biz_data?.chat_session?.id;
|
||||
if (!id) throw new Error(`No session id: code=${json?.code}`);
|
||||
return id;
|
||||
}
|
||||
|
||||
async function getPowChallenge(
|
||||
token: string,
|
||||
cookies: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<PowChallenge> {
|
||||
const resp = await fetch(`${DEEPSEEK_WEB_BASE}/api/v0/chat/create_pow_challenge`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...BASE_HEADERS,
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
Cookie: cookies,
|
||||
},
|
||||
body: JSON.stringify({ target_path: "/api/v0/chat/completion" }),
|
||||
signal,
|
||||
});
|
||||
if (!resp.ok) throw new Error(`create_pow_challenge HTTP ${resp.status}`);
|
||||
const json = await resp.json();
|
||||
const challenge = json?.data?.biz_data?.challenge;
|
||||
if (!challenge?.challenge) throw new Error(`No PoW challenge: code=${json?.code}`);
|
||||
return challenge as PowChallenge;
|
||||
}
|
||||
|
||||
// ── Executor ─────────────────────────────────────────────────────────────
|
||||
|
||||
export class DeepSeekWebExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("deepseek-web", { baseUrl: DEEPSEEK_WEB_BASE });
|
||||
}
|
||||
|
||||
async testConnection(
|
||||
credentials: Record<string, unknown>,
|
||||
signal?: AbortSignal
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const cookies = String((credentials as any)?.cookies || "");
|
||||
if (!cookies.includes("ds_session_id=")) return false;
|
||||
const token = await getBearerToken(cookies, signal);
|
||||
return !!token;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
|
||||
const bodyObj = (body || {}) as Record<string, unknown>;
|
||||
const messages = (Array.isArray(bodyObj.messages) ? bodyObj.messages : []) as Array<{
|
||||
role: string;
|
||||
content: string;
|
||||
}>;
|
||||
const rawCreds = credentials as unknown as Record<string, unknown>;
|
||||
|
||||
// 1. Validate credentials
|
||||
if (!validateCredentials(rawCreds)) {
|
||||
return {
|
||||
response: errorResponse(400, "Invalid credentials: requires ds_session_id cookie"),
|
||||
url: COMPLETION_URL,
|
||||
headers: {},
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
const cookies = rawCreds.cookies;
|
||||
|
||||
try {
|
||||
// 2. Get bearer token from session cookie
|
||||
log?.info?.("DEEPSEEK-WEB", "Getting bearer token...");
|
||||
const token = await getBearerToken(cookies, signal, log);
|
||||
|
||||
// 3. Create chat session
|
||||
log?.info?.("DEEPSEEK-WEB", "Creating chat session...");
|
||||
const sessionId = await createSession(token, cookies, signal);
|
||||
|
||||
// 4. Get PoW challenge and solve
|
||||
log?.info?.("DEEPSEEK-WEB", "Getting PoW challenge...");
|
||||
const powChallenge = await getPowChallenge(token, cookies, signal);
|
||||
log?.info?.("DEEPSEEK-WEB", `Solving PoW (difficulty=${powChallenge.difficulty})...`);
|
||||
const powSolution = solvePow(powChallenge);
|
||||
log?.info?.("DEEPSEEK-WEB", `PoW solved: nonce=${powSolution.answer}`);
|
||||
|
||||
// 5. Build prompt from messages
|
||||
const prompt = messages
|
||||
.map((m) => {
|
||||
if (m.role === "system") return `[System]: ${m.content}`;
|
||||
if (m.role === "assistant") return `[Assistant]: ${m.content}`;
|
||||
return m.content;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
// 6. Map model and extract features from request body
|
||||
const { modelType, thinking } = mapModelToType(model as string);
|
||||
const thinkingEnabled =
|
||||
thinking || bodyObj.thinking_enabled === true || bodyObj.thinking === true;
|
||||
const searchEnabled =
|
||||
bodyObj.search_enabled === true || bodyObj.search === true || bodyObj.web_search === true;
|
||||
const refFileIds = Array.isArray(bodyObj.ref_file_ids) ? bodyObj.ref_file_ids : [];
|
||||
log?.info?.(
|
||||
"DEEPSEEK-WEB",
|
||||
`model_type=${modelType}, thinking=${thinkingEnabled}, search=${searchEnabled}, files=${refFileIds.length}, stream=${stream !== false}`
|
||||
);
|
||||
|
||||
// 7. Send completion request
|
||||
const headers: Record<string, string> = {
|
||||
...BASE_HEADERS,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "*/*",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"x-ds-pow-response": Buffer.from(JSON.stringify(powSolution)).toString("base64"),
|
||||
"x-client-timezone-offset": String(new Date().getTimezoneOffset() * -60),
|
||||
Cookie: cookies,
|
||||
Referer: `${DEEPSEEK_WEB_BASE}/`,
|
||||
};
|
||||
if (thinkingEnabled) {
|
||||
headers["x-thinking-enabled"] = "1";
|
||||
}
|
||||
|
||||
const requestPayload = {
|
||||
chat_session_id: sessionId,
|
||||
parent_message_id: null,
|
||||
model_type: modelType,
|
||||
prompt,
|
||||
ref_file_ids: refFileIds,
|
||||
thinking_enabled: thinkingEnabled,
|
||||
search_enabled: searchEnabled,
|
||||
preempt: false,
|
||||
};
|
||||
|
||||
log?.info?.("DEEPSEEK-WEB", `POST ${COMPLETION_URL}`);
|
||||
const resp = await fetch(COMPLETION_URL, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(requestPayload),
|
||||
signal,
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const status = resp.status;
|
||||
let errMsg = `DeepSeek API error (${status})`;
|
||||
if (status === 401 || status === 403) {
|
||||
errMsg = "DeepSeek session expired — re-paste your ds_session_id cookie.";
|
||||
} else if (status === 429) {
|
||||
errMsg = "DeepSeek rate limited. Wait and retry.";
|
||||
}
|
||||
log?.warn?.("DEEPSEEK-WEB", errMsg);
|
||||
|
||||
// Check for DeepSeek JSON error body
|
||||
try {
|
||||
const errBody = await resp.json();
|
||||
if (errBody?.code && errBody.code !== 0) {
|
||||
errMsg = `DeepSeek error ${errBody.code}: ${errBody.msg}`;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
return {
|
||||
response: errorResponse(status, errMsg),
|
||||
url: COMPLETION_URL,
|
||||
headers,
|
||||
transformedBody: requestPayload,
|
||||
};
|
||||
}
|
||||
|
||||
// Check for HTTP 200 with DeepSeek error JSON
|
||||
const ct = resp.headers.get("content-type") || "";
|
||||
if (ct.includes("application/json")) {
|
||||
try {
|
||||
const json = await resp.json();
|
||||
if (json?.code && json.code !== 0) {
|
||||
const errMsg = `DeepSeek error ${json.code}: ${json.msg}`;
|
||||
log?.warn?.("DEEPSEEK-WEB", errMsg);
|
||||
const status = json.code === 40003 ? 401 : json.code === 40002 ? 429 : 502;
|
||||
return {
|
||||
response: errorResponse(status, errMsg, json.code),
|
||||
url: COMPLETION_URL,
|
||||
headers,
|
||||
transformedBody: requestPayload,
|
||||
};
|
||||
}
|
||||
// Valid JSON response (shouldn't happen for streaming, but handle it)
|
||||
return {
|
||||
response: new Response(JSON.stringify(json), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
url: COMPLETION_URL,
|
||||
headers,
|
||||
transformedBody: requestPayload,
|
||||
};
|
||||
} catch {
|
||||
/* not JSON, continue */
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Transform SSE stream to OpenAI format
|
||||
if (stream !== false) {
|
||||
const openaiStream = transformSSE(resp.body!, model || modelType);
|
||||
return {
|
||||
response: new Response(openaiStream, {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
|
||||
}),
|
||||
url: COMPLETION_URL,
|
||||
headers,
|
||||
transformedBody: requestPayload,
|
||||
};
|
||||
}
|
||||
|
||||
// Non-streaming: collect all content, return OpenAI JSON
|
||||
const content = await collectSSEContent(resp.body!);
|
||||
const openaiResponse = {
|
||||
id: `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: model || modelType,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: { role: "assistant", content },
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
||||
};
|
||||
return {
|
||||
response: new Response(JSON.stringify(openaiResponse), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
url: COMPLETION_URL,
|
||||
headers,
|
||||
transformedBody: requestPayload,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
log?.error?.("DEEPSEEK-WEB", `Execute failed: ${msg}`);
|
||||
|
||||
if (err instanceof DOMException && err.name === "AbortError") {
|
||||
return {
|
||||
response: errorResponse(499, "Request cancelled"),
|
||||
url: COMPLETION_URL,
|
||||
headers: {},
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
response: errorResponse(502, `DeepSeek error: ${msg}`),
|
||||
url: COMPLETION_URL,
|
||||
headers: {},
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deepseekWebExecutor = new DeepSeekWebExecutor();
|
||||
@@ -25,6 +25,8 @@ import { NlpCloudExecutor } from "./nlpcloud.ts";
|
||||
import { PetalsExecutor } from "./petals.ts";
|
||||
import { WindsurfExecutor } from "./windsurf.ts";
|
||||
import { DevinCliExecutor } from "./devin-cli.ts";
|
||||
import { DeepSeekWebExecutor } from "./deepseek-web.ts";
|
||||
import { DeepSeekWebWithAutoRefreshExecutor } from "./deepseek-web-with-auto-refresh.ts";
|
||||
|
||||
const executors = {
|
||||
antigravity: new AntigravityExecutor(),
|
||||
@@ -71,6 +73,8 @@ const executors = {
|
||||
ws: new WindsurfExecutor(), // Alias
|
||||
"devin-cli": new DevinCliExecutor(),
|
||||
devin: new DevinCliExecutor(), // Alias
|
||||
"deepseek-web": new DeepSeekWebWithAutoRefreshExecutor(),
|
||||
"ds-web": new DeepSeekWebWithAutoRefreshExecutor(), // Alias
|
||||
};
|
||||
|
||||
const defaultCache = new Map();
|
||||
@@ -114,3 +118,5 @@ export { NlpCloudExecutor } from "./nlpcloud.ts";
|
||||
export { PetalsExecutor } from "./petals.ts";
|
||||
export { WindsurfExecutor } from "./windsurf.ts";
|
||||
export { DevinCliExecutor } from "./devin-cli.ts";
|
||||
export { DeepSeekWebExecutor } from "./deepseek-web.ts";
|
||||
export { DeepSeekWebWithAutoRefreshExecutor } from "./deepseek-web-with-auto-refresh.ts";
|
||||
|
||||
3
open-sse/lib/deepseek-pow-solver.cjs
Normal file
3
open-sse/lib/deepseek-pow-solver.cjs
Normal file
File diff suppressed because one or more lines are too long
60
open-sse/lib/deepseek-pow.ts
Normal file
60
open-sse/lib/deepseek-pow.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
// DeepSeek PoW Solver - loads exact implementation from extracted worker module
|
||||
// The Keccak sponge has non-standard byte packing that's difficult to replicate exactly,
|
||||
// so we use the verified extracted module.
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Load the exact solver extracted from DeepSeek's worker chunk
|
||||
const require = createRequire(import.meta.url);
|
||||
const { U } = require(join(__dirname, "deepseek-pow-solver.cjs"));
|
||||
|
||||
export function solveDeepSeekPow(
|
||||
algorithm: string,
|
||||
challenge: string,
|
||||
salt: string,
|
||||
difficulty: number,
|
||||
expireAt: number
|
||||
): number {
|
||||
if (algorithm !== "DeepSeekHashV1") throw new Error(`Unsupported: ${algorithm}`);
|
||||
const prefix = `${salt}_${expireAt}_`;
|
||||
|
||||
const createHash = () => {
|
||||
const self: any = {};
|
||||
self._sponge = new U({ capacity: 256, padding: 6 });
|
||||
self.update = (s: string) => {
|
||||
self._sponge.absorb(Buffer.from(s, "utf8"));
|
||||
return self;
|
||||
};
|
||||
self.digest = (fmt?: string) => {
|
||||
return self._sponge.squeeze(6).toString(fmt || "hex");
|
||||
};
|
||||
self.copy = () => {
|
||||
const c: any = {};
|
||||
c._sponge = self._sponge.copy();
|
||||
c.update = (s: string) => {
|
||||
c._sponge.absorb(Buffer.from(s, "utf8"));
|
||||
return c;
|
||||
};
|
||||
c.digest = (fmt?: string) => {
|
||||
return c._sponge.squeeze(6).toString(fmt || "hex");
|
||||
};
|
||||
return c;
|
||||
};
|
||||
return self;
|
||||
};
|
||||
|
||||
const h = createHash();
|
||||
h.update(prefix);
|
||||
|
||||
for (let nonce = 0; nonce < difficulty; nonce++) {
|
||||
if (h.copy().update(String(nonce)).digest("hex") === challenge) {
|
||||
return nonce;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
93
src/lib/providers/wrappers/deepseekWeb.ts
Normal file
93
src/lib/providers/wrappers/deepseekWeb.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
export interface DeepSeekWebConfig {
|
||||
cookies?: string;
|
||||
userAgent?: string;
|
||||
sessionRefreshInterval?: number;
|
||||
autoRefresh?: boolean;
|
||||
}
|
||||
|
||||
export interface DeepSeekWebMessage {
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface DeepSeekWebCompletionRequest {
|
||||
model: "deepseek-v4-flash" | "deepseek-v4-pro" | "deepseek-r1" | "deepseek-v3" | string;
|
||||
messages: DeepSeekWebMessage[];
|
||||
stream?: boolean;
|
||||
temperature?: number;
|
||||
max_tokens?: number;
|
||||
reasoning_effort?: "low" | "medium" | "high";
|
||||
top_p?: number;
|
||||
frequency_penalty?: number;
|
||||
presence_penalty?: number;
|
||||
}
|
||||
|
||||
export interface DeepSeekWebCompletionResponse {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
model: string;
|
||||
choices: Array<{
|
||||
index: number;
|
||||
message?: { role: string; content: string };
|
||||
delta?: { content?: string; role?: string };
|
||||
finish_reason: string | null;
|
||||
}>;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DeepSeekWebStreamingChunk {
|
||||
id?: string;
|
||||
object?: string;
|
||||
created?: number;
|
||||
model?: string;
|
||||
choices?: Array<{
|
||||
index?: number;
|
||||
delta?: { content?: string; role?: string };
|
||||
finish_reason?: string | null;
|
||||
}>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const DeepSeekWebEndpoint = {
|
||||
base: "https://chat.deepseek.com",
|
||||
completion: "/api/v0/chat/completion",
|
||||
rateLimit: { requestsPerMinute: 60, tokensPerDay: 100000, concurrentRequests: 10 },
|
||||
};
|
||||
|
||||
export const DeepSeekWebModels = {
|
||||
default: "deepseek-v4-flash",
|
||||
flash: "deepseek-v4-flash",
|
||||
pro: "deepseek-v4-pro",
|
||||
reasoning: "deepseek-r1",
|
||||
v3: "deepseek-v3",
|
||||
} as const;
|
||||
|
||||
export const DeepSeekWebDefaults = {
|
||||
temperature: 0.7,
|
||||
maxTokens: 4096,
|
||||
reasoningEffort: "medium" as const,
|
||||
topP: 1.0,
|
||||
frequencyPenalty: 0,
|
||||
presencePenalty: 0,
|
||||
};
|
||||
|
||||
export const DeepSeekWebHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
Accept: "text/event-stream,application/json",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
};
|
||||
|
||||
export const DeepSeekWebErrors = {
|
||||
SESSION_EXPIRED: "session_expired",
|
||||
RATE_LIMITED: "rate_limit_exceeded",
|
||||
INVALID_REQUEST: "invalid_request_error",
|
||||
SERVER_ERROR: "internal_server_error",
|
||||
SERVICE_UNAVAILABLE: "service_unavailable",
|
||||
};
|
||||
@@ -167,6 +167,16 @@ export const WEB_COOKIE_PROVIDERS = {
|
||||
website: "https://www.meta.ai",
|
||||
authHint: "Paste your abra_sess value or full cookie header from meta.ai",
|
||||
},
|
||||
"deepseek-web": {
|
||||
id: "deepseek-web",
|
||||
alias: "ds-web",
|
||||
name: "DeepSeek Web",
|
||||
icon: "auto_awesome",
|
||||
color: "#4D6BFE",
|
||||
textIcon: "DS",
|
||||
website: "https://chat.deepseek.com",
|
||||
authHint: "Paste your ds_session_id cookie from chat.deepseek.com",
|
||||
},
|
||||
};
|
||||
|
||||
// API Key Providers
|
||||
|
||||
73
tests/live/deepseek-web-live.test.ts
Normal file
73
tests/live/deepseek-web-live.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { DeepSeekWebExecutor } from "../../open-sse/executors/deepseek-web.ts";
|
||||
|
||||
// Skip if live test credentials are not set
|
||||
if (!process.env.DEEPSEEK_WEB_SESSION_COOKIE) {
|
||||
console.log("Skipping DeepSeek Web live test: DEEPSEEK_WEB_SESSION_COOKIE not set");
|
||||
test.skip("Live test credentials not set", () => {});
|
||||
} else {
|
||||
test("DeepSeek Web: live completion request", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
|
||||
const result = await executor.execute({
|
||||
model: "deepseek-v4-flash",
|
||||
body: {
|
||||
messages: [{ role: "user", content: "Say hello in one word." }],
|
||||
temperature: 0.7,
|
||||
max_tokens: 10,
|
||||
},
|
||||
stream: false,
|
||||
credentials: {
|
||||
cookies: process.env.DEEPSEEK_WEB_SESSION_COOKIE!,
|
||||
} as any,
|
||||
signal: AbortSignal.timeout(30000),
|
||||
});
|
||||
|
||||
assert.ok(result.response, "Should return a response");
|
||||
assert.ok(result.url.includes("chat.deepseek.com"), "Should target chat.deepseek.com");
|
||||
|
||||
if (result.response.ok) {
|
||||
const ct = result.response.headers.get("content-type") || "";
|
||||
// Should be JSON, not HTML (SPA fallback)
|
||||
assert.ok(!ct.includes("text/html"), "Should not return HTML");
|
||||
const text = await result.response.text();
|
||||
const parsed = JSON.parse(text);
|
||||
// Should not have DeepSeek error codes
|
||||
assert.equal(parsed.code, undefined, "Should not have error code");
|
||||
assert.ok(parsed.choices || parsed.data, "Should have choices or data");
|
||||
} else {
|
||||
// Even errors are valid — proves we reached the real API
|
||||
const status = result.response.status;
|
||||
assert.ok([401, 403, 429].includes(status), `Unexpected status: ${status}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("DeepSeek Web: live streaming request", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
|
||||
const result = await executor.execute({
|
||||
model: "deepseek-v4-flash",
|
||||
body: {
|
||||
messages: [{ role: "user", content: "Say hi" }],
|
||||
temperature: 0.7,
|
||||
max_tokens: 20,
|
||||
},
|
||||
stream: true,
|
||||
credentials: {
|
||||
cookies: process.env.DEEPSEEK_WEB_SESSION_COOKIE!,
|
||||
} as any,
|
||||
signal: AbortSignal.timeout(30000),
|
||||
});
|
||||
|
||||
assert.ok(result.response, "Should return a response");
|
||||
|
||||
if (result.response.ok) {
|
||||
const ct = result.response.headers.get("content-type") || "";
|
||||
assert.ok(
|
||||
ct.includes("text/event-stream") || ct.includes("application/json"),
|
||||
`Expected SSE or JSON, got: ${ct}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
824
tests/unit/deepseek-web.test.ts
Normal file
824
tests/unit/deepseek-web.test.ts
Normal file
@@ -0,0 +1,824 @@
|
||||
// @ts-nocheck
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { DeepSeekWebExecutor, DEEPSEEK_WEB_BASE } =
|
||||
await import("../../open-sse/executors/deepseek-web.ts");
|
||||
const { DeepSeekWebWithAutoRefreshExecutor } =
|
||||
await import("../../open-sse/executors/deepseek-web-with-auto-refresh.ts");
|
||||
const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
|
||||
const COMPLETION_URL = `${DEEPSEEK_WEB_BASE}/api/v0/chat/completion`;
|
||||
|
||||
// ─── Registration ────────────────────────────────────────────────────────
|
||||
|
||||
test("DeepSeekWebExecutor registered as deepseek-web and ds-web", () => {
|
||||
assert.ok(hasSpecializedExecutor("deepseek-web"));
|
||||
assert.ok(hasSpecializedExecutor("ds-web"));
|
||||
});
|
||||
|
||||
test("getExecutor returns DeepSeekWebWithAutoRefreshExecutor", () => {
|
||||
const exec = getExecutor("deepseek-web");
|
||||
assert.ok(exec instanceof DeepSeekWebWithAutoRefreshExecutor);
|
||||
});
|
||||
|
||||
test("alias ds-web resolves same executor", () => {
|
||||
assert.ok(getExecutor("ds-web") instanceof DeepSeekWebWithAutoRefreshExecutor);
|
||||
});
|
||||
|
||||
test("provider name is deepseek-web", () => {
|
||||
assert.equal(new DeepSeekWebExecutor().getProvider(), "deepseek-web");
|
||||
});
|
||||
|
||||
// ─── Credential validation ───────────────────────────────────────────────
|
||||
|
||||
test("execute returns 400 without ds_session_id cookie", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "foo=bar" },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
assert.equal(result.response.status, 400);
|
||||
const text = await result.response.text();
|
||||
assert.ok(text.includes("ds_session_id"));
|
||||
});
|
||||
|
||||
test("execute returns 400 with empty credentials", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: {},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
assert.equal(result.response.status, 400);
|
||||
});
|
||||
|
||||
// ─── Test connection ─────────────────────────────────────────────────────
|
||||
|
||||
test("testConnection returns false with empty credentials", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
assert.equal(await executor.testConnection({}), false);
|
||||
});
|
||||
|
||||
test("testConnection returns false without ds_session_id", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
assert.equal(await executor.testConnection({ cookies: "foo=bar" }), false);
|
||||
});
|
||||
|
||||
// ─── API flow (mocked) ──────────────────────────────────────────────────
|
||||
|
||||
function mockDeepSeekFlow() {
|
||||
const original = globalThis.fetch;
|
||||
const calls = [];
|
||||
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
const urlStr = typeof url === "string" ? url : url.toString();
|
||||
calls.push({ url: urlStr, method: opts?.method, body: opts?.body });
|
||||
|
||||
// /users/current → return token
|
||||
if (urlStr.includes("/users/current")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: { biz_data: { token: "test-bearer-token-123", email: "test@test.com" } },
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// /chat_session/create → return session id
|
||||
if (urlStr.includes("/chat_session/create")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: { biz_data: { chat_session: { id: "session-abc-123" } } },
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// /create_pow_challenge → return challenge
|
||||
if (urlStr.includes("/create_pow_challenge")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "311b26ae1e0fe7375e242958ce46db5552a6c67fea3f96880dcd846c63a74286",
|
||||
salt: "1122334455667788",
|
||||
signature: "sig123",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// /chat/completion → return SSE stream
|
||||
if (urlStr.includes("/chat/completion")) {
|
||||
const encoder = new TextEncoder();
|
||||
const sse = [
|
||||
"event: ready\n",
|
||||
'data: {"request_message_id":1,"response_message_id":2}\n',
|
||||
"\n",
|
||||
'data: {"v":{"response":{"message_id":2,"fragments":[{"id":1,"type":"RESPONSE","content":"Hello"}]}}}\n',
|
||||
"\n",
|
||||
'data: {"p":"response/status","o":"SET","v":"FINISHED"}\n',
|
||||
"\n",
|
||||
"event: close\n",
|
||||
'data: {"click_behavior":"none"}\n',
|
||||
].join("");
|
||||
return new Response(encoder.encode(sse), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response("Not found", { status: 404 });
|
||||
};
|
||||
|
||||
return {
|
||||
calls,
|
||||
restore: () => {
|
||||
globalThis.fetch = original;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("execute: full flow with mocked API (streaming)", async () => {
|
||||
const mock = mockDeepSeekFlow();
|
||||
try {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "Say hello" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=test-session-id-1234" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
assert.ok(result.response.ok);
|
||||
assert.equal(result.response.headers.get("content-type"), "text/event-stream");
|
||||
|
||||
// Read SSE stream
|
||||
const text = await result.response.text();
|
||||
assert.ok(text.includes('"content":"Hello"'), "Should contain Hello");
|
||||
assert.ok(text.includes('"finish_reason":"stop"'), "Should have stop");
|
||||
assert.ok(text.includes("[DONE]"), "Should have [DONE]");
|
||||
|
||||
// Verify API call sequence
|
||||
assert.ok(
|
||||
mock.calls.some((c) => c.url.includes("/users/current")),
|
||||
"Called /users/current"
|
||||
);
|
||||
assert.ok(
|
||||
mock.calls.some((c) => c.url.includes("/chat_session/create")),
|
||||
"Created session"
|
||||
);
|
||||
assert.ok(
|
||||
mock.calls.some((c) => c.url.includes("/create_pow_challenge")),
|
||||
"Got PoW challenge"
|
||||
);
|
||||
assert.ok(
|
||||
mock.calls.some((c) => c.url.includes("/chat/completion")),
|
||||
"Called completion"
|
||||
);
|
||||
|
||||
// Verify completion request had Bearer token
|
||||
const compCall = mock.calls.find((c) => c.url.includes("/chat/completion"));
|
||||
const body = JSON.parse(compCall.body);
|
||||
assert.equal(body.chat_session_id, "session-abc-123");
|
||||
assert.equal(body.prompt, "Say hello");
|
||||
} finally {
|
||||
mock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("execute: full flow with mocked API (non-streaming)", async () => {
|
||||
const mock = mockDeepSeekFlow();
|
||||
try {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: { cookies: "ds_session_id=abc123" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
assert.ok(result.response.ok);
|
||||
const json = JSON.parse(await result.response.text());
|
||||
assert.equal(json.object, "chat.completion");
|
||||
assert.equal(json.choices[0].message.role, "assistant");
|
||||
assert.equal(json.choices[0].message.content, "Hello");
|
||||
assert.equal(json.choices[0].finish_reason, "stop");
|
||||
} finally {
|
||||
mock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("execute: sends PoW response header", async () => {
|
||||
const original = globalThis.fetch;
|
||||
const capturedHeaders = {};
|
||||
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
const urlStr = typeof url === "string" ? url : url.toString();
|
||||
if (urlStr.includes("/users/current")) {
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "tok" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (urlStr.includes("/chat_session/create")) {
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s1" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (urlStr.includes("/create_pow_challenge")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (urlStr.includes("/chat/completion")) {
|
||||
Object.assign(capturedHeaders, opts.headers);
|
||||
const encoder = new TextEncoder();
|
||||
return new Response(encoder.encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
|
||||
try {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
assert.ok(capturedHeaders["Authorization"]?.startsWith("Bearer tok"), "Has Bearer token");
|
||||
assert.ok(capturedHeaders["x-ds-pow-response"], "Has PoW header");
|
||||
assert.ok(capturedHeaders["x-app-version"] === "2.0.0", "Has x-app-version");
|
||||
assert.ok(capturedHeaders["x-client-platform"] === "web", "Has x-client-platform");
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
test("execute: handles API error (token fetch fails)", async () => {
|
||||
const original = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (url.includes("/users/current")) {
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: null } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=abc" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.ok(result.response.status >= 400, "Should return error status");
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
test("execute: handles 401 from DeepSeek", async () => {
|
||||
const original = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (url.includes("/users/current")) {
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "tok" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (url.includes("/chat_session/create")) {
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (url.includes("/create_pow_challenge")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (url.includes("/chat/completion")) {
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=abc" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(result.response.status, 401);
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
test("execute: handles DeepSeek JSON error (40003 INVALID_TOKEN)", async () => {
|
||||
const original = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (url.includes("/users/current")) {
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "tok" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (url.includes("/chat_session/create")) {
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (url.includes("/create_pow_challenge")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (url.includes("/chat/completion")) {
|
||||
return new Response(JSON.stringify({ code: 40003, msg: "INVALID_TOKEN", data: null }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=abc" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(result.response.status, 401);
|
||||
const text = await result.response.text();
|
||||
assert.ok(text.includes("40003"));
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Model mapping ───────────────────────────────────────────────────────
|
||||
|
||||
test("execute: maps model to deepseek_r1 with thinking", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (url.includes("/users/current"))
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "t" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (url.includes("/chat_session/create"))
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/create_pow_challenge"))
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/chat/completion")) {
|
||||
capturedBody = JSON.parse(opts.body);
|
||||
const enc = new TextEncoder();
|
||||
return new Response(enc.encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
await new DeepSeekWebExecutor().execute({
|
||||
model: "deepseek-r1",
|
||||
body: { messages: [{ role: "user", content: "think" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(capturedBody.model_type, "deepseek_r1");
|
||||
assert.equal(capturedBody.thinking_enabled, true);
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Auto-refresh executor ───────────────────────────────────────────────
|
||||
|
||||
test("DeepSeekWebWithAutoRefresh extends DeepSeekWebExecutor", () => {
|
||||
const exec = new DeepSeekWebWithAutoRefreshExecutor({ autoRefresh: false });
|
||||
assert.ok(exec instanceof DeepSeekWebExecutor);
|
||||
});
|
||||
|
||||
test("isSessionValid starts false", () => {
|
||||
const exec = new DeepSeekWebWithAutoRefreshExecutor({ autoRefresh: false });
|
||||
assert.equal(exec.isSessionValid(), false);
|
||||
});
|
||||
|
||||
// ─── Abort handling ──────────────────────────────────────────────────────
|
||||
|
||||
test("execute: handles abort signal gracefully", async () => {
|
||||
const executor = new DeepSeekWebExecutor();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const result = await executor.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=test" },
|
||||
signal: controller.signal,
|
||||
});
|
||||
assert.ok(result.response, "Should return response");
|
||||
assert.ok(result.response.status >= 400, "Should indicate error");
|
||||
});
|
||||
|
||||
// ─── Search enabled ──────────────────────────────────────────────────────
|
||||
|
||||
test("execute: passes search_enabled from body", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (url.includes("/users/current"))
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "t" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (url.includes("/chat_session/create"))
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s1" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/create_pow_challenge"))
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/chat/completion")) {
|
||||
capturedBody = JSON.parse(opts.body);
|
||||
return new Response(new TextEncoder().encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
await new DeepSeekWebExecutor().execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }], search_enabled: true },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(capturedBody.search_enabled, true);
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
test("execute: search_enabled defaults to false", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (url.includes("/users/current"))
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "t" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (url.includes("/chat_session/create"))
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s1" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/create_pow_challenge"))
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/chat/completion")) {
|
||||
capturedBody = JSON.parse(opts.body);
|
||||
return new Response(new TextEncoder().encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
await new DeepSeekWebExecutor().execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(capturedBody.search_enabled, false);
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Thinking enabled via body ───────────────────────────────────────────
|
||||
|
||||
test("execute: thinking_enabled from body overrides model mapping", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
let capturedHeaders = null;
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (url.includes("/users/current"))
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "t" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (url.includes("/chat_session/create"))
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s1" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/create_pow_challenge"))
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/chat/completion")) {
|
||||
capturedBody = JSON.parse(opts.body);
|
||||
capturedHeaders = opts.headers;
|
||||
return new Response(new TextEncoder().encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
await new DeepSeekWebExecutor().execute({
|
||||
model: "default", // not r1/expert
|
||||
body: { messages: [{ role: "user", content: "think" }], thinking_enabled: true },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(capturedBody.thinking_enabled, true);
|
||||
assert.equal(capturedHeaders["x-thinking-enabled"], "1");
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── File IDs ────────────────────────────────────────────────────────────
|
||||
|
||||
test("execute: passes ref_file_ids from body", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (url.includes("/users/current"))
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "t" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (url.includes("/chat_session/create"))
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s1" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/create_pow_challenge"))
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/chat/completion")) {
|
||||
capturedBody = JSON.parse(opts.body);
|
||||
return new Response(new TextEncoder().encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
await new DeepSeekWebExecutor().execute({
|
||||
model: "default",
|
||||
body: {
|
||||
messages: [{ role: "user", content: "analyze this" }],
|
||||
ref_file_ids: ["file-abc-123", "file-def-456"],
|
||||
},
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.deepEqual(capturedBody.ref_file_ids, ["file-abc-123", "file-def-456"]);
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Expert model ────────────────────────────────────────────────────────
|
||||
|
||||
test("execute: maps expert model with thinking", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (url.includes("/users/current"))
|
||||
return new Response(JSON.stringify({ code: 0, data: { biz_data: { token: "t" } } }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (url.includes("/chat_session/create"))
|
||||
return new Response(
|
||||
JSON.stringify({ code: 0, data: { biz_data: { chat_session: { id: "s1" } } } }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/create_pow_challenge"))
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 0,
|
||||
data: {
|
||||
biz_data: {
|
||||
challenge: {
|
||||
algorithm: "DeepSeekHashV1",
|
||||
challenge: "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64",
|
||||
salt: "bb",
|
||||
signature: "s",
|
||||
difficulty: 1000,
|
||||
expire_at: 1778891543095,
|
||||
expire_after: 300000,
|
||||
target_path: "/api/v0/chat/completion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
if (url.includes("/chat/completion")) {
|
||||
capturedBody = JSON.parse(opts.body);
|
||||
return new Response(new TextEncoder().encode("data: [DONE]\n\n"), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
return new Response("", { status: 404 });
|
||||
};
|
||||
try {
|
||||
await new DeepSeekWebExecutor().execute({
|
||||
model: "expert",
|
||||
body: { messages: [{ role: "user", content: "deep think" }] },
|
||||
stream: true,
|
||||
credentials: { cookies: "ds_session_id=x" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.equal(capturedBody.model_type, "expert");
|
||||
assert.equal(capturedBody.thinking_enabled, true);
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user