feat: align Go/Node DSML tool-call parsing drift tolerance and update API docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CJACK
2026-05-10 16:17:46 +08:00
parent cee8757d14
commit eaeb403fda
32 changed files with 879 additions and 102 deletions

View File

@@ -85,6 +85,33 @@ async function fetchStreamPow(req, leaseID) {
};
}
async function fetchStreamSwitch(req, leaseID) {
const url = buildInternalGoURL(req);
url.searchParams.set('__stream_switch', '1');
const upstream = await fetch(url.toString(), {
method: 'POST',
headers: buildInternalGoHeaders(req, { withInternalToken: true, withContentType: true }),
body: Buffer.from(JSON.stringify({ lease_id: leaseID })),
});
const text = await upstream.text();
let body = {};
try {
body = JSON.parse(text || '{}');
} catch (_err) {
body = {};
}
return {
ok: upstream.ok,
status: upstream.status,
contentType: upstream.headers.get('content-type') || 'application/json',
text,
body,
};
}
function relayPreparedFailure(res, prep) {
if (prep.status === 401 && looksLikeVercelAuthPage(prep.text)) {
writeOpenAIError(
@@ -223,6 +250,7 @@ module.exports = {
readRawBody,
fetchStreamPrepare,
fetchStreamPow,
fetchStreamSwitch,
relayPreparedFailure,
safeReadText,
buildInternalGoURL,

View File

@@ -7,9 +7,9 @@ const {
SKIP_EXACT_PATHS,
} = require('../shared/deepseek-constants');
const LEAKED_BOS_MARKER_PATTERN = /<[||]\s*begin[_▁]of[_▁]sentence\s*[||]>/gi;
const LEAKED_THOUGHT_MARKER_PATTERN = /<[||]\s*(?:begin[_▁])?[_▁]*of[_▁]thought\s*[||]>/gi;
const LEAKED_META_MARKER_PATTERN = /<[||]\s*(?:assistant|tool|end[_▁]of[_▁]sentence|end[_▁]of[_▁]thinking|end[_▁]of[_▁]thought|end[_▁]of[_▁]toolresults|end[_▁]of[_▁]instructions)\s*[||]>/gi;
const LEAKED_BOS_MARKER_PATTERN = /<[\|\uFF5C]\s*begin[_▁]of[_▁]sentence\s*[\|\uFF5C]>/gi;
const LEAKED_THOUGHT_MARKER_PATTERN = /<[\|\uFF5C]\s*(?:begin[_▁])?[_▁]*of[_▁]thought\s*[\|\uFF5C]>/gi;
const LEAKED_META_MARKER_PATTERN = /<[\|\uFF5C]\s*(?:assistant|tool|end[_▁]of[_▁]sentence|end[_▁]of[_▁]thinking|end[_▁]of[_▁]thought|end[_▁]of[_▁]toolresults|end[_▁]of[_▁]instructions)\s*[\|\uFF5C]>/gi;

View File

@@ -25,6 +25,7 @@ const {
isAbortError,
fetchStreamPrepare,
fetchStreamPow,
fetchStreamSwitch,
relayPreparedFailure,
createLeaseReleaser,
} = require('./http_internal');
@@ -46,11 +47,11 @@ async function handleVercelStream(req, res, rawBody, payload) {
}
const model = asString(prep.body.model) || asString(payload.model);
const sessionID = asString(prep.body.session_id) || `chatcmpl-${Date.now()}`;
const responseID = asString(prep.body.session_id) || `chatcmpl-${Date.now()}`;
const leaseID = asString(prep.body.lease_id);
const deepseekToken = asString(prep.body.deepseek_token);
let deepseekToken = asString(prep.body.deepseek_token);
const initialPowHeader = asString(prep.body.pow_header);
const completionPayload = prep.body.payload && typeof prep.body.payload === 'object' ? prep.body.payload : null;
let completionPayload = prep.body.payload && typeof prep.body.payload === 'object' ? prep.body.payload : null;
const finalPrompt = asString(prep.body.final_prompt);
const thinkingEnabled = toBool(prep.body.thinking_enabled);
const searchEnabled = toBool(prep.body.search_enabled);
@@ -133,13 +134,14 @@ async function handleVercelStream(req, res, rawBody, payload) {
}
};
const fetchCompletion = (bodyPayload) => fetchDeepSeekStream(DEEPSEEK_COMPLETION_URL, bodyPayload, currentPowHeader);
let activeDeepSeekSessionID = responseID;
const fetchContinue = async (messageID) => {
const powHeader = await refreshPowHeader('continue');
if (!powHeader) {
return null;
}
return fetchDeepSeekStream(DEEPSEEK_CONTINUE_URL, {
chat_session_id: sessionID,
chat_session_id: activeDeepSeekSessionID,
message_id: messageID,
fallback_to_resume: true,
}, powHeader);
@@ -185,7 +187,7 @@ async function handleVercelStream(req, res, rawBody, payload) {
let ended = false;
const { sendFrame, sendDeltaFrame } = createChatCompletionEmitter({
res,
sessionID,
sessionID: responseID,
created,
model,
isClosed: () => clientClosed,
@@ -242,7 +244,7 @@ async function handleVercelStream(req, res, rawBody, payload) {
}
ended = true;
sendFrame({
id: sessionID,
id: responseID,
object: 'chat.completion.chunk',
created,
model,
@@ -261,7 +263,7 @@ async function handleVercelStream(req, res, rawBody, payload) {
const processStream = async (initialResponse, allowDeferEmpty) => {
let currentResponse = initialResponse;
let continueState = createContinueState(sessionID);
let continueState = createContinueState(activeDeepSeekSessionID);
let continueRounds = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
@@ -412,13 +414,39 @@ async function handleVercelStream(req, res, rawBody, payload) {
};
let retryAttempts = 0;
let accountSwitchAttempted = false;
// eslint-disable-next-line no-constant-condition
while (true) {
const processed = await processStream(completionRes, retryAttempts < EMPTY_OUTPUT_RETRY_MAX_ATTEMPTS);
const allowDeferEmpty = retryAttempts < EMPTY_OUTPUT_RETRY_MAX_ATTEMPTS || !accountSwitchAttempted;
const processed = await processStream(completionRes, allowDeferEmpty);
if (processed.terminal) {
return;
}
if (!processed.retryable || retryAttempts >= EMPTY_OUTPUT_RETRY_MAX_ATTEMPTS) {
if (!processed.retryable) {
await finish('stop');
return;
}
if (retryAttempts >= EMPTY_OUTPUT_RETRY_MAX_ATTEMPTS) {
if (!accountSwitchAttempted) {
accountSwitchAttempted = true;
const switched = await fetchStreamSwitch(req, leaseID);
if (switched.ok && switched.body && switched.body.payload && typeof switched.body.payload === 'object') {
completionPayload = switched.body.payload;
deepseekToken = asString(switched.body.deepseek_token) || deepseekToken;
currentPowHeader = asString(switched.body.pow_header) || currentPowHeader;
activeDeepSeekSessionID = asString(switched.body.session_id) || activeDeepSeekSessionID;
usagePrompt = finalPrompt;
completionRes = await fetchCompletion(completionPayload);
if (completionRes === null) {
return;
}
if (!completionRes.ok || !completionRes.body) {
await finish('stop');
return;
}
continue;
}
}
await finish('stop');
return;
}