@juspay/neurolink 4.1.0 → 4.2.0
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 +14 -2
- package/README.md +105 -116
- package/dist/cli/commands/mcp.d.ts +11 -0
- package/dist/cli/commands/mcp.js +332 -223
- package/dist/cli/index.js +69 -8
- package/dist/core/factory.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/core/factory.js +2 -2
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/mcp/context-manager.d.ts +6 -0
- package/dist/lib/mcp/context-manager.js +8 -0
- package/dist/lib/mcp/contracts/mcpContract.d.ts +1 -0
- package/dist/lib/mcp/external-client.js +6 -2
- package/dist/lib/mcp/initialize.d.ts +2 -1
- package/dist/lib/mcp/initialize.js +8 -7
- package/dist/lib/mcp/orchestrator.js +9 -0
- package/dist/lib/mcp/registry.d.ts +1 -1
- package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.js +1 -1
- package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +3 -3
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +1 -1
- package/dist/lib/mcp/session-manager.js +1 -1
- package/dist/lib/mcp/session-persistence.js +1 -1
- package/dist/lib/mcp/tool-registry.d.ts +31 -11
- package/dist/lib/mcp/tool-registry.js +226 -38
- package/dist/lib/mcp/unified-mcp.d.ts +12 -2
- package/dist/lib/mcp/unified-registry.d.ts +21 -7
- package/dist/lib/mcp/unified-registry.js +179 -17
- package/dist/lib/neurolink.js +17 -25
- package/dist/lib/providers/googleVertexAI.js +19 -1
- package/dist/lib/providers/openAI.js +18 -1
- package/dist/lib/utils/provider-setup-messages.d.ts +8 -0
- package/dist/lib/utils/provider-setup-messages.js +120 -0
- package/dist/lib/utils/provider-validation.d.ts +35 -0
- package/dist/lib/utils/provider-validation.js +625 -0
- package/dist/lib/utils/providerUtils-fixed.js +20 -1
- package/dist/lib/utils/providerUtils.d.ts +2 -2
- package/dist/lib/utils/providerUtils.js +38 -7
- package/dist/lib/utils/timeout-manager.d.ts +75 -0
- package/dist/lib/utils/timeout-manager.js +244 -0
- package/dist/mcp/context-manager.d.ts +6 -0
- package/dist/mcp/context-manager.js +8 -0
- package/dist/mcp/contracts/mcpContract.d.ts +1 -0
- package/dist/mcp/external-client.js +6 -2
- package/dist/mcp/initialize.d.ts +2 -1
- package/dist/mcp/initialize.js +8 -7
- package/dist/mcp/orchestrator.js +9 -0
- package/dist/mcp/plugins/core/neurolink-mcp.json +15 -15
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/servers/ai-providers/ai-analysis-tools.js +1 -1
- package/dist/mcp/servers/ai-providers/ai-core-server.js +3 -3
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +1 -1
- package/dist/mcp/session-manager.js +1 -1
- package/dist/mcp/session-persistence.js +1 -1
- package/dist/mcp/tool-registry.d.ts +31 -11
- package/dist/mcp/tool-registry.js +226 -38
- package/dist/mcp/unified-mcp.d.ts +12 -2
- package/dist/mcp/unified-registry.d.ts +21 -7
- package/dist/mcp/unified-registry.js +179 -17
- package/dist/neurolink.js +17 -25
- package/dist/providers/googleVertexAI.js +19 -1
- package/dist/providers/openAI.js +18 -1
- package/dist/utils/provider-setup-messages.d.ts +8 -0
- package/dist/utils/provider-setup-messages.js +120 -0
- package/dist/utils/provider-validation.d.ts +35 -0
- package/dist/utils/provider-validation.js +625 -0
- package/dist/utils/providerUtils-fixed.js +20 -1
- package/dist/utils/providerUtils.d.ts +2 -2
- package/dist/utils/providerUtils.js +38 -7
- package/dist/utils/timeout-manager.d.ts +75 -0
- package/dist/utils/timeout-manager.js +244 -0
- package/package.json +245 -245
|
@@ -3,17 +3,48 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { logger } from "./logger.js";
|
|
5
5
|
/**
|
|
6
|
-
* Get the best available provider based on preferences and availability
|
|
6
|
+
* Get the best available provider based on preferences and availability (async)
|
|
7
7
|
* @param requestedProvider - Optional preferred provider name
|
|
8
8
|
* @returns The best provider name to use
|
|
9
9
|
*/
|
|
10
|
-
export function
|
|
11
|
-
// If a specific provider is requested, return it
|
|
12
|
-
if (requestedProvider) {
|
|
10
|
+
export async function getBestProvider(requestedProvider) {
|
|
11
|
+
// If a specific provider is requested, return it (existing logic)
|
|
12
|
+
if (requestedProvider && requestedProvider !== "auto") {
|
|
13
13
|
return requestedProvider;
|
|
14
14
|
}
|
|
15
|
-
//
|
|
16
|
-
|
|
15
|
+
// 🔧 FIX: Check for explicit default provider in env
|
|
16
|
+
if (process.env.DEFAULT_PROVIDER &&
|
|
17
|
+
isProviderConfigured(process.env.DEFAULT_PROVIDER)) {
|
|
18
|
+
return process.env.DEFAULT_PROVIDER;
|
|
19
|
+
}
|
|
20
|
+
// 🔧 FIX: Special case for Ollama when explicitly configured
|
|
21
|
+
if (process.env.OLLAMA_BASE_URL && process.env.OLLAMA_MODEL) {
|
|
22
|
+
// Quick connectivity check for Ollama (non-blocking)
|
|
23
|
+
try {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
26
|
+
let res;
|
|
27
|
+
try {
|
|
28
|
+
res = await fetch("http://localhost:11434/api/tags", {
|
|
29
|
+
method: "GET",
|
|
30
|
+
signal: controller.signal,
|
|
31
|
+
});
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
if (res.ok) {
|
|
34
|
+
return "ollama"; // Prioritize working local AI
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
// Fall through to cloud providers
|
|
40
|
+
}
|
|
41
|
+
// Removed redundant if (res.ok) block here
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Fall through to cloud providers
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Existing provider priority logic...
|
|
17
48
|
const providers = [
|
|
18
49
|
"google-ai",
|
|
19
50
|
"anthropic",
|
|
@@ -23,7 +54,7 @@ export function getBestProviderSync(requestedProvider) {
|
|
|
23
54
|
"azure",
|
|
24
55
|
"huggingface",
|
|
25
56
|
"bedrock",
|
|
26
|
-
"ollama",
|
|
57
|
+
"ollama", // Keep as fallback
|
|
27
58
|
];
|
|
28
59
|
// Check which providers have their required environment variables
|
|
29
60
|
for (const provider of providers) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Timeout Manager for NeuroLink
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent timeout handling across all operations with proper cleanup,
|
|
5
|
+
* abort controller integration, and graceful degradation.
|
|
6
|
+
*/
|
|
7
|
+
export interface TimeoutConfig {
|
|
8
|
+
operation: string;
|
|
9
|
+
timeout?: number | string;
|
|
10
|
+
gracefulShutdown?: boolean;
|
|
11
|
+
retryOnTimeout?: boolean;
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
abortSignal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
export interface TimeoutResult<T> {
|
|
16
|
+
success: boolean;
|
|
17
|
+
data?: T;
|
|
18
|
+
error?: Error;
|
|
19
|
+
timedOut: boolean;
|
|
20
|
+
executionTime: number;
|
|
21
|
+
retriesUsed: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Enhanced timeout manager with proper cleanup and abort controller integration
|
|
25
|
+
*/
|
|
26
|
+
export declare class TimeoutManager {
|
|
27
|
+
private activeTimeouts;
|
|
28
|
+
/**
|
|
29
|
+
* Execute operation with timeout and proper cleanup
|
|
30
|
+
*/
|
|
31
|
+
executeWithTimeout<T>(operation: () => Promise<T>, config: TimeoutConfig): Promise<TimeoutResult<T>>;
|
|
32
|
+
/**
|
|
33
|
+
* Execute single operation with timeout
|
|
34
|
+
*/
|
|
35
|
+
private performSingleOperation;
|
|
36
|
+
/**
|
|
37
|
+
* Execute operation with abort signal support
|
|
38
|
+
*/
|
|
39
|
+
private executeWithAbortSignal;
|
|
40
|
+
/**
|
|
41
|
+
* Get timeout in milliseconds from config
|
|
42
|
+
*/
|
|
43
|
+
private getTimeoutMs;
|
|
44
|
+
/**
|
|
45
|
+
* Generate unique operation ID
|
|
46
|
+
*/
|
|
47
|
+
private generateOperationId;
|
|
48
|
+
/**
|
|
49
|
+
* Cleanup specific operation
|
|
50
|
+
*/
|
|
51
|
+
cleanup(operationId: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Cleanup all active timeouts (call on shutdown)
|
|
54
|
+
*/
|
|
55
|
+
cleanupAll(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Get current active timeout count (for debugging)
|
|
58
|
+
*/
|
|
59
|
+
getActiveTimeoutCount(): number;
|
|
60
|
+
/**
|
|
61
|
+
* Create a timeout wrapper for child process operations
|
|
62
|
+
*/
|
|
63
|
+
wrapChildProcess<T>(processFactory: () => Promise<T>, config: TimeoutConfig): Promise<TimeoutResult<T>>;
|
|
64
|
+
/**
|
|
65
|
+
* Create a timeout wrapper for MCP server operations
|
|
66
|
+
*/
|
|
67
|
+
wrapMCPOperation<T>(operation: () => Promise<T>, operationName: string, timeoutMs?: number): Promise<TimeoutResult<T>>;
|
|
68
|
+
/**
|
|
69
|
+
* Create a timeout wrapper for CLI operations
|
|
70
|
+
*/
|
|
71
|
+
wrapCLIOperation<T>(operation: () => Promise<T>, operationName: string, timeoutMs?: number): Promise<TimeoutResult<T>>;
|
|
72
|
+
}
|
|
73
|
+
export declare function createTimeoutManager(): TimeoutManager;
|
|
74
|
+
export declare function getDefaultTimeoutManager(): TimeoutManager;
|
|
75
|
+
export declare const defaultTimeoutManager: TimeoutManager;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Timeout Manager for NeuroLink
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent timeout handling across all operations with proper cleanup,
|
|
5
|
+
* abort controller integration, and graceful degradation.
|
|
6
|
+
*/
|
|
7
|
+
import { parseTimeout, TimeoutError, DEFAULT_TIMEOUTS } from "./timeout.js";
|
|
8
|
+
/**
|
|
9
|
+
* Enhanced timeout manager with proper cleanup and abort controller integration
|
|
10
|
+
*/
|
|
11
|
+
export class TimeoutManager {
|
|
12
|
+
activeTimeouts = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Execute operation with timeout and proper cleanup
|
|
15
|
+
*/
|
|
16
|
+
async executeWithTimeout(operation, config) {
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
const operationId = this.generateOperationId(config.operation);
|
|
19
|
+
let retriesUsed = 0;
|
|
20
|
+
const maxRetries = config.retryOnTimeout ? (config.maxRetries ?? 1) : 0;
|
|
21
|
+
while (retriesUsed <= maxRetries) {
|
|
22
|
+
try {
|
|
23
|
+
const result = await this.performSingleOperation(operation, config, operationId);
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
data: result,
|
|
27
|
+
timedOut: false,
|
|
28
|
+
executionTime: Date.now() - startTime,
|
|
29
|
+
retriesUsed,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Clean up any active timeouts for this operation
|
|
34
|
+
this.cleanup(operationId);
|
|
35
|
+
if (error instanceof TimeoutError && retriesUsed < maxRetries) {
|
|
36
|
+
retriesUsed++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
42
|
+
timedOut: error instanceof TimeoutError,
|
|
43
|
+
executionTime: Date.now() - startTime,
|
|
44
|
+
retriesUsed,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// This should never be reached, but TypeScript needs it
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: new Error("Maximum retries exceeded"),
|
|
52
|
+
timedOut: true,
|
|
53
|
+
executionTime: Date.now() - startTime,
|
|
54
|
+
retriesUsed,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Execute single operation with timeout
|
|
59
|
+
*/
|
|
60
|
+
async performSingleOperation(operation, config, operationId) {
|
|
61
|
+
const timeoutMs = this.getTimeoutMs(config);
|
|
62
|
+
if (!timeoutMs) {
|
|
63
|
+
// No timeout specified, execute directly
|
|
64
|
+
return await operation();
|
|
65
|
+
}
|
|
66
|
+
// Create abort controller for this operation
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const existingSignal = config.abortSignal;
|
|
69
|
+
// Merge with existing abort signal if provided
|
|
70
|
+
if (existingSignal) {
|
|
71
|
+
existingSignal.addEventListener("abort", () => {
|
|
72
|
+
controller.abort(existingSignal.reason);
|
|
73
|
+
});
|
|
74
|
+
if (existingSignal.aborted) {
|
|
75
|
+
throw new Error("Operation aborted before execution");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Set up timeout
|
|
79
|
+
const timer = setTimeout(() => {
|
|
80
|
+
controller.abort(new TimeoutError(`Operation '${config.operation}' timed out after ${timeoutMs}ms`, timeoutMs, "timeout-manager", "generate"));
|
|
81
|
+
}, timeoutMs);
|
|
82
|
+
// Cleanup function
|
|
83
|
+
const cleanup = () => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
this.activeTimeouts.delete(operationId);
|
|
86
|
+
};
|
|
87
|
+
// Store active timeout for potential cleanup
|
|
88
|
+
this.activeTimeouts.set(operationId, {
|
|
89
|
+
timer,
|
|
90
|
+
controller,
|
|
91
|
+
cleanup,
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
// Execute operation with abort signal
|
|
95
|
+
const result = await this.executeWithAbortSignal(operation, controller.signal);
|
|
96
|
+
cleanup();
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
cleanup();
|
|
101
|
+
// Convert abort errors to timeout errors if appropriate
|
|
102
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
103
|
+
throw new TimeoutError(`Operation '${config.operation}' was aborted`, timeoutMs, "timeout-manager", "generate");
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Execute operation with abort signal support
|
|
110
|
+
*/
|
|
111
|
+
async executeWithAbortSignal(operation, signal) {
|
|
112
|
+
// Check if already aborted
|
|
113
|
+
if (signal.aborted) {
|
|
114
|
+
throw new Error("Operation aborted");
|
|
115
|
+
}
|
|
116
|
+
// Race between operation and abort signal
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
// Listen for abort
|
|
119
|
+
signal.addEventListener("abort", () => {
|
|
120
|
+
reject(signal.reason || new Error("Operation aborted"));
|
|
121
|
+
});
|
|
122
|
+
// Execute operation
|
|
123
|
+
operation().then(resolve).catch(reject);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get timeout in milliseconds from config
|
|
128
|
+
*/
|
|
129
|
+
getTimeoutMs(config) {
|
|
130
|
+
if (config.timeout !== undefined) {
|
|
131
|
+
return parseTimeout(config.timeout);
|
|
132
|
+
}
|
|
133
|
+
// Use default timeout based on operation type
|
|
134
|
+
const operation = config.operation.toLowerCase();
|
|
135
|
+
// MCP operations
|
|
136
|
+
if (operation.includes("mcp") || operation.includes("server")) {
|
|
137
|
+
return parseTimeout(DEFAULT_TIMEOUTS.tools.network);
|
|
138
|
+
}
|
|
139
|
+
// File operations
|
|
140
|
+
if (operation.includes("file") ||
|
|
141
|
+
operation.includes("read") ||
|
|
142
|
+
operation.includes("write")) {
|
|
143
|
+
return parseTimeout(DEFAULT_TIMEOUTS.tools.filesystem);
|
|
144
|
+
}
|
|
145
|
+
// Network operations
|
|
146
|
+
if (operation.includes("network") ||
|
|
147
|
+
operation.includes("http") ||
|
|
148
|
+
operation.includes("fetch")) {
|
|
149
|
+
return parseTimeout(DEFAULT_TIMEOUTS.tools.network);
|
|
150
|
+
}
|
|
151
|
+
// Default
|
|
152
|
+
return parseTimeout(DEFAULT_TIMEOUTS.tools.default);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Generate unique operation ID
|
|
156
|
+
*/
|
|
157
|
+
generateOperationId(operation) {
|
|
158
|
+
return `${operation}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Cleanup specific operation
|
|
162
|
+
*/
|
|
163
|
+
cleanup(operationId) {
|
|
164
|
+
const timeout = this.activeTimeouts.get(operationId);
|
|
165
|
+
if (timeout) {
|
|
166
|
+
timeout.cleanup();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Cleanup all active timeouts (call on shutdown)
|
|
171
|
+
*/
|
|
172
|
+
cleanupAll() {
|
|
173
|
+
for (const [id, timeout] of this.activeTimeouts) {
|
|
174
|
+
timeout.cleanup();
|
|
175
|
+
}
|
|
176
|
+
this.activeTimeouts.clear();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get current active timeout count (for debugging)
|
|
180
|
+
*/
|
|
181
|
+
getActiveTimeoutCount() {
|
|
182
|
+
return this.activeTimeouts.size;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a timeout wrapper for child process operations
|
|
186
|
+
*/
|
|
187
|
+
wrapChildProcess(processFactory, config) {
|
|
188
|
+
return this.executeWithTimeout(processFactory, {
|
|
189
|
+
...config,
|
|
190
|
+
gracefulShutdown: true,
|
|
191
|
+
timeout: config.timeout || DEFAULT_TIMEOUTS.tools.network,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Create a timeout wrapper for MCP server operations
|
|
196
|
+
*/
|
|
197
|
+
wrapMCPOperation(operation, operationName, timeoutMs) {
|
|
198
|
+
return this.executeWithTimeout(operation, {
|
|
199
|
+
operation: `mcp-${operationName}`,
|
|
200
|
+
timeout: timeoutMs || parseTimeout(DEFAULT_TIMEOUTS.tools.network),
|
|
201
|
+
retryOnTimeout: false,
|
|
202
|
+
gracefulShutdown: true,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Create a timeout wrapper for CLI operations
|
|
207
|
+
*/
|
|
208
|
+
wrapCLIOperation(operation, operationName, timeoutMs) {
|
|
209
|
+
return this.executeWithTimeout(operation, {
|
|
210
|
+
operation: `cli-${operationName}`,
|
|
211
|
+
timeout: timeoutMs || 120000, // 2 minutes default for CLI
|
|
212
|
+
retryOnTimeout: false,
|
|
213
|
+
gracefulShutdown: true,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Factory function to create a new TimeoutManager instance
|
|
218
|
+
export function createTimeoutManager() {
|
|
219
|
+
return new TimeoutManager();
|
|
220
|
+
}
|
|
221
|
+
// Lazy-loaded default timeout manager instance
|
|
222
|
+
let _defaultTimeoutManager = null;
|
|
223
|
+
export function getDefaultTimeoutManager() {
|
|
224
|
+
if (!_defaultTimeoutManager) {
|
|
225
|
+
_defaultTimeoutManager = createTimeoutManager();
|
|
226
|
+
}
|
|
227
|
+
return _defaultTimeoutManager;
|
|
228
|
+
}
|
|
229
|
+
// Export default instance for backwards compatibility
|
|
230
|
+
export const defaultTimeoutManager = getDefaultTimeoutManager();
|
|
231
|
+
// Cleanup on process exit
|
|
232
|
+
if (typeof process !== "undefined") {
|
|
233
|
+
process.once("exit", () => {
|
|
234
|
+
getDefaultTimeoutManager().cleanupAll();
|
|
235
|
+
});
|
|
236
|
+
process.once("SIGINT", () => {
|
|
237
|
+
getDefaultTimeoutManager().cleanupAll();
|
|
238
|
+
process.exit(0);
|
|
239
|
+
});
|
|
240
|
+
process.once("SIGTERM", () => {
|
|
241
|
+
getDefaultTimeoutManager().cleanupAll();
|
|
242
|
+
process.exit(0);
|
|
243
|
+
});
|
|
244
|
+
}
|