@juspay/neurolink 7.7.1 → 7.9.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 +25 -2
- package/README.md +34 -2
- package/dist/cli/commands/config.d.ts +3 -3
- package/dist/cli/commands/sagemaker.d.ts +11 -0
- package/dist/cli/commands/sagemaker.js +778 -0
- package/dist/cli/factories/commandFactory.js +7 -2
- package/dist/cli/index.js +3 -0
- package/dist/cli/utils/interactiveSetup.js +28 -0
- package/dist/core/baseProvider.d.ts +2 -2
- package/dist/core/types.d.ts +16 -4
- package/dist/core/types.js +24 -3
- package/dist/factories/providerFactory.js +10 -1
- package/dist/factories/providerRegistry.js +6 -1
- package/dist/lib/core/baseProvider.d.ts +2 -2
- package/dist/lib/core/types.d.ts +16 -4
- package/dist/lib/core/types.js +24 -3
- package/dist/lib/factories/providerFactory.js +10 -1
- package/dist/lib/factories/providerRegistry.js +6 -1
- package/dist/lib/neurolink.d.ts +15 -0
- package/dist/lib/neurolink.js +73 -1
- package/dist/lib/providers/amazonSagemaker.d.ts +67 -0
- package/dist/lib/providers/amazonSagemaker.js +149 -0
- package/dist/lib/providers/googleVertex.d.ts +4 -0
- package/dist/lib/providers/googleVertex.js +44 -3
- package/dist/lib/providers/index.d.ts +4 -0
- package/dist/lib/providers/index.js +4 -0
- package/dist/lib/providers/sagemaker/adaptive-semaphore.d.ts +86 -0
- package/dist/lib/providers/sagemaker/adaptive-semaphore.js +212 -0
- package/dist/lib/providers/sagemaker/client.d.ts +156 -0
- package/dist/lib/providers/sagemaker/client.js +462 -0
- package/dist/lib/providers/sagemaker/config.d.ts +73 -0
- package/dist/lib/providers/sagemaker/config.js +308 -0
- package/dist/lib/providers/sagemaker/detection.d.ts +176 -0
- package/dist/lib/providers/sagemaker/detection.js +596 -0
- package/dist/lib/providers/sagemaker/diagnostics.d.ts +37 -0
- package/dist/lib/providers/sagemaker/diagnostics.js +137 -0
- package/dist/lib/providers/sagemaker/error-constants.d.ts +78 -0
- package/dist/lib/providers/sagemaker/error-constants.js +227 -0
- package/dist/lib/providers/sagemaker/errors.d.ts +83 -0
- package/dist/lib/providers/sagemaker/errors.js +216 -0
- package/dist/lib/providers/sagemaker/index.d.ts +35 -0
- package/dist/lib/providers/sagemaker/index.js +67 -0
- package/dist/lib/providers/sagemaker/language-model.d.ts +182 -0
- package/dist/lib/providers/sagemaker/language-model.js +755 -0
- package/dist/lib/providers/sagemaker/parsers.d.ts +136 -0
- package/dist/lib/providers/sagemaker/parsers.js +625 -0
- package/dist/lib/providers/sagemaker/streaming.d.ts +39 -0
- package/dist/lib/providers/sagemaker/streaming.js +320 -0
- package/dist/lib/providers/sagemaker/structured-parser.d.ts +117 -0
- package/dist/lib/providers/sagemaker/structured-parser.js +625 -0
- package/dist/lib/providers/sagemaker/types.d.ts +456 -0
- package/dist/lib/providers/sagemaker/types.js +7 -0
- package/dist/lib/sdk/toolRegistration.d.ts +1 -1
- package/dist/lib/sdk/toolRegistration.js +13 -5
- package/dist/lib/types/cli.d.ts +36 -1
- package/dist/lib/utils/providerHealth.js +19 -4
- package/dist/neurolink.d.ts +15 -0
- package/dist/neurolink.js +73 -1
- package/dist/providers/amazonSagemaker.d.ts +67 -0
- package/dist/providers/amazonSagemaker.js +149 -0
- package/dist/providers/googleVertex.d.ts +4 -0
- package/dist/providers/googleVertex.js +44 -3
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/sagemaker/adaptive-semaphore.d.ts +86 -0
- package/dist/providers/sagemaker/adaptive-semaphore.js +212 -0
- package/dist/providers/sagemaker/client.d.ts +156 -0
- package/dist/providers/sagemaker/client.js +462 -0
- package/dist/providers/sagemaker/config.d.ts +73 -0
- package/dist/providers/sagemaker/config.js +308 -0
- package/dist/providers/sagemaker/detection.d.ts +176 -0
- package/dist/providers/sagemaker/detection.js +596 -0
- package/dist/providers/sagemaker/diagnostics.d.ts +37 -0
- package/dist/providers/sagemaker/diagnostics.js +137 -0
- package/dist/providers/sagemaker/error-constants.d.ts +78 -0
- package/dist/providers/sagemaker/error-constants.js +227 -0
- package/dist/providers/sagemaker/errors.d.ts +83 -0
- package/dist/providers/sagemaker/errors.js +216 -0
- package/dist/providers/sagemaker/index.d.ts +35 -0
- package/dist/providers/sagemaker/index.js +67 -0
- package/dist/providers/sagemaker/language-model.d.ts +182 -0
- package/dist/providers/sagemaker/language-model.js +755 -0
- package/dist/providers/sagemaker/parsers.d.ts +136 -0
- package/dist/providers/sagemaker/parsers.js +625 -0
- package/dist/providers/sagemaker/streaming.d.ts +39 -0
- package/dist/providers/sagemaker/streaming.js +320 -0
- package/dist/providers/sagemaker/structured-parser.d.ts +117 -0
- package/dist/providers/sagemaker/structured-parser.js +625 -0
- package/dist/providers/sagemaker/types.d.ts +456 -0
- package/dist/providers/sagemaker/types.js +7 -0
- package/dist/sdk/toolRegistration.d.ts +1 -1
- package/dist/sdk/toolRegistration.js +13 -5
- package/dist/types/cli.d.ts +36 -1
- package/dist/utils/providerHealth.js +19 -4
- package/package.json +8 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS SageMaker Runtime Client Wrapper
|
|
3
|
+
*
|
|
4
|
+
* This module provides a wrapper around the AWS SDK SageMaker Runtime client
|
|
5
|
+
* with enhanced error handling, retry logic, and NeuroLink-specific features.
|
|
6
|
+
*/
|
|
7
|
+
import type { SageMakerConfig, InvokeEndpointParams, InvokeEndpointResponse } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Enhanced SageMaker Runtime client with retry logic and error handling
|
|
10
|
+
*/
|
|
11
|
+
export declare class SageMakerRuntimeClient {
|
|
12
|
+
private client;
|
|
13
|
+
private config;
|
|
14
|
+
private isDisposed;
|
|
15
|
+
constructor(config: SageMakerConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Invoke a SageMaker endpoint for synchronous inference
|
|
18
|
+
*
|
|
19
|
+
* @param params - Endpoint invocation parameters
|
|
20
|
+
* @returns Promise resolving to the inference response
|
|
21
|
+
* @throws {SageMakerError} When the request fails
|
|
22
|
+
*/
|
|
23
|
+
invokeEndpoint(params: InvokeEndpointParams): Promise<InvokeEndpointResponse>;
|
|
24
|
+
/**
|
|
25
|
+
* Invoke a SageMaker endpoint with streaming response
|
|
26
|
+
*
|
|
27
|
+
* @param params - Endpoint invocation parameters for streaming
|
|
28
|
+
* @returns Promise resolving to async iterable of response chunks
|
|
29
|
+
* @throws {SageMakerError} When the request fails
|
|
30
|
+
*/
|
|
31
|
+
invokeEndpointWithStreaming(params: InvokeEndpointParams): Promise<{
|
|
32
|
+
Body: AsyncIterable<Uint8Array>;
|
|
33
|
+
ContentType?: string;
|
|
34
|
+
InvokedProductionVariant?: string;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Execute a request with automatic retry logic
|
|
38
|
+
*
|
|
39
|
+
* @param operation - Function that executes the AWS SDK command
|
|
40
|
+
* @param endpointName - Endpoint name for error context
|
|
41
|
+
* @param attempt - Current attempt number (for recursive retries)
|
|
42
|
+
* @returns Promise resolving to the operation result
|
|
43
|
+
*/
|
|
44
|
+
private executeWithRetry;
|
|
45
|
+
/**
|
|
46
|
+
* Validate endpoint connectivity and permissions
|
|
47
|
+
*
|
|
48
|
+
* @param endpointName - Name of the endpoint to validate
|
|
49
|
+
* @returns Promise resolving to validation result
|
|
50
|
+
*/
|
|
51
|
+
validateEndpoint(endpointName: string): Promise<{
|
|
52
|
+
isValid: boolean;
|
|
53
|
+
status?: string;
|
|
54
|
+
error?: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Get client configuration summary for debugging
|
|
58
|
+
*
|
|
59
|
+
* @returns Configuration summary (with sensitive data masked)
|
|
60
|
+
*/
|
|
61
|
+
getConfigSummary(): Record<string, unknown>;
|
|
62
|
+
/**
|
|
63
|
+
* Check if the client is properly configured
|
|
64
|
+
*
|
|
65
|
+
* @returns True if client appears to be properly configured
|
|
66
|
+
*/
|
|
67
|
+
isConfigured(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Convert AWS SDK async iterable stream with payload structure
|
|
70
|
+
*/
|
|
71
|
+
private convertAsyncIterableStream;
|
|
72
|
+
/**
|
|
73
|
+
* Convert Node.js readable stream with reader interface
|
|
74
|
+
*/
|
|
75
|
+
private convertReadableStream;
|
|
76
|
+
/**
|
|
77
|
+
* Convert non-stream data to single Uint8Array chunk as fallback
|
|
78
|
+
*/
|
|
79
|
+
private convertFallbackData;
|
|
80
|
+
/**
|
|
81
|
+
* Convert AWS response stream to async iterable of Uint8Array chunks
|
|
82
|
+
* Refactored into smaller focused methods for different stream types
|
|
83
|
+
*/
|
|
84
|
+
private convertAWSStreamToIterable;
|
|
85
|
+
/**
|
|
86
|
+
* Check if the client has been disposed
|
|
87
|
+
*/
|
|
88
|
+
get disposed(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Dispose of the client and clean up resources using explicit disposed state pattern
|
|
91
|
+
*
|
|
92
|
+
* AWS SDK v3 Automatic Resource Management:
|
|
93
|
+
* ========================================
|
|
94
|
+
*
|
|
95
|
+
* The AWS SDK v3 uses automatic resource cleanup and doesn't require explicit disposal
|
|
96
|
+
* of client instances in most cases. Here's how it works:
|
|
97
|
+
*
|
|
98
|
+
* 1. **HTTP Connection Pools**: AWS SDK v3 uses Node.js's built-in HTTP agent with
|
|
99
|
+
* connection pooling. These connections are automatically managed and will be
|
|
100
|
+
* closed when the Node.js process exits or becomes idle.
|
|
101
|
+
*
|
|
102
|
+
* 2. **Memory Management**: SDK clients don't hold significant resources that require
|
|
103
|
+
* manual cleanup. The JavaScript garbage collector handles memory deallocation
|
|
104
|
+
* when client references are removed.
|
|
105
|
+
*
|
|
106
|
+
* 3. **Background Timers**: Any internal timers (for retries, timeouts) are automatically
|
|
107
|
+
* cleared when operations complete or the client goes out of scope.
|
|
108
|
+
*
|
|
109
|
+
* 4. **Keep-Alive Connections**: HTTP keep-alive connections are managed by the
|
|
110
|
+
* underlying HTTP agent and will timeout automatically based on the configured
|
|
111
|
+
* keep-alive timeout (typically 15 seconds).
|
|
112
|
+
*
|
|
113
|
+
* Why We Still Implement dispose():
|
|
114
|
+
* =================================
|
|
115
|
+
*
|
|
116
|
+
* 1. **Explicit State Management**: Provides clear lifecycle control and prevents
|
|
117
|
+
* accidental usage of disposed clients.
|
|
118
|
+
*
|
|
119
|
+
* 2. **Resource Tracking**: Allows our application to track when clients are no
|
|
120
|
+
* longer needed, which is useful for debugging and monitoring.
|
|
121
|
+
*
|
|
122
|
+
* 3. **Defensive Programming**: Ensures we don't rely on automatic cleanup in
|
|
123
|
+
* environments where it might not work as expected.
|
|
124
|
+
*
|
|
125
|
+
* 4. **Future Compatibility**: If future SDK versions require explicit cleanup,
|
|
126
|
+
* we already have the infrastructure in place.
|
|
127
|
+
*
|
|
128
|
+
* For more information, see:
|
|
129
|
+
* - https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/
|
|
130
|
+
* - https://aws.amazon.com/blogs/developer/node-js-configuring-maxsockets-in-sdk-for-javascript/
|
|
131
|
+
*/
|
|
132
|
+
dispose(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Ensure client is not disposed before operations
|
|
135
|
+
*/
|
|
136
|
+
private ensureNotDisposed;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Factory function to create a SageMaker Runtime client
|
|
140
|
+
*
|
|
141
|
+
* @param config - SageMaker configuration
|
|
142
|
+
* @returns Configured SageMakerRuntimeClient instance
|
|
143
|
+
*/
|
|
144
|
+
export declare function createSageMakerRuntimeClient(config: SageMakerConfig): SageMakerRuntimeClient;
|
|
145
|
+
/**
|
|
146
|
+
* Utility function to test SageMaker connectivity
|
|
147
|
+
*
|
|
148
|
+
* @param config - SageMaker configuration
|
|
149
|
+
* @param endpointName - Endpoint to test
|
|
150
|
+
* @returns Promise resolving to connectivity test result
|
|
151
|
+
*/
|
|
152
|
+
export declare function testSageMakerConnectivity(config: SageMakerConfig, endpointName: string): Promise<{
|
|
153
|
+
connected: boolean;
|
|
154
|
+
latency?: number;
|
|
155
|
+
error?: string;
|
|
156
|
+
}>;
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS SageMaker Runtime Client Wrapper
|
|
3
|
+
*
|
|
4
|
+
* This module provides a wrapper around the AWS SDK SageMaker Runtime client
|
|
5
|
+
* with enhanced error handling, retry logic, and NeuroLink-specific features.
|
|
6
|
+
*/
|
|
7
|
+
import { SageMakerRuntimeClient as AWSClient, InvokeEndpointCommand, InvokeEndpointWithResponseStreamCommand, } from "@aws-sdk/client-sagemaker-runtime";
|
|
8
|
+
import { handleSageMakerError, SageMakerError, isRetryableError, getRetryDelay, } from "./errors.js";
|
|
9
|
+
import { logger } from "../../utils/logger.js";
|
|
10
|
+
/**
|
|
11
|
+
* Enhanced SageMaker Runtime client with retry logic and error handling
|
|
12
|
+
*/
|
|
13
|
+
export class SageMakerRuntimeClient {
|
|
14
|
+
client;
|
|
15
|
+
config;
|
|
16
|
+
isDisposed = false;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
// Initialize AWS SDK client with configuration
|
|
20
|
+
this.client = new AWSClient({
|
|
21
|
+
region: config.region,
|
|
22
|
+
credentials: {
|
|
23
|
+
accessKeyId: config.accessKeyId,
|
|
24
|
+
secretAccessKey: config.secretAccessKey,
|
|
25
|
+
sessionToken: config.sessionToken,
|
|
26
|
+
},
|
|
27
|
+
maxAttempts: config.maxRetries || 3,
|
|
28
|
+
requestHandler: {
|
|
29
|
+
requestTimeout: config.timeout || 30000,
|
|
30
|
+
httpsAgent: {
|
|
31
|
+
// Keep connections alive for better performance
|
|
32
|
+
keepAlive: true,
|
|
33
|
+
maxSockets: 50,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
...(config.endpoint && { endpoint: config.endpoint }),
|
|
37
|
+
});
|
|
38
|
+
logger.debug("SageMaker Runtime client initialized", {
|
|
39
|
+
region: config.region,
|
|
40
|
+
timeout: config.timeout,
|
|
41
|
+
maxRetries: config.maxRetries,
|
|
42
|
+
hasSessionToken: !!config.sessionToken,
|
|
43
|
+
customEndpoint: !!config.endpoint,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Invoke a SageMaker endpoint for synchronous inference
|
|
48
|
+
*
|
|
49
|
+
* @param params - Endpoint invocation parameters
|
|
50
|
+
* @returns Promise resolving to the inference response
|
|
51
|
+
* @throws {SageMakerError} When the request fails
|
|
52
|
+
*/
|
|
53
|
+
async invokeEndpoint(params) {
|
|
54
|
+
this.ensureNotDisposed();
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
try {
|
|
57
|
+
logger.debug("Invoking SageMaker endpoint", {
|
|
58
|
+
endpointName: params.EndpointName,
|
|
59
|
+
contentType: params.ContentType,
|
|
60
|
+
bodySize: typeof params.Body === "string"
|
|
61
|
+
? params.Body.length
|
|
62
|
+
: params.Body?.length || 0,
|
|
63
|
+
});
|
|
64
|
+
// Prepare the command input
|
|
65
|
+
const input = {
|
|
66
|
+
EndpointName: params.EndpointName,
|
|
67
|
+
Body: params.Body,
|
|
68
|
+
ContentType: params.ContentType || "application/json",
|
|
69
|
+
Accept: params.Accept || "application/json",
|
|
70
|
+
CustomAttributes: params.CustomAttributes,
|
|
71
|
+
TargetModel: params.TargetModel,
|
|
72
|
+
TargetVariant: params.TargetVariant,
|
|
73
|
+
InferenceId: params.InferenceId,
|
|
74
|
+
};
|
|
75
|
+
const command = new InvokeEndpointCommand(input);
|
|
76
|
+
const client = this.client;
|
|
77
|
+
if (!client) {
|
|
78
|
+
throw new Error("SageMaker client has been disposed");
|
|
79
|
+
}
|
|
80
|
+
const response = (await this.executeWithRetry(() => client.send(command), params.EndpointName));
|
|
81
|
+
const duration = Date.now() - startTime;
|
|
82
|
+
logger.debug("SageMaker endpoint invocation successful", {
|
|
83
|
+
endpointName: params.EndpointName,
|
|
84
|
+
duration,
|
|
85
|
+
responseSize: response.Body?.length || 0,
|
|
86
|
+
invokedVariant: response.InvokedProductionVariant,
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
Body: response.Body,
|
|
90
|
+
ContentType: response.ContentType,
|
|
91
|
+
InvokedProductionVariant: response.InvokedProductionVariant,
|
|
92
|
+
CustomAttributes: response.CustomAttributes,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const duration = Date.now() - startTime;
|
|
97
|
+
logger.error("SageMaker endpoint invocation failed", {
|
|
98
|
+
endpointName: params.EndpointName,
|
|
99
|
+
duration,
|
|
100
|
+
error: error instanceof Error ? error.message : String(error),
|
|
101
|
+
});
|
|
102
|
+
throw handleSageMakerError(error, params.EndpointName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Invoke a SageMaker endpoint with streaming response
|
|
107
|
+
*
|
|
108
|
+
* @param params - Endpoint invocation parameters for streaming
|
|
109
|
+
* @returns Promise resolving to async iterable of response chunks
|
|
110
|
+
* @throws {SageMakerError} When the request fails
|
|
111
|
+
*/
|
|
112
|
+
async invokeEndpointWithStreaming(params) {
|
|
113
|
+
this.ensureNotDisposed();
|
|
114
|
+
const startTime = Date.now();
|
|
115
|
+
try {
|
|
116
|
+
logger.debug("Starting SageMaker streaming invocation", {
|
|
117
|
+
endpointName: params.EndpointName,
|
|
118
|
+
contentType: params.ContentType,
|
|
119
|
+
bodySize: typeof params.Body === "string"
|
|
120
|
+
? params.Body.length
|
|
121
|
+
: params.Body?.length || 0,
|
|
122
|
+
});
|
|
123
|
+
// Prepare the command input for streaming
|
|
124
|
+
const input = {
|
|
125
|
+
EndpointName: params.EndpointName,
|
|
126
|
+
Body: params.Body,
|
|
127
|
+
ContentType: params.ContentType || "application/json",
|
|
128
|
+
Accept: params.Accept || "application/json",
|
|
129
|
+
CustomAttributes: params.CustomAttributes,
|
|
130
|
+
// Note: TargetModel, TargetVariant, InferenceId not available in streaming interface
|
|
131
|
+
};
|
|
132
|
+
const command = new InvokeEndpointWithResponseStreamCommand(input);
|
|
133
|
+
const client = this.client;
|
|
134
|
+
if (!client) {
|
|
135
|
+
throw new Error("SageMaker client has been disposed");
|
|
136
|
+
}
|
|
137
|
+
const response = (await this.executeWithRetry(() => client.send(command), params.EndpointName));
|
|
138
|
+
logger.debug("SageMaker streaming invocation started", {
|
|
139
|
+
endpointName: params.EndpointName,
|
|
140
|
+
setupDuration: Date.now() - startTime,
|
|
141
|
+
invokedVariant: response.InvokedProductionVariant,
|
|
142
|
+
});
|
|
143
|
+
// Return the response with streaming body
|
|
144
|
+
if (!response.Body) {
|
|
145
|
+
throw new SageMakerError("No response body received from streaming endpoint", "MODEL_ERROR", 500, undefined, params.EndpointName);
|
|
146
|
+
}
|
|
147
|
+
// Convert AWS response stream to async iterable of Uint8Array
|
|
148
|
+
const streamIterable = this.convertAWSStreamToIterable(response.Body);
|
|
149
|
+
return {
|
|
150
|
+
Body: streamIterable,
|
|
151
|
+
ContentType: response.ContentType,
|
|
152
|
+
InvokedProductionVariant: response.InvokedProductionVariant,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const duration = Date.now() - startTime;
|
|
157
|
+
logger.error("SageMaker streaming invocation failed", {
|
|
158
|
+
endpointName: params.EndpointName,
|
|
159
|
+
duration,
|
|
160
|
+
error: error instanceof Error ? error.message : String(error),
|
|
161
|
+
});
|
|
162
|
+
throw handleSageMakerError(error, params.EndpointName);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Execute a request with automatic retry logic
|
|
167
|
+
*
|
|
168
|
+
* @param operation - Function that executes the AWS SDK command
|
|
169
|
+
* @param endpointName - Endpoint name for error context
|
|
170
|
+
* @param attempt - Current attempt number (for recursive retries)
|
|
171
|
+
* @returns Promise resolving to the operation result
|
|
172
|
+
*/
|
|
173
|
+
async executeWithRetry(operation, endpointName, attempt = 1) {
|
|
174
|
+
try {
|
|
175
|
+
return await operation();
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const maxRetries = this.config.maxRetries || 3;
|
|
179
|
+
// Check if we should retry
|
|
180
|
+
if (attempt < maxRetries && isRetryableError(error)) {
|
|
181
|
+
const delay = getRetryDelay(error, attempt);
|
|
182
|
+
logger.warn(`SageMaker request failed, retrying in ${delay}ms`, {
|
|
183
|
+
endpointName,
|
|
184
|
+
attempt,
|
|
185
|
+
maxRetries,
|
|
186
|
+
error: error instanceof Error ? error.message : String(error),
|
|
187
|
+
});
|
|
188
|
+
// Wait before retrying
|
|
189
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
190
|
+
// Recursive retry
|
|
191
|
+
return this.executeWithRetry(operation, endpointName, attempt + 1);
|
|
192
|
+
}
|
|
193
|
+
// No more retries or not retryable
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Validate endpoint connectivity and permissions
|
|
199
|
+
*
|
|
200
|
+
* @param endpointName - Name of the endpoint to validate
|
|
201
|
+
* @returns Promise resolving to validation result
|
|
202
|
+
*/
|
|
203
|
+
async validateEndpoint(endpointName) {
|
|
204
|
+
this.ensureNotDisposed();
|
|
205
|
+
try {
|
|
206
|
+
// Try a minimal test request to validate endpoint
|
|
207
|
+
const testPayload = JSON.stringify({ test: true });
|
|
208
|
+
await this.invokeEndpoint({
|
|
209
|
+
EndpointName: endpointName,
|
|
210
|
+
Body: testPayload,
|
|
211
|
+
ContentType: "application/json",
|
|
212
|
+
Accept: "application/json",
|
|
213
|
+
});
|
|
214
|
+
return { isValid: true };
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (error instanceof SageMakerError) {
|
|
218
|
+
return {
|
|
219
|
+
isValid: false,
|
|
220
|
+
error: error.message,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
isValid: false,
|
|
225
|
+
error: error instanceof Error ? error.message : "Unknown validation error",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get client configuration summary for debugging
|
|
231
|
+
*
|
|
232
|
+
* @returns Configuration summary (with sensitive data masked)
|
|
233
|
+
*/
|
|
234
|
+
getConfigSummary() {
|
|
235
|
+
this.ensureNotDisposed();
|
|
236
|
+
return {
|
|
237
|
+
region: this.config.region,
|
|
238
|
+
timeout: this.config.timeout,
|
|
239
|
+
maxRetries: this.config.maxRetries,
|
|
240
|
+
hasCustomEndpoint: !!this.config.endpoint,
|
|
241
|
+
credentialsConfigured: !!(this.config.accessKeyId && this.config.secretAccessKey),
|
|
242
|
+
hasSessionToken: !!this.config.sessionToken,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check if the client is properly configured
|
|
247
|
+
*
|
|
248
|
+
* @returns True if client appears to be properly configured
|
|
249
|
+
*/
|
|
250
|
+
isConfigured() {
|
|
251
|
+
this.ensureNotDisposed();
|
|
252
|
+
return !!(this.config.region &&
|
|
253
|
+
this.config.accessKeyId &&
|
|
254
|
+
this.config.secretAccessKey);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Convert AWS SDK async iterable stream with payload structure
|
|
258
|
+
*/
|
|
259
|
+
async *convertAsyncIterableStream(awsStream) {
|
|
260
|
+
for await (const chunk of awsStream) {
|
|
261
|
+
// Handle AWS SDK payload structure
|
|
262
|
+
if (chunk && typeof chunk === "object" && "PayloadPart" in chunk) {
|
|
263
|
+
const payloadChunk = chunk;
|
|
264
|
+
if (payloadChunk.PayloadPart?.Data) {
|
|
265
|
+
yield payloadChunk.PayloadPart.Data;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (chunk instanceof Uint8Array) {
|
|
269
|
+
yield chunk;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Convert Node.js readable stream with reader interface
|
|
275
|
+
*/
|
|
276
|
+
async *convertReadableStream(streamObj) {
|
|
277
|
+
const reader = typeof streamObj.getReader === "function"
|
|
278
|
+
? streamObj.getReader()
|
|
279
|
+
: undefined;
|
|
280
|
+
if (!reader) {
|
|
281
|
+
return; // No valid reader available
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
while (true) {
|
|
285
|
+
const { done, value } = await reader.read();
|
|
286
|
+
if (done) {
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (value instanceof Uint8Array) {
|
|
290
|
+
yield value;
|
|
291
|
+
}
|
|
292
|
+
else if (typeof value === "string") {
|
|
293
|
+
yield new TextEncoder().encode(value);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
reader.releaseLock?.();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Convert non-stream data to single Uint8Array chunk as fallback
|
|
303
|
+
*/
|
|
304
|
+
*convertFallbackData(awsStream) {
|
|
305
|
+
logger.warn("Unsupported stream type, treating as single chunk", {
|
|
306
|
+
type: typeof awsStream,
|
|
307
|
+
isUint8Array: awsStream instanceof Uint8Array,
|
|
308
|
+
});
|
|
309
|
+
if (awsStream instanceof Uint8Array) {
|
|
310
|
+
yield awsStream;
|
|
311
|
+
}
|
|
312
|
+
else if (typeof awsStream === "string") {
|
|
313
|
+
yield new TextEncoder().encode(awsStream);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
yield new TextEncoder().encode(JSON.stringify(awsStream));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Convert AWS response stream to async iterable of Uint8Array chunks
|
|
321
|
+
* Refactored into smaller focused methods for different stream types
|
|
322
|
+
*/
|
|
323
|
+
async *convertAWSStreamToIterable(awsStream) {
|
|
324
|
+
try {
|
|
325
|
+
// AWS SDK streaming response handling
|
|
326
|
+
if (awsStream &&
|
|
327
|
+
typeof awsStream === "object" &&
|
|
328
|
+
Symbol.asyncIterator in awsStream) {
|
|
329
|
+
// Direct async iterable (AWS SDK event stream)
|
|
330
|
+
yield* this.convertAsyncIterableStream(awsStream);
|
|
331
|
+
}
|
|
332
|
+
else if (awsStream &&
|
|
333
|
+
typeof awsStream === "object" &&
|
|
334
|
+
"pipe" in awsStream) {
|
|
335
|
+
// Node.js stream conversion (readable stream with reader)
|
|
336
|
+
yield* this.convertReadableStream(awsStream);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
// Fallback: treat as single response (non-streaming data)
|
|
340
|
+
yield* this.convertFallbackData(awsStream);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
logger.error("Error converting AWS stream", {
|
|
345
|
+
error: error instanceof Error ? error.message : String(error),
|
|
346
|
+
streamType: typeof awsStream,
|
|
347
|
+
});
|
|
348
|
+
throw new SageMakerError(`Stream conversion failed: ${error instanceof Error ? error.message : String(error)}`, "NETWORK_ERROR", 500);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Check if the client has been disposed
|
|
353
|
+
*/
|
|
354
|
+
get disposed() {
|
|
355
|
+
return this.isDisposed;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Dispose of the client and clean up resources using explicit disposed state pattern
|
|
359
|
+
*
|
|
360
|
+
* AWS SDK v3 Automatic Resource Management:
|
|
361
|
+
* ========================================
|
|
362
|
+
*
|
|
363
|
+
* The AWS SDK v3 uses automatic resource cleanup and doesn't require explicit disposal
|
|
364
|
+
* of client instances in most cases. Here's how it works:
|
|
365
|
+
*
|
|
366
|
+
* 1. **HTTP Connection Pools**: AWS SDK v3 uses Node.js's built-in HTTP agent with
|
|
367
|
+
* connection pooling. These connections are automatically managed and will be
|
|
368
|
+
* closed when the Node.js process exits or becomes idle.
|
|
369
|
+
*
|
|
370
|
+
* 2. **Memory Management**: SDK clients don't hold significant resources that require
|
|
371
|
+
* manual cleanup. The JavaScript garbage collector handles memory deallocation
|
|
372
|
+
* when client references are removed.
|
|
373
|
+
*
|
|
374
|
+
* 3. **Background Timers**: Any internal timers (for retries, timeouts) are automatically
|
|
375
|
+
* cleared when operations complete or the client goes out of scope.
|
|
376
|
+
*
|
|
377
|
+
* 4. **Keep-Alive Connections**: HTTP keep-alive connections are managed by the
|
|
378
|
+
* underlying HTTP agent and will timeout automatically based on the configured
|
|
379
|
+
* keep-alive timeout (typically 15 seconds).
|
|
380
|
+
*
|
|
381
|
+
* Why We Still Implement dispose():
|
|
382
|
+
* =================================
|
|
383
|
+
*
|
|
384
|
+
* 1. **Explicit State Management**: Provides clear lifecycle control and prevents
|
|
385
|
+
* accidental usage of disposed clients.
|
|
386
|
+
*
|
|
387
|
+
* 2. **Resource Tracking**: Allows our application to track when clients are no
|
|
388
|
+
* longer needed, which is useful for debugging and monitoring.
|
|
389
|
+
*
|
|
390
|
+
* 3. **Defensive Programming**: Ensures we don't rely on automatic cleanup in
|
|
391
|
+
* environments where it might not work as expected.
|
|
392
|
+
*
|
|
393
|
+
* 4. **Future Compatibility**: If future SDK versions require explicit cleanup,
|
|
394
|
+
* we already have the infrastructure in place.
|
|
395
|
+
*
|
|
396
|
+
* For more information, see:
|
|
397
|
+
* - https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/
|
|
398
|
+
* - https://aws.amazon.com/blogs/developer/node-js-configuring-maxsockets-in-sdk-for-javascript/
|
|
399
|
+
*/
|
|
400
|
+
dispose() {
|
|
401
|
+
// Check for race condition - already disposed using explicit state
|
|
402
|
+
if (this.isDisposed) {
|
|
403
|
+
logger.debug("SageMaker Runtime client already disposed");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
// Mark as disposed first to prevent race conditions
|
|
407
|
+
this.isDisposed = true;
|
|
408
|
+
// Clear our client reference to enable garbage collection
|
|
409
|
+
// Note: AWS SDK v3 handles all internal resource cleanup automatically
|
|
410
|
+
this.client = null;
|
|
411
|
+
logger.debug("SageMaker Runtime client disposed", {
|
|
412
|
+
note: "AWS SDK v3 handles internal resource cleanup automatically",
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Ensure client is not disposed before operations
|
|
417
|
+
*/
|
|
418
|
+
ensureNotDisposed() {
|
|
419
|
+
if (this.isDisposed) {
|
|
420
|
+
throw new SageMakerError("Cannot perform operation on disposed SageMaker client", "VALIDATION_ERROR", 400);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Factory function to create a SageMaker Runtime client
|
|
426
|
+
*
|
|
427
|
+
* @param config - SageMaker configuration
|
|
428
|
+
* @returns Configured SageMakerRuntimeClient instance
|
|
429
|
+
*/
|
|
430
|
+
export function createSageMakerRuntimeClient(config) {
|
|
431
|
+
return new SageMakerRuntimeClient(config);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Utility function to test SageMaker connectivity
|
|
435
|
+
*
|
|
436
|
+
* @param config - SageMaker configuration
|
|
437
|
+
* @param endpointName - Endpoint to test
|
|
438
|
+
* @returns Promise resolving to connectivity test result
|
|
439
|
+
*/
|
|
440
|
+
export async function testSageMakerConnectivity(config, endpointName) {
|
|
441
|
+
const client = new SageMakerRuntimeClient(config);
|
|
442
|
+
const startTime = Date.now();
|
|
443
|
+
try {
|
|
444
|
+
const result = await client.validateEndpoint(endpointName);
|
|
445
|
+
const latency = Date.now() - startTime;
|
|
446
|
+
if (result.isValid) {
|
|
447
|
+
return { connected: true, latency };
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
return { connected: false, error: result.error };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
return {
|
|
455
|
+
connected: false,
|
|
456
|
+
error: error instanceof Error ? error.message : "Unknown connectivity error",
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
finally {
|
|
460
|
+
client.dispose();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Amazon SageMaker Provider
|
|
3
|
+
*
|
|
4
|
+
* This module handles loading, validation, and management of SageMaker
|
|
5
|
+
* configuration from environment variables, files, and defaults.
|
|
6
|
+
*/
|
|
7
|
+
import type { SageMakerConfig, SageMakerModelConfig } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Load and validate SageMaker configuration from environment variables
|
|
10
|
+
*
|
|
11
|
+
* Environment variable priority:
|
|
12
|
+
* 1. SAGEMAKER_* variables (highest priority)
|
|
13
|
+
* 2. AWS_* variables (standard AWS SDK variables)
|
|
14
|
+
* 3. Default values (lowest priority)
|
|
15
|
+
*
|
|
16
|
+
* @returns Validated SageMaker configuration
|
|
17
|
+
* @throws {Error} When required configuration is missing or invalid
|
|
18
|
+
*/
|
|
19
|
+
export declare function getSageMakerConfig(): SageMakerConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Load and validate SageMaker model configuration
|
|
22
|
+
*
|
|
23
|
+
* @param endpointName - Name of the SageMaker endpoint
|
|
24
|
+
* @returns Validated model configuration
|
|
25
|
+
*/
|
|
26
|
+
export declare function getSageMakerModelConfig(endpointName?: string): SageMakerModelConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Get the default SageMaker endpoint name from environment variables
|
|
29
|
+
*
|
|
30
|
+
* @returns Default endpoint name
|
|
31
|
+
*/
|
|
32
|
+
export declare function getDefaultSageMakerEndpoint(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Get SageMaker model name from environment variables
|
|
35
|
+
*
|
|
36
|
+
* @returns Model name
|
|
37
|
+
*/
|
|
38
|
+
export declare function getSageMakerModel(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Validate AWS credentials are properly configured
|
|
41
|
+
*
|
|
42
|
+
* @param config - SageMaker configuration to validate
|
|
43
|
+
* @returns true if credentials are valid
|
|
44
|
+
* @throws {Error} When credentials are missing or invalid
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateAWSCredentials(config: SageMakerConfig): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Create a comprehensive configuration summary for debugging
|
|
49
|
+
*
|
|
50
|
+
* @returns Configuration summary (sensitive data masked)
|
|
51
|
+
*/
|
|
52
|
+
export declare function getConfigurationSummary(): Record<string, unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Clear configuration cache (useful for testing or credential rotation)
|
|
55
|
+
*/
|
|
56
|
+
export declare function clearConfigurationCache(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Load configuration from a JSON file (alternative to environment variables)
|
|
59
|
+
*
|
|
60
|
+
* @param filePath - Path to configuration JSON file
|
|
61
|
+
* @returns Loaded configuration
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadConfigurationFromFile(filePath: string): Promise<SageMakerConfig>;
|
|
64
|
+
/**
|
|
65
|
+
* Check if SageMaker provider is properly configured
|
|
66
|
+
*
|
|
67
|
+
* @returns Configuration check result
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkSageMakerConfiguration(): {
|
|
70
|
+
configured: boolean;
|
|
71
|
+
issues: string[];
|
|
72
|
+
summary: Record<string, unknown>;
|
|
73
|
+
};
|