修复首次会话等待与上游饱和重试

This commit is contained in:
2026-08-12 23:26:19 +08:00
parent b02c3f22e1
commit 08da976634
10 changed files with 506 additions and 14 deletions

View File

@@ -336,20 +336,21 @@ describe('ai proxy routes', () => {
expect(response.body()).toContain('works_square_gateway_authorize_failed');
});
it('logs a sanitized one-api error summary when the upstream group is saturated', async () => {
it('maps explicit upstream group saturation to a non-retryable response status', async () => {
seedWorksSquareAIGatewayCredential({
accessToken: 'ws-ai-token',
expiresIn: 3600,
oneApiBaseUrl: 'https://one-api.example.com/v1',
});
const body = JSON.stringify({
error: {
message: '当前分组上游负载已饱和,请稍后再试 (request id: req-saturated)',
code: 'rate_limit_exceeded',
type: 'one_api_error',
},
});
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
error: {
message: '当前分组上游负载已饱和,请稍后再试 (request id: req-saturated)',
code: 'rate_limit_exceeded',
type: 'one_api_error',
},
}), {
new Response(body, {
status: 429,
headers: { 'content-type': 'application/json' },
}),
@@ -364,8 +365,10 @@ describe('ai proxy routes', () => {
{} as never,
);
expect(response.statusCode).toBe(429);
expect(response.body()).toContain('当前分组上游负载已饱和');
expect(fetchMock).toHaveBeenCalledOnce();
expect(response.statusCode).toBe(400);
expect(response.header('content-type')).toContain('application/json');
expect(response.body()).toBe(body);
expect(loggerWarnMock).toHaveBeenCalledWith(
'[ai-proxy] One-api returned non-success response',
expect.objectContaining({
@@ -380,6 +383,40 @@ describe('ai proxy routes', () => {
expect(JSON.stringify(loggerWarnMock.mock.calls)).not.toContain('ws-ai-token');
});
it('preserves a generic rate-limit response without explicit saturation evidence', async () => {
seedWorksSquareAIGatewayCredential({
accessToken: 'ws-ai-token',
expiresIn: 3600,
oneApiBaseUrl: 'https://one-api.example.com/v1',
});
const body = JSON.stringify({
error: {
message: 'Rate limit exceeded',
code: 'rate_limit_exceeded',
type: 'one_api_error',
},
});
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(body, {
status: 429,
headers: { 'content-type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
await handleAiProxyRoutes(
createRequest('POST', { model: 'qwen3.7-max', messages: [] }),
response.res,
new URL('http://127.0.0.1:13210/api/ai-proxy/v1/chat/completions'),
{} as never,
);
expect(fetchMock).toHaveBeenCalledOnce();
expect(response.statusCode).toBe(429);
expect(response.body()).toBe(body);
});
it('strips decoded compression headers from proxied responses', async () => {
seedWorksSquareAIGatewayCredential({
accessToken: 'ws-ai-token',