fix: Converting circular structure to JSON
This commit is contained in:
1 parent
5a004d9d16
commit
b0ec96d4a6
5 files changed
+91
-12
No files matched your search
@@ -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
@@ -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]';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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}`;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user