@aws/ml-container-creator 0.9.1 → 0.10.3

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.
Files changed (90) hide show
  1. package/LICENSE-THIRD-PARTY +9304 -0
  2. package/bin/cli.js +2 -0
  3. package/config/bootstrap-e2e-stack.json +341 -0
  4. package/config/bootstrap-stack.json +40 -3
  5. package/config/parameter-schema-v2.json +2049 -0
  6. package/config/tune-catalog.json +1781 -0
  7. package/infra/ci-harness/buildspec.yml +1 -0
  8. package/infra/ci-harness/lambda/path-prover/brain.ts +306 -0
  9. package/infra/ci-harness/lambda/path-prover/write-results.ts +152 -0
  10. package/infra/ci-harness/lib/ci-harness-stack.ts +837 -7
  11. package/infra/ci-harness/state-machines/path-prover.asl.json +496 -0
  12. package/package.json +53 -68
  13. package/servers/base-image-picker/index.js +121 -121
  14. package/servers/e2e-status/index.js +297 -0
  15. package/servers/e2e-status/manifest.json +14 -0
  16. package/servers/e2e-status/package.json +15 -0
  17. package/servers/endpoint-picker/LICENSE +202 -0
  18. package/servers/endpoint-picker/index.js +536 -0
  19. package/servers/endpoint-picker/manifest.json +14 -0
  20. package/servers/endpoint-picker/package.json +18 -0
  21. package/servers/hyperpod-cluster-picker/index.js +125 -125
  22. package/servers/instance-sizer/index.js +138 -138
  23. package/servers/instance-sizer/lib/instance-ranker.js +76 -76
  24. package/servers/instance-sizer/lib/model-resolver.js +61 -61
  25. package/servers/instance-sizer/lib/quota-resolver.js +113 -113
  26. package/servers/instance-sizer/lib/vram-estimator.js +31 -31
  27. package/servers/lib/bedrock-client.js +38 -38
  28. package/servers/lib/catalogs/jumpstart-public.json +101 -16
  29. package/servers/lib/catalogs/model-servers.json +201 -3
  30. package/servers/lib/catalogs/models.json +182 -26
  31. package/servers/lib/custom-validators.js +13 -13
  32. package/servers/lib/dynamic-resolver.js +4 -4
  33. package/servers/marketplace-picker/index.js +342 -0
  34. package/servers/marketplace-picker/manifest.json +14 -0
  35. package/servers/marketplace-picker/package.json +18 -0
  36. package/servers/model-picker/index.js +382 -382
  37. package/servers/region-picker/index.js +56 -56
  38. package/servers/workload-picker/LICENSE +202 -0
  39. package/servers/workload-picker/catalogs/workload-profiles.json +67 -0
  40. package/servers/workload-picker/index.js +171 -0
  41. package/servers/workload-picker/manifest.json +16 -0
  42. package/servers/workload-picker/package.json +16 -0
  43. package/src/app.js +4 -390
  44. package/src/lib/bootstrap-command-handler.js +710 -1148
  45. package/src/lib/bootstrap-config.js +36 -0
  46. package/src/lib/bootstrap-profile-manager.js +641 -0
  47. package/src/lib/bootstrap-provisioners.js +421 -0
  48. package/src/lib/ci-register-helpers.js +74 -0
  49. package/src/lib/config-loader.js +408 -0
  50. package/src/lib/config-manager.js +66 -1685
  51. package/src/lib/config-mcp-client.js +118 -0
  52. package/src/lib/config-validator.js +634 -0
  53. package/src/lib/cuda-resolver.js +149 -0
  54. package/src/lib/e2e-catalog-validator.js +251 -3
  55. package/src/lib/e2e-ci-recorder.js +103 -0
  56. package/src/lib/generated/cli-options.js +315 -311
  57. package/src/lib/generated/parameter-matrix.js +671 -0
  58. package/src/lib/generated/validation-rules.js +71 -71
  59. package/src/lib/marketplace-flow.js +276 -0
  60. package/src/lib/mcp-query-runner.js +768 -0
  61. package/src/lib/parameter-schema-validator.js +62 -18
  62. package/src/lib/path-prover-brain.js +607 -0
  63. package/src/lib/prompt-runner.js +41 -1504
  64. package/src/lib/prompts/feature-prompts.js +172 -0
  65. package/src/lib/prompts/index.js +48 -0
  66. package/src/lib/prompts/infrastructure-prompts.js +690 -0
  67. package/src/lib/prompts/model-prompts.js +552 -0
  68. package/src/lib/prompts/project-prompts.js +82 -0
  69. package/src/lib/prompts.js +2 -1446
  70. package/src/lib/registry-command-handler.js +135 -3
  71. package/src/lib/secrets-prompt-runner.js +251 -0
  72. package/src/lib/template-variable-resolver.js +422 -0
  73. package/src/lib/tune-catalog-validator.js +37 -4
  74. package/templates/Dockerfile +9 -0
  75. package/templates/code/adapter_sidecar.py +444 -0
  76. package/templates/code/serve +6 -0
  77. package/templates/code/serve.d/vllm.ejs +1 -1
  78. package/templates/do/.benchmark_writer.py +1476 -0
  79. package/templates/do/.tune_helper.py +982 -57
  80. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  81. package/templates/do/adapter +149 -0
  82. package/templates/do/benchmark +639 -85
  83. package/templates/do/config +108 -5
  84. package/templates/do/deploy.d/managed-inference.ejs +192 -11
  85. package/templates/do/optimize +106 -37
  86. package/templates/do/register +89 -0
  87. package/templates/do/test +13 -0
  88. package/templates/do/tune +378 -59
  89. package/templates/do/validate +44 -4
  90. package/config/parameter-schema.json +0 -88
@@ -0,0 +1,408 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Config Loader - Handles loading configuration from all sources.
6
+ * Uses delegation pattern: receives parent ConfigManager reference to access shared state.
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import BootstrapConfig from './bootstrap-config.js';
12
+ import { parseKeyValue } from './key-value-parser.js';
13
+ import { ConfigurationError } from './config-manager.js';
14
+
15
+ export default class ConfigLoader {
16
+ constructor(manager) {
17
+ this.manager = manager;
18
+ }
19
+
20
+ /**
21
+ * Load from bootstrap config (~/.ml-container-creator/config.json)
22
+ * @private
23
+ */
24
+ async _loadBootstrapConfig() {
25
+ try {
26
+ const bootstrapConfig = new BootstrapConfig();
27
+ const activeProfile = bootstrapConfig.getActiveProfile();
28
+ if (!activeProfile) {
29
+ return;
30
+ }
31
+
32
+ const profileConfig = activeProfile.config;
33
+ const mapped = {};
34
+
35
+ if (profileConfig.roleArn) {
36
+ mapped.awsRoleArn = profileConfig.roleArn;
37
+ }
38
+ if (profileConfig.awsRegion) {
39
+ mapped.awsRegion = profileConfig.awsRegion;
40
+ }
41
+ if (profileConfig.awsProfile) {
42
+ mapped.awsProfile = profileConfig.awsProfile;
43
+ }
44
+ if (profileConfig.ciBenchmarkResultsBucket) {
45
+ mapped.ciBenchmarkResultsBucket = profileConfig.ciBenchmarkResultsBucket;
46
+ }
47
+
48
+ this.manager._mergeConfig(mapped);
49
+ } catch (error) {
50
+ // Ignore errors — config file may not exist or may be malformed
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Load from package.json "ml-container-creator" section (filtered by matrix)
56
+ * @private
57
+ */
58
+ async _loadPackageJsonConfig() {
59
+ try {
60
+ const packageJsonPath = path.resolve(process.cwd(), 'package.json');
61
+ if (fs.existsSync(packageJsonPath)) {
62
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
63
+ const generatorConfig = packageJson['ml-container-creator'];
64
+ if (generatorConfig) {
65
+ const filteredConfig = {};
66
+ Object.entries(generatorConfig).forEach(([key, value]) => {
67
+ if (this.manager._isSourceSupported(key, 'packageJson')) {
68
+ filteredConfig[key] = this.manager._parseValue(key, value);
69
+ }
70
+ });
71
+ this.manager._mergeConfig(filteredConfig);
72
+ }
73
+ }
74
+ } catch (error) {
75
+ // Ignore errors - this is optional
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Load from config/mcp.json
81
+ * @private
82
+ */
83
+ async _loadCustomConfigFile() {
84
+ try {
85
+ const configPath = path.join(this.manager.GENERATOR_ROOT, 'config', 'mcp.json');
86
+ if (fs.existsSync(configPath)) {
87
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
88
+ this.manager._mergeConfig(config);
89
+ }
90
+ } catch (error) {
91
+ // Ignore errors - this is optional
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Load from CLI --config file or --config-json inline string.
97
+ * @private
98
+ */
99
+ async _loadCliConfigFile() {
100
+ let configFile = this.manager.options.config;
101
+
102
+ // Check environment variable if CLI option not provided
103
+ if (!configFile && process.env.ML_CONTAINER_CREATOR_CONFIG) {
104
+ configFile = process.env.ML_CONTAINER_CREATOR_CONFIG;
105
+ }
106
+
107
+ if (configFile) {
108
+ this._loadConfigFromFile(configFile);
109
+ }
110
+
111
+ // --config-json: inline JSON string or path to a JSON file
112
+ const configJson = this.manager.options['config-json'];
113
+ if (configJson) {
114
+ this._loadConfigFromJson(configJson);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Load configuration from a JSON file path.
120
+ * @param {string} configFile - Path to the JSON config file
121
+ * @private
122
+ */
123
+ _loadConfigFromFile(configFile) {
124
+ try {
125
+ const configPath = path.resolve(configFile);
126
+ if (!fs.existsSync(configPath)) {
127
+ throw new ConfigurationError(
128
+ `Config file not found: ${configPath}`,
129
+ 'configFile',
130
+ 'cli'
131
+ );
132
+ }
133
+
134
+ try {
135
+ fs.accessSync(configPath, fs.constants.R_OK);
136
+ } catch (accessError) {
137
+ throw new ConfigurationError(
138
+ `Config file is not readable: ${configPath}`,
139
+ 'configFile',
140
+ 'cli'
141
+ );
142
+ }
143
+
144
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
145
+ this._applyJsonConfig(config);
146
+ } catch (error) {
147
+ if (error instanceof ConfigurationError) {
148
+ throw error;
149
+ } else {
150
+ throw new ConfigurationError(
151
+ `Failed to load config file ${configFile}: ${error.message}`,
152
+ 'configFile',
153
+ 'cli'
154
+ );
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Load configuration from an inline JSON string or a JSON file path.
161
+ * @param {string} configJson - Inline JSON string or path to a JSON file
162
+ * @private
163
+ */
164
+ _loadConfigFromJson(configJson) {
165
+ let config;
166
+ try {
167
+ config = JSON.parse(configJson);
168
+ } catch {
169
+ try {
170
+ const configPath = path.resolve(configJson);
171
+ if (!fs.existsSync(configPath)) {
172
+ throw new ConfigurationError(
173
+ `--config-json value is not valid JSON and file not found: ${configJson}`,
174
+ 'configJson',
175
+ 'cli'
176
+ );
177
+ }
178
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
179
+ } catch (error) {
180
+ if (error instanceof ConfigurationError) {
181
+ throw error;
182
+ }
183
+ throw new ConfigurationError(
184
+ `Failed to parse --config-json: ${error.message}`,
185
+ 'configJson',
186
+ 'cli'
187
+ );
188
+ }
189
+ }
190
+ this._applyJsonConfig(config);
191
+ }
192
+
193
+ /**
194
+ * Apply a parsed JSON config object, filtering to supported parameters.
195
+ * @param {Object} config - Parsed JSON config object
196
+ * @private
197
+ */
198
+ _applyJsonConfig(config) {
199
+ const filteredConfig = {};
200
+ Object.entries(config).forEach(([key, value]) => {
201
+ // Handle nested endpointConfig object
202
+ if (key === 'endpointConfig' && typeof value === 'object' && value !== null) {
203
+ const endpointMapping = {
204
+ initialInstanceCount: 'endpointInitialInstanceCount',
205
+ dataCapturePercent: 'endpointDataCapturePercent',
206
+ variantName: 'endpointVariantName',
207
+ volumeSize: 'endpointVolumeSize'
208
+ };
209
+ Object.entries(value).forEach(([nestedKey, nestedValue]) => {
210
+ const flatKey = endpointMapping[nestedKey];
211
+ if (flatKey && this.manager._isSourceSupported(flatKey, 'configFile')) {
212
+ filteredConfig[flatKey] = nestedValue;
213
+ this.manager._recordSource(flatKey, nestedValue, 'config-file');
214
+ }
215
+ });
216
+ return;
217
+ }
218
+
219
+ // Handle nested icConfig object
220
+ if (key === 'icConfig' && typeof value === 'object' && value !== null) {
221
+ const icMapping = {
222
+ cpuCount: 'icCpuCount',
223
+ memorySize: 'icMemorySize',
224
+ gpuCount: 'icGpuCount',
225
+ copyCount: 'icCopyCount',
226
+ modelWeight: 'icModelWeight'
227
+ };
228
+ Object.entries(value).forEach(([nestedKey, nestedValue]) => {
229
+ const flatKey = icMapping[nestedKey];
230
+ if (flatKey && this.manager._isSourceSupported(flatKey, 'configFile')) {
231
+ filteredConfig[flatKey] = nestedValue;
232
+ this.manager._recordSource(flatKey, nestedValue, 'config-file');
233
+ }
234
+ });
235
+ return;
236
+ }
237
+
238
+ // Handle modelEnvVars object
239
+ if (key === 'modelEnvVars' && typeof value === 'object' && value !== null) {
240
+ if (!this.manager.config.modelEnvVars) {
241
+ this.manager.config.modelEnvVars = {};
242
+ }
243
+ const cliModelEnvVars = (this.manager.explicitConfig && this.manager.explicitConfig.modelEnvVars) || {};
244
+ Object.entries(value).forEach(([envKey, envValue]) => {
245
+ if (!(envKey in cliModelEnvVars)) {
246
+ this.manager.config.modelEnvVars[envKey] = envValue;
247
+ this.manager._recordSource(`modelEnvVars.${envKey}`, envValue, 'config-file');
248
+ }
249
+ });
250
+ return;
251
+ }
252
+
253
+ // Handle serverEnvVars object
254
+ if (key === 'serverEnvVars' && typeof value === 'object' && value !== null) {
255
+ if (!this.manager.config.serverEnvVars) {
256
+ this.manager.config.serverEnvVars = {};
257
+ }
258
+ const cliServerEnvVars = (this.manager.explicitConfig && this.manager.explicitConfig.serverEnvVars) || {};
259
+ Object.entries(value).forEach(([envKey, envValue]) => {
260
+ if (!(envKey in cliServerEnvVars)) {
261
+ this.manager.config.serverEnvVars[envKey] = envValue;
262
+ this.manager._recordSource(`serverEnvVars.${envKey}`, envValue, 'config-file');
263
+ }
264
+ });
265
+ return;
266
+ }
267
+
268
+ if (this.manager._isSourceSupported(key, 'configFile')) {
269
+ filteredConfig[key] = this.manager._parseValue(key, value);
270
+ this.manager._recordSource(key, this.manager._parseValue(key, value), 'config-file');
271
+ }
272
+ });
273
+ this.manager._mergeConfig(filteredConfig);
274
+ }
275
+
276
+ /**
277
+ * Load from environment variables (filtered by matrix)
278
+ * @private
279
+ */
280
+ async _loadEnvironmentVariables() {
281
+ const envMapping = {};
282
+ Object.entries(this.manager.parameterMatrix).forEach(([param, config]) => {
283
+ if (config.envVar) {
284
+ envMapping[config.envVar] = { param, ambient: false };
285
+ }
286
+ // Also check ambient env vars (e.g., AWS_REGION, AWS_ROLE, HF_TOKEN)
287
+ if (config.ambientEnvVar) {
288
+ envMapping[config.ambientEnvVar] = { param, ambient: true };
289
+ }
290
+ });
291
+
292
+ Object.entries(envMapping).forEach(([envVar, { param: configKey, ambient }]) => {
293
+ const value = process.env[envVar];
294
+ if (value !== undefined && value !== '' && this.manager._isSourceSupported(configKey, 'envVar')) {
295
+ this.manager.config[configKey] = this.manager._parseValue(configKey, value);
296
+ this.manager._recordSource(configKey, this.manager._parseValue(configKey, value), 'env-var');
297
+ if (!ambient) {
298
+ if (!this.manager.explicitConfig) {
299
+ this.manager.explicitConfig = {};
300
+ }
301
+ this.manager.explicitConfig[configKey] = this.manager._parseValue(configKey, value);
302
+ }
303
+ }
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Load from CLI arguments (positional)
309
+ * @private
310
+ */
311
+ async _loadCliArguments() {
312
+ if (this.manager.args && this.manager.args.length > 0) {
313
+ this.manager.config.projectName = this.manager.args[0];
314
+ if (!this.manager.explicitConfig) {
315
+ this.manager.explicitConfig = {};
316
+ }
317
+ this.manager.explicitConfig.projectName = this.manager.args[0];
318
+ this.manager.projectNameFromArgument = true;
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Load from CLI options (highest precedence, filtered by matrix)
324
+ * @private
325
+ */
326
+ async _loadCliOptions() {
327
+ const options = this.manager.options;
328
+
329
+ Object.entries(this.manager.parameterMatrix).forEach(([param, config]) => {
330
+ if (config.cliOption && options[config.cliOption] !== undefined) {
331
+ this.manager.config[param] = this.manager._parseValue(param, options[config.cliOption]);
332
+ this.manager._recordSource(param, this.manager._parseValue(param, options[config.cliOption]), 'cli');
333
+ if (!this.manager.explicitConfig) {
334
+ this.manager.explicitConfig = {};
335
+ }
336
+ this.manager.explicitConfig[param] = this.manager._parseValue(param, options[config.cliOption]);
337
+ }
338
+ });
339
+
340
+ // Parse --model-env KEY=VALUE pairs
341
+ this._parseEnvVarOptions('model-env', 'modelEnvVars');
342
+
343
+ // Parse --server-env KEY=VALUE pairs
344
+ this._parseEnvVarOptions('server-env', 'serverEnvVars');
345
+ }
346
+
347
+ /**
348
+ * Normalizes deprecated parameter values to their canonical equivalents.
349
+ * @private
350
+ */
351
+ _normalizeDeprecatedValues() {
352
+ const DEPRECATED_VALUES = {
353
+ deploymentTarget: {
354
+ 'managed-inference': {
355
+ canonical: 'realtime-inference',
356
+ message: '--deployment-target=managed-inference is deprecated, use realtime-inference instead'
357
+ }
358
+ }
359
+ };
360
+
361
+ for (const [param, aliases] of Object.entries(DEPRECATED_VALUES)) {
362
+ const currentValue = this.manager.config[param];
363
+ if (currentValue && aliases[currentValue]) {
364
+ const { canonical, message } = aliases[currentValue];
365
+ console.log(`\n⚠️ Deprecation: ${message}`);
366
+ this.manager.config[param] = canonical;
367
+ if (this.manager.explicitConfig && this.manager.explicitConfig[param] === currentValue) {
368
+ this.manager.explicitConfig[param] = canonical;
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Parse --model-env or --server-env CLI options into env var collections.
376
+ * @param {string} optionName - CLI option name
377
+ * @param {string} configKey - Config key to store results
378
+ * @private
379
+ */
380
+ _parseEnvVarOptions(optionName, configKey) {
381
+ const rawValue = this.manager.options[optionName];
382
+ if (rawValue === undefined || rawValue === null) {
383
+ return;
384
+ }
385
+
386
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
387
+
388
+ if (!this.manager.config[configKey] || typeof this.manager.config[configKey] !== 'object') {
389
+ this.manager.config[configKey] = {};
390
+ }
391
+
392
+ for (const entry of values) {
393
+ if (typeof entry !== 'string' || entry.trim() === '') {
394
+ continue;
395
+ }
396
+ const { key, value } = parseKeyValue(entry);
397
+ this.manager.config[configKey][key] = value;
398
+ this.manager._recordSource(`${configKey}.${key}`, value, 'cli');
399
+ }
400
+
401
+ if (Object.keys(this.manager.config[configKey]).length > 0) {
402
+ if (!this.manager.explicitConfig) {
403
+ this.manager.explicitConfig = {};
404
+ }
405
+ this.manager.explicitConfig[configKey] = { ...this.manager.config[configKey] };
406
+ }
407
+ }
408
+ }