mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-13 18:34:51 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e938acda | ||
|
|
b08d3f926e |
No files matched your search
@@ -633,6 +633,40 @@ describe('ChatCompletionDriver exhausted-chain classification', () => {
|
||||
expect(err.message).toBe('unsupported parameter: top_k');
|
||||
});
|
||||
|
||||
it('maps a gateway 4xx with no upstream error body to 400 upstream_provider_unavailable', async () => {
|
||||
const err = await errorFor({
|
||||
status: 400,
|
||||
message: '400 bad response status code 400',
|
||||
error: {
|
||||
type: 'upstream_error',
|
||||
message: 'bad response status code 400',
|
||||
code: 400,
|
||||
},
|
||||
});
|
||||
expect(err).toMatchObject({
|
||||
statusCode: 400,
|
||||
legacyCode: 'upstream_provider_unavailable',
|
||||
});
|
||||
expect(err.message).toBe('AI provider unavailable');
|
||||
});
|
||||
|
||||
it('keeps a gateway 4xx that carries the vendor message as the caller’s upstream_bad_request', async () => {
|
||||
const err = await errorFor({
|
||||
status: 400,
|
||||
message:
|
||||
'400 bad response status code 400, message: max_tokens too large, body: {}',
|
||||
error: {
|
||||
type: 'upstream_error',
|
||||
message:
|
||||
'bad response status code 400, message: max_tokens too large, body: {}',
|
||||
},
|
||||
});
|
||||
expect(err).toMatchObject({
|
||||
statusCode: 400,
|
||||
legacyCode: 'upstream_bad_request',
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a completion with no choices to a 400, not an internal 500', async () => {
|
||||
vi.spyOn(FakeChatProvider.prototype, 'complete').mockImplementation(
|
||||
() =>
|
||||
@@ -809,6 +843,26 @@ describe('ChatCompletionDriver cross-provider fallback', () => {
|
||||
expect(line).toContain('first 422');
|
||||
});
|
||||
|
||||
it('falls back past a gateway 4xx with no upstream error body and marks the route unhealthy', async () => {
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||
vi.spyOn(AzureChatProvider.prototype, 'complete').mockRejectedValue(
|
||||
Object.assign(new Error('400 bad response status code 400'), {
|
||||
status: 400,
|
||||
error: { type: 'upstream_error' },
|
||||
}),
|
||||
);
|
||||
vi.spyOn(OpenAiChatProvider.prototype, 'complete').mockResolvedValue({
|
||||
message: { role: 'assistant', content: 'from the fallback' },
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1 },
|
||||
} as never);
|
||||
|
||||
const result = (await completeShared()) as {
|
||||
message: { content: string };
|
||||
};
|
||||
expect(result.message.content).toBe('from the fallback');
|
||||
expect(isRouteUnhealthy('azure-openai', SHARED_MODEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('surfaces the primary route-level 4xx after a non-strict fallback also fails', async () => {
|
||||
vi.spyOn(AzureChatProvider.prototype, 'complete').mockRejectedValue(
|
||||
Object.assign(new Error('unsupported parameter: frobnicate'), {
|
||||
|
||||
@@ -323,8 +323,17 @@ const isAuthFailure = (a: ProviderAttempt) =>
|
||||
a.status === 403 ||
|
||||
/unauthorized|forbidden|invalid api key/i.test(a.error));
|
||||
|
||||
/**
|
||||
* A new-api gateway's (Infron's) wrapper for an upstream that answered without
|
||||
* a JSON error body. It mirrors the upstream's status, so the 4xx says nothing
|
||||
* about the caller's request; a real vendor rejection carries its own message.
|
||||
*/
|
||||
const isOpaqueGatewayFailure = (a: ProviderAttempt) =>
|
||||
/bad response status code \d+(?:,\s*body:|$)/i.test(a.error);
|
||||
|
||||
const isUpstream5xx = (a: ProviderAttempt) =>
|
||||
(a.status !== undefined && a.status >= 500) ||
|
||||
isOpaqueGatewayFailure(a) ||
|
||||
/provider returned error|internal server error|service unavailable|bad gateway/i.test(
|
||||
a.error,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user