fix: Converting circular structure to JSON

This commit is contained in:
Draculabo committed 2026-01-15 22:58:05 +08:00
1 parent 5a004d9d16
commit b0ec96d4a6
5 files changed
+91 -12

No files matched your search

+6 -1
View File
@@ -126,7 +126,12 @@ export class QuotaService {
} catch (error: any) {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const text = JSON.stringify(error.response?.data || '');
let text = '';
try {
text = JSON.stringify(error.response?.data || '');
} catch {
text = '[Unable to serialize response data]';
}
// ✅ Handle 403 Forbidden specifically - return immediately, do not retry
if (status === 403) {
+20 -1
View File
@@ -20,9 +20,28 @@ import { ConfigManager } from './ipc/config/manager';
const packetLogPath = path.join(app.getPath('userData'), 'orpc_packets.log');
/**
* Safely stringify an object, handling circular references
*/
function safeStringifyPacket(obj: unknown): string {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
return value;
});
}
function logPacket(data: any) {
try {
fs.appendFileSync(packetLogPath, `[${new Date().toISOString()}] ${JSON.stringify(data)}\n`);
fs.appendFileSync(
packetLogPath,
`[${new Date().toISOString()}] ${safeStringifyPacket(data)}\n`,
);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
@@ -27,7 +27,8 @@ export class GeminiClient {
this.logger.error(`Gemini Stream API Error: ${error.message}`);
throw new Error(error.response?.data?.error?.message || error.message);
}
throw error;
// Re-throw as clean Error to avoid circular reference issues
throw error instanceof Error ? new Error(error.message) : new Error(String(error));
}
}
@@ -50,11 +51,12 @@ export class GeminiClient {
} catch (error) {
if (axios.isAxiosError(error)) {
this.logger.error(
`Gemini API Error: ${error.message} - ${JSON.stringify(error.response?.data)}`,
`Gemini API Error: ${error.message} - ${this.safeStringify(error.response?.data)}`,
);
throw new Error(error.response?.data?.error?.message || error.message);
}
throw error;
// Re-throw as clean Error to avoid circular reference issues
throw error instanceof Error ? new Error(error.message) : new Error(String(error));
}
}
@@ -77,7 +79,6 @@ export class GeminiClient {
return response.data;
} catch (error) {
this.handleAxiosError(error, 'StreamInternal');
throw error;
}
}
@@ -96,17 +97,38 @@ export class GeminiClient {
return response.data.response || response.data;
} catch (error) {
this.handleAxiosError(error, 'GenerateInternal');
throw error;
}
}
private handleAxiosError(error: any, context: string) {
if (axios.isAxiosError(error)) {
this.logger.error(
`Gemini ${context} API Error: ${error.message} - ${JSON.stringify(error.response?.data)}`,
`Gemini ${context} API Error: ${error.message} - ${this.safeStringify(error.response?.data)}`,
);
throw new Error(error.response?.data?.error?.message || error.message);
}
throw error;
// Re-throw as clean Error to avoid circular reference issues
throw error instanceof Error ? new Error(error.message) : new Error(String(error));
}
/**
* Safely stringify an object, handling circular references
*/
private safeStringify(obj: unknown): string {
if (obj === undefined || obj === null) {
return String(obj);
}
try {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
} catch {
return '[Unserializable]';
}
}
}
+8 -2
View File
@@ -217,7 +217,11 @@ export class ProxyService {
subscriber.complete();
});
upstreamStream.on('error', (err: any) => subscriber.error(err));
upstreamStream.on('error', (err: any) => {
// Convert to clean Error to avoid circular reference issues (socket objects)
const cleanError = err instanceof Error ? new Error(err.message) : new Error(String(err));
subscriber.error(cleanError);
});
});
}
@@ -372,7 +376,9 @@ export class ProxyService {
});
upstreamStream.on('error', (err: any) => {
subscriber.error(err);
// Convert to clean Error to avoid circular reference issues (socket objects)
const cleanError = err instanceof Error ? new Error(err.message) : new Error(String(err));
subscriber.error(cleanError);
});
});
}
+28 -1
View File
@@ -4,6 +4,33 @@ import { getAgentDir } from './paths';
export type LogLevel = 'info' | 'warn' | 'error' | 'debug';
/**
* Safely stringify an object, handling circular references
* This prevents "Converting circular structure to JSON" errors
* when logging objects like axios errors that contain socket references
*/
function safeStringify(obj: unknown): string {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
// Handle Error objects specially
if (value instanceof Error) {
return {
name: value.name,
message: value.message,
stack: value.stack,
};
}
// Handle circular references
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
return value;
});
}
class Logger {
private logFilePath: string;
@@ -22,7 +49,7 @@ class Logger {
private formatMessage(level: LogLevel, message: string, ...args: unknown[]): string {
const timestamp = new Date().toISOString();
const formattedArgs = args
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
.map((arg) => (typeof arg === 'object' ? safeStringify(arg) : String(arg)))
.join(' ');
return `[${timestamp}] [${level.toUpperCase()}] ${message} ${formattedArgs}`;
}