@lobehub/chat 1.110.4 → 1.110.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.110.5](https://github.com/lobehub/lobe-chat/compare/v1.110.4...v1.110.5)
6
+
7
+ <sup>Released on **2025-08-07**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Optimize Gemini error message display & Filter empty messages.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Optimize Gemini error message display & Filter empty messages, closes [#8489](https://github.com/lobehub/lobe-chat/issues/8489) ([5b409cc](https://github.com/lobehub/lobe-chat/commit/5b409cc))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.110.4](https://github.com/lobehub/lobe-chat/compare/v1.110.3...v1.110.4)
6
31
 
7
32
  <sup>Released on **2025-08-06**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Optimize Gemini error message display & Filter empty messages."
6
+ ]
7
+ },
8
+ "date": "2025-08-07",
9
+ "version": "1.110.5"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.110.4",
3
+ "version": "1.110.5",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -14,7 +14,7 @@ import { imageUrlToBase64 } from '@/utils/imageToBase64';
14
14
  import { safeParseJSON } from '@/utils/safeParseJSON';
15
15
 
16
16
  import { LobeRuntimeAI } from '../BaseAI';
17
- import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../error';
17
+ import { AgentRuntimeErrorType } from '../error';
18
18
  import {
19
19
  ChatCompletionTool,
20
20
  ChatMethodOptions,
@@ -25,6 +25,7 @@ import {
25
25
  import { CreateImagePayload, CreateImageResponse } from '../types/image';
26
26
  import { AgentRuntimeError } from '../utils/createError';
27
27
  import { debugStream } from '../utils/debugStream';
28
+ import { parseGoogleErrorMessage } from '../utils/googleErrorParser';
28
29
  import { StreamingResponse } from '../utils/response';
29
30
  import { GoogleGenerativeAIStream, VertexAIStream } from '../utils/streams';
30
31
  import { parseDataUri } from '../utils/uriParser';
@@ -249,7 +250,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
249
250
  }
250
251
 
251
252
  console.log(err);
252
- const { errorType, error } = this.parseErrorMessage(err.message);
253
+ const { errorType, error } = parseGoogleErrorMessage(err.message);
253
254
 
254
255
  throw AgentRuntimeError.chat({ error, errorType, provider: this.provider });
255
256
  }
@@ -292,7 +293,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
292
293
  const err = error as Error;
293
294
  console.error('Google AI image generation error:', err);
294
295
 
295
- const { errorType, error: parsedError } = this.parseErrorMessage(err.message);
296
+ const { errorType, error: parsedError } = parseGoogleErrorMessage(err.message);
296
297
  throw AgentRuntimeError.createImage({
297
298
  error: parsedError,
298
299
  errorType,
@@ -509,59 +510,12 @@ export class LobeGoogleAI implements LobeRuntimeAI {
509
510
  .filter((message) => message.role !== 'function')
510
511
  .map(async (msg) => await this.convertOAIMessagesToGoogleMessage(msg, toolCallNameMap));
511
512
 
512
- return Promise.all(pools);
513
+ const contents = await Promise.all(pools);
514
+
515
+ // 筛除空消息: contents.parts must not be empty.
516
+ return contents.filter((content: Content) => content.parts && content.parts.length > 0);
513
517
  };
514
518
 
515
- private parseErrorMessage(message: string): {
516
- error: any;
517
- errorType: ILobeAgentRuntimeErrorType;
518
- } {
519
- const defaultError = {
520
- error: { message },
521
- errorType: AgentRuntimeErrorType.ProviderBizError,
522
- };
523
-
524
- if (message.includes('location is not supported'))
525
- return { error: { message }, errorType: AgentRuntimeErrorType.LocationNotSupportError };
526
-
527
- const startIndex = message.lastIndexOf('[');
528
- if (startIndex === -1) {
529
- return defaultError;
530
- }
531
-
532
- try {
533
- // 从开始位置截取字符串到最后
534
- const jsonString = message.slice(startIndex);
535
-
536
- // 尝试解析 JSON 字符串
537
- const json: GoogleChatErrors = JSON.parse(jsonString);
538
-
539
- const bizError = json[0];
540
-
541
- switch (bizError.reason) {
542
- case 'API_KEY_INVALID': {
543
- return { ...defaultError, errorType: AgentRuntimeErrorType.InvalidProviderAPIKey };
544
- }
545
-
546
- default: {
547
- return { error: json, errorType: AgentRuntimeErrorType.ProviderBizError };
548
- }
549
- }
550
- } catch {
551
- //
552
- }
553
-
554
- const errorObj = this.extractErrorObjectFromError(message);
555
-
556
- const { errorDetails } = errorObj;
557
-
558
- if (errorDetails) {
559
- return { error: errorDetails, errorType: AgentRuntimeErrorType.ProviderBizError };
560
- }
561
-
562
- return defaultError;
563
- }
564
-
565
519
  private buildGoogleTools(
566
520
  tools: ChatCompletionTool[] | undefined,
567
521
  payload?: ChatStreamPayload,
@@ -610,50 +564,6 @@ export class LobeGoogleAI implements LobeRuntimeAI {
610
564
  };
611
565
  };
612
566
 
613
- private extractErrorObjectFromError(message: string) {
614
- // 使用正则表达式匹配状态码部分 [数字 描述文本]
615
- const regex = /^(.*?)(\[\d+ [^\]]+])(.*)$/;
616
- const match = message.match(regex);
617
-
618
- if (match) {
619
- const prefix = match[1].trim();
620
- const statusCodeWithBrackets = match[2].trim();
621
- const message = match[3].trim();
622
-
623
- // 提取状态码数字
624
- const statusCodeMatch = statusCodeWithBrackets.match(/\[(\d+)/);
625
- const statusCode = statusCodeMatch ? parseInt(statusCodeMatch[1]) : null;
626
-
627
- // 创建包含状态码和消息的JSON
628
- const resultJson = {
629
- message: message,
630
- statusCode: statusCode,
631
- statusCodeText: statusCodeWithBrackets,
632
- };
633
-
634
- return {
635
- errorDetails: resultJson,
636
- prefix: prefix,
637
- };
638
- }
639
-
640
- // 如果无法匹配,返回原始消息
641
- return {
642
- errorDetails: null,
643
- prefix: message,
644
- };
645
- }
646
567
  }
647
568
 
648
569
  export default LobeGoogleAI;
649
-
650
- type GoogleChatErrors = GoogleChatError[];
651
-
652
- interface GoogleChatError {
653
- '@type': string;
654
- 'domain': string;
655
- 'metadata': {
656
- service: string;
657
- };
658
- 'reason': string;
659
- }
@@ -0,0 +1,228 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { AgentRuntimeErrorType } from '../error';
4
+
5
+ import {
6
+ cleanErrorMessage,
7
+ extractStatusCodeFromError,
8
+ parseGoogleErrorMessage,
9
+ } from './googleErrorParser';
10
+
11
+ describe('googleErrorParser', () => {
12
+ describe('cleanErrorMessage', () => {
13
+ it('should remove leading asterisk and spaces', () => {
14
+ const input = '* API key not valid. Please check your credentials.';
15
+ const expected = 'API key not valid. Please check your credentials.';
16
+ expect(cleanErrorMessage(input)).toBe(expected);
17
+ });
18
+
19
+ it('should convert escaped newlines to actual newlines', () => {
20
+ const input = 'Error occurred\\nPlease try again';
21
+ const expected = 'Error occurred Please try again';
22
+ expect(cleanErrorMessage(input)).toBe(expected);
23
+ });
24
+
25
+ it('should replace multiple newlines with single space', () => {
26
+ const input = 'Line 1\n\n\nLine 2\nLine 3';
27
+ const expected = 'Line 1 Line 2 Line 3';
28
+ expect(cleanErrorMessage(input)).toBe(expected);
29
+ });
30
+
31
+ it('should trim whitespace', () => {
32
+ const input = ' \t Error message \t ';
33
+ const expected = 'Error message';
34
+ expect(cleanErrorMessage(input)).toBe(expected);
35
+ });
36
+
37
+ it('should handle combined formatting issues', () => {
38
+ const input = '* API key not valid.\\nPlease check your credentials.\\n\\nContact support if needed. ';
39
+ const expected = 'API key not valid. Please check your credentials. Contact support if needed.';
40
+ expect(cleanErrorMessage(input)).toBe(expected);
41
+ });
42
+ });
43
+
44
+ describe('extractStatusCodeFromError', () => {
45
+ it('should extract status code and message correctly', () => {
46
+ const input = 'Connection failed [503 Service Unavailable] Please try again later';
47
+ const result = extractStatusCodeFromError(input);
48
+
49
+ expect(result.errorDetails).toEqual({
50
+ message: 'Please try again later',
51
+ statusCode: 503,
52
+ statusCodeText: '[503 Service Unavailable]',
53
+ });
54
+ expect(result.prefix).toBe('Connection failed');
55
+ });
56
+
57
+ it('should handle different status codes', () => {
58
+ const input = 'Request failed [401 Unauthorized] Invalid credentials';
59
+ const result = extractStatusCodeFromError(input);
60
+
61
+ expect(result.errorDetails).toEqual({
62
+ message: 'Invalid credentials',
63
+ statusCode: 401,
64
+ statusCodeText: '[401 Unauthorized]',
65
+ });
66
+ expect(result.prefix).toBe('Request failed');
67
+ });
68
+
69
+ it('should return null for messages without status codes', () => {
70
+ const input = 'Simple error message without status code';
71
+ const result = extractStatusCodeFromError(input);
72
+
73
+ expect(result.errorDetails).toBeNull();
74
+ expect(result.prefix).toBe('Simple error message without status code');
75
+ });
76
+
77
+ it('should handle empty message after status code', () => {
78
+ const input = 'Error [404 Not Found]';
79
+ const result = extractStatusCodeFromError(input);
80
+
81
+ expect(result.errorDetails).toEqual({
82
+ message: '',
83
+ statusCode: 404,
84
+ statusCodeText: '[404 Not Found]',
85
+ });
86
+ expect(result.prefix).toBe('Error');
87
+ });
88
+ });
89
+
90
+ describe('parseGoogleErrorMessage', () => {
91
+ it('should handle location not supported error', () => {
92
+ const input = 'This location is not supported for Google AI services';
93
+ const result = parseGoogleErrorMessage(input);
94
+
95
+ expect(result.errorType).toBe(AgentRuntimeErrorType.LocationNotSupportError);
96
+ expect(result.error.message).toBe(input);
97
+ });
98
+
99
+ it('should handle status JSON format', () => {
100
+ const input = 'got status: UNAVAILABLE. {"error":{"code":503,"message":"Service temporarily unavailable","status":"UNAVAILABLE"}}';
101
+ const result = parseGoogleErrorMessage(input);
102
+
103
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
104
+ expect(result.error).toEqual({
105
+ code: 503,
106
+ message: 'Service temporarily unavailable',
107
+ status: 'UNAVAILABLE',
108
+ });
109
+ });
110
+
111
+ it('should handle direct JSON parsing', () => {
112
+ const input = '{"error":{"code":400,"message":"* API key not valid. Please pass a valid API key.","status":"INVALID_ARGUMENT"}}';
113
+ const result = parseGoogleErrorMessage(input);
114
+
115
+ expect(result.errorType).toBe(AgentRuntimeErrorType.InvalidProviderAPIKey);
116
+ expect(result.error).toEqual({
117
+ code: 400,
118
+ message: 'API key not valid. Please pass a valid API key.',
119
+ status: 'INVALID_ARGUMENT',
120
+ });
121
+ });
122
+
123
+ it('should handle quota limit error', () => {
124
+ const input = '{"error":{"code":429,"message":"Quota limit reached","status":"RESOURCE_EXHAUSTED"}}';
125
+ const result = parseGoogleErrorMessage(input);
126
+
127
+ expect(result.errorType).toBe(AgentRuntimeErrorType.QuotaLimitReached);
128
+ expect(result.error).toEqual({
129
+ code: 429,
130
+ message: 'Quota limit reached',
131
+ status: 'RESOURCE_EXHAUSTED',
132
+ });
133
+ });
134
+
135
+ it('should handle nested JSON format', () => {
136
+ const input = '{"error":{"message":"{\\"error\\":{\\"code\\":400,\\"message\\":\\"Invalid request\\"}}"}}';
137
+ const result = parseGoogleErrorMessage(input);
138
+
139
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
140
+ expect(result.error).toEqual({
141
+ code: 400,
142
+ message: 'Invalid request',
143
+ status: '',
144
+ });
145
+ });
146
+
147
+ it('should handle array format with API_KEY_INVALID', () => {
148
+ const input = 'Request failed [{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "API_KEY_INVALID", "domain": "googleapis.com"}]';
149
+ const result = parseGoogleErrorMessage(input);
150
+
151
+ expect(result.errorType).toBe(AgentRuntimeErrorType.InvalidProviderAPIKey);
152
+ });
153
+
154
+ it('should handle array format with other errors', () => {
155
+ const input = 'Request failed [{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "QUOTA_EXCEEDED", "domain": "googleapis.com"}]';
156
+ const result = parseGoogleErrorMessage(input);
157
+
158
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
159
+ expect(result.error).toEqual([{
160
+ "@type": "type.googleapis.com/google.rpc.ErrorInfo",
161
+ "reason": "QUOTA_EXCEEDED",
162
+ "domain": "googleapis.com"
163
+ }]);
164
+ });
165
+
166
+ it('should handle status code extraction fallback', () => {
167
+ const input = 'Connection failed [503 Service Unavailable] Please try again later';
168
+ const result = parseGoogleErrorMessage(input);
169
+
170
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
171
+ expect(result.error).toEqual({
172
+ message: 'Please try again later',
173
+ statusCode: 503,
174
+ statusCodeText: '[503 Service Unavailable]',
175
+ });
176
+ });
177
+
178
+ it('should handle complex nested JSON with message cleaning', () => {
179
+ const input = '{"error":{"code":400,"message":"* Request contains invalid parameters\\nPlease check the documentation\\n\\nContact support for help"}}';
180
+ const result = parseGoogleErrorMessage(input);
181
+
182
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
183
+ expect(result.error.message).toBe('Request contains invalid parameters Please check the documentation Contact support for help');
184
+ });
185
+
186
+ it('should return default error for unparseable messages', () => {
187
+ const input = 'Some random error message that cannot be parsed';
188
+ const result = parseGoogleErrorMessage(input);
189
+
190
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
191
+ expect(result.error.message).toBe(input);
192
+ });
193
+
194
+ it('should handle malformed JSON gracefully', () => {
195
+ const input = '{"error":{"code":400,"message":"Invalid JSON{incomplete';
196
+ const result = parseGoogleErrorMessage(input);
197
+
198
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
199
+ expect(result.error.message).toBe(input);
200
+ });
201
+
202
+ it('should handle empty error object in JSON', () => {
203
+ const input = '{"error":{}}';
204
+ const result = parseGoogleErrorMessage(input);
205
+
206
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
207
+ expect(result.error).toEqual({
208
+ code: null,
209
+ message: input,
210
+ status: '',
211
+ });
212
+ });
213
+
214
+ it('should handle recursion depth limit', () => {
215
+ // Create a deeply nested JSON that exceeds the recursion limit
216
+ let deeplyNested = '{"error":{"code":400,"message":"Deep error"}}';
217
+ for (let i = 0; i < 6; i++) {
218
+ deeplyNested = `{"error":{"message":"${deeplyNested.replaceAll('"', '\\"')}"}}`;
219
+ }
220
+
221
+ const result = parseGoogleErrorMessage(deeplyNested);
222
+
223
+ // Should still return a valid result, but might not reach the deepest level
224
+ expect(result.errorType).toBe(AgentRuntimeErrorType.ProviderBizError);
225
+ expect(result.error).toBeDefined();
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,228 @@
1
+ import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../error';
2
+
3
+ export interface ParsedError {
4
+ error: any;
5
+ errorType: ILobeAgentRuntimeErrorType;
6
+ }
7
+
8
+ export interface GoogleChatError {
9
+ '@type': string;
10
+ 'domain': string;
11
+ 'metadata': {
12
+ service: string;
13
+ };
14
+ 'reason': string;
15
+ }
16
+
17
+ export type GoogleChatErrors = GoogleChatError[];
18
+
19
+ /**
20
+ * 清理错误消息,移除格式化字符和多余的空格
21
+ * @param message - 原始错误消息
22
+ * @returns 清理后的错误消息
23
+ */
24
+ export function cleanErrorMessage(message: string): string {
25
+ return message
26
+ .replaceAll(/^\*\s*/g, '') // 移除开头的星号和空格
27
+ .replaceAll('\\n', '\n') // 转换转义的换行符
28
+ .replaceAll(/\n+/g, ' ') // 将多个换行符替换为单个空格
29
+ .trim(); // 去除首尾空格
30
+ }
31
+
32
+ /**
33
+ * 从错误消息中提取状态码信息
34
+ * @param message - 错误消息
35
+ * @returns 提取的错误详情和前缀
36
+ */
37
+ export function extractStatusCodeFromError(message: string): {
38
+ errorDetails: any;
39
+ prefix: string;
40
+ } {
41
+ // 使用正则表达式匹配状态码部分 [数字 描述文本]
42
+ const regex = /^(.*?)(\[\d+ [^\]]+])(.*)$/;
43
+ const match = message.match(regex);
44
+
45
+ if (match) {
46
+ const prefix = match[1].trim();
47
+ const statusCodeWithBrackets = match[2].trim();
48
+ const messageContent = match[3].trim();
49
+
50
+ // 提取状态码数字
51
+ const statusCodeMatch = statusCodeWithBrackets.match(/\[(\d+)/);
52
+ const statusCode = statusCodeMatch ? parseInt(statusCodeMatch[1]) : null;
53
+
54
+ // 创建包含状态码和消息的JSON
55
+ const resultJson = {
56
+ message: messageContent,
57
+ statusCode: statusCode,
58
+ statusCodeText: statusCodeWithBrackets,
59
+ };
60
+
61
+ return {
62
+ errorDetails: resultJson,
63
+ prefix: prefix,
64
+ };
65
+ }
66
+
67
+ // 如果无法匹配,返回原始消息
68
+ return {
69
+ errorDetails: null,
70
+ prefix: message,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * 解析Google AI API返回的错误消息
76
+ * @param message - 原始错误消息
77
+ * @returns 解析后的错误对象和错误类型
78
+ */
79
+ export function parseGoogleErrorMessage(message: string): ParsedError {
80
+ const defaultError = {
81
+ error: { message },
82
+ errorType: AgentRuntimeErrorType.ProviderBizError,
83
+ };
84
+
85
+ // 快速识别特殊错误
86
+ if (message.includes('location is not supported')) {
87
+ return { error: { message }, errorType: AgentRuntimeErrorType.LocationNotSupportError };
88
+ }
89
+
90
+ // 统一的错误类型判断函数
91
+ const getErrorType = (code: number | null, message: string): ILobeAgentRuntimeErrorType => {
92
+ if (code === 400 && message.includes('API key not valid')) {
93
+ return AgentRuntimeErrorType.InvalidProviderAPIKey;
94
+ } else if (code === 429) {
95
+ return AgentRuntimeErrorType.QuotaLimitReached;
96
+ }
97
+ return AgentRuntimeErrorType.ProviderBizError;
98
+ };
99
+
100
+ // 递归解析JSON,处理嵌套的JSON字符串
101
+ const parseJsonRecursively = (str: string, maxDepth: number = 5): any => {
102
+ if (maxDepth <= 0) return null;
103
+
104
+ try {
105
+ const parsed = JSON.parse(str);
106
+
107
+ // 如果解析出的对象包含error字段
108
+ if (parsed && typeof parsed === 'object' && parsed.error) {
109
+ const errorInfo = parsed.error;
110
+
111
+ // 清理错误消息
112
+ if (typeof errorInfo.message === 'string') {
113
+ errorInfo.message = cleanErrorMessage(errorInfo.message);
114
+
115
+ // 如果error.message还是一个JSON字符串,继续递归解析
116
+ try {
117
+ const nestedResult = parseJsonRecursively(errorInfo.message, maxDepth - 1);
118
+ // 只有当深层结果包含带有 code 的 error 对象时,才优先返回深层结果
119
+ if (nestedResult && nestedResult.error && nestedResult.error.code) {
120
+ return nestedResult;
121
+ }
122
+ } catch {
123
+ // 如果嵌套解析失败,使用当前层的信息
124
+ }
125
+ }
126
+
127
+ return parsed;
128
+ }
129
+
130
+ return parsed;
131
+ } catch {
132
+ return null;
133
+ }
134
+ };
135
+
136
+ // 1. 处理 "got status: UNAVAILABLE. {JSON}" 格式
137
+ const statusJsonMatch = message.match(/got status: (\w+)\.\s*({.*})$/);
138
+ if (statusJsonMatch) {
139
+ const statusFromMessage = statusJsonMatch[1];
140
+ const jsonPart = statusJsonMatch[2];
141
+
142
+ const parsedError = parseJsonRecursively(jsonPart);
143
+ if (parsedError && parsedError.error) {
144
+ const errorInfo = parsedError.error;
145
+ const finalMessage = errorInfo.message || message;
146
+ const finalCode = errorInfo.code || null;
147
+ const finalStatus = errorInfo.status || statusFromMessage;
148
+
149
+ return {
150
+ error: {
151
+ code: finalCode,
152
+ message: finalMessage,
153
+ status: finalStatus,
154
+ },
155
+ errorType: getErrorType(finalCode, finalMessage),
156
+ };
157
+ }
158
+ }
159
+
160
+ // 2. 尝试直接解析整个消息作为JSON
161
+ const directParsed = parseJsonRecursively(message);
162
+ if (directParsed && directParsed.error) {
163
+ const errorInfo = directParsed.error;
164
+ const finalMessage = errorInfo.message || message;
165
+ const finalCode = errorInfo.code || null;
166
+ const finalStatus = errorInfo.status || '';
167
+
168
+ return {
169
+ error: {
170
+ code: finalCode,
171
+ message: finalMessage,
172
+ status: finalStatus,
173
+ },
174
+ errorType: getErrorType(finalCode, finalMessage),
175
+ };
176
+ }
177
+
178
+ // 3. 处理嵌套JSON格式,特别是message字段包含JSON的情况
179
+ try {
180
+ const firstLevelParsed = JSON.parse(message);
181
+ if (firstLevelParsed && firstLevelParsed.error && firstLevelParsed.error.message) {
182
+ const nestedParsed = parseJsonRecursively(firstLevelParsed.error.message);
183
+ if (nestedParsed && nestedParsed.error) {
184
+ const errorInfo = nestedParsed.error;
185
+ const finalMessage = errorInfo.message || message;
186
+ const finalCode = errorInfo.code || null;
187
+ const finalStatus = errorInfo.status || '';
188
+
189
+ return {
190
+ error: {
191
+ code: finalCode,
192
+ message: finalMessage,
193
+ status: finalStatus,
194
+ },
195
+ errorType: getErrorType(finalCode, finalMessage),
196
+ };
197
+ }
198
+ }
199
+ } catch {
200
+ // 继续其他解析方式
201
+ }
202
+
203
+ // 4. 原有的数组格式解析逻辑
204
+ const startIndex = message.lastIndexOf('[');
205
+ if (startIndex !== -1) {
206
+ try {
207
+ const jsonString = message.slice(startIndex);
208
+ const json: GoogleChatErrors = JSON.parse(jsonString);
209
+ const bizError = json[0];
210
+
211
+ if (bizError?.reason === 'API_KEY_INVALID') {
212
+ return { ...defaultError, errorType: AgentRuntimeErrorType.InvalidProviderAPIKey };
213
+ }
214
+
215
+ return { error: json, errorType: AgentRuntimeErrorType.ProviderBizError };
216
+ } catch {
217
+ // 忽略解析错误
218
+ }
219
+ }
220
+
221
+ // 5. 使用状态码提取逻辑作为最后的后备方案
222
+ const errorObj = extractStatusCodeFromError(message);
223
+ if (errorObj.errorDetails) {
224
+ return { error: errorObj.errorDetails, errorType: AgentRuntimeErrorType.ProviderBizError };
225
+ }
226
+
227
+ return defaultError;
228
+ }