feat: Add support for OpenAI Responses endpoint and enhance proxy retry logic
- Updated proxyRetryPolicy to retry on all 4xx and 5xx status codes. - Introduced 'responses' target format in TestChatRequestPayload. - Implemented extractResponsesContent function to handle responses format in ModelTester. - Enhanced ModelTester to support responses target format and display appropriate labels. - Updated ProxyLogs to extract and display upstream paths from error messages. - Added tests to ensure responses target format is preserved during payload sync. - Created responses proxy route to handle requests to the new OpenAI Responses endpoint. - Updated upstreamEndpoint logic to include responses endpoint handling. - Added modelService tests to validate token route rebuilding logic.
This commit is contained in:
@@ -247,6 +247,21 @@ function parseClaudeMessageContent(content: unknown): string {
|
||||
return extractTextAndReasoning(content).content;
|
||||
}
|
||||
|
||||
function parseResponsesOutputText(payload: Record<string, unknown>): string {
|
||||
const direct = typeof payload.output_text === 'string' ? payload.output_text : '';
|
||||
if (direct) return direct;
|
||||
|
||||
const output = Array.isArray(payload.output) ? payload.output : [];
|
||||
const parts: string[] = [];
|
||||
for (const item of output) {
|
||||
if (!isRecord(item)) continue;
|
||||
const parsed = extractTextAndReasoning(item.content ?? item);
|
||||
if (parsed.content) parts.push(parsed.content);
|
||||
}
|
||||
|
||||
return parts.join('\n\n');
|
||||
}
|
||||
|
||||
function convertClaudeRequestToOpenAiBody(body: Record<string, unknown>): {
|
||||
model: string;
|
||||
stream: boolean;
|
||||
@@ -354,6 +369,19 @@ export function parseDownstreamChatRequest(
|
||||
};
|
||||
}
|
||||
|
||||
const hasMessages = Array.isArray(raw.messages) && raw.messages.length > 0;
|
||||
if (!hasMessages) {
|
||||
const hint = raw.input !== undefined
|
||||
? 'messages is required for /v1/chat/completions. For Responses payload, use /v1/responses.'
|
||||
: 'messages is required';
|
||||
return {
|
||||
error: {
|
||||
statusCode: 400,
|
||||
payload: { error: { message: hint, type: 'invalid_request_error' } },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
requestedModel: model,
|
||||
@@ -396,6 +424,17 @@ export function normalizeUpstreamFinalResponse(
|
||||
};
|
||||
}
|
||||
|
||||
if (isRecord(payload) && ((payload as any).object === 'response' || Array.isArray((payload as any).output))) {
|
||||
return {
|
||||
id: isNonEmptyString(payload.id) ? payload.id : fallbackId,
|
||||
model: isNonEmptyString(payload.model) ? payload.model : fallbackModel,
|
||||
created: ensureIntegerTimestamp(payload.created, now),
|
||||
content: parseResponsesOutputText(payload) || fallbackText,
|
||||
reasoningContent: '',
|
||||
finishReason: normalizeStopReason(payload.finish_reason ?? payload.status) || 'stop',
|
||||
};
|
||||
}
|
||||
|
||||
if (isRecord(payload) && Array.isArray(payload.candidates)) {
|
||||
const candidate = payload.candidates[0] || {};
|
||||
const parsedCandidate = extractTextAndReasoning(candidate?.content?.parts || candidate?.content);
|
||||
@@ -468,6 +507,25 @@ export function normalizeUpstreamStreamEvent(
|
||||
}
|
||||
|
||||
const type = typeof payload.type === 'string' ? payload.type : '';
|
||||
if (type.startsWith('response.output_text')) {
|
||||
const deltaText = typeof payload.delta === 'string'
|
||||
? payload.delta
|
||||
: extractTextAndReasoning(payload.delta).content;
|
||||
return {
|
||||
contentDelta: deltaText || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'response.completed' && isRecord((payload as any).response)) {
|
||||
const responsePayload = (payload as any).response as Record<string, unknown>;
|
||||
if (isNonEmptyString(responsePayload.id)) context.id = responsePayload.id;
|
||||
if (isNonEmptyString(responsePayload.model)) context.model = responsePayload.model;
|
||||
return {
|
||||
finishReason: normalizeStopReason(responsePayload.status) || 'stop',
|
||||
done: true,
|
||||
};
|
||||
}
|
||||
|
||||
const message = isRecord(payload.message) ? payload.message : null;
|
||||
|
||||
if (message) {
|
||||
|
||||
Reference in New Issue
Block a user