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

@@ -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',