1
0
forked from boos/BoosAPI

fix: preserve repeated short responses deltas (#336)

This commit is contained in:
Cita
2026-03-30 22:54:05 +08:00
committed by GitHub
parent 5d54e92dc6
commit eb65a2be88
2 changed files with 55 additions and 2 deletions
@@ -2039,6 +2039,51 @@ describe('chat proxy stream behavior', () => {
expect(response.body).toContain('"text":"The user asked \\"whoru\\" which is a common internet slang and shorthand for who are you."');
});
it('preserves legitimate repeated short deltas when /v1/responses is converted from /v1/messages stream', async () => {
fetchModelPricingCatalogMock.mockResolvedValue({
models: [
{
modelName: 'upstream-gpt',
supportedEndpointTypes: ['/v1/messages'],
},
],
groupRatio: {},
});
const encoder = new TextEncoder();
const upstreamBody = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(encoder.encode('event: message_start\ndata: {"type":"message_start","message":{"id":"msg_repeat_short_1","model":"upstream-gpt"}}\n\n'));
controller.enqueue(encoder.encode('event: content_block_delta\ndata: {"type":"content_block_delta","delta":{"type":"text_delta","text":"ha"}}\n\n'));
controller.enqueue(encoder.encode('event: content_block_delta\ndata: {"type":"content_block_delta","delta":{"type":"text_delta","text":"ha"}}\n\n'));
controller.enqueue(encoder.encode('event: message_stop\ndata: {"type":"message_stop"}\n\n'));
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
});
fetchMock.mockResolvedValue(new Response(upstreamBody, {
status: 200,
headers: { 'content-type': 'text/event-stream; charset=utf-8' },
}));
const response = await app.inject({
method: 'POST',
url: '/v1/responses',
payload: {
model: 'claude-sonnet-4-6',
stream: true,
input: 'laugh',
},
});
expect(response.statusCode).toBe(200);
const deltaMatches = response.body.match(/event: response\.output_text\.delta/g) || [];
expect(deltaMatches.length).toBe(2);
expect(response.body).toContain('"delta":"ha"');
expect(response.body).toContain('"text":"haha"');
});
it('preserves function_call/function_call_output when /v1/responses falls back to /v1/chat/completions', async () => {
fetchModelPricingCatalogMock.mockResolvedValue({
models: [
@@ -656,13 +656,21 @@ function mergeImageGenerationFields(
}
function computeNovelDelta(existingText: string, incomingDelta: string): string {
const replayWindowMinLength = 24;
if (!incomingDelta) return '';
if (!existingText) return incomingDelta;
if (existingText.endsWith(incomingDelta)) return '';
if (existingText.endsWith(incomingDelta)) {
return incomingDelta.length >= replayWindowMinLength ? '' : incomingDelta;
}
if (incomingDelta.startsWith(existingText)) {
return incomingDelta.slice(existingText.length);
}
if (existingText.includes(incomingDelta)) return '';
if (
incomingDelta.length >= replayWindowMinLength
&& existingText.includes(incomingDelta)
) {
return '';
}
const maxOverlap = Math.min(existingText.length, incomingDelta.length);
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {