🐛 fix: preserve error message in ChatCompletionErrorPayload (#13736)

* 🐛 fix: preserve error message in ChatCompletionErrorPayload for ProviderBizError

Add `message` field to `ChatCompletionErrorPayload` and extract SDK error messages in `handleOpenAIError` and `handleAnthropicError`, so downstream consumers (agent tracing, error state) receive human-readable error details instead of generic "ProviderBizError".

Closes LOBE-7019

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 🐛 fix: guard nullish error in handleAnthropicError

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arvin Xu 2026-04-11 23:42:03 +08:00 committed by GitHub
parent 08769e5bf1
commit fb7f0c3e92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 57 additions and 25 deletions

View file

@ -1,5 +1,9 @@
export const handleAnthropicError = (error: any) => {
let errorResult: any = error;
export const handleAnthropicError = (error: any): { errorResult: any; message?: string } => {
if (!error) {
return { errorResult: { message: 'Unknown error' }, message: 'Unknown error' };
}
let errorResult: any;
if (error.error) {
errorResult = error.error;
@ -11,5 +15,5 @@ export const handleAnthropicError = (error: any) => {
errorResult = { headers: error.headers, stack: error.stack, status: error.status };
}
return { errorResult };
return { errorResult, message: error.message || errorResult?.message };
};

View file

@ -291,7 +291,7 @@ export const handleDefaultAnthropicError = <T extends Record<string, any> = any>
}
}
const { errorResult } = handleAnthropicError(error);
const { errorResult, message } = handleAnthropicError(error);
const errorMsg = errorResult.message || errorResult.error?.message;
if (isExceededContextWindowError(errorMsg)) {
@ -299,6 +299,7 @@ export const handleDefaultAnthropicError = <T extends Record<string, any> = any>
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.ExceededContextWindow,
message,
};
}
@ -307,6 +308,7 @@ export const handleDefaultAnthropicError = <T extends Record<string, any> = any>
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.QuotaLimitReached,
message,
};
}
@ -314,6 +316,7 @@ export const handleDefaultAnthropicError = <T extends Record<string, any> = any>
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.ProviderBizError,
message,
};
};
@ -676,17 +679,7 @@ export const createAnthropicCompatibleRuntime = <T extends Record<string, any> =
}
}
const errorResult = (() => {
if (error?.error) {
const innerError = error.error;
if ('error' in innerError) {
return innerError.error;
}
return innerError;
}
return { headers: error?.headers, stack: error?.stack, status: error?.status };
})();
const { errorResult, message } = handleAnthropicError(error);
const errorMsg = errorResult.message || errorResult.error?.message;
if (isExceededContextWindowError(errorMsg)) {
@ -694,6 +687,7 @@ export const createAnthropicCompatibleRuntime = <T extends Record<string, any> =
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.ExceededContextWindow,
message,
provider: this.id,
});
}
@ -703,6 +697,7 @@ export const createAnthropicCompatibleRuntime = <T extends Record<string, any> =
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.QuotaLimitReached,
message,
provider: this.id,
});
}
@ -711,6 +706,7 @@ export const createAnthropicCompatibleRuntime = <T extends Record<string, any> =
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: ErrorType.bizError,
message,
provider: this.id,
});
}

View file

@ -634,6 +634,7 @@ describe('LobeOpenAICompatibleFactory', () => {
status: 400,
},
errorType: bizErrorType,
message: expect.any(String),
provider,
});
}
@ -672,6 +673,7 @@ describe('LobeOpenAICompatibleFactory', () => {
cause: { message: 'api is undefined' },
},
errorType: bizErrorType,
message: expect.any(String),
provider,
});
}
@ -706,6 +708,7 @@ describe('LobeOpenAICompatibleFactory', () => {
cause: { message: 'api is undefined' },
},
errorType: bizErrorType,
message: expect.any(String),
provider,
});
}
@ -784,6 +787,7 @@ describe('LobeOpenAICompatibleFactory', () => {
status: 400,
},
errorType: AgentRuntimeErrorType.InsufficientQuota,
message: expect.any(String),
provider,
});
}
@ -822,6 +826,7 @@ describe('LobeOpenAICompatibleFactory', () => {
status: 400,
},
errorType: AgentRuntimeErrorType.ExceededContextWindow,
message: expect.any(String),
provider,
});
}
@ -858,6 +863,7 @@ describe('LobeOpenAICompatibleFactory', () => {
status: 429,
},
errorType: AgentRuntimeErrorType.QuotaLimitReached,
message: expect.any(String),
provider,
});
}
@ -885,6 +891,7 @@ describe('LobeOpenAICompatibleFactory', () => {
name: genericError.name,
},
errorType: 'AgentRuntimeError',
message: expect.any(String),
provider,
});
}
@ -1899,6 +1906,7 @@ describe('LobeOpenAICompatibleFactory', () => {
status: 400,
},
errorType: AgentRuntimeErrorType.ExceededContextWindow,
message: expect.any(String),
provider,
});
});

View file

@ -961,7 +961,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
}
}
const { errorResult, RuntimeError } = handleOpenAIError(error);
const { errorResult, RuntimeError, message } = handleOpenAIError(error);
log('error code: %s, message: %s', errorResult.code, errorResult.message);
@ -973,6 +973,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.InsufficientQuota,
message,
provider: this.id,
});
}
@ -984,6 +985,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.InsufficientQuota,
message,
provider: this.id,
});
}
@ -994,6 +996,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.ModelNotFound,
message,
provider: this.id,
});
}
@ -1006,6 +1009,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.ExceededContextWindow,
message,
provider: this.id,
});
}
@ -1018,6 +1022,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.ExceededContextWindow,
message,
provider: this.id,
});
}
@ -1028,6 +1033,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: AgentRuntimeErrorType.QuotaLimitReached,
message,
provider: this.id,
});
}
@ -1037,6 +1043,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
endpoint: desensitizedEndpoint,
error: errorResult,
errorType: RuntimeError || ErrorType.bizError,
message,
provider: this.id,
});
}

View file

@ -162,6 +162,7 @@ export const testProvider = ({
status: 400,
},
errorType: bizErrorType,
message: expect.any(String),
provider,
});
}
@ -204,6 +205,7 @@ export const testProvider = ({
cause: { message: 'api is undefined' },
},
errorType: bizErrorType,
message: expect.any(String),
provider,
});
}
@ -241,6 +243,7 @@ export const testProvider = ({
cause: { message: 'api is undefined' },
},
errorType: bizErrorType,
message: expect.any(String),
provider,
});
}
@ -300,6 +303,7 @@ export const testProvider = ({
name: genericError.name,
},
errorType: 'AgentRuntimeError',
message: expect.any(String),
provider,
});
}

View file

@ -420,6 +420,7 @@ describe('LobeAnthropicAI', () => {
endpoint: 'https://api.anthropic.com',
error: apiError.error.error,
errorType: bizErrorType,
message: "Anthropic's API is temporarily overloaded",
provider,
});
}

View file

@ -625,6 +625,7 @@ describe('LobeKimiCodingPlanAI', () => {
endpoint: 'https://api.***.com/coding',
error: apiError.error.error,
errorType: bizErrorType,
message: 'API is temporarily overloaded',
provider,
});
}

View file

@ -86,6 +86,7 @@ describe('LobeOpenAI', () => {
status: 400,
},
errorType: 'ProviderBizError',
message: expect.any(String),
provider: 'openai',
});
}
@ -124,6 +125,7 @@ describe('LobeOpenAI', () => {
cause: { message: 'api is undefined' },
},
errorType: 'ProviderBizError',
message: expect.any(String),
provider: 'openai',
});
}
@ -158,6 +160,7 @@ describe('LobeOpenAI', () => {
cause: { message: 'api is undefined' },
},
errorType: 'ProviderBizError',
message: expect.any(String),
provider: 'openai',
});
}
@ -185,6 +188,7 @@ describe('LobeOpenAI', () => {
name: genericError.name,
},
errorType: 'AgentRuntimeError',
message: expect.any(String),
provider: 'openai',
});
}

View file

@ -14,6 +14,7 @@ export interface ChatCompletionErrorPayload {
endpoint?: string;
error: object;
errorType: ILobeAgentRuntimeErrorType;
message?: string;
provider: string;
}

View file

@ -16,9 +16,10 @@ describe('handleOpenAIError', () => {
const result = handleOpenAIError(apiError);
expect(result).toEqual({
errorResult: { error: { message: 'API error', type: 'invalid_request' } },
expect(result.errorResult).toEqual({
error: { message: 'API error', type: 'invalid_request' },
});
expect(result.message).toBe(apiError.message);
expect(result.RuntimeError).toBeUndefined();
});
@ -31,9 +32,8 @@ describe('handleOpenAIError', () => {
const result = handleOpenAIError(apiError);
expect(result).toEqual({
errorResult: cause,
});
expect(result.errorResult).toEqual(cause);
expect(result.message).toBe(apiError.message);
expect(result.RuntimeError).toBeUndefined();
});
@ -50,6 +50,7 @@ describe('handleOpenAIError', () => {
headers: { headers, status: 401 },
status: 472,
});
expect(result.message).toBe(apiError.message);
expect(result.RuntimeError).toBeUndefined();
});
@ -64,9 +65,8 @@ describe('handleOpenAIError', () => {
const result = handleOpenAIError(apiError);
// Should prioritize error over cause
expect(result).toEqual({
errorResult: { error: errorObject },
});
expect(result.errorResult).toEqual({ error: errorObject });
expect(result.message).toBe(apiError.message);
});
});
@ -84,6 +84,7 @@ describe('handleOpenAIError', () => {
message: 'Generic error',
name: 'Error',
},
message: 'Generic error',
});
});
@ -99,6 +100,7 @@ describe('handleOpenAIError', () => {
message: 'Simple error',
name: 'Error',
},
message: 'Simple error',
});
});
@ -120,6 +122,7 @@ describe('handleOpenAIError', () => {
message: 'Custom error message',
name: 'CustomError',
},
message: 'Custom error message',
});
});
@ -138,6 +141,7 @@ describe('handleOpenAIError', () => {
message: 'Object error',
name: undefined,
},
message: 'Object error',
});
});
});

View file

@ -4,7 +4,7 @@ import { AgentRuntimeErrorType } from '../types/error';
export const handleOpenAIError = (
error: any,
): { RuntimeError?: 'AgentRuntimeError'; errorResult: any } => {
): { RuntimeError?: 'AgentRuntimeError'; errorResult: any; message?: string } => {
let errorResult: any;
// Check if the error is an OpenAI APIError
@ -25,6 +25,7 @@ export const handleOpenAIError = (
return {
errorResult,
message: error.message,
};
} else {
const err = error as Error;
@ -34,6 +35,7 @@ export const handleOpenAIError = (
return {
RuntimeError: AgentRuntimeErrorType.AgentRuntimeError,
errorResult,
message: err.message,
};
}
};