From d53780b07f2caa5188ef2a6d876d6647f9506ec9 Mon Sep 17 00:00:00 2001 From: "Mr. Meowgi" Date: Sat, 23 May 2026 09:45:37 +0300 Subject: [PATCH] fix(deepseek-web): fix SSE parser, prompt format, error handling, and cache keys (#2616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrated into release/v3.8.3 — fix(deepseek-web): SSE parser (APPEND + bare tokens), prompt builder, error handling, session cache cleanup --- open-sse/executors/deepseek-web.ts | 104 ++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/open-sse/executors/deepseek-web.ts b/open-sse/executors/deepseek-web.ts index 6b88576709..6463b6013d 100644 --- a/open-sse/executors/deepseek-web.ts +++ b/open-sse/executors/deepseek-web.ts @@ -212,7 +212,7 @@ function transformSSE(deepseekStream: ReadableStream, model: string): ReadableSt } } - // response/fragments path (incremental updates) + // response/fragments path (array of fragments) if ((data as any)?.p === "response/fragments" && Array.isArray((data as any)?.v)) { if (!emittedRole) { emittedRole = true; @@ -229,6 +229,36 @@ function transformSSE(deepseekStream: ReadableStream, model: string): ReadableSt } } + // Incremental APPEND to fragment content (expert/pro mode) + const p = (data as any)?.p; + const o = (data as any)?.o; + if ( + typeof p === "string" && + p.includes("/content") && + o === "APPEND" && + typeof (data as any)?.v === "string" + ) { + if (!emittedRole) { + emittedRole = true; + chunk({ role: "assistant", content: "" }); + } + const isThink = p.includes("thinking") || p.includes("think"); + if (isThink) { + chunk({ reasoning_content: (data as any).v }); + } else { + chunk({ content: (data as any).v }); + } + } + + // Bare string tokens: {"v": "word"} with no "p" field + if (typeof (data as any)?.v === "string" && !p && !o) { + if (!emittedRole) { + emittedRole = true; + chunk({ role: "assistant", content: "" }); + } + chunk({ content: (data as any).v }); + } + if ((data as any)?.p === "response/status" && (data as any)?.v === "FINISHED") { if (!emittedRole) { emittedRole = true; @@ -286,6 +316,19 @@ async function collectSSEContent(deepseekStream: ReadableStream): Promise): string { + const extractText = (content: unknown): string => { + if (Array.isArray(content)) { + return (content as any[]) + .filter((item: any) => item.type === "text") + .map((item: any) => item.text) + .join("\n"); + } + return String(content || ""); + }; + + if (messages.length === 0) return ""; + + // Collect system prompt(s) and find the last user message + const systemParts: string[] = []; + let lastUserContent = ""; + for (const m of messages) { + if (m.role === "system") { + const text = extractText(m.content).trim(); + if (text) systemParts.push(text); + } else if (m.role === "user") { + lastUserContent = extractText(m.content).trim(); + } + } + + const parts: string[] = []; + if (systemParts.length > 0) { + parts.push(systemParts.join("\n\n")); + } + if (lastUserContent) { + parts.push(lastUserContent); + } + + return parts.join("\n\n").replace(/!\[.*?\]\(.*?\)/g, ""); +} + // ── DeepSeek API calls (Bearer token auth, like Chat2API) ─────────────── async function acquireAccessToken( @@ -324,6 +405,11 @@ async function acquireAccessToken( } const json = await resp.json(); + if (json?.code && json.code !== 0) { + const errMsg = json.msg || json?.data?.biz_msg || `error code ${json.code}`; + tokenCache.delete(userToken); + throw new Error(`DeepSeek rejected token: ${errMsg}`); + } const bizData = json?.data?.biz_data || json?.biz_data; if (!bizData?.token) { const errMsg = json?.msg || json?.data?.biz_msg || "Unknown error"; @@ -462,14 +548,8 @@ export class DeepSeekWebExecutor extends BaseExecutor { const powAnswer = await solvePow(powChallenge); log?.info?.("DEEPSEEK-WEB", `PoW solved in ${Date.now() - t0}ms`); - // 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"); + // 5. Build prompt from messages (DeepSeek native chat markers, matching Chat2API) + const prompt = messagesToPrompt(messages); // 6. Resolve model type, thinking, and search from model name + body flags const { modelType, thinkingEnabled, searchEnabled } = resolveModelOptions( @@ -522,6 +602,7 @@ export class DeepSeekWebExecutor extends BaseExecutor { let errMsg = `DeepSeek API error (${status})`; if (status === 401 || status === 403) { tokenCache.delete(userToken); + sessionCache.delete((rawCreds.connectionId as string) || accessToken); errMsg = "DeepSeek token expired — get a fresh userToken from localStorage."; } else if (status === 429) { errMsg = "DeepSeek rate limited. Wait and retry."; @@ -554,7 +635,10 @@ export class DeepSeekWebExecutor extends BaseExecutor { const errMsg = `DeepSeek error ${json.code}: ${json.msg}`; log?.warn?.("DEEPSEEK-WEB", errMsg); const status = json.code === 40003 ? 401 : json.code === 40002 ? 429 : 502; - if (json.code === 40003) tokenCache.delete(userToken); + if (json.code === 40003) { + tokenCache.delete(userToken); + sessionCache.delete((rawCreds.connectionId as string) || accessToken); + } return { response: errorResponse(status, errMsg, json.code), url: COMPLETION_URL,