@aiaiaichain/agent 0.1.5 → 0.1.7
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/dist/api/ExtensionAPI.d.ts +0 -1
- package/dist/api/ExtensionAPI.js +3 -7
- package/dist/api/Registry.d.ts +0 -1
- package/dist/api/Registry.js +54 -57
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +684 -685
- package/dist/core/AgentDir.d.ts +1 -1
- package/dist/core/AgentDir.js +45 -39
- package/dist/core/ChainConfig.d.ts +0 -1
- package/dist/core/ChainConfig.js +51 -55
- package/dist/core/EnvLoader.d.ts +4 -1
- package/dist/core/EnvLoader.js +97 -84
- package/dist/core/SystemMonitor.d.ts +0 -1
- package/dist/core/SystemMonitor.js +72 -85
- package/dist/index.d.ts +0 -1
- package/dist/index.js +19 -26
- package/dist/loader.d.ts +0 -1
- package/dist/loader.js +64 -67
- package/dist/mcp/entry.d.ts +0 -1
- package/dist/mcp/entry.js +3 -6
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +152 -156
- package/dist/models/CostTracker.d.ts +0 -1
- package/dist/models/CostTracker.js +58 -61
- package/dist/models/ModelRegistry.d.ts +0 -1
- package/dist/models/ModelRegistry.js +195 -155
- package/dist/providers/ProviderRegistry.d.ts +0 -1
- package/dist/providers/ProviderRegistry.js +33 -36
- package/dist/runner/AgentRunner.d.ts +0 -1
- package/dist/runner/AgentRunner.js +180 -184
- package/dist/runner/ModelClient.d.ts +0 -1
- package/dist/runner/ModelClient.js +133 -134
- package/dist/runner/SwarmRouter.d.ts +0 -1
- package/dist/runner/SwarmRouter.js +18 -22
- package/dist/runner/ToolDispatcher.d.ts +0 -1
- package/dist/runner/ToolDispatcher.js +30 -33
- package/dist/scheduler/AgentScheduler.d.ts +0 -1
- package/dist/scheduler/AgentScheduler.js +99 -103
- package/dist/session/ContextStore.d.ts +1 -1
- package/dist/session/ContextStore.js +76 -78
- package/dist/session/GoalManager.d.ts +0 -1
- package/dist/session/GoalManager.js +96 -100
- package/dist/session/MemoryStore.d.ts +2 -1
- package/dist/session/MemoryStore.js +108 -87
- package/dist/session/SessionManager.d.ts +5 -4
- package/dist/session/SessionManager.js +83 -62
- package/dist/session/SessionStore.d.ts +0 -1
- package/dist/session/SessionStore.js +112 -116
- package/dist/setup/SetupWizard.d.ts +0 -1
- package/dist/setup/SetupWizard.js +61 -64
- package/dist/tools/CrossTools.d.ts +0 -1
- package/dist/tools/CrossTools.js +140 -144
- package/dist/tools/GmgnIntegration.d.ts +0 -1
- package/dist/tools/GmgnIntegration.js +220 -230
- package/dist/tools/MarketSentiment.d.ts +0 -1
- package/dist/tools/MarketSentiment.js +213 -195
- package/dist/tools/NewsSentiment.d.ts +0 -1
- package/dist/tools/NewsSentiment.js +126 -130
- package/dist/tools/PriceFeed.d.ts +6 -1
- package/dist/tools/PriceFeed.js +201 -133
- package/dist/tools/TechnicalAnalysis.d.ts +1 -2
- package/dist/tools/TechnicalAnalysis.js +248 -216
- package/dist/tools/TechnicalAnalysis.worker.d.ts +25 -0
- package/dist/tools/TechnicalAnalysis.worker.js +92 -0
- package/dist/tools/TokenCalendar.d.ts +0 -1
- package/dist/tools/TokenCalendar.js +63 -68
- package/dist/tools/TokenSecurityScanner.d.ts +0 -1
- package/dist/tools/TokenSecurityScanner.js +93 -96
- package/dist/tools/TransactionSim.d.ts +0 -1
- package/dist/tools/TransactionSim.js +65 -71
- package/dist/tui/App.d.ts +0 -1
- package/dist/tui/App.js +895 -824
- package/dist/tui/ModelSelector.d.ts +0 -1
- package/dist/tui/ModelSelector.js +46 -49
- package/dist/tui/REPL.d.ts +0 -1
- package/dist/tui/REPL.js +222 -210
- package/dist/tui/Sparkline.d.ts +0 -1
- package/dist/tui/Sparkline.js +36 -37
- package/dist/tui/StatusBar.d.ts +0 -1
- package/dist/tui/StatusBar.js +9 -10
- package/dist/tui/ThemePresets.d.ts +0 -1
- package/dist/tui/ThemePresets.js +99 -103
- package/dist/tui/theme.d.ts +0 -1
- package/dist/tui/theme.js +50 -31
- package/dist/util/clipboard.d.ts +0 -1
- package/dist/util/clipboard.js +16 -20
- package/dist/util/commandSuggest.d.ts +0 -1
- package/dist/util/commandSuggest.js +34 -38
- package/dist/util/confirmation.d.ts +0 -1
- package/dist/util/confirmation.js +8 -11
- package/dist/util/errorHandler.d.ts +0 -1
- package/dist/util/errorHandler.js +20 -23
- package/dist/util/errors.d.ts +59 -0
- package/dist/util/errors.js +93 -0
- package/dist/util/logger.d.ts +0 -1
- package/dist/util/logger.js +30 -33
- package/dist/util/processManager.d.ts +0 -1
- package/dist/util/processManager.js +33 -36
- package/dist/util/resilientFetch.d.ts +6 -1
- package/dist/util/resilientFetch.js +134 -80
- package/dist/util/responseCache.d.ts +0 -1
- package/dist/util/responseCache.js +36 -45
- package/dist/util/rpc.d.ts +16 -0
- package/dist/util/rpc.js +69 -0
- package/dist/util/safeLog.d.ts +0 -1
- package/dist/util/safeLog.js +52 -53
- package/dist/util/scheduler.d.ts +0 -1
- package/dist/util/scheduler.js +53 -58
- package/dist/util/webhooks.d.ts +0 -1
- package/dist/util/webhooks.js +54 -58
- package/dist/wallet/ActionFeed.d.ts +3 -3
- package/dist/wallet/ActionFeed.js +189 -187
- package/dist/wallet/AgentWallet.d.ts +9 -7
- package/dist/wallet/AgentWallet.js +121 -141
- package/dist/wallet/ProfitTracker.d.ts +0 -1
- package/dist/wallet/ProfitTracker.js +71 -74
- package/package.json +12 -7
- package/scripts/build-esbuild.mjs +40 -0
- package/scripts/bundle-dts.mjs +58 -0
- package/scripts/minify.mjs +44 -0
- package/scripts/postinstall.js +27 -0
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
* Global error handling utilities for AIAIAI agent
|
|
3
|
-
*/
|
|
1
|
+
|
|
4
2
|
import { safeLog } from "./safeLog.js";
|
|
5
|
-
|
|
3
|
+
|
|
6
4
|
export function withErrorHandling(operation, context) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
throw error; // Re-throw to maintain expected behavior
|
|
14
|
-
});
|
|
5
|
+
return operation().catch((error) => {
|
|
6
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7
|
+
safeLog(`[${context}] ERROR: ${errorMessage}`);
|
|
8
|
+
if (error instanceof Error) {
|
|
9
|
+
safeLog(`Stack trace: ${error.stack}`);
|
|
15
10
|
}
|
|
16
|
-
|
|
11
|
+
throw error;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
17
15
|
export function setupGlobalErrorHandlers() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
17
|
+
console.error('[UNHANDLED_REJECTION] Reason:', reason);
|
|
18
|
+
console.error('[PROMISE]', promise);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
process.on('uncaughtException', (error) => {
|
|
22
|
+
console.error('[UNCAUGHT_EXCEPTION]', error);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
27
25
|
}
|
|
28
|
-
//# sourceMappingURL=errorHandler.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #61: Structured error types with codes for consistent error handling
|
|
3
|
+
* #65: Error code system — AUTH_401, RATE_429, TIMEOUT_408, etc.
|
|
4
|
+
*/
|
|
5
|
+
export type ErrorCode = 'AUTH_401' | 'RATE_429' | 'TIMEOUT_408' | 'PARSE_001' | 'RPC_ERROR' | 'NETWORK_ERROR' | 'VALIDATION_ERROR' | 'TOOL_ERROR' | 'UNKNOWN';
|
|
6
|
+
export declare class AgentError extends Error {
|
|
7
|
+
readonly code: ErrorCode;
|
|
8
|
+
readonly context?: Record<string, unknown>;
|
|
9
|
+
readonly timestamp: number;
|
|
10
|
+
readonly recoverable: boolean;
|
|
11
|
+
constructor(message: string, code?: ErrorCode, options?: {
|
|
12
|
+
context?: Record<string, unknown>;
|
|
13
|
+
recoverable?: boolean;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export declare class RpcError extends AgentError {
|
|
17
|
+
readonly method: string;
|
|
18
|
+
readonly statusCode?: number;
|
|
19
|
+
constructor(message: string, method: string, options?: {
|
|
20
|
+
statusCode?: number;
|
|
21
|
+
code?: ErrorCode;
|
|
22
|
+
context?: Record<string, unknown>;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export declare class ApiError extends AgentError {
|
|
26
|
+
readonly host: string;
|
|
27
|
+
readonly statusCode?: number;
|
|
28
|
+
constructor(message: string, host: string, options?: {
|
|
29
|
+
statusCode?: number;
|
|
30
|
+
code?: ErrorCode;
|
|
31
|
+
recoverable?: boolean;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export declare class ValidationError extends AgentError {
|
|
35
|
+
readonly field: string;
|
|
36
|
+
constructor(message: string, field: string, context?: Record<string, unknown>);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* #62: Error aggregation — collect errors per session for /errors command
|
|
40
|
+
*/
|
|
41
|
+
declare class ErrorAggregator {
|
|
42
|
+
private errors;
|
|
43
|
+
private maxErrors;
|
|
44
|
+
record(error: AgentError): void;
|
|
45
|
+
getRecent(count?: number): Array<{
|
|
46
|
+
error: AgentError;
|
|
47
|
+
ts: number;
|
|
48
|
+
}>;
|
|
49
|
+
getByCode(code: ErrorCode): AgentError[];
|
|
50
|
+
getSummary(): Record<string, number>;
|
|
51
|
+
clear(): void;
|
|
52
|
+
get count(): number;
|
|
53
|
+
}
|
|
54
|
+
export declare const errorAggregator: ErrorAggregator;
|
|
55
|
+
/**
|
|
56
|
+
* #66: User-friendly error messages with suggested actions
|
|
57
|
+
*/
|
|
58
|
+
export declare function formatUserError(error: AgentError): string;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
export class AgentError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
context;
|
|
5
|
+
timestamp;
|
|
6
|
+
recoverable;
|
|
7
|
+
constructor(message, code = 'UNKNOWN', options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'AgentError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.context = options?.context;
|
|
12
|
+
this.timestamp = Date.now();
|
|
13
|
+
this.recoverable = options?.recoverable ?? false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class RpcError extends AgentError {
|
|
17
|
+
method;
|
|
18
|
+
statusCode;
|
|
19
|
+
constructor(message, method, options) {
|
|
20
|
+
super(message, options?.code ?? 'RPC_ERROR', options);
|
|
21
|
+
this.name = 'RpcError';
|
|
22
|
+
this.method = method;
|
|
23
|
+
this.statusCode = options?.statusCode;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class ApiError extends AgentError {
|
|
27
|
+
host;
|
|
28
|
+
statusCode;
|
|
29
|
+
constructor(message, host, options) {
|
|
30
|
+
super(message, options?.code ?? 'NETWORK_ERROR', options);
|
|
31
|
+
this.name = 'ApiError';
|
|
32
|
+
this.host = host;
|
|
33
|
+
this.statusCode = options?.statusCode;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ValidationError extends AgentError {
|
|
37
|
+
field;
|
|
38
|
+
constructor(message, field, context) {
|
|
39
|
+
super(message, 'VALIDATION_ERROR', { context, recoverable: true });
|
|
40
|
+
this.name = 'ValidationError';
|
|
41
|
+
this.field = field;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class ErrorAggregator {
|
|
46
|
+
errors = [];
|
|
47
|
+
maxErrors = 100;
|
|
48
|
+
record(error) {
|
|
49
|
+
this.errors.push({ error, ts: Date.now() });
|
|
50
|
+
if (this.errors.length > this.maxErrors) {
|
|
51
|
+
this.errors = this.errors.slice(-this.maxErrors);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
getRecent(count = 20) {
|
|
55
|
+
return this.errors.slice(-count);
|
|
56
|
+
}
|
|
57
|
+
getByCode(code) {
|
|
58
|
+
return this.errors.filter(e => e.error.code === code).map(e => e.error);
|
|
59
|
+
}
|
|
60
|
+
getSummary() {
|
|
61
|
+
const summary = {};
|
|
62
|
+
for (const { error } of this.errors) {
|
|
63
|
+
summary[error.code] = (summary[error.code] ?? 0) + 1;
|
|
64
|
+
}
|
|
65
|
+
return summary;
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.errors = [];
|
|
69
|
+
}
|
|
70
|
+
get count() {
|
|
71
|
+
return this.errors.length;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export const errorAggregator = new ErrorAggregator();
|
|
75
|
+
|
|
76
|
+
export function formatUserError(error) {
|
|
77
|
+
switch (error.code) {
|
|
78
|
+
case 'AUTH_401':
|
|
79
|
+
return `Authentication failed. Run: aiaiai setup`;
|
|
80
|
+
case 'RATE_429':
|
|
81
|
+
return `Rate limited. Wait a moment and try again.`;
|
|
82
|
+
case 'TIMEOUT_408':
|
|
83
|
+
return `Request timed out. Check your connection or try again.`;
|
|
84
|
+
case 'RPC_ERROR':
|
|
85
|
+
return `Solana RPC error. The network may be congested. Try again in 30s.`;
|
|
86
|
+
case 'NETWORK_ERROR':
|
|
87
|
+
return `Network error. Check your internet connection.`;
|
|
88
|
+
case 'VALIDATION_ERROR':
|
|
89
|
+
return `Invalid input: ${error.message}`;
|
|
90
|
+
default:
|
|
91
|
+
return error.message;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/util/logger.d.ts
CHANGED
package/dist/util/logger.js
CHANGED
|
@@ -1,43 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
* src/util/logger.ts — Structured logging with levels for AIAIAI Agent
|
|
3
|
-
*/
|
|
1
|
+
|
|
4
2
|
const LEVEL_PRIORITY = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3,
|
|
9
7
|
};
|
|
10
8
|
function getMinLevel() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const env = process.env.AIAIAI_LOG_LEVEL?.toLowerCase();
|
|
10
|
+
if (env && env in LEVEL_PRIORITY)
|
|
11
|
+
return env;
|
|
12
|
+
return 'info';
|
|
15
13
|
}
|
|
16
14
|
function shouldLog(level) {
|
|
17
|
-
|
|
15
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[getMinLevel()];
|
|
18
16
|
}
|
|
19
17
|
function formatMessage(level, context, message, meta) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const ts = new Date().toISOString();
|
|
19
|
+
const prefix = `[${ts}] [${level.toUpperCase()}] [${context}]`;
|
|
20
|
+
const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
|
|
21
|
+
return `${prefix} ${message}${metaStr}`;
|
|
24
22
|
}
|
|
25
23
|
export const logger = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
debug(context, message, meta) {
|
|
25
|
+
if (shouldLog('debug'))
|
|
26
|
+
console.error(formatMessage('debug', context, message, meta));
|
|
27
|
+
},
|
|
28
|
+
info(context, message, meta) {
|
|
29
|
+
if (shouldLog('info'))
|
|
30
|
+
console.error(formatMessage('info', context, message, meta));
|
|
31
|
+
},
|
|
32
|
+
warn(context, message, meta) {
|
|
33
|
+
if (shouldLog('warn'))
|
|
34
|
+
console.error(formatMessage('warn', context, message, meta));
|
|
35
|
+
},
|
|
36
|
+
error(context, message, meta) {
|
|
37
|
+
if (shouldLog('error'))
|
|
38
|
+
console.error(formatMessage('error', context, message, meta));
|
|
39
|
+
},
|
|
42
40
|
};
|
|
43
|
-
//# sourceMappingURL=logger.js.map
|
|
@@ -1,39 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
* Process management utilities for AIAIAI Agent
|
|
3
|
-
*/
|
|
1
|
+
|
|
4
2
|
import { safeLog } from "./safeLog.js";
|
|
5
3
|
export class ProcessManager {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
4
|
+
static setupGlobalHandlers() {
|
|
5
|
+
|
|
6
|
+
process.on('uncaughtException', (error) => {
|
|
7
|
+
safeLog('[UNCAUGHT_EXCEPTION] ' + error.message);
|
|
8
|
+
if (error.stack) {
|
|
9
|
+
safeLog('[STACK_TRACE] ' + error.stack);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.shutdown(1);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
16
|
+
safeLog('[UNHANDLED_REJECTION] ' + reason?.message || String(reason));
|
|
17
|
+
|
|
18
|
+
this.shutdown(1);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
process.on('SIGTERM', () => {
|
|
22
|
+
safeLog('[SIGTERM] Received shutdown signal');
|
|
23
|
+
this.shutdown(0);
|
|
24
|
+
});
|
|
25
|
+
process.on('SIGINT', () => {
|
|
26
|
+
safeLog('[SIGINT] Received shutdown signal');
|
|
27
|
+
this.shutdown(0);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
static shutdown(exitCode) {
|
|
31
|
+
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
process.exit(exitCode);
|
|
34
|
+
}, 100);
|
|
35
|
+
}
|
|
38
36
|
}
|
|
39
|
-
//# sourceMappingURL=processManager.js.map
|
|
@@ -17,5 +17,10 @@ export interface CircuitState {
|
|
|
17
17
|
state: "closed" | "open" | "half-open";
|
|
18
18
|
}
|
|
19
19
|
export declare function resilientFetch(url: string, options?: FetchOptions): Promise<Response>;
|
|
20
|
+
export declare function getCircuitStats(): Array<{
|
|
21
|
+
key: string;
|
|
22
|
+
state: string;
|
|
23
|
+
failures: number;
|
|
24
|
+
lastFailureTime: number;
|
|
25
|
+
}>;
|
|
20
26
|
export declare function isReachable(url: string, timeout?: number): Promise<boolean>;
|
|
21
|
-
//# sourceMappingURL=resilientFetch.d.ts.map
|
|
@@ -1,94 +1,148 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
|
|
2
|
+
import dns from 'node:dns';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import https from 'node:https';
|
|
5
|
+
import { AgentError, errorAggregator } from './errors.js';
|
|
6
|
+
|
|
7
|
+
let _requestIdCounter = 0;
|
|
8
|
+
|
|
9
|
+
const dnsCache = new Map();
|
|
10
|
+
const DNS_CACHE_TTL = 5 * 60_1000;
|
|
11
|
+
const dnsCacheTime = new Map();
|
|
4
12
|
const circuits = new Map();
|
|
5
13
|
const CIRCUIT_THRESHOLD = 3;
|
|
6
14
|
const CIRCUIT_RESET_MS = 60_000;
|
|
15
|
+
|
|
16
|
+
const keepAliveAgent = new http.Agent({ keepAlive: true, maxSockets: 10, maxFreeSockets: 5 });
|
|
17
|
+
const httpsKeepAliveAgent = new https.Agent({ keepAlive: true, maxSockets: 10, maxFreeSockets: 5 });
|
|
7
18
|
function getCircuit(key) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
let c = circuits.get(key);
|
|
20
|
+
if (!c) {
|
|
21
|
+
c = { failures: 0, lastFailureTime: 0, state: "closed" };
|
|
22
|
+
circuits.set(key, c);
|
|
23
|
+
}
|
|
24
|
+
return c;
|
|
14
25
|
}
|
|
15
26
|
function canExecute(key) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const c = getCircuit(key);
|
|
28
|
+
if (c.state === "closed")
|
|
29
|
+
return true;
|
|
30
|
+
if (c.state === "open" && Date.now() - c.lastFailureTime > CIRCUIT_RESET_MS) {
|
|
31
|
+
c.state = "half-open";
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return c.state === "half-open";
|
|
24
35
|
}
|
|
25
36
|
function recordSuccess(key) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
const c = getCircuit(key);
|
|
38
|
+
c.failures = 0;
|
|
39
|
+
c.state = "closed";
|
|
29
40
|
}
|
|
30
41
|
function recordFailure(key) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
const c = getCircuit(key);
|
|
43
|
+
c.failures++;
|
|
44
|
+
c.lastFailureTime = Date.now();
|
|
45
|
+
if (c.failures >= CIRCUIT_THRESHOLD) {
|
|
46
|
+
c.state = "open";
|
|
47
|
+
}
|
|
37
48
|
}
|
|
38
49
|
export async function resilientFetch(url, options = {}) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
50
|
+
const { timeout = 8_000, retries = 2, backoffMs = 1_000, onRetry, method = "GET", headers, body, signal: externalSignal, } = options;
|
|
51
|
+
const circuitKey = new URL(url).hostname;
|
|
52
|
+
if (!canExecute(circuitKey)) {
|
|
53
|
+
throw new Error("Circuit open for " + circuitKey + " - service unavailable, retry later");
|
|
54
|
+
}
|
|
55
|
+
let lastError = null;
|
|
56
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
59
|
+
|
|
60
|
+
if (externalSignal) {
|
|
61
|
+
if (externalSignal.aborted)
|
|
62
|
+
controller.abort();
|
|
63
|
+
else
|
|
64
|
+
externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
|
|
68
|
+
const urlObj = new URL(url);
|
|
69
|
+
const hostname = urlObj.hostname;
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const cachedIp = dnsCache.get(hostname);
|
|
72
|
+
const cachedTime = dnsCacheTime.get(hostname);
|
|
73
|
+
if (!cachedIp || !cachedTime || now - cachedTime > DNS_CACHE_TTL) {
|
|
74
|
+
try {
|
|
75
|
+
dns.resolve4(hostname, (err, ips) => {
|
|
76
|
+
if (!err && ips && ips.length > 0) {
|
|
77
|
+
dnsCache.set(hostname, ips[0]);
|
|
78
|
+
dnsCacheTime.set(hostname, now);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const agent = urlObj.protocol === 'https:' ? httpsKeepAliveAgent : keepAliveAgent;
|
|
86
|
+
|
|
87
|
+
const fetchHeaders = { ...headers, 'Accept-Encoding': 'gzip' };
|
|
88
|
+
const fetchOpts = {
|
|
89
|
+
signal: controller.signal,
|
|
90
|
+
method,
|
|
91
|
+
headers: fetchHeaders,
|
|
92
|
+
body,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (urlObj.protocol === 'https:' || urlObj.protocol === 'http:') {
|
|
96
|
+
fetchOpts.agent = urlObj.protocol === 'https:' ? httpsKeepAliveAgent : keepAliveAgent;
|
|
97
|
+
}
|
|
98
|
+
const response = await fetch(url, fetchOpts);
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error("HTTP " + response.status + ": " + response.statusText);
|
|
102
|
+
}
|
|
103
|
+
recordSuccess(circuitKey);
|
|
104
|
+
return response;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
109
|
+
if (attempt < retries) {
|
|
110
|
+
onRetry?.(attempt + 1, lastError);
|
|
111
|
+
|
|
112
|
+
const baseDelay = backoffMs * Math.pow(2, attempt);
|
|
113
|
+
const jitter = baseDelay * 0.2 * (Math.random() * 2 - 1);
|
|
114
|
+
await new Promise((r) => setTimeout(r, baseDelay + jitter));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
recordFailure(circuitKey);
|
|
119
|
+
|
|
120
|
+
if (lastError) {
|
|
121
|
+
const aggError = new AgentError(lastError.message, 'NETWORK_ERROR', {
|
|
122
|
+
context: { url, attempts: retries + 1 },
|
|
123
|
+
recoverable: true,
|
|
124
|
+
});
|
|
125
|
+
errorAggregator.record(aggError);
|
|
126
|
+
}
|
|
127
|
+
throw lastError;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getCircuitStats() {
|
|
131
|
+
const stats = [];
|
|
132
|
+
for (const [key, c] of circuits) {
|
|
133
|
+
stats.push({ key, state: c.state, failures: c.failures, lastFailureTime: c.lastFailureTime });
|
|
134
|
+
}
|
|
135
|
+
return stats;
|
|
81
136
|
}
|
|
82
137
|
export async function isReachable(url, timeout = 5_000) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
//# sourceMappingURL=resilientFetch.js.map
|
|
138
|
+
try {
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
141
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
return res.ok;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|