feat(proxy): enhance error handling for chat proxy requests with fallback logic
This commit is contained in:
@@ -1845,6 +1845,67 @@ describe('chat proxy stream behavior', () => {
|
|||||||
expect(secondUrl).toContain('/v1/chat/completions');
|
expect(secondUrl).toContain('/v1/chat/completions');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('falls back to /v1/responses for /v1/chat/completions when messages/chat endpoints return 502', async () => {
|
||||||
|
selectChannelMock.mockReturnValue({
|
||||||
|
channel: { id: 11, routeId: 22 },
|
||||||
|
site: { name: 'generic-site', url: 'https://generic.example.com', platform: 'new-api' },
|
||||||
|
account: { id: 33, username: 'demo-user' },
|
||||||
|
tokenName: 'default',
|
||||||
|
tokenValue: 'sk-generic',
|
||||||
|
actualModel: 'claude-haiku-4-5-20251001',
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchMock
|
||||||
|
.mockResolvedValueOnce(new Response(
|
||||||
|
'<!DOCTYPE html><html><head><title>502 Bad Gateway</title></head><body>Cloudflare</body></html>',
|
||||||
|
{
|
||||||
|
status: 502,
|
||||||
|
headers: { 'content-type': 'text/html; charset=UTF-8' },
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.mockResolvedValueOnce(new Response(
|
||||||
|
'<!DOCTYPE html><html><head><title>502 Bad Gateway</title></head><body>Cloudflare</body></html>',
|
||||||
|
{
|
||||||
|
status: 502,
|
||||||
|
headers: { 'content-type': 'text/html; charset=UTF-8' },
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.mockResolvedValueOnce(new Response(JSON.stringify({
|
||||||
|
id: 'resp_anyrouter_fallback',
|
||||||
|
object: 'response',
|
||||||
|
model: 'claude-haiku-4-5-20251001',
|
||||||
|
status: 'completed',
|
||||||
|
output_text: 'ok via responses fallback after 502',
|
||||||
|
usage: { input_tokens: 5, output_tokens: 2, total_tokens: 7 },
|
||||||
|
}), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/v1/chat/completions',
|
||||||
|
payload: {
|
||||||
|
model: 'claude-haiku-4-5-20251001',
|
||||||
|
stream: false,
|
||||||
|
messages: [{ role: 'user', content: 'hello' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
|
const [firstUrl] = fetchMock.mock.calls[0] as [string, any];
|
||||||
|
const [secondUrl] = fetchMock.mock.calls[1] as [string, any];
|
||||||
|
const [thirdUrl] = fetchMock.mock.calls[2] as [string, any];
|
||||||
|
expect(firstUrl).toContain('/v1/messages');
|
||||||
|
expect(secondUrl).toContain('/v1/chat/completions');
|
||||||
|
expect(thirdUrl).toContain('/v1/responses');
|
||||||
|
|
||||||
|
const body = response.json();
|
||||||
|
expect(body?.choices?.[0]?.message?.content).toContain('ok via responses fallback after 502');
|
||||||
|
});
|
||||||
|
|
||||||
it('forces openai platform to use /v1/chat/completions for claude downstream requests', async () => {
|
it('forces openai platform to use /v1/chat/completions for claude downstream requests', async () => {
|
||||||
selectChannelMock.mockReturnValue({
|
selectChannelMock.mockReturnValue({
|
||||||
channel: { id: 11, routeId: 22 },
|
channel: { id: 11, routeId: 22 },
|
||||||
|
|||||||
@@ -285,7 +285,8 @@ async function handleChatProxyRequest(
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
shouldDowngrade: (ctx) => (
|
shouldDowngrade: (ctx) => (
|
||||||
isEndpointDowngradeError(ctx.response.status, ctx.rawErrText)
|
ctx.response.status >= 500
|
||||||
|
|| isEndpointDowngradeError(ctx.response.status, ctx.rawErrText)
|
||||||
|| isMessagesRequiredError(ctx.rawErrText)
|
|| isMessagesRequiredError(ctx.rawErrText)
|
||||||
),
|
),
|
||||||
onDowngrade: (ctx) => {
|
onDowngrade: (ctx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user