mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-11 03:37:40 +08:00
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:
@@ -224,6 +224,80 @@ test('vercel stream retries thinking-only output once', async () => {
|
||||
assert.equal(parsed[2].choices[0].finish_reason, 'stop');
|
||||
});
|
||||
|
||||
test('vercel stream switches managed account after empty retry exhaustion', async () => {
|
||||
const originalFetch = global.fetch;
|
||||
const fetchURLs = [];
|
||||
const completionBodies = [];
|
||||
const completionAuth = [];
|
||||
let completionCalls = 0;
|
||||
global.fetch = async (url, init = {}) => {
|
||||
const textURL = String(url);
|
||||
fetchURLs.push(textURL);
|
||||
if (textURL.includes('__stream_prepare=1')) {
|
||||
return jsonResponse({
|
||||
session_id: 'chatcmpl-test',
|
||||
lease_id: 'lease-test',
|
||||
model: 'gpt-test',
|
||||
final_prompt: 'hello',
|
||||
thinking_enabled: true,
|
||||
search_enabled: false,
|
||||
tool_names: [],
|
||||
deepseek_token: 'token-1',
|
||||
pow_header: 'pow-1',
|
||||
payload: { chat_session_id: 'session-1', prompt: 'hello', ref_file_ids: ['file-1'] },
|
||||
});
|
||||
}
|
||||
if (textURL.includes('__stream_pow=1')) {
|
||||
return jsonResponse({ pow_header: 'pow-retry' });
|
||||
}
|
||||
if (textURL.includes('__stream_switch=1')) {
|
||||
return jsonResponse({
|
||||
session_id: 'session-2',
|
||||
lease_id: 'lease-test',
|
||||
model: 'gpt-test',
|
||||
final_prompt: 'hello',
|
||||
thinking_enabled: true,
|
||||
search_enabled: false,
|
||||
tool_names: [],
|
||||
deepseek_token: 'token-2',
|
||||
pow_header: 'pow-2',
|
||||
payload: { chat_session_id: 'session-2', prompt: 'hello', ref_file_ids: ['file-2'] },
|
||||
});
|
||||
}
|
||||
if (textURL.includes('__stream_release=1')) {
|
||||
return jsonResponse({ success: true });
|
||||
}
|
||||
if (textURL === 'https://chat.deepseek.com/api/v0/chat/completion') {
|
||||
completionBodies.push(JSON.parse(String(init.body)));
|
||||
completionAuth.push(init.headers.authorization);
|
||||
completionCalls += 1;
|
||||
if (completionCalls <= 2) {
|
||||
return sseResponse([`data: {"response_message_id":${40 + completionCalls},"p":"response/thinking_content","v":"plan"}\n\n`, 'data: [DONE]\n\n']);
|
||||
}
|
||||
return sseResponse(['data: {"p":"response/content","v":"visible"}\n\n', 'data: [DONE]\n\n']);
|
||||
}
|
||||
throw new Error(`unexpected fetch url: ${textURL}`);
|
||||
};
|
||||
try {
|
||||
const req = new MockStreamRequest();
|
||||
const res = new MockStreamResponse();
|
||||
const payload = { model: 'gpt-test', stream: true };
|
||||
await handleVercelStream(req, res, Buffer.from(JSON.stringify(payload)), payload);
|
||||
const frames = parseSSEDataFrames(res.bodyText());
|
||||
const parsed = frames.filter((frame) => frame !== '[DONE]').map((frame) => JSON.parse(frame));
|
||||
assert.equal(fetchURLs.filter((url) => url.includes('__stream_switch=1')).length, 1);
|
||||
assert.equal(completionBodies.length, 3);
|
||||
assert.match(completionBodies[1].prompt, /Previous reply had no visible output/);
|
||||
assert.equal(completionBodies[1].parent_message_id, 41);
|
||||
assert.equal(completionBodies[2].prompt, 'hello');
|
||||
assert.deepEqual(completionBodies[2].ref_file_ids, ['file-2']);
|
||||
assert.deepEqual(completionAuth, ['Bearer token-1', 'Bearer token-1', 'Bearer token-2']);
|
||||
assert.equal(parsed.at(-1).choices[0].finish_reason, 'stop');
|
||||
} finally {
|
||||
global.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('vercel stream coalesces many small content deltas while keeping one choice', async () => {
|
||||
const lines = Array.from({ length: 100 }, () => `data: ${JSON.stringify({ p: 'response/content', v: '字' })}\n\n`);
|
||||
lines.push('data: [DONE]\n\n');
|
||||
@@ -653,6 +727,17 @@ test('parseChunkForContent strips leaked thought control markers from content',
|
||||
assert.deepEqual(parsed.parts, [{ text: 'ABC', type: 'text' }]);
|
||||
});
|
||||
|
||||
test('parseChunkForContent strips fullwidth-delimited leaked control markers from content', () => {
|
||||
const fw = '\uff5c';
|
||||
const chunk = {
|
||||
p: 'response/content',
|
||||
v: `<${fw}begin▁of▁sentence${fw}>A<${fw}▁of▁thought${fw}>B<${fw} end_of_sentence ${fw}>C`,
|
||||
};
|
||||
const parsed = parseChunkForContent(chunk, false, 'text');
|
||||
assert.equal(parsed.finished, false);
|
||||
assert.deepEqual(parsed.parts, [{ text: 'ABC', type: 'text' }]);
|
||||
});
|
||||
|
||||
test('parseChunkForContent detects content_filter status and ignores upstream output tokens', () => {
|
||||
const chunk = {
|
||||
p: 'response',
|
||||
|
||||
Reference in New Issue
Block a user