forked from boos/BoosAPI
fix: align explicit group route picker and details (#158)
* fix: align explicit group route picker and details * fix: move responses stream closeout into transformer facade
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { createResponsesProxyStreamSession } from './proxyStream.js';
|
||||
|
||||
describe('createResponsesProxyStreamSession', () => {
|
||||
it('serializes non-SSE fallback payloads into canonical responses SSE closeout events', () => {
|
||||
const lines: string[] = [];
|
||||
let ended = false;
|
||||
const usage = {
|
||||
promptTokens: 5,
|
||||
completionTokens: 3,
|
||||
totalTokens: 8,
|
||||
cacheReadTokens: 0,
|
||||
cacheCreationTokens: 0,
|
||||
promptTokensIncludeCache: null,
|
||||
};
|
||||
const payload = {
|
||||
id: 'resp_fallback_1',
|
||||
object: 'response',
|
||||
status: 'completed',
|
||||
model: 'gpt-5.2',
|
||||
output_text: 'hello from responses upstream',
|
||||
output: [
|
||||
{
|
||||
id: 'msg_fallback_1',
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
status: 'completed',
|
||||
content: [{ type: 'output_text', text: 'hello from responses upstream' }],
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
input_tokens: usage.promptTokens,
|
||||
output_tokens: usage.completionTokens,
|
||||
total_tokens: usage.totalTokens,
|
||||
},
|
||||
};
|
||||
|
||||
const session = createResponsesProxyStreamSession({
|
||||
modelName: 'gpt-5.2',
|
||||
successfulUpstreamPath: '/v1/responses',
|
||||
getUsage: () => usage,
|
||||
writeLines: (nextLines) => {
|
||||
lines.push(...nextLines);
|
||||
},
|
||||
writeRaw: () => {},
|
||||
});
|
||||
|
||||
const result = session.consumeUpstreamFinalPayload(
|
||||
payload,
|
||||
JSON.stringify(payload),
|
||||
{
|
||||
end() {
|
||||
ended = true;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
status: 'completed',
|
||||
errorMessage: null,
|
||||
});
|
||||
expect(ended).toBe(true);
|
||||
|
||||
const output = lines.join('');
|
||||
expect(output).toContain('event: response.created');
|
||||
expect(output).toContain('event: response.completed');
|
||||
expect(output).toContain('"type":"response.completed"');
|
||||
expect(output).toContain('"output_text":"hello from responses upstream"');
|
||||
expect(output).toContain('data: [DONE]');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createProxyStreamLifecycle } from '../../shared/protocolLifecycle.js';
|
||||
import { type ParsedSseEvent } from '../../shared/normalized.js';
|
||||
import { completeResponsesStream, createOpenAiResponsesAggregateState, failResponsesStream, serializeConvertedResponsesEvents } from './aggregator.js';
|
||||
import { openAiResponsesOutbound } from './outbound.js';
|
||||
import { openAiResponsesStream } from './stream.js';
|
||||
|
||||
type StreamReader = {
|
||||
@@ -148,6 +149,51 @@ export function createResponsesProxyStreamSession(input: ResponsesProxyStreamSes
|
||||
};
|
||||
|
||||
return {
|
||||
consumeUpstreamFinalPayload(payload: unknown, fallbackText: string, response?: ResponseSink): ResponsesProxyStreamResult {
|
||||
if (payload && typeof payload === 'object') {
|
||||
input.onParsedPayload?.(payload);
|
||||
}
|
||||
|
||||
const payloadType = (isRecord(payload) && typeof payload.type === 'string')
|
||||
? payload.type
|
||||
: '';
|
||||
if (payloadType === 'error' || payloadType === 'response.failed') {
|
||||
fail(payload);
|
||||
response?.end();
|
||||
return terminalResult;
|
||||
}
|
||||
|
||||
const normalizedFinal = openAiResponsesOutbound.normalizeFinal(payload, input.modelName, fallbackText);
|
||||
streamContext.id = normalizedFinal.id;
|
||||
streamContext.model = normalizedFinal.model;
|
||||
streamContext.created = normalizedFinal.created;
|
||||
|
||||
const streamPayload = openAiResponsesOutbound.serializeFinal({
|
||||
upstreamPayload: payload,
|
||||
normalized: normalizedFinal,
|
||||
usage: input.getUsage(),
|
||||
serializationMode: 'response',
|
||||
});
|
||||
const createdPayload = {
|
||||
...streamPayload,
|
||||
status: 'in_progress',
|
||||
output: [],
|
||||
output_text: '',
|
||||
};
|
||||
|
||||
finalized = true;
|
||||
terminalResult = {
|
||||
status: 'completed',
|
||||
errorMessage: null,
|
||||
};
|
||||
input.writeLines([
|
||||
`event: response.created\ndata: ${JSON.stringify({ type: 'response.created', response: createdPayload })}\n\n`,
|
||||
`event: response.completed\ndata: ${JSON.stringify({ type: 'response.completed', response: streamPayload })}\n\n`,
|
||||
'data: [DONE]\n\n',
|
||||
]);
|
||||
response?.end();
|
||||
return terminalResult;
|
||||
},
|
||||
async run(reader: StreamReader | null | undefined, response: ResponseSink): Promise<ResponsesProxyStreamResult> {
|
||||
const lifecycle = createProxyStreamLifecycle<ParsedSseEvent>({
|
||||
reader,
|
||||
|
||||
Reference in New Issue
Block a user